Everything on the other architecture pages — the deterministic combat sim, the rollback netcode, the roster, the racing rulesets — eventually has to be built into a shippable game for a fleet of platforms and kept inside a budget tight enough to hold a locked frame rate on a Switch 2. This page is V2's production substrate: the BuildGraph/cook/patch pipeline that turns source into platform packages, the asset budgets that bound what artists can ship, and the data architecture that makes V2's frame-data numbers a CSV a designer edits rather than C++ a programmer recompiles. It is the least glamorous and most load-bearing layer in the project — a frame-data typo that escapes the validators is a balance bug in everyone's hands, and an asset that escapes the budget catalog is a streaming stall on the platform with the least memory to spare. This page is also deliberate about which parts are running config and CI, which are typed-and-validated C++ policy, and which are still specification no cooked build yet satisfies. The section hub is ../V2_ARCHITECTURE.md.
What ships, honestly#
Four layers, stated plainly, because the split between real and aspirational is the whole point of this page.
- The build pipeline is real config.
V2/ue/Build/Build.xmlis a genuine, 95-line BuildGraph script with six per-platform agents, realCompile/Cook/BuildCookRunnodes, a shared deterministic DDC, and anAggregatethat ties the whole fan-out together (Build.xml:93). Three real*.Target.csrules underV2/ue/Sourcedefine the game client, editor, and dedicated server. - The asset budgets are typed C++ policy with validators — not prose. The
numbers below (80k tris, 64 MB textures, the Switch 2 1.5 GB streaming pool)
live as fields on
FV2AssetBudgetStreamingCatalogand its sibling structs inV2/ue/Source/V2Editor/Public/V2EditorTypes.h, each with anIsValidPolicy()validator that fails on a wrong value, exercised byAssetBudgetStreaming.spec.cppand cross-checked against a committed contract bycheck-v2-asset-budget-streaming.py. This is stronger than "a budget the audit checks against": the budget is a validated object. - The data pipeline is real, but narrower than the old prose implied. The
spreadsheet-of-truth (
V2/balance/data/frame-data.csv) and its branch-stamped export (export-v2-frame-data-csv.py→V2/balance/exports/<branch>/frame-data.csv, validated bycheck-v2-frame-data-csv-export.py) are real, and the CSV is ingested into the cook by Sophia. But the often-claimed "CSV↔DataTable round-trip importer commandlet" does not exist inV2Editor(a grep for CSV/DataTable import returns nothing); the only commandlet there is the region-variant one. That gap is named here, not papered over. - The content is a logic-and-data skeleton. Measured live in this tree: 8
real
.uassetbinaries (theDA_RegionVariant_*data assets underV2/ue/Content/V2/Regions/) and 0.umap— no character meshes, stages, animations, VFX, or audio binaries. The budgets and validators are real and would catch regressions; the cooked content they bound is not in the repository. LikewiseV2/legal/rights-manifest.json(cited by the architecture) is not present —V2/legal/holdsrating-boards.json,platform-cert-bans.json, andsub-processors.mdinstead.
The honest summary: the process and the validators are real, the cooked binaries the process exists to produce are not.
Build, cook & patch#
The BuildGraph script#
V2/ue/Build/Build.xml declares the platform matrix as three properties: client
platforms Win64;PS5;XSX;Switch2 (Build.xml:13), server platforms
LinuxServer-x86_64;LinuxServer-ARM64;Win64Server (:14), and Mac as
dev-only editor (:15). It then defines six Agents —
Win64 Editor And Client (:18), PS5 Client (:42), XSX Client (:51),
Switch2 Client (:60), Linux Dedicated Server (:69), and
Mac Developer Editor (:84). Each client agent runs the same disciplined node
sequence: compile the shipping V2 target, run a Sophia release-candidate
ingest, Cook with -ddc=$(ReferenceDDC) -unversioned -compressed, then
BuildCookRun with -build -cook -stage -pak -archive into a per-platform
archive directory (e.g. Build.xml:28-32 for Win64). The Linux server agent
builds both x86_64 and ARM64 dedicated-server architectures (:70, :76) — the
dedicated / validator server the
online backbone re-simulation
pool runs on. An Aggregate Name="V2 BuildGraph All" (:93) requires every
platform build plus the shader-distribution plan, so "build everything" is one
named target rather than a runbook.
Two pieces make the cook reproducible. ReferenceDDC (Build.xml:5) is a
shared Derived Data Cache path described in the file itself as being there "for
deterministic cooks," and the Plan V2 Shader Distribution node (:22) shards
shader permutations across the farm via plan-v2-shader-distribution.py with a
--max-permutations-per-shard cap of 512 (:7).
Targets & determinism#
The three real target rules under V2/ue/Source are not interchangeable
boilerplate. V2.Target.cs is the Game client and bakes determinism into the
binary: it adds DETERMINISM=1, V2_DETERMINISM=1, and V2_STRICT_FP=1 and
compiles Win64 with /fp:strict /fp:except-, so the floating-point behavior the
rollback netcode depends on is a compiler contract, not a hope.
V2Editor.Target.cs is the Editor target and pulls in the V2Tests module so
automation runs in-editor. V2Server.Target.cs is the Server target and
strips the client surface — V2_NO_RENDERER, V2_NO_AUDIO,
V2_HEADLESS_SERVER, and disabled Niagara / CommonUI / Metasound plugins — for
the minimal-memory dedicated server, building for Linux x86_64, Linux ARM64, and
Win64.
Cook, distributed compile & Sophia ingest#
The cook itself is the Cook task inside each platform node, always
-unversioned -compressed against the shared ReferenceDDC. Distributed builds
run on UnrealBuildAccelerator / Horde with a ≥ 75% cache-hit-rate gate and
shared DDC for editor performance (architecture §Build). The distinctive wiring
is Sophia release-candidate ingestion: before each platform's cook,
run-sophia-release-candidate-ingest.py ingests
V2/balance/data/frame-data.csv and the patch-notes corpus into a per-platform
ingestion manifest (Build.xml:9, :30, :45, :63, :72 …), so the same
frame-data spreadsheet that documents a move's startup is the artifact stamped
into the release candidate at cook time.
CI exercises a slice of this on every PR. .github/workflows/v2-build.yml runs
a Win64 editor, automation, cook job (:28) that compiles the editor
(:2334), runs the V2.* automation suite (:2344), enforces the combat
frame budget (:2357), and cooks a Win64 shipping build (:2367) before
uploading reports (:2377). Upstream of the cook the same workflow runs
check-v2-buildgraph.py (:46), check-frame-budget.py --self-test (:2312),
the asset-name linter (:2313), and the frame-data export check (:1942).
Patch & chunking#
Patching is chunked and incremental per platform with signed manifests, and the
size budgets are not prose — they are an enforced gate.
V2/ue/Tools/check-v2-day-one-patch-hotfix-recovery.py asserts
dayOnePatchBudgetGB == 5 (:124) and subsequentPatchTypicalGB == 2
(:125), failing the build with "Day-one patch policy must cap day-one patch at
5GB and subsequent patches at 2GB typical." Siblings cover the rest of the
post-launch surface: check-v2-server-hot-patching.py (data-only balance
hotfix), check-v2-anti-cheat-self-update.py, and
check-v2-drm-removal-pathway.py (the documented Denuvo-removal window). The
chunk boundaries patching rides are the same per-fighter chunks the streaming
catalog defines (next section), which is exactly why a DLC fighter can ship
without re-downloading the base game — the
live-ops & store DLC delivery
relies on those boundaries.
Asset budgets & streaming#
A fighting game is a worst case for memory: up to eight high-detail characters, a stage, VFX, and per-locale audio resident at once at a locked frame rate. V2's budget is therefore explicit, per-fighter, per-stage, and — crucially — a typed C++ catalog with validators, not a wiki page.
The typed budget catalog#
check-v2-asset-budget-streaming.py enumerates the real surface it validates:
FV2AssetBudgetFighterProfile, FV2AssetBudgetStageProfile,
FV2AssetBudgetStreamingPolicy, FV2AssetBudgetAuditPolicy,
FV2AssetBudgetShaderComplexityPolicy, FV2AssetBudgetStageParticlePolicy,
FV2AssetBudgetMemoryPolicy, FV2AssetBudgetPSOCachePolicy,
FV2AssetBudgetAsyncLoadingPolicy, all aggregated by
FV2AssetBudgetStreamingCatalog with an IsValidCatalog() validator — every
one a real struct in V2/ue/Source/V2Editor/Public/V2EditorTypes.h. The checker
cross-references the C++ against a committed
AssetBudgetStreaming_V2_Contract.json, and AssetBudgetStreaming.spec.cpp
(V2/ue/Source/V2Tests/Private/Editor/) exercises the validators in-engine.
The numbers are real field defaults, and they are the values the validators reject deviations from:
| Surface | Budget | Source |
|---|---|---|
| Principal fighter, LOD0 | ≤ 80k tris | V2EditorTypes.h:4601 |
| Mid-roster / crowd, LOD0 | ≤ 60k / ≤ 25k tris | :4604, :4607 |
| Fighter textures | ≤ 64 MB (4K) / 32 MB (2K) / 8 MB (Switch 2 1K) | :4610, :4613, :4616 |
| Fighter animation | ≤ 800 unique anims, ≤ 80 MB compressed package | :4619, :4622 |
| Fighter audio | ≤ 50 MB per locale | :4625 |
| Stage | ≤ 4M tris, ≤ 256 MB textures, ≤ 30 MB locale audio | :4657, :4660, :4666 |
| Stage particles | 100k outdoor / 60k indoor / 80k arena | :4793, :4796, :4799 |
| Streaming pools | PS5 / XSX 4 GB, PC 8 GB, Switch 2 1.5 GB | :4692–:4701 |
FV2AssetBudgetStreamingPolicy::IsValidPolicy (V2EditorTypes.cpp:3725) hard-
checks the pool sizes —
PS5StreamingPoolMB != 4096 || … || Switch2StreamingPoolMB != 1536 fails with
"Streaming pool policy must be PS5 XSX 4GB, PC 8GB, and Switch2 1.5GB."
(:3727-3732). The 1.5 GB Switch 2 pool is the binding constraint that drives
the entire LOD/streaming design.
The streaming model & per-fighter chunks#
Streaming is priority-driven, and the priorities are boolean policy fields, not
comments: bVisibleFightersAndStageHighPriority (:4707),
bOffscreenPartnerFightersDemoted (:4710), bVoiceBanksStreamedOnDemand
(:4713), bMusicTracksStreamed (:4716), and
bCrowdInstancedMeshBoneImpostorLOD (:4681) — in-match fighters and the stage
are prioritized, off-screen tag partners are demoted, voice and music stream on
demand, and crowds use instanced static meshes promoting to bone-animated
impostors at distance. HasRequiredFighterChunks() (V2EditorTypes.cpp:3716)
requires each fighter to split into exactly the Character, Voice,
BaseCostume, AltCostume, and CosmeticPack chunks (:3718-3722) — the
boundaries incremental install and DLC ride.
The weekly audit#
FV2AssetBudgetAuditPolicy (V2EditorTypes.h:4723) makes the audit a policy
object too: a WeeklyAuditCheckIds set with bWeeklyAssetAuditReport,
bNamingConventionLinterGate, bBudgetLinterGate, and
bEveryNewAssetMustPassCI flags, validated by HasRequiredAuditChecks() /
IsValidPolicy(). The weekly report flags orphan / oversize / missing-LOD /
missing-skin / redundant-material assets, and a coarser CI gate
(check-v2-asset-size-budget.py against
V2/ue/Build/AssetPipeline/v2-asset-size-budget.json) warns at 100 MiB and
fails at 500 MiB per asset — so budget drift is caught as a report or a red PR,
not a shipped regression.
Data architecture: the spreadsheet is the source of truth#
This is the part most worth understanding, because it is what makes V2 balanceable without recompiling C++.
frame-data.csv → branch-stamped export#
The canonical source is V2/balance/data/frame-data.csv, an 11-column sheet
(fighter_id, move_id, move_name, ruleset, startup_frames,
active_frames, recovery_frames, on_hit_advantage, on_block_advantage,
damage, build_branch). export-v2-frame-data-csv.py (144 lines) reads it,
validates that the header tuple matches exactly and that no
(fighter_id, move_id) pair is duplicated (:69, :82), stamps the current
branch into build_branch, and writes
V2/balance/exports/<branch>/frame-data.csv. Its --check mode (:104-109)
re-derives the export and fails if the committed file drifts from the source, so
the documentation, the balance dashboards, and the game read one canonical
dataset. check-v2-frame-data-csv-export.py (219 lines) layers a JSON-schema
(frame-data.schema.json) and a contract
(FrameDataCsvExport_V2_Contract.json) check on top, comparing every exported
row against the canonical source.
Typed DataAsset contracts & validators#
Runtime move/fighter data lives in typed C++ data assets — UV2MoveFrameData,
UV2FighterData, and their moveset/style/arena siblings — and the integrity
gates are real spec suites, not shape checks. MoveDataAsset.spec.cpp (1,428
lines, V2/ue/Source/V2Tests/Private/Combat/) asserts startup/active/ recovery
frames, hitbox/hurtbox geometry, dash-cancel windows, armor and invincibility
frames, adaptive-trigger profiles, and haptic curves;
FighterDataAsset.spec.cpp builds a complete fighter (skeleton, mesh, voice
bank, theme music, signature moveset) and validates loadout, stat block,
finishers, and weapons. Per-asset Python checkers
(check-v2-move-data-asset.py, …fighter…, …moveset…, …style…) gate the
same contracts in CI. These are the "move / fighter / moveset / finisher / style
/ ruleset / arena / entrance" validators the architecture lists.
What is honestly still spec#
The frequently-claimed "CSV↔DataTable round-trip importer commandlet" in
V2Editor is not implemented — a grep across V2/ue/Source/V2Editor for
CSV parsing or DataTable import returns nothing, and the module's only
commandlet is V2CreateRegionVariantsCommandlet. The real round-trip is the
CSV-to-CSV branch-stamping export above, plus Sophia's cook-time ingest of the
sheet; the import of authored CSV into engine DataTables on cook is the
specified-but-unbuilt half. Naming this is the difference between a documented
pipeline and a fabricated one.
Production pipeline#
The production pipeline binds capture, consent, and cert into the build. Most of
it lives as real apps/v2 packages rather than engine code, and the
region-variant path is the one place it produces a real binary.
Mocap, consent & NIL#
Studio and markerless capture run through real service packages:
aja-studio-mocap-pipeline, aja-markerless-capture-pipeline, and
aja-bellona-livelink-export handle Vicon / OptiTrack ingest, cleanup,
V2-skeleton retarget, and Bellona/UE import. Every capture first passes
aja-consent-nil-ledger, which requires active consent and a Themis likeness
(NIL) link before ingest — revocation blocks future Aja processing and Bellona
cook for dependent clips. The likeness-safety gate is
aphrodite-licensed-likeness-safety, backed by aphrodite-consent-surfaces and
aphrodite-age-gate, run before likeness surfaces, creator-suite publishing, or
replay export. These directories exist under apps/v2/; the VO
(loudness-normalized Opus banks) and music (MetaSounds graph) pipelines they
feed are documented in the architecture.
Region & cert variants — the one real binary surface#
V2CreateRegionVariantsCommandlet (V2/ue/Source/V2Editor/Private/) reads the
rating-board manifest (V2/legal/rating-boards.json), applies per-region
content-policy flags (gore, dismemberment, profanity, localization compliance),
and writes the 8 DA_RegionVariant_*.uasset data assets under
V2/ue/Content/V2/Regions/ (JP, BR, KR, AU, NZ, DE, US, EU). These are the only
real cooked binaries in V2's content tree, and they stand as the
proof-of-concept for what the rest of the pipeline would produce against an
attached editor and art depot.
Milestones & rights — what's specified#
The milestone gates (Greenlight → Alpha → Beta → Gold master) and the per-asset
rights verification are documented obligations rather than committed artifacts
in this tree: V2/legal/ carries rating-boards.json,
platform-cert-bans.json, and sub-processors.md, but the architecture-cited
V2/legal/rights-manifest.json is not present, and no
V2/docs/production/milestones/ directory exists here. The rights-verification
step that gates "every shipping asset has cleared rights or is original-IP"
therefore remains spec until that manifest lands.
Edge cases & failure modes#
- The Switch 2 pool is the hard floor.
IsValidPolicyrejects any streaming catalog whose Switch 2 pool isn't exactly 1.5 GB; the LOD chains and 1K texture packs exist to fit it, and every other platform inherits the discipline. - A frame-data typo fails the build, not the player. The export
--checkmode pluscheck-v2-frame-data-csv-export.pyfail CI if the committed export drifts fromV2/balance/data/frame-data.csv, and the per-asset spec suites assert computed frame values — so the documented startup and the executed startup cannot disagree. - Hotfixes can never become code pushes. The day-one/hotfix checker caps the day-one patch at 5 GB and constrains hotfixes to data-only payloads, so a balance change ships without a binary update and a binary change can't smuggle through the hotfix channel.
- Revoked consent blocks the cook. A likeness whose NIL consent is revoked
fails
aja-consent-nil-ledger/aphrodite-licensed-likeness-safetybefore Bellona cook for any dependent clip — capture rights are a build gate, not a legal afterthought. - No binaries means open claims stay open. With 8 region-variant assets and
zero
.umap, anything requiring cooked content — profiled frame rates, PSO caches, a playable stage — is not demonstrable from this tree, and this page declines to claim it is.
Where this connects#
- Up to the content it budgets: Roster, Presentation & Stages and the combat system, whose frame data this pipeline owns and exports.
- Sideways:
Telemetry, Performance, Testing & Release Gates
(the cook frame-budget gate and the
frame-data.csvthe dashboards read) and Live-Ops, Store, Progression & Community (DLC delivery over the same per-fighter chunk boundaries). - Integrity: Online Backbone & Competitive Integrity — the Linux dedicated/validator server this BuildGraph cooks and the signed patch manifests it ships.
- Platform: the shared platform foundations
the
apps/v2production packages build on.