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,10 +1,17 @@
namespace AcDream.UI.Abstractions.Panels.Settings;
/// <summary>
/// Audio mixer preferences persisted to <c>settings.json</c>. Drives the
/// existing Phase E.2 OpenAL engine — the host wires these values into
/// <c>OpenAlAudioEngine.MasterVolume</c> / <c>SfxVolume</c> /
/// <c>MusicVolume</c> / <c>AmbientVolume</c> on Save and on startup.
/// Audio mixer preferences persisted to <c>settings.json</c>. The host wires
/// these into <c>OpenAlAudioEngine.MasterVolume</c> / <c>SfxVolume</c> /
/// <c>AmbientVolume</c> on Save and on startup.
///
/// <para>
/// A <c>Music</c> field lived here until 2026-08-08 (Campaign A slice A6).
/// Retail has no music system to turn down — see the note on
/// <c>IAudioEngine</c> — so the knob moved nothing. Old <c>settings.json</c>
/// files carrying a <c>"music"</c> key still load: the reader simply ignores
/// keys it does not know, and the next save drops it.
/// </para>
///
/// <para>
/// Defaults match the engine's hard-coded starting values so a user
@ -14,15 +21,16 @@ namespace AcDream.UI.Abstractions.Panels.Settings;
/// </summary>
public sealed record AudioSettings(
float Master,
float Music,
float Sfx,
float Ambient)
{
/// <summary>Values used on first launch. Mirror the engine's
/// constructor-default Volume properties.</summary>
/// <summary>
/// Values used on first launch. Retail's own defaults are 1.0 for every
/// sound preference (<c>SoundManager::InitPrefs</c> @ <c>0x005503F0</c>),
/// so ambient starts at unity rather than the invented 0.8.
/// </summary>
public static AudioSettings Default { get; } = new(
Master: 1.0f,
Music: 0.7f,
Sfx: 1.0f,
Ambient: 0.8f);
Ambient: 1.0f);
}

View file

@ -253,15 +253,15 @@ public sealed class SettingsPanel : IPanel
}
/// <summary>
/// Render the Audio tab — Master + SFX volume sliders (live preview
/// against the running OpenAL engine). Music + Ambient fields exist
/// in <see cref="AudioSettings"/> and persist round-trip, but their
/// sliders are intentionally hidden here because the underlying
/// engine paths (PlayMusic / StartAmbient) are stubbed for R5 MIDI
/// playback that hasn't shipped yet — exposing the sliders would be
/// "moving a knob that does nothing." When R5 lands, restore the
/// hidden sliders below and the JSON-persisted values will already
/// be in place.
/// Render the Audio tab — Master, SFX and Ambient volume sliders, all with
/// live preview against the running engine.
///
/// <para>
/// Ambient was hidden until Campaign A slice A5 because nothing drove it;
/// the region ambient soundscape now does, so the knob moves something and
/// is exposed. The Music slider is gone entirely: retail has no music
/// system, so there was never anything for it to turn down.
/// </para>
/// </summary>
private void RenderAudioTab(IPanelRenderer renderer)
{
@ -275,16 +275,9 @@ public sealed class SettingsPanel : IPanel
if (renderer.SliderFloat("SFX", ref sfx, 0f, 1f))
_vm.SetAudio(a with { Sfx = sfx });
// Music + Ambient hidden until R5 MIDI / ambient-loop engines
// exist. AudioSettings still carries the fields so the JSON
// round-trips and a future client doesn't drop them on save.
//
// float music = a.Music;
// if (renderer.SliderFloat("Music", ref music, 0f, 1f))
// _vm.SetAudio(a with { Music = music });
// float ambient = a.Ambient;
// if (renderer.SliderFloat("Ambient", ref ambient, 0f, 1f))
// _vm.SetAudio(a with { Ambient = ambient });
float ambient = a.Ambient;
if (renderer.SliderFloat("Ambient", ref ambient, 0f, 1f))
_vm.SetAudio(a with { Ambient = ambient });
renderer.Spacing();
renderer.TextWrapped(

View file

@ -108,7 +108,6 @@ public sealed class SettingsStore
var d = AudioSettings.Default;
return new AudioSettings(
Master: ReadFloat(audio, "master", d.Master),
Music: ReadFloat(audio, "music", d.Music),
Sfx: ReadFloat(audio, "sfx", d.Sfx),
Ambient: ReadFloat(audio, "ambient", d.Ambient));
}
@ -588,7 +587,6 @@ public sealed class SettingsStore
{
["ambient"] = a.Ambient,
["master"] = a.Master,
["music"] = a.Music,
["sfx"] = a.Sfx,
};