fix(audio): _delayedMix never initialized — one NRE line behind the 'known failure', the class hang, AND the log flood; loopback gain now honest

The creator-declared known failure (Mix_HonorsProviderGains) and AudioPipelineTests'
standalone hang shared ONE root cause, born in TASK 22: AudioMixer.FillAndMix
dereferenced _delayedMix (declared float[]? , never assigned) as delayed.Length —
every live-mix tick NRE'd before the pipe write, so NO audio ever reached the wire
(tests starved -> hung/fail; app -> swallowed catch logged only ex.Message, 10ms
flood). Now: ctor-allocated + null-check, catch logs WITH stack via AppLog and is
throttled 5s, and a cancelled token breaks out before logging.

Bonus contract fix: AudioGainProvider.LoopbackGain returned unity on a disproven
premise ('loopback scales with endpoint volume'). The creator's tonight observation —
volume at 20%, meter pegged — proves the WASAPI tap is pre-endpoint-volume, so the
mixer must multiply by GameAudioVolume for stream honesty (this is what ai.md always
specified; the 'failing' test encoded the same and was RIGHT).

AudioPipelineTests: 25/25 in 26ms, standalone, no hang. Known-failure count: ZERO.
ai.md tests paragraph rewritten (no more suite-total claims, both ex-'knowns'
explained); TASK 22 regression recorded; MyMistakes: the known-failure-label rules.
This commit is contained in:
2026-09-01 21:20:45 -07:00
parent 5a1a3c566a
commit 5ead064d54
5 changed files with 61 additions and 20 deletions
+8 -4
View File
@@ -28,8 +28,12 @@ public sealed class AudioGainProvider
/// <summary>Mic gain for the live mix (0 = muted).</summary>
public double MicGain() => _micMuted() ? 0 : _micVolume();
/// <summary>Loopback (desktop/game) gain for the live mix (0 = muted, 1 = unity).
/// Unity because the slider now drives system volume via PushSystemVolume,
/// so the WASAPI loopback signal is already scaled by the user's volume choice.</summary>
public double LoopbackGain() => _gameMuted() ? 0 : 1.0;
/// <summary>Loopback (desktop/game) gain for the live mix (0 = muted).
/// Honesty rule (2026-09-01): WASAPI loopback capture does NOT shrink with the
/// endpoint volume — the creator dropped system volume to 20% and the captured
/// level stayed pegged — so the mixer must multiply by GameAudioVolume itself
/// for the stream to follow the knob (the old unity assumption left the stream
/// at full desktop volume while the headphones went quiet; ai.md always
/// specified loopbackGain = GameAudioVolume — the code had drifted).</summary>
public double LoopbackGain() => _gameMuted() ? 0 : _gameVolume();
}