103 lines
6.0 KiB
Markdown
103 lines
6.0 KiB
Markdown
# 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.
|