Write the v2 production tree into the config files

recipes.toml: full v2 rewrite - iron/copper/quartz mining, smelting
incl. the value-losing scrap sink, reprocessing (4 scrap / 4 s,
voidsteel at 20% of the full pool), tiered intermediates, hulls,
module prefabs, and the three drop-only shortcut recipes.

modules.toml: lasers renamed to railguns (implementation unchanged),
prefab materials and numbers-pass production times; combat stats stay
placeholders for the arena pass.

ships.toml: hull-item materials, numbers-pass base production times,
and geometry-validated default_modules loadouts for every ship (waves
now spawn armed).

visuals.toml: item entries for the new palette (quartz, silicon,
copper_coil, control_chip, capacitor_bank, hardened_steel,
ceramic_plate, voidsteel, voidsteel_plate, railgun modules); retired
titanium/alloy/laser items removed.

world.toml: scrap_per_threat = 0.25 per the numbers pass.

threat_report.py: commit an item's threat only once every eligible
recipe is computable - the previous first-resolved-wins behavior let
shortcut recipes underprice items (same flaw exists in
ThreatCostCalculator, recorded as action item 9). Verified: all
default_modules placements valid, verify_recipes and verify_layouts
pass, and the report reproduces the numbers-pass tables exactly
(fitted: 10.5/47/99/233.5/354.5/722.5/1491.5/1436.5). Doc tables
updated for the three geometry-corrected loadouts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
This commit is contained in:
2026-07-03 17:05:19 +02:00
parent b9e70ba83a
commit d889b79658
8 changed files with 487 additions and 400 deletions

View File

@@ -1,26 +1,29 @@
# modules.toml # modules.toml
# #
# First real-content iteration: module ids and surface masks are the designed # Production tree v2: all weapons are railguns for now — the implementation
# content; stats, materials, and threat costs are placeholders until the # (instant damage, no projectile, no ammunition) stays as-is and the beam
# recipe and balancing passes. # visual reads as a tracer round. Lasers are reserved for a future distinct
# weapon type (see docs/content_design.md, "Production tree v2 — Weapons").
# Combat stats are placeholders until the arena balancing pass;
# production_time_seconds values come from the numbers pass.
# #
# Unlock progression is mostly disabled (unlock_at_station_level = -1) pending # Unlock progression is mostly disabled (unlock_at_station_level = -1) pending
# the balancing pass. The laser cannons are the exception: laser_cannon_m is # the pacing pass. The railguns are the exception: railgun_m is gated to
# gated to station level 1, and laser_cannon_l requires laser_cannon_m to be # station level 1, and railgun_l requires railgun_m to be unlocked first
# unlocked first (unlock_requires) — a demonstration of the prerequisite chain. # (unlock_requires) — a demonstration of the prerequisite chain.
# #
# Surface mask footprint ladder — footprints gate which hulls can mount a # Surface mask footprint ladder — footprints gate which hulls can mount a
# module, purely through geometry (see ships.toml for the matching hull # module, purely through geometry (see ships.toml for the matching hull
# grids): # grids):
# #
# 1x1 laser_cannon_s, salvager, repair_tool fits every hull, incl. drones # 1x1 railgun_s, salvager, repair_tool fits every hull, incl. drones
# 1x2 maneuvering_thrusters, sensor_booster, # 1x2 maneuvering_thrusters, sensor_booster,
# armor_plates frigate and up # armor_plates frigate and up
# 1x3 afterburner frigate and up (eats most of a frigate) # 1x3 afterburner frigate and up (eats most of a frigate)
# L-shape weapon_stabilizer, weapon_primer, # L-shape weapon_stabilizer, weapon_primer,
# weapon_upgrade frigate and up # weapon_upgrade frigate and up
# 2x2 laser_cannon_m, drone_bay cruiser and up (no 2x2 area on s hulls) # 2x2 railgun_m, drone_bay cruiser and up (no 2x2 area on s hulls)
# 3x3 laser_cannon_l battleship and up (no 3x3 area on m hulls) # 3x3 railgun_l battleship and up (no 3x3 area on m hulls)
# 2x6 drone_hangar carrier only # 2x6 drone_hangar carrier only
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -28,13 +31,13 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[[module]] [[module]]
id = "laser_cannon_s" id = "railgun_s"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["O"] surface_mask = ["O"]
materials = [{item = "laser_cannon_s_module", amount = 1}] materials = [{item = "railgun_s_module", amount = 1}]
production_time_seconds = 0.5 production_time_seconds = 1
fill_color = "#FF8040" fill_color = "#FF8040"
glyph = "Ls" glyph = "Rs"
[module.weapon] [module.weapon]
damage = 2 damage = 2
@@ -43,15 +46,15 @@ attack_rate_hz = 2.0
[[module]] [[module]]
id = "laser_cannon_m" id = "railgun_m"
unlock_at_station_level = 1 unlock_at_station_level = 1
surface_mask = [ surface_mask = [
"OO", "OO",
"OO"] "OO"]
materials = [{item = "laser_cannon_m_module", amount = 1}] materials = [{item = "railgun_m_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 3
fill_color = "#FF8040" fill_color = "#FF8040"
glyph = "Lm" glyph = "Rm"
[module.weapon] [module.weapon]
damage = 10 damage = 10
@@ -60,17 +63,17 @@ attack_rate_hz = 1.5
[[module]] [[module]]
id = "laser_cannon_l" id = "railgun_l"
unlock_at_station_level = 1 unlock_at_station_level = 1
unlock_requires = ["laser_cannon_m"] unlock_requires = ["railgun_m"]
surface_mask = [ surface_mask = [
"OOO", "OOO",
"OOO", "OOO",
"OOO"] "OOO"]
materials = [{item = "laser_cannon_l_module", amount = 1}] materials = [{item = "railgun_l_module", amount = 1}]
production_time_seconds = 8 production_time_seconds = 4
fill_color = "#FF8040" fill_color = "#FF8040"
glyph = "Ll" glyph = "Rl"
[module.weapon] [module.weapon]
damage = 40 damage = 40
@@ -86,7 +89,7 @@ id = "salvager"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["O"] surface_mask = ["O"]
materials = [{item = "salvager_module", amount = 1}] materials = [{item = "salvager_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 1
fill_color = "#AACC44" fill_color = "#AACC44"
glyph = "Sv" glyph = "Sv"
@@ -101,7 +104,7 @@ id = "repair_tool"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["O"] surface_mask = ["O"]
materials = [{item = "repair_tool_module", amount = 1}] materials = [{item = "repair_tool_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 1
fill_color = "#66CCFF" fill_color = "#66CCFF"
glyph = "Rp" glyph = "Rp"
@@ -119,7 +122,7 @@ id = "afterburner"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["OOO"] surface_mask = ["OOO"]
materials = [{item = "afterburner_module", amount = 1}] materials = [{item = "afterburner_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 1
fill_color = "#40A0FF" fill_color = "#40A0FF"
glyph = "Ab" glyph = "Ab"
@@ -133,7 +136,7 @@ id = "maneuvering_thrusters"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "maneuvering_thrusters_module", amount = 1}] materials = [{item = "maneuvering_thrusters_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 1
fill_color = "#40A0FF" fill_color = "#40A0FF"
glyph = "Mt" glyph = "Mt"
@@ -150,7 +153,7 @@ id = "armor_plates"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "armor_plates_module", amount = 1}] materials = [{item = "armor_plates_module", amount = 1}]
production_time_seconds = 3 production_time_seconds = 1
fill_color = "#808080" fill_color = "#808080"
glyph = "A" glyph = "A"
@@ -163,7 +166,7 @@ id = "sensor_booster"
unlock_at_station_level = -1 unlock_at_station_level = -1
surface_mask = ["OO"] surface_mask = ["OO"]
materials = [{item = "sensor_booster_module", amount = 1}] materials = [{item = "sensor_booster_module", amount = 1}]
production_time_seconds = 2 production_time_seconds = 1
fill_color = "#40A0FF" fill_color = "#40A0FF"
glyph = "S" glyph = "S"
@@ -182,7 +185,7 @@ surface_mask = [
"OX", "OX",
] ]
materials = [{item = "weapon_upgrade_module", amount = 1}] materials = [{item = "weapon_upgrade_module", amount = 1}]
production_time_seconds = 4 production_time_seconds = 2
fill_color = "#FF4040" fill_color = "#FF4040"
glyph = "Wu" glyph = "Wu"
@@ -198,7 +201,7 @@ surface_mask = [
"OX", "OX",
] ]
materials = [{item = "weapon_primer_module", amount = 1}] materials = [{item = "weapon_primer_module", amount = 1}]
production_time_seconds = 4 production_time_seconds = 2
fill_color = "#FF4040" fill_color = "#FF4040"
glyph = "Wp" glyph = "Wp"
@@ -214,7 +217,7 @@ surface_mask = [
"OX", "OX",
] ]
materials = [{item = "weapon_stabilizer_module", amount = 1}] materials = [{item = "weapon_stabilizer_module", amount = 1}]
production_time_seconds = 4 production_time_seconds = 1
fill_color = "#FF4040" fill_color = "#FF4040"
glyph = "Ws" glyph = "Ws"
@@ -236,7 +239,7 @@ surface_mask = [
"OO", "OO",
"OO"] "OO"]
materials = [{item = "drone_bay_module", amount = 1}] materials = [{item = "drone_bay_module", amount = 1}]
production_time_seconds = 5 production_time_seconds = 3
fill_color = "#CC66FF" fill_color = "#CC66FF"
glyph = "Db" glyph = "Db"
@@ -248,6 +251,6 @@ surface_mask = [
"OOOOOO", "OOOOOO",
"OOOOOO"] "OOOOOO"]
materials = [{item = "drone_hangar_module", amount = 1}] materials = [{item = "drone_hangar_module", amount = 1}]
production_time_seconds = 20 production_time_seconds = 6
fill_color = "#9933CC" fill_color = "#9933CC"
glyph = "Dh" glyph = "Dh"

View File

@@ -1,26 +1,26 @@
# recipes.toml # recipes.toml
# #
# First real-content iteration of the production tree. Quantities and # Production tree v2 (see docs/content_design.md, "Production tree v2" and
# durations are a first guess; the balancing pass will tune them and assign # "Numbers — first pass"). Quantities and durations are tuned so that every
# real unlock_at_station_level values. Almost everything is unlocked for now so # fitted ship lands on the threat-cost ladder and the ratio curve is
# the full tree is testable; the only gated recipe schematics are the medium and # realized: tier 1 ratios are 1:1, tier 2 ratios are 2:3, tier 3+ ratios
# large laser-cannon item recipes, which demonstrate the unlock_requires # are deliberately strange.
# prerequisite chain (large requires medium — see modules.toml / ships.toml).
# #
# Input chain per game phase — each phase adds exactly one new base input: # Input chain per game phase — each phase transition adds exactly one new
# base input:
# #
# early iron_ore + copper_ore -> ingots -> copper_wire, steel_plate, # early iron_ore + copper_ore minable on every asteroid tile (the
# circuit_board # asteroid is an M-type body — its bulk
# mid + titanium_ore -> titanium_frame; assembler-made # rock IS ore)
# mechanical_parts, targeting_unit, # mid + quartz geode deposits in expansion territory
# drive_unit # (deposit gating pending — see action
# late + advanced_alloy -> reinforced_plating, capital_core. # item 5 in docs/progression_design.md;
# advanced_alloy CANNOT be mined; it only # until then quartz mines anywhere)
# comes from reprocessing salvaged scrap, # late + voidsteel battle-forged: ONLY from reprocessing
# so capital production requires combat. # salvaged scrap, so capital production
# requires combat
# #
# Run tools/verify_recipes.py after editing to check that every consumed # Run tools/verify_recipes.py and tools/threat_report.py after editing.
# item has a producer and every item has a visuals.toml entry.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Mining (tier 0) # Mining (tier 0)
@@ -38,405 +38,406 @@ id = "mine_copper_ore"
building = "miner" building = "miner"
inputs = [] inputs = []
outputs = [{item = "copper_ore", amount = 1}] outputs = [{item = "copper_ore", amount = 1}]
duration_seconds = 1.5 duration_seconds = 1.0
# Titanium is the midgame ore: mined three times slower than iron.
[[recipe]] [[recipe]]
id = "mine_titanium_ore" id = "mine_quartz"
building = "miner" building = "miner"
inputs = [] inputs = []
outputs = [{item = "titanium_ore", amount = 1}] outputs = [{item = "quartz", amount = 1}]
duration_seconds = 3.0 duration_seconds = 2.0
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Smelting (tier 1) # Smelting (tier 1) — one recipe per input item; ratios are 1:1 with miners.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[[recipe]] [[recipe]]
id = "iron_ingot" id = "iron_ingot"
building = "smelter" building = "smelter"
inputs = [{item = "iron_ore", amount = 2}] inputs = [{item = "iron_ore", amount = 1}]
outputs = [{item = "iron_ingot", amount = 1}] outputs = [{item = "iron_ingot", amount = 1}]
duration_seconds = 2.0 duration_seconds = 1.0
[[recipe]] [[recipe]]
id = "copper_ingot" id = "copper_ingot"
building = "smelter" building = "smelter"
inputs = [{item = "copper_ore", amount = 2}] inputs = [{item = "copper_ore", amount = 1}]
outputs = [{item = "copper_ingot", amount = 1}] outputs = [{item = "copper_ingot", amount = 1}]
duration_seconds = 2.5 duration_seconds = 1.0
[[recipe]] [[recipe]]
id = "titanium_ingot" id = "silicon"
building = "smelter" building = "smelter"
inputs = [{item = "titanium_ore", amount = 3}] inputs = [{item = "quartz", amount = 1}]
outputs = [{item = "titanium_ingot", amount = 1}] outputs = [{item = "silicon", amount = 1}]
duration_seconds = 4.0 duration_seconds = 2.0
# Scrap smelting: the safe, boring sink. Deliberately value-losing (4 threat
# of scrap becomes a 2-threat ingot) — reprocessing is the value-preserving
# path.
[[recipe]]
id = "scrap_smelting"
building = "smelter"
inputs = [{item = "scrap", amount = 1}]
outputs = [{item = "iron_ingot", amount = 1}]
duration_seconds = 1.0
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Reprocessing # Reprocessing — the only source of voidsteel (battle-forged; formed when
# # weapon plasma anneals hull metal in the violence of ship destruction).
# The only source of advanced_alloy: salvaged scrap from destroyed ships. # Weights are authored for the fully unlocked pool state; the pool
# renormalizes over implicitly unlocked items early game.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[[recipe]] [[recipe]]
id = "reprocessing_cycle" id = "reprocessing_cycle"
building = "reprocessing_plant" building = "reprocessing_plant"
inputs = [{item = "scrap", amount = 5}] inputs = [{item = "scrap", amount = 4}]
duration_seconds = 3.0 duration_seconds = 4.0
[[recipe.outputs]] [[recipe.outputs]]
item = "iron_ingot" item = "iron_ingot"
amount = 2 amount = 1
probability = 0.45 probability = 0.3
[[recipe.outputs]] [[recipe.outputs]]
item = "copper_ingot" item = "copper_ingot"
amount = 1 amount = 1
probability = 0.25 probability = 0.3
[[recipe.outputs]] [[recipe.outputs]]
item = "titanium_ingot" item = "silicon"
amount = 1 amount = 1
probability = 0.15 probability = 0.2
[[recipe.outputs]] [[recipe.outputs]]
item = "advanced_alloy" item = "voidsteel"
amount = 1 amount = 1
probability = 0.15 probability = 0.2
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Basic components (tier 2, early game) # Tier 2 early intermediates (clean ratios, ~2:3)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[[recipe]]
id = "copper_wire"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "copper_ingot", amount = 1}]
outputs = [{item = "copper_wire", amount = 2}]
duration_seconds = 1.5
[[recipe]] [[recipe]]
id = "steel_plate" id = "steel_plate"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [{item = "iron_ingot", amount = 2}] inputs = [{item = "iron_ingot", amount = 2}]
outputs = [{item = "steel_plate", amount = 1}] outputs = [{item = "steel_plate", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "copper_wire"
building = "assembler"
inputs = [{item = "copper_ingot", amount = 1}]
outputs = [{item = "copper_wire", amount = 2}]
duration_seconds = 1.0
[[recipe]]
id = "copper_coil"
building = "assembler"
inputs = [{item = "copper_wire", amount = 2}]
outputs = [{item = "copper_coil", amount = 1}]
duration_seconds = 1.5
# Depth-3 chain (ore -> ingot -> plate -> block) is the factory's
# doubling-time knob; see the block economy rules in progression_design.md.
[[recipe]]
id = "building_block"
building = "assembler"
inputs = [{item = "steel_plate", amount = 2}]
outputs = [{item = "building_block", amount = 4}]
duration_seconds = 2.0
# -----------------------------------------------------------------------------
# Tier 3 — mid intermediates (strange ratios begin; need quartz)
# -----------------------------------------------------------------------------
[[recipe]]
id = "control_chip"
building = "assembler"
inputs = [{item = "silicon", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "control_chip", amount = 1}]
duration_seconds = 5.0
[[recipe]]
id = "capacitor_bank"
building = "assembler"
inputs = [{item = "copper_coil", amount = 2}, {item = "silicon", amount = 1}]
outputs = [{item = "capacitor_bank", amount = 1}]
duration_seconds = 5.0
# The quality gate for m+ hulls: a deliberately long-running recipe
# (time-heavy archetype).
[[recipe]]
id = "hardened_steel"
building = "assembler"
inputs = [{item = "steel_plate", amount = 3}]
outputs = [{item = "hardened_steel", amount = 1}]
duration_seconds = 12.0
[[recipe]]
id = "ceramic_plate"
building = "assembler"
inputs = [{item = "quartz", amount = 2}]
outputs = [{item = "ceramic_plate", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "drive_unit"
building = "assembler"
inputs = [
{item = "steel_plate", amount = 2},
{item = "copper_coil", amount = 2},
{item = "control_chip", amount = 1},
]
outputs = [{item = "drive_unit", amount = 1}]
duration_seconds = 8.0
# -----------------------------------------------------------------------------
# Tier 4 — late intermediates (need voidsteel)
# -----------------------------------------------------------------------------
[[recipe]]
id = "voidsteel_plate"
building = "assembler"
inputs = [{item = "voidsteel", amount = 1}, {item = "hardened_steel", amount = 1}]
outputs = [{item = "voidsteel_plate", amount = 1}]
duration_seconds = 8.0
[[recipe]]
id = "capital_core"
building = "assembler"
inputs = [
{item = "voidsteel", amount = 2},
{item = "capacitor_bank", amount = 1},
{item = "control_chip", amount = 1},
]
outputs = [{item = "capital_core", amount = 1}]
duration_seconds = 10.0
# -----------------------------------------------------------------------------
# Shortcut recipes — drop-only assembler recipe schematics
# (unlock_at_station_level >= 0). Pure rewards: item threat stays defined by
# the base (expensive) path via the max rule, so shortcuts give real factory
# efficiency without shifting any balance.
# -----------------------------------------------------------------------------
[[recipe]]
id = "shortcut_steel_plate"
unlock_at_station_level = 1
building = "assembler"
inputs = [{item = "iron_ore", amount = 3}]
outputs = [{item = "steel_plate", amount = 1}]
duration_seconds = 2.0 duration_seconds = 2.0
[[recipe]] [[recipe]]
id = "circuit_board" id = "shortcut_control_chip"
unlock_at_station_level = -1 unlock_at_station_level = 2
building = "assembler" building = "assembler"
inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_wire", amount = 2}] inputs = [{item = "quartz", amount = 2}]
outputs = [{item = "circuit_board", amount = 1}] outputs = [{item = "control_chip", amount = 1}]
duration_seconds = 2.0 duration_seconds = 4.0
[[recipe]] [[recipe]]
id = "building_blocks" id = "shortcut_hardened_steel"
unlock_at_station_level = -1 unlock_at_station_level = 2
building = "assembler" building = "assembler"
inputs = [{item = "iron_ingot", amount = 4}] inputs = [{item = "iron_ingot", amount = 4}]
outputs = [{item = "building_block", amount = 10}] outputs = [{item = "hardened_steel", amount = 1}]
duration_seconds = 4.0
# -----------------------------------------------------------------------------
# Advanced components (tier 3, midgame)
# -----------------------------------------------------------------------------
[[recipe]]
id = "mechanical_parts"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "iron_ingot", amount = 1}]
outputs = [{item = "mechanical_parts", amount = 2}]
duration_seconds = 2.5
[[recipe]]
id = "targeting_unit"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
outputs = [{item = "targeting_unit", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "drive_unit"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "steel_plate", amount = 1},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "drive_unit", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "titanium_frame"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "titanium_ingot", amount = 2}, {item = "steel_plate", amount = 1}]
outputs = [{item = "titanium_frame", amount = 1}]
duration_seconds = 4.0
# -----------------------------------------------------------------------------
# Capital components (tier 4, lategame — gated on advanced_alloy)
# -----------------------------------------------------------------------------
[[recipe]]
id = "reinforced_plating"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 2}, {item = "advanced_alloy", amount = 1}]
outputs = [{item = "reinforced_plating", amount = 1}]
duration_seconds = 5.0
[[recipe]]
id = "capital_core"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "targeting_unit", amount = 1},
{item = "drive_unit", amount = 1},
{item = "advanced_alloy", amount = 2},
]
outputs = [{item = "capital_core", amount = 1}]
duration_seconds = 8.0 duration_seconds = 8.0
# -----------------------------------------------------------------------------
# Module items — early game
# -----------------------------------------------------------------------------
[[recipe]]
id = "laser_cannon_s_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "iron_ingot", amount = 2}, {item = "circuit_board", amount = 1}]
outputs = [{item = "laser_cannon_s_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "salvager_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "circuit_board", amount = 1}]
outputs = [{item = "salvager_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "repair_tool_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
outputs = [{item = "repair_tool_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "armor_plates_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "steel_plate", amount = 2}]
outputs = [{item = "armor_plates_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "sensor_booster_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "circuit_board", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "sensor_booster_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "maneuvering_thrusters_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "mechanical_parts", amount = 1}, {item = "copper_wire", amount = 1}]
outputs = [{item = "maneuvering_thrusters_module", amount = 1}]
duration_seconds = 3.0
# -----------------------------------------------------------------------------
# Module items — midgame
# -----------------------------------------------------------------------------
[[recipe]]
id = "afterburner_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "drive_unit", amount = 1}, {item = "steel_plate", amount = 1}]
outputs = [{item = "afterburner_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_upgrade_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "steel_plate", amount = 1}]
outputs = [{item = "weapon_upgrade_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_primer_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "weapon_primer_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_stabilizer_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "mechanical_parts", amount = 1}]
outputs = [{item = "weapon_stabilizer_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "laser_cannon_m_module"
unlock_at_station_level = 1
building = "assembler"
inputs = [{item = "targeting_unit", amount = 1}, {item = "titanium_frame", amount = 1}]
outputs = [{item = "laser_cannon_m_module", amount = 1}]
duration_seconds = 6.0
[[recipe]]
id = "drone_bay_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "titanium_frame", amount = 1},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "drone_bay_module", amount = 1}]
duration_seconds = 6.0
# -----------------------------------------------------------------------------
# Module items — lategame
# -----------------------------------------------------------------------------
[[recipe]]
id = "laser_cannon_l_module"
unlock_at_station_level = 1
unlock_requires = ["laser_cannon_m_module"]
building = "assembler"
inputs = [
{item = "targeting_unit", amount = 2},
{item = "reinforced_plating", amount = 2},
{item = "titanium_frame", amount = 1},
]
outputs = [{item = "laser_cannon_l_module", amount = 1}]
duration_seconds = 12.0
[[recipe]]
id = "drone_hangar_module"
unlock_at_station_level = -1
building = "assembler"
inputs = [
{item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 2},
{item = "reinforced_plating", amount = 2},
]
outputs = [{item = "drone_hangar_module", amount = 1}]
duration_seconds = 20.0
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Ship hulls # Ship hulls
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
[[recipe]] [[recipe]]
id = "drone_hull" id = "drone_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 1}] inputs = [{item = "iron_ingot", amount = 1}]
outputs = [{item = "drone_hull", amount = 1}] outputs = [{item = "drone_hull", amount = 1}]
duration_seconds = 4.0 duration_seconds = 1.0
[[recipe]] [[recipe]]
id = "frigate_hull" id = "frigate_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [{item = "steel_plate", amount = 2}, {item = "copper_wire", amount = 1}]
{item = "steel_plate", amount = 3},
{item = "mechanical_parts", amount = 1},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "frigate_hull", amount = 1}] outputs = [{item = "frigate_hull", amount = 1}]
duration_seconds = 8.0 duration_seconds = 2.0
[[recipe]] [[recipe]]
id = "destroyer_hull" id = "destroyer_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [{item = "steel_plate", amount = 3}, {item = "copper_coil", amount = 2}]
{item = "steel_plate", amount = 5},
{item = "mechanical_parts", amount = 2},
{item = "circuit_board", amount = 1},
]
outputs = [{item = "destroyer_hull", amount = 1}] outputs = [{item = "destroyer_hull", amount = 1}]
duration_seconds = 10.0 duration_seconds = 4.0
[[recipe]] [[recipe]]
id = "cruiser_hull" id = "cruiser_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [{item = "hardened_steel", amount = 2}, {item = "control_chip", amount = 2}]
{item = "titanium_frame", amount = 2},
{item = "steel_plate", amount = 4},
{item = "drive_unit", amount = 1},
]
outputs = [{item = "cruiser_hull", amount = 1}] outputs = [{item = "cruiser_hull", amount = 1}]
duration_seconds = 15.0 duration_seconds = 6.0
[[recipe]] [[recipe]]
id = "battlecruiser_hull" id = "battlecruiser_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [
{item = "titanium_frame", amount = 3}, {item = "hardened_steel", amount = 3},
{item = "steel_plate", amount = 6}, {item = "control_chip", amount = 2},
{item = "drive_unit", amount = 1}, {item = "drive_unit", amount = 1},
{item = "targeting_unit", amount = 1},
] ]
outputs = [{item = "battlecruiser_hull", amount = 1}] outputs = [{item = "battlecruiser_hull", amount = 1}]
duration_seconds = 20.0 duration_seconds = 8.0
[[recipe]] [[recipe]]
id = "battleship_hull" id = "battleship_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [
{item = "titanium_frame", amount = 4}, {item = "voidsteel_plate", amount = 3},
{item = "reinforced_plating", amount = 2}, {item = "drive_unit", amount = 1},
{item = "drive_unit", amount = 2}, {item = "control_chip", amount = 2},
] ]
outputs = [{item = "battleship_hull", amount = 1}] outputs = [{item = "battleship_hull", amount = 1}]
duration_seconds = 30.0 duration_seconds = 10.0
[[recipe]] [[recipe]]
id = "dreadnought_hull" id = "dreadnought_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [
{item = "voidsteel_plate", amount = 5},
{item = "capital_core", amount = 1}, {item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 6},
{item = "reinforced_plating", amount = 4},
{item = "drive_unit", amount = 2}, {item = "drive_unit", amount = 2},
] ]
outputs = [{item = "dreadnought_hull", amount = 1}] outputs = [{item = "dreadnought_hull", amount = 1}]
duration_seconds = 60.0 duration_seconds = 12.0
[[recipe]] [[recipe]]
id = "carrier_hull" id = "carrier_hull"
unlock_at_station_level = -1
building = "assembler" building = "assembler"
inputs = [ inputs = [
{item = "voidsteel_plate", amount = 5},
{item = "capital_core", amount = 1}, {item = "capital_core", amount = 1},
{item = "titanium_frame", amount = 5},
{item = "reinforced_plating", amount = 3},
{item = "drive_unit", amount = 2}, {item = "drive_unit", amount = 2},
] ]
outputs = [{item = "carrier_hull", amount = 1}] outputs = [{item = "carrier_hull", amount = 1}]
duration_seconds = 60.0 duration_seconds = 12.0
# -----------------------------------------------------------------------------
# Module prefabs
# -----------------------------------------------------------------------------
[[recipe]]
id = "railgun_s_module"
building = "assembler"
inputs = [{item = "copper_coil", amount = 1}]
outputs = [{item = "railgun_s_module", amount = 1}]
duration_seconds = 1.0
[[recipe]]
id = "salvager_module"
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "salvager_module", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "repair_tool_module"
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "copper_wire", amount = 2}]
outputs = [{item = "repair_tool_module", amount = 1}]
duration_seconds = 2.0
# Material-heavy, fast: the armor archetype.
[[recipe]]
id = "armor_plates_module"
building = "assembler"
inputs = [{item = "steel_plate", amount = 4}]
outputs = [{item = "armor_plates_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "maneuvering_thrusters_module"
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "copper_coil", amount = 1}]
outputs = [{item = "maneuvering_thrusters_module", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "sensor_booster_module"
building = "assembler"
inputs = [{item = "copper_wire", amount = 2}, {item = "copper_coil", amount = 1}]
outputs = [{item = "sensor_booster_module", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "afterburner_module"
building = "assembler"
inputs = [{item = "copper_coil", amount = 2}, {item = "steel_plate", amount = 1}]
outputs = [{item = "afterburner_module", amount = 1}]
duration_seconds = 3.0
[[recipe]]
id = "weapon_stabilizer_module"
building = "assembler"
inputs = [{item = "steel_plate", amount = 1}, {item = "copper_coil", amount = 1}]
outputs = [{item = "weapon_stabilizer_module", amount = 1}]
duration_seconds = 2.0
[[recipe]]
id = "weapon_primer_module"
building = "assembler"
inputs = [{item = "capacitor_bank", amount = 1}, {item = "copper_coil", amount = 1}]
outputs = [{item = "weapon_primer_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "weapon_upgrade_module"
building = "assembler"
inputs = [{item = "control_chip", amount = 1}, {item = "copper_coil", amount = 1}]
outputs = [{item = "weapon_upgrade_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "railgun_m_module"
building = "assembler"
inputs = [
{item = "capacitor_bank", amount = 1},
{item = "steel_plate", amount = 2},
{item = "copper_coil", amount = 1},
]
outputs = [{item = "railgun_m_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "drone_bay_module"
building = "assembler"
inputs = [
{item = "control_chip", amount = 1},
{item = "steel_plate", amount = 2},
{item = "copper_coil", amount = 1},
]
outputs = [{item = "drone_bay_module", amount = 1}]
duration_seconds = 4.0
[[recipe]]
id = "railgun_l_module"
building = "assembler"
inputs = [
{item = "capacitor_bank", amount = 1},
{item = "hardened_steel", amount = 2},
{item = "ceramic_plate", amount = 1},
]
outputs = [{item = "railgun_l_module", amount = 1}]
duration_seconds = 6.0
[[recipe]]
id = "drone_hangar_module"
building = "assembler"
inputs = [
{item = "voidsteel_plate", amount = 1},
{item = "control_chip", amount = 2},
{item = "drive_unit", amount = 1},
]
outputs = [{item = "drone_hangar_module", amount = 1}]
duration_seconds = 10.0

View File

@@ -24,11 +24,11 @@
id = "drone" id = "drone"
unlock_at_station_level = -1 unlock_at_station_level = -1
layout = ["O"] layout = ["O"]
default_modules = [{type = "laser_cannon_s", x = 0, y = 0, rotation = "east"}] default_modules = [{type = "railgun_s", x = 0, y = 0, rotation = "east"}]
[ship.schematic] [ship.schematic]
materials = [{item = "iron_ore", amount = 1}] materials = [{item = "drone_hull", amount = 1}]
production_time_seconds = 5 production_time_seconds = 1
[ship.health] [ship.health]
hp = 3 hp = 3
@@ -55,10 +55,15 @@ layout = [
"OOO", "OOO",
"XOX", "XOX",
] ]
default_modules = [
{type = "railgun_s", x = 1, y = 0, rotation = "east"},
{type = "railgun_s", x = 2, y = 1, rotation = "east"},
{type = "maneuvering_thrusters", x = 0, y = 1, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "frigate_hull", amount = 1}] materials = [{item = "frigate_hull", amount = 1}]
production_time_seconds = 10 production_time_seconds = 2
[ship.health] [ship.health]
hp = 30 hp = 30
@@ -84,10 +89,17 @@ layout = [
"OXOXO", "OXOXO",
"OOOOO", "OOOOO",
] ]
default_modules = [
{type = "railgun_s", x = 0, y = 0, rotation = "east"},
{type = "railgun_s", x = 2, y = 0, rotation = "east"},
{type = "railgun_s", x = 4, y = 0, rotation = "east"},
{type = "armor_plates", x = 0, y = 1, rotation = "east"},
{type = "sensor_booster", x = 3, y = 1, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "destroyer_hull", amount = 1}] materials = [{item = "destroyer_hull", amount = 1}]
production_time_seconds = 15 production_time_seconds = 3
[ship.health] [ship.health]
hp = 50 hp = 50
@@ -115,10 +127,16 @@ layout = [
"OOOO", "OOOO",
"XOOX", "XOOX",
] ]
default_modules = [
{type = "railgun_m", x = 0, y = 1, rotation = "east"},
{type = "railgun_m", x = 2, y = 1, rotation = "east"},
{type = "armor_plates", x = 1, y = 0, rotation = "east"},
{type = "maneuvering_thrusters", x = 1, y = 3, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "cruiser_hull", amount = 1}] materials = [{item = "cruiser_hull", amount = 1}]
production_time_seconds = 25 production_time_seconds = 4
[ship.health] [ship.health]
hp = 120 hp = 120
@@ -147,10 +165,18 @@ layout = [
"XOOOOX", "XOOOOX",
"XXOOXX", "XXOOXX",
] ]
default_modules = [
{type = "railgun_m", x = 0, y = 0, rotation = "east"},
{type = "railgun_m", x = 4, y = 0, rotation = "east"},
{type = "railgun_m", x = 2, y = 1, rotation = "east"},
{type = "armor_plates", x = 2, y = 3, rotation = "east"},
{type = "railgun_s", x = 1, y = 2, rotation = "east"},
{type = "railgun_s", x = 4, y = 2, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "battlecruiser_hull", amount = 1}] materials = [{item = "battlecruiser_hull", amount = 1}]
production_time_seconds = 35 production_time_seconds = 5
[ship.health] [ship.health]
hp = 180 hp = 180
@@ -182,10 +208,18 @@ layout = [
"OOOOOO", "OOOOOO",
"XOOOOX", "XOOOOX",
] ]
default_modules = [
{type = "railgun_l", x = 1, y = 0, rotation = "east"},
{type = "railgun_m", x = 1, y = 3, rotation = "east"},
{type = "railgun_m", x = 3, y = 3, rotation = "east"},
{type = "weapon_stabilizer", x = 4, y = 1, rotation = "east"},
{type = "railgun_s", x = 4, y = 0, rotation = "east"},
{type = "railgun_s", x = 0, y = 1, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "battleship_hull", amount = 1}] materials = [{item = "battleship_hull", amount = 1}]
production_time_seconds = 60 production_time_seconds = 6
[ship.health] [ship.health]
hp = 350 hp = 350
@@ -217,10 +251,20 @@ layout = [
"OOOXOOOXOOO", "OOOXOOOXOOO",
"XXOOXXXOOXX", "XXOOXXXOOXX",
] ]
default_modules = [
{type = "railgun_l", x = 0, y = 1, rotation = "east"},
{type = "railgun_l", x = 4, y = 1, rotation = "east"},
{type = "railgun_l", x = 8, y = 1, rotation = "east"},
{type = "armor_plates", x = 3, y = 0, rotation = "east"},
{type = "armor_plates", x = 5, y = 0, rotation = "east"},
{type = "armor_plates", x = 2, y = 4, rotation = "east"},
{type = "armor_plates", x = 7, y = 4, rotation = "east"},
{type = "railgun_s", x = 7, y = 0, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "dreadnought_hull", amount = 1}] materials = [{item = "dreadnought_hull", amount = 1}]
production_time_seconds = 120 production_time_seconds = 8
[ship.health] [ship.health]
hp = 800 hp = 800
@@ -250,10 +294,18 @@ layout = [
"XOOOOOOOOX", "XOOOOOOOOX",
"XXXOOOOXXX", "XXXOOOOXXX",
] ]
default_modules = [
{type = "drone_hangar", x = 2, y = 0, rotation = "east"},
{type = "railgun_m", x = 3, y = 2, rotation = "east"},
{type = "railgun_m", x = 6, y = 2, rotation = "east"},
{type = "armor_plates", x = 0, y = 1, rotation = "east"},
{type = "armor_plates", x = 8, y = 1, rotation = "east"},
{type = "sensor_booster", x = 3, y = 4, rotation = "east"},
]
[ship.schematic] [ship.schematic]
materials = [{item = "carrier_hull", amount = 1}] materials = [{item = "carrier_hull", amount = 1}]
production_time_seconds = 120 production_time_seconds = 8
[ship.health] [ship.health]
hp = 700 hp = 700

View File

@@ -116,11 +116,11 @@ outline = "#201010"
fill = "#c47a3a" fill = "#c47a3a"
outline = "#3a1a0a" outline = "#3a1a0a"
[items.titanium_ore] [items.quartz]
fill = "#9aa3ad" fill = "#e0d4f0"
outline = "#2a2e33" outline = "#40345a"
# --- ingots --- # --- smelted basics ---
[items.iron_ingot] [items.iron_ingot]
fill = "#b0b0b8" fill = "#b0b0b8"
@@ -130,9 +130,9 @@ outline = "#202028"
fill = "#d48a4a" fill = "#d48a4a"
outline = "#402010" outline = "#402010"
[items.titanium_ingot] [items.silicon]
fill = "#c8d2dc" fill = "#33415e"
outline = "#3a4048" outline = "#0e1420"
# --- salvage loop --- # --- salvage loop ---
@@ -140,9 +140,9 @@ outline = "#3a4048"
fill = "#7a7268" fill = "#7a7268"
outline = "#201a14" outline = "#201a14"
[items.advanced_alloy] [items.voidsteel]
fill = "#a06acc" fill = "#4a3a6a"
outline = "#201030" outline = "#151020"
# --- basic components --- # --- basic components ---
@@ -154,9 +154,9 @@ outline = "#3a2008"
fill = "#8a92a0" fill = "#8a92a0"
outline = "#22262c" outline = "#22262c"
[items.circuit_board] [items.copper_coil]
fill = "#2ea35a" fill = "#d07030"
outline = "#0a2a14" outline = "#381808"
[items.building_block] [items.building_block]
fill = "#c8b070" fill = "#c8b070"
@@ -164,26 +164,30 @@ outline = "#302810"
# --- advanced components --- # --- advanced components ---
[items.mechanical_parts] [items.control_chip]
fill = "#6f7a66" fill = "#2ea35a"
outline = "#1c2018" outline = "#0a2a14"
[items.targeting_unit] [items.capacitor_bank]
fill = "#3a9e8c" fill = "#d0a030"
outline = "#0c2824" outline = "#302408"
[items.hardened_steel]
fill = "#6a7280"
outline = "#181c22"
[items.ceramic_plate]
fill = "#e0d8c8"
outline = "#3a3428"
[items.drive_unit] [items.drive_unit]
fill = "#4a6ad0" fill = "#4a6ad0"
outline = "#101a38" outline = "#101a38"
[items.titanium_frame]
fill = "#b8c4d4"
outline = "#343c48"
# --- capital components --- # --- capital components ---
[items.reinforced_plating] [items.voidsteel_plate]
fill = "#8a6ad0" fill = "#7a5aaa"
outline = "#1c1038" outline = "#1c1038"
[items.capital_core] [items.capital_core]
@@ -192,15 +196,15 @@ outline = "#280c30"
# --- module items --- # --- module items ---
[items.laser_cannon_s_module] [items.railgun_s_module]
fill = "#691313" fill = "#691313"
outline = "#f3ff4f" outline = "#f3ff4f"
[items.laser_cannon_m_module] [items.railgun_m_module]
fill = "#892020" fill = "#892020"
outline = "#f3ff4f" outline = "#f3ff4f"
[items.laser_cannon_l_module] [items.railgun_l_module]
fill = "#a92d2d" fill = "#a92d2d"
outline = "#f3ff4f" outline = "#f3ff4f"

View File

@@ -3,7 +3,7 @@ height_tiles = 40
refund_percentage = 100 refund_percentage = 100
starting_building_blocks = 1000 starting_building_blocks = 1000
scrap_despawn_seconds = 30 scrap_despawn_seconds = 30
scrap_per_threat = 0.01 scrap_per_threat = 0.25
tile_size_m = 10 tile_size_m = 10
belt_speed_mps = 20 belt_speed_mps = 20
tunnel_max_distance_tiles = 10 tunnel_max_distance_tiles = 10

View File

@@ -341,11 +341,15 @@ prints per-item threat values and producer:consumer ratio tables.
### Numbers — first pass ### Numbers — first pass
Computed with a recursive threat calculator (now available as These numbers are live in the config files and verified by
`tools/threat_report.py`, which reads the real config files — it `tools/threat_report.py`; quantities and durations are tuned so fitted
verifies these numbers once the v2 tree lands in the configs); ships land on the threat-cost ladder and the ratio curve is realized.
quantities and durations tuned so fitted ships land on the threat-cost The typical loadouts are geometry-validated against the hull grids and
ladder and the ratio curve is realized. 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 **Economy constants:** `scrap_per_threat = 0.25` (1 scrap per 4 threat
destroyed — a cruiser kill drops ~59 scrap). Reprocessing: 4 scrap per destroyed — a cruiser kill drops ~59 scrap). Reprocessing: 4 scrap per
@@ -405,9 +409,9 @@ typical loadouts double as the `default_modules` for enemy waves):
| frigate | 2 steel_plate + 1 copper_wire | 2 | 2 | 2× railgun_s, maneuvering_thrusters | 47 (40) | | 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) | | 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) | | 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, weapon_primer, armor_plates | 370 (350) | | 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, armor_plates, sensor_booster | 752 (700) | | 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, weapon_primer, weapon_upgrade, 2× armor_plates | 1472 (1500) | | 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) | | 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:** **Checks:**

View File

@@ -392,3 +392,12 @@ in `requirements.md` and the git history). Still open:
pass instead of iterating; their consumers silently drop the missing pass instead of iterating; their consumers silently drop the missing
materials, so capital hull threat is currently underestimated (found materials, so capital hull threat is currently underestimated (found
by `tools/threat_report.py`, which implements the correct fixpoint). by `tools/threat_report.py`, which implements the correct fixpoint).
9. **Max rule across staggered recipes in ThreatCostCalculator.** An
item is committed at the first iteration where *any* of its recipes
resolves, taking the max only over the recipes resolvable at that
point. A shallow shortcut recipe (e.g. steel plate from raw ore)
resolves one iteration earlier than the base path and wins, silently
underpricing the item and everything downstream — violating the
"shortcuts are pure rewards" rule. Fix: commit an item's threat only
once every eligible recipe for it is computable (as
`tools/threat_report.py` does), with a fallback for recipe cycles.

View File

@@ -86,23 +86,30 @@ def resolve_items(recipes, scrap_threat):
item_threat = {"scrap": scrap_threat} item_threat = {"scrap": scrap_threat}
def resolve_pass(): # All eligible (recipe, output) pairs per item: the max rule requires
progress = False # committing an item only once EVERY eligible recipe for it is
best = {} # computable — otherwise a shallow shortcut recipe that resolves one
# pass earlier than the base path would win and underprice the item.
recipes_per_item = {}
for recipe in non_repro: for recipe in non_repro:
for output in recipe.get("outputs", []): for output in recipe.get("outputs", []):
if output["item"] in item_threat: if eligible(recipe, output):
recipes_per_item.setdefault(output["item"], []).append(
(recipe, output))
def resolve_pass(require_all_recipes=True):
progress = False
best = {}
for item, pairs in recipes_per_item.items():
if item in item_threat:
continue continue
if not eligible(recipe, output): threats = [recipe_threat_per_unit(r, o, item_threat)
for r, o in pairs]
if require_all_recipes and any(t is None for t in threats):
continue continue
threat = recipe_threat_per_unit(recipe, output, item_threat) threats = [t for t in threats if t is not None]
if threat is None: if threats:
continue best[item] = max(threats)
key = output["item"]
if key not in best or threat > best[key]:
best[key] = threat
# A full sweep before committing keeps the max rule correct when
# several recipes for the same item resolve in the same pass.
for item, threat in best.items(): for item, threat in best.items():
item_threat[item] = threat item_threat[item] = threat
progress = True progress = True
@@ -139,6 +146,13 @@ def resolve_items(recipes, scrap_threat):
progress = resolve_pass() progress = resolve_pass()
progress = repro_pass() or progress progress = repro_pass() or progress
# Deadlock guard: if recipe cycles keep items waiting on each other,
# fall back to committing with the computable subset of recipes.
progress = True
while progress:
progress = resolve_pass(require_all_recipes=False)
progress = repro_pass() or progress
return item_threat return item_threat