nx_sim_snapshot.nx
buildroot/runtime/nx_sim_snapshot.nx
about
nx_sim_snapshot.nx -- generic simulation state save/restore.
Foundation primitive for:
- Rollback netcode (GGPO model, Cannon 2006): save last N frames,
roll back when remote input correction arrives, re-simulate.
- Turn-based replay (XCOM 2 / Into the Breach honestmode model):
save state at turn boundary, replay deterministically.
- VN save-anywhere (Ren'Py model): full-VM state snapshot for
load/quicksave/rollback over the last ~128 statements.
- Desync recovery (RTS lockstep): when frame_hash mismatches across
clients, roll back to last known-good snapshot and re-simulate
from authoritative input log.
Two layers:
1. Flat byte-buffer save/restore (memcpy semantics). Caller owns
the byte layout of their simulation state.
2. Ring buffer of N snapshots indexed by frame number with
wraparound. Sized for rollback budget (default 8 frames at
60Hz = 133ms maximum prediction window per GGPO standard).
Honest scope: this primitive does NOT serialize POINTERS. Caller is
responsible for flattening pointer-graph state into a byte block
before save and rebuilding pointers after restore. This matches
how GGPO's `save_game_state` callback works.
Source references (open):
- GGPO Rollback SDK: https://github.com/pond3r/ggpo (MIT)
- Tony Cannon "GGPO Inside Out": ggpo.net/file/Inside_GGPO.pdf
- Skullgirls/Killer Instinct postmortems (GDC vault open)
- Bernier "Latency Compensating Methods" (Valve PDF, open)
genealogy_id: ggpo_2006_rollback + xcom_honestmode_replay + renpy_rollback_log
lineage_id: simulation_state_persistence
dependencies 2 imports · 1 importers
imports: nx_syscalls.nxnx_tier.nx
imported by: nx_rollback_session.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 46 | const NX_SS_DEFAULT_RING_DEPTH: nx_int = 8 |
| 49 | const NX_SS_FRAME_INVALID: nx_int = -1 |
| 103 | const NX_SS_HDR_STATE_SIZE: nx_int = 0 |
| 104 | const NX_SS_HDR_RING_DEPTH: nx_int = 1 |
| 105 | const NX_SS_HDR_NEWEST_FRAME: nx_int = 2 |
| 106 | const NX_SS_HDR_OLDEST_FRAME: nx_int = 3 |
| 107 | const NX_SS_HDR_BASE: nx_int = 4 |
functions
| 58 | func nx_sim_snapshot_save(dst: *u8, src: *u8, n: nx_int) -> nx_int |
| 71 | func nx_sim_snapshot_restore(dst: *u8, src: *u8, n: nx_int) -> nx_int |
| 78 | func nx_sim_snapshot_eq(a: *u8, b: *u8, n: nx_int) -> nx_int called by 1: main |
| 109 | func _ss_slot_for_frame(ring: *i64, frame: nx_int) -> nx_int |
| 117 | func _ss_slot_frame_ptr(ring: *i64, slot: nx_int) -> *i64 |
| 123 | func _ss_bytes_base(ring: *i64) -> *u8 called by 1: _ss_slot_bytes_ptr |
| 130 | func _ss_slot_bytes_ptr(ring: *i64, slot: nx_int) -> *u8 |
| 141 | func nx_snapshot_ring_alloc(state_size: nx_int, ring_depth: nx_int) -> *i64 |
| 167 | func nx_snapshot_ring_save(ring: *i64, frame: nx_int, src: *u8) -> nx_int |
| 201 | func nx_snapshot_ring_restore(ring: *i64, frame: nx_int, dst: *u8) -> nx_int |
| 220 | func nx_snapshot_ring_oldest(ring: *i64) -> nx_int |
| 226 | func nx_snapshot_ring_newest(ring: *i64) -> nx_int called by 1: main |
| 233 | func nx_snapshot_ring_has(ring: *i64, frame: nx_int) -> nx_int |
| 245 | func main() -> i64 |