Files
dota_factory/docs/content_design.md
Malte Langkabel f822047b4e Combat stats round 3 from arena round 2 margins
Round 2 (with the new EHP-margin logging): mirrors healthy and the
round-1 side bias was noise; drones-vs-cruisers (10%) and
mixed-vs-battlecruisers (14%) near parity; battleship over at +33%
while dreadnought under at -37%; glass-vs-armored narrowed to 11%
(watch); repair escort fair at 11%; two-to-one decisive; the 3x swarm
now cracks the station set at 13%.

Round-3 knobs: weapon_stabilizer range multiplier 1.5 -> 1.3 (range is
the strongest stat under the orbit AI, and the stabilized battleship
was the one loadout still overperforming), plus per-hull HP trims with
the 15 HP/threat anchor demoted to a prior and per-hull HP documented
as the empirical trim knob: battlecruiser 2700 -> 2500, battleship
7500 -> 7000, dreadnought/carrier 15500 -> 19000.

Action items: mirror-bias and EHP-logging items resolved (the latter
implemented upstream); new item to verify the arena end condition seen
idling in round 1 with all ships dead.

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

30 KiB
Raw Blame History

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/lrailgun_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-2 trims: drone 60, frigate 300, destroyer 550, cruiser 1500, battlecruiser 2500, battleship 7000, dreadnought/carrier 19000.
  • Armor HP ≈ 31 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 1000 HP at 32 threat (arena round 1: at 640 the all-guns destroyer beat the armored one).
  • 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 ≈ 1 HP/s per threat: repair_tool heals 12 HP × 1 Hz at 13 threat (arena round 1: at 2 HP/s-per-threat the escorted team won flawlessly — in-combat sustain effectively removes enemy DPS and must be priced like DPS, not like HP). 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 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_xslaser_cannon_s (the old 2x2 laser_cannon_s became laser_cannon_m), armor_platearmor_plates, manuvering_thrustersmaneuvering_thrusters (typo fix). Test data under bin/test/data/config intentionally still uses the old ids — it is an independent fixture set.