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:
@@ -1,7 +1,7 @@
|
|||||||
# recipes.toml
|
# recipes.toml
|
||||||
#
|
#
|
||||||
# Production tree v2 (see docs/content_design.md, "Production tree v2" and
|
# Production tree v2 (structure in docs/content_design.md, numbers with
|
||||||
# "Numbers — first pass"). Quantities and durations are tuned so that every
|
# 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
|
# 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
|
# realized: tier 1 ratios are 1:1, tier 2 ratios are 2:3, tier 3+ ratios
|
||||||
# are deliberately strange.
|
# are deliberately strange.
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
# rock IS ore)
|
# rock IS ore)
|
||||||
# mid + quartz geode deposits in expansion territory
|
# mid + quartz geode deposits in expansion territory
|
||||||
# (deposit gating pending — see action
|
# (deposit gating pending — see action
|
||||||
# item 5 in docs/progression_design.md;
|
# item 5 in docs/balancing/README.md;
|
||||||
# until then quartz mines anywhere)
|
# until then quartz mines anywhere)
|
||||||
# late + voidsteel battle-forged: ONLY from reprocessing
|
# late + voidsteel battle-forged: ONLY from reprocessing
|
||||||
# salvaged scrap, so capital production
|
# salvaged scrap, so capital production
|
||||||
@@ -141,7 +141,7 @@ outputs = [{item = "copper_coil", amount = 1}]
|
|||||||
duration_seconds = 1.5
|
duration_seconds = 1.5
|
||||||
|
|
||||||
# Depth-3 chain (ore -> ingot -> plate -> block) is the factory's
|
# 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
|
# Explicitly unlocked at start (-1): building blocks appear in no
|
||||||
# schematic's materials, so implicit unlocking can never reach this recipe.
|
# schematic's materials, so implicit unlocking can never reach this recipe.
|
||||||
[[recipe]]
|
[[recipe]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# stations.toml
|
# 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
|
# a fresh player defence station holds one early parity wave unaided; the
|
||||||
# enemy station at level 0 matches the player station exactly and scales
|
# enemy station at level 0 matches the player station exactly and scales
|
||||||
# with the push level x. Station scrap drops stay authored (pushing rewards
|
# with the push level x. Station scrap drops stay authored (pushing rewards
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ enemy_buffer_width_tiles = 20
|
|||||||
|
|
||||||
[expansion]
|
[expansion]
|
||||||
columns_per_expansion_tiles = 10
|
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"
|
cost_building_blocks_formula = "300 + 50*x + 10*x*x"
|
||||||
|
|
||||||
[push]
|
[push]
|
||||||
@@ -35,7 +37,7 @@ artifact_chance_formula = "0.05 * x" # 5% chance per station level
|
|||||||
artifact_win_count = 5
|
artifact_win_count = 5
|
||||||
|
|
||||||
[waves]
|
[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,
|
# targets): stays below the player's achievable military output early,
|
||||||
# crosses it around the late boundary (~cycle 15), overwhelms by ~24.
|
# crosses it around the late boundary (~cycle 15), overwhelms by ~24.
|
||||||
threat_rate_formula = "2*x + 0.15*x*x"
|
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,
|
Rules and principles that govern the production tree, progression pacing,
|
||||||
and balancing. This document contains **rules only** — concrete content
|
and balancing. This document contains **rules only** — the chosen base
|
||||||
(item lists, recipes, unlock levels, stat numbers) lives in the config files
|
numbers live in `targets.md`, everything derived from them in
|
||||||
and `content_design.md`; those numbers must follow the rules stated here.
|
`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
|
## Player-experience goals
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ adapts later (see Refactorability).
|
|||||||
- The fourth input is the core loop hook: capital ship production requires
|
- The fourth input is the core loop hook: capital ship production requires
|
||||||
fighting (salvaging and reprocessing), not just mining.
|
fighting (salvaging and reprocessing), not just mining.
|
||||||
- Every gating has a fictional reason (concrete fiction in
|
- 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
|
rock is minable anywhere; the mid resource sits in rare pockets; the
|
||||||
late input is battle-forged — created only in the violence of ship
|
late input is battle-forged — created only in the violence of ship
|
||||||
destruction, which is why any wreck (including the player's own)
|
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
|
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
|
is never offered a shortcut for a chain they have not built yet. The
|
||||||
output-item half of this check already exists in
|
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
|
- **Shortcuts are pure rewards, never balance factors.** An item's threat
|
||||||
value is the *maximum* across its producing recipes (REQ-THREAT-ITEM), so
|
value is the *maximum* across its producing recipes (REQ-THREAT-ITEM), so
|
||||||
unlocking a cheaper recipe does not lower the item's threat accounting —
|
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
|
modules, and tiers. Higher tiers are better per *ship* and per *module
|
||||||
slot*, not per invested factory-second — their advantage is
|
slot*, not per invested factory-second — their advantage is
|
||||||
concentration (fewer, bigger things; slot geometry per
|
concentration (fewer, bigger things; slot geometry per
|
||||||
`content_design.md`) and qualitative capabilities, not a better exchange
|
`../content_design.md`) and qualitative capabilities, not a better
|
||||||
rate. Deviations from this rule are deliberate and documented.
|
exchange rate. Deviations from this rule are deliberate and documented.
|
||||||
- **Difficulty race:** the enemy threat rate (`threat_rate_formula`) is
|
- **Difficulty race:** the enemy threat rate (`threat_rate_formula`) is
|
||||||
tuned against the factory output (threat/s) achievable by a competent
|
tuned against the factory output (threat/s) achievable by a competent
|
||||||
player — slightly below it early, crossing above it eventually. The game
|
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
|
- **All time scaling lives in the threat rate** — waves get bigger, ships
|
||||||
of a given schematic never get individually stronger. There is no ship
|
of a given schematic never get individually stronger. There is no ship
|
||||||
level dimension: stat formulas are plain values, and per-ship level
|
level dimension: stat formulas are plain values, and per-ship level
|
||||||
scaling does not exist. Push
|
scaling does not exist. Push scaling on enemy defence stations is the
|
||||||
scaling on enemy defence stations is the separate, player-triggered
|
separate, player-triggered difficulty axis and keeps its level formulas.
|
||||||
difficulty axis and keeps its level formulas.
|
|
||||||
|
|
||||||
## Unlock & drop pacing
|
## 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
|
gate, a schematic (ship, module, or assembler recipe) may list
|
||||||
prerequisite schematics (`unlock_requires`, REQ-LOCK-PREREQ) that must
|
prerequisite schematics (`unlock_requires`, REQ-LOCK-PREREQ) that must
|
||||||
already be unlocked before it enters the drop pool — e.g. the medium
|
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.
|
gun requires the small gun; a future Mk2 requires its base version.
|
||||||
Station level gates the earliest
|
Station level gates the earliest *when*; prerequisites gate the
|
||||||
*when*; prerequisites gate the *order*, keeping drop offers coherent
|
*order*, keeping drop offers coherent with what the player already
|
||||||
with what the player already owns.
|
owns.
|
||||||
- **No duplicate drops.** Ship and module schematics leave the drop pool
|
- **No duplicate drops.** Ship and module schematics leave the drop pool
|
||||||
once owned, exactly as assembler recipe schematics already do. There are
|
once owned, exactly as assembler recipe schematics already do. There are
|
||||||
no schematic level-ups; player power grows through unlock breadth and
|
no schematic level-ups; player power grows through unlock breadth and
|
||||||
factory scale only, which keeps power-per-threat exact on both sides.
|
factory scale only, which keeps power-per-threat exact on both sides.
|
||||||
The pool therefore shrinks over a run and late pushes increasingly offer
|
The pool therefore shrinks over a run and late pushes increasingly offer
|
||||||
artifacts — intended: the late game is a race for the win condition.
|
artifacts — intended: the late game is a race for the win condition.
|
||||||
Per-item progression may return later as Mk2 upgrade recipes (see Open
|
Per-item progression may return later as Mk2 upgrade recipes (see Future
|
||||||
tasks), never as free level-ups.
|
work), never as free level-ups.
|
||||||
- **Artifacts trade power for progress.** Artifact options compete with
|
- **Artifacts trade power for progress.** Artifact options compete with
|
||||||
schematic picks in the same choice dialog; the artifact chance must be
|
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
|
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
|
through the HQ's single belt port, so income is capped at belt
|
||||||
throughput regardless of assembler count. Per-building costs should be
|
throughput regardless of assembler count. Per-building costs should be
|
||||||
high enough that this cap can bind late-game (see the condensed-block
|
high enough that this cap can bind late-game (see the condensed-block
|
||||||
idea under Open tasks).
|
idea under Future work).
|
||||||
|
|
||||||
## Numeric guardrails
|
## 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
|
- **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.
|
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
|
- **Condensed building blocks** — a drop-unlockable shortcut-style
|
||||||
recipe that packs several blocks' worth of value into one belt item,
|
recipe that packs several blocks' worth of value into one belt item,
|
||||||
relieving the HQ intake ceiling (see Building block economy) as a
|
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
|
assembler), and keeps balancing one-dimensional (no level variable
|
||||||
anywhere). Enemy-side progression happens via `default_modules`
|
anywhere). Enemy-side progression happens via `default_modules`
|
||||||
variants per era instead of a level formula.
|
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
|
The designed game content: hull layout grids, module footprints and the
|
||||||
module surface masks; pass 2 defined the production tree (recipes). Stats and
|
gating between them, and the production tree (items, chains, fiction).
|
||||||
threat costs in the config files are still placeholders for the balancing
|
All numbers — quantities, durations, threat values, stats, unlock levels
|
||||||
pass.
|
— 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
|
## 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 |
|
| 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 |
|
| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate |
|
||||||
| 1x3 | afterburner | frigate (eats most of it) |
|
| 1x3 | afterburner | frigate (eats most of it) |
|
||||||
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
|
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
|
||||||
| 2x2 | laser_cannon_m, drone_bay | cruiser |
|
| 2x2 | railgun_m, drone_bay | cruiser |
|
||||||
| 3x3 | laser_cannon_l | battleship |
|
| 3x3 | railgun_l | battleship |
|
||||||
| 2x6 | drone_hangar | carrier (only) |
|
| 2x6 | drone_hangar | carrier (only) |
|
||||||
|
|
||||||
### Hull grids
|
### Hull grids
|
||||||
@@ -129,58 +130,11 @@ Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
|
|||||||
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
|
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
|
||||||
drone hangar — carrier 1.
|
drone hangar — carrier 1.
|
||||||
|
|
||||||
## Production tree (first pass — superseded)
|
## Production tree
|
||||||
|
|
||||||
**Superseded (July 2026):** this first-pass tree predates the rules in
|
Designed against the rules in `docs/balancing/rules.md` (ratio curve,
|
||||||
`docs/progression_design.md` and will be replaced. The decisions for the
|
cost ladder, cost archetypes, refactorability). Quantities, durations,
|
||||||
redesign are recorded in "Production tree v2 — decisions" below; the text
|
and threat values live in `docs/balancing/derived.md`.
|
||||||
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:
|
|
||||||
|
|
||||||
### Base inputs (4) and fiction
|
### 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.
|
*is* ore, which is why the shipyard operation was built here at all.
|
||||||
- **quartz** — mid-game, minable only on geode deposit patches in
|
- **quartz** — mid-game, minable only on geode deposit patches in
|
||||||
expansion territory (see the Resource deposits rules in
|
expansion territory (see the Resource deposits rules in
|
||||||
`progression_design.md`). Fiction: ordinary silicate dust is everywhere
|
`docs/balancing/rules.md`; the deposit mechanic itself is an open
|
||||||
and worthless; chips and optics need rare, pocket-bound optical-grade
|
action item — until it lands, quartz mines anywhere). Fiction:
|
||||||
crystal.
|
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.
|
- **voidsteel** — late-game, obtained only by reprocessing scrap.
|
||||||
Fiction: battle-forged — formed when weapon plasma anneals hull metal
|
Fiction: battle-forged — formed when weapon plasma anneals hull metal
|
||||||
in the violence of ship destruction. Any wreck yields it, including the
|
in the violence of ship destruction. Any wreck yields it, including the
|
||||||
player's own; no foundry can replicate it.
|
player's own; no foundry can replicate it.
|
||||||
- **titanium is dropped.** Its hull-gating role moves to quartz-era
|
- **titanium was dropped** (v1 tree). Its hull-gating role moved to
|
||||||
control systems ("you can smelt all the steel you want, but you cannot
|
quartz-era control systems ("you can smelt all the steel you want, but
|
||||||
steer a battlecruiser without electronics") and possibly a *quality*
|
you cannot steer a battlecruiser without electronics") plus the
|
||||||
steel step — e.g. a long-running hardened-steel recipe (time-heavy
|
hardened-steel quality step (a deliberately long-running, time-heavy
|
||||||
archetype). Open question for the tree draft: quality step vs.
|
recipe) — explicitly not sheer steel quantity alone.
|
||||||
electronics-only gating; explicitly **not** sheer steel quantity alone.
|
|
||||||
|
|
||||||
### Material palette (fingerprints per family)
|
### Material palette (fingerprints per family)
|
||||||
|
|
||||||
- **iron/steel** — structure.
|
- **iron/steel** — structure.
|
||||||
- **copper** — conduction and heat: wiring, coils, heat sinks.
|
- **copper** — conduction and heat: wiring, coils, heat sinks.
|
||||||
- **silicon family** (all derived from quartz): silicon (logic,
|
- **silicon family** (all derived from quartz): silicon (logic,
|
||||||
sensors), glass/optics (lenses, focusing crystals), ceramics (heat
|
sensors), ceramics (heat shielding, insulators); glass/optics are cut
|
||||||
shielding, insulators). Carries the non-metal variety without extra
|
from v1 — their only consumers would be lasers, which are deferred.
|
||||||
base inputs.
|
|
||||||
- **voidsteel** — capital-tier structure and exotics.
|
- **voidsteel** — capital-tier structure and exotics.
|
||||||
- Deliberately skipped: carbon (mostly redundant with copper/ceramics),
|
- Deliberately skipped: carbon (mostly redundant with copper/ceramics),
|
||||||
plastics (drags in Factorio-style chemical chains; ceramics read more
|
plastics (drags in Factorio-style chemical chains; ceramics read more
|
||||||
@@ -219,23 +172,25 @@ refactorability). Decisions fixed so far:
|
|||||||
|
|
||||||
### Weapons
|
### Weapons
|
||||||
|
|
||||||
- All current lasers are renamed to **railguns** (`laser_cannon_s/m/l` →
|
- All v1 weapons are **railguns** (`railgun_s/m/l`, renamed from the
|
||||||
`railgun_s/m/l`); footprints and the gating matrix are unchanged.
|
laser placeholders; footprints and the gating matrix unchanged).
|
||||||
Implementation stays as-is (instant damage application, no projectile,
|
Implementation is instant damage application with no projectile and no
|
||||||
no ammunition) — the beam visual reads as a tracer round. Materials:
|
ammunition — the beam visual reads as a tracer round. Materials: iron
|
||||||
iron slugs, copper coils, steel rails — the starting-metal fingerprint.
|
slugs, copper coils, steel rails — the starting-metal fingerprint.
|
||||||
- **Lasers are reserved for later** as a genuinely distinct weapon type
|
- **Lasers are reserved for later** as a genuinely distinct weapon type
|
||||||
(e.g. once projectile/ammunition mechanics exist for other families),
|
(e.g. once projectile/ammunition mechanics exist for other families),
|
||||||
arriving with quartz optics. More weapon types are planned; railguns
|
arriving with quartz optics. More weapon types are planned; railguns
|
||||||
are simply the baseline tech that ships with v1.
|
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
|
Input lists only — quantities, durations, and per-item threat values are
|
||||||
in the numbers pass, tuned so every chain sums to its threat-ladder
|
in `docs/balancing/derived.md` and the configs.
|
||||||
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.
|
|
||||||
|
|
||||||
**Mined (miner):** `iron_ore`, `copper_ore` (every tile), `quartz`
|
**Mined (miner):** `iron_ore`, `copper_ore` (every tile), `quartz`
|
||||||
(geode deposits in expansion territory).
|
(geode deposits in expansion territory).
|
||||||
@@ -244,7 +199,7 @@ deferred; the silicon family ships as silicon + ceramics.
|
|||||||
|
|
||||||
| output | input | ratio class |
|
| 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 |
|
| copper_ingot | copper_ore | nice |
|
||||||
| silicon | quartz | mid entry |
|
| silicon | quartz | mid entry |
|
||||||
| iron_ingot | scrap | the safe, boring scrap sink |
|
| 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 |
|
| capital_core | voidsteel + capacitor_bank + control_chip | capital heart |
|
||||||
|
|
||||||
**Hull items** (`<ship>_hull`, assembler-made; the shipyard consumes the
|
**Hull items** (`<ship>_hull`, assembler-made; the shipyard consumes the
|
||||||
hull item plus module materials). The m+ hull gate is resolved as
|
hull item plus module materials). The m+ hull gate is **both**
|
||||||
**both** of the open-question options: hardened_steel (quality steel, a
|
hardened_steel (quality steel, the time-heavy step) *and* control_chip
|
||||||
deliberately long-running recipe — the time-heavy step) *and*
|
(electronics):
|
||||||
control_chip (electronics):
|
|
||||||
|
|
||||||
| hull | inputs |
|
| hull | inputs |
|
||||||
|---|---|
|
|---|---|
|
||||||
| drone_hull | steel_plate |
|
| drone_hull | iron_ingot |
|
||||||
| frigate_hull | steel_plate + copper_wire |
|
| frigate_hull | steel_plate + copper_wire |
|
||||||
| destroyer_hull | steel_plate + copper_coil |
|
| destroyer_hull | steel_plate + copper_coil |
|
||||||
| cruiser_hull | hardened_steel + control_chip |
|
| cruiser_hull | hardened_steel + control_chip |
|
||||||
@@ -302,7 +256,7 @@ stockpiled):
|
|||||||
|
|
||||||
| module | inputs | archetype |
|
| module | inputs | archetype |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| railgun_s | steel_plate + copper_coil | balanced |
|
| railgun_s | copper_coil | lean |
|
||||||
| salvager | steel_plate + copper_wire | balanced |
|
| salvager | steel_plate + copper_wire | balanced |
|
||||||
| repair_tool | steel_plate + copper_wire | balanced |
|
| repair_tool | steel_plate + copper_wire | balanced |
|
||||||
| armor_plates | steel_plate (many) | material-heavy, fast |
|
| armor_plates | steel_plate (many) | material-heavy, fast |
|
||||||
@@ -317,395 +271,24 @@ stockpiled):
|
|||||||
| railgun_l | capacitor_bank + hardened_steel + ceramic_plate | late |
|
| railgun_l | capacitor_bank + hardened_steel + ceramic_plate | late |
|
||||||
| drone_hangar | voidsteel_plate + control_chip + drive_unit | late (carrier only) |
|
| drone_hangar | voidsteel_plate + control_chip + drive_unit | late (carrier only) |
|
||||||
|
|
||||||
Refactorability check (the default technique holds): railgun_s → m
|
**Refactorability check** (the default technique holds): railgun_s → m
|
||||||
introduces capacitor_bank, built from a subset of the small gun's
|
introduces capacitor_bank, built from a subset of the small gun's inputs
|
||||||
inputs (copper_coil) plus the new base resource (silicon); the m gun
|
(copper_coil) plus the new base resource (silicon); the m gun otherwise
|
||||||
otherwise reuses the small gun's inputs. Hulls likewise: cruiser adds
|
reuses the small gun's inputs. Hulls likewise: the cruiser adds
|
||||||
hardening (fed by the existing steel line) and chips (fed by the new
|
hardening (fed by the existing steel line) and chips (fed by the new
|
||||||
quartz territory) without touching the iron/copper core.
|
quartz territory) without touching the iron/copper core.
|
||||||
|
|
||||||
**Shortcut recipe candidates** (drop-only assembler schematics; not
|
**Shortcut recipes** (drop-only assembler schematics; not every strange
|
||||||
every strange chain gets one):
|
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
|
Consistency is checked by `tools/verify_recipes.py` — re-run it after
|
||||||
chain in the game.
|
editing recipes, ship/module materials, or visuals:
|
||||||
- `quartz → control_chip` — skips silicon on the electronics chain.
|
|
||||||
- `iron_ingot → hardened_steel` — a nicer-ratio route past the
|
|
||||||
deliberately awkward hardening step.
|
|
||||||
|
|
||||||
Item count: 3 mined + scrap + 3 smelted + 4 t2 + 5 t3 + 3 t4 (incl.
|
python dota_factory/tools/verify_recipes.py
|
||||||
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.
|
|
||||||
|
|
||||||
### Numbers — first pass
|
It verifies every consumed item has a producer, every item has a visuals
|
||||||
|
entry, flags orphaned items, and prints which items are
|
||||||
These numbers are live in the config files and verified by
|
reprocessing-only (currently exactly voidsteel).
|
||||||
`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.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user