nx_rollback_session.nx
buildroot/runtime/nx_rollback_session.nx
about
nx_rollback_session.nx -- GGPO-class rollback netcode session handle.
Composes the five foundation primitives:
- nx_sim_snapshot : ring of last-N state snapshots
- nx_state_delta_codec : 16-byte input packets via bit-packed varint
- nx_frame_hash : per-frame state hash for desync detection
- nx_zigzag_varint : signed-integer wire encoding
- (caller's sim primitives -- whatever advances the game state)
...into a single session handle that game code holds for one
multiplayer match. Modelled directly on GGPO's session callbacks
(Cannon 2006 / pond3r/ggpo) but adapted to NishiLang's callback-
less calling convention: storage-only, caller drives the sim loop.
Caller pattern per tick:
1. read local input -> save via session_save_local_input
2. snapshot state -> save via session_save_state
3. advance sim using local + last-known remote input
4. compute frame_hash -> save via session_save_hash
5. transmit (tick, local_input, frame_hash) to peer
6. when (peer_tick, peer_input) arrives:
a. session_set_remote_input(peer_tick, peer_input)
b. if peer_tick < current_tick:
- session_restore_state(peer_tick) into sim state
- re-advance from peer_tick to current_tick with
freshly-corrected remote input each tick
7. when (peer_tick, peer_hash) arrives:
a. our_hash = session_get_hash(peer_tick)
b. verdict = nx_frame_hash_compare(...)
c. if MISMATCH -> trigger desync recovery
Session layout (i64 cells in a flat block):
h[0] = state_size
h[1] = max_rollback_frames (snapshot/input ring depth)
h[2] = current_tick (highest local frame saved)
h[3] = oldest_pending_remote_frame
(= highest local frame we have NOT yet received remote input for;
before we can rollback to frame F we need remote input at F)
h[4] = snapshot_ring_ptr (nx_snapshot_ring_alloc handle)
h[5] = local_input_ring_ptr (parallel i64 array, depth slots)
dependencies 4 imports · 0 importers
imports: nx_syscalls.nxnx_tier.nxnx_sim_snapshot.nxnx_frame_hash.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 61 | const NX_RBS_HDR_STATE_SIZE: nx_int = 0 |
| 62 | const NX_RBS_HDR_MAX_ROLLBACK: nx_int = 1 |
| 63 | const NX_RBS_HDR_CURRENT_TICK: nx_int = 2 |
| 64 | const NX_RBS_HDR_OLDEST_PEND_REM: nx_int = 3 |
| 65 | const NX_RBS_HDR_SNAPSHOT_RING: nx_int = 4 |
| 66 | const NX_RBS_HDR_LOCAL_INPUT: nx_int = 5 |
| 67 | const NX_RBS_HDR_REMOTE_INPUT: nx_int = 6 |
| 68 | const NX_RBS_HDR_REMOTE_PRESENT: nx_int = 7 |
| 69 | const NX_RBS_HDR_LOCAL_HASH: nx_int = 8 |
| 70 | const NX_RBS_HDR_HASH_PRESENT: nx_int = 9 |
| 71 | const NX_RBS_HDR_SIZE: nx_int = 10 |
| 74 | const NX_RBS_NO_ROLLBACK: nx_int = 0 |
| 75 | const NX_RBS_NEED_ROLLBACK: nx_int = 1 |
| 76 | const NX_RBS_REMOTE_TOO_OLD: nx_int = 2 |
functions
| 80 | func nx_rollback_session_new(state_size: nx_int, max_rollback: nx_int) -> *i64 |
| 119 | func _rbs_slot(h: *i64, frame: nx_int) -> nx_int |
| 128 | func nx_rollback_session_save_local_input(s: *i64, frame: nx_int, input: i64) -> nx_int |
| 139 | func nx_rollback_session_save_state(s: *i64, frame: nx_int, src: *u8) -> nx_int |
| 145 | func nx_rollback_session_save_hash(s: *i64, frame: nx_int, hash: i64) -> nx_int |
| 171 | func nx_rollback_session_set_remote_input(s: *i64, frame: nx_int, input: i64) -> nx_int |
| 212 | func nx_rollback_session_restore_state(s: *i64, frame: nx_int, dst: *u8) -> nx_int |
| 220 | func nx_rollback_session_get_local_input(s: *i64, frame: nx_int) -> i64 |
| 228 | func nx_rollback_session_get_remote_input(s: *i64, frame: nx_int) -> i64 |
| 236 | func nx_rollback_session_has_remote_input(s: *i64, frame: nx_int) -> nx_int |
| 244 | func nx_rollback_session_get_hash(s: *i64, frame: nx_int) -> i64 |
| 254 | func nx_rollback_session_current_tick(s: *i64) -> nx_int called by 1: main |
| 266 | func _faux_advance(state: i64, local_in: i64, remote_in: i64) -> i64 called by 1: main |
| 270 | func main() -> i64 |