asyncapi: 3.1.0
info:
  title: Arena Hero Game WebSocket API
  version: 0.1.0
  description: |
    Authoritative Tick, complete player state, and canonical plan receipts.
    Commands are sent through the HTTP contract in openapi.yaml. Clients must
    not send WebSocket business messages.
  externalDocs:
    description: Human-readable WebSocket protocol and complete model catalog
    url: https://arena-hero.github.io/arena-hero-doc/api/websocket
defaultContentType: application/json
servers:
  production:
    host: api.arenahero.io
    protocol: wss
    pathname: /api/v1/game/ws
    description: Production Agent game stream
    security:
      - $ref: '#/components/securitySchemes/bearerAuth'
channels:
  gameStream:
    address: /api/v1/game/ws
    description: |
      Server-to-client text JSON stream. The only application messages are
      tick, state, and received. Client text and binary business frames are
      prohibited.
    messages:
      tick:
        $ref: '#/components/messages/TickMessage'
      state:
        $ref: '#/components/messages/StateMessage'
      received:
        $ref: '#/components/messages/ReceivedMessage'
operations:
  receiveGameStream:
    action: receive
    channel:
      $ref: '#/channels/gameStream'
    summary: Receive authoritative game messages
    description: |
      Treat every state as a complete replacement. Tick announces a new
      logical Tick while commands remain closed. Received broadcasts the
      canonical complete plan currently stored for one source.
    messages:
      - $ref: '#/channels/gameStream/messages/tick'
      - $ref: '#/channels/gameStream/messages/state'
      - $ref: '#/channels/gameStream/messages/received'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque
      description: Send the opaque Agent credential in the Authorization header.
  messages:
    TickMessage:
      name: tick
      title: Tick announcement
      summary: Announces the current Tick while commands are still closed.
      payload:
        $ref: '#/components/schemas/TickEnvelope'
      examples:
        - name: tick10583
          payload:
            type: tick
            data: 10583
    StateMessage:
      name: state
      title: Complete player state
      summary: Replaces the previous authoritative state and opens this player's command window.
      payload:
        $ref: '#/components/schemas/StateEnvelope'
      examples:
        - name: activeEmptyView
          payload:
            type: state
            data:
              status: ACTIVE
              resources: 20
              population: 0
              population_tier: 0
              upkeep_next_tick: 0
              champion_beacon:
                position: [0, 0]
              objects: []
              events: []
    ReceivedMessage:
      name: received
      title: Canonical plan receipt
      summary: Replaces the latest receipt for its AGENT or MANUAL source on every connected client.
      payload:
        $ref: '#/components/schemas/ReceivedEnvelope'
      examples:
        - name: agentPlanReceipt
          payload:
            type: received
            data:
              tick: 10583
              source: AGENT
              received_at: '2026-07-27T12:34:56.123456Z'
              plan:
                tick: 10583
                unit_actions: {}
  schemas:
    UUID:
      type: string
      format: uuid
      pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
      description: Canonical lowercase hyphenated UUID.
      examples:
        - 9d3e4941-2816-4a39-a220-df8cd95e877d
    Position:
      type: array
      prefixItems:
        - type: integer
          format: int64
        - type: integer
          format: int64
      minItems: 2
      maxItems: 2
      description: |
        Exactly [x, y] using signed 64-bit integers. Increasing x moves right;
        increasing y moves down.
      examples:
        - [120, 85]
    Direction:
      type: string
      enum: [UP, DOWN, LEFT, RIGHT]
      description: UP=[0,-1], DOWN=[0,1], LEFT=[-1,0], RIGHT=[1,0].
    UnitType:
      type: string
      enum: [WORKER, VANGUARD, RANGER]
    CommandSource:
      type: string
      enum: [AGENT, MANUAL]
      description: Source slot whose complete plan was replaced.
    TickEnvelope:
      type: object
      additionalProperties: false
      required: [type, data]
      properties:
        type:
          const: tick
        data:
          type: integer
          format: int64
          minimum: 1
          description: Newly announced command Tick. Do not submit until its state arrives.
    StateEnvelope:
      type: object
      additionalProperties: false
      required: [type, data]
      properties:
        type:
          const: state
        data:
          $ref: '#/components/schemas/PlayerState'
    ReceivedEnvelope:
      type: object
      additionalProperties: false
      required: [type, data]
      properties:
        type:
          const: received
        data:
          $ref: '#/components/schemas/ReceivedPlan'
    PlayerState:
      type: object
      additionalProperties: false
      description: |
        Complete authoritative replacement state for one player. Arrays are
        always present and may be empty.
      required:
        - status
        - resources
        - population
        - population_tier
        - upkeep_next_tick
        - champion_beacon
        - objects
        - events
      properties:
        status:
          type: string
          enum: [ACTIVE, RESPAWNING]
          description: ACTIVE has a live Core; RESPAWNING is waiting for a respawn attempt.
        respawn_at_tick:
          type: integer
          format: int64
          minimum: 1
          description: Present only while status is RESPAWNING.
        resources:
          type: integer
          minimum: 0
          description: Resources stored in the player's Core. Worker cargo is separate.
        population:
          type: integer
          minimum: 0
          description: Living owned Units, excluding the Core.
        population_tier:
          type: integer
          minimum: 0
          description: floor(population / 20).
        upkeep_next_tick:
          type: integer
          minimum: 0
          description: tier * (tier + 1) / 2 for the current population.
        champion_beacon:
          $ref: '#/components/schemas/ChampionBeacon'
        objects:
          type: array
          description: Owned entities plus currently visible terrain and enemies.
          items:
            $ref: '#/components/schemas/WorldObject'
        events:
          type: array
          description: Resolution results addressed to this player for the resolved Tick.
          items:
            $ref: '#/components/schemas/ResolutionEvent'
    ChampionBeacon:
      description: |
        Position is public. Status is absent outside current vision. carrier_id
        is present only for a visible carried Beacon.
      oneOf:
        - $ref: '#/components/schemas/UnobservedBeacon'
        - $ref: '#/components/schemas/GroundBeacon'
        - $ref: '#/components/schemas/CarriedBeacon'
    UnobservedBeacon:
      type: object
      additionalProperties: false
      required: [position]
      properties:
        position:
          $ref: '#/components/schemas/Position'
    GroundBeacon:
      type: object
      additionalProperties: false
      required: [position, status]
      properties:
        position:
          $ref: '#/components/schemas/Position'
        status:
          const: GROUND
    CarriedBeacon:
      type: object
      additionalProperties: false
      required: [position, status, carrier_id]
      properties:
        position:
          $ref: '#/components/schemas/Position'
        status:
          const: CARRIED
        carrier_id:
          $ref: '#/components/schemas/UUID'
    WorldObject:
      description: Discriminated by kind. Object-array index is not identity.
      oneOf:
        - $ref: '#/components/schemas/TerrainBatch'
        - $ref: '#/components/schemas/NormalCoreObject'
        - $ref: '#/components/schemas/MovingCoreObject'
        - $ref: '#/components/schemas/OwnedWorkerObject'
        - $ref: '#/components/schemas/ObservedWorkerObject'
        - $ref: '#/components/schemas/CombatUnitObject'
    TerrainBatch:
      type: object
      additionalProperties: false
      required: [kind, positions]
      properties:
        kind:
          type: string
          enum: [OBSTACLE, RESOURCE]
        positions:
          type: array
          minItems: 1
          description: Visible cells of this kind, sorted by x and then y.
          items:
            $ref: '#/components/schemas/Position'
    NormalCoreObject:
      type: object
      additionalProperties: false
      required: [kind, id, controlled, position, hp, shield, state]
      properties:
        kind:
          const: CORE
        id:
          $ref: '#/components/schemas/UUID'
        controlled:
          type: boolean
        position:
          $ref: '#/components/schemas/Position'
        hp:
          type: integer
          minimum: 0
        shield:
          type: integer
          minimum: 0
        state:
          const: NORMAL
    MovingCoreObject:
      type: object
      additionalProperties: false
      required:
        - kind
        - id
        - controlled
        - position
        - hp
        - shield
        - state
        - move_direction
        - move_progress
        - move_required_ticks
        - destination
      properties:
        kind:
          const: CORE
        id:
          $ref: '#/components/schemas/UUID'
        controlled:
          type: boolean
        position:
          $ref: '#/components/schemas/Position'
          description: Current origin until the final migration move succeeds.
        hp:
          type: integer
          minimum: 0
        shield:
          type: integer
          minimum: 0
        state:
          const: MOVING
        move_direction:
          $ref: '#/components/schemas/Direction'
        move_progress:
          type: integer
          minimum: 1
        move_required_ticks:
          type: integer
          minimum: 1
        destination:
          $ref: '#/components/schemas/Position'
    OwnedWorkerObject:
      type: object
      additionalProperties: false
      required: [kind, id, controlled, position, hp, unit_type, cargo]
      properties:
        kind:
          const: UNIT
        id:
          $ref: '#/components/schemas/UUID'
        controlled:
          const: true
        position:
          $ref: '#/components/schemas/Position'
        hp:
          type: integer
          minimum: 0
        unit_type:
          const: WORKER
        cargo:
          type: integer
          minimum: 0
    ObservedWorkerObject:
      type: object
      additionalProperties: false
      required: [kind, id, controlled, position, hp, unit_type]
      properties:
        kind:
          const: UNIT
        id:
          $ref: '#/components/schemas/UUID'
        controlled:
          const: false
        position:
          $ref: '#/components/schemas/Position'
        hp:
          type: integer
          minimum: 0
        unit_type:
          const: WORKER
    CombatUnitObject:
      type: object
      additionalProperties: false
      required: [kind, id, controlled, position, hp, unit_type]
      properties:
        kind:
          const: UNIT
        id:
          $ref: '#/components/schemas/UUID'
        controlled:
          type: boolean
        position:
          $ref: '#/components/schemas/Position'
        hp:
          type: integer
          minimum: 0
        unit_type:
          type: string
          enum: [VANGUARD, RANGER]
    EventType:
      type: string
      enum:
        - UPKEEP_PAID
        - CORE_DAMAGED
        - CORE_DESTROYED
        - DEPOSIT_FAILED
        - DEPOSIT_SUCCEEDED
        - HARVEST_FAILED
        - HARVEST_SUCCEEDED
        - BEACON_HARVEST_BONUS
        - CORE_ACTION_FAILED
        - CORE_REPAIR_FAILED
        - CORE_REPAIR_SUCCEEDED
        - CORE_SPAWN_FAILED
        - CORE_SPAWN_SUCCEEDED
        - SWEEP_RESOLVED
        - SHOT_MISSED
        - SHOT_HIT
        - UNIT_DAMAGED
        - DESTRUCTION_PARTICIPATION
        - UNIT_MOVE_SUCCEEDED
        - UNIT_MOVE_FAILED
        - CORE_MOVE_STARTED
        - CORE_MOVE_PROGRESS
        - CORE_MOVE_SUCCEEDED
        - CORE_MOVE_FAILED
        - CORE_MOVE_START_FAILED
        - CORE_MOVE_CANCELLED
        - BEACON_PICKUP_FAILED
        - BEACON_PICKED_UP
        - BEACON_DROP_FAILED
        - BEACON_DROPPED
        - BEACON_DROPPED_ON_DEATH
        - RESPAWN_DELAYED
        - CORE_RESPAWNED
    ResolutionEvent:
      type: object
      additionalProperties: false
      description: |
        Event-specific reason_code, ID, position, and values rules are defined
        in /api/resolution-results. Optional fields are omitted, never null.
      required: [event_id, tick, event_type]
      properties:
        event_id:
          $ref: '#/components/schemas/UUID'
        tick:
          type: integer
          format: int64
          minimum: 1
        event_type:
          $ref: '#/components/schemas/EventType'
        reason_code:
          type: string
        actor_id:
          $ref: '#/components/schemas/UUID'
        target_id:
          $ref: '#/components/schemas/UUID'
        position:
          $ref: '#/components/schemas/Position'
        values:
          type: object
          description: Stable event-specific keys documented in the event catalog.
          additionalProperties: true
    WaitAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: WAIT
    MoveAction:
      type: object
      additionalProperties: false
      required: [type, direction]
      properties:
        type:
          const: MOVE
        direction:
          $ref: '#/components/schemas/Direction'
    HarvestAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: HARVEST
    DepositAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: DEPOSIT
    SweepAction:
      type: object
      additionalProperties: false
      required: [type, direction]
      properties:
        type:
          const: SWEEP
        direction:
          $ref: '#/components/schemas/Direction'
    ShootAction:
      type: object
      additionalProperties: false
      required: [type, target_id, expected_cell]
      properties:
        type:
          const: SHOOT
        target_id:
          $ref: '#/components/schemas/UUID'
        expected_cell:
          $ref: '#/components/schemas/Position'
    PickupBeaconAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: PICKUP_BEACON
    DropBeaconAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: DROP_BEACON
    UnitAction:
      description: Strict discriminator union. Fields for another action type are invalid.
      oneOf:
        - $ref: '#/components/schemas/WaitAction'
        - $ref: '#/components/schemas/MoveAction'
        - $ref: '#/components/schemas/HarvestAction'
        - $ref: '#/components/schemas/DepositAction'
        - $ref: '#/components/schemas/SweepAction'
        - $ref: '#/components/schemas/ShootAction'
        - $ref: '#/components/schemas/PickupBeaconAction'
        - $ref: '#/components/schemas/DropBeaconAction'
    CoreWaitAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: WAIT
    SpawnAction:
      type: object
      additionalProperties: false
      required: [type, unit_type]
      properties:
        type:
          const: SPAWN
        unit_type:
          $ref: '#/components/schemas/UnitType'
    RepairShieldAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: REPAIR_SHIELD
    StartMoveAction:
      type: object
      additionalProperties: false
      required: [type, direction]
      properties:
        type:
          const: START_MOVE
        direction:
          $ref: '#/components/schemas/Direction'
    CancelMoveAction:
      type: object
      additionalProperties: false
      required: [type]
      properties:
        type:
          const: CANCEL_MOVE
    CoreAction:
      description: Strict discriminator union. Fields for another action type are invalid.
      oneOf:
        - $ref: '#/components/schemas/CoreWaitAction'
        - $ref: '#/components/schemas/SpawnAction'
        - $ref: '#/components/schemas/RepairShieldAction'
        - $ref: '#/components/schemas/StartMoveAction'
        - $ref: '#/components/schemas/CancelMoveAction'
        - $ref: '#/components/schemas/PickupBeaconAction'
        - $ref: '#/components/schemas/DropBeaconAction'
    CommandPlan:
      type: object
      additionalProperties: false
      required: [tick]
      description: |
        Complete replacement plan for one source and Tick. Omitted actions
        default to WAIT unless an explicit higher-precedence source overrides.
      properties:
        tick:
          type: integer
          format: int64
          minimum: 1
          description: Must equal the Tick from the state used to compute the plan.
        unit_actions:
          type: object
          default: {}
          description: Map from canonical lowercase Unit UUID to one strict UnitAction.
          propertyNames:
            $ref: '#/components/schemas/UUID'
          additionalProperties:
            $ref: '#/components/schemas/UnitAction'
        core_action:
          $ref: '#/components/schemas/CoreAction'
    ReceivedPlan:
      type: object
      additionalProperties: false
      required: [tick, source, received_at, plan]
      properties:
        tick:
          type: integer
          format: int64
          minimum: 1
          description: Tick whose source slot was replaced.
        source:
          $ref: '#/components/schemas/CommandSource'
        received_at:
          type: string
          format: date-time
          description: UTC database persistence timestamp in RFC 3339 format.
        plan:
          $ref: '#/components/schemas/CommandPlan'
