mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
optimize scheduler and button edge case
This commit is contained in:
@@ -3185,3 +3185,51 @@ provisioning — investigate `rb wire hole` lines before raising D further.
|
||||
Trade-off to feel for in play: each +1 D adds ~17 ms of local input latency;
|
||||
if the WAN session feels laggy but smooth, that's the controller working —
|
||||
step 3 (dense snapshots) then attacks replay cost so D can ride lower.
|
||||
|
||||
## 58. Dense tip snapshots + Start debounce in replay produce (2026-08-02)
|
||||
|
||||
**§57 soak verdict (healthiest yet — committed):** the arrival-driven
|
||||
controller worked with evidence on every move (`5→6` at 153‰ miss / 11 ms
|
||||
lateness, later `6→5` at 0‰ sustained). Scorecard trend across windows:
|
||||
miss_need 198→150→32 (host) / 72→19→17 (guest), gap1 +116→+24 / +50→+8,
|
||||
episodes ~1–2 per 30 s (was 6–8), `lead avg` sitting on the achievable
|
||||
`D−1−transit_est`, `cushion rebuilt ACH` replacing the NEAR parks. One abort
|
||||
per side, zero baseline mismatch / desync. Two items remained: replay
|
||||
`depth_avg` 14–41 with pred_depth 1–2 (snap granularity dominates episode
|
||||
cost), and one user-visible bug — a Start press opened TWO overlapping pause
|
||||
menus (online only).
|
||||
|
||||
**1. Start doubling root cause — §47 replay produce bypassed the §34
|
||||
debounce.** `np_rb_produce_local_tip_for_sim` copied the raw live snapshot
|
||||
(`staged = live`) without `np_digital_debounce_staged()`, so a press
|
||||
overlapping an episode could publish press/idle/press across consecutive tip
|
||||
rows (SDL snapshot bounce mid-resim); MotK's pause is edge-triggered → two
|
||||
menus. This soak had an episode every ~30 s, which is exactly the exposure
|
||||
window. Fix: the replay producer now runs the same debounce, keeping the
|
||||
sticky release state coherent across live↔replay transitions.
|
||||
|
||||
**2. Dense tip snapshots (`PSX_NET_SNAP_DENSE`, default 8, 0=off, max 24).**
|
||||
Live path now saves EVERY tick; non-interval ticks are tracked in a sliding
|
||||
FIFO and evicted from the ring once they age past the window (pinned
|
||||
baseline / episode load / commit-span snaps are never dense-evicted), so
|
||||
interval history is untouched. Ring depth 64→80 to absorb the extra
|
||||
occupancy. Cost: one raw boot_state save per frame instead of per 16 —
|
||||
watch the [FPS] guest ms/f; PSX_NET_SNAP_DENSE=0 restores §57 behavior.
|
||||
|
||||
**3. choose_load can now raise to the raw HC-proven tick** when a local
|
||||
snap exists there (`hc_dense=` in the raise log). The §53b interval-only
|
||||
raise rule predates dense snaps; with both peers keeping the same window the
|
||||
snap is normally present on the follower too, and when it is not, follower
|
||||
NACK → demote already covers it (one extra round trip, rare). Together with
|
||||
the walk's existing HC-confirmed acceptance, a near-tip mispredict should
|
||||
now load at (or within a tick of) the mismatch.
|
||||
|
||||
**Re-soak watch:** `rb snap ring ready … tip_dense=8`; scorecard
|
||||
`depth_avg` should drop from 14–41 toward `pred_depth + handshake span`
|
||||
(single digits); watch `[FPS]` guest ms/f for the per-frame save tax (if
|
||||
Live drops below 60, set PSX_NET_SNAP_DENSE=4 or 0 and re-evaluate);
|
||||
occasional `choose_load raise … hc_dense=…` lines are the new path working —
|
||||
a burst of follower NACK/demotes right after them would mean the peer's
|
||||
dense window is out of phase and the raise should be narrowed. For the Start
|
||||
fix: pausing near resim activity should be single-menu; if a double recurs,
|
||||
enable the pad edge log and capture the row sequence.
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NETPLAY_SNAP_RING_DEFAULT_DEPTH 64u
|
||||
/* §58: 64 → 80 — dense tip snaps (PSX_NET_SNAP_DENSE window) occupy up to
|
||||
* ~24 extra slots alongside the interval snaps; keep the interval history
|
||||
* span (~1024 ticks at iv=16) from shrinking under dense pressure. */
|
||||
#define NETPLAY_SNAP_RING_DEFAULT_DEPTH 80u
|
||||
|
||||
typedef struct NetplaySnapRing NetplaySnapRing;
|
||||
|
||||
@@ -53,6 +56,8 @@ const uint8_t* netplay_snap_ring_peek(const NetplaySnapRing* r, uint32_t tick,
|
||||
/* Invalidate every snapshot with tick > tick (dead-timeline snaps after an
|
||||
* abort realign). Returns the number of slots dropped. */
|
||||
uint32_t netplay_snap_ring_drop_after(NetplaySnapRing* r, uint32_t tick);
|
||||
/* Drop the single snap at tick (dense-window eviction). 1 if dropped. */
|
||||
int netplay_snap_ring_drop_tick(NetplaySnapRing* r, uint32_t tick);
|
||||
uint32_t netplay_snap_ring_oldest_tick(const NetplaySnapRing* r);
|
||||
uint32_t netplay_snap_ring_newest_tick(const NetplaySnapRing* r);
|
||||
|
||||
|
||||
@@ -178,6 +178,14 @@ int netplay_snap_ring_load(NetplaySnapRing* r, uint32_t tick, CPUState* cpu,
|
||||
return boot_state_load_buffer(data, size, bios_checksum, entry_pc, cpu);
|
||||
}
|
||||
|
||||
int netplay_snap_ring_drop_tick(NetplaySnapRing* r, uint32_t tick) {
|
||||
int idx = find_slot(r, tick);
|
||||
if (idx < 0) return 0;
|
||||
slot_clear(&r->slots[idx]);
|
||||
if (r->count) r->count--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_drop_after(NetplaySnapRing* r, uint32_t tick) {
|
||||
uint32_t i, n = 0;
|
||||
if (!r) return 0;
|
||||
|
||||
@@ -2009,6 +2009,8 @@ static void np_rollback_reconcile_wire(void)
|
||||
}
|
||||
|
||||
|
||||
static void np_digital_debounce_staged(void);
|
||||
|
||||
/* §47: Replay participates in the protocol like Live — produce local tip
|
||||
* from the live physical pad while consuming remote confirmed rows. */
|
||||
static void np_rb_produce_local_tip_for_sim(rnet_u32 sim)
|
||||
@@ -2023,6 +2025,12 @@ static void np_rb_produce_local_tip_for_sim(rnet_u32 sim)
|
||||
g_np.staged = g_np.live;
|
||||
psx_netplay_normalize_pad(&g_np.staged);
|
||||
g_np.staged_valid = 1;
|
||||
/* §58: this producer bypassed the §34 release debounce — a Start
|
||||
* press overlapping an episode could publish press/idle/press across
|
||||
* consecutive tip rows (SDL snapshot bounce mid-resim) and MotK's
|
||||
* edge-triggered pause opened twice. Same debounce as stage_local so
|
||||
* the sticky state stays coherent across live↔replay transitions. */
|
||||
np_digital_debounce_staged();
|
||||
}
|
||||
g_np.latched_for_tick = 0;
|
||||
(void)rnet_session_prepare_local_tip(g_np.session, sim);
|
||||
|
||||
@@ -163,6 +163,40 @@ static uint32_t snap_interval(void)
|
||||
}
|
||||
return iv;
|
||||
}
|
||||
/* §58 dense tip snaps: live saves every tick inside a sliding window so a
|
||||
* near-tip mispredict loads at (or within a tick of) the mismatch instead of
|
||||
* up to snap_interval-1 ticks below it (§57 soak: depth_avg 14–41 with
|
||||
* pred_depth 1–2 — replay work was dominated by snap granularity). The
|
||||
* window is bounded: non-interval ticks older than the window are dropped
|
||||
* from the ring so interval history is not evicted. PSX_NET_SNAP_DENSE
|
||||
* overrides the window size (0 disables, max 24). */
|
||||
#define NP_DENSE_SNAP_MAX 24u
|
||||
static uint32_t g_tip_dense_fifo[NP_DENSE_SNAP_MAX];
|
||||
static uint32_t g_tip_dense_fifo_n;
|
||||
static uint32_t g_tip_dense_fifo_head;
|
||||
|
||||
static uint32_t tip_dense_window(void)
|
||||
{
|
||||
static int latched;
|
||||
static uint32_t win = 8u;
|
||||
if (!latched) {
|
||||
const char *e = getenv("PSX_NET_SNAP_DENSE");
|
||||
if (e && e[0]) {
|
||||
unsigned v = 0;
|
||||
if (sscanf(e, "%u", &v) == 1 && v <= NP_DENSE_SNAP_MAX)
|
||||
win = v;
|
||||
}
|
||||
latched = 1;
|
||||
}
|
||||
return win;
|
||||
}
|
||||
|
||||
static void tip_dense_reset(void)
|
||||
{
|
||||
g_tip_dense_fifo_n = 0;
|
||||
g_tip_dense_fifo_head = 0;
|
||||
}
|
||||
|
||||
static int g_local_baseline_sent;
|
||||
static uint32_t g_local_baseline_digest;
|
||||
static uint32_t g_local_baseline_av;
|
||||
@@ -384,6 +418,32 @@ static uint32_t g_last_begin_mismatch = 0xffffffffu;
|
||||
/* Baseline mismatch with no agreed tip — don't reopen every few ticks. */
|
||||
#define RB_BASELINE_MISMATCH_COOLDOWN_TICKS 90u
|
||||
|
||||
/* §58: track a live non-interval snap tick; evict the oldest dense snap once
|
||||
* the window is full (never the pinned baseline / episode load / commit
|
||||
* span — those stay until normal oldest-tick eviction). */
|
||||
static void tip_dense_push(uint32_t tick)
|
||||
{
|
||||
uint32_t win = tip_dense_window();
|
||||
if (win == 0u)
|
||||
return;
|
||||
if (g_tip_dense_fifo_n >= win) {
|
||||
uint32_t victim =
|
||||
g_tip_dense_fifo[(g_tip_dense_fifo_head + NP_DENSE_SNAP_MAX -
|
||||
g_tip_dense_fifo_n) % NP_DENSE_SNAP_MAX];
|
||||
g_tip_dense_fifo_n--;
|
||||
if (!(g_pin_valid && victim == g_pin_tick) &&
|
||||
!(g_rb && rnet_rb_is_active(g_rb) &&
|
||||
victim == rnet_rb_get_load_tick(g_rb)) &&
|
||||
!(g_agreed_valid && victim >= g_agreed_span_lo &&
|
||||
victim <= g_agreed_through)) {
|
||||
(void)netplay_snap_ring_drop_tick(g_snaps, victim);
|
||||
}
|
||||
}
|
||||
g_tip_dense_fifo[g_tip_dense_fifo_head] = tick;
|
||||
g_tip_dense_fifo_head = (g_tip_dense_fifo_head + 1u) % NP_DENSE_SNAP_MAX;
|
||||
g_tip_dense_fifo_n++;
|
||||
}
|
||||
|
||||
static int g_was_in_fmv;
|
||||
static uint32_t g_fmv_settle_until;
|
||||
static uint32_t g_fmv_lockstep_until;
|
||||
@@ -2054,8 +2114,18 @@ static int choose_load_tick_inner(uint32_t mismatch, uint32_t *out_load)
|
||||
* (ADVANCE/HC-HEAL/commit advertise; never HEAL-FORCE). */
|
||||
if (!post_tip_hold_holdoff_active()) {
|
||||
uint32_t raised = have_shared ? shared : 0u;
|
||||
uint32_t hc_dense = 0u;
|
||||
if (hc_iv > raised)
|
||||
raised = hc_iv;
|
||||
/* §58: dense tip snaps — the raw HC-proven tick is raise-safe when a
|
||||
* snap exists locally (both peers keep the same dense window; a
|
||||
* missing peer snap is recoverable via follower NACK → demote,
|
||||
* unlike the §53b interval-only rule which predates dense snaps). */
|
||||
if (tip_dense_window() > 0u && hc_floor > raised &&
|
||||
netplay_snap_ring_has(g_snaps, hc_floor)) {
|
||||
raised = hc_floor;
|
||||
hc_dense = hc_floor;
|
||||
}
|
||||
if (g_peer_resolved_through > raised)
|
||||
raised = g_peer_resolved_through;
|
||||
if (raised > 0u && (!have_shared || raised > shared)) {
|
||||
@@ -2064,10 +2134,10 @@ static int choose_load_tick_inner(uint32_t mismatch, uint32_t *out_load)
|
||||
if (s_raise_log != raised) {
|
||||
fprintf(stderr,
|
||||
"psxrecomp: rb choose_load raise %u→%u "
|
||||
"(agreed=%u hc_iv=%u peer_resolved=%u)\n",
|
||||
"(agreed=%u hc_iv=%u hc_dense=%u peer_resolved=%u)\n",
|
||||
(unsigned)shared, (unsigned)raised,
|
||||
(unsigned)(g_agreed_valid ? g_agreed_through : 0u),
|
||||
(unsigned)hc_iv,
|
||||
(unsigned)hc_iv, (unsigned)hc_dense,
|
||||
(unsigned)g_peer_resolved_through);
|
||||
fflush(stderr);
|
||||
s_raise_log = raised;
|
||||
@@ -3776,6 +3846,7 @@ void psx_netplay_rb_start(void)
|
||||
return;
|
||||
|
||||
g_snaps = netplay_snap_ring_create(NETPLAY_SNAP_RING_DEFAULT_DEPTH);
|
||||
tip_dense_reset();
|
||||
if (!g_snaps) {
|
||||
fprintf(stderr, "psxrecomp: rb snap ring create FAILED — rewind disabled\n");
|
||||
return;
|
||||
@@ -3863,8 +3934,11 @@ void psx_netplay_rb_start(void)
|
||||
g_bl_fork_cap = 0u; /* §55: commit/reset clears the bisect cap */
|
||||
clear_baseline_pin();
|
||||
clear_episode_wire_state();
|
||||
fprintf(stderr, "psxrecomp: rb snap ring ready depth=%u snap_interval=%u\n",
|
||||
(unsigned)netplay_snap_ring_depth(g_snaps), (unsigned)snap_interval());
|
||||
fprintf(stderr,
|
||||
"psxrecomp: rb snap ring ready depth=%u snap_interval=%u "
|
||||
"tip_dense=%u\n",
|
||||
(unsigned)netplay_snap_ring_depth(g_snaps),
|
||||
(unsigned)snap_interval(), (unsigned)tip_dense_window());
|
||||
}
|
||||
|
||||
void psx_netplay_rb_shutdown(void)
|
||||
@@ -3931,7 +4005,7 @@ void psx_netplay_rb_shutdown(void)
|
||||
/* Dense tip snaps: media/settle, plus the digest lockstep/+tip window even
|
||||
* after invent is allowed again (§26) — first Cross/Start after FMV still
|
||||
* wants a near-tip load. */
|
||||
static int rb_dense_snap_window(void)
|
||||
static int rb_fmv_dense_snap_window(void)
|
||||
{
|
||||
RNetSession *s = sess();
|
||||
uint32_t sim = s ? rnet_session_sim_tick(s) : 0u;
|
||||
@@ -3962,10 +4036,16 @@ void psx_netplay_rb_request_snap(uint32_t tick)
|
||||
* invent-miss loads near the mismatch (954→944 was a ~10-tick hitch;
|
||||
* with tip snaps + shared frontier → load≈953). */
|
||||
if (!(g_rb && rnet_rb_is_resimulating(g_rb))) {
|
||||
if (!rb_dense_snap_window()) {
|
||||
if (!rb_fmv_dense_snap_window()) {
|
||||
iv = snap_interval();
|
||||
if (iv > 1u && (tick % iv) != 0u)
|
||||
return;
|
||||
if (iv > 1u && (tick % iv) != 0u) {
|
||||
/* §58: dense tip window — save every live tick and keep the
|
||||
* last N non-interval snaps so a near-tip mispredict loads
|
||||
* at the mismatch, not an interval boundary. */
|
||||
if (tip_dense_window() == 0u)
|
||||
return;
|
||||
tip_dense_push(tick);
|
||||
}
|
||||
} else if (tick >= g_fmv_dense_through) {
|
||||
g_fmv_dense_through = tick;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user