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:
parent
7c4dd1ade7
commit
dd2cb92b99
13 changed files with 152 additions and 65 deletions
|
|
@ -341,7 +341,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| TS-6 | Weather particle emission suppressed — all weathery DayGroups map to Overcast (correct fog/cloud tone, no precipitation); retail's camera-attached weather subsystem not yet located in the decomp | `src/AcDream.Core/World/WeatherState.cs:200` | Decomp research verified the sky loop never reads `DefaultPesObjectId`; an earlier name-based rain spawn regressed (rained where retail didn't, 2026-04-23) — inventing a name→rain path is forbidden until the real subsystem is found | Rainy/snowy/stormy days never show retail's precipitation effects (permanent missing visuals until the subsystem is found and ported) | FUN_00508010 / FUN_0051bed0→FUN_0051bfb0 (negative findings) |
|
||||
| TS-7 | SkyObject `weather_enabled` gate not honored — weather-flagged sky objects (bit 0x04) always instantiate | `src/AcDream.Core/World/SkyDescLoader.cs:50` | No weather_enabled toggle exists yet; IsWeather flag parsed + documented as the gate to wire | Weather-only sky meshes (rain cylinders) appear where retail-with-weather-off suppresses them | `GameSky::MakeObject` 0x00506ee0, guard at decomp:268630 |
|
||||
| ~~TS-8~~ | **RETIRED 2026-07-31 (#268 stat-chain closeout).** `EnchantmentWireReader` parses the complete 0x02C2 payload and `GameEventWiring` publishes its StatMod type/key/value and bucket through the same `ActiveEnchantmentRecord` used at login. An end-to-end dispatch test proves a mid-session skill modifier changes `LocalPlayerState.GetEffectiveSkill` immediately. | `src/AcDream.Core.Net/Messages/EnchantmentWireReader.cs`; `src/AcDream.Core.Net/GameEventWiring.cs`; `tests/AcDream.Core.Net.Tests/GameEventWiringTests.cs` | — | — | `CEnchantmentRegistry::EnchantAttribute @ 0x00594570`; `CEnchantmentRegistry::EnchantSkill @ 0x005947B0`; holtburger `messages/magic/types.rs` |
|
||||
| TS-9 | MP3 (0x55) and MS-ADPCM (0x02) waves undecoded — affected sounds skipped; retail decoded both via winmm ACM | `src/AcDream.Core/Audio/WaveDecoder.cs:33` | Managed decoder (NAudio or similar) deferred; PCM covers the vast majority of ~3500 waves | Any MP3 (common for music-ish clips) or ADPCM cue plays as silence where retail plays it | winmm ACM path (r05 §2.1) |
|
||||
| TS-9 | **RE-SCOPED 2026-08-08 (Campaign A slice A6) — the blast radius is one wave, measured.** MP3 (0x55) and MS-ADPCM (0x02) waves still decode to null and play as silence where retail decoded both through the winmm ACM. What changed is the size of the problem: an independent walk of the shipped dats found exactly **1 MP3 among 786 waves** (`0x0A000393`, a ~2 s mono clip) and the row's original "any MP3 cue, common for music-ish clips" framing was wrong — it was written when we believed retail had a music system, and retail has none. A managed decoder for one two-second asset is not worth the dependency; the honest options are a ~50-line decode or an accepted-loss row, and this row is now the accepted-loss record. The ADPCM count has NOT been measured and is the one open question here. | `src/AcDream.Core/Audio/WaveDecoder.cs:33` | Measured rather than assumed. PCM covers 785 of 786 waves. | One ~2 s clip is silent, plus an unmeasured number of ADPCM clips. | winmm ACM path; dat census in `docs/research/2026-08-08-audio-retail-dat-layer.md` §4c |
|
||||
| TS-14 | Setup `Flatten` ignores ParentIndex part hierarchy (treats every placement as root-local); still in production use (GameWindow hydration, SkyRenderer) | `src/AcDream.Core/Meshing/SetupMesh.cs:15` | Most Setups are flat single-level rigs where root-local equals composed; hierarchical composition deferred ("Phase 3") | Any Setup with genuinely nested parts renders them at wrong offsets — mis-assembled multi-part objects in the Flatten paths | retail Setup ParentIndex chain composition |
|
||||
| TS-15 | No distance-driven degrade (LOD): always close-detail slot 0; plus the **#47** static `Degrades[0]` swap for 34-part humanoids only (structural sentinel detector) | `src/AcDream.Core/Meshing/GfxObjDegradeResolver.cs:57` (+ `src/AcDream.App/Rendering/GameWindow.cs:2608`) | LOD plumbing doesn't exist; slot 0 is correct for player + nearby NPCs; #47 closed the visible low-detail-arms bug without porting UpdateViewerDistance | Distant objects render max-detail (perf + wrong visuals where far meshes intentionally differ/hide parts); a future 34-part non-humanoid matching the sentinel gets the wrong mesh swap | `CPhysicsPart::UpdateViewerDistance` 0x0050E030; ::Draw 0x0050D7A0; ::LoadGfxObjArray 0x0050DCF0 |
|
||||
| TS-17 | AttackConditions suffix always empty in combat chat — formatting ported, wire bitflag not plumbed (Phase I.7 follow-up) | `src/AcDream.Core/Chat/CombatChatTranslator.cs:233` | Only the wire plumbing is missing; the holtburger-ported formatter is ready | Combat log omits "[Sneak Attack]"-style suffixes retail displays — hidden combat-mechanic feedback | holtburger chat.rs:588-595 |
|
||||
|
|
|
|||
|
|
@ -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 `(n−1)` 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.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,35 @@
|
|||
# R5 — Audio System Deep Dive
|
||||
|
||||
> **SUPERSEDED 2026-08-08 (Campaign A).** This document predates the named
|
||||
> retail decomp and the byte-level verification pass; several of its
|
||||
> load-bearing sections are wrong and were used to build code that had to be
|
||||
> undone. Read the six lane notes instead —
|
||||
> `docs/research/2026-08-08-audio-retail-*.md` — and
|
||||
> `docs/plans/2026-08-08-audio-parity-campaign.md` for the summary.
|
||||
>
|
||||
> Specifically corrected:
|
||||
>
|
||||
> - **§5.1 falloff.** The `FUN_00550c30` reading is not retail's curve.
|
||||
> `SoundManager::GetAttenuation` @ `0x00550020` is
|
||||
> `g = dist < 5 ? vol : 25·vol/dist²`, clamped BEFORE one master multiply,
|
||||
> then `ceil(20·log10 g)` with a hard −50 dB no-allocate floor (audible
|
||||
> radius ≈94.2 m). Binary Ninja elides the x87 constants and prints `* 0f`;
|
||||
> porting its rendering yields silence at every distance.
|
||||
> - **§5.2 pan / §5.3 pool.** Pan is `(int)(−15·sin Δbearing)`, truncating
|
||||
> toward zero, dead centre inside 5 m, with no front/back and no elevation.
|
||||
> The voice allocator is `PlaySoundInternal` @ `0x0054FEC0` and it evicts on
|
||||
> DAT-authored float PRIORITY, never gain; `FUN_00550AD0` is a hash-table
|
||||
> constructor.
|
||||
> - **§4 selection.** Probability is a Bernoulli play/skip GATE at the play
|
||||
> site, not a selection weight; selection is a uniform index over `(n−1)`.
|
||||
> - **§6 music.** There is no music system in retail EoR. The linked MIDI
|
||||
> player has zero callers. The `PlayMusic` API this section inspired modelled
|
||||
> dead code and was deleted at slice A6.
|
||||
> - **§7 ambient.** Ambients are region-authored, per-LAND-CELL over a 3×3
|
||||
> landblock ring, and play as re-fired one-shots off an absolute-deadline
|
||||
> queue — not landblock-attached loops. Indoors is silent by design.
|
||||
|
||||
|
||||
Ground-truth port plan for acdream's audio subsystem, derived from decompiled
|
||||
`acclient.exe`, cross-referenced against ACE (server), ACViewer (dat loader),
|
||||
holtburger (client protocol), AC2D (C++ client), and DatReaderWriter (dat
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue