SCRAPLINEStandingsResultsStatsTeamsLiveDocsAbout

SCRAPLINE — Bot API (developer contract)

2026-08-25. The contract between a bot and the engine. All game rules and numbers live in GAME-SPEC.md — this document never restates them, only references them. The API is frozen for the duration of each season.

Status: pre-build draft v0.1, matching GAME-SPEC v0.1.


1. The model

A bot is a Python module exposing one function:

def act(observation: dict) -> dict

The engine calls act once per tick for both bots simultaneously — neither bot ever sees the other’s orders for the current tick. Your return value is your orders for every rig you own plus an optional HQ build (GAME-SPEC §5–§6).

2. The observation (raw facts only)

The observation is a plain dict of observations, not interpretations: positions, HP, amounts, ownership. It contains no derived judgements (no “threatened”, no “reachable”, no “safe”) — every predicate you need, you compute, so no two callers can ever disagree about what a field means. Nothing in the API raises: every entity in the dict exists this tick, and entities from previous ticks are simply absent.

Key Content
tick the tick about to resolve (1..300); the snapshot is phase 1 of that tick
you / enemy team objects (below)
map width, height, asteroids: [[x,y],…] (static per match)
nodes list of {tile: [x,y], energy: int} — exhausted nodes are absent
piles list of {tile: [x,y], energy: int}
rng_seed your per-match deterministic seed (§5)
fuel_budget your fuel allowance per tick (constant all match)

Team object (you and enemy have identical shape):

Key Content
hq {id, tiles: [[x,y]×4], hp}
bank stored energy
rigs list of {id, chassis, modules: [..], tile: [x,y], hp, max_hp, cargo, hold, speed, last_damaged_tick} (last_damaged_tick is 0 if never damaged)
ghosts list of {id, chassis, modules, tile, hp, ticks_remaining} (0 = becomes a rig at the end of this tick, acts next tick)
damage_dealt, energy_collected the tiebreak counters (GAME-SPEC §8)
faults your bot_fault count so far (on you only)

Example (abbreviated):

{"tick": 42,
 "you": {"hq": {"id": 1, "tiles": [[2,2],[3,2],[2,3],[3,3]], "hp": 500},
          "bank": 85,
          "rigs": [{"id": 7, "chassis": "MEDIUM", "modules": ["DRILL","DRILL"],
                    "tile": [6,4], "hp": 100, "max_hp": 100, "cargo": 20,
                    "hold": 60, "speed": 1, "last_damaged_tick": 0}],
          "ghosts": [], "damage_dealt": 0, "energy_collected": 210, "faults": 0},
 "enemy": {"...": "same shape"},
 "nodes": [{"tile": [7,4], "energy": 480}],
 "piles": [{"tile": [11,12], "energy": 66}],
 "map": {"width": 24, "height": 24, "asteroids": [[10,3],[13,20]]},
 "rng_seed": 813402241, "fuel_budget": 30000000}

3. The orders

Return a dict:

{"orders": {"7":  {"action": "MOVE", "steps": ["E", "E"]},
            "9":  {"action": "FIRE", "target": 14},
            "11": {"action": "MINE", "tile": [7, 4]},
            "12": {"action": "TRANSFER", "target": 7, "amount": 30},
            "13": {"action": "RESTORE", "target": 9}},
 "build": {"chassis": "MEDIUM", "modules": ["BLASTER", "BLASTER"],
           "tile": [4, 2]},
 "intent": "massing lances at mid",
 "win_prob": 0.55}

4. Budget: fuel, not milliseconds

Your compute budget per tick is measured in fuel — deterministic WebAssembly instruction counts — not wall-clock time. Consequences you can rely on:

5. Determinism rules

Matches are bit-reproducible; your bot must not break that (it cannot, if it stays inside the sandbox — these rules describe the sandbox rather than trust):

6. The starter kit (the 10-minute path)

The kit ships: bot.py (a working default bot), the engine + local runner, the replay viewer, and the calibration harness. First match:

  1. Install the kit, run scrapline play bot.py bots/doorman — watch the replay in your browser.
  2. Edit act in bot.py. Run again. That loop is the whole workflow.

The default bot’s strategy (readable in ~40 lines): build two Miners (GAME-SPEC §2 names the standard loadouts); each Miner walks to the nearest node with a free adjacent tile and MINEs; deposit happens automatically when passing the HQ; once the bank clears a threshold, alternate building Troopers; when four Troopers live, walk them at the enemy HQ and FIRE at whatever enemy is nearest, HQ included. It plays a legal, complete game and loses to anyone paying attention — that is its job. The Doorman (the division-entry boss bot) is this bot plus escorts; beating it is the ladder’s front door.

6.1 botkit — helpers you may import

The kit ships botkit (kit/src/scrapline/botkit.py), importable from your bot in both native and sandboxed runs (import botkit as bk). It is frozen per season with the rest of the API. It computes predicates from the observation — nothing in it knows more than you do:

The reference bot and the red-team archetypes (bots/) are written on it and are the worked examples.

6.2 What you may import

The freeze gate rejects imports outside this list at submit time with a message naming the module (SECURITY §3) — the sandbox has no clock, filesystem, network or processes regardless, so this is about good errors, not security: abc array bisect collections copy dataclasses decimal enum fractions functools heapq itertools json math operator random re statistics string typing, plus botkit and your own modules inside the zip. random is seeded from rng_seed if you seed it; an unseeded random still differs between interpreters — don’t.

7. Versioning and fairness