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.
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/andV2/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.pyexports the spreadsheet-of-truth (V2/balance/data/frame-data.csv) to a branch-scoped CSV underV2/balance/exports/<branch>/, and its--checkmode 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
USTRUCTpolicies insideFV2AssetBudgetStreamingCatalog(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 byV2.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-reviewbecause 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 (the gates that consume balance telemetry and block a bad build) and ../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
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.
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
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.
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 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.
Related#
- The feature hub: ../V2_features.md
- Platform, Accessibility, Localization & Production — the VR/AR, hardware-peripheral, locale, and production surfaces this group's launch gate also covers
- Foundations, Glossary & Conventions — the rollback/determinism vocabulary and the implemented/spec/gated labelling this page uses
- Telemetry, Performance, Testing & Release Gates — the architecture companion for the balance dashboards, per-frame budgets, and the release gate
- Build, Cook, Assets, Data & Production — the spreadsheet-of-truth data pipeline and the asset budgets the engineering-polish catalogue enforces