PROCITY/docs/LANES/LANE_C_AUDIO.md
m3ultra 89f278b603 Lane C: round-11 audio contract — room.audio on buildInterior
buildInterior returns room.audio = { musicKey, toneKey }, seeded per shop: keys into Lane E's manifest.audio (interior music bed + room-tone). Lane F's interior_mode plays it, B's audio.js resolves keys->files. Keys not files: buildInterior stays synchronous + asset-free; silence on missing/mute/noassets (house audio law).

Maps mirror the manifest per-type types arrays: record/milkbar/video/dept/arcade always play their bed; other types room-tone-only except a seeded ~1-in-3 general radio (the 'general'-tagged milk-bar bed).

Verified 9 types x 8 seeds: 0 bad keys, 0 nondeterminism, all keys resolve + match manifest type membership, deterministic per shop. Contract + F wiring: docs/LANES/LANE_C_AUDIO.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 15:46:58 +10:00

3.1 KiB
Raw Blame History

LANE C — audio contract (round 11) → for Lane F + Lane B

PROCITY-C, 2026-07-15. Answers the R11 §Lane C question: "where should the interior bed + room-tone attach?" ConfirmedbuildInterior now 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.

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.

Optional (deferred to v2.2, per the round scope)

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. Not in this round — proposing the userData.audioEmitter hook + the two props as a tidy v2.2 item. v2.1 plays the bed non-spatially (room-filling), which is the right default for an interior anyway.