v0.6.0 UE 5.5 / 5.6 / 5.7 Protocol v6 Win64 · Mac · Linux Full C++ Source Blueprint Surface Pro · Fab

Rollback Core Pro

Welcome to Rollback Core, a GGPO-style rollback netcode plugin for Unreal Engine 5. It features 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.

60 Hz Default fixed tick
8 Max peers per match
~12 Typical rollback depth
3 Included demos

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:

Why use this instead of rolling your own?

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.

Run the demo
Rollback.SpawnDemo
Host a UDP peer
Rollback.NetHost 7777
Open the debugger
Rollback.SpawnDebugger
Dump performance
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.

N N+1 N+2 N+3 N+4 N+5 LOCAL SIM predicting opponent... remote input from N arrives at N+3 cyan ghost trail = rolled-back & resimulated

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:

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
PlayerMoveAttackBlock
Player 1AD or arrow keysJ or left mouseK or right mouse
Player 2Driven 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

RequirementSupportedNotes
Unreal Engine5.5 / 5.6 / 5.7Each shipped as its own Fab package, compiled against an installed engine build
PlatformWin64, Mac, LinuxExplicit PlatformAllowList in .uplugin (Win64 and Linux Clang 20 verified locally)
CompilerVS2022 (17.8+) or VS2026Source build only — binary plugins ship pre-compiled
Project typeC++ or Blueprint-onlyBlueprint-only projects auto-build the plugin's runtime module on first launch if installed via Fab
OnlineSubsystem (optional)NULL / EOS / Steam / customRequired only for matchmaking; transport itself is provider-agnostic

Install from Fab marketplace

  1. Open Epic Games Launcher → Library → Fab → search for Rollback Core Pro.
  2. Click Install to Engine and select your engine version (5.5, 5.6, or 5.7).
  3. In your project, open Edit → Plugins, search for Rollback Core Pro, and check Enabled.
  4. Restart the editor when prompted. Done.

Install into project Plugins folder (source build)

  1. Copy or clone the RollbackCorePro/ folder into YourProject/Plugins/.
  2. Right-click your .uproject file and choose Generate Visual Studio project files.
  3. Open the resulting .sln in Visual Studio and build the editor target.
  4. Launch the editor — the plugin auto-enables on first load.
Tip — Blueprint-only projects

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.

YOUR GAMEPLAY CODE APawn / AActor IRollbackInputProvider UPROPERTY(SaveGame) Velocity, Health, etc. Mesh / Anim / VFX visual shell, not rolled back ROLLBACK CORE PRO · RUNTIME MODULE URollbackStateComponent per-actor SaveGame reflection ring-buffer history URollbackManager world subsystem AdvanceFrame() RollbackToFrame() URollbackNetSubsystem world subsystem UDP transport multi-peer · matchmaking URollbackNetworkBlueprintLibrary static helpers — wraps everything for Blueprint URollbackCoreProSettings UDeveloperSettings — Project Settings entries RollbackCoreProEditor module · ARollbackDebuggerPanel · Setup wizard · Editor styling Unreal Sockets · OnlineSubsystem (NULL · EOS · Steam) · OS UDP stack

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:

  1. Increment CurrentFrame.
  2. For each registered entity (anything implementing IRollbackEntity): call RollbackTick(dt, frame). This is where gameplay logic runs.
  3. For each registered entity: call SaveRollbackState(frame). The state component writes the actor's SaveGame properties + transform + velocity into the ring buffer.

The rollback path

When a late authoritative input arrives over UDP and changes a frame from the past:

  1. The network subsystem buffers the input under (PlayerId, Frame).
  2. Your gameplay code (or ApplyBufferedInputsAndRollback from the Blueprint library) calls URollbackManager::RollbackToFrame(EarliestMismatchFrame).
  3. For each entity, LoadRollbackState(Frame) restores the snapshot.
  4. The manager replays frames Frame+1..Current with the corrected input history.
  5. Latest state is now authoritative; the visual shell (mesh, animation, camera) follows on the next render frame.
Note

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.

AYourFighter + UPROPERTY(SaveGame) Health: int32 + UPROPERTY(SaveGame) Velocity: FVector + UPROPERTY(SaveGame) FacingDir: uint8 UPROPERTY() CosmeticTimer (ignored) + URollbackStateComponent BeginPlay scan cache FProperty pointers StateBuffer (ring, 60 frames) frame 117 → [ActorData: 24B] T=(...) V=(...) frame 118 → [ActorData: 24B] T=(...) V=(...) frame 119 → [ActorData: 24B] T=(...) V=(...) frame 120 → [ActorData: 24B] T=(...) V=(...) ... RollbackToFrame(119) → restore the highlighted slot

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 automaticallyCaptured because flaggedNot 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 flag
Plain 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:

PropertyMeaning
LastSavedFrameFrame index of the most recent snapshot
LastRestoredFrameMost recently restored frame
LastSavedByteCountSnapshot size — useful for budgeting
LastSavedChecksumFNV-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:

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
Important

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 13 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

Protocol v6 — breaking change in 0.5.0

Every packet now carries the sender's own frame advantage, so each peer can pace on the difference between the two clocks rather than on how old its newest confirmation is. The handshake string moved to RollbackCorePro/6, so a 0.4.x peer and a 0.5.0 peer cannot connect at all. Be aware that the failure is quiet: version-mismatched packets are discarded on receipt — including the handshake — so it presents as a connection timeout, with no version error in the log. The only visible signal is PacketsRejected climbing. Through matchmaking they never even meet, because the protocol string is part of the session search filter. Upgrade every peer together.

Every packet is a single UDP datagram, capped by MaxPacketBytes (default 1200, safe under typical MTU):

PACKET LAYOUT (network byte order) MAGICu32 VERu8 TYPEu8 SEQu32 ACKu32 PLAYERi32 Nu8 ADVANTAGEi32 · v6 INPUT[N]{frame, buttons, axes}×N 8 byte fixed header · 8 byte transport meta · 9 byte input meta (incl. sender advantage) · variable-length input list PACKET TYPES 1 · Hello 2 · Input 3 · Ack 4 · Heartbeat REDUNDANCY Each input packet repeats the last N frames of input. If packet for frame F is lost, the next packet (F+1) carries F again. N = InputRedundancyFrames, default 8.

Compact, hand-rolled wire format. Overhead per packet: 25 bytes of header (21 before v6 added the 4-byte sender advantage) + 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:

Peer lifecycle

  1. Connection starts when either side calls ConnectToPeer(PlayerId, Host, Port) or accepts an incoming Hello.
  2. Heartbeats are sent at HeartbeatIntervalSeconds (default 2 s) if no other packet has been sent recently.
  3. If no packet is received for PeerTimeoutSeconds (default 10 s), the peer is dropped and OnPeerDisconnected fires.

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

FieldDefaultPurpose
LocalPort7777UDP bind port (0 = auto)
RemoteHost / RemotePort"" / 7778Optional auto-connect on start
SocketSubsystemNameNAME_NoneOverride OS sockets, e.g. for platform P2P
MaxPacketBytes1200Safe UDP MTU floor
InputRedundancyFrames8How many past frames each packet repeats
ResendAfterSeconds0.08Reliable resend timeout
MaxReliableRetryCount20Resends before declaring peer failed
HeartbeatIntervalSeconds2.0Keepalive interval
PeerTimeoutSeconds10.0Drop peer after this much silence
MaxPeers4Hard cap on connected peers (≤ 8)
bAcceptFirstRemotePeertrueIf true, an inbound Hello from any address creates a peer
bEnablePacketLossSimulationfalseMaster switch for the simulated network
SimulatedOutgoingPacketLossPercent0Drop outgoing packets by chance
SimulatedIncomingPacketLossPercent0Drop incoming packets by chance
SimulatedMinLatencyMs / SimulatedMaxLatencyMs0 / 0Range of artificial delay applied to delivered packets

08 Pacing & the Latency Envelope

Two peers simulating the same match will never run at exactly the same speed. One machine hitches, one has a slower GPU, one's crystal drifts. Left alone the clocks separate, and once they separate by more than the rollback window, corrections arrive too old to apply and the match desyncs permanently with no way back.

Pacing is the mechanism that stops that: the peer that is ahead holds its clock for a frame and lets the other catch up.

Why "how late is my confirmation?" is the wrong question

The obvious metric is how old your newest confirmed frame is — if you're at frame 400 and only confirmed through 380, you're 20 frames "ahead". Before 0.5.0 that is what drove the stall.

It is wrong, and the reason matters: latency alone drives that number up even when both clocks are in perfect step. On a 100 ms link your confirmation is always ~6 frames old, in both directions, forever. The old rule read that as drift and stalled continuously — so a high-latency match ran in slow motion for both players, which is the opposite of what pacing is for.

SYMMETRIC LATENCY IS NOT DRIFT Absolute advantage (pre-0.5.0) Peer A: at frame 400, confirmed 394 Peer B: at frame 400, confirmed 394 Both read "6 frames ahead" → both stall Neither is actually ahead. The link is just slow. Difference (0.5.0+) A reports advantage 6 → B receives it B reports advantage 6 → A receives it (6 − 6) / 2 = 0 → neither stalls Symmetric latency cancels exactly. A genuine divergence still stalls A hitched and fell behind: A reports 2, B reports 14 B computes (14 − 2) / 2 = 6 → B holds its clock until A catches up The deltas are antisymmetric, so at most one peer stalls at a time.

The halving is not cosmetic: the difference counts each peer's contribution twice, so a delta of N means N×2 frames of real divergence. That is why MaxFrameAdvantageFrames defaults to 4 and tolerates 8 frames of separation.

What it measures now

AccessorReturns
GetFrameAdvantage()This peer's raw advantage — CurrentFrame − ConfirmedFrame. Tracks link latency; a high value is not by itself a problem.
GetRemoteFrameAdvantage()The advantage the peer most recently reported for itself. 0 when unpaced.
GetFrameAdvantageDelta()The smoothed difference. This is the number to watch. It should hover near 0.
ShouldStallForFrameAdvantage()Whether the clock is being held this frame.
StalledFrameCountCumulative held frames. In a healthy match this stays near zero.

Why pacing cannot deadlock

A stall suppresses local input. No input means the peer's confirmation freezes, which makes the peer stall too — a loop with no exit if pacing is ever allowed to hold the clock indefinitely.

Pacing therefore corrects at a duty cycle rather than by withholding every frame: at most one frame in four is held (StallDutyCycleFrames), so the clock still yields ground — at 75% rate — while the input stream never stops and the peer is never starved into stalling. Withholding every frame was measured on the 180 ms profile halving the effective rate, with one peer's confirmation frozen for 15.58 s of a 20 s run.

The clock holds outright in exactly one case: the advantage is already past the rollback window and the peer has genuinely gone silent (no confirmation progress for SilentPeerFrames, 30 timesteps). Past the window advancing buys nothing — RollbackToFrame rejects corrections deeper than the window, so those frames could never be reconciled — and the 10-second peer timeout resolves a genuinely dead peer by disconnecting it, which drops the advantage to 0 and lets the clock resume on its own.

Both conditions are required. A merely slow peer must not be held against: on a high-latency link a healthy session sits close enough to the window that ordinary jitter crosses it, and holding there would starve the peer into stalling — the very loop the duty cycle exists to break.

Two peers cannot deadlock under the delta rule

The two peers' deltas are antisymmetric — A's is exactly the negative of B's, both derived from the same pair of advantages — so at most one peer is above the stall threshold at any instant. Mutual stalling becomes reachable only with 3+ peers, where GetPeerReportedFrameAdvantage() takes the max across peers and two peers could stall against different opponents — which is what the duty cycle guards. MaxPeers defaults to 4, so that case is reachable.

Choosing a rollback depth for your latency

Rollback depth is a budget, not a safety net. A correction older than MaxRollbackDepthFrames is discarded, and a discarded correction is a permanent desync. So the window has to be wider than the confirmation lag your players will actually see.

Confirmation lag in frames is roughly one-way ms ÷ 16.67, plus your input delay, plus a margin for jitter. Measured against the shipped soak profiles:

THE ROLLBACK WINDOW IS A BUDGET — SPEND IT, DON'T EXCEED IT Default window · MaxRollbackDepthFrames = 12 input delay 2 one-way lag ≈ 6 frames @ 100 ms jitter 2 headroom FITS Transcontinental @ 180 ms · same 12-frame window input delay 2 one-way lag ≈ 11 frames + jitter → peak advantage 13–14 window ends here OVERRUNS A correction that lands past the window edge is discarded, not clamped — and a discarded correction is a permanent desync. Watch RejectedRollbackCount: any sustained climb means the window is too narrow for the link.

Widen the window (or lower input delay) until RejectedRollbackCount stays flat under your worst realistic connection.

ConnectionOne-wayMeasured peak advantageRecommended MaxRollbackDepthFrames
Same machine / LAN0–15 ms1–212 (default) — ample
Regional40–60 ms512 (default)
Cross-country80–120 ms812 (default) — near the edge
Transcontinental140–180 ms13–1420 — the default is not enough

Peak-advantage figures come from the plugin's own RCPSoak suite — five real-time latency profiles run for 20 s each — so you can re-derive them rather than take them on trust.

Why the default is 12 and not simply higher

A deeper window costs on both axes: more frames re-simulated per correction, and proportionally more snapshot memory held. 180 ms one-way is 360 ms round trip — well beyond what rollback fighting games target. 12 is the right default for the connections this genre is played on; raise it deliberately if you are shipping to players who will genuinely be that far apart.

Input-stream integrity

Confirmation never advances across a missing frame, and redundancy cannot heal a hole — SendInputFrame only re-sends frames already in local history, and a frame that was never submitted was never there. So one missing input frame freezes that peer's confirmation for the rest of the session.

This is easy to cause by accident. Callers poll input once per engine tick while URollbackManager can advance up to MaxCatchUpFramesPerTick (4) fixed frames in that same tick — so any engine frame slower than 60 fps drops a frame from the wire.

ONE DROPPED FRAME FREEZES CONFIRMATION FOR THE REST OF THE SESSION SendInputFrame(CurrentFrame) — one send per engine tick F 10 F 11 — 12 — — 13 — F 14 F 15 F 16 engine hitch — clock stepped 3 frames, only 1 was sent confirmed stuck at 11 forever — redundancy cannot resend what was never sent SendInputFramesThrough(CurrentFrame) — fills from the watermark F 10 F 11 F 12 F 13 F 14 F 15 F 16 gap back-filled with the polled value confirmed advances to 16 Repeating the polled value across the gap is exact, not an approximation: the local simulation used that same value for every fixed frame it stepped.

The gap is filled once, from a per-player watermark, and capped at MaxInputGapFillFrames (120) so a session that has already fallen minutes behind cannot flood the link.

Use SendInputFramesThrough, which sends every frame from a per-player watermark through the current frame:

// Sends every frame from the last one sent through CurrentFrame, filling any gap.
// Repeating the polled value across skipped frames is exact, not an approximation:
// the local simulation used that same value for every fixed frame it stepped.
int32 Sent = Net->SendInputFramesThrough(PlayerId, Manager->CurrentFrame, Input, false);
// Returns frames sent, 0 if none were due, -1 on transport failure. 0 is success.

The Blueprint node ApplyBufferedInputsAndRollback and all three shipped demo environments already route through it. If you wrote your own send path against SendInputFrame, switch it.

09 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

ProviderUse forSetup
NULLLAN tests, in-editor PIEEnabled by default in this sample project — no credentials required
EOSCross-platform shippingConfigure EOS plugin + Developer Portal credentials in host project
SteamSteam-only shippingSteamworks SDK + AppID configured in DefaultEngine.ini
CustomFirst-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

// 0. (Optional) Tune the search timeout, then size your own "Searching..."
//    watchdog/UI to it instead of hard-coding a matching number.
RollbackNetSubsystem->SetSessionSearchTimeoutSeconds(15.f);
const float SearchTimeout = RollbackNetSubsystem->GetSessionSearchTimeoutSeconds();

// 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.

Blueprint: async matchmaking nodes (Find / Host / Join)

For Blueprint graphs, three Advanced-Sessions-style latent nodes wrap the whole flow so it's one node each — no manual delegate wiring:

NodeExec pinsWhat it does
Find Rollback Sessions (Async)On Found (Sessions) / On Failed (Error)Starts a search, waits for it to finish (bounded by the search timeout), delivers the whole list. Optional Timeout Seconds pin; leave -1 to use the configured SessionSearchTimeoutSeconds.
Host Rollback Session (Async)On Hosted (SessionName) / On Failed (Error)Brings up the UDP transport on Host Port (if needed), then advertises a session for Match Id.
Join Rollback Session (Async)On Joined / On Failed (Error)Joins a result from the Find node and waits until the UDP transport actually connects to the host.

Helper pure nodes: Find Best Rollback Session (lowest ping with an open slot) and Format Rollback Session (one-line summary for menus).

// C++ equivalent of the Find node's payload (Host/Join mirror this shape).
URollbackFindSessionsAsync* Search =
    URollbackFindSessionsAsync::FindRollbackSessions(this, "Ranked-1v1", /*MaxResults*/ 20, /*bLan*/ false, /*Timeout*/ 15.f);
Search->OnFound.AddDynamic(this, &UMyMenu::HandleSessionsFound);   // TArray<FRollbackMatchmakingResult>
Search->OnFailed.AddDynamic(this, &UMyMenu::HandleSearchFailed);
Search->Activate();

Example assets & one-click generation

The C++ actor ARollbackMatchmakingExampleActor wires all three nodes into one Blueprintable actor with editable defaults (Match Id, LAN, port, timeout) and OnSessionsFound / OnHosted / OnJoined / OnError events to bind to UI. A bundled generator authors ready-to-open example assets under /RollbackCorePro/Examples/:

AssetParentUse
BP_RollbackMatchmakingExampleARollbackMatchmakingExampleActorDrop into a level; call HostMatch / FindMatches / JoinBestMatch (or enable Auto-Find) and bind the events to your UI.
WBP_RollbackMatchmakingMenuURollbackMatchmakingMenuWidgetOpen in the UMG designer to restyle the ready-made menu (below); behaviour stays in the C++ parent.

Generate them from the editor in one click — Rollback toolbar → Examples → Generate Example Content, or the Setup Wizard → Integration → Generate Example Blueprints button. Both call FRollbackCoreProEditorModule::GenerateExampleContent(), which runs the generator through the Python Editor Script Plugin and reports whether it ran (a clear on-screen note appears if Python is disabled). You can also run Content/Python/generate_matchmaking_examples.py manually via Tools > Execute Python Script…. Re-running is idempotent — existing assets are left untouched.

Ready-made menu widget

URollbackMatchmakingMenuWidget is a fully native UMG menu — no .uasset required, it builds its own widget tree in RebuildWidget. It drives the Find / Host / Join async nodes for you and shows discovered sessions as a scrollable list, each row with its own Join button. Pop it on screen instantly with Rollback.ShowMatchmakingMenu, or add it from your HUD with CreateWidget + AddToViewport.

Rollback Matchmaking Host Find
Found 3 session(s).
match=Ranked-1v1  host=10.0.0.2:7777  45ms  open=1 Join
match=Ranked-1v1  host=10.0.0.9:7777  82ms  open=2 Join
match=Ranked-1v1  host=10.0.0.4:7777  120ms  open=0 Join

The native menu as rendered by Rollback.ShowMatchmakingMenu: Host / Find in the header, a live status line, and one row per discovered session. Row text comes from Format Rollback Session; each Join routes through the Join Rollback Session (Async) node.

Config & events

MemberKindNotes
MatchId · bLanMatch · MaxSearchResults · PublicConnections · HostPort · SearchTimeoutSecondsEditAnywhereSame configurable surface as the example actor; tweak per-instance in the designer.
OnHosted · OnJoined · OnSessionsFound · OnErrorBlueprintAssignableRe-broadcast so a subclass can drive game flow (e.g. travel on OnJoined).
HostMatch() · FindMatches() · JoinMatch(Session)BlueprintCallableThe same actions the header / row buttons call — invoke them from your own UI too.

Console & headless parity

The whole async flow is reachable from the console, so you can test or script matchmaking with no Blueprint at all: Rollback.Host, Rollback.Find, and Rollback.Join spin up the same URollbackCreateSessionAsync / URollbackFindSessionsAsync / URollbackJoinSessionAsync nodes and report through URollbackNetConsoleSink. Because OnlineSubsystemNull discovery only works across separate processes, a genuine find/join is a two-process exercise:

PROCESS A · HOST Rollback.Host Ranked-1v1 7777 → URollbackCreateSessionAsync Null LAN session · advertised host=…:7777 · open slots UDP transport · bound :7777 PROCESS B · JOINER Rollback.Find Ranked-1v1 → URollbackFindSessionsAsync Rollback.Join 0 → URollbackJoinSessionAsync UDP transport · auto port Null LAN beacon (discovery) UDP rollback A single process can't see its own session — host and joiner must be separate processes.

Two-process Null-LAN parity flow. Use Tools/Verify-Matchmaking.ps1 to launch a host + joiner side by side; it auto-runs Host/Find and reports discovery, leaving Rollback.Join 0 + Rollback.NetPeers as the one interactive step.

# Process A — host
Rollback.Host Ranked-1v1 7777 2 1

# Process B — joiner (once the host is advertising)
Rollback.Find Ranked-1v1 20 1 8     # logs: Find complete: 1 session(s)
Rollback.Join 0                       # joins the cached result; transport auto-connects
Rollback.NetPeers                     # confirm the link on both sides

Delegates

DelegateFires when
OnMatchmakingResultEach session that matches a FindPlatformSessions query (provider, ID, owner, ping, host:port)
OnMatchmakingCompleteAsync create / find / join operation finishes; carries success bool + error string
OnFindSessionsCompleteA FindPlatformSessions search finishes; carries success bool, the full session list, and error string (also wrapped by the Find Rollback Sessions (Async) node)
OnCreateSessionCompleteA CreatePlatformSession attempt finishes; carries success bool, session name, and error string (wrapped by the Host Rollback Session (Async) node)
OnJoinSessionCompleteA JoinPlatformSessionByIndex attempt resolves (after the transport connects on success); carries success bool + error (wrapped by the Join Rollback Session (Async) node)
OnTransportConnectedThe UDP transport has a remote peer talking back
OnPeerDisconnectedA peer timed out or was explicitly disconnected
OnPeerMaxRetriesExceededA 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.

NAT and connectivity

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.

10 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:

Scrubber console interface

CommandEffect
Rollback.Debugger.Follow 1Resume following the live frame
Rollback.Debugger.Follow 0Pause at current scrub frame
Rollback.Debugger.Frame 120Jump to absolute frame 120
Rollback.Debugger.Step -1Step back one frame
Rollback.Debugger.Step 1Step 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:

Tip — chasing a desync

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.

11 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:

FieldWhat it measures
AvgSimulationTimeMs / MaxSimulationTimeMsHow long one normal AdvanceFrame() takes
AvgRollbackTimeMs / MaxRollbackTimeMsHow long a RollbackToFrame() call takes (restore + replay)
MaxRollbackDepthFramesLargest rollback distance seen recently
TotalRollbackCount / RollbacksInLastSecondFrequency tracking
AvgStateSnapshotBytesHow much memory each frame's snapshot consumes
AvgStateSerializeTimeMs / MaxStateSerializeTimeMsSnapshot serialization cost
BytesSentPerSecond / BytesReceivedPerSecondTransport bandwidth
ConnectedPeerCountCurrent peer count
RegisteredEntityCountHow many entities the manager simulates
DesyncCountCumulative desync detections
FramesSimulated / FramesWithRollbackLifetime 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 AvgRollbackTimeMsReduce FixedTickRateHz (e.g. 60→30) or trim per-frame sim cost. Reduce MaxRollbackDepthFrames to cap worst-case.
High AvgStateSnapshotBytesRemove unused SaveGame flags. Pack booleans into bitmasks. Use smaller integer types.
High BytesSentPerSecondLower InputRedundancyFrames. Send less often (every 2 ticks instead of every tick).
High DesyncCountYou have non-deterministic gameplay. See section 12.
Large RollbacksInLastSecondEither 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.

12 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 authorityWill 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:

DETERMINISTIC CORE

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
VISUAL SHELL

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

Rollback Core Pro provides five complementary mechanisms to detect, audit, and pinpoint non-determinism:

  1. Single-process local validation (ValidateDeterminism). Call URollbackManager::ValidateDeterminism(FramesBack) during unit tests or soak runs to resimulate historical frames locally from saved inputs. It catches unseeded RNG, TMap/TSet iteration order issues, and uninitialized variables in a single process without needing a network connection.
  2. Field-level & property-level desync diagnostics. When a desync occurs, CompareRollbackStates and DescribeRollbackStateDifference compare snapshots and report exact field differences (e.g. Velocity: (12, 0, 0) -> (12.5, 0, 0)). Call SetDetailedDesyncDiagnosticsEnabled(true) to add per-property checksums to ActorData, allowing desyncs inside reflection properties (e.g., Health, ComboCounter) to be identified by name rather than as an opaque byte offset. Read the report via GetLastDesyncDetail().
  3. Cross-build float semantics (FPSemanticsMode.Precise). Both RollbackCorePro and RollbackVikingGame build with FPSemantics = FPSemanticsMode.Precise in their .Build.cs. This prevents compiler fast-math reassociation and FMA contraction that otherwise cause DebugGame, Development, and Shipping builds (or MSVC vs Clang) to desync over identical input streams.
  4. Differential build testing (Diff-BuildDeterminism.ps1). Run Tools\Diff-BuildDeterminism.ps1 to generate per-frame state checksum traces (via RunDeterminismTrace) across different build configurations and verify bit-level reproducibility.
  5. Cross-peer checksum monitoring & loss testing. Bind OnDesyncDetected to catch real-time state divergence during multi-peer matches, or run Rollback.SpawnNetworkDemo with high simulated loss (30%+) to test resimulation under latency.
Common desync sources

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, (5) bypassing input normalization when writing analog axis values into snapshots.

13 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 ProNetwork Prediction / Mover
TopologySymmetric peer-to-peerServer-authoritative
Tick modelFixed-step, both peers run identical simVariable, server reconciles client predictions
Correction styleResimulate N frames from snapshotReceive server state, blend/snap to it
State captureSaveGame reflection, automaticMove record structs, manually authored
Best forCompact deterministic gameplay (fighting, combat, projectiles)Predicted locomotion that interops with Epic's movement stack

When to use which

Recommended integration patterns

  1. Deterministic core + visual shell. Rollback Core Pro simulates compact gameplay state. Meshes, animation, VFX, audio, and cameras follow the corrected state after rollback.
  2. 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.
  3. 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.
Test mixed architectures aggressively

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.

14 API Reference

URollbackManager — world subsystem

MemberTypeDescription
AdvanceFrame()methodTick all registered entities one fixed step; snapshot state
RollbackToFrame(Frame, EarliestMismatch = -1)methodRestore state at Frame, replay forward to current
RegisterEntity(IRollbackEntity)methodAdd an entity to the simulated set (auto-called by URollbackStateComponent)
UnregisterEntity(IRollbackEntity)methodRemove from the simulated set
ValidateDeterminism(FramesBack = 1)method → boolResimulates historical frames locally from saved inputs; reports field differences on replay divergence
CompareRollbackStates(Before, After)static method → TArray<FRollbackStateFieldDifference>Compares two snapshots and names every field that diverged (transform, velocity, SaveGame fields)
DescribeRollbackStateDifference(Before, After)static method → FStringOne-line, log-ready summary of state differences between two snapshots
SetDetailedDesyncDiagnosticsEnabled(bEnabled)methodEnables per-property snapshot checksums on ActorData for naming reflection properties by name
GetLastDesyncDetail()method → FStringHuman-readable, field-level report for the most recent desync
GetLastDeterminismViolation()method → FStringField-level detail for the most recent local determinism violation
RunDeterminismTrace(World, FrameCount, Seed)static method → TArray<int32>Runs self-contained simulation trace and returns frame checksums for differential build testing
DrawDebugState(Frame)methodRender 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(Δ)methodProgrammatic scrubber control
CurrentFrameint32The live simulation frame index
RollbackCount / LastRollbackFrame / LastRollbackFramesReplayedint32Lifetime rollback diagnostics
DesyncCount / LastDesyncFrameint32Desync diagnostics
OnDesyncDetecteddelegate (Frame, EntityName, ChecksumMismatch)Fires when post-rollback checksum diverges from snapshot
bEnableVisualDebuggingboolToggle inline ghost rendering
DebugLiveFrameLagint32How many frames behind to draw the ghost

URollbackStateComponent — per-actor component

MemberTypeDescription
CurrentLocalInputFRollbackInputWrite here before AdvanceFrame; used as the input for the next tick
InjectInputForFrame(Frame, Input)methodAuthoritatively override the input that was used at a past frame
GetInputForFrame(Frame)method → FRollbackInputRead the input recorded at a past frame
OnRollbackTickBlueprint event (Δt, Frame, Input)Fires during each deterministic tick — main gameplay hook for Blueprint actors
OnRollbackTickDelegatedelegate (Δt, Frame, Input)C++ counterpart of the above
MaxBufferSizeint32 (default 60)Ring-buffer length for state and input history
LastSavedFrame / LastRestoredFrameint32Most recent snapshot/restore frame
LastSavedByteCountint32Size of the most recent snapshot in bytes
LastSavedChecksum / LastRestoredChecksumint32FNV-style checksum of the snapshot bytes
GetTrackedPropertyCount()method → int32How many SaveGame properties the component is capturing

URollbackNetSubsystem — world subsystem

MemberDescription
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
SendInputFramesThrough(PlayerId, ThroughFrame, Input, bReliable)Fills input gaps from last watermark through target frame to prevent rate dips from dropping input
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
GetWireProtocolVersion()Returns current wire protocol version (6)
GetLastProtocolMismatchVersion()Version byte of the last version-mismatched packet received
IsTransportRunning() / IsConnectedToPeer()State queries
GetTransportStats() / GetPerformanceStats()Diagnostics (includes ProtocolMismatches count)
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
GetSessionSearchTimeoutSeconds() / SetSessionSearchTimeoutSeconds(Seconds)Read/configure the FindPlatformSessions search timeout (defaults to 10s) so watchdogs/UI can match it
delegatesOnTransportConnected, OnTransportError, OnRemoteInputReceived, OnMatchmakingResult, OnMatchmakingComplete, OnPeerDisconnected, OnPeerReconnected, OnProtocolVersionMismatch, 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

TypeFields
FRollbackInputint32 Buttons (bitmask), FVector Axes
FRollbackFrameStateTArray<uint8> ActorData, FVector Location, FQuat Rotation, FVector Velocity
FRollbackTransportConfigSee section 7 reference table
FRollbackTransportStatsPacket/byte counters, RTT, peer count
FRollbackPerformanceStatsSim/rollback/serialize timing, bandwidth, rollback frequency
FRollbackPeerInfoPer-peer PlayerId, endpoint, connection state, RTT, packet counts
FRollbackMatchmakingResultSessionId, provider, owner, host:port, ping, open slots
FRollbackDebugFrameRecordFrame, entity name, state availability, location/rotation/velocity, byte count, checksum, input

Interfaces

InterfacePurpose
IRollbackEntityImplemented by URollbackStateComponent. Override RollbackTick, SaveRollbackState, LoadRollbackState if you implement your own entity type.
IRollbackInputProviderImplement on your pawn. GetRollbackInput(OutInput) is called once per deterministic tick.

15 Console Commands

All commands are registered globally and operate on the active UWorld's subsystems. Open the console with ~.

CommandArgumentsPurpose
Rollback.SpawnDemoSpawn the basic two-pawn demo
Rollback.SpawnNetworkDemoSpawn the network packet-loss demo
Rollback.SpawnVikingDemoSpawn the 2D viking duel demo
Rollback.SpawnDebuggerSpawn 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.NetPeersList 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.Host[MatchId] [Port=7777] [Slots=2] [LAN=1]Host a match via the async node (parity with Blueprint's Host Rollback Session)
Rollback.Find[MatchId] [Max=20] [LAN=1] [Timeout=-1]Find sessions via the async node; lists & caches results for Rollback.Join
Rollback.Join[Index=0]Join a cached Rollback.Find result via the async node; the transport auto-connects
Rollback.ShowMatchmakingMenuCreate the example UMG matchmaking menu and add it to the viewport (game / PIE)
Rollback.PerfDump 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
Tip — bind for iteration

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)

16 Project Settings

Surfaced under Edit → Project Settings → Plugins → Rollback Core Pro. Backed by URollbackCoreProSettings (UDeveloperSettings) and persisted to DefaultEngine.ini.

GroupSettingDefaultRangePurpose
SimulationFixedTickRateHz6015–240Deterministic simulation rate. Must match across all peers.
SimulationMaxRollbackDepthFrames120–240Deepest rollback the sim will perform. Corrections older than this are rejected, not clamped — see §8 Latency envelope.
SimulationMaxCatchUpFramesPerTick41–16Caps how many fixed frames one engine tick may advance after a hitch.
SimulationMaxAccumulatedTimeSeconds0.250.05–2.0Accumulator clamp. Time beyond this after a stall is discarded rather than simulated.
SimulationbRequireExplicitStableEntityIdstrueboolRejects transient actor names as entity IDs, which would differ between peers.
Simulation → LatencyInputDelayFrames20–10Input polled at frame F drives frame F+D. Must match across all peers. Set 0 for offline-only projects.
Simulation → LatencybEnableFrameAdvantagePacingtrueboolHolds the local clock when this peer genuinely runs ahead of the other.
Simulation → LatencyMaxFrameAdvantageFrames40–60Bounds the difference between this peer's advantage and the peer's own reported advantage. Halved internally, so 4 tolerates 8 frames of real divergence. Clamped at runtime to Max Rollback Depth.
NetworkingDefaultLocalPort00–655350 binds an ephemeral port. Demos override this explicitly.
NetworkingDefaultInputRedundancyFrames80–32Past input frames each packet repeats.
NetworkingDefaultMaxPeers41–8Peer cap for default transport configs.
NetworkingDefaultResendAfterSeconds0.080.01–2.0Reliable resend timeout.
Networking → ReliabilityDefaultHeartbeatIntervalSeconds2.00.1–30.0Keepalive interval.
Networking → ReliabilityDefaultPeerTimeoutSeconds10.01.0–120.0Peer timeout. Also the escape hatch when a peer goes permanently silent — see §8.
Networking → TransportDefaultSocketSubsystemNameNoneFNameWhich ISocketSubsystem carries transport by default. Set to EOS to route packets over EOS P2P for NAT traversal.
DebugbEnableVisualDebuggingByDefaultfalseboolInline ghost rendering on by default.
DebugDebugLiveFrameLag50–60How many frames behind to draw the ghost.
DebugbAutoOpenStatsPanelInPIEfalseboolAuto-open the stats panel on first PIE each session.
OnboardingbShowSetupWizardOnStartupfalseboolShow 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.

17 What's New

v0.6.0 — Precise float semantics, field-level desync diagnostics & protocol mismatch handling

v0.6.0 Highlights

v0.6.0 focuses on cross-build bit-level determinism, single-process determinism auditing, field-level desync attribution, explicit protocol version mismatch reporting, and diagnosable socket subsystem selection.

v0.5.0 — Difference-based pacing on protocol v6

Breaking — upgrade every peer together

The wire protocol moved v5 → v6. A 0.4.x peer and a 0.5.0 peer cannot play together, and the failure is quiet rather than loud: mismatched packets are dropped on receipt — the handshake included — so it looks like a connection timeout rather than a version error. It will not silently desync you mid-match; it simply will not connect. Max Frame Advantage also changed meaning (it now bounds the difference between peers, not absolute confirmation age) and its default moved 8 → 4. If you overrode it, re-read section 8 before carrying your old value across.

The defect. Frame-advantage pacing stalled on how old the newest confirmation was. Latency alone pushes that past the bound even when both clocks are in perfect step, so peers on a slow link stalled continuously and the match ran in slow motion for both players. Worse, a stall suppresses local input, which freezes the peer's confirmation, which stalls the peer — a loop with no exit.

Measured across five real-time latency profiles, 20 s each, on the plugin's own soak suite:

Profile (one-way)BeforeAfter
Clean (0 ms)47.35 Hz59.95 Hz
LAN (5–15 ms)59.93 Hz59.95 Hz
Regional (40–60 ms)59.93 Hz59.95 Hz
Cross-country (80–120 ms)17.04 Hz59.95 Hz
Transcontinental (140–180 ms)0.80 Hz59.95 Hz

After the fix: 0 stalls, 0 rejected rollbacks, 0 desyncs on every profile. The "before" column is one run of three — no profile's pre-fix result was stable across runs, which is why the fix was validated over repeated clean runs rather than one.

v0.2.3 — Multi-engine support & desync detection

This release focused on multi-engine support, cross-peer desync detection, complete physics state rollback, and Fab marketplace readiness.

5.7
Unreal Engine
✓ Verified
5.6
Unreal Engine
✓ Verified
5.5
Unreal Engine
✓ Verified

Each engine version is packaged separately with RunUAT BuildPlugin -StrictIncludes against an installed engine build, from a clean source copy with no stale Binaries or Intermediate. -StrictIncludes disables precompiled headers and unity builds, so every header must be self-contained — the same condition marketplace validation compiles under, and the one that unity builds hide.

🌐 Cross-Peer Desync Detection

Peer A CRC: 0xA3F1 Peer B CRC: 0xA3F1 StateChecksum packet ✓ Checksums match Confirmed frame is in sync Mismatch → OnDesyncDetected(Frame, Peer, Delta)

Peers exchange per-frame state checksums on confirmed frames. A mismatch fires OnDesyncDetected — no false positives from in-flight prediction.

🧊 Complete Physics & CMC State Rollback

Before (v0.2.1)
  • Velocity saved but not restored
  • Angular velocity not captured
  • CMC MovementMode not tracked
  • Local-only desync check
After (v0.2.3)
  • Linear + angular velocity fully restored
  • CMC MovementMode + CustomMovementMode tracked
  • URollbackCharacterMovementComponent subclass
  • Cross-peer checksum exchange

🎯 Rollback Correctness

📦 Fab Marketplace Packaging

Fab-Ready Distribution

Each engine version ships as an isolated HostProject/Plugins/RollbackCorePro zip with Installed: true, proper FilterPlugin.ini, Icon128.png, and no stale Binaries/ or Intermediate/ directories. PlatformAllowList covers Win64, Mac, Linux, Android, and IOS.

🔧 UE 5.7 Compatibility

UE 5.7 tightened NewObject constraints for UActorComponent subclasses — they now require an AActor* outer instead of UPackage*. The Movement.RollbackStepSafe test was updated to create a proper test world/actor while still validating that RollbackStep is safe to call without registered or initialized movement data.

18 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

TestValidates
State.SaveRestoreSaveGame property round-trip through the state component
Rollback.LateInputCorrectionThe canonical rollback case: late input changes a past frame
Rollback.NoOpReplayIsIdentityNo-op rollback reproduces exact pre-rollback state (off-by-one regression guard)
Network.BufferApplyReceived input applied to a state component, change detected
Debug.HistoryScrubFrame-by-frame scrubber data integrity
VikingDemo.MetadataCombatMetadata-driven hit detection in the viking demo
Network.UdpLoopbackSmokeTwo bound peers exchange a reliable rollback input through real sockets
Network.MultiPeerConnectThree peers connect and exchange input simultaneously
Network.PeerDisconnectHeartbeat timeout detects a vanished peer
Matchmaking.NullProviderValidationOnlineSubsystemNull diagnostic check
Movement.RollbackStepSafeCMC subclass safe to call without registered/valid data (UE 5.7+ compatible)
Movement.CMCDrivenByRollbackTickState component drives CMC rollback step integration
Movement.CharacterMovementRestoredCMC velocity + movement mode round-trip through rollback
Desync.DetectionChecksum-based cross-peer desync surfacing
Performance.StatsPerformance counters populate after sim and rollback
Matchmaking.FindSessionsAsyncNodeFind node routes to On Found / On Failed pins
Matchmaking.HostSessionAsyncNodeFailureHost node reports through On Failed on error
Matchmaking.JoinSessionAsyncNodeFailureJoin node reports through On Failed on error

19 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 of YourProject/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.log and search for LogPluginManager.
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. How do I pinpoint the cause?

An actor with a state component is doing something non-deterministic during RollbackTick. To find the exact property causing the divergence:

  1. Call URollbackManager::SetDetailedDesyncDiagnosticsEnabled(true) during your test session.
  2. When a desync occurs, call GetLastDesyncDetail() or inspect the warning log to see the exact field and values that diverged (e.g. Velocity: (12, 0, 0) -> (12.5, 0, 0) or Health: 100 -> 90).
  3. Common causes include iterating TMap/TSet (unordered) inside tick, unseeded RNG, FApp::GetDeltaTime() usage, or floating-point fast-math reassociation (fixed in plugin v0.6.0 via FPSemanticsMode.Precise).
Two builds will not connect and time out immediately.

If the peers are running different plugin versions, the wire protocol version byte differs. In v0.6.0, version-mismatched packets trigger the OnProtocolVersionMismatch delegate, increment FRollbackTransportStats::ProtocolMismatches, and emit warning logs naming both version numbers. Bind OnProtocolVersionMismatch to notify players to update their game build. Through matchmaking, incompatible sessions are filtered out automatically using the protocol search string (RollbackCorePro/6).

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.

20 Glossary

TermMeaning
Fixed-step tickSimulation runs at a constant rate (60 Hz here) regardless of render framerate, ensuring identical state evolution across peers.
RollbackRestoring the simulation to a past frame and replaying with corrected inputs.
Rollback depthHow many frames the simulation goes back during a single RollbackToFrame call.
PredictionFilling in unknown remote input by repeating the most recent known input.
Snapshot / state captureWriting the simulation state into a buffer that can be restored later.
DesyncTwo peers diverging in simulation state despite identical inputs. Caused by non-determinism.
Input redundancyRepeating recent input frames in every packet so transient loss doesn't stall the simulation.
Reliable ACKAcknowledgment mechanism for packets that must be delivered; absence triggers resend.
HitstunCombat state where a character is unable to act after being hit. Always integer-frame counted.
Visual shellThe non-deterministic rendering layer (mesh, anim, VFX, audio) that follows the deterministic core.
OnlineSubsystemUnreal'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.

21 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
PriceFree, MITPaid, Fab EULA
Source availableYesYes
Engine versions5.7 verified5.5 / 5.6 / 5.7 verified packages
SupportCommunity (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.5–5.7 verified packages without maintaining backports
  • Worked 2D fighter demo as a starting point
  • Time-to-first-rollback-match matters more than license cost