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

103 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. ~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.ts`** — `generatePatron(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.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.