V2 ships as one fighting game that has to host dozens of distinct experiences —
a CPU arcade ladder, a branching story campaign, a 100-player shard hub, a
card-collector faction season, a turn-based hex campaign, a 30-entrant Royal
Rumble — without any one of them forking the match loop or the save system. The
seam that makes that possible is the V2Modes module: a game-instance
subsystem that holds a registry of mode definitions, plans how each one is
loaded, drives a seven-state lifecycle, and injects per-mode HUD without ever
touching the deterministic combat core. The registry is small and real
(V2/ue/Source/V2Modes/Public/V2ModeRegistrySubsystem.h,
V2/ue/Source/V2Modes/Private/V2ModeRegistrySubsystem.cpp, ~500 lines); the
data it governs is enormous. V2ModeTypes.h is a single 347 KB header
declaring 135 enum classes and 286 USTRUCTs, and V2ModeCatalog.cpp
(~274 KB) is the C++ factory that builds the canonical content for every mode.
This is the page that explains how a player goes from "pick Arcade" to "a match
is running," how training and replay are layered on top of the same
deterministic simulation, and — just as importantly — which parts of that story
are shipped C++ versus convention-built path strings pointing at artifacts that
do not yet exist on disk. The hub for the V2 architecture set is
../V2_ARCHITECTURE.md.
Why a registry of definitions rather than 52 hand-written game modes? Because V2
treats a "mode" as data plus a load plan, not a subclass. A mode is an
FV2ModeDefinition value — an id, a category, a network model, a save model, a
player-count band, a Game Feature plugin URL, a primary map path, a dependency
list, and a set of HUD injections — and activating it is a deterministic,
testable transformation of that value into an ordered list of load steps. That
choice is the same discipline the rest of V2 follows (see
V2 Product Promise on the target-artifact
convention): the registry can be exhaustively unit-tested headless, long before
any of the maps, widgets, or Game Feature plugins it names actually exist.
What ships, honestly#
The mode-registry machinery is real, complete, and heavily tested.
UV2ModeRegistrySubsystem implements registration, lookup, validation, the
load-plan builder, activation/deactivation, the lifecycle delegate, and the
Hathor UGC-world bridge as ordinary C++ that compiles into the editor and runs
under automation. FV2ModeCatalog::BuildDefaultModeDefinitions() constructs a
fixed roster of 52 canonical modes (Modes.Reserve(52) and exactly 52
MakeMode(...) calls, V2ModeCatalog.cpp:6285+), and the catalogue's per-mode
content builders (arcade ladders, story chapters, MyGM booking sim, Royal Rumble
entry scheduling, the arcade scoring evaluators) contain real domain math, not
placeholders. The replay subsystem is equally real: a versioned binary file
format with determinism hashes (V2Input), a hand-rolled encoder/decoder, and a
replay-takeover-to-practice creator in V2OnlineServices that validates against
a live replay store and fails loud when the replay is missing. The single
automation spec V2/ue/Source/V2Tests/Automation/Modes.Module.spec.cpp carries
713 TestTrue/TestEqual/TestNotNull assertions over the registry,
catalogue, and championship data; Input.Module.spec.cpp references the replay
types 156 times.
Three honest caveats matter. First, the per-mode Game Feature plugins do not
exist as content on disk. MakeMode synthesizes each definition's
GameFeaturePluginURL as Plugins/GameFeatures/V2Mode_<Id>/V2Mode_<Id>.uplugin
and its PrimaryMapPath as /Game/V2/Modes/<Id>/L_<Id>
(BuildPluginURL/BuildMapPath, V2ModeCatalog.cpp:30-44), but a scan of
V2/ue/Plugins finds only three .uplugin files total (V2AICommentary,
V2AdaptiveAI, V2AssetLinter) and none named V2Mode_* or V2Event_*.
These URLs and map paths are target artifacts in the precise sense
V2 Product Promise defines — names the registry can
plan against today and content production fills in later. Second, activation
records the plan; it does not execute a real GameFeature load. ActivateMode
builds the plan, loads any required C++ modules through FModuleManager, and
records an ActivateGameFeature step in ExecutedSteps — but it never calls
UGameFeaturesSubsystem, never opens a level, and never spawns a widget. It is
an honest orchestration model that stops at the engine-integration boundary
rather than faking it. Third, a spec/code discrepancy on the replay budget:
the architecture prose says "≤ 1 MB / 5-min match," but the shipped
FV2ReplayFileCompressionProfile pins TargetMaxBytesPerMinute = 1048576 — 1
MiB per minute, five times looser than the prose. The code is the authority;
treat the prose as aspirational. The narrative tables in the monolith
(V2Event_Marathon, V2Mode_TimeAttack, …) are likewise design intent, not
shipped plugin directories.
The mode-definition data model#
Everything starts from FV2ModeDefinition (V2ModeTypes.h:9037). Its fields
are the vocabulary the rest of the system reasons over:
Category—EV2ModeCategory, 14 values:Arcade, Story, Tower, Vault, Hub, OpenWorld, Brawler, Career, Booking, Collector, BattleRoyal, StrategyRpg, Crew, Roguelike. Category drives default HUD injection selection.NetworkModel—EV2ModeNetworkModel, 7 values:Offline, RollbackOneVsOne, ClientServerHub, ClientServerMultiFighter, ClientServerOpenWorld, AsyncService, LocalTurnBased. This is the field that decides whether a mode needs the online backbone.PreferredSessionMode— anEV2NetcodeSessionMode(e.g.Rollback,ClientServer,LanTournament) handed toV2Netcode; see Rollback Netcode & Tag Team.SaveModel—EV2ModeSaveModel, 8 values:None, Profile, StorySlot, CareerSlot, UniverseSeason, SharedWorld, OnlineService, Vault. Each maps to a slot shape in Live-Ops, Store & Progression.- Player bands —
MinPlayers/MaxPlayers/MaxSpectators, plus the flagsbRequiresOnlineServices,bRequiresProfile,bSupportsReplay,bSupportsRollbackDeterminism,bAvailableAtLaunch. - Wiring —
GameFeaturePluginURL,OwningModuleName,PrimaryMapPath,Tags, an array ofFV2ModeDependencySpec, and an array ofFV2ModeHUDInjectionSpec.
IsValidDefinition() (V2ModeTypes.cpp:10090) is a real gate, not a truthiness
check: it rejects a definition with no id, no display name, no DesignSection,
no Game Feature URL, no owning module, an inverted player range
(MaxPlayers < MinPlayers), a negative spectator cap, or any invalid dependency
or HUD injection. RequiresNetworkServices() returns true when
bRequiresOnlineServices is set or the network model is any of the three
client-server variants or AsyncService — that single predicate is what the
load planner uses to refuse an online mode that the caller asked to run offline.
The canonical catalogue — 52 modes#
BuildDefaultModeDefinitions() is the source of truth for what V2 offers. Each
entry is one MakeMode(...) call carrying the id, display name, the
design-section reference that ties it back to V2_TODOS.md, and the
category/net/save tuple. MakeMode (V2ModeCatalog.cpp:1744) fills in the
convention-built plugin URL, owning module, and map path, attaches the default
dependency set, and asks BuildDefaultHUDInjectionsForMode for category-aware
HUD. A representative slice:
| Mode id | Category | Network model | Save model | Players |
|---|---|---|---|---|
Mode.Arcade.Classic |
Arcade | Offline | Profile | 1–2 |
Mode.Arcade.GhostBattle |
Arcade | AsyncService | OnlineService | 1–2 |
Mode.Story |
Story | Offline | StorySlot | 1 |
Mode.Tower.Time |
Tower | AsyncService | OnlineService | 1–2 |
Mode.BattleHub |
Hub | ClientServerHub | SharedWorld | 1–100 |
Mode.WorldTour |
OpenWorld | ClientServerOpenWorld | SharedWorld | 1 |
Mode.WWE.Universe |
Booking | AsyncService | UniverseSeason | 1–4 |
Mode.MyFaction |
Collector | AsyncService | OnlineService | 1–5 |
Mode.RoyalRumble |
BattleRoyal | ClientServerMultiFighter | Profile | 2–30 |
Mode.TagTeam.ThreeVsThree |
Brawler | ClientServerMultiFighter | Profile | 2–6 |
Mode.ChessKombat |
StrategyRpg | LocalTurnBased | Profile | 1–2 |
Mode.RoguelikeAdventure |
Roguelike | AsyncService | Profile | 1–2 |
The full 52 cover three Arcade variants, Story, four Tower/Tournament/Survivor
modes, the Krypt vault, Battle Hub and its sub-modes, World Tour and its
vehicle/social variants, Tekken Force, Devil Within, the WWE Universe/Showcase/
Slammys trio, four career modes, MyGM, MyFACTION, MyRISE, four Royal Rumble /
Battle Royale entries (including three 100-player variants up to
Mode.BattleRoyale.Hybrid100), tag-team 2v2/3v3, UFC Fight Night booking, the
SoulCalibur strategy-RPG suite, Def Jam story and tag battle, Crew/Faction, a
roguelike, and three "specialty" rulesets (boxing sim, Bushido Blade one-hit, MK
one-hit tournament). BuildCanonicalModeIds() simply projects the ids out of
this list, and IsCanonicalModeId() is Contains over it — so the registry's
notion of "a real mode" is exactly this catalogue and nothing else. This page's
job is the core catalogue; the monolith also carries a separate match-flow
variant matrix (Local Versus, Time/Score Attack, Wager, Handicap, First Blood,
Best-of-N, LAN — each a lightweight wrapper around a standard fight) and a list
of signature-event plugins (V2Event_KingOfIronFist,
V2Event_WrestleMania, V2Event_EVO_Top8, …), both of which are design intent
rather than shipped plugin directories today.
The one piece of mode content that lives as external data rather than C++ is the
arcade mini-game suite: V2/balance/modes/arcade-mini-game-suite.json
(schema v2.modes.arcade-mini-game-suite-data.v1, section 133) enumerates eight
cabinets — Pinball, Air Hockey, Mini-Golf, Darts, Pool/Billiards, Cooking, Photo
Tournament, Tekken Bowl — and those eight match EV2ArcadeMiniGameSuiteKind
one-for-one. BuildDefaultArcadeMiniGameSuiteCatalog() provides the runtime
mirror, and RegisterArcadeMiniGameSuiteCatalog lets a caller override it after
validation.
Activating a mode — the load plan#
The interesting logic is BuildModeLoadPlan
(V2ModeRegistrySubsystem.cpp:283). Given an FV2ModeLoadRequest (mode id,
profile id, source context, and the bPreloadOnly / bForceReload /
bAllowOnlineServices / bInjectHUD / bOpenPrimaryMap switches), it
validates the request, finds the definition, refuses online modes when
bAllowOnlineServices is false, and then emits an ordered list of
FV2ModeLoadSteps. The step kinds are a nine-value enum
(EV2ModeLoadStepKind), and the planner adds them conditionally with explicit
sort orders so the final plan is deterministic:
ActivateMode then runs that plan: it sets lifecycle to Loading, deactivates
any previously-active mode, loads the required C++ modules for real through
FModuleManager::LoadModulePtr (failing the whole activation with a specific
reason if a required module won't load), and records a successful
FV2ModeLoadResult carrying the applied HUD injections, the executed steps, and
the resolved network/session models. The lifecycle itself is a seven-state enum
(EV2ModeLifecycleState:
Uninitialized → Idle → Loading → Preloaded/Active → Unloading → Failed), and
every transition broadcasts OnModeLifecycleChanged(ModeId, NewState) so UI and
telemetry can follow a load without polling. UnregisterMode refuses to remove
the currently-active mode; DeactivateActiveMode is idempotent (deactivating
with nothing active returns success at Idle). A worked example: requesting
Mode.BattleHub with bAllowOnlineServices = false never produces a plan at
all — BuildModeLoadPlan returns false with "Mode.BattleHub requires online
services, but the request disallows them," because BattleHub's
ClientServerHub network model trips RequiresNetworkServices(). Requesting it
with services allowed yields a nine-step (or fewer) plan whose OpenPrimaryMap
step is marked non-required and is dropped entirely under bPreloadOnly, which
is how the front end pre-warms a hub before the player commits.
CaptureRegistrySnapshot() rolls the whole registry into one
FV2ModeRegistrySnapshot value — registered modes, active id, lifecycle state,
published Hathor world versions, and a battery of tag-team / arcade-suite
readiness counts and booleans — which is the single struct the automation spec
asserts against and the natural payload for a debug overlay.
HUD injection — presentation without GAS access#
Each mode definition carries FV2ModeHUDInjectionSpec entries
(V2ModeTypes.h:4228): an InjectionId, a SlotName (default Slot.Center),
a FSoftClassPath WidgetClassPath, a Priority, and the flags bRequired,
bClearOnModeExit, and — critically — bGameplayInert. That last flag encodes
the architectural rule that mode HUD is presentation only: a Royal Rumble
entrants strip or a UFC scorecard interlude can be slotted in and out without
ever reading the Ability System Component directly. The load planner only adds
an InjectHUD step when bInjectHUD is set and the definition actually has
injections, and BuildDefaultHUDInjectionsForMode derives sensible defaults
from the mode's category. This dovetails with the MVVM HUD architecture
described in
UI, HUD, VR/AR & Accessibility, where
widgets bind to view-models rather than to live gameplay state.
Training, trials & the sim/present split#
Training mode is not a separate engine; it is the same deterministic
simulation as a live match, with non-deterministic tooling layered on the
presentation side. V2Netcode exposes two real types
(V2/ue/Source/V2Netcode/Public/V2RollbackSimWorld.h): FV2SimWorld — the
integer-frame, side-effect-free simulator the rollback path drives — and
FV2PresentWorld (aliased FV2_PresentWorld), which turns the latest confirmed
sim frame into an FV2PresentWorldFrame for rendering. Lab overlays,
hit-confirm trainers, and frame-data readouts attach to the present world; they
never feed the sim world, so determinism is preserved by construction. See
Combat System for the sim
world's internals and
Animation & Input Pipeline for how inputs
reach it.
Two training-adjacent systems are modeled as real catalogue data. World Tour
master training (FV2WorldTourMasterTrainingSpec, V2ModeTypes.h:5336) binds
a fighter to a master, a 3–5 mission band, a SignatureMoveIds list, an 8-move
equip cap, and per-relationship-tier unlocks for advanced moves and finishers —
the data behind "learn a move from an NPC master and equip it on your avatar."
Ghost Battle (FV2ArcadeGhostBattleRules, default mode id
Mode.Arcade.GhostBattle) names the GhostBattle.GetUploadedGhosts service and
flags bRequiresPlayerUploadedGhostData, the async-service hook that lets an
arcade ladder node fight a recording of another player. The arcade scoring that
grades those runs is a genuine algorithm, not a stub: EvaluateArcadeFightScore
(V2ModeCatalog.cpp:4778) computes TimeBonus = max(0, 6000 − seconds×40),
DamageBonus = round((100 − damage%) × 25) with a +500 flawless bonus,
ComboBonus = min(combo×120, 2400), a flat 1500 finisher bonus, a 1000
base, and resolves a letter grade through the tuning; EvaluateArcadeRunScore
aggregates per-fight results into a cumulative score and average-based rank,
rejecting the whole run if any fight is invalid. These would fail immediately on
a random or hardcoded return — the tests pin the exact arithmetic.
Replay pipeline & file format#
A replay in V2 is inputs plus determinism hashes, re-simulated rather than
recorded as video. The file model lives in V2Input
(V2/ue/Source/V2Input/Public/V2InputTypes.h):
FV2ReplayFileHeader(:4721) — file magicV2RF, format version, replay and match ids, ruleset and stage ids, build and content versions, platform and region, aCustomRulesHashandCosmeticManifestHash, the match start time, duration,RngSeed, tick rate (default 60 Hz), total frames, and three determinism anchors:InputStreamHash,InitialStateHash,FinalStateHash. The codec defaults toOodle;bServerAuthoritativerecords provenance.FV2ReplayInputEventRecord— a per-frame, per-slotFV2InputSample, the payload the re-sim actually consumes.FV2ReplayStateSnapshotRecord— periodic delta-encoded snapshots with aStateHash, abKeyframeflag, and rawPayloadBytes, used to seek without replaying from frame zero.FV2ReplayFileChunkIndexEntry— a compressed-chunk index over input events and snapshots, with per-chunk CRC and compressed/uncompressed sizes.
FV2InputReplayEncoder (V2InputReplayEncoder.h) is a real binary serializer —
EncodeReplayFile/DecodeReplayFile, EncodeMatchStream/DecodeMatchStream,
EncodeSamples/DecodeSamples, and a HashEncodedStream — and its
implementation writes the literal V,2,R,F magic bytes and length-checked
names/strings, bailing out to an empty buffer on any malformed field rather than
emitting a corrupt file (V2InputReplayEncoder.cpp). The byte budget is
computed, not asserted: FV2ReplayFile::EstimateCompressedBytesPerMinute() does
a real ceiling division,
(compressedBytes × 60000 + durationMs − 1) / durationMs, against the
FV2ReplayFileCompressionProfile's TargetMaxBytesPerMinute (1 MiB/min, codec
non-None, positive packing sizes, bounded keyframe interval). The replay
determinism gate the architecture promises — every replay re-played in CI
must reproduce bit-identical outcomes — is anchored on the header's three hashes
plus the per-snapshot StateHash; a drifted re-sim mismatches FinalStateHash
and fails. The anti-cheat replay-drift pipeline in
Online Backbone & Competitive Integrity
reuses the same hashes.
Replay takeover → offline practice#
The "pause a replay at any frame and take over a side" feature is implemented in
V2OnlineServices, not V2Modes. FV2ReplayTakeoverSpec
(V2OnlineServicesTypes.h:13051) declares the capability — supported sides
(EV2ReplayTakeoverSide: PlayerOne/PlayerTwo), use cases
(EV2ReplayTakeoverUseCase: Tech, SetupResponse, CrewLab), and the flags
bPauseAtAnyFrame, bConvertsToOfflinePracticeScenario,
bPreservesOriginalReplay, and bFrameExactStateRestore. The runtime entry
point is UV2OnlineServicesSubsystem::CreateReplayTakeoverPracticeScenario, and
it is honest about its preconditions: it validates the request against the
takeover spec, then requires the replay to actually exist — registered in
CloudReplays or present as a queued local download — and returns false with
"Replay takeover requires a registered cloud replay or queued local replay
download" otherwise. Only then does it build, validate, de-duplicate, and store
a FV2ReplayTakeoverPracticeScenario. The original replay is preserved
read-only; the takeover produces a new practice scenario. This is the
architecture's "Replay Takeover → Training Mode Scenario" arrow realized as
fail-loud C++.
Tag team, multi-fighter & the crew lab#
Multi-fighter topology is its own data axis. FV2MultiFighterModeSpec
(V2ModeTypes.h:4274) carries an EV2MultiFighterTopology
(OneVsOne/TagTeam/FreeForAll/RoyalRumble), team and per-team fighter counts,
active-fighter and total-entrant caps, and capability flags. The tag-team
mechanics catalogue (FV2TagTeamMechanicsCatalog, registered through the
subsystem) enumerates formats (EV2TagTeamFormatKind: 2v2 / 3v3 / Trinity),
mechanics (EV2TagTeamMechanicKind:
TagCancel, Snapback, DelayedHyperCombo, AssistCall, CrossAssault, XFactorPandora, BaroqueDuoCancel),
and online queues (RankedTagTeam, TagCoop, CrewBattle). The registry snapshot
exposes counts and readiness booleans for each, which is how a launch gate can
assert "tag-team is catalogued and valid" headless. The deterministic
execution of tagging, assists, and DHC lives in V2Gameplay/V2Netcode; this
module owns the catalogue and topology, while
Rollback Netcode & Tag Team owns the
frame-accurate handshake and the per-side snapshot budget.
The Hathor UGC-world bridge#
One non-obvious seam: the mode registry is a subscriber to the Hathor sister
monorepo's world-publication events. On Initialize the subsystem calls
SubscribeToHathorWorldPublishedEvents(), and HandleHathorWorldPublishedEvent
validates an incoming hathor.world.published event, converts it to a
FV2HathorWorldVersionRef available to gameplay, and queues a
FV2HathorLoreCompilerExportRequest targeting @hathor/lore-compiler with
bOutOfBandMetadata and bRollbackSafe set. The "rollback-safe / out-of-band"
flags are the whole point: published UGC worlds can feed lore and content into
V2 modes without ever entering the deterministic frame path. This is the
mode-side hook for the cross-monorepo integration detailed in
Security, Compliance & Sister-Monorepo Integration;
the open-world and special modes that consume those worlds are covered in
Open World, Co-op & Special Modes.
Where this connects#
The mode registry is the orchestration layer that almost every other V2 system
hangs off. The catalogue's network and save models are honored by the
Online Backbone and persisted
through
Live-Ops, Store & Progression;
the racing modes have their own parallel module set described in
Racing & Vehicle Architecture; replays
and takeovers surface in the companion and AI services
(Esports, Companion & AI Services);
the presentation beats that bracket each mode (intros, win quotes, entrances)
are in
Presentation, A/V & Signature Content;
and the catalogue counts, validation gates, and replay determinism checks are
enforced by the test and release machinery in
Telemetry, Performance, Testing & Release Gates.
For the module names and the dependency graph that keeps V2Modes from reaching
into frame-critical code, start at
Glossary & Module Topology. For how the
cooked content behind every PrimaryMapPath is produced, see
Build, Cook, Assets, Data & Production.
Related#
- V2 Product Promise — the target-artifact convention that makes "the plugin URL exists but the plugin doesn't" a feature, not a lie
- Glossary & Module Topology — the
27-module map
V2Modessits inside - Combat System: GAS, Frame Data & Determinism
and Rollback Netcode & Tag Team — the
FV2SimWorldtraining runs on - Animation & Input Pipeline — the input samples replays are made of
- Open World, Co-op & Special Modes and Racing & Vehicle Architecture — the heaviest mode categories
- Online Backbone & Competitive Integrity — replay drift detection and the network models modes declare
- The architecture hub: ../V2_ARCHITECTURE.md