Five gameplay cells, one shared roster, one online backbone, ten platforms — and
all of it eventually has to answer two unglamorous questions: what is the live
game telling us, and how does this tree become a signed package inside a console
frame budget? This page is V5's production substrate. It covers the telemetry
spine (the in-engine V5Telemetry module plus the telemetry online service
that lands events in ClickHouse), the build/cook/patch story (the real
UnrealBuildTool targets and module graph, versus the cook-farm prose the
architecture aspires to), and the data architecture (typed event schemas,
JSON manifests, and the honest gap where cooked binaries and a balance ledger
would sit). It is the layer where a privacy bug ships to every player at once
and a missing strip-profile blows a memory budget on Switch 2, so it is
documented the way it actually exists in the repo — real code cited as real,
intent labelled as intent. The orientation hub is
../V5_ARCHITECTURE.md.
What ships, honestly#
Three layers, stated plainly.
- Telemetry is real, and it is the strongest part of this surface.
V5/ue/Source/V5Telemetryis a genuine UE5 C++ module: three enums, eightUSTRUCTs, and fiveUBlueprintFunctionLibraryclasses implementing a real event batcher, a TypeBox-shaped edge validator, a 32-entry critical-event catalog, an opt-out gate, and a DSAR exporter — backed by three automation specs that assert concrete values. The matchingapps/v5/telemetryNestJS service does real per-cell ClickHouse routing and reports fail-loud health (it serves a 503 outage contract when its backing store is unconfigured rather than fakingok). - The build targets are real; the build farm is not in this tree. There
are five genuine
*.Target.csrules and 74*.Build.csModuleRulesacross the source tree, so the module graph and its dependency edges are real and compilable. But unlike V4, there is no BuildGraph script, no FASTBuild/SN-DBS config, no Jenkinsfile, and nobuild-cook-patch.jsonin V5 — the "Build, Cook, Patch" architecture section is specified intent (UnrealBuildTool + Horde, per-platform cook, UnrealPak iterative patching), not an enforced pipeline. It is labelled that way throughout. - The data is text-only, and the balance ledger is scaffolding. Measured
live in this tree: 0
.uassetbinaries, exactly 1.umap, 0DT_*DataTables, against 393.jsonfiles underV5/ue/Content(manifest stand-ins and the performance catalog). TheV5/balance/directory is 12 Markdown files — READMEs and period-authenticity research — with no balance data, no ledger, no CSV. Any claim that needs cooked content or a telemetry-evidenced balance ledger stays open; the typed event schema is the one piece of data that is fully concrete, because it is generated in C++.
The honest summary: the telemetry pipeline is implemented and tested, the build targets are real but the cook farm is not attached, and the content and balance data remain authored-intent, exactly as with the rest of V5.
Telemetry: the V5Telemetry module#
Event taxonomy and privacy classes#
The vocabulary is defined in
V5/ue/Source/V5Telemetry/Public/V5TelemetryTypes.h. Every event is an
FV5TelemetryEvent carrying a Sequence, an EventName/SchemaName pair, an
AccountOpaqueId, a Region (defaulting to "iad"), the originating
EV5Cell, a UnixSeconds stamp, a PayloadBytes count, and an
EV5TelemetryPrivacyClass. That last field is the spine of the privacy model —
three classes, Anonymous, AccountOpaque, and ConsentRequired — and it is
assigned per event type, not per call site, so a designer cannot accidentally
promote an anonymous event to PII.
The critical-event catalog lives in
UV5_Telemetry_CriticalEvents::BuildCriticalEventHandlers
(V5TelemetrySystems.cpp) and ships 32 handlers, broader than the 24 the
architecture doc enumerates. Beyond the session/mission/combat/narrative events
it adds the online and operations surface the
persistence & online page depends on:
matchmaking_queue_join/leave, party_ready_check,
leaderboard_score_submit, replay_upload, crash_report_submitted,
anti_cheat_signal, and currency_balance_change. Each handler is stamped with
a Category (session, combat, mindpalace, hunter, ship, online,
ops, privacy, …) and its privacy class — notably privacy_consent_change is
Anonymous, which is what lets it survive opt-out (below). The automation spec
FV5TelemetrySchemaAndHandlersTest asserts Handlers.Num() >= 30 and checks
each of the 24 documented events resolves to a handler, so the catalog cannot
silently shrink below the documented contract.
Edge validation, TypeBox-shaped#
Events are validated at the client edge before they are ever batched.
UV5_Telemetry_Schema_TypeBox::BuildSchemaCatalog derives one
FV5TelemetrySchemaDefinition per handler, naming each schema
typebox.v5.telemetry.<event>.v1 and synthesising a TypeBox-shaped JSON blob
via MakeTypeBoxSchema ({"kind":"Type.Object","event":"…","required":[…]}).
ValidateEvent then runs a real four-stage check and returns one of five
EV5TelemetryValidationStatus values:
MissingSchema— no schema for the event name, or the event'sSchemaNamedisagrees with the catalog's.PayloadTooLarge—PayloadBytes > MaxPayloadBytes(2048).MissingField— any of the required fields (event,account,region,sequence) is absent fromPayloadJson.Valid— all checks pass,bValid = true.
FV5TelemetrySchemaAndHandlersTest exercises both branches: a well-formed
session_start event passes, and an event whose payload is
{"event": "session_start"} fails with exactly MissingField. This is a domain
assertion that would break if the validator started rubber-stamping — not a
shape-only smoke test.
Batching: greedy, ordered, three flush reasons#
UV5_Telemetry_EventBatcher::BuildBatches is the real send-side accumulator.
Its default FV5TelemetryBatcherConfig is the published budget — 2048 bytes /
5 seconds / 500 events — and the batcher greedily packs events into an
FV5TelemetryBatch, cutting a new batch whenever the next event would exceed
the byte cap, the count cap, or the age window. Each finalised batch records a
FlushReason (max_bytes, max_events, max_age, or drain for the tail), a
deterministic BatchId of the form telemetry.batch.<first>.<last>, and a
bOrderingPreserved flag computed by walking the sequence numbers.
FV5TelemetryBatchOrderingTest feeds 10 padded events through the batcher,
asserts the stream splits into more than one batch, asserts every batch stays
<= 2048 bytes and preserves local ordering, and checks that flattening all
batches reproduces the source sequence 0..9 exactly — i.e. no event is dropped
or reordered across the split.
Opt-out and DSAR: privacy as code#
Two libraries make the privacy story executable rather than aspirational.
UV5_Telemetry_OptOut::ShouldCollectEvent implements the in-game opt-out: if
the account has not opted out, everything collects; if it has, the function
returns false for all events except privacy_consent_change, which remains
essential. UV5_Telemetry_DSAR_Export::BuildExport builds the data-subject
export — it filters a stream to the requesting AccountOpaqueId, emits one
escaped JSON line per matching event, stamps a 30-day SLA, marks the export
bRedacted and bCompanionAppVerified. FV5TelemetryOptOutAndDSARTest
confirms opt-out suppresses a mission_complete but lets
privacy_consent_change through, and that a three-event mixed-account stream
exports exactly the two events belonging to the requester. The compliance
laws behind this (GDPR/CCPA, COPPA age-gating, EU data residency) are enforced
in the compliance-dsar service detailed on the
persistence & online page.
The telemetry service: ClickHouse routing and fail-loud health#
The in-engine module produces validated batches; apps/v5/telemetry ingests
them. The contract (contract.json) declares a single endpoint —
POST /v5/telemetry/batch, JWT-required, offline-queue-capable, regional — with
storage { primary: ClickHouse, cache: Kafka, analytics: Grafana },
capabilities batch-limits, schema-validation, privacy-opt-out,
clickhouse-export, and a regional-active-active failover plan across
iad/fra/sin with pdx/dublin/syd fallbacks. The service is one of 16 in
the @v5/service-* catalog and runs on port 4216 (asserted by
contract.test.ts).
The routing is real, not decorative. selectClickHouseTarget(cell, region) in
apps/v5/service-shared/src/runtime.ts returns a target with database
v5_telemetry, table events_<cell>, and the request region — so each cell's
events land in their own ClickHouse table, which is what makes the per-cell
Grafana dashboards possible. Health is fail-loud: resolveServiceHealthy
walks every backing dependency the catalog declares (V5_POSTGRES_URL,
V5_REDIS_URL, and for telemetry specifically V5_CLICKHOUSE_URL) and reports
status: 'ok' only when all are configured; otherwise it returns
not_configured and the service serves its 503 outage contract. The source
comment records that this replaced a hardcoded serviceHealthy: true that had
made the outage branch dead code — a deliberate de-fabrication, and the correct
honest-seam pattern for an integration with no live backing store attached.
Where @oshun/metrics is — and isn't#
One honest divergence from the V2 bar is worth stating directly. V2's telemetry
page can claim every service composes the shared
@oshun/metrics/@oshun/tracing observability libraries. V5 does not. A
repo-wide search for @oshun/metrics or @oshun/tracing under V5/ returns
nothing; the V5 services instead build on their own @v5/service-shared runtime
(buildHealth, handleRealRequest, resolveBackingDependencies). The shared
library is real and capable — it lives at libs/shared/metrics and exports
Prometheus-compatible Counter, Gauge, Histogram, and Summary types with
HISTOGRAM_BUCKETS/SUMMARY_PERCENTILES presets — but it is not currently
wired into V5, and the architecture's Grafana/ClickHouse observability story
is carried by the service contracts and the (intent-level) dashboards, not by a
@oshun/metrics binding. Treat any Prometheus-scrape claim for V5 as
not-yet-implemented.
Build, cook & patch#
Targets and the module graph#
V5 is a real UnrealBuildTool project. There are five target rules under
V5/ue/Source: V5.Target.cs (the game client), V5Editor.Target.cs,
V5DedicatedServer.Target.cs, V5ListenServer.Target.cs, and
V5ArcadeCabinet.Target.cs. V5.Target.cs is a genuine TargetRules subclass
— Type = TargetType.Game, DefaultBuildSettings = BuildSettingsVersion.V5,
IncludeOrderVersion = EngineIncludeOrderVersion.Latest, the UE5.5-era
defaults, matching the same posture V4's targets take. Below the targets, the
tree splits into the engine-module set the
topology page enumerates, each with its own
*.Build.cs ModuleRules — 74 of them in total. The dependency edges are
real and meaningful: V5Telemetry.Build.cs declares
PublicDependencyModuleNames of Core, CoreUObject, Engine, and V5Core,
pulling in the shared core at the module boundary rather than ad hoc, and the
cross-cell-shared modules forbid per-cell dependencies one-way.
The Arcade Cabinet target#
V5ArcadeCabinet.Target.cs is the most distinctive build artifact — a
location-based four-player cabinet SKU. It sets the project-scope compile
definitions V5_ARCADE_CABINET, V5_LOCAL_COOP_CABINET,
V5_CABINET_ONLINE_SERVICES=0, and V5_CABINET_COIN_OP_ECONOMY=0, packages
through Platforms/ArcadeCabinet/Config/ArcadeCabinetEngine.ini, and cooks only
a curated local-co-op roster (Spec Ops Co-op, Horde Defense, Fleet Co-op, Hunter
Card Game, Training Range). Its profile manifest at
V5/ue/Content/V5Input/Data/arcade_cabinet_build_manifest.json binds
four-player ArcadePanel input contexts, 4K60 output, offline boot, attract
mode, and a no-coin-op-economy gate — runtime mode gating maps the target to
EV5ModeBuildTarget::ArcadeCabinet. It is a real example of the
GameFeature-plugin breadth meeting a build target, and is covered further on the
modes, streaming & procgen page.
Cook & patch: specified, not yet a farm#
Here the honest line matters. The architecture's "Build, Cook, Patch" section
describes UnrealBuildTool + Horde for distributed compile, per-platform
parallel cook with iterative dev cook, UnrealPak Iterative chunked patching
with per-cell chunk sizes, a single-binary day-one patch policy, and
per-platform signed packages. None of those pipeline artifacts exist as files
in this tree. Unlike V4 — which ships a V4BuildCookPatch.xml BuildGraph
script, FASTBuild.bff/SNDBS.ps5.xml distributed-compile configs, a
Jenkinsfile.pc, and a build-cook-patch.json manifest all enforced by a check
script — V5 has only the target/module rules and a launch-readiness validator.
The cook/patch design is therefore specified intent, and the per-platform
strip profiles, chunk sizing, and signed-package flow described in the hub doc
are not yet machine-enforced. Stated plainly so nobody mistakes the prose for a
green build farm.
The launch-readiness gate#
What is enforced as code is the release gate, via
V5/tools/release/validate-launch-readiness.py. Its docstring is itself a
de-fabrication record: a prior revision hardcoded "17/17 gates passed,
releaseBlocked false, 99.72% crash-free" and was rewritten on 2026-06-12 to
validate honest state and fail closed — every one of the 17 gates may be
passed, pending, or failed; the summary counters must equal what the
per-section data actually says; and a gate may claim passed (and the manifest
may claim releaseBlocked: false) only when the backing observations actually
exist. The validator cross-checks real artifacts: the
performance_platform_profiles.json catalog, the season-1 live-service
manifest, legal/platform-cert-bans.json, the
matchmaking/balance-ledger/compliance service contracts, and
V5Tests/Private/Tests/V5TestsAutomation.cpp. Its constants encode the launch
contract — 5 cells, 16 languages (12 voiced + 4 text-only), 9 platforms,
packet-loss tolerance at {5, 20}%, validated ping ≤ 80 ms. The broader testing
tiers, feel-test suites, and crash-rate gates this composes belong to the
observability, performance-testing & content-pipeline page.
Data architecture#
The text-only reality#
The architecture describes V5 data as UDataTables (characters, weapons,
vehicles, NPCs, ingredients, bestiary, factions, missions), UCurveTables,
asset registries, the Localization Dashboard, and a telemetry schema. Measured
in the tree, the reality is the same text-only posture V4 documents: 0
.uasset, exactly 1 .umap, 0 DT_* DataTables, and 393 .json
files under V5/ue/Content. Those JSON files are the manifest stand-ins
referenced throughout the hub doc — the year1_* and fullvision_*
live-service manifests, the NG+/save-archaeology manifests, the arcade-cabinet
profile, and the performance catalog. They carry authored intent (typed fields,
references to C++ symbols and other manifests) and are what the
V5/tools/test_validate_*.py suite and tools/validate-v5-docs.py
referential-integrity checker attest to, but they cannot be cooked, loaded,
profiled, or played. The single concrete exception to the "no typed data" rule
is the telemetry event schema, which is not a DataTable at all — it is
generated in C++ by MakeTypeBoxSchema and declared in the @v5/service-shared
service catalog, which is precisely why the event contract is the most testable
data in the project.
Balance: scaffolding, honestly#
V4's data page can point to a publicly published, telemetry-evidenced balance
ledger (public-balance-ledger.json, a gadget-balance-ledger.csv, matchup
matrices with validated win-rate algebra) gated by a real check script. V5 has
no such ledger. The V5/balance/ directory is 12 Markdown files — a
per-cell README.md for urban/period/frontier/hunter/scifi/shared, the
period-authenticity research notes (1930s/1947/1968), and
hunter/cardgame-rules.md — with no data files of any kind. The
balance/schema/README.md states the directory "owns JSON Schema and
spreadsheet contract definitions used by V5 balance linters and cook-time data
validation," but those schema and ledger files do not yet exist; the
directory is documentation-of-intent and authoring scaffolding. There is a
balance-ledger online service in the catalog (one of the 16, cross-checked by
the launch validator), but the published numbers, telemetry evidence, and tier
lists that V4's ledger enforces are not present in V5 as data. Any
balance-dashboard claim is therefore intent, fed by the telemetry events above
once they run against a live ClickHouse.
The performance profile catalog#
The one substantial typed dataset on this surface is
V5/ue/Content/V5Performance/Data/performance_platform_profiles.json — a real
37 KB catalog with schemaVersion, catalogId, a contentLock, a summary,
11 platform profiles, and regressionSuites. It binds the per-platform
frame-time budgets from the hub doc (PS5 33.3/16.6 ms, Switch 2 8 GB, Windows
ultra 8.3 ms, …) to engine config sections and to the release-blocking
performance-regression suite, and it is one of the artifacts
validate-launch-readiness.py reads to decide whether the performance gate may
claim passed. It is the closest thing V5 has to a real, machine-checked data
table, and it is the bridge from this page to the performance budgets enforced
on the
observability & performance-testing page.
Where this connects#
- What the events instrument: the modes, streaming & procgen page — the GameFeature plugins and build targets (Arcade Cabinet included) whose per-mode sessions emit the mission/combat/online events the batcher carries.
- What gates the build: the
observability, performance-testing & content-pipeline page
— the testing tiers, the
performance_platform_profiles.jsonregression suite, and the crash-rate/feel-test gates the launch-readiness validator composes. - What the dashboards visualise: the audio, VFX, cinematics & UI page — the presentation systems (and the replay/photo-mode surfaces) whose usage and export events feed the same per-cell ClickHouse tables.
- The orientation hub: ../V5_ARCHITECTURE.md.