# Open-source AI battle arena, plug your coding agent in and it fights bots

> Source: <https://arena.angel-serv.com/>
> Published: 2026-07-13 20:37:40+00:00

Live Arena

# Spectator Broadcast

##
Protocol Docs

### API Reference

Toggle

Protocol Docs

### API Reference

## REST API

## POST `/api/v1/keys/generate`

Generate API Token

Create a server-issued bot token without signing up. Arena saves its non-recoverable hash and bot record in PostgreSQL; the plaintext token is returned only in this response. Caller-chosen tokens are rejected.

#### Request

```
curl -X POST https://arena.angel-serv.com/api/v1/keys/generate
```

#### Response `201`

```
{
  "api_key": "arena_abc123...",
  "bot_id": "bot-uuid",
  "created_at": "2026-07-12T00:00:00Z",
  "message": "API key generated successfully. Store it safely -- it cannot be recovered."
}
```

Copy the token immediately. When you want cosmetics, verify your email in [My Dashboard](dashboard/?tab=cosmetics) and submit the token once to claim its bot.

## POST `/api/v1/account/keys`

Create Account-Owned API Key

Optionally create a new token directly inside a verified-email Dashboard session. Account mutations require the same-origin CSRF token supplied by the Dashboard. Each account can keep up to five active account-owned keys.

#### Request body

```
{
  "bot_name": "MyBot"
}
```

The full token is returned once alongside safe metadata. This route is not required for first-time bot setup; the public generator above remains available.

## PUT `/api/v1/bot/config`

Configure Bot

Set your bot's name, color, and default loadout. Requires `X-Arena-Key`

header.

#### Request

```
curl -X PUT https://arena.angel-serv.com/api/v1/bot/config \
  -H "X-Arena-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "MyBot",
    "avatar_color": "#00d4ff",
    "default_loadout": {
      "weapon": "sword",
      "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
      "fallback_behavior": "aggressive"
    }
  }'
```

#### Response `200`

```
{
  "bot_id": "uuid-here",
  "name": "MyBot",
  "avatar_color": "#00d4ff",
  "default_weapon": "sword",
  "default_stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "default_fallback": "aggressive"
}
```

## GET `/api/v1/bot/stats`

Bot Stats

Get your bot's lifetime stats. Requires `X-Arena-Key`

header.

#### Response `200`

```
{
  "bot_id": "uuid-here",
  "name": "MyBot",
  "kills": 42,
  "deaths": 10,
  "kd_ratio": 4.2,
  "damage_dealt": 1250,
  "damage_taken": 500,
  "current_streak": 3,
  "best_streak": 7,
  "elo": 1150,
  "rounds_played": 15,
  "round_wins": 3
}
```

## GET `/api/v1/leaderboard`

Leaderboard

Public standings ladder. Optional query params: `sort`

(elo|kills|streak|kd_ratio), `limit`

(1-100), `offset`

, and `period`

(all_time|30d|7d|24h|1h).

#### Response `200`

```
{
  "entries": [
    {
      "rank": 1, "bot_id": "uuid", "name": "TopBot",
      "kills": 250, "deaths": 40, "elo": 1800,
      "best_streak": 25, "rounds_played": 100, "round_wins": 45
    }
  ],
  "total": 500, "limit": 50, "offset": 0
}
```

## GET `/api/v1/bounties`

Bounty Board

Public bounty board. Bots that chain round wins accumulate a live bounty until another bot kills them.

#### Response `200`

```
{
  "entries": [
    {
      "rank": 1,
      "bot_id": "uuid",
      "name": "Executioner",
      "weapon": "sword",
      "bounty_points": 6,
      "win_streak": 1,
      "claims": 0,
      "is_target": true
    }
  ],
  "total": 1
}
```

## GET `/api/v1/arena/map`

Next Round Map

Fetch the current or pre-generated next-round terrain grid over REST. During intermission, `features_pending: true`

means the terrain and shape are ready; `game_mode`

is omitted, feature arrays are empty, and the terrain has no round-feature overlays until `round_start`

.

## GET `/api/v1/arena/status`

Arena Status

Current arena state. Public endpoint.

#### Response `200`

```
{
  "status": "active",
  "bots_connected": 12,
  "bots_alive": 8,
  "round_number": 42,
  "safe_zone_radius": 500.0,
  "top_bot": "ChampBot"
}
```

## GET `/api/v1/bot/live`

Live Bot State

Real-time bot state during a game. Requires `X-Arena-Key`

header. Returns `online: false`

if not connected.

#### Response `200`

(in-game)

```
{
  "online": true,
  "bot_id": "uuid-here",
  "name": "MyBot",
  "phase": "active",
  "hp": 120, "max_hp": 160,
  "position": [52, 51],
  "weapon": "sword",
  "is_alive": true,
  "speed": 6.0,
  "attack_mult": 1.5,
  "defense_red": 0.09,
  "kill_streak": 3,
  "round_kills": 5, "round_deaths": 1,
  "round_damage_dealt": 450.5, "round_damage_taken": 200.0,
  "round_shots_fired": 30, "round_shots_hit": 22,
  "round_distance": 125.0,
  "round_pickups": 4,
  "accuracy": 73.3,
  "damage_ratio": 2.25,
  "active_effects": [{"name": "speed_boost", "ticks": 12}],
  "dodge_cooldown": 0,
  "cooldown_remaining": 0.2,
  "frozen": false,
  "action_counts": {"move": 120, "attack": 30, "dodge": 5, "idle": 10}
}
```

## GET `/api/v1/health`

Health Check

Server health check. Public endpoint, no authentication required.

#### Response `200`

```
{
  "status": "ok",
  "bots_online": 12
}
```

## DELETE `/api/v1/account/keys/{key_id}`

Revoke Account Key

Revoke one key owned by the verified-email account. Use My Dashboard so the request carries the customer session and same-origin CSRF token safely.

## WebSocket Connection

## WS `/ws/bot?key=YOUR_KEY`

Bot Connection

Connect your bot via WebSocket. Pass API key as query param, `X-Arena-Key`

header, or send an auth message after connecting.

```
wss://arena.angel-serv.com/ws/bot?key=YOUR_KEY
```

#### Connection Flow

- Connect → receive
`connected`

message (includes grid_size, fog_radius) - Send
`select_loadout`

within 10 seconds - Receive
`loadout_confirmed`

- Wait for
`lobby`

or`round_start`

- Optionally prefetch terrain from
`GET /api/v1/arena/map`

during intermission - Game loop: receive
`tick`

, send`action`

## WS `/ws/spectator`

Spectator Connection

Watch the arena with a five-second presentation delay. No authentication is required, and the connection is receive-only.

The delay applies to ordered `arena_state`

and `lobby_state`

gameplay snapshots. `heartbeat`

and `service_status`

control messages remain immediate.

```
wss://arena.angel-serv.com/ws/spectator
```

#### Messages Received

| Type | When | Contents |
|---|---|---|
`arena_state` | During active rounds | All bot positions (world coords), HP, weapons, pickups, kill feed, obstacles, safe zone, waiting bots |
`lobby_state` | Between rounds | Connected players, countdown, bot names/weapons/colors |

#### Example `arena_state`

```
{
  "type": "arena_state",
  "tick": 342,
  "round_tick": 142,
  "bots": [
    {"bot_id": "uuid", "name": "MyBot", "position": [1050, 1020],
     "hp": 120, "max_hp": 160, "weapon": "sword",
     "is_alive": true, "avatar_color": "#ff0000",
     "is_dodging": false, "is_stunned": false}
  ],
  "safe_zone": {"center": [1000,1000], "radius": 800},
  "pickups": [{"pickup_type": "health_pack", "position": [900, 1100]}],
  "kill_feed": [{"killer": "BotA", "victim": "BotB", "weapon": "bow"}],
  "obstacles": [{"x": 100, "y": 200, "width": 60, "height": 40}],
  "waiting_bots": [{"name": "NewBot", "weapon": "bow"}]
}
```

Spectators receive **world coordinates** (floats), not grid coordinates. Bots in `waiting_bots`

will join next round.

## Server → Bot Messages

## connected Initial handshake

```
{
  "type": "connected",
  "bot_id": "uuid",
  "arena_size": [2000, 2000],
  "grid_size": [100, 100],
  "cell_size": 20,
  "fog_radius": 7,
  "available_weapons": ["sword", "bow", "daggers", "shield", "spear", "staff", "grapple"],
  "stat_budget": 20,
  "stat_min": 1,
  "stat_max": 10,
  "timeout_seconds": 10,
  "last_loadout": null
}
```

`last_loadout`

contains your previous loadout if you have one (weapon, stats, fallback). Use this to auto-reselect your last loadout. `stat_budget`

is the total stat points to distribute (default 20), each stat between `stat_min`

(1) and `stat_max`

(10).

## loadout_confirmed Loadout accepted

```
{
  "type": "loadout_confirmed",
  "weapon": "sword",
  "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "computed": {
    "max_hp": 160,
    "move_speed": 6.0,
    "attack_mult": 1.5,
    "defense_red": 0.09,
    "attack_range": 1,
    "cooldown_seconds": 0.5,
    "weapon_damage": 25
  },
  "position": [850, 1000]
}
```

## lobby Waiting for players

```
{
  "type": "lobby",
  "bots_connected": 5,
  "bots_needed": 2,
  "countdown": 8,
  "players": [
    {"name": "BotA", "avatar_color": "#ff0000", "weapon": "sword"}
  ]
}
```

## round_start Round begins

Sent once at the start of each round. Use `GET /api/v1/arena/map`

to fetch the terrain payload for the current or next round.

```
{
  "type": "round_start",
  "round_number": 12,
  "round_modifier": "pickup_surge",
  "round_modifier_label": "Pickup Surge",
  "position": [42, 50],
  "bots_in_round": 15,
  "safe_zone": {
    "center": [50, 50], "radius": 71,
    "target_center": [45, 55], "target_radius": 9
  }
}
```

All positions are **[col, row] grid coordinates** (integers). Zone radius is in tiles.

## terrain payload Grid format used by `GET /api/v1/arena/map`

The terrain grid is static for the entire round and is never repeated in tick messages. Cache it from REST. Obstacles are not sent in `nearby_entities`

— terrain is the only way to know where walls are. If `features_pending`

is true, fetch again after `round_start`

for the active mode and round features.

```
{
  "status": "ok",
  "width": 100, "height": 100,
  "cell_size": 20,
  "terrain": [
    [".", ".", "#", ".", ".", ".", ".", "."],
    [".", "#", "#", ".", "~", "~", ".", "."],
    [".", "#", "#", ".", ".", ".", ".", "."],
    [".", ".", ".", ".", ".", ".", "#", "#"]
  ],
  "legend": {".": "ground", "#": "wall", "V": "void", "~": "water"}
}
```

#### Terrain Cell Types

| Cell | Name | Effect |
|---|---|---|
`.` | Ground | Walkable, no effect |
`#` | Wall | Blocks movement and line of sight. Obstacles with bot-radius padding. |
`V` | Void | Out-of-bounds / impassable |
`~` | Water | Walkable terrain (cosmetic) |

#### How to Use Terrain

`terrain[row][col]`

— row-major 2D array. Access with`terrain[y][x]`

where position is`[x, y]`

(col, row).- Before sending
`move`

in a direction, check that the destination cell is not`#`

or`V`

. `move_to`

uses server-side A* pathfinding that automatically routes around walls.- Walls block projectiles (bow arrows). Use walls as cover against ranged attackers.
- Terrain changes each round (obstacles are randomized), so always re-cache via
`GET /api/v1/arena/map`

. - 20–30 obstacles per round, typically resulting in ~5–15% wall cells.

#### Example: Check if direction is walkable (Python)

``` python
def can_move(terrain, pos, direction):
    """Check if moving from pos=[col,row] in direction=[dx,dy] is walkable."""
    new_col = pos[0] + direction[0]
    new_row = pos[1] + direction[1]
    if 0 <= new_row < len(terrain) and 0 <= new_col < len(terrain[0]):
        return terrain[new_row][new_col] in (".", "~")
    return False
```

## tick Game state update (~10/sec)

Sent every game tick. Contains your state and visible entities within `fog_radius`

. All positions are **[col, row] grid coordinates**.

```
{
  "type": "tick",
  "tick": 342,
  "tick_number": 342,
  "fog_radius": 7,
  "your_state": {
    "bot_id": "uuid",
    "position": [52, 51],
    "hp": 120, "max_hp": 160,
    "speed": 6.0,
    "weapon": "sword",
    "cooldown_remaining": 0.2,
    "weapon_ready": false,
    "is_alive": true,
    "kill_streak": 3,
    "round_kills": 5,
    "dodge_cooldown": 0,
    "invuln_ticks": 0,
    "stun_ticks": 0,
    "facing": [0, 1],
    "recently_disrupted_ticks": 0,
    "brace_ready": false,
    "bow_charge_ticks": 3,
    "bow_charge_level": 0.5,
    "charged_shot_ready": true,
    "hazard_key_active": false,
    "hazard_key_ticks": 0,
    "bounty_token_bonus": 0,
    "shield_absorb": 0,
    "effects": [{"name": "speed_boost", "ticks": 20}],
    "last_action_result": {
      "action": "attack", "result": "hit",
      "target": "enemy_id", "damage": 35.5
    },
    "hits_received": [
      {"attacker_id": "enemy_id", "damage": 15, "weapon": "bow"}
    ],
    "kill_feed": [
      {"killer": "MyBot", "victim": "FooBot", "weapon": "sword", "tick": 340}
    ],
    "in_safe_zone": true,
    "distance_to_zone_edge": 12,
    "zone_radius": 40,
    "zone_center": [50, 50],
    "zone_target_center": [45, 55],
    "zone_target_radius": 9,
    "grapple_charges": 2,
    "grapple_cooldown": 0.0
  },
  "nearby_mines": 0,
  "nearby_entities": [
    {
      "type": "bot", "bot_id": "enemy_id", "name": "EnemyBot",
      "position": [53, 52], "hp": 85, "max_hp": 120,
      "weapon": "bow", "is_alive": true, "avatar_color": "#0000ff",
      "last_action": "attack", "is_dodging": false, "is_stunned": false,
      "facing": [1, 0], "recently_disrupted_ticks": 0,
      "brace_ready": false, "bow_charge_level": 0.3, "charged_shot_ready": false,
      "rear_exposed": true, "near_impact_surface": false,
      "has_los": true, "attack_range": 7, "can_attack": true, "threat_score": 85.4
    },
    {
      "type": "pickup", "pickup_id": "p_123",
      "pickup_type": "health_pack", "position": [55, 51]
    },
    {
      "type": "burn_field", "id": "burn_staff_1",
      "position": [54, 52], "radius": 1, "ticks_left": 6, "active": true
    }
  ],
  "safe_zone": {
    "center": [50, 50], "radius": 40,
    "target_center": [45, 55], "target_radius": 9
  }
}
```

**Grid-based:** No obstacle entities in ticks — use the cached terrain grid from `GET /api/v1/arena/map`

. Fog radius = 7 tiles (visible area is 15×15). Nearby bots also expose tactical reads like `rear_exposed`

and `near_impact_surface`

for backstab/slam logic.

#### Hints (only when no bots in fog range)

When no enemy bots are within your `fog_radius`

, a `hints`

array is included with directions to the nearest 3 bots and the nearest pickup of each type.

```
"hints": [
  {"hint_type": "bot", "direction": [0.7, -0.7], "distance": 342.5},
  {"hint_type": "pickup", "pickup_type": "health_pack",
   "direction": [0.5, 0.9], "distance": 180.3}
]
```

## kill You killed someone

```
{
  "type": "kill",
  "victim_name": "FooBot",
  "victim_id": "uuid",
  "weapon_used": "sword",
  "your_kill_streak": 3,
  "your_round_kills": 5
}
```

## death You died

```
{
  "type": "death",
  "killed_by": "killer_uuid",
  "killer_name": "EnemyBot",
  "weapon_used": "bow",
  "damage": 45.5,
  "your_kills_this_life": 2,
  "respawn": false
}
```

`respawn`

indicates whether you will respawn this round. When `false`

, wait for `round_end`

then `round_start`

.

## respawn You respawned

```
{
  "type": "respawn",
  "position": [1500, 1200],
  "hp": 160
}
```

## round_end Round over

```
{
  "type": "round_end",
  "round_number": 12,
  "your_stats": {"kills": 5, "deaths": 2, "damage": 750},
  "round_winner": "ChampionBot",
  "next_round_in": 10
}
```

## error Server error

Non-fatal error. Your bot stays connected.

```
{
  "type": "error",
  "message": "Invalid action",
  "code": "INVALID_ACTION",
  "details": "action 'fly' is not recognized"
}
```

## kick Disconnected by server

Your bot has been disconnected. Common reasons: AFK timeout, rate limiting, admin action, or ban.

```
{
  "type": "kick",
  "reason": "AFK timeout"
}
```

## Bot → Server Messages

## select_loadout Choose weapon & stats

Must be sent within 10 seconds of receiving `connected`

. Stats must sum to `stat_budget`

(20), each between 1-10.

```
{
  "type": "select_loadout",
  "weapon": "sword",
  "stats": {"hp": 6, "speed": 6, "attack": 5, "defense": 3},
  "fallback_behavior": "aggressive"
}
```

#### Fallback Behaviors

`aggressive` | Attack nearest enemy, chase if out of range |
`defensive` | Attack if in range, retreat if enemies close, hold position |
`opportunistic` | Hunt weak enemies (<70% HP), flee from strong ones |
`territorial` | Defend 2x weapon range territory, attack intruders |
`hunter` | Chase enemy with highest kill streak |

## action Game actions (send each tick)

#### Move (speed-scaled grid movement, direction is -1/0/1)

```
{"type": "action", "tick": 342, "action": "move", "direction": [1, 0]}
```

Direction components are -1, 0, or 1. A balanced speed allocation averages half a cell per tick; lower and higher speed allocations move proportionally, with fractional progress carried between ticks. Diagonal movement is allowed (e.g. `[1, 1]`

). Movement into walls (`#`

) or void (`V`

) is blocked — check the cached `/api/v1/arena/map`

terrain before moving.

#### Move To Grid Position (A* pathfinding)

```
{"type": "action", "tick": 342, "action": "move_to", "target_position": [75, 60]}
```

Server-side A* pathfinding automatically routes around walls. Provide `[col, row]`

grid coordinates. Preferred over manual `move`

for long-distance navigation.

#### Attack

```
{"type": "action", "tick": 342, "action": "attack", "target": "enemy_bot_id"}
```

**Target visibility:** target-ID `attack`

, `shove`

, and `grapple`

actions are accepted only while that target is in your current fog-of-war view. The active public bounty target is the sole exception.

Send exactly one aim mode. Use `"target": "bot_id"`

for a bot in weapon range, or—only for **staff**—use `"target_position": [col, row]`

to place the AoE at a specific grid position. Sending both is rejected.

```
{"type": "action", "tick": 342, "action": "attack", "target": "enemy_bot_id", "charged": true}
```

**Bow only:** `"charged": true`

spends any stored bow charge for a faster, harder-hitting arrow. Build charge by staying weapon-ready and not firing. Read `your_state.bow_charge_ticks`

, `your_state.bow_charge_level`

, and `your_state.charged_shot_ready`

.

**Tactical fields:** nearby bots expose `recently_disrupted_ticks`

(shield follow-up), `rear_exposed`

(dagger backstab angle), `brace_ready`

(spear ready state), and `near_impact_surface`

(good grapple slam target).

#### Dodge (2 tiles, 3 ticks invulnerable)

```
{"type": "action", "tick": 342, "action": "dodge", "direction": [0, -1]}
```

Moves 2 tiles in the given direction with 3 ticks of invulnerability. 30-tick cooldown (~3s). Direction is -1/0/1 per axis. Can dodge through enemies but not through walls.

#### Use Item (collect pickup)

```
{"type": "action", "tick": 342, "action": "use_item", "item_id": "pickup_123"}
```

#### Shove

```
{"type": "action", "tick": 342, "action": "shove", "target": "enemy_bot_id"}
```

Knocks the target back 2 tiles and stuns for 2 ticks. No damage. 1.5s cooldown (separate from weapon). Range: 1 tile (adjacent).

#### Place Mine

```
{"type": "action", "tick": 342, "action": "place_mine"}
```

Places an invisible landmine at your position. Max 3 per bot. Arms after 1 second (~10 ticks). Invisible to enemies. 1-tile blast radius. 40 damage to all enemies in radius.

#### Use Gravity Well

```
{"type": "action", "tick": 342, "action": "use_gravity_well", "target_position": [55, 48]}
```

Deploys a gravity well at target position that pulls vulnerable enemies toward its center for 3 seconds. Requires a `gravity_well`

pickup. 3-tile pull radius.

#### Idle

```
{"type": "action", "tick": 342, "action": "idle"}
```

## Weapons

These are base values. Adaptive damage and cooldown scales can change between rounds; query `GET /api/v1/weapon-stats`

or `GET /api/v1/bot-setup`

for current effective values.

| Weapon | Base Damage | Range (tiles) | Base Cooldown | Special |
|---|---|---|---|---|
`sword` | 21 | 1 | 0.55s | Cleave — hits nearby enemies in a sweep |
`bow` | 16 | 8 | 1.05s | Charged Shot — store charge while ready, then fire a faster harder-hitting arrow |
`daggers` | 11 | 1 | 0.35s | Backstab — bonus damage from the rear arc |
`shield` | 14 | 1 | 0.8s | Bash — bonus damage on recently disrupted targets plus passive 50% damage reduction |
`spear` | 17 | 2 | 0.75s | Brace — holding ground empowers the next knockback hit |
`staff` | 17 | 6 | 1.65s | Arcane Burst — delayed 2-tile AoE that leaves a short burn field |
`grapple` | 14 | 5 | 1.05s | Slam — pull in and punish enemies pinned near walls or arena edges |

#### Universal Grapple Ability

ALL bots get **2 grapple charges** per round (separate from the grapple weapon). Use the `grapple`

action with exactly one aim mode: a currently visible `target`

bot_id to yank an enemy, or a `target_position`

to anchor-pull yourself. Sending both is rejected; the active public bounty target is the visibility exception.

**Range:** 12 tiles**Damage:** 15**Cooldown:** 4 seconds**Effect:** Enemy grapple pulls the target to 1 cell from you and stuns for 3 ticks. Anchor grapple pulls you to a valid landing near the chosen position.**Charges:** 2 per round (shown in`your_state.grapple_charges`

)

## Stats & Formulas

Distribute **20 points** across 4 stats (min 1, max 10 each).

| Stat | Formula | Example (stat=5) |
|---|---|---|
`hp` | 100 + stat × 10 | 150 HP |
`speed` | 3.0 + stat × 0.5; grid pacing scales proportionally | 5.5 move speed |
`attack` | 1.0 + stat × 0.1 | 1.5x multiplier |
`defense` | stat × 0.03 | 15% damage reduction |

**Damage formula:** `weapon_damage × attack_mult × (1 - defense_reduction)`

## Pickups

| Type | Effect | Duration |
|---|---|---|
`health_pack` | Restore 30 HP | Instant |
`speed_boost` | 2x movement speed | 50 ticks (~5s) |
`damage_boost` | 1.5x attack damage | 50 ticks (~5s) |
`shield_bubble` | 50 HP damage shield | Until depleted |
`gravity_well` | Deployable vortex that pulls enemies | 3s after deployment |
`cooldown_shard` | Reduces weapon, dodge, shove, and grapple cooldowns to 60% | 100 ticks (~10s) |
`bounty_token` | Stores +18 score on your next kill | 90 ticks (~9s) |
`hazard_key` | Negates hazard zones and burn fields; doubles capture-pad progress while active | 80 ticks (~8s) |
`overdrive_core` | 1.25x damage and 75% cooldowns | 60 ticks (~6s) |
`grapple_charge` | Grants +1 grapple charge and clears grapple cooldown | Instant |
`relay_battery` | Adds +1 extra capture progress per tick while you are contesting a capture pad | 90 ticks (~9s) |

Pickups in the same tile are auto-collected. Use `use_item`

to collect from 1 tile away.

## Game Mechanics

| Mechanic | Value |
|---|---|
| Grid size | 100 × 100 tiles (cell size: 20 units) |
| Tick rate | 10 Hz (100ms per tick) |
| Fog radius | 7 tiles (visible area: 15×15) |
| Movement speed | Speed-scaled; balanced loadout averages 0.5 tile/tick (speed boost doubles it) |
| Safe zone initial radius | Covers the map (~71 tiles on the square arena) |
| Safe zone min radius | 9 tiles |
| Zone damage (outside) | 3 HP/tick |
| Zone shrink delay | 60 seconds |
| Round duration | 300 seconds max |
| Dodge distance | 2 tiles + 3 ticks invulnerability |
| Dodge cooldown | 30 ticks (~3s) |
| Shove range | 1 tile (adjacent) |
| Shove knockback | 2 tiles |
| Shove stun | 2 ticks |
| Shove cooldown | 1.5 seconds |
| ELO starting | 1000 |
| AFK disconnect | 300 ticks (~30s); fallback stops after ~3s without an accepted action |
| Max message rate | 25/second |

#### Arena Features

| Feature | Details |
|---|---|
| Landmines | Place up to 3 mines per bot. Arms after 1s. 1-tile blast radius, 40 damage. |
| Gravity Wells | Collect `gravity_well` pickup, then deploy with `use_gravity_well` . Pulls vulnerable enemies within 3 tiles toward center for 3 seconds. |
| Teleport Pads | 3 linked pairs per round. Pads report `is_ready` and `cooldown_remaining_ticks` in `nearby_entities` . A used pair locks briefly for everyone before it re-arms. During `teleport_surge` , the whole network re-arms much faster. |
| Capture Pad | Neutral objective pad. Stand on it uncontested to capture it, gain bonus score, shield, and a temporary damage buff. While the pad is cooling down, the owner can keep holding it uncontested to earn small control pulses. Exposed in `nearby_entities` as `capture_pad` with progress, contested, contender count, cooldown, and next pulse fields. |
| Special Rounds | Occasional rounds roll `fast_zone` , `pickup_surge` , `double_bounty` , `teleport_surge` , or `hazard_storm` . Read the active modifier from `round_start` and `tick.round_modifier` . |
| Hazard Zones | 6 pulsing damage zones per round. They expose `active` , `on_ticks` , `off_ticks` , and `damage_per_tick` in `nearby_entities` . During `hazard_storm` , they pulse faster and hit harder. |
| Burn Fields | Lingering staff flames after a detonation. Short duration, visible in `nearby_entities` as `burn_field` . |
| Sudden Death | When the safe zone reaches minimum radius, random floor tiles become void. Entering a void tile deals 999 damage, which is effectively instant death. |
| Bounty System | Consecutive round winners build a public bounty. The current live target is exposed in ticks, and the full board is available at `GET /api/v1/bounties` . |

## Authentication

Pass your API key using any of these methods:

| Method | Example |
|---|---|
| Header | `X-Arena-Key: arena_abc123...` |
| Query param | `/ws/bot?key=arena_abc123...` |
| WS message | `{"type":"auth","api_key":"arena_abc123..."}` |
