not-tonight/LANEHANDOVER.md
type-two f5e1151c6e LANE-JUICE: session block + contract change request for door:phoneTheatre
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 18:53:23 +10:00

9.3 KiB
Raw Blame History

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

---
### 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)

---
### 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 devhttp://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-JUICE — 2026-07-19 18:55

Branch/commits: lane/juice @ c262ac8..2eb3c94 Done:

  • audio/TechnoEngine.ts — synthesized 128 BPM track (kick / off-beat hats / 2-bar 16th bassline / 8-bar pad), look-ahead scheduler (25ms timer, 100ms horizon) as the beat authority. Emits beat:tick {beatIndex, audioTimeMs} with the scheduled time, plus beat:bar on downbeats. Drop-in replacement for core/StubBeatClock. Music → lowpass → master; SFX bypass the filter.
  • audio/Sfx.ts — 10 procedural one-shots + startRain/stopRain. One shared noise buffer; voices disconnect on ended.
  • audio/scheduling.ts + audio/filterCurve.ts — pure, node-testable beat grid and cutoff-ramp math (the WebAudio classes are thin wrappers over these).
  • ui/Stamp, Phone, DialogueBox, Clicker, MeterHud, plus style.ts (chunky widget kit) and typo.ts (deterministic drunk typos).
  • ui/JuiceDemoScene.ts — exercises every widget and SFX; log-mapped cutoff slider, DOOR/FLOOR toggle routed through the bus, beat pad that flashes when ctx.currentTime reaches the scheduled time (not on event receipt). How to see it: npm run devhttp://localhost:5199/#juice (or press J; P returns to the parade). Click once to unlock audio, then: drag MUFFLE to sweep the filter by hand, hit STEP INSIDE for the door→floor sweep, mash the SOUNDS column, stamp someone with NOT TONIGHT. Tests: 111 passing / 0 failing (was 49 at Phase 0; +62 this lane). Verified by ear: NOTHING. No audio output available in this environment. Everything below was logic-checked in a real browser via the exposed engine:
  • door→floor sweep ramps 250Hz→18kHz over 600ms, monotonic, exponential (t=0.5 lands ~2.1kHz vs the ~9.1kHz a linear ramp gives).
  • under a genuinely hidden/throttled tab: 52 consecutive beats, zero scheduled in the past, 7598ms lead, max 1 beat scheduled per timer wake. John/Fable still need to ear-check: does the kick read as a kick, is the bassline musical, is typeTick quiet enough at the real mix level (it fires per character), and does the sweep feel like a door rather than a filter. Broke / known-wonky:
  • src/main.ts is edited (2 lines + a ~12-line demo route). Outside LANE-JUICE ownership — see request 2 below. Without it the demo scene is unreachable and Vite tree-shakes it out entirely.
  • TechnoEngine has no test file — it needs a real AudioContext and the suite runs in node. Its scheduling/filter math is tested via the pure modules, but the resync arithmetic is only reachable through the class. Extracting resyncBeat(grid, nextBeat, nowMs) into scheduling.ts would close that gap.
  • Phone thread truncates an over-tall newest message rather than scrolling.
  • Stamp decals are caller-owned now; the demo caps them at 5. The door scene must destroy them (or pass parent) or they accumulate over a night. Contract change requests:
  1. Add door:phoneTheatre: { durationMs: number } to EventMap. Emitted by ui/Phone when the player closes the phone, carrying how long they stared at it while a queue waited — the door scene rewards it with hype. It is a door event, not an audio one, so it is outside my namespace. I did not edit data/types.ts. ui/juiceEvents.ts declaration-merges it into EventMap instead: fully typed, zero edits to the frozen file, and folding it into the real contract later is a copy-paste. Same file also adds audio:unlocked and beat:bar, which ARE mine per LANE_JUICE.md but are declared together so the whole juice surface reads in one place.
  2. Sign-off requested on the src/main.ts edit (scene registration + J/#juice route, marked // TODO(integration)). Happy to revert it if you would rather wire the demo from the Phase 2 shell yourself. Questions for reviewer:
  3. TechnoEngine.unlock() anchors beat 0 at currentTime + 60ms so beat 0 is as honest as every other beat. If a consumer assumes beat 0 lands exactly at audio:unlocked.atMs, that is a 60ms discrepancy — is that acceptable, or should atMs carry the anchored origin instead?
  4. MeterHud counts heat strikes from heat:strike events it receives. On a mid-run scene swap it would restart from 0. Should it take an initial count in its options, or read NightState.heatStrikes.length at construction? Next session should:
  • LANE-DOOR: swap the local stamp for ui/Stamp (// TODO(juice)), and take MeterHud in place of HudStub.
  • Someone with speakers should ear-check the mix and tell me what to retune.
  • Consider extracting resyncBeat into scheduling.ts so the throttled-tab resync gets direct test coverage.