Split the cluttered progression_design.md and content_design.md into
separated documents by role:
- docs/balancing/rules.md - design rules and principles (moved from
progression_design.md, which is removed)
- docs/balancing/targets.md - the chosen base numbers: run shape,
factory curve, threat ladder (achieved
values adopted), combat anchors, pacing
anchors
- docs/balancing/derived.md - current tuned state of all derived
numbers, mirroring the configs
- docs/balancing/process.md - pass order, tuning discipline learned in
arena rounds 1-5, tools, next-round
checklist
- docs/balancing/history.md - chronological record: decisions, numbers
pass, calculator bugs, arena rounds,
pacing pass
- docs/balancing/README.md - index, status, open action items (moved
from progression_design.md)
content_design.md slims back down to actual content: footprint gating,
hull grids, gating matrix (module names updated to railguns), and the
production tree structure with its fiction - numbers, anchors, arena
logs, and pacing all moved to docs/balancing/. Stale references in the
config comments updated to the new locations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
5.0 KiB
5.0 KiB
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.
- 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. - 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. - 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 bytools/threat_report.py. - Calculator/tooling parity — the game's
ThreatCostCalculatorandtools/threat_report.pymust produce identical values; the Python tool is the design reference. Any semantic change to REQ-THREAT-* needs both updated plus tests. - 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. - Pacing — unlock ladder,
unlock_requiresedges, 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.mdinstead 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 (
balancingtarget) — parallel arena simulation ofbin/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
- Pull; run
verify_recipes.py,verify_layouts.py,threat_report.py; compare against the tables inderived.md. - If recipes/materials changed: re-check fitted threats vs. the ladder
in
targets.md; update arena suite ship counts if fitted values moved. - Run the arena suite; read EHP margins and durations against the
expectations noted in
balancing.tomland the anchors. - Apply changes per the tuning discipline (two-round signals only);
record the round and its knob changes in
history.md. - Update
derived.mdwhere values moved; if an anchor moved, updatetargets.mdand state why. - Commit and push (the review workflow reads the remote).