Fighting Game · Features

Balance Ops, Wellness, Engineering Polish & Launch

A focused page within the Fighting Game Features documentation. The full map and every sibling page live in the Features hub.

7sections15 minread1diagram

On this page
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.

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 (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.