# 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.md` → `docs/BUILD_PLAN.md` → `docs/CONTRACTS.md` → `LANEHANDOVER.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 `BootScene` → `ParadeScene`. ### 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. ~8–12 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.ts`** — `generatePatron(rng, clockMin, archetypeOverride?)`: full Patron per CONTRACTS.md. Critical details: - `almost18`: age 17, ID fake with 1–2 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.ts`** — `renderDoll(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.