# 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.
