The venue ladder has always listed four venues in data/venues.ts, but they shared ONE floor map and differed only by difficulty knobs — so a promotion you survived a whole week for looked exactly like the room you had just left. - venueMap.ts grows a FloorLayout contract (map + props + lights + posts + probes + palette) and the newGrid() primitives every room is painted with. Voltage stays IN venueMap rather than moving to layouts/, so the new rooms can import primitives from it without closing an import cycle. - Three new rooms in src/scenes/floor/layouts/: The Royal (horseshoe public bar, pool room, pokies corner, trough + two cubicles, a beer garden that is plainly the nicest room in the pub, and a DJ "corner" that is a folding table because this pub never built a booth), Elevate (open-air rooftop — most of the grid is sky, the DJ is on an unwalled plinth, you arrive by lift), ROOM (concrete warehouse, pillars you path around, central booth, loading-dock smoking area, twelve lights in the whole venue). - Nothing about a room is a module singleton any more. FloorView, sweep and FloorDemoScene take the layout; the door picks its street plate by venue id. - FloorDemoScene's six hardcoded station spots (TAPS_SPOT, DECKS_SPOT, ...) were Voltage's tile coordinates. At four venues a hardcoded (47,3) puts the bar shift inside The Royal's pool room, so each layout now names its own stations and the scene reads them from there. - tests/floor/layoutInvariants.ts is an executable rulebook: perimeter holds, every walkable tile reachable from the entry, anchors on walkable ground, posts not sealed (a post on a sealed tile freezes the player for the night — that has shipped twice), probes reachable, props on-grid. Proved against Voltage BEFORE the new rooms were authored against it. Dev route #floor:<venueId> boots the floor straight into one room, because otherwise seeing ROOM means surviving three weeks of the ladder. Gate: lint clean, build clean, 821 tests passing (was 784). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
40 lines
1.4 KiB
Python
40 lines
1.4 KiB
Python
#!/usr/bin/env python3
|
|
"""rebuild_manifest.py — make public/props/manifest.json match what is on disk.
|
|
|
|
python3 tools/gen/rebuild_manifest.py
|
|
|
|
The manifest is the list the game loads at boot; a prop missing from it renders
|
|
as a placeholder even though its PNG is sitting right there. Two ways that
|
|
happens: a concurrent batch losing an entry to a read-modify-write race (now
|
|
locked in props_import.py, but earlier runs were not), or a sprite copied into
|
|
the directory by hand.
|
|
|
|
Directory listing is the source of truth here, deliberately — the PNGs are what
|
|
the game can actually load.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
PROPS = Path(__file__).resolve().parent.parent.parent / "public" / "props"
|
|
|
|
|
|
def main() -> None:
|
|
names = sorted(p.stem for p in PROPS.glob("*.png"))
|
|
man_path = PROPS / "manifest.json"
|
|
before = set(json.loads(man_path.read_text()).get("props", [])) if man_path.exists() else set()
|
|
man_path.write_text(json.dumps({"props": names}, indent=2) + "\n")
|
|
|
|
added = [n for n in names if n not in before]
|
|
dropped = sorted(before - set(names))
|
|
print(f"manifest now lists {len(names)} props")
|
|
if added:
|
|
print(f" recovered (on disk, were missing from the manifest): {', '.join(added)}")
|
|
if dropped:
|
|
print(f" removed (listed but no PNG on disk): {', '.join(dropped)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|