ALGOHOL Docs

SciFi — Observation

The player observation stream is a WebSocket. Each player-frame message carries a frame, and the frame carries the observation. The state field is universal (see Observation envelope). The fields described here are SciFi-specific.

Everything in observation reflects only what the unit can currently perceive. Walls, terrain, vision arc, and line of sight constrain all entity and zone visibility.


Frame layout

Session constants and per-tick data live at different levels of the frame:

{
  "gaii": "<your-gaii>",
  "playerId": "player-1",
  "arenaBaseline": { "ruleset": "scifi", "boundary": {…}, "agentStats": {…}, … },
  "arenaBaselineRevision": "<hash>",
  "state":  { "phase": "live", "generation": …, "sessionId": "…", … },
  "observation": {
    "arena": { "allowedActions": […], "modeParams": {…}, "worldZones": […] },
    "self":  {…},
    "visibleTiles": […], "visiblePlayers": […], "events": […], "actionResults": […]
  }
}
Where What Cadence
frame.arenaBaseline Session constants: ruleset, mode, mapWidth, mapHeight, minRequiredPlayers, boundary, agentStats, towParams, recoveryStationEnergyPerSecond Sent once, then only when it changes
frame.observation.arena Per-tick arena state: allowedActions, modeParams, worldZones Every frame

arenaBaseline is omitted from frames where it has not changed, so a client that reads it only from the current frame loses boundary and agentStats after the first frame. Cache it from whichever frame last carried it and refresh when arenaBaselineRevision changes.

Over HTTP, GET /api/arenas/:arenaId/players/:gaii/frame wraps the same frame:

{ "ok": true, "data": { "frame": { … } } }

frame.arenaBaseline

Session constants. Cache these; do not expect them on every frame.

Field Type Description
ruleset string Always "scifi"
mode string Objective mode: "last-man-standing", "navigation", "king-of-the-hill", etc.
mapWidth, mapHeight number Arena size in tiles
minRequiredPlayers number Players needed before the match starts
boundary object Arena extent and elimination thresholds (see below)
agentStats object Unit parameters for this session (see below)
towParams object Tow and grab envelope; present when grab is allowed
recoveryStationEnergyPerSecond number Energy restored per second inside a recovery zone

boundary

Field Type Description
kind string "walled" (standard) or "open" (open-football only)
minX, maxX number Arena tile span on X axis
minY, maxY number Arena tile span on Y axis
safeMinX, safeMaxX number X span of the walkable interior
safeMinY, safeMaxY number Y span of the walkable interior
penalty string "none" (walled) or "out-of-bounds" (open)

Every standard SciFi arena is "walled" with penalty: "none". The outermost ring of tiles is solid wall — impassable and sight-blocking — so a unit cannot walk off the edge. Arenas generated with an irregular footprint are enclosed the same way: the eroded boundary is re-walled.

The safe* values are the inside face of that wall, not a danger margin.

Out-of-bounds elimination comes from falling, not from walking: a unit is eliminated when z < -2.0 (void floor), or when its position exceeds the outer boundary by more than 1 m. In practice that means stepping off an elevated-platform or into a gap tile.

agentStats

Unit capability parameters for this session. Read on first observation; changes are re-broadcast if the arena is reconfigured.

Field Type Description
energyMax number Maximum energy reserve
energyRegenPerSecond number Passive energy regeneration rate. 0 in the default arena configuration, in which case recovery zones are the only energy source.
motorEnergyCost number Cost coefficient for move: energy spent each physics tick is motorEnergyCost × appliedAcceleration × dt, not a flat per-tick charge. appliedAcceleration is near zero at a steady cruise speed and rises toward accelerationMps2 while speeding up, slowing down, or blocked by terrain. Nonzero (0.06) in the default SciFi configuration.
aimEnergyCost number Energy cost of aim. 0 in the default configuration.
moveSpeedMetersPerAction number Maximum movement speed
accelerationMps2 number Acceleration rate
bodyTurnRateDegPerSec number Rotation speed
reactionTimeSeconds number Action latency budget
projectOverheatThreshold number Heat level that triggers combat channel lockout
projectOverheatLockSeconds number How long the combat channel stays locked after overheat
projectHeatDecayPerSecond number Heat dissipation rate between shots
projectCooldownSeconds number Minimum time between project actions, independent of heat
siphonCooldownSeconds number Minimum time between siphon actions
droneMaxCount number Maximum drone supply per match
sightRange number The arena's diagonal length in meters, a fixed reference value. self.resolvedMaxSightRangeMeters is the value that actually gates visibility and varies with the unit's position.
aimTurnRateDegPerSec number Torso turn rate, derived from mass, radius and torque. Limits how fast aim can re-centre the vision arc.
projectHeatPerShot number Heat added to self.projectHeatLevel per project action. Independent of projectHeatDecayPerSecond below.

Movement (move) and torso rotation (aim) each carry their own heat model, independent of the project overheat system above and independent of each other:

Field Type Description
motorHeatFactor number Heat gained per unit of energy the move motor spends. 0 disables motor heat entirely.
motorCoolingRate number Heat lost per second, continuously, regardless of whether the unit is moving
motorSafeHeat number Once accumulated motor heat reaches this level, movement acceleration is derated by motorThermalDerate
motorThermalDerate number Fraction of movement acceleration lost while at or above motorSafeHeat (0.4 = 40% less acceleration)
motorCriticalHeat number Once accumulated motor heat reaches this level, motorDamageRate energy is drained from self.energyReserve every second until it cools back down
motorDamageRate number Energy drained from self.energyReserve per second while motor heat is at or above motorCriticalHeat — sustained overheating can eliminate the unit the same way running out of energy does
aimHeatFactor number Heat gained per unit of energy the aim motor spends. 0 in the default configuration — aiming carries no thermal cost by default.
aimCoolingRate number Heat lost per second, continuously, regardless of whether the unit is turning
aimSafeHeat number Once accumulated aim heat reaches this level, aim torque is derated by aimThermalDerate
aimThermalDerate number Fraction of aim torque lost while at or above aimSafeHeat
aimCriticalHeat number Once accumulated aim heat reaches this level, aimDamageRate energy is drained from self.energyReserve every second
aimDamageRate number Energy drained from self.energyReserve per second while aim heat is at or above aimCriticalHeat

Heat accumulates as heat = max(0, heat + energyUsed × heatFactor − coolingRate × dt) every physics tick, where energyUsed is the energy the motor/aim actuator actually spent that tick. A 0 for any *SafeHeat/*CriticalHeat field disables that threshold. Heat and any resulting damage are internal engine state, not exposed directly in the observation — only their effect (reduced effective acceleration/torque, and self.energyReserve draining faster than the plain motorEnergyCost/aimEnergyCost numbers would predict) is externally visible.

towParams

Always present in SciFi (towing is always enabled).

Field Type Description
grabEdgeGapMeters number Maximum edge distance to initiate a grab
tetherClearanceMeters number Clearance margin for the tow link
breakDistanceMeters number Distance at which the tow tether snaps
loadCoefficient number Movement and energy penalty factor per kg of cargo

observation.arena

Per-tick arena state, present on every frame. Only these three keys are here; everything else is in frame.arenaBaseline above.

Field Type Description
allowedActions string[] Action types the engine will accept right now
modeParams object Mode-specific parameters (see below)
worldZones Zone[] Zones currently visible to this unit

allowedActions

Not constant across a match:

  • Combat actions are absent when the arena runs with combat disabled.
  • During a scenario pause the list collapses to ["withdraw"] only.

worldZones

Only zones the unit can currently observe. A zone becomes visible when it is within detection range and has line of sight, so the set of known zones grows as the unit explores.

Field Type Description
id string Zone id
type string One of the zone types below
position {x, y, z} Zone center
radiusMeters number Zone radius
sizeX, sizeY number | undefined Footprint for non-circular zones
label string | undefined Display label
sequenceOrder number | undefined Order index for navigation-ordered goals
portalPairId string | undefined Id of the paired portal zone
surfaceNormal {x, y, z} | undefined Surface tilt, for zones on ramps or relief
ramps array | undefined Ramp entry points, for elevated-platform zones
Zone type Description
recovery Energy recovery station. Standing inside restores energy at arenaBaseline.recoveryStationEnergyPerSecond. self.recoveryContact tells you when you are inside one.
portal Teleporter. Entering moves the unit to the paired portal. Portals come in pairs.
drone-resource Drone resupply zone. Grants additional drone supply when occupied.
elevated-platform Raised platform area. Improves line of sight; stepping off the edge eliminates the unit.
navigation-objective Navigation mode goal. Reaching all goals ends the match.
king-of-the-hill Zone control area. Occupying scores toward the win condition.

Zones are often placed close to the arena wall, which is not a hazard.

modeParams

Varies by mode. Fields guaranteed present:

Mode objectiveType Additional fields
last-man-standing "elimination"
navigation "reach-goal" goalCount, orderedGoals
navigation-ordered "reach-goal" goalCount, orderedGoals: true
king-of-the-hill "zone-control" scoreTarget, playerScores
football "football" See below

Team-keyed fields (teamProgress, teamScores, teamTileCounts) appear whenever any player has a team assigned — including a single cooperative team — not only in multi-team matches.

football

Field Type Description
goalTarget number Score needed to win outright, independent of the timer
winCondition string "timer" (only the clock ends it), "goal-target" (only reaching the score ends it), or "both"
combat boolean Whether combat actions are enabled in this football configuration
incapacitation string "none", "respawn-delay", "permanent", or "freeze-in-place" — see below
openness string "open" or "cluttered" arena layout
fallPenaltySeconds number Time penalty applied for falling off the field
resetEnergyOnKickoff boolean Whether energy resets to full at each kickoff
teamScores object Goals scored, keyed by team id
sides {sideA, sideB} | null The two team ids, once both are assigned
actionsPaused boolean true during a goal-reset pause; matches allowedActions collapsing to ["withdraw"]
restartNotice object | null {text, remainingSeconds, durationSeconds} during a kickoff/restart countdown
playerPenaltyUntilMs object Per-player penalty expiry timestamps, keyed by player id

incapacitation governs what happens to a unit at zero energy, and is the field to read before relying on hack or on a corpse persisting: "permanent" behaves like other modes (the unit stays down, visibleCorpses persists, hack has a stable target); "respawn-delay" and "none" bring the unit back into play, so a corpse from these is transient or absent — hack may be present in allowedActions without ever having a usable target. Other modes don't set incapacitation in modeParams at all; they always behave as "permanent".


observation.self

Authenticated unit state.

Identity

Field Type Description
id string In-arena player id, e.g. "player-1". Not the GAII. This is how other units refer to this one, and the value passed as targetPlayerId / targetId.
gaii string The algorithm's GAII — the value used in request bodies and endpoint paths
name string Display name
team string | undefined Team identifier
ready boolean Whether the unit has joined this session
alive boolean Whether the unit is alive

Position and orientation

Field Type Description
position {x, y, z} World position in meters
lowerBodyFacingDegrees number Direction the body is facing; the reference for move in body frame
upperBodyFacingDegrees number Direction the torso is facing; the centre of the vision arc, and the reference for aim and project
physicsVelocity {x, y, z} Current velocity
physicsGrounded boolean Whether the unit is standing on a surface
surfaceNormal {x, y, z} Normal of the surface underfoot; tilts on ramps and relief
lastMoveSpeedMetersPerSecond number Speed actually achieved last tick
resolvedMaxMoveSpeedMetersPerSecond number Speed ceiling right now, after terrain and cargo penalties
bodyRadiusMeters, bodyHeightMeters number Physical dimensions

The two facings are independent, so the unit can move in one direction while looking in another.

Vision

Field Type Description
resolvedMaxSightRangeMeters number Maximum sight range — the distance to the arena's farthest corner
activeVisionRangeMeters number Sight range in effect right now
activeVisionArcDegrees number Field-of-view arc in effect right now, centred on upperBodyFacingDegrees
visionStartedAtMs number When the current active scan began; 0 when passive
visionExpiresAtMs number When the current scan lapses back to passive; 0 when passive
seenPlayerIds string[] Ids of players this unit has observed at some point this match

The Vision section below describes what these mean in practice.

Energy and combat

Field Type Description
energyReserve number Current energy. Reaching zero eliminates the unit. The ceiling is arenaBaseline.agentStats.energyMax; there is no maxEnergy field in self.
shieldPool number Shield energy currently absorbing damage
projectHeatLevel number Current weapon heat
projectOverheatSecondsRemaining number Time left on an overheat lock
projectCooldownSecondsRemaining number Time left before the next projection
siphonCooldownSecondsRemaining number Time left before the next siphon
dronesRemaining number Remaining drone supply
stunExecutesRemaining number Remaining partial-stun executes (halves the effectiveness of some actions; does not block anything)
fullStunExecutesRemaining number Remaining full-stun executes (blocks all actions)

Recovery, tow and hacking

Field Type Description
recoveryContact boolean Whether the unit is currently inside a recovery zone. Engine-computed; no need to derive it from zone geometry.
recoveryOverlapRatio number How much of the unit is inside the zone, 0–1; scales the recovery rate
recoveryEnergyGainLastUpdate number Energy gained from recovery on the last tick
towingTargetId string | undefined Id of the entity currently being towed
towedById string | undefined Id of the unit towing this unit
allowsTow boolean | undefined Whether this unit has consented to being towed
hackingCorpseId string | undefined Id of the enemy corpse currently being hacked
activeChannels array Actions resolved on the last tick, with their energy cost and labels

agentStats is not carried inside self — it lives in frame.arenaBaseline.agentStats.

Two further self fields — lastActionDebug (latest command resolution breakdown, including exact energy cost) and lastContactFeedback (physics contact/collision detail) — are omitted by default and can be requested per stream. See Observation envelope — Optional detail fields.


Vision

A unit that has never scanned still has a full field of view. Passive vision is always on and costs nothing:

Range self.resolvedMaxSightRangeMeters — the distance to the arena's farthest corner
Arc 180°, centred on self.upperBodyFacingDegrees

Range is therefore rarely the limit. Arc, walls and line of sight are: nothing behind the unit is visible, and nothing through a wall or rift-wall tile.

aim re-centres the arc and costs agentStats.aimEnergyCost energy (0 by default). move changes position and costs agentStats.motorEnergyCost scaled by the motor's applied acceleration — see agentStats.motorEnergyCost above for the exact shape; a steady cruise costs little, accelerating or being blocked costs close to the stated rate.

scan replaces the passive arc with one you specify — up to 360° — for durationSeconds, and costs rangeMeters × (arcDegrees / 360) × 0.4 × durationSeconds. Two constraints follow from the implementation:

  • aim cancels an active scan, resetting vision to the passive 180° arc.
  • rangeMeters cannot exceed resolvedMaxSightRangeMeters, and rangeMeters: 0 is a zero-metre scan, not an unlimited one. Omit the field to scan at full range.

visibleTiles accumulates as terrain enters the arc, so the map is built by moving and aiming rather than by scanning from a fixed position.


Stun

Two independent mechanics, both counted in executes — resolved commands, not physics ticks or seconds — and both capped at 6.

Full stun (self.fullStunExecutesRemaining) blocks all actions. Triggered by falling: 1 execute per meter fallen, capped at 6. While active, submitting a command discards it entirely — the first action in the command gets outcome full-stunned in actionResults, the rest of the command's actions get no result at all, and events logs "Full stun: N executes remaining. Actions blocked." Submitting a command consumes one execute regardless of what the command contained; not submitting one does not.

Partial stun (self.stunExecutesRemaining) halves the effectiveness of move speed, scan range, and the energyAmount of project, defend and siphon. It does not block or reject anything: the action resolves normally at half magnitude, success/outcome in actionResults are unaffected, and the only visible signal is events logging "Stunned: 50% effectiveness. N stuns remaining." Triggered by:

Trigger Executes
Colliding with a wall at ≥0.8 m/s impact velocity 1
Being hit by a projectile 3

A trigger raises stunExecutesRemaining to the higher of its current value and the trigger's amount (capped at 6) — repeated hits don't stack additively. Wall collisions only trigger a new stun while neither stun counter is already above zero; projectile hits always apply. Submitting a command consumes one execute from whichever counter is active, same as full stun.


Visible entities

All entity lists reflect what the unit can currently observe. Range, line of sight, and scan state determine what appears.

Entity id paths are not uniform across these lists — most are a direct [].id, but visiblePlayers nests it one level deeper:

List Id path
self self.id
teammates teammates[].id
visiblePlayers visiblePlayers[].player.id
visibleCorpses visibleCorpses[].id
visibleDrones visibleDrones[].id

observation.visiblePlayers

Living observed players within detection range and line of sight. The authenticated unit is represented by self; dead players are represented by visibleCorpses.

Each entry has three fields:

Field Type Description
player object The observed unit, limited to externally observable fields (below)
visible boolean Whether the unit is currently observed (entries are only emitted when true)
relative object Offset from the authenticated unit: dx, dy, dz, manhattanDistance, chebyshevDistance, horizontalDirection, verticalDirection, heightDirection, bearing, elevationAngleDegrees

player exposes externally observable fields for another unit:

player field Type Description
id string Player id
position {x, y, z} World position
upperBodyFacingDegrees number Facing direction
team string | null Team identifier
bodyRadiusMeters number Physical radius
alive boolean Always true here (dead units appear in visibleCorpses)

observation.visibleCorpses

Dead players within detection range and line of sight. See the visibleCorpses structure (id, team, position, bodyRadiusMeters, towedById).

observation.visibleProjectiles

Field Type Description
id string Projectile id
position {x, y, z} Current position
velocity {x, y, z} Current velocity
ownerId string Player who fired it

observation.visibleDrones

The authenticated unit's own drones are always visible regardless of distance. Other players' drones are visible within normal detection range.

Field Type Description
id string Drone id
ownerId string Owning player id
position {x, y, z} Current position
phase string "scout", "seek", or "chase"

observation.visibleObjects

World objects (crates, tow targets, etc.) within detection range.

observation.visibleTiles

Tiles within the unit's current sight cone. Terrain enters the observation stream as the unit discovers it, so this list grows as you explore.

Field Type Description
x, y number Tile coordinates
z number Tile elevation; platforms sit above the base floor
terrain string Terrain type (see below)
heightMeters number Height of the tile's solid volume
blocksMovement boolean Whether the unit can walk into this tile
blocksSight boolean Whether the tile blocks line of sight through it
frictionCoefficient number Surface friction; low values are slippery
absorptionCoefficient number | undefined How much projectile energy the tile absorbs
surfaceKind "flat" | "ramp" | undefined Whether the tile can be climbed
rampDirection "north" | "south" | "east" | "west" | undefined Uphill direction of a ramp
sizeX, sizeY number | undefined Footprint for tiles larger than one cell
riftWallDurability number | undefined Remaining durability (rift-wall tiles only)

blocksMovement is the field to route around. Movement on a straight bearing will stall against a wall.

Tile x/y are spaced 1 meter apart and share the same coordinate space as self.position — round a unit's position to the nearest integer to find the tile it currently occupies. arenaBaseline.mapWidth/mapHeight are given in the same meters/tiles, since the arena is a square grid with one tile per meter.

Terrain types:

Terrain Description
floor Passable ground
wall Impassable solid. The arena's outer ring is always wall.
gap Void opening. Passable but unsupported — entering drops the unit and eliminates it.
hazard Passable but drains energy continuously while occupied.
rift-wall Destructible wall with finite durability. Projectiles reduce durability; at zero it becomes passable.

Action feedback

Neither of these is returned by the action POST. Both arrive on the next frame after a command resolves.

observation.actionResults

One structured entry per action in the last command.

Field Type Description
commandId string The command this result belongs to
actionCount number Position of this action within the command
actionType string Which action this is a result for
channel string | undefined Channel classification
success boolean Whether the action took effect
outcome string scanned, projected, out-of-energy, ruleset-disallowed, …
details string | undefined Extra text for the outcome
targetPlayerId string | undefined Target of siphon or hack
blocked boolean | undefined Whether physics prevented the action from having its full effect
resolvedSpeedMetersPerSecond number | undefined Speed actually applied, for move
rangeMeters, arcDegrees number | undefined Resolved values, for scan
energySpent, energyGained number | undefined Energy delta for this action
projectileId string | undefined Id of the projectile fired, for project
droneId string | undefined Id of the deployed drone, for deploy-drone
directionDegrees, directionFrame, elevationDegrees number, string, number | undefined Resolved aim/fire direction
actuatorFeedback object | undefined Physics result of a move, see below

actuatorFeedback

Present on move results. This is the field to read for real-time collision detection: visibleTiles only reflects terrain the unit has already seen, so a unit can be blocked by a tile it has never observed, and blocksMovement gives no signal for that case.

Field Type Description
requested number Speed requested by the action
applied number Speed the motor attempted to apply after acceleration/energy limits
achieved number Speed actually achieved this tick
error number applied − achieved
saturation number 0–1, how close the motor is to its output limit
blocked boolean Whether a physics contact prevented movement this tick
slip number | undefined Fraction of applied force lost to low friction
energyUsed number | undefined Energy spent this tick
heat number | undefined Motor heat added this tick
damage number | undefined Energy drained this tick from motor overheat
stress number | undefined Load factor from cargo or terrain
fuelUsed number | undefined Reserved, unused in SciFi

observation.events

Human-readable log lines for this tick, including the exact energy a command cost:

"Execute e55dedfd: cost 6.28e, reserve 93.72."
"Arena started. 1 players ready. You may now submit actions."

For a structured version of the same energy breakdown, open the stream with actionDebug=1 and read self.lastActionDebug.

observation.incomingHits

Projectiles that struck this unit since the last observation.

Field Type Description
type string Always "projectile"
projectileId string Projectile id
ownerId string Player who fired it
position {x, y, z} Impact position
originPosition {x, y, z} | undefined Where it was fired from
bearingDegrees number | undefined Direction the shot came from
strength number | undefined Energy carried by the projectile
absorbed number | undefined Energy absorbed by shield or terrain

observation.contacts and observation.capabilities

contacts reports physics contact state for this tick, independent of whether a move was submitted. capabilities reports actuator feedback for movement, aim, body and combat: how the engine executed the last intent, which resolves cases where a command was accepted but the unit did not move as expected.

contacts:

Field Type Description
grounded boolean Whether the unit is standing on a surface
groundNormal {x, y, z} Normal of the surface underfoot
contactNormals {x, y, z}[] Normals of all current physics contacts
blockedDirections {x, y, z}[] Directions currently obstructed by a contact — the general-purpose collision signal, always populated regardless of the last submitted action
frictionAvailable number Friction currently usable, after load
frictionCoefficient number Friction of the surface underfoot
slip number Fraction of applied force lost to low friction
recentEvents array Recent contact events: kind ("wall", "player-alive", "player-dead"), impactVelocityMps, normal, otherEntityId

capabilities.movement, capabilities.aim, capabilities.body and capabilities.combat each have the same shape as actionResults[].actuatorFeedback above, and update every tick regardless of whether a new command was submitted.


Teammates and signals

observation.teammates

Living teammates. Always visible, regardless of range or line of sight.

Field Type Description
id string Teammate's player id
position {x, y, z} World position
energyReserve number The teammate's current energy
shieldPool number The teammate's current shield
alive boolean Whether the teammate is alive
bodyRadiusMeters number Physical radius
upperBodyFacingDegrees number | undefined Direction the teammate's torso is facing
towingTargetId, towedById string | undefined Tow state

observation.teamSignals

Signals sent by teammates since the last observation.

Field Type Description
fromId string Sender's player id
kind string Signal label (max 16 chars, engine delivers as-is)
position {x, y} | undefined Sender's position at send time
targetId string | undefined Player reference attached by the sender

teamSignals is delivered exactly once: a signal appears in the one frame built after it was sent, and is gone from every frame after that — there is no backlog, and resync does not bring back a signal from a frame the client never read. A client that wants to reliably catch every signal must read teamSignals on every frame it receives, not just the frames following its own submitted actions.

observation.interceptedSignals

Signals intercepted via hack. Present when self.hackingCorpseId is set. Same structure as teamSignals, and delivered the same one-frame-only way. These originate from the hacked unit's team.