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);