diff --git a/bin/app/data/config/modules.toml b/bin/app/data/config/modules.toml index 16ae89c..91f59c2 100644 --- a/bin/app/data/config/modules.toml +++ b/bin/app/data/config/modules.toml @@ -1,26 +1,29 @@ # modules.toml # -# First real-content iteration: module ids and surface masks are the designed -# content; stats, materials, and threat costs are placeholders until the -# recipe and balancing passes. +# Production tree v2: all weapons are railguns for now — the implementation +# (instant damage, no projectile, no ammunition) stays as-is and the beam +# 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 -# the balancing pass. The laser cannons are the exception: laser_cannon_m is -# gated to station level 1, and laser_cannon_l requires laser_cannon_m to be -# unlocked first (unlock_requires) — a demonstration of the prerequisite chain. +# the pacing pass. The railguns are the exception: railgun_m is gated to +# station level 1, and railgun_l requires railgun_m to be unlocked first +# (unlock_requires) — a demonstration of the prerequisite chain. # # Surface mask footprint ladder — footprints gate which hulls can mount a # module, purely through geometry (see ships.toml for the matching hull # 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, # armor_plates frigate and up # 1x3 afterburner frigate and up (eats most of a frigate) # L-shape weapon_stabilizer, weapon_primer, # weapon_upgrade frigate and up -# 2x2 laser_cannon_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) +# 2x2 railgun_m, drone_bay cruiser and up (no 2x2 area on s hulls) +# 3x3 railgun_l battleship and up (no 3x3 area on m hulls) # 2x6 drone_hangar carrier only # ----------------------------------------------------------------------------- @@ -28,13 +31,13 @@ # ----------------------------------------------------------------------------- [[module]] -id = "laser_cannon_s" +id = "railgun_s" unlock_at_station_level = -1 surface_mask = ["O"] -materials = [{item = "laser_cannon_s_module", amount = 1}] -production_time_seconds = 0.5 +materials = [{item = "railgun_s_module", amount = 1}] +production_time_seconds = 1 fill_color = "#FF8040" -glyph = "Ls" +glyph = "Rs" [module.weapon] damage = 2 @@ -43,15 +46,15 @@ attack_rate_hz = 2.0 [[module]] -id = "laser_cannon_m" +id = "railgun_m" unlock_at_station_level = 1 surface_mask = [ "OO", "OO"] -materials = [{item = "laser_cannon_m_module", amount = 1}] -production_time_seconds = 2 +materials = [{item = "railgun_m_module", amount = 1}] +production_time_seconds = 3 fill_color = "#FF8040" -glyph = "Lm" +glyph = "Rm" [module.weapon] damage = 10 @@ -60,17 +63,17 @@ attack_rate_hz = 1.5 [[module]] -id = "laser_cannon_l" +id = "railgun_l" unlock_at_station_level = 1 -unlock_requires = ["laser_cannon_m"] +unlock_requires = ["railgun_m"] surface_mask = [ "OOO", "OOO", "OOO"] -materials = [{item = "laser_cannon_l_module", amount = 1}] -production_time_seconds = 8 +materials = [{item = "railgun_l_module", amount = 1}] +production_time_seconds = 4 fill_color = "#FF8040" -glyph = "Ll" +glyph = "Rl" [module.weapon] damage = 40 @@ -86,7 +89,7 @@ id = "salvager" unlock_at_station_level = -1 surface_mask = ["O"] materials = [{item = "salvager_module", amount = 1}] -production_time_seconds = 2 +production_time_seconds = 1 fill_color = "#AACC44" glyph = "Sv" @@ -101,7 +104,7 @@ id = "repair_tool" unlock_at_station_level = -1 surface_mask = ["O"] materials = [{item = "repair_tool_module", amount = 1}] -production_time_seconds = 2 +production_time_seconds = 1 fill_color = "#66CCFF" glyph = "Rp" @@ -119,7 +122,7 @@ id = "afterburner" unlock_at_station_level = -1 surface_mask = ["OOO"] materials = [{item = "afterburner_module", amount = 1}] -production_time_seconds = 2 +production_time_seconds = 1 fill_color = "#40A0FF" glyph = "Ab" @@ -133,7 +136,7 @@ id = "maneuvering_thrusters" unlock_at_station_level = -1 surface_mask = ["OO"] materials = [{item = "maneuvering_thrusters_module", amount = 1}] -production_time_seconds = 2 +production_time_seconds = 1 fill_color = "#40A0FF" glyph = "Mt" @@ -150,7 +153,7 @@ id = "armor_plates" unlock_at_station_level = -1 surface_mask = ["OO"] materials = [{item = "armor_plates_module", amount = 1}] -production_time_seconds = 3 +production_time_seconds = 1 fill_color = "#808080" glyph = "A" @@ -163,7 +166,7 @@ id = "sensor_booster" unlock_at_station_level = -1 surface_mask = ["OO"] materials = [{item = "sensor_booster_module", amount = 1}] -production_time_seconds = 2 +production_time_seconds = 1 fill_color = "#40A0FF" glyph = "S" @@ -182,7 +185,7 @@ surface_mask = [ "OX", ] materials = [{item = "weapon_upgrade_module", amount = 1}] -production_time_seconds = 4 +production_time_seconds = 2 fill_color = "#FF4040" glyph = "Wu" @@ -198,7 +201,7 @@ surface_mask = [ "OX", ] materials = [{item = "weapon_primer_module", amount = 1}] -production_time_seconds = 4 +production_time_seconds = 2 fill_color = "#FF4040" glyph = "Wp" @@ -214,7 +217,7 @@ surface_mask = [ "OX", ] materials = [{item = "weapon_stabilizer_module", amount = 1}] -production_time_seconds = 4 +production_time_seconds = 1 fill_color = "#FF4040" glyph = "Ws" @@ -236,7 +239,7 @@ surface_mask = [ "OO", "OO"] materials = [{item = "drone_bay_module", amount = 1}] -production_time_seconds = 5 +production_time_seconds = 3 fill_color = "#CC66FF" glyph = "Db" @@ -248,6 +251,6 @@ surface_mask = [ "OOOOOO", "OOOOOO"] materials = [{item = "drone_hangar_module", amount = 1}] -production_time_seconds = 20 +production_time_seconds = 6 fill_color = "#9933CC" glyph = "Dh" diff --git a/bin/app/data/config/recipes.toml b/bin/app/data/config/recipes.toml index aa3c54a..256bb1c 100644 --- a/bin/app/data/config/recipes.toml +++ b/bin/app/data/config/recipes.toml @@ -1,26 +1,26 @@ # recipes.toml # -# First real-content iteration of the production tree. Quantities and -# durations are a first guess; the balancing pass will tune them and assign -# real unlock_at_station_level values. Almost everything is unlocked for now so -# the full tree is testable; the only gated recipe schematics are the medium and -# large laser-cannon item recipes, which demonstrate the unlock_requires -# prerequisite chain (large requires medium — see modules.toml / ships.toml). +# Production tree v2 (see docs/content_design.md, "Production tree v2" and +# "Numbers — first pass"). Quantities and durations are tuned so that every +# fitted ship lands on the threat-cost ladder and the ratio curve is +# realized: tier 1 ratios are 1:1, tier 2 ratios are 2:3, tier 3+ ratios +# are deliberately strange. # -# 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, -# circuit_board -# mid + titanium_ore -> titanium_frame; assembler-made -# mechanical_parts, targeting_unit, -# drive_unit -# late + advanced_alloy -> reinforced_plating, capital_core. -# advanced_alloy CANNOT be mined; it only -# comes from reprocessing salvaged scrap, -# so capital production requires combat. +# early iron_ore + copper_ore minable on every asteroid tile (the +# asteroid is an M-type body — its bulk +# rock IS ore) +# mid + quartz geode deposits in expansion territory +# (deposit gating pending — see action +# item 5 in docs/progression_design.md; +# until then quartz mines anywhere) +# late + voidsteel battle-forged: ONLY from reprocessing +# salvaged scrap, so capital production +# requires combat # -# Run tools/verify_recipes.py after editing to check that every consumed -# item has a producer and every item has a visuals.toml entry. +# Run tools/verify_recipes.py and tools/threat_report.py after editing. # ----------------------------------------------------------------------------- # Mining (tier 0) @@ -38,405 +38,406 @@ id = "mine_copper_ore" building = "miner" inputs = [] 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]] -id = "mine_titanium_ore" +id = "mine_quartz" building = "miner" inputs = [] -outputs = [{item = "titanium_ore", amount = 1}] -duration_seconds = 3.0 +outputs = [{item = "quartz", amount = 1}] +duration_seconds = 2.0 # ----------------------------------------------------------------------------- -# Smelting (tier 1) +# Smelting (tier 1) — one recipe per input item; ratios are 1:1 with miners. # ----------------------------------------------------------------------------- [[recipe]] id = "iron_ingot" building = "smelter" -inputs = [{item = "iron_ore", amount = 2}] +inputs = [{item = "iron_ore", amount = 1}] outputs = [{item = "iron_ingot", amount = 1}] -duration_seconds = 2.0 +duration_seconds = 1.0 [[recipe]] id = "copper_ingot" building = "smelter" -inputs = [{item = "copper_ore", amount = 2}] +inputs = [{item = "copper_ore", amount = 1}] outputs = [{item = "copper_ingot", amount = 1}] -duration_seconds = 2.5 +duration_seconds = 1.0 [[recipe]] -id = "titanium_ingot" +id = "silicon" building = "smelter" -inputs = [{item = "titanium_ore", amount = 3}] -outputs = [{item = "titanium_ingot", amount = 1}] -duration_seconds = 4.0 +inputs = [{item = "quartz", amount = 1}] +outputs = [{item = "silicon", amount = 1}] +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 -# -# The only source of advanced_alloy: salvaged scrap from destroyed ships. +# Reprocessing — the only source of voidsteel (battle-forged; formed when +# weapon plasma anneals hull metal in the violence of ship destruction). +# Weights are authored for the fully unlocked pool state; the pool +# renormalizes over implicitly unlocked items early game. # ----------------------------------------------------------------------------- [[recipe]] id = "reprocessing_cycle" building = "reprocessing_plant" -inputs = [{item = "scrap", amount = 5}] -duration_seconds = 3.0 +inputs = [{item = "scrap", amount = 4}] +duration_seconds = 4.0 [[recipe.outputs]] item = "iron_ingot" - amount = 2 - probability = 0.45 + amount = 1 + probability = 0.3 [[recipe.outputs]] item = "copper_ingot" amount = 1 - probability = 0.25 + probability = 0.3 [[recipe.outputs]] - item = "titanium_ingot" + item = "silicon" amount = 1 - probability = 0.15 + probability = 0.2 [[recipe.outputs]] - item = "advanced_alloy" + item = "voidsteel" 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]] id = "steel_plate" -unlock_at_station_level = -1 building = "assembler" inputs = [{item = "iron_ingot", amount = 2}] 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 [[recipe]] -id = "circuit_board" -unlock_at_station_level = -1 +id = "shortcut_control_chip" +unlock_at_station_level = 2 building = "assembler" -inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_wire", amount = 2}] -outputs = [{item = "circuit_board", amount = 1}] -duration_seconds = 2.0 +inputs = [{item = "quartz", amount = 2}] +outputs = [{item = "control_chip", amount = 1}] +duration_seconds = 4.0 [[recipe]] -id = "building_blocks" -unlock_at_station_level = -1 +id = "shortcut_hardened_steel" +unlock_at_station_level = 2 building = "assembler" inputs = [{item = "iron_ingot", amount = 4}] -outputs = [{item = "building_block", amount = 10}] -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}] +outputs = [{item = "hardened_steel", amount = 1}] 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 # ----------------------------------------------------------------------------- [[recipe]] id = "drone_hull" -unlock_at_station_level = -1 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}] -duration_seconds = 4.0 +duration_seconds = 1.0 [[recipe]] id = "frigate_hull" -unlock_at_station_level = -1 building = "assembler" -inputs = [ - {item = "steel_plate", amount = 3}, - {item = "mechanical_parts", amount = 1}, - {item = "circuit_board", amount = 1}, -] +inputs = [{item = "steel_plate", amount = 2}, {item = "copper_wire", amount = 1}] outputs = [{item = "frigate_hull", amount = 1}] -duration_seconds = 8.0 +duration_seconds = 2.0 [[recipe]] id = "destroyer_hull" -unlock_at_station_level = -1 building = "assembler" -inputs = [ - {item = "steel_plate", amount = 5}, - {item = "mechanical_parts", amount = 2}, - {item = "circuit_board", amount = 1}, -] +inputs = [{item = "steel_plate", amount = 3}, {item = "copper_coil", amount = 2}] outputs = [{item = "destroyer_hull", amount = 1}] -duration_seconds = 10.0 +duration_seconds = 4.0 [[recipe]] id = "cruiser_hull" -unlock_at_station_level = -1 building = "assembler" -inputs = [ - {item = "titanium_frame", amount = 2}, - {item = "steel_plate", amount = 4}, - {item = "drive_unit", amount = 1}, -] +inputs = [{item = "hardened_steel", amount = 2}, {item = "control_chip", amount = 2}] outputs = [{item = "cruiser_hull", amount = 1}] -duration_seconds = 15.0 +duration_seconds = 6.0 [[recipe]] id = "battlecruiser_hull" -unlock_at_station_level = -1 building = "assembler" inputs = [ - {item = "titanium_frame", amount = 3}, - {item = "steel_plate", amount = 6}, + {item = "hardened_steel", amount = 3}, + {item = "control_chip", amount = 2}, {item = "drive_unit", amount = 1}, - {item = "targeting_unit", amount = 1}, ] outputs = [{item = "battlecruiser_hull", amount = 1}] -duration_seconds = 20.0 +duration_seconds = 8.0 [[recipe]] id = "battleship_hull" -unlock_at_station_level = -1 building = "assembler" inputs = [ - {item = "titanium_frame", amount = 4}, - {item = "reinforced_plating", amount = 2}, - {item = "drive_unit", amount = 2}, + {item = "voidsteel_plate", amount = 3}, + {item = "drive_unit", amount = 1}, + {item = "control_chip", amount = 2}, ] outputs = [{item = "battleship_hull", amount = 1}] -duration_seconds = 30.0 +duration_seconds = 10.0 [[recipe]] id = "dreadnought_hull" -unlock_at_station_level = -1 building = "assembler" inputs = [ + {item = "voidsteel_plate", amount = 5}, {item = "capital_core", amount = 1}, - {item = "titanium_frame", amount = 6}, - {item = "reinforced_plating", amount = 4}, {item = "drive_unit", amount = 2}, ] outputs = [{item = "dreadnought_hull", amount = 1}] -duration_seconds = 60.0 +duration_seconds = 12.0 [[recipe]] id = "carrier_hull" -unlock_at_station_level = -1 building = "assembler" inputs = [ + {item = "voidsteel_plate", amount = 5}, {item = "capital_core", amount = 1}, - {item = "titanium_frame", amount = 5}, - {item = "reinforced_plating", amount = 3}, {item = "drive_unit", amount = 2}, ] 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 diff --git a/bin/app/data/config/ships.toml b/bin/app/data/config/ships.toml index f21bf9d..39394a2 100644 --- a/bin/app/data/config/ships.toml +++ b/bin/app/data/config/ships.toml @@ -24,11 +24,11 @@ id = "drone" unlock_at_station_level = -1 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] -materials = [{item = "iron_ore", amount = 1}] -production_time_seconds = 5 +materials = [{item = "drone_hull", amount = 1}] +production_time_seconds = 1 [ship.health] hp = 3 @@ -55,10 +55,15 @@ layout = [ "OOO", "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] materials = [{item = "frigate_hull", amount = 1}] -production_time_seconds = 10 +production_time_seconds = 2 [ship.health] hp = 30 @@ -84,10 +89,17 @@ layout = [ "OXOXO", "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] materials = [{item = "destroyer_hull", amount = 1}] -production_time_seconds = 15 +production_time_seconds = 3 [ship.health] hp = 50 @@ -115,10 +127,16 @@ layout = [ "OOOO", "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] materials = [{item = "cruiser_hull", amount = 1}] -production_time_seconds = 25 +production_time_seconds = 4 [ship.health] hp = 120 @@ -147,10 +165,18 @@ layout = [ "XOOOOX", "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] materials = [{item = "battlecruiser_hull", amount = 1}] -production_time_seconds = 35 +production_time_seconds = 5 [ship.health] hp = 180 @@ -182,10 +208,18 @@ layout = [ "OOOOOO", "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] materials = [{item = "battleship_hull", amount = 1}] -production_time_seconds = 60 +production_time_seconds = 6 [ship.health] hp = 350 @@ -217,10 +251,20 @@ layout = [ "OOOXOOOXOOO", "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] materials = [{item = "dreadnought_hull", amount = 1}] -production_time_seconds = 120 +production_time_seconds = 8 [ship.health] hp = 800 @@ -250,10 +294,18 @@ layout = [ "XOOOOOOOOX", "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] materials = [{item = "carrier_hull", amount = 1}] -production_time_seconds = 120 +production_time_seconds = 8 [ship.health] hp = 700 diff --git a/bin/app/data/config/visuals.toml b/bin/app/data/config/visuals.toml index df788dc..7a31445 100644 --- a/bin/app/data/config/visuals.toml +++ b/bin/app/data/config/visuals.toml @@ -116,11 +116,11 @@ outline = "#201010" fill = "#c47a3a" outline = "#3a1a0a" -[items.titanium_ore] -fill = "#9aa3ad" -outline = "#2a2e33" +[items.quartz] +fill = "#e0d4f0" +outline = "#40345a" -# --- ingots --- +# --- smelted basics --- [items.iron_ingot] fill = "#b0b0b8" @@ -130,9 +130,9 @@ outline = "#202028" fill = "#d48a4a" outline = "#402010" -[items.titanium_ingot] -fill = "#c8d2dc" -outline = "#3a4048" +[items.silicon] +fill = "#33415e" +outline = "#0e1420" # --- salvage loop --- @@ -140,9 +140,9 @@ outline = "#3a4048" fill = "#7a7268" outline = "#201a14" -[items.advanced_alloy] -fill = "#a06acc" -outline = "#201030" +[items.voidsteel] +fill = "#4a3a6a" +outline = "#151020" # --- basic components --- @@ -154,9 +154,9 @@ outline = "#3a2008" fill = "#8a92a0" outline = "#22262c" -[items.circuit_board] -fill = "#2ea35a" -outline = "#0a2a14" +[items.copper_coil] +fill = "#d07030" +outline = "#381808" [items.building_block] fill = "#c8b070" @@ -164,26 +164,30 @@ outline = "#302810" # --- advanced components --- -[items.mechanical_parts] -fill = "#6f7a66" -outline = "#1c2018" +[items.control_chip] +fill = "#2ea35a" +outline = "#0a2a14" -[items.targeting_unit] -fill = "#3a9e8c" -outline = "#0c2824" +[items.capacitor_bank] +fill = "#d0a030" +outline = "#302408" + +[items.hardened_steel] +fill = "#6a7280" +outline = "#181c22" + +[items.ceramic_plate] +fill = "#e0d8c8" +outline = "#3a3428" [items.drive_unit] fill = "#4a6ad0" outline = "#101a38" -[items.titanium_frame] -fill = "#b8c4d4" -outline = "#343c48" - # --- capital components --- -[items.reinforced_plating] -fill = "#8a6ad0" +[items.voidsteel_plate] +fill = "#7a5aaa" outline = "#1c1038" [items.capital_core] @@ -192,15 +196,15 @@ outline = "#280c30" # --- module items --- -[items.laser_cannon_s_module] +[items.railgun_s_module] fill = "#691313" outline = "#f3ff4f" -[items.laser_cannon_m_module] +[items.railgun_m_module] fill = "#892020" outline = "#f3ff4f" -[items.laser_cannon_l_module] +[items.railgun_l_module] fill = "#a92d2d" outline = "#f3ff4f" diff --git a/bin/app/data/config/world.toml b/bin/app/data/config/world.toml index f67aca8..3a2a92f 100644 --- a/bin/app/data/config/world.toml +++ b/bin/app/data/config/world.toml @@ -3,7 +3,7 @@ height_tiles = 40 refund_percentage = 100 starting_building_blocks = 1000 scrap_despawn_seconds = 30 -scrap_per_threat = 0.01 +scrap_per_threat = 0.25 tile_size_m = 10 belt_speed_mps = 20 tunnel_max_distance_tiles = 10 diff --git a/docs/content_design.md b/docs/content_design.md index 7d0ff1d..bb2a727 100644 --- a/docs/content_design.md +++ b/docs/content_design.md @@ -341,11 +341,15 @@ prints per-item threat values and producer:consumer ratio tables. ### Numbers — first pass -Computed with a recursive threat calculator (now available as -`tools/threat_report.py`, which reads the real config files — it -verifies these numbers once the v2 tree lands in the configs); -quantities and durations tuned so fitted ships land on the threat-cost -ladder and the ratio curve is realized. +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 @@ -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) | | 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, weapon_primer, armor_plates | 370 (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) | -| dreadnought | 5 voidsteel_plate + 1 capital_core + 2 drive_unit | 12 | 8 | 3× railgun_l, weapon_primer, weapon_upgrade, 2× armor_plates | 1472 (1500) | +| 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:** diff --git a/docs/progression_design.md b/docs/progression_design.md index b96d972..1c5853c 100644 --- a/docs/progression_design.md +++ b/docs/progression_design.md @@ -392,3 +392,12 @@ in `requirements.md` and the git history). Still open: pass instead of iterating; their consumers silently drop the missing materials, so capital hull threat is currently underestimated (found 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. diff --git a/tools/threat_report.py b/tools/threat_report.py index e176779..92a6614 100644 --- a/tools/threat_report.py +++ b/tools/threat_report.py @@ -86,23 +86,30 @@ def resolve_items(recipes, scrap_threat): item_threat = {"scrap": scrap_threat} - def resolve_pass(): + # All eligible (recipe, output) pairs per item: the max rule requires + # committing an item only once EVERY eligible recipe for it is + # 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 output in recipe.get("outputs", []): + if eligible(recipe, output): + recipes_per_item.setdefault(output["item"], []).append( + (recipe, output)) + + def resolve_pass(require_all_recipes=True): progress = False best = {} - for recipe in non_repro: - for output in recipe.get("outputs", []): - if output["item"] in item_threat: - continue - if not eligible(recipe, output): - continue - threat = recipe_threat_per_unit(recipe, output, item_threat) - if threat is None: - continue - 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, pairs in recipes_per_item.items(): + if item in item_threat: + continue + 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 + threats = [t for t in threats if t is not None] + if threats: + best[item] = max(threats) for item, threat in best.items(): item_threat[item] = threat progress = True @@ -139,6 +146,13 @@ def resolve_items(recipes, scrap_threat): progress = resolve_pass() 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