SCRAPLINE — Game Specification (normative)
2026-08-25. This document is the rulebook: the engine is built from it and
disputes are settled by it. GAME-DESIGN.md explains why; this file defines
what. Where the two disagree, this file wins and the disagreement is a bug
to fix in one of them. All values here are canonical; no other document may
restate a number without linking here.
Status: pre-build draft v0.1. Open items are marked DECISION NEEDED.
1. Conventions
- Grid: 24×24 tiles, coordinates
(x, y),xandyin0..23.(0,0)is the north-west corner;xgrows east,ygrows south. - Symmetry: 180° rotational. Tile
(x, y)pairs with(23−x, 23−y). No tile is self-paired (even grid), so every map feature exists as a symmetric pair. - Distance: Chebyshev (
max(|dx|, |dy|)) unless stated otherwise. For any multi-tile entity (HQ, and only the HQ), distance is measured from the nearest tile of its footprint. - Adjacent: orthogonally adjacent (Chebyshev 1, diagonals excluded) unless stated otherwise. A tile is never adjacent to itself.
- Arithmetic: integers only. Fractions round down (
floor) unless stated otherwise. No floating point exists in game state. - Determinism: every rule in this file resolves identically given the same inputs. Where entities must be processed “in order”, the order is ascending entity id (§2.4). There is no randomness anywhere in match resolution.
2. Entities
2.1 Headquarters (HQ)
| Property | Value |
|---|---|
| Footprint | 2×2 tiles, impassable |
| Hit points | 500 (single pool; the 25-block display is visualization: block k shatters when HP ≤ 500 − 20·k) |
| Repair | impossible — HP only decreases, ever |
| Passive income | +1 energy/tick to the bank |
| Action | one BUILD per tick (§7.6) |
| Damage class | STRUCTURE (§3.3) |
2.2 Chassis
| Chassis | Cost | HP | Base speed | Slots | Collision mass |
|---|---|---|---|---|---|
| LIGHT | 20 | 60 | 2 | 1 | 1 |
| MEDIUM | 30 | 100 | 1 | 2 | 2 |
| HEAVY | 60 | 200 | 1 | 3 | 3 |
2.3 Modules (20 energy each; duplicates allowed up to slot count)
| Module | Effect |
|---|---|
| BLASTER | Weapon. Range 2, base damage 24/tick. Class multipliers §3.3 |
| LANCE | Weapon. Range 3, base damage 16/tick. Class multipliers §3.3 |
| DRILL | +5 mining rate/tick, +30 cargo hold |
| BOOSTER | +1 speed. Speed cap: 3 |
| REPAIR | Enables RESTORE: 10 HP/tick to one adjacent friendly rig, 1 energy per 2 HP (§7.5) |
A rig’s cost = chassis cost + 20 × modules. Its damage class equals its chassis (LIGHT / MEDIUM / HEAVY).
2.4 Entity ids
Ids are positive integers assigned in creation order, starting with HQ A = 1, HQ B = 2, then each team’s opening state in order (team A first, then team B), then per tick as entities are created. When both teams create entities in the same tick, team A’s are assigned first. Ids are never reused. Ids serve only as deterministic tie-breakers; the residual team-A/team-B asymmetry this creates is controlled for by the league’s both-sides pairing.
2.5 Terrain and objects
| Object | Passable | Notes |
|---|---|---|
| Ground | yes | |
| Asteroid | no | blocks movement only; never blocks weapons |
| Energy node | no | finite pool; mined from adjacent tiles |
| Scrap pile | yes | finite pool on a ground tile; rigs may stand on it |
| Build ghost | no | under-construction rig; attackable (§7.6) |
| HQ footprint | no |
Weapons are never blocked by anything (no line-of-sight system).
3. Canonical constants
3.1 Match
| Constant | Value |
|---|---|
| Tick cap | 300 |
| Fleet cap (living rigs + pending ghosts, per team) | 16 |
| Starting bank | 150 energy per team |
| HQ hit points | 500 |
| Trickle income | +1 energy/tick per team |
3.2 Economy
| Constant | Value |
|---|---|
| Mining rate | 5 energy/tick per DRILL |
| Cargo hold | 30 per DRILL (rigs without drills have hold 0) |
| Transfer cap | 30 energy per TRANSFER order |
| Salvage rate | scrap pile = floor(0.6 × rig cost) on death |
| Cargo drop | 100% of carried cargo joins the pile on death |
| Home nodes | 2 per side × 600 energy |
| Center nodes | 2 symmetric pairs: 2 × 600 and 2 × 450 (total 2100) |
| Build time | ceil(cost / 10) ticks |
| Repair rate / price | 10 HP per tick / 1 energy per 2 HP (cost ceil(heal/2)) |
| Repair combat lockout | target must have taken no damage for the last 5 completed ticks (§7.5) |
(Design note: GAME-DESIGN.md describes center nodes as 600/900/600. The
180° symmetry makes a single central node impossible on an even grid, so
the 900 is realized as a symmetric 450-pair at the same total. DECISION
NEEDED (D1): accept the 450-pair (default) or make the 900 a 2-tile
self-paired node straddling the center.)
3.3 Damage multipliers (attacker weapon × defender class)
| vs LIGHT | vs MEDIUM | vs HEAVY | vs STRUCTURE (HQ, ghost) | |
|---|---|---|---|---|
| BLASTER (24) | ×1.0 → 24 | ×0.5 → 12 | ×0.25 → 6 | ×0.25 → 6 |
| LANCE (16) | ×0.5 → 8 | ×0.75 → 12 | ×1.0 → 16 | ×1.0 → 16 |
Damage per weapon = floor(base × multiplier). A rig with several weapons fires all of them at the same single target in one FIRE order; their damage sums.
3.4 Collision
| Constant | Value |
|---|---|
| Collision damage to a surviving (heavier) rig | 40, once per collision event |
| Mass order | HEAVY > MEDIUM > LIGHT |
4. Match structure
A match is (bot A, bot B, map seed) and is a pure function of those three
inputs. Ticks are numbered 1..300. Each tick resolves in the phase order of
§6. The match ends immediately when a win condition (§8) is met.
5. Orders
Each tick, each bot returns at most one order per living rig it owns, plus at most one HQ BUILD. Orders are keyed by rig id (the schema makes duplicates impossible). Recognized actions:
MOVE(steps), FIRE(target), MINE(tile), TRANSFER(target, amount),
RESTORE(target), IDLE.
Validation happens when the order is read. An order is invalid if it:
names an entity that does not exist or is not the bot’s own living rig;
has a malformed shape; uses an action the rig lacks the module for (FIRE
without a weapon, MINE without a DRILL, RESTORE without REPAIR); exceeds
MOVE’s step count (> speed) or uses a non-cardinal step; targets something
type-invalid (e.g. TRANSFER to an enemy, RESTORE on a ghost or HQ, FIRE at
a friendly — there is no friendly fire). An invalid order becomes IDLE
and emits a loud invalid_order replay event. Range/adjacency conditions
that depend on positions are checked at resolution, not at validation
(a FIRE that ends up out of range is a whiff, not an invalid order).
A rig with no order IDLEs silently. A bot that crashes, exceeds its fuel
budget, or returns unparseable output has all its orders for that tick
replaced by IDLE (and no BUILD), with a loud bot_fault event. This never
forfeits the match.
6. Tick phase order
- Snapshot — the engine serializes the full state; both bots receive identical observations (modulo team labeling).
- Orders — both bots are called simultaneously (each under its own fuel budget) and their orders validated (§5).
- Movement — §7.1, including collisions.
- Combat — §7.2. Then the win check (§8); if the match is over, phases 5–7 do not run.
- Economy — in this order: MINE (§7.3), TRANSFER (§7.4), RESTORE (§7.5), auto-deposit (§7.3), trickle (+1 per team).
- Production — ghosts advance; completed ghosts become rigs (§7.6); new BUILD orders start ghosts.
- End of tick — state hash appended to the replay every 32 ticks and on the final tick.
7. Resolution rules
7.1 Movement and collisions
A MOVE order carries 1..speed cardinal steps (N/E/S/W). Diagonal movement does not exist; diagonal repositioning costs two steps.
Movement resolves in 3 sub-steps (the speed cap). In sub-step s,
every rig whose order has an s-th step and that has not been stopped
attempts to advance one tile. Within one sub-step, resolution is
simultaneous, in this order:
- Intents: every moving rig’s target tile is computed.
- Swaps: two rigs whose moves exchange tiles (A→B’s tile, B→A’s tile) collide (rule 4). This also covers the case where they would “pass through” each other.
- Contested tiles: all rigs (2 or more) targeting the same tile collide (rule 4).
- Collision rule (mass rule): among the colliding rigs, if there is a unique heaviest chassis, that rig survives, takes 40 damage, and completes its move; every other collider is destroyed. If two or more colliders tie for heaviest, all colliders are destroyed and the contested tile is entered by no one. Collisions are team-blind (allies collide exactly like enemies). A rig destroyed in a collision drops scrap + cargo on the tile it occupied at the start of the sub-step. A survivor that dies of the 40 collision damage (possible only if already wounded to ≤40) is destroyed on its origin tile and enters nothing.
- Blocked moves: a rig whose target tile is impassable (asteroid,
node, ghost, HQ) or occupied by a rig that is not moving this sub-step
bounces: it stays put, its remaining steps this tick are cancelled,
and a
bounceevent is logged. No damage. Bounces cascade: if A’s target is occupied by B and B bounced (or never moved), A bounces too — evaluated to fixpoint. A rig may enter a tile vacated in the same sub-step (chains are legal). - Sub-step deaths merge scrap piles per §7.7 immediately, before the next sub-step.
Standing still is safe from collisions: a stationary rig cannot be rammed (movers bounce off it). The counter to a stationary blocker is FIRE. (DECISION NEEDED (D2): this is as designed (“the mover stays put”), but it makes parked bodies strong at chokes. Alternative: apply the mass rule to movers hitting stationary rigs too. Default: bounce, as designed.)
7.2 Combat
All FIRE orders resolve simultaneously against post-movement positions:
- For each firing rig: if the target no longer exists, or the Chebyshev
distance from the firer’s tile to the target’s nearest footprint tile
exceeds the weapon’s range, the shot whiffs — a loud
whiffevent, no effect. Range is evaluated per weapon module (a BLASTER+LANCE rig at distance 3 whiffs the blaster and lands the lance). - All landing damage is computed from the pre-combat state and applied at once. Mutual kills are legal; a rig that dies this phase still deals its damage. Overkill is discarded.
- Rigs and ghosts at ≤0 HP are destroyed and drop scrap + cargo on their
tile (§7.7). HQ damage is subtracted from its pool and added to the
attacker’s
damage_dealttotal.
last_damaged_tick is recorded for every rig that takes ≥1 damage from
any source (weapon or collision), for §7.5.
7.3 Mining and deposit
MINE targets a tile that is the rig’s own tile or orthogonally adjacent
and contains an energy node or scrap pile. Miners resolve in ascending
entity id: each takes min(5 × drills, pool remaining, hold space) from
the pool into its cargo. A pool at 0 is removed from the map (nodes: the
tile becomes ground and passable; a loud node_exhausted event).
Auto-deposit (no order needed): during phase 5, after RESTORE, every
rig orthogonally adjacent to its own HQ footprint moves its entire cargo
into the team bank. Unlimited per tick. Deposited energy and trickle both
increment the team’s energy_collected counter (starting bank does not).
7.4 Transfer
TRANSFER moves min(requested, 30, carrier cargo, receiver hold space)
energy to an orthogonally adjacent friendly rig. Transfers resolve in
ascending id of the sending rig, sequentially (later transfers see
earlier results). Receivers with hold 0 make the order resolve to 0 (a
loud transfer_void event, not an invalid order).
7.5 Restore
RESTORE heals one orthogonally adjacent friendly rig (never a ghost,
never the HQ) by min(10, missing HP, 2 × available energy), where
available energy = the restorer’s cargo, then the team bank. Cost =
ceil(heal/2), drawn cargo-first. Valid only if the target’s
last_damaged_tick is at least 6 ticks ago (i.e. it took no damage in
the 5 last completed ticks). Multiple restorers may heal the same target
in the same tick (ascending restorer id, capped at max HP).
7.6 Build and ghosts
The HQ may issue one BUILD per tick: (chassis, modules, spawn tile).
Valid iff: the spawn tile is ground (scrap piles allowed), unoccupied,
and orthogonally adjacent to the HQ footprint; the module list fits the
chassis slots; the team’s living rigs + pending ghosts < 16; and the bank
covers the full cost, which is deducted immediately.
A ghost then occupies the spawn tile: impassable, attackable
(damage class STRUCTURE), HP 40, mass — none (movers bounce off it). It
completes at the end of ceil(cost/10) Production phases and becomes a
rig at full HP on its tile. A destroyed ghost drops scrap = floor(0.6 ×
full cost) on its tile (its cargo is nil). Ghosts are visible to both
bots and the audience from the tick of the BUILD.
7.7 Scrap piles
A death drops scrap = floor(0.6 × cost) plus 100% of carried cargo onto the relevant tile. If the tile already holds a pile, amounts merge into one pile. Piles on a tile under a rig are legal (rigs stand on them). Piles never decay. If a death would drop scrap onto a non-ground tile (impossible under current rules — deaths happen on the dying rig’s own tile, which is always ground), the amount is dropped on the dying rig’s tile regardless.
8. Winning
Checked immediately after Combat (§6 step 4):
- Enemy HQ at ≤0 HP and yours above 0 → you win
(
win_condition: hq_destroyed). - Both HQs at ≤0 HP in the same tick → tiebreak chain (rule 3) applied immediately.
- After tick 300’s Combat with both HQs alive — or under rule 2 — the
winner is decided by, in order: higher
damage_dealt(cumulative HP inflicted on the enemy HQ) → higherenergy_collected→ draw (win_condition: draw; league scores it 1–1).
Final scoring fields (in every replay): winner, win_condition
(hq_destroyed | damage_tiebreak | energy_tiebreak | draw), final tick,
and per team: HQ HP remaining, damage_dealt, energy_collected, rigs
living, rigs lost, energy mined / leaked-to-piles, bot_fault count.
9. Map generator
Input: a 64-bit seed. Output: a map, deterministically.
| Parameter | Range |
|---|---|
| Board | 24×24, 180° rotationally symmetric |
| HQ A footprint | tiles (2,2)–(3,3); HQ B at the rotational image (20,20)–(21,21) |
| Home nodes | 2 per side, 600 each, at Chebyshev 3–5 from the owner’s HQ footprint |
| Center nodes | 2 symmetric pairs (600×2, 450×2), every node tile within Chebyshev 4 of the board center point |
| Asteroids | 4–12 symmetric pairs (8–24 tiles) |
Validation (regenerate with the next derived sub-seed until all pass): (a) every passable tile reachable from every other (single connected component); (b) each HQ has ≥6 free orthogonally adjacent spawn tiles; (c) every node has ≥2 passable orthogonally adjacent mining tiles; (d) no node or asteroid orthogonally adjacent to an HQ footprint; (e) shortest passable path between HQ footprints ≥ 14 steps.
The league’s census map set is drawn from this generator, stratified over
its parameter space (see LEAGUE-RULES.md §5).
10. Glossary
| Term | Meaning |
|---|---|
| Rig | A mobile unit: chassis + modules |
| Ghost | A rig under construction, standing on its spawn tile |
| Hold | A rig’s cargo capacity, 30 × drills |
| Bank | A team’s stored energy at the HQ |
| Pile | Scrap and/or dropped cargo lying on a tile |
| Whiff | A FIRE whose target is out of range/gone at resolution |
| Bounce | A move into an occupied/impassable tile: mover stays put |
| Fault | A bot crash, fuel exhaustion, or unparseable reply for one tick |
| energy_collected | Cumulative deposits + trickle (excludes starting bank) |
| damage_dealt | Cumulative HP inflicted on the enemy HQ |
11. Clarifications
Where the rules above under-specified an order or a boundary, the engine adopts the reading below. These are normative: they are what the engine implements, and they are frozen for the season along with everything else in this document.
| Ref | Section | Reading the engine implements |
|---|---|---|
| C1 | §7.1 | Per sub-step: blocked moves bounce first (to a fixpoint — off-board, impassable, or a tile whose rig will not vacate it), then collision components (swap pairs and shared targets, unioned) resolve by the mass rule, then everyone left enters. Two rigs walking into one asteroid bounce; they do not collide |
| C2 | §7.1 | A collision survivor keeps its remaining steps this tick |
| C3 | §7.2 | damage_dealt counts HP actually removed from the enemy HQ |
| C4 | §5, §7.3–7.5 | Position/state failures at resolution are loud no-ops: mine_void, transfer_void, restore_void (with a reason), like whiff |
| C5 | §7.5 | RESTORE legal iff never damaged or tick − last_damaged_tick ≥ 6; damage earlier in the same tick blocks it |
| C6 | §7.6 | BUILD in tick T ⇒ ready_tick = T + ceil(cost/10); rig at the end of that Production; acts from ready_tick + 1. Spawn conditions checked at Production time, after completions; refused builds are not charged |
| C7 | §2.4 | Cross-team contention for a nearly-empty pool is the only place ascending-id order changes an outcome; the engine marks it with id_order_asymmetry |
| C10 | §5 | FIRE at a non-existent or friendly id is invalid_order; a target that dies during Movement is a whiff |
| C11 | §9 | Center-node coordinates lie in 8..=15 on both axes |
| C15 | §9 | With the fixed footprints, validation (b) is implied by (d) and (e) always holds (open-board path = 32 steps); both checks are kept |