# Game Modes & Live Service

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](../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."

```mermaid
flowchart TD
    subgraph Disk["V4/ue/Plugins/*"]
      U[*.uplugin descriptor]
      S[Content/**/*.uasset.v4asset.json stand-in]
    end
    Boot[UV4ModeBootstrapSubsystem::Initialize] -->|FindFilesRecursive| S
    Boot -->|parse modeId/displayName/entryMap| Desc[FV4ModeStandInDescriptor]
    Desc --> Action[UV4GameFeatureAction_ActivateModeAssets]
    Action -->|OnGameFeatureActivating| Stream[UAssetManager async load]
    Action -->|RegisterModeWithSubsystem| Reg[UV4ModeSubsystem::RegisterMode]
    Reg --> Map[(TMap ModeId to FV4ModeDefinition)]
    Player[AV4ModeRoutingActor / QuickPlay] -->|TransitionToMode| Map
    Map --> Result{EV4ModeTransitionResult}
    Result -->|Succeeded| Cur[CurrentModeId set + OnModeTransitionCompleted]
    Result -->|UnknownMode / AlreadyActive| Broadcast[delegate broadcast, no state change]
```

**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](./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
`FV4DLCGameFeatureDefinition`s 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 correct `q = ln(10)/400`
  scale factor — a real bug fixed 2026-06-12 when the missing factor was
  flattening every pairing toward even.
- **Sessions** (`lib.rs:672`) — an
  `allocated → starting → in_progress → completed` lifecycle state machine with
  `aborted` reachable from every non-terminal state, enforced by
  `valid_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. `SpeedrunGhostArchive`
  accepts 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](./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](./per-cell-deep-dives.md); the GameFeature hosting
model is in [./high-level-architecture.md](./high-level-architecture.md). The
network authority and lockstep/replication determinism that a mode's net model
implies live in [./networking-determinism.md](./networking-determinism.md); the
World-Partition streaming and PCG seeding behind quick-play and daily missions
are in [./world-streaming-procgen.md](./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](./online-services-persistence.md). For the
module map `V4Modes` and `V4OnlineServices` sit inside, see
[./glossary.md](./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](./product-promise.md). The
catalogue index for the whole set is
[../V4_ARCHITECTURE.md](../V4_ARCHITECTURE.md).
