V4 is six genuinely different games — a Rainbow-Six breach, a Hitman sandbox, a
Commandos squad-tactics puzzle, a Black-Myth boss duel, an Age-of-Empires
ladder, a couch-co-op Contra run — that share one roster, one perception model,
and one online backbone. Nothing fuses that catalogue into a shippable product
unless a player can go from "pick a mode" to "a match is running" without each
genre forking the engine, and unless the game keeps growing for years after
launch through seasons, battle passes, ranked ladders, and DLC operators. Those
two jobs — mode orchestration and live service — are the subject of this
page. The seam that makes the first work is the V4Modes module: a pair of
game-instance subsystems plus a real UGameFeatureAction that discovers ruleset
plugins on disk, streams their assets, and registers them as switchable modes.
The seam that makes the second work is UV4LiveServiceSubsystem composing
hand-authored JSON manifests into season/battle-pass/ranked/calendar data,
backed by a Rust online-services crate that mounts the §56 service surface.
This page explains how both are wired, what is real C++/Rust on disk versus
authored-but- uncooked content, and where the shipped code is honestly simpler
than the architecture prose. The hub for the V4 architecture set is
../V4_ARCHITECTURE.md.
The design choice underneath everything is the same one the rest of V4 follows:
a "mode" is data plus a load plan, not a C++ subclass. A mode is an
FV4ModeDefinition value, and a ruleset is a GameFeature plugin discovered
from a JSON stand-in. That keeps the mode registry exhaustively unit-testable
headless — long before any map is cooked — and it keeps live-service content
(cosmetics, DLC, events) flowing through the same plugin boundary that the
per-cell rulesets already use, never touching the deterministic gameplay core.
What ships, honestly#
The mode-orchestration machinery is real, compiled, and tested, and it goes
one engine-integration step further than its V2 ancestor. UV4ModeSubsystem
(V4/ue/Source/V4Modes/Private/V4ModeSubsystem.cpp, 245 lines) implements
registration, lookup, a four-result transition, weighted quick-play selection,
and a local-multiplayer admission gate as ordinary C++.
UV4ModeBootstrapSubsystem (268 lines) scans the plugins directory for mode
stand-ins and registers them. UV4GameFeatureAction_ActivateModeAssets (200
lines) is a genuine UGameFeatureAction subclass that performs real async
asset streaming through UAssetManager on activation — the exact boundary
V2's mode module stopped short of. The catalogue on disk is substantial: 34
*.uplugin GameFeature descriptors under V4/ue/Plugins/ (e.g.
V4Mode_Tactical_R6Modern, V4Mode_Stealth_Hitman, V4Mode_RTS_Asymmetric,
V4DLC_Operator_Season01_Nyx), most with their own Source/ module,
*.Build.cs, and a compiled Binaries/Linux/libUnrealEditor-*.so. Two
automation specs pin it:
V4/ue/Source/V4Tests/Private/V4ModesTests/ModeSpec.cpp (89
TestTrue/TestEqual assertions over the registry, bootstrap, quick-play, and
GameFeature action) and V4LiveServiceTests/LiveServiceSpec.cpp (84
assertions), alongside per-plugin specs such as R6ModernSpec.cpp (103) and
ReplayModeSpec.cpp (123) inside the ~92-file V4Tests suite.
The live-service subsystem is real domain code, not a manifest reader that
returns its input: UV4LiveServiceSubsystem
(V4/ue/Source/V4OnlineServices/Private/V4LiveServiceSubsystem.cpp, 1116 lines)
exposes ~40 BlueprintPure builders and validators for season cadence, battle
passes, cell-themed cosmetics, operator/mission DLC, the live calendar, ranked
ladders, partnerships, and signature events. The Rust backend is real too:
apps/v4/online-services is one Axum crate whose service_router()
(src/lib.rs:24) nests 20 sub-routers onto tested domain services — OAuth+PKCE
login, Glicko-2 matchmaking, session lifecycle, the replay vault, leaderboards,
moderation, compliance, community, store, partnerships, and esports brackets.
Four honest caveats matter. First, the content is uncooked. A scan of the 34
plugins finds 170 *.uasset.v4asset.json JSON stand-ins and zero binary
.uasset — V4 represents every asset and level as a JSON descriptor under the
.uasset.v4asset.json / .umap.v4asset.json convention. The plugin
scaffolding, per-mode C++ (e.g. V4R6ModernGadgetCatalog.cpp), and data tables
(DT_Operator_R6Modern.json) are genuinely on disk; the cooked binaries are
not. Second, the shipped mode "state machine" is flatter than the prose. The
architecture's MainMenu → ModeSelect → … → MatchResult diagram (§Mode-State
Machine) is design intent; V4ModeTypes.h encodes only a current-mode pointer
and EV4ModeTransitionResult (four values: Succeeded, AlreadyActive,
UnknownMode, Blocked). Third, the 25-row service-inventory table is one
crate, not 25 deployments, and its state is in-memory Arc<Mutex<T>>, not the
PostgreSQL/Redis/ClickHouse the Data Stores section names — those are the
deployment target. Fourth, a stale-doc correction: the architecture's
2026-06-12 note that "only the login router mounts real routes" is now outdated
— the shipped src/http.rs mounts real REST routes onto every service
(matchmaking /tickets+/find, sessions /allocate+/transition, replays
/star, …). Code is the authority.
The mode-definition data model#
Everything starts from FV4ModeDefinition
(V4/ue/Source/V4Modes/Public/V4ModeTypes.h:17). It is deliberately lean — five
fields, not V2's twenty-odd:
ModeId(FName) — the registry key; an empty id is rejected at registration.DisplayName(FText) — localized label.GameFeaturePluginURL(FString) — the plugin the mode activates.EntryMap(TSoftObjectPtr<UWorld>) — the level to open, by soft path.ActivationAssets(TArray<FSoftObjectPath>) — assets streamed on activation.
There is no NetworkModel/SaveModel enum carried on the definition the way V2
does it; in V4 those concerns live in the cell module and the online subsystems,
and the mode definition stays a thin routing record. EV4ModeTransitionResult
(:8) and FV4QuickPlayEntry (:56, an id + clamped Weight + bEnabled
flag) complete the type surface. This minimalism is the point: the registry is a
router, and everything heavier hangs off the plugin it names.
Mode discovery, registration & activation#
The interesting architecture is the three-stage pipeline that turns an on-disk plugin tree into switchable, registered modes. It is encoded across three files, and it is the part of V4 that crosses the GameFeature engine boundary V2 left as a recorded "step."
Stage one — discovery. On Initialize, UV4ModeBootstrapSubsystem
(V4ModeBootstrapSubsystem.cpp:51) walks FPaths::ProjectPluginsDir(), finds
each *.uplugin, and recursively collects *.uasset.v4asset.json stand-ins
under its Content/. ParseModeStandIn (:137) deserializes each with
FJsonSerializer, keeps only files carrying a modeId (catalogs and charts
share the suffix and are skipped), derives the mount root and object path, and
reads displayName, contentRoot, entryMap, and maxPlayers. Duplicate ids
are de-duplicated with a logged warning, and the descriptor list is sorted for
determinism. This is real file I/O and JSON parsing, not a hardcoded roster.
Stage two — the GameFeature action. Each descriptor becomes a
UV4GameFeatureAction_ActivateModeAssets. Its lifecycle methods are real
overrides of the engine's UGameFeatureAction contract:
OnGameFeatureActivating (V4GameFeatureAction_ActivateModeAssets.cpp:25)
issues a genuine
UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(...) at
AsyncLoadHighPriority for every valid ActivationAssets path, registers the
mode with every live game instance's UV4ModeSubsystem, and subscribes to
FWorldDelegates::OnStartGameInstance so late-starting instances still get the
mode. OnGameFeatureDeactivating unregisters and releases the streamable
handle; FlushAssetLoad/GetLoadedAssetCount (:114) let a test block on and
count the load. This is the seam V2's ActivateMode explicitly stopped at
("records an ActivateGameFeature step … never calls
UGameFeaturesSubsystem"). V4 actually drives the streamable manager and the
GameFeature callbacks. The honest limit: with no cooked .uasset on disk, the
soft paths resolve to nothing at runtime — but the loading machinery, handle
lifecycle, and counting are real and pinned by ModeSpec.cpp.
Stage three — the registry. UV4ModeSubsystem::RegisterMode (:15) stores
the definition by id (rejecting an empty id); TransitionToMode (:44) is the
switch. It returns UnknownMode for an unregistered id, AlreadyActive if you
re-select the current mode (still echoing the plugin URL), and otherwise
CompleteTransition sets CurrentModeId, records LastTransition, and
broadcasts OnModeTransitionCompleted so UI and telemetry follow the switch
without polling. UnregisterMode refuses to remove the currently-active mode —
the same self-protection V2's registry has. AV4ModeRoutingActor
(V4ModeRoutingActor.cpp:11) is the in-level entry point: a placeable actor
with RouteToMode, RouteToConfiguredMode, and RouteToQuickPlay, each
resolving the subsystem off the game instance and returning success only on a
Succeeded transition.
Quick play & deterministic mode selection#
Quick play is a deterministic weighted lottery, which matters because V4
shares daily-mission and playlist seeds across the community (see
./world-streaming-procgen.md on seed-identical
generation). The algorithm appears twice —
UV4ModeSubsystem::SelectQuickPlayMode (V4ModeSubsystem.cpp:193) and the
standalone UV4QuickPlayPlaylistRunner::SelectMode
(V4QuickPlayPlaylistRunner.cpp:10) — and both compute the same thing: sum the
Weight of every enabled entry whose mode is registered, take
Roll = FMath::Abs(Seed) % TotalWeight, then walk the playlist subtracting
weights until Roll lands in an entry's band. Same seed, same registered set →
same mode, every time. The runner exists so a UI flow can preview a selection
without committing a transition, while RunQuickPlay (:101) does both:
select, then TransitionToMode, returning true only if the transition succeeds.
It is a small, real algorithm whose test would fail on any non-deterministic or
hardcoded return.
Local multiplayer & couch/LAN admission#
Split-screen, hot-seat, and LAN skirmish are modeled as a policy gate, not a
separate runtime. FV4LocalMultiplayerPolicy
(V4/ue/Source/V4Modes/Public/V4LocalMultiplayer.h:50) binds an ExperienceId
to a BackingModeId, an EV4LocalSessionKind
(SplitScreen/HotSeat/LANSkirmish), an EV4LocalInputMode
(SeparateDevices/SharedKeyboardAlternation/LANPeerInputs), a min/max
local-player band or explicit SupportedPlayerCounts, console/PC support flags,
and a bBackendConnectionRequired switch. CanStartLocalMultiplayerSession
(V4ModeSubsystem.cpp:112) is a real fail-closed predicate: it rejects an
unknown experience, an unsupported player count, the wrong platform class, or —
crucially — a backend-required session when the backend is unavailable. That
last clause is the honest seam between a couch session and the online services:
a LAN skirmish that needs matchmaking or anti-cheat will refuse to start offline
rather than fake a connection. EV4SplitScreenLayout and FV4LocalViewportRect
carry the viewport geometry for one-to-four-player grids.
Live service: seasons, battle pass & the calendar#
UV4LiveServiceSubsystem (in V4OnlineServices) is where post-launch content
becomes runtime data. It is a pure composition layer over hand-authored JSON
manifests, and the composition is real arithmetic, not pass-through. Season
cadence: BuildSeasonOneCadence and the predicates IsSeasonActiveDay,
IsPreSeasonResetDay, RequiresPlacementMatchesDuringPreSeason, and
IsMidSeasonBalancePatchDay encode the 90-day season with a 10-day pre-season
reset, 5 placement matches, and a mid-season balance patch on day 45 — the same
constants the Rust launch_season_cadence() returns
(online-services/src/lib.rs:645), so client and server agree. Battle pass:
BuildBattlePassTracks mints free and premium tracks at 1000 XP/tier
(V4LiveServiceSubsystem.cpp:5). Calendar: BuildLiveServiceThemeWindows
and ResolveLiveServiceThemeForMonth mirror
V4/liveops/live-service-calendar.json's five recurring windows (Winter
Operation, Lunar New Year, Spring, Summer Heat, Harvest) through a real
month-in-window test, V4MonthInThemeWindow (:75), that handles the
December→February wraparound correctly. BuildAnniversaryEvents,
IsAnniversaryLoginBonusActive (a MM*100+DD range test, :90),
BuildAnnualCharityDrives (one 100%-proceeds drive per year, validated by
HasOneCharityDriveForYear), and BuildPlatformCosmeticTieIns (the six
platform-family cosmetic tie-ins in the manifest, each with an approval ticket
and gameplayStatsAffected: false) round out the calendar. The hard competitive
line is enforced in the data itself: every cosmetic tie-in asserts no gameplay
effect, and cause-led collaborations route 100% of proceeds through the store
ledger.
DLC rides the same GameFeature boundary as the rulesets:
BuildOperatorDLCDefinitions/BuildMissionDLCDefinitions produce
FV4DLCGameFeatureDefinitions whose GameFeaturePluginURL is
plugin://<PluginName> (V4MakeDLC, :34), ValidateMissionPriceTier bounds
the price, and IsOperatorFreeForPremiumPass keeps the free/paid split clean.
The four V4DLC_* plugins already on disk (V4DLC_Operator_Season01_Nyx,
V4DLC_Campaign_Season01_Blacksite, two ADR community/parody packs) are the
shipped scaffolding for that pipeline.
Ranked operations & signature events#
Ranked is its own manifest, V4/liveops/ranked-season-architecture.json,
surfaced by BuildRankedLadderDefinitions over the five year-1 ladders
(Ranked.Tactical.R6Modern, Ranked.Stealth.SpiesVsMercs,
Ranked.RTS.Asymmetric, Ranked.RTS.Historical, Ranked.ARPG.BossRush). The
rank-decay rule is real, small logic, not a flag: RequiresRankDecay
(V4LiveServiceSubsystem.cpp:989) returns
IsDiamondOrHigherRank(RankId) && DaysSinceLastRankedMatch >= 14, so Diamond
and above decay after 14 idle days while Platinum and below never do — and
ValidateRankedSeasonArchitecture pins exactly that (Diamond@14 true,
Grandmaster@21 true, Platinum@30 false, :1113).
BuildRankedRewardBundles, BuildMMRDistributionHistograms (the public
/ranked MMR histograms), and BuildSeasonalBalanceRetrospectives complete the
ranked surface.
Signature event modes are the per-cell evergreen rulesets in
V4/modes/signature-modes.json — ten of them, each binding a signatureModeId
to a baseModeId, a cell, a stand-in asset, and a requiredFeatures list
(Hitman Elusive Targets'
WeeklyOneShotContracts/NoRetry/CommunityLeaderboard, Raven-Shield
Anniversary's Original2003MissionFlow, CoD-Zombies horde defense, StarCraft
co-op commanders). BuildSignatureEventModes and ValidateSignatureEventModes
load and check them; the limited-run event modes (Halloween Night Shift, Holiday
Hometown Skirmish, Pride Signal Colors, Esports All-Stars) come from
DA_SignatureEventModes. Two further mode manifests round out the catalogue:
arcade-mini-game-suite.json and karaoke-rhythm-mode.json.
The Rust online-services backbone#
The §56 backend lives in apps/v4/, which on disk is three crates —
online-services, telemetry-ingest, and a shared library — not 25 separate
deployments. online-services/src/lib.rs is the spine: service_router() nests
20 sub-routers, and service_names() enumerates the 25 logical services the
table describes. The domain logic behind each is real and unit-tested:
- Login (
oauth.rs,lib.rs:311) — OAuth 2.0 Authorization-Code + PKCE (pkce_code_challenge_s256), platform identity providers (EOS/PSN/XBL/NN/Steam/ Apple/Google), and single-use refresh-token rotation with replay defense (redeem_refresh_token,:341, rejecting a malformed or already-consumed token). - Matchmaking (
matchmaking.rs) — a Glicko-2 fairness queue with a hard window,FAIR_MATCH_EXPECTED_SCORE_WINDOW = 0.15, solo-preferred priority, and shared-region QoS selection. The Glicko-2 E-function (glicko2_expected_score,lib.rs:545) carries the correctq = ln(10)/400scale factor — a real bug fixed 2026-06-12 when the missing factor was flattening every pairing toward even. - Sessions (
lib.rs:672) — anallocated → starting → in_progress → completedlifecycle state machine withabortedreachable from every non-terminal state, enforced byvalid_transitions. - Replay vault (
lib.rs:774) — 14-day default retention, lifetime retention for starred replays, a 500-starred-per-account cap, expiry computation, and an account-deletion scrub that anonymizes pawn tracks.SpeedrunGhostArchiveaccepts only server-verified submissions and preserves supersession lineage. - Moderation/community/compliance/store/esports — content-scan thresholds
(nudity ≥85 block / ≥60 review, Lilith rights-cascade), a six-state
Player-of-the-Week curation flow, Challonge/Start.gg bracket clients
(
brackets.rs), and a WebRTC voice SFU (sfu.rs).
telemetry-ingest is the separate event-ingest crate (256 KiB batch cap,
30-second flush). The honest framing: this is real, tested domain logic with
real Axum routes over in-memory state — the PostgreSQL/Redis/ClickHouse/S3
stores named in the architecture are the production target, and persistence is
the subject of
./online-services-persistence.md.
Where this connects#
The mode registry is the orchestration layer almost every other V4 system hangs
off. The cells it activates and the rulesets layered on them are detailed in
./per-cell-deep-dives.md; the GameFeature hosting
model is in ./high-level-architecture.md. The
network authority and lockstep/replication determinism that a mode's net model
implies live in ./networking-determinism.md; the
World-Partition streaming and PCG seeding behind quick-play and daily missions
are in ./world-streaming-procgen.md; and the
accounts, ledger, save sync, ranked MMR, and replay storage the live-service
subsystem and the Rust crate read from are in
./online-services-persistence.md. For the
module map V4Modes and V4OnlineServices sit inside, see
./glossary.md; for the target-artifact and JSON-stand-in
conventions that make "the plugin exists, the binary doesn't" a documented
choice rather than a gap, see ./product-promise.md. The
catalogue index for the whole set is
../V4_ARCHITECTURE.md.