Restructure balancing docs into docs/balancing/
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# recipes.toml
|
||||
#
|
||||
# Production tree v2 (see docs/content_design.md, "Production tree v2" and
|
||||
# "Numbers — first pass"). Quantities and durations are tuned so that every
|
||||
# Production tree v2 (structure in docs/content_design.md, numbers with
|
||||
# derivations in docs/balancing/derived.md). Quantities and durations are tuned so that every
|
||||
# fitted ship lands on the threat-cost ladder and the ratio curve is
|
||||
# realized: tier 1 ratios are 1:1, tier 2 ratios are 2:3, tier 3+ ratios
|
||||
# are deliberately strange.
|
||||
@@ -14,7 +14,7 @@
|
||||
# rock IS ore)
|
||||
# mid + quartz geode deposits in expansion territory
|
||||
# (deposit gating pending — see action
|
||||
# item 5 in docs/progression_design.md;
|
||||
# item 5 in docs/balancing/README.md;
|
||||
# until then quartz mines anywhere)
|
||||
# late + voidsteel battle-forged: ONLY from reprocessing
|
||||
# salvaged scrap, so capital production
|
||||
@@ -141,7 +141,7 @@ outputs = [{item = "copper_coil", amount = 1}]
|
||||
duration_seconds = 1.5
|
||||
|
||||
# Depth-3 chain (ore -> ingot -> plate -> block) is the factory's
|
||||
# doubling-time knob; see the block economy rules in progression_design.md.
|
||||
# doubling-time knob; see the block economy rules in docs/balancing/rules.md.
|
||||
# Explicitly unlocked at start (-1): building blocks appear in no
|
||||
# schematic's materials, so implicit unlocking can never reach this recipe.
|
||||
[[recipe]]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stations.toml
|
||||
#
|
||||
# Combat-pass anchors (see docs/content_design.md, "Combat stat anchors"):
|
||||
# Combat-pass anchors (see docs/balancing/targets.md, "Combat anchors"):
|
||||
# a fresh player defence station holds one early parity wave unaided; the
|
||||
# enemy station at level 0 matches the player station exactly and scales
|
||||
# with the push level x. Station scrap drops stay authored (pushing rewards
|
||||
|
||||
@@ -19,6 +19,8 @@ enemy_buffer_width_tiles = 20
|
||||
|
||||
[expansion]
|
||||
columns_per_expansion_tiles = 10
|
||||
# x = expansions already purchased; ~1 per cycle mid-game, decelerating
|
||||
# to 2-3 cycles late (docs/balancing/derived.md).
|
||||
cost_building_blocks_formula = "300 + 50*x + 10*x*x"
|
||||
|
||||
[push]
|
||||
@@ -35,7 +37,7 @@ artifact_chance_formula = "0.05 * x" # 5% chance per station level
|
||||
artifact_win_count = 5
|
||||
|
||||
[waves]
|
||||
# Tuned against the factory-size curve (content_design.md, balancing
|
||||
# Tuned against the factory-size curve (docs/balancing/targets.md, balancing
|
||||
# targets): stays below the player's achievable military output early,
|
||||
# crosses it around the late boundary (~cycle 15), overwhelms by ~24.
|
||||
threat_rate_formula = "2*x + 0.15*x*x"
|
||||
|
||||
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).
|
||||
@@ -1,9 +1,10 @@
|
||||
# Progression & Balancing Design
|
||||
# Balancing & Progression Rules
|
||||
|
||||
Rules and principles that govern the production tree, progression pacing,
|
||||
and balancing. This document contains **rules only** — concrete content
|
||||
(item lists, recipes, unlock levels, stat numbers) lives in the config files
|
||||
and `content_design.md`; those numbers must follow the rules stated here.
|
||||
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
|
||||
|
||||
@@ -36,7 +37,7 @@ adapts later (see Refactorability).
|
||||
- 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
|
||||
`../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)
|
||||
@@ -122,7 +123,8 @@ adapts later (see Refactorability).
|
||||
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 new (see Action items).
|
||||
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 —
|
||||
@@ -178,8 +180,8 @@ supporting different fleet doctrines feel structurally different to build.
|
||||
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.
|
||||
`../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
|
||||
@@ -188,9 +190,8 @@ supporting different fleet doctrines feel structurally different to build.
|
||||
- **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.
|
||||
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
|
||||
|
||||
@@ -206,18 +207,18 @@ supporting different fleet doctrines feel structurally different to build.
|
||||
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
|
||||
laser requires the small laser; 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.
|
||||
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 Open
|
||||
tasks), never as free level-ups.
|
||||
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
|
||||
@@ -295,7 +296,7 @@ supporting different fleet doctrines feel structurally different to build.
|
||||
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 Open tasks).
|
||||
idea under Future work).
|
||||
|
||||
## Numeric guardrails
|
||||
|
||||
@@ -314,15 +315,8 @@ Constraints that every recipe must respect, independent of tuning:
|
||||
- **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.
|
||||
|
||||
## Open tasks / future work
|
||||
## Future work
|
||||
|
||||
- **Rework `recipes.toml`** once the rules in this document are fixed: the
|
||||
current tree feels too close to Factorio; apply the thematic-naming rule
|
||||
and the ratio curve to it (renames and quantity changes, not new items).
|
||||
- **Balancing pass** (see placeholders in `content_design.md`): set the
|
||||
`unlock_at_station_level` ladder, real threat costs and
|
||||
`default_modules`, reprocessing weights, and the threat-rate formulas
|
||||
according to the rules above.
|
||||
- **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
|
||||
@@ -337,39 +331,3 @@ Constraints that every recipe must respect, independent of tuning:
|
||||
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.
|
||||
|
||||
## Action items — changes beyond this document
|
||||
|
||||
Agreed changes that require edits to `requirements.md`, the code, and the
|
||||
configs. Completed items are removed from this list (their outcomes live
|
||||
in `requirements.md` and the git history). Still open:
|
||||
|
||||
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 carries alone.** With per-ship level scaling
|
||||
removed, `threat_rate_formula` is the only time-scaling axis; verify
|
||||
the current `world.toml` values still produce the intended difficulty
|
||||
curve (likely a review, not a retune, since the config already ran
|
||||
with a flat ship level before the removal).
|
||||
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. **Expansion cost formula.** Replace the flat
|
||||
`world.toml [expansion].cost_building_blocks` with a formula of the
|
||||
number of expansions already purchased (x = expansions bought so
|
||||
far), so expansion costs can escalate per the block economy rules.
|
||||
Update REQ-EXP-COST and wherever the UI displays the expansion cost.
|
||||
5. **Resource deposits.** Add a terrain deposit layer per the Resource
|
||||
deposits rules: 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`.
|
||||
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.
|
||||
@@ -1,9 +1,10 @@
|
||||
# Content Design — Ships & Modules
|
||||
# Content Design — Ships, Modules & Production Tree
|
||||
|
||||
First real-content iterations (June 2026). Pass 1 defined ship hull grids and
|
||||
module surface masks; pass 2 defined the production tree (recipes). Stats and
|
||||
threat costs in the config files are still placeholders for the balancing
|
||||
pass.
|
||||
The designed game content: hull layout grids, module footprints and the
|
||||
gating between them, and the production tree (items, chains, fiction).
|
||||
All numbers — quantities, durations, threat values, stats, unlock levels
|
||||
— live in the config files and are documented with their derivations in
|
||||
`docs/balancing/` (see `balancing/README.md` for the index).
|
||||
|
||||
## Design principle: footprint gating
|
||||
|
||||
@@ -17,12 +18,12 @@ and makes them trivially moddable through the config files alone.
|
||||
|
||||
| Footprint | Modules | Smallest hull that fits it |
|
||||
|-----------|---------|----------------------------|
|
||||
| 1x1 | laser_cannon_s, salvager, repair_tool | drone |
|
||||
| 1x1 | railgun_s, salvager, repair_tool | drone |
|
||||
| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate |
|
||||
| 1x3 | afterburner | frigate (eats most of it) |
|
||||
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
|
||||
| 2x2 | laser_cannon_m, drone_bay | cruiser |
|
||||
| 3x3 | laser_cannon_l | battleship |
|
||||
| 2x2 | railgun_m, drone_bay | cruiser |
|
||||
| 3x3 | railgun_l | battleship |
|
||||
| 2x6 | drone_hangar | carrier (only) |
|
||||
|
||||
### Hull grids
|
||||
@@ -129,58 +130,11 @@ Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
|
||||
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
|
||||
drone hangar — carrier 1.
|
||||
|
||||
## Production tree (first pass — superseded)
|
||||
## Production tree
|
||||
|
||||
**Superseded (July 2026):** this first-pass tree predates the rules in
|
||||
`docs/progression_design.md` and will be replaced. The decisions for the
|
||||
redesign are recorded in "Production tree v2 — decisions" below; the text
|
||||
of this section is kept for reference until the new tree lands.
|
||||
|
||||
Design principle: each game phase adds exactly one new base input chain, so
|
||||
factory complexity ramps alongside ship size.
|
||||
|
||||
| Phase | New input | How acquired | Unlocks |
|
||||
|-------|-----------|--------------|---------|
|
||||
| early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports |
|
||||
| mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers |
|
||||
| late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar |
|
||||
|
||||
The advanced_alloy gate is the core loop hook: capital ship production
|
||||
requires fighting (salvaging scrap from kills and reprocessing it), not just
|
||||
mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots
|
||||
or advanced_alloy probabilistically.
|
||||
|
||||
Intermediate components, by tier:
|
||||
|
||||
- **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board
|
||||
(iron + wire), building_block (iron).
|
||||
- **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits
|
||||
+ wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame
|
||||
(titanium + steel).
|
||||
- **Tier 4 (late):** reinforced_plating (steel + advanced_alloy),
|
||||
capital_core (targeting_unit + drive_unit + 2 advanced_alloy).
|
||||
|
||||
Hulls and modules consume intermediates of their tier: early items are built
|
||||
from tier-2 parts, midgame items require tier-3 parts (deeper chains, more
|
||||
assemblers), capital items require tier-4 parts (and therefore combat). Hull
|
||||
items are named `<ship>_hull`; module items `<module>_module`. Every item has
|
||||
an `[items.*]` entry in visuals.toml; hull item outlines match the ship's
|
||||
fleet color from `[ships.*]`.
|
||||
|
||||
Consistency is checked by `tools/verify_recipes.py` — re-run it after editing
|
||||
recipes, ship/module materials, or visuals:
|
||||
|
||||
python dota_factory/tools/verify_recipes.py
|
||||
|
||||
It verifies every consumed item has a producer, every item has a visuals
|
||||
entry, flags orphaned items, and prints which items are reprocessing-only
|
||||
(currently exactly advanced_alloy).
|
||||
|
||||
## Production tree v2 — decisions (July 2026)
|
||||
|
||||
The old tree is discarded; the new one is designed against the rules in
|
||||
`docs/progression_design.md` (ratio curve, cost ladder, cost archetypes,
|
||||
refactorability). Decisions fixed so far:
|
||||
Designed against the rules in `docs/balancing/rules.md` (ratio curve,
|
||||
cost ladder, cost archetypes, refactorability). Quantities, durations,
|
||||
and threat values live in `docs/balancing/derived.md`.
|
||||
|
||||
### Base inputs (4) and fiction
|
||||
|
||||
@@ -189,28 +143,27 @@ refactorability). Decisions fixed so far:
|
||||
*is* ore, which is why the shipyard operation was built here at all.
|
||||
- **quartz** — mid-game, minable only on geode deposit patches in
|
||||
expansion territory (see the Resource deposits rules in
|
||||
`progression_design.md`). Fiction: ordinary silicate dust is everywhere
|
||||
and worthless; chips and optics need rare, pocket-bound optical-grade
|
||||
crystal.
|
||||
`docs/balancing/rules.md`; the deposit mechanic itself is an open
|
||||
action item — until it lands, quartz mines anywhere). Fiction:
|
||||
ordinary silicate dust is everywhere and worthless; chips and optics
|
||||
need rare, pocket-bound optical-grade crystal.
|
||||
- **voidsteel** — late-game, obtained only by reprocessing scrap.
|
||||
Fiction: battle-forged — formed when weapon plasma anneals hull metal
|
||||
in the violence of ship destruction. Any wreck yields it, including the
|
||||
player's own; no foundry can replicate it.
|
||||
- **titanium is dropped.** Its hull-gating role moves to quartz-era
|
||||
control systems ("you can smelt all the steel you want, but you cannot
|
||||
steer a battlecruiser without electronics") and possibly a *quality*
|
||||
steel step — e.g. a long-running hardened-steel recipe (time-heavy
|
||||
archetype). Open question for the tree draft: quality step vs.
|
||||
electronics-only gating; explicitly **not** sheer steel quantity alone.
|
||||
- **titanium was dropped** (v1 tree). Its hull-gating role moved to
|
||||
quartz-era control systems ("you can smelt all the steel you want, but
|
||||
you cannot steer a battlecruiser without electronics") plus the
|
||||
hardened-steel quality step (a deliberately long-running, time-heavy
|
||||
recipe) — explicitly not sheer steel quantity alone.
|
||||
|
||||
### Material palette (fingerprints per family)
|
||||
|
||||
- **iron/steel** — structure.
|
||||
- **copper** — conduction and heat: wiring, coils, heat sinks.
|
||||
- **silicon family** (all derived from quartz): silicon (logic,
|
||||
sensors), glass/optics (lenses, focusing crystals), ceramics (heat
|
||||
shielding, insulators). Carries the non-metal variety without extra
|
||||
base inputs.
|
||||
sensors), ceramics (heat shielding, insulators); glass/optics are cut
|
||||
from v1 — their only consumers would be lasers, which are deferred.
|
||||
- **voidsteel** — capital-tier structure and exotics.
|
||||
- Deliberately skipped: carbon (mostly redundant with copper/ceramics),
|
||||
plastics (drags in Factorio-style chemical chains; ceramics read more
|
||||
@@ -219,23 +172,25 @@ refactorability). Decisions fixed so far:
|
||||
|
||||
### Weapons
|
||||
|
||||
- All current lasers are renamed to **railguns** (`laser_cannon_s/m/l` →
|
||||
`railgun_s/m/l`); footprints and the gating matrix are unchanged.
|
||||
Implementation stays as-is (instant damage application, no projectile,
|
||||
no ammunition) — the beam visual reads as a tracer round. Materials:
|
||||
iron slugs, copper coils, steel rails — the starting-metal fingerprint.
|
||||
- All v1 weapons are **railguns** (`railgun_s/m/l`, renamed from the
|
||||
laser placeholders; footprints and the gating matrix unchanged).
|
||||
Implementation is instant damage application with no projectile and no
|
||||
ammunition — the beam visual reads as a tracer round. Materials: iron
|
||||
slugs, copper coils, steel rails — the starting-metal fingerprint.
|
||||
- **Lasers are reserved for later** as a genuinely distinct weapon type
|
||||
(e.g. once projectile/ammunition mechanics exist for other families),
|
||||
arriving with quartz optics. More weapon types are planned; railguns
|
||||
are simply the baseline tech that ships with v1.
|
||||
- `drone_bay` and `drone_hangar` are footprint-only placeholders: the
|
||||
drone-launching capability does not exist in the simulation yet, so
|
||||
they define no capability section. The carrier is deliberately weak
|
||||
until that capability lands (see the accepted imbalances in
|
||||
`docs/balancing/targets.md`).
|
||||
|
||||
### Tree structure — draft
|
||||
### Tree structure
|
||||
|
||||
Recipes are sketched as input lists only; quantities and durations come
|
||||
in the numbers pass, tuned so every chain sums to its threat-ladder
|
||||
value and follows the ratio curve (t1 nice → t4 strange). Glass/optics
|
||||
are cut from v1 — their only consumers would be lasers, which are
|
||||
deferred; the silicon family ships as silicon + ceramics.
|
||||
Input lists only — quantities, durations, and per-item threat values are
|
||||
in `docs/balancing/derived.md` and the configs.
|
||||
|
||||
**Mined (miner):** `iron_ore`, `copper_ore` (every tile), `quartz`
|
||||
(geode deposits in expansion territory).
|
||||
@@ -244,7 +199,7 @@ deferred; the silicon family ships as silicon + ceramics.
|
||||
|
||||
| output | input | ratio class |
|
||||
|---|---|---|
|
||||
| iron_ingot | iron_ore | nice (1:1 or 1:2) |
|
||||
| iron_ingot | iron_ore | nice (1:1) |
|
||||
| copper_ingot | copper_ore | nice |
|
||||
| silicon | quartz | mid entry |
|
||||
| iron_ingot | scrap | the safe, boring scrap sink |
|
||||
@@ -280,14 +235,13 @@ fully unlocked pool state.
|
||||
| capital_core | voidsteel + capacitor_bank + control_chip | capital heart |
|
||||
|
||||
**Hull items** (`<ship>_hull`, assembler-made; the shipyard consumes the
|
||||
hull item plus module materials). The m+ hull gate is resolved as
|
||||
**both** of the open-question options: hardened_steel (quality steel, a
|
||||
deliberately long-running recipe — the time-heavy step) *and*
|
||||
control_chip (electronics):
|
||||
hull item plus module materials). The m+ hull gate is **both**
|
||||
hardened_steel (quality steel, the time-heavy step) *and* control_chip
|
||||
(electronics):
|
||||
|
||||
| hull | inputs |
|
||||
|---|---|
|
||||
| drone_hull | steel_plate |
|
||||
| drone_hull | iron_ingot |
|
||||
| frigate_hull | steel_plate + copper_wire |
|
||||
| destroyer_hull | steel_plate + copper_coil |
|
||||
| cruiser_hull | hardened_steel + control_chip |
|
||||
@@ -302,7 +256,7 @@ stockpiled):
|
||||
|
||||
| module | inputs | archetype |
|
||||
|---|---|---|
|
||||
| railgun_s | steel_plate + copper_coil | balanced |
|
||||
| railgun_s | copper_coil | lean |
|
||||
| salvager | steel_plate + copper_wire | balanced |
|
||||
| repair_tool | steel_plate + copper_wire | balanced |
|
||||
| armor_plates | steel_plate (many) | material-heavy, fast |
|
||||
@@ -317,395 +271,24 @@ stockpiled):
|
||||
| railgun_l | capacitor_bank + hardened_steel + ceramic_plate | late |
|
||||
| drone_hangar | voidsteel_plate + control_chip + drive_unit | late (carrier only) |
|
||||
|
||||
Refactorability check (the default technique holds): railgun_s → m
|
||||
introduces capacitor_bank, built from a subset of the small gun's
|
||||
inputs (copper_coil) plus the new base resource (silicon); the m gun
|
||||
otherwise reuses the small gun's inputs. Hulls likewise: cruiser adds
|
||||
**Refactorability check** (the default technique holds): railgun_s → m
|
||||
introduces capacitor_bank, built from a subset of the small gun's inputs
|
||||
(copper_coil) plus the new base resource (silicon); the m gun otherwise
|
||||
reuses the small gun's inputs. Hulls likewise: the cruiser adds
|
||||
hardening (fed by the existing steel line) and chips (fed by the new
|
||||
quartz territory) without touching the iron/copper core.
|
||||
|
||||
**Shortcut recipe candidates** (drop-only assembler schematics; not
|
||||
every strange chain gets one):
|
||||
**Shortcut recipes** (drop-only assembler schematics; not every strange
|
||||
chain gets one): `iron_ore → steel_plate` (skips the ingot step on the
|
||||
highest-volume chain), `quartz → control_chip` (skips silicon),
|
||||
`iron_ingot → hardened_steel` (a nicer-ratio route past the deliberately
|
||||
awkward hardening step).
|
||||
|
||||
- `iron_ore → steel_plate` — skips the ingot step on the highest-volume
|
||||
chain in the game.
|
||||
- `quartz → control_chip` — skips silicon on the electronics chain.
|
||||
- `iron_ingot → hardened_steel` — a nicer-ratio route past the
|
||||
deliberately awkward hardening step.
|
||||
Consistency is checked by `tools/verify_recipes.py` — re-run it after
|
||||
editing recipes, ship/module materials, or visuals:
|
||||
|
||||
Item count: 3 mined + scrap + 3 smelted + 4 t2 + 5 t3 + 3 t4 (incl.
|
||||
voidsteel) + 8 hulls + 14 modules ≈ 41 item types — same scale as the
|
||||
first-pass tree. `verify_recipes.py` must be re-run once this lands in
|
||||
recipes.toml; the numbers pass should add a threat-report tool that
|
||||
prints per-item threat values and producer:consumer ratio tables.
|
||||
python dota_factory/tools/verify_recipes.py
|
||||
|
||||
### Numbers — first pass
|
||||
|
||||
These numbers are live in the config files and verified by
|
||||
`tools/threat_report.py`; quantities and durations are tuned so fitted
|
||||
ships land on the threat-cost ladder and the ratio curve is realized.
|
||||
The typical loadouts are geometry-validated against the hull grids and
|
||||
configured as each ship's `default_modules`: the L-shaped weapon
|
||||
modifiers turned out not to fit alongside the full gun complement on
|
||||
the battlecruiser and dreadnought, so their loadouts use armor and
|
||||
small-railgun fillers instead — which moved all three heavy ships
|
||||
closer to their ladder targets.
|
||||
|
||||
**Economy constants:** `scrap_per_threat = 0.25` (1 scrap per 4 threat
|
||||
destroyed — a cruiser kill drops ~59 scrap). 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 smelting: 1 scrap → 1 iron_ingot, 1 s — deliberately
|
||||
value-losing (4 threat of scrap becomes a 2-threat ingot); reprocessing
|
||||
is the value-preserving path.
|
||||
|
||||
**Recipes** (dur in seconds; threat is per output item):
|
||||
|
||||
| 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 |
|
||||
|
||||
**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 + typical loadout; the
|
||||
typical loadouts double as the `default_modules` for enemy waves):
|
||||
|
||||
| ship | hull recipe | dur | base | typical loadout | fitted (target) |
|
||||
|---|---|---|---|---|---|
|
||||
| drone | 1 iron_ingot | 1 | 1 | railgun_s | 10 (10) |
|
||||
| frigate | 2 steel_plate + 1 copper_wire | 2 | 2 | 2× railgun_s, maneuvering_thrusters | 47 (40) |
|
||||
| destroyer | 3 steel_plate + 2 copper_coil | 4 | 3 | 3× railgun_s, armor_plates, sensor_booster | 99 (80) |
|
||||
| cruiser | 2 hardened_steel + 2 control_chip | 6 | 4 | 2× railgun_m, armor_plates, maneuvering_thrusters | 234 (200) |
|
||||
| battlecruiser | 3 hardened_steel + 2 control_chip + 1 drive_unit | 8 | 5 | 3× railgun_m, armor_plates, 2× railgun_s | 355 (350) |
|
||||
| battleship | 3 voidsteel_plate + 1 drive_unit + 2 control_chip | 10 | 6 | railgun_l, 2× railgun_m, weapon_stabilizer, 2× railgun_s | 723 (700) |
|
||||
| dreadnought | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | 3× railgun_l, 4× armor_plates, railgun_s | 1492 (1500) |
|
||||
| carrier | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | drone_hangar, 2× railgun_m, 2× armor_plates, sensor_booster | 1436 (1500) |
|
||||
|
||||
**Checks:**
|
||||
|
||||
- *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 feasibility:* worst input demand is 1.33 items/s (wire→coil,
|
||||
plate→armor) — under the ~2/s single-belt cap everywhere; no
|
||||
accidental multi-belt recipes.
|
||||
- *Block economy:* building_block = 4 threat ⇒ for the 4-minute
|
||||
doubling at 30% capacity, the average building must cost ≈ 18 blocks
|
||||
(0.3 × 240 / 4). buildings.toml costs should be set around that mean
|
||||
(belts cheap, producers ~20–30).
|
||||
- *Small-end deviation:* frigate–cruiser land 6–24% hot because the
|
||||
fixed chain overhead dominates small hulls. Recommendation: accept
|
||||
and adjust the ladder targets to the achieved values (the ~×2-per-
|
||||
class curve shape is preserved) rather than thinning the early
|
||||
chains below readability.
|
||||
- *Rule bug discovered:* the scrap→iron_ingot smelter recipe combined
|
||||
with REQ-THREAT-ITEM's max-across-recipes rule would set
|
||||
threat(iron_ingot) to the scrap path (1 + 4 = 5, or more at other
|
||||
scrap values) instead of 2, inflating every downstream item. Fix:
|
||||
scrap-consuming recipes count toward an item's threat only when no
|
||||
scrap-free recipe produces that item, mirroring the reprocessing-path
|
||||
rule (see progression_design.md action items).
|
||||
|
||||
### Combat stat anchors — first pass
|
||||
|
||||
All combat stats derive from a few anchors so that power-per-threat holds
|
||||
by construction; the arena suite in `bin/balancing/data/balancing.toml`
|
||||
verifies it empirically. Change the anchors first, re-derive stats second.
|
||||
|
||||
- **Weapon DPS per threat pays a concentration tax that grows with gun
|
||||
size**: railgun_s 2 dmg × 2.0 Hz = 4.0 DPS at 6.5 threat (0.62),
|
||||
range 50; railgun_m 14 × 1.5 = 21 at 43.5 (0.48), range 70;
|
||||
railgun_l 52 × 0.8 = 41.6 at 102 (0.41), range 100. Arena round 1
|
||||
(see below) showed that at equal threat, concentrated fleets win
|
||||
flawlessly with a flat-ish DPS curve — concentration itself (focus
|
||||
survivability, range, no losses-to-attrition of own DPS) is worth
|
||||
paying for, so bigger guns get less raw DPS per threat.
|
||||
- **Hull HP = 15 per threat of hull contribution** (incl. ship base
|
||||
time) as the *prior*; per-hull HP is the empirical trim knob for
|
||||
arena results (guns are shared across many hulls, so gun changes move
|
||||
many matchups at once — hull HP moves exactly one). Current values
|
||||
after round-5 trims: drone 60, frigate 300, destroyer 550,
|
||||
cruiser 1500, battlecruiser 2400, battleship 6300,
|
||||
dreadnought/carrier 24000. (The capital hulls sit well above the
|
||||
15/threat prior while their guns sit below the DPS prior — the arena
|
||||
consistently prices big ships as tanks with taxed guns.)
|
||||
- **Armor HP ≈ 37 per threat** — a strong premium over hull HP (15)
|
||||
because armor is pure HP with no capability, and fights snowball:
|
||||
killing enemies removes their DPS, while surviving merely delays —
|
||||
so a point of HP must be cheaper than a point of DPS.
|
||||
armor_plates adds 1200 HP at 32 threat (raised from 640 → 1000 →
|
||||
1200: the all-guns destroyer beat the armored one in three
|
||||
consecutive arena rounds).
|
||||
- **TTK check:** a mirror cruiser duel (51 DPS vs 2140 EHP) lasts ~42 s —
|
||||
inside the 30–60 s parity-fight band implied by the 25-ship fleet
|
||||
target. Capital mirrors run longer (~90 s), deliberately.
|
||||
- **Repair ≈ 0.7 HP/s per threat:** repair_tool heals 9 HP × 1 Hz at 13
|
||||
threat (nerfed 25 → 12 → 9: in-combat sustain effectively removes
|
||||
enemy DPS and must be priced like DPS, not like HP — the escorted
|
||||
team held a persistent +24% at 12). Salvager: collection range 60 (was
|
||||
a nonsensical 500, beyond any sensor), cargo 20, 0.5 collections/s.
|
||||
`scrap_despawn_seconds` raised 30 → 120: a capital kill drops
|
||||
hundreds of scrap collected one per cycle, so 30 s despawned nearly
|
||||
everything.
|
||||
- **Mobility ladder is monotone in size** — small = fast and nimble
|
||||
(drone 45 m/s, 60 accel) down to capitals (10 m/s, 8 accel); the old
|
||||
placeholder had the drone as the least agile ship. Sensor ranges keep
|
||||
the existing 150→350 ladder; all weapon ranges stay below sensor
|
||||
ranges.
|
||||
- **Weapon modifiers are capital economy:** ×1.2 damage at 22.5 threat
|
||||
beats adding a gun once a ship carries more than ~68 threat of weapons
|
||||
— so modifiers pay off on gun-heavy big hulls and are a waste on small
|
||||
ones. Damage/rate kept at ×1.2; the stabilizer's range multiplier was
|
||||
cut ×1.5 → ×1.3 after round 2 — range is the strongest stat in the
|
||||
orbit-AI's hands (free approach fire), and the stabilized battleship
|
||||
was the one loadout still overperforming.
|
||||
- **Stations:** a fresh player station (3000 HP, 25 dmg × 1 Hz,
|
||||
range 120) holds one early parity wave unaided; the enemy station at
|
||||
level 0 matches it exactly and scales per push level
|
||||
(+1500 HP, +12 dmg, +0.1 Hz per level). HQ 5000 HP. Range reduced
|
||||
from 200 in round 1: at 200 (4× a small gun's range) two stations
|
||||
annihilated a 3× threat swarm through approach fire alone — range,
|
||||
not HP, is the station dominance lever.
|
||||
- **Known imbalance, accepted:** the carrier loses its equal-threat
|
||||
arena until the drone-launching capability exists — the hangar is 224
|
||||
threat of dead weight. Do not compensate with stats; fix by
|
||||
implementing drones.
|
||||
|
||||
**Arena round 1 results (2026-07-04):** mirrors snowball as expected
|
||||
(40–50% of the winner survives) but team 1 won all three mirrors —
|
||||
possible sim-side bias, under investigation (progression_design.md
|
||||
action items 6–7). All four equal-threat cross-tier matchups were
|
||||
flawless wins for the concentrated side; glass destroyers beat armored;
|
||||
repair escort beat raw numbers flawlessly; two stations shrugged off a
|
||||
3× threat swarm. Round-2 knob changes: concentration tax on m/l gun
|
||||
DPS (17→14, 70→52 damage), armor 640→1000, repair 25→12, station range
|
||||
200→120. Gun ranges deliberately untouched — if concentrated fleets
|
||||
still win flawlessly in round 2, the range ladder (kiting) is the next
|
||||
suspect, ahead of further DPS cuts.
|
||||
|
||||
**Arena round 5 results (2026-07-04, with fight durations) — combat
|
||||
pass converged:** durations validate the TTK anchor: drone mirror
|
||||
22.7 s, cruiser mirror 71.2 s, battleship mirror 94.6 s, most fights
|
||||
in the 22–72 s band; the destroyers-vs-dreadnought slugfest is the
|
||||
outlier at 214 s (extreme tank vs. chip damage — acceptable for the
|
||||
extreme case). Converged: mirrors 5–10%, swarm vs cruisers +15%,
|
||||
frigates vs battleship dropped to +13% on its own (confirming the
|
||||
noise-floor read — now inside band anyway), dreadnought at +3% knife
|
||||
edge, glass vs armored +10%, two-to-one 78%, station assault stable at
|
||||
44%. Two two-round signals actioned in round 6: mixed beat
|
||||
battlecruisers again (+20%, +18%) → battlecruiser hull 2200→2400;
|
||||
repair escort held +24% twice with all frigates surviving →
|
||||
repair_amount 12→9. Everything else is frozen; further refinement
|
||||
moves to real-game playtests.
|
||||
|
||||
**Arena round 4 results (2026-07-04) and convergence policy:** mirrors
|
||||
at 0–3%; swarm vs cruisers +7%; glass vs armored resolved at +3% for
|
||||
armored (armor 1200 confirmed after four rounds of signal). Noise
|
||||
floor established: the battleship held +23% straight through a −10%
|
||||
EHP cut, and repair drifted 14→24% with no repair changes — single-run
|
||||
margins move ~±10% on a re-roll. Policy from here: only act on signals
|
||||
that persist two rounds; treat anything inside ±20% as converged for
|
||||
v1 (real-game playtests refine further). Armor-coupling overshoots
|
||||
corrected in round 5: battlecruiser 2000→2200, dreadnought/carrier
|
||||
22500→24000 (two-round persistent deficit vs destroyers). **Accepted,
|
||||
not chased:** the stabilized battleship beats a pure frigate fleet by
|
||||
~23% — the edge survived a stat cut, so it is mechanical (range), and
|
||||
"the extreme smallest-ship fleet modestly loses to a range-fitted
|
||||
capital" is desirable doctrine texture; the fair anti-capital answer
|
||||
is the mixed fleet, whose arena is balanced.
|
||||
|
||||
**Arena round 3 results (2026-07-04, narrow lanes):** arenas were
|
||||
narrowed (height 10) so fleets can no longer pass each other — arena
|
||||
geometry is part of the fixture from here on; margins are only
|
||||
comparable within the same geometry. Results: mirrors healthy (1–7%);
|
||||
dreadnought closed to −11%; battleship improved to +22% (still over);
|
||||
drone swarm now beats cruisers by 14% (full engagement in the narrow
|
||||
lane favors numbers); mixed vs battlecruisers +18%; glass beat armored
|
||||
for the third consecutive round (+12%); repair 14%, two-to-one 78%,
|
||||
station set cracked at 51% by the 3× swarm (geometry changed under the
|
||||
stations — watch before touching stats). Round-4 knob changes:
|
||||
armor_plates 1000→1200 (the persistent glass signal; also lifts the
|
||||
armor-carrying cruiser/BC/destroyer/DN loadouts), battlecruiser hull
|
||||
2500→2000 (net −300 EHP after its armor gain), battleship 7000→6300
|
||||
(no armor in its loadout, clean −10%), dreadnought/carrier
|
||||
19000→22500 (the opposing destroyers gain armor too, so the DN needs
|
||||
the larger step).
|
||||
|
||||
**Arena round 2 results (2026-07-04, with EHP-margin logging):** the
|
||||
mirror "bias" was noise — repeated runs split randomly, and winners
|
||||
finish at 5–7% EHP (near-mutual annihilation, healthy). Drones vs
|
||||
cruisers 10% and mixed vs battlecruisers 14% — near parity. The two
|
||||
capital fights diverged: battleship beat frigates at +33% while the
|
||||
dreadnought lost to destroyers at −37% — same guns, so the difference
|
||||
is the destroyers carrying the buffed armor plus the battleship's
|
||||
stabilizer putting all its guns at 75–150 m against 50 m opponents.
|
||||
Glass vs armored narrowed to 11% (watch; if it persists, armor
|
||||
1000→1200). Repair escort 11% — fair. Two-to-one decisive at 80%;
|
||||
station assault cracked by the 3× swarm at 13% (stations strong but
|
||||
breakable). Round-3 knob changes: stabilizer range ×1.5→×1.3, and
|
||||
per-hull HP trims — battlecruiser 2700→2500, battleship 7500→7000,
|
||||
dreadnought/carrier 15500→19000.
|
||||
|
||||
### Pacing pass — first values
|
||||
|
||||
The mapping between station levels and boss cycles: at the intended
|
||||
push cadence (first set around cycle 2–3, roughly one per cycle from
|
||||
mid on), the destroyed set's level ℓ is reached around cycle ℓ+2. So
|
||||
level 2 ≈ the mid boundary, level 6 ≈ mid-late, levels 8–9 ≈ capitals
|
||||
with enough cycles left to ramp voidsteel production before late.
|
||||
|
||||
**Starting set** (`unlock_at_station_level = -1`), per the starting-set
|
||||
rule (exactly enough to reach the first push): drone, frigate,
|
||||
railgun_s, salvager. Note: the building_block recipe must carry an
|
||||
explicit `-1` — building blocks appear in no schematic's materials, so
|
||||
implicit unlocking can never reach it (fixed in this pass; it was
|
||||
silently locked).
|
||||
|
||||
**Unlock ladder** (level → unlocks; ← marks `unlock_requires`):
|
||||
|
||||
| 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, so the first push offers a
|
||||
full dialog with no artifacts competing. Level 2 is the quartz gate:
|
||||
cruiser and railgun_m are the first schematics whose chains reach
|
||||
quartz (the shortcut recipes' outputs only become implicitly unlocked
|
||||
alongside them, so they cannot drop early).
|
||||
|
||||
**Threat rate** `2*x + 0.15*x*x`, tuned against the factory-size curve
|
||||
with roughly 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 | — |
|
||||
|
||||
Below the player early, crossing at the late boundary, overwhelming by
|
||||
~24 — the endless-race shape from the progression rules. Typical wave
|
||||
at x=6 (30 s gap): ~520 threat ≈ two cruisers plus escorts; boss at
|
||||
x=15: ~3800 threat ≈ two and a half dreadnoughts.
|
||||
|
||||
**Economy**: `starting_building_blocks` 1000 → 200 (minimal block loop
|
||||
+ first shipyard ≈ 150, plus beginner slack); expansion cost 200 → 400
|
||||
flat (placeholder until the escalating formula, action item 4);
|
||||
`artifact_win_count` 3 → 5 with chance `0.05*x`: ~15–18 pushes in a
|
||||
winning run offer ~7 artifact options cumulatively, so winning takes
|
||||
choosing the artifact over a schematic five times — a real decision
|
||||
each time, and the win lands in the cycle 20–24 target window.
|
||||
|
||||
## Balancing targets (first pass, July 2026)
|
||||
|
||||
The six root numbers for the balancing pass. Every derived value (threat
|
||||
rate, recipe quantities, block costs, scrap rates, unlock ladder) is tuned
|
||||
to hit these; when rebalancing later, change these first and re-derive,
|
||||
never the other way around. The rules they follow live in
|
||||
`docs/progression_design.md`.
|
||||
|
||||
All targets are in **game time**. The player can pause and accelerate, so
|
||||
real session length differs; playtests should 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 — these
|
||||
targets deliberately ignore that; playtesting will show real run lengths.
|
||||
|
||||
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 roughly one station set per
|
||||
cycle from mid onward. This fixes the `unlock_at_station_level`
|
||||
ladder and, with the artifact win count, the `artifact_chance_formula`
|
||||
pacing.
|
||||
3. **Factory size curve** — producing buildings over time; when
|
||||
saturated, output threat/s equals this count, so this curve IS the
|
||||
player power curve. Targets: ~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, and buildings plus belts must
|
||||
physically fit the asteroid plus affordable expansions.
|
||||
4. **Threat-cost ladder** — total production-seconds per *fitted* hull
|
||||
(including a typical module loadout): drone ~10, frigate ~40,
|
||||
destroyer ~80, cruiser ~200, battlecruiser ~350, battleship ~700,
|
||||
dreadnought/carrier ~1500. Every production chain must sum to its
|
||||
ladder value. Cross-check against (3): fitted-ship cadence =
|
||||
ladder value / (factory threat/s devoted to military) — e.g. a mid
|
||||
factory spending half of 60 threat/s on ships fields a fitted
|
||||
cruiser roughly every 7 s.
|
||||
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 the
|
||||
combat stat magnitudes tuned in the arena pass.
|
||||
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, with escalating costs that eventually
|
||||
outrun any income (see the growth curve rules in
|
||||
`progression_design.md`).
|
||||
|
||||
## Deliberate placeholders / open questions for later passes
|
||||
|
||||
- All new hulls have `threat.cost_formula = "0"` so enemy waves do not spawn
|
||||
them yet (WaveSystem treats any ship with positive threat cost as wave-
|
||||
eligible, regardless of unlock level). The balancing pass should set real
|
||||
threat costs together with `default_modules` loadouts so waves spawn them
|
||||
armed.
|
||||
- All new hulls and all assembler recipes are `unlock_at_station_level = -1`
|
||||
(available from the start) to make testing easy; the balancing pass should
|
||||
stagger these so mid/lategame recipes drop as schematics from enemy defence
|
||||
stations.
|
||||
- Recipe quantities and durations are a first guess, deliberately roughly
|
||||
tiered (capital hulls ~60 s, drones 4 s); the balancing pass tunes them.
|
||||
- `drone_bay` and `drone_hangar` are footprint-only placeholders: the drone
|
||||
launching capability does not exist in the simulation yet, so they define
|
||||
no capability section.
|
||||
- Renames in this pass: `laser_cannon_xs` → `laser_cannon_s` (the old 2x2
|
||||
`laser_cannon_s` became `laser_cannon_m`), `armor_plate` → `armor_plates`,
|
||||
`manuvering_thrusters` → `maneuvering_thrusters` (typo fix). Test data
|
||||
under `bin/test/data/config` intentionally still uses the old ids — it is
|
||||
an independent fixture set.
|
||||
It verifies every consumed item has a producer, every item has a visuals
|
||||
entry, flags orphaned items, and prints which items are
|
||||
reprocessing-only (currently exactly voidsteel).
|
||||
|
||||
Reference in New Issue
Block a user