# Rollback Netcode, Client-Server & Tag-Team Sim

V2's online-fighting backbone is the part of the project that has to be _frame-
perfect under loss and latency_, not just plausible. A 1v1 ranked match between
two players 80 ms apart cannot wait for the opponent's input to arrive before
drawing a frame, so V2 predicts the missing input, draws immediately, and — when
the real input arrives and disagrees — rewinds the world to the last agreed
frame and re-simulates everything in between, in a single tick, before the
player ever sees it. That whole mechanism lives in the **`V2Netcode`** Unreal
C++ module (`V2/ue/Source/V2Netcode/`, ten `.cpp`/ten `.h` plus
`V2Netcode.Build.cs`, which depends on Unreal's `ReplicationGraph` and `NetCore`
modules and on `V2Combat` / `V2Core` / `V2Input`). The module is real and
compiles on the on-box engine — the build tree carries sixteen `V2Netcode/*.o`
objects under
`V2/ue/Intermediate/Build/Linux/x64/UnrealEditor/Development/V2Netcode/`. This
page is the architecture-side companion for the rollback session, the transport,
the network-quality fallback ladder, the client-server backbone, and tag-team;
the section hub is [../V2_ARCHITECTURE.md](../V2_ARCHITECTURE.md).

The design rests on a deliberate split. A deterministic, integer-frame, side-
effect-free simulator — `FV2SimWorld` (`V2RollbackSimWorld.h:6`) — holds the
only state that ever rolls back, and a separate `FV2PresentWorld`
(`V2RollbackSimWorld.h:117`) interpolates that state to the display refresh rate
without ever feeding back into the sim. The combat layer that decides _what a
hit does_ lives next door in `V2Combat`/`V2Gameplay` and is documented in
[./combat-system-gas-frame-data-and-determinism.md](./combat-system-gas-frame-data-and-determinism.md);
this page owns the layer that decides _which frames to simulate, in what order,
under what network conditions_. The orchestrator that ties them together is
`FV2RollbackSession` (`V2RollbackSession.cpp`), and almost everything below is a
method on it or a type it consumes.

## What ships, honestly

The **rollback core is real and tested**. `FV2RollbackSession::TickFrame`,
prediction, correction detection, rewind-and-resimulate, the "Recovering
Connection…" freeze, the one-time delay-fallback offer, snapshot capture/restore
under an 8 KB budget, the deterministic state hash, and the network-quality
handshake/fallback analyzer are all substantive C++ exercised by the automation
suite. `Netcode.Module.spec.cpp` is a single large automation test
(`V2.Netcode.Module` plus `V2.Rollback.CustomSocket`) with **604** assertions
that drive `FV2SimWorld::StepFrame` directly, run a live `FV2RollbackSession`
through resimulation and the frozen-overlay path (`Netcode.Module.spec.cpp`
around lines 4672 and 4733), and round-trip the packet codec. A second test,
`V2Tests/Private/Netcode/GoldenReplayCorpus.spec.cpp`, backs the determinism
claim with a golden-replay corpus.

Three honest qualifications matter. **First, the transport is a wire-format
codec, not a live socket.** `FV2RollbackPacketCodec` (`V2RollbackTransport.cpp`)
builds, encodes, decodes, ACK-bitfields and XOR-parity-protects packets into
`TArray<uint8>`, and produces a `FV2RollbackUdpDatagram` carrying UDP/DTLS
_flags_ — but there is no `FSocket`, `ISocketSubsystem`, `SendTo`, `RecvFrom`,
or DTLS handshake anywhere in the module. The byte format and the adaptive-send
policy are implemented and tested; pushing those bytes over a real wire is not
yet wired. **Second, FEC parity is generated and preserved but not
reconstructed** — `BuildXorParity` and `AttachFecParity` exist and survive an
encode/decode round-trip, but there is no function that rebuilds a dropped
packet from parity. **Third, several "server" surfaces are data contracts with
validators, not live services**: the dedicated-server remote-admin (RCON-style)
and server-replay recording types expose `Evaluate()`/`IsValid…()` and
default-builders, not a running socket or recorder. Where the source monolith
claims more than the code delivers, this page says so — the most consequential
case is tag-team, below.

## The two worlds: deterministic sim vs. interpolated present

`FV2SimWorld` is the authority. It advances on a fixed integer step
(`FV2RollbackConfig::SimulationHz = 60`, `GetFixedDeltaSeconds()`), stores
fighter state as integers — fixed-point position (`FixedX`/`FixedY`), integer
`Health` (default 1000), integer `Meter`, frame counters, a `RngCursor` — and
exposes exactly the primitives rollback needs: `Reset(MatchSeed, FighterCount)`,
`CaptureSnapshot()`, `RestoreSnapshot()`,
`StepFrame(FrameInputs, SideEffectPolicy)`, and `ComputeStateHash()`. Crucially
it is _side-effect-free on demand_: `StepFrame` takes an
`EV2RollbackSideEffectPolicy` (`Emit`, `SuppressCosmetics`, `TelemetryOnly`), so
the same code path that plays audio and spawns particles on a live frame can be
told to stay silent during a resimulation.

`FV2PresentWorld` never decides anything. It receives sim snapshots via
`SubmitSimSnapshot`, keeps the previous and current snapshot, and
`TickPresentation(DisplayDeltaSeconds)` interpolates between them at the display
refresh rate — so a 60 Hz sim can be presented at 120 Hz or 144 Hz without
touching the simulation clock. It also owns the recovery overlay
(`ShowRecoveryOverlay`/`ClearRecoveryOverlay`). The automation suite asserts the
contract that keeps these honest: _"present interpolation leaves sim hash
unchanged"_ — drawing a frame must never perturb the rolled-back state. The two
classes are aliased `FV2_SimWorld` / `FV2_PresentWorld` for readers coming from
the monolith's naming.

## The rollback session loop

`FV2RollbackSession::TickFrame(LocalInput, LocalPlayerIndex, NewlyConfirmedRemoteInputs)`
is the per-frame entry point. The sequence is:

```mermaid
sequenceDiagram
    participant Game as Game tick
    participant Session as FV2RollbackSession
    participant Pred as InputPredictor
    participant Sim as FV2SimWorld
    participant Present as FV2PresentWorld

    Game->>Session: TickFrame(localInput, idx, confirmedRemote[])
    Session->>Pred: SubmitConfirmedInput(remote) for each
    Pred-->>Session: Correction (RequiresRollback?)
    alt a prediction was wrong
        Session->>Session: ApplyCorrections → earliest frame
        Session->>Sim: RestoreSnapshot(CorrectedFrame-1)
        loop CorrectedFrame..CurrentFrame
            Session->>Sim: StepFrame(inputs, SuppressCosmetics)
            Session->>Present: SubmitSimSnapshot(...)
        end
        Session-->>Game: TickResult{ bRecovered, RollbackFrames }
    else rollback budget exceeded
        Session->>Present: ShowRecoveryOverlay()
        Session-->>Game: TickResult{ bSimulationFrozen, "Recovering Connection..." }
    else normal
        Session->>Pred: BuildInputsForFrame(NextFrame)
        Session->>Sim: StepFrame(inputs, Emit)
        Session->>Present: SubmitSimSnapshot(...)
        Session-->>Game: TickResult{ FrameNumber, StateHash }
    end
```

Concretely (`V2RollbackSession.cpp:18`): each newly-confirmed remote input is
handed to the predictor, which returns a `FV2RollbackCorrection`. Corrections
that `RequiresRollback()` are collected; `ApplyCorrections` picks the
**earliest** corrected frame and calls `ResimulateFromCorrection`. That method
computes `RestoreFrame = CorrectedFrame − 1` and
`RollbackFrames = CurrentFrame − RestoreFrame`, restores the snapshot for
`RestoreFrame`, then replays every frame from `CorrectedFrame` to `CurrentFrame`
through `StepFrame` — using `SuppressCosmetics` when
`bSuppressCosmeticsDuringResim` is set, so the rewind is silent. Each replayed
frame is re-snapshotted and re-submitted to the present world, and a
`FV2RollbackTelemetryEvent` is emitted carrying the before/after state hashes,
the resimulated frame range, and a per-frame `ReplayedFrames` array for
postmortem. If no correction is pending, the session simply advances one frame
with `Emit`.

A worked example: at `CurrentFrame = 100` the session has been predicting player
1's input as "hold forward" since frame 96. Player 1's real frame-97 input
("crouch") arrives. The predictor flags a correction at frame 97;
`RestoreFrame = 96`; the session restores the frame-96 snapshot and re-steps 97,
98, 99, 100 with the corrected input and `SuppressCosmetics`, producing a new
frame-100 state hash — four frames of rewind, resolved inside one tick, with no
audio or VFX replayed. The returned `FV2RollbackTickResult` reports
`RollbackFrames = 4`, `bRecovered = true`, and the new `StateHash`.

## Input prediction and the rollback trigger

Prediction is **last-confirmed repetition**, the classic GGPO-style choice.
`FV2RollbackInputPredictor::PredictInput(Frame, Player)`
(`V2RollbackInputPredictor.cpp`) returns the confirmed input if one exists for
that (frame, player); otherwise it repeats `LastConfirmedByPlayer[Player]` —
re-stamped to the new frame via `BuildPredictionSample` and flagged
`bPredicted = true`. Frame 0 predicts neutral. Inputs are keyed by a packed
`(FrameNumber << 16) | PlayerIndex`.

The rollback trigger is deliberately narrow.
`FV2RollbackCorrection:: RequiresRollback()` (`V2NetcodeTypes.cpp:2033`) returns
true only when
`CorrectedFrame != INDEX_NONE && !PredictedInput.IsSameInput(ConfirmedInput)` —
i.e., a confirmed input only forces a rewind when the prediction was _actually
wrong_. A correctly-predicted "hold forward" that turns out to be "hold forward"
costs nothing; this is what keeps a clean connection at zero rewinds despite
predicting every frame. The net-graph's **frame advantage** is computed as
`GetLastConfirmedFrame(0) − GetLastConfirmedFrame(1)`
(`V2RollbackSession.cpp:79`), the standard "who is ahead" diagnostic.

## Snapshots and the 8 KB budget

A `FV2SimSnapshot` (`V2NetcodeTypes.h:1157`) is the complete rollback state for
a frame: the fighter array, the frame inputs, and explicit sub-snapshots for
every system that must survive a rewind — `ASCStateHashes`,
`AttributeSetValues`, `AbilityActivations`, `AnimationTimers`, `HitboxStates`,
`InputBufferFrames`, `RngStreams`, and the Tekken-family state (movement,
air-juggle, Heat, Rage, Ki Charge, Power Crush, stance). The automation suite
checks each of these individually (_"sim snapshot serializes ability activation
state,"_ _"…animation timer state,"_ _"…hitbox state,"_ _"…RNG streams,"_
_"…input buffer"_).

The budget is enforced, not aspirational. `SnapshotBudgetBytesPerFighter`
defaults to **8192** and is clamped `[512, 8192]` in the config.
`IsWithinBudget` computes
`EstimateSerializedBytesPerFighter() <= BudgetBytesPerFighter`, where the
per-fighter figure is the total estimate divided (rounded up) by fighter count
(`V2NetcodeTypes.cpp:1090`). The suite asserts both directions — _"initial
snapshot is within 8KB per fighter"_ and _"oversized snapshot fails budget."_
For tag-team this is what gives the monolith's "≤ 24 KB per side" figure: three
fighters × 8 KB, computed rather than stored. The session keeps
`SnapshotHistoryFrames = 90` frames of history (`TrimSnapshotHistory` evicts
anything older than `CurrentFrame − 90`), so the rewind depth is bounded by
memory as well as by `MaxRollbackFrames`.

## Determinism: hashing, fixed-point, strict floating point

Rollback is only correct if two machines re-simulating the same inputs reach
byte-identical state. `FV2SimWorld::ComputeStateHash()`
(`V2RollbackSimWorld.cpp:1833`) folds `MatchSeed`, `CurrentFrame`, and each
fighter's `RefreshStateHash()` with `HashCombineFast`. The subtle part is FName
hashing: `GetTypeHash(FName)` hashes the _process-local_ name-table index, which
differs between processes, so the module defines `V2StableNameHash`
(`V2NetcodeTypes.h:13`) as `FCrc::StrCrc32` over the name string and uses it for
the stance tags inside `RefreshStateHash` (`V2NetcodeTypes.cpp:921`). That makes
the hash content-based and stable across the client, the server's re-simulation
check, and golden-replay playback.

Determinism is also a _build_ property. `V2Server.Target.cs` sets
`DETERMINISM=1`, `V2_DETERMINISM=1`, and `V2_STRICT_FP=1`, and injects strict
floating-point compiler arguments — `/fp:strict /fp:except-` on MSVC,
`-fno-fast-math -ffp-contract=off` on Clang — so the compiler cannot reorder
floats or fuse multiply-adds differently on two platforms. Combined with the
integer-fixed-point sim state, this is what lets the golden-replay corpus assert
bit-identical re-simulation rather than "close enough." The deeper treatment of
`StepFrame`, the per-tick order, and the golden corpus lives in the combat page
([./combat-system-gas-frame-data-and-determinism.md](./combat-system-gas-frame-data-and-determinism.md));
the input-encoding side is in
[./animation-and-input-pipeline.md](./animation-and-input-pipeline.md).

## Transport: the wire-format codec

`FV2RollbackPacketCodec` (`V2RollbackTransport.cpp`) is a complete, tested
packet format and adaptive-send policy. `EncodePacket` writes a `V2RB` magic, a
version, sequence/ACK/ACK-bits, the frame range, a payload CRC, FEC
group/index/size, a flags byte, then the encoded input bytes and parity bytes;
`DecodePacket` reverses it and rejects anything malformed. Inputs are encoded
through `FV2InputReplayEncoder::EncodeSamples` (reused from `V2Input`), so the
netcode payload and the replay format share one encoder. Three pieces give the
source's "adaptive send" promise teeth:

- **ACK piggyback.** `BuildAckBits(LastAck, ReceivedSequences)` packs the last
  32 acknowledgements into a bitfield carried on every outbound packet — no
  separate ACK traffic.
- **Adaptive resend window.** `BuildAdaptiveFramePacket` calls
  `BuildResendWindow` to attach the current frame _plus_ the last
  `ResendInputFrames` (default 8) frames of input, so a single lost datagram is
  covered by the next one. The header flags `bEveryFrameInputSend` and
  `bBatchResendForLossRecovery` are set precisely when the window is contiguous
  and current.
- **FEC parity.** `BuildXorParity` XORs a group of packets into parity bytes;
  `AttachFecParity` rides them along. As noted above, parity is _generated and
  round-tripped_ but not yet used to reconstruct a dropped packet — an honest
  gap.

`BuildUdpDatagram` wraps an encoded packet in a `FV2RollbackUdpDatagram` with
`SocketProfile = "V2.Rollback.UDP.DTLS"` and `bUsesUdp` / `bUsesDtls` /
`bSequenceNumbered` / `bAckPiggybacked` / `bFecProtected` flags. The channel
topology (`BuildDefaultReplicationChannels`, in `V2NetworkQuality.cpp:236`)
confirms the monolith's "custom socket for rollback, Iris for everything else":

| Channel        | Transport profile           | Iris | Reliable | Max Hz | Notes                        |
| -------------- | --------------------------- | ---- | -------- | ------ | ---------------------------- |
| RollbackInputs | `V2.Rollback.CustomSocket`  | no   | no       | 60     | deterministic, custom socket |
| MatchState     | `UE.Iris.MatchState`        | yes  | yes      | 30     | out-of-rollback state        |
| ChatPresence   | `UE.Iris.ChatPresence`      | yes  | yes      | 10     | social                       |
| Spectator      | `UE.Iris.Spectator`         | yes  | no       | 20     | read-only viewers            |
| Telemetry      | `UE.Iris.RollbackTelemetry` | yes  | yes      | 5      | postmortem feed              |
| Lobby          | `UE.Iris.Lobby`             | yes  | yes      | 15     | pre-match                    |

The suite asserts the load-bearing invariant directly: _"rollback input channel
does not use Iris"_ and _"non-rollback Iris channels stay off rollback socket."_

## Network quality, handshake, and graceful fallback

`FV2NetworkQualityAnalyzer` (`V2NetworkQuality.cpp`) turns measured samples into
a mode recommendation. `EvaluateHandshake` averages RTT and packet loss and
takes the max jitter across the pre-match samples, then grades:

| Grade          | Condition (defaults)                                         | Recommended mode | Ranked                  |
| -------------- | ------------------------------------------------------------ | ---------------- | ----------------------- |
| **Poor**       | avg RTT > 150 ms **or** avg loss ≥ 8 %                       | Delay            | not eligible            |
| **Borderline** | avg RTT > 100 ms **or** max jitter > 18 ms **or** loss ≥ 4 % | Hybrid           | eligible                |
| **Good**       | otherwise                                                    | Rollback         | eligible (Wi-Fi warned) |

The thresholds are the real config fields — `DelayFallbackRttThresholdMs = 150`,
`HybridRttThresholdMs = 100`, `PoorPacketLossPercent = 8`. A Wi-Fi or mobile
sample sets a non-blocking "wired recommended" warning. Mid-match, the system
does not silently downgrade: `EvaluateDelayFallbackDecision` only offers the
delay fallback after **sustained** pressure — `ShouldOfferDelayFallback`
requires `SustainedRollbackPressureFrames` (1800 frames = 30 s at 60 Hz) of
_consecutive_ samples whose `RollbackFrames` exceed `MaxRollbackFrames` (8).
When that fires in a ranked match, the decision sets `bRankedSuspended` and, if
the player declines, `bRankedNoContest` — the source's "one-time fallback offer
→ ranked no-contest." Extreme RTT (> 150 ms) defaults straight to delay without
an offer.

```mermaid
flowchart LR
    Start[1v1 match start] --> HS[Pre-match handshake]
    HS -->|grade| G{Quality?}
    G -->|Good| RB[Rollback 60Hz]
    G -->|Borderline| HY[Hybrid]
    G -->|Poor| DL[Delay-based]
    RB -->|rewinds > 8f for 30s| OFF[One-time fallback offer]
    OFF -->|accept| DL
    OFF -->|decline ranked| NC[Ranked no-contest]
```

`ComputeReliabilityScore` produces a per-account 0–100 figure — `100` minus an
average penalty of
`RTT/200·25 + jitter/40·25 + loss/10·40 + (10 if rollback > 8)`, clamped — and
`BuildDiagnosticsStamp` writes RTT/jitter/loss/grade/reliability into a
`FV2NetworkDiagnosticsStamp` bound to a match record and an ops endpoint
`Ops.NetworkHealth.V2`. That stamp is the data behind the monolith's "results
stamped in match record" and "ops dashboard"; the dashboard consumer itself is
outside this module — see
[./online-backbone-and-competitive-integrity.md](./online-backbone-and-competitive-integrity.md)
and
[./telemetry-performance-testing-and-release-gates.md](./telemetry-performance-testing-and-release-gates.md).

## Client-server netcode

Not every mode rolls back. Free-for-all, Royal Rumble's full ring, open-world
co-op, hubs, and tooling-heavy training use Unreal's authoritative client-server
path. The dedicated-server build target is real: `V2Server.Target.cs`
(`Type = Server`, `[SupportedPlatforms(Server)]`) with the deterministic build
flags above and a gRPC generation step.

The **replication graph** is `UV2ReplicationGraph : UReplicationGraph`
(`V2ReplicationGraph.h`). `BuildDefaultPriorityBands` and
`RouteActorForConnection` encode exactly the four priority bands the monolith
promises, and the suite asserts each weight:

| Band           | Priority | Relevance                        |
| -------------- | -------- | -------------------------------- |
| OwnPawn        | **1.0**  | always relevant, not spatialized |
| NearbyFighter  | **0.8**  | spatialized, within max distance |
| DistantFighter | **0.3**  | spatialized                      |
| AmbientProp    | **0.1**  | spatialized                      |

Server tickrate adapts via `FV2AdaptiveServerTickratePolicy`: default 60 Hz, 128
Hz for ≤ 2-player high-frequency sessions, 30 Hz under load (downshift at 85 %
CPU, recover at 65 %), allowed set `{30, 60, 128}` — but
`bRollbackModesAlways60Hz`, `bRankedOneVsOneMandatory60Hz`, and
`bTournamentMandatory60Hz` pin the competitive paths to 60 Hz regardless. For
the client-server hit path,
`FV2ClientServerHitConfirmationRequest::ResolveHostConfirmation` implements
client-side prediction with host authority and a 2-frame confirmation grace,
resolving to `ConfirmedByHost` / `RejectedByHost` / `LateHostConfirmation`. This
is the **other** authority model: rollback uses `SharedDeterministicLoop` (both
peers run the same sim, no arbiter — the suite asserts _"rollback hit authority
does not use server arbitration"_), while client-server uses host arbitration.

Two further server surfaces are **data contracts with validators**, not running
services, and should be read that way: server-replay recording
(`FV2ServerReplayRecordingConfig`/`InputFrame`/`Manifest` — authoritative input
capture, deterministic input hashing, dispute-evidence metadata, server
signature, PII redaction, 30-day retention, 54000-frame cap) and the RCON-style
remote admin (`FV2ServerRemoteAdminPolicy`/`CommandEnvelope`/`Result` —
token-hash auth, loopback-only by default, an allow-list of
Status/ListPlayers/Kick/Broadcast/ SetTickRate/GracefulShutdown, audit). Their
`Evaluate()` and `IsValid…()` methods are real and validated; the live socket
and on-disk recorder are not in this module.

## Tag-team architecture — honest correction

The monolith states that "Snapback / DHC / X-Factor / Cross Assault are
abilities in `V2Gameplay` with their own GAS subclass." **In the current code
they are not.** There are no `UGameplayAbility` subclasses for these mechanics —
a search of `V2Gameplay` finds none, and the names resolve instead to a
**deterministic data-contract catalog** in `V2Modes`:
`FV2TagTeamMechanicsCatalog` (`V2ModeTypes.h:4514`), validated by
`TagTeamMechanics.spec.cpp` (`V2.Modes.TagTeamMechanics.AssetContract`). Treat
the GAS-subclass language as aspirational until those abilities ship; what
exists today is the spec layer that describes them.

That spec layer is, however, genuine and tested. The catalog carries:

- **3 formats** (`EV2TagTeamFormatKind`): 2v2 (two fighters/team, one active,
  switchable mid-combo via tag button), 3v3 (assist calls enabled), and Trinity
  (three fighters, once-per-match anchor assist).
- **7 mechanics** (`EV2TagTeamMechanicKind`): TagCancel (designer-curated
  launcher animation, combo extender), Snapback (forces opponent tag-out;
  snapped-out fighter regenerates HP off-screen), DelayedHyperCombo
  (partner-super cancel, spends meter), AssistCall (single-move assist with a
  **360-frame** cooldown), CrossAssault (one player controls two fighters),
  XFactorPandora (trades HP for damage/speed), and BaroqueDuoCancel
  (partner-call cost, combo extender). The suite asserts each is discoverable
  and that the assist cooldown is exactly 360.
- **Team HP** (`FV2TagTeamTeamHpSpec`): per-fighter bars, partner HP on the HUD,
  off-screen regen at `0.35 %/s`, team KO only when the entire roster is at 0
  HP.
- **Net/sim** (`FV2TagTeamNetSimSpec`): 60 Hz rollback, 2 active sim
  fighters/side, deterministic tag transition, frame-accurate tag animation,
  replay encoder records tag actions — i.e. tag-team rides the _same_
  `FV2RollbackSession` loop, just with more fighters per `FV2SimSnapshot`.
- **Authoring** (`FV2TagTeamAuthoringSpec`): real CSVs on disk —
  `V2/balance/tag/tag_cancel_dhc_matrix.csv` (per-fighter pairing rows with
  cross-IP and cinematic restriction columns) and
  `V2/balance/tag/team_theme_music.csv`.
- **3 online queues**: RankedTagTeam (4 players, role-preference balancing),
  TagCoop (two players per team), CrewBattle (best-of-five single-elimination, 6
  players).

The registry round-trip is verified too: `UV2ModeRegistrySubsystem` accepts the
catalog and `CaptureRegistrySnapshot` reports counts (3 formats / 7 mechanics /
3 queues) and readiness flags. The broader mode lifecycle is in
[./game-modes-training-and-replay.md](./game-modes-training-and-replay.md).

## Edge cases and failure modes

- **Rollback budget exceeded.** When `RollbackFrames > MaxRollbackFrames` (8),
  `EnterRecoveringConnection` freezes the sim, sets `bSimulationFrozen`, shows
  the `Netcode.RecoveringConnection` overlay with "Recovering Connection…", and
  reports the state as `Recovering`. The session stays frozen until corrections
  stop arriving, then clears.
- **Missing or un-restorable snapshot.** If the snapshot for `RestoreFrame` is
  absent (evicted past the 90-frame history) or `RestoreSnapshot` fails, the
  session transitions to `FallbackOffered` rather than corrupting state — a
  fail-loud seam, not a silent guess.
- **Cosmetic double-fire.** Resimulation always runs under `SuppressCosmetics`
  (when configured), so a hit that already played its sound on the predicted
  frame does not replay it on rewind; only the final, reconciled frame emits.
- **Empty handshake.** `EvaluateHandshake` with zero samples grades **Poor** and
  recommends Delay — absence of data is treated as bad, not good.
- **Cross-process hash drift.** Mitigated by `V2StableNameHash`; a regression
  here surfaces as a golden-replay mismatch in CI, not as a desync in the wild.

## Configuration reference

`FV2RollbackConfig` (`V2NetcodeTypes.h:114`) is the single tuning surface, with
clamped UPROPERTYs and an `IsValidConfig()` guard:

| Field                             | Default | Meaning                                     |
| --------------------------------- | ------- | ------------------------------------------- |
| `SimulationHz`                    | 60      | fixed sim step                              |
| `MaxRollbackFrames`               | 8       | rewind cap before "Recovering Connection"   |
| `PredictionWindowFrames`          | 8       | how far ahead inputs are predicted          |
| `SnapshotBudgetBytesPerFighter`   | 8192    | per-fighter snapshot cap (clamped 512–8192) |
| `SnapshotHistoryFrames`           | 90      | rewind-able history depth                   |
| `ResendInputFrames`               | 8       | adaptive resend-window size                 |
| `FecGroupSize`                    | 4       | XOR parity group                            |
| `AckWindowBits`                   | 32      | ACK bitfield width                          |
| `HybridRttThresholdMs`            | 100     | Good→Borderline boundary                    |
| `DelayFallbackRttThresholdMs`     | 150     | Borderline→Poor / extreme-RTT boundary      |
| `PoorPacketLossPercent`           | 8       | loss threshold for Poor                     |
| `SustainedRollbackPressureFrames` | 1800    | 30 s of pressure before a fallback offer    |
| `bRequireEncryptedTransport`      | true    | sets DTLS flags on packets                  |
| `bSuppressCosmeticsDuringResim`   | true    | silence rewinds                             |

`UV2NetcodeBlueprintLibrary` exposes 27 `UFUNCTION`s over this surface
(`BuildDefaultRollbackConfig`, `EvaluateNetworkHandshake`,
`EvaluateDelayFallbackDecision`, `EncodeRollbackPacket`/`DecodeRollbackPacket`,
`ConfirmClientServerPredictedHit`, the adaptive-tickrate and server-admin
builders, …), which is how designers and tests reach the C++ without owning a
session object.

## How it connects

The rollback session _consumes_ combat: `FV2SimWorld::ResolveRollbackHits` and
`StepFrame` apply the frame-data and hit geometry owned by
[./combat-system-gas-frame-data-and-determinism.md](./combat-system-gas-frame-data-and-determinism.md),
and the input samples it predicts come from
[./animation-and-input-pipeline.md](./animation-and-input-pipeline.md) (whose
`FV2InputReplayEncoder` is the same encoder the transport reuses). It _feeds_
the replay and training systems in
[./game-modes-training-and-replay.md](./game-modes-training-and-replay.md) — the
sim/present split is exactly why training runs on the live sim while lab
overlays attach to the present world. Its diagnostics flow into
[./online-backbone-and-competitive-integrity.md](./online-backbone-and-competitive-integrity.md)
and
[./telemetry-performance-testing-and-release-gates.md](./telemetry-performance-testing-and-release-gates.md);
the net-graph overlay surfaces through
[./ui-hud-vr-ar-and-accessibility.md](./ui-hud-vr-ar-and-accessibility.md). The
module vocabulary (`FV2SimWorld`, snapshot, state hash, priority band) is
indexed in
[./glossary-and-module-topology.md](./glossary-and-module-topology.md), and the
"netcode that respects the player's connection" promise is set in
[./v2-product-promise.md](./v2-product-promise.md). The racing and special-mode
backbones reuse the same client-server primitives —
[./racing-and-vehicle-architecture.md](./racing-and-vehicle-architecture.md) and
[./open-world-coop-and-special-modes.md](./open-world-coop-and-special-modes.md)
— while store/progression, esports services, build/cook, and compliance sit at
[./live-ops-store-progression-and-community.md](./live-ops-store-progression-and-community.md),
[./esports-companion-and-ai-services.md](./esports-companion-and-ai-services.md),
[./build-cook-assets-data-and-production.md](./build-cook-assets-data-and-production.md),
and
[./security-compliance-and-sister-monorepo-integration.md](./security-compliance-and-sister-monorepo-integration.md).

## Related

- [Combat System: GAS, Frame Data & Determinism](./combat-system-gas-frame-data-and-determinism.md)
  — the sim-world step, hit resolution, and golden-replay corpus
- [Animation & Input Pipeline](./animation-and-input-pipeline.md) — the input
  samples and the shared replay encoder
- [Game Modes, Training & Replay](./game-modes-training-and-replay.md) —
  tag-team mode lifecycle and the replay/training takeover
- [Online Backbone & Competitive Integrity](./online-backbone-and-competitive-integrity.md)
  and
  [Telemetry, Performance, Testing & Release Gates](./telemetry-performance-testing-and-release-gates.md)
  — reliability scores, diagnostics stamps, and CI determinism gates
- [Glossary & Module Topology](./glossary-and-module-topology.md) and
  [V2 Product Promise](./v2-product-promise.md) — vocabulary and intent
- The section hub: [../V2_ARCHITECTURE.md](../V2_ARCHITECTURE.md)
