Skip to content

Engine Concepts

Short primer on the wire contract used by swu-simulator. Read this before the WebSocket or HTTP references.

Match access

A player session is identified by three values:

  • matchId — public match identifier (UUID).
  • seatone or two.
  • seatToken — private token authorizing one seat on one match.

matches.create() and matches.join() return all three plus the first private snapshot for that seat. Treat seatToken like an auth credential — never expose it to the opponent or log it publicly.

Snapshot vs event

The socket stream sends five kinds of server messages:

  • welcome — confirms seat identity on the socket.
  • snapshot — full current projected game state for that seat.
  • event — incremental public gameplay event.
  • error — protocol or game rejection.
  • pong — response to a client ping.

App rule:

  • Use snapshot as the source of truth for current UI state.
  • Use event for timelines, animations, and toast-style updates.

Hidden information

Snapshots are seat-aware.

  • Your own hand, resource row, and opponent-visible state are present.
  • Opponent private hidden zones are redacted on your snapshot.
  • Sideboards stay hidden. Snapshots expose sideboardCount, not sideboard card ids.
  • The public GET /api/matches/{id} projection redacts private zones for both seats.

Card ids vs runtime ids

Fields named cardId, cardIds, leader, leaders, base, cards, and sideboard contain card ids from the simulator card cache. Fields like unitId, upgradeId, and abilityId are match-local runtime handles. See card ids for examples and lookup guidance.

Match format

Every snapshot includes format, the value chosen on matches.create(). Supported runtime formats are premier, eternal, sealed, draft, trilogy, and twinSuns. Non-Twin Suns formats currently require exactly two players; Twin Suns accepts 2-4 players.

See Deckbuilding & Formats for deck rules, sideboards, Limited validation, and unsupported-format handling.

Enum shapes

The Rust server uses serde's default enum representations. Two conventions show up on the wire:

  • ClientMessage, ServerMessage — internally tagged with type.
  • GameCommand, PromptState, GameEvent — externally tagged.

Concrete examples:

json
{ "type": "ready" }
json
{ "type": "command", "payload": "pass" }
json
{ "type": "command", "payload": { "chooseMulligan": { "takeMulligan": false } } }
json
"lobbyReady"
json
{ "chooseInitiative": { "seat": "one" } }

The generated TypeScript types match these shapes exactly. The runtime validators reject anything else.

Prompt lifecycle

Every snapshot carries a prompt value. It tells the UI which player input the engine is waiting for. Common transitions:

lobbyReady → chooseInitiative → mulligan → openingResources →
action (loop) → regroupResource → action (next round) … → gameOver

Pair snapshot.prompt with the playActions, actionAbilities, and availableAttacks arrays on the same snapshot to render legal input.

Setup-modifying leader/base text can change opening hand size, mulligan availability, and opening resource count. First-action-phase triggers can also insert resolveAbilities before the first action prompt. See Setup & Variant Rules.

Released under the MIT License.