Battle lifecycle
A battle is a state machine with a fixed number of seats. The recording, the live feed and the webhook all hang off a transition in that machine, so read this page before the API pages.
The six states
OPEN accepts players, RUNNING means seats are recording, and the remaining three states
are terminal.
Transitions are deny-by-default in both directions: a state with no outgoing entry cannot transition at all, and an unlisted target is rejected. A rejected transition leaves the status unchanged rather than erroring into a half-applied state.
| From | May become |
|---|---|
OPEN | STARTING, CANCELLED |
STARTING | RUNNING, COMPLETED, FAILED |
RUNNING | COMPLETED, FAILED |
COMPLETED / FAILED / CANCELLED | nothing, terminal |
STARTING reaches COMPLETED or FAILED without passing through RUNNING when a launch
resolves or fails before any seat starts recording. Both edges are reachable.
Seats
A battle has between 2 and 8 seats, fixed at creation. Each seat runs its own lifecycle, independent of the battle's.
| Seat status | Meaning |
|---|---|
EMPTY | Nobody in it |
FILLED | Claimed, but no game session yet, so it cannot play |
READY | Claimed with a game session URL, so it can play |
PLAYING | Recording in progress |
DONE | Recording finished, outcome known |
FILLED is not READY. A seat becomes READY only once it has a game session URL, which is
why the creator's own seat can land in either state.
The creator's seat
Creating a battle seats the creator at index 0. One field decides whether that seat lands
FILLED or READY:
creator_game_urlsupplied: seat 0 isREADYcreator_game_urlomitted: seat 0 isFILLED, and the creator must supply a URL later
A two-seat battle created without creator_game_url therefore does not auto-start when the
other player joins, because seat 0 is still FILLED.
Auto-start
There is no start endpoint. The lobby starts itself the moment every seat is READY,
checked after each seat change. An empty seat list never satisfies the check, so a zero-seat
battle can never auto-start.
The three ways to reach STARTING are therefore all seat changes:
- The last human player takes the last seat.
- The creator fills the remaining seats with bots.
- A player whose seat was
FILLEDupgrades it toREADYby supplying a URL.
What happens in RUNNING
On the transition, SlotBattle dispatches one recorder per seat, each an isolated invocation running a headless browser against the real game. Seats coordinate through Redis barriers so they start spinning together rather than drifting apart by however long each browser took to boot.
Each recorder captures its seat to HLS video and uploads segments as they are produced, which is what lets the audience watch a seat while that seat is still spinning. Each recorder reports its seat's terminal result over a signed internal callback, and SlotBattle aggregates all of them into exactly one terminal battle event.
The control plane never touches a browser or a video encoder. See Architecture.
Reaching a terminal state
| Terminal state | How a battle gets there |
|---|---|
COMPLETED | Every seat reported back |
FAILED | The launch failed, a seat failed unrecoverably, or the run window expired |
CANCELLED | The creator cancelled, the creator left, or the lobby window expired while the battle was still OPEN |
Cancelling is creator-only and OPEN-only. A non-creator asking to cancel is refused, and
so is cancelling a battle that already started. A creator who leaves cancels the battle:
leaving does not hand the lobby to someone else. A non-creator who leaves frees their seat
back to EMPTY.
The three windows
Three TTLs bound how long a battle may sit in a non-terminal state. A background sweep reconciles anything that overstays.
| Window | Bounds | Overstay becomes |
|---|---|---|
| Lobby | How long an OPEN lobby accepts players | CANCELLED |
| Starting | How long a battle may sit in STARTING | FAILED |
| Run | How long a RUNNING battle may take | FAILED |
These are the only tuning values changeable at runtime, from the admin console under
platform-scoped lobby settings. They take effect on the next battle, not the next deploy.
An untouched form field submits zero, and reading that literally would close every lobby the instant it opened. The form, the resolver and the service each reject zero as a window independently.
Two rules that shape integrations
One active battle per player, per casino. A player already seated in a non-terminal battle cannot create or join another one in the same tenant. The attempt is rejected rather than granted as a second seat.
Game session URLs never leave the process. The URL that lets a browser play a real-money session is encrypted at rest and appears in no HTTP response, log line, WebSocket frame, pub/sub message or webhook. The API never exposes it. See Keys and credentials.
Next
- Events: what each transition emits, and which events your front end can see
- Architecture: what runs where during
RUNNING - Battles API: the endpoints that drive this machine