Fighting Game · Architecture

Rollback Netcode, Client-Server & Tag-Team Sim

A focused page within the Fighting Game Architecture documentation. The full map and every sibling page live in the Architecture hub.

14sections16 minread2diagrams4tables

On this page

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.

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; 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 reconstructedBuildXorParity 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:

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); the input-encoding side is in ./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.

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

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 UFUNCTIONs 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, and the input samples it predicts come from ./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 — 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 and ./telemetry-performance-testing-and-release-gates.md; the net-graph overlay surfaces through ./ui-hud-vr-ar-and-accessibility.md. The module vocabulary (FV2SimWorld, snapshot, state hash, priority band) is indexed in ./glossary-and-module-topology.md, and the "netcode that respects the player's connection" promise is set in ./v2-product-promise.md. The racing and special-mode backbones reuse the same client-server primitives — ./racing-and-vehicle-architecture.md and ./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, ./esports-companion-and-ai-services.md, ./build-cook-assets-data-and-production.md, and ./security-compliance-and-sister-monorepo-integration.md.