first full balancing round

This commit is contained in:
2026-07-08 20:27:25 +02:00
parent 6ea0655eaf
commit c6db4bf24a
8 changed files with 1421 additions and 378 deletions

View File

@@ -129,7 +129,12 @@ Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
drone hangar — carrier 1.
## Production tree
## 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.
@@ -171,6 +176,319 @@ 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).
## 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