# LANE-FLOOR — The Floor (Phase 1, parallel)

**Executor:** Opus 4.8. **Branch:** `lane/floor` (from main after Phase 0 merge).
**Owns:** `src/scenes/floor/` + its tests. **Prereq reading:** `docs/GAME_DESIGN.md`
§3.2 → `docs/CONTRACTS.md` → `LANEHANDOVER.md` (check for REVIEW blocks for you).

## Deliverable

`FloorDemoScene` runnable via `npm run dev` (menu key F): a populated top-down
venue you patrol with a flashlight, containing live instances of all three v0.2
infractions (cut-off, toilet stall, pat-down) + UV stamp checks. Runs standalone —
it self-populates from the patron generator; integration (Phase 2) will hand it the
real admitted-patron list instead. Code against `Patron[]` input from day one.

## 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. Floor-relevant specifics:
- Drunk thresholds exist in `rules/drunk.ts` (`drunkStage`, `hasVisibleTells`) —
  no local copy needed; delete that TODO from your plan.
- `renderDoll(scene, patron, 'floorTopDown')` already produces the 16×16
  top-down doll (hair blob over shoulders/top colour).
- `StubBeatClock` exists in core; instantiate it in your demo scene and call
  `update(delta)` each frame — it emits `beat:tick` at 128 BPM.
- `generatePatron` + `ParadeScene`'s `resetRun` lifecycle are the reference
  patterns for self-populating your demo.
- Register your demo scene in `src/main.ts`'s scene list; launch order stays
  Boot → Parade. Menu keys (F for floor demo) can live in ParadeScene as a
  scene-switch keybind — that one-line touch outside your dirs is pre-approved.

## Scene: the venue (top-down, ~2× screen scrollable)

Tilemap (hand-authored array is fine, no Tiled dependency): entry corridor, bar
(along a wall, stools), dance floor (centre), booths, toilets (2 stalls each,
door tiles), smoking-area door. **Darkness is the aesthetic:** black base with neon
accent strips; use Phaser Light2D (`setPipeline('Light2D')`) — ambient very low,
neon point lights, and the player's cone.

### 1. Player & flashlight
WASD/arrows patrol, slightly heavy acceleration (you're a big unit). Flashlight
cone in facing direction — a masked light + a cone polygon that REVEALS detail:
outside the cone patrons render as dim silhouettes (tint), inside it their full
doll colours + tell animations show. TAB toggles **UV mode**: cone goes purple,
normal detail hidden, `uvStamped` hands glow green. No stamp glow on a patron who
re-entered = infraction (march them doorward: walk-behind escort, below).

### 2. Crowd sim (`floor/CrowdSim.ts`)
Patrons wander between anchor points (bar → dance floor → booth → toilet loop)
with per-archetype weights. Dance-floor patrons bob **on `beat:tick`** (StubBeatClock
until integration — never your own timer). Intoxication drifts up over clock time
(bar visits accelerate it); drunk-stage thresholds from `rules/` if LANE-DOOR has
landed them, else local `// TODO(contract)` copy. Visible drunk tells escalate:
sway amplitude, stumble pathing, bumping others (small vibe leaks via
`meters:delta` if un-handled).

### 3. Infraction: The Cut-Off
Approach a `messy`+ patron, interact (E) → dialogue tree modal: three escalation
lines ("Have some water, mate" → "You're done for the night" → firm escort).
Right escalation level for their stage = clean ejection (+vibe, defeated-slouch
walk you escort to the exit); too aggressive on a merely `loose` patron = scene
(−vibe, +aggro); too soft on a `maggot` = they stagger free, try again. Emit
`floor:infractionSpotted` / `floor:ejection` per contract. Leaving a `maggot`
unhandled for >N clock-min queues a deferred heat risk (v0.1: log to
`NightState.incidents`; inspector wiring is Phase 3).

### 4. Infraction: Toilet stalls (the rhythm game)
Stall doors show feet beneath: TWO pairs under one door = infraction. Interact →
close-up view of the door + a beat bar fed by **`beat:tick` events only** (do not
infer beat from elapsed time; measure hit accuracy vs the last tick's
`audioTimeMs`). Bang (SPACE) on-beat 3× consecutively: door flies open, two
patrons scatter in opposite directions with comic speed (+vibe, big). Off-beat
bangs: muffled "piss off mate" from inside, reset combo, small aggro. TEST: hit
windows off recorded tick timestamps (pure function, unit test —
`judgeBang(bangTimeMs, lastTick): 'perfect'|'ok'|'miss'`).

### 5. Infraction: Pat-down (contraband)
Suspicious idle (pocket-pat animation, driven by `flags.contraband`). Interact →
pat-down overlay: patron outline with 5–6 pocket zones; timed 10s; click zones to
reveal items, drag each to the **AMNESTY BIN** on the right. Items from
`data/contraband.ts` with silly sprites (placeholder: labelled coloured rects —
the budgie flaps). Missed items walk with them (deferred vibe leak). Contraband
found = +vibe, patron stays (confiscation, not ejection) unless it's severity 3.

### 6. Escort-out (shared mechanic)
Ejections attach the patron to walk-in-front-of-you pathing to the exit; they play
the defeated slouch. While escorting you can't interact — travel time is the cost
of every ejection (this is the floor's core resource: your position).

## Constraints
- Input: `Patron[]` + EventBus + clock. No imports from `scenes/door/` or `audio/`.
- All scoring via `meters:delta`; incidents appended via NightState API from core.
- Keep every judgement rule (bang windows, escalation matrix, pat-down scoring) in
  pure functions with tests — scene code stays thin.
- Performance: 40+ patrons with Light2D at 60fps; profile before optimizing, but
  silhouette-tint rendering outside the cone should be cheap (no per-frame texture work).

## Definition of done
Demo scene: all three minigames + UV check playable and readable in the dark
aesthetic; `lint`/`build`/`test` clean; pure-logic tests for bang timing,
escalation matrix, pat-down scoring; SESSION block appended to LANEHANDOVER.md;
branch pushed.
