208 KiB
Requirements
Config Files
Config files use the TOML format. The following config files drive game parameters:
-
world.toml — world dimensions, region widths, expansion amounts, building refund percentage, building deconstruction time, wave timing, boss wave timing, belt speed, starting building blocks, departure interval, ship orbit factor, rally orbit radius, scrap-per-threat conversion, combat target-selection parameters (target score formula, overclaim penalty formula, target hysteresis), artifact chance formula, artifact win count, view pan speeds (slow and fast horizontal pan speed and pan ramp band width), an optional building blocks tooltip string (shown as the header bar's building blocks stock hover tooltip, REQ-UI-BLOCKS-TOOLTIP; omitted when unset), and an optional artifact tooltip string (shown as the header bar's artifact count hover tooltip, REQ-UI-ARTIFACTS-TOOLTIP; omitted when unset).
-
buildings.toml — building block cost and construction time per building type, plus an optional tooltip description string per building type (shown as the build button's hover tooltip, REQ-UI-BUILD-TOOLTIP; omitted when unset). Whether a building type is available from game start or must be unlocked during play is not defined here but in unlocks.toml (REQ-LOCK-EXPLICIT): a building type granted by an unlock group starts locked and is hidden from the build menu until its group is awarded (REQ-LOCK-BUILDING).
-
recipes.toml — crafting recipes: inputs, outputs, quantities, durations, and reprocessing plant probabilities. Assembler recipe entries may optionally define
unlocked_at_start(boolean, default false): when true the recipe is available from game start regardless of the implicit item graph — used for base recipes that no schematic's materials reach (such as building blocks; see REQ-LOCK-IMPLICIT). Which assembler recipes must instead be awarded during play (explicitly gated) is defined in unlocks.toml, not here (REQ-LOCK-EXPLICIT); every remaining assembler recipe is implicitly unlocked through the item graph (REQ-LOCK-IMPLICIT). Any recipe entry may optionally defineicon(string): the id of an item whose icon represents the recipe in the recipe-selection dialog (REQ-UI-RECIPE-ICON); when omitted, the recipe's first output item is used. -
ships.toml — per schematic: a human-readable display name (used in the UI), hull stats (HP, max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, max rotation speed) as plain values, required build materials, a layout grid defining the ship's module slots, and a
default_moduleslist used for enemy wave ships (see REQ-WAV-DEFAULT-MODULES). Whether a ship schematic is available from game start or must be unlocked during play is defined in unlocks.toml (REQ-LOCK-EXPLICIT), not here. -
modules.toml — per module type: id, surface mask, materials list, production time, fill color, glyph, an optional tooltip description string (shown as the module selection button's hover tooltip, REQ-MOD-UI-MODULE-TOOLTIP; omitted when unset), and an optional capability section and/or stat modifier formulas. Whether a module schematic is available from game start or must be unlocked during play is defined in unlocks.toml (REQ-LOCK-EXPLICIT), not here. A module with a capability section (
[module.weapon],[module.salvage], or[module.repair]) containing base stat formulas is a capability module that grants the ship a weapon, salvage bay, or repair tool per instance (see REQ-MOD-CONFIG for the full list of formulas per capability type). A module with onlyadded_*/multiplied_*formulas is a passive module that modifies stats on the ship or on capability module instances (see REQ-MOD-STAT-CALC). -
unlocks.toml — unlock groups: each
[[unlock]]entry names a group of ship schematics, module schematics, building types, and/or assembler recipes that are awarded together from a single defence station drop (see Unlock Group Format, REQ-LOCK-EXPLICIT, REQ-DEF-SCHEMATIC-DROP). Anything not granted by any unlock group is available from game start. -
stations.toml — HP, damage, range, fire rate, and scrap drop for player and enemy defence stations, defined as formulas of station level.
-
visuals.toml — rendering-only config (not game parameters): fill and outline colors and glyphs (identity labels; used in the world for building types not covered by an icon and as the fallback when an icon file is missing — REQ-UI-WORLD-ICON) for every building type, item type, ship schematic, and station type; for items, the
fillandoutlinecolors are drawn as the item's belt/port square and serve as the fallback when the item's icon file is missing (REQ-UI-ITEM-ICON); a distinct beam color per tool type (weapon, repair, salvage) and beam width; overlay and toast colors; and building status light colors (grey, green, red, and yellow fills plus the outline color, REQ-UI-STATUS-LIGHT). Loaded by the UI at startup; the simulation does not read it. -
ship_layouts.toml — named layout blueprints per ship type; written and read by the application to persist the layout blueprint panel (REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD). Not a game parameter file; the simulation does not read it.
-
REQ-CFG-RELOAD: When the player triggers a Restart (REQ-UI-GAME-MENU), all config files are reloaded from disk before the simulation is reset to its initial state. Formula strings are recompiled at that point. This allows config edits made while the application is running to take effect without a full application restart.
Surface Mask Format
Buildings in buildings.toml define a surface_mask — a list of strings that describes the building's tile footprint and output port(s). Each character occupies one cell in the grid:
A— building tile that must be placed on an asteroid tile.S— building tile that must be placed on a space tile.(space) — empty cell; not part of the building footprint.>— output port indicator: the building tile immediately to its left has a rightward-facing output port (items push right).<— output port indicator: the building tile immediately to its right has a leftward-facing output port (items push left).^— output port indicator: the building tile immediately below it has an upward-facing output port (items push up).v— output port indicator: the building tile immediately above it has a downward-facing output port (items push down).
Output port indicators are not building tiles themselves. A building may have more than one output port (e.g. a splitter uses <A> to declare both a left and a right output on the same tile).
Ship Layout Format
Ships in ships.toml define a layout — a list of strings that describes the ship's module grid. Each character occupies one cell:
O— buildable cell; modules can be placed here.X— non-buildable cell; not part of the ship's interior.
The layout grid determines which cells are available for module placement in the layout configuration dialog.
Layout Blueprint TOML Format
Each entry in ship_layouts.toml represents a named layout blueprint:
[[blueprint]]
name = "Heavy Shields" # display name; must be unique within a ship type
ship_type = "fighter" # matches a schematic id in ships.toml
modules = [
{type = "shield_module", x = 0, y = 0, rotation = 0},
{type = "cannon_module", x = 2, y = 1, rotation = 90},
]
name— human-readable display name. Must be unique within a ship type; need not be globally unique.ship_type— the schematic id this blueprint belongs to. Must match a schematic defined inships.toml.modules— array of placed module instances. Each entry:type(module id frommodules.toml),xandy(zero-based column/row in the ship's layout grid),rotation(0, 90, 180, or 270 degrees clockwise). Ifmodulesis absent or empty, the blueprint represents an empty layout.
The modules array format is reused verbatim in balancing.toml ship entries (see REQ-BAL-TEAM).
Module Surface Mask Format
Modules in modules.toml define a surface_mask — a list of strings that describes the module's tile footprint within the ship layout grid. Each character occupies one cell:
O— module cell: must be placed on an unoccupied buildable cell (O) of the ship's layout.X— ignored cell: may overlap any cell (non-buildable, unoccupied buildable, or occupied buildable) or extend outside the layout grid entirely.
Unlock Group Format
Unlock groups in unlocks.toml define what the player can be awarded from defence station drops (REQ-DEF-SCHEMATIC-DROP); by their absence they also define what is available from game start (REQ-LOCK-EXPLICIT). Each entry:
[[unlock]]
id = "salvage_operations" # unique unlock-group id
station_level = 2 # eligible once a destroyed station set's level >= this
requires = [] # prerequisite unlock-group ids (REQ-LOCK-PREREQ); default empty
ships = [] # ship schematic ids granted (from ships.toml)
modules = ["salvager"] # module schematic ids granted (from modules.toml)
buildings = ["salvage_bay"] # building type ids granted (from buildings.toml)
recipes = [] # assembler recipe ids granted (from recipes.toml)
id— unique identifier of the unlock group; referenced by other groups'requires. Its display name in the schematic choice dialog is derived from the id (same convention as building, module, and recipe ids); there is no separate name field for now.station_level— the minimum destroyed enemy defence station level at which this group becomes eligible to drop (REQ-DEF-SCHEMATIC-DROP).requires— optional list of prerequisite unlock-group ids that must already have been awarded before this group can drop (REQ-LOCK-PREREQ). Defaults to empty.ships,modules,buildings,recipes— the ids granted when this group is awarded. Each list defaults to empty, but a group must grant at least one item overall. Every id must resolve to a definition in the corresponding config file, andrecipesids must name assembler recipes. Each grantable id (ship, module, building, or assembler recipe) may be granted by at most one unlock group; violations fail config load (REQ-LOCK-EXPLICIT).
Any ship, module, building, or assembler recipe id that appears in no unlock group's grant lists is available from game start (REQ-LOCK-EXPLICIT).
Game World
- REQ-GW-COORDS: Tile coordinates are integer
(x, y). The origin(0, 0)is the first column of space — the tile immediately to the right of the asteroid's right edge at game start, at the top of the world. X grows right; Y grows down. All asteroid tiles havex < 0; asteroid left-expansions add tiles at increasingly negative X. The origin never shifts. - REQ-GW-TILE-SIZE: Tiles are square. The tile size in pixels is derived automatically so that the world height (in tiles) exactly fills the game world view's height in pixels. Items on belts are rendered at half-tile size (drawn as an item icon, or a colored square as the fallback — REQ-UI-ITEM-ICON); when multiple items occupy the same tile they are spaced quarter-tile apart along the direction of travel and overlap, rendered in ascending order of progress — the least-progressed item is drawn first (bottom) and the furthest-progressed item is drawn last (on top). Items emerging from a building's output port are rendered by these same rules on that port's output belt (REQ-MAT-OUTPUT-EMERGE).
- REQ-GW-BELT-CAPACITY: Belt tiles and tunnel entry/exit tiles each hold up to four items simultaneously, queued one behind the other in the direction of travel. Splitter tiles hold up to four items: two unassigned items (progress < 0.5, not yet routed to an output) and one item per output slot (progress ≥ 0.5, committed to a specific output direction). Output-slot items are rendered on top of unassigned items; when both output slots are occupied, their rendering order follows the clockwise port order starting from East.
- REQ-GW-BELT-SPEED: Items on belts move at
world.toml [world].belt_speed_tiles_per_secondtiles per second (default 2). - REQ-GW-HEIGHT: The world height (in tiles) is read from
world.toml [world].height_tiles. - REQ-GW-REGIONS: The world is divided into horizontal regions whose widths (in tiles) are read from
world.toml [regions]:- Asteroid — the player's build area (
asteroid_width). - Player buffer zone — space between the asteroid and the player's defence stations (
player_buffer_width). - Contest zone — space between the player's and enemy's defence stations. This is where combat happens (
contest_zone_width). - Enemy buffer zone — space between the enemy's defence stations and the enemy spawn boundary (
enemy_buffer_width).
- Asteroid — the player's build area (
- REQ-GW-SCROLL-LIMIT: The player can scroll the view horizontally from the asteroid's left edge to the current set of enemy defence stations (the enemy buffer zone is not visible).
- REQ-GW-PUSH-EXPAND: When the player destroys a set of enemy defence stations, the scrollable area is extended by
world.toml [push].push_expand_columnstiles. A new enemy buffer zone ofworld.toml [regions].enemy_buffer_widthtiles is added beyond the new enemy defence stations. - REQ-GW-ASTEROID-EXPAND: When the player unlocks an asteroid expansion,
world.toml [expansion].columns_per_expansiontile columns are added to the left of the asteroid.
HQ & Game Over
- REQ-HQ-PLACEMENT: The HQ is pre-placed at the asteroid's right edge at game start.
- REQ-HQ-BELT-INPUT: The HQ has a belt input port. Building blocks delivered to it are added to the global building blocks stock.
- REQ-HQ-STATS: HQ stats (HP) are read from
stations.toml [hq]. - REQ-HQ-STARTING-BLOCKS: At game start, the global building blocks stock is initialized to
world.toml [world].starting_building_blocks(default 100). - REQ-HQ-GAME-OVER: If the HQ is destroyed, the game ends. A game-over screen shows the final survival time and offers "Restart" and "Quit" buttons.
- REQ-HQ-INVULNERABLE: Factory buildings (other than the HQ) are never targeted or destroyed by enemies.
Win Condition
- REQ-WIN-ARTIFACT-COUNT: The player has an artifact count, starting at 0 at game start and resetting to 0 on Restart (REQ-CFG-RELOAD). When the player selects an artifact option in the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP), the artifact count increments by 1. When the artifact count reaches
world.toml [world].artifact_win_count, the player wins and the win screen is shown (REQ-WIN-SCREEN). - REQ-WIN-SCREEN: When the player wins (REQ-WIN-ARTIFACT-COUNT), the simulation stops and a win screen is shown. The win screen functions identically to the game-over screen (REQ-HQ-GAME-OVER) — it displays the final survival time and offers "Restart" and "Quit" buttons — but with the caption "Won!" instead of the game-over caption.
Building Placement & Management
- REQ-BLD-COST: The player places buildings from a build menu. Placement costs building blocks from the global stock. The cost per building type is read from
buildings.toml [[building]].cost. - REQ-BLD-QUEUE: Placed buildings enter a construction queue and are built one at a time. Each building takes a duration defined in
buildings.toml [[building]].construction_time_secondsto construct. - REQ-BLD-ASTEROID-ONLY: Buildings can only be placed on asteroid tiles (per surface_mask; tiles marked
Smay extend into space). - REQ-BLD-BUILDER-MODE: Clicking a build button activates builder mode for that building type. Builder mode is exited by right-clicking in the game world or clicking the same build button again. (Exception: while a belt drag placement is in progress, right-clicking cancels that drag instead of exiting, and builder mode stays active — REQ-BLD-BELT-DRAG.)
- REQ-BLD-GHOST: While in builder mode, a ghost of the building is rendered at the tile under the cursor, showing where it would be placed. The ghost is drawn semi-transparently in the building type's own visuals — its
fillandoutlinecolors andglyphfromvisuals.toml— so that different building types are visually distinguishable in builder mode rather than all looking alike. When the current cursor position is invalid, the ghost instead uses the distinct "invalid" color (REQ-BLD-PLACE-VALID), which overrides the per-building coloring. - REQ-BLD-ROTATE: While in builder mode, pressing Shift+R rotates the ghost 90° clockwise and R rotates it 90° counter-clockwise. Rotation affects the direction of the output port.
- REQ-BLD-PLACE: Clicking a valid tile in builder mode places a construction site and adds it to the build queue, consuming building blocks from the global stock. (For belts, placement is instead deferred to a drag gesture and happens on mouse release — REQ-BLD-BELT-DRAG.)
- REQ-BLD-PLACE-VALID: A placement position is valid only if (a) every footprint cell in the rotated
surface_maskis satisfied by the underlying terrain —Acells coincide with asteroid tiles,Scells coincide with space tiles — (b) no footprint cell overlaps an existing placed building or construction site, except as allowed by REQ-BLD-ROTATE-IN-PLACE (builder mode) or REQ-UI-BLUEPRINT-OVERLAP and REQ-UI-BLUEPRINT-TRANSFER (blueprint placement mode), and (c) the player has enough building blocks to afford the building. The ghost (REQ-BLD-GHOST) is rendered in a distinct "invalid" color — overriding its per-building coloring (REQ-BLD-GHOST) — when the current cursor position fails any of these conditions. - REQ-BLD-ROTATE-IN-PLACE: If the ghost's footprint exactly coincides with the footprint of an existing placed building or construction site of the same building type, clicking places no new construction site and consumes no building blocks. Instead, the existing building or site is rotated to match the ghost's rotation. If the target is a construction site, its construction progress is preserved. Exception: Tunnel Entries and Tunnel Exits are never rotated in place — re-orienting a tunnel requires deconstructing and re-placing it (REQ-BLD-TUNNEL-MODE). A tunnel ghost whose footprint coincides with an existing tunnel is therefore treated as an ordinary occupied-tile placement, which is invalid. This applies only in normal builder mode, including the belt drag of REQ-BLD-BELT-DRAG. Blueprint placement mode never rotates an existing building in place: there, a ghost whose footprint coincides with an existing same-type building or site is a configuration-transfer target (REQ-UI-BLUEPRINT-TRANSFER), or a compatible overlap that is left untouched (REQ-UI-BLUEPRINT-OVERLAP), or else an ordinary occupied-tile overlap, which is invalid.
- REQ-BLD-BELT-DRAG: Belt drag placement. For belts, placement is a deferred drag gesture rather than immediate per-tile placement: construction sites are not placed while the cursor hovers new tiles, but only once the player releases the left mouse button. Pressing the left mouse button in the game world while in belt builder mode starts a drag anchored at the tile under the cursor. As the cursor moves, a rectilinear (L-shaped) path of belt tiles is computed from the anchor tile to the tile under the cursor: the path first runs along the axis parallel to the belt's current orientation (REQ-BLD-ROTATE) — stepping toward the cursor's coordinate on that axis to a corner tile — and then runs along the orthogonal axis to the cursor tile. When the cursor shares the anchor's row or column the path degenerates to a straight line, and when it is on the anchor tile the path is a single tile.
- Snapping to a building. When the tile under the cursor is occupied by a non-belt building or construction site (the target), the path does not end on that occupied tile. Instead the end tile is the tile closest to the cursor (by distance from the cursor position to the tile) among the tiles orthogonally adjacent to the target across one of its input-capable edges — any footprint edge that is not one of the target's output ports, i.e. an edge on which the target can accept an incoming item (REQ-MAT-INPUT-PORTS for buildings, REQ-MAT-ACCEPT-DIR for splitters and tunnels). The geometrically closest such tile is always used, even if it turns out not to be a valid belt endpoint — in that case it is previewed and applied by the ordinary rules below (invalid color and skipped if occupied by a non-belt building or invalid terrain; re-oriented if it already holds a belt). The rest of the L-shaped path is computed from the anchor to this end tile exactly as above. The end tile's belt direction points toward the target (across the shared input edge), overriding the "final tile keeps its incoming step" rule; this applies whether the end tile is a newly placed belt or an existing belt re-oriented in place, and is reflected both in the ghost preview and in the placement on release.
- Rotating during the drag. Rotating the belt with R / Shift+R (REQ-BLD-ROTATE) while a drag is in progress re-picks the path's primary axis immediately from the new orientation and re-derives the whole path from the anchor to the current cursor tile, without waiting for the next cursor movement.
- Ghosts. While dragging, a belt ghost (REQ-BLD-GHOST) is rendered on every path tile that would be acted on, instead of a single ghost under the cursor. Each ghost is oriented to point toward the next tile along the path toward the cursor, so the path forms one connected belt run that turns at the corner (curved belts along the path auto-derive per REQ-BLD-BELT); the final tile keeps the direction of its incoming step (unless the end tile is snapped to a building, in which case it points into the target — see Snapping to a building), and a single-tile path keeps the belt's current orientation. A tile occupied by only an existing belt or belt construction site is a valid target — its belt is re-oriented to follow the path — and shows a normal belt ghost. A tile occupied by a non-belt building or construction site, or otherwise an invalid belt position (REQ-BLD-PLACE-VALID), is drawn in the distinct invalid color, overriding the belt coloring. A tile whose new belt is unaffordable — the cumulative cost of the belts newly placed up to and including it exceeds the global stock — shows no ghost at all.
- Placement on release. No construction site is placed while dragging. On releasing the left mouse button, the path is applied in order (anchor to cursor): each cell occupied by only an existing belt or belt construction site has that belt re-oriented in place to its path direction, consuming no building blocks and preserving any construction progress (REQ-BLD-ROTATE-IN-PLACE); each empty, valid cell gets a new belt construction site, consuming building blocks from the global stock (REQ-BLD-COST). Cells occupied by a non-belt building or construction site, cells that are otherwise invalid (REQ-BLD-PLACE-VALID), and cells whose new belt can no longer be afforded once the running total has been spent are skipped. This supersedes the click-to-place of REQ-BLD-PLACE for belts, including both the single-tile case and multi-tile drags that pass over existing belts.
- Right-click cancels the drag. Right-clicking while a belt drag is in progress cancels it: the path is discarded, no construction site is placed, and belt builder mode stays active (the exception to REQ-BLD-BUILDER-MODE). Right-clicking when no drag is in progress exits builder mode as usual (REQ-BLD-BUILDER-MODE).
- REQ-BLD-TUNNEL-MODE: Unified tunnel build mode. The build button bar contains a single Tunnel button rather than separate Tunnel Entry and Tunnel Exit buttons (REQ-UI-BUILD-BAR), activated by that button or by hotkey 3 (REQ-UI-HOTKEYS). This one builder mode places either a Tunnel Entry or a Tunnel Exit construction site depending on the hovered position, so the player never manually chooses between the two ends. Both remain distinct building types (REQ-BLD-TUNNEL-ENTRY, REQ-BLD-TUNNEL-EXIT) with their own costs and construction; only their build-menu entry point is unified.
- Default type. The ghost (REQ-BLD-GHOST) is a Tunnel Entry by default; clicking places a Tunnel Entry construction site (REQ-BLD-PLACE). Rotation (REQ-BLD-ROTATE) sets the ghost's facing direction as for any building.
- Exit-completion match. While the ghost is at a valid position, the game tests whether placing a Tunnel Exit at the hovered tile with the current ghost rotation would pair — per the pairing rules of REQ-BLD-TUNNEL-PAIR (same facing direction, within
tunnel_max_distance, first same-direction building along the search, nearest-claim semantics) — with an existing Tunnel Entry. If so, that Entry is the exit-completion match and the ghost turns into a Tunnel Exit; clicking then places a Tunnel Exit construction site. - Entry-completion match. The game also tests whether placing a Tunnel Entry at the hovered tile with the current ghost rotation would pair — again per REQ-BLD-TUNNEL-PAIR — with an existing Tunnel Exit. If so, that Exit is the entry-completion match and the ghost stays a Tunnel Entry.
- Resolving the type. If neither match exists, the ghost is a Tunnel Entry (the default). If only one kind of match exists, the ghost is the kind that produces it (Tunnel Exit for an exit-completion match, Tunnel Entry for an entry-completion match). If both an exit-completion match (an existing Entry) and an entry-completion match (an existing Exit) exist, the mode resolves to the completion whose existing partner building is closer to the mouse cursor position — the actual sub-tile cursor position, not the hovered tile's center — and the ghost becomes the corresponding type (a Tunnel Exit to complete the nearer Entry, or a Tunnel Entry to complete the nearer Exit). Because the comparison uses the sub-tile cursor position, when the two partners are at the same tile distance the player can move the cursor within the hovered tile to switch which end is placed. When more than one candidate qualifies on a side, the nearest qualifying partner on that side is used.
- Connection preview (green). Whenever a completion match is resolved, the matched existing partner building is highlighted green, and every tile strictly between that partner and the hovered ghost tile (along the tunnel's straight run) is marked green, previewing the connection that placing the ghost would create.
- Invalid positions. The completion tests, type switch, and green preview apply only while the hovered position is a valid placement (REQ-BLD-PLACE-VALID). At an invalid position the ordinary invalid-colored ghost is shown (REQ-BLD-GHOST) with no green preview and no switch away from the default Tunnel Entry.
- REQ-BLD-DECONSTRUCT: The player can deconstruct a placed factory building. Deconstructing a fully-built factory building does not remove it instantly: it is added to the deconstruction queue (REQ-BLD-DECON-QUEUE) and, once its deconstruction completes,
world.toml [world].refund_percentagepercent of the original building block cost (default 75%) is returned to the global stock. Exception: if the building is still in the construction queue (not yet fully built, including the one currently being constructed), it is not queued for deconstruction but removed instantly from the construction queue, and the full building block cost is refunded immediately. The HQ and player defence stations cannot be deconstructed. - REQ-BLD-DECON-QUEUE: Fully-built factory buildings marked for demolition (REQ-BLD-DECONSTRUCT) enter a deconstruction queue that is processed one building at a time and runs in parallel with the construction queue (REQ-BLD-QUEUE) — the two queues advance independently and simultaneously. Each building takes
world.toml [world].deconstruction_time_seconds(default 0.1) to deconstruct, the same duration for every building type. When a building's deconstruction completes it is removed from the world and its refund is credited (REQ-BLD-DECONSTRUCT). A building stops operating the moment it enters the queue: it runs no production and transports no items, and no longer participates as a live building (its tunnel pairing is re-evaluated as if it were gone, REQ-BLD-TUNNEL-PAIR), but it still physically occupies its tiles until removed, so those tiles stay blocked for placement. A queued building can be taken back out of the deconstruction queue before it is removed (REQ-BLD-DECONSTRUCT-CLICK, REQ-BLD-DECONSTRUCT-BOX) — including the one currently being deconstructed; doing so discards any deconstruction progress, credits no refund, and the building resumes operating (and re-pairs, REQ-BLD-TUNNEL-PAIR). Construction sites never enter the deconstruction queue (REQ-BLD-DECONSTRUCT). Every building in the deconstruction queue is rendered with the deconstruct tint — thevisuals.toml [overlays].deconstruct_tintcolor, the same tint applied to a building hovered in deconstruct mode (REQ-UI-DECONSTRUCT-BORDER) — so queued buildings are visually distinct. - REQ-BLD-DECONSTRUCT-CLICK: While in deconstruct mode (REQ-UI-HOTKEYS, REQ-UI-DECONSTRUCT-BUTTON), left-clicking a placed factory building or construction site in the game world marks it for demolition, following the rules of REQ-BLD-DECONSTRUCT: a fully-built building is added to the deconstruction queue (REQ-BLD-DECON-QUEUE), and a construction site is removed instantly with the full refund. Left-clicking a fully-built building that is already in the deconstruction queue instead removes it from the queue (un-queues it, REQ-BLD-DECON-QUEUE), with no refund; repeated clicks on the same building therefore alternate between queueing and un-queueing it. Clicking a building that cannot be deconstructed (the HQ or a player defence station, per REQ-BLD-DECONSTRUCT), or clicking empty world space, has no effect. Deconstruct mode stays active after each action so the player can continue without re-entering the mode; it is exited via the Q toggle (REQ-UI-HOTKEYS) or the Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON).
- REQ-BLD-DECONSTRUCT-BOX: While in deconstruct mode (REQ-UI-HOTKEYS, REQ-UI-DECONSTRUCT-BUTTON), the player can click and drag a selection box in the game world. A selection rectangle is drawn while dragging, using the same box-drag gesture and coverage semantics as the multi-select box (REQ-UI-MULTI-SELECT). On mouse up, following the rules of REQ-BLD-DECONSTRUCT: every construction site covered by the box is removed instantly with the full refund; and among the fully-built deconstructible buildings covered by the box, if all of them are already in the deconstruction queue they are all removed from it (un-queued, REQ-BLD-DECON-QUEUE), otherwise every covered building not yet in the queue is added to the deconstruction queue (already-queued ones stay). Buildings that cannot be deconstructed (the HQ and player defence stations, per REQ-BLD-DECONSTRUCT) are excluded from the box demolition; ships and defence stations are never affected.
- REQ-BLD-SITE-CONFIG: A construction site — a building that has been placed but is still queued or under construction (REQ-BLD-QUEUE) — can be selected and configured exactly like the equivalent operational building, before it finishes building. Whatever configuration the building type supports is available on the site: the recipe for a Miner or Assembler (REQ-UI-SELECT-BUTTON), the produced-ship schematic and its module layout for a Shipyard (REQ-UI-SELECT-BUTTON, REQ-MOD-UI-PREVIEW, REQ-MOD-UI-DIALOG), and the output filters for a Splitter (REQ-BLD-SPLITTER) — all set through the same selection panel controls (REQ-UI-CONFIG-INLINE). Only currently unlocked recipes and schematics are offered, exactly as for operational buildings (REQ-LOCK-UI-RECIPE, REQ-LOCK-UI-SCHEMATIC, REQ-LOCK-UI-SPLITTER). The configuration is stored on the construction site and carries over unchanged when construction completes, so the building becomes operational already configured. A construction site has no input/output buffers and runs no production cycle, so the buffer and production-progress portions of the panel (REQ-UI-SINGLE-SELECTION, REQ-UI-PRODUCTION-PROGRESS) are not shown for it; only its construction progress (REQ-UI-CONSTRUCTION-PROGRESS) and its configuration controls appear. (Blueprint placement already applies a stored recipe or schematic to a construction site on placement per REQ-UI-BLUEPRINT-PLACE; this requirement additionally lets the player set or change that configuration directly on an existing site.)
Building Types
- REQ-BLD-MINER: Miner (2×2): The player selects which ore type it extracts. Each ore type corresponds to a
recipes.toml [[recipe]]entry withbuilding = "miner", defining the output item andduration_seconds. Every asteroid tile is equivalent for mining — any miner can produce any ore type based solely on its selected recipe. Ore never depletes. Only implicitly unlocked ore-type recipes are available for selection (REQ-LOCK-UI-RECIPE). - REQ-BLD-SMELTER: Smelter (2×2): Converts ore or scrap into basic materials. No recipe selection required. Inputs, outputs, and rates are defined in
recipes.toml [[recipe]]entries withbuilding = "smelter". - REQ-BLD-ASSEMBLER: Assembler (3×3): The player selects a recipe from the config-defined crafting tree. Produces the selected output item at the rate defined in the corresponding
recipes.toml [[recipe]]entry withbuilding = "assembler". Only implicitly unlocked recipes are available for selection (REQ-LOCK-UI-RECIPE). - REQ-BLD-REPROCESSING: Reprocessing Plant (3×3): Consumes scrap per cycle and produces exactly one higher-level intermediate product per cycle via weighted random pick. The input quantity, possible output items, per-output weights, and amounts are defined in
recipes.toml [[recipe]]entries withbuilding = "reprocessing_plant"(inputs,outputs[].item,outputs[].amount,outputs[].weight). Weights are normalized at load time; their sum does not need to equal 1. The output is rolled at cycle start (see REQ-MAT-CYCLE); the pool of eligible outputs is restricted to implicitly unlocked item types (REQ-LOCK-REPROCESSING-POOL). The output buffer holds at most one cycle's output — see REQ-MAT-OUTPUT-BUFFER-REPROCESSING. - REQ-BLD-SHIPYARD: Shipyard (4×2): The player selects a schematic. When all required materials — the ship's base materials (
[ship.schematic].materials) plus the materials of all modules in the configured layout (REQ-MOD-MATERIALS) — are present in its input buffer, the shipyard consumes them and begins a production cycle lasting the ship's base[ship.schematic].production_time_secondsplus the sum of production times contributed by all module instances in the configured layout (REQ-MOD-PRODUCTION-TIME). One ship of that type is spawned with the configured modules when the cycle completes. The shipyard cannot start a new cycle while one is in progress. If the player confirms a layout change (REQ-MOD-UI-DIALOG) while a production cycle is in progress, the current cycle is cancelled and all consumed materials are discarded; the shipyard returns to idle with the new layout configuration. Confirming a layout identical to the one already configured is not a change and cancels nothing (REQ-MAT-INPUT-BUFFER). - REQ-BLD-SALVAGE-BAY: Salvage Bay (3×2): A dedicated drop-off point for salvage ships. It has an output buffer whose holding capacity is defined by the
output_buffer_capacityfield of thesalvage_bayentry inbuildings.toml(rather than by a production cycle, since the Salvage Bay has no recipe). A ship at the bay hands over one unit of scrap per tick while the buffer has free space; a full buffer blocks further drop-off until space frees up (consistent with the buffer-full semantics of REQ-MAT-OUTPUT-BUFFER). Held scrap is pushed onto connected output belts. - REQ-BLD-BELT: Belt (1×1): Transports items. A belt tile has one direction (N, S, E, W) set at placement (modified by rotation). Curved belts are auto-derived: when a belt tile's outgoing direction leads into another belt whose direction is orthogonal, the downstream belt is rendered and behaves as a curve. Belt speed is defined in
world.toml [world].belt_speed_tiles_per_second(REQ-GW-BELT-SPEED). A belt accepts items only through a non-output edge (REQ-MAT-ACCEPT-DIR). - REQ-BLD-SPLITTER: Splitter (1×1): Distributes incoming items between two output directions. Incoming items are accepted only through the splitter's non-output edges (REQ-MAT-ACCEPT-DIR). Each output can optionally have a filter (a list of item types), configurable via the selection panel; only implicitly unlocked item types are available as filter options (REQ-LOCK-UI-SPLITTER). Routing rules:
- An item matching only one output's filter is routed to that output.
- An item matching both outputs' filters is distributed by strict alternation between those outputs.
- An item matching neither output's filter is routed to the unfiltered output. If both outputs have a filter and the item matches neither, the splitter stalls and moves no items until the situation is resolved.
- If neither output has a filter, items are distributed by strict alternation.
- In all alternation cases, if one output is blocked the item goes to the other output until it unblocks.
- REQ-BLD-TUNNEL-ENTRY: Tunnel Entry (1×1): The sending end of a tunnel pair. The player sets a direction (N, S, E, W) at placement, rotatable with R/Shift+R. Items arriving from an adjacent belt tile on a non-output edge (i.e. not the mouth edge in the entry's facing direction — see REQ-MAT-ACCEPT-DIR) whose direction points into the entry are forwarded through the tunnel to the paired Tunnel Exit (see REQ-BLD-TUNNEL-PAIR, REQ-BLD-TUNNEL-TRANSIT). If the entry is unpaired, or if the paired exit's output is blocked, the entry blocks like a full belt tile.
- REQ-BLD-TUNNEL-EXIT: Tunnel Exit (1×1): The receiving end of a tunnel pair. The player sets a direction at placement, rotatable with R/Shift+R. Items received from the paired Tunnel Entry emerge from the output side of the exit tile — the tile adjacent in the exit's facing direction — continuing in that direction. If the exit is unpaired or its output is blocked, it holds received items until they can advance.
- REQ-BLD-TUNNEL-PAIR: Tunnel pairing rules. Pairing is re-evaluated for all Tunnel Entries whenever any Tunnel Entry or Tunnel Exit is placed, deconstructed, or enters or leaves the deconstruction queue (REQ-BLD-DECON-QUEUE; a tunnel end that is queued for deconstruction counts as removed for pairing).
- A Tunnel Entry searches tile-by-tile in its facing direction for a partner. Any tunnel building (entry or exit) that faces a different direction is ignored and skipped. The search stops at the first tunnel building that faces the same direction as the searching entry.
- If that first same-direction tunnel building is a Tunnel Exit, is within
tunnel_max_distancetiles of the entry, and is not already paired with a closer entry, the two form a pair. - Otherwise the entry is unpaired.
- Pairing is one-to-one: each Tunnel Entry pairs with at most one Tunnel Exit, and vice versa. A Tunnel Exit is claimed by the nearest Tunnel Entry that can validly reach it; all other entries for which it would otherwise qualify are unpaired.
- When one end of a pair is deconstructed, the pair is dissolved and any items currently in transit are discarded.
- REQ-BLD-TUNNEL-TRANSIT: Tunnel transit. Items inside a tunnel are not rendered (they travel invisibly). Transit time equals the tile-coordinate distance between entry and exit divided by
world.toml [world].belt_speed_tiles_per_second, matching the time a chain of belt tiles of equivalent length would take. Multiple items may be in transit simultaneously, spaced as they would be on a belt chain of the same length. Clearing a tunnel entry or exit tile (REQ-UI-BELT-CLEAR) also discards all items currently in transit through that tunnel. - REQ-BLD-TUNNEL-SELECT-HIGHLIGHT: Selected-tunnel connection highlight. While a Tunnel Entry or Tunnel Exit — operational building or construction site — is part of the current selection (single selection or multi-selection, REQ-UI-MULTI-SELECT), its tunnel connection is marked green in the game world, using the same
visuals.toml [overlays].tunnel_previewgreen as the placement connection preview (REQ-BLD-TUNNEL-MODE). The matching end is found by applying the pairing scan of REQ-BLD-TUNNEL-PAIR over both built tunnels and construction-site tunnels (site-inclusive, matching the placement preview): for a selected entry, the first same-direction tunnel withintunnel_max_distancealong its facing direction, if it is a Tunnel Exit; for a selected exit, the first same-direction tunnel withintunnel_max_distanceopposite its facing direction, if it is a Tunnel Entry. When a matching end is found, the entry tile, the exit tile, and every tile strictly between them (along the tunnel's straight run) are marked green. A selected tunnel with no matching end shows no green highlight (it still receives the normal selection outline). In multi-selection each selected tunnel end that has a matching end contributes its connection, and a given connection is shown whenever either of its ends is selected. The highlight is presentation-only and has no effect on the simulation.
Material Transport & Buffers
- REQ-MAT-BELT-ONLY: Materials are transported exclusively via belts, splitters, and tunnels, with one exception: two directly adjacent buildings whose output and input ports meet transfer items straight between them without an intervening transport tile (REQ-MAT-DIRECT-COUPLE).
- REQ-MAT-INPUT-PORTS: A building accepts items from any adjacent belt tile on any edge of its footprint (excluding cells occupied by output port(s)) whose direction points toward the building, provided the item is an input required by the currently selected recipe and the matching per-material input buffer has free space. An accepted item does not enter the building instantly; it is removed from the belt and travels inward across the input port's footprint cell on that port's own input belt before being added to the buffer (REQ-MAT-INPUT-INTAKE).
- REQ-MAT-INPUT-INTAKE: Accepted input items travel into a building as an animation rather than vanishing off the belt instantly — the input-side mirror of REQ-MAT-OUTPUT-EMERGE. Each input port has its own input belt — a virtual belt tile occupying the input port's footprint cell (the body cell the feeding belt points into), oriented in the port's inward flow direction, with progress 0.0 at the outer edge adjacent to the feeding belt and 0.5 at the tile centre. It reuses the belt subsystem: movement at belt speed (REQ-GW-BELT-SPEED), item rendering and spacing (REQ-GW-TILE-SIZE), and capacity/packing (REQ-GW-BELT-CAPACITY), but restricted to the 0.0→0.5 half of the tile. This applies to every building that pulls items from adjacent belts into an input buffer (Smelter, Assembler, Reprocessing Plant, Shipyard); a building may run several input belts at once when belts feed it from more than one side. The HQ is included with the one difference noted below.
- Acceptance & reservation. The acceptance test of REQ-MAT-INPUT-PORTS is unchanged — an item is accepted only if it is a required input whose per-material input buffer has space — except that "has space" now counts both the items already buffered and the items of that material currently travelling on the building's input belts (reserved but not yet arrived), so the total (buffered + in-transit) never exceeds that material's buffer cap (REQ-MAT-INPUT-BUFFER). An item that fails this test is not placed on an input belt and stays on the feeding belt exactly as before, so items that are not required inputs never enter the building.
- Feeding. An accepted item is removed from the feeding belt on the same tick it would have been taken without this animation, and placed on the input belt at progress 0.0, reserving a slot in its per-material buffer. (An input belt may also be fed directly by an adjacent producer's output belt rather than by a real belt — see REQ-MAT-DIRECT-COUPLE — with the same reservation and entry rules.) A new item is placed only when the input belt's entry slot at progress 0.0 is free (per REQ-GW-BELT-CAPACITY spacing — no in-transit item within a quarter tile of 0.0). The 0.0→0.5 span holds at most three in-transit items (progress 0.0, 0.25, 0.5); the reservation limit above may permit fewer.
- Travel & arrival. An in-transit item advances from progress 0.0 to 0.5 at belt speed. On reaching progress 0.5 it leaves the input belt and is added to its per-material input buffer, turning its reservation into buffered stock; only then does it count toward starting a production cycle (REQ-MAT-CYCLE). Because the slot was reserved on entry, arrival always succeeds — there is no deadlock.
- Reservation may delay production. A reserved item occupies buffer capacity for its whole 0.0→0.5 travel without yet being consumable, so an input-starved building may briefly wait for an in-transit item to arrive before it can start a cycle. This is accepted.
- Clearing. Clearing the input buffers on a recipe or schematic change (REQ-MAT-INPUT-BUFFER) also discards any items currently travelling on the input belts and releases their reservations.
- HQ. The HQ has no input buffer (REQ-HQ-BELT-INPUT); a building block accepted at an HQ input port travels its input belt the same way but reserves nothing, and is added to the global building blocks stock (REQ-MAT-GLOBAL-STOCK) on reaching progress 0.5.
- Intake rendering (no pop-out). Mirror of the emergence rendering in REQ-MAT-OUTPUT-EMERGE: the building is rendered over the input belt, so an in-transit item is occluded while inside the footprint and is only visible as it crosses the outer edge — appearing to sink into the port. The portion inside the footprint is hidden, and the item disappears at the tile centre (progress 0.5) as it enters the buffer.
- REQ-MAT-OUTPUT-PORT: Each building has one or more fixed output port(s) defined by its surface_mask (direction determined by rotation). Produced items do not appear on the outgoing belt instantly; each item leaves the building by first emerging across the output port tile on that port's own output belt and then transferring onto the adjacent real belt tile (REQ-MAT-OUTPUT-EMERGE). The adjacent belt's direction is otherwise unconstrained (it may flow away from the building or perpendicular to it), except that a belt oriented with its own output edge facing back into the building refuses the transfer and the item stays stuck at the port (REQ-MAT-ACCEPT-DIR, REQ-MAT-OUTPUT-EMERGE).
- REQ-MAT-OUTPUT-EMERGE: Items emerge from a building output port as an animation rather than popping directly onto the outgoing belt. Each output port has its own output belt — a virtual belt tile occupying the output port tile, oriented in the port's facing direction, with progress 0.0 at the tile's inner edge and 1.0 at the outer (port) edge adjacent to the next real belt tile. It reuses the belt subsystem: movement at belt speed (REQ-GW-BELT-SPEED), item rendering and spacing (REQ-GW-TILE-SIZE), and capacity/packing (REQ-GW-BELT-CAPACITY), but restricted to the 0.5→1.0 half of the tile. This applies to every building that outputs items onto belts (Miner, Smelter, Assembler, Reprocessing Plant, Salvage Bay); it does not apply to the Shipyard, which spawns a ship rather than a belt item (REQ-SHP-SPAWN-PLAYER).
- Feeding. While the output buffer (REQ-MAT-OUTPUT-BUFFER) holds an item that has not yet begun emerging and the output belt's entry slot at progress 0.5 is free (per REQ-GW-BELT-CAPACITY spacing — no emerging item within a quarter tile of progress 0.5), the next buffered item is placed on the output belt at progress 0.5. Because only the 0.5→1.0 span is used, the output belt holds at most three emerging items (progress 0.5, 0.75, 1.0); once that span is full the building places no further items on it even if the output buffer still holds more.
- Cosmetic hold. An emerging item still counts as residing in the output buffer (REQ-MAT-GLOBAL-STOCK) for the whole animation; it only leaves the building when it transfers onto a real belt tile at progress 1.0. The output belt therefore adds no inventory capacity beyond the output buffer, and clearing the output buffer on a recipe or schematic change (REQ-MAT-OUTPUT-BUFFER) also removes any items currently emerging.
- Travel & handoff. An emerging item advances from progress 0.5 to 1.0 at belt speed. At progress 1.0 it attempts to transfer onto the adjacent real belt tile using the normal belt hand-off and accept-direction rules (REQ-MAT-OUTPUT-PORT, REQ-MAT-ACCEPT-DIR): the transfer succeeds only if a transport tile exists there, is not oriented with its output edge facing back into the building, and has free space. On success the item leaves the output buffer and becomes an ordinary item on that belt tile. If instead the output port tile is a directly adjacent building's input edge, the item transfers straight into that building (REQ-MAT-DIRECT-COUPLE).
- Stuck items. If there is no next real belt tile and no directly-coupled building (REQ-MAT-DIRECT-COUPLE), or the transfer is refused or blocked, the emerging item stops at progress 1.0 and is rendered there (still counted in the output buffer). Following items pile up behind it at progress 0.75 and 0.5 per the packing above, and once the 0.5→1.0 span is full no further items emerge until the front item transfers.
- Emergence rendering (no pop-in). An emerging item must not simply appear at progress 0.5. The output port tile's building is rendered over the output belt, so an emerging item is occluded while inside the footprint and is revealed progressively as it slides past the port edge — appearing to physically emerge from the building. The portion of the item still within the output port tile is hidden; the portion past the outer edge is drawn.
- REQ-MAT-DIRECT-COUPLE: Direct port coupling. Two directly adjacent buildings whose ports meet transfer items between them with no intervening transport tile. A direct coupling exists at a shared edge where a producer building's output port tile (the tile it pushes toward, REQ-MAT-OUTPUT-PORT) is a body cell of a consumer building, and the producer's output direction carries the item across that edge into the consumer through one of the consumer's input edges (any perimeter edge other than the consumer's own output port, per REQ-MAT-INPUT-PORTS). Over a direct coupling the two virtual belts chain end to end: an item that reaches progress 1.0 on the producer's output belt at the shared edge (REQ-MAT-OUTPUT-EMERGE) is handed, instead of onto a real belt tile, directly onto the consumer's input belt at progress 0.0 (REQ-MAT-INPUT-INTAKE) and continues inward to the consumer's buffer — so the item appears to slide continuously across the shared edge from one building into the next.
- Acceptance. The hand-off obeys the consumer's normal input rules (REQ-MAT-INPUT-PORTS, REQ-MAT-INPUT-INTAKE): it succeeds only if the item is a required input of the consumer whose per-material buffer has space (reservation-aware — buffered + in-transit below the cap) and the consumer's input belt entry at progress 0.0 is free. On success the item leaves the producer's output buffer and reserves a slot in the consumer's input buffer, exactly as a belt-fed intake would. If the consumer does not accept the item — it is not one of its inputs, or the buffer is full, or the input-belt entry is occupied — the item stays stuck at the producer's output port at progress 1.0, exactly as when a downstream belt is blocked (REQ-MAT-OUTPUT-EMERGE stuck items).
- Scope. Direct coupling is the only case in which materials move between buildings without a belt, splitter, or tunnel (REQ-MAT-BELT-ONLY); it bridges only two buildings that are directly adjacent with meeting output/input ports. Transport tiles feeding a building (belt, splitter, or tunnel exit) continue to work through the normal pull, and a producer still hands off to a transport tile placed in the gap as before; a single such tile between two buildings is unaffected by this requirement.
- REQ-MAT-ACCEPT-DIR: A transport tile (belt, splitter, tunnel entry, or tunnel exit) accepts an incoming item only through a non-output edge; an item that would enter through one of the tile's output edges is refused. For a belt or a tunnel entry/exit the sole output edge is the one in its facing direction; for a splitter either of its two output directions is an output edge. This applies both to items pushed from an adjacent transport tile and to items deposited by a building's output port (REQ-MAT-OUTPUT-PORT).
- REQ-MAT-INPUT-BUFFER: Each building has one input buffer per required input material. Each per-material buffer holds up to twice that material's per-cycle requirement. When the player selects a new recipe or schematic, all items in all input buffers are cleared.
- Setting a configuration to the value it already holds is a no-op. Selecting the recipe or schematic already set, or applying a ship layout identical to the one already configured, changes nothing: buffers are not cleared, an in-progress production cycle is not cancelled (REQ-BLD-SHIPYARD), and a construction site's progress and stored settings are untouched. This holds however the setting is applied — through the selection dialog (REQ-UI-SELECT-BUTTON), the layout configuration dialog (REQ-MOD-UI-DIALOG), a blueprint placement (REQ-UI-BLUEPRINT-PLACE), or a blueprint configuration transfer (REQ-UI-BLUEPRINT-TRANSFER). Only a setting that genuinely differs has effects.
- REQ-MAT-OUTPUT-BUFFER: Each building has an output buffer that holds up to twice the quantity produced by one production cycle. If the output buffer is full, production stops until space is available. When the player selects a new recipe or schematic, all items in the output buffer are cleared (relevant when the adjacent belt is jammed and items have accumulated). Re-applying a setting the building already has clears nothing, per REQ-MAT-INPUT-BUFFER.
- REQ-MAT-OUTPUT-BUFFER-REPROCESSING: Exception to REQ-MAT-OUTPUT-BUFFER — the Reprocessing Plant's output buffer holds at most one cycle's output. This prevents exploits where the player stalls the output belt to force the plant to reroll.
- REQ-MAT-CYCLE: Production cycle lifecycle. When a building is idle, it attempts to start a new cycle: (a) all required inputs must be present in the per-material input buffers, and (b) the cycle's output must fit in the output buffer. For the Reprocessing Plant, the output is picked at cycle start (weighted pick); the cycle only starts if that chosen output fits. On cycle start, inputs are consumed immediately and the production timer begins. On cycle completion, the (already-decided) output is deposited into the output buffer and the building returns to idle.
- REQ-MAT-GLOBAL-STOCK: The building blocks stock is the only global inventory. All other materials exist only in building buffers or on belt tiles.
Resources
- REQ-RES-DEBRIS-DROP: Destroyed ships (both player and enemy) and destroyed defence stations (both player and enemy) drop a piece of debris at their location. A piece of debris carries a scrap amount. For a ship this amount is derived from the ship's threat cost (REQ-MOD-THREAT) for its as-built layout, multiplied by
world.toml [world].scrap_per_threat(default 0.01) and rounded to the nearest integer (at least 1 for any ship whose threat cost is greater than 0); for stations it is defined asstations.toml [player_station].scrap_drop_formulaand[enemy_station].scrap_drop_formula. Salvage modules collect from a piece of debris one scrap per cycle (REQ-SHP-SALVAGE), and the debris is removed from the world once its remaining scrap amount reaches zero orworld.toml [world].debris_despawn_secondsseconds have elapsed since it was dropped, whichever comes first. - REQ-RES-SCRAP-COLLECT: Scrap is collected from debris by salvage ships and delivered to a Salvage Bay on the asteroid. From there it can be fed via belt into a smelter (same output as ore) or a Reprocessing Plant.
Ships
-
REQ-SHP-AUTONOMOUS: Ships are produced by shipyards and are fully autonomous once produced.
-
REQ-SHP-STATS: Base hull stats are defined as plain values in
ships.toml: HP ([ship.health].hp), max linear speed ([ship.movement].speed), sensor range ([ship.sensors].range), main acceleration ([ship.movement].main_acceleration, tiles/s²), maneuvering acceleration ([ship.movement].maneuvering_acceleration, tiles/s²), angular acceleration ([ship.movement].angular_acceleration, rad/s²), max rotation speed ([ship.movement].max_rotation_speed, rad/s). Required build materials ([ship.schematic].materials) are also defined there; whether the schematic starts unlocked or must be awarded during play is defined inunlocks.toml(REQ-LOCK-EXPLICIT). Combat, salvage, and repair capabilities are provided by modules (see REQ-MOD-CONFIG). Final hull stats incorporate passive module modifiers per REQ-MOD-STAT-CALC. -
REQ-SHP-SPAWN-PLAYER: A ship produced by a shipyard spawns centered on the shipyard's output port tile.
-
REQ-SHP-SPAWN-ENEMY: Enemy ships spawn at a uniformly random position within the current enemy buffer zone — random X across the buffer's width and random Y across the world height.
-
REQ-SHP-MOVEMENT: Ships move using a physics-based model. Each ship has a velocity and a facing direction, both updated each tick. The main acceleration (
main_acceleration) is applied along the ship's current facing direction only. The maneuvering acceleration (maneuvering_acceleration) can be applied in any direction independently of the facing direction, enabling lateral or braking movement without rotating. The angular acceleration (angular_acceleration) controls how quickly the ship rotates. Linear speed is capped at the ship'sspeedvalue; rotation rate is capped at the ship'smax_rotation_speedvalue. Ship position refers to the ship's center for all range, sensor, and attack checks. -
REQ-SHP-ORBIT: Several behaviors keep a ship circling its target at a fixed standoff distance (an orbit) rather than approaching a fixed point. The orbit radius depends on the behavior:
- Combat engagement (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI):
world.toml [world].orbit_factormultiplied by the maximum weaponattack_rangeacross the ship's weapon module instances. - Repair (REQ-SHP-REPAIR):
orbit_factormultiplied by the maximumrepair_rangeacross the ship's repair module instances. - Salvage (REQ-SHP-SALVAGE):
orbit_factormultiplied by the maximumcollection_rangeacross the ship's salvage module instances. - Rally (REQ-SHP-RALLY):
world.toml [world].rally_orbit_radius_tiles— a fixed radius in tiles, independent of any tool range (the rally point is a position, not a tool-bearing target).
All tool ranges incorporate passive module modifiers (REQ-MOD-STAT-CALC). While orbiting, the ship navigates to maintain the orbit radius from the target's current center (REQ-SHP-MOVEMENT) while moving tangentially around it: if it is farther than the orbit radius it closes in, if it is nearer it backs off, and at the radius it circles. The orbit direction (clockwise or counter-clockwise) is fixed for the duration of orbiting a given target. Orbiting uses the standard physics movement model (REQ-SHP-MOVEMENT) and introduces no new movement constraints. Orbiting does not by itself trigger tool use — weapons, repair tools, and salvage bays still fire/heal/collect strictly per their own range and rate checks (REQ-SHP-FIRING, REQ-SHP-REPAIR, REQ-SHP-SALVAGE). With
orbit_factor≤ 1 the orbit lies within the maximum tool range, so the longest-range tool of that type remains in range while the ship orbits. - Combat engagement (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI):
-
REQ-SHP-NO-COLLISION: Ships do not collide with each other or with defence stations; they may visually overlap.
-
REQ-SHP-SENSOR: A ship perceives only entities within its sensor range. Behavior is driven by what is in sensor range; entities outside sensor range are ignored.
-
REQ-SHP-FIRING: All weapons — on ships and on defence stations — fire when off cooldown and the target is within attack range. Firing emits a fire event and starts a 0.15-second damage delay (half the beam duration). When that delay expires, damage is applied to the target — unless the target has already been destroyed, in which case the damage is silently dropped. If the shooter is destroyed before the delay expires, damage is still applied when the delay expires. There is no projectile entity and no intervening collision. The weapon's cooldown begins at the moment of firing, not at damage application.
-
REQ-SHP-FIRING-BEAM: Each weapon fire event (REQ-SHP-FIRING), repair-tool activation (REQ-SHP-REPAIR), and salvage activation (REQ-SHP-SALVAGE) produces a visual beam drawn from the acting ship's position to the target for 0.3 seconds; repair and salvage beams have the same duration as weapon beams. The beam is rendered in the tool type's beam color from
visuals.toml(a distinct color for weapon, repair, and salvage beams). The beam endpoint is not the target's center but a point randomly offset from it: the offset direction is uniformly random and the offset magnitude is uniformly random up to half the target's visual size (for ships: half their rendered radius; for buildings/stations: half the shorter side of their tile footprint, in world units; for a piece of debris: half its rendered size). The offset is chosen once per activation event and held fixed for the beam's lifetime. The beam is a pure rendering effect and has no simulation state (does not block movement, does not re-apply its effect over its lifetime). Beams follow the acting ship and target positions if either moves during the 0.3-second window. The beam is rendered for its full 0.3-second duration even if the acting ship or target is destroyed before it expires. -
REQ-SHP-COMBAT: Ships with at least one weapon module (player) — engage enemy ships within sensor range. When engaging an enemy, the ship orbits it at the combat orbit radius (REQ-SHP-ORBIT) rather than approaching its center.
-
REQ-SHP-RALLY: After spawning, ships with weapon modules move to and orbit the rally point — the midpoint between the two player defence stations (center of their Y-span, at the player defence stations' X position) — at the rally orbit radius (REQ-SHP-ORBIT). While orbiting the rally point, ships still engage any enemy that enters sensor range (switching to the combat orbit per REQ-SHP-COMBAT). Every
world.toml [world].departure_interval_secondsseconds (default 20), all ships with weapon modules currently at the rally point depart simultaneously and begin their normal aggressive advance toward the enemy. The departure timer is global and shared across all shipyards; it is not reset by individual ship arrivals at the rally point. -
REQ-SHP-SALVAGE: Ships with at least one salvage module (player) — patrol by moving forward (rightward, away from the asteroid) while searching sensor range. If debris enters sensor range, navigate toward it by orbiting it at the salvage orbit radius (REQ-SHP-ORBIT); when it is within a module's
collection_range, that module begins collecting from it, one scrap per cycle (see below). Once the ship's cargo pool is full, fly to a Salvage Bay and deliver (a direct approach, not an orbit — the ship must reach the bay); after delivery, resume patrol. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range, then resumes patrol — this applies regardless of whether the ship is targeting debris or carrying scrap. Ships with salvage modules are vulnerable to enemy ships while operating.All salvage modules on a ship deposit into a single shared cargo pool whose size is the ship's cargo capacity stat (REQ-MOD-CARGO-CAPACITY). Each salvage module instance still runs its own collection cycle independently, with its own collection range (
collection_range) and collection rate (collection_rate, in collection cycles per second). A module starts a collection cycle when it is off cooldown, the shared cargo pool has free space, and a piece of debris is within itscollection_range. Free space is measured against the pool's current contents plus the collection cycles already in flight toward the pool (scrap claimed by cycles whose effect delay has not yet elapsed); each in-flight cycle is registered against the ship so that concurrent modules on the same ship never start more cycles than the remaining capacity can hold. Starting a cycle emits a collection beam toward that debris (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of1 / collection_rateseconds begins at cycle start, not at effect application. When the delay expires, exactly 1 scrap is removed from the targeted debris and added to the ship's cargo pool — unless the debris has already been fully depleted or despawned, or the pool is now full, in which case the collection is silently dropped. A piece of debris worth more than 1 (REQ-RES-DEBRIS-DROP) is depleted one scrap per cycle and persists, with its remaining scrap amount decremented, until it is fully collected or despawns. A ship with multiple salvage modules can therefore run multiple collection cycles concurrently (one per ready module), and instances of different module types may have different ranges and rates. The ship navigates based on the maximum collection range across all installed salvage modules.Salvage collection cycles and delivery are processed regardless of which behavior the ship is currently executing; the salvage behavior only governs where the ship navigates (toward debris, toward a Salvage Bay, or — when retreating — toward the rally point).
-
REQ-SHP-REPAIR: Ships with at least one repair module (player) — when no more urgent behavior applies, hold with the fleet (REQ-SHP-STANDBY) rather than charging the enemy, so damaged allies stay within sensor range. If a damaged player defence station or player ship enters sensor range, navigate toward it by orbiting it at the repair orbit radius (REQ-SHP-ORBIT) and repair. If an enemy ship enters sensor range, the ship retreats (REQ-SHP-RETREAT) until no enemy is in sensor range — except that it holds its ground and keeps repairing while a damaged friendly remains within sensor range (REQ-SHP-RETREAT), retreating only once there is nothing left to repair — then resumes patrol.
Each repair module instance operates independently: it has its own repair rate (
repair_rate, in repair cycles per second), per-cycle heal amount (repair_amount_hp), and repair range (repair_range). A module starts a repair cycle when it is off cooldown and a valid repair target is in range. To choose the target, the module first considers the ship's current behavior-level navigation target if that target is within the module'srepair_rangeand is damaged (HP above zero and below maximum HP). If those conditions are not met — because the target is out of the module'srepair_range, already at full health, or destroyed — the module independently searches for the nearest damaged friendly (player ship or player defence station) within its ownrepair_range. If no valid target is found within range, the module idles and starts no cycle. On starting a cycle, the module emits a repair beam toward the chosen target (REQ-SHP-FIRING-BEAM) and begins a 0.15-second effect delay (half the beam duration); the module's cooldown of1 / repair_rateseconds begins at cycle start, not at effect application. When the delay expires,repair_amount_hpHP is restored to the targeted entity, clamped to its maximum HP — unless that entity is no longer damaged or has been destroyed, in which case the heal is silently dropped. A ship with multiple repair modules can therefore run multiple repair cycles concurrently, healing different targets. Navigation is driven solely by the behavior-level target; individual module fallback targets do not affect which direction the ship moves. Repair cycles are processed regardless of which behavior the ship is currently executing. -
REQ-SHP-STANDBY: Ships with at least one repair module hold with their fleet when idle, whether or not they also carry weapon modules. Standby is a low-priority fallback — above the baseline forward advance (REQ-SHP-COMBAT/REQ-SHP-ENEMY-AI advance) but below rally (REQ-SHP-RALLY), so it only wins when no attack, repair, salvage, rally, or retreat behavior applies. A standing-by ship navigates toward the centroid of its other same-faction ships, falling back to the centroid of its own defence stations, and holding position when it has no allies. This keeps repair ships among the allies they exist to heal instead of advancing alone into the enemy. Armed repair ships therefore still rally and depart on the normal schedule (REQ-SHP-RALLY); standby only governs them once rally no longer applies.
-
REQ-SHP-RETREAT: Player ships retreat to the rally point (REQ-SHP-RALLY) when threatened. A ship retreats while either condition holds: (a) its HP is below a low-HP threshold (currently 30% of its maximum HP); or (b) it has no weapon modules and an enemy ship is within its sensor range — with one exception: a weaponless ship that has at least one repair module does not retreat under condition (b) while a damaged friendly (player ship or player defence station, excluding itself) is within its sensor range, so it can keep repairing under fire; it retreats only when no such repair target remains in range. Condition (a) still forces a low-HP repair ship to retreat regardless of available repair targets. Retreating takes priority over the ship's other behaviors and moves it toward the rally point; the ship resumes its normal behavior once neither condition holds. Enemy ships never retreat (REQ-SHP-ENEMY-AI).
-
REQ-SHP-ENEMY-AI: Enemy ships — engage the closest valid target (player defence station, HQ, or player ship) within their sensor range, orbiting the engaged target at the combat orbit radius (REQ-SHP-ORBIT). If no target is in sensor range, they move toward the asteroid (leftward in world coordinates).
-
REQ-SHP-TARGET-SELECT: Combat target selection. Both player combat ships (REQ-SHP-COMBAT) and enemy ships (REQ-SHP-ENEMY-AI) pick which hostile to engage by scoring every valid target (an opposing-faction ship, defence station, or HQ) within sensor range and engaging the highest-scoring one. A target's score is the product of a base desirability and an overclaim penalty (REQ-SHP-TARGET-CLAIM). The base desirability is
world.toml [targeting].target_score_formulaevaluated withxset to the target's distance from the ship divided by the ship's maximum weaponattack_range(falling back to sensor range for a ship with no weapon), clamped to a minimum of 0. The default formula1 / (1 + x)decreases with distance, so — absent any claims — the nearest target is chosen, realizing the closest-target priority referenced by REQ-SHP-COMBAT and REQ-SHP-ENEMY-AI. A ship engages at most one target at a time; all of its weapons fire on that target subject to their own range and rate checks (REQ-SHP-FIRING). -
REQ-SHP-TARGET-CLAIM: Overclaim penalty. To stop every ship from dogpiling the same hostile, each target a ship is currently engaging counts as a claim on that target. When scoring a candidate, its base desirability (REQ-SHP-TARGET-SELECT) is multiplied by
world.toml [targeting].overclaim_penalty_formulaevaluated withxset to the number of ships currently claiming that candidate — a ship never counts its own claim against the target it already holds — clamped to the range [0, 1]. The penalty is 1 (no reduction) at zero claims and decreases as claims accumulate, so heavily-claimed targets become less attractive and ships spread across the available hostiles. The default formulamax(0.5, 1 - 0.1*x)reduces desirability by 0.1 per claim down to a floor of 0.5. Because claims reflect the previous tick's engagements, target distribution converges over successive ticks rather than instantaneously. -
REQ-SHP-TARGET-HYSTERESIS: Target stickiness. A ship keeps engaging its current target as long as that target remains valid and within sensor range, switching to a different target only when the best alternative's score exceeds the current target's score by more than the fractional margin
world.toml [targeting].target_hysteresis(default 0.10). This prevents ships from rapidly oscillating between targets of near-equal score and preserves focus fire. -
REQ-SHP-SCHEMATICS: The player selects a schematic per shipyard by clicking it. New schematics are unlocked by destroying enemy defence station sets (REQ-DEF-SCHEMATIC-DROP) — there is no physical loot to collect.
Ship Modules
Module Configuration
-
REQ-MOD-CONFIG: Module types are defined in
modules.toml. Each module entry specifies:id— unique identifier, also used as the display name in the UI.surface_mask— footprint within the ship layout grid (see Module Surface Mask Format).materials— list of materials required per instance (added to the ship's build cost).production_time_seconds— time added to the ship's production cycle per instance.fill_color— fill color used to render this module's cells in the layout grid.glyph— single character rendered on this module's cells in the layout grid and preview widget.- An optional capability section (
[module.weapon],[module.salvage], or[module.repair]) containing base stat values. A module with base stat values is a capability module — each placed instance grants the ship an independent weapon, salvage bay, or repair tool with its own state (cooldown, target). A ship may have multiple capability module instances of the same or different types. Base stat values per capability type:- Weapon (
[module.weapon]):damage,attack_range,attack_rate. - Salvage (
[module.salvage]):collection_range(tiles),cargo_capacity(integer scrap units; contributes to the ship's cargo capacity stat per REQ-MOD-CARGO-CAPACITY),collection_rate(collection cycles per second; each cycle collects 1 scrap). - Repair (
[module.repair]):repair_rate(repair cycles per second),repair_amount_hp(HP restored per repair cycle),repair_range(tiles).
- Weapon (
- Zero or more passive stat modifiers (
added_*/multiplied_*) that boost stats on the ship hull or on capability module instances (see REQ-MOD-STAT-CALC). A single module may be both a capability module and provide passive modifiers.
-
REQ-MOD-LAYOUT: Each ship in
ships.tomldefines alayout— a list of strings representing the ship's module grid (see Ship Layout Format). All ships define a layout.
Module Placement
- REQ-MOD-PLACEMENT: In the layout configuration dialog (REQ-MOD-UI-DIALOG), the player places modules onto the ship's layout grid. Clicking a module button in the module selection grid enters module placement mode for that module type. While in placement mode, a ghost of the module's surface mask is rendered at the cell under the cursor. Clicking a valid position places one instance of the module. A position is valid if every
Ocell in the module's (rotated) surface mask coincides with an unoccupied buildable cell of the ship's layout. The player may place unlimited instances of the same module type. - REQ-MOD-ROTATION: While in module placement mode, pressing R rotates the module ghost 90° counter-clockwise and Shift+R rotates it 90° clockwise. Rotation transforms the surface mask grid identically to building rotation (REQ-BLD-ROTATE).
- REQ-MOD-REMOVE: The module selection grid includes a "Remove" button. Clicking it enters remove mode. In remove mode, clicking on a cell occupied by a placed module removes that entire module instance from the layout. Remove mode is exited by clicking the Remove button again or by selecting a module for placement.
Module Effects
-
REQ-MOD-MATERIALS: The total materials required to build a ship are the union of the ship's base
[ship.schematic].materialsand thematerialsof every module instance in the configured layout. Quantities of the same item type are summed. -
REQ-MOD-PRODUCTION-TIME: The total production time is the ship's base
[ship.schematic].production_time_secondsplus the sum ofproduction_time_secondsfor every module instance in the configured layout. -
REQ-MOD-THREAT: The threat cost of a ship is dynamically derived from the accumulated total production time required to produce that ship from scratch. One second of production time equals one threat. The total production time is the sum of:
- The ship's base
production_time_seconds. - The
production_time_secondsof every module instance in the configured layout. - For every material required (the union of the ship's base materials and all module instance materials, with quantities summed per item type): the recursive production time of that material multiplied by the required quantity (see REQ-THREAT-ITEM).
- The ship's base
-
REQ-THREAT-ITEM: The threat value of an item type (in production-seconds per unit) is determined by the recipe that produces it:
- Miner recipe:
duration_seconds / output_amount, whereoutput_amountis the number of units produced per cycle. - Smelter recipe:
(duration_seconds + Σ (input_threat × input_amount)) / output_amount, where the sum is over all inputs. - Assembler recipe:
(duration_seconds + Σ (input_threat × input_amount)) / output_amount, where the sum is over all inputs. - Reprocessing-only item (an item type that has no miner, smelter, or assembler recipe producing it, and is only obtainable via reprocessing):
(scrap_threat × scrap_per_cycle + duration_seconds) / probability, wherescrap_threatis the threat value of scrap (see REQ-THREAT-SCRAP),scrap_per_cycleis the number of scrap consumed per reprocessing cycle,duration_secondsis the reprocessing cycle time, andprobabilityis the normalized weight of that item in the reprocessing output pool. (Reprocessing output amounts are 1 in practice, so per-unit division is already implicit in the formula.) - Multiple recipes: if an item type can be produced by more than one non-reprocessing recipe (miner, smelter, or assembler), its threat value is the maximum across all such eligible recipes, and the threat is committed only once every eligible recipe is computable (so a shallow shortcut recipe that resolves earlier than a deeper base recipe cannot lower the item's threat). The reprocessing path is only used when no other recipe exists. If recipe cycles prevent full resolution, the max over the currently computable subset is used as a fallback.
- Scrap-consuming recipe fallback: a non-reprocessing recipe that takes
scrapas an input participates in an item's threat computation only if no scrap-free recipe (miner, smelter, or assembler) produces that item. This mirrors the reprocessing fallback rule and prevents the scrap-to-ingot smelter recipe from inflating basic material threats via the max rule.
- Miner recipe:
-
REQ-THREAT-SCRAP: The threat value of scrap is the constant
1 / world.toml [world].scrap_per_threat. This is the exact inverse of the scrap conversion in REQ-RES-DEBRIS-DROP, so a destroyed ship drops debris worth precisely its own threat cost. Because scrap threat is now a fixed constant, it no longer depends on any ship's threat cost, removing the potential circularity with REQ-MOD-THREAT for ships built from reprocessing-only materials. -
REQ-MOD-STAT-CALC: For each stat (on the ship hull or on a capability module instance), the final value is computed as:
final = base × total_multiplier + total_additive, where:baseis the stat's base value — the hull stat value (for hull stats) or the capability module's base stat value (for capability module stats).total_multiplier= 1 + sum of (m_i − 1) for each multiplicative modifier m_i from all passive module instances. Each m_i is the module's multiplicative modifier value.total_additive= sum of all additive modifier values from all passive module instances. Each additive value is the module's additive modifier value.
Passive modifiers follow the naming convention: a module may define
added_<stat>(additive) and/ormultiplied_<stat>(multiplicative) under[module.<category>]. The category determines what the modifier targets:[module.health],[module.movement],[module.sensor]— modifiers apply to the ship hull's stats.[module.weapon]— modifiers apply to every weapon module instance on the ship.[module.salvage]— modifiers apply to every salvage module instance on the ship.[module.repair]— modifiers apply to every repair module instance on the ship.[module.cargo]— modifiers (added_cargo_capacity/multiplied_cargo_capacity) apply to the ship's cargo capacity, a ship-level stat (see REQ-MOD-CARGO-CAPACITY).
Example:
[module.sensor].added_sensor_rangeadds to the ship's sensor range.[module.weapon].multiplied_damagemultiplies the damage of every weapon module instance on the ship. -
REQ-MOD-CARGO-CAPACITY: Cargo capacity is a first-class ship stat — the total number of scrap units the ship can hold in the single shared cargo pool used by its salvage modules (REQ-SHP-SALVAGE). Unlike other ship stats it has no hull base formula; its
base(per REQ-MOD-STAT-CALC) is the sum of thecargo_capacitybase values of every cargo-providing capability module instance on the ship — currently each salvage module'scargo_capacityvalue. Passive modifiers targetingcargo_capacityare declared under the[module.cargo]category and apply to this ship-level sum (final = base × total_multiplier + total_additive); they are not salvage-category modifiers and therefore scale the whole pool rather than any single instance. A ship whose cargo capacity is 0 (no cargo-providing module) is given no cargo pool.
Module UI
-
REQ-MOD-UI-PREVIEW: For a selected shipyard (operational building or construction site), the selection panel always shows a small non-interactive ship layout preview widget below the schematic selection button (REQ-UI-SELECT-BUTTON) and a "Configure" button below the preview. Both are disabled while no schematic is selected, and enabled once one is; the preview then shows an empty placeholder in place of a layout grid. When a schematic is selected, the preview renders the ship's layout grid at a reduced scale: buildable cells without a module are shown as white, non-buildable cells are shown as black, and cells occupied by a module are shown in that module's
fill_colorwith the module'sglyphcharacter. For non-shipyard buildings, neither the preview nor the "Configure" button is shown. -
REQ-MOD-UI-DIALOG: Clicking the "Configure" button opens the layout configuration dialog as a modal. While the dialog is open, the game is paused (speed set to 0×). On close, the game speed is restored to what it was before the dialog was opened.
The dialog contains:
- Top: The ship's layout grid rendered at full scale. Buildable cells are white; non-buildable cells are black. Placed modules are rendered with their
fill_colorandglyph. The ghost of the currently selected module is shown at the cursor position when in placement mode. - Left (below the grid): The ship stats panel (see REQ-MOD-UI-STATS-PANEL).
- Center (below the grid): A grid of module selection buttons (one per unlocked module type; see REQ-DEF-SCHEMATIC-DROP) plus a "Remove" button. Each module button shows the module id and its glyph.
- Right (below the grid): The layout blueprint panel (see REQ-MOD-UI-BLUEPRINT-PANEL through REQ-MOD-UI-BLUEPRINT-FILE-LOAD).
- Bottom: A "Confirm" button and a "Cancel" button. Cancel discards all changes made in this dialog session and closes the dialog. Confirm applies the changes: the shipyard's configured layout is updated, the required materials and cycle time displayed in the selection panel are recalculated, and the ship layout preview is refreshed.
- Top: The ship's layout grid rendered at full scale. Buildable cells are white; non-buildable cells are black. Placed modules are rendered with their
-
REQ-MOD-UI-EMPTY-PULSE: While a module is selected for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG), the empty buildable cells of the layout grid pulse smoothly around their normal fill shade, oscillating between a slightly darker and a slightly brighter shade at approximately 1 Hz (one full cycle per second), to draw the player's attention to where the module can be placed. All empty buildable cells pulse in phase. When no module is selected for placement (including remove mode), empty buildable cells render at their normal static shade. Non-buildable cells and cells occupied by a placed module do not pulse.
-
REQ-MOD-UI-AUTO-DIALOG: When the player selects a schematic for a shipyard (operational building or construction site) through the schematic selection dialog (REQ-UI-SELECT-BUTTON), and the chosen schematic differs from the shipyard's current schematic, the layout configuration dialog (REQ-MOD-UI-DIALOG) opens automatically and immediately once the selection dialog closes — exactly as if the player had then clicked "Configure". Re-selecting the schematic already set does not reopen the dialog. This auto-open applies only to the manual schematic selection dialog; a schematic applied by blueprint placement (REQ-UI-BLUEPRINT-PLACE) does not auto-open the dialog. The player may still cancel the auto-opened dialog (REQ-MOD-UI-DIALOG), which leaves the newly selected schematic in place with its default empty layout; the "Configure" button (REQ-MOD-UI-PREVIEW) remains available to open the dialog again later.
-
REQ-MOD-UI-MODULE-TOOLTIP: Each module selection button in the layout configuration dialog (REQ-MOD-UI-DIALOG) shows a hover tooltip with the descriptive text defined for that module type in
modules.toml(the optional per-module tooltip field). If a module type defines no tooltip text, its button shows no tooltip. The "Remove" button is not a module type and has no config-defined tooltip. -
REQ-MOD-UI-STATS-PANEL: The ship stats panel in the layout configuration dialog shows the stats of the currently configured ship layout as they would be computed, incorporating all passive module modifiers per REQ-MOD-STAT-CALC. The panel updates in real time whenever modules are placed or removed in the layout grid.
The panel always shows all hull stats as final computed values:
- HP
- Max linear speed
- Sensor range
- Main acceleration
- Maneuvering acceleration
- Angular acceleration
- Max rotation speed
- Cargo capacity — shown only when the ship's cargo capacity (REQ-MOD-CARGO-CAPACITY) is greater than 0
In addition, the panel shows capability module stats conditioned on which capability module types are present in the current layout:
- Weapons (shown only if at least one weapon module is placed): combined DPS = Σ(damage_i × attack_rate_i) across all weapon module instances; maximum range = max(attack_range_i) across all weapon module instances.
- Salvage (shown only if at least one salvage module is placed): combined collection rate = Σ(collection_rate_i) across all salvage module instances; maximum range = max(collection_range_i) across all salvage module instances.
- Repair (shown only if at least one repair module is placed): combined repair rate (HP/s) = Σ(repair_rate_i × repair_amount_hp_i) across all repair module instances; maximum range = max(repair_range_i) across all repair module instances.
All capability module stat values incorporate passive modifiers targeting the relevant capability category per REQ-MOD-STAT-CALC.
While debug draw mode is active (REQ-UI-DEBUG-DRAW), the panel additionally shows the ship's derived threat cost (REQ-MOD-THREAT) for the current layout configuration. This value updates in real time as modules are placed or removed.
-
REQ-MOD-UI-LAYOUT-SIZE: Ship layouts are small enough to display in the layout configuration dialog without scrolling (maximum grid size fits within the dialog).
Layout Blueprints
-
REQ-MOD-UI-BLUEPRINT-PANEL: The right column of the layout configuration dialog is the layout blueprint panel. It shows only blueprints whose
ship_typematches the schematic of the shipyard for which the dialog was opened. The panel contains, from top to bottom: a "Create Blueprint" button, followed by a scrollable list of blueprint entries (one per matching blueprint, in creation order). -
REQ-MOD-UI-BLUEPRINT-CREATE: Clicking "Create Blueprint" opens a modal dialog prompting for a name. The dialog has Confirm and Cancel buttons. Clicking Cancel closes the dialog with no effect. Clicking Confirm with a non-empty name creates a blueprint from the module layout currently shown in the left-side layout grid (the in-progress state of the dialog, not the previously confirmed shipyard layout) and appends it to the blueprint list.
-
REQ-MOD-UI-BLUEPRINT-ENTRY: Each blueprint entry shows the blueprint name and a delete icon ("×") to the right of the name. Clicking the entry (name area) loads that blueprint's module list into the left-side layout grid, replacing all currently placed modules. Module instances that are invalid for the current ship layout (unknown module type, locked module type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same blueprint) are silently skipped; the remaining valid instances are placed. Clicking the delete icon ("×") removes that blueprint entry from the list immediately.
-
REQ-MOD-UI-BLUEPRINT-STARTUP: At application startup, layout blueprints are loaded from
ship_layouts.tomlin the same directory as the application executable. Blueprint entries missing required fields (nameorship_type) are silently skipped. If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty. -
REQ-MOD-UI-BLUEPRINT-SHUTDOWN: On application shutdown, all layout blueprints (across all ship types) are written to
ship_layouts.tomlin the same directory as the application executable. The TOML structure matches the Layout Blueprint TOML Format. Write errors are silently ignored on shutdown.
Defence Stations
-
REQ-DEF-PLAYER-PLACEMENT: 2 player defence stations are pre-placed in space at the start. Their positions are determined by
world.toml [regions].asteroid_widthandplayer_buffer_width. -
REQ-DEF-PLAYER-FIRE: Player defence stations automatically fire at approaching enemy ships. Stats are defined as formulas of a fixed station level (
stations.toml [player_station].level) instations.toml [player_station]:hp_formula,damage_formula,range_formula,fire_rate_formula,scrap_drop_formula. -
REQ-DEF-PLAYER-DESTRUCTIBLE: Player defence stations can be destroyed by enemies and repaired by repair ships.
-
REQ-DEF-ENEMY-PLACEMENT: 2 enemy defence stations are placed at the right boundary of the scrollable area at game start, and again each time a new set is spawned after a push. Stats scale with the station level (REQ-PSH-STATION-STATS).
-
REQ-DEF-ENEMY-FIRE: Enemy defence stations automatically fire at player ships within range.
-
REQ-DEF-NO-CROSSFIRE: Enemy and player defence stations are never in each other's firing range.
-
REQ-DEF-PUSH: When both enemy defence stations in a set are destroyed, the boss countdown is advanced (REQ-WAV-BOSS-ADVANCE), the scrollable area is extended (REQ-GW-PUSH-EXPAND), a new set of enemy defence stations is placed at the new boundary, and exactly one schematic drop is awarded for the destroyed set (REQ-DEF-SCHEMATIC-DROP).
-
REQ-DEF-SCHEMATIC-DROP: Each destroyed set of enemy defence stations awards exactly one drop (not one per station). The drop opens a schematic choice dialog — a modal dialog that pauses the game (speed set to 0×; on close, speed is restored to what it was before the dialog opened). Before drawing picks, an artifact roll is made: evaluate
world.toml [world].artifact_chance_formulawithxset to the level of the destroyed station set, clamp the result to [0, 1], then compare against a uniform random value in [0, 1). If the roll succeeds, the dialog presents one artifact option plus two unlock picks drawn from the eligible pool; otherwise it presents three unlock picks. Up to three (or two, if an artifact option is present) unlock options are drawn uniformly at random without replacement from the eligible pool. If the pool contains fewer than the required number of entries, only that many unlock options are shown (the artifact option is always shown if the roll succeeded).The eligible pool contains every unlock group (REQ-LOCK-EXPLICIT) that (a) has not yet been awarded, (b) whose
station_levelis ≤ the level of the destroyed station set, and (c) every prerequisite in itsrequireslist is currently satisfied (REQ-LOCK-PREREQ). Because the pool is rebuilt for each drop, an unlock group gated behind prerequisites first appears only after all of its prerequisites have themselves been awarded.Each option in the dialog displays the unlock group's display name — derived from its
id(same display convention as building, module, and recipe ids) — and the list of items it would grant: its ship, module, building, and assembler-recipe ids (each shown with the same display convention as its respective selection dialog). The artifact option (if present) is displayed as a distinct entry with the name "Artifact".Each option additionally displays a vertical list of recipe names labeled "Unlocks recipes:", showing which miner and assembler recipes would newly become implicitly unlocked (REQ-LOCK-IMPLICIT) if this option were selected — specifically, the miner recipes and implicitly-gated assembler recipes that are not currently implicitly unlocked but would become so after applying this option's effect. To compute this, all
materialsof the group's granted ship and module schematics are added to the base set per REQ-LOCK-IMPLICIT step 1a, and the output items of the group's granted assembler recipes are added per step 1b, before recomputation.Each recipe is listed by its
id(using the same display convention as the assembler recipe-selection dialog), sorted alphabetically. Hovering a recipe in this list displays the recipe info tooltip described for a recipe in REQ-UI-SELECT-TOOLTIP (the recipe name; the name and quantity of each input item; the completion time; and the name and quantity of the produced output item). If no recipes would be newly unlocked, the list shows "None".The player selects one option by clicking it. If the player selects the artifact option, the player's artifact count is incremented by 1 (REQ-WIN-ARTIFACT-COUNT) and the dialog closes; no unlock is applied. Otherwise the selected unlock group is awarded and the dialog closes: every ship, module, building, and assembler recipe the group grants becomes unlocked at once — ship schematics unlock the corresponding shipyard selection; module schematics unlock the module type for placement in the layout configuration dialog (REQ-MOD-UI-DIALOG); building types become available in the build menu (REQ-LOCK-BUILDING); assembler recipes become available in the assembler recipe-selection dialog (subject to REQ-LOCK-UI-RECIPE). The unlock group is removed from the pool permanently (REQ-LOCK-EXPLICIT), and the implicit unlock set is recomputed (REQ-LOCK-IMPLICIT).
Progression & Locking
-
REQ-LOCK-EXPLICIT: The unit of unlocking is an unlock group, defined by an
[[unlock]]entry inunlocks.toml(see Unlock Group Format). Each unlock group grants a set of ship schematics, module schematics, building types, and/or assembler recipes. A ship, module, building, or assembler recipe is locked at game start if and only if some unlock group grants it; anything not granted by any unlock group starts unlocked. (For assembler recipes this "starts unlocked" is further governed by implicit gating — see REQ-LOCK-IMPLICIT; an assembler recipe granted by an unlock group is explicitly gated and never subject to implicit unlocking, while one flaggedunlocked_at_startis always available.) A locked item is unlocked only by awarding its unlock group via REQ-DEF-SCHEMATIC-DROP, which grants all of the group's members at once. Once awarded, an unlock group and its members are never re-locked within a run, and the group is removed from the drop pool permanently; lock states reset to their initial values on Restart (REQ-CFG-RELOAD). Each grantable id may be granted by at most one unlock group; a grant id that names no defined ship/module/building/assembler-recipe, that names a non-assembler recipe, or that is granted by more than one unlock group, is a configuration error that fails config load with a descriptive message (REQ-CFG-RELOAD). -
REQ-LOCK-PREREQ: An unlock group may optionally define
requires— a list of prerequisite unlock-group ids that must already have been awarded before this group may enter the drop pool. A prerequisite is satisfied only when the unlock group it names has been awarded (REQ-LOCK-EXPLICIT). This check is applied in addition to the conditions in REQ-DEF-SCHEMATIC-DROP: a group enters the eligible pool only when itsstation_levelcondition is met, it has not yet been awarded, and every id in itsrequiresis satisfied.requiresdefaults to empty (no prerequisites). The check is re-evaluated against the current set of awarded unlock groups every time a drop pool is built (after each REQ-DEF-SCHEMATIC-DROP and on Restart per REQ-CFG-RELOAD), so a gated group becomes eligible in the first drop after its last prerequisite is awarded. Every id listed in anyrequiresmust resolve to an unlock group defined inunlocks.toml; an id that names no such group is a configuration error that fails config load with a descriptive message (config is loaded at startup and reloaded on Restart, REQ-CFG-RELOAD). An unlock group that lists itself, or a cycle of mutually dependent prerequisites, is not a load error but can never become eligible, since no group in the cycle can be the first to be awarded. -
REQ-LOCK-IMPLICIT: Item types and miner/assembler recipes are implicitly unlocked or locked based on the current set of unlocked ship, module, and assembler recipe schematics. The implicit unlock set is recomputed whenever any schematic changes lock state (on Restart or after REQ-DEF-SCHEMATIC-DROP). Computation:
- Start with the union of: (a) all item types listed in
materialsacross all currently unlocked ship schematics and all currently unlocked module schematics, and (b) the output item type of every assembler recipe that is currently explicitly available — that is, either flaggedunlocked_at_startinrecipes.toml, or granted by an unlock group that has been awarded (REQ-LOCK-EXPLICIT). - For each item type in the current set: for every recipe (miner, smelter, or assembler) that produces it — skipping any assembler recipe that is granted by an unlock group whose group has not yet been awarded — add each of that recipe's input item types to the set. If the recipe is a miner recipe, or an assembler recipe that is not granted by any unlock group, mark it as implicitly unlocked. Assembler recipes that are explicitly available (flagged
unlocked_at_start, or granted by an awarded unlock group) are available in the assembler recipe-selection dialog by virtue of REQ-LOCK-EXPLICIT; their inputs are also added to the implicit set in this step. - Repeat step 2 until no new item types are added. Item types and miner/assembler recipes not reached by this process (and not explicitly unlocked) are locked. Smelter recipes participate in the traversal to propagate unlocking to their inputs but are never themselves shown in any UI dropdown.
- Start with the union of: (a) all item types listed in
-
REQ-LOCK-REPROCESSING-POOL: The pool of possible outputs for a Reprocessing Plant cycle (REQ-BLD-REPROCESSING) is restricted to item types that are currently implicitly unlocked (REQ-LOCK-IMPLICIT). Weights are renormalized over the eligible outputs. If no eligible outputs remain, the Reprocessing Plant cannot start a production cycle.
-
REQ-LOCK-UI-RECIPE: Locked miner ore-type recipes and assembler recipes are not shown in their respective recipe-selection dialogs (REQ-UI-SELECT-BUTTON).
-
REQ-LOCK-UI-SCHEMATIC: Locked ship schematics are not shown in the shipyard's schematic-selection dialog (REQ-UI-SELECT-BUTTON).
-
REQ-LOCK-BUILDING: A building type granted by an unlock group (REQ-LOCK-EXPLICIT) is locked until that group is awarded. A locked building type has no button in the build button bar (REQ-UI-BUILD-BAR) and cannot be placed, selected as a build tool, or triggered by its build hotkey (REQ-UI-HOTKEYS); its button appears in the bar only once the building type is unlocked, at which point the bar re-centers. Building types not granted by any unlock group are available from game start. Lock state resets on Restart (REQ-CFG-RELOAD).
-
REQ-LOCK-UI-SPLITTER: Item types that are not implicitly unlocked are excluded from splitter filter dropdowns (REQ-BLD-SPLITTER).
-
REQ-LOCK-UI-BLUEPRINT: When a blueprint is placed (REQ-UI-BLUEPRINT-PLACE): if a building in the blueprint is of a currently locked building type (REQ-LOCK-BUILDING), that building is silently skipped — no ghost, no validity check, no construction site, and its cost is excluded from the total — exactly as if it were not part of the blueprint; if a stored recipe ID for a miner or assembler is currently locked, that building's recipe is left unset rather than applied; if a stored splitter filter entry refers to a locked item type, that entry is silently removed. (The analogous rule for locked ship schematics is defined in REQ-UI-BLUEPRINT-PLACE.)
Threat Level & Enemy Waves
- REQ-WAV-BOSS-COUNTER: A global boss wave counter
xstarts at 1 at game start and increments by 1 immediately after each boss wave fires. It represents the current boss wave cycle number and is used as the variable in the threat rate formula. - REQ-WAV-THREAT-RATE: A global threat level accumulates continuously over real game time. The rate of increase per second is determined by
world.toml [waves].threat_rate_formulawherexis the boss wave counter (REQ-WAV-BOSS-COUNTER), clamped to a minimum of 0 (negative formula values are treated as 0). The rate is constant within each boss wave cycle and steps up each timexincrements. Threat accumulation is paused during quiet windows (REQ-WAV-QUIET). Example:1*x - 30yields 0 threat/s when x ≤ 30 and increases linearly beyond that. - REQ-WAV-GAP: At game start and immediately after each normal wave is triggered, a random inter-wave gap is drawn uniformly from [
world.toml [waves].gap_min_seconds,gap_max_seconds]. The gap timer does not advance while inside a quiet window (REQ-WAV-QUIET); if a gap would expire inside a quiet window, its expiry is deferred until the quiet window ends. - REQ-WAV-TRIGGER: When the gap timer expires outside a quiet window, a normal wave is triggered. Ships are selected one at a time: from all schematics whose threat cost (REQ-MOD-THREAT) is > 0, uniformly randomly pick one whose cost fits the remaining threat budget. For wave ship selection, the threat cost is computed using the schematic's
default_moduleslayout (REQ-WAV-DEFAULT-MODULES). Repeat until no eligible schematic fits. Any remaining threat carries over to the next normal wave. A longer gap results in a larger wave. - REQ-WAV-BOSS-COUNTDOWN: A boss countdown timer starts at
world.toml [waves].boss_countdown_seconds(default 300) at game start and counts down continuously in real game-time seconds. It is not paused during quiet windows. When it reaches 0, a boss wave is triggered (REQ-WAV-BOSS-TRIGGER). Immediately after the boss wave fires,xincrements (REQ-WAV-BOSS-COUNTER) and a fresh countdown starts at the same configured value. - REQ-WAV-BOSS-ADVANCE: When the player destroys a set of enemy defence stations, the boss countdown is reduced by
world.toml [push].boss_advance_seconds(default 60), clamped to a minimum of 0. Threat that would have accumulated during the skipped time is not added. If the countdown reaches 0 by this reduction, the boss wave is triggered immediately. - REQ-WAV-QUIET: A quiet window suppresses normal wave spawning around each boss wave. The pre-boss quiet window begins when the boss countdown falls to or below
world.toml [waves].boss_quiet_before_secondsand ends when the countdown reaches 0. The post-boss quiet window begins immediately when the boss wave fires and lastsworld.toml [waves].boss_quiet_after_secondsseconds. Threat accumulation is paused during both windows. The normal wave gap timer does not advance during either window (REQ-WAV-GAP). The new boss countdown runs during the post-boss quiet window. - REQ-WAV-BOSS-TRIGGER: When the boss countdown reaches 0, a boss wave is triggered. Its threat budget is the sum of: (a)
world.toml [waves].boss_threat_duration_seconds(default 60) multiplied by the current threat rate, and (b) all unspent threat carried over from normal waves. Ships are selected using the same random process as normal waves (REQ-WAV-TRIGGER). Any threat remaining unspent after ship selection carries over to the first normal wave of the new cycle. - REQ-WAV-DEFAULT-MODULES: Enemy ships spawned by waves use the
default_moduleslist defined per schematic inships.toml. Thedefault_modulesarray uses the same format as layout blueprints (see Layout Blueprint TOML Format). Ifdefault_modulesis absent or empty, the ship spawns with no modules. Invalid module instances (unknown type, position outside the grid, position on a non-buildable cell, or overlapping another module) are silently skipped. - REQ-WAV-SPAWN-DURATION: Ships in a wave are spawned one at a time over
world.toml [waves].spawn_duration_seconds.
Push Effects
- REQ-PSH-STATION-STATS: Enemy defence station stats are each defined as formulas in
stations.toml [enemy_station]:hp_formula,damage_formula,range_formula,fire_rate_formula,scrap_drop_formula, where x is the station level — an integer starting at 0 for the initial set and incrementing by 1 each time a new set is placed.
Asteroid Expansion
- REQ-EXP-UNLOCK: The player can unlock additional asteroid tile columns to the left of the existing asteroid by spending building blocks from the global stock.
- REQ-EXP-COST: Each expansion adds
world.toml [expansion].columns_per_expansioncolumns. The building block cost of an expansion is defined by the formulaworld.toml [expansion].cost_building_blocks_formula, wherexis the number of expansions already purchased (0 for the first expansion, incrementing by 1 for each subsequent expansion). The formula is evaluated at purchase time and its result is floored to an integer number of building blocks.
UI
Layout
The screen is a single column: a header bar across the top and the game world view filling the whole area below it. There is no side panel. All three permanent UI widgets float over the game world — the build button bar (REQ-UI-BUILD-BAR) at its bottom center, the selection panel (REQ-UI-SELECTION-PANEL) at its right edge, centered vertically within the height left above the build button bar and shown only while something is selected, and the controls panel (REQ-UI-CONTROLS-PANEL) at its left edge, bottom-aligned within that same height. Blueprints have no permanent screen real estate; they are reached through modal dialogs (REQ-UI-BLUEPRINT-DIALOG):
+-----------------------------------------------------------+
| Header Bar |
+-----------------------------------------------------------+
| +-----------+ |
| | Selection | |
| Game World | Panel | |
| +-----------+ |
| +----------+ |
| | Controls | |
| +----------+ +------------------+ |
| | Build Button Bar | |
+------------------+------------------+---------------------+
(full window width)
-
REQ-UI-HEADER: The header bar spans the full width of the game window and always shows the elapsed survival time, the current global building blocks stock, and the artifact count (REQ-WIN-ARTIFACT-COUNT) displayed as
Artifacts: x/y(wherexis the current artifact count andyisworld.toml [world].artifact_win_count) on the left, the boss wave counter and boss countdown (REQ-UI-BOSS-STATUS) and an asteroid expansion button (REQ-UI-EXPAND-BUTTON) to the left of the speed buttons, and game speed controls on the right. -
REQ-UI-BLOCKS-ICON: In the header bar (REQ-UI-HEADER), the global building blocks stock is displayed as
Stock: <n>followed by thebuilding_blockitem icon (REQ-UI-ITEM-ICON) — e.g.Stock: 200then a small block icon — replacing theBuilding Blocks: <n>text label. The icon is sized to the header text height. When no icon file exists forbuilding_block(a missing icon is not an error, REQ-UI-ITEM-ICON), the display falls back to theStock: <n> Blockstext. The hover tooltip (REQ-UI-BLOCKS-TOOLTIP) applies in either form. -
REQ-UI-BLOCKS-TOOLTIP: The header bar's building blocks stock display (REQ-UI-HEADER) shows a hover tooltip with the descriptive text defined in
world.toml [world].building_blocks_tooltip— intended to tell the player what building blocks are used for and how to obtain them. If the field is unset, the stock display shows no tooltip. This tooltip is distinct from the build/module button tooltips (REQ-UI-BUILD-TOOLTIP, REQ-MOD-UI-MODULE-TOOLTIP). -
REQ-UI-ARTIFACTS-TOOLTIP: The header bar's artifact count display (REQ-UI-HEADER) shows a hover tooltip with the descriptive text defined in
world.toml [world].artifact_tooltip— intended to tell the player what artifacts are, how they are obtained (REQ-DEF-SCHEMATIC-DROP), and that collectingworld.toml [world].artifact_win_countof them wins the game (REQ-WIN-ARTIFACT-COUNT). If the field is unset, the artifact count display shows no tooltip. This tooltip is distinct from the building blocks tooltip (REQ-UI-BLOCKS-TOOLTIP) and the build/module button tooltips (REQ-UI-BUILD-TOOLTIP, REQ-MOD-UI-MODULE-TOOLTIP). -
REQ-UI-BOSS-STATUS: The header bar displays, to the left of the speed buttons, the current boss wave counter (REQ-WAV-BOSS-COUNTER) and the time remaining on the boss countdown (REQ-WAV-BOSS-COUNTDOWN). The boss wave counter is shown as
Boss Wave #<x>and the countdown asNext boss: <M:SS>, where<M:SS>is the remaining seconds formatted as whole minutes and two-digit seconds. Both values update continuously as the simulation runs. -
REQ-UI-SPEED: The game speed controls in the header bar are buttons for 0×, 0.5×, 1×, 2×, and 10× speed. The currently active speed is shown as selected. All game simulation (production, movement, threat accumulation, wave timing) scales with the selected speed. 0× pauses the game.
-
REQ-UI-PAUSE-BORDER: While the game is paused (speed 0×, whether set via the speed controls (REQ-UI-SPEED), the Space toggle (REQ-UI-HOTKEYS), or an auto-pausing modal), a vignette border is drawn around the edges of the game world view to make the paused state hard to miss. The border is black and fades in the alpha channel from fully transparent at its inner (center-facing) edge to 50% opacity at the viewport edge, over a thickness of 100 pixels (capped at half the smaller viewport dimension on very small views).
-
REQ-UI-DECONSTRUCT-BORDER: While deconstruct mode is active (REQ-UI-DECONSTRUCT-BUTTON, REQ-UI-HOTKEYS), a vignette border is drawn around the edges of the game world view to signal the mode, matching the geometry of the paused-state vignette (REQ-UI-PAUSE-BORDER): a 100-pixel thickness (capped at half the smaller viewport dimension on very small views) with the four sides meeting along mitred corner diagonals. It fades in the alpha channel from fully transparent at its inner (center-facing) edge to the deconstruct tint color at the viewport edge. The color — including its alpha, which sets the peak opacity at the viewport edge — is read from
visuals.toml [overlays].deconstruct_tint, the same deconstruct-mode color used for the hover tint. The border is presentation-only and has no effect on the simulation. If the game is both paused and in deconstruct mode, both vignettes are drawn and compose over each other. -
REQ-UI-EXPAND-BUTTON: The header bar shows an asteroid expansion button captioned
Expand: <x>followed by thebuilding_blockitem icon (REQ-UI-BLOCKS-ICON, REQ-UI-ITEM-ICON) in place of the trailingBlocksword, where<x>is the current expansion cost computed fromworld.toml [expansion].cost_building_blocks_formulaat the current number of purchased expansions (REQ-EXP-COST). When no icon file exists forbuilding_block, the caption falls back to theExpand: <x> Blockstext. Clicking the button unlocks the next asteroid expansion (REQ-EXP-UNLOCK, REQ-GW-ASTEROID-EXPAND), spending that many building blocks from the global stock. The button is disabled when the player cannot currently afford the cost (consistent with REQ-UI-BUILD-DISABLED). The caption updates as the cost changes with each purchased expansion. -
REQ-UI-WORLD-SIZE: The game world view occupies the full width of the game window and the full height below the header bar. No widget insets it: the build button bar (REQ-UI-BUILD-BAR), the selection panel (REQ-UI-SELECTION-PANEL), and the controls panel (REQ-UI-CONTROLS-PANEL) float over it.
-
REQ-UI-SELECTION-PANEL: The selection panel (the panel described under Selection Panel, REQ-UI-SINGLE-SELECTION and following) is a widget that floats over the game world view (REQ-UI-WORLD-SIZE), anchored to the view's right edge with a small margin. It is sized to its content in both width and height, so it grows and shrinks as the selection changes, staying right-anchored as its size changes.
The panel's vertical placement and maximum height are defined against an available band: the height of the game world view, less the panel's top and bottom margins, less the height of the horizontal strip occupied by the build button bar at the bottom of the view (the bar's height plus its bottom margin, REQ-UI-BUILD-BAR) — the bar's band is subtracted over the full view width, regardless of how wide the bar currently is. The panel is vertically centered within that available band, not within the full view height, so it sits slightly above the view's vertical center. Should its content ever be taller than the band, the panel's height is capped at the band height and the content scrolls vertically within it.
- Visibility. The panel is shown only while at least one object is selected. With an empty selection it is not shown at all (REQ-UI-EMPTY-SELECTION), leaving the full game world view visible.
- Overlay behavior. As for the build button bar (REQ-UI-BUILD-BAR): the panel occludes the strip of the game world it covers; the world view itself keeps its full extent and the view's scrolling, ghost rendering, and tile geometry are unaffected. It is drawn above the pause and deconstruct vignettes (REQ-UI-PAUSE-BORDER, REQ-UI-DECONSTRUCT-BORDER), which keep their full right-hand band underneath it, and below the modal dim (REQ-UI-MODAL-DIM), which covers the entire game window including the panel. The panel and the build button bar never overlap, because the panel's available band excludes the bar's strip; the bar itself never moves on the panel's account (REQ-UI-BUILD-BAR).
- Input. Mouse events over the panel are consumed by the panel and never reach the game world: hovering it shows no builder-mode ghost at the tile beneath, and clicking it neither places a building nor changes the selection. Right-clicking the panel does not exit builder mode (REQ-BLD-BUILDER-MODE) or cancel a belt drag (REQ-BLD-BELT-DRAG).
-
REQ-UI-MODAL-DIM: While a modal dialog, menu, or full-screen state screen is open on top of the game, a transparent black overlay (a dim/scrim) is drawn over the entire game window — the header bar, the game world view, and the widgets floating over it (the build button bar, REQ-UI-BUILD-BAR, the selection panel, REQ-UI-SELECTION-PANEL, and the controls panel, REQ-UI-CONTROLS-PANEL) — behind that modal, so the game reads as inactive while the modal holds focus. The overlay is shown for every modal that auto-pauses the simulation — the escape menu (REQ-UI-GAME-MENU), the recipe/schematic selection dialog (REQ-UI-SELECT-BUTTON), the layout configuration dialog (REQ-MOD-UI-DIALOG), the schematic choice dialog (REQ-DEF-SCHEMATIC-DROP), the blueprint save dialog (REQ-UI-BLUEPRINT-CREATE), and the blueprint selection dialog (REQ-UI-BLUEPRINT-DIALOG) — as well as the game-over screen (REQ-HQ-GAME-OVER) and the win screen (REQ-WIN-SCREEN), which end rather than pause the game. When modals are nested (for example the Create Blueprint name dialog (REQ-MOD-UI-BLUEPRINT-CREATE) opened from the layout configuration dialog), only a single dim is shown over the game window; nested modals do not stack additional overlays. The same applies when one modal hands directly off to another — the blueprint save dialog opening the blueprint selection dialog on confirm (REQ-UI-BLUEPRINT-CREATE): the dim persists across the handoff rather than flickering off and back on, and the simulation is not resumed in between. The dim color and opacity are read from
visuals.toml [overlays](a semi-transparent black modal-dim color), consistent with the other overlay colors. The overlay is presentation-only and has no effect on the simulation.
Game World
-
REQ-UI-SCROLL: The player scrolls the view horizontally across the scrollable area by pressing A (scroll left) and D (scroll right). The pan speed is not constant; it varies with the view's position per REQ-UI-SCROLL-SPEED.
-
REQ-UI-SCROLL-SPEED: The horizontal pan speed varies with position so the player crosses the empty middle of the world quickly while retaining fine control near the asteroid and near the front line. Two pan speeds are read from
world.toml [scroll]:pan_speed_slow_tiles_per_second(the base speed, used while the view is over the asteroid and player buffer zone) andpan_speed_fast_tiles_per_second(the faster speed, used while the view is over the contest zone). Both are expressed in tiles per second and apply equally to the A and D scroll directions. The current pan speed is a function of the view's horizontal center X (REQ-GW-REGIONS defines the regions):- Flat regions: while the view center is left of the contest zone (over the asteroid or player buffer zone) and outside any ramp band, the pan speed is the slow speed; while the view center is inside the contest zone and outside any ramp band, the pan speed is the fast speed.
- Ramp bands: a transition ramp band of width
world.toml [scroll].pan_ramp_band_width_tilestiles straddles each contest-zone boundary (the player defence stations on the left, the enemy defence stations on the right), centered on the boundary with half the band width on each side. While the view center is within a ramp band, the pan speed is linearly interpolated between the slow speed at the band's outer (non-contest-zone) edge and the fast speed at the band's inner (contest-zone) edge, by the view center's fractional position across the band. This produces smooth speed changes when entering and exiting the fast contest-zone range rather than an abrupt jump. - Narrow contest zone: should the two ramp bands overlap (a contest zone narrower than the band width), each ramp is clamped at the contest-zone center so the bands do not cross; the fast plateau then reduces to a single point at the center and the peak speed there may be below the fast speed.
Because the contest-zone boundaries shift as the scrollable area grows with each push (REQ-GW-PUSH-EXPAND, REQ-GW-SCROLL-LIMIT), the ramp bands are recomputed from the current contest-zone boundaries. This is a presentation-only concern and does not affect the simulation, consistent with REQ-UI-NO-ZOOM.
-
REQ-UI-WORLD-ICON: In the game world, a building is drawn with an icon's glyph symbol centered on its footprint, in place of the letter identity glyph. The icon is an SVG loaded from
data/icons/buildings/; only the icon's glyph is drawn in the world — in a contrasting ink (white over dark fills, dark over light fills) so it stays legible — and its colored chip background is omitted, because the footprint is already filled with the building'svisuals.tomlfill color. This applies to the production buildings (Miner, Smelter, Assembler, Reprocessing Plant, Shipyard, Salvage Bay), the HQ, and the player and enemy defence stations, wherever the identity label appears: operational buildings, construction sites (REQ-UI-CONSTRUCTION-PROGRESS), and the builder-mode and blueprint-placement ghosts. Belts, splitters, and tunnels are excluded — they keep their existing tile rendering so their orientation and flow stay readable (a centered icon would obscure direction). Their build-menu buttons still use icons (REQ-UI-BUILD-ICON); in particular the shared Tunnel button'stunnel_entry.svgis a build-button icon only, not a world icon. The directional output-port glyphs (REQ-UI-PORT-GLYPH, REQ-UI-PORT-TARGET-GLYPH) are a separate indicator and are unaffected. A building or station with no icon file falls back to itsvisuals.tomltext glyph; a type with neither icon nor glyph shows no identity label. A missing icon is not an error, consistent with REQ-UI-BUILD-ICON. -
REQ-UI-ITEM-ICON: In the game world, an item is drawn with its item icon in place of the colored square of REQ-GW-TILE-SIZE. The icon is a self-contained, full-color SVG (rendered as-is, unlike the glyph-only building icons of REQ-UI-WORLD-ICON), loaded at runtime from
data/icons/items/— a sibling of the config directory, read the same way as the building icons (REQ-UI-BUILD-ICON) — one file per item type named after the item's id (e.g.iron_ore.svg). It fills the item's half-tile rect, keeping the size, spacing, and draw-order rules of REQ-GW-TILE-SIZE, and applies wherever an item is drawn: on belts, splitters, and tunnel ends, and while emerging from or sinking into a building port (REQ-MAT-OUTPUT-EMERGE, REQ-MAT-INPUT-INTAKE). An item type with no icon file falls back to itsvisuals.tomlcolored square (fill+outline); a missing icon is not an error, consistent with REQ-UI-BUILD-ICON. For performance, each item icon is rasterized to a pixmap cached per target pixel size — re-rasterized only when the tile pixel size changes (e.g. on view resize) — rather than re-rendered from vector every frame. -
REQ-UI-CONSTRUCTION-PROGRESS: Construction sites display the building's identity symbol centered on the footprint (same as an operational building) — its icon glyph, or the text glyph as a fallback (REQ-UI-WORLD-ICON). Below the symbol — or centered on the footprint if the building has neither an icon nor a glyph — a construction progress percentage is shown (integer, e.g.
42%), increasing from 0% to 100% as construction completes. -
REQ-UI-PORT-GLYPH: Every output port of every building is indicated by a directional glyph drawn on the port's tile. The glyph is a
>rotated to face the port's exit direction (>for East,^for North,<for West,vfor South). It is drawn at the midpoint between the tile center and the tile edge that the port exits through (i.e. halfway from center toward the exit edge). The indicator is rendered for all building states: operational buildings, construction sites, and the builder-mode ghost. Buildings with multiple output ports (e.g. splitters) show one indicator per port. -
REQ-UI-PORT-TARGET-GLYPH: While in builder mode (REQ-BLD-BUILDER-MODE), the builder-mode ghost additionally shows, for each of the building's output ports, a directional glyph drawn centered in the port's target cell — the cell immediately outside the footprint that the port pushes into, i.e. the cell the surface-mask output-port indicator occupies (see Surface Mask Format). As in REQ-UI-PORT-GLYPH the glyph is a
>rotated to face the port's exit direction (>East,^North,<West,vSouth), previewing where the port's output will go before placement. This is in addition to the on-tile port glyph of REQ-UI-PORT-GLYPH, and — unlike that indicator — is shown only for the builder-mode ghost, not for operational buildings, construction sites, or the blueprint-placement ghost (REQ-UI-BLUEPRINT-PLACE). A building with multiple output ports (e.g. a splitter) shows one target-cell glyph per port. The target-cell glyph is drawn larger than the on-tile port glyph so it stands out as the flow-direction preview. Exceptions: the Tunnel Entry shows no target-cell glyph, because it receives items (which may arrive from any of its non-mouth edges, REQ-BLD-TUNNEL-ENTRY) rather than emitting into a single adjacent cell; the Shipyard shows none either, because its output port is a ship-spawn point (REQ-SHP-SPAWN-PLAYER) rather than a belt-item output (REQ-MAT-OUTPUT-EMERGE). -
REQ-UI-STATUS-LIGHT: Every operational production building — Miner, Smelter, Assembler, Reprocessing Plant, Shipyard, and Salvage Bay — renders a small status light: a filled circle with a black outline drawn in the building's upper-right corner, letting the player read a building's production state without selecting it. The light is anchored to the footprint corner that is the upper-right corner in the building's default orientation and rotates with the building — like the output-port glyph (REQ-UI-PORT-GLYPH) — so it stays on the same physical corner of the building as it is rotated. The status light is rendered only for operational buildings; construction sites (which instead show construction progress, REQ-UI-CONSTRUCTION-PROGRESS) and the builder-mode ghost do not render it. Buildings that are not production buildings — belts, splitters, tunnel entries/exits, and the HQ — have no status light. The black outline is constant; the fill color reflects the building's current production state.
- For the five production buildings (Miner, Smelter, Assembler, Reprocessing Plant, Shipyard), the fill color is determined by evaluating, in order:
- Grey — no recipe or schematic is selected. This applies only to buildings with a player-facing selection (Miner, Assembler, Shipyard); the Smelter and Reprocessing Plant always run an implicit recipe (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING) and are never grey.
- Green — the building is currently producing: a production cycle is active (REQ-MAT-CYCLE; for the Shipyard, an in-progress production cycle per REQ-BLD-SHIPYARD).
- Red — the building is idle because a required input is missing from its input buffers, so it cannot start a cycle. Missing input takes precedence over a full output buffer: if any required input is missing the light is red even when the output buffer is also full.
- Yellow — the building is idle with all required inputs present but its output buffer full, so no new cycle can start (REQ-MAT-OUTPUT-BUFFER, REQ-MAT-CYCLE).
- A configured building that is momentarily idle yet blocked by neither condition (all inputs present and the output buffer has room — a transient state that resolves into a started cycle on the same or the next tick per REQ-MAT-CYCLE) shows green.
- The Salvage Bay has no recipe and no production cycle (REQ-BLD-SALVAGE-BAY); its status light uses only two states: green while its output buffer holds at least one unit of scrap, and red while its output buffer is empty. The Salvage Bay's status light is never grey or yellow.
- The four fill colors (grey, green, red, yellow) and the outline color are read from
visuals.toml [status_light], consistent with the other rendering-only colors. The status light is presentation-only and has no effect on the simulation.
- For the five production buildings (Miner, Smelter, Assembler, Reprocessing Plant, Shipyard), the fill color is determined by evaluating, in order:
-
REQ-UI-HP-BARS: All entities with HP — the HQ, player and enemy defence stations, and player and enemy ships — render an HP bar below them. The bar is always visible regardless of current HP. The bar's filled portion represents the fraction of current HP to maximum HP.
-
REQ-UI-NO-ZOOM: The view has a fixed zoom level; the player cannot zoom in or out.
-
REQ-UI-HOTKEYS: Global keyboard shortcuts:
- Space — toggles pause. Pressing Space pauses (sets speed to 0×) and stores the previously selected non-zero speed; pressing Space again restores that speed.
- W — increases game speed by one step in the sequence 0×, 0.5×, 1×, 2×, 10× (no wrap-around past 10×).
- S — decreases game speed by one step in the same sequence (no wrap-around past 0×).
- A / D — scroll the view left / right (REQ-UI-SCROLL).
- Q — context-sensitive. If a build mode is active (builder mode or blueprint placement mode), pressing Q exits it. Otherwise, pressing Q toggles deconstruct mode: it enters deconstruct mode if inactive, or exits deconstruct mode if already active. (See also REQ-UI-DECONSTRUCT-BUTTON for the equivalent button.)
- R / Shift+R — in builder mode, rotate the ghost counter-clockwise / clockwise (REQ-BLD-ROTATE).
- C — create a temporary blueprint from the current selection and enter its placement mode (REQ-UI-BLUEPRINT-TEMP). Has effect only when at least one player-placeable building is selected; otherwise it does nothing.
- V — re-enter placement mode for the last temporary blueprint created with C (REQ-UI-BLUEPRINT-TEMP). Does nothing when no temporary blueprint exists.
- Ctrl+C — save the current selection as a named blueprint: opens the blueprint save dialog (REQ-UI-BLUEPRINT-CREATE). Has effect only when at least one player-placeable building is selected; otherwise it does nothing.
- Ctrl+V — opens the blueprint selection dialog (REQ-UI-BLUEPRINT-DIALOG), from which a saved blueprint is picked for placement. It is available whenever the game is being played, regardless of the current selection or of which build mode is active, and opens the dialog even when no blueprints are saved yet.
- Escape — opens the escape menu (REQ-UI-GAME-MENU). While a blueprint dialog is open, Escape closes that dialog instead (REQ-UI-BLUEPRINT-DIALOG).
- Build mode selection — pressing a build hotkey activates builder mode for the corresponding building type, equivalent to clicking its build button (REQ-BLD-BUILDER-MODE):
- 1 — Belt, 2 — Splitter, 3 — Tunnel (the unified tunnel build mode, REQ-BLD-TUNNEL-MODE). Hotkey 4 is unused.
- Shift+1 — Miner, Shift+2 — Smelter, Shift+3 — Assembler, Shift+4 — Shipyard, Shift+5 — Salvage Bay, Shift+6 — Reprocessing Plant.
These shortcuts are the definition; the controls panel (REQ-UI-CONTROLS-PANEL) displays the subset of them that applies to the player's current situation, and the build buttons carry the build hotkeys on their badges (REQ-UI-BUILD-COST). Neither display defines a binding of its own.
Debug Draw
-
REQ-UI-DEBUG-DRAW: A debug draw mode can be toggled on and off with the F3 key. It is inactive by default. While active, the sensor range of every ship — both player and enemy — is drawn as a circle centered on the ship, using that ship schematic's outline color from
visuals.toml. -
REQ-UI-DEBUG-OVERLAY: While debug draw mode is active (REQ-UI-DEBUG-DRAW), a text overlay is drawn in the upper left corner of the game world view. The overlay has a semi-transparent black background sized to fit its content. It displays the following lines of text:
Accumulated Threat Level: <level>— where<level>is the current accumulated threat level (see REQ-WAV-THREAT-RATE).Time until Wave: <time_s>— where<time_s>is the remaining time in seconds on the normal-wave inter-wave gap timer (see REQ-WAV-GAP). During a quiet window the gap timer is frozen; the displayed value reflects that frozen state.Threat Accumulation Rate: <rate> threat/s— the rate at which the accumulated threat level is currently increasing (see REQ-WAV-THREAT-RATE). During a quiet window (REQ-WAV-QUIET), this is 0, reflecting that accumulation is currently paused.Max Factory Production: <rate> threat/s— the threat-equivalent of the factory's total possible production: 1 threat/second for each completed (operational, not under construction) miner, smelter, assembler, reprocessing plant, and shipyard. One second of production equals one threat (see REQ-MOD-THREAT).Current Factory Production: <rate> threat/s— the threat-equivalent of the factory's current production: 1 threat/second for each completed miner, smelter, assembler, reprocessing plant, or shipyard that currently has an active production cycle (see REQ-MAT-CYCLE; for shipyards, an in-progress production cycle per REQ-BLD-SHIPYARD).
Escape Menu
- REQ-UI-GAME-MENU: Pressing Escape at any time opens the escape menu as a modal dialog and pauses the simulation (sets speed to 0×). On close, the simulation speed is restored to what it was before the menu was opened — so if the game was already paused, it remains paused. The menu contains three buttons:
- Continue — closes the menu and returns to the game.
- Restart — resets the simulation to its initial state and closes the menu at 1× speed.
- Quit — closes the application. Pressing Escape while the escape menu is open is equivalent to clicking Continue.
Selection Panel
The selection panel shows the details of the current selection, whatever its category (REQ-UI-SELECTION-CATEGORIES): buildings and construction sites, ships, defence stations, and debris. Its position, size, and overlay behavior are defined in REQ-UI-SELECTION-PANEL; the requirements below define its content.
The panel shows exactly one content at a time, picked from the catalog in REQ-UI-SELECTION-CONTENT by what is selected. Every content is assembled from the same small set of parts and follows the same card structure (REQ-UI-SELECTION-CARD), so different selections read alike and a part means the same thing wherever it appears.
-
REQ-UI-EMPTY-SELECTION: When nothing is selected (no building, construction site, ship, defence station, or piece of debris), the selection panel is not shown at all — it is hidden rather than shown empty, so the full game world view is visible (REQ-UI-SELECTION-PANEL). It reappears as soon as an object is selected.
-
REQ-UI-SELECTION-CATEGORIES: Selection categories and precedence. Every selectable object belongs to one of two mutually exclusive selection categories: buildings (buildings and construction sites) and field objects (ships and defence stations — player or enemy — together with debris). A single selection holds objects from only one category at a time. Field objects of different kinds may be selected together (e.g. several ships plus debris, freely mixing player and enemy actors). Buildings are exclusive and take precedence — buildings win: selecting a building (by click, Ctrl+click, or a box-drag covering at least one building) clears any field selection and yields a buildings-only selection, and conversely selecting any field object clears any building selection. Point hit-testing prefers a building over a coincident field object, and among field objects prefers an actor (ship or defence station) over a coincident piece of debris (REQ-UI-ENTITY-CLICK-SELECT, REQ-UI-DEBRIS-CLICK-SELECT).
-
REQ-UI-SELECTION-CARD: Card structure. Every panel content is a card with the same three parts, top to bottom:
- Header — always shown. It holds the selection's identity symbol on the left — the building's icon glyph (REQ-UI-WORLD-ICON), a ship's schematic color swatch, or the kind symbol of a defence station or piece of debris — the selection's name beside it, and one optional right slot. The right slot holds a status indicator (REQ-UI-SELECTION-STATUS), a ship's current behavior (REQ-UI-SHIP-BEHAVIOR), or an object count — never more than one of them; which one applies is stated per content in REQ-UI-SELECTION-CONTENT.
- Configuration group — the controls that change how the selected object is set up: the recipe/schematic selection control (REQ-UI-SELECT-BUTTON), a shipyard's layout preview and Configure button (REQ-MOD-UI-PREVIEW), and a splitter's output filters (REQ-BLD-SPLITTER). It is shown identically for an operational building and for a construction site of the same type (REQ-BLD-SITE-CONFIG).
- Runtime group — what the object is currently doing: buffer contents, production progress, HP, remaining scrap, and the belt clear action (REQ-UI-BELT-CLEAR). For a construction site the entire runtime group is replaced by a captioned
Constructionsection: a progress bar filled to the site's construction completion with that completion as an integer percentage beside the caption — the same value the world draws on the footprint (REQ-UI-CONSTRUCTION-PROGRESS) — followed by a note that buffers appear once the building is built, because a site has neither buffers nor a production cycle (REQ-BLD-SITE-CONFIG). The configuration group is unaffected and stays visible on a site.
A group with nothing to show takes no space, so a content may consist of a header alone. Within a group, related parts form sections carrying a short caption above them (e.g.
Layout,Input buffers,Production,Output buffer); a section and its caption are shown only while that section has content, so e.g. a Miner (which consumes nothing) shows no input buffer section. -
REQ-UI-SELECTION-CONTENT: Content catalog. Which content the panel shows follows from the selection alone:
Selection Header right slot Configuration group Runtime group Miner, Assembler status recipe control + recipe summary buffers + production Smelter, Reprocessing Plant status recipe summary buffers + production Shipyard status schematic control + layout preview + Configure buffers + production Salvage Bay status — buffers HQ — — block stock + HP Belt, Tunnel Entry, Tunnel Exit count — clear action Splitter — output filters clear action Several buildings — — type counts + total cost One ship behavior — HP + hull stats + module summaries One defence station — — HP + stats Debris (one or several) count — remaining scrap Several mixed field objects count — type counts + scrap total Selections sharing a row of this table get the same content and differ only in the name and symbol in the header. A count in the right slot appears only for an aggregated multi-selection (REQ-UI-SELECTION-AGGREGATE); a single selection of those types shows an empty slot.
-
REQ-UI-SELECTION-STATUS: Status indicator. For a building whose production state is already rendered in the world as a status light (REQ-UI-STATUS-LIGHT) — Miner, Smelter, Assembler, Reprocessing Plant, Shipyard, Salvage Bay — the header's right slot repeats that same state as a colored dot with a short caption beside it, so the panel and the world never disagree. The state is derived from the evaluation defined in REQ-UI-STATUS-LIGHT rather than from a second definition, and the dot uses that state's fill color from
visuals.toml [status_light]. The captions name the state:no recipe(grey),producing(green),missing input(red),output full(yellow); for the Salvage Bay,holding scrap(green) andempty(red). A selected construction site shows the captionconstructingwith no dot, whatever its type. Buildings with no status light — belts, splitters, tunnel ends, the HQ — show nothing in the slot. -
REQ-UI-SELECTION-AGGREGATE: Aggregating a homogeneous multi-selection. When several objects are selected and their content can be shown as one — the same content, with its values aggregated over the whole selection — the panel shows that single content with the number of selected objects in the header's right slot (
x<count>), instead of the count summary of REQ-UI-MULTI-SELECTION / REQ-UI-FIELD-MULTI-SELECTION. This applies exactly where every part of the content aggregates:- Belt-subsystem tiles — any mix of belts, tunnel entries, and tunnel exits. Their content is the clear action alone, which already acts on the whole selection (REQ-UI-BELT-CLEAR).
- Debris — several pieces of debris and nothing else. Their remaining scrap sums into one value (REQ-UI-DEBRIS-PANEL).
Every other multi-selection falls back to the count summary. In particular a selection mixing a splitter with belts does not aggregate (a splitter carries per-object output filters, which have no aggregate), and neither do several production buildings of one type (per-building buffers and cycle progress have no aggregate).
-
REQ-UI-SINGLE-SELECTION: When one building is selected, the panel shows its symbol and name in the header (REQ-UI-SELECTION-CARD), its current recipe or schematic selection (REQ-UI-SELECT-BUTTON) and recipe summary (REQ-UI-RECIPE-SUMMARY) in the configuration group, and its input and output buffer contents in the runtime group. Each buffered item is shown as an item chip bearing that item's icon (REQ-UI-ITEM-ICON) and its current count:
- an input chip shows the per-cycle amount below the count (the items consumed per run, e.g.
/ 2 per cycle), or the count alone when the building has no selected recipe or schematic to give one; - an output chip shows the count against the output buffer's capacity as
a / b(REQ-MAT-OUTPUT-BUFFER), with the item's name below.
Input and output chips form separately captioned sections, each shown only while it holds something (REQ-UI-SELECTION-CARD). For a selected construction site the buffer sections are omitted (REQ-BLD-SITE-CONFIG).
- an input chip shows the per-cycle amount below the count (the items consumed per run, e.g.
-
REQ-UI-RECIPE-SUMMARY: Below the recipe/schematic selection control, a building running a recipe or schematic shows a one-line recipe summary: each input item's icon with its per-cycle amount, an arrow, each output item's icon with its per-cycle amount, and the cycle time in seconds. It restates what the building will do without opening the selection dialog, and it is the panel's only display of the cycle time. For a Shipyard the summary is built from the schematic's materials and production time including the placed modules' contributions (REQ-BLD-SHIPYARD, REQ-MOD-STAT-CALC), matching the buffers beneath it. Auto-recipe buildings (Smelter, Reprocessing Plant — REQ-BLD-SMELTER, REQ-BLD-REPROCESSING) have no player-selected recipe and so show no selection control; they show the summary of the recipe currently in production, and none while idle. A building with no recipe or schematic selected shows no summary.
-
REQ-UI-PRODUCTION-PROGRESS: For buildings that produce items or ships (miner, smelter, assembler, reprocessing plant, shipyard), the panel's runtime group shows a captioned production section: a horizontal progress bar filled to the completion of the active production cycle, with that completion beside the caption as an integer percentage (e.g.
72%), or the textidlein place of the percentage and an empty bar when no production cycle is active. The cycle time is shown in the recipe summary (REQ-UI-RECIPE-SUMMARY) rather than repeated here. When no recipe or schematic is selected, the production section is not shown at all. -
REQ-UI-MULTI-SELECT: The player selects multiple objects by box-drag or by Ctrl+clicking individual objects to add or remove them from the selection. Multi-select operates within a single category (REQ-UI-SELECTION-CATEGORIES). A box-drag that covers at least one building selects buildings (any field objects within the box are ignored — buildings win); a box-drag that covers no building but does cover ships, defence stations, or debris selects all of those field objects together (REQ-UI-ENTITY-CLICK-SELECT, REQ-UI-DEBRIS-MULTI-SELECT).
-
REQ-UI-MULTI-SELECTION: When multiple buildings are selected and the selection does not aggregate (REQ-UI-SELECTION-AGGREGATE), the panel shows a count summary. Its header names the size of the selection as
<n> buildingsin place of an object name, and carries no symbol and nothing in its right slot. Below it is one row per selected building type — the type's symbol, its name, and the number selected asx<count>— one type per row, and no per-building detail. A final row shows the total building block cost of the selection, captionedTotal costwith the value followed by thebuilding_blockitem icon (REQ-UI-BLOCKS-ICON, REQ-UI-ITEM-ICON): the sum of each selected building's placement cost (buildings.toml [[building]].cost, per REQ-BLD-COST), counting only player-placeable buildings (buildings with a button in the build button bar); non-player-placeable buildings (the HQ and defence stations) are excluded from the total, consistent with the blueprint total (REQ-UI-BLUEPRINT-CARD). Construction sites count at their building type's full placement cost regardless of construction progress. -
REQ-UI-CONFIG-INLINE: Recipe and schematic configuration for a selected building is shown within this panel, in its configuration group (REQ-UI-SELECTION-CARD). Recipe selection (miner, assembler) and schematic selection (shipyard) use the selection button and dialog (REQ-UI-SELECT-BUTTON) rather than an inline control. For shipyards, the panel additionally shows the ship layout preview and "Configure" button below the schematic selection button (REQ-MOD-UI-PREVIEW).
-
REQ-UI-SELECT-BUTTON: Recipe and schematic selection control. Recipe selection (Miner ore type, Assembler recipe) and schematic selection (Shipyard) are each presented in the selection panel as a single selection button whose caption is the name of the currently selected recipe or schematic, or a placeholder ("Select recipe" / "Select schematic") when none is selected. Clicking the button opens a modal selection dialog that pauses the game (speed set to 0×; on close, the speed is restored to what it was before the dialog was opened). The dialog contains a grid of option buttons, one per selectable option — only options that are currently unlocked are shown (REQ-LOCK-UI-RECIPE for recipes, REQ-LOCK-UI-SCHEMATIC for schematics). Hovering an option button shows the selection info tooltip (REQ-UI-SELECT-TOOLTIP). Clicking an option button selects that recipe/schematic, closes the dialog, and updates the selection button's caption in the selection panel. The dialog can be dismissed without changing the current selection (e.g. closing it without clicking an option). Selecting a new recipe or schematic has the same effects as before (REQ-MAT-INPUT-BUFFER, REQ-MAT-OUTPUT-BUFFER, REQ-BLD-SHIPYARD).
-
REQ-UI-SELECT-TOOLTIP: Selection info tooltip. Hovering an option button in the selection dialog (REQ-UI-SELECT-BUTTON), and hovering the selection button in the selection panel when a selection is set, displays an info tooltip:
- For a recipe (Miner or Assembler): the recipe name; the name and quantity of each input item (no inputs are listed for miner recipes, which consume nothing); the completion time (
duration_seconds); and the name and quantity of the produced output item. - For a ship schematic (Shipyard): the ship's
display_name; the name and quantity of each base required material ([ship.schematic].materials, excluding any module contributions); the base production time ([ship.schematic].production_time_seconds); and "Produces: 1 ".
- For a recipe (Miner or Assembler): the recipe name; the name and quantity of each input item (no inputs are listed for miner recipes, which consume nothing); the completion time (
-
REQ-UI-RECIPE-ICON: In the recipe-selection dialog (REQ-UI-SELECT-BUTTON) for a Miner or Assembler, each recipe option button shows the icon of the recipe's produced item instead of its name caption (icon-only). The item shown is the recipe's
iconfield if set, otherwise its first output item; the icon is that item's icon per REQ-UI-ITEM-ICON. When the item has no icon file, the button falls back to the recipe/item name caption. The recipe name and details remain available on hover via the selection info tooltip (REQ-UI-SELECT-TOOLTIP). The(None)option keeps its text caption. This applies only to recipe options; the Shipyard schematic-selection dialog is unaffected and continues to show ship name captions. -
REQ-UI-BELT-CLEAR: When one or more belt, splitter, tunnel entry, or tunnel exit tiles are selected, the panel's runtime group shows a "Clear stuck items" button that removes all items from the selected tiles. Clearing a tunnel entry or exit also discards all items currently in transit through that tunnel (REQ-BLD-TUNNEL-TRANSIT). This can be used to resolve stalled belts, splitters, and tunnels. The button acts on every selected tile, which is why a selection of belts and tunnel ends aggregates into one content rather than a count summary (REQ-UI-SELECTION-AGGREGATE).
-
REQ-UI-HQ-PANEL: When the HQ is selected, the panel shows the global building blocks stock — the same value as the header bar's stock display (REQ-UI-BLOCKS-ICON), rendered as an item chip (REQ-UI-SINGLE-SELECTION) carrying the
building_blockicon — and the HQ's HP as a bar labelledcurrent / maximum(REQ-HQ-STATS, REQ-UI-HP-BARS). The HQ has no input or output buffers of its own: building blocks delivered by belt go straight into the global stock (REQ-HQ-BELT-INPUT), and showing that stock on the HQ is what tells the player to route blocks there. The HQ has no configuration group and no status indicator (REQ-UI-SELECTION-STATUS), and it is never a construction site. -
REQ-UI-ENTITY-CLICK-SELECT: The player can click any ship (player or enemy) or any defence station (player or enemy) in the game world to select it. A plain click on a ship or defence station makes it the sole selection, clearing any previous selection. Ships and defence stations can be multi-selected — by Ctrl+clicking individual actors to add or remove them, or by box-drag (REQ-UI-MULTI-SELECT) — and can be selected together with debris and with one another in a single field selection (REQ-UI-SELECTION-CATEGORIES), freely mixing player and enemy actors. Actors cannot be selected together with buildings: selecting a ship or defence station clears any building selection, and selecting a building clears the actors (buildings win). Clicking a piece of debris adds to or establishes a field selection (REQ-UI-DEBRIS-CLICK-SELECT). Clicking empty world space (no building, ship, defence station, or piece of debris) clears the selection.
-
REQ-UI-SHIP-STATS-PANEL: When exactly one ship is selected (REQ-UI-ENTITY-CLICK-SELECT) and no debris is selected, the selection panel shows a ship stats panel. (If debris is also selected, the panel shows the compact count summary instead, per REQ-UI-FIELD-MULTI-SELECTION.) The panel structure mirrors REQ-MOD-UI-STATS-PANEL but reflects the ship's actual live state: stats are computed from its installed modules per REQ-MOD-STAT-CALC. Its header (REQ-UI-SELECTION-CARD) carries the schematic's color swatch and display name, with the ship's current behavior in the right slot (REQ-UI-SHIP-BEHAVIOR). The panel always shows all hull stats: HP (current / maximum) as a bar with the two values beside its caption, then max linear speed, sensor range, main acceleration, maneuvering acceleration, angular acceleration, and max rotation speed as label/value rows. In addition, capability module summaries are shown below the hull stats, each as its own outlined row, conditioned on which module types are installed and using the same aggregation rules as REQ-MOD-UI-STATS-PANEL: weapons (combined DPS, maximum range), salvage (combined collection rate, maximum range), and repair (combined repair rate, maximum range), each appearing only if at least one instance of that module type is installed. While debug draw mode is active (REQ-UI-DEBUG-DRAW), the panel additionally shows the ship's derived threat cost (REQ-MOD-THREAT).
-
REQ-UI-SHIP-BEHAVIOR: The ship stats panel (REQ-UI-SHIP-STATS-PANEL) additionally displays the selected ship's current behavior in its header's right slot (REQ-UI-SELECTION-CARD) — a single label naming the top-priority behavior currently governing the ship's navigation, as resolved by the fixed-priority behavior arbitration. Only the winning behavior is named; lower-priority behaviors that are suppressed are not shown, and neither are the salvage/repair cycles that run regardless of the active behavior (REQ-SHP-SALVAGE, REQ-SHP-REPAIR). The label updates live as the ship's behavior changes, and it is always shown (independent of debug draw mode, unlike the threat-cost line of REQ-UI-SHIP-STATS-PANEL). This applies to both player and enemy ships (REQ-UI-ENTITY-CLICK-SELECT); enemy ships only ever show Engaging or Advancing. The behavior labels (all wrapped in
tr()) are:- Retreating — the ship is retreating (REQ-SHP-RETREAT).
- Engaging — the ship is engaging a combat target (player: REQ-SHP-COMBAT; enemy: REQ-SHP-ENEMY-AI).
- Salvaging — the ship is executing salvage navigation: seeking debris, collecting, or delivering to a Salvage Bay (REQ-SHP-SALVAGE).
- Repairing — the ship is navigating to a repair target (REQ-SHP-REPAIR).
- Rallying — the ship is moving to or orbiting the rally point (REQ-SHP-RALLY).
- Standby — the ship is holding with its fleet (REQ-SHP-STANDBY).
- Advancing — the ship is executing the baseline forward advance with no higher-priority behavior active (player: REQ-SHP-COMBAT advance toward the enemy; enemy: REQ-SHP-ENEMY-AI advance toward the asteroid).
-
REQ-UI-STATION-STATS-PANEL: When exactly one defence station is selected (REQ-UI-ENTITY-CLICK-SELECT) and no debris is selected, the selection panel shows a station stats panel displaying the station's stats computed at its current level: HP (current / maximum) as a bar with the two values beside its caption, then damage, range, and fire rate as label/value rows, matching the ship stats panel's rendering (REQ-UI-SHIP-STATS-PANEL). Its header carries no right slot: a station has no behavior label and no status light. (If debris is also selected, the panel shows the compact count summary instead, per REQ-UI-FIELD-MULTI-SELECTION.)
-
REQ-UI-FIELD-MULTI-SELECTION: A full single-object stats panel (REQ-UI-SHIP-STATS-PANEL, REQ-UI-STATION-STATS-PANEL, REQ-UI-DEBRIS-PANEL) is shown when the field selection holds exactly one object — one ship, one defence station, or one piece of debris — and, for debris only, when it holds several pieces of debris and nothing else, which aggregate into that same content (REQ-UI-SELECTION-AGGREGATE). Every other field selection of more than one object — multiple actors, or any mix of actors and debris — shows a count summary instead. Its header reads
Mixed selectionwith the total number of selected objects in the right slot (REQ-UI-SELECTION-CARD). Below it is one row per type — the type's symbol, its name, and the number selected asx<count>, the samex-count notation as the recipe tooltip and the building multi-selection (REQ-UI-MULTI-SELECTION). Ships are grouped by schematic display name and defence stations as a group, distinguishing player from enemy; all selected pieces of debris are grouped into a singleDebrisrow whose count is the number of selected pieces. No per-object detail is shown. If debris is part of the selection, its row is followed by an indented sub-row giving the summed remaining scrap across all selected debris (REQ-UI-DEBRIS-PANEL). Building selections use REQ-UI-SINGLE-SELECTION / REQ-UI-MULTI-SELECTION instead. -
REQ-UI-DEBRIS-CLICK-SELECT: The player can click any piece of debris (REQ-RES-DEBRIS-DROP) in the game world to select it. Debris are field objects (REQ-UI-SELECTION-CATEGORIES) and can be selected together with ships and defence stations, but not with buildings. A plain click on a piece of debris makes it the sole selection, clearing any previous selection; selecting a building clears any debris (buildings win), and selecting a piece of debris clears any building selection. Hit-testing prefers a building over a coincident actor or piece of debris, and an actor (ship or defence station) over a coincident piece of debris: a piece of debris is selected only when no building or actor is under the cursor. A selected piece of debris that despawns or is fully collected (REQ-RES-DEBRIS-DROP) is removed from the selection; if no selected object remains, the panel becomes empty (REQ-UI-EMPTY-SELECTION).
-
REQ-UI-DEBRIS-MULTI-SELECT: Multiple pieces of debris can be selected by box-drag or by Ctrl+clicking individual pieces to add or remove them, mirroring building multi-select (REQ-UI-MULTI-SELECT). Debris shares the field-object category with ships and defence stations (REQ-UI-SELECTION-CATEGORIES), so a field selection may hold debris and actors together. Ctrl+clicking a piece of debris while a field selection is active adds or removes that piece within the same selection; Ctrl+clicking a piece of debris while a building selection is active first clears the buildings and begins a field selection (buildings win). Conversely, selecting a building while a field selection is active clears it. Box-drag disambiguation follows REQ-UI-MULTI-SELECT (a box covering any building selects buildings; a box covering no building selects the ships, defence stations, and debris within it).
-
REQ-UI-DEBRIS-PANEL: When debris is selected and no actors are (REQ-UI-FIELD-MULTI-SELECTION), the selection panel shows a debris stats panel structured like the ship and station stats panels (REQ-UI-SHIP-STATS-PANEL, REQ-UI-STATION-STATS-PANEL): a header reading "Debris", followed by a single stat row, "Scrap remaining", in the same label/value style as a ship hull stat row. With one piece selected the row shows that piece's remaining scrap amount (REQ-RES-DEBRIS-DROP) and the header's right slot is empty. With several pieces selected the same content is shown aggregated (REQ-UI-SELECTION-AGGREGATE): the number of selected pieces appears in the header's right slot as
x<count>and the row shows the summed remaining scrap across them. When debris is selected together with actors, the debris are instead summarized within the count summary (REQ-UI-FIELD-MULTI-SELECTION): aDebrisrow giving the number of selected pieces, followed by an indented sub-row with their summed remaining scrap. The displayed scrap value updates as selected debris are partially collected or despawn (REQ-UI-DEBRIS-CLICK-SELECT).
Build Button Bar
- REQ-UI-BUILD-BAR: All placeable building types are shown as a single horizontal row of buttons with no grouping and no wrapping, inside a widget that floats over the game world view (REQ-UI-WORLD-SIZE), horizontally centered and anchored at the bottom edge with a small margin. Tunnel Entry and Tunnel Exit share a single Tunnel button (REQ-BLD-TUNNEL-MODE) rather than one button each. The bar is sized to its buttons and re-centers whenever the set of shown buttons changes (REQ-LOCK-BUILDING) or the view is resized.
- Overlay behavior. The bar occludes the strip of the game world it covers; the world view itself keeps its full extent and the view's scrolling, ghost rendering, and tile geometry are unaffected (the world is not inset for the bar). The bar is drawn above the pause and deconstruct vignettes (REQ-UI-PAUSE-BORDER, REQ-UI-DECONSTRUCT-BORDER), which keep their full 100-pixel bottom band underneath it, and below the modal dim (REQ-UI-MODAL-DIM), which covers the entire game window including the bar.
- No overlap with the selection panel. The bar and the selection panel (REQ-UI-SELECTION-PANEL) never overlap, and keeping them apart is entirely the panel's job: the bar's position depends only on its own button set and the view size, and it never moves, re-centers, or resizes because the panel appears, disappears, or changes size. The panel instead confines itself to the view height less the bar's strip (REQ-UI-SELECTION-PANEL).
- Input. Mouse events over the bar are consumed by the bar and never reach the game world: hovering it shows no builder-mode ghost at the tile beneath, and clicking it neither places a building nor changes the selection. Right-clicking the bar does not exit builder mode (REQ-BLD-BUILDER-MODE) or cancel a belt drag (REQ-BLD-BELT-DRAG).
- REQ-UI-BUILD-COST: Each button is icon-only with a cost, its face composed of three elements: the button's hotkey badge in the top-left corner, the building's icon (REQ-UI-BUILD-ICON) centered below it, and the building block cost centered under the icon, shown with the
building_blockitem icon (REQ-UI-BLOCKS-ICON, REQ-UI-ITEM-ICON) to the right of the number in place of the trailingBlocksword, e.g.2then a small block icon. The building name is not shown on the button; it is shown in the button's hover tooltip instead (REQ-UI-BUILD-TOOLTIP). When no icon file exists forbuilding_block, the cost is shown as the bare number. The Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON) has no cost and shows its name as a text caption in the cost's place.- Hotkey badge. The badge names the build hotkey that activates the button (REQ-UI-HOTKEYS), so the player can learn the shortcuts from the bar itself. It is rendered dimmer than the cost so it reads as secondary, but at the same size and in bold, because a smaller badge is not legible. A plain-digit hotkey is shown as the bare digit (
1,2,3); a Shift+digit hotkey is shown with an upwards arrow prefixed and no separator (↑1…↑6); the Deconstruct button showsQ. A button whose building type has no build hotkey shows no badge and keeps the same face size, so the row stays even.
- Hotkey badge. The badge names the build hotkey that activates the button (REQ-UI-HOTKEYS), so the player can learn the shortcuts from the bar itself. It is rendered dimmer than the cost so it reads as secondary, but at the same size and in bold, because a smaller badge is not legible. A plain-digit hotkey is shown as the bare digit (
- REQ-UI-BUILD-ICON: Each build button shows an icon. Icons are SVG files loaded at runtime from
data/icons/buildings/(a sibling of the config directory, read the same way asvisuals.toml), one file per button named after the building's id (e.g.belt.svg,reprocessing_plant.svg). The shared Tunnel button (REQ-UI-BUILD-BAR) usestunnel_entry.svg; the Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON) usesdeconstruct.svg. Each icon is a rounded colored "chip" bearing a white line glyph, the chip color following the building's fill color invisuals.toml. A missing icon file leaves the button showing its building name as a text caption in place of the icon, so the button stays identifiable in the icon-only bar (REQ-UI-BUILD-COST); it is not an error. - REQ-UI-BUILD-TOOLTIP: Each building-type button shows a hover tooltip consisting of the building name followed by the descriptive text defined for that building type in
buildings.toml(the optional per-building tooltip field). Because the button caption is icon-only (REQ-UI-BUILD-COST), the name is always part of the tooltip; if a building type defines no tooltip text, the tooltip shows the name alone. This tooltip is distinct from the recipe/schematic selection tooltip (REQ-UI-SELECT-TOOLTIP). The Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON) is not a building type and so has no config-defined tooltip; it instead shows its own refund tooltip defined in REQ-UI-DECONSTRUCT-BUTTON. - REQ-UI-BUILD-DISABLED: Buttons for buildings the player cannot currently afford are shown as disabled. A disabled button's icon (REQ-UI-BUILD-ICON) is rendered in a greyed variant, with its colored chip background recolored grey while the white glyph is retained.
- REQ-UI-DECONSTRUCT-BUTTON: A dedicated Deconstruct button is shown in the build button bar (REQ-UI-BUILD-BAR), as the last entry of the row and visually separated from the building-type buttons by a gap (not a divider line), because it toggles a mode rather than selecting a building type. Its face follows REQ-UI-BUILD-COST with two differences: its hotkey badge reads
Q, and because it has no building block cost it shows its Deconstruct name as a text caption where the building-type buttons show their cost — so it is the one labelled button in the bar. It is therefore wider than the building-type buttons, which share a uniform width. Clicking it toggles deconstruct mode on and off, equivalent to the Q deconstruct toggle (REQ-UI-HOTKEYS). The button is shown in a visually active/pressed state while deconstruct mode is active. The button shows a hover tooltip stating the deconstruction refund (REQ-BLD-DECONSTRUCT): that deconstructing a fully-built building returnsworld.toml [world].refund_percentagepercent of its building block cost once deconstruction completes, and that a construction site removed before it finishes building is refunded in full. Whenrefund_percentageis 100% both cases yield the same refund, and the tooltip is simplified to state the single refund percentage without distinguishing the two cases. Unlike the building-type button tooltips (REQ-UI-BUILD-TOOLTIP), this tooltip is not config-defined text but is composed from the refund percentage.
Controls Panel
The controls panel tells the player which controls are available right now. It is context-sensitive: the game is always in exactly one control context, derived from the active build mode and the current selection, and the panel shows that context's rows and no others. Its position, size, and overlay behavior are defined in REQ-UI-CONTROLS-PANEL; its structure in REQ-UI-CONTROLS-CARD; and which rows each context shows in REQ-UI-CONTROLS-CONTENT. The panel never defines a binding: every row restates one already defined in REQ-UI-HOTKEYS or in the mouse gestures cited beside it.
-
REQ-UI-CONTROLS-PANEL: The controls panel is a widget that floats over the game world view (REQ-UI-WORLD-SIZE), anchored to the view's left edge with a small margin and bottom-aligned within the available band — the same band the selection panel is placed against (REQ-UI-SELECTION-PANEL): the height of the game world view, less the panel's top and bottom margins, less the height of the horizontal strip occupied by the build button bar at the bottom of the view (the bar's height plus its bottom margin, REQ-UI-BUILD-BAR), the bar's strip being subtracted over the full view width regardless of how wide the bar currently is. Bottom-aligning within that band puts the panel just above the bar's strip, so the two never overlap whatever the bar's current width, and the bar never moves on the panel's account (REQ-UI-BUILD-BAR). The panel is sized to its content in both width and height, growing and shrinking upward from the band's bottom edge as the context changes. Should its content ever be taller than the band, the panel's height is capped at the band height and the content scrolls vertically within it. The panel and the selection panel are on opposite edges of the view and never overlap.
- Visibility. The panel is shown whenever the game is being played. Unlike the selection panel it has no empty state (REQ-UI-EMPTY-SELECTION): every context has rows, so there is never nothing to show.
- Collapsing. Clicking anywhere on the panel's header (REQ-UI-CONTROLS-CARD) toggles the panel between expanded and collapsed. Collapsed, it shows its header alone, keeping the context name visible and the header clickable so the panel can be expanded again; expanded, it shows the header followed by every row of the current context. The panel starts expanded. Changing context does not change the collapsed state: a panel collapsed in one context stays collapsed in the next, and its header updates in place. The collapsed state is presentation-only — it is not a player command, never enters the replay stream, and has no effect on the simulation. It persists for as long as the application runs, including across a restart from the escape menu (REQ-UI-GAME-MENU), and is not saved to disk.
- Overlay behavior. As for the build button bar and the selection panel (REQ-UI-BUILD-BAR, REQ-UI-SELECTION-PANEL): the panel occludes the strip of the game world it covers; the world view itself keeps its full extent and the view's scrolling, ghost rendering, and tile geometry are unaffected. It is drawn above the pause and deconstruct vignettes (REQ-UI-PAUSE-BORDER, REQ-UI-DECONSTRUCT-BORDER), which keep their full left-hand band underneath it, and below the modal dim (REQ-UI-MODAL-DIM), which covers the entire game window including the panel.
- Input. Mouse events over the panel are consumed by the panel and never reach the game world: hovering it shows no builder-mode ghost at the tile beneath, and clicking it neither places a building nor changes the selection. Right-clicking the panel does not exit builder mode (REQ-BLD-BUILDER-MODE) or cancel a belt drag (REQ-BLD-BELT-DRAG). The only control the panel itself offers is the header click that collapses and expands it.
-
REQ-UI-CONTROLS-CARD: Card structure. The panel is a card with two parts, top to bottom:
- Header — always shown, and the panel's only interactive element (REQ-UI-CONTROLS-PANEL). It holds a colored context dot on the left, the context's name beside it in upper case, and, for contexts that define one, a detail suffix separated by a middle dot (
BUILD MODE · Assembler). The name and detail per context are given in REQ-UI-CONTROLS-CONTENT. - Rows — one per available control, shown only while the panel is expanded. Each row is one or more key badges on the left — the key or mouse button drawn as a small bordered chip — and a label beside them naming what it does. An action reachable two ways carries both badges in the same row (
RMBQ— Exit placement) rather than occupying two rows. A row whose action leaves the current mode is drawn with the destructive badge styling, distinguishing it from the rows that act within the mode. No row is ever drawn greyed or otherwise disabled: a control the player cannot currently use is not shown at all (REQ-UI-CONTROLS-ACCURACY).
The rows that are live in every context (REQ-UI-CONTROLS-CONTENT) are shown last, under a divider and the caption
ALWAYS AVAILABLE. This holds in every context including the General one, which has context rows of its own above the divider like any other, so the card is read the same way wherever the player is. - Header — always shown, and the panel's only interactive element (REQ-UI-CONTROLS-PANEL). It holds a colored context dot on the left, the context's name beside it in upper case, and, for contexts that define one, a detail suffix separated by a middle dot (
-
REQ-UI-CONTROLS-CONTENT: Content catalog. The control context follows from the active build mode and the selection alone. Build modes are mutually exclusive (REQ-BLD-BUILDER-MODE), so exactly one context applies at any moment:
Context When Header name Header detail General no build mode active, nothing selected GENERAL— Selection no build mode active, at least one object selected SELECTION<n> buildingsor<n> objectsBuild builder mode active (REQ-BLD-BUILDER-MODE) BUILD MODEthe building type's name Blueprint blueprint placement mode active (REQ-UI-BLUEPRINT-MODE) BLUEPRINT MODEthe blueprint's name, or Temporaryfor a temporary blueprint (REQ-UI-BLUEPRINT-TEMP)Deconstruct deconstruct mode active (REQ-UI-DECONSTRUCT-BUTTON) DECONSTRUCT MODE— The Selection context's detail counts the selection and names its category (REQ-UI-SELECTION-CATEGORIES):
<n> buildingsfor a building selection,<n> objectsfor a field selection, in the singular at a count of one. The Build and Blueprint contexts show the same rows and differ only in their header.Always-available rows, shown in every context:
Badges Label Shown ADMove always WSGame speed always SpaceToggle pause always VPaste last only while a temporary blueprint exists (REQ-UI-BLUEPRINT-TEMP) CtrlVBlueprints always EscMenu always Context rows, shown above the always-available block:
Context Badges Label General LMBSelect LMBdragSelect area QDeconstruct mode Selection LMBSelect / clear selection LMBdragSelect area CtrlLMBAdd / remove from selection CtrlLMBdragAdd area to selection QDeconstruct mode CCopy to temporary blueprint CtrlCCreate blueprint Build, Blueprint LMBPlace LMBdragPlace belt line R/ShiftRRotate RMBQExit placement Deconstruct LMBToggle deconstruct LMBdragDeconstruct area RMBQExit deconstruct mode Four rows are conditional on more than the context, because the binding behind them is:
C/CtrlCare shown only while the selection holds at least one player-placeable building, the condition under which those keys do anything (REQ-UI-HOTKEYS). A selection of ships, defence stations, or debris shows neither.LMBdrag — Place belt line is shown only in builder mode for the Belt type, the only type placed by dragging (REQ-BLD-BELT-DRAG). Every other builder type and blueprint placement omit the row.RMBQ— Exit placement splits into two rows while a belt drag is in progress, because the two bindings then part company (REQ-BLD-BELT-DRAG):RMBreads Cancel belt line and cancels the drag while leaving builder mode active, andQreads Exit placement and leaves the mode outright.LMB— Place reads Apply settings instead whenever the ghost under the cursor resolves to a configuration transfer (REQ-UI-BLUEPRINT-TRANSFER) — a single-building blueprint hovering a same-type building of a configurable type, which is the case in which clicking hands over settings rather than placing anything. A blueprint holding more than one building keeps thePlacelabel, since its click both places and transfers (REQ-UI-BLUEPRINT-PLACE).
-
REQ-UI-CONTROLS-ACCURACY: The panel never advertises a binding that would do nothing. Every row shown must, if triggered in the situation the panel is showing it in, have the effect its label names; a binding that is inert in the current context is omitted rather than shown greyed (REQ-UI-CONTROLS-CARD). The relation holds in one direction only: the panel may omit a binding that is available, and deliberately does so in three cases:
- Build hotkeys (REQ-UI-HOTKEYS) are live in every context, but are advertised on the build buttons' badges (REQ-UI-BUILD-COST) instead of taking eleven rows in every context of this panel.
CandCtrlCare omitted from the Build, Blueprint, and Deconstruct contexts even though a selection surviving into a build mode keeps them working. They belong to the Selection context, and repeating them in every mode would defeat the panel's purpose of showing what the player's current situation affords.CtrlLMBandCtrlLMBdrag work with nothing selected — they select the object under the cursor much as a plain click would — but are shown only in the Selection context. "Add / remove from selection" names an operation on a selection, and there is none to operate on until something is selected; the plainLMBrow already covers what the gesture does before then.F3andF4(REQ-UI-DEBUG-DRAW) are development controls rather than player controls and appear in no context.
This asymmetry between what is available and what is shown is why the two are separate questions in the implementation, and why the tests assert that a resolvable input is available rather than that it is displayed.
Blueprints
Blueprints occupy no permanent screen space. They are saved with Ctrl+C from the current selection (REQ-UI-BLUEPRINT-CREATE) and picked for placement from the blueprint selection dialog, opened with Ctrl+V (REQ-UI-BLUEPRINT-DIALOG). The unmodified C and V keys are the throwaway counterparts of the same two gestures: they capture and re-place a single unnamed temporary blueprint that is never saved and never listed (REQ-UI-BLUEPRINT-TEMP). Blueprints have no widget on the game screen at all. (The ship layout blueprint panel of the layout configuration dialog, REQ-MOD-UI-BLUEPRINT-PANEL, is a separate feature and is unaffected.)
-
REQ-UI-BLUEPRINT-CREATE: Pressing Ctrl+C (REQ-UI-HOTKEYS) opens the modal blueprint save dialog, which pauses the simulation and dims the game window (REQ-UI-MODAL-DIM). It has effect only when at least one player-placeable building (i.e. a building with a button in the build button bar) is currently selected; non-player-placeable buildings (HQ, defence stations) in the selection do not count toward this condition, and pressing Ctrl+C with an empty selection or a selection of only non-player-placeable buildings does nothing (no dialog opens). A selected player-placeable building may be either an operational building or a construction site (a building placed but not yet fully built, REQ-BLD-SITE-CONFIG); both count toward this condition and are captured identically (REQ-UI-BLUEPRINT-STORAGE). The dialog prompts the player to enter a name and has Confirm and Cancel buttons. Clicking Cancel — or pressing Escape, or closing the dialog — closes it with no effect and does not open the blueprint selection dialog. Clicking Confirm with a non-empty name creates a blueprint from the current selection, silently excluding any non-player-placeable buildings, appends it to the blueprint list, closes the save dialog, and immediately opens the blueprint selection dialog (REQ-UI-BLUEPRINT-DIALOG) showing the new blueprint among the others.
-
REQ-UI-BLUEPRINT-DIALOG: The blueprint selection dialog is the only place saved blueprints are shown. It is opened by pressing Ctrl+V (REQ-UI-HOTKEYS) and by confirming a save (REQ-UI-BLUEPRINT-CREATE). It is modal, pauses the simulation, and dims the game window (REQ-UI-MODAL-DIM). The dialog has a fixed size and consists of:
- A title bar reading
Blueprints, followed by a small dimmed hotkey badge readingCtrl+V— the same "learn the shortcut from the widget" device as the build button badges (REQ-UI-BUILD-COST) — and, at the far right, a close ("×") button. - Below it, a scrollable two-column grid of blueprint cards (REQ-UI-BLUEPRINT-CARD), one per saved blueprint, filling the grid left to right and top to bottom in creation order. The column count is fixed at two; the grid scrolls vertically when the cards do not fit, and does not scroll horizontally.
- When no blueprints are saved, the dialog still opens and shows an empty-state message in place of the grid, telling the player that blueprints are created with Ctrl+C from a selection of buildings.
Clicking the close button, pressing Escape, or closing the dialog through the window manager closes it with no other effect: the current selection, build mode, and blueprint list are unchanged, and the simulation speed is restored to what it was before the dialog was opened. While the dialog is open, Escape closes it rather than opening the escape menu (REQ-UI-GAME-MENU).
- A title bar reading
+------------------------------------------------------+
| Blueprints [Ctrl+V] [x] |
+------------------------------------------------------+
| +---------------------+ +---------------------+ |^| |
| | Smelter array | | Gear cell | | | |
| | 4 Smelters, 1 Miner | | 2 Assemblers, ... | | | |
| | 528 [blk] (x) | | 460 [blk] (x) | | | |
| +---------------------+ +---------------------+ | | |
| +---------------------+ | | |
| | Defence line | |_v_|
+------------------------------------------------------+
-
REQ-UI-BLUEPRINT-TEMP: Pressing the C key (REQ-UI-HOTKEYS) creates a temporary blueprint from the current selection and immediately enters blueprint placement mode for it, without opening the naming dialog. It has effect only when at least one player-placeable building is currently selected — the same condition as REQ-UI-BLUEPRINT-CREATE; pressing C with an empty selection, or a selection containing only non-player-placeable buildings (HQ, defence stations), does nothing at all, and in particular leaves any existing temporary blueprint in place. Entering this mode replaces any currently active build, blueprint placement, or deconstruct mode. The temporary blueprint is captured exactly as a saved blueprint (REQ-UI-BLUEPRINT-STORAGE), silently excluding any non-player-placeable buildings from the selection, but it is never named, never shown in the blueprint selection dialog (REQ-UI-BLUEPRINT-DIALOG), and never persisted to
blueprints.toml(REQ-UI-BLUEPRINT-SAVE). Placement behaves identically to a saved blueprint's placement mode (REQ-UI-BLUEPRINT-MODE, REQ-UI-BLUEPRINT-PLACE): a ghost is rendered per building, R / Shift+R rotate the entire constellation, placement follows the same per-building validity and total-cost rules, and after a successful placement the mode stays active so the blueprint can be placed again. Right-clicking in the game world exits placement mode; unlike the mode, the temporary blueprint itself survives, so it can be entered again with V.Pressing the V key re-enters blueprint placement mode for the temporary blueprint, capturing nothing new: it is independent of the current selection, can be pressed any number of times, and yields exactly the mode described above. Like C it replaces any currently active build, blueprint placement, or deconstruct mode, and it does not test whether the player can currently afford the blueprint — cost is enforced at placement (REQ-UI-BLUEPRINT-PLACE), consistently with C. Pressing V when no temporary blueprint exists does nothing: no mode is entered and any currently active mode is left untouched.
There is at most one temporary blueprint at a time; pressing C replaces the previous one. It is held only in memory for the current run: it is discarded when the application closes and when the simulation is restarted from the escape menu (REQ-UI-GAME-MENU), after which V does nothing until C captures a new one.
-
REQ-UI-BLUEPRINT-STORAGE: A blueprint stores its name and, for each building in the selection, the building type, its rotation, its tile offset (integer dx, dy) from the center of the bounding box of all selected buildings' footprints, and — where applicable — the selected recipe ID (miners and assemblers) or schematic ID (shipyards), and for splitters the two output filters (each a list of item types; an empty list means accept-all), at the time of capture. A source building may be either an operational building or a construction site (REQ-BLD-SITE-CONFIG); a construction site is captured identically, storing whatever configuration it currently holds and never any buffer or construction-progress state. If no recipe or schematic was selected at capture time, none is stored; for a splitter with no filters set, no filter lists are stored. This structure maps directly to a TOML representation (e.g. one
[[building]]array entry per constituent building, with the splitter filters asfilter_a/filter_barrays of item-type ids). -
REQ-UI-BLUEPRINT-CARD: Each blueprint in the blueprint selection dialog (REQ-UI-BLUEPRINT-DIALOG) is shown as a card. All cards share a uniform size. A card contains, from top to bottom:
- The blueprint name.
- A contents line summarizing what the blueprint holds: one
<building name> x <count>entry per building type it contains, comma-separated, in descending count order with ties broken by the order the types appear in the build button bar (REQ-UI-BUILD-BAR). This is the samex-count notation as the building multi-selection summary (REQ-UI-MULTI-SELECTION). If the entries do not fit on the line, the line is elided at its end rather than wrapped or shrunk, so every card keeps the same height. - The total building block cost of the blueprint (sum of the individual costs of all constituent buildings), shown with the
building_blockitem icon to the right of the number in place of a trailingBlocksword, exactly as elsewhere in the UI (REQ-UI-BLOCKS-ICON, REQ-UI-BUILD-COST). When no icon file exists forbuilding_block, the cost is shown as the bare number. - A delete icon ("×") in the card's bottom-right corner (REQ-UI-BLUEPRINT-DELETE).
A card shows no preview of the blueprint's layout.
Clicking anywhere on an enabled card other than its delete icon closes the dialog and enters blueprint placement mode for that blueprint (REQ-UI-BLUEPRINT-MODE). A card is disabled when the player cannot currently afford its total cost; a disabled card is rendered dimmed and clicking it does nothing (consistent with REQ-UI-BUILD-DISABLED), and neither closes the dialog nor enters placement mode. The delete icon is always enabled regardless of whether the player can afford the blueprint.
-
REQ-UI-BLUEPRINT-MODE: In blueprint placement mode a ghost is rendered for every building in the blueprint (excluding any of a currently locked building type, REQ-LOCK-BUILDING, which is omitted entirely per REQ-LOCK-UI-BLUEPRINT) at the position determined by its stored tile offset from the bounding-box center, which is anchored to the tile under the cursor. Each ghost is rendered individually as valid or invalid, applying REQ-BLD-PLACE-VALID conditions (a) and (b) per building (the other ghosts in the same blueprint do not count as existing buildings for the overlap check). A valid ghost uses its building type's semi-transparent per-building coloring (REQ-BLD-GHOST); an invalid ghost uses the distinct "invalid" color, as in single-building builder mode; a ghost over a configuration-transfer target uses the distinct "transfer" color instead of either and, for a single-building blueprint, is drawn snapped onto the target rather than at the blueprint's own position (REQ-UI-BLUEPRINT-TRANSFER); a ghost over a compatible overlap counts as valid and keeps the ordinary per-building coloring (REQ-UI-BLUEPRINT-OVERLAP). Pressing R / Shift+R rotates the entire constellation 90° counter-clockwise / clockwise: each building's tile offset is rotated around the bounding-box center and each building's own rotation is updated, consistent with REQ-BLD-ROTATE. Blueprint placement mode is exited by right-clicking in the game world. Opening the blueprint selection dialog while placement mode is active (REQ-UI-BLUEPRINT-DIALOG) leaves the mode active — closing the dialog without picking a card returns to it unchanged — while clicking a card exits the current mode and enters blueprint placement mode for the newly picked blueprint.
-
REQ-UI-BLUEPRINT-PLACE: This describes placing the blueprint's buildings as new construction sites. Ghosts sitting on a building that is already there are handled elsewhere and place nothing: a configuration-transfer target receives the blueprint's stored settings instead (REQ-UI-BLUEPRINT-TRANSFER), and a compatible overlap is left alone (REQ-UI-BLUEPRINT-OVERLAP). Both still take part in the single all-or-nothing click described here. Buildings of a currently locked building type (REQ-LOCK-BUILDING) are first excluded from the blueprint for this placement, per REQ-LOCK-UI-BLUEPRINT — they are not ghosted, not validity-checked, not placed, and their cost is excluded from the total. Left-clicking in blueprint placement mode then places the (remaining) blueprint if (a) every building in the constellation satisfies REQ-BLD-PLACE-VALID conditions (a) and (b) at its resolved tile, and (b) the player has enough building blocks to afford the total cost. Buildings that are compatible overlaps (REQ-UI-BLUEPRINT-OVERLAP) or configuration-transfer targets (REQ-UI-BLUEPRINT-TRANSFER) are excluded from the total cost and are not placed, but do not block the placement; a transfer target additionally receives the blueprint's stored settings. If both conditions are met, a construction site is added to the build queue for each remaining building in the blueprint and the full total cost is deducted from the global building blocks stock in one transaction. If a recipe ID is stored for a building, it is applied to the construction site immediately. If a schematic ID is stored, it is applied only if that schematic is currently unlocked; if it is not unlocked, the shipyard's schematic is left unset. If splitter output filters are stored, they are applied to the construction site immediately and carry over when it finishes building (REQ-BLD-SITE-CONFIG). Locked recipe IDs and splitter filter entries for locked item types are handled on placement per REQ-LOCK-UI-BLUEPRINT. After a successful placement the game remains in blueprint placement mode, allowing the player to place the same blueprint again immediately.
-
REQ-UI-BLUEPRINT-OVERLAP: Compatible overlap. In blueprint placement mode, a ghost whose footprint exactly coincides with the footprint of an existing placed building or construction site that is of the same building type and has the same rotation is a compatible overlap: the building the blueprint wants is already there. Such a ghost is valid despite the occupied tiles (REQ-BLD-PLACE-VALID condition (b)), so it does not block the placement of the rest of the constellation — dropping a blueprint over a partially-built copy of itself fills in what is missing. It is drawn in the ordinary per-building ghost color (REQ-BLD-GHOST), like any other valid ghost.
On placement the overlapped building is left completely untouched: no construction site is placed on it, no building blocks are charged for it (it is excluded from the total cost of REQ-UI-BLUEPRINT-PLACE), its rotation is not changed, and a construction site's progress is preserved. This applies per building in the blueprint, independently, and to blueprints of any size. Unlike REQ-BLD-ROTATE-IN-PLACE, which it replaces in this mode, it applies to Tunnel Entries and Tunnel Exits too — nothing is re-oriented, so the reason for their exception does not arise.
A coinciding same-type building whose rotation differs is not a compatible overlap: the blueprint cannot rotate it (REQ-BLD-ROTATE-IN-PLACE no longer applies here), so the position is an ordinary occupied-tile overlap and therefore invalid.
Order of the two rules. A ghost is tested for a configuration transfer (REQ-UI-BLUEPRINT-TRANSFER) first, and this requirement governs only what that test does not claim. Because a coinciding building of a configurable type always transfers, this requirement covers exactly the types that have nothing to configure:
- Configurable type (Miner, Assembler, Shipyard, Splitter) — a transfer, never a compatible overlap. It is drawn in the transfer color and hands the blueprint's settings over.
- Type with no settings (Smelter, Reprocessing Plant, Salvage Bay, belt, tunnel end) — a compatible overlap if the rotation matches, invalid otherwise. There is nothing to hand over, so the building is simply left as it is and the ghost keeps its ordinary color. This holds for a single-building blueprint too: the cursor hit-test of REQ-UI-BLUEPRINT-TRANSFER applies only to configurable types, so hovering a belt with a belt blueprint still just overlaps it.
A single blueprint can hold both kinds at once, and each ghost is judged on its own: dropping a constellation over a partial copy of itself may reconfigure some of the buildings already there (cyan) while leaving others untouched (ordinary color) and placing the rest as new construction sites.
-
REQ-UI-BLUEPRINT-TRANSFER: Configuration transfer. A blueprint hands its stored settings to buildings that are already standing where it wants them, instead of only to ones it places. A single-building blueprint therefore doubles as a way to copy one building's settings onto others of the same type: select a configured building, press C to capture it as a temporary blueprint and enter placement mode (REQ-UI-BLUEPRINT-TEMP), then click same-type buildings to stamp its settings onto them. V re-enters that mode later. A multi-building blueprint does the same for each of its buildings as it is placed.
A blueprint ghost is a configuration-transfer target when all of the following hold:
- The building is of a configurable building type — one with player-facing settings: Miner and Assembler (recipe), Shipyard (schematic and module layout), Splitter (output filters). Whether anything was actually configured at capture time is irrelevant; an unconfigured source transfers its unconfigured state (see below). Building types with no settings at all (Smelter, Reprocessing Plant, Salvage Bay, belts, tunnel entries and exits, the HQ) never transfer; a coinciding building of those types is a compatible overlap instead (REQ-UI-BLUEPRINT-OVERLAP).
- There is a target, found in one of two ways depending on the blueprint's size, because the two are different gestures:
- Single-building blueprint — the target is the building or construction site under the cursor, if it is of the same type. There is no coincidence test at all: the target's rotation, its footprint, and its alignment with the ghost are all irrelevant. The ghost is drawn snapped onto the target, at the target's own anchor and rotation, so it shows what the click will act on rather than where a building would go. This is the copying gesture, and a footprint test made it unusable for buildings that are not square: a Shipyard rotated 90° covers different tiles altogether, so no amount of lining up would ever match a differently-facing one.
- Multi-building blueprint — the ghost's footprint must exactly coincide with the footprint of an existing building or site of the same type (the coincidence test of REQ-BLD-ROTATE-IN-PLACE), and that target must have the same rotation as the ghost, which is what makes the position valid at all (REQ-UI-BLUEPRINT-OVERLAP). A constellation is placed as a layout, so its ghosts stay where the blueprint puts them: nothing snaps to the cursor and nothing is re-oriented.
At a transfer target:
- The ghost is drawn in a distinct transfer color read from
visuals.toml [overlays], overriding both the per-building coloring and the "invalid" color (REQ-BLD-GHOST, REQ-BLD-PLACE-VALID). The position counts as valid despite the occupied tiles (REQ-BLD-PLACE-VALID condition (b)). - Left-clicking transfers the configuration to the existing building or site, making the target's settings identical to the source's: the recipe ID (Miner, Assembler), the schematic ID together with the ship layout (Shipyard), or the two output filters (Splitter). No construction site is placed, no building blocks are consumed (it is excluded from the total cost of REQ-UI-BLUEPRINT-PLACE), and the target's rotation is not changed — a transfer never rotates.
- The transfer is a full mirror, including the absence of a setting: where the blueprint stores no configuration for a field (REQ-UI-BLUEPRINT-STORAGE stores nothing for an unselected recipe or schematic, and no filter lists for a splitter whose filters were empty at capture time), the target's corresponding setting is cleared rather than left as it was. So a splitter captured with no filters clears the target splitter's filters back to accept-all, and a miner captured with no recipe selected clears the target miner's recipe. This holds for every blueprint size, so a constellation captured from unconfigured buildings clears the settings of every matching building it is dropped on.
- The transfer has the same effects as making that selection through the selection panel, clearing included: buffer clearing per REQ-MAT-INPUT-BUFFER and REQ-MAT-OUTPUT-BUFFER, and, for a Shipyard, in-progress cycle cancellation per REQ-BLD-SHIPYARD. It inherits the no-op rule of REQ-MAT-INPUT-BUFFER with them: a transfer onto a building whose settings already match the source changes nothing at all — no buffers cleared, no production cycle cancelled, no construction progress lost — so repeatedly clicking already-matching buildings is harmless. Each field is judged on its own, so transferring an identical recipe with a differing layout affects only the layout. The layout configuration dialog does not auto-open (REQ-MOD-UI-AUTO-DIALOG).
- Unlock gating matches placement (REQ-UI-BLUEPRINT-PLACE): a stored schematic is applied only if it is currently unlocked, and locked recipe IDs and splitter filter entries for locked item types are handled per REQ-LOCK-UI-BLUEPRINT.
- Both operational buildings and construction sites are transfer targets (REQ-BLD-SITE-CONFIG); a configuration applied to a site carries over unchanged when it finishes building.
- After the transfer the game stays in blueprint placement mode, so further same-type buildings can be clicked in turn.
- A blueprint placement applies every transfer among its ghosts in the same click that places its new construction sites (REQ-UI-BLUEPRINT-PLACE); the placement is all-or-nothing, so if any ghost is invalid nothing is placed and nothing is transferred.
-
REQ-UI-BLUEPRINT-DELETE: Clicking the delete icon ("×") on a blueprint card (REQ-UI-BLUEPRINT-CARD) immediately removes that blueprint from the list, without a confirmation prompt. The blueprint selection dialog stays open and its card grid reflows to close the gap. If the deleted blueprint was active in blueprint placement mode, that mode is exited.
-
REQ-UI-BLUEPRINT-SAVE: On application shutdown, all current blueprints are serialized to a file named
blueprints.tomllocated in the same directory as the application executable. The TOML structure matches REQ-UI-BLUEPRINT-STORAGE. Write errors are silently ignored on shutdown (no button, no dialog). -
REQ-UI-BLUEPRINT-LOAD: At application startup, blueprints are loaded from
blueprints.tomlin the same directory as the application executable, populating the blueprint list (in the order they appear in the file). If the file does not exist, the blueprint list starts empty with no error. If the file exists but cannot be parsed (malformed TOML), a modal error dialog describes the failure and the blueprint list starts empty. There is no Load button and no runtime reload.
Balancing Tool
A separate executable target (balancing) that links against lib but contains no main-game UI code. It provides an automated arena-based ship balancing tool. Code written exclusively for this target does not go into lib.
Config
- REQ-BAL-CONFIG: The balancing tool reads arena definitions from a
balancing.tomlfile. The file is read at startup and again each time the player triggers a config reload (REQ-BAL-UI-RELOAD). If parsing fails or required fields are missing at startup, the tool aborts with a clear error message. If parsing fails during a reload, a modal error dialog is shown describing the failure and the current arena list is left unchanged. - REQ-BAL-CONFIG-GAME: Ship stats are read from
ships.tomland defence station stats are read fromstations.toml, using the same config loading as the main game. Ship stats are the plain values fromships.toml; defence station formula evaluation uses the station levels specified in the arena config.
Arena Definition
- REQ-BAL-ARENA: Each arena in
balancing.tomldefines:- A human-readable arena name.
- Region widths (in tiles): player buffer width, contest zone width, enemy buffer width. The arena world is pure space — no asteroid region.
- World height (in tiles).
- Exactly two teams, each with a human-readable team name.
- REQ-BAL-TEAM: Each team defines:
- A list of ship entries, each specifying: ship schematic (type), count, and an optional
modulesarray defining the module layout applied to every ship of that entry. Themodulesarray format is identical to that used inship_layouts.toml(see Layout Blueprint TOML Format). Ifmodulesis omitted, ships of that entry have no modules. Invalid module instances (unknown type, position outside the grid, position on a non-buildable cell, or overlapping another module in the same entry) are silently skipped during loading. - An optional list of defence station entries, each specifying: station type (
player_stationorenemy_stationfromstations.toml), level, and tile position (x, y).
- A list of ship entries, each specifying: ship schematic (type), count, and an optional
- REQ-BAL-HQ: Each team has an HQ placed automatically at the vertical center of the arena at the far end of that team's buffer zone. HQ stats are read from
stations.toml [hq]at level 1. Team 1's HQ is at the left edge; team 2's HQ is at the right edge. - REQ-BAL-SPAWN: Team 1's ships spawn in team 1's buffer zone (left side); team 2's ships spawn in team 2's buffer zone (right side). Spawn positions are uniformly random within the respective buffer zone.
Simulation
- REQ-BAL-SIM-ENV: Each arena simulates a pure-space environment using the same tick-based simulation as the main game. There is no asteroid, no buildings, no belts, no wave system, and no threat accumulation. Only ships, HQs, defence stations, and combat are active.
- REQ-BAL-SIM-AI: Ships use the same AI and stats as in the main game. Ships with no target in sensor range advance toward the enemy team's HQ. Ships that detect an enemy in sensor range engage it as in the normal game (REQ-SHP-COMBAT, REQ-SHP-ENEMY-AI).
- REQ-BAL-SIM-SPEED: Each arena that is not being inspected runs its simulation at maximum tick rate (as many ticks per second as the hardware allows), with no rendering. An inspected arena runs at a player-controllable game speed (same speed steps as the main game: 0×, 0.5×, 1×, 2×, 10×) with full rendering in the inspect window, defaulting to 1× on open.
- REQ-BAL-SIM-PARALLEL: All arenas are simulated in parallel, each on its own thread.
- REQ-BAL-SIM-END: An arena fight ends when either team's HQ is destroyed or all ships and defence stations of one team have been destroyed. If a team has no defence stations, destroying all its ships is sufficient. When the fight ends, the simulation for that arena stops.
UI
- REQ-BAL-UI-WINDOW: On startup the tool displays a window containing a "Reload Config" button, a "Start All" button, and a "Log" button at the top (in that order, left to right), followed by a scrollable vertical list of arena widgets, one per arena defined in
balancing.toml. Simulations do not start automatically on startup. All buttons and controls in the main window are disabled while an arena is being inspected (REQ-BAL-UI-INSPECT). - REQ-BAL-UI-RELOAD: The "Reload Config" button reloads all config files from disk (
balancing.toml,ships.toml,stations.toml), stops any running simulations, and replaces the arena widget list with freshly created widgets from the reloaded config. The button is disabled while any arena simulation is currently running. - REQ-BAL-UI-START-ALL: The "Start All" button is placed above the scrollable arena list, to the right of the "Reload Config" button. Clicking it starts (or restarts) the simulation for every arena that is not currently running. The button is disabled when all arenas are currently running.
- REQ-BAL-UI-LOG: The "Log" button is placed in the header to the right of the "Start All" button. It is enabled whenever the main window's controls are enabled — including while simulations are running (it captures a live snapshot) — and, like all main window controls, is disabled only while an arena is being inspected (REQ-BAL-UI-WINDOW). Clicking it writes the current state of every arena to a file named
balancing_log.mdin the tool's current working directory, replacing (overwriting) any previous content of that file. The log captures, for every arena inbalancing.tomlorder, exactly the information shown in that arena's widget (REQ-BAL-UI-WIDGET) at the moment the button is clicked. Each arena is written as its own section:- A heading with the arena name followed by the arena's current state —
not started,running, orended(corresponding to the widget border colors of REQ-BAL-UI-WIDGET-BORDER). For anendedarena, the heading also includes the battle duration (REQ-BAL-UI-WIDGET), for exampleended, 42.3 s. - A markdown table with one column per team (team 1 left, team 2 right). Each team's column header shows the team name — prefixed with
[WON]when that team won, matching REQ-BAL-UI-WIDGET — the team's accumulated threat level, and the team's remaining EHP percentage (REQ-BAL-UI-WIDGET). - Below the header, each table row holds one of that team's entries, in the same order and text format as the widget (REQ-BAL-UI-WIDGET): the HQ first, then ship types, then defence stations, formatted
surviving/total TypeNamefor ship entries andsurviving/total TypeName Llevelfor the HQ and defence station entries. When the two teams have different numbers of entries, the shorter column's remaining cells are left blank.
- A heading with the arena name followed by the arena's current state —
- REQ-BAL-UI-WIDGET: Each arena widget displays the arena name, an "Inspect" button (to the right of the arena name), and two columns (one per team). Each column shows the team name as a header, then directly below the header the team's accumulated threat level — the sum, across the team's configured ship entries, of each entry's
countmultiplied by the threat cost (REQ-MOD-THREAT) of one ship of that entry computed from its level-independent module layout. Only ships contribute; the HQ and defence stations are excluded. This value is static: it is computed once from the full configured roster and does not change as ships are destroyed during the fight. Directly below the threat level, the column shows the team's remaining EHP percentage — the sum of the current HP of all of the team's ships and defence stations, divided by the sum of their maximum HP, expressed as a percentage rounded to a whole number. Maximum HP is the final per-entity maximum (REQ-MOD-STAT-CALC), so module HP bonuses such as armor plates are included. (The game has no damage mitigation, so effective HP equals raw HP.) The HQ is excluded from both sums. A destroyed ship or station contributes 0 to the numerator and its maximum HP to the denominator, so the value measures how much of the team's fielded durability remains: it starts at 100% and decreases as units take damage or are destroyed. Unlike the static threat level, this value updates live as the simulation progresses. If the team has neither ships nor defence stations (the denominator is 0), the percentage is shown asn/a. Below the EHP percentage, the column shows a list of entries. The HQ is always the first entry in each column. Below the HQ, ship types are listed, followed by defence stations (if any). Each entry uses the formatsurviving/total TypeNamefor ship entries andsurviving/total TypeName Llevelfor the HQ and defence station entries — for example2/3 Fighter,1/1 HQ L1, or2/2 Enemy Station L3. The surviving count updates live as the simulation progresses. When the fight ends, the winning team's name header is prefixed with[WON]. When the fight has ended, the widget also displays the arena's battle duration — an arena-level value (not per team) giving the game time the fight lasted, computed as the number of simulated ticks at completion multiplied by the simulation's fixed tick duration (the same tick-based simulation as the main game, REQ-BAL-SIM-ENV). This is game time, not wall-clock time, so it is independent of how fast the arena was simulated (non-inspected arenas run at maximum tick rate, REQ-BAL-SIM-SPEED). It is shown in seconds with one decimal place, for exampleDuration: 42.3 s. The battle duration is shown only for completed (ended) runs; it is not shown while the arena is not started or running. - REQ-BAL-UI-WIDGET-START: Each arena widget contains a "Start" button that starts the simulation for that arena. The button is disabled while the arena's simulation is running. When a finished arena's Start button is clicked, a fresh simulation is created and started (the widget resets to initial unit counts, the border returns to blue, and the previous results are replaced).
- REQ-BAL-UI-WIDGET-BORDER: Each arena widget has a colored border indicating its state: grey when not yet started, blue while its simulation is running, and green when the fight has ended.
- REQ-BAL-UI-INSPECT: Clicking an arena widget's "Inspect" button opens a new inspect window for that arena. Any previously open inspect window is closed first (its arena's simulation is aborted and its widget border returns to grey). The inspected arena is restarted with a fresh simulation that runs at controllable game speed with full rendering (REQ-BAL-SIM-SPEED). The arena widget updates live during inspection (surviving counts, border color,
[WON]prefix) as it does for non-inspected arenas. Only one inspect window may be open at a time. - REQ-BAL-UI-INSPECT-WINDOW: The inspect window consists of three sections, top to bottom: a title bar area containing the arena name and game speed controls (same buttons as the main game: 0×, 0.5×, 1×, 2×, 10×, with Space to toggle pause — see REQ-UI-SPEED and REQ-UI-HOTKEYS), the arena view in the center, and an info panel at the bottom displaying the same team columns and entry format as the arena widget in the main window (REQ-BAL-UI-WIDGET), updated live, including the arena's battle duration once the fight has ended (REQ-BAL-UI-WIDGET).
- REQ-BAL-UI-INSPECT-VIEW: The arena view renders all tiles of the arena and displays ships, HQs, defence stations, and laser beams using the same visual elements and
visuals.tomlcolors as the main game. Team 1 uses player visual styles; team 2 uses enemy visual styles. The view has a fixed zoom level — no zoom or scroll is possible. The tile size is derived so that the full arena (all tiles) fits within the view. - REQ-BAL-UI-INSPECT-CLOSE: Closing the inspect window (via the window's close button) aborts the inspected arena's simulation. The arena widget's border returns to grey and its surviving counts are left as they were at the moment of closing. All main window buttons and controls are re-enabled.