PROCITY/docs/LANES/LANE_C_AUDIO.md
m3ultra 62e38c5df8 Lane C R14 (v3.0 release): venue audit + docs freeze + guitar_amp ruling
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>
2026-07-16 08:40:42 +10:00

3.5 KiB
Raw Blame History

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 audioroom.audio.gigKey, the live-band bed, canonical form gig-<genreKey> (gig-pubrock / gig-grunge / gig-covers), present only when opts.gig is on — is specified in LANE_C_PUB.md §1. room.audio carries gigKey? 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. buildInterior stays synchronous and asset-free — it only names what should play. Resolve musicKeymanifest.audio.music[musicKey], toneKeymanifest.audio.ambience[toneKey] (the .ogg + .m4a fallback + gain/loop are on those entries).
  • Silence is legal (house audio law): missing key / ?mute=1 / ?noassets=1 → play nothing, no error. musicKey is frequently null — treat "no music, room-tone only" as the common case.
  • Seeded per shop, stable per revisit. Derived from shop.seed on 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.