Open-World Narrative · Architecture

Telemetry, Build & Data

A focused page within the Open-World Narrative Architecture documentation. The full map and every sibling page live in the Architecture hub.

5sections13 minread1diagram

On this page

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/V5Telemetry is a genuine UE5 C++ module: three enums, eight USTRUCTs, and five UBlueprintFunctionLibrary classes 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 matching apps/v5/telemetry NestJS 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 faking ok).
  • The build targets are real; the build farm is not in this tree. There are five genuine *.Target.cs rules and 74 *.Build.cs ModuleRules across 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 no build-cook-patch.json in 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 .uasset binaries, exactly 1 .umap, 0 DT_* DataTables, against 393 .json files under V5/ue/Content (manifest stand-ins and the performance catalog). The V5/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:

  1. MissingSchema — no schema for the event name, or the event's SchemaName disagrees with the catalog's.
  2. PayloadTooLargePayloadBytes > MaxPayloadBytes (2048).
  3. MissingField — any of the required fields (event, account, region, sequence) is absent from PayloadJson.
  4. 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 ModuleRules74 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.

flowchart TD subgraph Client [In-engine · V5Telemetry C++] EV[FV5TelemetryEvent<br/>privacy class · cell · region] VAL[Schema_TypeBox.ValidateEvent<br/>schema · size · required fields] OPT[OptOut.ShouldCollectEvent<br/>essential-only on opt-out] BATCH[EventBatcher.BuildBatches<br/>2048 B · 5 s · 500 ev] EV --> OPT --> VAL --> BATCH end subgraph Service [services/telemetry · NestJS] ING[POST /v5/telemetry/batch<br/>JWT · offline queue] ROUTE[selectClickHouseTarget<br/>db v5_telemetry · events_cell] HEALTH{resolveServiceHealthy<br/>PG · Redis · ClickHouse?} ING --> ROUTE ING --> HEALTH end subgraph Sinks [Storage] CH[(ClickHouse<br/>per-cell tables)] GRAF[Grafana dashboards] DSAR[DSAR_Export · 30-day SLA] end BATCH --> ING ROUTE --> CH --> GRAF EV -. account-filtered .-> DSAR HEALTH -->|unconfigured| OUT[503 outage contract]

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.json regression 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.