July 5, 2026
I've been building a draft assistant for Magic: The Gathering Arena Cube. The goal is simple: given the current pack, my previous picks, the cards I've seen, and the cube list, recommend a pick.
This post summarizes the current state of the project: the models, the offline results, the first Monte Carlo tree search experiments, and one real-pack example where several model variants make noticeably different choices.
A draft pick is represented with only information available to the drafter:
The base model is a contextual pick policy trained from public 17lands Powered Cube draft logs. Each card is represented by combining a card-specific learned vector with features extracted from Scryfall metadata and rules text: mana value, color and color-identity bits, card-type bits, rarity, power/toughness fields, keyword indicators, and oracle-text features.
In model terms, the representation is roughly:
card_repr = learned_card_embedding + alpha * MLP(scryfall_features)
where alpha starts high and is annealed to a nonzero floor. This lets common cube cards specialize through their learned embeddings while still giving reasonable representations to changed or unseen cube cards from their rules text and metadata. The policy scores every card in the current pack and is trained to imitate human picks.
There are several model variants in the post, and the names mean different things:
The pick model is a cube-context contextual preference ranker. For a cube with C cards, every training example has a C-dimensional current-pack mask, pool count vector, seen-but-unpicked count vector, and cube mask. The model first computes card_repr for every card in the cube. It then uses separate DeepSets encoders for three unordered sets:
pool_repr = DeepSet(cards in my pool)
seen_repr = DeepSet(cards I saw but passed)
cube_repr = DeepSet(cards in the cube list)
The draft context is the concatenation of those three set embeddings, an explicit WUBRG color-commitment vector from the pool, and learned embeddings for pack number and pick number. A small MLP maps that context to the same dimension as a card representation. Each card is scored by a dot product plus a card bias:
context = MLP(pool_repr, seen_repr, cube_repr, color_commit, pack_idx, pick_idx)
score(card | context) = dot(context, card_repr(card)) + bias(card)
The policy is trained over the in-pack support only. The main loss is a contextual preference / triplet ranking loss: the logged human pick should score at least a margin above every other card in the pack.
loss = mean_over_unpicked_cards max(0, margin - score(picked) + score(unpicked))
In the current implementation the representation dimension is 128, the hidden dimension is 256, and the default margin is 0.2. The Scryfall feature contribution alpha anneals from 1.0 to a floor of 0.3 over early training. I also use random cube-list masking during training so the model is robust to partial or changing Arena Cube lists.
The outcome model is separate. It takes a built deck and sideboard, encodes maindeck and sideboard cards with DeepSets over the same card representation, concatenates rank/event/color covariates, and predicts game win probability. I train this on 17lands game data, both per-game and aggregated by (draft_id, build_index). For deployment, a simple heuristic deckbuilder maps a draft pool to a 40-card deck before scoring.
The game-card bonus model is an even simpler outcome model:
logit(win_probability) = intercept + sum(card_in_maindeck * card_bonus) + covariates
I tested pair-interaction variants, but they overfit relative to the card-only model. The learned top bonuses were plausible Powered Cube cards: Ancestral Recall, Time Walk, Black Lotus, Sol Ring, Mana Crypt, and the Moxen.
Finally, I generated static preference pairs for DPO. For a logged pick state, if one in-pack candidate has a sufficiently higher learned game-card bonus than another, that pair becomes (preferred, rejected). DPO fine-tunes the pick policy toward those game-data preferences while anchoring it to the human-pick reference policy with a KL penalty. This gives the safe/aggressive trade-off in the table below.
| Model / setting | Human top-1 | Human top-3 | Game-data pref. acc. |
|---|---|---|---|
| Human continued, no value rerank | 0.6240 | 0.9093 | 0.5494 |
| Human continued + value rerank | 0.6234 | 0.9077 | 0.6062 |
| Safer game-data DPO + value rerank | 0.6161 | 0.9048 | 0.6406 |
| Aggressive game-data + value rerank | 0.5092 | 0.8411 | 0.7262 |
The trade-off is clear: the safer game-data model keeps most of the human-pick accuracy while improving game-data preference agreement. The aggressive model optimizes game-data preferences much harder, but it no longer behaves like a human drafter and often makes suspicious-looking picks.
A lot. I tested the human-continued model by removing or corrupting the previous-pick context on a 20k held-out sample:
| Context | Top-1 | Top-3 | Same top pick as full context |
|---|---|---|---|
| full pool + seen | 0.6168 | 0.9070 | 1.0000 |
| no pool, keep seen | 0.4268 | 0.7545 | 0.5116 |
| keep pool, no seen | 0.6160 | 0.9065 | 0.9409 |
| no pool, no seen | 0.4070 | 0.7384 | 0.4856 |
| shuffled pool | 0.3507 | 0.6533 | 0.4088 |
The pool matters enormously. The seen-but-unpicked channel matters much less in the current model.
The next experiment was to add Monte Carlo tree search at inference time. This is not training. The model is already trained; MCTS is a search procedure that evaluates each legal pick by simulating possible continuations of the draft and scoring the resulting pools.
For each candidate pick, the search forces that pick, samples plausible future packs from the cube, completes the draft with the policy, builds/scores the final pool with the value model, and combines rollout value with the policy prior.
Because MTGA logs do not reveal hidden packs or opponent picks, this is an information-set approximation. It knows my pack and my pool, but future packs are sampled rather than perfectly reconstructed.
| Simulations | p50 | p95 | Mean |
|---|---|---|---|
| 10 | 418 ms | 574 ms | 378 ms |
| 25 | 942 ms | 1370 ms | 850 ms |
| 50 | 1825 ms | 2697 ms | 1642 ms |
So 25 simulations is a reasonable default for live search-based reranking.
--simulations 25
--rank-by final
--root-policy-weight 0.15
--root-value-weight 1.0
--root-value-mode prob
--rollout-temperature 0.0
--cpuct 1.5
The following trace uses real packs from the held-out 17lands eval data. These are not independently sampled fake packs. They are the packs as seen by a real drafter, already depleted by previous picks. Pack sizes go from 14 down to 7.
Caveat: the later packs are real for the human's actual draft path. If a model makes a different earlier pick, the later packs are not a perfect counterfactual.
Pick 1, pack size 14
Arena of Glory; Blood Crypt; Containment Priest; Endurance; Godless Shrine; Grief; Omnath, Locus of Creation; Ouroboroid; Skyclave Apparition; Snapcaster Mage; Sundering Titan; The Wandering Emperor; Wishclaw Talisman; Yavimaya, Cradle of Growth
Pick 2, pack size 13
Birds of Paradise; Chain Lightning; Cosmogrand Zenith; Gitaxian Probe; Hymn to Tourach; Life // Death; Multiversal Passage; Soul-Guide Lantern; Talisman of Indulgence; Talisman of Unity; Torsten, Founder of Benalia; Wasteland; Witch Enchanter // Witch-Blessed Meadow
Pick 3, pack size 12
Dark Confidant; Deathrite Shaman; Demonic Tutor; Expressive Iteration; Get Lost; Liliana of the Veil; Questing Druid // Seek the Beast; Razorverge Thicket; Sylvan Caryatid; Underground Mortuary; Virtue of Loyalty // Ardenvale Fealty; Windswept Heath
Pick 4, pack size 11
Copperline Gorge; Elvish Reclaimer; Flickerwisp; Mine Collapse; Savai Triome; Scrubland; Talisman of Progress; Trumpeting Carnosaur; Unexpectedly Absent; Valki, God of Lies // Tibalt, Cosmic Impostor; Zuran Orb
Pick 5, pack size 10
Archon of Cruelty; Bleachbone Verge; Mana Confluence; Questing Beast; Sacred Foundry; Blazing Firesinger // Seething Song; Stomping Ground; Taiga; Tersa Lightshatter; Titania, Protector of Argoth
Pick 6, pack size 9
Crucible of Worlds; Emperor of Bones; Grim Lavamancer; Jetmir's Garden; Restless Cottage; Restless Fortress; Rofellos, Llanowar Emissary; Sanguine Evangelist; Vampire Hexmage
Pick 7, pack size 8
Bone Shards; Collective Brutality; Exploration; Keen-Eyed Curator; Overgrown Tomb; Restless Vents; Utopia Sprawl; Winds of Abandon
Pick 8, pack size 7
Celestial Colonnade; Deep-Cavern Bat; Gloomlake Verge; Jadar, Ghoulcaller of Nephalia; Prismatic Ending; Sink into Stupor // Soporific Springs; Talisman of Conviction
| Pick | Human log | Human imitation | Safe game-data | Aggressive game-data | MCTS |
|---|---|---|---|---|---|
| 1 | Skyclave Apparition | Snapcaster Mage | Skyclave Apparition | Ouroboroid | Blood Crypt |
| 2 | Gitaxian Probe | Gitaxian Probe | Cosmogrand Zenith | Birds of Paradise | Hymn to Tourach |
| 3 | Windswept Heath | Demonic Tutor | Windswept Heath | Demonic Tutor | Demonic Tutor |
| 4 | Scrubland | Scrubland | Unexpectedly Absent | Zuran Orb | Valki, God of Lies // Tibalt, Cosmic Impostor |
| 5 | Sacred Foundry | Archon of Cruelty | Sacred Foundry | Titania, Protector of Argoth | Archon of Cruelty |
| 6 | Sanguine Evangelist | Emperor of Bones | Sanguine Evangelist | Emperor of Bones | Emperor of Bones |
| 7 | Winds of Abandon | Collective Brutality | Winds of Abandon | Keen-Eyed Curator | Bone Shards |
| 8 | Deep-Cavern Bat | Deep-Cavern Bat | Prismatic Ending | Deep-Cavern Bat | Deep-Cavern Bat |
Human log: Skyclave Apparition; Gitaxian Probe; Windswept Heath; Scrubland; Sacred Foundry; Sanguine Evangelist; Winds of Abandon; Deep-Cavern Bat
Human imitation: Snapcaster Mage; Gitaxian Probe; Demonic Tutor; Scrubland; Archon of Cruelty; Emperor of Bones; Collective Brutality; Deep-Cavern Bat
Safe game-data: Skyclave Apparition; Cosmogrand Zenith; Windswept Heath; Unexpectedly Absent; Sacred Foundry; Sanguine Evangelist; Winds of Abandon; Prismatic Ending
Aggressive game-data: Ouroboroid; Birds of Paradise; Demonic Tutor; Zuran Orb; Titania, Protector of Argoth; Emperor of Bones; Keen-Eyed Curator; Deep-Cavern Bat
MCTS value-conservative: Blood Crypt; Hymn to Tourach; Demonic Tutor; Valki, God of Lies // Tibalt, Cosmic Impostor; Archon of Cruelty; Emperor of Bones; Bone Shards; Deep-Cavern Bat
The safe game-data model most closely follows the human's white/fixing lane. It takes Skyclave Apparition, Windswept Heath, Sacred Foundry, Sanguine Evangelist, and Winds of Abandon.
The human-imitation model actually chases more raw power here: Snapcaster Mage, Demonic Tutor, Archon of Cruelty. That is interesting because it is the model optimized for human agreement overall, but on this trace it is less committed to the logged human's fixing-heavy path.
The aggressive game-data model still looks unsafe. Picks like Ouroboroid, Zuran Orb, Titania, and Keen-Eyed Curator are plausible outputs of a model chasing static game-outcome bonuses, but they look contextually dubious.
MCTS is heavily path-dependent. After taking Blood Crypt, it moves into a black/red power lane: Hymn to Tourach, Demonic Tutor, Valki/Tibalt, Archon, Emperor of Bones, Bone Shards. This may be a coherent line, but it is also a reminder that search amplifies early assumptions. If the first pick is off, the whole searched line can drift.
The next thing I want is a larger disagreement audit: sample many real draft prefixes, compare human / safe / MCTS / aggressive choices, and manually review the cases where MCTS changes the pick.