Commit Graph

67 Commits

Author SHA1 Message Date
m3ultra
74cbd71ad3 Materials, and the floor-cache bug that would have eaten the promotion
Two things, one of them a real bug.

THE BUG: bakeTiles baked to a single global 'floor:tiles' and early-returned if
that key existed. Textures outlive a scene, so the first venue you played baked
the floor for every venue after it — you would survive a week at The Royal, get
promoted, walk into Voltage and still be standing on the pub's carpet. It only
shows on promotion, which is exactly the moment the venue ladder is supposed to
pay off, and never in a dev route because those reload the page. The baked
texture is now keyed per venue; verified by switching venues inside one session
and watching the concrete arrive.

MATERIALS: FloorLayout.materials names a generated plate per tile kind and the
baker stamps it TINTED to the palette colour. The plate carries grain, the
palette keeps hue — an untinted photo-real plate blows straight past the
brightness band the whole room was tuned in, and darkness is the mechanic.
Plates are 16x16, the game's own tile, because at that size "carpet" is
structured noise, which is also what pub carpet looks like from head height.
Ten of them: carpet, concrete, deck, parquet, looTile, paving, plaster, brick,
timber, steel. No plate named, or art not landed, falls back to flat colour
exactly as before.

Wave 2 filler for the two spaces the review found genuinely empty — The Royal's
central carpet (four props across ~500 tiles) and ROOM's east hall (~23% of its
interior with no lights and three props) — plus the kit every real room has and
no game room does: fire extinguishers, exit signs, CCTV, bins, a notice board,
ceiling fans. Voltage gets the same pass; being the reference room is how it
quietly missed the last one.

The invariant caught a duplicate prop id I introduced while placing these, which
is what it is for.

Also relaxed one ROOM assertion that keyed on array order: it checked that the
FIRST sodium light stands over the loading dock, which broke the moment the east
hall legitimately burned the same colour. It now checks that one of them does.

Gate: lint clean, tsc clean, 821 tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 21:56:20 +10:00
m3ultra
7225eed349 Forty-eight sprites: every prop in every room is real art now
37 generated props -> 86. Every one of the 76 prop kinds placed across the four
venues has a sprite; nothing renders as a placeholder.

tools/gen/ is the pipeline: assets.py is the manifest (one entry per sprite —
prompt, ship size, rake, route), run_batch.py walks it, resumable, --workers N.
Cloudflare Workers AI first for its 10k free daily neurons, falling back to
MODELBEAST flux_local automatically. Today's allocation was already spent when
the run started — confirmed from Cloudflare's own error body rather than assumed
— so this batch is entirely farm-rendered.

The mesh tier is the part worth keeping: TRELLIS's defaults (1024_cascade + a
2048px metal PBR bake) cost 5-12 minutes each on a queue that runs one gpu job
at a time, which is four hours for a tileset. mb.FAST_MESH (512 + vertex baker)
lands in ~90s, and A/B'd at ship size the difference does not survive the
downscale to 32px.

Also adds the door's per-venue street plates, keyed on the venue id exactly —
'theRoyal', not a case fold, because props_import writes ':' as '_' and a
lowercased key looks for a plate nobody made.

Gate: lint clean, build clean, 821 tests passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 21:14:33 +10:00
m3ultra
22bd43857c Review pass: the room-specific bugs a shared FloorView was hiding
Three independent reviewers checked each new venue against its brief. No
blockers, all three rooms genuinely distinct — but two of the findings were
mine, in the file the layout authors were told not to touch.

FloorView was still drawing two things at Voltage's coordinates:
- The scan beams pivoted on a hardcoded (38,23) — Voltage's mirror ball. In The
  Royal that swept two beams across the empty middle of the pub, twenty-five
  tiles from that venue's dance floor. Now derived from the layout's own
  discoball, and a venue without one gets no beams, because the beams ARE the
  ball's light.
- The resident DJ stood at a hardcoded (55.5, 22.5) — Voltage's booth, which in
  The Royal is the middle of the bistro. Now placed from the venue's own gear
  and stepped clear of `posts.decks`, so a player on a DJ shift is never
  standing inside him. The beat-bob had the same constant baked into it.

Elevate's staffDj prop is dropped as a consequence: with a resident DJ derived
per venue it would have made two DJs on the plinth, and three once the player
took the shift.

New invariant: a staff prop may not stand on a post tile. Elevate's bartender
was on the exact tile the bar shift teleports the player to, so the shift would
have been played from inside her. Caught by the rule, then fixed.

`mirror` and `graffiti` kinds added. Elevate was standing BOTH its mirrors up as
`poster3` (the author raised it as a contract request and stubbed it honestly),
which would have hung the same torn gig poster in a marble bathroom and called
it a mirror. ROOM's toilets get the graffiti its brief always asked for.

The Royal: TAB screens moved onto the wall they belong on rather than floating
mid-carpet, the trough given its own fluoro (the one fixture that room is known
for was rendering unlit), the beer garden's dark middle band lit — 288 tiles on
two lights left the gate everyone walks through in the dark — the out-of-order
sign moved off the tile the queue stands on, and two comments corrected to
describe what the code actually does.

Also hardened the farm client: `_req` raised SystemExit, which is a
BaseException, so `except Exception` in the batch's worker threads did not catch
it and one stray 401 during a poll killed a 43-asset run after a single asset.
It now raises a normal error and retries transient 401/429/5xx with backoff.

Gate: lint clean, tsc clean, floor suite 271 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 20:01:40 +10:00
m3ultra
4caa901df2 Four venues, four rooms: the ladder stops being one map with different numbers
The venue ladder has always listed four venues in data/venues.ts, but they
shared ONE floor map and differed only by difficulty knobs — so a promotion you
survived a whole week for looked exactly like the room you had just left.

- venueMap.ts grows a FloorLayout contract (map + props + lights + posts +
  probes + palette) and the newGrid() primitives every room is painted with.
  Voltage stays IN venueMap rather than moving to layouts/, so the new rooms can
  import primitives from it without closing an import cycle.
- Three new rooms in src/scenes/floor/layouts/: The Royal (horseshoe public bar,
  pool room, pokies corner, trough + two cubicles, a beer garden that is plainly
  the nicest room in the pub, and a DJ "corner" that is a folding table because
  this pub never built a booth), Elevate (open-air rooftop — most of the grid is
  sky, the DJ is on an unwalled plinth, you arrive by lift), ROOM (concrete
  warehouse, pillars you path around, central booth, loading-dock smoking area,
  twelve lights in the whole venue).
- Nothing about a room is a module singleton any more. FloorView, sweep and
  FloorDemoScene take the layout; the door picks its street plate by venue id.
- FloorDemoScene's six hardcoded station spots (TAPS_SPOT, DECKS_SPOT, ...) were
  Voltage's tile coordinates. At four venues a hardcoded (47,3) puts the bar
  shift inside The Royal's pool room, so each layout now names its own stations
  and the scene reads them from there.
- tests/floor/layoutInvariants.ts is an executable rulebook: perimeter holds,
  every walkable tile reachable from the entry, anchors on walkable ground,
  posts not sealed (a post on a sealed tile freezes the player for the night —
  that has shipped twice), probes reachable, props on-grid. Proved against
  Voltage BEFORE the new rooms were authored against it.

Dev route #floor:<venueId> boots the floor straight into one room, because
otherwise seeing ROOM means surviving three weeks of the ladder.

Gate: lint clean, build clean, 821 tests passing (was 784).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 19:51:54 +10:00
type-two
ae036d9d0e One camera for the whole tileset: nine chunky props re-meshed and ortho-rendered
9/9 through flux product shot -> trellis2_mlx -> glb_to_sprite, unattended.
Rake corrected after the first pass: tall objects need a STEEPER angle (76), not
a shallower one — an arcade at 55 is a front-on slab. Re-angled from the meshes
already on disk, no re-generation, which is the point of keeping the GLB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 14:55:19 +10:00
type-two
433bf77a78 The [GLB] hop, closed: headless Blender ortho-render turns TRELLIS meshes into props
tools/glb_to_sprite.py completes SPACES.md's 3D route — fixed ortho camera fitted
to the mesh bounds, flat light, transparent film, straight into props_import.
A/B'd on one pool table: TRELLIS wins for objects with volume (worn felt, real
pockets, rails), flux stays for flat things. Rake is 60 not 90 — straight down
turns a fridge into a grey box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:37:36 +10:00
type-two
9b6bc6355c Furnish the empty rooms: the dunnies get fittings, the bar gets fridges, the yard gets a heater
Audit found the dunnies were the biggest room in the venue with ONE prop in it
(and a minigame happening there). Nine new kinds placed across the five thinnest
spaces. House-style note recorded: props are raked three-quarter top-down, not
strict orthographic — a fridge from directly overhead is a grey box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:29:55 +10:00
type-two
9bc7dd1bfd Three jobs: sweep out of the god object, story beats per role, and dolls that breathe
- scenes/floor/sweep.ts: the closing sweep extracted behind an accessor host.
  Scene 2100 -> 1981 lines, behaviour verified identical.
- data/roleBeats.ts: 18 beats across 6 roles x 3 nights, landing in the venue
  group chat. The glassie is spoken about; the HoS is spoken to.
- dollPlan(p, pose, frame): 4-frame idle on the queue pose. NOT pre-rendered
  sheets — those would hide the outfit tells the dress code convicts on, which
  design 4.1 forbids; the pre-render path must go UNDER the outfit rects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:49:42 +10:00
type-two
195325f8f1 Role permissions: the glassie can see everything and touch nothing
authority (eject/pat-down/cut-off/busts) and stations (taps/decks) are now per
role. Gated verbs become 'radio it in' — reusing the staff competence roll, so
~2 in 3 calls bring somebody over and the rest just hang in the air. Doorgirl
keeps floor authority on purpose: that loop is the shipped game.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 20:02:54 +10:00
type-two
700bd48bcb The bar sells the room, and the inspector makes one bad pour matter
- pourAdjust pays hype: clean 0.03, heavy 0.05, short nothing. The crowd-pleaser
  is also what walks the room toward the RSA line — same lever, both ways.
  Role pay gap closes from ~$180 to $45.
- breachSeenChance(inspectorWatching): 0.35 alone, 0.9 with a lanyard in the room
  (design §6). Corrects SOLO-19: detection was never the weak part — dedupe caps
  strikes at one a night and 0.35 already saturates across several pours.
- Sim harness was missing the 3 AM audit and under-reporting late-night strikes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:53:25 +10:00
type-two
fbc959fde8 The report catches up: frequency-aware interest weights and eleven banks for the floor's own paperwork
A bar shift logs ~26 serves and buildReport takes the top 3 with no dedupe, so
bartender nights filed three identical generic pour questions. Weights now price
frequency as well as interest, and the loaded kinds have real questions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 19:43:06 +10:00
type-two
3dd61fef36 Sim harness: state the bar sim's coverage limits (it would not have caught the escalation-seam bug)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 21:05:02 +10:00
type-two
754f2b4767 Floor-economy sim: bot nights for the carpet and the bar — caught the carpet's uncapped hype ratchet (vibe-pin 0.27 to 0.71) and capped the trickle
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 21:03:44 +10:00
type-two
7af3732f39 Fix build break in the shoe tests: freshNightState takes (venueId, nightIndex, licensed)
vitest does not typecheck, so these passed under `npm test` while `npm run build`
failed. The gate is lint AND build AND test for exactly this reason — run it in
that order after touching tests, not before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 03:18:49 +10:00
type-two
4118a60aea Review sweep: close the softlock class with data+tests, un-deadlock the door, and make the booth shoe real money
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 03:17:39 +10:00
type-two
0d3ce80c6b Six defects from the adversarial review: two softlocks, a dead door, and a bar that punished the right call
- DJ post stood the player inside the sealed djbooth (same class as the taps
  softlock): every direction blocked, and since the sweep needs you to walk to
  the entry, the night could never be paid. DECKS_SPOT moves to the booth's west
  counter, which is where the map comment always said the gear faces from.
- Role stations are now DATA (venueMap POSTS/PROBES) with tests/floor/stations.test.ts
  pinning the whole bug class: posts must be walkable AND escapable, probes must
  have somewhere walkable within interact range. Written as a MOVEMENT test, not
  a tile-name test — the question is whether the player can leave.
- DoorScene.busy is released by a delayedCall on the scene clock, and stopping a
  scene discards pending events, so a ruling in the last 2.6s of a night left it
  stuck true: a dead door — no rope, no verdicts, no way inside — for the rest
  of the run. Now reset per night, along with seenCards/kebab/franko/retiredRules.
- Bar WATER and CUT OFF never settled the patron, so watering a maggot from
  behind the taps left them ticking as unhandled and re-logging every grace
  period: the call that fixed the problem kept being charged for it.
- Every bar verb logged as 'barServe', putting drinks in the record that were
  never poured. One kind per verb; the report system takes unknown kinds by design.
- carpetMsFor() is consumed on read: a regular parked, recalled and knocked back
  came round again later and was paid the entrance dividend a second time.
- Sweep state joins resetNightState() so an interrupted floor score cannot carry
  its cash, or its previous-night pay callback, into the next night.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 03:13:09 +10:00
type-two
bcc06c104a Handover: record the taps softlock fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:32:44 +10:00
type-two
3b8682dd40 Fix the run-ending softlock: handing the taps back stranded you on a stool tile
toggleTaps() teleported the player to TAPS_FRONT on exit, and TAPS_FRONT was
tile (47,6) — inside venueMap's stool rect, which is not in WALKABLE. Verified
in the live build: after handing the taps back the player sat at (760,104) and
W/A/S/D each moved them exactly 0px, for the rest of the night. The bartender
roster role mounts the taps automatically, so one keypress ended the run.

Exit no longer teleports at all: you stay behind the bar and walk out around
its east end, which is both walkable (verified end to end) and how staff
actually leave a rail. TAPS_FRONT moves to row 7 so the approach probe sits on
the punter side rather than inside the furniture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:32:11 +10:00
type-two
6d2fc56bfb Handover: FABLE-SOLO-12 — three lifecycle fixes and a soak against the roster wave
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:28:48 +10:00
type-two
164b4480c5 Fix floor-start shifts booting inert, and the floor carrying last night into this one
applyLocation() only moved flags — it never announced the move. FloorDemoScene
latches `present` when it binds the night context, which (now the night preloads
every prop texture) happens BEFORE the initial applyLocation runs, so every
floor-start shift — bartender, glassie, DJ — came up visible but completely
inert, with the audio filter still in door mode. The night now emits
night:phaseChange and audio:location for the starting post.

Phaser reuses scene instances across restarts, so class-field initialisers run
once per GAME, not once per night: a DJ shift left djMode set and the next
night's floor booted with movement locked and the keyboard eaten, last night's
puddles kept slipping people through destroyed sprites, and the handled-patron
sets marked fresh arrivals as already dealt with. resetNightState() now wipes
all of it in create().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:24:22 +10:00
type-two
79cd80dc01 Fix the dev route killing the door: N mid-night left a live Night driving a dead Door
showScene() stopped 'Door' (it was in the stop list) and then declined to
restart the already-active 'Night', leaving an unplayable black screen with no
way back. Route only ROOT scenes, and restart the target when it is already
running so N means 'fresh night'. NightScene.teardown() now stops Door and
Floor, making real the ownership main.ts always claimed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:13:47 +10:00
type-two
d4f802407b The floor score: lights on at close — sweep the notes, the shrapnel, the untouched kebab; the baggie is a choice and the pay line knows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 03:40:24 +10:00
type-two
ec9afaff0f The roster board: role × venue level select — six shifts, wages on the card, the night opens wherever the shift stands you
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 03:10:17 +10:00
type-two
e008fb442f Radio command: the Head of Security verb — Bump sorts the mess, Shan waters the worst, Kayden holds the rope (Elevate+, competence not guaranteed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:49:14 +10:00
type-two
5334a30150 Prop batch 2: monitor, booth lamp, dishwasher, pool table, wet-floor cone, high tables, plants, and a real red-carpet plate for the door
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:39:11 +10:00
type-two
5ebdfc61eb The red carpet: 3-slot velvet holding pen — park them, they pay hype; patience by archetype, storms, and the entrance dividend on admit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:32:15 +10:00
type-two
b7bb8f49ab The money layer: booth tips (cash buys the yes, not the landing), the pour meter, bar tabs with the card jar and the 3-card handback
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:17:45 +10:00
type-two
022ea32005 Handover: fix deploy URL typo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:06:49 +10:00
type-two
c20a689086 ROOM boss week: venue 4 — seeded 15-min dress-code shuffle (the board is the law), lip-read rope dialogue, ROOM crowd/knobs; SCENARIOS shelf cleared
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 02:06:16 +10:00
type-two
b273c5c833 The bartender shift: hold the taps — per-stage order lines, SERVE/WATER/CUT OFF priced per §4.3, RSA breach rides a deferred write-up
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:56:49 +10:00
type-two
41e93d01ab Door tween leak: tutorial fade spawned an 800ms tween per frame — now pure tutorialAlpha() driven from update()
Ported from claude/epic-faraday-332679 (6c5889c) — merge was permission-blocked,
same diff applied directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:41:21 +10:00
type-two
fbcbb2e349 The DJ shift: hold the decks — bass-out builds, bar-line-judged drops, booth requests with odds
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:49:33 +10:00
type-two
1b0842024b The whole shelf: 9 scripted characters (cop/celebrity/sovereign/rider/promoter/neighbour/hens/twins/ex-doorman) + 5 floor systems (vomit/phone/water/first-aid/kebab run)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 19:27:43 +10:00
type-two
d8c3b0507d SCENARIOS shelf: mark lockout, passback, step-up lines, fence-jumpers shipped
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 18:46:12 +10:00
type-two
4471bf4376 Late game: 1:30 lockout law, ID passback (SEEN TONIGHT), regular step-up lines, yard fence-jumpers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 18:45:50 +10:00
type-two
389376c2f1 The joint bust (ChoiceOverlay, corruption verb, report accounts) + the glassie run (kitchen, rack carry, wobble physics, glass smash)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:51:49 +10:00
type-two
7803873825 The club gets dressed: full MODELBEAST art drop (20 sprites + street backdrop), scan-beam light show, colour-cycling dance grid, pool corner + furniture, present-race fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:19:46 +10:00
type-two
f38ea0743c Cross-night regulars with disguises, venue crowd identities, retractions as texts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:43:40 +10:00
type-two
9827e4ebe6 Venue ladder: Royal→Voltage→Elevate with promotion flow, per-venue knobs, sim coverage; yard rain + booth DJ
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:53:52 +10:00
type-two
b1e2f038bc Spaces: DJ booth, smoking yard, sinks, glassie station; data-driven PROPS/LIGHTS with beat-pulsed zone lighting; art drop-in pipeline + generation queue
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 14:38:19 +10:00
type-two
2e0b1917b8 Asset survey: RIGROOM library, modular kit, repose pipeline, procity notes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 13:36:50 +10:00
type-two
22f5f11dde Fix card/judge disagreement under text lag; session handover
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 06:06:42 +10:00
type-two
7b44e01333 Solo build-out: floor SFX + drunk typos, 8-encounter table with cross-night gating, scheduler rework
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 05:51:16 +10:00
type-two
63a3b4d4fc Economy tuning pass: sim harness + 9 feel-bands, Dazza rule cap, natural churn, capacity strike fix, hype decay, vibe cooling
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 05:35:47 +10:00
type-two
a961e1853b Document live deploy at monsterrobot.games/not-tonight
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 23:35:48 +10:00
type-two
662060388a Phase 3 review: merge content/floor2/juice2, port stale-base floor work into night mode, aggro decay, Ambience+MixDesk wiring
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 22:17:57 +10:00
type-two
df70c99dd0 Merge branch 'lane/floor'
# Conflicts:
#	LANEHANDOVER.md
#	src/scenes/floor/FloorDemoScene.ts
2026-07-19 22:14:08 +10:00
type-two
e1972c1c07 Merge branch 'lane/juice2'
# Conflicts:
#	LANEHANDOVER.md
2026-07-19 22:03:48 +10:00
type-two
10462ebada LANE-JUICE2: session block — ear pass is rigged and waiting on ears
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 21:48:44 +10:00
type-two
1a66c602fd LANE-JUICE2: ear-pass mix desk, ambience beds, night arc in the engine
The Phase-1 debt was 'verified by ear: NOTHING'. I still can't hear, so this
lane builds the instrument that lets someone who can tell me what's wrong, and
makes their answer a diff instead of a conversation.

- TechnoEngine drives every voice from the live TrackMix and the arc, instead of
  module consts. Behaviour-identical at DEFAULT_MIX (verified constant by
  constant). Voices whose effective gain rounds to silence are skipped, not
  clamped — at 9PM the bass builds zero nodes rather than ~8/sec of silence.
  setMixValue quantises integer paths, so what formatMix dumps is what played.
- MixDeskScene: solo/mute per voice with a per-voice listening prompt, every
  mix param on log-mapped sliders, an arc scrubber (audition 1AM at 9PM), and
  DUMP MIX -> a pasteable TrackMix literal. Restores the arc pin and solo state
  it found on close, rather than forcing a default — the demo deliberately pins
  PEAK and the desk was silently un-tuning it.
- Ambience: rain outside, crowd murmur inside, kebab-shop fluoro hum. On the SFX
  bus, not the music lowpass — that filter models the PA being behind a wall,
  and rain is not behind anything. Headcount comes off door:verdict admits and
  floor:ejection, NOT door:clicker: the clicker is the player's claimed count and
  its divergence from reality is the mechanic, so the room would sound like the
  lie. Verified live: 120 + 2 admits - 1 ejection = 121, clicker ignored.
- JuiceDemoScene pins the arc at PEAK on entry. GameClock runs 360 game-min in
  13 real min, so an unpinned demo opens on kick+hat and anyone judging the bass
  would conclude it's broken.
- Phone thread scrolls with visible-window culling and a binary-searched range,
  a history cap, and a 'new below' cue when a text lands off-screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 21:47:53 +10:00