Skip to main content

Arena Hero Python

The official typed Python SDK for Arena Hero.

You own the game loop. The SDK connects to the HTTP and WebSocket APIs, parses authoritative state, exposes control methods for every Unit type and Core, and submits one complete plan when you call submit().

Documentation

  • Quickstart: installation, synchronous and asynchronous loops, state access, Unit control, and local development.
  • API reference: every client option, Turn field, control method, model, event, enum, and exception.
  • Game rules and wire API: authoritative gameplay, HTTP, and WebSocket behavior.

Install

Python 3.11 or newer is required.

Install the published release:

pip install arena-hero

Synchronous game loop

from getpass import getpass

from arena_hero import ArenaHeroClient, Direction


api_key = getpass("Arena Hero API key: ")

with ArenaHeroClient(api_key=api_key) as game:
    for turn in game.turns():
        for worker in turn.workers:
            if worker.position in turn.resource_cells:
                worker.harvest()
            else:
                worker.move(Direction.RIGHT)

        turn.submit()

worker.harvest() and worker.move() only queue actions on the current Turn. They do not make network requests. turn.submit() sends the complete queued plan in one HTTP request.

Asynchronous game loop

import asyncio
from getpass import getpass

from arena_hero import AsyncArenaHeroClient, Direction


async def play(api_key: str) -> None:
    async with AsyncArenaHeroClient(api_key=api_key) as game:
        async for turn in game.turns():
            for vanguard in turn.vanguards:
                vanguard.sweep(Direction.LEFT)

            await turn.submit()


asyncio.run(play(getpass("Arena Hero API key: ")))

The synchronous and asynchronous clients use the same models and control methods:

  • ArenaHeroClient with for and turn.submit()
  • AsyncArenaHeroClient with async for and await turn.submit()

Every Core state includes its owner's public owner_username without a leading @. Display it as f"@{core.owner_username}"; Unit owners remain private.

Control interfaces

Every controlled object exposes its authoritative state through .view. Calling another method for the same object replaces its earlier queued action on that Turn.

Object Available methods
Worker move, harvest, deposit, pickup_beacon, drop_beacon, heal, self_destruct, wait, clear_action
Vanguard move, sweep, pickup_beacon, drop_beacon, heal, self_destruct, wait, clear_action
Ranger move, shoot, pickup_beacon, drop_beacon, heal, self_destruct, wait, clear_action
Core spawn, heal, repair_shield, start_move, cancel_move, pickup_beacon, drop_beacon, self_destruct, wait, clear_action

Useful Turn data:

turn.tick
turn.state
turn.resources
turn.resource_capacity
turn.resource_space
turn.core
turn.units
turn.workers
turn.vanguards
turn.rangers
turn.visible_enemies
turn.resource_cells
turn.obstacle_cells
turn.beacon
turn.events
turn.plan

Core storage has a minimum capacity of 10, then accepts 5 resources per living Unit: resource_capacity is max(10, state.population * 5). resource_space is the non-negative amount a Worker can still deposit. If population falls, Core resources above the new capacity are destroyed immediately.

Production prices depend on the current living population. Use unit_cost(UnitType.WORKER, turn.state.population) (or the other Unit type) to calculate the exact server price. Units 1-20 use base prices 5/10/12; Units 21-25 cost 30% more, and the multiplier rises again after every five Units.

turn.resource_cells includes visible natural points and Worker cargo piles. Pile amounts are not exposed; a partially recovered pile remains in the set. Use event.resource_amount and event.harvest_source on turn.events to read private cargo-drop, recovery, deposit, and overflow-destruction amounts without unpacking values yourself.

Worker

worker = turn.workers[0]

worker.move(Direction.UP)
worker.harvest()
worker.deposit()  # stores what fits; any remainder stays on the Worker
worker.pickup_beacon()
worker.drop_beacon()
worker.heal()
worker.self_destruct()
worker.wait()
worker.clear_action()

If a Worker dies through combat, Core destruction, or self_destruct(), its complete cargo amount becomes a recoverable resource pile on its final cell.

Vanguard

vanguard = turn.vanguards[0]

vanguard.move(Direction.DOWN)
vanguard.sweep(Direction.RIGHT)
vanguard.pickup_beacon()
vanguard.drop_beacon()
vanguard.heal()
vanguard.self_destruct()
vanguard.wait()

Ranger

Pass a visible Unit or Core to derive both the target UUID and expected cell:

ranger = turn.rangers[0]
enemy = turn.visible_enemies[0]

ranger.shoot(enemy)

To fire at a cell even when it is currently empty, omit a specific target:

ranger.shoot_cell((120, 85))

Movement resolves first. The server hits the lowest-HP hostile at that cell, breaking ties by UUID, or reports SHOT_MISSED if the cell is empty. Use shoot() when you intentionally want to track one specific object instead.

The server can hit a target 1-3 cells away on the same row, column, or exact 45-degree diagonal. A relative offset such as (3, 3) is in range; (2, 1) is not. Only obstacles on the intermediate shot cells block fire. Units, Cores, and obstacles beside a diagonal do not block it.

When you only have a UUID, provide the expected cell:

from uuid import UUID

target_id = UUID("8d60b600-78d4-4aba-83fd-4e5e27b88c9d")
ranger.shoot(target_id, expected_cell=(120, 85))

Core

The Core is absent only during initial admission or a failed-spawn retry. Core destruction has no cooldown and normally creates a replacement in the same Tick.

if turn.core is not None:
    turn.core.spawn(UnitType.WORKER)
    turn.core.heal()
    turn.core.repair_shield()
    turn.core.start_move(Direction.RIGHT)
    turn.core.cancel_move()
    turn.core.pickup_beacon()
    turn.core.drop_beacon()
    turn.core.self_destruct()
    turn.core.wait()

heal() resolves after combat and spends one Core resource per HP actually recovered, up to full HP. A Unit must still be alive on the same cell as its own stationary Core. Unit heals spend resources before the Core action. It is valid to queue a heal while HP is full or resources are currently empty: damage and captured Core resources from that Tick are resolved first. A fatal hit cannot be healed.

self_destruct() is always valid, including while the Core is moving, and has no resource, Unit, or cooldown requirement. Movement and combat resolve first. If an enemy attack destroys the Core, normal attack credit and resource capture apply. Otherwise the surviving Core destroys its inventory and all owned Units, drops Worker cargo and the Champion Beacon at their actual positions, and immediately enters the normal respawn flow without awarding loot.

Complete event stream

game.turns() yields each actionable Tick once. Use game.events() when the program also needs tick notices and canonical plans submitted by this or another client:

from arena_hero import Received, Tick, Turn


with ArenaHeroClient(api_key=api_key) as game:
    for event in game.events():
        if isinstance(event, Tick):
            current_tick = event.tick
        elif isinstance(event, Turn):
            # Queue actions, then submit one complete plan.
            event.submit()
        elif isinstance(event, Received):
            latest_received = event

The latest current-Tick AGENT and MANUAL plans are also available through game.latest_receipts. A new Received value replaces the earlier value for that source.

Use either events() or turns() on a client, not both at the same time.

Direct plan submission

Advanced callers can build and submit the exact public protocol model:

from uuid import UUID

from arena_hero import CommandPlan, Direction, MoveAction


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

receipt = game.submit(plan)

For asynchronous code, use await game.submit(plan).

Connection behavior

The SDK:

  • sends the API key only in the Authorization header;
  • never reads credentials or endpoints from environment variables;
  • disables WebSocket message compression to match the server contract;
  • uses protocol Ping/Pong automatically;
  • reconnects with jittered exponential backoff from 250 ms to 5 seconds;
  • retries an uncertain HTTP submission with the same idempotency key and exact request bytes;
  • stops on WebSocket close code 1008;
  • treats each state as a complete replacement;
  • preserves unknown resolution event names and reason codes as strings.

The default backend is https://api.arenahero.io. Pass test endpoints explicitly:

client = ArenaHeroClient(
    api_key=api_key,
    base_url="http://localhost:8080",
    websocket_url="ws://localhost:8080/api/v1/game/ws",
)

Errors

All SDK exceptions inherit from ArenaHeroError.

Exception Meaning
ConfigurationError Invalid constructor option or idempotency key
AuthenticationError WebSocket authentication was rejected
PolicyViolationError WebSocket closed with code 1008
ProtocolError The server returned an invalid public-protocol message
APIError The command API returned a structured rejection
TransportError A network operation failed after safe retries
TurnClosedError Code tried to change a Turn after a newer Tick arrived
InvalidActionError A local action target or owned Unit was invalid

Dynamic gameplay failures are not exceptions. They arrive in the next Turn.events as normal resolution results.

Development

This project uses uv, a src/ layout, and a locked development environment.

uv sync --locked --all-groups
uv run ruff format --check .
uv run ruff check .
uv run ty check
uv run bandit -r src -c pyproject.toml
uv run pytest
uv build

The public game protocol is documented at doc.arenahero.io.

License

Apache License 2.0

Metadata

Release files for arena-hero 0.2.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for arena-hero 0.2.9
File Size Uploaded Provenance
arena_hero-0.2.9.tar.gz 87.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for arena-hero 0.2.9
File Interpreter ABI Platform
arena_hero-0.2.9-py3-none-any.whl Python 3 none any Details

Total release size: 116.3 kB

Release files / arena_hero-0.2.9.tar.gz

Download URL arena_hero-0.2.9.tar.gz
Size 87.1 kB
Tags Source
SHA-256 checksum
How to use checksums
620c5fbd5a7dd1d5c32c145ee8f3f184538d0a7c08ee69e7eda34459916db8d1
BLAKE2b-256 checksum
How to use checksums
32dcd6a83d22e6df55eb09bd205af0aa3fd0508bd92eb0df619f8f75779818eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.

Transparency log

Release files / arena_hero-0.2.9-py3-none-any.whl

Download URL arena_hero-0.2.9-py3-none-any.whl
Size 29.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
822e4de0999e8f26e8c8beb910c786513fcc5be6e426175d9b5e2e3e32b899d9
BLAKE2b-256 checksum
How to use checksums
68208e7a20b23be76526c840d0aea4ac62e4402a48dbd707f38106635789cb99
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.

Transparency log

Security for arena-hero 0.2.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for arena_hero-0.2.9.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page