Rollback Core Pro
Production-ready, GGPO-style rollback netcode for Unreal Engine 5 — a deterministic fixed-step simulation loop, automatic state capture via SaveGame property reflection, a peer-to-peer UDP transport with input redundancy and reliable ACKs, OnlineSubsystem matchmaking, and an in-editor frame-scrubber debugger. Ships with three demo maps and a complete automation-test suite.
01 Overview
Rollback netcode is the prediction strategy invented for fighting games (GGPO, 2006) and now standard across the competitive genre — Street Fighter 6, Guilty Gear Strive, Skullgirls, Mortal Kombat 1. Instead of waiting for the remote player's input (delay-based netcode), each peer predicts that the opponent will repeat their last input and simulates immediately. When the real input arrives later, the simulation rolls back to the predicted frame, replays with the corrected input, and fast-forwards to the present. The local player feels zero latency.
The cost is CPU (resimulating up to a dozen frames per tick) and a hard requirement: the simulation must be deterministic. Same inputs in, same state out, every time.
Rollback Core Pro provides:
- A deterministic fixed-step simulation loop (
URollbackManager) that decouples gameplay from render rate. - Automatic per-frame state capture (
URollbackStateComponent) driven byUPROPERTY(SaveGame)reflection — no manual serialization code per actor. - A UDP peer-to-peer transport (
URollbackNetSubsystem) with input redundancy, reliable ACK/resend, heartbeats, and peer-timeout detection. - OnlineSubsystem-backed matchmaking with provider abstraction (LAN
NULL, EOS, Steam, or any configured OSS). - A frame-by-frame visual debugger with scrubbing, entity-state inspection, and desync checksum diff.
- A complete Blueprint surface via
URollbackNetworkBlueprintLibrary. - Performance instrumentation (
FRollbackPerformanceStats) covering sim time, rollback time, snapshot size, bandwidth, and rollback frequency. - Three demos: a basic top-down two-pawn rollback, a network packet-loss simulator, and a sprite-based 2D viking duel.
- Editor automation tests covering the full pipeline.
Rollback netcode is one of the highest-effort systems to build from scratch. The simulation loop, state capture, transport, prediction, resimulation, and debug tooling each take weeks to get right. This plugin is what was learned across multiple fighting-game prototypes, packaged so you can drop it in and ship.
How to read this manual
If you're new, follow sections 1–3 in order to install and run the demos, then read section 4 (Architecture) before writing any code. If you're integrating into an existing project, jump straight to Installation and API Reference. If you're debugging desyncs, head to Determinism and Visual Debugger.
Rollback.SpawnDemo
Rollback.NetHost 7777
Rollback.SpawnDebugger
Rollback.Perf
02 Quick Start
Before writing any code, run the included demo environments. Each demonstrates a different facet of the plugin and serves as a working reference you can read alongside this manual.
Demo 1 — Basic prediction & correction
Open any empty level and type into the Unreal console (default ~):
Rollback.SpawnDemo
The demo spawns two pawns. You control the Blue pawn with WASD. The Red pawn moves automatically — but its inputs reach your simulation delayed by 15 frames. When Red changes direction, your prediction fails, and you see Red snap to its true state while a cyan ghost trail shows the corrected timeline.
The local pawn keeps running; when remote input lands, frames are silently resimulated and the ghost trail visualizes the correction.
Demo 2 — Network packet-loss simulator
To exercise the UDP transport, packet-loss simulation, input redundancy, and rollback correction path in a single PIE session:
Rollback.SpawnNetworkDemo
The Blue pawn is local WASD. The Red pawn is driven through the rollback network buffer with simulated packet loss and latency. The on-screen HUD reports:
- Local + remote UDP endpoints
- Packets sent, packets received, simulated drops
- Resend count and pending reliable packets
- Rollback count and correction distance
Demo 3 — 2D Viking duel
The sprite-based fighting demo shows Rollback Core Pro in a genre-relevant setup with extracted transparent frames, metadata-driven animation, hitboxes, hurtboxes, blocking, hitstun, KO states, and rollback correction:
Rollback.SpawnVikingDemo
| Player | Move | Attack | Block |
|---|---|---|---|
| Player 1 | AD or arrow keys | J or left mouse | K or right mouse |
| Player 2 | Driven by delayed deterministic AI input — triggers rollback corrections continuously | ||
Use Rollback.SpawnDebugger alongside any demo to open the runtime frame-scrubber panel.
03 Installation
Requirements
| Requirement | Supported | Notes |
|---|---|---|
| Unreal Engine | 5.4 / 5.5 / 5.6 / 5.7 | Pre-built binary packages for each version on Fab |
| Platform | Windows 10/11 x64 | Other platforms compile but are not officially verified |
| Compiler | VS2022 (17.8+) or VS2026 | Source build only — binary plugins ship pre-compiled |
| Project type | C++ or Blueprint-only | Blueprint-only projects auto-build the plugin's runtime module on first launch if installed via Fab |
| OnlineSubsystem (optional) | NULL / EOS / Steam / custom | Required only for matchmaking; transport itself is provider-agnostic |
Install from Fab marketplace
- Open Epic Games Launcher → Library → Fab → search for Rollback Core Pro.
- Click Install to Engine and select your engine version (5.4–5.7).
- In your project, open Edit → Plugins, search for Rollback Core Pro, and check Enabled.
- Restart the editor when prompted. Done.
Install into project Plugins folder (source build)
- Copy or clone the
RollbackCorePro/folder intoYourProject/Plugins/. - Right-click your
.uprojectfile and choose Generate Visual Studio project files. - Open the resulting
.slnin Visual Studio and build the editor target. - Launch the editor — the plugin auto-enables on first load.
Even if your project has no Source/ folder, installing from Fab works fine. The marketplace package ships pre-compiled binaries for each supported engine version. You only need a source build if you want to modify the plugin itself.
Verify the installation
After enabling the plugin and restarting the editor, open the console (~) and run:
Rollback.SpawnDemo
If a two-pawn demo appears with rollback correction visualization, you're ready. If the command isn't recognized, the plugin failed to load — check Edit → Plugins → Networking → Rollback Core Pro and verify there are no compile errors in the Output Log.
Project layout reference
YourProject/
├── Plugins/
│ └── RollbackCorePro/
│ ├── RollbackCorePro.uplugin # Plugin descriptor
│ ├── Config/
│ │ └── FilterPlugin.ini # Files staged in packaged builds
│ ├── Content/
│ │ ├── Demo/ # Generated viking sprites + JSON
│ │ └── Maps/ # RC_BasicDemo, RC_NetworkDemo, RC_VikingDemo
│ ├── Docs/
│ │ ├── index.html # This manual
│ │ ├── editor-ui.md
│ │ ├── viking-demo.md
│ │ └── network-prediction-mover.md
│ ├── Resources/ # Plugin icon
│ └── Source/
│ ├── RollbackCorePro/ # Runtime module
│ │ ├── Public/ # Headers
│ │ └── Private/ # Implementations + Tests
│ └── RollbackCoreProEditor/ # Editor-only module (debugger, wizard)
04 Architecture
Rollback Core Pro is a runtime plugin built around three world subsystems, one actor component, and one Blueprint function library. Each piece has a single, well-bounded responsibility.
High-level architecture. The runtime module is the entire C++ surface; the editor module adds the debugger panel and setup wizard.
The simulation lifecycle
Standard Unreal games tick on render-frame cadence. Rollback netcode must tick at a fixed rate, identical across peers — otherwise the same input sequence won't produce identical results. URollbackManager takes over the gameplay loop using an accumulator:
// Conceptual per-engine-tick loop inside URollbackManager
Accumulator += DeltaTime;
while (Accumulator >= FixedTimeStep) { // FixedTimeStep = 1/60 by default
AdvanceFrame(); // — one deterministic step
Accumulator -= FixedTimeStep;
}
Each AdvanceFrame() does the following, in order:
- Increment
CurrentFrame. - For each registered entity (anything implementing
IRollbackEntity): callRollbackTick(dt, frame). This is where gameplay logic runs. - For each registered entity: call
SaveRollbackState(frame). The state component writes the actor'sSaveGameproperties + transform + velocity into the ring buffer.
The rollback path
When a late authoritative input arrives over UDP and changes a frame from the past:
- The network subsystem buffers the input under
(PlayerId, Frame). - Your gameplay code (or
ApplyBufferedInputsAndRollbackfrom the Blueprint library) callsURollbackManager::RollbackToFrame(EarliestMismatchFrame). - For each entity,
LoadRollbackState(Frame)restores the snapshot. - The manager replays frames
Frame+1..Currentwith the corrected input history. - Latest state is now authoritative; the visual shell (mesh, animation, camera) follows on the next render frame.
The player never sees a hitch because rendering is decoupled from simulation. Only the final resimulated state is visible on the next draw call.
05 Auto-State Saving
The most error-prone part of any rollback system is state capture — getting every byte of gameplay state into a snapshot, and back out, without missing a field or accidentally including non-deterministic data. Rollback Core Pro solves this by repurposing Unreal's existing SaveGame property flag.
The SaveGame flag
UE's reflection system already supports marking properties as part of a save file:
UPROPERTY(SaveGame, BlueprintReadWrite) int32 Health = 100;
UPROPERTY(SaveGame, BlueprintReadWrite) FVector SimulatedVelocity = FVector::ZeroVector;
UPROPERTY(SaveGame, BlueprintReadWrite) uint8 HitstunFramesRemaining = 0;
UPROPERTY(SaveGame, BlueprintReadWrite) uint8 FacingDirection = 0;
In Blueprints, you mark a variable as SaveGame by selecting it and toggling the SaveGame checkbox in the Advanced section of the Details panel.
Rollback Core Pro repurposes this same flag for per-frame snapshot/restore. One annotation, two uses — your rollback state list and your save-file state list are identical, which is almost always what you want anyway.
The component scans the owning actor once at BeginPlay, caches the SaveGame-flagged FProperty pointers, and writes them into the ring buffer each tick.
How the component works internally
On BeginPlay, URollbackStateComponent walks the owning actor's class properties via UE's reflection system. Every FProperty whose PropertyFlags contains CPF_SaveGame is cached into TrackedProperties. Iteration happens once; per-frame serialization just walks the cached array.
Each tick the component writes the tracked properties into an FArchive wrapping a TArray<uint8>, plus the actor's Transform and Velocity directly (cheap path, no Serialize overhead). The byte array is stored in StateBuffer[Frame]. On restore, the inverse happens.
What gets captured
| Captured automatically | Captured because flagged | Not captured |
|---|---|---|
| Actor world transform (location + rotation) Velocity (FVector) |
Any UPROPERTY(SaveGame) on the owning actor — integers, floats, FVectors, enums, structs, arrays of any of those, etc. |
Any UPROPERTY() without the SaveGame flagPlain C++ members not exposed to reflection UObject pointers (must be re-resolved deterministically) Sub-actors and components (each needs its own state component) |
Multi-actor and projectiles
Every actor that should be rolled back needs its own URollbackStateComponent. Projectiles, particle-driven hitboxes, status-effect carriers — give each a state component and they'll automatically participate in the rollback. The component registers itself with the world's URollbackManager on BeginPlay; you don't need to call anything manually.
Inspecting captured state
The component exposes diagnostics that are useful for inline UI or debug overlays:
| Property | Meaning |
|---|---|
LastSavedFrame | Frame index of the most recent snapshot |
LastRestoredFrame | Most recently restored frame |
LastSavedByteCount | Snapshot size — useful for budgeting |
LastSavedChecksum | FNV-style checksum of the snapshot bytes; compare across peers to detect desyncs |
GetTrackedPropertyCount() | How many SaveGame fields the component found at BeginPlay |
06 Deterministic Movement
Unreal's built-in CharacterMovementComponent and Chaos Physics are fundamentally non-deterministic across peers. Sub-tick interpolation, root-motion blending, physics sub-stepping, and floating-point error accumulation all cause two clients to diverge given identical inputs over enough frames.
Rollback Core Pro ships URollbackMovementComponent, a minimal deterministic alternative. It's intentionally tiny — it's a starting point you customize, not a full character controller.
What it does
URollbackMovementComponent::DeterministicMove(FVector InputVector, float DeltaTime) integrates input into actor location using:
- An explicit fixed-delta multiplication — never the variable engine
DeltaSeconds. - Input scaling clamped to the component's configured
MaxSpeed. - Truncation of intermediate floating-point results to a fixed precision, reducing per-platform float-rounding divergence.
Typical usage
void AMyFighter::HandleRollbackTick(float Dt, int32 Frame, FRollbackInput Input)
{
const FVector InputVec(Input.Axes.X, Input.Axes.Y, 0.f);
MoveComp->DeterministicMove(InputVec, Dt);
// Then deterministic combat logic here, reading from Input.Buttons
if (Input.Buttons & ButtonAttack) {
StartAttackFrame(Frame);
}
}
OnRollbackTick event:
→ URollbackMovementComponent → DeterministicMove(InputVector, DeltaTime)
→ Branch on Input.Buttons bits → custom attack/block logic
Do not use CharacterMovementComponent on the same actor you want to roll back. Pick one: either use this deterministic movement (or your own deterministic kinematic code), or follow the "separated systems" pattern in section 12 where Mover/NetPred handles locomotion and Rollback Core handles combat state only.
07 Network Transport
URollbackNetSubsystem is a thin, purpose-built UDP transport for rollback inputs. It is symmetric — any peer can be a host. It supports up to 8 peers per match. It is intentionally not wrapped through Unreal's replication system, because rollback transport has different priorities (latency > ordering > reliability) than gameplay replication.
Wire packet layout
Every packet is a single UDP datagram, capped by MaxPacketBytes (default 1200, safe under typical MTU):
Compact, hand-rolled wire format. Total overhead per packet: 21 bytes of header + 5 bytes per repeated input frame.
Reliability model
Input packets are sent unreliably by default — the redundancy window almost always covers transient packet loss without round-trips. For data that must arrive (initial Hello, configuration changes), the transport supports a reliable mode:
- Reliable packets receive a unique sequence number and are queued in
PendingReliable. - Each incoming packet carries a cumulative ACK of the highest contiguous sequence the sender has received.
- If a reliable packet hasn't been ACKed after
ResendAfterSeconds(default 80 ms), it is resent. - After
MaxReliableRetryCountresends (default 20), the peer is considered failed andOnPeerMaxRetriesExceededfires.
Peer lifecycle
- Connection starts when either side calls
ConnectToPeer(PlayerId, Host, Port)or accepts an incoming Hello. - Heartbeats are sent at
HeartbeatIntervalSeconds(default 2 s) if no other packet has been sent recently. - If no packet is received for
PeerTimeoutSeconds(default 10 s), the peer is dropped andOnPeerDisconnectedfires.
Usage from C++
URollbackNetSubsystem* Net = GetWorld()->GetSubsystem<URollbackNetSubsystem>();
// 1. Open the UDP socket.
FRollbackTransportConfig Config;
Config.LocalPort = 7777;
Config.InputRedundancyFrames = 8;
FString Error;
if (!Net->StartUdpPeer(Config, Error)) {
UE_LOG(LogTemp, Error, TEXT("Transport failed: %s"), *Error);
return;
}
// 2. Connect to a remote peer.
Net->ConnectToPeer(/*PlayerId*/ 2, TEXT("203.0.113.42"), 7777, Error);
// 3. Each deterministic frame, send local input.
Net->SendInputFrame(LocalPlayerId, CurrentFrame, MyInput, /*bReliable*/ false);
// 4. Apply received remote input and trigger rollback if predictions diverged.
bool bChanged = false;
int32 EarliestChangedFrame = -1;
Net->ApplyBufferedInputsToState(RemoteStateComp, RemotePlayerId,
CurrentFrame - 15, CurrentFrame,
bChanged, EarliestChangedFrame);
if (bChanged) {
Manager->RollbackToFrame(EarliestChangedFrame);
}
Usage from Blueprint
URollbackNetworkBlueprintLibrary wraps the common cases:
MakeLoopbackTransportConfig(LocalPort, PacketLossPercent, MinLatencyMs, MaxLatencyMs)
StartLoopbackPacketLossTransport(WorldContext, LocalPort, ...)
SendRollbackInputForCurrentFrame(WorldContext, PlayerId, Input, bReliable)
SendRollbackInputToAllPeers(WorldContext, PlayerId, Input, bReliable)
ApplyBufferedInputsAndRollback(WorldContext, RemoteStateComp, RemotePlayerId,
FromFrame, ToFrame, bOutRolledBack, OutRollbackFrame)
ConnectToRemotePeer(WorldContext, PlayerId, RemoteHost, RemotePort)
GetConnectedPeerIds(WorldContext) / GetAllPeerInfo(WorldContext)
Local two-instance smoke test
Run two editor instances or two machines on the same LAN. On the host:
Rollback.NetHost 7777 RollbackCoreProSmoke 0
On the client, replace the address with the host's IP:
Rollback.NetClient 192.168.0.10 7777 7778 0
The final numeric argument is simulated packet loss percent. Use 10 for a stress test:
Rollback.NetClient 192.168.0.10 7777 7778 10
Configuration reference
| Field | Default | Purpose |
|---|---|---|
LocalPort | 7777 | UDP bind port (0 = auto) |
RemoteHost / RemotePort | "" / 7778 | Optional auto-connect on start |
SocketSubsystemName | NAME_None | Override OS sockets, e.g. for platform P2P |
MaxPacketBytes | 1200 | Safe UDP MTU floor |
InputRedundancyFrames | 8 | How many past frames each packet repeats |
ResendAfterSeconds | 0.08 | Reliable resend timeout |
MaxReliableRetryCount | 20 | Resends before declaring peer failed |
HeartbeatIntervalSeconds | 2.0 | Keepalive interval |
PeerTimeoutSeconds | 10.0 | Drop peer after this much silence |
MaxPeers | 4 | Hard cap on connected peers (≤ 8) |
bAcceptFirstRemotePeer | true | If true, an inbound Hello from any address creates a peer |
bEnablePacketLossSimulation | false | Master switch for the simulated network |
SimulatedOutgoingPacketLossPercent | 0 | Drop outgoing packets by chance |
SimulatedIncomingPacketLossPercent | 0 | Drop incoming packets by chance |
SimulatedMinLatencyMs / SimulatedMaxLatencyMs | 0 / 0 | Range of artificial delay applied to delivered packets |
08 Platform Matchmaking
For LAN testing or shipping platform integration, URollbackNetSubsystem can advertise and discover matches through Unreal's configured OnlineSubsystem. The session itself carries the rollback transport's UDP endpoint as session settings, so a client that joins a session immediately knows where to point its UDP socket.
Provider matrix
| Provider | Use for | Setup |
|---|---|---|
NULL | LAN tests, in-editor PIE | Enabled by default in this sample project — no credentials required |
EOS | Cross-platform shipping | Configure EOS plugin + Developer Portal credentials in host project |
Steam | Steam-only shipping | Steamworks SDK + AppID configured in DefaultEngine.ini |
| Custom | First-party platforms (PSN, Xbox Live, etc.) | Implement a custom IOnlineSubsystem; matchmaking abstraction is provider-agnostic |
Host flow
FRollbackTransportConfig Config;
Config.LocalPort = 7777;
FString Error;
RollbackNetSubsystem->StartUdpPeer(Config, Error);
// Advertise the rollback endpoint via OnlineSubsystem.
RollbackNetSubsystem->CreatePlatformSession(
/*SessionName*/ FName("RollbackCorePro"),
/*MatchId */ "Ranked-1v1",
/*PublicConn */ 2,
/*bLan */ false,
Error);
Client flow
// 1. Search.
RollbackNetSubsystem->FindPlatformSessions("Ranked-1v1", /*MaxResults*/ 20, /*bLan*/ false, Error);
// 2. Pick a result (delivered via OnMatchmakingResult delegate).
RollbackNetSubsystem->OnMatchmakingResult.AddDynamic(this, &UMyMenu::HandleResult);
// 3. Join.
RollbackNetSubsystem->JoinPlatformSessionByIndex(ResultIndex, Error);
On successful join, the transport auto-connects its UDP socket to the host's advertised endpoint and OnMatchmakingComplete fires with bWasSuccessful = true.
Delegates
| Delegate | Fires when |
|---|---|
OnMatchmakingResult | Each session that matches a FindPlatformSessions query (provider, ID, owner, ping, host:port) |
OnMatchmakingComplete | Async create / find / join operation finishes; carries success bool + error string |
OnTransportConnected | The UDP transport has a remote peer talking back |
OnPeerDisconnected | A peer timed out or was explicitly disconnected |
OnPeerMaxRetriesExceeded | A reliable packet exhausted its resend budget |
Provider validation
Before shipping a new provider, validate the OSS is loaded:
Rollback.NetProvider default 1
Rollback.NetProvider NULL 1
Rollback.NetProvider EOS 1
Rollback.NetProvider STEAM 1
Rollback.NetFindNull RollbackCoreProSmoke 20
The first three forms check that the named provider is loaded and exposes a session interface. The last one performs an actual LAN session search using OnlineSubsystemNull, which is the fastest in-editor sanity check.
The matchmaking layer publishes the host's advertised address and port as session settings. Public IPs and LAN setups work out of the box. For NAT-restricted setups, configure a platform-specific socket subsystem (EOS P2P, Steam Sockets) and the transport will route through it rather than raw UDP.
09 Visual Debugger
Rollback bugs are invisible by default — the player just sees a snap or a desync. The debugger surfaces what was actually happening on each historical frame.
Inline ghost trails
When bEnableVisualDebugging is true on URollbackManager, the manager continuously renders a cyan ghost (drawn via DrawDebugBox) at the actor's true position from DebugLiveFrameLag frames ago (default 5). If your local prediction is drifting, the ghost separates from the live actor.
Frame-scrubber panel
For frame-by-frame inspection, spawn the runtime debugger panel:
Rollback.SpawnDebugger
The panel reads from URollbackManager's saved frames and surfaces:
- The currently-selected frame number (scrub via slider or commands)
- All registered rollback entities at that frame
- Per-entity saved byte count, checksum, location, velocity, and the input that was used
- Yellow ghost renders for the selected frame's state
- Cyan trail renders for the frames between the selected frame and live
Scrubber console interface
| Command | Effect |
|---|---|
Rollback.Debugger.Follow 1 | Resume following the live frame |
Rollback.Debugger.Follow 0 | Pause at current scrub frame |
Rollback.Debugger.Frame 120 | Jump to absolute frame 120 |
Rollback.Debugger.Step -1 | Step back one frame |
Rollback.Debugger.Step 1 | Step forward one frame |
Programmatic access
C++ and Blueprint projects can use the same data without the panel:
const TArray<int32> AvailableFrames = Manager->GetAvailableDebugFrames();
const TArray<FRollbackDebugFrameRecord> Records = Manager->GetDebugFrameRecords(Frame);
Manager->SetDebugScrubFrame(Frame);
Manager->StepDebugScrubFrame(+1);
Manager->SetDebugScrubFollowLive(true);
Desync detection
The state component stores an FNV-style checksum of every snapshot. The manager compares pre-rollback vs post-rollback checksums for the same frame; any mismatch indicates a non-deterministic actor and fires the OnDesyncDetected delegate with:
- The frame where the divergence was detected
- The entity name that desynced
- The XOR of the two checksums (useful for rough bucketing of error types)
If OnDesyncDetected fires repeatedly on the same actor, set Rollback.Debugger.Frame <F> to the reported frame and inspect FRollbackDebugFrameRecord::Input — comparing inputs at the moment of divergence almost always reveals the source.
10 Performance & Tuning
Rollback's CPU cost is proportional to (rollback depth) × (per-frame sim cost) × (number of entities). The plugin instruments all three so you can tune confidently.
Performance stats
URollbackNetSubsystem::GetPerformanceStats() returns an FRollbackPerformanceStats struct sampled over a rolling 60-frame window plus 1-second bandwidth window:
| Field | What it measures |
|---|---|
AvgSimulationTimeMs / MaxSimulationTimeMs | How long one normal AdvanceFrame() takes |
AvgRollbackTimeMs / MaxRollbackTimeMs | How long a RollbackToFrame() call takes (restore + replay) |
MaxRollbackDepthFrames | Largest rollback distance seen recently |
TotalRollbackCount / RollbacksInLastSecond | Frequency tracking |
AvgStateSnapshotBytes | How much memory each frame's snapshot consumes |
AvgStateSerializeTimeMs / MaxStateSerializeTimeMs | Snapshot serialization cost |
BytesSentPerSecond / BytesReceivedPerSecond | Transport bandwidth |
ConnectedPeerCount | Current peer count |
RegisteredEntityCount | How many entities the manager simulates |
DesyncCount | Cumulative desync detections |
FramesSimulated / FramesWithRollback | Lifetime counters |
Quick dump
Rollback.Perf
Prints a multi-line summary to the on-screen log and the output log.
Tuning levers
| If you're seeing... | Try... |
|---|---|
High AvgRollbackTimeMs | Reduce FixedTickRateHz (e.g. 60→30) or trim per-frame sim cost. Reduce MaxRollbackDepthFrames to cap worst-case. |
High AvgStateSnapshotBytes | Remove unused SaveGame flags. Pack booleans into bitmasks. Use smaller integer types. |
High BytesSentPerSecond | Lower InputRedundancyFrames. Send less often (every 2 ticks instead of every tick). |
High DesyncCount | You have non-deterministic gameplay. See section 11. |
Large RollbacksInLastSecond | Either your network is genuinely lossy, or your prediction is wrong too often. Inspect via the visual debugger. |
Memory footprint estimate
Default MaxBufferSize on the state component is 60 frames. With 4 entities averaging 64 bytes of SaveGame state each, that's 60 × 4 × 64 = 15 KB of rolling snapshot memory. Comfortable for any platform.
11 Determinism Rules
If "same inputs in, same state out" breaks for one entity on one frame, every peer's prediction silently diverges. This section is the canonical reference for what you may and may not do inside rollback-authoritative code.
The do / don't matrix
| Safe inside rollback authority | Will cause desyncs |
|---|---|
UPROPERTY(SaveGame) POD state (int, float, FVector, enum, FName, FString) |
CharacterMovementComponent on the rolled-back actor |
| Integer-tick countdowns and hitstun/freeze timers | Mover plugin on the rolled-back actor |
| Deterministic LUTs and frame-data tables loaded once at startup | Chaos physics simulation on the rolled-back actor |
Seeded FRandomStream where the seed is derived from the frame index |
Latent Blueprint actions, Delay nodes, FTimerManager callbacks |
Fixed-step integration via URollbackMovementComponent::DeterministicMove |
FMath::Rand* without a frame-derived seed |
Anything entirely driven by the current frame's FRollbackInput |
Wall-clock time (FDateTime::Now, FPlatformTime::Seconds) as gameplay input |
Bitmask reads on Input.Buttons and component reads on Input.Axes |
Async asset loads gating gameplay behavior |
| Reading data tables, curves, and content via cached pointers | Floating-point math that depends on CPU/SSE rounding mode |
The two-layer pattern
The pattern every shipping rollback game uses is what GGPO calls the deterministic core plus visual shell:
What is rolled back
- Logical position (FVector)
- Health, meter, status effects
- Hitbox/hurtbox active flags
- Hitstun and freeze counters
- Facing direction, blocking state
- Combat state machines
What follows the corrected state
- Skeletal mesh animation playback
- Particle effects and VFX
- Sound effects and music
- Camera shake and zoom
- UI flashes and screen-space FX
- Decals and screen-space effects
The visual shell is allowed to be non-deterministic — it can use timelines, latent actions, async loads, anything you want. It just reads from the core's state and reacts. If a rollback happens, the visual shell will see the corrected state and naturally update; it never participates in the rollback itself.
Verifying determinism
Two tools detect non-determinism early:
- Checksum monitoring. Bind
OnDesyncDetectedand log when the post-rollback state for any entity differs from the original snapshot. If this fires ever during normal gameplay, you have a non-deterministic actor. - The packet-loss demo. Run
Rollback.SpawnNetworkDemowith high simulated loss (30%+). If gameplay diverges visibly between local and remote pawns, something in the rolled-back code is non-deterministic.
In order of how often they trip projects: (1) using CharacterMovementComponent on a rolled-back actor, (2) reading FApp::GetDeltaTime() inside rollback code instead of the explicit step delta, (3) iterating TMap/TSet (unordered) instead of TArray inside rollback code, (4) random number generation without a frame-derived seed.
12 Network Prediction & Mover Integration
Rollback Core Pro is intentionally a standalone deterministic layer. It does not wrap Unreal's Network Prediction plugin or Mover because the two systems solve different problems with different assumptions.
| Rollback Core Pro | Network Prediction / Mover | |
|---|---|---|
| Topology | Symmetric peer-to-peer | Server-authoritative |
| Tick model | Fixed-step, both peers run identical sim | Variable, server reconciles client predictions |
| Correction style | Resimulate N frames from snapshot | Receive server state, blend/snap to it |
| State capture | SaveGame reflection, automatic | Move record structs, manually authored |
| Best for | Compact deterministic gameplay (fighting, combat, projectiles) | Predicted locomotion that interops with Epic's movement stack |
When to use which
- Rollback Core Pro only — fighting games, platform fighters, deterministic projectile combat, small symmetric multiplayer prototypes
- Network Prediction / Mover only — shooters, racing games, anything where the server is authoritative and the client is predicting locomotion
- Both, layered — Mover handles locomotion (movement, jumping, traversal), Rollback Core Pro handles combat state on top (hit windows, blocking, freeze frames, projectile interactions). The combat layer rolls back; the movement layer does not.
Recommended integration patterns
- Deterministic core + visual shell. Rollback Core Pro simulates compact gameplay state. Meshes, animation, VFX, audio, and cameras follow the corrected state after rollback.
- Separated systems. Mover or Network Prediction owns locomotion. Rollback Core Pro owns deterministic combat, projectile, hit-validation, or frame-data logic. The two systems exchange information only through clean read interfaces — no shared mutable state.
- Prototype migration. Use Rollback Core Pro to validate rollback game feel during prototyping. Migrate production movement to Network Prediction or Mover when the project needs Epic's server-authoritative pipeline, but keep the combat layer on Rollback Core Pro.
Any mixed architecture must be tested under forced packet loss, latency, and repeated rollback. A system is safe to include in the rollback authority only if resimulating the same frame range from the same input history produces the same gameplay state, every time.
13 API Reference
URollbackManager — world subsystem
| Member | Type | Description |
|---|---|---|
AdvanceFrame() | method | Tick all registered entities one fixed step; snapshot state |
RollbackToFrame(Frame, EarliestMismatch = -1) | method | Restore state at Frame, replay forward to current |
RegisterEntity(IRollbackEntity) | method | Add an entity to the simulated set (auto-called by URollbackStateComponent) |
UnregisterEntity(IRollbackEntity) | method | Remove from the simulated set |
DrawDebugState(Frame) | method | Render cached state for a frame |
GetDebugFrameRecords(Frame) | method → TArray<FRollbackDebugFrameRecord> | Per-entity inspectable record for a frame |
GetAvailableDebugFrames() | method → TArray<int32> | All frame numbers currently in the ring buffer |
SetDebugScrubFrame(Frame) / StepDebugScrubFrame(Δ) | method | Programmatic scrubber control |
CurrentFrame | int32 | The live simulation frame index |
RollbackCount / LastRollbackFrame / LastRollbackFramesReplayed | int32 | Lifetime rollback diagnostics |
DesyncCount / LastDesyncFrame | int32 | Desync diagnostics |
OnDesyncDetected | delegate (Frame, EntityName, ChecksumMismatch) | Fires when post-rollback checksum diverges from snapshot |
bEnableVisualDebugging | bool | Toggle inline ghost rendering |
DebugLiveFrameLag | int32 | How many frames behind to draw the ghost |
URollbackStateComponent — per-actor component
| Member | Type | Description |
|---|---|---|
CurrentLocalInput | FRollbackInput | Write here before AdvanceFrame; used as the input for the next tick |
InjectInputForFrame(Frame, Input) | method | Authoritatively override the input that was used at a past frame |
GetInputForFrame(Frame) | method → FRollbackInput | Read the input recorded at a past frame |
OnRollbackTick | Blueprint event (Δt, Frame, Input) | Fires during each deterministic tick — main gameplay hook for Blueprint actors |
OnRollbackTickDelegate | delegate (Δt, Frame, Input) | C++ counterpart of the above |
MaxBufferSize | int32 (default 60) | Ring-buffer length for state and input history |
LastSavedFrame / LastRestoredFrame | int32 | Most recent snapshot/restore frame |
LastSavedByteCount | int32 | Size of the most recent snapshot in bytes |
LastSavedChecksum / LastRestoredChecksum | int32 | FNV-style checksum of the snapshot bytes |
GetTrackedPropertyCount() | method → int32 | How many SaveGame properties the component is capturing |
URollbackNetSubsystem — world subsystem
| Member | Description |
|---|---|
StartUdpPeer(Config, Error) | Open the UDP socket, start ticking the transport |
StopTransport() | Close the socket and tear down all peers |
ConnectToPeer(PlayerId, Host, Port, Error) | Add a remote peer (sends a Hello and waits for acceptance) |
DisconnectPeer(PlayerId) | Drop a peer |
SendInputFrame(PlayerId, Frame, Input, bReliable) | Send one input frame; unreliable by default |
ConsumeRemoteInput(PlayerId, Frame, OutInput) | Pop one received input |
BufferRemoteInputForRollback(PlayerId, Frame, Input) | Queue a received input for the rollback path |
ApplyBufferedInputsToState(StateComp, PlayerId, FromFrame, ToFrame, bOutChanged, OutEarliestChanged) | Apply buffered inputs and report what changed |
IsTransportRunning() / IsConnectedToPeer() | State queries |
GetTransportStats() / GetPerformanceStats() | Diagnostics |
GetRemoteEndpointString() / GetLocalEndpointString() | Human-readable endpoints |
GetConnectedPeerIds() / GetAllPeerInfo() | Multi-peer queries |
FlushTransport() | Force send/receive immediately (DevelopmentOnly) |
CreatePlatformSession(Name, MatchId, Connections, bLan, Error) | Advertise the rollback endpoint via OnlineSubsystem |
FindPlatformSessions(MatchId, MaxResults, bLan, Error) | Search for advertised sessions |
JoinPlatformSessionByIndex(ResultIndex, Error) | Join a result and auto-connect the UDP transport |
DestroyPlatformSession(Name) | Remove the platform session |
ValidateOnlineProvider(SubsystemName, bRequireSessions, OutStatus) | Provider diagnostic check |
GetCachedMatchmakingResults() | Last search results |
| delegates | OnTransportConnected, OnTransportError, OnRemoteInputReceived, OnMatchmakingResult, OnMatchmakingComplete, OnPeerDisconnected, OnPeerMaxRetriesExceeded, OnDesyncDetected |
URollbackNetworkBlueprintLibrary — Blueprint helpers
Static helpers that wrap the most common URollbackNetSubsystem calls in WorldContext-aware Blueprint nodes. See section 7 for the full list.
Core data types
| Type | Fields |
|---|---|
FRollbackInput | int32 Buttons (bitmask), FVector Axes |
FRollbackFrameState | TArray<uint8> ActorData, FVector Location, FQuat Rotation, FVector Velocity |
FRollbackTransportConfig | See section 7 reference table |
FRollbackTransportStats | Packet/byte counters, RTT, peer count |
FRollbackPerformanceStats | Sim/rollback/serialize timing, bandwidth, rollback frequency |
FRollbackPeerInfo | Per-peer PlayerId, endpoint, connection state, RTT, packet counts |
FRollbackMatchmakingResult | SessionId, provider, owner, host:port, ping, open slots |
FRollbackDebugFrameRecord | Frame, entity name, state availability, location/rotation/velocity, byte count, checksum, input |
Interfaces
| Interface | Purpose |
|---|---|
IRollbackEntity | Implemented by URollbackStateComponent. Override RollbackTick, SaveRollbackState, LoadRollbackState if you implement your own entity type. |
IRollbackInputProvider | Implement on your pawn. GetRollbackInput(OutInput) is called once per deterministic tick. |
14 Console Commands
All commands are registered globally and operate on the active UWorld's subsystems. Open the console with ~.
| Command | Arguments | Purpose |
|---|---|---|
Rollback.SpawnDemo | — | Spawn the basic two-pawn demo |
Rollback.SpawnNetworkDemo | — | Spawn the network packet-loss demo |
Rollback.SpawnVikingDemo | — | Spawn the 2D viking duel demo |
Rollback.SpawnDebugger | — | Spawn the frame-scrubber debugger panel |
Rollback.NetHost | [LocalPort=7777] [MatchId] [LossPercent=0] [MaxPeers=4] | Start UDP transport and advertise via OnlineSubsystemNull |
Rollback.NetClient | <RemoteHost> <RemotePort> [LocalPort=7778] [PlayerId=-1] [LossPercent=0] | Start UDP transport and connect to a host |
Rollback.NetConnect | <RemoteHost> <RemotePort> <PlayerId> | Add an additional peer to a running transport |
Rollback.NetDisconnect | <PlayerId> | Drop a peer by ID |
Rollback.NetPeers | — | List connected peers with RTT and packet counts |
Rollback.NetProvider | [SubsystemName=default] [RequireSessions=1] | Validate that a named OnlineSubsystem is loaded |
Rollback.NetFindNull | [MatchId] [MaxResults=20] | Find OnlineSubsystemNull LAN sessions |
Rollback.Perf | — | Dump full performance statistics |
Rollback.Debugger.Follow | <0|1> | Pause / resume live-frame following in the debugger |
Rollback.Debugger.Frame | <Frame> | Jump to an absolute frame |
Rollback.Debugger.Step | <Δ> | Step the scrub frame by a delta |
For fast iteration in PIE, add bindings to your project's DefaultInput.ini:
+ConsoleKeys=Tilde
+ActionMappings=(ActionName="ToggleDebugger",Key=F2,bShift=False,bCtrl=False,bAlt=False,bCmd=False)
15 Project Settings
Surfaced under Edit → Project Settings → Plugins → Rollback Core Pro. Backed by URollbackCoreProSettings (UDeveloperSettings) and persisted to DefaultEngine.ini.
| Group | Setting | Default | Range | Purpose |
|---|---|---|---|---|
| Simulation | FixedTickRateHz | 60 | 15–240 | Deterministic simulation rate. Must match across all peers. |
| Simulation | MaxRollbackDepthFrames | 12 | 0–240 | Soft target for the deepest rollback the sim will perform. |
| Networking | DefaultLocalPort | 7777 | 1024–65535 | Used by Blueprint helpers and demos. |
| Networking | DefaultInputRedundancyFrames | 8 | 0–32 | Past input frames each packet repeats. |
| Networking | DefaultMaxPeers | 4 | 1–8 | Peer cap for default transport configs. |
| Networking | DefaultResendAfterSeconds | 0.08 | 0.01–2.0 | Reliable resend timeout. |
| Networking → Reliability | DefaultHeartbeatIntervalSeconds | 2.0 | 0.1–30.0 | Keepalive interval. |
| Networking → Reliability | DefaultPeerTimeoutSeconds | 10.0 | 1.0–120.0 | Peer timeout. |
| Debug | bEnableVisualDebuggingByDefault | true | bool | Inline ghost rendering on by default. |
| Debug | DebugLiveFrameLag | 5 | 0–60 | How many frames behind to draw the ghost. |
| Debug | bAutoOpenStatsPanelInPIE | false | bool | Auto-open the stats panel on first PIE each session. |
| Onboarding | bShowSetupWizardOnStartup | true | bool | Show the setup wizard once when the project loads. |
Settings are read by URollbackManager::Initialize and by any code that constructs an FRollbackTransportConfig from defaults. Changing them while the editor is running takes effect on the next PIE session or world load.
16 Automation Tests
The plugin ships with automation coverage under WITH_DEV_AUTOMATION_TESTS in Source/RollbackCorePro/Private/Tests/RollbackCoreProAutomationTests.cpp. Run them from the Session Frontend (Tools → Test Automation → Automation) or via the command line:
"<UE_path>\Engine\Binaries\Win64\UnrealEditor-Cmd.exe" YourProject.uproject ^
-ExecCmds="Automation RunTests RollbackCorePro; Quit" ^
-unattended -nop4 -nosplash -nullrhi
What's covered
| Test | Validates |
|---|---|
State.SaveRestore | SaveGame property round-trip through the state component |
Rollback.LateInputCorrection | The canonical rollback case: late input changes a past frame |
Network.BufferApply | Received input applied to a state component, change detected |
Debug.HistoryScrub | Frame-by-frame scrubber data integrity |
VikingDemo.MetadataCombat | Metadata-driven hit detection in the viking demo |
Network.UdpLoopbackSmoke | Two bound peers exchange a reliable rollback input through real sockets |
Network.MultiPeerConnect | Three peers connect and exchange input simultaneously |
Network.PeerDisconnect | Heartbeat timeout detects a vanished peer |
Matchmaking.NullProviderValidation | OnlineSubsystemNull diagnostic check |
Desync.Detection | Checksum-based desync surfacing |
Performance.Stats | Performance counters populate after sim and rollback |
17 Troubleshooting & FAQ
The plugin won't enable / the console commands are not recognized.
The plugin failed to compile or load. Open Edit → Plugins and look for a yellow warning icon next to Rollback Core Pro — clicking shows the load error. Common causes:
- The plugin folder was placed inside
YourProject/Content/instead ofYourProject/Plugins/. - For source builds, you didn't regenerate the Visual Studio project files after copying the plugin in.
- OnlineSubsystem dependencies failed to load. Open
Saved/Logs/YourProject.logand search forLogPluginManager.
Predictions look perfect locally but the remote pawn rubber-bands.
Either the remote inputs are arriving outside the InputRedundancyFrames window (raise the value), or your rollback path isn't actually rolling back when it should. Check Rollback.Perf — if TotalRollbackCount isn't growing, your code isn't calling RollbackToFrame. The Blueprint helper ApplyBufferedInputsAndRollback handles this for you; if you're going manual, verify your call site.
The pawn slides indefinitely after I press a movement key.
You're applying input persistently. FRollbackInput should represent the input for that frame only. If you set Axes to a non-zero value and never clear it, every frame will use it. Either reset CurrentLocalInput at the start of input gathering, or only set it when input is actually held.
OnDesyncDetected fires on the same actor every match.
An actor with a state component is doing something non-deterministic during RollbackTick. The most common culprit is iterating a TMap or TSet inside the tick — iteration order is not stable. Use TArray for any container you walk inside rolled-back code.
How do I support Steam matchmaking?
Enable the OnlineSubsystem Steam plugin in your project, add your Steam AppID to DefaultEngine.ini under [OnlineSubsystem] / [OnlineSubsystemSteam], and verify with Rollback.NetProvider STEAM 1. The matchmaking API calls are unchanged — the provider abstraction handles the rest.
The visual debugger panel doesn't render anything.
The panel renders state from the world's URollbackManager. If no rollback entities are registered, there's nothing to show. Confirm by running Rollback.SpawnDemo first — if the panel shows demo entities, your gameplay code isn't registering with the manager (which usually means no URollbackStateComponent on your actors).
Can I use this with dedicated servers?
The transport is symmetric — any peer can host. There's no client/server asymmetry in the protocol. For a server-authoritative topology, run a headless peer and treat its state as ground truth. The plugin won't object.
What's the maximum match size?
Hard cap is 8 peers (protocol limit). Practical limit depends on per-frame simulation cost — every rollback resimulates every registered entity. Two-player fighting games are well within budget at 60 Hz with depth-12 rollbacks on a 2019 CPU. Heavier simulations should reduce MaxRollbackDepthFrames or drop the tick rate.
How do I integrate this with GAS (Gameplay Ability System)?
GAS ability execution is generally deterministic if you avoid latent tasks, WaitDelay, and async loads inside abilities. Replication mode Mixed or Minimal is friendlier. The recommended pattern is to keep abilities purely as input → instant state change, then let Rollback Core Pro snapshot the resulting attributes via SaveGame-flagged FGameplayAttributeData.
18 Glossary
| Term | Meaning |
|---|---|
| Fixed-step tick | Simulation runs at a constant rate (60 Hz here) regardless of render framerate, ensuring identical state evolution across peers. |
| Rollback | Restoring the simulation to a past frame and replaying with corrected inputs. |
| Rollback depth | How many frames the simulation goes back during a single RollbackToFrame call. |
| Prediction | Filling in unknown remote input by repeating the most recent known input. |
| Snapshot / state capture | Writing the simulation state into a buffer that can be restored later. |
| Desync | Two peers diverging in simulation state despite identical inputs. Caused by non-determinism. |
| Input redundancy | Repeating recent input frames in every packet so transient loss doesn't stall the simulation. |
| Reliable ACK | Acknowledgment mechanism for packets that must be delivered; absence triggers resend. |
| Hitstun | Combat state where a character is unable to act after being hit. Always integer-frame counted. |
| Visual shell | The non-deterministic rendering layer (mesh, anim, VFX, audio) that follows the deterministic core. |
| OnlineSubsystem | Unreal's pluggable abstraction over platform online services (LAN NULL, EOS, Steam, custom). |
| Peer-to-peer (P2P) | Topology where each player is equally authoritative; no central server. This plugin is P2P. |
19 Rollback Core (OSS) vs Rollback Core Pro
A stripped-down open-source distribution — Rollback Core — is available on GitHub at gregorik/Rollback-Core under the MIT license. It contains the same deterministic simulation, transport, and basic demo. Rollback Core Pro (this product) adds matchmaking, the visual debugger, multi-engine-version packages, additional demos, and direct support.
| Feature | Rollback Core (OSS) | Rollback Core Pro |
|---|---|---|
| Price | Free, MIT | Paid, Fab EULA |
| Source available | Yes | Yes |
| Engine versions | 5.7 verified | 5.4 / 5.5 / 5.6 / 5.7 packages |
| Support | Community (GitHub Issues) | Direct author support |
| Core simulation | ||
| Deterministic fixed-step tick | ✓ | ✓ |
SaveGame-reflection state capture | ✓ | ✓ |
RollbackToFrame API | ✓ | ✓ |
| Per-frame ring-buffer history | ✓ | ✓ |
| Network transport | ||
| UDP transport | ✓ | ✓ |
| Input redundancy + reliable ACK | ✓ | ✓ |
| Multi-peer (up to 8) | ✓ | ✓ |
| Heartbeats + peer-timeout detection | ✓ | ✓ |
| Simulated packet loss + latency | ✓ | ✓ |
| Matchmaking | ||
| OnlineSubsystem LAN session discovery | — | ✓ |
| OnlineSubsystem EOS / Steam ready | — | ✓ |
CreatePlatformSession / Find / Join | — | ✓ |
| Editor tooling | ||
| Visual frame-scrubber debugger | — | ✓ |
| Per-frame state ghost rendering | — | ✓ |
| Desync inspector with checksum diff | — | ✓ |
| Setup wizard on first project load | — | ✓ |
| Demos & content | ||
| Basic top-down demo with correction markers | ✓ | ✓ |
| Network-packet-loss simulator demo | — | ✓ |
| 2D Viking fighter demo (sprite, hitboxes, combat) | — | ✓ |
When to use which
FREE / MIT
Rollback Core (OSS)
- Prototyping rollback into an existing project
- You already have matchmaking (Steamworks, EOS, custom backend)
- Comfortable building your own debug UI
- Open project or commercial title that can't accept a marketplace EULA
- Only targeting one engine version
COMMERCIAL · FAB
Rollback Core Pro
- OnlineSubsystem-backed matchmaking out of the box
- Visual frame-scrubber debugger for desync hunting
- 5.4–5.7 binary packages without maintaining backports
- Worked 2D fighter demo as a starting point
- Time-to-first-rollback-match matters more than license cost