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 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-09 20:03:14 -07:00
parent 09ac81e920
commit e88823ee4f
3 changed files with 22 additions and 0 deletions
+2
View File
@@ -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. */
+15
View File
@@ -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;
+5
View File
@@ -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);