Skip to content

Setup & Variant Rules ​

Setup is driven by the same snapshots and commands as the rest of the match. Clients should follow snapshot.prompt and avoid hardcoding Premier defaults when card text changes setup.

Setup flow ​

After both seats call ready(), setup advances through this prompt chain:

txt
lobbyReady -> chooseInitiative -> mulligan -> openingResources -> action

Server order:

  1. For game 1, pick the initiative chooser from the match seed.
  2. For later two-player best-of-three games, prompt the prior game's loser; after a draw, prompt the player who held initiative.
  3. Prompt that player to choose who starts with initiative.
  4. Shuffle both decks.
  5. Draw each player's opening hand.
  6. Prompt mulligans for each player that can take one. The prompt goes out to every player at once and the answers are accepted in any order.
  7. Prompt each player to choose opening resources, again in any order.
  8. Start round 1 action phase.

Use openingHandDrawn, mulliganResolved, and openingResourcesChosen events for timelines or animations. Use snapshots as current state.

Setup text ​

The server reads setup-modifying text from canonical leader and base cards. Current shared support covers:

Text shapeEffect
Draw N more cards in your starting handAdds to that player's opening and mulligan hand size.
Draw N less card(s) in your starting handSubtracts from that player's opening and mulligan hand size.
You can't take a mulliganRejects chooseMulligan with takeMulligan: true.
Choose N cards as your starting resourcesOverrides opening resource count when a live card uses that shape.

Examples from the canonical card-data cache:

  • Colossus (19576) draws 5 instead of the default 6 and mulligans back to 5.
  • Nabat Village (19611) draws 9, cannot mulligan, then resolves its first action phase setup trigger.

Mulligan handling ​

The mulligan prompt shape does not list which player is allowed to mulligan. It lists which seats have decided:

json
{
  "mulligan": {
    "seatOneDecided": false,
    "seatTwoDecided": false
  }
}

Every seat that has not decided may answer, in any order. That is a deliberate deviation from CR 2.1.e / 11.2.1, which sequence the mulligan from the initiative player — see "Known CR deviation" in RULES.md. The only rejection is a second answer from a seat that already decided.

For a normal keep:

ts
// `false` is a keep decision and is valid even when mulligan is forbidden.
matchSession
.
sendCommand
({
chooseMulligan
: {
takeMulligan
: false,
}, })

If a card says that player cannot mulligan, takeMulligan: true is rejected. Send false for that seat to continue setup.

Opening resources ​

When the prompt becomes openingResources, choose match-local card instance ids from that seat's visible hand. Commands address individual cards in play by cardInstanceId, never by the shared cardId — see card ids:

ts
// Find this seat's private view so hidden hand cards are available.
const 
activeSeatState
=
matchSnapshot
.
players
.
find
(
player
=>
player
.
seat
=== 'one')
matchSession
.
sendCommand
({
chooseOpeningResources
: {
// Example chooses the first two visible cards; real UI should use player input.
cardInstanceIds
: (
activeSeatState
?.
hand
?? [])
.
slice
(0, 2)
.
map
(
handCard
=>
handCard
.
cardInstanceId
),
}, })

Default setup requires 2 resources. The engine validates the exact count for that player's setup profile. If future card text changes the count, submitting the wrong number returns invalid_selection.

After a seat chooses resources, its private snapshot shows those cards in the resource row. Opponent snapshots keep resource identities redacted according to hidden-information rules.

First action phase triggers ​

Some bases have text that resolves after setup but before the first action. The engine now has an explicit first-action-phase trigger window.

For Nabat Village:

  1. Setup completes and round 1 action phase starts.
  2. Snapshot prompt becomes resolveAbilities.
  3. Resolve the pending ability from source card 19611 (Nabat Village).
  4. Snapshot prompt becomes chooseAbilityCards with min: 3 and max: 3.
  5. Choose 3 cards from hand.
  6. Engine puts those cards on the bottom of the deck in chosen order.
  7. Normal action prompt appears for the initiative player.

Client pattern:

ts
if (
  typeof 
matchSnapshot
.
prompt
=== 'object'
&&
matchSnapshot
.
prompt
&& 'resolveAbilities' in
matchSnapshot
.
prompt
) { // Pending ability ids are stable handles for the command payload. const
pendingAbility
=
matchSnapshot
.
pendingAbilities
[0]
if (
pendingAbility
) {
matchSession
.
sendCommand
({
resolveAbility
: {
abilityId
:
pendingAbility
.
id
,
// These lists are always sent. They stay empty for an ability that // captures nothing, has a single mode, and needs no target.
captureSelections
: [],
modeIndices
: [],
targets
: [],
}, }) } } const
cardChoice
=
matchSnapshot
.
pendingChoice
if ( typeof
matchSnapshot
.
prompt
=== 'object'
&&
matchSnapshot
.
prompt
&& 'chooseAbilityCards' in
matchSnapshot
.
prompt
&&
cardChoice
?.
type
=== 'abilityCard'
) { // Candidate cards are already filtered to cards this ability can choose.
matchSession
.
sendCommand
({
chooseAbilityCards
: {
abilityId
:
matchSnapshot
.
prompt
.
chooseAbilityCards
.
abilityId
,
cardInstanceIds
:
cardChoice
.
candidateCards
.
slice
(0,
cardChoice
.
min
)
.
map
(
candidateCard
=>
candidateCard
.
cardInstanceId
),
// Credits are only spent by abilities that price a choice in Credits.
creditTokensToSpend
: 0,
}, }) }

Render this like any other pending ability. Do not assume the first action phase always starts with an action prompt.

Format variants ​

premier, eternal, peasant, preview, open, sealed, and draft share the same two-player setup and round runtime after deck validation; they differ only in which cards their deck validation accepts. trilogy adds pre-game deck bans and deck selection before setup starts. twinSuns uses the multiplayer runtime.

See Deckbuilding & Formats for request shapes and validation rules.

Released under the MIT License.