Expose the runtime block/item registry through compact material search and single-material descriptions. Preserve private block-entity data through checked edits and durable undo without sending payloads to model context. Include the completed terrain tools, isolated world/map plugin, station lift, ACP streaming and guidance fixes, local camera auto-connect, construction scripts, and their public documentation, references and verification records. Active station decoration and private runtime data remain outside this commit. Validation: 145 Maven tests, 47 Bridge tests, successful camera Gradle build, and isolated Paper verification of all 1,196 block defaults plus 5,392 independent property cases for placement, same-material edits and restoration.
175 lines
8.8 KiB
Python
175 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile small arrival details and a separately reversible invisible enclosure.
|
|
|
|
Reads observed states only; apply the two recipes through scripts/layout.py.
|
|
The normal-player boundary has a full floor, side membrane and transparent roof.
|
|
Existing full stone cells can form the membrane; partial collision shapes cannot.
|
|
"""
|
|
import argparse
|
|
from collections import Counter
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
STAGE = ROOT / '.runtime/zone01-stage05'
|
|
AIR = 'minecraft:air'
|
|
BARRIER = 'minecraft:barrier[waterlogged=false]'
|
|
N = ((1, 0), (-1, 0), (0, 1), (0, -1))
|
|
|
|
|
|
def module(name, path):
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
value = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(value)
|
|
return value
|
|
|
|
|
|
survey = module('zone01_survey', ROOT / 'scripts/foundation-survey.py')
|
|
geometry = module('zone01_geometry', ROOT / 'scripts/foundation-study/geometry.py')
|
|
FULL = survey.FULL | {'waxed_oxidized_cut_copper', 'barrier'}
|
|
|
|
|
|
def full(state):
|
|
return state.split('[')[0].removeprefix('minecraft:') in FULL
|
|
|
|
|
|
def compile_plans(before):
|
|
design = json.loads((ROOT / '.runtime/plaza-stage03/design-final.json').read_text())
|
|
geo = json.loads((ROOT / '.runtime/foundations-stage02/foundations-final.geometry.json').read_text())
|
|
rails = json.loads((ROOT / '.runtime/balustrade-stage04/balustrade-final.metadata.json').read_text())
|
|
outer = geometry.polygon_cells(design['outer_hex'])
|
|
roads = {tuple(p) for r in geo['routes'] for p in r['clear_cells']}
|
|
details, groups = {}, {}
|
|
|
|
def decorate(x, y, z, state, group):
|
|
at = x, y, z
|
|
old = before.state(*at)
|
|
if y >= 96 and (old != AIR or (x, z) in roads):
|
|
raise ValueError(f'Furniture touches existing decoration or an approach at {at}')
|
|
if y == 95 and not full(old):
|
|
raise ValueError(f'Inlay would change a stair or unsupported floor at {at}')
|
|
details[at] = 'minecraft:'+state
|
|
groups[at] = group
|
|
|
|
urns = [(-10, 35), (10, 35)]
|
|
for cx, cz in urns:
|
|
decorate(cx, 96, cz, 'cut_sandstone', 'urn-pedestals')
|
|
for dx, dz in N:
|
|
decorate(cx+dx, 96, cz+dz, 'smooth_sandstone_slab[type=bottom,waterlogged=false]', 'urn-plinths')
|
|
for dx in range(-1, 2):
|
|
for dz in range(-1, 2):
|
|
if dx == dz == 0:
|
|
bowl = 'dirt'
|
|
elif dx and dz:
|
|
bowl = 'cut_sandstone'
|
|
else:
|
|
facing = 'east' if dx == 1 else 'west' if dx == -1 else 'south' if dz == 1 else 'north'
|
|
bowl = f'smooth_sandstone_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]'
|
|
decorate(cx+dx, 97, cz+dz, bowl, 'urn-bowls')
|
|
decorate(cx, 98, cz, 'white_tulip', 'urn-flowers')
|
|
# A low freestanding enamel nameboard; its text display is a separate receipt.
|
|
decorate(-8, 96, 43, 'cut_sandstone', 'welcome-pedestal')
|
|
for x in range(-9, -6):
|
|
decorate(x, 97, 43, 'green_concrete', 'welcome-nameboard')
|
|
decorate(x, 98, 43, 'waxed_oxidized_cut_copper_slab[type=bottom,waterlogged=false]', 'welcome-coping')
|
|
thresholds = {
|
|
'north': [(x, z) for x in range(-4, 5) for z in (-31, -30)],
|
|
'south': [(x, z) for x in range(-4, 5) for z in (50, 51)],
|
|
'east': [(x, z) for x in (38, 39) for z in range(13, 20)],
|
|
'west': [(x, z) for x in (-40, -39) for z in range(16, 21)],
|
|
'northwest': [(x, z) for x in range(-36, -31) for z in range(-16, -9)
|
|
if (x, z) in roads and x-z in (-21, -20)],
|
|
}
|
|
for name, columns in thresholds.items():
|
|
for x, z in columns:
|
|
decorate(x, 95, z, 'smooth_stone', 'flush-threshold-'+name)
|
|
# Restrained green/brass corner accents stay at the exact paving height.
|
|
for x, z in (min(columns), max(columns)):
|
|
decorate(x, 95, z, 'waxed_oxidized_cut_copper', 'threshold-copper-insets')
|
|
|
|
# Solid joint piers stop a partial-shaped road rail from carrying a folded
|
|
# membrane through a whole garden-side balustrade and its connected plants.
|
|
for x, z in ((-42, 22), (41, 11), (42, 20)):
|
|
at = x, 96, z
|
|
if not before.state(*at).startswith('minecraft:stone_brick_wall['):
|
|
raise ValueError(f'Joint pier baseline changed at {at}')
|
|
details[at], groups[at] = 'minecraft:cut_sandstone', 'finished-road-joint-piers'
|
|
|
|
# Allow the complete plaza, its balustrade and short level approach aprons.
|
|
near = {(x+dx, z+dz) for x, z in outer for dx in range(-2, 3) for dz in range(-2, 3)}
|
|
interior = outer | {tuple(p) for p in rails['fence_columns']}
|
|
interior |= {(c['x'], c['z']) for c in geo['cells'] if c['kind'] == 'full' and c['block_y'] == 95
|
|
and (c['x'], c['z']) in near}
|
|
# Fold the six-face membrane inward around partial collision shapes instead
|
|
# of replacing visible rails, stair treads, plants or fixture overhangs.
|
|
n6 = ((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1))
|
|
original_volume = {(x, y, z) for x, z in interior for y in range(96, 116)}
|
|
volume = set(original_volume)
|
|
cache = {}
|
|
def observed(p):
|
|
if p not in cache:
|
|
cache[p] = details.get(p, before.state(*p))
|
|
return cache[p]
|
|
for _ in range(128):
|
|
membrane = {(x+dx, y+dy, z+dz) for x, y, z in volume for dx, dy, dz in n6} - volume
|
|
partial = {p for p in membrane if observed(p) != AIR and not full(observed(p))}
|
|
if not partial:
|
|
break
|
|
remove = {(x+dx, y+dy, z+dz) for x, y, z in partial for dx, dy, dz in n6} & volume
|
|
if not remove:
|
|
raise ValueError('Partial-shape boundary cannot be sealed without editing decoration')
|
|
volume -= remove
|
|
else:
|
|
raise ValueError('Boundary folding did not converge')
|
|
if (0, 96, 43) not in volume:
|
|
raise ValueError('Spawn is outside the finished enclosure')
|
|
shell = {(x, z) for x, y, z in membrane if 96 <= y < 116}
|
|
floor = {p for p in membrane if p[1] == 95}
|
|
roof = {p for p in membrane if p[1] == 116}
|
|
walls = membrane - floor - roof
|
|
barriers, barrier_groups = {}, {}
|
|
for group, positions in [('invisible-side-wall', walls), ('invisible-floor-gap', floor), ('invisible-roof', roof)]:
|
|
for at in sorted(positions):
|
|
state = details.get(at, before.state(*at))
|
|
if full(state):
|
|
continue
|
|
if state != AIR:
|
|
raise ValueError(f'Enclosure would erase a visible/partial block at {at}: {state}')
|
|
barriers[at], barrier_groups[at] = BARRIER, group
|
|
if details.keys() & barriers.keys():
|
|
raise ValueError('Decoration and containment recipes overlap')
|
|
def recipe(states, labels):
|
|
return {'version': 1, 'scope': before.scope, 'blocks': [dict(zip(('x', 'y', 'z'), p)) |
|
|
{'block': state, 'expected': before.state(*p), 'group': labels[p]}
|
|
for p, state in sorted(states.items()) if state != before.state(*p)]}
|
|
finish, envelope = recipe(details, groups), recipe(barriers, barrier_groups)
|
|
combined = {'version': 1, 'scope': before.scope, 'blocks': finish['blocks']+envelope['blocks']}
|
|
metadata = {'version': 1, 'scope': before.scope, 'interior_columns': sorted(interior),
|
|
'shell_columns': sorted(shell), 'floor_y': 95, 'roof_y': 116,
|
|
'source': {'x': .5, 'y': 96, 'z': 43.5},
|
|
'interior_excluded_voxels': sorted(original_volume-volume),
|
|
'decoration': {'urn_centers': urns, 'welcome_plinth': [-8, 43], 'thresholds': thresholds},
|
|
'barrier_groups': dict(Counter(b['group'] for b in envelope['blocks'])),
|
|
'detail_groups': dict(Counter(b['group'] for b in finish['blocks'])),
|
|
'scope_note': 'Static collision enclosure for normal players; spectator and operator teleport/break commands bypass it.'}
|
|
return finish, envelope, combined, metadata
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--before', type=Path, default=STAGE / 'before.json.gz')
|
|
parser.add_argument('--output-dir', type=Path, default=STAGE)
|
|
args = parser.parse_args()
|
|
result = compile_plans(survey.load_snapshot(args.before))
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
for name, value in zip(('finishing.json', 'barriers.json', 'combined.json', 'zone-boundary.metadata.json'), result):
|
|
(args.output_dir/name).write_text(json.dumps(value, separators=(',', ':'))+'\n')
|
|
print(json.dumps({'details': len(result[0]['blocks']), 'barriers': len(result[1]['blocks']),
|
|
'interior_columns': len(result[3]['interior_columns']), 'shell_columns': len(result[3]['shell_columns']),
|
|
'groups': result[3]['barrier_groups']}))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|