The Phase-1 engine baked every level, pitch and envelope into module consts, so
nothing could be tuned without an edit-reload cycle. Three things need them
live: the ear pass (a human turns a knob and hears it on the next beat), the
night arc, and the Phase-4 DJ role, whose control panel is specified as this
exact surface (BUILD_PLAN §4).
- mix.ts: TrackMix + DEFAULT_MIX (reproducing the Phase-1 values exactly, so
lifting them changes no sound), MIX_RANGES bounds, and formatMix() — which is
what turns a tuning session into a diff instead of numbers that die with the
tab.
- arc.ts: the night's shape as per-voice gain scalars. Thin kick at 9PM, bass
in once there's a crowd to carry it, everything at midnight, low end pulled
first at Last Drinks. Pure keyframes, so it's testable and the DJ role can
drive the same surface by hand.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The important one: the dress-code card was highlighting every rule a patron
broke the moment they stepped up, so optimal play was reading a red line off the
desk. That deleted the inspection loop the whole scene exists for. The highlight
now fires after the ruling, as feedback.
Second: tonight's date appeared nowhere in the game, so working out whether an ID
was underage — the tell that files a heat strike and costs the licence — was
guesswork. It is now on the card beside the DOB and under the HUD clock, which
finally makes the boundary cases idCheck.ts was written for reachable.
Also:
- determinism: the partner's generatePatron call sat behind an enqueue-success
check, and enqueue fails at maxQueue, which is player-controlled — so from the
first queue overflow the rest of the night's patrons diverged. Same class as
the comeback-stream bug, fixed for couples and missed for patrons.
- the sobriety modal's backdrop wasn't interactive; you could rule straight
through it
- tooltips read "navy a blazer" — articles baked into the noun tables, then
colour-prefixed. They are the only detection channel for four rules under
placeholder art, so they have to read cleanly.
- bumbag and chain are drawn on the body but were described only in the head zone
- NightScene.endNight pushed heat strikes past core/meters.ts
- dropped the diff's one `as never` for a real type guard (CCR-4)
- replaced a vocabulary-coverage test whose string-equality heuristic silently
stopped working once a legitimate word matched its own type id
231 tests. Balance findings (vibe pins at 100, stalling is dominant, the slam
never materialises) are measured and written up in LANEHANDOVER.md but not
applied — that is a whole-economy retune and it wants a human playtest, not a
bot with perfect information.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audio (src/audio/), raw WebAudio, no samples or libraries:
- TechnoEngine: 128 BPM four-on-the-floor kick, off-beat hats, 2-bar 16th
bassline, 8-bar pad. Look-ahead scheduler (25ms timer, 100ms horizon) is the
beat authority — emits beat:tick with the SCHEDULED audio time, so consumers
can compare honestly. Replaces core/StubBeatClock at integration; same event.
Stale beats are resynced to the next downbeat rather than caught up: a
throttled/hidden tab otherwise dumps a 64-beat backlog onto one sample.
Music runs through the location lowpass; SFX bypass it and stay crisp.
- Sfx: 10 procedural one-shots + rain bed, one shared noise buffer, voices
self-disconnect on ended so a six-hour night doesn't accumulate nodes.
UI (src/ui/), reusable Phaser containers, no door/floor imports:
- Stamp (the signature interaction), Phone (Dazza thread + phoneTheatre),
DialogueBox (typed-out, drunk-typo render), Clicker (digit roll),
MeterHud (neon vibe / crowd-temp aggro / hype badge / 3 heat slots).
- JuiceDemoScene exercises every widget and SFX, with a log-mapped cutoff
slider and a DOOR/FLOOR toggle that routes through the bus.
Verified in-browser: the door->floor sweep ramps 250Hz->18kHz over 600ms,
monotonic and exponential (halfway lands ~2.1kHz, not the ~9kHz a linear ramp
would give) — that late bloom is what sells the door opening. Under a real
hidden tab, 52 consecutive beats scheduled with zero in the past, 75-98ms lead,
max one beat per wake.
main.ts touched to route J / #juice to the demo — outside lane ownership,
flagged for sign-off in LANEHANDOVER.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Playable start to finish: 9 PM to 3 AM, patrons arrive on a curve, you work the
rope, inspect them, and rule. Dazza texts escalate the dress code through the
night, deferred consequences land when you can no longer argue, and the night
ends in a summary or one of two fail states.
Rules engine (src/rules/, pure, no Phaser):
- dressCode: 8 rules whose text/activeFrom are read out of data/strings/dazza.ts
by ruleId, with a load-time throw if a rule and its announcing text disagree
- idCheck: all ID date maths, built on core/GameClock's ageOn
- judge: the single scoring function, every magnitude named in JUDGE_TUNING
- sobriety: challenge content and the drunk-performance model
Scene layer (src/scenes/door/): NightScene shell, DoorScene, pure QueueManager +
arrivalCurve, patron-up inspection view, ID card, phone, dress-code card,
sobriety modal, stamp, summary.
Tests 49 -> 229.
Ten bugs found by playing it rather than by testing it, including one patron
being ruled eleven times (state was mutating inside a tween callback; it now
settles synchronously), a correctly-played night still rioting at 1:37 AM, and
28% of patrons being deniable for a logo the renderer never drew.
Contract change requests CCR-1..3 are in LANEHANDOVER.md. src/data/types.ts,
src/core/ and docs/ are byte-identical to main; the two additive extensions are
declare-module augmentations. src/main.ts is the one file touched outside the
lane (8 lines, to boot into the night) and is flagged for the reviewer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Testable-in-node math extracted ahead of the WebAudio/Phaser layers:
- audio/scheduling.ts: look-ahead beat grid (dueBeats never repeats or skips a
beat however ragged the timer wakes; burst-capped for suspended tabs)
- audio/filterCurve.ts: the location filter's exponential cutoff ramp — the
door-opening sweep, in frequency-space so it blooms late like a real door
- ui/style.ts: chunky pixel widget kit sharing the doll PALETTE
- ui/typo.ts: deterministic drunk-typo/typing/wobble renderer (seeded, no
Math.random)
- ui/juiceEvents.ts: EventMap extensions via declaration merging, so
door:phoneTheatre types correctly without editing frozen data/types.ts.
TODO(contract) — CONTRACT CHANGE REQUEST filed in LANEHANDOVER.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>