not-tonight/LANEHANDOVER.md
type-two 3eee8432d4 LANE-FLOOR: the Floor — patrol, flashlight/UV, cut-off, stalls, pat-down
FloorDemoScene (F from Parade): an 80x45-tile venue you patrol in the dark with
a flashlight cone that reveals detail, plus all three v0.2 infractions and the
UV stamp check. Self-populates from the patron generator; consumes Patron[] only,
so Phase 2 can hand it the real admitted list.

Pure, tested logic (venueMap, cone, player, crowdSim, bangJudge, escalation,
patDown); Phaser layer stays thin (FloorView + three overlays). 120 new tests,
169 total.

Two bugs found by running it rather than by the suite, both fixed:
- bump penalties had a per-agent cooldown but no sim-wide bound, so the leak rate
  scaled with crowd size and an unattended venue pinned vibe to 0 / aggro to 100
  by ~1AM. Sim-wide gate added, with a regression test.
- beat.update() sat below the overlay early-return, freezing the beat clock behind
  modals and making the stall rhythm game unwinnable. Clock and beat now always run.

Also retuned dwell times and the tile/darkness palette for readability, and
dropped Light2D in favour of the cone mask it was fighting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:49:13 +10:00

181 lines
10 KiB
Markdown

# LANE HANDOVER LOG
Coordination file between lanes (Opus 4.8 execution sessions) and the reviewer
(Fable). **Append-only** — never edit or delete previous blocks. Newest block at the
BOTTOM. Push after every session so the reviewer can see state remotely.
## Protocol
1. Before starting work: read this whole file + your lane file in `lanes/` +
`docs/CONTRACTS.md`. Check for a REVIEW block addressed to your lane — it
overrides your lane file where they conflict.
2. Work only inside your lane's owned directories (see BUILD_PLAN.md §2).
3. End of session: append a SESSION block (template below), commit, push your
`lane/<name>` branch.
4. Never merge to main — the reviewer does that.
5. Blocked on a contract ambiguity? Don't stall and don't silently change the
contract: stub locally with `// TODO(contract):`, log a CONTRACT CHANGE REQUEST
below, keep moving.
## SESSION block template
```markdown
---
### SESSION — <LANE-NAME> — <date> <time>
**Branch/commits:** lane/<name> @ <short-sha>..<short-sha>
**Done:** bullet list of what actually works now (be honest — "renders but untested" ≠ "done")
**How to see it:** exact command + what to click (e.g. `npm run dev` → press D → deny 3 patrons)
**Tests:** X passing / Y failing (name the failures)
**Broke / known-wonky:** anything you left imperfect
**Contract change requests:** none | numbered list with justification
**Questions for reviewer:** none | numbered list
**Next session should:** bullet list
```
## REVIEW block template (reviewer only)
```markdown
---
### REVIEW — Fable — <date>
**Reviewed:** <lanes/commits>
**Merged to main:** <yes/no + sha>
**Contract decisions:** rulings on any open change requests
**Instructions for <LANE>:** what to do next / what to fix first
```
---
*(log starts below)*
---
### SESSION — LANE-0 (executed by Fable, reviewer) — 2026-07-19 16:30
**Branch/commits:** main (reviewer executed Phase 0 directly; no lane branch)
**Done:**
- Vite + strict-TS + Phaser 3.90 + Vitest + ESLint scaffold; `Math.random` banned by lint rule.
- All CONTRACTS.md types verbatim in `src/data/types.ts`.
- `core/`: SeededRNG (mulberry32, named streams), typed EventBus (snapshot-emit
semantics), GameClock (real-ms driven, `ageOn` birthday math incl. leap-day),
Meters (single-writer, hype multiplies positive vibe only, clicker drift),
StubBeatClock (128 BPM), versioned corrupt-safe localStorage save.
- `data/`: outfit vocabulary (+PALETTE), 9 archetypes, 10 contraband items,
17 Dazza texts (8 rule-bearing with ruleIds).
- `patrons/`: generator (fake-ID tell system, DOBs clustered near the 18
boundary, late-arrival drunk drift), pure `dollPlan` + Phaser `renderDoll`
(3 poses, drunk tells baked), regulars' memory store.
- `rules/drunk.ts`: drunkStage thresholds.
- ParadeScene demo: rainy street, kebab-shop + venue dressing, 24-walker parade
with ID cards rendered from the same data, SPACE reseed / D toggle cards.
- 49 tests green; lint/build/test gate clean.
**How to see it:** `npm run dev` → http://localhost:5199 (in the Claude preview
pane, drive frames per CLAUDE.md "Dev notes"). Watch for an `almost18` label
walking under an ID card claiming 18+ — that's the whole game in one image.
**Tests:** 49 passing / 0 failing.
**Broke / known-wonky:**
- ID cards overlap when the footpath is crowded — cosmetic, parade-only.
- `window.game` debug handle exposed in main.ts (deliberate, for pane-driving).
- Spec deviations: doll renderer split into `dollPlan.ts` (pure) + `doll.ts`
(Phaser) — better than spec'd, plans are pixel-testable without a GPU; added
a 5th fake-ID tell (expired real card, no boolean tells); no MainMenu scene
(Boot → Parade direct) — menu is Phase 2 shell work.
**Contract change requests:** none (contracts implemented as written).
**Questions for reviewer:** n/a (self-reviewed).
**Next session should:** Phase 1 lanes are OPEN — see REVIEW block below.
---
### REVIEW — Fable — 2026-07-19
**Reviewed:** Phase 0 (self-executed; verified via test gate + in-browser parade).
**Merged to main:** yes (direct).
**Contract decisions:** CONTRACTS.md now frozen. Extension requests via this file.
**Instructions for Phase 1 (LANE-DOOR, LANE-FLOOR, LANE-JUICE):**
1. Branch from current main into `lane/door` / `lane/floor` / `lane/juice`
(worktrees if sharing a machine). Read your lane file's new
"Phase-0 reality" section FIRST — it corrects the original spec where the
landed code differs.
2. LANE-DOOR is the critical path; if only one Opus session is available, run
DOOR first, JUICE second, FLOOR third.
3. Every session ends with the gate + a SESSION block here + a push. Fable
reviews on request ("review the lanes").
---
### SESSION — LANE-FLOOR — 2026-07-19 18:50
**Branch/commits:** lane/floor @ 9805ff8..(this commit) — branched from main, worktree `../not-tonight-floor`
**Done:**
- `FloorDemoScene` (menu key **F** from Parade): 80x45-tile venue (1280x720, 2x screen,
camera-follow), 44-patron crowd sim, flashlight cone, UV mode, all three v0.2
infractions + the stamp check, playable end-to-end.
- Pure logic, all tested: `venueMap` (programmatic layout + anchors + stalls),
`cone` (vision maths), `player` (heavy accel + wall slide), `crowdSim`
(archetype-weighted anchor wandering, clock-driven intoxication drift, beat-driven
dance bob, stall double-occupancy, escort-out), `bangJudge` (nearest-beat-boundary
timing), `escalation` (5x3 cut-off matrix), `patDown` (pocket layout + scoring).
- Phaser layer: `FloorView` (baked tile texture, silhouette/detail split with a
cone GeometryMask, neon glow pools, UV stamp dots) + three overlays
(`CutOffOverlay`, `StallOverlay`, `PatDownOverlay`).
- All floor dialogue in `src/scenes/floor/strings.ts` (see deviation #1).
**How to see it:** `npm run dev` → press **F** at the Parade → WASD patrol, **TAB** UV,
**E** interact, **SPACE** bang, **ESC** cancel, **R** reseed. Walk into the dark until
the torch finds someone. In Claude's preview pane drive frames per CLAUDE.md.
**Tests:** 169 passing / 0 failing (49 pre-existing + 120 new floor tests).
**Verified in-browser, not just green:** cut-off (correct rung ejects, one rung too
soft on a maggot lets them stagger free), escort walks to the exit and logs an
ejection, stall busts on 3 on-beat bangs and *never* busts on 8 off-beat ones,
pat-down bins an item (+2 vibe, right flavour line) and leaks it on timeout (-1.5),
UV shows the stamp glow and marches an unstamped patron out with a Kayden radio line.
**Broke / known-wonky:**
- **Two bugs the test suite could not catch, both found by running it and both fixed:**
1. *Ambient meter collapse.* The bump penalty had a per-agent cooldown but no
sim-wide bound, so leak rate scaled with crowd size — an unattended venue
pinned vibe to 0 and aggro to 100 by ~1AM before the player touched anything.
Added a sim-wide gate (`BUMP_GATE_MS`); an unattended night now drifts
50→27 vibe / 0→34 aggro. Regression test added.
2. *Frozen beat behind modals.* `beat.update()` sat below the overlay early-return
in the scene loop, so opening the stall minigame stopped the beat clock and the
rhythm game could never receive a tick. Clock + beat now run regardless of overlays.
- Dwell times raised (2.5-9s → 7-22s): at the old values ~68% of the crowd was
permanently in transit and the bar/dance floor read as empty.
- Tile palette + darkness alpha retuned; at the original values the venue
multiplied down to near-black and you could not tell a dance floor from a wall.
- Light2D dropped in `FloorView` (documented in-file) — the pipeline swap fights the
cone GeometryMask, and the mask is the actual gameplay. The look is carried by a
darkness sheet + additive cone + neon glow pools instead.
- `GeometryMask` on a container is a WebGL-first path; fine under `Phaser.AUTO`,
would misbehave if anything ever forced the Canvas renderer.
- The 16x16 top-down doll reads as a coloured ring from above (dark hair over
outer-layer shoulders). Correct per Phase 0's `dollPlan`, just noting it.
- Stall interiors are deliberately sealed (`stall` walkable, `stallDoor` not), so
patrons are moved in/out via `StallDef.inside`/`.door` rather than pathed.
**Contract change requests:**
1. **`incident:log` event.** `core` exposes no incident API and `EventMap` is frozen,
so `FloorDemoScene` appends to `NightState.incidents` directly (marked
`// TODO(contract):`). Three lanes writing that array directly is exactly the
shared-mutable-state fight the bus discipline exists to prevent, and Phase 3's
inspector/audit needs a single writer. Requesting
`'incident:log': IncidentRecord` plus a handler in `core/meters.ts`.
2. **Aggro has no decay.** `Meters` only ratchets aggro upward, so it is a one-way
trip to 100 over a long night. Ambient pressure is now bounded (above), but a slow
downward drift on a quiet floor would make the meter a *state* rather than a
countdown. Flagging as a design/contract question, not patching it unilaterally.
**Questions for reviewer:**
1. **Floor strings placement.** CONTRACTS.md §7 says Aussie dialogue lives in
`src/data/strings/`, but `data/` is LANE-0's and lanes commit only in their own
dirs. Parked in `src/scenes/floor/strings.ts`. Move it to
`src/data/strings/floor.ts` on your word — it is a pure file move.
2. **`.gitignore` gap (affects every lane using a worktree).** It lists
`node_modules/` with a trailing slash, which does not match a *symlinked*
`node_modules` — worktrees that symlink it show `?? node_modules` as untracked.
I staged paths explicitly, but a lane running `git add -A` would commit a symlink
pointing at an absolute path on John's machine. One-char fix (drop the slash);
left alone since `.gitignore` is not in my lane.
3. Escalation numbers (clean +4 vibe, scene -3/+5 per rung of overreach) are
deliberately small so aggression never feels rewarded. Confirm the balance intent
before LANE-DOOR mirrors it at the rope.
**Next session should:**
- Phase 2 integration: replace the self-populating spawner with the real admitted-patron
list from LANE-DOOR (the scene already only ever consumes `Patron[]`).
- Swap `StubBeatClock` for LANE-JUICE's `TechnoEngine``StallOverlay` takes
`beatIntervalMs` in its constructor and reads `beat:tick` off the bus, so it should
be a one-line change.
- Fight-brewing infraction (design §3.2) is specced but NOT built — the only v0.2
floor mechanic still missing.
- Replace `HudStub` with LANE-JUICE's real meter HUD.