BATTLE — complete guide for bots ================================ Battle is a turn-based strategy game played only through an HTTP+JSON API. A public viewer is available after a game ends. You are a program: you read your view of the world, decide a batch of orders, submit them, wait for the turn to resolve, and repeat. Two players fight on a mirrored graph of regions. THE OBJECTIVE: capture or destroy the opponent's original starting capital. Losing that capital at any point during a resolved turn eliminates its original owner, even if they retake or refound it before the turn ends. Other colonies and surviving workers do not prevent elimination. If both capitals are lost in the same turn, the game is a draw. Reaching the turn limit (1000 turns) without a winner is also a draw, with no score tiebreak. Score describes wealth only. There is one game, `battle`, with materials in every new match. Public rules and catalog values are available from `GET /v1/rules`. The rules visible to you in a specific game are returned by `GET /v1/games/{id}/rules`; generated material physics stays hidden there. Contents 1. Getting started (register, keys, rules) 2. Getting a game (matchmaking, challenges) 3. The turn loop and the API you call every turn 4. Full API reference 5. How the world works (graph, nodes, edges, fog) 6. Economy (workers, allocation, buildings, production, research, upkeep, housing) 7. Movement, colonization, combat, siege 8. Special mechanics (bombard, raids, scans, stealth, decoys, market, espionage, sabotage, tribute, roads, gates, mines, collapse, tunnels, jumps, strikes, interception, ruins, watchtowers, minor factions) 9. Victory, score, ratings 10. JSON shapes: view, orders, facts, rejections 11. The battle catalog 12. Turn resolution order 13. Materials science 1. GETTING STARTED ------------------ Register once. The key is returned once and never again. There is no password and no recovery: if you lose the key you lose the account. POST /v1/register {"username": "mybot", "name": "My Bot", "version": "0.1", "author": "me", "language": "python", "repo_url": "", "description": ""} -> 201 {"account": {...}, "key": "gk_...", "key_id": "..."} Username: 3-32 characters from A-Z a-z 0-9 _ -. All fields except username are optional. Send the key on authenticated requests (public rules and viewer routes need no key): Authorization: Bearer gk_... Keys: GET /v1/keys lists yours; POST /v1/keys {"scope":"play"|"read","label":"..."} makes another (read keys can only GET); DELETE /v1/keys/{id} revokes. PATCH /v1/me updates your metadata; the `version` string is recorded on every game you play. GET /v1/rules returns the public template for battle. There are no pools, variants, combat formulas or phase orders to select. Materials science is always part of the game. All games are duels between two registered accounts. There are no computer opponents. The sole scheduler is on_ready: the world advances only when both players have sent ready. Thinking faster does not advance the world. Not acting for the inactivity timeout is a forfeit (a loss), not a forced turn. Rated matchmaking uses 1000 turns and a 1800-second (30-minute) inactivity timeout. 2. GETTING A GAME ----------------- By matchmaking. The call blocks up to 30 s. 204 means "nothing yet, call again"; your waiting time carries over between consecutive calls. You are paired with another account waiting in the single battle queue whose rating is close to yours (the window widens the longer you wait). Games are created only through matchmaking or accepted challenges. POST /v1/match {} -> 200 {"game_id":"...","your_slot":0,"opponent":"otherbot","pool":"battle","ruleset_id":"battle"} -> 204 (retry) By private, unrated challenge: POST /v1/challenges {"username":"friend","inactivity_timeout_s":600} POST /v1/challenges/{id}/accept (by the challenged account) POST /v1/challenges/{id}/decline GET /v1/challenges Challenges use the same battle rules and 1000-turn limit. Their optional inactivity_timeout_s defaults to 1800 and is clamped to 10..604800 seconds. Matchmaking requires JSON {}, not an empty body. Both creation endpoints deny unknown fields: old pool/ruleset/variant/scheduler selection returns HTTP 422, even when naming battle. Matchmaking does not accept a timeout override. Legacy pending challenges cannot be accepted (409 conflict), but either party may decline them and create a new challenge. An account may have at most 4 unfinished games at a time. GET /v1/games?status=active lists them. Player slots are 0 and 1; the neutral faction (minor factions on the map) is slot 4294967295. 3. THE TURN LOOP ---------------- loop: view = GET /v1/games/{id}/view # your fog-of-war view if view.phase.phase == "finished": stop turn = view.phase.turn if not view.ready[""]: orders = decide(view, rules) POST /v1/games/{id}/orders {"turn": turn, "orders": {"orders": [...]}, "ready": true} GET /v1/games/{id}/wait?turn=&timeout=30 # blocks until turn resolved (204 = poll again) GET /v1/games/{id}/events?since= # what happened, from your point of view Rules of submission: - Orders are validated immediately against the frozen world. The response lists, per order, accepted or a typed rejection. Accepted orders are guaranteed to be attempted. - Resubmitting before ready REPLACES your whole pending batch. - "ready": true in the orders body is the same as a separate POST /ready. - After ready, more orders for that turn return 409 already_ready. Wrong turn returns 409 stale_turn with the current turn. Ready is final. - Standing state persists: allocations, production queues and research continue until you change them. An empty batch is legal. - The world only changes when the turn resolves. Your opponent never sees your orders; you never see theirs. Both of you see the same age of information. - Turn numbers start at 0. view.turn is the current collecting turn. 4. FULL API REFERENCE --------------------- All bodies JSON. Application errors: {"error":"","message":"..."}; codes: unauthorized, forbidden, not_found, conflict, bad_request, username_taken, wrong_phase, stale_turn, already_ready, too_many_games, internal. Decimals are JSON numbers. Node, edge, slot and player ids are integers; unit/building/tech/resource ids are strings; games/accounts/keys are UUIDs. JSON-extraction errors, including unknown creation fields (422), need not use this envelope. Accounts POST /v1/register none {username, name?, version?, author?, language?, repo_url?, description?} -> 201 {account, key, key_id} GET /v1/me any -> Account PATCH /v1/me play {any metadata fields} -> Account GET /v1/keys any -> [{id, prefix, label, scope, created_at, last_used_at}] POST /v1/keys play {scope, label?} -> 201 {key, record} DELETE /v1/keys/{id} play -> 204 Rules GET /v1/rules none -> battle Ruleset template (no generated material physics) GET /rules.txt none -> this document Matchmaking POST /v1/match play {} -> 200 {game_id, your_slot, opponent, pool, ruleset_id} | 204 POST /v1/challenges play {username, inactivity_timeout_s?} -> 201 Challenge GET /v1/challenges any -> [Challenge] POST /v1/challenges/{id}/accept play -> Challenge (with game_id) POST /v1/challenges/{id}/decline play -> Challenge Games GET /v1/games?status=active|finished&limit=50 any -> [GameSummary] GET /v1/games/{id} any -> GameSummary GET /v1/games/{id}/rules[?format=toml] any -> Ruleset as you may see it (material physics stays hidden, even after research) GET /v1/games/{id}/view any -> PlayerView (participants only) POST /v1/games/{id}/orders play {turn, orders:{orders:[Order]}, ready?} -> {turn, accepted, rejections:[{index, reason}], ready} POST /v1/games/{id}/ready play {turn} -> GameSummary GET /v1/games/{id}/wait?turn=N&timeout=30 any -> GameSummary when turn N is resolved or the game ended; 204 on timeout (max 120 s) GET /v1/games/{id}/events?since=C any -> {items:[FeedItem], next} (pass next as the following since) POST /v1/games/{id}/concede play -> GameSummary GET /v1/games/{id}/replay any -> {game_id, events:[EventEnvelope]} (finished games only; full information) Leaderboards GET /v1/leaderboard?limit=50 any -> [{username, rating, deviation, games}] (Glicko-2 for battle) Public viewer GET / none -> viewer page GET /v1/public/games?status=active|finished&limit=50 none -> [GameSummary] GET /v1/public/games/{id} none -> {summary, view, score_history:[{turn, scores}]} GET /v1/public/leaderboard?limit=50 none -> leaderboard entries for battle Both leaderboard routes accept optional limit (default 50, clamped to 1..500). The old /v1/pools, /v1/rulesets, /v1/rulesets/{id} and pool-qualified leaderboard routes are removed, not aliases. The public game detail is full-information and available only for finished or aborted games. Live requests return 409 wrong_phase: no delay parameter or configuration override permits live spectating. Public listings may include live games, but only safe summaries, not world state, orders, resources, unit composition or score history. GameSummary: {id, pool, rated, ruleset_id, phase:"collecting"|"resolving"|"finished"|"aborted", turn, last_resolved_turn, players:[{slot, username, bot_version, ready}], your_slot?, outcome?:{winner?, end_reason, scores}, created_at} pool and ruleset_id remain response metadata: both are battle for new games. Historical records may have different values; these do not represent selectable modes. 5. HOW THE WORLD WORKS ---------------------- The map is a graph. Nodes are regions; edges connect them and take a whole number of turns to cross. Maps are generated per game and mirrored: your half is a mirror image of the opponent's. Typical size: 20-30 nodes, travel times 1-5 turns. Node: id, slots (building capacity, 2-5; start nodes 6), defense (multiplier around 1.0), deposits [{index, resource, remaining, quality, cap}], colony? {owner, level, hp, founding_progress}, buildings [...], stocks {slot: {counts:{kind:n}, damage_carry}}, allocations {job: workers}, features ["watchtower" | "ruins"]. Edge: id, a, b, domain "ground"|"air", travel_turns, hidden, private_owner?, collapsed_until?, mines. Ground edges form the terrain. Air edges connect nearby nodes for air units (and for ground units carried by transports). Hidden edges exist but are unknown until surveyed at an endpoint. Private edges (gates) are usable only by their owner. Units are STOCKS: counts per kind per node, e.g. {"infantry": 12, "worker": 6}. There are no individual units or coordinates. Each player's stock at a node is separate. FOG OF WAR. You never see the true world, only your KNOWLEDGE. Each node you know has an observation level and a snapshot taken the last time you observed it (as_of_turn): unknown < presence < counts < composition < detected < full presence owner and colony level, slots counts + apparent total number of units present (decoys inflate this) composition + which kinds and how many (enemy decoys vanish), colony hp detected + enemy stealth kinds revealed by a detection source full + deposits, buildings, allocations (your own colonies), NOT detection Full is economic visibility, not permission to see enemy stealth. Enemy stealth stays hidden without a detector, a detection-granting vision source or a current scan, even at your own colony. Your own stocks, including stealth units, remain visible to you. Sources: your colonies give full at the node and presence at neighbours; your stock at a node gives composition there (detected if a detector unit is present) and presence at neighbours; scouts give composition at range 1; active sensor buildings give counts within 2 hops; a held watchtower gives counts within 2 hops; a scan gives detected at one node for one turn. Observers also grant detected vision within 1 hop. Edges you know carry a travel estimate [lo, hi] until surveyed or crossed. At counts+, edge departures and arrivals produce sighted reports; edgeless jumps produce movement_observed {node, count} instead. These counts hide undetected stealth, inflate decoys at counts-only visibility and exclude enemy decoys at composition or better. An entirely hidden stock produces no sighting. Combat and colony reports are filtered to your observations (section 10), not raw enemy stocks or damage carry. Nothing about the opponent's current-turn orders is ever visible. 6. ECONOMY ---------- Resources: ore (extracted, the currency) and alloy (refined from ore). Your pool is global: anything a connected colony produces is spendable anywhere. Start: one colony (level "colony", 200 hp, 16 housing, 6 slots, two deposits of 3000 ore with cap 16), 6 workers, 1 scout, 300 ore. Workers work through the node's standing ALLOCATION, set with set_allocation {node, allocations}. Jobs: "extract:" mine that deposit. Yield per turn = min(workers x 4 x quality, cap + extractor boosts, remaining). Workers beyond the cap add nothing. "staff:" operate a building that needs staff (barracks 1, factory 2, refinery 2, lab 2). Unstaffed buildings do nothing. "construct:" build a placed building: progress += workers per turn until the building's build_labor is reached. The sum of newly assigned workers must not exceed your existing workers staying at that node, excluding commitments to accepted moves, jumps and raids in the same batch. set_allocation replaces the node's whole allocation. Standing allocations persist, but each turn they are normalized before the economy runs: invalid jobs are removed and labor is clamped to the workers still available. Priority is Extract, then Staff, then Construct, with lower deposit/slot indices first within each job type. Lost or departing workers cannot keep staffing or constructing through an old allocation. Staying workers at an established colony not covered by the allocation mine automatically: each such worker is assigned, one at a time, to the deposit with the most unused capacity (ties: lowest deposit index). Workers for whom no deposit has unused capacity stay idle. Staffing and construction are never automatic. Workers committed to moves, jumps or raids do not auto-mine either, even though those actions resolve later in the turn. Connectivity: a colony's extraction reaches your pool only if a path of nodes you own or uncontested neutral nodes leads back to one of your hubs (a colony of level "colony" or higher). Enemy-held nodes and neutral nodes with enemy stocks block the path. Collapsed edges, another player's private edges and unlinked gates cannot carry supply. Your capital is a hub. Construction: construct {node, kind} places the building in a free slot and pays its cost immediately at resolution (validated against your pool). Then allocate construct workers. Slots: node slots + colony level bonus (colony +1, city +2). Production: set_production_queue {node, slot, queue:[kind,...]} on an active producer (barracks, factory). Each turn a staffed producer advances the queue head by 1; a unit finishes after its build_turns and appears at that node. The unit's cost is paid when its production starts; if you cannot afford it or lack housing, new production stalls. Kinds not unlocked by your techs cannot be queued. Housing: colony levels give 8/16/30, habitats +8. Every unit costs housing (1, armour and artillery 2), including units in transit. Each paid production queue head reserves its unit's housing until completion, even while unstaffed; canceling that head releases the reservation. Losing housing can put you over the cap: already-paid units may still finish, but no new queue head starts unless its full housing fits. Refining: a staffed refinery converts 12 ore -> 1 alloy per turn. Alloy is needed for factory units, labs, gates, silos and upgrades to city. Material labs can produce alloy from recipes under the generated dynamics (section 13). Research: research {tech} starts or continues one project; staffed labs add 10 points per turn; a tech completes when its cost is reached. Techs modify stats (e.g. +25% heavy hp), unlock kinds, or enable actions. Exclusive groups ("doctrine") allow one pick. Switching project mid-way is rejected while progress > 0. Upkeep: every unit costs ore per turn (worker 0.5, infantry 1, armour 2 ...), plus 0.5 ore per hop of distance between each colony and your nearest hub over usable supply paths. An unreachable colony is charged a hop count equal to the map's node count, not zero. If you cannot pay, the most expensive units, including those in transit, are disbanded until the deficit is covered. Colonization: move workers to a neutral node and issue colonize {node}. Founding takes 3 consecutive uncontested turns with a worker present, then consumes one worker and creates an outpost (8 housing, 100 hp). Two players founding the same node in the same turn cancel each other. Upgrade {node}: outpost -> colony costs 300 ore; colony -> city costs 600 ore + 100 alloy; each level adds housing, hp and slots. 7. MOVEMENT, COMBAT, SIEGE -------------------------- move {from, edge, counts, on_capture?}: the stock departs along a known edge. Travel time is the edge's travel_turns minus the slowest kind's speed bonus (minimum 1). The stock is in transit (visible to you in view.transits) and lands in the arrivals phase of the turn it is due. Air edges need air movers; ground units may ride along if the same move includes transports with enough capacity (transport capacity 6 housing). Gate edges are usable only by their owner; collapsed edges by nobody. Moves, jumps and raids carry their share of existing wounds instead of healing the stock. For each kind, departing carry in integer milli-units is carry x departing count / total count, rounded down; the remainder stays at the source. Moving the whole kind takes all its carry. New transits have a stable transit_id so arrivals, mines and interception refer to the exact convoy, not another stock on the same route. The field is optional for legacy transits. Combat happens at any node where two players' stocks are present: ONE round per turn. Each kind with attack > 0 deals attack x count, distributed over the enemy kinds in proportion to their targetable counts (Spread damage), multiplied by the class table and divided by the defender's defense factor. There is no formula selection: class table (attacker -> defender): light vs ranged 1.5, heavy vs light 1.5, ranged vs heavy 1.5; the reverse pairings 0.67; same class 1.0 defense factor = 1.2 x node.defense x fort multipliers, only for the player whose established colony the node is; 1.0 otherwise Damage accumulates per kind as carry; whole units die when carry reaches hp. Units without a combat component (workers, engineers) have 10 hp. Undetected stealth units take no damage and are not targeted. Battles last several turns; you get one decision between rounds: reinforce, or move out (departures resolve before arrivals, so a retreat always gets away). Siege: when a node with an enemy colony holds only YOUR stock, your siege power (sum of units' siege values, divided by fort multipliers) reduces the colony's hp each turn. At 0: capture (you own it as an outpost at half hp; its buildings stay, queues reset) or raze if your move carried on_capture "raze" (colony and buildings destroyed). Founding colonies are razed immediately. A siege by undetected stealth units works at half power; the owner's combat_observed reports do not identify the attacker. Bombard {from, edge}: ranged kinds (artillery: range 1, factor 0.5) fire across the edge without moving: 0.5 x attack per unit spread over enemy stocks at the target, and 0.5 x siege x 0.5 against the colony. Each player may bombard once per origin per turn. It reserves all ranged units there, so those units cannot also move or jump in the batch. Bombardments, strikes, combat and sieges each use their own phase-start state and apply results together. Destroying an enemy force, silo, detector or colony during one of these phases cannot cancel its already-computed contribution to that phase. 8. SPECIAL MECHANICS -------------------- Raids raid {from, edge, counts}: raider kinds cross, hit and return in one turn. Raiders lost = ceil(0.25 x enemy combat units at the target). Survivors each kill 0.5 worker and steal 12 ore from the colony owner's pool, then head home as a transit. Ground raiders cannot cross air edges without enough transport capacity in the raiding stock; private and collapsed edge restrictions also apply. raid_resolved is private to the raider. The victim receives units_lost {player, node, counts}, not the raider's source stock or route. Sensors and scans A sensor building sees counts within 2 hops and stores energy (+1 per turn, cap 5). scan {node, sensor_node, sensor_slot} spends 3 energy to observe one known node at detected level for this turn. Everyone with a colony or units there is warned (fact "scanned"). Stealth and detection Enemy stealth kinds (observer, infiltrator, saboteur, beacon) need an actual detection source to be seen or targeted. Owning a colony and having Full economic visibility there does not detect them. Detectors, detected-level vision and scans provide detection separately. Undetected stealth takes no damage, including from mines and interception, and keeps its existing damage carry. Scans expire after one turn. Decoys An enemy decoy counts as 6 units in counts-only observations and reports, and is excluded at composition or better. Presence alone does not reveal unit counts. Market buy/sell {resource:"alloy", amount}: trades clear blind at the stored posted price, initialized from rules.market.start_prices (4 ore per alloy). The spread is 0.1: buys pay price x amount in ore; sells receive price x amount x 0.9. Fractional buy payments round up to 0.001 ore, sale proceeds round down. Orders partially fill within the available budget or holdings, shared across that player's trades; purchases in the same clearing cannot be sold immediately. Trading never moves the price: market_cleared.new_price equals price. Legacy elasticity, liquidity, min_price and max_price fields remain readable but have no effect on new market clearing. Espionage espionage {target: slot} costs 150 ore. If the target runs an active counterintel building anywhere, both sides get espionage_detected and you learn nothing. Otherwise you receive intel_gained: their techs, pool and current research (kept in view.knowledge.intel). Sabotage sabotage {node, slot}: a saboteur of yours at an enemy colony, undetected by its owner, sets a building back 6 labor and resets its production. The saboteur is consumed whether or not it succeeded. Tribute tribute {node}: with your stock at a minor faction's colony and no enemy present, pay 250 ore to take the colony; its garrison disbands. Roads improve {edge, from}: an engineer at an endpoint and 120 ore reduce the edge's travel time by 1 (minimum 1) for everyone. Gates Build "gate" buildings (tech "gates") at two of your colonies and link_gates {a, b}: a private edge of travel 1 appears between them. Losing either colony unlinks it. Mines mine_edge {edge, from}: engineer + 80 ore. The next enemy stock arriving over that edge takes 60 damage spread over targetable kinds unless it includes a sweeper (engineers sweep); the mine is consumed either way. Undetected stealth takes no damage. Owners pass freely. Collapse collapse_edge {edge, from}: tech "demolition", engineer, 150 ore. The edge is unusable for 8 turns; stocks already in transit still arrive. Tunnels tunnel {from, to}: tech "tunneling", engineer, 250 ore. Creates a new hidden edge (travel 2) to a node within 2 hops that is not already adjacent. Only you know it until someone surveys an endpoint. Jumps jump {from, to, counts}: tech "jump_drive"; a beacon unit of yours must be at the target; 40 ore. The stock arrives next turn ignoring edges. Strikes A "silo" (tech "long_range_strike") stores energy (+1, cap 4); strike {from, slot, target} spends 4 energy to deal 60 damage to every enemy stock and 60 to the colony at a node within 2 hops. "shield" buildings absorb 50% of strike damage. Interception Tech "interception" + interceptor units at a node: enemy stocks arriving over edges take 0.5 x interceptor attack before landing. Undetected stealth is not targeted. Ruins survey {target:{node}} with your stock on a node with the "ruins" feature loots 250 ore + 40 alloy once. Watchtowers Whoever solely holds a "watchtower" node (colony or the only stock present) sees counts within 2 hops. Minor factions Neutral colonies (slot 4294967295) with garrisons (4 infantry) sit on the map, mirrored for both players. Conquer them by siege, or pay tribute. They never move or attack; their garrison defends if you attack. 9. VICTORY, SCORE, RATINGS -------------------------- The game is won by capturing or destroying the opponent's original starting capital, recorded in view.me.capital for your own player. Any loss of that capital during the resolved turn eliminates its original owner, even if it is recaptured or refounded later in that turn. Surviving workers and other colonies do not prevent this. The last active player wins with end_reason "elimination"; losing both capitals in one turn gives winner: null with the same end_reason. If both players are still active at the turn limit (1000), winner is null and end_reason is "turn_limit", regardless of wealth. There is no score tiebreak or low-score elimination. Score V = ore + alloy (x1.0) + 0.8 x sum(unit cost x count) + 1.0 x building costs + 200 x sum(colony level rank: outpost 1, colony 2, city 3). Score is per player and appears in facts score_updated and in outcomes as a descriptive wealth statistic only. The low_score_turns response field is always zero in current games and can be ignored. concede is a loss. Not acting for the inactivity timeout is a forfeit (loss); if both players are inactive the game is abandoned (unrated). Ratings: one Glicko-2 rating table for battle, updated shortly after each rated game finishes. Private challenges are unrated. New server games have no per-turn action-point cap; individual order constraints still apply. 10. JSON SHAPES --------------- PlayerView (GET /view): { "player": 0, "turn": 12, "phase": {"phase":"collecting","turn":12} | {"phase":"resolving","turn":12} | {"phase":"finished","outcome":{...}}, "me": {"slot":0, "status":"active", "pool":{"ore":162.5,"alloy":0}, "techs":["deep_mining"], "research":{"tech":"gates","progress":40} | null, "score":812.4, "capital":15, "low_score_turns":0}, "knowledge": { "player": 0, "nodes": {"15": {"node":15, "current_level":"full", "as_of_turn":11, "observation": {"level":"full", "owner":0, "colony_level":"colony", "colony_hp":200, "total_count":7, "stocks": {"0": {"counts":{"worker":6,"scout":1},"damage_carry":{}}}, "deposits":[{"index":0,"resource":"ore","remaining":2980,"quality":1,"cap":16}], "buildings":[{"slot":0,"kind":"barracks","state":"active","progress":8, "production_queue":["worker"],"production_progress":1, "production_paid":true,"energy":0}], "slots":6, "allocations":{"extract:0":5,"staff:0":1}}}, "22": {"node":22, "current_level":"presence", "as_of_turn":11, "observation": {"level":"presence","owner":null,"colony_level":null,"slots":3, ...}}}, "edges": {"7": {"edge":7, "a":15, "b":22, "domain":"ground", "state": {"estimated":{"lo":2,"hi":5}} | {"surveyed":{"travel_turns":3}}, "private_owner":null, "collapsed_until":null}}, "sightings": [{"turn":11,"node":22,"edge":9,"count":6}], "intel": {"1": {"target":1,"turn":30,"techs":[...],"pool":{...},"research":null}} }, "transits": [{"transit_id":51539607553,"player":0,"edge":7,"from":15,"to":22,"stock":{"counts":{"worker":2},"damage_carry":{}},"arrives_turn":14}], "pending": {"orders":[...]} | null, "ready": {"0": false, "1": true} } Map keys are strings (node ids, slots, resource and kind names). Building states: "under_construction" | "active". Colony levels: "founding" | "outpost" | "colony" | "city". transit_id is an optional unsigned 64-bit integer, stable within a game; legacy transits may omit it. edge is null for an edgeless jump. Orders (each element of orders.orders): {"type":"move","from":15,"edge":7,"counts":{"infantry":6},"on_capture":"capture"|"raze"} {"type":"set_allocation","node":15,"allocations":{"extract:0":5,"extract:1":3,"staff:0":1,"construct:2":2}} {"type":"construct","node":15,"kind":"barracks"} {"type":"set_production_queue","node":15,"slot":0,"queue":["worker","infantry"]} {"type":"research","tech":"deep_mining"} {"type":"upgrade","node":15} {"type":"colonize","node":22} {"type":"survey","target":{"edge":7}} {"type":"survey","target":{"node":22}} {"type":"bombard","from":15,"edge":7} {"type":"raid","from":15,"edge":7,"counts":{"raider":4}} {"type":"scan","node":30,"sensor_node":15,"sensor_slot":3} {"type":"improve","edge":7,"from":15} {"type":"link_gates","a":15,"b":18} {"type":"mine_edge","edge":7,"from":15} {"type":"collapse_edge","edge":7,"from":15} {"type":"tunnel","from":15,"to":19} {"type":"jump","from":15,"to":30,"counts":{"infantry":8}} {"type":"strike","from":15,"slot":4,"target":30} {"type":"buy","resource":"alloy","amount":20} {"type":"sell","resource":"alloy","amount":30} {"type":"espionage","target":1} {"type":"sabotage","node":30,"slot":1} {"type":"tribute","node":24} Orders response: {"turn":12,"accepted":3,"rejections":[{"index":1,"reason":{"code":"insufficient_stock","node":15,"kind":"infantry","have":4,"want":6}}],"ready":true} Rejection codes: unknown_node, unknown_edge, edge_not_incident, no_stock_at_node, empty_order, unknown_unit_kind, unknown_building_kind, unknown_tech, insufficient_stock, unit_cannot_move, domain_mismatch, already_surveyed, not_owner, colony_not_established, already_colonized, no_colonist_present, duplicate_order, no_free_slot, over_budget, locked, no_building_in_slot, building_not_active, building_cannot_produce, queue_too_long, invalid_job, workers_over_allocated, tech_already_known, tech_requirements_unmet, tech_requires_building, exclusive_group_taken, research_in_progress, too_many_orders, edge_unusable, transport_capacity, no_upgrade_available, no_ranged_units, no_raiders, no_sensor, insufficient_energy, no_engineer, no_gate, road_at_minimum, tech_required, already_collapsed, edge_exists, out_of_range, no_beacon, no_striker, feature_unavailable, not_tradable, invalid_amount, invalid_target, no_saboteur, not_neutral_colony, private_edge. Within one batch, orders compete for the same stock and pool: a second move of the same units is rejected; construction costs are summed against your pool. Bombard reserves every ranged unit at its origin and cannot repeat there or overlap those units with a move/jump. Rejected orders reserve neither units nor actions. Allocation validation excludes only accepted movement commitments. Events (GET /events?since=C): {"items":[...], "next": C'} {"type":"turn_resolved","index":41,"turn":12,"facts":[...]} {"type":"player_ready","index":40,"turn":12,"player":1} {"type":"player_conceded",...} {"type":"game_finished","index":..,"outcome":{...}} {"type":"game_aborted",...} Facts are {"type": "...", ...} and are only those visible to you, not a copy of the world log. New damage results expose filtered reports: {"type":"combat_observed","node":22,"cause":"battle", "losses":[{"player":null,"total_count":3,"counts":{}}],"colony":null} {"type":"combat_observed","node":15,"cause":"siege","losses":[], "colony":{"owner":0,"damage":12,"hp_after":188,"outcome":"damaged"}} combat_observed has node, cause, losses:[{player?, total_count, counts}] and colony? {owner, damage?, hp_after?, outcome}. Causes: battle, bombard, strike, interception, mine, siege, colony. Colony outcomes: damaged, captured, razed. Colony owner is the owner BEFORE the change, not the attacker or new owner; there is no attacker field. Nullable fields serialize as null. At counts-only visibility, enemy losses are anonymous (player: null, counts: {}), with an apparent total inflated by decoys. At composition or better, enemy losses include only visible kinds, excluding decoys and undetected stealth. Your own losses are identified. Reports never expose raw damage carry. Colony damage and hp may be hidden even when its capture or destruction is visible. Movement reports: sighted {player, node, edge, count}, where player is the observer, covers filtered edge departures and arrivals. movement_observed {node, count} covers edgeless movement without revealing a route, owner or stock. observations_updated {player, nodes} contains that player's node snapshots (the NodeKnowledge entries shown in PlayerView) and is visible only to that player; it preserves the turn's filtered observations in replay. Canonical combat_round, bombarded, colony_damaged, colony_captured, colony_razed, mine_triggered and transit_intercepted mutation facts are hidden from newly generated player feeds. Full finished replays retain them. stock_departed, stock_arrived, stock_jumped and raid_resolved carry private stock details for their owner, never enemy observers; movement reports and the raid victim's units_lost provide the safe notifications. Other fact types include resources_spent, building_placed, construction_progressed, building_completed, production_started, production_progressed, units_produced, extracted, deposit_depleted, refined, research_progressed, research_completed, node_discovered, edge_discovered, edge_surveyed, observation_changed, colony_founding_started, colony_progressed, colony_founded, upkeep_charged, units_disbanded, transit_units_disbanded, transit_identified, allocation_set, production_queue_set, score_updated {player, score, low_score_turns}, player_eliminated, colony_upgraded, siege_intent_set, energy_updated, edge_improved, gate_linked, gate_unlinked, edge_mined, edge_collapsed, edge_created, market_cleared {resource, currency, price, buys, sells, new_price}, intel_gained, espionage_detected, sabotaged, ruins_looted, feature_removed, units_lost {player, node, counts}, scanned {by, node}. Each retains its own visibility restriction. Outcome: {"winner": 0 | null, "end_reason": "elimination"|"turn_limit"|"concede"|"forfeit"|"abandoned", "scores": {"0": 3104.0, "1": 943.12}} 11. THE BATTLE CATALOG ---------------------- Units (cost / upkeep per turn / housing / build turns / hp atk class / notes) worker 50 ore / 0.5 / 1 / 2 / non-combatant extracts (rate 4), constructs, colonizes; ground engineer 90 ore / 1 / 1 / 3 / non-combatant roads, mines, collapse, tunnels; sweeps mines; ground scout 40 ore / 0.5 / 1 / 2 / 10 hp 0 atk light air, speed +1, sees composition at range 1 infantry 60 ore / 1 / 1 / 2 / 40 hp 6 atk light siege 2; ground raider 70 ore / 1 / 1 / 2 / 25 hp 4 atk light raids; speed +1; ground armor 100 ore 40 alloy / 2 / 2 / 3 / 120 hp 14 atk heavy siege 6; ground artillery 90 ore 60 alloy / 2 / 2 / 3 / 50 hp 16 atk ranged siege 15; bombards at range 1 (factor 0.5) interceptor 80 ore 30 alloy / 1.5 / 1 / 3 / 35 hp 9 atk light air, speed +1; intercepts arrivals; tech interception transport 120 ore 20 alloy / 1.5 / 2 / 3 / 60 hp 0 atk heavy air; carries 6 housing of ground units observer 70 ore 20 alloy / 1 / 1 / 3 / 15 hp 0 atk light air, speed +1, stealth, detector; sees detected at range 1 infiltrator 110 ore 30 alloy / 1.5 / 1 / 3 / 30 hp 10 atk light stealth; siege 4; speed +1; tech cloaking saboteur 100 ore 20 alloy / 1 / 1 / 3 / 15 hp 0 atk light stealth; sabotage; speed +1; tech cloaking decoy 30 ore / 0.5 / 1 / 1 / 5 hp 0 atk light appears as 6 at counts, excluded at composition+ beacon 80 ore 20 alloy / 1 / 1 / 3 / 20 hp 0 atk light stealth; jump target; speed +1; tech jump_drive Producers: barracks -> worker, scout, infantry, raider, decoy, engineer; factory -> armor, artillery, transport, observer, infiltrator, saboteur, beacon, interceptor. Buildings (cost / build labor / staff / effect) extractor 120 ore / 6 / 0 +12 to each deposit's cap per turn at the node refinery 180 ore / 8 / 2 12 ore -> 1 alloy per turn material_lab 30 ore / 2 / 0 one experiment or material-production action per turn (section 13) barracks 150 ore / 8 / 1 produces basic units factory 250 ore 40 alloy / 12 / 2 produces advanced units habitat 100 ore / 6 / 0 +8 housing fort 200 ore / 10 / 0 x1.5 defense and siege resistance at the node sensor 120 ore / 6 / 0 counts within 2 hops; energy +1/turn cap 5 (scan costs 3) lab 200 ore 20 alloy / 10 / 2 10 research points per turn gate 300 ore 60 alloy / 12 / 0 link two gates for a private travel-1 edge (tech gates) silo 350 ore 120 alloy / 14 / 0 strike range 2, 60 damage, 4 energy (tech long_range_strike) shield 250 ore 60 alloy / 10 / 0 absorbs 50% of strike damage counterintel 150 ore / 6 / 0 blocks espionage against you Techs (all need a staffed lab; points at 10 per lab per turn) deep_mining 100 worker extraction x1.25 hardened_armor 120 heavy class hp x1.25 siege_doctrine 120 ranged siege x1.5 (doctrine group: pick one) mobility_doctrine 120 all units speed +1 (doctrine group: pick one) cloaking 150 unlocks infiltrator, saboteur gates 160 unlocks gate buildings jump_drive 200 requires gates; unlocks beacon and jump orders tunneling 140 enables tunnel orders demolition 120 enables collapse_edge orders interception 130 unlocks interceptor long_range_strike 220 requires siege_doctrine; unlocks silo The catalog has 14 units, 13 buildings and 11 technologies. Every new game uses it; only the map and hidden material model are generated per match. Read GET /v1/games/{id}/rules for the rules visible to you in that game. 12. TURN RESOLUTION ORDER ------------------------- When both players are ready the turn resolves in this fixed order. Within a phase every effect is computed from the phase-start state and applied together; no player is processed before the other. 1 standing_orders allocations normalized to staying workers, queues, research starts, upgrades; immediately followed by material experiments and production 2 graph_orders roads, gate links, mines, collapses, tunnels 3 construction place new buildings (pay), progress construction, complete buildings 4 production pay and advance queues, deliver units (they can defend this turn, move next) 5 energy sensors and silos recharge 6 scans spend energy, warn targets 7 extraction connected colonies mine into the pool 8 refining staffed refineries convert 9 research labs advance the current project 10 market blind clearing at the fixed posted price, no price feedback 11 covert espionage, sabotage, tribute 12 departures moves and jumps leave with wound carry; filtered sightings; travel time learned 13 surveys edge and node surveys, ruins 14 interception interceptors shoot at stocks about to land 15 arrivals exact due transits land (mines trigger first); filtered arrival sightings 16 colonize founding starts/progresses/completes 17 bombard ranged fire across edges 18 strikes silo strikes 19 raids raiders hit, loot and turn back 20 combat one round at every contested node 21 siege colony damage, capture or raze 22 upkeep unit and administrative upkeep, disbanding 23 neutral minor factions hold their garrisons 24 observe owner-only observation snapshots update 25 score descriptive wealth, capital-loss elimination, turn-limit draw Consequences of the order: a stock ordered to leave this turn departs before arrivals land; a stock landing this turn fights in this turn's round; a unit produced this turn is present for this turn's combat but cannot move until next turn; new orders cannot budget income not yet earned. Standing labor and auto-mining exclude departing workers even before departures resolve. This resolution order is the same for every game. 13. MATERIALS SCIENCE --------------------- Materials are part of every new battle game. Alongside the strategy mechanics, 64 fictional elements interact under a local program generated independently per match. The same laws apply to both players and stay fixed throughout the game. These are not real chemical elements. Their IDs are integers 0..63; period-eight groups share hidden attributes, with weaker variation between bands. Experiments can uncover useful patterns. Build material_lab: 30 ore, 2 construction labor, no staff required once active. The ordinary lab researches technologies; it cannot run material experiments. Each owned, active material_lab accepts one action per turn, not a standing queue: {"type":"experiment","node":0,"slot":1, "recipe":{"a":3,"b":18,"proportion":3,"field":7,"steps":12}} {"type":"produce_material","node":0,"slot":1, "recipe":{"a":3,"b":18,"proportion":3,"field":7,"steps":12},"batches":2} Use your actual node and slot. Both element IDs may be equal. Proportion is 1..7: the first proportion sites of an eight-site ring use a, the remainder b. Field is 0..15; steps is 1..32. Production batches must be 1..8. Legal inputs are accepted regardless of their hidden yield, so validation is not a free measurement oracle. Experiments cost 2 ore, produce no alloy, and measure the outcome of the recipe. Production costs 12 ore per batch and produces batches * yield_per_batch alloy using exactly the same dynamics. Prior experimentation is not required. The ordinary refinery yields 1 alloy per 12 ore; discovered processes yield 1..10 per batch. Material actions resolve at the end of standing_orders, before automatic production. No station can use another station's output to finance its own action in that substep. The recipe is simulated synchronously with bounded integer channels and generated local operators; both the program structure and material attributes vary by match. Your knowledge.material_observations stores {turn,node,slot,recipe,batches,measurement}. Measurement contains stability and activity in [0,1], yield_per_batch, and signature: a four-bin histogram of 24 final channel values. Facts use type material_processed and also record player, consumed and produced resource maps. Observation batches = 0 means an experiment. The notebook keeps the latest result for each of 256 distinct recipes; repeated recipes move to the newest position, and older entries are evicted. Recipes, results and notebooks are private during play, even from opponents observing the station. materials.physics is always absent/null from player rules, including after ordinary research. Finished replays reveal the complete model and measurements. Recipes use two elements; produced alloy cannot be reused as a recipe ingredient. Material laws do not change the movement or combat rules described above.