# Balancing Process How balancing is done in this project: the pass order, the tuning discipline, and the tools. Refer to this when starting the next balancing round. ## The pass order Each pass depends on the ones before it; a change in an earlier pass invalidates the later ones (but not vice versa). Redo from the earliest pass whose inputs changed. 1. **Targets** (`targets.md`) — choose the root numbers: run shape, factory curve, threat-cost ladder, fleet size, block roots, combat anchors, pacing anchors. These are design decisions, not measurements. Everything else is derived from them. 2. **Tree structure** (`../content_design.md`) — items, chains, what-consumes-what, per the production tree rules (one input per phase transition, generic parts, archetypes, refactorability). Structure only, no quantities. 3. **Numbers** (`derived.md`, recipes/materials in the configs) — quantities and durations so every fitted ship sums to its ladder value, the ratio curve is realized, and the belt/buffer guardrails hold. Verified computationally by `tools/threat_report.py`. 4. **Calculator/tooling parity** — the game's `ThreatCostCalculator` and `tools/threat_report.py` must produce identical values; the Python tool is the design reference. Any semantic change to REQ-THREAT-* needs both updated plus tests. 5. **Combat stats** (arena-driven) — derive stats from the combat anchors, then iterate against the arena suite (`bin/balancing/data/balancing.toml`) until equal-threat matchups are near-draws. Threat costs are stat-independent, so arena ship counts stay valid across stat changes. 6. **Pacing** — unlock ladder, `unlock_requires` edges, threat rate, block/artifact/expansion values, per the pacing anchors. Then: **full-game playtests**, which are the only check for the pacing pass and feed back into targets. ## Tuning discipline (learned in arena rounds 1–5) - **Change anchors, not symptoms.** When a class of results is off, adjust the anchor that explains all of them (e.g. the concentration tax) rather than individual stats. - **Fewest knobs per round.** Attribution dies when many knobs move at once. Prefer one anchor change plus its mechanical compensations. - **Shared vs. local knobs.** Guns and module stats are shared across many hulls — changing them moves many matchups. Per-hull HP moves exactly one matchup; it is the designated per-ship trim knob on top of the HP-per-threat prior. - **Mind the ride-alongs.** A module buff lands on every default loadout containing it (e.g. an armor buff strengthens the destroyer swarm that opposes the dreadnought). Compute the net effect per matchup before choosing step sizes. - **Two-round signal policy.** Single arena runs re-roll by ~±10% EHP margin; a margin inside ±20% counts as converged for v1. Only act on signals that persist across two rounds. - **Arena geometry is part of the fixture.** Lane width/height changes the results (full engagement vs. fleets slipping past); margins are only comparable within the same geometry. - **Accept mechanical texture.** Not every deviation is a bug: a margin that survives a stat change is mechanical (usually range/kiting under the orbit AI) and may be desirable doctrine texture. Document the acceptance in `targets.md` instead of chasing it. - **Range is the strongest stat** under the orbit AI — free approach fire. Price range modifiers conservatively; station dominance is controlled via range, not HP. ## Tools - `tools/threat_report.py` — item threats, module contributions, hull/fitted ship threats, producer:consumer ratios, belt feasibility; reads the real configs. The design reference for threat semantics. - `tools/verify_recipes.py` — recipe tree closure, visuals coverage, orphans, reprocessing-only items. - `tools/verify_layouts.py` — module footprint gating matrix per hull. - **Balancing tool** (`balancing` target) — parallel arena simulation of `bin/balancing/data/balancing.toml`; logs winner, surviving counts, team EHP %, and fight duration per arena. The suite covers: class mirrors (expect near-mutual annihilation, symmetric winners), equal-threat cross-tier matchups (expect near-draws — power-per-threat made empirical), a 2:1 decisiveness check, doctrine matchups (armored-vs-glass, repair-escort), and station assault. ## Checklist for the next balancing round 1. Pull; run `verify_recipes.py`, `verify_layouts.py`, `threat_report.py`; compare against the tables in `derived.md`. 2. If recipes/materials changed: re-check fitted threats vs. the ladder in `targets.md`; update arena suite ship counts if fitted values moved. 3. Run the arena suite; read EHP margins and durations against the expectations noted in `balancing.toml` and the anchors. 4. Apply changes per the tuning discipline (two-round signals only); record the round and its knob changes in `history.md`. 5. Update `derived.md` where values moved; if an anchor moved, update `targets.md` and state why. 6. Commit and push (the review workflow reads the remote).