D322: periodic 'garbled' bursts are the host audio device, not the port

A no-game SDL drain monitor (new tools_pc/sdl_drain_monitor.c, opens SDL
exactly like port/src/audio.c) shows the maintainer's Focusrite USB interface
draining at 88-95% for ~15 s every ~312 s, matching the in-game overflow
bursts' period. Level loads, combat, the SDL pull pattern and a
pacemaker-vs-vsync beat were ruled out. Remaining D322 items: the 8-voice SFX
cap (D207/D241) and #87's progressive decay. porting-notes: measure the host
device before chasing periodic audio glitches.
This commit is contained in:
James Dansereau committed 2026-09-25 19:15:22 -04:00
1 parent 82548a0fe9
commit 2a20a93318
4 files changed
+52 -2

No files matched your search

+1 -1
View File
@@ -200,7 +200,7 @@ D318,OPEN,Facility (regular play path):,"OPEN — re-classified 2026-09-23 by D3
D319,FIXED,"Frame cap slider was mostly a dead zone; replaced with a plain 30/60 toggle (user ask, 2026-09-18).",FIXED — new ROW_FPSCAP cycle (30/60 only) in port/src/optionsoverlay.c; default changed to 60 in port/src/video.c.
D320,OPEN,"D318-class sweep: aim-hold → _update AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).","OPEN — re-classified 2026-09-23 by D329 (tick-granularity divergence at 1 tick/frame, not a platform-invariant lock); fix decision pending, watchdog stays shipped."
D321,OPEN,D318 trigger chain fully mapped:,"OPEN — re-classified 2026-09-23 by D329 (tick-granularity divergence at 1 tick/frame, not a platform-invariant lock); fix decision pending, watchdog stays shipped."
D322,OPEN,Long-session audio degradation:,OPEN — 2026-09-25 capture:
D322,OPEN,Long-session audio degradation:,OPEN — 2026-09-25: pools healthy (voice-leak hypothesis REFUTED).
D323,OPEN,G_TL_LOD bindings of explicit-LOD textures:,"OPEN / parked — mechanism confirmed in code; census (tools_pc/bg_lod_audit.py) shows 15 levels with explicit-LOD textures bound under TEXTLOD-on classes (stat 378, azt 806 binds heaviest); overwhelmi…"
D324,FIXED,"GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes —",FIXED (2026-09-22) — proven impact:
D325,CLOSED,ROM-mod (xdelta) compatibility:,CLOSED as a question —
1 # GENERATED by tools_pc/gen_findings_index.py -- do not hand-edit.
200 D319
201 D320
202 D321
203 D322
204 D323
205 D324
206 D325
+8 -1
View File
@@ -608,7 +608,7 @@ covers D24–D69; the log continues in §H (D32 procedure, D70–D121).
| D319 | **Frame cap slider was mostly a dead zone; replaced with a plain 30/60 toggle (user ask, 2026-09-18).** — full `## D319` entry at file tail | FIXED — new `ROW_FPSCAP` cycle (30/60 only) in `port/src/optionsoverlay.c`; default changed to 60 in `port/src/video.c`. Root cause was D186 (sim tick ceiling + low-cap guard); this just fixes the menu's exposure of it. Build-verified; interactive confirmation owed. |
| D320 | **D318-class sweep: aim-hold → `_update` AI lists in 8 levels carry the same as-authored softlock race (static analysis, 2026-09-20).** — full `## D320` entry at file tail | OPEN — re-classified 2026-09-23 by D329 (tick-granularity divergence at 1 tick/frame, not a platform-invariant lock); fix decision pending, watchdog stays shipped. Earlier resolution text kept in the full entry. |
| D321 | **D318 trigger chain fully mapped: tanks → combat bit is an authored ~3.5 s gas-cascade delay (chr 254 script); derail lands same-tick; PC behavior confirmed faithful to N64 (probe captures, 2026-09-20).** — full `## D321` entry at file tail | OPEN — re-classified 2026-09-23 by D329 (tick-granularity divergence at 1 tick/frame, not a platform-invariant lock); fix decision pending, watchdog stays shipped. Earlier resolution text kept in the full entry. |
| D322 | **Long-session audio degradation: full campaign on v0.3.0 — audio progressively worsens from Silo, by Caverns/Cradle the OST is inaudible and SFX "come and go"; restarting the game restores it (issue #87, user report, 2026-09-20).** — full `## D322` entry at file tail | OPEN — 2026-09-25 capture: pools healthy (no leak; voice-leak hypothesis REFUTED for a 46-min session). Two mechanisms: (a) the 8-voice SFX soft cap saturates in firefights (= D207/D241, N64-design cap); (b) NEW: periodic SDL output-queue overflow bursts every ~314 s (±10 s), ~45 s each, independent of level, VI rate and production rate; reproduces from t=30 s at the title, so it is headless-reproducible. |
| D322 | **Long-session audio degradation: full campaign on v0.3.0 — audio progressively worsens from Silo, by Caverns/Cradle the OST is inaudible and SFX "come and go"; restarting the game restores it (issue #87, user report, 2026-09-20).** — full `## D322` entry at file tail | OPEN — 2026-09-25: pools healthy (voice-leak hypothesis REFUTED). The maintainer-machine "garbled" bursts (periodic SDL queue overflow, ~314 s) are the HOST AUDIO DEVICE, not the port: a no-game SDL drain monitor (`tools_pc/sdl_drain_monitor.c`) shows the Focusrite USB interface draining at 88–95 % for ~15 s every ~312 s. Remaining port-side item: SFX drop-outs in firefights = 8-voice soft cap (D207/D241). #87's progressive campaign-long decay is not reproduced here. |
| D323 | **G_TL_LOD bindings of explicit-LOD textures: PC samples driver mips from LOD0, N64 samples the authored mip chain (static census, 2026-09-21).** — full `## D323` entry at file tail | OPEN / parked — mechanism confirmed in code; census (`tools_pc/bg_lod_audit.py`) shows 15 levels with explicit-LOD textures bound under TEXTLOD-on classes (stat 378, azt 806 binds heaviest); overwhelmingly CI8/IA8 (subtle), the only RGBA5551 cut-outs are the D236 set (already fixed). No reported artifact; fix path documented (upload GL mip chains for TEXTLOD-on bindings) but not worth doing speculatively. Also documents GE's OMH field-write encoding (op 0xBA, value at absolute bit position in w1). |
| D324 | **GE's <stdlib.h> stub declares nothing, so TUs that include it get NO CRT prototypes — implicit `int f()` declarations silently corrupt non-int returns: `atof` garbage in every float ini parse (D38 class).** — full `## D324` entry at file tail | FIXED (2026-09-22) — proven impact: `Game.ScreenShakeIntensity` (the only float ini option) silently parsed to garbage whenever a user set it; caught via the D294 `GE_ROOMPOOL` hook. Fix: `port/shim/stdlib.h` now declares atof/strtod/strtol/atexit/getenv unconditionally (K&R form); pc_protos.h gains atof/strtod for game TUs; glass2.c PORT block uses `__builtin_memset`. Residual harmless implicit decls (atoi/floorf/sqrtf/memset builtins, lround) documented. |
| D325 | **ROM-mod (xdelta) compatibility: relocating/total-conversion mods cannot work while the file table is compiled in; in-place data-only mods are one sidecar-staleness fix away (Goldfinger 64 probe, 2026-09-22).** — full `## D325` entry at file tail | CLOSED as a question — mechanism measured (GF64 relocates 797/803 vanilla file slices; 12→24 MB expanded ROM; `d43_emit` dies on a vanilla-offset zlib slice). ASM mods never work; relocating mods need a runtime-parsed file table (rule-2 collision for added levels); in-place data-only mods need only a ROM-identity stamp in `manifest.csv` (`port/src/romconvert.c`). |
@@ -12702,6 +12702,13 @@ Full campaign (or at minimum Silo → Caverns/Cradle) with **`GE_D322=1 GE_D204=
- **(b) NEW: periodic output-queue overflow ("garbled").** 1,244 blocks (~33 ms each) dropped at `audioSetNextBuffer` (`port/src/audio.c`, `q >= queueLimit` 2880) in **10 bursts starting at t = 30, 356, 672, 984, 1295, 1612, 1923, 2229, 2546 s**: period **~314 s** (306–326), each ~20–50 s, drops per 5 s ramping up (4 → 7 → … → 20) then stopping abruptly with `q` collapsing (e.g. 2576 → 624). Across bursts: VI steady at 60/s; produced-audio rate (`rt`, which counts dropped blocks too) ~0.99 throughout. So the queue sits near the limit while production is *not* above nominal, which implies the SDL device drains more slowly during a burst (or the regulator's floor exceeds the drain rate). It happens on every level and **already at t=30 s (title/menus)**, so it is **not gameplay load** and should reproduce headless.
- **Next (cheap):** headless 6–7 min run at the title with `GE_D204=1` (expect bursts at ~30 s and ~345 s); add per-block timestamps / SDL queued-bytes deltas to measure drain rate inside vs outside a burst; check the regulator floor (`g_MinFrameSize` × audio ticks/s vs 22050) and the `AUDIO_TARGET_FRAMES` setpoint (D204/F5) for a beat against the 60 Hz VI timer. A ~314 s period at a 2000-frame swing corresponds to a ~6 frames/s (~290 ppm) mismatch between the clocks.
**2026-09-25 (later): the periodic overflow bursts are the host audio device, not the port.**
- **Ruled out, cheaply:** the SDL pull pattern (512 frames every 20–30 ms, steady); level loads and combat (bursts hit with 0 SFX playing and no load stall nearby, and are spaced 306–326 s over 50 min of varied play); a pacemaker-vs-vsync beat (the VI pacemaker's integer `1000000/60` = 16,666 µs = 60.0024 Hz predicts ~417 s against the 60/1 Hz monitor; but a 3-min run with the pacemaker nudged to a ~69 s beat, via a TEMP `GE_VI_US` override, showed no periodic bursts). The in-game capture's arithmetic already showed the drain side falling behind: during bursts production fell to 0.97× (the regulator at its 720-frame floor) while 12–15 % of blocks were dropped and the queue stayed near the limit, so SDL consumed only ~85 %.
- **Decisive:** `tools_pc/sdl_drain_monitor.c` (SDL opened exactly like `port/src/audio.c`, silence, no game, no window, 11 min) on the maintainer's **Focusrite USB Audio** (WASAPI) showed drain dips of **88–95 % for t=76–89 s and 93 % at t=388–390 s: 312 s apart**, the same period as the in-game bursts, with a long-run average that is exact (22,005.7 frames/s; SDL 2.32's 22050→48k path drains at ~22,002). The device/driver/audio engine periodically falls behind, and during those windows our 2880-frame queue overflows and drops 33 ms blocks. A TEMP cadence probe (`GE_D322T`) also caught a queue jump with perfectly regular submissions.
- **Port impact:** none to fix for this mechanism. A bigger `Audio.QueueLimit` would only trade drops for up to ~2 s of latency during a dip. Suggested user-side checks: Focusrite Control sample rate = the Windows device format; driver update; A/B on onboard audio.
- **Still open under D322:** (a) the 8-voice SFX cap starving gunshots in firefights (D207/D241, N64-configured cap: is it faithful to the N64's audible behaviour?); (b) #87's own report of *progressive* campaign-long decay, which this machine does not show. The reporter's hardware may have its own periodic device dips; the monitor tool is the first thing to run there.
## D323 — G_TL_LOD bindings of explicit-LOD textures: PC samples driver mips from LOD0; N64 samples the authored mip chain (static census, 2026-09-21)
**Context.** D107 established that GE's room DLs emit `G_TL_LOD` and that the port folds every LOD draw to tile 0 (driver-generated GL mips from the finest level). D236 then found + fixed the one case where that folding was *visibly* wrong for a type-1 (DETAIL) binding of an RGBA5551 cut-out. This entry closes the loop on the rest of the surface: how often do LOD-on draw classes bind textures that actually carry **authored** mip levels, and does the N64 hardware behavior differ from the port's?
+7
View File
@@ -1244,6 +1244,13 @@ GE on N64 renders gameplay at ~20–30 fps, so `g_ClockTimer` (= `speedgraphfram
## E. Process / method notes
- **Periodic audio glitches: measure the host audio device before touching
the pipeline (D322).** A clock-regular overflow/garble pattern (~5 min period,
~45 s bursts, independent of level and load) looked like a port timing beat
and cost several game runs to chase. `tools_pc/sdl_drain_monitor.c` opens SDL
exactly like `port/src/audio.c` with no game and no window; on the
maintainer's Focusrite USB interface it showed the same ~312 s drain dips
(to 88 %) outright. Run it first on any "audio gets garbled" report.
- Investigation loop is: reproduce → env-gated capped probe → root-cause
→ narrow `#ifdef PORT` fix → visual verify (`GE_PCDUMP` +
`tools_pc/pixcount.py` vs `docs/reference/n64-footage-*`).
+36
View File
@@ -0,0 +1,36 @@
/* sdl_drain_monitor: is the host audio device draining at real time?
*
* Opens SDL queued audio exactly like port/src/audio.c (22050 Hz, S16,
* stereo, 512 samples), keeps the queue deep with silence, and measures the
* drain rate per 1 s window against the performance counter. No game, no
* window. Windows of +-2.3% are one 512-frame pull landing either side of a
* window edge (granularity, ignore); a sustained run of lower values means the
* device/driver/audio engine is falling behind, and the game's audio queue
* will overflow (D322: a Focusrite USB interface dipped to 88% for ~15 s
* every ~312 s, the whole "garbled audio" signature, with no game running).
*
* Build (MSYS2 MINGW64):
* gcc -O2 tools_pc/sdl_drain_monitor.c -o sdl_drain_monitor.exe * $(sdl2-config --cflags --libs | sed 's/-mwindows//') -lwinmm
* Run: ./sdl_drain_monitor.exe [seconds, default 660]
*/
#include <SDL2/SDL.h>
#include <stdio.h>
#include <windows.h>
static SDL_AudioDeviceID d; static volatile long long pushed=0; static volatile int run=1;
static int prod(void*a){(void)a; static Sint16 b[735*2];
while(run){ if(SDL_GetQueuedAudioSize(d)/4<8000){SDL_QueueAudio(d,b,735*4); pushed+=735;} SDL_Delay(5);} return 0;}
int main(int c,char**v){ int secs=c>1?atoi(v[1]):660; timeBeginPeriod(1); SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec w={0},h; w.freq=22050;w.format=AUDIO_S16SYS;w.channels=2;w.samples=512;
d=SDL_OpenAudioDevice(NULL,0,&w,&h,0);
printf("device=%s driver=%s\n",SDL_GetAudioDeviceName(0,0),SDL_GetCurrentAudioDriver()); fflush(stdout);
SDL_PauseAudioDevice(d,0); SDL_Thread*t=SDL_CreateThread(prod,"p",0); SDL_Delay(1000);
Uint64 f=SDL_GetPerformanceFrequency(), t0=SDL_GetPerformanceCounter(), tp=t0;
long long p0=pushed; Uint32 q0=SDL_GetQueuedAudioSize(d)/4; double mn=1e9,mx=0,sum=0; int n=0,bad=0;
for(int s=1;s<=secs;s++){ SDL_Delay(1000);
Uint64 now=SDL_GetPerformanceCounter(); long long p=pushed; Uint32 q=SDL_GetQueuedAudioSize(d)/4;
double el=(double)(now-tp)/f, rate=((double)(p-p0)+(double)q0-(double)q)/el;
if(rate<mn)mn=rate; if(rate>mx)mx=rate; sum+=rate;n++;
if(rate<22002*0.98||rate>22002*1.02){bad++; printf("t=%ds drain=%.0f frames/s (%.1f%%) q=%u\n",s,rate,100*rate/22002,q); fflush(stdout);}
tp=now;p0=p;q0=q; }
printf("done %ds: avg=%.1f min=%.0f max=%.0f anomalous_windows=%d\n",n,sum/n,mn,mx,bad);
run=0; SDL_WaitThread(t,0); return 0;}