Game Programming · June 30, 2026
Save and Replay Systems in Shmups: Recording Inputs for Deterministic Playback
A replay in a shmup is not a video. It is a compressed record of the player's inputs and the initial state of the simulation. On playback, the game re-runs the entire simulation from that starting state, feeding the recorded inputs on the correct frames, and produces the same sequence of events the original play session did. This is elegant and compact — a five-minute run fits in a few kilobytes — but it requires something the game may not yet have: full determinism.
Published June 30, 2026
The replay system is the strictest possible test of simulation consistency. Any source of non-determinism — a random number that does not replay identically, a floating-point calculation that depends on execution order, a timer that reads wall-clock time — will cause the replay to diverge from the original, usually within the first thirty seconds. Implementing replays is therefore not an audio or serialization problem; it is first and foremost a disciplined engineering constraint on how the simulation is written.
Determinism preconditions
The simulation must be purely functional given its inputs. Every piece of game state that changes must be determined by the previous state plus the player's input on that frame. The three common violators are:
- Non-seeded RNG: Any call to a global random number generator that uses a runtime seed (e.g., the system clock) will produce different values in the replay. Every random call in gameplay code must use a seeded RNG whose seed is included in the replay file.
- Wall-clock time: Code that reads the actual time of day for anything other than display purposes will differ between the original run and the replay. Use the simulation's own frame counter as the canonical clock for all gameplay logic.
- Variable timestep: If the simulation uses delta-time scaling and any part of it is not perfectly frame-rate-independent, the replay will diverge at a different frame rate. A fixed-timestep simulation sidesteps this entirely.
What the replay file contains
Once the simulation is deterministic, the replay file is small. The minimum required contents:
Run-length encoding of the input stream (storing only frames where the input state changes, not a record on every frame) is the key to keeping file size manageable. A five-minute run at 60 fps is 18,000 frames, but most of those frames have identical input to the previous one. The change-only format shrinks the average input log to a few hundred entries.
File versioning and forward compatibility
A replay recorded against version 0.9.4 of the game will not play back correctly on version 1.0.0 if anything affecting the simulation changed between the two versions. Enemy move speed, bullet pattern parameters, collision hitbox sizes — any change to these makes old replays invalid. There are two approaches:
The strict approach is to embed the full game version in the replay header and refuse to play back replays from incompatible versions. This is honest but means replays become unplayable after patches. The archival approach is to version the simulation itself separately from the game build and ship older simulation modules alongside the current one, switching based on the replay's embedded simulation version. This is significantly more engineering effort but produces replays that remain valid indefinitely — a meaningful feature for competitive communities.
Most indie shmups use the strict approach and accept that replays have a limited lifespan. Communicating this clearly to players (a visible expiry warning in the replay metadata) is better than silently showing a desynchronized playback that the player interprets as a bug.
Divergence detection
Even with a carefully deterministic simulation, subtle bugs can cause replays to diverge in specific conditions. A hash check at regular intervals catches this early: during a replay, hash the full simulation state every 300 frames and compare against hashes stored in the replay file at record time. When a mismatch is detected, the replay system can log which frame the divergence occurred on and which state fields differ, rather than silently producing wrong output until the playback is visually obviously wrong.
Practical limits of long replays
Replays longer than about ten minutes introduce a usability problem: seeking. A user who wants to watch a specific boss fight from a 40-minute run cannot jump to minute 30 — the replay must re-simulate from frame zero to reach that point, which takes real time at game speed. Snapshot checkpoints solve this: every five minutes of simulation, record a full state snapshot. Seeking to a checkpoint then replaying forward from there takes at most five minutes of simulation time instead of forty.
State snapshots are much larger than the input log — a full simulation snapshot might be 50–200 KB depending on entity count — but the total overhead for a 40-minute run with snapshots every 5 minutes is seven snapshots plus the input log, which is still well under a few megabytes. For a competitive community where watching top scores and verifying records is a regular activity, the seeking capability the snapshots enable is worth every byte.