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

127 lines
7.2 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-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: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.