From 6162c4e30df986f406a2bf9a289be0a3ebd29e0d Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:30:59 -0700 Subject: [PATCH] SPU: anchor the restored sample phase to the restored clock at load The SPU's guest-clock phase was applied lazily at the next catch-up, after the guest had run on, so the phase was taken against a later clock and lost; a rollback replay rendered SPU samples at other guest cycles than the live run (voice / SPU RAM split with an identical CPU path, PSX_RB_DET_SELFTEST). boot_state writes CLOCK before SPU, so the load anchors it immediately. Co-Authored-By: Claude Opus 5.5 --- runtime/src/spu.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/runtime/src/spu.c b/runtime/src/spu.c index cd0d2189..4a4cdf05 100644 --- a/runtime/src/spu.c +++ b/runtime/src/spu.c @@ -2028,9 +2028,20 @@ int spu_snapshot_read(const uint8_t *p, uint32_t len) { } uint32_t phase = 0; if (len == spu_snapshot_bytes() && !pst_r_u32(&r, &phase)) return 0; - /* Applied at the next catch-up, once the guest clock is restored too. */ - s_clock_carry = phase % SPU_CYCLES_PER_SAMPLE; - s_clock_valid = 0; + /* Anchor the SPU's sample phase to the restored clock NOW. boot_state + * writes CLOCK before SPU, so the clock is already restored here. It used + * to be applied lazily at the next catch-up -- after the guest had run on, + * so the phase was taken against a later clock and lost: a rollback + * replay rendered SPU samples at other guest cycles than the live run + * (voice state / SPU RAM split with identical CPU paths, + * PSX_RB_DET_SELFTEST). */ + { + const uint64_t now = psx_get_cycle_count(); + const uint32_t carry = phase % SPU_CYCLES_PER_SAMPLE; + s_clock = now >= carry ? now - carry : now; + s_clock_carry = 0; + s_clock_valid = 1; + } spu_output_flush(); return 1; }