712 lines
35 KiB
Markdown
712 lines
35 KiB
Markdown
# 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 0–1) 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 ~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.
|