Build Your First Session¶
This guide builds a small deterministic simulation and runs it through real save, load, and
resimulation requests. It uses SyncTestSession, so you can verify the part your game controls
before adding sockets and another machine.
Install¶
Add Fortress Rollback and Serde derives:
Fortress Rollback supports Rust 1.86 and newer.
Run a maintained example¶
From a clone of this repository, run:
The example drives 20 frames, forces rollback and resimulation, and fails with a structured error if the same inputs produce a different checksum.
Build the smallest useful loop¶
The following program is complete. Inputs use fixed-width integers because network sessions require every input value to serialize to the same nonzero width. Game state uses integer arithmetic so the same inputs produce the same result across targets.
use fortress_rollback::{
compute_checksum, Config, FortressRequest, InputStatus, PlayerHandle, SessionBuilder,
};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
struct GameInput {
direction: i8,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct GameState {
frame: i32,
position: i32,
}
struct GameConfig;
impl Config for GameConfig {
type Input = GameInput;
type State = GameState;
type Address = SocketAddr;
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut session = SessionBuilder::<GameConfig>::new()
.with_num_players(1)?
.with_check_distance(2)
.with_max_prediction_window(8)
.start_synctest_session()?;
let mut state = GameState::default();
for _ in 0..20 {
session.add_local_input(
PlayerHandle::new(0),
GameInput { direction: 1 },
)?;
for request in session.advance_frame()? {
match request {
FortressRequest::SaveGameState { cell, frame } => {
let checksum = compute_checksum(&state)?;
cell.save(frame, Some(state.clone()), Some(checksum));
},
FortressRequest::LoadGameState { cell, .. } => {
if let Some(saved) = cell.load() {
state = saved;
}
},
FortressRequest::AdvanceFrame { inputs } => {
for (input, status) in inputs {
if status != InputStatus::Disconnected {
state.position = state
.position
.saturating_add(i32::from(input.direction));
}
}
state.frame = state.frame.saturating_add(1);
},
}
}
}
Ok(())
}
Three rules matter:
- Submit input for every local player before each
advance_framecall. - Process every returned request once, in order.
- Save every value that affects gameplay and restore the saved state on a load request.
Apply both confirmed and predicted inputs to the simulation. Skip an input only when its status is
InputStatus::Disconnected; rollback corrects a predicted value later if the peer sends a different
one.
Do not use render-only state, wall-clock time, unordered collection iteration, or unseeded random values in the deterministic simulation. The determinism model explains the full contract.
Connect two peers¶
Once the local loop stays green, keep the same input and request code and change session setup:
- Bind a
UdpNonBlockingSocketor provide a customNonBlockingSocket. - Add each participant with a stable
PlayerHandle: onePlayerType::Localand one or morePlayerType::Remote(address)entries. - Call
start_p2p_session(socket). - Call
poll_remote_clientsevery game tick, even while the session is synchronizing or stalled. - Submit local inputs and advance only while
current_state()isSessionState::Running.
The maintained configuration example
shows session construction. The request-handling example
shows both explicit matching and the handle_requests! macro. For engine or browser transports, use
the custom socket example
after the basic UDP path works.
Before real playtests¶
- Run the SyncTest loop in CI with representative inputs and states.
- Read The Game Loop, especially polling, event draining, and frame pacing.
- Choose measured defaults from Network Tuning; do not guess at prediction windows.
- Decide how the game handles disconnects and desync evidence.
- Complete the Production Checklist before shipping.
Continue with the User Guide for session types, events, spectators, hot-join, telemetry, and the complete configuration reference.