Skip to content

Match Lifecycle

A match is created over HTTP, optionally joined over HTTP, then driven through one SDK MatchSession per seat.

Create

ts
// Match creation validates the host deck before issuing seat credentials.
const 
premierHostDeck
= {
leader
: 46102, // Leia Organa - Someone Who Loves You
base
: 308, // Echo Base
cards
:
Array
.
from
({
length
: 50 }, () => 45), // Alliance X-Wing
} const
hostAccess
= await
simulatorClient
.
matches
.
create
({
// Fixed seed keeps setup and shuffles deterministic.
seed
: 7,
format
: 'premier',
hostDeck
:
premierHostDeck
,
})

The response includes:

  • matchId: public id for routing.
  • seat: assigned seat.
  • seatToken: private credential for this seat.
  • snapshot: first seat-aware state projection.

Treat seatToken like an auth credential. Do not log it publicly or send it to the opponent.

Join

ts
// Seat two submits its own legal deck and receives its own private token.
const 
premierGuestDeck
= {
leader
: 46107, // Darth Vader - Unstoppable
base
: 309, // Tarkintown
cards
:
Array
.
from
({
length
: 50 }, () => 35), // TIE/ln Fighter
} const
guestAccess
= await
simulatorClient
.
matches
.
join
(
hostAccess
, {
deck
:
premierGuestDeck
,
})

Seat two receives its own token and private snapshot. Deck validation happens before the seat joins.

Twin Suns matches with playerCount above 2 keep using join() until every seat has private access. The creator is seat 'one'; joins fill 'two', 'three', then 'four'.

Session

ts
// Use private access here because the socket must authenticate one seat.
const 
matchSession
=
simulatorClient
.
session
.
create
(
hostAccess
, {
handshakeTimeoutMs
: 10_000,
}) await
matchSession
.
connect
()

connect() resolves only after the socket opens, the server sends welcome, and the first snapshot arrives. The SDK encodes the seat token in the socket URL and validates every inbound server frame.

Create one session for each player seat in multiplayer games. A seat token authenticates exactly one player projection; do not reuse one token for a different player UI.

Subscribe

ts
const 
stopSnapshot
=
matchSession
.
on
('snapshot',
matchSnapshot
=> {
// Always render from the latest authoritative snapshot.
render
(
matchSnapshot
)
}) const
stopError
=
matchSession
.
on
('error',
errorPayload
=> {
// Engine rejections and SDK decode failures both arrive here.
report
(
errorPayload
.
code
,
errorPayload
.
message
)
})

Events:

EventPayloadUse
connectionState'idle' | 'connecting' | 'open' | 'closed'Transport lifecycle.
welcomeWelcomePayloadSeat identity.
snapshotGameStateAuthoritative UI state.
eventGameEventTimeline, animations, notifications.
errorErrorPayloadEngine rejection or SDK decode failure.
pongPongPayloadPing latency checks.
messageServerMessageRaw typed envelope.

Wait for state

ts
await 
matchSession
.
waitForSnapshot
(
// Resolve when the match has entered normal action-phase play.
matchSnapshot
=>
matchSnapshot
.
phase
=== 'action',
{
timeoutMs
: 15_000 },
)

If the current snapshot already matches, the promise resolves immediately.

Reconnect

ts
// Reuses the original matchId and seatToken, then requests a fresh snapshot.
await 
matchSession
.
reconnect
({
handshakeTimeoutMs
: 5_000 })

Reconnect closes the current socket, opens a new one with the same credentials, waits for the normal handshake, then sends syncRequest. Registered listeners stay attached. If the handshake fails, connectionState becomes closed.

Close

ts
matchSession
.
close
()

close() detaches socket listeners and emits connectionState: 'closed' once.

Released under the MIT License.