{"version":"0.25.6","events":{"your_action_required":{"target":"team","description":"The server is asking your team for one action. Submit it via POST /api/team/action with an `action_type` taken from `pending_action.action_types`. `state` is your current view of the game; `pending_action` carries the deadline and remaining attempts. Ignore unknown fields — future game variants may add new ones.","required_for_play":true,"fields":{"game_id":{"type":"str","description":"Game identifier — echo it back on POST /api/team/action.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"state":{"type":"dict","description":"Your team's view of the game right now. Same shape POST /api/team/state returns. Contents depend on game_type and phase — consult the per-game action schema in this endpoint's `games` block.","required":true},"pending_action":{"type":"dict","description":"The action your team owes this turn (always present).","required":true,"fields":{"action_types":{"type":"list","description":"Action types you may submit this turn — pick one and put it in your POST. A list (today single-element for classical Battleship/TTT); future game variants may offer a menu.","required":true},"deadline_ts":{"type":"str","description":"Absolute ISO-8601 UTC deadline for this attempt. Remaining time = deadline_ts − now.","required":true},"attempts_left":{"type":"int","description":"Attempts remaining including this one. Reaches 1 on the final attempt, never 0 while an action is owed.","required":true}}}}},"your_action_resolved":{"target":"team","description":"The outcome of one action your team submitted — emitted after every attempt, success or failure. On success, `result.details` carries the engine outcome (e.g. hit/miss for a Battleship move). On failure, `result.details.errors` lists what to fix and a fresh `your_action_required` arrives next on the same stream if you have attempts left.","required_for_play":true,"fields":{"game_id":{"type":"str","description":"Game identifier.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"action_type":{"type":"str","description":"Which action this resolves (e.g. \"placement\", \"move\").","required":true},"created_at":{"type":"str","description":"ISO-8601 UTC timestamp (microsecond precision).","required":true},"result":{"type":"dict","description":"Outcome envelope — see `result_codes` in this endpoint for the full code vocabulary.","required":true,"fields":{"code":{"type":"str","description":"\"OK\" on success; a dotted LAYER.DOMAIN.SPECIFIC code on failure. See `result_codes` for the catalogue.","required":true},"details":{"type":"dict","description":"Always present. On OK: the engine outcome for this action — for a Battleship move, {coordinate, outcome} where outcome is \"hit\", \"miss\", or \"sunk\"; a \"sunk\" outcome additionally carries `ship` (the sunk ship's name) and `ship_cells` (the coordinates it occupied). A plain \"hit\" does not name the ship — ship identity is revealed only on a sink. On failure: {errors: list[str]} naming what to fix.","required":true}}}}},"game_start":{"target":"team","description":"A game your team is in is about to begin. Cache `game_config` and `rules` — subsequent events don't repeat them.","required_for_play":true,"fields":{"game_id":{"type":"str","description":"Game identifier.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"game_mode":{"type":"str","description":"\"practice\", \"ranked\", or \"tournament\".","required":true},"opponent":{"type":"str","description":"Opponent team's display name.","required":true},"game_config":{"type":"dict","description":"Per-team engine config (board dimensions, fleet, etc.). Shape depends on game_type — see the per-game `actions[...].receives` block in this endpoint's `games` section.","required":true},"rules":{"type":"dict","description":"Platform safety nets — how many attempts you get and the game's overall time cap.","required":true,"fields":{"max_placement_attempts":{"type":"int","description":"Total placement attempts allowed (includes the first try).","required":true},"max_move_attempts":{"type":"int","description":"Total move attempts allowed (includes the first try).","required":true},"max_game_duration_ms":{"type":"int","description":"Wallclock cap for the whole game, in milliseconds.","required":true}}}}},"game_end":{"target":"team","description":"Your team's game just ended. Five self-sufficient fields — you can act on the outcome (including your own score) without consulting any other stream or endpoint.","required_for_play":true,"fields":{"game_id":{"type":"str","description":"Game identifier.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"terminal_reason":{"type":"str","description":"Why the game ended. Game-specific values are bare (e.g. \"all_ships_sunk\", \"placement_timeout\", \"move_timeout\"); platform-wide values carry an underscore prefix (\"_admin_abort\", \"_duration_cap\", \"_mutual_disconnect\", \"_team_no_show\").","required":true},"winner_team_id":{"type":"str|null","description":"team_id of the winner; null for a draw, abort, or no-winner outcome. Compare against your own team_id to know if you won.","required":true},"your_end_state":{"type":"str|null","description":"Your team's classification at game-end. One of: \"Present\" (you won — opponent eliminated or forfeited), \"Eliminated\" (you lost), \"Disqualified\" (you timed out, sent repeated invalid actions, or were DQ'd by admin), \"Disconnected\" (you lost the SSE connection past the grace window). null when no per-team outcome was authored — typically a game-wide abort (consult `terminal_reason`).","required":true},"your_score":{"type":"float|null","description":"Your team's score for this game. 1.0 for a win, 0.0 for a loss, 0.5 for a draw (in games that produce draws). null when the game ended without scoring data (e.g. admin abort).","required":true}}},"game_started":{"target":"broadcast","description":"Observer: a game started.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier","required":true},"match_id":{"type":"str","description":"Parent match identifier; null for practice","required":true},"game_number":{"type":"int","description":"Game number within match","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"game_mode":{"type":"str","description":"Game mode: tournament or practice","required":true},"teams":{"type":"list","description":"Teams list [{name}]; index 0 = first mover. Clients resolve identity (colour, avatar) from the team registry (GET /api/public/teams) by name.","required":true},"config":{"type":"dict","description":"Per-game config; shape depends on game_type. For battleship: {board_width, board_height, fleet, time_limit_ms}. Board dimensions live here — every game in a match may use different ones.","required":true}}},"game_ended":{"target":"broadcast","description":"Observer event — a game ended. Carries the full outcome for both teams (display names, scores, end-states) plus admin void/reschedule metadata when applicable. Bots that only need their own outcome should listen for `game_end` on their team channel instead — it is self-sufficient.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"match_id":{"type":"str","description":"Parent match identifier; null for practice.","required":true},"tournament_id":{"type":"str","description":"Parent tournament identifier; null for practice/friendly.","required":true},"outcome":{"type":"dict","description":"Game outcome. Fields: team_ids (list[str], co-indexed for all per-team lists below); winner_team_id (str|null — null for draws, aborts, no-winner outcomes); winner_team_name (str|null — display translation of the id); team_scores (list[float|null] — same scoring vocab as game_end.your_score); team_end_states (list[str|null] — \"Present\" / \"Eliminated\" / \"Disqualified\" / \"Disconnected\" per team); terminal_reason; terminal_reason_description; valid (bool — false for aborts, duration caps, mutual disconnects, and admin-nullified games); voided_reason / voided_at / voided_by (set when admin flipped validity post-hoc); replaces (game_id of original if this is a rescheduled replacement); ended_at (ISO-8601 UTC).","required":true},"stats":{"type":"dict","description":"Game statistics.","required":true}}},"action_resolved":{"target":"broadcast","description":"Observer event — one entry per applied action (both teams' moves) plus system events that affect game state (forfeit, admin abort, duration cap, mutual disconnect). Bots can subscribe to track the opponent's moves in real time instead of inferring from their next `your_action_required`.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier.","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\").","required":true},"seq":{"type":"int","description":"Monotonic per-game sequence number (1-indexed).","required":true},"created_at":{"type":"str","description":"ISO-8601 UTC timestamp (microsecond precision).","required":true},"action_type":{"type":"str","description":"The action that was applied. Bare names for team actions (e.g. \"placement\", \"move\", \"place\"); underscore-prefixed for system events (e.g. \"_team_forfeit\", \"_admin_abort\").","required":true},"team":{"type":"int|null","description":"Zero-based team index for team actions; null for system events.","required":true},"match_id":{"type":"str","description":"Parent match identifier; null for non-tournament games.","required":true},"result":{"type":"dict","description":"Outcome envelope. `details` is engine-specific and present only on success — see `result_codes` for the full code vocabulary.","required":true,"fields":{"code":{"type":"str","description":"\"OK\" on success; a dotted LAYER.DOMAIN.SPECIFIC code on failure. See `result_codes` for the catalogue.","required":true},"summary":{"type":"str","description":"Human-readable one-line summary of the outcome.","required":true},"details":{"type":"dict","description":"Engine-specific outcome payload; present only when code is \"OK\".","required":false}}}}},"tournament_created":{"target":"broadcast","description":"A tournament was created.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"name":{"type":"str","description":"Tournament name","required":true},"format":{"type":"str","description":"Tournament format","required":true}}},"tournament_ready":{"target":"broadcast","description":"Pairings generated, awaiting start.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"name":{"type":"str","description":"Tournament name","required":true},"format":{"type":"str","description":"Tournament format (round_robin, single_elimination)","required":true},"team_count":{"type":"int","description":"Number of participating teams","required":true},"pairings":{"type":"list","description":"Match pairings with team names","required":true},"layout_name":{"type":"str","description":"Display name of the layout configuration in use","required":true},"layout_config":{"type":"dict","description":"Opaque layout config from Layout.config","required":true}}},"tournament_started":{"target":"broadcast","description":"Tournament underway.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"name":{"type":"str","description":"Tournament name","required":true},"format":{"type":"str","description":"Tournament format (round_robin, single_elimination)","required":true},"team_count":{"type":"int","description":"Number of participating teams","required":true},"standings":{"type":"list","description":"Initial standings","required":true},"layout_name":{"type":"str","description":"Display name of the layout configuration in use","required":true},"layout_config":{"type":"dict","description":"Opaque layout config from Layout.config","required":true}}},"tournament_paused":{"target":"broadcast","description":"Tournament paused between rounds.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"reason":{"type":"str","description":"Pause reason (round_boundary, admin_request)","required":true},"round_completed":{"type":"int","description":"Round number just completed","required":false}}},"tournament_resumed":{"target":"broadcast","description":"Tournament resumed.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true}}},"tournament_ended":{"target":"broadcast","description":"Tournament finished. Winner, final standings.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"winner":{"type":"str","description":"Winning team display name","required":true},"winner_team_id":{"type":"str","description":"Winning team canonical id (null for draw/no-winner)","required":true},"final_standings":{"type":"list","description":"Ordered standings list","required":true}}},"tournament_aborted":{"target":"broadcast","description":"Tournament cancelled.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"reason":{"type":"str","description":"Abort cause (e.g. 'driver_exception' on uncaught tournament exception)","required":true}}},"match_started":{"target":"broadcast","description":"A match (best-of-N) started.","required_for_play":false,"fields":{"match_id":{"type":"str","description":"Match identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"teams":{"type":"list","description":"Teams list [{name}]; index 0 = bracket position","required":true},"best_of":{"type":"int","description":"Best-of-N","required":true}}},"match_ended":{"target":"broadcast","description":"A match ended.","required_for_play":false,"fields":{"match_id":{"type":"str","description":"Match identifier","required":true},"game_type":{"type":"str","description":"Game type identifier; null for orphan matches (no parent tournament)","required":true},"teams":{"type":"list","description":"Teams list [{name, wins}]","required":true},"winner":{"type":"str","description":"Winning team name","required":true},"score":{"type":"str","description":"Match score","required":true},"games_played":{"type":"int","description":"Valid completed games in the match (decisive + drawn)","required":true},"draws":{"type":"int","description":"Drawn games in the match (games_played minus decisive wins)","required":true}}},"standings_updated":{"target":"broadcast","description":"Rankings changed.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"standings":{"type":"list","description":"Current standings list","required":true}}},"pairings_updated":{"target":"broadcast","description":"Bracket advancement.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"pairings":{"type":"list","description":"Current pairings","required":true}}},"team_registered":{"target":"broadcast","description":"A team registered.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Team identifier","required":true},"game_type":{"type":"str","description":"Game type the team is bound to (immutable after registration)","required":true},"name":{"type":"str","description":"Team name","required":true},"team_count":{"type":"int","description":"Total registered teams","required":true}}},"team_connected":{"target":"broadcast","description":"Team SSE stream connected (online).","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Team identifier","required":true},"game_type":{"type":"str","description":"Game type the team is bound to","required":true},"team_name":{"type":"str","description":"Team name","required":true}}},"team_disconnected":{"target":"broadcast","description":"Team SSE stream disconnected (offline).","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Team identifier","required":true},"game_type":{"type":"str","description":"Game type the team is bound to","required":true},"team_name":{"type":"str","description":"Team name","required":true}}},"team_deregistered":{"target":"broadcast","description":"Team deregistered (self-service or admin).","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Team identifier","required":true},"game_type":{"type":"str","description":"Game type the team was bound to","required":true},"team_name":{"type":"str","description":"Team name","required":true}}},"team_updated":{"target":"broadcast","description":"A team's details changed (operator edit: colour slot or description). Refresh team data from /api/public/teams.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Team identifier","required":true},"game_type":{"type":"str","description":"Game type the team is bound to","required":true},"team_name":{"type":"str","description":"Team name","required":true}}},"teams_cleared":{"target":"broadcast","description":"All teams cleared (server reset).","required_for_play":false,"fields":{"reason":{"type":"str","description":"Reason for clearing (e.g. server_reset)","required":true}}},"registration_opened":{"target":"broadcast","description":"Registration is now open.","required_for_play":false,"fields":{}},"registration_closed":{"target":"broadcast","description":"Registration is now closed.","required_for_play":false,"fields":{}},"bot_created":{"target":"broadcast","description":"A server bot was created.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Bot team identifier","required":true},"game_type":{"type":"str","description":"Game type the bot is bound to","required":true},"team_name":{"type":"str","description":"Bot name","required":true},"strategy_name":{"type":"str","description":"Strategy module name","required":true}}},"bot_updated":{"target":"broadcast","description":"A server bot was updated.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Bot team identifier","required":true},"game_type":{"type":"str","description":"Game type the bot is bound to","required":true},"team_name":{"type":"str","description":"Bot name","required":true},"strategy_name":{"type":"str","description":"Strategy module name","required":true}}},"bot_deleted":{"target":"broadcast","description":"A server bot was removed.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"Bot team identifier","required":true},"game_type":{"type":"str","description":"Game type the bot was bound to","required":true},"team_name":{"type":"str","description":"Bot name","required":true}}},"announcement_update":{"target":"broadcast","description":"An operator banner announcement changed state. Upsert/remove by id: active=true shows or updates the strip, active=false removes it. show_until is the client's disconnect self-expiry backstop.","required_for_play":false,"fields":{"id":{"type":"str","description":"Stable announcement id","required":true},"message":{"type":"str","description":"Body text","required":true},"severity":{"type":"str","description":"Display tier: info | important | critical","required":true},"active":{"type":"bool","description":"True → show/keep the strip; False → remove it","required":true},"show_until":{"type":"str","description":"Window end (ISO-8601 UTC) — client self-expiry backstop","required":true}}},"challenge_created":{"target":"team","description":"Incoming challenge from another team.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type — challenges are partitioned per game_type; both teams share it","required":true},"challenger":{"type":"str","description":"Challenger team name","required":true},"mode":{"type":"str","description":"Game mode (practice/ranked)","required":true},"layout_id":{"type":"str","description":"Layout identifier","required":true},"expires_at":{"type":"str","description":"Expiry timestamp (ISO 8601)","required":true}}},"challenge_accepted":{"target":"team","description":"Your outgoing challenge was accepted.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type","required":true},"game_id":{"type":"str","description":"Game identifier","required":true},"opponent":{"type":"str","description":"Opponent team name","required":true}}},"challenge_declined":{"target":"team","description":"Your outgoing challenge was declined.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type","required":true},"opponent":{"type":"str","description":"Opponent team name","required":true}}},"challenge_expired":{"target":"team","description":"A challenge expired without response.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type","required":true},"challenger":{"type":"str","description":"Challenger team name","required":true},"opponent":{"type":"str","description":"Opponent team name","required":true}}},"challenge_updated":{"target":"broadcast","description":"Observer tick fired alongside every challenge state change. Carries minimal identification; observers refresh via the challenge-list endpoints to pick up the latest state. Bots that care about their own challenges read the team-targeted challenge_* events instead.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type the challenge is partitioned by","required":true},"status":{"type":"str","description":"New status: pending | accepted | declined | cancelled | expired | started","required":true}}},"challenge_cancelled":{"target":"team","description":"An incoming challenge was cancelled by the challenger.","required_for_play":false,"fields":{"challenge_id":{"type":"str","description":"Challenge identifier","required":true},"game_type":{"type":"str","description":"Game type","required":true},"challenger":{"type":"str","description":"Challenger team name","required":true}}},"queue_joined":{"target":"team","description":"Team joined the matchmaking queue.","required_for_play":false,"fields":{"game_type":{"type":"str","description":"Game type — the queue is partitioned per game_type; you only match against teams on your game","required":true},"mode":{"type":"str","description":"Game mode","required":true},"position":{"type":"int","description":"Position in queue","required":true},"timeout_s":{"type":"int","description":"Seconds until queue timeout","required":true}}},"queue_matched":{"target":"team","description":"Queue match found — game starting.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier","required":true},"game_type":{"type":"str","description":"Game type","required":true},"opponent":{"type":"str","description":"Opponent team name","required":true}}},"queue_left":{"target":"team","description":"Team left the matchmaking queue.","required_for_play":false,"fields":{"game_type":{"type":"str","description":"Game type the queue was partitioned by","required":true},"reason":{"type":"str","description":"Reason (manual, timeout, matched)","required":true},"mode":{"type":"str","description":"Game mode that was left","required":true}}},"queue_updated":{"target":"broadcast","description":"Observer tick fired alongside every queue state change (join/match/leave). Carries minimal identification; observers refresh via the queue-status endpoint to pick up the latest state. Bots that joined the queue read the team-targeted queue_* events instead.","required_for_play":false,"fields":{"game_type":{"type":"str","description":"Game type the queue is partitioned by","required":true},"mode":{"type":"str","description":"Game mode: practice or ranked","required":true}}},"rating_updated":{"target":"team","description":"Team rating changed after a ranked/tournament game.","required_for_play":false,"fields":{"game_type":{"type":"str","description":"Game type the rating is scoped to","required":true},"rating":{"type":"float","description":"New rating","required":true},"rating_deviation":{"type":"float","description":"New RD","required":true},"change":{"type":"float","description":"Rating change from previous","required":true}}},"leaderboard_updated":{"target":"broadcast","description":"Leaderboard changed after a rating update.","required_for_play":false,"fields":{"game_type":{"type":"str","description":"Game type the leaderboard is scoped to","required":true}}},"game_paused":{"target":"broadcast","description":"Observer event — an admin paused an in-flight game. The server holds at the per-turn checkpoint. Bots don't receive this event; their in-flight action deadline freezes at the pause boundary and resumes with the remaining time when the game is resumed.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"match_id":{"type":"str","description":"Match identifier","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true}}},"game_resumed":{"target":"broadcast","description":"Observer event — an admin resumed a previously-paused game; play continues from where it was held.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"match_id":{"type":"str","description":"Match identifier","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true}}},"match_paused":{"target":"broadcast","description":"Observer event — an admin paused a match. The active game (if any) completes naturally before the match loop exits, unless `cascade_game=True`.","required_for_play":false,"fields":{"match_id":{"type":"str","description":"Match identifier","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\")","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true},"cascade_game":{"type":"bool","description":"True iff the pause cascaded to the active game","required":false}}},"match_resumed":{"target":"broadcast","description":"Observer event — an admin resumed a previously-paused match. Standalone resume of a match outside a tournament is a status-only operation today.","required_for_play":false,"fields":{"match_id":{"type":"str","description":"Match identifier","required":true},"game_type":{"type":"str","description":"Game type identifier (e.g. battleship)","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true}}},"game_voided":{"target":"broadcast","description":"Observer event — an admin nullified a previously-ended game. The game's outcome is now invalid; standings recompute. Bots affected by the void receive this on their team channel as well; bots that don't track tournament standings can ignore.","required_for_play":false,"fields":{"game_id":{"type":"str","description":"Game identifier","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\")","required":true},"match_id":{"type":"str","description":"Match identifier","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true},"rescheduled":{"type":"bool","description":"True iff the admin appended a replacement game (which carries `replaces=<this game_id>`)","required":true}}},"match_voided":{"target":"broadcast","description":"Observer event — an admin nullified a previously-ended match. All games in the match are voided as a cascade; tournament standings recompute.","required_for_play":false,"fields":{"match_id":{"type":"str","description":"Match identifier","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\")","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true},"rescheduled":{"type":"bool","description":"True iff the admin appended a replacement match (which carries `replaces=<this match_id>`)","required":true},"cascaded_game_ids":{"type":"list","description":"IDs of contained games auto-voided as part of the match-level void.","required":true}}},"team_disqualified":{"target":"broadcast","description":"Observer event — an admin disqualified a team at game, match, or tournament scope. The team's score in the affected layer is suppressed (null) and its end-state is recorded as \"Disqualified\". Bots receive this on their team channel when they are the affected team.","required_for_play":false,"fields":{"team_id":{"type":"str","description":"DQ'd team identifier","required":true},"game_type":{"type":"str","description":"Game type the team is bound to","required":true},"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"scope":{"type":"str","description":"DQ scope: \"game\" | \"match\" | \"tournament\"","required":true},"target_id":{"type":"str","description":"ID of the layer the DQ targets (game_id / match_id / tournament_id)","required":true},"reason":{"type":"str","description":"Admin-supplied reason","required":true}}},"tournament_outcome_revised":{"target":"broadcast","description":"Observer event — the final standings or winner of an already-ended tournament shifted because of a subsequent admin operation (game void, match void, retroactive DQ, or force-decide). Carries the new standings snapshot. Broadcast-only; bots don't subscribe.","required_for_play":false,"fields":{"tournament_id":{"type":"str","description":"Tournament identifier","required":true},"game_type":{"type":"str","description":"Game type (e.g. \"battleship\", \"tictactoe\")","required":true},"reason":{"type":"str","description":"Admin operation that caused the revision (e.g. \"nullify_match\")","required":true},"standings":{"type":"list","description":"New final standings (same shape as standings_updated)","required":true},"winner_team_id":{"type":"str","description":"New winner; null on tie","required":true}}}},"games":{"battleship":{"coordinate_system":"Cells are addressed as a column letter followed by a 1-based row number. Columns are A, B, C, … and AA, AB, … for wide boards; A1 is the top-left corner. Ships occupy a horizontal or vertical run of cells from `start` to `end` (inclusive).","actions":{"placement":{"description":"Place your fleet on the board before play begins. Submitted once at the start of a game.","receives":{"board_width":"int — number of columns (default 10)","board_height":"int — number of rows (default 10)","fleet":"list of {ship: str, type: str, size: int} — ships you must place this game"},"returns":{"placements":"list of {ship: str, start: str, end: str} — one entry per ship in `fleet`. start/end are coordinates like \"A1\" and \"A5\"; the run must be horizontal or vertical, no diagonals, no overlaps, no off-board."}},"move":{"description":"Fire one shot at the opponent's board. Submitted every turn during play.","receives":{"board_width":"int — number of columns","board_height":"int — number of rows","your_board":"2D int array, your_board[row][col]. Cell values: 0=empty water, 1=your ship intact, 2=your ship hit by opponent, 3=opponent shot here and missed.","opponent_board":"2D int array, opponent_board[row][col]. Cell values: 0=unknown, 2=your shot hit something, 3=your shot missed.","your_ships":"dict keyed by ship name → {cells: list[str], hits: list[str], sunk: bool} — your placement plus damage to each of your ships.","opponent_ships_sunk":"list[str] — names of opponent ships you have sunk so far.","opponent_ships_sunk_cells":"dict keyed by sunk ship name → list[str] of that ship's cells (e.g. {\"destroyer\": [\"A1\", \"A2\"]}). Lets you tell a wreck cell apart from a still-live hit when ships were placed touching; only sunk ships appear, surviving ships are never revealed.","shots_fired":"list[str] — coordinates you've fired at, in order.","shots_received":"list[str] — coordinates the opponent has fired at you, in order.","current_turn":"int — turn number (1-based)."},"returns":{"coordinate":"str — the cell to fire at, e.g. \"D4\". Must be on the board and not already fired at."}}},"timeouts":{"placement_s":30.0,"move_s":10.0},"default_config":{"board_width":10,"board_height":10,"fleet":[{"ship":"carrier","type":"carrier","size":5},{"ship":"battleship","type":"battleship","size":4},{"ship":"cruiser","type":"cruiser","size":3},{"ship":"submarine","type":"submarine","size":3},{"ship":"destroyer","type":"destroyer","size":2}]}},"tictactoe":{"coordinate_system":"Cells are addressed as a column letter followed by a 1-based row number. Columns are A, B, C, …; A1 is the top-left corner. Board dimensions and target line length vary per layout.","actions":{"place":{"description":"Place one letter from your word on an empty cell during your turn. Goal is to complete the configured number of target lines (rows, columns, or diagonals of `line_length`).","receives":{"board_height":"int — number of rows","board_width":"int — number of columns","your_word":"str — your team's word; the letters you place must come from this word's letter set","opponent_word":"str — opponent's word, for reference","target_lines":"int | None — number of completed lines required to win; null means play until the board fills (score-on-fill mode)","board":"2D array, board[row][col]. Each cell is the placed letter as a string, \"#\" for an obstacle, or null for empty."},"returns":{"cell":"str — the cell to place into, e.g. \"A1\" or \"C3\". Must be empty.","symbol":"str — a single letter drawn from your word's letter set."}}},"timeouts":{"move_s":10.0},"default_config":{"board_height":3,"board_width":3,"word_p0":"XXX","word_p1":"OOO","target_lines":1,"obstacles":[],"random_obstacles":0,"consume_used_symbols":false,"enforce_equal_word_length":true,"time_limit_ms":10000}}},"result_codes":{"OK":"Action accepted and applied successfully.","BOT.ACTION.ILLEGAL":"Action parses but breaks a game rule (e.g. firing at a cell already shot, placing a ship off-board, playing out of turn within the game's rules).","BOT.ACTION.MALFORMED":"Action payload did not parse — wrong types, missing required fields, or otherwise unreadable. Only emitted by POST /api/team/validate/{action_type}; in live games malformed payloads are caught earlier as HTTP 422.","BOT.ACTION.OUT_OF_TURN":"An action was submitted while it was not your team's turn to act on this action type.","BOT.ACTION.UNKNOWN":"The submitted action_type is not a known action for this game (e.g. \"shoot\" instead of \"move\" in Battleship).","BOT.ACTION.WRONG_TYPE":"The submitted action_type is a valid action for this game but is not what the current obligation expects (e.g. submitting \"move\" while a \"placement\" is owed).","BOT.COMMS.TIMEOUT":"Your team did not submit an action before the deadline. The attempt is consumed; if attempts_left was 1, the team forfeits the game.","SERVER.RUNTIME.EXCEPTION":"An unexpected exception was raised while handling the request. Not your fault; the operator should investigate.","SERVER.ENGINE.INTERNAL":"The game engine raised an internal error while processing the action. Not your fault; the operator should investigate.","ADMIN.LIFECYCLE.TEAM_WITHDRAWN":"An operator removed your team from the affected layer (game / match / tournament)."}}