From e88823ee4fec4868132526be41f974fddee7e40e Mon Sep 17 00:00:00 2001 From: Shokunin <30949000+tetrisgm@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:03:14 -0700 Subject: [PATCH] Netplay rb: admit the Live tick a realign resumes into A Live realign restores the snap at T, sets the session to T+1 and longjmps back into the guest from the admit spin. The resume ran before T+1 was admitted, so that frame latched no inputs and did not advance; the next frame was then counted as T+1 and the realigned seat ran one frame ahead of its tick label from there on (ring saves after a NACK realign carried the next tick's clock, 2x the cycles in the first tick). Only the realigned seat shifted, so it forked from every other seat; with four seats this produced the "resim core diverge" splits and the later hc-fork / hang cascade. flush_resume now admits the tick first on the Live realign path and keeps spinning until it can. Co-Authored-By: Claude Opus 5.5 --- runtime/include/psx_netplay_rb.h | 2 ++ runtime/src/psx_netplay.c | 15 +++++++++++++++ runtime/src/psx_netplay_rb.c | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/runtime/include/psx_netplay_rb.h b/runtime/include/psx_netplay_rb.h index e3780b12..4d40d512 100644 --- a/runtime/include/psx_netplay_rb.h +++ b/runtime/include/psx_netplay_rb.h @@ -52,6 +52,8 @@ void psx_netplay_rb_poll(struct CPUState *cpu, uint32_t resume_pc); * (same as savestate_poll). No-op when nothing is pending. Never returns on * success. */ void psx_netplay_rb_flush_resume(void); +/* psx_netplay.c: admit the Live tick a realign resumes into (1 = admitted). */ +int psx_netplay_live_realign_admit(void); /* 1 after flush_resume longjmp until finish_frame / abort — top-level * dispatch has no native call chain under the resume PC. */ diff --git a/runtime/src/psx_netplay.c b/runtime/src/psx_netplay.c index d4a912ff..81c66a36 100644 --- a/runtime/src/psx_netplay.c +++ b/runtime/src/psx_netplay.c @@ -5016,6 +5016,21 @@ static int np_try_admit_load_barrier_unconfirmed(void) return 1; } +/* A Live realign restores the snap at T and resumes the guest at the frame + * boundary after it, with the session already at T+1. Admit T+1 before the + * resume: resuming first ran frame T+1 unadmitted (no inputs latched, no + * advance), so the next frame was counted as T+1 and this peer ran one frame + * ahead of its tick label (4-seat race: L-saves after a NACK realign carried + * the next tick's clock; the realigned seat forked from every other). */ +int psx_netplay_live_realign_admit(void) +{ + if (!psx_netplay_active() || !g_np.rollback || g_np.xfer != NP_XFER_NONE) + return 1; + if (g_np.needs_advance) + return 1; + return np_try_admit_rollback(); +} + int psx_netplay_poll_admit(void) { rnet_u32 sim; diff --git a/runtime/src/psx_netplay_rb.c b/runtime/src/psx_netplay_rb.c index 415f9983..be3f282e 100644 --- a/runtime/src/psx_netplay_rb.c +++ b/runtime/src/psx_netplay_rb.c @@ -7117,6 +7117,11 @@ void psx_netplay_rb_flush_resume(void) live_realign_resume = !g_rb || !rnet_rb_is_active(g_rb); if (!live_realign_resume && rnet_rb_get_phase(g_rb) != nRNetRbPhaseReplay) return; + /* Live realign: the resumed frame must be an admitted tick, or it runs + * uncounted and this seat ends one frame ahead of its label. Stay + * spinning until the tick's inputs admit. */ + if (live_realign_resume && !psx_netplay_live_realign_admit()) + return; pc = g_pending_resume_pc; if (!rb_resume_pc_ok(pc)) { uint32_t alt = pick_snap_resume_pc(cpu(), pc);