mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
Netplay rollback: count post-movie agreement in confirmed ticks
The post-FMV lockstep gate counted a tick as agreed only when sim-1 was already hash-confirmed. A peer digest arrives one RTT after its tick, so the peer running ahead (the host, cross-machine) never saw sim-1 confirmed, reset the streak every tick and logged 'rb DESYNC - FMV lockstep MAX unmatched' after every movie while every digest matched. Each newly confirmed tick past the movie's end now counts once; only a real mismatch resets the streak. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,9 @@ if(BUILD_TESTING)
|
||||
target_include_directories(netplay_sim_pad_cache_test PRIVATE include)
|
||||
target_compile_definitions(netplay_sim_pad_cache_test PRIVATE PSX_MAX_PLAYERS=4)
|
||||
add_test(NAME netplay_sim_pad_cache_test COMMAND netplay_sim_pad_cache_test)
|
||||
add_executable(netplay_fmv_confirm_test tests/test_netplay_fmv_confirm.c)
|
||||
target_include_directories(netplay_fmv_confirm_test PRIVATE include)
|
||||
add_test(NAME netplay_fmv_confirm_test COMMAND netplay_fmv_confirm_test)
|
||||
add_executable(netplay_local_view_test tests/test_netplay_local_view.c)
|
||||
target_include_directories(netplay_local_view_test PRIVATE include)
|
||||
add_test(NAME netplay_local_view_test COMMAND netplay_local_view_test)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/* netplay_fmv_confirm.h - post-movie agreement counting for rollback netplay.
|
||||
*
|
||||
* After a movie, both peers must agree for RB_FMV_LOCKSTEP_CONFIRM ticks
|
||||
* before prediction is unlocked. Agreement is measured in hash-confirmed
|
||||
* ticks: each newly confirmed tick past the movie's end counts once, however
|
||||
* late its confirmation arrives. A confirmation is always about one RTT
|
||||
* behind the tick it confirms, so testing "is sim-1 confirmed yet" every tick
|
||||
* never succeeds on the peer that runs ahead. */
|
||||
#ifndef PSX_NETPLAY_FMV_CONFIRM_H
|
||||
#define PSX_NETPLAY_FMV_CONFIRM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Confirmed ticks gained when the confirmed frontier moves from `from` (the
|
||||
* highest tick already counted, or the movie's end) to `resolved`. */
|
||||
static inline uint32_t rb_fmv_confirm_gain(uint32_t from, uint32_t resolved)
|
||||
{
|
||||
return resolved > from ? resolved - from : 0u;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -116,6 +116,7 @@ uint32_t psx_netplay_rb_rtt_estimate_ms(void) { return 0; }
|
||||
#else
|
||||
|
||||
#include "netplay_hash_confirm.h"
|
||||
#include "netplay_fmv_confirm.h"
|
||||
#include "netplay_input_hist.h"
|
||||
#include "netplay_rb_post.h"
|
||||
#include "netplay_snap_ring.h"
|
||||
@@ -715,6 +716,8 @@ static uint32_t g_fmv_settle_until;
|
||||
static uint32_t g_fmv_lockstep_until;
|
||||
static uint32_t g_fmv_media_end_sim;
|
||||
static uint32_t g_fmv_core_match_streak;
|
||||
/* Highest hash-confirmed tick already counted into the streak. */
|
||||
static uint32_t g_fmv_confirm_seen;
|
||||
static int g_fmv_lockstep_released; /* sticky: never re-lock after invent on */
|
||||
/* Inclusive last tick both peers dense-snapped (media/lockstep/+tip). */
|
||||
static uint32_t g_fmv_dense_through;
|
||||
@@ -1037,21 +1040,31 @@ static int rb_fmv_media_active(void)
|
||||
/* After media: grow/shrink lockstep_until from hash_confirm agreement. */
|
||||
static void rb_fmv_update_lockstep_gate(uint32_t sim)
|
||||
{
|
||||
int matched = 0;
|
||||
uint32_t cap;
|
||||
uint32_t prev_until;
|
||||
if (!g_fmv_media_end_sim || rb_fmv_media_active())
|
||||
return;
|
||||
|
||||
if (g_b.hc && !netplay_hc_peek_mismatch(g_b.hc, NULL, NULL, NULL)) {
|
||||
uint32_t need = (sim > 0u) ? (sim - 1u) : 0u;
|
||||
if (netplay_hc_confirm_through(g_b.hc, need))
|
||||
matched = 1;
|
||||
/* Agreement is counted in CONFIRMED ticks, not in "sim-1 is already
|
||||
* confirmed" ticks. The peer's digest for a tick arrives one RTT after
|
||||
* the tick; the peer that runs ahead (usually the host, cross-machine)
|
||||
* never has sim-1 confirmed when it asks, so the old per-tick test reset
|
||||
* the streak every tick and fired a false "MAX unmatched" DESYNC after
|
||||
* every movie even though every digest matched. Only a real mismatch
|
||||
* resets the streak now; a confirm that is merely late leaves it. */
|
||||
{
|
||||
uint32_t gained = 0u;
|
||||
if (g_b.hc && netplay_hc_peek_mismatch(g_b.hc, NULL, NULL, NULL)) {
|
||||
g_fmv_core_match_streak = 0;
|
||||
} else if (g_b.hc && netplay_hc_confirm_through(g_b.hc, g_fmv_media_end_sim)) {
|
||||
const uint32_t rt = netplay_hc_resolved_through(g_b.hc);
|
||||
const uint32_t from = (g_fmv_confirm_seen > g_fmv_media_end_sim)
|
||||
? g_fmv_confirm_seen : g_fmv_media_end_sim;
|
||||
gained = rb_fmv_confirm_gain(from, rt);
|
||||
if (rt > g_fmv_confirm_seen) g_fmv_confirm_seen = rt;
|
||||
}
|
||||
g_fmv_core_match_streak += gained;
|
||||
}
|
||||
if (matched)
|
||||
g_fmv_core_match_streak++;
|
||||
else
|
||||
g_fmv_core_match_streak = 0;
|
||||
|
||||
/* §93: after MAX-unmatched DESYNC, keep counting so a later rematch can
|
||||
* clear invent/begin hold without requiring a new media bout. */
|
||||
@@ -1237,6 +1250,7 @@ static void rb_fmv_tick_settle(void)
|
||||
if (sim >= g_fmv_media_hi)
|
||||
g_fmv_media_hi = sim;
|
||||
g_fmv_core_match_streak = 0;
|
||||
g_fmv_confirm_seen = 0;
|
||||
g_fmv_lockstep_released = 0;
|
||||
g_fmv_settle_until = sim + RB_FMV_SETTLE_TICKS;
|
||||
g_fmv_lockstep_until = sim + RB_FMV_LOCKSTEP_MIN;
|
||||
@@ -6498,6 +6512,7 @@ void psx_netplay_rb_cold_reset(void)
|
||||
g_fmv_settle_until = 0;
|
||||
g_fmv_lockstep_until = 0;
|
||||
g_fmv_media_end_sim = 0;
|
||||
g_fmv_confirm_seen = 0;
|
||||
g_fmv_core_match_streak = 0;
|
||||
g_fmv_lockstep_released = 0;
|
||||
g_fmv_dense_through = 0;
|
||||
@@ -6551,6 +6566,7 @@ void psx_netplay_rb_shutdown(void)
|
||||
g_fmv_settle_until = 0;
|
||||
g_fmv_lockstep_until = 0;
|
||||
g_fmv_media_end_sim = 0;
|
||||
g_fmv_confirm_seen = 0;
|
||||
g_fmv_core_match_streak = 0;
|
||||
g_fmv_lockstep_released = 0;
|
||||
g_fmv_dense_through = 0;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "netplay_fmv_confirm.h"
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
|
||||
/* Model the gate's counting: the peer's digests trail by `lag` ticks. */
|
||||
static uint32_t streak_after(uint32_t media_end, uint32_t ticks, uint32_t lag)
|
||||
{
|
||||
uint32_t seen = 0, streak = 0, sim;
|
||||
for (sim = media_end; sim < media_end + ticks; ++sim) {
|
||||
const uint32_t resolved = sim > lag ? sim - lag : 0;
|
||||
if (resolved < media_end) continue; /* nothing past the movie yet */
|
||||
streak += rb_fmv_confirm_gain(seen > media_end ? seen : media_end, resolved);
|
||||
if (resolved > seen) seen = resolved;
|
||||
}
|
||||
return streak;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
assert(rb_fmv_confirm_gain(10, 10) == 0);
|
||||
assert(rb_fmv_confirm_gain(10, 9) == 0);
|
||||
assert(rb_fmv_confirm_gain(10, 13) == 3);
|
||||
/* Same machine (no lag) and cross-machine (2-6 ticks of lag) both reach
|
||||
* the 16-tick confirmation well inside the 300-tick cap. */
|
||||
assert(streak_after(1000, 300, 0) >= 16);
|
||||
assert(streak_after(1000, 300, 2) >= 16);
|
||||
assert(streak_after(1000, 300, 6) >= 16);
|
||||
/* Bursty confirmations (a frontier jump) still count every tick once. */
|
||||
assert(streak_after(1000, 40, 30) == 9);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user