GregOrigin - Rollback Core Pro: Accessible Netcode Framework for Fighting & Platform Games

Initial video (v0.2) | ✍🏼Read the manual. | 💬 Discord support. | 🎁 Get the Core version on GitHub: a functional open-source variety that I recommend you try before buying the Pro version. | 👨‍💼 Contact for custom integration / implementation.

🔥Say goodbye to input lag and latency in Unreal Engine! Introducing Rollback Core (Pro), the ultimate deterministic GGPO-style rollback netcode framework built specifically for UE5.

Whether you're building a fast-paced fighting game, a platform brawler, or a tight co-op experience, standard server-client networking just doesn't cut it. Rollback Core flips the script: simulate locally, predict remote inputs, and seamlessly re-simulate history when the truth arrives late. The result? Zero perceived input lag for your players. ⚡️🎮

✨ Why Choose Rollback Core?

🔹 Auto State-Saving (No Code Required): Forget writing complex C++ serializers. Just check the standard SaveGame flag on any Blueprint variable, and we'll automatically handle snapshots, transforms, and velocities via reflection. 💾

📖 Custom UDP Transport: Highly-optimized netcode featuring per-frame input redundancy, reliable ACKs, and built-in latency/packet-loss simulation for effortless stress testing. 📡

💬 Deterministic Fixed-Step Logic: Decouple your visual framerate from your game logic! Runs a perfectly rigid, predictable simulation under the hood. ⏱️ 👀 Visual Frame Debugger: Spawn our intuitive debugger right into your world to scrub through frames, view prediction ghost trails, and analyze rollback markers in real-time. 👻

👇 Seamless Matchmaking: Full OnlineSubsystem support so your players can effortlessly advertise, query, and join sessions out-of-the-box. 🤝

👾 3 Playable Demos Included: Learn by doing! We've included a fully playable 2D Viking Duel (with hitboxes & blockstun), a Network Packet-loss stress test, and a Basic Prediction environment. ⚔️

🛡️ Guided Editor Wizard: Get set up in 5 minutes with the onboarding Wizard. It automatically fixes project settings and injects the necessary rollback components directly into your existing Pawn. 🪄

🚀 Ready to upgrade your netcode?


Disclaimer: this plugin assumes a working knowledge of UE5 workflows, editor tooling, asset setup and so on. Custom integration, extension or modification may also require comfort reading and writing C++. If you are new to UE5 or plugin-based development, expect a learning curve. Before purchasing the Fab version, it is recommended that you explore the open-source GitHub version and the online manual first to understand the architecture, setup requirements, and integration style. This will help you determine whether the plugin fits your project and experience level.

Launch video:

Can it be used for a 3D fighting game in the likes of Tekken?

Hi there. It can, in fact 2 of the basic demos that come with it are fully 3D. And I’ll make more demos in the future.

1 Like

0.2.0, 2026-06-16 :wireless:

Networking correctness pass for peer identity and disconnect handling,
driven by reported issues with the Rollback.Net* console
workflow. Contains a UDP wire-protocol change (packet
version 1 → 2): all participants must run this build to interoperate.

Changed

  • :shield: Wire protocol v2. Every UDP packet header now carries a
    SenderPlayerId field and PacketVersion is bumped 1 → 2. Mismatched
    versions are rejected (never misparsed), so v2 peers cannot talk to v1
    peers. Ship all participants on this version.
  • Rollback.NetClient argument semantics. The 4th arg is
    now this client’s own PlayerId, announced to the host, instead of
    the remote peer’s id. The host now identifies the client by it, so
    Rollback.NetDisconnect <id> / Rollback.NetPeers can reference it.
    Rollback.NetConnect still takes the remote peer’s id. Help and
    usage text updated.
  • Peers now learn each other’s PlayerId from the handshake header on
    any packet (including Hello), instead of only from received input
    frames. An id set explicitly via ConnectToPeer is never overridden.
  • Rollback.NetHost now announces a host identity (LocalPlayerId = 0).
  • Rollback.NetClient now opens a single connection. It previously both
    auto-connected (via StartUdpPeer with RemoteHost set) and called
    ConnectToPeer, creating two peer records for the same endpoint.
  • URollbackNetSubsystem::ConnectToPeer is now idempotent per endpoint:
    reconnecting to an already-connected endpoint refreshes that peer (and
    promotes its id) instead of adding a duplicate record.
  • URollbackNetSubsystem::DisconnectPeer now returns bool (was
    void): true when a matching peer was found and removed.
  • URollbackNetworkBlueprintLibrary::DisconnectRemotePeer Blueprint node
    now returns bool. This adds an output pin; existing graphs that
    ignore it are unaffected.
  • Rollback.NetDisconnect logging is now accurate: it reports remaining
    peers from GetAllPeerInfo() (consistent with Rollback.NetPeers) and
    prints “No connected peer with PlayerId=N” when nothing matched,
    instead of always claiming success.

Added

  • FRollbackTransportConfig::LocalPlayerId (default -1): this peer’s
    own id, announced in every packet header. -1 preserves the previous
    behaviour of learning ids from input frames.
  • FRollbackTransportConfig::DisconnectCooldownSeconds (default 3.0):
    after an explicit DisconnectPeer, the endpoint cannot be
    auto-re-accepted for this window.
  • Automation tests: RollbackCorePro.Network.PeerDisconnectStaysGone,
    .PeerIdentityHandshake, .ConnectEndpointDedup, .DisconnectReturn.

Fixed

  • Disconnected peer immediately reappearing. DisconnectPeer removed
    the peer, but the still-running remote’s next packet hit the
    auto-accept path and re-created it with reset counters. A per-endpoint
    cooldown now prevents resurrection.
  • Rollback.NetDisconnect <id> doing nothing yet reporting success
    for console-connected peers. The host never learned the peer’s id (it
    stayed -1), so the id never matched. Header identity + accurate
    logging fix this.
  • Duplicate peer record on the connecting client. Connecting with an
    explicit id produced two records for the host endpoint (one -1, one
    the id); the -1 record received all traffic and showed real
    RTT/counters while the id record sat idle. Single-connect + endpoint
    dedup fix this.
  • Rollback.NetDisconnect “Remaining peers” disagreeing with
    Rollback.NetPeers
    for unassigned (-1) peers: the two commands
    counted different collections. Both now read GetAllPeerInfo().
  • Flaky RollbackCorePro.Network.PeerDisconnect automation test (it
    asserted connectivity before the receiving subsystem had pumped the
    handshake) made deterministic with a pump loop.

Build / verification

  • Compile-validated clean on UE 5.5, 5.6, and 5.7 via rcp_assemble.ps1; full suite green on 5.7 (15/15).
  • Per-engine staging regenerated for 5.5 / 5.6 / 5.7. The Blueprint DisconnectRemotePeer return-bool change landed afterward and is source-only.

0.2.0a update, 2026-06-16

Added

  • URollbackNetSubsystem::OnPeerReconnected (BlueprintAssignable): fired when a previously-known endpoint is auto-accepted again after a timeout, carrying the restored PlayerId and endpoint. Additive; lets UI distinguish a transient reconnect from a brand-new connection.
  • Rollback.NetJoinNull [Index=0] [LocalPlayerId=1] [LocalPort=0] and Rollback.NetSessions console commands. These complete the console-driven Null-LAN matchmaking loop in a packaged build: a host advertises (Rollback.NetHost), a second instance discovers (Rollback.NetFindNull), lists results (Rollback.NetSessions), and joins (Rollback.NetJoinNull), which brings up the UDP transport (announcing its own id) and connects to the advertised host endpoint. Previously JoinPlatformSessionByIndex was only reachable from the editor Setup Wizard.

Fixed

  • Timed-out peer reconnecting as a fresh PlayerId=-1. When a peer
    went quiet long enough to time out (CheckPeerTimeouts) and then
    resumed sending, the auto-accept path re-created it as a brand-new
    -1 record with reset counters, so OnPeerDisconnected (“Peer timed
    out”) was immediately followed by Rollback.NetPeers showing a
    connected -1 peer with recent activity, which looked contradictory.
    A timeout (unlike an explicit kick) now records the endpoint’s
    PlayerId; the returning peer is re-accepted as the same peer with
    that id restored and fires OnPeerReconnected instead. The explicit
    DisconnectPeer cooldown path is unchanged (kicked peers stay gone).
  • Disconnected peer immediately reappearing. DisconnectPeer removed
    the peer, but the still-running remote’s next packet hit the
    auto-accept path and re-created it with reset counters. A per-endpoint
    cooldown now prevents resurrection.
  • Rollback.NetDisconnect <id> doing nothing yet reporting success
    for console-connected peers. The host never learned the peer’s id (it
    stayed -1), so the id never matched. Header identity + accurate
    logging fix this.
  • Duplicate peer record on the connecting client. Connecting with an
    explicit id produced two records for the host endpoint (one -1, one
    the id); the -1 record received all traffic and showed real
    RTT/counters while the id record sat idle. Single-connect + endpoint
    dedup fix this.
  • Rollback.NetDisconnect “Remaining peers” disagreeing with
    Rollback.NetPeers
    for unassigned (-1) peers — the two commands
    counted different collections. Both now read GetAllPeerInfo().

Optional map (Content/Maps/RollbackNetDemo) for the available 2-Peer UDP Demo:

RollbackNetDemo.umap (20.8 KB)
DefaultEngine.ini (601 Bytes)

Please note that the plugin is still 0.2.0: not recommended for live production games yet. It’s robust, but still “early access” till indicated otherwise.
As always, liberal experimentation with the open source core is recommended at this point. :microscope:

0.2.2 update, 2026-06-18 :chequered_flag:

:video_game: Matchmaking — async Blueprint nodes, configurable search timeout & examples

A Blueprint-first matchmaking pass: the previously hidden session-search timeout is now configurable and readable, and the Find / Host / Join flow is exposed as Advanced-Sessions-style latent nodes with example assets. Additive with no API breaks.

Legend: :sparkles: new Blueprint node · :plus: new API · :counterclockwise_arrows_button: changed · :white_check_mark: tests · :memo: docs · :wrench: tooling · :snake: Python · :puzzle_piece: example asset

:stopwatch: Configurable session-search timeout

  • :counterclockwise_arrows_button: URollbackNetSubsystem::FindPlatformSessions no longer hard-codes a
    10.0f search timeout; it reads a configurable value, so callers can size
    their own “Searching…” watchdog/UI to the real search instead of guessing.
  • :plus: SessionSearchTimeoutSeconds (EditAnywhere, default 10s, ClampMin 0.1)
    plus GetSessionSearchTimeoutSeconds() (BlueprintPure) and
    SetSessionSearchTimeoutSeconds() (BlueprintCallable, clamps to 0.1).

:satellite_antenna: Results-carrying completion delegates

  • :plus: OnFindSessionsComplete(bool, TArray<FRollbackMatchmakingResult>, FString); hands back the whole session list in one shot when a search finishes.
  • :plus: OnCreateSessionComplete(bool, FName SessionName, FString).
  • :plus: OnJoinSessionComplete(bool, FString); fires after the UDP transport
    actually connects on a successful join (kept in lock-step with the generic
    OnMatchmakingComplete via an internal BroadcastJoinComplete helper).

:sparkles: Async / latent matchmaking nodes (the Find / Host / Join trio)

  • :sparkles: Find Rollback Sessions (Async); URollbackFindSessionsAsync: runs a
    search, waits for completion (bounded by the timeout), delivers the list on
    On Found / On Failed. Optional Timeout Seconds pin (-1 = use configured).
  • :sparkles: Host Rollback Session (Async); URollbackCreateSessionAsync: brings up
    the UDP transport on Host Port if needed, advertises the session →
    On Hosted (SessionName) / On Failed.
  • :sparkles: Join Rollback Session (Async); URollbackJoinSessionAsync: joins a Find
    result and waits until the transport connects → On Joined / On Failed.
  • :plus: Pure helpers FindBestRollbackSession (lowest ping with an open slot) and
    FormatRollbackSession (one-line summary) on URollbackNetworkBlueprintLibrary.

:puzzle_piece: Example actor & Blueprint generator

  • :puzzle_piece: ARollbackMatchmakingExampleActor (Blueprintable) wires all three nodes
    into one actor with editable defaults (Match Id, LAN, port, timeout, auto-find)
    and OnSessionsFound / OnHosted / OnJoined / OnError events for UI.
  • :snake: Content/Python/generate_matchmaking_examples.py; run in-editor to create
    /RollbackCorePro/Examples/BP_RollbackMatchmakingExample (a ready-to-drop
    subclass of the example actor).

:white_check_mark: Tests

  • :white_check_mark: New automation tests: RollbackCorePro.Matchmaking.FindSessionsCompleteDelegate,
    .FindSessionsAsyncNode, .FindSessionsAsyncNodeFailure,
    .HostSessionAsyncNodeFailure, .JoinSessionAsyncNodeFailure.
  • :white_check_mark: Added URollbackMatchmakingTestSink, a reflected UObject that captures the
    dynamic-delegate / node-pin payloads tests assert on.
  • :counterclockwise_arrows_button: RollbackCorePro.Network.NullMatchmakingConnect now derives its poll window
    from GetSessionSearchTimeoutSeconds() instead of a magic 12.0 constant.

:memo: Docs

  • :memo: HTML manual: new delegate rows, an async-node table (Find/Host/Join), the
    pure helpers, the example-asset + Python-generator instructions, and an updated
    client-flow snippet showing the obtainable timeout.

:wrench: Project / tooling

  • :wrench: Enabled PythonScriptPlugin in RollbackCorePro.uproject (required to run
    the example generator).

:hammer_and_wrench: Build / verification

  • :hammer_and_wrench: Clean on UE 5.5 - 5.7; RollbackCorePro.Matchmaking automation suite 6/6
    green
    ; the Python generator verified to produce the example Blueprint asset.

A 2-peer demo in action, version 0.2.2+

0.2.2a update, 2026-06-20 :up_arrow:

🎮 UMG matchmaking menu example

  • 🧩 URollbackMatchmakingMenuWidget (with row widget URollbackMatchmakingMenuRow): a fully native UMG menu (no .uasset required): Host / Find buttons and a scrollable list of found sessions, each with a Join button. It builds its own tree in RebuildWidget, drives the Find / Host / Join async nodes (mirroring ARollbackMatchmakingExampleActor), and exposes config (Match Id, LAN, port, timeout, …) plus OnHosted / OnJoined / OnSessionsFound / OnError events. Subclass it as a Widget Blueprint to restyle: the behaviour stays in C++.
  • Rollback.ShowMatchmakingMenu: creates the menu and adds it to the viewport (running game / PIE).
  • 🧩 The example generator now also emits /RollbackCorePro/Examples/WBP_RollbackMatchmakingMenu (a Widget Blueprint subclass of the menu widget).

🖥️ Console-command parity for the async Host/Find/Join flow

  • Rollback.Host [MatchId] [Port=7777] [Slots=2] [LAN=1], Rollback.Find [MatchId] [Max=20] [LAN=1] [Timeout=-1], and Rollback.Join [Index=0]: drive the same URollbackCreateSessionAsync / URollbackFindSessionsAsync / URollbackJoinSessionAsync nodes the Blueprint flow uses, for console/headless testing parity. Rollback.Join joins a result cached by a prior Rollback.Find. (Previously the async flow was Blueprint-only; the existing Rollback.Net* commands call the subsystem directly.)
  • URollbackNetConsoleSink: a small UObject that binds the async nodes' dynamic result pins (which cannot bind lambdas), logs each outcome, and self-releases.

🔧 Editor: one-click example generation

  • 🔧 Generate Example Content on the Rollback toolbar combo (new Examples section) and Generate Example Blueprints on the Setup Wizard's Integration page run the bundled generator via IPythonScriptPlugin (FRollbackCoreProEditorModule::GenerateExampleContent() → returns whether it ran), degrading gracefully with an on-screen note when the Python Editor Script Plugin is unavailable. The wizard reflects the real outcome in its toast and Finish-page summary.
  • 🔧 RollbackCoreProEditor now privately depends on PythonScriptPlugin, and the plugin descriptor enables it.
  • 🐍 generatematchmakingexamples.py extended to also author the WBP menu via WidgetBlueprintFactory.

✅ Tests & verification

  • ✅ New automation tests: RollbackCorePro.Matchmaking.ConsoleSinkHostFailure, .ConsoleCommandsRegistered, RollbackCorePro.UI.MatchmakingMenuRowJoin, .ShowMatchmakingMenuRegistered.

Rollback P2P demo. Edit the file to use your own paths. (Powershell scripts are not allowed in Fab builds.)

README.md (4.3 KB)
RunRollbackP2PDemo.ps1 (3.7 KB)

0.3.0 update, 2026-06-23 :roller_skate:

Rollback correctness & netcode pass: fixes the core replay off-by-one, completes
state restore (velocity / angular velocity / CharacterMovement), and upgrades desync
detection from a local replay check to genuine cross-peer checksum exchange
(auto-wired). Contains a breaking UDP wire-protocol change (packet version 2 → 3):
all peers must run this build to interoperate.

Legend: :bullseye: correctness · :shield: hardening · :globe_with_meridians: networking · :ice: physics/CMC · :plus: new API · :sparkles: new component · :counterclockwise_arrows_button: changed · :warning: breaking · :white_check_mark: tests · :hammer_and_wrench: build/verify · :memo: docs

:bullseye: Rollback correctness

  • :bullseye: Off-by-one double-application fixed. SimulateFrame now snapshots the
    start-of-frame state before ticking, so RollbackToFrame(F) restores
    StateBuffer[F] and replays from F applying the target frame’s input exactly
    once
    . Previously the snapshot was taken after the tick, so every rollback
    re-applied the corrected frame twice and drifted by one frame of its motion. (The
    prior tests used all-neutral input, where the double-apply was invisible.)
  • :white_check_mark: RollbackCorePro.Rollback.NoOpReplayIsIdentity: a no-op rollback (identical
    recorded inputs) must reproduce the exact pre-rollback state.

:globe_with_meridians: Cross-peer desync detection: was local-only

  • :warning: BREAKING: wire protocol v3. New StateChecksum packet type; PacketVersion
    bumped 2 → 3. Mismatched versions are rejected (never misparsed), so v3 peers
    cannot talk to ≤ 0.2.2 (v2) peers. Ship all participants on this build.
  • :plus: Genuine cross-peer detection: peers exchange per-frame state checksums and
    compare each against their own; a true mismatch calls RecordDesync() and broadcasts
    OnDesyncDetected(Frame, peer, delta). Previously the “desync detector” only compared
    an entity’s own pre/post-rollback checksums locally (a replay-determinism check) and
    could never observe two machines diverging.
  • :plus: Auto-wired: URollbackManager records and (on an interval) sends the
    confirmed-frame checksum automatically while transport is connected — the demos
    inherit it for free. New API: RecordLocalStateChecksum, SendStateChecksum,
    ComputeStateChecksum (order-independent XOR aggregate), GetConfirmedFrame /
    GetConfirmedFrameForPlayer.
  • :counterclockwise_arrows_button: Confirmed-frame policy: only frames whose inputs are all in from every peer
    (highest contiguous received input) are checksummed, so the state is final and a
    mismatch is a real desync: no false positives from in-flight prediction.

:ice: State-complete physics / CharacterMovement rollback

  • :wrench: Velocity is now restored on rollback (it was saved but never re-applied).
    Physics roots get SetPhysicsLinearVelocity; every entity also surfaces it via
    LastRestoredVelocity and the new OnRollbackStateLoaded(Frame, Velocity) event so
    custom/kinematic movement can re-apply it.
  • :ice: Full rigid-body + CharacterMovement state captured/restored. The snapshot gains
    angular velocity and CMC MovementMode/CustomMovementMode; save/load now restore
    linear + angular physics velocity and CMC velocity/movement mode. Surfaced via
    LastRestoredAngularVelocity.
  • :sparkles: URollbackCharacterMovementComponent: a UCharacterMovementComponent subclass
    driven by the rollback loop (its own engine tick disabled); the state component calls
    its RollbackStep(dt) once per fixed frame for both live and replayed frames.
  • :warning: Determinism boundary unchanged: restore is now correct and complete, but
    cross-machine forward-sim determinism (collision / Chaos) is not guaranteed: use
    flat/kinematic movement or a deterministic mover where that matters.

:shield: Robustness

  • :shield: Rollback to an evicted/missing frame now aborts cleanly instead of replaying
    forward from the live state and corrupting the buffer. RollbackToFrame no-ops (no
    state change, no rollback counted) when no entity holds the target frame’s snapshot;
    usable rollback depth is bounded to MaxBufferSize snapshots per entity.

:white_check_mark: Tests & verification

  • :white_check_mark: 12 new automation tests (Rollback.NoOpReplayIsIdentity,
    Rollback.MissingFrameAborts, State.VelocityRestored,
    State.AngularVelocityRestored, State.CharacterMovementRestored,
    State.ChecksumDeterministic, State.ChecksumOrderIndependent,
    Network.CrossPeerChecksumDesync, Network.ConfirmedFrameTracking,
    Network.ManagerAutoChecksum, Movement.RollbackStepSafe,
    Movement.CMCDrivenByRollbackTick). Full RollbackCorePro suite 40/41 green (the
    two-process-only Network.NullMatchmakingConnect fails single-process by design).
  • :hammer_and_wrench: Two-process packaged P2P session verified: Null OSS matchmaking + UDP
    transport + the new v3 StateChecksum traffic ran ~1440 frames with both peers
    connected, no desync false positives, and no transport/parse errors.

Updating the manual to 0.3.0 (online & PDF) will take a few days, until then these features are undocumented. Thanks for your patience. :folded_hands:

1 Like

[0.5.0 update] - 2026-08-07 :roller_coaster:

Breaking changes: all peers must upgrade together :palms_up_together:

  • The wire protocol moves from v5 to v6. Every packet header now carries the sender’s own frame
    advantage, which is what lets pacing act on the difference between two peers instead of on the
    local confirmation age. A 0.4.0 peer and a 0.5.0 peer cannot play together. The failure is at
    connect time rather than a mid-match desync: through matchmaking the protocol string is a search
    filter, so a 0.4.0 peer never discovers a 0.5.0 session; connecting directly, version-mismatched
    packets are discarded on receipt — including the handshake — so it presents as a connection
    timeout with no explicit version error. Upgrade every participant, including packaged demos.
  • Max Frame Advantage changes meaning, and its default moves 8 → 4. It used to bound how far
    the local clock ran ahead of its own newest confirmed frame; it now bounds the smoothed
    difference between the two peers’ self-reported advantages. The difference counts each peer’s
    contribution once, so the new default of 4 tolerates 8 frames of real divergence. Projects that
    raised the old value should not carry that number across — start from the new default.

Behaviour changes: read before upgrading :coral:

  • Input delay now defaults to 2 frames (Simulation | Latency → Input Delay). Input polled
    while simulating frame F now drives frame F+2. This is the right default for netplay and
    removes the large majority of rollbacks, but it does change gameplay timing. Offline-only
    projects that want zero added latency should set it to 0. It must be identical on every peer.
  • The rollback subsystems are no longer created in editor worlds (Game and PIE only). The
    Rollback Stats panel now reports “inactive” outside PIE instead of showing an editor-world
    frame counter.

Fixed

  • Peers’ fixed-step clocks no longer free-run apart. Nothing previously kept two peers in
    step: the faster machine pulled ahead until incoming corrections were older than the rollback
    window, at which point RollbackToFrame rejected them outright and the peers diverged for good.
    URollbackManager now paces the local clock against the peer, clamped to Max Rollback Depth so
    a correction is always still shallow enough to apply. Exposed via GetFrameAdvantage(),
    GetFrameAdvantageDelta(), ShouldStallForFrameAdvantage() and StalledFrameCount. Regression
    test: RollbackCorePro.Production.FrameAdvantagePacing (a peer previously reached a 58-frame
    advantage against a 12-frame window in 60 ticks).
  • A high-latency match no longer runs in permanent slow motion. Pacing originally stalled on
    confirmation age: how old the newest confirmed frame was — which latency alone drives past the
    bound even when both clocks are perfectly in step, so every peer on a slow link stalled
    continuously. The pre-fix soak measured both peers pinned at the bound with 1183 stalls out of
    ~1199 frames and an effective rate of 0.80 Hz. Pacing now acts on the smoothed difference
    between the two peers’ self-reported advantages, so latency shared by both sides cancels to zero
    and only genuine divergence stalls anything. Regression test:
    RollbackCorePro.Production.SymmetricLatencyDoesNotStall.
  • Pacing no longer starves the peer it is pacing against. Withholding a frame also withholds
    the input the other peer needs to advance its own confirmation, so a clock that stopped entirely
    drove its peer’s advantage up until it stalled too, and the pair oscillated — measured on the
    180 ms profile as the effective rate halving to 40.84 Hz with confirmation frozen for 15.58 s of
    a 20 s run. Corrections are now applied at a duty cycle, withholding at most one frame in four,
    so the clock yields ground at 75% rate while the input stream never stops. The clock still holds
    outright against a peer that has genuinely gone silent while already past the rollback window,
    since frames advanced there could never be reconciled. Regression tests:
    RollbackCorePro.Production.PacingKeepsInputFlowing,
    RollbackCorePro.Production.StallHoldsInsideRollbackWindow.
  • A two-second lag spike no longer latches a session into permanent divergence.
    Max Future Input Frames (was 120) rejected any input frame more than that far ahead of the
    receiver’s clock, and the rejection path discards the whole packet — including the redundant
    copies of in-window frames that exist to heal holes. So once a peer fell 2 s behind, every packet
    from the leader was discarded wholesale and its confirmation froze permanently. That freeze then
    fed pacing the wrong sign, because GetFrameAdvantage() is CurrentFrame - ConfirmedFrame: the
    peer that was behind looked far ahead and got stalled, driving it further behind. Measured: 464
    packets rejected on one peer, confirmation stuck at frame 133 while the peer had produced 904.
    The window now defaults to 600, matching Max Remote Input Frames Per Player, which is what
    actually bounds the memory a peer can make you hold — so this costs no new exposure.
  • One dropped frame no longer freezes a peer’s confirmation for the rest of the session.
    Confirmation advances only over a contiguous prefix, and redundancy cannot heal a frame that was
    never sent in the first place. Callers poll input once per engine tick while the fixed clock can
    step up to Max Catch-Up Frames Per Tick frames, so any engine frame slower than the fixed rate
    silently dropped a frame from the wire — permanently. The documented Blueprint path
    (SendRollbackInputForCurrentFrame) and both demo environments were all affected. New
    SendInputFramesThrough() sends every frame from a per-player watermark through the current one,
    repeating the polled input across the gap — which is exactly what the local simulation did with
    it. Regression tests: RollbackCorePro.Network.InputStreamHasNoHoles,
    RollbackCorePro.Network.InputStreamPartialFillRetries.
  • Analog input no longer desyncs peers. FRollbackInput::Axes is a double-precision FVector
    but the wire carries three floats, so any stick value that is not exactly float-representable
    left the sender simulating a frame with 0.59999999999999998 while the receiver used
    0.60000002384185791. Axes are now normalised to wire precision at every entry point
    (InjectInputForFrame, SendInputFrame, BufferRemoteInputForRollback, local input capture)
    via FRollbackInput::QuantizeAxes(). Regression test:
    RollbackCorePro.Network.AnalogInputSurvivesWire.
  • URollbackManager and URollbackNetSubsystem no longer instantiate in editor worlds, where
    they ticked a second fixed-step clock and pumped a transport for the editor’s whole lifetime and
    gave every PIE session a sibling manager on an unrelated CurrentFrame. Regression test:
    RollbackCorePro.Production.NoEditorWorldSubsystems.
  • The Setup Wizard now persists settings with TryUpdateDefaultConfigFile() instead of
    SaveConfig(). URollbackCoreProSettings is a DefaultConfig class, so wizard changes were
    landing in the machine-local Saved/Config tree rather than the project’s DefaultEngine.ini
    that the Project Settings panel writes: the two paths could disagree and wizard choices were
    never source-controlled.
  • The wizard’s FilterPlugin check tested for a path that the blanket /Content/... rule already
    matched, so it always reported the rule missing and its “fix” appended a redundant line. It now
    tests for the exclusion rule that actually decides whether the demo content ships, and the fix
    removes that rule.

Known issues :smiling_face_with_tear:

  • The 180 ms / 3% loss soak profile still fails intermittently: roughly one run in four. When
    it trips, one peer’s confirmation freezes for around 3 s and the effective rate drops to ~53 Hz.
    Correctness is not affected in any observed run: DesyncCount and RejectedRollbackCount stayed
    at zero throughout, so this degrades pacing, it does not diverge peers. It is also distinct from
    the packet-rejection latch fixed above — rejections are zero in the failing runs — and its cause
    is not yet identified. For reference, 180 ms one-way is 360 ms RTT, well past what rollback
    fighting games target, and that profile already needs Max Rollback Depth raised to 20. The four
    profiles at or below 120 ms one-way are clean across every run.

Added

  • Simulation | Latency project settings: Input Delay (frames) (default 2), Enable
    Frame-Advantage Pacing
    (default on), Max Frame Advantage (frames) (default 8).
  • URollbackStateComponent::SetInputDelayFrames() / GetInputDelayFrames() for runtime control.
  • Rollback.NetAutoJoinNull [MatchId] [LocalPlayerId] [LocalPort]: searches for a Null LAN
    session and joins the best result automatically. The search is async, so a single -ExecCmds
    could not previously sequence find and join; this makes the whole matchmaking path scriptable.
  • Tools/Run-AutomationTests.ps1 — headless suite runner that exits non-zero on failure and
    fails the build if suspiciously few tests ran (which is how a silently-skipped plugin load
    presents itself).
  • .github/workflows/ci.yml — builds and runs the suite on UE 5.7, verifies two-process
    matchmaking, then compile-validates the plugin against 5.5, 5.6 and 5.7.
  • Tools/README.md documenting the supported packaging pipeline.