mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 18:25:35 -04:00
ffc15219fd
Root cause (freeze dump 1781046069, addr2line-verified): the wall-clock pacing loop in sdl_vblank_present read the performance counter twice per iteration — once in the while-condition, once to compute remaining time. If the frame deadline was crossed between the two reads (a preemption window), the unsigned subtraction underflowed, *1000 overflowed, and SDL_Delay received ~2^64/10^4 ms mod 2^32 ≈ 24.7 days at the standard 10 MHz QPC. The app parks in one SDL_Delay with every counter frozen, wedged between 'i_stat |= VBLANK' (interrupts.c:286) and exception delivery — which is why dumps showed i_stat=1 pinned. VBLANK was being ACK'd every frame right up to the wedge in every dump examined; the 'VBLANK-ACK reentry storm' framing was a symptom misread. - frame_pacing.c/h: shared race-free pacer (single counter read per decision, compare-before-subtract, sleep clamped to one period). Both call sites switched: main.cpp sdl_vblank_present and beetle_main.cpp. Unit test pins the underflow case. - Vsync self-heal: the 'recovered storm' dump (1781045865) was 8/8 main-thread samples inside wglSwapBuffers — NV driver blocking ~1.5s per present. After 3 presents >250ms, drop driver vsync (we pace to 59.94 Hz ourselves). Counters surfaced in the freeze dump. - Watchdog classifier: 'reentry_storm' threshold (5K/sec) was below the healthy baseline (~2K reentry blocks/frame × 30-60 fps), mislabeling normal play and host stalls — that label misdirected two prior investigations. Now normalized per frame (>20K/frame), plus a new slow_frames kind (<5 fps) with multi-sample stack capture. Verified: test_frame_pacing ALL PASS; live Tomba paced at 59.962 fps over 20s (single-process measurement). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>