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

59 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 form `gig-<genreKey>`
> (`gig-pubrock` / `gig-grunge` / `gig-covers`), present only when `opts.gig` is on — is specified in
> [LANE_C_PUB.md](LANE_C_PUB.md) §1. `room.audio` carries `gigKey?` as a third, optional key alongside the
> two below.
## 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.
## 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.