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:
lobbyReady -> chooseInitiative -> mulligan -> openingResources -> actionServer order:
- Pick the initiative chooser from the match seed.
- Prompt that player to choose who starts with initiative.
- Shuffle both decks.
- Draw each player's opening hand.
- Prompt mulligans for each player that can take one.
- Prompt each player to choose opening resources.
- 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 shape | Effect |
|---|---|
Draw N more cards in your starting hand | Adds to that player's opening and mulligan hand size. |
Draw N less card(s) in your starting hand | Subtracts from that player's opening and mulligan hand size. |
You can't take a mulligan | Rejects chooseMulligan with takeMulligan: true. |
Choose N cards as your starting resources | Overrides 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:
{
"mulligan": {
"seatOneDecided": false,
"seatTwoDecided": false
}
}For a normal keep:
// `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 card ids from that seat's visible hand:
// Find this seat's private view so hidden hand card ids 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.
cardIds: activeSeatState?.hand?.slice(0, 2) ?? [],
},
})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:
- Setup completes and round 1 action phase starts.
- Snapshot prompt becomes
resolveAbilities. - Resolve the pending ability from source card
19611(Nabat Village). - Snapshot prompt becomes
chooseAbilityCardswithmin: 3andmax: 3. - Choose 3 cards from hand.
- Engine puts those cards on the bottom of the deck in chosen order.
- Normal
actionprompt appears for the initiative player.
Client pattern:
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,
},
})
}
}
if (
typeof matchSnapshot.prompt === 'object'
&& matchSnapshot.prompt
&& 'chooseAbilityCards' in matchSnapshot.prompt
) {
// Candidate card ids are already filtered to cards this ability can choose.
const cardChoice = matchSnapshot.pendingAbilityCardChoice
matchSession.sendCommand({
chooseAbilityCards: {
abilityId: matchSnapshot.prompt.chooseAbilityCards.abilityId,
cardIds: cardChoice?.candidateCardIds.slice(0, cardChoice.min) ?? [],
},
})
}Render this like any other pending ability. Do not assume the first action phase always starts with an action prompt.
Format variants
premier, eternal, sealed, and draft share the same two-player setup and round runtime after deck validation. 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.
