not-tonight/lanes/LANE0_SCAFFOLD.md
2026-07-19 16:20:53 +10:00

6.0 KiB
Raw Permalink Blame History

LANE-0 — Scaffold & Contracts (Phase 0, single lane)

EXECUTED 2026-07-19 by Fable (reviewer) directly on main. Kept for reference. Deviations from the original spec are noted in the LANE-0 SESSION block in LANEHANDOVER.md — that block is the accurate record.

Executor: Opus 4.8. Branch: lane/scaffold. Prereq reading (in order): docs/GAME_DESIGN.mddocs/BUILD_PLAN.mddocs/CONTRACTS.mdLANEHANDOVER.md. Everything downstream depends on this lane being solid. Favour boring correctness over cleverness; three parallel lanes will build on your exact interfaces.

Deliverable

Repo compiles strict-TS clean, ≥25 unit tests green, and npm run dev shows the Patron Parade: an endless stream of generated patrons walking across a dark street scene, each with their ID card popping up beside them, rendered from the SAME data record. This demo is the proof that the generator, paper-doll renderer, RNG determinism, and clock all work.

Tasks (in order)

1. Project scaffold

  • npm create vite@latest (vanilla-ts) + phaser + vitest. Strict tsconfig (strict, noUncheckedIndexedAccess). ESLint flat config with a rule banning Math.random (no-restricted-properties).
  • Scripts: dev, build, test, lint. All four must work before anything else.
  • Pixel-art rendering config: pixelArt: true, integer zoom, 640×360 base resolution scaled up. Phaser game boots to a BootSceneParadeScene.

2. src/core/

  • SeededRNG.ts — mulberry32. rng.stream(name) returns an independent child generator derived from (seed, name). Helpers: int(min,max), pick(arr), chance(p), weighted(entries). TEST: same seed+stream ⇒ identical sequence; different streams don't correlate; 10k-draw distribution sanity checks.
  • EventBus.ts — typed emitter over EventMap from CONTRACTS.md §4 (implement the full map now, even events nothing emits yet). on, off, emit, once. TEST: typed payloads, off during emit, once semantics.
  • GameClock.ts — converts real elapsed ms → clock minutes (config: night is 360 clock-min over 13 real minutes; curve can be linear v0). Emits clock:tick once per clock-minute. Pausable. Exposes nightDate (an actual date, needed for ID birthday math — pick Sat 2026-07-18 as night 1). TEST: tick counts, pause, date math across a leap-year birthday.
  • meters.ts — owns NightState numbers. Listens meters:delta, clamps 0..100, emits meters:changed; heat:strike appends and is run-scoped. TEST: clamping, single-writer behaviour, hype multiplier applied to positive vibe deltas.
  • StubBeatClock.ts — emits beat:tick at fixed 128 BPM from setInterval-free Phaser time events. LANE-JUICE will replace it behind the same event; keep it dumb.
  • save.ts — (de)serialise run-scoped GameState to localStorage, versioned ({v: 1, ...}), corrupt-save-safe (falls back to fresh). TEST: roundtrip, corruption fallback.

3. src/data/

  • types.ts — the CONTRACTS.md types, verbatim.
  • outfits.ts — the outfit vocabulary: per-slot type lists with weights and colour palettes. Include everything the design's dress codes will need: thongs, singlets, white sneakers (with vintage variants), logo tops, blazers, bucket hats. ~812 types per major slot.
  • archetypes.ts — per-archetype generation params (age ranges, outfit biases, tolerance ranges, flag probabilities). All 9 archetypes from CONTRACTS.md, even though v0.1 only surfaces some — cheap now, annoying later.
  • contraband.ts — item table (id, name, sprite hint, severity): baggie, flask, goon sack, whole kebab, someone-else's-ID, live budgie.
  • strings/dazza.ts — 15+ Dazza texts incl. the 4 dress-code escalations from the design doc. Typed const maps.

4. src/patrons/

  • generator.tsgeneratePatron(rng, clockMin, archetypeOverride?): full Patron per CONTRACTS.md. Critical details:
    • almost18: age 17, ID fake with 12 tells; fresh18: age 18 + days(0..60), ID REAL. DOBs must make the birthday math genuinely non-trivial (cluster DOBs within ±90 days of the 18-year boundary relative to nightDate).
    • photoSeed === dollSeed unless photoMismatch.
    • intoxication start scales with clockMin (crowd arrives drunker later). TEST heavily: determinism, age/ID consistency matrix (real IDs never have fake; every fake has ≥1 tell), archetype constraint spot-checks.
  • doll.tsrenderDoll(scene, patron, pose) per CONTRACTS.md §5. Procedural placeholder v0: layered coloured-rect/pixel shapes onto a Phaser.Textures.CanvasTexture — body silhouette from dollSeed (skin tone, build, hair shape/colour), then outfit layers in slot order, distinct enough that a player can SEE "white sneakers" vs "black boots" at queue size (~32×48px). Poses: queue (front-facing), idPhoto (head crop, flat background), floorTopDown (~16×16 blob with hair colour + top colour). Drunk tells: sway offset + red eye pixels above intoxication thresholds. Deterministic per (patron, pose): TEST that texture pixels for same input are identical.
  • memory.ts — RegularMemory store keyed by patron id (timesDenied, lastSeen night, grudge). Just the store + tests; behaviour wiring is Phase 2/3.

5. src/scenes/shared/ stubs

  • BootScene (asset-less boot, config), ParadeScene (the deliverable demo: patrons spawn on the left, walk across a dark street with rain particles and a kebab-shop glow rectangle, ID card texture shown above each; press SPACE to respawn with a new seed, seed shown on screen), HudStub (renders meters:changed values as text).

6. Handover

Append your SESSION block to LANEHANDOVER.md (template inside it), push lane/scaffold. Reviewer merges to main and opens Phase 1.

Definition of done

npm run lint && npm run build && npm test all clean; parade runs at 60fps with 20+ patrons on screen; same seed reproduces the same parade; handover written.