diff --git a/runtime/include/frame_pacing.h b/runtime/include/frame_pacing.h index b3c581e8..00f33322 100644 --- a/runtime/include/frame_pacing.h +++ b/runtime/include/frame_pacing.h @@ -39,10 +39,10 @@ uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline, uint64_t freq, uint64_t period); /* Block until the pacer's next deadline (sleep + final sub-ms spin), - * then advance the deadline by one period. If the pacer has not - * started, or the caller fell more than one period behind, re-anchors - * to now + period without sleeping. period_ms is the frame period in - * milliseconds (e.g. 1000.0 / 59.94). */ + * then advance the deadline by one period. If the pacer has not started, + * or the caller fell beyond the implementation's bounded transient-catch-up + * window, re-anchor to now + period without sleeping. period_ms is the frame + * period in milliseconds (e.g. 1000.0 / 59.94). */ void frame_pacer_wait(FramePacer *p, double period_ms); #ifdef __cplusplus diff --git a/runtime/src/frame_pacing.c b/runtime/src/frame_pacing.c index 7af5bab8..c37e41b2 100644 --- a/runtime/src/frame_pacing.c +++ b/runtime/src/frame_pacing.c @@ -34,7 +34,13 @@ uint32_t frame_pacing_sleep_ms(uint64_t now, uint64_t deadline, * target = -0.4% chronic audio underrun). Only debt beyond this window — * sustained sub-realtime emulation, not a hiccup — is forgiven, else the * pacer would demand unbounded catch-up. */ -#define FRAME_PACER_CATCHUP_MAX_PERIODS 8u +/* Vigilante 8's streamed FMV transitions have measured host stalls near + * 140 ms. Eight 60 Hz periods are only 133.3 ms, so the old bound classified + * those finite transitions as sustained slowness and permanently forgave the + * guest/audio debt. Keep a bounded 12-period (200 ms at 60 Hz) window: enough + * to repay the observed transition without turning a real hang, suspend, or + * sub-realtime workload into an unbounded catch-up burst. */ +#define FRAME_PACER_CATCHUP_MAX_PERIODS 12u void frame_pacer_wait(FramePacer *p, double period_ms) { uint64_t freq = SDL_GetPerformanceFrequency(); diff --git a/runtime/src/recomp_audio_drc.h b/runtime/src/recomp_audio_drc.h index be3670af..c49378cb 100644 --- a/runtime/src/recomp_audio_drc.h +++ b/runtime/src/recomp_audio_drc.h @@ -39,8 +39,8 @@ typedef struct { double host_rate; /* host device sample rate (Hz) */ int taps; /* prototype length; 16 min, 32 preferred */ int phases; /* fractional phases; 256-1024 (512 default) */ - double target_ms; /* steady-state ring fill target (default 160) */ - double ring_ms; /* ring capacity in ms (default 250) */ + double target_ms; /* steady-state ring fill target (default 180) */ + double ring_ms; /* ring capacity in ms (default 280) */ double kp; /* proportional gain (default 0.02) */ double ki_per_s; /* optional integral gain per second (default 0): P-only is bounded and cannot wind up across @@ -50,7 +50,7 @@ typedef struct { double slew_pp_per_s; /* correction slew, percent-points/sec (default 0.75) */ double deadband_ms; /* +/- band around target with no correction (1.0) */ double em_low_ms; /* below this = underrun emergency (default 12) */ - double em_high_ms; /* above this = overflow emergency (default 105) */ + double em_high_ms; /* above this = overflow emergency (default 235) */ } rab_config; typedef struct { @@ -143,12 +143,14 @@ void rab_config_defaults(rab_config *c) { c->taps = 32; c->phases = 512; /* Streamed stage transitions can pause PSX audio production for ~140 ms - * while guest cadence catches up inside the same reporting window. Keep a - * measured 160 ms reserve; sustained slow emulation still drains it and - * remains visible in telemetry. The existing 250 ms ring leaves 90 ms of - * overflow headroom without changing guest timing or presentation. */ - c->target_ms = 160.0; - c->ring_ms = 250.0; + * while guest cadence catches up inside the same reporting window. A + * 160 ms target proved just short once: the 1024-frame host callback + * crossed the remaining edge and emitted 216 faded frames (4.9 ms). + * Keep 180 ms so the measured pause retains more than one callback of + * reserve. Sustained slow emulation still drains it and remains visible + * in telemetry. A 280 ms ring preserves 100 ms of overflow headroom. */ + c->target_ms = 180.0; + c->ring_ms = 280.0; c->kp = 0.02; /* P-only intentionally: the former 0.02/s integral retained a +0.5% * correction after transition bursts and drained a steady 59.94 Hz @@ -160,7 +162,7 @@ void rab_config_defaults(rab_config *c) { c->slew_pp_per_s = 0.75; c->deadband_ms = 1.0; c->em_low_ms = 12.0; - c->em_high_ms = 205.0; + c->em_high_ms = 235.0; } int rab_init(rab_bridge *b, const rab_config *cfg) {