Rollback State Snapshots in Fighting Games

Rollback netcode relies on the ability to continuously save, restore, and resimulate game states within single-frame windows to hide network latency. To execute these rollbacks without causing frame drops or exceeding strict platform memory limits, fighting game developers decouple logic from presentation, pack game state into contiguous data structures, and use circular memory buffers. This article explains the architectural patterns, memory alignment techniques, and serialization strategies developers use to structure deterministic state snapshots to ensure high performance within constrained memory footprints.

Decoupling Logic from Presentation

The most critical step in reducing snapshot size is strictly isolating game simulation state from presentation assets. Rendering objects, 3D meshes, particle effects, audio triggers, and animation blend trees take up megabytes of memory and are non-deterministic or purely cosmetic.

Developers split the game loop into two distinct systems:

  • The Simulation State: The minimal set of variables required to determine the exact outcome of the next frame. This includes player positions, velocities, hurtboxes, hitboxes, current animation frame indices, health values, input buffers, and internal timers.
  • The Presentation Layer: A visual interpolation layer that reads the simulation state and renders graphics or plays audio.

Because the presentation layer can be completely recreated from the simulation state, rollback snapshots discard presentation data entirely. This reduces the raw data footprint of a single frame from hundreds of megabytes down to a few kilobytes or even bytes.

Plain Old Data (POD) and Monolithic Structs

In traditional object-oriented programming, game entities are scattered across heap allocations with pointers, vtables, and dynamic data collections. Traversing these structures causes cache misses and makes serialization slow.

For rollback architectures, developers enforce Plain Old Data (POD) layouts. The entire mutable state of a match is stored inside a single, flat C/C++ struct.

Key characteristics of this struct include:

  • No Pointers: Every field is a value type or an array of fixed maximum size. Pointers point to dynamic heap memory that can be invalidated during a state restore.
  • No Virtual Functions: Removing vtables eliminates hidden metadata pointers that bloat the struct size.
  • Fixed-Capacity Arrays: Dynamic lists (such as active projectiles) are replaced with fixed-capacity arrays sized to the theoretical maximum permitted by the game rules (e.g., a maximum of 4 active projectiles per character).

Because the state is a contiguous block of memory without pointers, saving and restoring the game state becomes a single, ultra-fast memory copy operation (memcpy) rather than a deep object traversal.

Bit-Packing and Data Quantization

To fit within strict target limits—often under 2 to 8 kilobytes per frame—developers aggressively compress data types inside the state struct:

  • Fixed-Point Math: Floating-point numbers introduce cross-platform determinism issues and waste precision. Developers use fixed-point integers (such as 32-bit or 16-bit integers) to represent world-space coordinates and velocities.
  • Bitfields for Boolean Flags: States such as grounded, airborne, blocking, invulnerable, and counter-hit states are packed into single-bit flags within bitfields or bitsets, turning dozens of standard booleans into a single 32-bit integer.
  • Enumeration Clamping: Enums indicating character actions or attack phases are typed explicitly as uint8_t or packed into custom bit-widths rather than the default compiler-dependent 32-bit integer.

Pre-Allocated Circular Buffers

Rollback engines, such as GGPO or proprietary equivalents, only need to simulate backward by the maximum anticipated network ping—typically between 7 and 15 frames.

Instead of allocating memory dynamically during gameplay, developers pre-allocate a fixed-size ring buffer (circular array) during match initialization. If the snapshot size is 4 KB and the rollback limit is 10 frames, the entire rollback history requires a static allocation of only 40 KB. Each incoming tick writes into the circular buffer at frame_number % buffer_size, overwriting the oldest state without generating heap fragmentation or triggering garbage collection.

Delta Compression Between Frames

For games with larger state requirements (such as tag-team fighters featuring multiple characters, assists, and complex physics), developers employ delta compression across the snapshot ring buffer.

Rather than storing a complete (keyframe) snapshot every tick, the engine stores a baseline keyframe followed by the difference (delta) of changed bytes for subsequent ticks. Because the majority of variables (such as maximum health, stage bounds, and inactive character data) remain static across consecutive frames, XOR delta encoding or run-length encoding (RLE) dramatically compresses intermediate frames. When a rollback occurs, the engine reconstructs the desired frame by applying sequential deltas to the nearest historical keyframe.