7.8 KiB
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 <ship>_hull; module items <module>_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 withdefault_modulesloadouts 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_bayanddrone_hangarare 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 2x2laser_cannon_sbecamelaser_cannon_m),armor_plate→armor_plates,manuvering_thrusters→maneuvering_thrusters(typo fix). Test data underbin/test/data/configintentionally still uses the old ids — it is an independent fixture set.