diff --git a/context/wintesla-port.md b/context/wintesla-port.md index 269263a..c538ae3 100644 --- a/context/wintesla-port.md +++ b/context/wintesla-port.md @@ -16,21 +16,25 @@ on top of it. Full detail: `docs/PROGRESS_LOG.md §5b, §8`. ## What WinTesla already did (do NOT rebuild) - **Renderer bypass: DONE** — `MUNGA_L4/L4D3D.cpp` + `DXUtils` replace libDPL / the IG board with Direct3D9. No `libdpl.lib` in the tree. (The early from-scratch D3D9 viewer was retired and removed.) [T1] -- **Audio SOURCE BUDGET — dropouts in heavy combat, FIXED 2026-07-28** [T2]. Field report "audio - cutting in and out toward the end of the match"; night-5 logs carried **2017 `ACQUIRE FAILED` lines in - a single match, every one at `live=256`**. It is **NOT a leak** — the census sits at a healthy 45-60 - live, spikes during firefights, and drains straight back, so the engine's priority steal loop +- **Audio SOURCE BUDGET — the dropout complaint was fixed 2026-07-23; the leftover is NOT the same + bug** [T2, 2026-07-28]. **The reported bug is closed**: `a999e5c` (per-source delete instead of the + spec-atomic bulk `alDeleteSources`) stopped the pool leaking, and there have been **no field + complaints since**. Night-5 logs still show 2017 `ACQUIRE FAILED` lines in a match, all at + `live=256` — but that is **transient exhaustion, not a leak**: the census sits at 45-60 live, spikes + during firefights, and drains straight back (226→42), i.e. the engine's priority steal loop (`AudioSourceStop`/`SuspendMaintenance` → `ReleaseChannels` → `ReleaseSourceSet` → - `alDeleteSources`) works. The cause: `L4AudioRenderer` called `alcCreateContext(device, NULL)` with - **no attribute list**, so OpenAL Soft applied its DEFAULT budget of 256 mono sources — an OpenAL - default, unrelated to the 1995 audio hardware. A busy match logs ~7200 explosions × 3 - `DPLIndependantEffect` voices each; while the pool is pinned every acquire fails and that sound is - dropped outright. FIX: pass `ALC_MONO_SOURCES` (default **1024**, `BT_AUDIO_SOURCES` overrides) and - **read back what was granted** — a request is not a promise. Measured: 1024→1024, 2048→2048, and - 64→**240** (OpenAL Soft's own floor, which is why the NULL default landed at 256). Observed bursts - peaked at 226 against the old 256 cap, i.e. they were clipping the ceiling. Note the earlier - 2026-07-23 fix (`a999e5c`, per-source delete instead of the spec-atomic bulk `alDeleteSources`) was - real and is still needed — it is what makes the pool drain; it just was not the ceiling problem. + `alDeleteSources`) is working. A dropped voice there competes with ~256 already sounding, so it is + very likely sub-perceptual — treat the log noise as a symptom to watch, **not** as the old bug. + Mechanism if it ever does matter: `alcCreateContext(device, NULL)` passes no attribute list, so + OpenAL Soft applies its DEFAULT 256 mono sources (an OpenAL default, unrelated to the 1995 audio + hardware), while a busy match logs ~7200 explosions × 3 `DPLIndependantEffect` voices. + ⚠ **Raising the cap is deliberately OPT-IN (`BT_AUDIO_SOURCES=`, no rebuild needed)** — the 256 + ceiling doubles as a **governor**. The steal loop only steals when the incoming source outranks a + running one, so a higher cap means far more voices mixing at once, and with EFX reverb + lowpass + chains live that is real CPU spent exactly during heavy combat. **Measure frame time before making + it default.** Granted values measured: 1024→1024, 2048→2048, 64→**240** (OpenAL Soft's own floor, + which is also why the NULL default lands on 256) — always read back what was granted, a request is + not a promise. - **Audio: BACKEND now REAL + soundbank loaded; only game-triggering remains** [T2, updated 2026-07-15]. OpenAL replaces the HMI "SOS" engine and the renderer→device→buffer→source→play chain IS all implemented (`L4AUDRND`/`L4AUDRES`/`L4AUDLVL`; `PatchLevelOfDetail::PlayNote` really calls diff --git a/engine/MUNGA_L4/L4AUDRND.cpp b/engine/MUNGA_L4/L4AUDRND.cpp index 386aee4..057dd28 100644 --- a/engine/MUNGA_L4/L4AUDRND.cpp +++ b/engine/MUNGA_L4/L4AUDRND.cpp @@ -395,29 +395,51 @@ void // DPLIndependantEffect voices, and while the pool is pinned every new // acquire fails outright and that sound is silently dropped. // - // Ask for a budget that fits the content. OpenAL Soft mixes in software, - // so this costs mixing headroom, not hardware voices, and it clamps to what - // it can honour -- hence the read-back below rather than an assumption. - // BT_AUDIO_SOURCES overrides for A/B testing. - ALCint monoWanted = 1024; + // ⚠ OFF BY DEFAULT, DELIBERATELY. Raising the cap is NOT a free win, and + // the reported dropout was already fixed on 2026-07-23 by a999e5c (the + // atomic-delete leak) -- no field complaints since. What remains in the + // night-5 logs is transient exhaustion at the peak of a firefight, where + // a dropped voice competes with ~256 already sounding, so it is very + // likely sub-perceptual. Against that: the 256 ceiling also acts as a + // GOVERNOR. The steal loop only steals when the incoming source outranks + // a running one, so a higher cap means many more voices mixing at once -- + // with EFX reverb + the lowpass chains live, that is real CPU, spent + // exactly during heavy combat when the frame budget is tightest. + // + // So this stays opt-in: set BT_AUDIO_SOURCES= to raise it (no rebuild + // needed) if dropouts are ever reported again, and measure frame time + // while you do. Unset = the shipped OpenAL Soft default, unchanged. + ALCint monoWanted = 0; if (const char *sv = getenv("BT_AUDIO_SOURCES")) { int n = atoi(sv); if (n >= 64 && n <= 4096) monoWanted = n; } - ALCint attrs[5]; - attrs[0] = ALC_MONO_SOURCES; attrs[1] = monoWanted; - attrs[2] = ALC_STEREO_SOURCES; attrs[3] = 16; - attrs[4] = 0; - ALCcontext *context = alcCreateContext(device, attrs); + ALCcontext *context; + if (monoWanted > 0) + { + ALCint attrs[5]; + attrs[0] = ALC_MONO_SOURCES; attrs[1] = monoWanted; + attrs[2] = ALC_STEREO_SOURCES; attrs[3] = 16; + attrs[4] = 0; + context = alcCreateContext(device, attrs); + } + else + { + context = alcCreateContext(device, NULL); // default budget (256) + } alcMakeContextCurrent(context); // Report what the driver actually GRANTED -- a request is not a promise. + // (Asking for 64 grants 240: OpenAL Soft has a floor of its own, which is + // also why the NULL default lands on 256.) + if (getenv("BT_AUDIO_LOG") || monoWanted > 0) { ALCint monoGot = 0, stereoGot = 0; alcGetIntegerv(device, ALC_MONO_SOURCES, 1, &monoGot); alcGetIntegerv(device, ALC_STEREO_SOURCES, 1, &stereoGot); - DEBUG_STREAM << "[audio] source budget: requested mono=" << monoWanted + DEBUG_STREAM << "[audio] source budget: requested mono=" + << (monoWanted > 0 ? monoWanted : 0) << " (0 = driver default)" << " granted mono=" << monoGot << " stereo=" << stereoGot << std::endl << std::flush; }