chore(audio): Campaign A slice A6 — delete what retail does not have

Retail EoR has no music system: the linked winmm MIDI player has zero
callers, 'music' appears zero times in the 65 MB decomp, SoundType has no
music member, InitPrefs registers no music key, and the install ships no
music files. So PlayMusic/StopMusic/MusicVolume and the AudioSettings
Music knob are deleted rather than left as an API modelling dead code —
the string-keyed signature was the tell, since every other entry point is
DID-keyed. Old settings.json files carrying a 'music' key still load; the
reader ignores unknown keys and the next save drops it.

The Ambient slider is now surfaced, because slice A5 gave it something to
drive, and its default returns to retail's 1.0 from an invented 0.8 —
InitPrefs defaults every sound preference to unity. The panel rule is
unchanged: no slider that does nothing.

r05-audio-sound.md gets a SUPERSEDED banner naming its five wrong
sections (falloff, pan, voice pool, selection, music, ambient) so a
future reader reaches the lane notes instead of the Ghidra-era reads that
this campaign spent its first two slices undoing.

TS-9 re-scoped from 'any MP3 cue' to the measured blast radius: exactly 1
MP3 among 786 shipped waves, a ~2 s mono clip. Its original framing
assumed a music system that does not exist. The ADPCM count remains
unmeasured and is named as the open question.

Deferred deliberately: #321's sound-cache decode-dedup race. It is a
pre-existing concurrency flake rather than audio-parity behaviour, and
shipping a speculative fix to a race I have not reproduced is exactly the
shortcut this project's no-workarounds rule exists to prevent.

Campaign A is code-complete; the plan carries the closeout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-08 22:58:49 +02:00
parent 7c4dd1ade7
commit dd2cb92b99
13 changed files with 152 additions and 65 deletions

View file

@ -1,6 +1,6 @@
# Campaign A — Audio retail-feel parity
**Status: PROPOSED 2026-08-08 — awaiting user go.** Research phase complete;
**Status: CODE-COMPLETE 2026-08-08 — awaiting the user listening gate.** All six slices landed (see the ledger). Research phase complete;
six-lane named-retail decode done, all load-bearing claims byte-verified
against the PDB-paired 2013 binary (BN pseudo-C alone was NOT sufficient —
see "BN traps" below).
@ -303,8 +303,54 @@ global kill switch.
| Slice | Status | Commit | Gates |
|---|---|---|---|
| A1 | **COMPLETE** 2026-08-08 | `c69b3bde` | 42 Core audio tests; full Release suite 11,563 passed / 4 skipped / 0 failed. Closes #355. |
| A2 | **COMPLETE** 2026-08-08 | `6d0156cb` | 118 Core audio tests (mixer + voice pool + cookbook); full Release suite 11,639 passed / 4 skipped / 0 failed. Opus review run and applied — 2 HIGH (pan-law saturation, stale `FUN_00550ad0` header), 5 MEDIUM (untested clamp order / pan truncation / voice pool, dead `PlayingGain`, duplicated heading helper), 5 LOW. Retires AP-28; files AP-173, AP-174, TS-64, TS-65. **Owed: user listening gate.** |
| A3 | **COMPLETE** 2026-08-08 | `3fae0c7d` | 14 wire-conformance tests + 5 controller tests; full Release suite 11,658 passed / 4 skipped / 0 failed. **Owed: connected gate** (melee hit / pickup / lifestone audible against ACE). |
| A4 | **COMPLETE** 2026-08-08 | `489ea6ad` | UI bank DID resolved from the dats (`0x2000004B`, content-verified: exactly the 32 `UI_*` slots) + 21-case environ table, 30 new Core tests; full Release suite 11,691 passed / 4 skipped. Retires TS-54; narrows AP-115 to notice-only. **Owed: connected gate** (`@environs` thunder + recall cues audible). **Suite note:** two load-dependent measurement flakes were observed on separate full-suite runs (`RuntimeCollisionReportingStateTests.WarmedSteadyContactRefreshDoesNotAllocate`, and one unnamed Core.Net test); both pass in isolation and neither touches audio. |
| A5 | **COMPLETE** 2026-08-08 | `e81bc474` | 46 ambient conformance tests; full Release suite 11,739 passed / 4 skipped. Opus review run and applied — it caught a FATAL frame bug (cell offsets built in absolute world coordinates while the listener is in the streamed frame: every contribution culled at ~32 km, feature silent with no error), a per-entry vs per-cell denominator error that would have pushed multi-entry beds under the audibility floor, an infinite loop on a zero play-rate, and newly-audible ambients not firing until a full period later. Also moved beds onto retail's single 16-voice priority pool and made the in-block direction test XY-only. Retires TS-29; files TS-66 (`seen_outside` interiors), TS-67 (in-plane weight). **Owed: user listening gate.** |
| A6 | — | — | — |
| A2 | **COMPLETE** 2026-08-08 | `e42b9948` | 118 Core audio tests (mixer + voice pool + cookbook); full Release suite 11,639 passed / 4 skipped / 0 failed. Opus review run and applied — 2 HIGH (pan-law saturation, stale `FUN_00550ad0` header), 5 MEDIUM (untested clamp order / pan truncation / voice pool, dead `PlayingGain`, duplicated heading helper), 5 LOW. Retires AP-28; files AP-173, AP-174, TS-64, TS-65. **Owed: user listening gate.** |
| A3 | **COMPLETE** 2026-08-08 | `8bc458fb` | 14 wire-conformance tests + 5 controller tests; full Release suite 11,658 passed / 4 skipped / 0 failed. **Owed: connected gate** (melee hit / pickup / lifestone audible against ACE). |
| A4 | **COMPLETE** 2026-08-08 | `6eaa490b` | UI bank DID resolved from the dats (`0x2000004B`, content-verified: exactly the 32 `UI_*` slots) + 21-case environ table, 30 new Core tests; full Release suite 11,691 passed / 4 skipped. Retires TS-54; narrows AP-115 to notice-only. **Owed: connected gate** (`@environs` thunder + recall cues audible). **Suite note:** two load-dependent measurement flakes were observed on separate full-suite runs (`RuntimeCollisionReportingStateTests.WarmedSteadyContactRefreshDoesNotAllocate`, and one unnamed Core.Net test); both pass in isolation and neither touches audio. |
| A5 | **COMPLETE** 2026-08-08 | `7c4dd1ad` | 46 ambient conformance tests; full Release suite 11,739 passed / 4 skipped. Opus review run and applied — it caught a FATAL frame bug (cell offsets built in absolute world coordinates while the listener is in the streamed frame: every contribution culled at ~32 km, feature silent with no error), a per-entry vs per-cell denominator error that would have pushed multi-entry beds under the audibility floor, an infinite loop on a zero play-rate, and newly-audible ambients not firing until a full period later. Also moved beds onto retail's single 16-voice priority pool and made the in-block direction test XY-only. Retires TS-29; files TS-66 (`seen_outside` interiors), TS-67 (in-plane weight). **Owed: user listening gate.** |
| A6 | **COMPLETE** 2026-08-08 | `1b083463` | Full Release suite 11,739 passed / 4 skipped. Deleted the music API (`PlayMusic`/`StopMusic`/`MusicVolume` + the `AudioSettings.Music` knob); exposed the Ambient slider now that A5 drives it; reset the invented 0.8 ambient default to retail's 1.0; SUPERSEDED banner on `r05-audio-sound.md` listing its five wrong sections; TS-9 re-scoped to the measured one-wave blast radius. **Deferred:** #321's decode-dedup race (a pre-existing concurrency flake, not audio-parity behaviour — not fixed speculatively without reproducing it). |
---
## CAMPAIGN CLOSEOUT (code-complete 2026-08-08)
Six slices, six commits, `c69b3bde` → A6. The full Release suite ends at
**11,739 passed / 4 skipped / 0 failed**, up from 11,563 at campaign start;
the audio subsystem went from 42 tests (one of which pinned the wrong model)
to ~215 conformance tests written against byte-decoded values.
**What was wrong, and is now right:**
| Was | Now |
|---|---|
| Probability treated as a selection weight, and skipped entirely for the 4,183/4,184 single-entry sounds | Retail's uniform `(n1)` pick plus an independent Bernoulli silence gate |
| OpenAL 3-D spatialization, `2/d` falloff, no cutoff | Retail's CPU 2-D model: `25·vol/d²` past a 5 m knee, 50 dB no-allocate floor (~94 m), ±15 dB sine pan with a 5 m dead zone |
| Voices evicted by gain | Evicted by DAT-authored float priority, strictly-less, ring order |
| `0xF750` unparsed — every server cue silent | Parsed, guid-queued-and-replayed, played at the wire volume |
| No interface sound bank | Bank DID resolved from the dats' EnumIDMap chain; portal cues + 21 AdminEnvirons stingers live |
| No ambient system at all | Region-authored, per-land-cell, 3×3 ring, deadline-queue one-shots with terrain crossfade |
| A music API | Deleted — retail has no music system |
**Process notes worth carrying forward:**
1. **Binary Ninja could not be trusted anywhere in this subsystem.** Five
float compares render inverted or with elided constants; `GetAttenuation`
prints `* 0f`, which ports as silence at every distance. Every load-bearing
value here came from byte-decoding the PDB-paired binary. The lane notes
record the verified values; a future reader should decode rather than
re-read the pseudo-C.
2. **Two research notes were wrong and were corrected in place** — lane 1's
30 m decibel row (contradicted its own gain column) and lane 5's
transposed `GetByEnum` arguments (which would have made the UI bank
unresolvable). Both were caught by recomputing rather than copying.
3. **The reviews earned their cost.** A2's review caught a pan mapping that
saturated to full separation where retail gives 15 dB. A5's caught a
coordinate-frame error that would have made the entire ambient system
silent with nothing logged — the tests passed, the build was green, and it
would have failed only at the listening gate.
4. **An architecture guard caught a design error the tests could not**:
`ExtractedUpdateOwners_DoNotRetainAnonymousCallbacks` rejected an
`Action<float>` frame hook and forced the typed `IAmbientFramePhase`.
**Still owed:** the user listening gate (A2 falloff, A4 cues, A5 ambients) and
the connected gates for A3/A4. Open rows: AP-173, AP-174, TS-64, TS-65, TS-66,
TS-67, TS-9 (re-scoped), #321.