This guide explains how to tune the rollback netcode configuration for your specific game and network conditions.
import { Topology, DesyncAuthority } from 'rollback-netcode';
interface SessionConfig {
tickRate: number; // Simulation ticks per second (default: 60)
maxPlayers: number; // Maximum players allowed (default: 4)
topology: Topology; // Topology.Star or Topology.Mesh (default: Star)
snapshotHistorySize: number; // Snapshots to keep (default: 120)
maxSpeculationTicks: number; // Max ticks ahead without confirmed inputs (default: 60)
hashInterval: number; // Ticks between desync checks (default: 60)
disconnectTimeout: number; // Ms before disconnecting idle peer (default: 5000)
debug: boolean; // Enable debug logging (default: false)
desyncAuthority: DesyncAuthority; // Who resolves desyncs (default: Peer)
lagReportThreshold: number; // Ticks behind before reporting lag (default: 30)
inputRedundancy: number; // Inputs per message for reliability (default: 3)
joinRateLimitRequests: number; // Max join requests per window (default: 3)
joinRateLimitWindowMs: number; // Rate limit window in ms (default: 10000)
}| Higher Tick Rate | Lower Tick Rate |
|---|---|
| Smoother gameplay | Less CPU usage |
| More bandwidth | Less bandwidth |
| Lower input latency | Higher input latency |
| More rollbacks | Fewer rollbacks |
Recommendations:
- Action games: 60 ticks/second for responsive controls
- Strategy games: 20-30 ticks/second is often sufficient
- Turn-based: 10 ticks/second or lower
Each snapshot stores your serialized game state. Memory usage is approximately:
Memory = snapshotHistorySize × (gameStateSize + overhead)
| Snapshot History | Memory (1KB state) | Memory (10KB state) |
|---|---|---|
| 60 snapshots | ~70 KB | ~650 KB |
| 120 snapshots | ~140 KB | ~1.3 MB |
| 240 snapshots | ~280 KB | ~2.6 MB |
Recommendations:
snapshotHistorySizemust be ≥maxSpeculationTicks- For 60 tick rate: 120 snapshots = 2 seconds of history
- For high-latency networks: increase to 180-240 snapshots
This controls how far the simulation can advance without confirmed inputs.
| More Speculation | Less Speculation |
|---|---|
| Smoother with high latency | More accurate simulation |
| More potential rollbacks | May pause waiting for inputs |
| Higher memory usage | Lower memory usage |
Recommendations:
- Local network (< 20ms): 15-30 ticks
- Regional (20-80ms): 30-60 ticks
- Global (80-200ms): 60-120 ticks
Formula: maxSpeculationTicks ≈ (expectedLatencyMs / 1000) × tickRate × 2
How often to broadcast state hashes for desync detection.
| Frequent Hashing | Infrequent Hashing |
|---|---|
| Faster desync detection | Slower detection |
| More bandwidth | Less bandwidth |
| More CPU for hashing | Less CPU overhead |
Recommendations:
- Development: 30 ticks (0.5 seconds) for quick desync detection
- Production: 60-120 ticks (1-2 seconds) to reduce overhead
- Increase if hash computation is expensive
| Star Topology | Mesh Topology |
|---|---|
| Fewer connections (N-1) | More connections (N×(N-1)/2) |
| Higher latency (2× for clients) | Lower latency (direct) |
| Host can relay inputs | Each peer sends to all others |
| Better for 4+ players | Better for 2-3 players |
Player count and connections:
| Players | Star Connections | Mesh Connections |
|---|---|---|
| 2 | 1 | 1 |
| 3 | 2 | 3 |
| 4 | 3 | 6 |
| 6 | 5 | 15 |
| 8 | 7 | 28 |
Recommendations:
- 2-3 players: Mesh for lower latency
- 4+ players: Star for connection simplicity
- Mobile/constrained: Star (fewer connections = less battery)
Each input message includes the last N inputs for reliability on lossy connections.
| Higher Redundancy | Lower Redundancy |
|---|---|
| Better packet loss recovery | Less bandwidth |
| More bandwidth | More rollbacks on loss |
Recommendations:
- Reliable networks: 1-2
- Typical networks: 3 (default)
- Lossy networks (mobile): 4-5
For games with large state (>16KB), use TransformingTransport to add compression and message segmentation.
Compression requires the pako package (optional peer dependency): npm install pako. Set compression: 'never' to use segmentation without pako.
import { TransformingTransport, WebRTCTransport } from 'rollback-netcode';
const webrtc = new WebRTCTransport(localPeerId);
const transport = new TransformingTransport(webrtc, {
compression: 'auto', // 'auto' | 'always' | 'never'
compressionThreshold: 128, // Only compress messages > 128 bytes
maxSegmentSize: 16000, // WebRTC DataChannel limit
reassemblyTimeout: 5000, // Timeout for incomplete messages
});
await transport.ready; // Wait for compression module to loadWhen to use:
- State sync messages exceed 16KB
- Bandwidth is constrained and state is compressible
- Playing over unreliable connections
Compression modes:
'auto'(default): Compress only if result is smaller'always': Always compress (good for text-heavy state)'never': Skip compression (if state is already compressed)
import { Topology } from 'rollback-netcode';
const config = {
tickRate: 60,
maxPlayers: 2,
topology: Topology.Mesh, // Direct connection for lowest latency
snapshotHistorySize: 120,
maxSpeculationTicks: 30, // Low latency priority
hashInterval: 120,
inputRedundancy: 2, // Minimal redundancy for low bandwidth
};import { Topology } from 'rollback-netcode';
const config = {
tickRate: 30,
maxPlayers: 4,
topology: Topology.Star, // Simpler with more players
snapshotHistorySize: 90,
maxSpeculationTicks: 45,
hashInterval: 60,
inputRedundancy: 3,
};import { Topology } from 'rollback-netcode';
const config = {
tickRate: 20,
maxPlayers: 8,
topology: Topology.Star,
snapshotHistorySize: 60,
maxSpeculationTicks: 40,
hashInterval: 40,
inputRedundancy: 4, // Higher for reliability
};import { Topology } from 'rollback-netcode';
const config = {
tickRate: 60,
maxPlayers: 4,
topology: Topology.Star,
snapshotHistorySize: 240, // 4 seconds of history
maxSpeculationTicks: 120, // Allow 2 seconds ahead
hashInterval: 60,
disconnectTimeout: 10000, // Longer timeout for high latency
inputRedundancy: 5, // Higher redundancy for reliability
};- Rollback frequency: If > 10% of ticks cause rollbacks, increase
maxSpeculationTicks - Rollback depth: If rollbacks exceed 30 ticks, consider reducing tick rate
- Memory usage: If snapshots consume too much memory, reduce
snapshotHistorySizeor optimize serialization - Desync frequency: If desyncs occur, ensure game logic is deterministic
Enable debug logging to monitor rollbacks and network events:
const session = createSession({
game,
transport,
config: { debug: true },
});Listen for events to monitor performance:
session.on('desync', (tick, localHash, remoteHash) => {
console.warn(`Desync at tick ${tick}`);
});
session.on('lagReport', (playerId, ticksBehind) => {
console.warn(`Player ${playerId} is ${ticksBehind} ticks behind`);
});Use the included benchmarks to measure performance:
npm run benchmarkThis helps identify if encoding, snapshot saving, or engine ticks are bottlenecks.
Smaller game state = faster serialization, less memory, less bandwidth.
Tips:
- Only serialize what changes (delta compression)
- Use fixed-point instead of floating-point where possible
- Pack booleans into bitfields
- Exclude derived state (recalculate after deserialize)
- Use appropriate integer sizes (uint8 vs uint32)
Example: Position optimization
// Before: 24 bytes per entity (3 × 8-byte floats)
{ x: number, y: number, z: number }
// After: 6 bytes per entity (3 × 2-byte fixed-point)
// Store as 1/100 units, range ±327 meters
{ x: int16, y: int16, z: int16 }For games with large state (complex simulations, many entities):
- Use TransformingTransport for automatic compression
- Optimize serialization - binary formats, not JSON
- Consider delta sync - only send changes (not built-in, game-level optimization)
- Reduce snapshot frequency - if rollbacks are rare
import { TransformingTransport, WebRTCTransport } from 'rollback-netcode';
// Wrap transport with compression for large states
const transport = new TransformingTransport(
new WebRTCTransport(peerId),
{
compression: 'auto',
maxSegmentSize: 14000, // Leave headroom for WebRTC overhead
}
);
await transport.ready;