From 997a7778e04b4d554d6116db87fc1eee0174fe5e Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Sun, 14 Jun 2026 14:23:53 +0200 Subject: [PATCH] first iteration of fable 5 on ship and module grids and recipes --- bin/app/data/config/modules.toml | 317 +++++++++++++-------- bin/app/data/config/recipes.toml | 442 ++++++++++++++++++++++++++---- bin/app/data/config/ships.toml | 271 +++++++++++++++++- bin/app/data/config/visuals.toml | 184 ++++++++++++- bin/balancing/data/balancing.toml | 12 +- docs/content_design.md | 194 +++++++++++++ 6 files changed, 1233 insertions(+), 187 deletions(-) create mode 100644 docs/content_design.md diff --git a/bin/app/data/config/modules.toml b/bin/app/data/config/modules.toml index af94ec4..da43435 100644 --- a/bin/app/data/config/modules.toml +++ b/bin/app/data/config/modules.toml @@ -1,122 +1,36 @@ -[[module]] -id = "armor_plate" -unlock_at_station_level = -1 -surface_mask = ["OO"] -materials = [{item = "armor_plate_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 3 -fill_color = "#808080" -glyph = "A" - -[module.health] -added_hp_formula = "40" +# 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. +# +# 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 +# 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) +# 2x6 drone_hangar carrier only +# ----------------------------------------------------------------------------- +# Weapons +# ----------------------------------------------------------------------------- [[module]] -id = "sensor_booster" +id = "laser_cannon_s" unlock_at_station_level = -1 surface_mask = ["O"] -materials = [{item = "sensor_booster_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 2 -fill_color = "#40A0FF" -glyph = "S" - -[module.sensor] -added_sensor_range_m_formula = "50" - - -[[module]] -id = "manuvering_thrusters" -unlock_at_station_level = -1 -surface_mask = ["O"] -materials = [{item = "manuvering_thrusters_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 2 -fill_color = "#40A0FF" -glyph = "Mt" - -[module.movement] -multiplied_speed_mps_formula = "1.2" -added_maneuvering_acceleration_mpss_formula = "10" - - -[[module]] -id = "afterburner" -unlock_at_station_level = -1 -surface_mask = ["O"] -materials = [{item = "afterburner_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 2 -fill_color = "#40A0FF" -glyph = "Ab" - -[module.movement] -multiplied_speed_mps_formula = "1.6" -added_main_acceleration_mpss_formula = "60" - - -[[module]] -id = "weapon_upgrade" -unlock_at_station_level = -1 -surface_mask = [ - "OO", - "O ", -] -materials = [{item = "weapon_upgrade_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 4 -fill_color = "#FF4040" -glyph = "Wu" - -[module.weapon] -multiplied_damage_formula = "1.2" - - -[[module]] -id = "weapon_primer" -unlock_at_station_level = -1 -surface_mask = [ - "OO", - "O ", -] -materials = [{item = "weapon_primer_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 4 -fill_color = "#FF4040" -glyph = "Wp" - -[module.weapon] -multiplied_attack_rate_hz_formula = "1.2" - - -[[module]] -id = "weapon_stabilizer" -unlock_at_station_level = -1 -surface_mask = [ - "OO", - "O ", -] -materials = [{item = "weapon_stabilizer_module", amount = 1}] -player_production_level = 1 -production_time_seconds = 4 -fill_color = "#FF4040" -glyph = "Ws" - -[module.weapon] -multiplied_attack_range_m_formula = "1.5" -multiplied_attack_rate_hz_formula = "0.8" - - -[[module]] -id = "laser_cannon_xs" -unlock_at_station_level = -1 -surface_mask = ["O"] -materials = [{item = "iron_ore", amount = 1}] +materials = [{item = "laser_cannon_s_module", amount = 1}] player_production_level = 1 production_time_seconds = 0.5 fill_color = "#FF8040" -glyph = "L" +glyph = "Ls" [module.weapon] damage_formula = "2" @@ -125,16 +39,16 @@ attack_rate_hz_formula = "2.0" [[module]] -id = "laser_cannon_s" +id = "laser_cannon_m" unlock_at_station_level = -1 surface_mask = [ "OO", "OO"] -materials = [{item = "laser_cannon_s_module", amount = 1}] +materials = [{item = "laser_cannon_m_module", amount = 1}] player_production_level = 1 -production_time_seconds = 0.5 +production_time_seconds = 2 fill_color = "#FF8040" -glyph = "L" +glyph = "Lm" [module.weapon] damage_formula = "10" @@ -142,6 +56,28 @@ attack_range_m_formula = "70" attack_rate_hz_formula = "1.5" +[[module]] +id = "laser_cannon_l" +unlock_at_station_level = -1 +surface_mask = [ + "OOO", + "OOO", + "OOO"] +materials = [{item = "laser_cannon_l_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 8 +fill_color = "#FF8040" +glyph = "Ll" + +[module.weapon] +damage_formula = "40" +attack_range_m_formula = "100" +attack_rate_hz_formula = "0.8" + +# ----------------------------------------------------------------------------- +# Utility tools +# ----------------------------------------------------------------------------- + [[module]] id = "salvager" unlock_at_station_level = -1 @@ -171,3 +107,154 @@ glyph = "Rp" [module.repair] repair_rate_hz_formula = "5 + x" repair_range_m_formula = "800" + +# ----------------------------------------------------------------------------- +# Propulsion +# ----------------------------------------------------------------------------- + +[[module]] +id = "afterburner" +unlock_at_station_level = -1 +surface_mask = ["OOO"] +materials = [{item = "afterburner_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 2 +fill_color = "#40A0FF" +glyph = "Ab" + +[module.movement] +multiplied_speed_mps_formula = "1.6" +added_main_acceleration_mpss_formula = "60" + + +[[module]] +id = "maneuvering_thrusters" +unlock_at_station_level = -1 +surface_mask = ["OO"] +materials = [{item = "maneuvering_thrusters_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 2 +fill_color = "#40A0FF" +glyph = "Mt" + +[module.movement] +multiplied_speed_mps_formula = "1.2" +added_maneuvering_acceleration_mpss_formula = "10" + +# ----------------------------------------------------------------------------- +# Defense & sensors +# ----------------------------------------------------------------------------- + +[[module]] +id = "armor_plates" +unlock_at_station_level = -1 +surface_mask = ["OO"] +materials = [{item = "armor_plates_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 3 +fill_color = "#808080" +glyph = "A" + +[module.health] +added_hp_formula = "40" + + +[[module]] +id = "sensor_booster" +unlock_at_station_level = -1 +surface_mask = ["OO"] +materials = [{item = "sensor_booster_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 2 +fill_color = "#40A0FF" +glyph = "S" + +[module.sensor] +added_sensor_range_m_formula = "50" + +# ----------------------------------------------------------------------------- +# Weapon modifiers +# ----------------------------------------------------------------------------- + +[[module]] +id = "weapon_upgrade" +unlock_at_station_level = -1 +surface_mask = [ + "OO", + "OX", +] +materials = [{item = "weapon_upgrade_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 4 +fill_color = "#FF4040" +glyph = "Wu" + +[module.weapon] +multiplied_damage_formula = "1.2" + + +[[module]] +id = "weapon_primer" +unlock_at_station_level = -1 +surface_mask = [ + "OO", + "OX", +] +materials = [{item = "weapon_primer_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 4 +fill_color = "#FF4040" +glyph = "Wp" + +[module.weapon] +multiplied_attack_rate_hz_formula = "1.2" + + +[[module]] +id = "weapon_stabilizer" +unlock_at_station_level = -1 +surface_mask = [ + "OO", + "OX", +] +materials = [{item = "weapon_stabilizer_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 4 +fill_color = "#FF4040" +glyph = "Ws" + +[module.weapon] +multiplied_attack_range_m_formula = "1.5" +multiplied_attack_rate_hz_formula = "0.8" + +# ----------------------------------------------------------------------------- +# Drone modules +# +# Footprint-only placeholders: the drone launching capability is not +# implemented yet, so these modules define no capability section. +# ----------------------------------------------------------------------------- + +[[module]] +id = "drone_bay" +unlock_at_station_level = -1 +surface_mask = [ + "OO", + "OO"] +materials = [{item = "drone_bay_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 5 +fill_color = "#CC66FF" +glyph = "Db" + + +[[module]] +id = "drone_hangar" +unlock_at_station_level = -1 +surface_mask = [ + "OOOOOO", + "OOOOOO"] +materials = [{item = "drone_hangar_module", amount = 1}] +player_production_level = 1 +production_time_seconds = 20 +fill_color = "#9933CC" +glyph = "Dh" diff --git a/bin/app/data/config/recipes.toml b/bin/app/data/config/recipes.toml index 363ea4e..dd80f09 100644 --- a/bin/app/data/config/recipes.toml +++ b/bin/app/data/config/recipes.toml @@ -1,3 +1,29 @@ +# 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 (everything is unlocked for now so the +# full tree is testable). +# +# Input chain per game phase — each phase 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. +# +# Run tools/verify_recipes.py after editing to check that every consumed +# item has a producer and every item has a visuals.toml entry. + +# ----------------------------------------------------------------------------- +# Mining (tier 0) +# ----------------------------------------------------------------------------- + [[recipe]] id = "mine_iron_ore" building = "miner" @@ -12,6 +38,18 @@ inputs = [] outputs = [{item = "copper_ore", amount = 1}] duration_seconds = 1.5 +# Titanium is the midgame ore: mined three times slower than iron. +[[recipe]] +id = "mine_titanium_ore" +building = "miner" +inputs = [] +outputs = [{item = "titanium_ore", amount = 1}] +duration_seconds = 3.0 + +# ----------------------------------------------------------------------------- +# Smelting (tier 1) +# ----------------------------------------------------------------------------- + [[recipe]] id = "iron_ingot" building = "smelter" @@ -27,54 +65,17 @@ outputs = [{item = "copper_ingot", amount = 1}] duration_seconds = 2.5 [[recipe]] -id = "circuit_board" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_ingot", amount = 2}] -outputs = [{item = "circuit_board", amount = 1}] -duration_seconds = 2.0 - -[[recipe]] -id = "drone_hull" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 1}] -outputs = [{item = "drone_hull", amount = 1}] +id = "titanium_ingot" +building = "smelter" +inputs = [{item = "titanium_ore", amount = 3}] +outputs = [{item = "titanium_ingot", amount = 1}] duration_seconds = 4.0 -[[recipe]] -id = "laser_cannon_xs_module" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 2}, {item = "circuit_board", amount = 1}] -outputs = [{item = "laser_cannon_xs_module", amount = 1}] -duration_seconds = 3.0 - -[[recipe]] -id = "laser_cannon_s_module" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}] -outputs = [{item = "laser_cannon_s_module", amount = 1}] -duration_seconds = 6.0 - -[[recipe]] -id = "salvager_module" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 1}] -outputs = [{item = "salvager_module", amount = 1}] -duration_seconds = 6.0 - -[[recipe]] -id = "repair_tool_module" -building = "assembler" -inputs = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 2}] -outputs = [{item = "repair_tool_module", amount = 1}] -duration_seconds = 6.0 - -[[recipe]] -id = "building_blocks" -unlock_at_station_level = -1 -building = "assembler" -inputs = [{item = "iron_ingot", amount = 4}] -outputs = [{item = "building_block", amount = 10}] -duration_seconds = 4.0 +# ----------------------------------------------------------------------------- +# Reprocessing +# +# The only source of advanced_alloy: salvaged scrap from destroyed ships. +# ----------------------------------------------------------------------------- [[recipe]] id = "reprocessing_cycle" @@ -85,15 +86,354 @@ duration_seconds = 3.0 [[recipe.outputs]] item = "iron_ingot" amount = 2 - probability = 0.6 + probability = 0.45 [[recipe.outputs]] - item = "circuit_board" + item = "copper_ingot" amount = 1 - probability = 0.3 + probability = 0.25 + + [[recipe.outputs]] + item = "titanium_ingot" + amount = 1 + probability = 0.15 [[recipe.outputs]] item = "advanced_alloy" amount = 1 - probability = 0.1 - \ No newline at end of file + probability = 0.15 + +# ----------------------------------------------------------------------------- +# Basic components (tier 2, early game) +# ----------------------------------------------------------------------------- + +[[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 = 2.0 + +[[recipe]] +id = "circuit_board" +unlock_at_station_level = -1 +building = "assembler" +inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_wire", amount = 2}] +outputs = [{item = "circuit_board", amount = 1}] +duration_seconds = 2.0 + +[[recipe]] +id = "building_blocks" +unlock_at_station_level = -1 +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}] +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 +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}] +outputs = [{item = "drone_hull", amount = 1}] +duration_seconds = 4.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}, +] +outputs = [{item = "frigate_hull", amount = 1}] +duration_seconds = 8.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}, +] +outputs = [{item = "destroyer_hull", amount = 1}] +duration_seconds = 10.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}, +] +outputs = [{item = "cruiser_hull", amount = 1}] +duration_seconds = 15.0 + +[[recipe]] +id = "battlecruiser_hull" +unlock_at_station_level = -1 +building = "assembler" +inputs = [ + {item = "titanium_frame", amount = 3}, + {item = "steel_plate", amount = 6}, + {item = "drive_unit", amount = 1}, + {item = "targeting_unit", amount = 1}, +] +outputs = [{item = "battlecruiser_hull", amount = 1}] +duration_seconds = 20.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}, +] +outputs = [{item = "battleship_hull", amount = 1}] +duration_seconds = 30.0 + +[[recipe]] +id = "dreadnought_hull" +unlock_at_station_level = -1 +building = "assembler" +inputs = [ + {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 + +[[recipe]] +id = "carrier_hull" +unlock_at_station_level = -1 +building = "assembler" +inputs = [ + {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 diff --git a/bin/app/data/config/ships.toml b/bin/app/data/config/ships.toml index 3e07a2f..7f879e8 100644 --- a/bin/app/data/config/ships.toml +++ b/bin/app/data/config/ships.toml @@ -1,8 +1,25 @@ +# ships.toml +# +# First real-content iteration: ship ids and layout grids are the designed +# content; stats, materials, and production times are placeholders until the +# recipe and balancing passes. +# +# Size classes: +# xs drone 1 cell — exactly one 1x1 module +# s frigate, destroyer no 2x2 area anywhere: only 1x1/1x2/1x3/L modules fit +# m cruiser, battlecruiser 2x2 areas (m guns, drone bays) but no 3x3 area +# l battleship four m guns, or exactly one 3x3 l gun at heavy +# opportunity cost +# xl dreadnought, carrier dreadnought fits three l guns but no drone +# hangar; carrier fits one drone hangar (2x6) +# but no l gun (its deck rows are broken up +# by elevator shafts) + [[ship]] id = "drone" unlock_at_station_level = -1 layout = ["O"] -default_modules = [{type = "laser_cannon_xs", x = 0, y = 0, rotation = "east"}] +default_modules = [{type = "laser_cannon_s", x = 0, y = 0, rotation = "east"}] [ship.schematic] materials = [{item = "iron_ore", amount = 1}] @@ -24,3 +41,255 @@ sensor_range_m_formula = "150" [ship.loot] scrap_drop = 2 + + +# Frigate — 5 cells in a plus shape. Holds a couple of small guns plus at +# most one 1x2 support (every 1x2 placement crosses the center cell), or one +# L-shaped weapon modifier, or an afterburner spanning the full center line. +[[ship]] +id = "frigate" +unlock_at_station_level = -1 +layout = [ + "XOX", + "OOO", + "XOX", +] + +[ship.schematic] +materials = [{item = "frigate_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 10 + +[ship.health] +hp_formula = "30" + +[ship.movement] +speed_mps_formula = "30" +main_acceleration_mpss_formula = "50" +maneuvering_acceleration_mpss_formula = "25" +angular_acceleration_radpss_formula = "8" +max_rotation_speed_radps_formula = "4" + +[ship.sensor] +sensor_range_m_formula = "200" + +[ship.loot] +scrap_drop = 5 + + +# Destroyer — 8 cells: a long gun deck with three turret bumps on top. +# Still no 2x2 area, so it packs more small guns than a frigate but can never +# mount medium hardware. +[[ship]] +id = "destroyer" +unlock_at_station_level = -1 +layout = [ + "OXOXO", + "OOOOO", +] + +[ship.schematic] +materials = [{item = "destroyer_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 15 + +[ship.health] +hp_formula = "50" + +[ship.movement] +speed_mps_formula = "25" +main_acceleration_mpss_formula = "40" +maneuvering_acceleration_mpss_formula = "20" +angular_acceleration_radpss_formula = "6" +max_rotation_speed_radps_formula = "3" + +[ship.sensor] +sensor_range_m_formula = "220" + +[ship.loot] +scrap_drop = 8 + + +# Cruiser — 12 cells with notched corners. Fits at most two 2x2 m guns +# (stacked through the middle), leaving the four side cells for small +# supports; no 3x3 area exists for an l gun. +[[ship]] +id = "cruiser" +unlock_at_station_level = -1 +layout = [ + "XOOX", + "OOOO", + "OOOO", + "XOOX", +] + +[ship.schematic] +materials = [{item = "cruiser_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 25 + +[ship.health] +hp_formula = "120" + +[ship.movement] +speed_mps_formula = "20" +main_acceleration_mpss_formula = "30" +maneuvering_acceleration_mpss_formula = "15" +angular_acceleration_radpss_formula = "4" +max_rotation_speed_radps_formula = "2" + +[ship.sensor] +sensor_range_m_formula = "250" + +[ship.loot] +scrap_drop = 15 + + +# Battlecruiser — 16 cells: a wide bow split into two gun cheeks, tapering +# toward the stern. Fits three 2x2 m guns (two in the cheeks, one through +# the middle) with small support slots left over; the split bow and tapered +# stern leave no 3x3 area for an l gun and no 2x6 area for a drone hangar. +[[ship]] +id = "battlecruiser" +unlock_at_station_level = -1 +layout = [ + "OOXXOO", + "OOOOOO", + "XOOOOX", + "XXOOXX", +] + +[ship.schematic] +materials = [{item = "battlecruiser_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 35 + +[ship.health] +hp_formula = "180" + +[ship.movement] +speed_mps_formula = "18" +main_acceleration_mpss_formula = "25" +maneuvering_acceleration_mpss_formula = "12" +angular_acceleration_radpss_formula = "3" +max_rotation_speed_radps_formula = "1.5" + +[ship.sensor] +sensor_range_m_formula = "260" + +[ship.loot] +scrap_drop = 20 + + +# Battleship — 24 cells: a broadside hull with notched flanks on every other +# row. Fits four 2x2 m guns (two per gun deck) with the bow, stern, and flank +# cells left for supports. All 3x3 placements crowd the center columns, so at +# most ONE l gun fits — and mounting it blocks every m gun mount, leaving +# only narrow support strips. The notched rows are never adjacent-and-full, +# so no 2x6 drone hangar fits. +[[ship]] +id = "battleship" +unlock_at_station_level = -1 +layout = [ + "XOOOOX", + "OOOOOO", + "XOOOOX", + "OOOOOO", + "XOOOOX", +] + +[ship.schematic] +materials = [{item = "battleship_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 60 + +[ship.health] +hp_formula = "350" + +[ship.movement] +speed_mps_formula = "14" +main_acceleration_mpss_formula = "18" +maneuvering_acceleration_mpss_formula = "8" +angular_acceleration_radpss_formula = "2" +max_rotation_speed_radps_formula = "1" + +[ship.sensor] +sensor_range_m_formula = "280" + +[ship.loot] +scrap_drop = 35 + + +# Dreadnought — 36 cells: the main battery deck is split into three 3x3 gun +# slots by structural spacer columns, so exactly three l guns fit side by +# side (or m guns / supports in unused slots). The spacers cap every +# horizontal run at 5 cells, so the 2x6 drone hangar can never fit — carriers +# stay the only hangar hull. Bow and stern strips hold supports. +[[ship]] +id = "dreadnought" +unlock_at_station_level = -1 +layout = [ + "XXXOOOOOXXX", + "OOOXOOOXOOO", + "OOOXOOOXOOO", + "OOOXOOOXOOO", + "XXOOXXXOOXX", +] + +[ship.schematic] +materials = [{item = "dreadnought_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 120 + +[ship.health] +hp_formula = "800" + +[ship.movement] +speed_mps_formula = "8" +main_acceleration_mpss_formula = "10" +maneuvering_acceleration_mpss_formula = "5" +angular_acceleration_radpss_formula = "1" +max_rotation_speed_radps_formula = "0.5" + +[ship.sensor] +sensor_range_m_formula = "300" + +[ship.loot] +scrap_drop = 60 + + +# Carrier — 37 cells: the top flight deck (rows 0-1) is the only place wide +# enough for the 2x6 drone hangar, and exactly one fits. The middle deck row +# is broken up by elevator shafts (the X cells) so no 3x3 l gun can ever fit; +# the lower decks hold supports and 2x2 point-defense m guns. +[[ship]] +id = "carrier" +unlock_at_station_level = -1 +layout = [ + "XOOOOOOOOX", + "OOOOOOOOOO", + "OOXOOXOOXO", + "XOOOOOOOOX", + "XXXOOOOXXX", +] + +[ship.schematic] +materials = [{item = "carrier_hull", amount = 1}] +player_production_level = 1 +production_time_seconds = 120 + +[ship.health] +hp_formula = "700" + +[ship.movement] +speed_mps_formula = "9" +main_acceleration_mpss_formula = "10" +maneuvering_acceleration_mpss_formula = "5" +angular_acceleration_radpss_formula = "1" +max_rotation_speed_radps_formula = "0.5" + +[ship.sensor] +sensor_range_m_formula = "350" + +[ship.loot] +scrap_drop = 60 diff --git a/bin/app/data/config/visuals.toml b/bin/app/data/config/visuals.toml index c513680..0a59ecf 100644 --- a/bin/app/data/config/visuals.toml +++ b/bin/app/data/config/visuals.toml @@ -106,6 +106,8 @@ glyph = "E" # drawn around it. One section per ItemType. # ----------------------------------------------------------------------------- +# --- ores --- + [items.iron_ore] fill = "#8a5a4a" outline = "#201010" @@ -114,6 +116,12 @@ outline = "#201010" fill = "#c47a3a" outline = "#3a1a0a" +[items.titanium_ore] +fill = "#9aa3ad" +outline = "#2a2e33" + +# --- ingots --- + [items.iron_ingot] fill = "#b0b0b8" outline = "#202028" @@ -122,30 +130,80 @@ outline = "#202028" fill = "#d48a4a" outline = "#402010" -[items.circuit_board] -fill = "#2ea35a" -outline = "#0a2a14" +[items.titanium_ingot] +fill = "#c8d2dc" +outline = "#3a4048" -[items.advanced_alloy] -fill = "#a06acc" -outline = "#201030" - -[items.building_block] -fill = "#c8b070" -outline = "#302810" +# --- salvage loop --- [items.scrap] fill = "#7a7268" outline = "#201a14" -[items.drone_hull] -fill = "#1b1b1b" -outline = "#1402b3" +[items.advanced_alloy] +fill = "#a06acc" +outline = "#201030" -[items.laser_cannon_xs_module] +# --- basic components --- + +[items.copper_wire] +fill = "#e09a50" +outline = "#3a2008" + +[items.steel_plate] +fill = "#8a92a0" +outline = "#22262c" + +[items.circuit_board] +fill = "#2ea35a" +outline = "#0a2a14" + +[items.building_block] +fill = "#c8b070" +outline = "#302810" + +# --- advanced components --- + +[items.mechanical_parts] +fill = "#6f7a66" +outline = "#1c2018" + +[items.targeting_unit] +fill = "#3a9e8c" +outline = "#0c2824" + +[items.drive_unit] +fill = "#4a6ad0" +outline = "#101a38" + +[items.titanium_frame] +fill = "#b8c4d4" +outline = "#343c48" + +# --- capital components --- + +[items.reinforced_plating] +fill = "#8a6ad0" +outline = "#1c1038" + +[items.capital_core] +fill = "#b040d0" +outline = "#280c30" + +# --- module items --- + +[items.laser_cannon_s_module] fill = "#691313" outline = "#f3ff4f" +[items.laser_cannon_m_module] +fill = "#892020" +outline = "#f3ff4f" + +[items.laser_cannon_l_module] +fill = "#a92d2d" +outline = "#f3ff4f" + [items.salvager_module] fill = "#b2cfdd" outline = "#236137" @@ -154,6 +212,76 @@ outline = "#236137" fill = "#2e9ba3" outline = "#689275" +[items.armor_plates_module] +fill = "#808080" +outline = "#202020" + +[items.sensor_booster_module] +fill = "#40a0ff" +outline = "#102840" + +[items.maneuvering_thrusters_module] +fill = "#5090e0" +outline = "#142438" + +[items.afterburner_module] +fill = "#6080c0" +outline = "#182030" + +[items.weapon_upgrade_module] +fill = "#ff4040" +outline = "#401010" + +[items.weapon_primer_module] +fill = "#e03838" +outline = "#380e0e" + +[items.weapon_stabilizer_module] +fill = "#c03030" +outline = "#300c0c" + +[items.drone_bay_module] +fill = "#cc66ff" +outline = "#331040" + +[items.drone_hangar_module] +fill = "#9933cc" +outline = "#260c33" + +# --- ship hulls (outline matches the ship's fleet color in [ships.*]) --- + +[items.drone_hull] +fill = "#1b1b1b" +outline = "#3366ff" + +[items.frigate_hull] +fill = "#1b1b1b" +outline = "#44aaff" + +[items.destroyer_hull] +fill = "#1b1b1b" +outline = "#33ccaa" + +[items.cruiser_hull] +fill = "#1b1b1b" +outline = "#66cc33" + +[items.battlecruiser_hull] +fill = "#1b1b1b" +outline = "#cccc33" + +[items.battleship_hull] +fill = "#1b1b1b" +outline = "#ff9933" + +[items.dreadnought_hull] +fill = "#1b1b1b" +outline = "#ff5533" + +[items.carrier_hull] +fill = "#1b1b1b" +outline = "#cc66ff" + # ----------------------------------------------------------------------------- # Ships # @@ -164,6 +292,34 @@ outline = "#689275" fill = "#3366ff" outline = "#ffffff" +[ships.frigate] +fill = "#44aaff" +outline = "#ffffff" + +[ships.destroyer] +fill = "#33ccaa" +outline = "#ffffff" + +[ships.cruiser] +fill = "#66cc33" +outline = "#ffffff" + +[ships.battlecruiser] +fill = "#cccc33" +outline = "#ffffff" + +[ships.battleship] +fill = "#ff9933" +outline = "#ffffff" + +[ships.dreadnought] +fill = "#ff5533" +outline = "#ffffff" + +[ships.carrier] +fill = "#cc66ff" +outline = "#ffffff" + # ----------------------------------------------------------------------------- # Laser beams (REQ-SHP-FIRING-BEAM) # ----------------------------------------------------------------------------- diff --git a/bin/balancing/data/balancing.toml b/bin/balancing/data/balancing.toml index 1705d91..3defd7e 100644 --- a/bin/balancing/data/balancing.toml +++ b/bin/balancing/data/balancing.toml @@ -12,7 +12,7 @@ enemy_buffer_width_tiles = 10 level = 1 count = 5 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, ] [[arena.team]] @@ -22,7 +22,7 @@ enemy_buffer_width_tiles = 10 level = 1 count = 2 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, {type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"}, {type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"}, {type = "weapon_upgrade", x = 1, y = 1, rotation = "east"}, @@ -44,7 +44,7 @@ enemy_buffer_width_tiles = 10 level = 1 count = 5 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, ] [[arena.team]] @@ -54,7 +54,7 @@ enemy_buffer_width_tiles = 10 level = 1 count = 3 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, ] [[arena.team.ship]] @@ -79,7 +79,7 @@ enemy_buffer_width_tiles = 15 level = 1 count = 3 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, ] [[arena.team.station]] type = "player_station" @@ -99,5 +99,5 @@ enemy_buffer_width_tiles = 15 level = 1 count = 8 modules = [ - {type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"}, + {type = "laser_cannon_s", x = 1, y = 1, rotation = "east"}, ] diff --git a/docs/content_design.md b/docs/content_design.md new file mode 100644 index 0000000..ed4e49a --- /dev/null +++ b/docs/content_design.md @@ -0,0 +1,194 @@ +# Content Design — Ships & Modules + +First real-content iterations (June 2026). Pass 1 defined ship hull grids and +module surface masks; pass 2 defined the production tree (recipes). Stats and +threat costs in the config files are still placeholders for the balancing +pass. + +## Design principle: footprint gating + +Which module fits on which hull is controlled purely by geometry — no +explicit allow-lists. Each hull grid is shaped so that it physically cannot +contain the footprint of modules from a larger size class. This keeps the +rules transparent to the player ("it doesn't fit because there is no room") +and makes them trivially moddable through the config files alone. + +### Module footprint ladder + +| Footprint | Modules | Smallest hull that fits it | +|-----------|---------|----------------------------| +| 1x1 | laser_cannon_s, salvager, repair_tool | drone | +| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate | +| 1x3 | afterburner | frigate (eats most of it) | +| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate | +| 2x2 | laser_cannon_m, drone_bay | cruiser | +| 3x3 | laser_cannon_l | battleship | +| 2x6 | drone_hangar | carrier (only) | + +### Hull grids + +`O` = buildable cell, `X` = hull structure (not buildable). + +**drone (xs, 1 cell)** — exactly one 1x1 module: a small gun, a salvager, or +a repair tool. This is what makes drone roles swappable. + + O + +**frigate (s, 5 cells)** — plus shape. Every 1x2 placement crosses the center +cell, so at most ONE 1x2 support fits; alternatively one L-shaped weapon +modifier or one afterburner through the center line. Gun-boat with one or two +support modules, as intended. + + XOX + OOO + XOX + +**destroyer (s, 8 cells)** — gun deck with three turret bumps. More cells +than the frigate (more small guns), but still no 2x2 area anywhere, so medium +hardware can never be mounted. + + OXOXO + OOOOO + +**cruiser (m, 12 cells)** — notched corners. Fits at most two 2x2 m guns +(stacked through the middle), leaving the side cells for supports. No 3x3 +area. + + XOOX + OOOO + OOOO + XOOX + +**battlecruiser (m, 16 cells)** — split bow with two gun cheeks, tapered +stern. Fits three 2x2 m guns — one more than the cruiser — with small support +slots left over. The bow split and stern taper prevent any 3x3 area (no l +gun) and any 2x6 area (no drone hangar). + + OOXXOO + OOOOOO + XOOOOX + XXOOXX + +**battleship (l, 24 cells)** — broadside hull with notched flanks on every +other row. Fits four 2x2 m guns (two per gun deck) — one more than the +battlecruiser — with bow, stern, and flank cells for supports. All 3x3 +placements crowd the center columns, so at most ONE l gun fits: mounted +center it blocks every m gun mount (pure support strips remain), mounted +offset it still allows two m guns. The notched rows are never adjacent-and- +full, so no 2x6 drone hangar fits. + + XOOOOX + OOOOOO + XOOOOX + OOOOOO + XOOOOX + +**dreadnought (xl, 36 cells)** — the main battery deck is split into three +3x3 gun slots by structural spacer columns, so exactly three l guns fit side +by side (or m guns / supports in unused slots), plus bow/stern strips for +supports. The spacers cap every horizontal run at 5 cells, so the 2x6 drone +hangar can never fit — the carrier stays the only hangar hull. + + XXXOOOOOXXX + OOOXOOOXOOO + OOOXOOOXOOO + OOOXOOOXOOO + XXOOXXXOOXX + +**carrier (xl, 37 cells)** — the top flight deck (rows 0–1) is the only +region wide enough for the 2x6 drone hangar, and exactly one fits. The middle +deck row is broken up by elevator shafts (X cells placed so every 3-column +window hits one), which is what prevents any 3x3 l gun from ever fitting. +Lower decks hold supports and 2x2 point-defense m guns. + + XOOOOOOOOX + OOOOOOOOOO + OOXOOXOOXO + XOOOOOOOOX + XXXOOOOXXX + +### Verified gating matrix + +Checked programmatically against the configs (all four mask rotations, +all placements) with `tools/verify_layouts.py` — re-run it after editing +layout grids or surface masks: + + python dota_factory/tools/verify_layouts.py + +| Footprint | drone | frigate | destroyer | cruiser | battlecruiser | battleship | dreadnought | carrier | +|-----------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +| 1x1 | x | x | x | x | x | x | x | x | +| 1x2 | | x | x | x | x | x | x | x | +| 1x3 | | x | x | x | x | x | x | x | +| L-shape | | x | x | x | x | x | x | x | +| 2x2 | | | | x | x | x | x | x | +| 3x3 | | | | | | x | x | | +| 2x6 | | | | | | | | x | + +Maximum simultaneous (disjoint) placements: m guns — cruiser 2, +battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3; +drone hangar — carrier 1. + +## Production tree + +Design principle: each game phase adds exactly one new base input chain, so +factory complexity ramps alongside ship size. + +| Phase | New input | How acquired | Unlocks | +|-------|-----------|--------------|---------| +| early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports | +| mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers | +| late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar | + +The advanced_alloy gate is the core loop hook: capital ship production +requires fighting (salvaging scrap from kills and reprocessing it), not just +mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots +or advanced_alloy probabilistically. + +Intermediate components, by tier: + +- **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board + (iron + wire), building_block (iron). +- **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits + + wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame + (titanium + steel). +- **Tier 4 (late):** reinforced_plating (steel + advanced_alloy), + capital_core (targeting_unit + drive_unit + 2 advanced_alloy). + +Hulls and modules consume intermediates of their tier: early items are built +from tier-2 parts, midgame items require tier-3 parts (deeper chains, more +assemblers), capital items require tier-4 parts (and therefore combat). Hull +items are named `_hull`; module items `_module`. Every item has +an `[items.*]` entry in visuals.toml; hull item outlines match the ship's +fleet color from `[ships.*]`. + +Consistency is checked by `tools/verify_recipes.py` — re-run it after editing +recipes, ship/module materials, or visuals: + + python dota_factory/tools/verify_recipes.py + +It verifies every consumed item has a producer, every item has a visuals +entry, flags orphaned items, and prints which items are reprocessing-only +(currently exactly advanced_alloy). + +## Deliberate placeholders / open questions for later passes + +- All new hulls have `threat.cost_formula = "0"` so enemy waves do not spawn + them yet (WaveSystem treats any ship with positive threat cost as wave- + eligible, regardless of unlock level). The balancing pass should set real + threat costs together with `default_modules` loadouts so waves spawn them + armed. +- All new hulls and all assembler recipes are `unlock_at_station_level = -1` + (available from the start) to make testing easy; the balancing pass should + stagger these so mid/lategame recipes drop as schematics from enemy defence + stations. +- Recipe quantities and durations are a first guess, deliberately roughly + tiered (capital hulls ~60 s, drones 4 s); the balancing pass tunes them. +- `drone_bay` and `drone_hangar` are footprint-only placeholders: the drone + launching capability does not exist in the simulation yet, so they define + no capability section. +- Renames in this pass: `laser_cannon_xs` → `laser_cannon_s` (the old 2x2 + `laser_cannon_s` became `laser_cannon_m`), `armor_plate` → `armor_plates`, + `manuvering_thrusters` → `maneuvering_thrusters` (typo fix). Test data + under `bin/test/data/config` intentionally still uses the old ids — it is + an independent fixture set.