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>
51 lines
3.1 KiB
Markdown
51 lines
3.1 KiB
Markdown
# 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?" **Confirmed** — `buildInterior` 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
|
||
```js
|
||
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 `musicKey` → `manifest.audio.music[musicKey]`, `toneKey` →
|
||
`manifest.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.
|