# 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.md` → `LANEHANDOVER.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 `ruleId`s — 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:30–12: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" ~2–5 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.