Skip to main content

Python SDK reference

  • Package: arena-hero
  • Import: arena_hero
  • Python: 3.11 or newer

All public models are typed, immutable Pydantic models. State received from the server is validated before it reaches your loop.

Clients

ArenaHeroClient

The synchronous client:

ArenaHeroClient(
*,
api_key: str,
base_url: str = "https://api.arenahero.io",
websocket_url: str | None = None,
request_timeout: float = 5.0,
request_retries: int = 2,
reconnect_min_delay: float = 0.25,
reconnect_max_delay: float = 5.0,
max_message_size: int = 2 * 1024 * 1024,
)

AsyncArenaHeroClient

The asynchronous client accepts the same arguments:

AsyncArenaHeroClient(
*,
api_key: str,
base_url: str = "https://api.arenahero.io",
websocket_url: str | None = None,
request_timeout: float = 5.0,
request_retries: int = 2,
reconnect_min_delay: float = 0.25,
reconnect_max_delay: float = 5.0,
max_message_size: int = 2 * 1024 * 1024,
)
ArgumentMeaning
api_keyRequired credential sent as Authorization: Bearer ….
base_urlHTTP API base. The command endpoint is derived from it.
websocket_urlWebSocket endpoint. When omitted, it is derived from base_url.
request_timeoutTimeout in seconds for one HTTP command attempt.
request_retriesNumber of safe HTTP retries after the first attempt.
reconnect_min_delayInitial WebSocket reconnect delay in seconds.
reconnect_max_delayMaximum WebSocket reconnect delay in seconds.
max_message_sizeMaximum accepted WebSocket message size in bytes.

The SDK reads none of these values from environment variables.

turns()

Synchronous: ArenaHeroClient.turns() -> Iterator[Turn]

Asynchronous: AsyncArenaHeroClient.turns() -> AsyncIterator[AsyncTurn]

Yields each actionable Tick once. Receipts are still processed internally and stored in latest_receipts.

events()

Synchronous: ArenaHeroClient.events() -> Iterator[Tick | Turn | Received]

Asynchronous: AsyncArenaHeroClient.events() -> AsyncIterator[Tick | AsyncTurn | Received]

Yields the complete application-level WebSocket stream:

EventWhen it appearsWhat to do
TickA new Tick has been announced.Record it; there is no state to act on yet.
Turn / AsyncTurnThe complete player state is ready.Read it, queue actions, then submit.
ReceivedA plan from AGENT or MANUAL was stored.Replace the earlier receipt for that source and Tick.

Only one events() or turns() iterator may consume a client at a time.

latest_receipts

from arena_hero import CommandSource


agent_receipt = game.latest_receipts.get(CommandSource.AGENT)
manual_receipt = game.latest_receipts.get(CommandSource.MANUAL)

This read-only mapping contains the latest current-Tick Received value for each source. It is cleared when a new Tick begins.

submit()

Submit an already-built complete plan:

accepted = game.submit(plan, idempotency_key="agent-10583-plan-1")

Asynchronous:

accepted = await game.submit(plan, idempotency_key="agent-10583-plan-1")

The return type is Accepted. Omitting idempotency_key makes the SDK generate one. If a network failure leaves the result uncertain, the SDK retries the exact same request bytes with the same key.

A custom key must contain 8–128 visible ASCII bytes with no spaces.

close()

Closes the active WebSocket and HTTP connection pool. Prefer with or async with, which closes the client automatically.

Turn

Turn and AsyncTurn expose the same state and control interface. Their only difference is that AsyncTurn.submit() must be awaited.

State

AttributeTypeMeaning
tickintTick this state and plan belong to.
statePlayerStateComplete authoritative player-state model.
resourcesintResources currently stored in the Core.
resource_capacityintCurrent storage capacity: max(10, state.population * 5).
resource_spaceintNon-negative space available for another deposit.
core`CoreNone`
unitstuple[Unit, ...]All controlled Units.
workerstuple[Worker, ...]Controlled Workers.
vanguardstuple[Vanguard, ...]Controlled Vanguards.
rangerstuple[Ranger, ...]Controlled Rangers.
visible_enemies`tuple[UnitViewCoreView, ...]`
terraintuple[TerrainView, ...]Visible obstacle and currently available resource batches.
resource_cellsfrozenset[Position]Resource points visible and available in this Turn only.
obstacle_cellsfrozenset[Position]Visible obstacle cells.
beaconChampionBeaconCurrent visibility-limited Beacon view.
eventstuple[ResolutionEvent, ...]Private results from the previous Tick.
planCommandPlanComplete plan currently queued in memory.

Position is tuple[int, int] in (x, y) order.

Production prices are dynamic. Call unit_cost(unit_type, state.population) to calculate the price shown for the current state. The server recalculates it when SPAWN resolves, after same-Tick self-destruction and combat deaths.

Methods

MethodMeaning
unit(unit_id)Find one controlled Unit by UUID or UUID string.
clear()Remove every queued Unit and Core action.
submit(idempotency_key=None)Submit the complete queued plan.

Calling an action on a Turn after a newer Tick arrives raises TurnClosedError. Never reuse controller objects from an old Turn.

Unit controls

All controlled Unit objects expose:

MemberType or signature
viewUnitView
idUUID
positionPosition
hpint
unit_typeUnitType
move(direction)Queue a one-cell move.
pickup_beacon()Pick up the Beacon on the current cell.
drop_beacon()Drop a carried Beacon.
heal()Recover HP after combat while sharing a cell with the owned stationary Core.
self_destruct()Remove this Unit before movement, with no refund or area damage.
wait()Queue an explicit WAIT.
clear_action()Remove this Unit from the queued plan.

Every Unit may have at most one queued action. A later method call replaces its earlier action.

Worker

Extra state:

MemberTypeMeaning
cargointResources currently carried.

Extra controls:

MethodMeaning
harvest()Try to consume the resource point on the current cell.
deposit()Deposit what fits while sharing a cell with the Core; any remainder stays on the Worker.

One successful natural harvest consumes one point. A normal winner carries 1 resource; a winner whose player holds the Beacon carries 2 from that same point. Cargo piles dropped by dead Workers are recovered first and never yield more than their remaining amount. If several eligible Workers harvest one cell, only the lowest UUID succeeds and the others receive HARVEST_FAILED with RESOURCE_DEPLETED.

A full Core resolves a deposit as DEPOSIT_FAILED with CORE_RESOURCE_FULL. When population falls, resources above the new capacity are destroyed immediately and reported as CORE_RESOURCE_OVERFLOW_DESTROYED.

Any Worker death from combat, Core destruction, or self-destruction leaves its complete cargo as a resource pile on the final cell.

Destroying an enemy Core may produce CORE_RESOURCES_CAPTURED. Read event.core_resource_capture for the typed amount stored, victim inventory, amount destroyed, and winner capacity. The highest-damage player wins; tied damage uses raw player UUID order. Overflow is destroyed, and all loot is lost if the winner's Core also dies in that combat Tick.

Vanguard

MethodMeaning
sweep(direction)Attack the adjacent cell in one direction.

Ranger

ranger.shoot_cell((120, 85))
ranger.shoot(target)
ranger.shoot(target_id, expected_cell=(120, 85))

shoot_cell(expected_cell) needs no current target. Movement resolves first; the server hits the lowest-HP hostile then in the cell, breaking ties by UUID, or reports SHOT_MISSED if the cell is empty.

target may be a visible Unit, Core, UnitView, or CoreView. The SDK copies its UUID and current position into the command. If you pass only a UUID or UUID string, expected_cell is required.

The server still resolves the shot using the game rules. Building a valid command does not guarantee a hit.

Core controls

The Core controller exposes:

MemberType or signature
viewCoreView
idUUID
positionPosition
hpint
shieldint
owner_usernamestr
spawn(unit_type)Spawn UnitType.WORKER, VANGUARD, or RANGER.
heal()Recover Core HP after combat.
repair_shield()Spend resources to repair shield.
start_move(direction)Start moving the Core.
cancel_move()Cancel current Core movement.
pickup_beacon()Pick up the Beacon on the current cell.
drop_beacon()Drop a carried Beacon.
self_destruct()Destroy the Core, its inventory, and all owned Units after combat, then enter the normal respawn flow.
wait()Queue an explicit WAIT.
clear_action()Remove the queued Core action.

The Core has one action slot. As with Units, a later call replaces its earlier queued action.

Core self-destruction is valid while moving and has no resource, Unit, or cooldown requirement. Combat destruction takes priority. Otherwise the Core destroys its inventory and army, drops cargo and the Beacon, awards no credit or loot, and immediately enters the normal respawn flow.

State models

PlayerState

FieldType
statusPlayerStatus
respawn_at_tick`int
resourcesint
populationint
champion_beaconChampionBeacon
objects`tuple[TerrainView
eventstuple[ResolutionEvent, ...]

See State model for field semantics and visibility rules.

Object models

ModelMain fields
UnitViewkind, id, controlled, position, hp, unit_type, cargo
CoreViewkind, id, controlled, owner_username, position, hp, shield, state, movement fields
TerrainViewkind, positions; RESOURCE positions are current visible availability
ChampionBeaconposition, status, carrier_id

The controller classes (Worker, Vanguard, Ranger, Core) are convenient views over controlled objects. Enemy objects remain immutable UnitView or CoreView models. Every Core exposes its owner's public owner_username without a leading @; Unit owners remain private.

ResolutionEvent

FieldType
event_idUUID
tickint
event_typestr
reason_code`str
actor_id`UUID
target_id`UUID
position`Position
values`dict[str, Any]
resource_amount`int
core_resource_capture`CoreResourceCapture
healing`HealingResult
harvest_source`HarvestSource

Event names and reason codes stay as strings so newer server values do not break an older SDK. See Resolution results for their meanings.

In particular, HARVEST_FAILED with RESOURCE_DEPLETED means a lower-UUID eligible Worker consumed the contested point in that Tick. resource_amount safely reads the positive amount from CORE_RESOURCES_CAPTURED, CORE_RESOURCE_OVERFLOW_DESTROYED, DEPOSIT_SUCCEEDED, WORKER_CARGO_DROPPED, and HARVEST_SUCCEEDED. core_resource_capture turns a well-formed CORE_RESOURCES_CAPTURED event into a typed model with amount, available, destroyed, and capacity. amount can be zero when no loot fits, and amount + destroyed == available. healing parses a successful Unit or Core heal into typed amount, post-heal hp, and cost values. Failed heals leave it as None; read reason_code. harvest_source is HarvestSource.DROPPED_CARGO identifies a recovery; HarvestSource.RESOURCE_NODE identifies an ordinary natural harvest. Unknown or inapplicable values return None.

Rule helpers

from arena_hero import (
CORE_RESOURCE_CAPACITY_PER_UNIT,
CORE_RESOURCE_MINIMUM_CAPACITY,
UNIT_BASE_COSTS,
UnitType,
core_resource_capacity,
unit_cost,
)

CORE_RESOURCE_CAPACITY_PER_UNIT is 5. CORE_RESOURCE_MINIMUM_CAPACITY is 10. core_resource_capacity(population) returns max(10, population * 5) and rejects a negative population. UNIT_BASE_COSTS is a read-only mapping with Worker 5, Vanguard 10, and Ranger 12. unit_cost(unit_type, population) applies the exact current production formula and rejects a negative population:

exponent = max(0, floor((population - 20) / 5) + 1)
price = round_half_up(base_price × (13 / 10)^exponent)

Only the final result is rounded. The 21st Unit is the first increased-price Unit. CORE_SPAWN_SUCCEEDED.values.cost and CORE_SPAWN_FAILED/INSUFFICIENT_RESOURCES.values.required are authoritative for the price actually used at settlement.

Tick, Received, and Accepted

ModelFields
Ticktick
Receivedtick, source, received_at, plan
Acceptedaccepted, tick, source, received_at

Accepted is the HTTP 202 acknowledgement. Received is the canonical plan broadcast over the WebSocket to every connected client for that player.

Command models

Most code should queue actions through a Turn. Advanced callers can construct the wire models directly:

from uuid import UUID

from arena_hero import CommandPlan, Direction, MoveAction


plan = CommandPlan(
tick=10583,
unit_actions={
UUID("9d3e4941-2816-4a39-a220-df8cd95e877d"): MoveAction(
direction=Direction.UP
)
},
)

accepted = game.submit(plan)

Public action models:

Unit actionRequired data
WaitActionnone
MoveActiondirection
HarvestActionnone
DepositActionnone
SweepActiondirection
ShootActionexpected_cell; optional target_id
PickupBeaconActionnone
DropBeaconActionnone
SelfDestructActionnone
HealActionnone
Core actionRequired data
WaitActionnone
SpawnActionunit_type
RepairShieldActionnone
HealActionnone
StartMoveActiondirection
CancelMoveActionnone
PickupBeaconActionnone
DropBeaconActionnone
SelfDestructActionnone

CommandPlan.unit_actions maps Unit UUIDs to actions. CommandPlan.core_action is one Core action or None.

Enums

EnumValues
DirectionUP, DOWN, LEFT, RIGHT
UnitTypeWORKER, VANGUARD, RANGER
PlayerStatusACTIVE, RESPAWNING
CoreStateNORMAL, MOVING
CommandSourceAGENT, MANUAL
BeaconStatusGROUND, CARRIED
HarvestSourceRESOURCE_NODE, DROPPED_CARGO

Direction.delta returns the corresponding (dx, dy) tuple.

Errors

All SDK exceptions inherit from ArenaHeroError.

ExceptionMeaning
ConfigurationErrorA constructor option or idempotency key is invalid, the client is closed, or two iterators were started.
AuthenticationErrorThe WebSocket handshake rejected the API key.
PolicyViolationErrorThe WebSocket closed with policy code 1008.
ProtocolErrorA server message does not match the public protocol.
APIErrorThe command API returned a structured rejection.
TransportErrorA network operation still failed after safe retries.
TurnClosedErrorCode tried to change a Turn after it stopped being current.
InvalidActionErrorA local target or action cannot be represented safely.

APIError exposes status_code, error, message, and details.

Gameplay failures are not Python exceptions. They arrive in the next Turn.events as ResolutionEvent values.

Connection behavior

The SDK:

  • sends the API key only in the Authorization header;
  • disables WebSocket message compression to match the server;
  • handles protocol Ping/Pong;
  • reconnects transient WebSocket failures with jittered exponential backoff;
  • stops reconnecting after close code 1008;
  • treats every state as a complete replacement;
  • safely retries uncertain command submissions with identical bytes and the same idempotency key.

The server's command window is global and may already be partly spent when a Turn arrives. Build and submit the plan promptly. Read Reliable command loop for the full timing and recovery rules.