# Content Design — Ships & Modules First real-content iterations (June 2026). Pass 1 defined ship hull grids and module surface masks; pass 2 defined the production tree (recipes). Stats and threat costs in the config files are still placeholders for the balancing pass. ## Design principle: footprint gating Which module fits on which hull is controlled purely by geometry — no explicit allow-lists. Each hull grid is shaped so that it physically cannot contain the footprint of modules from a larger size class. This keeps the rules transparent to the player ("it doesn't fit because there is no room") and makes them trivially moddable through the config files alone. ### Module footprint ladder | Footprint | Modules | Smallest hull that fits it | |-----------|---------|----------------------------| | 1x1 | laser_cannon_s, salvager, repair_tool | drone | | 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate | | 1x3 | afterburner | frigate (eats most of it) | | L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate | | 2x2 | laser_cannon_m, drone_bay | cruiser | | 3x3 | laser_cannon_l | battleship | | 2x6 | drone_hangar | carrier (only) | ### Hull grids `O` = buildable cell, `X` = hull structure (not buildable). **drone (xs, 1 cell)** — exactly one 1x1 module: a small gun, a salvager, or a repair tool. This is what makes drone roles swappable. O **frigate (s, 5 cells)** — plus shape. Every 1x2 placement crosses the center cell, so at most ONE 1x2 support fits; alternatively one L-shaped weapon modifier or one afterburner through the center line. Gun-boat with one or two support modules, as intended. XOX OOO XOX **destroyer (s, 8 cells)** — gun deck with three turret bumps. More cells than the frigate (more small guns), but still no 2x2 area anywhere, so medium hardware can never be mounted. OXOXO OOOOO **cruiser (m, 12 cells)** — notched corners. Fits at most two 2x2 m guns (stacked through the middle), leaving the side cells for supports. No 3x3 area. XOOX OOOO OOOO XOOX **battlecruiser (m, 16 cells)** — split bow with two gun cheeks, tapered stern. Fits three 2x2 m guns — one more than the cruiser — with small support slots left over. The bow split and stern taper prevent any 3x3 area (no l gun) and any 2x6 area (no drone hangar). OOXXOO OOOOOO XOOOOX XXOOXX **battleship (l, 24 cells)** — broadside hull with notched flanks on every other row. Fits four 2x2 m guns (two per gun deck) — one more than the battlecruiser — with bow, stern, and flank cells for supports. All 3x3 placements crowd the center columns, so at most ONE l gun fits: mounted center it blocks every m gun mount (pure support strips remain), mounted offset it still allows two m guns. The notched rows are never adjacent-and- full, so no 2x6 drone hangar fits. XOOOOX OOOOOO XOOOOX OOOOOO XOOOOX **dreadnought (xl, 36 cells)** — the main battery deck is split into three 3x3 gun slots by structural spacer columns, so exactly three l guns fit side by side (or m guns / supports in unused slots), plus bow/stern strips for supports. The spacers cap every horizontal run at 5 cells, so the 2x6 drone hangar can never fit — the carrier stays the only hangar hull. XXXOOOOOXXX OOOXOOOXOOO OOOXOOOXOOO OOOXOOOXOOO XXOOXXXOOXX **carrier (xl, 37 cells)** — the top flight deck (rows 0–1) is the only region wide enough for the 2x6 drone hangar, and exactly one fits. The middle deck row is broken up by elevator shafts (X cells placed so every 3-column window hits one), which is what prevents any 3x3 l gun from ever fitting. Lower decks hold supports and 2x2 point-defense m guns. XOOOOOOOOX OOOOOOOOOO OOXOOXOOXO XOOOOOOOOX XXXOOOOXXX ### Verified gating matrix Checked programmatically against the configs (all four mask rotations, all placements) with `tools/verify_layouts.py` — re-run it after editing layout grids or surface masks: python dota_factory/tools/verify_layouts.py | Footprint | drone | frigate | destroyer | cruiser | battlecruiser | battleship | dreadnought | carrier | |-----------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | 1x1 | x | x | x | x | x | x | x | x | | 1x2 | | x | x | x | x | x | x | x | | 1x3 | | x | x | x | x | x | x | x | | L-shape | | x | x | x | x | x | x | x | | 2x2 | | | | x | x | x | x | x | | 3x3 | | | | | | x | x | | | 2x6 | | | | | | | | x | Maximum simultaneous (disjoint) placements: m guns — cruiser 2, battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3; drone hangar — carrier 1. ## Production tree (first pass — superseded) **Superseded (July 2026):** this first-pass tree predates the rules in `docs/progression_design.md` and will be replaced. The decisions for the redesign are recorded in "Production tree v2 — decisions" below; the text of this section is kept for reference until the new tree lands. Design principle: each game phase adds exactly one new base input chain, so factory complexity ramps alongside ship size. | Phase | New input | How acquired | Unlocks | |-------|-----------|--------------|---------| | early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports | | mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers | | late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar | The advanced_alloy gate is the core loop hook: capital ship production requires fighting (salvaging scrap from kills and reprocessing it), not just mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots or advanced_alloy probabilistically. Intermediate components, by tier: - **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board (iron + wire), building_block (iron). - **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits + wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame (titanium + steel). - **Tier 4 (late):** reinforced_plating (steel + advanced_alloy), capital_core (targeting_unit + drive_unit + 2 advanced_alloy). Hulls and modules consume intermediates of their tier: early items are built from tier-2 parts, midgame items require tier-3 parts (deeper chains, more assemblers), capital items require tier-4 parts (and therefore combat). Hull items are named `_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). ## Production tree v2 — decisions (July 2026) The old tree is discarded; the new one is designed against the rules in `docs/progression_design.md` (ratio curve, cost ladder, cost archetypes, refactorability). Decisions fixed so far: ### Base inputs (4) and fiction - **iron_ore, copper_ore** — from the start, minable on every asteroid tile. Fiction: the asteroid is an M-type (metal) body — its bulk rock *is* ore, which is why the shipyard operation was built here at all. - **quartz** — mid-game, minable only on geode deposit patches in expansion territory (see the Resource deposits rules in `progression_design.md`). Fiction: ordinary silicate dust is everywhere and worthless; chips and optics need rare, pocket-bound optical-grade crystal. - **voidsteel** — late-game, obtained only by reprocessing scrap. Fiction: battle-forged — formed when weapon plasma anneals hull metal in the violence of ship destruction. Any wreck yields it, including the player's own; no foundry can replicate it. - **titanium is dropped.** Its hull-gating role moves to quartz-era control systems ("you can smelt all the steel you want, but you cannot steer a battlecruiser without electronics") and possibly a *quality* steel step — e.g. a long-running hardened-steel recipe (time-heavy archetype). Open question for the tree draft: quality step vs. electronics-only gating; explicitly **not** sheer steel quantity alone. ### Material palette (fingerprints per family) - **iron/steel** — structure. - **copper** — conduction and heat: wiring, coils, heat sinks. - **silicon family** (all derived from quartz): silicon (logic, sensors), glass/optics (lenses, focusing crystals), ceramics (heat shielding, insulators). Carries the non-metal variety without extra base inputs. - **voidsteel** — capital-tier structure and exotics. - Deliberately skipped: carbon (mostly redundant with copper/ceramics), plastics (drags in Factorio-style chemical chains; ceramics read more sci-fi anyway), volatiles/ice (materials are build costs only — no consumption mechanic to justify fuel). ### Weapons - All current lasers are renamed to **railguns** (`laser_cannon_s/m/l` → `railgun_s/m/l`); footprints and the gating matrix are unchanged. Implementation stays as-is (instant damage application, no projectile, no ammunition) — the beam visual reads as a tracer round. Materials: iron slugs, copper coils, steel rails — the starting-metal fingerprint. - **Lasers are reserved for later** as a genuinely distinct weapon type (e.g. once projectile/ammunition mechanics exist for other families), arriving with quartz optics. More weapon types are planned; railguns are simply the baseline tech that ships with v1. ### Tree structure — draft Recipes are sketched as input lists only; quantities and durations come in the numbers pass, tuned so every chain sums to its threat-ladder value and follows the ratio curve (t1 nice → t4 strange). Glass/optics are cut from v1 — their only consumers would be lasers, which are deferred; the silicon family ships as silicon + ceramics. **Mined (miner):** `iron_ore`, `copper_ore` (every tile), `quartz` (geode deposits in expansion territory). **Smelted (smelter — exactly one recipe per input item):** | output | input | ratio class | |---|---|---| | iron_ingot | iron_ore | nice (1:1 or 1:2) | | copper_ingot | copper_ore | nice | | silicon | quartz | mid entry | | iron_ingot | scrap | the safe, boring scrap sink | **Reprocessing pool (scrap):** `iron_ingot`, `copper_ingot`, `silicon`, `voidsteel` — the only source of voidsteel. Weights authored for the fully unlocked pool state. **Tier 2 — early intermediates (clean ratios, ~2:3):** | item | inputs | role | |---|---|---| | steel_plate | iron_ingot | structure backbone, highest volume | | copper_wire | copper_ingot | conductors | | copper_coil | copper_wire | electromagnets: railguns, thrusters | | building_block | steel_plate | depth-3 chain = the doubling-time knob | **Tier 3 — mid intermediates (strange ratios begin, need quartz):** | item | inputs | role | |---|---|---| | control_chip | silicon + copper_wire | electronics gate for m+ hulls | | capacitor_bank | copper_coil + silicon | power for railgun m/l | | hardened_steel | steel_plate (long cycle) | quality gate for m+ hulls; time-heavy | | ceramic_plate | quartz | heat shielding: drives, l guns, capitals | | drive_unit | steel_plate + copper_coil + control_chip | propulsion for m+ hulls | **Tier 4 — late intermediates (need voidsteel):** | item | inputs | role | |---|---|---| | voidsteel_plate | voidsteel + hardened_steel | capital structure | | capital_core | voidsteel + capacitor_bank + control_chip | capital heart | **Hull items** (`_hull`, assembler-made; the shipyard consumes the hull item plus module materials). The m+ hull gate is resolved as **both** of the open-question options: hardened_steel (quality steel, a deliberately long-running recipe — the time-heavy step) *and* control_chip (electronics): | hull | inputs | |---|---| | drone_hull | steel_plate | | frigate_hull | steel_plate + copper_wire | | destroyer_hull | steel_plate + copper_coil | | cruiser_hull | hardened_steel + control_chip | | battlecruiser_hull | hardened_steel + control_chip + drive_unit | | battleship_hull | voidsteel_plate + drive_unit + control_chip | | dreadnought_hull | voidsteel_plate + capital_core + drive_unit | | carrier_hull | voidsteel_plate + capital_core + drive_unit | **Module items** (`_module`, assembler-made prefabs — kept as items so shipyard belt inputs stay simple and module production can be stockpiled): | module | inputs | archetype | |---|---|---| | railgun_s | steel_plate + copper_coil | balanced | | salvager | steel_plate + copper_wire | balanced | | repair_tool | steel_plate + copper_wire | balanced | | armor_plates | steel_plate (many) | material-heavy, fast | | maneuvering_thrusters | steel_plate + copper_coil | balanced | | sensor_booster | copper_wire + copper_coil | lean (an antenna, no chip) | | afterburner | copper_coil + steel_plate | balanced | | weapon_stabilizer | steel_plate + copper_coil | balanced | | weapon_primer | capacitor_bank + copper_coil | mid; time-heavy | | weapon_upgrade | control_chip + copper_coil | mid; time-heavy | | railgun_m | capacitor_bank + steel_plate + copper_coil | mid | | drone_bay | control_chip + steel_plate + copper_coil | mid | | railgun_l | capacitor_bank + hardened_steel + ceramic_plate | late | | drone_hangar | voidsteel_plate + control_chip + drive_unit | late (carrier only) | Refactorability check (the default technique holds): railgun_s → m introduces capacitor_bank, built from a subset of the small gun's inputs (copper_coil) plus the new base resource (silicon); the m gun otherwise reuses the small gun's inputs. Hulls likewise: cruiser adds hardening (fed by the existing steel line) and chips (fed by the new quartz territory) without touching the iron/copper core. **Shortcut recipe candidates** (drop-only assembler schematics; not every strange chain gets one): - `iron_ore → steel_plate` — skips the ingot step on the highest-volume chain in the game. - `quartz → control_chip` — skips silicon on the electronics chain. - `iron_ingot → hardened_steel` — a nicer-ratio route past the deliberately awkward hardening step. Item count: 3 mined + scrap + 3 smelted + 4 t2 + 5 t3 + 3 t4 (incl. voidsteel) + 8 hulls + 14 modules ≈ 41 item types — same scale as the first-pass tree. `verify_recipes.py` must be re-run once this lands in recipes.toml; the numbers pass should add a threat-report tool that prints per-item threat values and producer:consumer ratio tables. ## Balancing targets (first pass, July 2026) The six root numbers for the balancing pass. Every derived value (threat rate, recipe quantities, block costs, scrap rates, unlock ladder) is tuned to hit these; when rebalancing later, change these first and re-derive, never the other way around. The rules they follow live in `docs/progression_design.md`. All targets are in **game time**. The player can pause and accelerate, so real session length differs; playtests should measure both. The time unit is the boss cycle (`world.toml boss_countdown_seconds`, 300 s). Destroying a station set advances the boss countdown by `boss_advance_seconds` (60 s), so cycles run shorter than nominal when pushing actively — these targets deliberately ignore that; playtesting will show real run lengths. 1. **Run length** — a winning run takes up to 2 hours of game time: win around boss cycle 20–24. Losing runs end earlier. 2. **Phase boundaries** — early = cycles 1–5 (iron/copper, small hulls), mid = cycles 6–14 (quartz, medium hulls), late = cycles 15+ (voidsteel, capitals). Push cadence roughly one station set per cycle from mid onward. This fixes the `unlock_at_station_level` ladder and, with the artifact win count, the `artifact_chance_formula` pacing. 3. **Factory size curve** — producing buildings over time; when saturated, output threat/s equals this count, so this curve IS the player power curve. Targets: ~25 when the starting asteroid is full (end of cycle 2), ~60 at the start of mid (cycle 6), ~120 at the start of late (cycle 15), ~150 near the win. `threat_rate_formula` must remain a fraction of this curve, and buildings plus belts must physically fit the asteroid plus affordable expansions. 4. **Threat-cost ladder** — total production-seconds per *fitted* hull (including a typical module loadout): drone ~10, frigate ~40, destroyer ~80, cruiser ~200, battlecruiser ~350, battleship ~700, dreadnought/carrier ~1500. Every production chain must sum to its ladder value. Cross-check against (3): fitted-ship cadence = ladder value / (factory threat/s devoted to military) — e.g. a mid factory spending half of 60 threat/s on ships fields a fitted cruiser roughly every 7 s. 5. **Fleet size** — swarm-leaning: ~25 player combat ships as the standing mid-game fleet. Standing fleet = build cadence (4) × average ship lifetime, so this target drives time-to-kill and therefore the combat stat magnitudes tuned in the arena pass. 6. **Block economy roots** — bootstrap complete (starting asteroid full) by the end of cycle 2; a factory spending ~30% of its capacity on blocks doubles in ~4 minutes early game; one expansion affordable per cycle at ~1/3 of block income, with escalating costs that eventually outrun any income (see the growth curve rules in `progression_design.md`). ## Deliberate placeholders / open questions for later passes - All new hulls have `threat.cost_formula = "0"` so enemy waves do not spawn them yet (WaveSystem treats any ship with positive threat cost as wave- eligible, regardless of unlock level). The balancing pass should set real threat costs together with `default_modules` loadouts so waves spawn them armed. - All new hulls and all assembler recipes are `unlock_at_station_level = -1` (available from the start) to make testing easy; the balancing pass should stagger these so mid/lategame recipes drop as schematics from enemy defence stations. - Recipe quantities and durations are a first guess, deliberately roughly tiered (capital hulls ~60 s, drones 4 s); the balancing pass tunes them. - `drone_bay` and `drone_hangar` are footprint-only placeholders: the drone launching capability does not exist in the simulation yet, so they define no capability section. - Renames in this pass: `laser_cannon_xs` → `laser_cannon_s` (the old 2x2 `laser_cannon_s` became `laser_cannon_m`), `armor_plate` → `armor_plates`, `manuvering_thrusters` → `maneuvering_thrusters` (typo fix). Test data under `bin/test/data/config` intentionally still uses the old ids — it is an independent fixture set.