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

7.2 KiB
Raw Permalink Blame History

LANE-DOOR — The Door (Phase 1, parallel)

Executor: Opus 4.8. Branch: lane/door (from main after Phase 0 merge). Owns: src/scenes/door/, src/rules/ (shared with reviewer approval — you are its primary author in Phase 1), tests/ mirrors. Prereq reading: docs/GAME_DESIGN.md §3.1 → docs/CONTRACTS.mdLANEHANDOVER.md (check for REVIEW blocks addressed to you).

You are the v0.1 critical path. When this lane is done, the game is shippable as a Door-only night. Bias every decision toward "playable and funny" over "architecturally complete".

Phase-0 reality (read before coding)

Phase 0 landed 2026-07-19 — see CLAUDE.md "Dev notes" for the API map and the browser-pane verification trick. Door-relevant specifics:

  • rules/drunk.ts already exists (drunkStage thresholds + hasVisibleTells) — build dressCode.ts, idCheck.ts, judge.ts beside it.
  • ID date math: ageOn(dob, nightDate) in core/GameClock and idAge(patron, nightDate) in patrons/generator exist and are tested — idCheck.ts should build on them, not reimplement.
  • Fake-ID tells now include a fifth: an expired real-looking card (fake present, all tell booleans false, expiry in the past). Your idVerdict must catch it — see tests/generator.test.ts "every fake has a tell".
  • Patron generation: generatePatron({rng, nightDate}, clockMin, override?); ids are serial (p0…), reset per run via _resetPatronSerial().
  • renderDoll(scene, patron, 'queue') returns a texture key; drunk tells are baked per sway-bucket (see dollTextureKey). For the ×3 patron-up view just scale the image — pixelArt rendering keeps it crisp.
  • ParadeScene shows the working pattern for bus/clock/meters lifecycle (resetRun) — copy that shape, don't invent one. StubBeatClock requires your scene to call update(delta) each frame.
  • Dazza strings live in data/strings/dazza.ts keyed with ruleIds — your dressCode.ts rule ids must match them (8 rule-bearing texts are already written: noThongsSinglets, noLogos, noWhiteSneakers, noSongRequesters, noBucketHats, noSunnies, noBlazers, noHandHolders).

Deliverable

npm run dev → full Door-only night: 9:00 PM to 3:00 AM, patrons queue and step up, you inspect and rule on them, Dazza texts land, meters move, night ends in a summary screen (win) or a fail state (Vibe 0 / Aggro 100). A stranger can play it with zero explanation.

Scene layout (640×360, static tableau)

Street view: queue snaking from the left, rope + patron-up position centre, venue door right, rain + kebab-shop glow (reuse ParadeScene ambience). Desk UI along the bottom: ID tray · dress-code card (latest rules) · clicker · phone · verdict buttons. Patron steps up BIG (queue-pose doll scaled ×3) so outfit details read.

Systems to build (in rough order)

1. Queue manager (door/QueueManager.ts)

Spawns patrons from the generator on a clock-driven arrival curve (quiet 9 PM, slams 10:3012:30). Maintains visible queue of ≤8 dolls + an offscreen count. The Rope: next patron only steps up when the player clicks the rope hook. Idle time while queue is non-empty and no patron is up: +hype (capped), +aggro (uncapped, accelerating). Wire via meters:delta. Phone prop: clicking your phone while someone waits gives bonus hype and a little animation — reward the theatre.

2. Inspection surface

Patron-up view with hover/click zones: face (compare to ID photo), each outfit slot (tooltip: "white sneakers — look new"), eyes (bloodshot?), stance (sway tween driven by intoxication tell from doll renderer). The ID card: click tray → card zooms to half-screen; DOB, expiry, name, photo (rendered idPhoto pose from photoSeed — mismatch tell = it's visibly a different doll). Fake tells per CONTRACTS.md: peeling laminate (corner pixels), wrong hologram (wrong colour shimmer), joke name.

3. Rules engine (src/rules/ — PURE, tested, no Phaser imports)

  • dressCode.ts: implement DressCodeRule[] for night 1 (the 4 escalations from the design doc) + 4 more of your own invention in the same escalating-absurdity register. activeRules(clockMin), violations(patron, clockMin).
  • idCheck.ts: idVerdict(idCard, nightDate){underage, expired, tells[]}. All date math here, tested against boundary cases (18th birthday IS tonight; expiry tomorrow; leap-day DOB).
  • judge.ts: judge(patron, verdict, ctx) → VerdictOutcome — the single scoring function. Encode the design's tensions:
    • deny a violator: +vibe; deny a clean punter: +vibe small, +aggro (arbitrary power is rewarded but heats the queue)
    • admit a violator: deferred vibe ("Dazza does a lap" ~25 min later → dazza text + vibe hit)
    • admit underage: deferred heat:strike (fires on audit/inspection, Phase 2 — for v0.1 fire it at night end so it still hurts)
    • deny an actual owner's mate: big vibe + furious Dazza text TEST every branch of this table.

4. Verdict flow & stamps

Verdict buttons: ADMIT / DENY / TEST / PAT-DOWN / (rope = wait). DENY = the big red stamp: button press → stamp slams down over the patron with screen shake and a dust ring, patron does the defeated-slouch walk-off. Spend real time on this feel — it's the game's signature interaction. (LANE-JUICE will supply the stamp widget + SFX later; build a local version now, swap on integration — mark it // TODO(juice): swap for ui/Stamp.) ADMIT: rope unhooks, patron walks in the door, clicker increments only if the player clicks it — the on-screen clicker count vs true inside-count divergence is tracked in NightState per CONTRACTS.md.

5. Sobriety test (dialogue minigame)

Modal dialogue: pick one of 3 challenges (alphabet backwards / riddle / cursor line-walk — implement the first two v0.1, line-walk stretch). Their answer quality is derived from intoxication + rng, rendered as typed-out text with drunk typos. Player then rules PASS/FAIL regardless of performance — failing someone who passed: +vibe, +aggro. It's a power trip, remember.

6. Dazza & the phone

Phone widget (bottom corner): buzz animation + preview on dazza:text; click to open thread history. Rule-bearing texts add the rule to the dress-code card with a NEW badge. Strings from data/strings/dazza.ts; add more as needed (keep his voice: aggressive lowercase, no punctuation, occasional fear of the owner).

7. Night shell (temporary, yours for v0.1)

Minimal NightScene wrapper: starts clock, runs Door, listens for fail states, 3 AM → NightSummaryScene (temporary version: vibe graph from sampled history, cash = base + hypePeak bonus, verdict stats table). Phase 2 integration will replace this shell — keep it thin and clearly marked // TODO(integration).

Constraints

  • Consume beat:tick from StubBeatClock for any rhythm flourish; never your own timer.
  • All scoring via meters:delta — never touch NightState directly.
  • No imports from scenes/floor/ or audio/.
  • Dialogue/strings in data/strings/, Aussie register per design doc §5.

Definition of done

Full night playable; both fail states reachable by playing badly on purpose; lint/build/test clean; rules-engine coverage includes every judge branch; SESSION block appended to LANEHANDOVER.md; branch pushed.