Audio-dropout fix: OpenAL source-pool lifecycle (uninit aliasing, atomic-delete leak, stranded partials)

Field report: audio cutting in and out toward the end of the match.
Three structural defects in the port's source-pool lifecycle:

1. SourceSet.sources[] was uninitialized heap garbage until first
   acquisition -- AL names are small ints, so a recycled-heap slot could
   alias a LIVE source owned by another sound; alIsSource() skipped
   generation, two sounds shared one source, and whichever released
   first deleted the other's mid-play.  SourceSet ctor now zero-inits.
2. ReleaseSourceSet's bulk alDeleteSources(count, sources) is ATOMIC on
   invalid names (AL spec) -- one empty/-1 slot and NOTHING deleted;
   after the first pool exhaustion every partial release leaked and the
   pool never recovered.  Now per-slot guarded delete, slots parked at 0.
3. Failed acquisitions stranded partial sets forever (dropped transients
   are never released by anything).  ReleaseChannels() handback at both
   failure sites (StartRequest + dormant-resume) + a dtor backstop.

Verified: 2-node MP smoke (mission + lobby-loop relaunch clean, census
live/acquireFails 0/0) + 100s solo combat smoke (52 audio triggers,
zero AL errors).  Awaiting live playtest confirmation at scale.
KB: context/wintesla-port.md audio-dropout section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-23 11:23:07 -05:00
co-authored by Claude Opus 4.8
parent cbebb0f1c1
commit a999e5c49f
4 changed files with 73 additions and 12 deletions
+21
View File
@@ -144,6 +144,27 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`.
watcher timing encore-erratic that session. Harness kept (env-gated, additive): `[aud-tail]`
logging in `L4AUDLVL.cpp` (PLAY/STOP/fade) + `emitter.cpp` (state transitions), and
`BT_FIRE_PULSE="on,off"` single-shot trigger pulsing in `mech4.cpp`.
- **Audio-dropout fix (field 2026-07-23: "cutting in and out toward the end of the match")** [T2,
code-verified vs the AL spec; awaiting live playtest confirm]: three structural defects in the
port's OpenAL source-pool lifecycle (`L4AUDRND.cpp` / `L4AUDIO.cpp` / `L4AUDHDW.h`):
(1) `SourceSet.sources[]` was **uninitialized heap garbage** until first acquisition — AL names
are small ints, so a recycled-heap slot could alias a LIVE source owned by another sound;
`alIsSource()` then skipped generation, two sounds shared one source, and whichever released
first deleted the other's source mid-play (random sound deaths worsening with heap churn).
Fix: `SourceSet` ctor zero-inits (0 is never a valid name). (2) `ReleaseSourceSet` used bulk
`alDeleteSources(count, sources)`, which is **ATOMIC on invalid names** — one empty/-1 slot
(partial set, double release) and NOTHING deleted; after the first pool exhaustion every partial
release leaked and the pool never recovered. Fix: per-slot guarded delete, slots parked at 0.
(3) Failed acquisitions stranded partial sets forever — dropped transients are never released by
anything (the dtor didn't release; maintenance only touches running sources). Fix: handback via
`ReleaseChannels()` at both failure sites (StartRequest + dormant-resume) + a dtor backstop.
Context: the port's `RequestAudioChannels` has NO channel budget (the 1995 per-card budget is
the commented-out block) — it alGenSources until OpenAL Soft's ~256 cap; on failure a transient
is silently dropped (StartRequest early-return), a sustained goes dormant — which is exactly the
"cutting in and out" presentation. Priority-steal (`RequestAudioResources`) only engages at cap.
PERMANENT diagnostics: 30 s `[audio] source census: live/acquireFails` + an always-on
`[audio] ACQUIRE FAILED` line. Baseline (2-node soak): live ≈ 21 peak under fire, 0 at mission
end; exhaustion needs 7-player scale or the leak spiral, aliasing needs neither.
- **L4 HAL on Windows + the VS build system: DONE.** [T1]
- **Red Planet game logic: COMPLETE & buildable** — `VTV/VTVMPPR/WEAPSYS/...`. RP is at/near playable. [T1]
- **Multiplayer: the DOS `NETNUB` driver is already replaced by a ~3.5k-line WinSock2 TCP