Implementing Game State Synchronization for Mobile Games
State synchronization—foundation of any multiplayer experience. Core problem: two devices must see identical game world picture at same moment, despite network latency, packet loss, and different compute power. Several solutions exist, choice depends on genre.
Snapshot vs State Delta vs Event Sourcing
Three main approaches to state distribution:
Full snapshot: server sends complete world state every N ms. Simple but wasteful. With 20 entities at 50 bytes each, 20 Hz = 20 KB/s per client. At 10 clients, server broadcasts 200 KB/s. Works for small games.
Delta compression: send only changes from last confirmed snapshot. Client confirms ack: last_received_tick, server computes delta. Cuts traffic 3-10x on dynamic scenes. Implementation more complex: store state history for delta calculation, handle loss of delta packet (can't apply delta without base snapshot).
Event sourcing: server broadcasts events (PlayerMoved, BulletFired, EntityDied), client replays them on base state. Good for deterministic games: chess, cards, strategy. Bad for physics: any float precision difference causes desync after 30 seconds.
Determinism and Floating Point
Event sourcing critical requirement: both platforms (iOS ARM64, Android ARM64/x86) must give identical results from same computations. Standard float in C#—deterministic on single platform but may differ across CPUs.
Solution—fixed-point math: instead of float 1.5f use FixedPoint 15000 (scale 1/10000). Integer add/multiply deterministic everywhere. FixedMath.Net library for Unity, libfixmath for native C++.
Godot uses deterministic physics via _physics_process—all steps fixed. Unity Physics (DOTS) supports determinism with identical object processing order. Classic PhysX—not deterministic across platforms.
Client-Side Prediction and Reconciliation
Detailed pattern breakdown for real-time games:
Client tick 100: apply input locally, send InputPayload{tick:100, input}
Client tick 101-110: continue predicting locally
Server: receives InputPayload{tick:100}, simulates, responds StatePayload{tick:100, pos, vel}
Client tick 112: receive server answer for tick 100
→ Compare predicted state at tick 100 with server's
→ If deviation > threshold: rollback to server state at tick 100
→ Replay buffer of inputs 101-112
Input buffer—circular array of fixed size (usually 64-128 ticks). Each element: { tick, inputData, predictedState }. On reconciliation—iterate through buffer and reapply.
Reconciliation threshold: not zero. If 0.001 unit deviation—constant rollback → client jitters. Typical threshold: 0.1-0.5 units depending on character speed.
Interpolation for Other Players
Own character—client prediction. Other players—interpolation:
Client stores buffer of snapshots with server timestamps:
[{time: 1000ms, pos: (10,0,5)}, {time: 1050ms, pos: (10.5,0,5)}, ...]
Render happens with delay interpolation_delay (usually 2-3 snapshots = 100-150 ms at 20 Hz). Find two closest snapshots and linearly interpolate position:
float t = (renderTime - fromState.time) / (toState.time - fromState.time);
renderPosition = Vector3.Lerp(fromState.position, toState.position, t);
Rotation—Quaternion.Slerp. For velocity—need Hermite interpolation or Catmull-Rom across multiple points—smoother on direction change.
Desync Problem
In deterministic games, desync doesn't manifest immediately. Standard diagnostic—state hash comparison: all clients send state hash every N ticks to server. If hashes mismatch—desync, server broadcasts full snapshot for resync.
Hash calculated from critical fields (positions, hp, statuses)—not everything to avoid non-essential differences (animation weights, UI state).
Bandwidth and Mobile Constraints
Mobile internet unstable. Design for worst case: 150 ms RTT, 5% packet loss, 50 KB/s traffic budget per player.
Practical measures:
- Positions:
int16instead offloat32with scaling (50% saving) - Rotation: quaternion → two angles
int8(75% saving) - Entities outside view: don't broadcast or reduce update frequency
- Priority-based updates: fast objects update more often
BitPacking libraries: NetStack (C#), LiteNetLib BitWriter—pack multiple small values into one byte.
Timeline
Snapshot sync with interpolation for 4-10 players: 2-3 weeks. Client prediction, reconciliation, delta compression, desync detection: 1.5-3 months. Deterministic simulation with fixed-point math: adds 3-6 weeks. Cost calculated individually.







