# Build, Cook, Assets, Data & Production

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](../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.xml` is a genuine,
  95-line BuildGraph script with six per-platform agents, real `Compile` /
  `Cook` / `BuildCookRun` nodes, a shared deterministic DDC, and an `Aggregate`
  that ties the whole fan-out together (`Build.xml:93`). Three real
  `*.Target.cs` rules under `V2/ue/Source` define 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 `FV2AssetBudgetStreamingCatalog` and its sibling structs in
  `V2/ue/Source/V2Editor/Public/V2EditorTypes.h`, each with an `IsValidPolicy()`
  validator that _fails_ on a wrong value, exercised by
  `AssetBudgetStreaming.spec.cpp` and cross-checked against a committed contract
  by `check-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 by
  `check-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** in `V2Editor` (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 `.uasset` binaries** (the `DA_RegionVariant_*` data assets under
  `V2/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. Likewise `V2/legal/rights-manifest.json` (cited by the
  architecture) is **not present** — `V2/legal/` holds `rating-boards.json`,
  `platform-cert-bans.json`, and `sub-processors.md` instead.

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 `Agent`s —
`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](./online-backbone-and-competitive-integrity.md) 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](./live-ops-store-progression-and-community.md) 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.

```mermaid
flowchart LR
  S[V2/balance/data/frame-data.csv<br/>spreadsheet of truth] -->|export + branch-stamp| EX[V2/balance/exports/branch/frame-data.csv]
  S -->|cook-time ingest| SOPHIA[Sophia release-candidate manifest]
  EX --> CHK[check-v2-frame-data-csv-export.py<br/>schema + contract]
  C[(FV2AssetBudgetStreamingCatalog<br/>typed budgets)] --> VAL[IsValidCatalog · AssetBudgetStreaming.spec.cpp]
  VAL --> CI{PR gate · v2-build.yml}
  CHK --> CI
  FB[check-frame-budget.py ≤16.6ms] --> CI
  CI --> COOK[BuildGraph cook · 6 platform agents]
  COOK -.->|no character/stage binaries in repo| BIN[(cooked .uasset / .umap<br/>NOT present)]
  RC[V2CreateRegionVariantsCommandlet] --> REG[(8 DA_RegionVariant_*.uasset<br/>the only real binaries)]
```

## 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.** `IsValidPolicy` rejects 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 `--check`
  mode plus `check-v2-frame-data-csv-export.py` fail CI if the committed export
  drifts from `V2/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-safety` before
  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](./presentation-av-and-signature-content.md)
  and the [combat system](./combat-system-gas-frame-data-and-determinism.md),
  whose frame data this pipeline owns and exports.
- **Sideways:**
  [Telemetry, Performance, Testing & Release Gates](./telemetry-performance-testing-and-release-gates.md)
  (the cook frame-budget gate and the `frame-data.csv` the dashboards read) and
  [Live-Ops, Store, Progression & Community](./live-ops-store-progression-and-community.md)
  (DLC delivery over the same per-fighter chunk boundaries).
- **Integrity:**
  [Online Backbone & Competitive Integrity](./online-backbone-and-competitive-integrity.md)
  — the Linux dedicated/validator server this BuildGraph cooks and the signed
  patch manifests it ships.
- **Platform:** the [shared platform](../../platform/overview.html) foundations
  the `apps/v2` production packages build on.
