# Balance Ops, Wellness, Engineering Polish & Launch

```mermaid
flowchart LR
  Balance[Canonical frame and balance data] --> Validate[Schema drift and simulation validation]
  Validate --> Views[Tier list matchup chart and public frame data]
  Validate --> Runtime[Versioned runtime catalogue]
  Runtime --> Telemetry[Match quality and performance telemetry]
  Telemetry --> Review[Balance privacy wellness and sensitivity review]
  Views --> Review
  Review --> Candidate[Cook PSO shader asset and platform candidate]
  Candidate --> Gate[Automated launch-readiness gate]
  Gate --> Release[Staged release and rollback]
```

The pipeline keeps public/player views, runtime tuning, and release evidence on
one versioned source while preserving independent privacy, wellness,
sensitivity, performance, and platform gates.

A fighting game lives or dies on two promises that are easy to make and hard to
keep: that the balance team argues from data the players can also see, and that
the build only ships when it is actually ready. This page covers the unglamorous
machinery behind both — the **balance operations** that publish a tier list,
matchup chart, and public frame data from the same spreadsheet the live game
runs on; the **privacy and studio-wellness** controls that gate content and
vendor decisions; the **engineering polish** (PSO pre-cache, shader budgets,
asset pre-load) that keeps 60 fps from stuttering on first launch; and the
**launch-readiness** gate that turns "are we done?" into a checklist a machine
can fail. The interesting thing about V2's implementation is how much of it is
_real artifact_ — versioned JSON data contracts, a validated Unreal C++
streaming catalogue, a Python export tool with a drift gate, a launch catalogue
pinned by an automation spec — and how honestly the few genuinely-pending pieces
(the contracted sensitivity-reader pool, the runtime shader-prewarm pass) are
labelled as pending rather than dressed up. It belongs to the "Racing, Platform
& Launch" group; the section hub is [../V2_features.md](../V2_features.md).

## What ships, honestly

- **The public balance surfaces are real, versioned data.** Tier lists, the
  per-rank sub-tier-list, the matchup chart, the ML-recommender output, and the
  per-fighter changelogs all live under `V2/balance/public-results/` and
  `V2/balance/changelog/` as schema-stamped JSON/CSV/Markdown, and a shipped
  static web tool (`apps/v2/web/balance/`) renders them.
- **The frame-data export is a real tool with a drift gate.**
  `V2/ue/Tools/export-v2-frame-data-csv.py` exports the spreadsheet-of-truth
  (`V2/balance/data/frame-data.csv`) to a branch-scoped CSV under
  `V2/balance/exports/<branch>/`, and its `--check` mode is the gate that fails
  CI if the export drifts from source.
- **Engineering polish is a validated C++ catalogue.** PSO caching, async asset
  pre-load, and shader budgets are real `USTRUCT` policies inside
  `FV2AssetBudgetStreamingCatalog` (`V2/ue/Source/V2Editor`), each with a
  cross-checking validator and a contract spec that asserts the negative cases.
- **Launch readiness is a validated C++ catalogue too.**
  `FV2LaunchReadinessCatalog` (`V2/ue/Source/V2Tests`) carries the §138 drill,
  go/no-go, critical-journey, and exit-criteria plans, built by the harness and
  pinned by `V2.Tests.LaunchReadiness.AssetContract`.
- **Privacy and wellness are documented policy with one honest fail-closed
  control.** The per-fighter sensitivity reviews are real packets, but the
  ledger fails _closed_ at `pending-external-review` because no reader pool is
  contracted — and a checker tool enforces that. Privacy-by-design, DPIA, and
  the sub-processor registry are policy/legal documents, not engine code.

The architecture companions for this cluster are
[../architecture/telemetry-performance-testing-and-release-gates.md](../architecture/telemetry-performance-testing-and-release-gates.md)
(the gates that consume balance telemetry and block a bad build) and
[../architecture/build-cook-assets-data-and-production.md](../architecture/build-cook-assets-data-and-production.md)
(the spreadsheet-of-truth data pipeline and asset budgets). This page stays on
the feature surface and dips into code where a claim needs backing.

## Balance operations: one dataset, many views

The discipline that makes V2 balanceable is that the documentation and the game
read **one canonical dataset**. The thousands of frame-data numbers are authored
in a spreadsheet-of-truth at `V2/balance/data/frame-data.csv`, flow into the
engine through the `V2Editor` CSV↔DataTable importer, and are exported back out
for the public surfaces — the loop the
[build/data architecture page](../architecture/build-cook-assets-data-and-production.md)
diagrams.

### The frame-data export and its drift gate

`V2/ue/Tools/export-v2-frame-data-csv.py` is the export half, and it is a real
tool, not a copy step. It validates that the source headers exactly equal the
eleven-column `REQUIRED_HEADERS` tuple (`fighter_id … build_branch`), rejects a
duplicate `(fighter_id, move_id)` row, rejects any row with a missing field,
sanitises the branch name against `BRANCH_COMPONENT_RE` before it becomes a path
component, and rewrites the `build_branch` column to the export branch. Its
`--check` mode is the **drift gate**: it re-renders the export and raises if the
on-disk `V2/balance/exports/<branch>/frame-data.csv` does not match
byte-for-byte, with the message
`run export-v2-frame-data-csv.py --branch <branch>`. The export contract
`V2/balance/exports/FrameDataCsvExport_V2_Contract.json`
(`v2.balance.frameDataCsvExport.v1`) names the tool as `generatedBy`, pins the
schema path, and records the shipped `oshun-v1-nisaba` export. The row shape
itself is schema-checked by `V2/balance/schema/frame-data.schema.json`, which
requires the integer frame fields to be non-negative (advantage fields may be
negative — `heavy_launcher` is `-14` on block). This is the mechanism behind the
architecture page's claim that the public frame data and the live game _cannot
disagree_: the export is mechanically derived from the same CSV the importer
feeds the runtime, and the `--check` gate fails the build if anyone hand-edits
the export.

### Tier lists, official and per-rank

The published tier list is `V2/balance/public-results/tier-list-2026-05-s1.json`
(`v2.balance.officialTierList.v1`). It declares
`sourceOfTruth: "@maat/dashboard"` and `communityPollingSupplement: true`, so
the official tier reflects telemetry first and community sentiment second. Each
fighter entry is exactly the source feature's "tier with rationale and suggested
counters" — Katana Vanguard at `S` ("down from S+"), Grapple Monk `A` ("up from
B+"), with `rationale`, `suggestedCounters`, and a `changeFromPreviousPatch`
delta. The `publication` block wires it to the surfaces: the web tool, the
companion-app balance screen, the raw-CSV download, and
`apps/oshun/web/api/v2/frame-data`.

The per-rank sub-tier-list is the source's "what is strong at Bronze differs
from what is strong at Master," made literal. `rank-tier-list-2026-05-s1.csv`
carries 28 rows — four fighters across all seven `rankSpecificViews`
(Bronze→Grandmaster) — and the data tells the story: **King is `S` at Bronze**
(throw loops dominate early defensive habits) and falls to `B` at Master (mature
counterplay), while **Katana Vanguard climbs from `A` at Bronze to `S` from Gold
upward** as launcher confirms stabilise. The official top-line tier and the
per-rank breakdown are two different cuts of the same patch.

### The auto-generated matchup chart

`matchup-chart-2026-05-s1.csv` is the "auto-generated from match-result data
with per-rank-tier variation and an optional per-stage variance view" promise as
concrete columns: `overall_win_rate`, `bronze_win_rate`, `master_win_rate`,
`stage_variance_delta`, and `matchup_notes`, one row per ordered matchup (twelve
rows for the four-fighter sample). The rank split is visible in the numbers —
Katana vs Grapple Monk is `54.8` overall but `51.1` at Bronze and `56.4` at
Master, because the launcher-punish that decides the matchup is a high-rank
skill. The web tool reads the mirrored `balance.json` to render a per-fighter
matchup list and a togglable stage-variance panel (e.g. "Metro Rooftop favors
Katana Vanguard into King by +3.0%").

### The public balance web tool

`apps/v2/web/balance/` is the shipped, static face of all of this —
`index.html` + `balance.js` + `balance.css` over a single `balance.json`
(`v2.web.balanceOperations.v1`). It is a real client, not a mock: `balance.js`
binds a rank-view `<select>` that swaps the official tier list for the per-rank
cut, a fighter `<select>` that filters matchups, a "show stage variance" button,
a frame-data search box, a **raw CSV download** link (`frame-data.csv`), a
per-patch frame-data diff list (`Cross-Up Kick startup +2`,
`Heavy Launcher recovery +2`), a pro-feedback panel, the ML-recommender output,
and the post-patch polls. The header states the cadence the source specifies —
"monthly major / bi-weekly minor" with a "2-week community comment window." The
same data is consumed by the `@v2/maat-balance-dashboard` service
(`apps/v2/maat-balance-dashboard`), the operator-side counterpart to this
read-only player surface.

### Patch process: changelog, recommender, polls

Each fighter keeps a public changelog at `V2/balance/changelog/<fighter>.md`
(`katana-vanguard`, `asha-storm`, `grapple-monk`, `king`). The format is a
disciplined audit table — Patch, PTB Signal, A/B Result, Move, Before, After,
Rationale — so `Cross-Up Kick startup 12f→14f` is justified by a named PTB
signal (`ptb.s1.cross-up-kick.block-winrate`) and an A/B id
(`balance.s1.cross-up-kick`), not a designer's hunch. The "ML balance
recommender proposes candidates but never auto-applies" promise is
`ml-recommendations-2026-05-s1.json` (`v2.balance.mlRecommendations.v1`): it
stamps `designerReviewedOnly: true` and `neverAutoApplied: true`, lists what it
`trainedOn` (patch delta, match outcome, community feedback, PTB survey, public
replay tags), and carries per-candidate `confidence`, `telemetryJustification`,
and a `designerDecision` of `accepted` or `watchlist` (King's walk-speed buff
sits at `0.61` confidence on the watchlist). Its `abTesting` block requires
`minimumSampleMatches: 5000` on the PTB branch and points at a
`rollbackRunbookUri`. Post-patch sentiment is the polls block, and the
`modern-vs-classic-balance.csv` carries the separate simplified-controls parity
scalars (King Modern gets an `0.82` scalar bump for under-performing Classic).
Nothing here auto-tunes the game; every number is a designer-reviewed,
publicly-justified change.

## Privacy-by-design & studio wellness

These controls sit beside balance ops because they are governed the same way —
documented policy enforced by a checker — and because the most honest thing in
this whole cluster is how one of them fails _closed_.

### Sensitivity reviews that fail closed

Every launch-roster fighter has a per-fighter cultural-review packet at
`V2/balance/fighters/<Fighter>/sensitivity-review.md` (60 packets), and a single
ledger, `V2/balance/fighters/sensitivity-review-ledger.csv`, tracks seven
sign-off dimensions per fighter (narrative, character design, costume, lore,
regional naming, stage context, overall). The honest part: **every row is
`pending-external-review`**, and the packets say why — "no sensitivity-reader
pool is contracted," so the §95.3 reader-pool task is held and no sign-off may
be flipped to approved "without a real, named reviewer-group record." This is
not a stub pretending to be done; it is a fail-loud control.
`check-v2-studio-wellness.py` (`V2/ue/Tools/`) enforces it — the ledger fails
closed at `pending` until a real reviewer record exists, and partner-IP fighters
(Chun-Li) additionally fail closed pending an executed licence. The
infrastructure is real; the sign-offs are honestly absent.

### Privacy and the sub-processor registry

The privacy-by-design, per-feature DPIA, AADC/Quebec/LGPD compliance, and
quarterly-DPO-review items the source lists are **policy and legal documents**,
not engine code — the operational privacy spine. The one shipped artifact worth
citing is `V2/legal/sub-processors.md`, the public sub-processor registry: it
requires DPO/legal approval and **30 days' notice** before live player data is
routed to a new vendor, mandates per-platform DPAs with Sony/Microsoft/Nintendo/
Steam/Epic and the cloud providers before launch, and mirrors to a public legal
page. The studio-wellness policy docs (`V2/docs/production/no-crunch.md`,
`mental-health-and-trauma-support.md`, `studio-wellness.md`) encode the
40-hour-baseline no-crunch commitment, the EAP/crisis-line/trauma-informed
support, and a per-release-gate wellness sign-off — and
`check-v2-studio-wellness.py` validates those docs against the
`StudioWellness_V2_Contract.json` snippet contract, so the wellness oath is a
checked artifact rather than a slogan. The PII-strip-at-ingest that protects the
balance telemetry feeding all of the above is covered on the
[telemetry architecture page](../architecture/telemetry-performance-testing-and-release-gates.md).

## Engineering polish: PSO pre-cache, shader budgets, asset pre-load

The performance-polish features the source lists are, where they have engine
backing, sub-policies of the **asset-budget streaming catalogue**
`FV2AssetBudgetStreamingCatalog`
(`V2/ue/Source/V2Editor/Public/V2EditorTypes.h:4911`), built by
`BuildDefaultAssetBudgetStreamingCatalog()`
(`V2/ue/Source/V2Editor/Private/V2EditorBlueprintLibrary.cpp:7609`) and gated by
`IsValidCatalog` (`V2EditorTypes.cpp:3909`), which only passes if all nine
sub-policies validate.

### The PSO cache policy

`FV2AssetBudgetPSOCachePolicy` (`V2EditorTypes.h:4855`) is the source's "PSO
pre-cache at first launch / per-platform blobs shipped pre-compiled /
per-driver-upgrade rebuild," as six booleans: `bPerPlatformPSOCacheBlobCooked`
(blobs cooked per platform), `bDriverVersionRebuildGateAtFirstLaunch` (rebuild
on a driver bump), `bCosmeticPackPSOSupplement` (a cosmetic pack ships its
supplemental PSOs), `bSupplementalCompileProgressBar`, `bBootIntegrityCheck`,
and `bSafeModeRebuildOnCorruption`. The validator `IsValidPolicy`
(`V2EditorTypes.cpp:3864`) requires **all six** — a build that drops the
per-driver rebuild gate or the safe-mode rebuild fails validation outright,
which is what keeps the "no first-match shader-compile hitch" promise from
quietly regressing.

### Shader budgets, and the prewarm caveat

The shader story is two distinct things, and conflating them would be dishonest.
The **shipped** piece is `FV2AssetBudgetShaderComplexityPolicy`
(`V2EditorTypes.h:4747`): per-platform instruction budgets (256 console/PC, 128
Switch 2 at LOD0), per-tier material-slot budgets, Niagara emitter caps, and a
`bPerCosmeticPackShaderAudit` so a cosmetic pack is audited for shader cost
before it ships. The source's "shader pre-warm dummy-rendering _every variant_
at load" is the runtime companion to this budget, and the per-cosmetic-pack
supplement is represented by the PSO policy's `bCosmeticPackPSOSupplement` above
— but the runtime dummy-render pass itself is **design intent, not a separate
struct** in this catalogue. (Note also that the `bRenderWarmup`/`WarmupFrames`
fields at `V2EditorTypes.h:2949` are a _Sequencer cinematic render_ warmup, an
unrelated feature — they are not the gameplay shader-prewarm pass.)

### Async loading & asset pre-load

`FV2AssetBudgetAsyncLoadingPolicy` (`V2EditorTypes.h:4881`) is the "asset
pre-load on save-game open + smart pre-load based on session pattern" feature.
`HasRequiredPriorityHints` (`V2EditorTypes.cpp:3880`) demands all four priority
bands — `Critical`, `High`, `Medium`, `Low` — be cooked, and `IsValidPolicy`
(`:3888`) additionally requires the per-mode preload hint table
(`bPerModePreloadHintTable`), mode-select preloading required assets
(`bModeSelectPreloadsRequiredAssets`), per-asset "now loading" progress, the
visible-vehicle/fighter/stage priority that keeps the streaming model from
demoting what the player can see, and `bDistantAssetsDemotedDuringPlay`. This is
the priority-driven streaming model the
[asset-budget architecture](../architecture/build-cook-assets-data-and-production.md)
describes, expressed as a checkable policy.

### One catalogue, one validator, one spec

All of this is exercised by `AssetBudgetStreaming.spec.cpp`
(`V2/ue/Source/V2Tests/Private/Editor/`). It builds the default catalogue,
asserts `IsValidCatalog` and each sub-policy's validator (the PSO cache "ships
platform blobs, supplements cosmetics, and safe-mode rebuilds," the async policy
"cooks priority hints, mode preloads, and per-asset progress"), and then asserts
the **negative cases** that make it a real test: it rejects an over-budget
principal-fighter triangle count, a missing voice chunk, and — crucially — a
catalogue with the `Low` priority hint removed. A test that fails when you
delete a priority band is testing the policy, not decorating it. The broader
source list (adaptive 60→30 Hz server tickrate, V-Sync/VRR, NVIDIA Reflex, AMD
Anti-Lag 2, background-service worker threads) is **specified polish** that
lives on the netcode and platform surfaces rather than in this editor catalogue;
the per-frame budget those features defend is the contract on the
[telemetry & release-gates page](../architecture/telemetry-performance-testing-and-release-gates.md).

## Launch readiness (§138)

The release gate the source describes — "V2 launches only when every gate is
satisfied; the gates are verified, not asserted" — is a real validated C++
catalogue, `FV2LaunchReadinessCatalog`
(`V2/ue/Source/V2Tests/Public/V2TestHarnessTypes.h:1016`). It carries
`CatalogId = LaunchReadiness.V2`, `SectionId = 138`, and four sub-plans:
`PrelaunchDrills`, `GoNoGo`, `CriticalJourneys`, and `ExitCriteria`. It is built
by `BuildDefaultLaunchReadinessCatalog()`
(`V2/ue/Source/V2Tests/Private/V2AutomationHarness.cpp:501`) and pinned by the
spec `V2.Tests.LaunchReadiness.AssetContract`
(`V2/ue/Source/V2Tests/Private/Launch/LaunchReadiness.spec.cpp`).

The scalar frame is concrete and asserted: `InternalDogfoodDays = 60` (`:902`),
`NetworkStressMultiplier = 5.0` (`:905`), `RosterFighterCount = 60` (`:989`),
`StageCount = 30` (`:992`), and `RollbackPlayableRttMs = 100` against a 30 ms
local-feel reference (`:995`). The drill plan requires a 60-day all-mode,
all-platform internal dogfood and closed-beta metric targets for matchmaking
time, rollback-frame distribution, crash rate, and anti-cheat false-positive
rate; the go/no-go plan pins the checklist path (`V2/docs/launch/go-no-go.md`),
the GA approval, and the rollout/rollback/canary plan ids; the critical-journey
plan enumerates the launch journeys (custom-content end-to-end, a 30-minute
accessibility verifier, cross-play/cross-progression smoke) and the feature
gates (production, UX, compliance, engineering reliability, telemetry-HMAC
replay determinism). What makes this a _checked_ gate rather than a checklist is
the spec's **negative cases**: it sets `InternalDogfoodDays = 30` and demands
`IsValidCatalog` fail, points the go/no-go path at a `missing.md` and demands
failure, removes the `Gate.TelemetryHmacReplayDeterminism` feature gate and
demands failure, and drops the roster to `59` and demands failure. A launch
catalogue that validates with a 30-day dogfood would be a fabricated gate; this
one refuses.

The data twin of the catalogue is `V2/balance/launch/launch-readiness.json`
(`v2.launch.readiness-data.v1`, section `138`): the 28 critical journeys, the 33
feature gates, `firstBootTutorialMaxMinutes: 25`, the 30-minute accessibility
verifier, and the 21 exit criteria (roster 60 / stages 30, rollback 100 ms /
local 30 ms, `noComingSoonPanelsVisible: true`). The human-readable decision
records are `V2/docs/launch/go-no-go.md` and `launch-readiness.md`, which spell
out that the checklist is complete only when seven leads sign the same packet,
and the cooked contract is
`V2/ue/Content/V2/Launch/LaunchReadiness_V2_Contract.json`. The "no coming-soon
panels" content-completeness promise the source repeats is literally an exit
criterion the spec would fail on if the roster fell short.

## How it connects

Balance ops and launch readiness are the two ends of one quality spine. The
spreadsheet-of-truth that the export tool publishes is the same dataset the
[telemetry dashboards](../architecture/telemetry-performance-testing-and-release-gates.md)
score and the release gate consumes; the asset-budget streaming catalogue that
holds first-launch PSO and pre-load in check is what keeps the per-frame budget
the same gate enforces; and the launch catalogue composes all of it — feel,
content completeness, stability, compliance, and reliability — into a single
go/no-go that a spec can fail. The honest edges are clearly marked: the public
balance surfaces are shipped data and a real export tool, the engineering-polish
and launch catalogues are validated C++ pinned by adversarial specs, the runtime
shader-prewarm pass and the contracted sensitivity-reader pool are design intent
that fails _visible and pending_ rather than faking done, and the privacy spine
is documented policy with a checker. The section hub is
[../V2_features.md](../V2_features.md).

## Related

- The feature hub: [../V2_features.md](../V2_features.md)
- [Platform, Accessibility, Localization & Production](./platform-accessibility-localization-and-production.md)
  — the VR/AR, hardware-peripheral, locale, and production surfaces this group's
  launch gate also covers
- [Foundations, Glossary & Conventions](./foundations-glossary-and-conventions.md)
  — the rollback/determinism vocabulary and the implemented/spec/gated labelling
  this page uses
- [Telemetry, Performance, Testing & Release Gates](../architecture/telemetry-performance-testing-and-release-gates.md)
  — the architecture companion for the balance dashboards, per-frame budgets,
  and the release gate
- [Build, Cook, Assets, Data & Production](../architecture/build-cook-assets-data-and-production.md)
  — the spreadsheet-of-truth data pipeline and the asset budgets the
  engineering-polish catalogue enforces
