Files
Emil 89c53d7058 Expand material support and checkpoint the completed building toolkit
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.
2026-09-13 16:59:13 +03:00

515 lines
25 KiB
Python

#!/usr/bin/env python3
"""Compile the two furnished Shacraft station floors without world I/O.
The caller owns the exterior, authoritative survey and expected-state writes.
This module owns only the two-block-eroded footprint at block Y 98..123.
All six SMASH panels are decorative, unassigned slots. Functional signs, text
displays and the individual lift transition are deliberately metadata only.
"""
from collections import Counter, deque
AIR = "minecraft:air"
CREAM = "minecraft:smooth_sandstone"
PALE = "minecraft:smooth_quartz"
CUT = "minecraft:cut_sandstone"
GREEN = "minecraft:waxed_oxidized_cut_copper"
GOLD = "minecraft:gold_block"
WOOD = "minecraft:spruce_planks"
DARK = "minecraft:dark_oak_planks"
CHAIN = "minecraft:iron_chain[axis=y,waterlogged=false]"
LANTERN = "minecraft:lantern[hanging=true,waterlogged=false]"
LEAF = "minecraft:spruce_leaves[distance=7,persistent=true,waterlogged=false]"
LOG = "minecraft:spruce_log[axis=y]"
def _slab(material, top=False):
return f"minecraft:{material}[type={'top' if top else 'bottom'},waterlogged=false]"
def _stair(material, facing, top=False):
return (f"minecraft:{material}[facing={facing},half={'top' if top else 'bottom'},"
"shape=straight,waterlogged=false]")
def _rect(box):
a, b, c, d = map(int, box)
return {(x, z) for x in range(a, b + 1) for z in range(c, d + 1)}
def _pose(x, y, z, text, facing="south", scale=0.7, **extra):
return {"position": {"x": x, "y": y, "z": z}, "text": text,
"facing": facing, "scale": scale, **extra}
class _Interior:
def __init__(self, footprint, layout):
self.footprint = {(int(x), int(z)) for x, z in footprint}
self.inside = {(x, z) for x, z in self.footprint
if all((x + dx, z + dz) in self.footprint
for dx in range(-2, 3) for dz in range(-2, 3))}
if not self.inside:
raise ValueError("The station needs an interior after its two-block setback")
self.layout = layout
self.states = {}
self.groups = {}
self.features = []
self.texts = []
self.targets = {99: [], 113: []}
self.selections = []
self.lift = {}
self.floor_walk = {}
def put(self, x, y, z, state, group):
p = (int(x), int(y), int(z))
if (p[0], p[2]) not in self.inside or not 98 <= p[1] <= 123:
raise ValueError(f"Interior write outside the owned volume: {p} ({group})")
self.states[p] = state
self.groups[p] = group
def box(self, bounds, lo, hi, state, group, clipped=False):
for x, z in sorted(_rect(bounds)):
if clipped and (x, z) not in self.inside:
continue
for y in range(lo, hi + 1):
self.put(x, y, z, state, group)
def target(self, feet, x, z, name):
self.targets[feet].append({"name": name, "x": x, "y": feet, "z": z})
def record(self, kind, bounds, feet, **extra):
self.features.append({"kind": kind, "bounds_xz": list(bounds),
"walk_y": feet, **extra})
def shell(self):
for x, z in sorted(self.inside):
for y in range(98, 124):
if y in (98, 112):
state, group = CREAM, "interior:continuous-floor"
elif y == 111:
state, group = "minecraft:stone_bricks", "interior:solid-intermediate-deck"
else:
state, group = AIR, "interior:owned-room-air"
self.put(x, y, z, state, group)
def floors(self, feet):
floor = feet - 1
group = f"interior:{feet}:floor-inlay"
# Large calm limestone panels, outlined by the architectural column grid.
for x, z in sorted(self.inside):
if x in (-51, -46, -21, -16, 15, 20) or z in (-129, -124, -106, -101):
self.put(x, floor, z, PALE, group)
if x in (-50, -17, 19) and -135 <= z <= -93:
self.put(x, floor, z, CUT, group)
for x in (-11, -10, -2, -1):
for z in range(-115, -84):
if (x, z) in self.inside:
self.put(x, floor, z, GREEN if x in (-10, -2) else PALE, group)
for z in (-111, -103, -95, -87):
for x in (-10, -2):
if (x, z) in self.inside:
self.put(x, floor, z, GOLD, group)
centres = [(-59, -117), (25, -117), (-34, -103), (5, -99)]
if feet == 99:
centres += [(-33, -117), (-31, -135), (5, -135)]
for cx, cz in centres:
for dx in range(-5, 6):
for dz in range(-5, 6):
if (cx + dx, cz + dz) not in self.inside:
continue
r = abs(dx) + abs(dz)
if r == 5:
state = GREEN
elif r == 4:
state = PALE
elif r <= 2 and (dx == 0 or dz == 0):
state = GOLD if r == 0 else CUT
else:
continue
self.put(cx + dx, floor, cz + dz, state, group)
if feet == 113:
# A terracotta runner belongs to SMASH, while the centre axis stays pale.
for x, z in sorted(_rect([-46, 11, -134, -130])):
self.put(x, floor, z, "minecraft:orange_terracotta", group)
for x in range(-46, 12):
for z in (-135, -129):
self.put(x, floor, z, PALE if x % 10 else GOLD, group)
for x, z in sorted(_rect([22, 29, -124, -112])):
edge = x in (22, 29) or z in (-124, -112)
self.put(x, floor, z, GREEN if edge else "minecraft:orange_terracotta", group)
def arcade(self, feet, ceiling):
group = f"interior:{feet}:arcade"
panel_y = ceiling - 1
# A solid coffer ceiling closes the upper floor below the inaccessible attic.
for x, z in sorted(self.inside):
self.put(x, panel_y, z, WOOD, group)
if z in (-139, -128, -125, -105, -102, -91) or x in (-50, -47, -20, -17, 16, 19):
self.put(x, panel_y, z, DARK, group)
if z in (-128, -125, -105, -102):
self.put(x, panel_y - 1, z, CREAM, group)
for bounds in self.layout["aligned_columns"]:
a, b, c, d = bounds
self.box(bounds, feet, feet, CUT, group)
self.box(bounds, feet + 1, feet + 1, PALE, group)
self.box(bounds, feet + 2, ceiling - 4,
"minecraft:quartz_pillar[axis=y]", group)
self.box(bounds, ceiling - 3, ceiling - 1, CREAM, group)
self.box([a - 1, b + 1, c - 1, d + 1], ceiling - 3, ceiling - 3,
_slab("smooth_sandstone_slab", top=True), group, clipped=True)
self.box([a - 1, b + 1, c - 1, d + 1], ceiling - 2, ceiling - 2,
CREAM, group, clipped=True)
self.record("aligned-column", bounds, feet)
# High corbels make the two column rows read as a shallow cream arcade.
for bounds in self.layout["aligned_columns"]:
a, b, c, d = bounds
for x, facing in ((a - 2, "east"), (b + 2, "west")):
for z in (c, d):
if (x, z) in self.inside:
self.put(x, ceiling - 3, z,
_stair("smooth_sandstone_stairs", facing, top=True), group)
def chandelier(self, x, z, feet, ceiling):
group = f"interior:{feet}:chandelier"
collar = ceiling - 4
for y in range(collar + 1, ceiling):
self.put(x, y, z, CHAIN, group)
self.put(x, collar, z, GOLD, group)
for dx, dz in ((-1, 0), (1, 0), (0, -1), (0, 1)):
self.put(x + dx, collar, z + dz, GREEN, group)
self.put(x + dx, collar - 1, z + dz, LANTERN, group)
self.record("four-lantern-chandelier", [x - 1, x + 1, z - 1, z + 1],
feet, lowest_block_y=collar - 1)
def bench(self, bounds, feet, facing="south", name="bench"):
a, b, c, d = bounds
if b - a < 3 or d - c != 1:
raise ValueError("Station benches use a long, two-block-deep rectangle")
group = f"interior:{feet}:bench"
back_z, seat_z = (c, d) if facing == "south" else (d, c)
stair_facing = "north" if facing == "south" else "south"
for x in range(a, b + 1):
if x in (a, b):
for z in (c, d):
self.put(x, feet, z, CUT, group)
self.put(x, feet + 1, z, _slab("smooth_sandstone_slab"), group)
else:
self.put(x, feet, seat_z, _stair("spruce_stairs", stair_facing), group)
self.put(x, feet, back_z, WOOD, group)
self.put(x, feet + 1, back_z, _slab("spruce_slab"), group)
front = d + 1 if facing == "south" else c - 1
for x in range(a + 1, b):
self.target(feet, x, front, f"{name}-access-{x}")
self.record("spruce-bench", bounds, feet, facing=facing)
def planter(self, x, z, feet, name="topiary"):
group = f"interior:{feet}:planter"
bounds = [x - 1, x + 1, z - 1, z + 1]
self.box(bounds, feet, feet, CUT, group)
self.put(x, feet, z, "minecraft:dirt", group)
self.put(x, feet + 1, z, LOG, group)
self.put(x, feet + 2, z, LOG, group)
for dx, dz in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)):
self.put(x + dx, feet + 3, z + dz, LEAF, group)
self.put(x, feet + 4, z, LEAF, group)
self.record(name, bounds, feet)
def lift_cabin(self, feet, ceiling):
group = f"interior:{feet}:lift"
housing = self.layout["lift"]["housing"]
cabin = self.layout["lift"]["clear_cabin"]
inner = _rect(cabin)
a, b, c, d = housing
for x, z in sorted(_rect(housing)):
if (x, z) in inner:
self.put(x, feet - 1, z, "minecraft:polished_andesite", group)
continue
for y in range(feet, ceiling):
state = GREEN
if x in (a, b) and z in (c, d):
state = CUT if y in (feet, feet + 5) else CREAM
if y == feet + 5 and z == d:
state = GOLD
self.put(x, y, z, state, group)
# The unused volume above each cabin is solid, never an open shaft.
self.box(cabin, feet + 7, ceiling - 1, WOOD, group)
self.box(cabin, feet, feet + 6, AIR, group)
self.box([-8, -4, -117, -116], feet, feet + 4, AIR, group)
self.box([-8, -4, -117, -116], feet + 5, feet + 5, GOLD, group)
for x in (-8, -4):
for z in range(-122, -117):
self.put(x, feet - 1, z, GREEN, group)
self.put(-6, feet + 6, -120, CHAIN, group)
self.put(-6, feet + 5, -120, LANTERN, group)
selector = (-6, feet + 2, -123)
self.put(*selector, GOLD, group)
number = 1 if feet == 99 else 2
self.lift[str(number)] = {
"housing_bounds_xz": housing, "clear_cabin_bounds_xz": cabin,
"walk_y": feet, "door_bounds_xz": [-8, -4, -117, -116],
"door_clear_height": 5, "selector_block": list(selector),
"destination": {"x": -5.5, "y": 113 if feet == 99 else 99,
"z": -119.5, "yaw": 0, "pitch": 0},
"landing_block": [-6, feet, -120], "floor_is_solid": True,
}
self.texts.append(_pose(-5.5, feet + 6.2, -114.93,
"1 ВЕСТИБЮЛЬ" if number == 1 else "2 SMASH",
scale=0.9, id=f"lift-{number}-front"))
self.texts.append(_pose(-5.5, feet + 3.3, -121.92,
"Вверх · SMASH" if number == 1 else "Вниз · Вестибюль",
scale=0.45, id=f"lift-{number}-selector"))
self.target(feet, -6, -120, f"lift-{number}-landing")
self.target(feet, -6, -114, f"lift-{number}-approach")
self.record("enclosed-lift-cabin", housing, feet, floor_number=number)
def gallery_panel(self, x, z, feet, title, subtitle, facing="east"):
group = f"interior:{feet}:gallery-panel"
# East-facing panels stand along the western wall, clear of the arcade.
for dz in range(-2, 3):
for dy in range(0, 6):
state = CUT if abs(dz) == 2 or dy in (0, 5) else DARK
self.put(x, feet + dy, z + dz, state, group)
for dz in (-2, 2):
self.put(x, feet + 2, z + dz, GOLD, group)
self.texts.append(_pose(x + 1.04, feet + 3.9, z + 0.5,
title + "\n" + subtitle, facing=facing, scale=0.52,
id=f"gallery-{feet}-{x}-{z}"))
self.target(feet, x + 3, z, title)
self.record("rules-panel" if feet == 113 else "station-gallery-panel",
[x, x, z - 2, z + 2], feet)
def diorama(self):
feet = 113
a, b, c, d = self.layout["smash_display"]
group = "interior:113:contained-island-diorama"
self.box([a, b, c, d], feet, feet, "minecraft:black_concrete", group)
for x, z in sorted(_rect([a, b, c, d])):
if x in (a, b) or z in (c, d):
self.put(x, feet, z, CUT, group)
for y in (feet + 1, feet + 2):
self.put(x, y, z, "minecraft:glass", group)
self.put(x, feet + 3, z, _slab("smooth_sandstone_slab"), group)
# Miniatures hover above an opaque, intact display base, inside a glass case.
for ix, (cx, cz, radius) in enumerate(((-38, -115, 2), (-29, -115, 2), (-34, -120, 2))):
self.put(cx, 114, cz, "minecraft:deepslate[axis=y]", group)
for dx in range(-radius, radius + 1):
for dz in range(-radius, radius + 1):
dist = abs(dx) + abs(dz)
if dist <= 2:
self.put(cx + dx, 115, cz + dz, "minecraft:stone", group)
if dist <= 3:
self.put(cx + dx, 116, cz + dz, "minecraft:moss_block", group)
self.put(cx, 117, cz, LOG, group)
self.put(cx, 118, cz, LOG, group)
for dx, dz in ((-1, 0), (0, -1), (0, 0), (0, 1), (1, 0)):
self.put(cx + dx, 119, cz + dz, LEAF, group)
self.put(cx, 120, cz, LEAF, group)
self.put(cx + (1 if ix != 1 else -1), 117, cz + 1,
"minecraft:red_concrete" if ix == 1 else "minecraft:light_blue_concrete", group)
self.texts.append(_pose((a + b) / 2 + 0.5, 115, d + 1.05,
"SMASH · УДЕРЖИСЬ НА ОСТРОВЕ", scale=0.6,
id="smash-diorama-caption"))
self.target(feet, -33, d + 2, "diorama-south-view")
self.target(feet, a - 2, -117, "diorama-west-view")
self.target(feet, b + 2, -117, "diorama-east-view")
self.record("closed-display-only-diorama", [a, b, c, d], feet,
enclosed=True, arena=False, floor_intact=True, top_y=120)
def selection_bays(self):
feet = 113
group = "interior:113:smash-selection-bays"
# Solid infill behind the recessed panels prevents unfinished rear rooms.
for x, z in sorted(self.inside):
if -47 <= x <= 13 and z <= -140:
for y in range(feet, 124):
self.put(x, y, z, CREAM, group)
for index, bounds in enumerate(self.layout["smash_selection_bays"], 1):
a, b, c, d = bounds
mid = (a + b) // 2
for x in range(a, b + 1):
for y in range(feet, feet + 8):
self.put(x, y, c, DARK, group)
for x in (a, b):
for z in range(c + 1, d + 1):
for y in range(feet, feet + 7):
self.put(x, y, z, CUT if y == feet else CREAM, group)
self.box([a, b, c + 1, d], feet + 7, feet + 7, CREAM, group)
self.box([a + 1, b - 1, c + 1, c + 1], feet + 1, feet + 6, GOLD, group)
# Five-wide miniature reliefs are explicitly generic, unassigned previews.
for dx in range(-2, 3):
for dy in range(4):
material = "light_blue_concrete"
summit = 1 + ((dx + index) % 3 == 0)
if dy < summit:
material = "stone" if dy == 0 else "moss_block"
if dy == 3 and dx == (index % 3) - 1:
material = "white_concrete"
self.put(mid + dx, feet + 2 + dy, c + 2,
"minecraft:" + material, group)
for x in range(mid - 1, mid + 2):
self.put(x, feet, d, CUT, group)
self.put(x, feet + 1, d, WOOD, group)
self.put(mid, feet + 6, c + 2, LANTERN, group)
self.put(mid, feet + 7, c + 2, GREEN, group)
sign = [mid, feet + 1, d + 1]
support = [mid, feet + 1, d]
self.selections.append({
"slot": f"{index:02d}", "bounds_xz": bounds,
"assigned": False, "destination": None,
"relief_is_decorative": True,
"sign_block": sign, "sign_support_block": support,
"sign_facing": "south", "sign_lines": [f"АРЕНА {index:02d}", "Не назначено", "", ""],
"standing_block": [mid, feet, d + 2],
"label_pose": _pose(mid + 0.5, feet + 6.6, d + 1.05,
f"{index:02d}", scale=0.55),
})
self.target(feet, mid, d + 2, f"arena-{index:02d}-sign")
self.record("unassigned-arena-selection-bay", bounds, feet, slot=index)
self.box([-35, 1, -139, -138], 121, 123, DARK, group)
self.texts.append(_pose(-16.5, 122.2, -136.94, "S M A S H", scale=2.2,
id="smash-header"))
def furniture(self, feet):
for i, bounds in enumerate(self.layout["wing_benches"]):
self.bench(bounds, feet, "south", f"wing-bench-{i + 1}")
for x, z in ((-56, -131), (-56, -106), (21, -131), (21, -106)):
self.planter(x, z, feet)
# The north and south galleries are furnished public rooms, without doors
# suggesting additional unfinished wings or upper-floor circulation.
for i, (bounds, facing) in enumerate((([-22, -15, -94, -93], "north"),
([0, 7, -94, -93], "north"))):
self.bench(bounds, feet, facing, f"south-gallery-bench-{i + 1}")
for x, z in ((-30, -94), (12, -94)):
if _rect([x - 1, x + 1, z - 1, z + 1]) <= self.inside:
self.planter(x, z, feet)
if feet == 99:
for i, bounds in enumerate(([-44, -37, -137, -136], [-26, -19, -137, -136],
[1, 8, -137, -136])):
self.bench(bounds, feet, "south", f"north-gallery-bench-{i + 1}")
for x in (-31, 11):
self.planter(x, -137, feet)
self.gallery_panel(-72, -116, feet, "SHACRAFT", "Площадь прибытия")
self.texts.append(_pose(-5.5, 108, -114.93,
"SHACRAFT", scale=0.9,
id="vestibule-heading"))
else:
for z, title, subtitle in ((-122, "УРОН", "Больше урона — сильнее отбрасывание"),
(-115, "ОТБРАСЫВАНИЕ", "Удержись на острове"),
(-108, "ДВОЙНОЙ ПРЫЖОК", "Вернись на площадку")):
self.gallery_panel(-72, z, feet, title, subtitle)
self.texts.append(_pose(26, 118.5, -105.9, "ЗОНА ОЖИДАНИЯ", scale=0.7,
id="smash-waiting-heading"))
def leaf_distances(self):
leaves = {p for p, s in self.states.items() if s.startswith("minecraft:spruce_leaves[")}
distance = {}
queue = deque()
for p, s in self.states.items():
if s.startswith("minecraft:spruce_log["):
queue.append((p, 0))
while queue:
(x, y, z), d = queue.popleft()
if d >= 6:
continue
for p in ((x - 1, y, z), (x + 1, y, z), (x, y - 1, z),
(x, y + 1, z), (x, y, z - 1), (x, y, z + 1)):
if p in leaves and d + 1 < distance.get(p, 7):
distance[p] = d + 1
queue.append((p, d + 1))
for p in leaves:
self.states[p] = (f"minecraft:spruce_leaves[distance={distance.get(p, 7)},"
"persistent=true,waterlogged=false]")
def navigation(self):
details = {}
for feet, seed in ((99, (-6, -86)), (113, (-6, -120))):
clear = {(x, z) for x, z in self.inside
if all(self.states[(x, y, z)] == AIR for y in range(feet, feet + 4))}
if seed not in clear:
raise ValueError(f"Interior navigation start is obstructed at {feet}: {seed}")
reached = {seed}
queue = deque([seed])
while queue:
x, z = queue.popleft()
for p in ((x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)):
if p in clear and p not in reached:
reached.add(p)
queue.append(p)
for target in self.targets[feet]:
if (target["x"], target["z"]) not in reached:
raise ValueError(f"Unreachable interior target: {target}")
# Close genuine inaccessible air pockets; never leave a half-built room.
sealed = clear - reached
for x, z in sorted(sealed):
for y in range(feet, 111 if feet == 99 else 124):
self.put(x, y, z, CREAM, f"interior:{feet}:sealed-service-infill")
reserved = [self.layout["reserved_clear_aisles"]["entry"]]
if feet == 113:
reserved.append(self.layout["reserved_clear_aisles"]["bay_front"])
for bounds in reserved:
required = _rect(bounds) & self.inside
blocked = required - reached
if blocked:
raise ValueError(f"Reserved circulation is blocked at Y{feet}: {sorted(blocked)[:5]}")
self.floor_walk[feet] = [{"x": x, "y": feet, "z": z}
for x, z in sorted(reached)]
details[str(feet)] = {"walk_columns": len(reached),
"minimum_verified_headroom": 4,
"named_targets": self.targets[feet],
"sealed_inaccessible_columns": len(sealed),
"all_named_targets_reachable": True,
"reserved_aisles_clear": True}
return details
def compile_interior(footprint, layout):
"""Return canonical voxel states, per-voxel groups, and JSON-safe metadata.
``footprint`` contains the actual surveyed foundation's (x, z) columns.
Geometry is deterministic and absolute, as specified by the approved layout.
The returned air states are intentional room clearance; callers must apply
the normal expected-state and protected-volume checks before any live write.
"""
b = _Interior(footprint, layout)
floors = [(int(f["walk_y"]), int(f["ceiling_underside_y"])) for f in layout["floors"]]
if floors != [(99, 111), (113, 124)]:
raise ValueError("This approved interior is defined only for walk Y99 and Y113")
b.shell()
for feet, ceiling in floors:
b.floors(feet)
b.arcade(feet, ceiling)
b.furniture(feet)
lights = [(-58, -116), (25, -116), (5, -107), (-32, -101)]
if feet == 99:
lights += [(-33, -116), (-30, -132)]
for x, z in lights:
b.chandelier(x, z, feet, ceiling)
b.lift_cabin(feet, ceiling)
b.diorama()
b.selection_bays()
b.leaf_distances()
navigation = b.navigation()
palette = sorted({s.partition("[")[0] for s in b.states.values()})
metadata = {
"schema_version": 1,
"name": "Shacraft station furnished vestibule and SMASH gallery",
"status": "compiled candidate; requires caller survey, collision and live verification",
"owned_y": [98, 123], "perimeter_setback_blocks": 2,
"foundation_columns": len(b.footprint), "interior_columns": len(b.inside),
"state_count": len(b.states), "non_air_count": sum(s != AIR for s in b.states.values()),
"group_counts": dict(sorted(Counter(b.groups.values()).items())),
"palette": palette, "features": b.features, "lift": b.lift,
"selection_bays": b.selections, "text_displays": b.texts,
"walk_points": b.floor_walk[99] + b.floor_walk[113],
"walk_points_by_floor": {str(k): v for k, v in b.floor_walk.items()},
"navigation": navigation,
"design_invariants": {
"only_two_furnished_floors": True, "upper_floor_has_no_holes": True,
"lift_has_no_open_shaft": True, "no_attic_or_clocktower_access": True,
"diorama_is_closed_and_display_only": True, "arena_slots_are_unassigned": True,
"functional_text_signs_and_lift_are_caller_owned": True,
},
}
return b.states, b.groups, metadata