Restructure balancing docs into docs/balancing/
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
This commit is contained in:
56
docs/balancing/README.md
Normal file
56
docs/balancing/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Balancing Documentation
|
||||
|
||||
Everything about balancing Dota Factory, separated by role:
|
||||
|
||||
- **[rules.md](rules.md)** — the design rules and principles. Timeless;
|
||||
changes only when the design changes.
|
||||
- **[targets.md](targets.md)** — the base numbers (roots/anchors) chosen
|
||||
by design. Change these first; everything else re-derives.
|
||||
- **[derived.md](derived.md)** — the current tuned state of all derived
|
||||
numbers, mirroring the configs. Updated whenever configs change.
|
||||
- **[process.md](process.md)** — how balancing is done: the pass order,
|
||||
tuning discipline, tools, and the checklist for the next round.
|
||||
- **[history.md](history.md)** — chronological record of decisions,
|
||||
findings, bugs, and arena rounds.
|
||||
|
||||
Related: game content (hull grids, footprint gating, tree design and
|
||||
fiction) in [../content_design.md](../content_design.md); rules with
|
||||
REQ-* ids in [../requirements.md](../requirements.md).
|
||||
|
||||
## Status
|
||||
|
||||
First full balancing round complete (2026-07-06): targets → tree →
|
||||
numbers → threat-calculator parity → combat stats (arena-converged) →
|
||||
pacing. Next step: full-game playtests against the run-shape targets in
|
||||
`targets.md`.
|
||||
|
||||
## Open action items
|
||||
|
||||
Agreed changes that require edits to `requirements.md`, the code, or the
|
||||
configs. Completed items are removed (their outcomes live in
|
||||
`requirements.md`, `history.md`, and the git history).
|
||||
|
||||
1. **Fill unfillable schematic slots with artifacts.** With duplicates
|
||||
removed, the schematic drop pool can run dry — previously unreachable.
|
||||
Decision: every slot in the choice dialog that cannot be filled with a
|
||||
schematic because the eligible pool is exhausted is filled with an
|
||||
artifact option instead (in addition to any artifact option granted by
|
||||
the regular artifact roll). A push therefore always awards a full
|
||||
dialog. Update REQ-DEF-SCHEMATIC-DROP.
|
||||
2. **Confirm wave scaling in playtests.** `threat_rate_formula` is the
|
||||
only time-scaling axis; verify the tuned curve (see `derived.md`)
|
||||
produces the intended difficulty race in real runs.
|
||||
3. **Gate shortcut-recipe drops on their inputs.** Extend the assembler
|
||||
recipe schematic pool eligibility in REQ-DEF-SCHEMATIC-DROP: in
|
||||
addition to the existing station-level and output-item checks, all of
|
||||
the recipe's input item types must be implicitly unlocked as well.
|
||||
4. **Resource deposits.** Add a terrain deposit layer per the Resource
|
||||
deposits rules (`rules.md`): deposit patches generated in expansion
|
||||
columns (deterministic content per expansion, randomized placement
|
||||
within the new columns), deposit rendering, and a miner condition (a
|
||||
resource recipe is selectable only if the miner's footprint overlaps
|
||||
at least one matching deposit tile). Touches REQ-BLD-MINER ("every
|
||||
asteroid tile is equivalent" no longer holds),
|
||||
REQ-GW-ASTEROID-EXPAND / REQ-EXP-*, `world.toml`, and `visuals.toml`.
|
||||
Until this lands, quartz mines anywhere and the mid-game is
|
||||
knowledge-gated only.
|
||||
164
docs/balancing/derived.md
Normal file
164
docs/balancing/derived.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Derived Values (current tuned state)
|
||||
|
||||
Everything here is derived from `targets.md` under the rules in
|
||||
`rules.md`, and mirrors the config files. Item threats, ship threats,
|
||||
ratios, and belt checks are verified by `tools/threat_report.py` — re-run
|
||||
it after any recipe or material change and update this file when values
|
||||
move. Combat stats were tuned empirically against the arena suite in
|
||||
`bin/balancing/data/balancing.toml` (round-by-round record in
|
||||
`history.md`).
|
||||
|
||||
## Economy constants
|
||||
|
||||
- `scrap_per_threat = 0.25` — 1 scrap per 4 threat destroyed (a cruiser
|
||||
kill drops ~59 scrap); threat(scrap) = 4.
|
||||
- Scrap smelting: 1 scrap → 1 iron_ingot, 1 s — deliberately
|
||||
value-losing; reprocessing is the value-preserving path.
|
||||
- Reprocessing: 4 scrap per cycle, 4 s; full-pool weights iron_ingot 30 /
|
||||
copper_ingot 30 / silicon 20 / voidsteel 20 → threat(voidsteel)
|
||||
= (4·4 + 4)/0.2 = 100.
|
||||
- `scrap_despawn_seconds = 120` (a capital kill drops hundreds of scrap,
|
||||
collected one per salvage cycle).
|
||||
|
||||
## Recipes and item threats
|
||||
|
||||
(dur in seconds; threat is per output unit)
|
||||
|
||||
| item | recipe | dur | out | threat |
|
||||
|---|---|---|---|---|
|
||||
| iron_ore / copper_ore | miner | 1 | 1 | 1 |
|
||||
| quartz | miner (deposit) | 2 | 1 | 2 |
|
||||
| iron_ingot | 1 iron_ore | 1 | 1 | 2 |
|
||||
| copper_ingot | 1 copper_ore | 1 | 1 | 2 |
|
||||
| silicon | 1 quartz | 2 | 1 | 4 |
|
||||
| steel_plate | 2 iron_ingot | 3 | 1 | 7 |
|
||||
| copper_wire | 1 copper_ingot | 1 | 2 | 1.5 |
|
||||
| copper_coil | 2 copper_wire | 1.5 | 1 | 4.5 |
|
||||
| building_block | 2 steel_plate | 2 | 4 | 4 |
|
||||
| control_chip | 1 silicon + 2 copper_wire | 5 | 1 | 12 |
|
||||
| capacitor_bank | 2 copper_coil + 1 silicon | 5 | 1 | 18 |
|
||||
| hardened_steel | 3 steel_plate | 12 | 1 | 33 |
|
||||
| ceramic_plate | 2 quartz | 4 | 1 | 8 |
|
||||
| drive_unit | 2 steel_plate + 2 copper_coil + 1 control_chip | 8 | 1 | 43 |
|
||||
| voidsteel_plate | 1 voidsteel + 1 hardened_steel | 8 | 1 | 141 |
|
||||
| capital_core | 2 voidsteel + 1 capacitor_bank + 1 control_chip | 10 | 1 | 240 |
|
||||
|
||||
Shortcut recipes (drop-only; item threat stays defined by the base path
|
||||
via the max rule): `shortcut_steel_plate` 3 iron_ore → 1 plate (2 s,
|
||||
level 1), `shortcut_control_chip` 2 quartz → 1 chip (4 s, level 2),
|
||||
`shortcut_hardened_steel` 4 iron_ingot → 1 hardened (8 s, level 2).
|
||||
|
||||
Ratio curve realized: t1 all 1:1 (miner:smelter); t2 clean 2:3
|
||||
(ingot→plate, wire→coil); t3 strange — 2:5 (silicon→chip), 3:5
|
||||
(coil→capacitor), 3:4 (plate→hardened, plate→drive); t4 inverted 3:2
|
||||
(hardened→voidsteel_plate). Belt check: worst input demand 1.33 items/s,
|
||||
under the ~2/s single-belt cap everywhere.
|
||||
|
||||
## Module prefabs
|
||||
|
||||
(contribution = item threat + module production time)
|
||||
|
||||
| module | recipe | dur | mod. time | contribution |
|
||||
|---|---|---|---|---|
|
||||
| railgun_s | 1 copper_coil | 1 | 1 | 6.5 |
|
||||
| salvager | 1 steel_plate + 2 copper_wire | 2 | 1 | 13 |
|
||||
| repair_tool | 1 steel_plate + 2 copper_wire | 2 | 1 | 13 |
|
||||
| armor_plates | 4 steel_plate | 3 | 1 | 32 |
|
||||
| maneuvering_thrusters | 1 steel_plate + 1 copper_coil | 2 | 1 | 14.5 |
|
||||
| sensor_booster | 2 copper_wire + 1 copper_coil | 2 | 1 | 10.5 |
|
||||
| afterburner | 2 copper_coil + 1 steel_plate | 3 | 1 | 20 |
|
||||
| weapon_stabilizer | 1 steel_plate + 1 copper_coil | 2 | 1 | 14.5 |
|
||||
| weapon_primer | 1 capacitor_bank + 1 copper_coil | 4 | 2 | 28.5 |
|
||||
| weapon_upgrade | 1 control_chip + 1 copper_coil | 4 | 2 | 22.5 |
|
||||
| railgun_m | 1 capacitor_bank + 2 steel_plate + 1 copper_coil | 4 | 3 | 43.5 |
|
||||
| drone_bay | 1 control_chip + 2 steel_plate + 1 copper_coil | 4 | 3 | 37.5 |
|
||||
| railgun_l | 1 capacitor_bank + 2 hardened_steel + 1 ceramic_plate | 6 | 4 | 102 |
|
||||
| drone_hangar | 1 voidsteel_plate + 2 control_chip + 1 drive_unit | 10 | 6 | 224 |
|
||||
|
||||
## Ships
|
||||
|
||||
(fitted = hull item + ship base time + default loadout; the default
|
||||
loadouts are the `default_modules` used by enemy waves and are
|
||||
geometry-validated against the hull grids)
|
||||
|
||||
| ship | hull recipe | dur | base | default loadout | fitted |
|
||||
|---|---|---|---|---|---|
|
||||
| drone | 1 iron_ingot | 1 | 1 | railgun_s | 10.5 |
|
||||
| frigate | 2 steel_plate + 1 copper_wire | 2 | 2 | 2× railgun_s, maneuvering_thrusters | 47 |
|
||||
| destroyer | 3 steel_plate + 2 copper_coil | 4 | 3 | 3× railgun_s, armor_plates, sensor_booster | 99 |
|
||||
| cruiser | 2 hardened_steel + 2 control_chip | 6 | 4 | 2× railgun_m, armor_plates, maneuvering_thrusters | 233.5 |
|
||||
| battlecruiser | 3 hardened_steel + 2 control_chip + 1 drive_unit | 8 | 5 | 3× railgun_m, armor_plates, 2× railgun_s | 354.5 |
|
||||
| battleship | 3 voidsteel_plate + 1 drive_unit + 2 control_chip | 10 | 6 | railgun_l, 2× railgun_m, weapon_stabilizer, 2× railgun_s | 722.5 |
|
||||
| dreadnought | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | 3× railgun_l, 4× armor_plates, railgun_s | 1491.5 |
|
||||
| carrier | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | drone_hangar, 2× railgun_m, 2× armor_plates, sensor_booster | 1436.5 |
|
||||
|
||||
## Combat stats
|
||||
|
||||
(arena-converged, 2026-07; see `history.md` rounds 1–5)
|
||||
|
||||
**Weapons:** railgun_s 2 dmg × 2.0 Hz (4.0 DPS), range 50 m;
|
||||
railgun_m 14 × 1.5 (21), range 70; railgun_l 52 × 0.8 (41.6), range 100.
|
||||
|
||||
**Hull HP** (15/threat prior + empirical trims): drone 60, frigate 300,
|
||||
destroyer 550, cruiser 1500, battlecruiser 2400, battleship 6300,
|
||||
dreadnought/carrier 24000.
|
||||
|
||||
**Mobility ladder** (speed m/s | main accel | maneuvering | angular |
|
||||
max rot): drone 45|60|30|12|6, frigate 35|45|22|8|4,
|
||||
destroyer 30|35|18|6|3, cruiser 24|25|12|4|2, battlecruiser 20|20|10|3|1.5,
|
||||
battleship 15|14|7|2|1, dreadnought/carrier 10|8|4|1|0.5.
|
||||
Sensors: 150/200/220/250/260/280/300/350 m.
|
||||
|
||||
**Other modules:** armor_plates +1200 HP; repair_tool 9 HP × 1 Hz,
|
||||
range 80; salvager range 60, cargo 20, 0.5 collections/s; afterburner
|
||||
×1.6 speed +60 accel; maneuvering_thrusters ×1.2 speed +10 maneuvering;
|
||||
sensor_booster +50 m; weapon_upgrade ×1.2 damage; weapon_primer ×1.2
|
||||
rate; weapon_stabilizer ×1.3 range ×0.8 rate.
|
||||
|
||||
**Stations:** HQ 5000 HP. Player station 3000 HP, 25 dmg × 1 Hz,
|
||||
range 120, scrap 40. Enemy station: 3000+1500x HP, 25+12x dmg,
|
||||
1.0+0.1x Hz, range 120, scrap 40+30x (x = push level).
|
||||
|
||||
## Pacing
|
||||
|
||||
**Unlock ladder** (level → unlocks; ← marks `unlock_requires`; starting
|
||||
set at −1: drone, frigate, railgun_s, salvager, building_block recipe):
|
||||
|
||||
| level | ships | modules | recipes |
|
||||
|---|---|---|---|
|
||||
| 0 | destroyer | repair_tool, armor_plates | |
|
||||
| 1 | | maneuvering_thrusters, sensor_booster | shortcut_steel_plate |
|
||||
| 2 | cruiser | railgun_m, afterburner | shortcut_control_chip, shortcut_hardened_steel |
|
||||
| 3 | | weapon_stabilizer | |
|
||||
| 4 | battlecruiser ← cruiser | weapon_primer, weapon_upgrade | |
|
||||
| 5 | | drone_bay | |
|
||||
| 6 | battleship ← battlecruiser | railgun_l ← railgun_m | |
|
||||
| 8 | dreadnought ← battleship | | |
|
||||
| 9 | carrier ← battleship | drone_hangar | |
|
||||
|
||||
Level 0's pool has exactly three entries (a full first dialog). Level 2
|
||||
is the quartz gate: cruiser and railgun_m are the first schematics whose
|
||||
chains reach quartz; the shortcut outputs only become implicitly
|
||||
unlocked alongside them, so shortcuts cannot drop early.
|
||||
|
||||
**Threat rate** `2*x + 0.15*x*x` (x = boss cycle counter), against the
|
||||
factory-size curve with ~half the player's output assumed military:
|
||||
|
||||
| cycle x | rate (threat/s) | player military (≈ curve/2) |
|
||||
|---|---|---|
|
||||
| 2 | 4.6 | ~12 |
|
||||
| 6 | 17.4 | ~30 |
|
||||
| 15 | 63.8 | ~60 |
|
||||
| 20 | 100 | ~75 |
|
||||
| 24 | 134 | — |
|
||||
|
||||
**Economy:** `starting_building_blocks = 200`; expansion cost formula
|
||||
`300 + 50*x + 10*x*x` (x = expansions already purchased: ~1 affordable
|
||||
per cycle mid-game at ~1/3 of block income, stretching to 2–3 cycles
|
||||
late — quadratic so costs outrun the roughly linear block income
|
||||
gradually, never with a hard wall); `artifact_win_count = 5` with
|
||||
`artifact_chance_formula = 0.05*x`. Building costs: belt 2, splitter 3,
|
||||
tunnels 5, miner 15, smelter 20, assembler 35, reprocessing plant 40,
|
||||
salvage bay 25, shipyard 60 — averaging ≈18 blocks per placed building
|
||||
(belts included), which meets the 4-minute doubling target at block
|
||||
threat 4.
|
||||
96
docs/balancing/history.md
Normal file
96
docs/balancing/history.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Balancing History
|
||||
|
||||
Chronological record of the balancing work: what was decided, what was
|
||||
found, what changed. Current values live in `derived.md`; this file
|
||||
explains how they got there.
|
||||
|
||||
## 2026-07-02/03 — rules and structural decisions
|
||||
|
||||
- Rules document written (now `rules.md`): ratio curve, shortcut
|
||||
recipes, refactorability, cost archetypes, threat model, growth curve.
|
||||
- Scrap derived from threat (`scrap_per_threat`), replacing authored
|
||||
per-ship scrap drops; scrap threat became the constant
|
||||
`1/scrap_per_threat`, removing the old min-scrap_drop derivation and
|
||||
its circularity.
|
||||
- Duplicate schematic drops removed (no level-ups); ship/module levels
|
||||
removed entirely — all time scaling lives in the threat rate, push
|
||||
scaling stays on stations. Mk2 upgrade recipes noted as the future
|
||||
per-item progression.
|
||||
- Growth-curve rules added: escalating expansion costs, designed
|
||||
doubling time, growth limited by economy not waiting; resource
|
||||
deposits designed (deposit-gated mid resource in expansion territory).
|
||||
- Production tree v2 decided: iron/copper everywhere (M-type asteroid),
|
||||
quartz in geodes (mid), voidsteel battle-forged from scrap (late);
|
||||
titanium dropped; lasers renamed to railguns, lasers reserved as a
|
||||
future weapon type.
|
||||
|
||||
## 2026-07-03 — targets, tree, numbers
|
||||
|
||||
- Balancing targets fixed: ≤2 h run, phases 1–5/6–14/15+, factory curve
|
||||
25/60/120/150, threat ladder, 25-ship swarm, block roots.
|
||||
- Tree structure drafted and numbers computed (recursive threat
|
||||
calculator); ratio curve realized; fitted ships within 96–124% of the
|
||||
strawman ladder (small end hot from fixed chain overhead — ladder
|
||||
later adopted the achieved values).
|
||||
- **Rule bugs found by the numbers work:** the scrap→ingot smelter
|
||||
recipe would inflate basic materials via the max rule (fixed:
|
||||
scrap-consuming recipes are threat fallback only); recipe output
|
||||
amounts were ignored (fixed: per-unit division); items downstream of
|
||||
reprocessing-only items never resolved (fixed: fixpoint resolution);
|
||||
a shortcut recipe resolving earlier than the base path silently
|
||||
underpriced items (fixed: commit only when all eligible recipes are
|
||||
computable). All four fixed in `ThreatCostCalculator` with tests, and
|
||||
implemented in `tools/threat_report.py`.
|
||||
- v2 tree written into the configs; `default_modules` loadouts
|
||||
geometry-validated (the numbers-pass loadouts for battlecruiser and
|
||||
dreadnought were geometrically impossible — L-modifiers don't fit
|
||||
beside full gun complements; corrected loadouts landed closer to the
|
||||
ladder).
|
||||
|
||||
## 2026-07-04 — combat stats, arena rounds 1–5
|
||||
|
||||
Initial stats derived from the anchors (weapon DPS ≈0.6/threat flat,
|
||||
hull 15 HP/threat, armor 20/threat, repair 2 HP/s/threat, station
|
||||
range 200).
|
||||
|
||||
- **Round 1:** concentrated fleets won all equal-threat cross-tier
|
||||
matchups flawlessly; glass beat armored; repair escort flawless; two
|
||||
stations shrugged off a 3× swarm. Changes: concentration tax on m/l
|
||||
gun damage (railgun_m 17→14, railgun_l 70→52), armor 640→1000,
|
||||
repair 25→12, station range 200→120. (Team-1 "bias" in mirrors later
|
||||
shown to be noise.)
|
||||
- **Round 2 (EHP-margin logging added):** battleship +33% while
|
||||
dreadnought −37% (stabilizer range + opposing armor); glass still
|
||||
+11%. Changes: stabilizer range ×1.5→×1.3; per-hull trims introduced
|
||||
(BC 2700→2500, BS 7500→7000, DN/CV 15500→19000).
|
||||
- **Round 3 (narrow lanes — geometry fixed into the fixture):**
|
||||
DN closed to −11%, BS +22%, swarm flipped to +14% over cruisers,
|
||||
glass +12% third time. Changes: armor 1000→1200, BC 2500→2000,
|
||||
BS 7000→6300, DN/CV 19000→22500.
|
||||
- **Round 4:** glass-vs-armored resolved (+3% armored); noise floor
|
||||
established (~±10%/run: BS ignored a −10% EHP cut; repair drifted
|
||||
14→24% untouched). Convergence policy adopted: two-round signals only,
|
||||
±20% converged. Changes: BC 2000→2200, DN/CV 22500→24000; BS +23%
|
||||
accepted as doctrine texture (mechanical range edge vs. pure small
|
||||
fleets).
|
||||
- **Round 5 (durations logged; end-condition bug fixed upstream):**
|
||||
TTK anchor validated (mirrors 23/71/95 s; DN-vs-swarm 214 s outlier
|
||||
accepted); dreadnought +3%, everything else inside band. Final
|
||||
changes: BC 2200→2400, repair 12→9 (persistent +24% escort margin).
|
||||
**Combat pass declared converged.**
|
||||
|
||||
## 2026-07-05/06 — pacing pass
|
||||
|
||||
- Unlock ladder set (starting set drone/frigate/railgun_s/salvager;
|
||||
quartz gate at level 2; capitals at 8–9 with `unlock_requires`
|
||||
chains); threat rate `2*x + 0.15*x*x`; starting blocks 1000→200;
|
||||
expansion 400 flat pending the cost formula; artifacts 3→5.
|
||||
- **Bug found:** the building_block recipe was silently locked at game
|
||||
start (building blocks appear in no schematic's materials, so implicit
|
||||
unlocking could never reach the recipe) — fixed with an explicit
|
||||
`unlock_at_station_level = -1`.
|
||||
- Expansion cost formula implemented and set (`300 + 50*x + 10*x*x`):
|
||||
quadratic, so costs outrun the roughly linear block income gradually
|
||||
— ~1 expansion per cycle mid-game, 2–3 cycles apart late.
|
||||
- **First full balancing round complete.** Next: full-game playtests
|
||||
against the run-shape targets.
|
||||
98
docs/balancing/process.md
Normal file
98
docs/balancing/process.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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).
|
||||
333
docs/balancing/rules.md
Normal file
333
docs/balancing/rules.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# Balancing & Progression Rules
|
||||
|
||||
Rules and principles that govern the production tree, progression pacing,
|
||||
and balancing. This document contains **rules only** — the chosen base
|
||||
numbers live in `targets.md`, everything derived from them in
|
||||
`derived.md`, and the concrete content in the config files and
|
||||
`../content_design.md`. All of those must follow the rules stated here.
|
||||
|
||||
## Player-experience goals
|
||||
|
||||
What each phase of a run should feel like:
|
||||
|
||||
- **Early:** learning belts and ratios with forgiving chains. The building
|
||||
block economy is the main constraint; the player bootstraps a
|
||||
self-sustaining factory from the starting stock.
|
||||
- **Mid:** deeper chains, the first real ratio puzzles, and the first
|
||||
meaningful drop decisions (which schematic, when to push).
|
||||
- **Late:** combat feeds the factory — capital production requires salvage.
|
||||
Progress means extending and refactoring the existing factory, not
|
||||
rebuilding it. Strange ratios are deliberate optimization puzzles.
|
||||
|
||||
Overarching: an experienced player gains efficiency through **knowledge** —
|
||||
layout foresight, understanding chains, exploiting shortcut recipes — never
|
||||
through hidden mechanics. An inexperienced setup should not cost much more
|
||||
than an experienced one; experience pays off in how easily the factory
|
||||
adapts later (see Refactorability).
|
||||
|
||||
## Resource phases
|
||||
|
||||
- A run has exactly **four base inputs**:
|
||||
1. Two mined resources available from the start, minable on **every**
|
||||
asteroid tile.
|
||||
2. A third mined resource unlocked mid-game, minable **only on deposit
|
||||
patches** found in expansion territory (see Resource deposits).
|
||||
3. A fourth input unlocked late-game, obtainable **only** from
|
||||
reprocessing salvaged scrap.
|
||||
- The fourth input is the core loop hook: capital ship production requires
|
||||
fighting (salvaging and reprocessing), not just mining.
|
||||
- Every gating has a fictional reason (concrete fiction in
|
||||
`../content_design.md`): the asteroid is a metal-rich body, so its bulk
|
||||
rock is minable anywhere; the mid resource sits in rare pockets; the
|
||||
late input is battle-forged — created only in the violence of ship
|
||||
destruction, which is why any wreck (including the player's own)
|
||||
yields it and no foundry can make it.
|
||||
- The mid resource is **dual-gated**: schematics (knowledge, via drops)
|
||||
and territory (deposits, via expansions). Tuning must guarantee the
|
||||
deposit-bearing expansion is comfortably affordable by the time the
|
||||
first mid-tier schematics drop, or those drops are dead picks.
|
||||
- There is no direct "resource unlock" mechanism. Miner recipes unlock
|
||||
**implicitly** (REQ-LOCK-IMPLICIT) when some unlocked schematic's material
|
||||
chain reaches that resource. Resource pacing is therefore controlled
|
||||
through the `unlock_at_station_level` values of ships, modules, and
|
||||
assembler recipe schematics — and the content must guarantee that the
|
||||
chains actually connect (a mid-game schematic must require an item whose
|
||||
chain reaches the mid resource, or it never unlocks).
|
||||
|
||||
### Resource deposits
|
||||
|
||||
- **Rule: freedom first, geography later.** The starting resources are
|
||||
minable everywhere, so the player has full layout freedom while
|
||||
learning. Later mined resources are bound to deposit patches — fixed
|
||||
geography as a layout puzzle, introduced once the player is competent.
|
||||
- **Rule: deposits exist only in expansion territory.** Expansions buy
|
||||
space *and* access to resource tiers — the second leg of the growth
|
||||
curve (see Building block economy).
|
||||
- **Rule: patch area is the throughput cap.** Deposits never deplete but
|
||||
are finite in area; the number of deposit tiles caps how many miners
|
||||
the chain supports. Buying deeper expansions raises the throughput
|
||||
ceiling of high-tier chains.
|
||||
- **Rule: no empty expansions.** Deposit content per expansion is
|
||||
deterministic and config-defined; only the placement within the new
|
||||
columns is randomized. Buying an expansion never rolls "nothing".
|
||||
- **Rule: mining is binary.** A miner whose footprint overlaps at least
|
||||
one deposit tile of a resource can select that resource's recipe; no
|
||||
partial-coverage rate scaling.
|
||||
- Deposits arrive at the periphery (expansions add columns on the left),
|
||||
so each new chain starts in fresh space — supporting the
|
||||
refactorability property — and high-tier chains have the longest belt
|
||||
runs to the shipyards, escalating the logistics puzzle with tier.
|
||||
|
||||
## Production tree rules
|
||||
|
||||
### Structure
|
||||
|
||||
- **Each phase transition adds exactly one new base input chain.** A base
|
||||
input is a bottom-level resource entering the factory from outside — a
|
||||
mined resource or the scrap-only input. The early game starts with two
|
||||
ores as the baseline; the transition to mid adds one (the deposit-bound
|
||||
mid resource), the transition to late adds one (the scrap-only input).
|
||||
No transition ever introduces more than one unfamiliar bottom-level
|
||||
chain, so the factory grows in one direction at a time.
|
||||
- **Intermediates are generic shared parts.** Keep the item count low —
|
||||
modules and hulls of a tier draw from a shared pool of that tier's and
|
||||
lower tiers' intermediates rather than each having bespoke inputs.
|
||||
- **Thematic naming over thematic items.** Inputs should be plausible for
|
||||
what the recipe produces (crystals for lasers, heat sinks for bigger
|
||||
lasers). Achieve this through naming and chain membership, not by adding
|
||||
item types: rename a generic part, don't add a parallel one.
|
||||
|
||||
### Ratios
|
||||
|
||||
- **Ratio "niceness" degrades with tier.** The producer:consumer ratios
|
||||
needed for 100% throughput follow a curve:
|
||||
- Tier 1 (ore → basic material): trivially nice (e.g. 1:1 or 1:2
|
||||
miner:smelter).
|
||||
- Tier 2: slightly complex but still clean (e.g. 2:3).
|
||||
- Higher tiers: increasingly strange ratios, as deliberate optimization
|
||||
puzzles.
|
||||
- Exceptions in both directions are allowed when there is a reason — a
|
||||
clean late chain as a breather, an odd early chain as a teaser — but the
|
||||
curve is the default.
|
||||
|
||||
### Shortcut recipes
|
||||
|
||||
- Some strange chains get a **shortcut recipe**: an explicitly unlockable
|
||||
assembler recipe schematic (`unlock_at_station_level ≥ 0`, drop-only per
|
||||
REQ-LOCK-EXPLICIT) that skips a step (e.g. t1 → t3 directly) and yields
|
||||
nice ratios for a chain whose base path is strange.
|
||||
- **Not every strange chain gets a shortcut.** Some strangeness is
|
||||
permanent; the absence of a fix is a valid design choice.
|
||||
- **Shortcuts drop only for known chains.** A shortcut recipe enters the
|
||||
drop pool only when both its input items and its output item are
|
||||
already unlocked (in addition to the station level check). The player
|
||||
is never offered a shortcut for a chain they have not built yet. The
|
||||
output-item half of this check already exists in
|
||||
REQ-DEF-SCHEMATIC-DROP; the input half is an open action item (see
|
||||
`README.md`).
|
||||
- **Shortcuts are pure rewards, never balance factors.** An item's threat
|
||||
value is the *maximum* across its producing recipes (REQ-THREAT-ITEM), so
|
||||
unlocking a cheaper recipe does not lower the item's threat accounting —
|
||||
the player gains real factory efficiency without their ships being
|
||||
valued cheaper and without enemy wave budgets shifting. Consequently:
|
||||
**balance every chain around its base (expensive) path**; the shortcut's
|
||||
savings define the size of the reward.
|
||||
|
||||
### Refactorability
|
||||
|
||||
- **Rule (the property):** unlocking the next tier or size of a thing must
|
||||
be a *local edit* of the existing production line — adding assemblers
|
||||
and belts, or replacing a machine or two in place — never a rebuild of
|
||||
the line.
|
||||
- **What this buys the player:** foresight pays off in space, not blocks.
|
||||
An experienced player leaves a little slack in the middle of a line,
|
||||
knowing the next size or tier upgrade means tearing out one assembler
|
||||
and a few belts there and inserting the new step — plus maybe swapping
|
||||
a recipe or two elsewhere — while the rest of the line keeps running
|
||||
untouched.
|
||||
- **Default technique:** the bigger version introduces one new intermediate
|
||||
that is produced from a subset of the smaller version's inputs (possibly
|
||||
plus one additional low-tier material), and otherwise reuses the smaller
|
||||
version's inputs. Existing lines keep running and feed the new
|
||||
intermediate's assemblers.
|
||||
- The property is the rule; the technique is only the default. It may be
|
||||
broken where it fights thematic plausibility, as long as the property
|
||||
still holds.
|
||||
|
||||
## Cost archetypes
|
||||
|
||||
Every item has two cost knobs: **material quantity** and **cycle time**.
|
||||
Both feed the threat value identically (threat = recursive
|
||||
production-seconds, REQ-MOD-THREAT), so the split between them does not
|
||||
change what an item is *worth* — it changes what kind of **factory
|
||||
pressure** it creates:
|
||||
|
||||
- **Material-heavy, fast** (e.g. armor plates): simple items; stress belt
|
||||
throughput, splitter logistics, and miner/smelter counts.
|
||||
- **Time-heavy, lean** (e.g. shield modules): technically complex items;
|
||||
few inputs — possibly higher-tier ones — but long cycles; stress
|
||||
assembler counts and parallelization.
|
||||
|
||||
**Rule:** each module family commits to a clear archetype, so factories
|
||||
supporting different fleet doctrines feel structurally different to build.
|
||||
|
||||
## Threat model (balancing backbone)
|
||||
|
||||
- Threat cost = total recursive production-seconds (REQ-MOD-THREAT). One
|
||||
factory-second equals one threat; player output and enemy wave budgets
|
||||
are denominated in the same currency.
|
||||
- **Rule: combat power per threat is roughly constant** across all ships,
|
||||
modules, and tiers. Higher tiers are better per *ship* and per *module
|
||||
slot*, not per invested factory-second — their advantage is
|
||||
concentration (fewer, bigger things; slot geometry per
|
||||
`../content_design.md`) and qualitative capabilities, not a better
|
||||
exchange rate. Deviations from this rule are deliberate and documented.
|
||||
- **Difficulty race:** the enemy threat rate (`threat_rate_formula`) is
|
||||
tuned against the factory output (threat/s) achievable by a competent
|
||||
player — slightly below it early, crossing above it eventually. The game
|
||||
is endless; enemy scaling must ultimately outpace any factory, and
|
||||
player skill shifts *when*, not *whether*.
|
||||
- **All time scaling lives in the threat rate** — waves get bigger, ships
|
||||
of a given schematic never get individually stronger. There is no ship
|
||||
level dimension: stat formulas are plain values, and per-ship level
|
||||
scaling does not exist. Push scaling on enemy defence stations is the
|
||||
separate, player-triggered difficulty axis and keeps its level formulas.
|
||||
|
||||
## Unlock & drop pacing
|
||||
|
||||
- **Starting set rule:** the schematics unlocked at game start
|
||||
(`unlock_at_station_level = -1`) must be exactly enough to reach the
|
||||
first push unaided — a functioning block loop, small hulls, a basic
|
||||
weapon, and the salvage loop. Nothing more.
|
||||
- The `unlock_at_station_level` ladder mirrors the resource phases:
|
||||
mid-tier hulls/modules/recipes at low station levels, capital content at
|
||||
higher levels. A schematic must not become available before the chains
|
||||
its materials need can be unlocked alongside it.
|
||||
- **Schematics can require other schematics.** Beyond the station-level
|
||||
gate, a schematic (ship, module, or assembler recipe) may list
|
||||
prerequisite schematics (`unlock_requires`, REQ-LOCK-PREREQ) that must
|
||||
already be unlocked before it enters the drop pool — e.g. the medium
|
||||
gun requires the small gun; a future Mk2 requires its base version.
|
||||
Station level gates the earliest *when*; prerequisites gate the
|
||||
*order*, keeping drop offers coherent with what the player already
|
||||
owns.
|
||||
- **No duplicate drops.** Ship and module schematics leave the drop pool
|
||||
once owned, exactly as assembler recipe schematics already do. There are
|
||||
no schematic level-ups; player power grows through unlock breadth and
|
||||
factory scale only, which keeps power-per-threat exact on both sides.
|
||||
The pool therefore shrinks over a run and late pushes increasingly offer
|
||||
artifacts — intended: the late game is a race for the win condition.
|
||||
Per-item progression may return later as Mk2 upgrade recipes (see Future
|
||||
work), never as free level-ups.
|
||||
- **Artifacts trade power for progress.** Artifact options compete with
|
||||
schematic picks in the same choice dialog; the artifact chance must be
|
||||
tuned so that taking one is a real decision (giving up an unlock), not
|
||||
automatic in either direction.
|
||||
|
||||
## Scrap & reprocessing economy
|
||||
|
||||
- Scrap is the bridge from combat back into the factory, with two sinks:
|
||||
**smelting** (same basic materials as ore — the safe, boring option) and
|
||||
**reprocessing** (probabilistic higher intermediates, including the
|
||||
late-game input — the gamble that eventually becomes mandatory).
|
||||
- The reprocessing output pool renormalizes over implicitly unlocked items
|
||||
(REQ-LOCK-REPROCESSING-POOL), so its output quality improves
|
||||
automatically as the run progresses. **Rule:** weights are authored for
|
||||
the *fully unlocked* pool state; early-game behavior falls out of
|
||||
renormalization for free and needs no separate staging.
|
||||
- **Rule: ship scrap drops are derived, never authored.** A destroyed ship
|
||||
drops `threat cost × scrap_per_threat` (a `world.toml` key), with the
|
||||
threat cost computed from its actual hull plus installed modules
|
||||
(REQ-MOD-THREAT) — a kitted-out ship drops more scrap than a bare hull
|
||||
automatically. `ships.toml` carries no scrap value. Defence stations are
|
||||
the exception: they keep authored `scrap_drop_formula`s, because pushing
|
||||
rewards are tuned independently of ship production costs.
|
||||
- Consequence: the threat value of scrap is the constant
|
||||
`1 / scrap_per_threat` (REQ-THREAT-SCRAP). The former min-`scrap_drop`
|
||||
schematic derivation and its potential circularity are gone.
|
||||
- **Rule:** the late-game input's income rate meaningfully gates capital
|
||||
production — unlocking a capital hull must not mean spamming it; the
|
||||
input trickles in slowly enough that every capital ship is a noticeable
|
||||
investment. The tuning target is relative, not absolute: assume a
|
||||
reference player who destroys and salvages roughly the threat the game
|
||||
spawns ("fighting at parity"), and tune `scrap_per_threat`, the
|
||||
reprocessing weights, and capital material costs so that this player
|
||||
affords roughly N capital ships per boss cycle. An absolute income rate
|
||||
would be meaningless (income depends entirely on how much the player
|
||||
fights) and would not self-scale; per boss cycle, the target tracks the
|
||||
threat rate as it steps up.
|
||||
|
||||
## Building block economy
|
||||
|
||||
- Building blocks are the only global currency and the early game's
|
||||
central constraint. The early game is a bootstrap problem: convert the
|
||||
starting stock into a self-sustaining block loop before the first waves
|
||||
bite.
|
||||
- **Rule:** the starting stock suffices for a minimal block loop plus the
|
||||
first shipyard — with a little slack for beginner mistakes, but not
|
||||
enough to skip the loop entirely.
|
||||
- **Rule: the growth curve lives here.** A saturated building produces
|
||||
exactly 1 threat/s, so the player's output curve *is* their
|
||||
building-count curve — shaping growth over a run means shaping the
|
||||
block and space economy, there is nowhere else it can live. Intended
|
||||
shape: exponential bootstrap (block-limited) → ramp
|
||||
(expansion-limited) → asymptotic squeeze as expansion costs outrun
|
||||
income, racing the enemy threat rate throughout.
|
||||
- **Rule: escalating expansion costs.** Expansion cost is a formula of
|
||||
the number of expansions already purchased, rising steeply enough that
|
||||
expansions eventually outrun any block income. The starting asteroid
|
||||
is deliberately small — filled within the first boss cycle or two, so
|
||||
the early exponential burst is a satisfying ramp, not a balance hole —
|
||||
and from then on the output curve is the expansion curve. Blocks keep
|
||||
a meaningful sink for the entire run, and "grow vs. army" stays a live
|
||||
decision at every moment.
|
||||
- **Rule: designed doubling time.** Block production is a positive
|
||||
feedback loop (blocks buy assemblers, assemblers make blocks); its
|
||||
time constant is a designed quantity, never an accident of quantity
|
||||
choice. The block chain's depth and the per-building costs are tuned
|
||||
against a stated target of the form: "a factory spending X% of its
|
||||
capacity on blocks doubles in ~T minutes."
|
||||
- **Rule: growth is limited by economy, never by waiting.** Construction
|
||||
times stay short; the serial build queue must not be used as a growth
|
||||
brake. Waiting for placed buildings to become operational — especially
|
||||
at the start of a run — is frustration, not gameplay. All growth
|
||||
limiting comes from block income and expansion pricing.
|
||||
- Note: block income has a structural ceiling — blocks enter the stock
|
||||
through the HQ's single belt port, so income is capped at belt
|
||||
throughput regardless of assembler count. Per-building costs should be
|
||||
high enough that this cap can bind late-game (see the condensed-block
|
||||
idea under Future work).
|
||||
|
||||
## Numeric guardrails
|
||||
|
||||
Constraints that every recipe must respect, independent of tuning:
|
||||
|
||||
- **Belt throughput:** belt speed and per-tile capacity cap how fast a
|
||||
single belt can feed an input. A recipe whose per-cycle inputs cannot be
|
||||
sustained by one belt per input at 100% duty cycle is a *deliberate*
|
||||
design (forcing parallel belts/splitters as part of a high-tier puzzle)
|
||||
— never an accident of quantity choice.
|
||||
- **Buffer burstiness:** input buffers hold 2× the per-cycle amount
|
||||
(REQ-MAT-INPUT-BUFFER), so large per-cycle quantities create bursty belt
|
||||
demand. Low tiers prefer small quantities with short cycles; big-batch
|
||||
recipes are reserved for high tiers where burstiness is part of the
|
||||
puzzle.
|
||||
- **Cycle times scale with tier** monotonically — a higher-tier item never
|
||||
has a shorter total chain time than a lower-tier item of the same role.
|
||||
|
||||
## Future work
|
||||
|
||||
- **Condensed building blocks** — a drop-unlockable shortcut-style
|
||||
recipe that packs several blocks' worth of value into one belt item,
|
||||
relieving the HQ intake ceiling (see Building block economy) as a
|
||||
late-game reward. The ceiling is the puzzle, the drop is the fix —
|
||||
same philosophy as shortcut recipes.
|
||||
- **Mk2 upgrade recipes** — the deferred design for per-item progression,
|
||||
to revisit once the config has stabilized. A duplicate-style drop
|
||||
unlocks a distinct `*_mk2` item whose recipe consumes the Mk1 item plus
|
||||
higher-tier parts. This preserves power-per-threat (the extra power is
|
||||
paid in real production-seconds, since threat is recursive), satisfies
|
||||
the refactorability rule (the Mk1 line keeps running and feeds one new
|
||||
assembler), and keeps balancing one-dimensional (no level variable
|
||||
anywhere). Enemy-side progression happens via `default_modules`
|
||||
variants per era instead of a level formula.
|
||||
104
docs/balancing/targets.md
Normal file
104
docs/balancing/targets.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# Balancing Targets (base numbers)
|
||||
|
||||
The root numbers of the balancing. Everything in `derived.md` is tuned to
|
||||
hit these; when rebalancing, **change these first and re-derive — never
|
||||
patch derived values directly**. The rules these numbers follow live in
|
||||
`rules.md`.
|
||||
|
||||
All time targets are in **game time**. The player can pause and
|
||||
accelerate, so real session length differs; playtests measure both. The
|
||||
time unit is the boss cycle (`world.toml boss_countdown_seconds`, 300 s).
|
||||
Destroying a station set advances the boss countdown by
|
||||
`boss_advance_seconds` (60 s), so cycles run shorter than nominal when
|
||||
pushing actively — targets deliberately ignore that.
|
||||
|
||||
## Run shape
|
||||
|
||||
1. **Run length** — a winning run takes up to 2 hours of game time: win
|
||||
around boss cycle 20–24. Losing runs end earlier.
|
||||
2. **Phase boundaries** — early = cycles 1–5 (iron/copper, small hulls),
|
||||
mid = cycles 6–14 (quartz, medium hulls), late = cycles 15+
|
||||
(voidsteel, capitals). Push cadence: first station set around cycle
|
||||
2–3, roughly one per cycle from mid onward — so the destroyed set's
|
||||
level ℓ is reached around cycle ℓ+2.
|
||||
3. **Factory size curve** — producing buildings over time; when
|
||||
saturated, output threat/s equals this count, so this curve IS the
|
||||
player power curve: ~25 when the starting asteroid is full (end of
|
||||
cycle 2), ~60 at the start of mid (cycle 6), ~120 at the start of
|
||||
late (cycle 15), ~150 near the win. `threat_rate_formula` must remain
|
||||
a fraction of this curve; buildings plus belts must physically fit
|
||||
the asteroid plus affordable expansions.
|
||||
4. **Threat-cost ladder** — total production-seconds per *fitted* hull
|
||||
(including the typical/default module loadout): drone 10.5,
|
||||
frigate 47, destroyer 99, cruiser 233.5, battlecruiser 354.5,
|
||||
battleship 722.5, dreadnought 1491.5, carrier 1436.5. Every
|
||||
production chain must sum to its ladder value. (The original strawman
|
||||
was 10/40/80/200/350/700/1500; the small end runs ~10–20% hot because
|
||||
fixed chain overhead dominates small hulls — accepted, and the
|
||||
achieved values adopted as the ladder. The ~×2-per-class curve shape
|
||||
is the invariant.)
|
||||
5. **Fleet size** — swarm-leaning: ~25 player combat ships as the
|
||||
standing mid-game fleet. Standing fleet = build cadence (4) × average
|
||||
ship lifetime, so this target drives time-to-kill and therefore all
|
||||
combat stat magnitudes.
|
||||
6. **Block economy roots** — bootstrap complete (starting asteroid full)
|
||||
by the end of cycle 2; a factory spending ~30% of its capacity on
|
||||
blocks doubles in ~4 minutes early game; one expansion affordable per
|
||||
cycle at ~1/3 of block income mid-game, decelerating to one per 2–3
|
||||
cycles late as escalating costs outrun income.
|
||||
|
||||
## Combat anchors
|
||||
|
||||
All combat stats derive from these; per-hull HP additionally carries
|
||||
empirical trims from arena rounds (values in `derived.md`).
|
||||
|
||||
- **Weapon DPS per threat pays a concentration tax that grows with gun
|
||||
size**: small ≈ 0.62, medium ≈ 0.48, large ≈ 0.41 DPS per threat of
|
||||
weapon contribution, compensated by the range ladder 50/70/100 m.
|
||||
Rationale: concentration itself (focus fire, no DPS loss to attrition,
|
||||
range) is worth paying for — with a flat curve, concentrated fleets
|
||||
win equal-threat fights outright (arena round 1).
|
||||
- **Hull HP = 15 per threat of hull contribution** as the prior; per-hull
|
||||
HP is the empirical trim knob (guns are shared across hulls, hull HP
|
||||
moves exactly one matchup). The arena consistently prices capitals as
|
||||
*tanks with taxed guns* — capital hulls sit well above the prior.
|
||||
- **Armor HP ≈ 37 per threat** — a strong premium over hull HP because
|
||||
armor is pure HP with no capability, and fights snowball: killing
|
||||
removes enemy DPS, surviving merely delays — HP must be cheaper than
|
||||
DPS.
|
||||
- **Repair ≈ 0.7 HP/s per threat** — in-combat sustain effectively
|
||||
removes enemy DPS and must be priced like DPS, not like HP.
|
||||
- **TTK / fight duration**: parity fights in the 30–60 s band at
|
||||
mid-game scale; capital mirrors ~90 s deliberately; the extreme
|
||||
tank-vs-chip-damage matchup (dreadnought vs destroyer swarm, ~3.5 min)
|
||||
is an accepted outlier.
|
||||
- **Mobility is monotone in size** — the smallest hulls are the fastest
|
||||
and nimblest. Sensor ranges (150→350 m) always exceed weapon ranges.
|
||||
- **Weapon modifiers are capital economy**: a ×1.2 damage modifier at
|
||||
~22.5 threat beats adding a gun once a ship carries more than ~68
|
||||
threat of weapons — modifiers pay off on gun-heavy big hulls, waste on
|
||||
small ones. Range modifiers are the strongest and are priced/kept
|
||||
small (×1.3): range is the dominant stat under the orbit AI (free
|
||||
approach fire).
|
||||
- **Stations**: a fresh player station holds one early parity wave
|
||||
unaided; the enemy station at level 0 matches the player station
|
||||
exactly and scales per push level. Station range is the dominance
|
||||
lever, not HP (at 4× a small gun's range, two stations annihilated a
|
||||
3× threat swarm through approach fire alone).
|
||||
- **Accepted imbalances**: the carrier loses its equal-threat fights
|
||||
until the drone-launching capability exists (the hangar is dead
|
||||
threat) — fix by implementing drones, not stats. A pure smallest-ship
|
||||
fleet modestly loses (~15–25%) to a range-fitted capital — desirable
|
||||
doctrine texture; the fair anti-capital answer is the mixed fleet.
|
||||
|
||||
## Pacing anchors
|
||||
|
||||
- **Starting set** is the rule-minimum: drone, frigate, small gun,
|
||||
salvager (plus the explicitly unlocked building-block recipe).
|
||||
- **Threat rate shape**: below the player's achievable military output
|
||||
(≈ half the factory curve) early, crossing at the late boundary
|
||||
(~cycle 15), overwhelming by ~cycle 24.
|
||||
- **Winning = five real decisions**: `artifact_win_count` is set so that
|
||||
across a winning run's ~15–18 pushes (~7 cumulative artifact offers at
|
||||
the current chance formula), the player must choose the artifact over
|
||||
a schematic about five times.
|
||||
Reference in New Issue
Block a user