# Online Services, Networking & Esports

V4 is one tactical-action universe wearing six bodies, and what makes that a
_universe_ rather than six unrelated games is the online backbone every cell
shares. A ranked Search & Destroy round, a 1v1 RTS ladder match, a
Spies-vs-Mercs hunt, and a boss-rush leaderboard run all sign in through the
same account, queue through the same skill-rated matchmaker, land on the same
dedicated-server fleet, store the same server-validated replay, and — when an
event spins up — feed the same in-client tournament stack. This page is the
player-facing tour of three layers that sit next to each other: the **online
services** plumbing (login, friends, parties, matchmaking, leaderboards,
replays), the **networking** that decides _how_ a given match is transported
(client-server with lag-compensated hit registration, deterministic lockstep for
RTS, rollback-emulated prediction for twitch PvP, plus the server fleet, LAN,
and private rooms underneath them), and the **esports** toolkit that lets an
organizer run a bracket, broadcast, and verify results without leaving the game.
The store, season pass, and progression vault that ride this same backbone live
in [Live Service, Progression & Vault](./live-service-progression-and-vault.md);
the workshop, contract author, and community surfaces are in
[Content, Creator & Community](./content-creator-and-community.md); the
engine-side treatment is the architecture companion,
[../architecture/online-services-persistence.md](../architecture/online-services-persistence.md).
For the full feature scope this slots into, start at the hub:
[../V4_features.md](../V4_features.md).

## What ships, honestly

The split is clean and worth stating up front. **Two tiers are real code**, and
a small set of seams are labelled as deployment substrate rather than wired
integrations.

The **client tier is real Unreal C++.** `V4/ue/Source/V4OnlineServices` ships
fourteen `UGameInstanceSubsystem`s — login, matchmaking, session, presence,
friends, party, leaderboard, replay-upload, crossplay, live-service, moderation,
compliance, online-contract, and contract-schedule — over a typed surface
(`V4OnlineTypes.h`), and `V4OnlineServices.Build.cs` really declares the
`OnlineSubsystem`, `OnlineServicesInterface`, `OnlineServicesCommon`, and
`EOSShared` dependencies that make the EOS abstraction load-bearing rather than
decorative. The networking module `V4/ue/Source/V4Netcode` is wired just as
seriously: its `V4Netcode.Build.cs` pulls `NetworkPrediction`, `Iris`,
`ReplicationGraph`, and `OnlineSubsystem`, and the implementations behind them
are real algorithms, not stubs.

The **service tier is real Rust + Axum.** `apps/v4/online-services` is a
`v4-online-services` crate whose `service-manifest.json` enumerates twenty-six
services, and whose `service_router()` (`src/lib.rs:24-47`) nests **twenty** of
them — login, matchmaking, sessions, replays, leaderboards, anti-cheat,
moderation, compliance, store, esports, and the rest — each backed by a real
handler in `src/http.rs` (the esports router alone is at `http.rs:1366`). The
domain logic is not CRUD: a backtracking exact-fill matchmaker, a
dependency-free SHA-256/PKCE OAuth implementation, and real Challonge/Start.gg
bracket clients, each carrying inline `#[cfg(test)]` cases that assert computed
answers.

Four honest qualifications travel with the rest of the page:

- **Services hold in-memory state.** Each Rust service keeps a `BTreeMap` behind
  an `Arc<Mutex<…>>`. The **PostgreSQL / Redis / ClickHouse / S3** stores the
  architecture names are the _specified deployment substrate_, not yet wired
  into these crates. The reference implementations are real and tested; the
  database backends are the next integration step.
- **Kernel anti-cheat is a plan; replay validation is real today.** The
  `UV4AntiCheatSubsystem` names EasyAntiCheat as the client driver tier and
  validates its config, but that kernel driver is a planned surface. The
  load-bearing integrity layer that ships is **server-side replay validation**,
  which rides the deterministic sim the engine already guarantees.
- **V4 does not ride the `@oshun` plane.** Unlike V2's event-bus-native
  backbone, V4's services are a self-contained Rust workspace
  (`v4_shared::ServiceName`); a grep for `@oshun` across `apps/v4` and `V4/ue`
  source returns nothing. Where V2 publishes cross-domain events, V4 keeps its
  own service graph.
- **Bracket egress is proxied.** The Challonge and Start.gg clients in
  `src/brackets.rs` speak the providers' real wire formats, but egress runs
  through the platform's configured TLS-terminating proxy (`host:port`), not a
  direct outbound socket.

## The online backbone, from the player's seat

### Login, accounts, and sessions

Gameplay code never branches on platform. Every menu and lobby calls a subsystem
whose types speak one enum —
`EV4OnlinePlatform { EOS, PSN, XBL, NN, Steam, Apple, Google }`
(`V4OnlineTypes.h:7`) — and the platform stacks sit behind it.
`UV4LoginSubsystem` is a clean client-side state machine over
`EV4LoginState { SignedOut, Pending, SignedIn, Failed }`: `BeginLogin` moves an
account to `Pending`, `CompleteLogin` resolves it to `SignedIn` or `Failed`, and
the first- party V4 account is the primary identity. The heavy cryptography
deliberately does _not_ live in the client — it lives in the Rust login-service,
where `src/oauth.rs` carries a dependency-free, FIPS 180-4 SHA-256 used for the
RFC 7636 S256 PKCE code-challenge binding, so the RFC 6749 authorization-code
exchange is genuinely cryptographic and deterministically testable rather than a
synthesised token. Refresh is single-use with replay defense: a presented token
is validated against its expected derivation, inserted into a consumed-token
set, and a replay is rejected before the generation rotates. Account linking
binds any of the seven providers to one first-party account; unlinking never
deletes progression. If the login service is unreachable, the client falls back
to offline play and reconciles to the cloud on the next successful sign-in.

### Friends, parties, and voice

The friends-service serves one cross-platform friend graph over a WebSocket
presence channel, so a friend's current cell, mode, and in-match/in-lobby state
are live. Party caps are per-cell — 4 for tactical PvP, 8 for RTS team modes, 2
for Wukong co-op, 4 for general co-op — and members who cannot meet a mode's
platform or content requirement are flagged before queue rather than after.
Voice runs over a WebRTC SFU (`voice-sfu` in the manifest) with positional or
channel audio, push-to-talk or open-mic per player, and per-channel mute of any
other player; a server-side whisper-transcription classifier feeds the report
pipeline, and voice defaults **off** for under-18 accounts pending parental
opt-in.

### Matchmaking, skill, and region

Matchmaking is the backbone's most fairness-sensitive job, and V4 implements it
on both sides of the wire. The authoritative skill model is server-side and
genuine: `MatchmakingService` (`src/matchmaking.rs`) runs a **Glicko-2**
expected-score fairness window. A candidate joins the match only when its
expected score against the seed stays within
`FAIR_MATCH_EXPECTED_SCORE_WINDOW = 0.15` of an even 0.5, and `find_match` is a
real depth-first **backtracking exact-fill** (`fill_exact`): it seeds on the
longest-waiting solo (`prefer_solo_queue`, FIFO within party size), recurses to
assemble _exactly_ `team_size` players from mixed party sizes, requires one
region every member can play in, then picks the region with the best
_worst-case_ latency. The tests prove behaviour, not shape: a 2400-rated smurf
is excluded from a 1500 lobby and stays queued; a 3+2 backtrack fills a 5-stack
while a lone solo waits; and `SA` wins over `EU` because its shared worst-case
QoS is 45 ms versus 80. On the client, `UV4MatchmakingSubsystem` carries
`FV4MatchmakingTicket` rows and a `FV4MatchmakingDiagnostic` that powers an
explanation panel — `PlayerMMR`, `OpponentAverageMMR`, `MMRDelta`,
`bWithinTolerance` — so a player can see _why_ a match formed. The launch SLO is
concrete: matchmaking p99 ≤ 35 s under 5× expected launch concurrency.

### Leaderboards and the replay vault

`UV4LeaderboardSubsystem` keeps `FV4LeaderboardEntry` rows under a `BoardId` and
serves `GetTopEntries`/`GetEntry`; the service slices boards per mode, per cell,
and per region with a global view, resets competitive boards on the 90-day
season boundary, and archives the prior season. Integrity is
server-authoritative: ladder and signature-mode entries (boss rush, contracts,
Contra Survival) are validated by replay analysis before they post — runs that
fail validation are _rejected, not silently dropped_. Replays default to a
14-day retention; starring moves one into a lifetime per-account vault capped
at 500. Verified speedrun submissions feed a ghost archive where a faster ghost
supersedes the active route ghost while preserving lineage, and a
right-to-be-forgotten scrub anonymises a deleted account's pawns in preserved
replays rather than erasing the match.

## Networking: three netcodes on one server fleet

V4's promise is per-ruleset feel, and that extends to the wire: a slow RTS macro
game and a 60 Hz duel cannot share one transport. The product makes
**dedicated-server client-server the default**, with **deterministic lockstep**
for RTS and **rollback-emulated prediction** for tight twitch PvP — three models
that all allocate off one fleet.

```mermaid
flowchart TD
  subgraph Client["V4 client · Unreal C++"]
    OSS["V4OnlineServices<br/>14 subsystems"]
    NET["V4Netcode<br/>3 transport models"]
  end
  OSS -->|OnlineSubsystem · EOSShared| EOS[(EOS · PSN · XBL · NN<br/>Steam · Apple · Google)]
  OSS -->|REST| GW[API gateway]
  GW --> RUST["v4-online-services · Rust + Axum<br/>service_router() · 20 routers"]
  RUST --> MM[matchmaking · Glicko-2]
  RUST --> ESP[esports · brackets · anti-collusion]
  MM -->|allocate| ROUTER["UV4DedicatedServerRouter<br/>10 regions"]
  ROUTER --> CS["Client-server<br/>+ lag-comp rewind"]
  ROUTER --> LS["Lockstep · 25 Hz<br/>desync recovery"]
  ROUTER --> RB["Rollback<br/>8 frames @ 60 Hz"]
  ESP -->|Challonge REST · Start.gg GraphQL| EXT[(External brackets)]
  RUST -.specified store.-> DB[(PostgreSQL · Redis<br/>ClickHouse · S3)]
```

### The server fleet and allocation

`UV4DedicatedServerRouter` (`V4ClientServerNetcode.cpp`) configures **ten
regions** — NA-East, NA-West, EU-West, EU-East, LATAM, APAC, AU-NZ, ME, Africa,
India — each with a provider code, endpoint host, capacity, and estimated ping.
`AllocateServerForMatchmaking` honours a preferred region when it is enabled and
under capacity, otherwise scans for the lowest-ping enabled region with free
slots, increments its active allocations, and mints a `QueueId.Region.MatchId`
server id; `ReleaseAllocation` decrements it back. The same router also triages
anti-cheat: `RouteAntiCheatSignal` kicks-and-escalates a severity ≥ 8 signal to
`anti-cheat.eac-review`, escalates severity ≥ 4 to `anti-cheat.live-review`, and
otherwise logs to `anti-cheat.telemetry`.

### Client-server with lag-compensated hit registration (default)

The default model is server-authoritative with a real rewind. A shot is an
`FV4HitRegistrationRequest` carrying the client's fire time, trace, a `Line` or
`Cone` shape, and a stack of `FV4PenetrationLayer`s; the server rewinds targets
to buffered `FV4LagCompensatedSample`s (server time, location, box extent),
clamps the rewind, and returns an `FV4HitRegistrationResult` whose booleans are
honest — `bHit`, `bRewindClamped`, `bRejectedByAntiCheat`,
`bBlockedByPenetration`, plus the rewound time/location and remaining
penetration energy. Hit acceptance is the server's call, not the client's claim,
and a rejected-by-anti-cheat flag is a first-class output rather than an
afterthought.

### Deterministic lockstep (RTS)

RTS modes (1v1, 2v2, 4v4) run `UV4RTSLockstepSubsystem` at a 25 Hz tick. Clients
exchange only `FV4LockstepInputBundle`s (frame, match-config hash, RNG seed,
per-player inputs); each peer advances the same `ComputeDeterministicHash` over
a _sorted_ input bundle so the simulation stays bit-identical. Desync is
detected, not hoped against: `CheckRemoteFrameHash` compares the local frame's
`StateHash` to a peer's and, on mismatch, computes a `RecoveryFrame`;
`RecoverFromDesync` rolls the sim back to it and adopts the authoritative hash.
The subsystem also draws match randomness through `DrawMatchRandomInt` and
exposes `ValidateRngConsumptionOrder`, so a replay can prove the RNG was
consumed in the same order on every machine — the foundation that makes RTS
replays true-rewindable and lockstep matches auditable. Late joiners rebuild
from `BuildLateJoinerRebuildFrame`, and a pause threshold backs the design's
pause-on-disconnect behaviour.

### Rollback-emulated prediction (twitch PvP)

Tight shooter PvP (TDM, Spies-vs-Mercs) runs `UV4RollbackEmulatedComponent`. Its
profile predicts movement and firing up to `MaxPredictionFrames = 8` at
`PredictionFrameRateHz = 60` — roughly a 133 ms prediction budget —
`SimulatePredictedInputFrame` advancing the local model and
`ReconcileAuthoritativeInputFrame` correcting it against an
`FV4RollbackAuthoritativeFrame`: when the predicted location is inside the
server's `ErrorTolerance` no correction applies, otherwise the component replays
the intervening frames and reports the delta. Fire is server-authoritative
(`bRequiresServerFireAuthority`), so a rejected shot surfaces as
`bServerRejectedFire` rather than a phantom kill.

### Bandwidth budgets, Iris migration, and host migration

Each match type gets a tuned replication profile.
`UV4ReplicationGraphProfileCatalog` ships per-mode band budgets and graph nodes
for `Tactical5v5`, `CoD6v6`, `BattleRoyale` (four distance bands from an
8192-byte critical tier to a 1024-byte far-dormant tier), and `SpiesVsMercs`,
loaded into the budgeter. `UV4IrisMigrationValidator` keeps rollback-sensitive
movement channels on _legacy_ replication while migrating inventory, objective,
cosmetic, and social-stealth channels to Iris, and `BuildMigrationReport`
computes the bandwidth-reduction fraction. For peer-hosted private rooms,
`UV4HostMigrationCoordinator::BuildMigrationPlan` picks the best-scoring
eligible peer and `ValidateSubTwoSecondSwap` confirms the estimated swap stays
under two seconds; matchmade dedicated-server play never migrates host.

### LAN, server browser, private rooms, and QoS

For offline events V4 ships a LAN lobby for RTS and tactical modes that runs
with no backend — local-network discovery, peer-hosted, no rating recorded. The
server browser lists community-hosted RTS, R6-style, and private rooms,
filterable by cell, mode, map, ping, player count, and mod-whitelist state,
honouring the room's password and content rules on join. Private rooms are
friends-only scrim/bracket spaces whose host configures cell, mode, map pool,
rules, and team assignment, and can snapshot state so a multi-game series pauses
and resumes. The client probes its NAT type at startup and warns on
strict/symmetric NAT (which degrades private-room peering; dedicated-server
modes are unaffected), and pings all ten regions to cache a sorted latency list
that matchmaking and the browser surface as the best three.

## Esports: run the whole event from the client

V4's tournament stack is real service code with a strict completeness
discipline, backed by `EsportsService` (`src/lib.rs`) and the bracket clients in
`src/brackets.rs`.

### The tournament server build and integrity

`configure_tournament_build` refuses any config that is not fully competitive:
the build flag must be `V4Tournament` and **all** of broadcast tools, observer
cameras, pause-on-disconnect, and forced replay-export must be on, or it returns
`InvalidTournamentBuild`. There is no half-configured tournament build. Sponsor
branding is data, not code: `register_branding_hooks` requires at least two
crews, enough faction icons, and a broadcast-approved banner card per crew, so
the esports-service populates crew names, team icons, and banner cards per event
rather than hard-coding them. `validate_tournament_mods` enforces a whitelist —
when `reject_unlisted_mods` is set, any loaded mod outside `allowed_mod_ids` is
rejected so every match runs on a known content set.

### Bracket sync — real provider wire formats

The bracket clients speak the providers' real APIs.
`ChallongeClient::fetch_matches` issues
`GET /v1/tournaments/{id}/matches.json?api_key=…` and parses Challonge's
`{"match": {...}}` envelopes; `StartggClient::fetch_event_sets` POSTs a real
GraphQL `EventSets` query to `/gql/alpha` with a bearer token and maps the
`sets.nodes` back to a common `BracketMatch`. Both are tested against a spawned
local HTTP server that asserts the exact request line, headers, and GraphQL
variables. Internally, `create_first_party_bracket` requires
server-authoritative reporting, and the design _pulls_ and cryptographically
verifies external results against the replay hash before they count — results a
client cannot forge.

### Anti-collusion and anti-cheat

Collusion detection is tuned for the modes where it pays.
`evaluate_rts_team_collusion` scores shared-vision seconds, resource-transfer
spikes, and mirror-pathing events and flags a pairing only past an
evidence-window-and-score threshold; `evaluate_search_and_destroy_collusion`
scores plant/defuse avoidance and thrown rounds the same way — each emits an
`AntiCollusionFinding` with a severity, a review queue, and an FNV-style
evidence hash, and flagged matches route to human review rather than
auto-discipline. On the client, `UV4AntiCheatSubsystem` backs this:
`EvaluateBehavior` flags impossible recoil (≥ 20 shots at ≥ 0.98 control) and
wallhack patterns, `CalculateTrustScore` is
`100 − strikes·25 − investigations·10`, `BuildBanTiers` enumerates the
mute/match/cell/account ladder, and `ValidateLadderReplay` insists the replay,
server-state, and input hashes are all present _and independently produced_
before a run is valid. The EAC config validation requires kernel mode plus
ranked-and-BR protection across PC and current-gen consoles — but, per the
honest label above, that driver is planned; the replay-hash check is the part
that ships real.

### Pro circuit, broadcast, and the Hall of Fame

The first 12-month calendar is authored data, not prose:
`V4/esports/pro-circuit/pro-circuit-2026.json` defines Major / Minor / Open
tiers with circuit points (1200 / 450 / 120), prize pools, and a twelve-event
calendar from a Launch Open in October 2026 to a World Finals Major in September
2027, plus a broadcast SLA of 8 / 4 / 2 observers by tier, a 365-day replay
archive, and a 90-second integrity delay;
`esports/calendar/first-12-months.json` mirrors the month-by-month cadence so
Open results feed Minor seeding and Minor feeds Major. The web spectator portal
adds a per-camera picture-in-picture grid, a director-cut observer, true
replay-rewind on lockstep matches, and the same 90-second delay. Finally,
`EsportsService` enshrines lifetime champions:
`enshrine_lifetime_champion_banner` validates a one-of-one art commission
anchored to `Lobby.BattleHub.Zone.HallOfFame`, `hall_of_fame_induction_criteria`
encodes per-cell evidence requirements (two Major wins for Tactical FPS, a
Grandmaster season plus two Major top-4s for RTS, a world-first boss kill for
ARPG), and a community-veto policy (`/hall-of-fame/community-veto`, 30-day
window, one-account-one-vote) keeps the honour accountable to the players.

## Where this connects

- **Sideways to live service:**
  [Live Service, Progression & Vault](./live-service-progression-and-vault.md) —
  the store, battle pass, currency ledger, and replay vault that ride this same
  backbone and account root.
- **Sideways to creators:**
  [Content, Creator & Community](./content-creator-and-community.md) — the
  workshop mods the tournament whitelist gates, the Hitman contract author, and
  the gallery/newsletter community surfaces.
- **Down to the engine:**
  [../architecture/online-services-persistence.md](../architecture/online-services-persistence.md)
  — the EOS abstraction, the Glicko-2/PKCE service internals, the persistence
  and cross-progression model, and the specified PostgreSQL / Redis / ClickHouse
  / S3 substrate these services deploy onto.
- The feature hub: [../V4_features.md](../V4_features.md)
