Files
dota_factory/docs/content_design.md
Malte Langkabel 27c3a7b65b Combat stats round 6: combat pass converged
Round 5 (with fight durations): TTK anchor validated - mirrors run
22.7/71.2/94.6 s (drone/cruiser/battleship), most fights within
22-72 s, destroyers-vs-dreadnought the accepted 214 s outlier. All
matchups converged: mirrors 5-10%, swarm vs cruisers +15%, frigates
vs battleship +13% (dropped on its own, confirming the noise floor),
dreadnought +3%, glass vs armored +10%, two-to-one 78%, stations 44%.

Two two-round signals actioned per the convergence policy:
battlecruiser hull 2200 -> 2400 (mixed fleet won +20%/+18% twice) and
repair_amount 12 -> 9 (~0.7 HP/s per threat; escorted team held +24%
twice with all frigates surviving). Everything else frozen; further
refinement moves to real-game playtests. Removed the arena
end-condition action item (fixed upstream).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
2026-07-04 12:43:05 +02:00

653 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Content Design — Ships & Modules
First real-content iterations (June 2026). Pass 1 defined ship hull grids and
module surface masks; pass 2 defined the production tree (recipes). Stats and
threat costs in the config files are still placeholders for the balancing
pass.
## Design principle: footprint gating
Which module fits on which hull is controlled purely by geometry — no
explicit allow-lists. Each hull grid is shaped so that it physically cannot
contain the footprint of modules from a larger size class. This keeps the
rules transparent to the player ("it doesn't fit because there is no room")
and makes them trivially moddable through the config files alone.
### Module footprint ladder
| Footprint | Modules | Smallest hull that fits it |
|-----------|---------|----------------------------|
| 1x1 | laser_cannon_s, salvager, repair_tool | drone |
| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate |
| 1x3 | afterburner | frigate (eats most of it) |
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
| 2x2 | laser_cannon_m, drone_bay | cruiser |
| 3x3 | laser_cannon_l | battleship |
| 2x6 | drone_hangar | carrier (only) |
### Hull grids
`O` = buildable cell, `X` = hull structure (not buildable).
**drone (xs, 1 cell)** — exactly one 1x1 module: a small gun, a salvager, or
a repair tool. This is what makes drone roles swappable.
O
**frigate (s, 5 cells)** — plus shape. Every 1x2 placement crosses the center
cell, so at most ONE 1x2 support fits; alternatively one L-shaped weapon
modifier or one afterburner through the center line. Gun-boat with one or two
support modules, as intended.
XOX
OOO
XOX
**destroyer (s, 8 cells)** — gun deck with three turret bumps. More cells
than the frigate (more small guns), but still no 2x2 area anywhere, so medium
hardware can never be mounted.
OXOXO
OOOOO
**cruiser (m, 12 cells)** — notched corners. Fits at most two 2x2 m guns
(stacked through the middle), leaving the side cells for supports. No 3x3
area.
XOOX
OOOO
OOOO
XOOX
**battlecruiser (m, 16 cells)** — split bow with two gun cheeks, tapered
stern. Fits three 2x2 m guns — one more than the cruiser — with small support
slots left over. The bow split and stern taper prevent any 3x3 area (no l
gun) and any 2x6 area (no drone hangar).
OOXXOO
OOOOOO
XOOOOX
XXOOXX
**battleship (l, 24 cells)** — broadside hull with notched flanks on every
other row. Fits four 2x2 m guns (two per gun deck) — one more than the
battlecruiser — with bow, stern, and flank cells for supports. All 3x3
placements crowd the center columns, so at most ONE l gun fits: mounted
center it blocks every m gun mount (pure support strips remain), mounted
offset it still allows two m guns. The notched rows are never adjacent-and-
full, so no 2x6 drone hangar fits.
XOOOOX
OOOOOO
XOOOOX
OOOOOO
XOOOOX
**dreadnought (xl, 36 cells)** — the main battery deck is split into three
3x3 gun slots by structural spacer columns, so exactly three l guns fit side
by side (or m guns / supports in unused slots), plus bow/stern strips for
supports. The spacers cap every horizontal run at 5 cells, so the 2x6 drone
hangar can never fit — the carrier stays the only hangar hull.
XXXOOOOOXXX
OOOXOOOXOOO
OOOXOOOXOOO
OOOXOOOXOOO
XXOOXXXOOXX
**carrier (xl, 37 cells)** — the top flight deck (rows 01) is the only
region wide enough for the 2x6 drone hangar, and exactly one fits. The middle
deck row is broken up by elevator shafts (X cells placed so every 3-column
window hits one), which is what prevents any 3x3 l gun from ever fitting.
Lower decks hold supports and 2x2 point-defense m guns.
XOOOOOOOOX
OOOOOOOOOO
OOXOOXOOXO
XOOOOOOOOX
XXXOOOOXXX
### Verified gating matrix
Checked programmatically against the configs (all four mask rotations,
all placements) with `tools/verify_layouts.py` — re-run it after editing
layout grids or surface masks:
python dota_factory/tools/verify_layouts.py
| Footprint | drone | frigate | destroyer | cruiser | battlecruiser | battleship | dreadnought | carrier |
|-----------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 1x1 | x | x | x | x | x | x | x | x |
| 1x2 | | x | x | x | x | x | x | x |
| 1x3 | | x | x | x | x | x | x | x |
| L-shape | | x | x | x | x | x | x | x |
| 2x2 | | | | x | x | x | x | x |
| 3x3 | | | | | | x | x | |
| 2x6 | | | | | | | | x |
Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
drone hangar — carrier 1.
## Production tree (first pass — superseded)
**Superseded (July 2026):** this first-pass tree predates the rules in
`docs/progression_design.md` and will be replaced. The decisions for the
redesign are recorded in "Production tree v2 — decisions" below; the text
of this section is kept for reference until the new tree lands.
Design principle: each game phase adds exactly one new base input chain, so
factory complexity ramps alongside ship size.
| Phase | New input | How acquired | Unlocks |
|-------|-----------|--------------|---------|
| early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports |
| mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers |
| late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar |
The advanced_alloy gate is the core loop hook: capital ship production
requires fighting (salvaging scrap from kills and reprocessing it), not just
mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots
or advanced_alloy probabilistically.
Intermediate components, by tier:
- **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board
(iron + wire), building_block (iron).
- **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits
+ wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame
(titanium + steel).
- **Tier 4 (late):** reinforced_plating (steel + advanced_alloy),
capital_core (targeting_unit + drive_unit + 2 advanced_alloy).
Hulls and modules consume intermediates of their tier: early items are built
from tier-2 parts, midgame items require tier-3 parts (deeper chains, more
assemblers), capital items require tier-4 parts (and therefore combat). Hull
items are named `<ship>_hull`; module items `<module>_module`. Every item has
an `[items.*]` entry in visuals.toml; hull item outlines match the ship's
fleet color from `[ships.*]`.
Consistency is checked by `tools/verify_recipes.py` — re-run it after editing
recipes, ship/module materials, or visuals:
python dota_factory/tools/verify_recipes.py
It verifies every consumed item has a producer, every item has a visuals
entry, flags orphaned items, and prints which items are reprocessing-only
(currently exactly advanced_alloy).
## Production tree v2 — decisions (July 2026)
The old tree is discarded; the new one is designed against the rules in
`docs/progression_design.md` (ratio curve, cost ladder, cost archetypes,
refactorability). Decisions fixed so far:
### Base inputs (4) and fiction
- **iron_ore, copper_ore** — from the start, minable on every asteroid
tile. Fiction: the asteroid is an M-type (metal) body — its bulk rock
*is* ore, which is why the shipyard operation was built here at all.
- **quartz** — mid-game, minable only on geode deposit patches in
expansion territory (see the Resource deposits rules in
`progression_design.md`). Fiction: ordinary silicate dust is everywhere
and worthless; chips and optics need rare, pocket-bound optical-grade
crystal.
- **voidsteel** — late-game, obtained only by reprocessing scrap.
Fiction: battle-forged — formed when weapon plasma anneals hull metal
in the violence of ship destruction. Any wreck yields it, including the
player's own; no foundry can replicate it.
- **titanium is dropped.** Its hull-gating role moves to quartz-era
control systems ("you can smelt all the steel you want, but you cannot
steer a battlecruiser without electronics") and possibly a *quality*
steel step — e.g. a long-running hardened-steel recipe (time-heavy
archetype). Open question for the tree draft: quality step vs.
electronics-only gating; explicitly **not** sheer steel quantity alone.
### Material palette (fingerprints per family)
- **iron/steel** — structure.
- **copper** — conduction and heat: wiring, coils, heat sinks.
- **silicon family** (all derived from quartz): silicon (logic,
sensors), glass/optics (lenses, focusing crystals), ceramics (heat
shielding, insulators). Carries the non-metal variety without extra
base inputs.
- **voidsteel** — capital-tier structure and exotics.
- Deliberately skipped: carbon (mostly redundant with copper/ceramics),
plastics (drags in Factorio-style chemical chains; ceramics read more
sci-fi anyway), volatiles/ice (materials are build costs only — no
consumption mechanic to justify fuel).
### Weapons
- All current lasers are renamed to **railguns** (`laser_cannon_s/m/l`
`railgun_s/m/l`); footprints and the gating matrix are unchanged.
Implementation stays as-is (instant damage application, no projectile,
no ammunition) — the beam visual reads as a tracer round. Materials:
iron slugs, copper coils, steel rails — the starting-metal fingerprint.
- **Lasers are reserved for later** as a genuinely distinct weapon type
(e.g. once projectile/ammunition mechanics exist for other families),
arriving with quartz optics. More weapon types are planned; railguns
are simply the baseline tech that ships with v1.
### Tree structure — draft
Recipes are sketched as input lists only; quantities and durations come
in the numbers pass, tuned so every chain sums to its threat-ladder
value and follows the ratio curve (t1 nice → t4 strange). Glass/optics
are cut from v1 — their only consumers would be lasers, which are
deferred; the silicon family ships as silicon + ceramics.
**Mined (miner):** `iron_ore`, `copper_ore` (every tile), `quartz`
(geode deposits in expansion territory).
**Smelted (smelter — exactly one recipe per input item):**
| output | input | ratio class |
|---|---|---|
| iron_ingot | iron_ore | nice (1:1 or 1:2) |
| copper_ingot | copper_ore | nice |
| silicon | quartz | mid entry |
| iron_ingot | scrap | the safe, boring scrap sink |
**Reprocessing pool (scrap):** `iron_ingot`, `copper_ingot`, `silicon`,
`voidsteel` — the only source of voidsteel. Weights authored for the
fully unlocked pool state.
**Tier 2 — early intermediates (clean ratios, ~2:3):**
| item | inputs | role |
|---|---|---|
| steel_plate | iron_ingot | structure backbone, highest volume |
| copper_wire | copper_ingot | conductors |
| copper_coil | copper_wire | electromagnets: railguns, thrusters |
| building_block | steel_plate | depth-3 chain = the doubling-time knob |
**Tier 3 — mid intermediates (strange ratios begin, need quartz):**
| item | inputs | role |
|---|---|---|
| control_chip | silicon + copper_wire | electronics gate for m+ hulls |
| capacitor_bank | copper_coil + silicon | power for railgun m/l |
| hardened_steel | steel_plate (long cycle) | quality gate for m+ hulls; time-heavy |
| ceramic_plate | quartz | heat shielding: drives, l guns, capitals |
| drive_unit | steel_plate + copper_coil + control_chip | propulsion for m+ hulls |
**Tier 4 — late intermediates (need voidsteel):**
| item | inputs | role |
|---|---|---|
| voidsteel_plate | voidsteel + hardened_steel | capital structure |
| capital_core | voidsteel + capacitor_bank + control_chip | capital heart |
**Hull items** (`<ship>_hull`, assembler-made; the shipyard consumes the
hull item plus module materials). The m+ hull gate is resolved as
**both** of the open-question options: hardened_steel (quality steel, a
deliberately long-running recipe — the time-heavy step) *and*
control_chip (electronics):
| hull | inputs |
|---|---|
| drone_hull | steel_plate |
| frigate_hull | steel_plate + copper_wire |
| destroyer_hull | steel_plate + copper_coil |
| cruiser_hull | hardened_steel + control_chip |
| battlecruiser_hull | hardened_steel + control_chip + drive_unit |
| battleship_hull | voidsteel_plate + drive_unit + control_chip |
| dreadnought_hull | voidsteel_plate + capital_core + drive_unit |
| carrier_hull | voidsteel_plate + capital_core + drive_unit |
**Module items** (`<module>_module`, assembler-made prefabs — kept as
items so shipyard belt inputs stay simple and module production can be
stockpiled):
| module | inputs | archetype |
|---|---|---|
| railgun_s | steel_plate + copper_coil | balanced |
| salvager | steel_plate + copper_wire | balanced |
| repair_tool | steel_plate + copper_wire | balanced |
| armor_plates | steel_plate (many) | material-heavy, fast |
| maneuvering_thrusters | steel_plate + copper_coil | balanced |
| sensor_booster | copper_wire + copper_coil | lean (an antenna, no chip) |
| afterburner | copper_coil + steel_plate | balanced |
| weapon_stabilizer | steel_plate + copper_coil | balanced |
| weapon_primer | capacitor_bank + copper_coil | mid; time-heavy |
| weapon_upgrade | control_chip + copper_coil | mid; time-heavy |
| railgun_m | capacitor_bank + steel_plate + copper_coil | mid |
| drone_bay | control_chip + steel_plate + copper_coil | mid |
| railgun_l | capacitor_bank + hardened_steel + ceramic_plate | late |
| drone_hangar | voidsteel_plate + control_chip + drive_unit | late (carrier only) |
Refactorability check (the default technique holds): railgun_s → m
introduces capacitor_bank, built from a subset of the small gun's
inputs (copper_coil) plus the new base resource (silicon); the m gun
otherwise reuses the small gun's inputs. Hulls likewise: cruiser adds
hardening (fed by the existing steel line) and chips (fed by the new
quartz territory) without touching the iron/copper core.
**Shortcut recipe candidates** (drop-only assembler schematics; not
every strange chain gets one):
- `iron_ore → steel_plate` — skips the ingot step on the highest-volume
chain in the game.
- `quartz → control_chip` — skips silicon on the electronics chain.
- `iron_ingot → hardened_steel` — a nicer-ratio route past the
deliberately awkward hardening step.
Item count: 3 mined + scrap + 3 smelted + 4 t2 + 5 t3 + 3 t4 (incl.
voidsteel) + 8 hulls + 14 modules ≈ 41 item types — same scale as the
first-pass tree. `verify_recipes.py` must be re-run once this lands in
recipes.toml; the numbers pass should add a threat-report tool that
prints per-item threat values and producer:consumer ratio tables.
### Numbers — first pass
These numbers are live in the config files and verified by
`tools/threat_report.py`; quantities and durations are tuned so fitted
ships land on the threat-cost ladder and the ratio curve is realized.
The typical loadouts are geometry-validated against the hull grids and
configured as each ship's `default_modules`: the L-shaped weapon
modifiers turned out not to fit alongside the full gun complement on
the battlecruiser and dreadnought, so their loadouts use armor and
small-railgun fillers instead — which moved all three heavy ships
closer to their ladder targets.
**Economy constants:** `scrap_per_threat = 0.25` (1 scrap per 4 threat
destroyed — a cruiser kill drops ~59 scrap). Reprocessing: 4 scrap per
cycle, 4 s, full-pool weights iron_ingot 30 / copper_ingot 30 /
silicon 20 / voidsteel 20 → threat(voidsteel) = (4·4 + 4)/0.2 = 100.
Scrap smelting: 1 scrap → 1 iron_ingot, 1 s — deliberately
value-losing (4 threat of scrap becomes a 2-threat ingot); reprocessing
is the value-preserving path.
**Recipes** (dur in seconds; threat is per output item):
| item | recipe | dur | out | threat |
|---|---|---|---|---|
| iron_ore / copper_ore | miner | 1 | 1 | 1 |
| quartz | miner (deposit) | 2 | 1 | 2 |
| iron_ingot | 1 iron_ore | 1 | 1 | 2 |
| copper_ingot | 1 copper_ore | 1 | 1 | 2 |
| silicon | 1 quartz | 2 | 1 | 4 |
| steel_plate | 2 iron_ingot | 3 | 1 | 7 |
| copper_wire | 1 copper_ingot | 1 | 2 | 1.5 |
| copper_coil | 2 copper_wire | 1.5 | 1 | 4.5 |
| building_block | 2 steel_plate | 2 | 4 | 4 |
| control_chip | 1 silicon + 2 copper_wire | 5 | 1 | 12 |
| capacitor_bank | 2 copper_coil + 1 silicon | 5 | 1 | 18 |
| hardened_steel | 3 steel_plate | 12 | 1 | 33 |
| ceramic_plate | 2 quartz | 4 | 1 | 8 |
| drive_unit | 2 steel_plate + 2 copper_coil + 1 control_chip | 8 | 1 | 43 |
| voidsteel_plate | 1 voidsteel + 1 hardened_steel | 8 | 1 | 141 |
| capital_core | 2 voidsteel + 1 capacitor_bank + 1 control_chip | 10 | 1 | 240 |
**Module prefabs** (contribution = item threat + module production
time):
| module | recipe | dur | mod. time | contribution |
|---|---|---|---|---|
| railgun_s | 1 copper_coil | 1 | 1 | 6.5 |
| salvager | 1 steel_plate + 2 copper_wire | 2 | 1 | 13 |
| repair_tool | 1 steel_plate + 2 copper_wire | 2 | 1 | 13 |
| armor_plates | 4 steel_plate | 3 | 1 | 32 |
| maneuvering_thrusters | 1 steel_plate + 1 copper_coil | 2 | 1 | 14.5 |
| sensor_booster | 2 copper_wire + 1 copper_coil | 2 | 1 | 10.5 |
| afterburner | 2 copper_coil + 1 steel_plate | 3 | 1 | 20 |
| weapon_stabilizer | 1 steel_plate + 1 copper_coil | 2 | 1 | 14.5 |
| weapon_primer | 1 capacitor_bank + 1 copper_coil | 4 | 2 | 28.5 |
| weapon_upgrade | 1 control_chip + 1 copper_coil | 4 | 2 | 22.5 |
| railgun_m | 1 capacitor_bank + 2 steel_plate + 1 copper_coil | 4 | 3 | 43.5 |
| drone_bay | 1 control_chip + 2 steel_plate + 1 copper_coil | 4 | 3 | 37.5 |
| railgun_l | 1 capacitor_bank + 2 hardened_steel + 1 ceramic_plate | 6 | 4 | 102 |
| drone_hangar | 1 voidsteel_plate + 2 control_chip + 1 drive_unit | 10 | 6 | 224 |
**Ships** (fitted = hull item + ship base time + typical loadout; the
typical loadouts double as the `default_modules` for enemy waves):
| ship | hull recipe | dur | base | typical loadout | fitted (target) |
|---|---|---|---|---|---|
| drone | 1 iron_ingot | 1 | 1 | railgun_s | 10 (10) |
| frigate | 2 steel_plate + 1 copper_wire | 2 | 2 | 2× railgun_s, maneuvering_thrusters | 47 (40) |
| destroyer | 3 steel_plate + 2 copper_coil | 4 | 3 | 3× railgun_s, armor_plates, sensor_booster | 99 (80) |
| cruiser | 2 hardened_steel + 2 control_chip | 6 | 4 | 2× railgun_m, armor_plates, maneuvering_thrusters | 234 (200) |
| battlecruiser | 3 hardened_steel + 2 control_chip + 1 drive_unit | 8 | 5 | 3× railgun_m, armor_plates, 2× railgun_s | 355 (350) |
| battleship | 3 voidsteel_plate + 1 drive_unit + 2 control_chip | 10 | 6 | railgun_l, 2× railgun_m, weapon_stabilizer, 2× railgun_s | 723 (700) |
| dreadnought | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | 3× railgun_l, 4× armor_plates, railgun_s | 1492 (1500) |
| carrier | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | drone_hangar, 2× railgun_m, 2× armor_plates, sensor_booster | 1436 (1500) |
**Checks:**
- *Ratio curve realized:* t1 all 1:1 (miner:smelter); t2 clean 2:3
(ingot→plate, wire→coil); t3 strange — 2:5 (silicon→chip), 3:5
(coil→capacitor), 3:4 (plate→hardened, plate→drive); t4 inverted 3:2
(hardened→voidsteel_plate).
- *Belt feasibility:* worst input demand is 1.33 items/s (wire→coil,
plate→armor) — under the ~2/s single-belt cap everywhere; no
accidental multi-belt recipes.
- *Block economy:* building_block = 4 threat ⇒ for the 4-minute
doubling at 30% capacity, the average building must cost ≈ 18 blocks
(0.3 × 240 / 4). buildings.toml costs should be set around that mean
(belts cheap, producers ~2030).
- *Small-end deviation:* frigatecruiser land 624% 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 3060 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
(4050% of the winner survives) but team 1 won all three mirrors —
possible sim-side bias, under investigation (progression_design.md
action items 67). 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 2272 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 510%, 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 03%; 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 (17%);
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 57% 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 75150 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.
## 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 2024. Losing runs end earlier.
2. **Phase boundaries** — early = cycles 15 (iron/copper, small hulls),
mid = cycles 614 (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.