not-tonight/tools/gen/rebuild_manifest.py
m3ultra 4caa901df2 Four venues, four rooms: the ladder stops being one map with different numbers
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>
2026-07-28 19:51:54 +10:00

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()