5.7 KiB
LANE-0 — Scaffold & Contracts (Phase 0, single lane)
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 banningMath.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 aBootScene→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 overEventMapfrom 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). Emitsclock:tickonce per clock-minute. Pausable. ExposesnightDate(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. Listensmeters:delta, clamps 0..100, emitsmeters:changed;heat:strikeappends and is run-scoped. TEST: clamping, single-writer behaviour, hype multiplier applied to positive vibe deltas.StubBeatClock.ts— emitsbeat:tickat fixed 128 BPM fromsetInterval-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 (withvintagevariants), 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 tonightDate).photoSeed === dollSeedunlessphotoMismatch.- 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 aPhaser.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 aboveintoxicationthresholds. 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(rendersmeters:changedvalues 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.