Light release round — C interior code unchanged since R13 (venue geometry stable by construction); this is the audit, the amp ruling, and the v3.0-FROZEN docs. - Re-verified vs the current tree (D's continuity + RY-fix + real instrument GLBs landed): contract byte-identical (caps 8/8/12, deckY/riserY, gigKeys, determinism), drawSweep <=350 (worst 161), gig-night draws pub 60 / band_room 52 / rsl 71 <=350. Release-law confirmed: D's rng namespace is disjoint from C's ctx.stream; no golden can move. - guitar_amp ruling (LANE_C_PUB §3): NOT blessed as-is. D's pose (stage.z+0.15, down-stage) interpenetrates the bass rig by 0.29 m; move it up-stage-right ~ (stage.w*0.36, deckY, stage.z - stage.d*0.22), clear of the bass + C's primitive ampStacks. Full amp unification = v3.1. Cosmetic + ?gigs opt-in -> not a tag blocker. C->D handshake. - Docs freeze: LANE_C_PUB.md + LANE_C_AUDIO.md marked v3.0-FROZEN; RY §0 reframed past-tense (the +Z fleet was fixed at source in D's buildFigure, R13 - no per-consumer flips); budgets updated to R14; in-flight handoffs removed. - Comment-only code (zero runtime/golden/rng change): fixed stale R12 inline return-shape comments (watchPoints 6..8 -> 8/8/12; stage bandPoses[3] -> [4] + riserY) in interiors.js + layout.js; documented the standalone gigKey hand-build. theme.js untouched. Note for E/F: settled gig-night interior is ~182k tris (E instruments 14k each x5); F's smoke reports ~34k measuring before the async GLBs load. Interior draw gate passes; v3.1 LOD candidate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.5 KiB
LANE C — interior audio contract (v3.0-FROZEN) → for Lane F + Lane B
v3.0-FROZEN · 2026-07-16 · durable contract for Lane F + Lane B. Describes the shipped state; changes require a version bump.
buildInterior returns room.audio = { musicKey, toneKey }, seeded per shop. Lane F's interior_mode just
plays it; Lane B's audio.js resolves the keys → files.
Scope: this doc covers the base interior audio (the seeded music bed + room-tone every shop returns). The gig audio —
room.audio.gigKey, the live-band bed, canonical formgig-<genreKey>(gig-pubrock/gig-grunge/gig-covers), present only whenopts.gigis on — is specified in LANE_C_PUB.md §1.room.audiocarriesgigKey?as a third, optional key alongside the two below.
The contract
room.audio = {
musicKey, // string | null — an interior MUSIC bed key in manifest.audio.music, or null (no music)
toneKey, // string — an interior ROOM-TONE key in manifest.audio.ambience (scope:'interior')
}
- Keys, not files.
buildInteriorstays synchronous and asset-free — it only names what should play. ResolvemusicKey→manifest.audio.music[musicKey],toneKey→manifest.audio.ambience[toneKey](the.ogg+.m4afallback +gain/loopare on those entries). - Silence is legal (house audio law): missing key /
?mute=1/?noassets=1→ play nothing, no error.musicKeyis frequentlynull— treat "no music, room-tone only" as the common case. - Seeded per shop, stable per revisit. Derived from
shop.seedon its own sub-stream, so the same shop is silent-or-playing identically every visit (no per-frame or per-enter randomness).
What each type resolves to (mirrors manifest.audio types arrays — one source of truth)
| type | toneKey | musicKey |
|---|---|---|
| record | roomtone-retail | record-shop (always — "record shop plays music") |
| milkbar | roomtone-milkbar | milkbar (radio, always) |
| video | roomtone-video | video-synth (always) |
| dept, arcade | roomtone-video | arcade (always) |
| opshop, book, toy, pawn, stall | roomtone-retail | null, or milkbar for a seeded ~1-in-3 (a general radio playing — the milk-bar bed is types:[…,'general']) |
Verified (9 types × 8 seeds): 0 bad keys, 0 nondeterminism, every key resolves + matches its manifest
types membership; dedicated-music types are stable, non-music types vary on/off by seed.
Lane F wiring (interior_mode)
on enter(room):
if room.audio.toneKey: playLoop(ambience[room.audio.toneKey], fadeIn) // room-tone always
if room.audio.musicKey: playLoop(music[room.audio.musicKey], fadeIn) // may be null → skip
on exit / dispose(room):
fadeOut + stop both, release AudioNodes // your enter/exit leak smoke checks this
Nothing before the first gesture; ?mute=1/?noassets=1 short-circuit before any fetch. room.audio
is plain data on the return — no new lifecycle, no dispose hook needed from Lane C's side.
Deferred idea (v3.1+, not in the frozen contract)
A places-tagged visual source ("the music comes from there") — e.g. a radio/hifi mesh with
userData.audioEmitter = musicKey so Lane B can position/spatialise the bed. Only the record shop has a
fitting for it today (listeningCorner); milk-bar/video would need a new radio prop. A tidy v3.1 item: the
userData.audioEmitter hook + the two props. v3.0 plays the bed non-spatially (room-filling), which is the
right default for an interior anyway.