Deckbuilding & Formats
Use format on matches.create() to pick the deckbuilding rules for the match. If you omit it, the server uses premier.
// Use real card ids; the server canonicalizes variant prints.
const premierMainDeck = [
45, 35, 100, 105, 110, 115, 120, 125, 130,
135, 140, 145, 1228, 1230, 1232, 1243, 1244,
].flatMap(cardId => [cardId, cardId, cardId])
const premierMatchAccess = await simulatorClient.matches.create({
// Seed is optional, but useful for reproducible examples.
seed: 7,
format: 'premier',
hostDeck: {
leader: 46102, // Leia Organa - Someone Who Loves You
base: 308, // Echo Base
cards: premierMainDeck,
sideboard: [1260, 1265, 1270],
},
})The chosen format is stored on every snapshot:
console.log(premierMatchAccess.snapshot.format) // "premier"Supported values
| Wire value | Match runtime | Deck validation |
|---|---|---|
premier | Supported two-player runtime. | Constructed Premier rules. |
eternal | Supported two-player runtime. | Eternal constructed rules. |
sealed | Supported two-player runtime. | Limited single-deck rules. |
draft | Supported two-player runtime. | Limited single-deck rules. |
trilogy | Supported two-player series runtime. | Three-deck Trilogy rules. |
twinSuns | Supported 2-4 player runtime. | Twin Suns singleton rules. |
Unsupported runtime shapes return HTTP 400 with unsupported_format. This includes Premier, Eternal, Sealed, Draft, and Trilogy with playerCount greater than 2, and Twin Suns outside 2 through 4.
import { EngineHttpError } from '@my-swu/simulator-client'
try {
// Non-Twin Suns formats currently run as two-player matches.
await simulatorClient.matches.create({
format: 'premier',
playerCount: 3,
hostDeck: {
leader: 46102, // Leia Organa - Someone Who Loves You
base: 308, // Echo Base
cards: premierMainDeck,
},
})
}
catch (error) {
if (error instanceof EngineHttpError) {
// The code tells the UI this is a format/runtime limit, not deck syntax.
console.log(error.status, error.code) // 400, "unsupported_format"
}
}Deck input
Every player submits a DeckInput:
type DeckInput = {
leader?: number | null
base?: number | null
cards: number[]
sideboard?: number[]
}Rules:
leadermust be a leader card id.basemust be a base card id.cardscontains main-deck card ids.sideboarddefaults to[].- Main-deck and sideboard cards must be units, events, or upgrades.
- Card ids are canonicalized through the server's card-data cache before validation, so variant ids count with their canonical card identity.
See card ids for the difference between card ids and match-local runtime ids such as unitId.
If you have a my-swu JSON deck export with SET_001 card ids, convert it before match creation:
const deckJsonExport = {
leader: { id: 'SOR_001', count: 1 },
base: { id: 'SOR_025', count: 1 },
deck: [{ id: 'SOR_010', count: 50 }],
}
const importedDeck = await simulatorClient.cards.convertDeckJson(deckJsonExport)
await simulatorClient.matches.create({
format: 'premier',
hostDeck: importedDeck,
})The host deck, join deck, and AI opponent deck all validate against the same match format. A rejected deck fails the HTTP call before the match advances.
Premier and Eternal
Premier and Eternal validation enforce:
- Exactly one leader.
- Exactly one base.
- At least 50 main-deck cards.
- At most 10 sideboard cards.
- No more than 3 copies of a card across main deck plus sideboard.
- Main-deck and sideboard cards must be units, events, or upgrades.
Eternal additionally allows all released, non-preview cards except official Eternal suspended cards. The current suspended list rejects War Juggernaut and every canonical print of IG-2000.
Sideboard cards are hidden information. Snapshots expose only sideboardCount, not the sideboard card ids. Best-of-three Premier and Eternal matches accept sideboard format events between finished games.
Limited
sealed and draft matches use the current two-player match runtime with Limited deck validation:
// Limited decks use smaller minimums and have no sideboard.
const sealedMatchAccess = await simulatorClient.matches.create({
format: 'sealed',
hostDeck: {
leader: 46102, // Leia Organa - Someone Who Loves You
base: 308, // Echo Base
cards: Array.from({ length: 30 }, () => 45), // Alliance X-Wing
},
})Limited validation enforces:
- Exactly one leader.
- Exactly one base.
- At least 30 main-deck cards.
- No sideboard.
- Main-deck cards must be units, events, or upgrades.
- No copy limit.
- With Limited provenance, the leader and main deck must come from the pool; the base may come from the pool or
commonBases.
For generated pools and draft pods, use Limited Events instead of creating a multiplayer match:
// Generate a draft pod first, then use its completed pool as provenance.
const draftEventAccess = await simulatorClient.limitedEvents.create({
kind: 'draft',
setCode: 'SOR',
playerCount: 4,
seed: 7,
})
const draftSeatAccess = await simulatorClient.limitedEvents.join(draftEventAccess, {})
await simulatorClient.limitedEvents.applyCommand(draftSeatAccess, {
type: 'pickLeader',
// Leader choices come from the private viewer snapshot for this seat.
cardId: draftSeatAccess.snapshot.viewer!.leaderChoices[0]!,
})Limited Events support:
- Sealed events for 1-12 players, with 6 standard boosters per player.
- Draft events for 4-12 players, with 3 standard boosters per player.
- Standard booster shape: 1 leader, 1 base slot, 9 commons, 3 uncommons, 1 rare-or-legendary slot, and 1 wildcard/foil slot.
- Draft flow: leaders pass right twice, then non-leader packs pass left, right, and left.
- Hidden picks: active pack drafts show only the current pack to the viewer; drafted cards are visible during review gates and after completion.
- Common bases:
deckSource.commonBaseslists every common base from the selected set, available to every player in Draft and Sealed deckbuilding.
Completed Limited Events expose each player's leaders, pool, packs, and common bases through the viewer deckSource. Clients can use that source as provenance when submitting normal two-player draft or sealed match decks.
Trilogy
trilogy uses a three-deck set per player:
- Exactly 3 decks in
formatSetup. - No sideboards.
- Unique leaders and bases across the set.
- At least 50 main-deck cards per deck.
- No more than 3 combined main-deck copies of any card across the set.
- Same Eternal card pool and suspension rules.
Before game 1, each seat bans one opponent deck, then selects one of their own available decks. Deck choices stay hidden until both seats have selected. In best-of-three play, a deck that wins a game cannot be selected again.
Twin Suns
twinSuns is the supported multiplayer match format. It accepts playerCount from 2 through 4 and uses leaders instead of leader:
const twinSunsMainDeck = [
15, 20, 25, 30, 35, 40, 45, 50, 70, 75, 80, 85, 90, 100, 105, 110,
115, 120, 125, 130, 135, 140, 145, 150, 155, 160, 165, 170, 175, 180,
185, 190, 210, 215, 220, 240, 245, 250, 255, 260, 265, 267, 268, 269,
270, 272, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338,
339, 340, 341, 342, 343, 344, 345, 346,
]
const twinSunsMatchAccess = await simulatorClient.matches.create({
format: 'twinSuns',
playerCount: 4,
hostDeck: {
leaders: [46102, 46107], // Leia Organa; Darth Vader
base: 308, // Echo Base
cards: twinSunsMainDeck,
},
})Twin Suns validation enforces:
- Exactly two leaders.
- Exactly one base.
- At least 80 main-deck cards.
- No sideboard.
- Main-deck cards must be units, events, or upgrades.
- No more than one copy of any canonical card identity across leaders, base, and main deck.
See Multiplayer for joining every seat and driving Twin Suns counters.
Joining and AI
Seat two must join with a deck legal for the host match format:
// Join decks validate against the host match format.
await simulatorClient.matches.join(premierMatchAccess, {
deck: {
leader: 46107, // Darth Vader - Unstoppable
base: 309, // Tarkintown
cards: premierMainDeck,
},
})AI opponents use the same rule:
await simulatorClient.matches.create({
format: 'draft',
// AI deck follows the same limited deck rules as the human host deck.
hostDeck: {
leader: 46102, // Leia Organa - Someone Who Loves You
base: 308, // Echo Base
cards: Array.from({ length: 30 }, () => 45), // Alliance X-Wing
},
opponent: {
kind: 'ai',
difficulty: 'easy',
deck: {
leader: 46107, // Darth Vader - Unstoppable
base: 309, // Tarkintown
cards: Array.from({ length: 30 }, () => 35), // TIE/ln Fighter
},
},
})Debug checklist
When deck validation fails:
- Check
EngineHttpError.code. - For
invalid_selection, readEngineHttpError.message; it names the rule that failed. - Confirm the requested
format. - Confirm
sideboardis omitted forsealedanddraft. - Confirm Premier and Eternal copy counts include both
cardsandsideboard. - Confirm Eternal and Trilogy decks do not use unreleased, preview, or suspended cards.
- Confirm Trilogy setup has exactly 3 decks with no sideboards.
- Confirm every card id is from the card cache, not local invented fixture data.
