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.
159 lines
8.0 KiB
Python
159 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile concealed station lighting against an explicit observed snapshot.
|
|
|
|
No world I/O. Each fitting replaces full cubes only: brown glass forms a flush
|
|
floor tile or a recessed ceiling lens, with glowstone hidden directly behind it.
|
|
The caller applies and independently verifies the resulting checked recipe.
|
|
"""
|
|
|
|
import argparse
|
|
from collections import Counter
|
|
from datetime import datetime, timezone
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
STAGE = ROOT / ".runtime/station-stage07"
|
|
GLOW = "minecraft:glowstone"
|
|
LENS = "minecraft:brown_stained_glass"
|
|
WOOD = "minecraft:spruce_planks"
|
|
CREAM = "minecraft:smooth_sandstone"
|
|
|
|
|
|
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("station_lighting_survey", ROOT / "scripts/foundation-survey.py")
|
|
|
|
|
|
def compile_lighting(before, station):
|
|
if station["scope"] != before.scope:
|
|
raise ValueError("Station metadata and observed snapshot have different scopes")
|
|
footprint = {tuple(p) for p in station["footprint"]}
|
|
inside = {(x, z) for x, z in footprint
|
|
if all((x + dx, z + dz) in footprint
|
|
for dx in range(-2, 3) for dz in range(-2, 3))}
|
|
targets = {feet: {(t["x"] + dx, t["z"] + dz)
|
|
for t in station["interior"]["navigation"][str(feet)]["named_targets"]
|
|
for dx in range(-1, 2) for dz in range(-1, 2)}
|
|
for feet in (99, 113)}
|
|
# Keep both cabin landings, controls, doorway and immediate approach unchanged.
|
|
lift_exclusion = {(x, z) for x in range(-11, 0) for z in range(-125, -112)}
|
|
changes = {}
|
|
fixtures = []
|
|
|
|
def full(state):
|
|
return state.split("[", 1)[0].removeprefix("minecraft:") in survey.FULL
|
|
|
|
def put(x, y, z, value, group):
|
|
if (x, z) not in inside or not 97 <= y <= 124:
|
|
raise ValueError(f"Lighting write outside authorized volume: {(x, y, z)}")
|
|
old = before.state(x, y, z)
|
|
if not full(old) or not full(value):
|
|
raise ValueError(f"Lighting may replace full cubes only: {(x, y, z)} {old}")
|
|
if old == value:
|
|
return
|
|
p = (x, y, z)
|
|
if p in changes and changes[p]["block"] != value:
|
|
raise ValueError(f"Lighting layers disagree at {p}")
|
|
changes[p] = {"x": x, "y": y, "z": z, "expected": old,
|
|
"block": value, "group": group}
|
|
|
|
for feet, ceiling in ((99, 110), (113, 123)):
|
|
public = {(p["x"], p["z"]) for p in station["interior"]["walk_points_by_floor"][str(feet)]}
|
|
public &= inside
|
|
candidates = {"floor": set(), "ceiling": set()}
|
|
for x, z in sorted(public - lift_exclusion):
|
|
# Existing flooring mosaics and green/brass bands are not recoloured.
|
|
if (x, z) not in targets[feet] and before.state(x, feet - 1, z) == CREAM:
|
|
neighborhood = {(x + dx, z + dz) for dx in range(-1, 2) for dz in range(-1, 2)}
|
|
if neighborhood <= public and all(
|
|
before.state(xx, y, zz) in survey.AIR
|
|
for xx, zz in neighborhood for y in range(feet, feet + 4)
|
|
) and full(before.state(x, feet - 2, z)):
|
|
candidates["floor"].add((x, z))
|
|
# Plain spruce cells only: beams, chains, chandeliers, panels and lift
|
|
# geometry are excluded by the observed material and air-column tests.
|
|
if (before.state(x, ceiling, z) == WOOD
|
|
and full(before.state(x, ceiling + 1, z))
|
|
and all(before.state(x, y, z) in survey.AIR for y in range(feet, ceiling))):
|
|
candidates["ceiling"].add((x, z))
|
|
|
|
for kind in ("floor", "ceiling"):
|
|
chosen = set()
|
|
# A common architectural grid; a maximum two-block adjustment avoids
|
|
# a column, mosaic or ceiling beam without creating dense bright rows.
|
|
for gx in range(-69, 40, 9):
|
|
for gz in range(-139, -83, 9):
|
|
nearby = [(x, z) for x in range(gx - 2, gx + 3)
|
|
for z in range(gz - 2, gz + 3)
|
|
if (x, z) in candidates[kind]]
|
|
nearby.sort(key=lambda p: ((p[0] - gx) ** 2 + (p[1] - gz) ** 2,
|
|
abs(p[0] - gx) + abs(p[1] - gz), p))
|
|
selected = next((p for p in nearby if all(
|
|
(p[0] - q[0]) ** 2 + (p[1] - q[1]) ** 2 >= 36 for q in chosen)), None)
|
|
if selected is None:
|
|
continue
|
|
x, z = selected
|
|
chosen.add(selected)
|
|
lens_y, emit_y = (feet - 1, feet - 2) if kind == "floor" else (ceiling, ceiling + 1)
|
|
group = f"station-lighting:{feet}:{kind}"
|
|
put(x, lens_y, z, LENS, group)
|
|
put(x, emit_y, z, GLOW, group)
|
|
fixtures.append({"floor_walk_y": feet, "kind": kind,
|
|
"lens": [x, lens_y, z], "emitter": [x, emit_y, z],
|
|
"grid_anchor_xz": [gx, gz]})
|
|
|
|
recipe = {"version": 1, "scope": before.scope,
|
|
"blocks": [changes[p] for p in sorted(changes)]}
|
|
metadata = {
|
|
"version": 1, "scope": before.scope, "world_writes": 0,
|
|
"status": "checked lighting candidate; live application and visual review pending",
|
|
"style": "Sparse warm floor tiles and small concealed ceiling lenses on a nine-block grid",
|
|
"grid_spacing": 9, "maximum_anchor_adjustment": 2,
|
|
"minimum_same_layer_fixture_distance": 6,
|
|
"owned_y": [97, 124], "perimeter_setback": 2,
|
|
"changed_states": len(changes), "fixtures": fixtures,
|
|
"fixture_counts": dict(sorted(Counter(f"{f['floor_walk_y']}:{f['kind']}" for f in fixtures).items())),
|
|
"changed_materials": dict(Counter(row["block"] for row in changes.values())),
|
|
"checks": {"only_full_cube_replacements": True, "no_new_collision_obstructions": True,
|
|
"existing_mosaic_bands_preserved": True, "lift_landings_and_doorway_excluded": True,
|
|
"floor_fixtures_outside_named_target_neighborhoods": True,
|
|
"floor_fixtures_have_clear_three_by_three_surrounds": True,
|
|
"ceiling_lenses_replace_only_plain_spruce": True,
|
|
"decorative_furniture_and_diorama_preserved": True},
|
|
"limits": "Fixture geometry does not predict the client's final rendered brightness. Review actual Minecraft images after applying and waiting for lighting updates.",
|
|
}
|
|
return recipe, metadata
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--before", type=Path, default=STAGE / "after.json.gz")
|
|
parser.add_argument("--station-metadata", type=Path, default=STAGE / "compiled/station.metadata.json")
|
|
parser.add_argument("--output", type=Path, default=STAGE / "lighting")
|
|
args = parser.parse_args()
|
|
recipe, metadata = compile_lighting(survey.load_snapshot(args.before), json.loads(args.station_metadata.read_text()))
|
|
metadata["compiled_at_utc"] = datetime.now(timezone.utc).isoformat()
|
|
metadata["inputs"] = {str(path): hashlib.sha256(path.read_bytes()).hexdigest()
|
|
for path in (args.before, args.station_metadata, Path(__file__))}
|
|
args.output.mkdir(parents=True, exist_ok=True)
|
|
for name, doc in (("lighting.json", recipe), ("lighting.metadata.json", metadata)):
|
|
destination = args.output / name
|
|
if destination.exists():
|
|
raise FileExistsError(f"Preserve the previous candidate: {destination}")
|
|
destination.write_text(json.dumps(doc, indent=2) + "\n")
|
|
print(json.dumps({"recipe": str(args.output / "lighting.json"), "changed_states": len(recipe["blocks"]),
|
|
"fixture_counts": metadata["fixture_counts"]}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|