DraftZero FDN graph network

A graph neural network that plays Magic: The Gathering Limited with Foundations (FDN) decks, trained by imitating 17lands' top players. It is DraftZero's strongest network on the XMage engine, built on Will Wroble's MageZero graph encoder and network.

  • What it does: given the acting player's view of an XMage game as a graph, it scores every legal option (policy) and estimates the chance of winning (value). A tree search can use both, or a bot can play the policy's top choice directly.
  • What it saw in training: 10.9 million decisions from 145,898 FDN Premier Draft games by players in 17lands' 60%-and-up win-rate group, replayed inside XMage. The opponent's hand is not part of its input.
  • Size: 13.4M parameters (width 256), 1,932 leaf features, 22 edge labels. Float32 weights, 54 MB.
  • Source: gnn/full_r1/best_policy_calibrated in DraftZero's runs (docs/024).

How strong it is

Its bot played MageZero's heuristic bot, which searches 100 simulations a decision. A search at N simulations plays N short look-ahead games before each decision. Both bots used PIMC search: each decision deals the hidden cards once into a world that fits what the bot has seen, guessing the opponent's deck from real 17lands decks, and runs MageZero's tree search on it. Every deck pair was played twice with the seats swapped (paired games), which lowers the variance.

Network's simulations a decision Won Time a decision
0: the policy's top choice 44.7% of 103 games ~0.2 s
100 62.5% of 104 ~7 s
300 69.2% of 104 ~20 s
1,000 69.3% of 101 40-65 s

Times are from those games: up to 30 games at once on one rented RTX 3090 machine, the network served in bfloat16, most of the time spent in XMage rather than in the network. On one CPU thread of a laptop, one network call takes about 20 ms (batch 1, median 226 nodes a state). On held-out test games, at decisions where the human made a play rather than passing, the policy's top choice was a play the human made 85.9% of the time.

Files

File What
model.safetensors the weights, float32
config.json the architecture, the encoder's constants (node types, value buckets, hash) and the source pins
vocab.json leaf_ids and edge_ids: the raw feature ids that have embedding rows, in row order
graph_net.py the network (MageZero's NetGraph), as DraftZero trains and serves it
gnn_release.py a loader with no pickles: weights, vocab, state -> network input, option logits, the golden check
goldens.jsonl.gz 300 encoded test states with this network's outputs (below)
coverage.json how much of the test data the vocab covers, a baseline for checking an encoder
check.json gnn_release.py check on this release
SHA256SUMS the files' hashes

Requirements: Python 3.10+, torch (2.1 or later), numpy, safetensors.

import gnn_release as r

model, leaves, edges, config = r.load(".")
goldens = r.read_goldens("goldens.jsonl.gz")
g = goldens[0]
out = r.evaluate(model, [g["state"]], leaves, edges)[0]
print(r.option_logits(out, g["graph_type"], g["options"]), out["value"])

How it plays: what a port needs

1. The encoder. The input is MageZero's state graph: typed nodes (ROOT, PLAYER, ZONE, STACK_OBJECT, PERMANENT, CARD, ABILITY) with LEAF children, and edges from child to parent with a label. Leaves are XMage strings: card names, type and subtype names, Ability.toString() rules text, decision text, step names. Each string becomes a feature id, StateEncoder.indexFor(StateEncoder.hash64(name)) (graph_net.feature_id in Python). Some leaves also carry a number (power, toughness, counters, life). The encoder is Java, at WillWroble/mage@e4afc9c77ba7e4a6dbc24966cbdc6dc0819e0bff (branch graph-encoder, Mage.Server.Plugins/Mage.Player.AI/src/main/java/mage/player/ai/encoder). DraftZero runs a vendored copy in java/mzbridge/src/org/draftzero/mzbridge/graph on XMage danieljbrooks/mage@48e49184 (branch v0.2-generalist). It encodes the acting player's view, with the opponent's hand hidden (perfectInfo false).

A leaf outside the vocab has no embedding: its edges are dropped silently, without an error. So an encoder running on another XMage version whose strings differ (rules text, decision text) plays worse without failing. To catch this, count how many leaf occurrences hit vocab.json. On DraftZero's test data the vocab covers more than 99.99% of them in priority, attack and target decisions, and 99.3% in block decisions (coverage.json).

2. Decisions and options. graph_type is MageZero's ActionType ordinal. Each legal option is a list of graph nodes, and its logit is the log-sum-exp of its nodes' scores under the head the decision reads:

Decision Head An option's nodes
Priority (graph_type 0) priority the ability's node, one per copy of a card (two copies of a card are one option); Pass has its own node
Target, including blocks (3) target the target's node (CARD, PERMANENT, STACK_OBJECT or PLAYER); Stop Choosing is a CARD node
Attack with a creature (3) target no = Stop Choosing's node, yes = the defending player's node
Other yes/no (5) use [no, yes] logits, no nodes

Mulligans, trigger ordering, damage assignment and similar choices have no head. In DraftZero's games mulligans were off (every opening hand was kept), and MageZero's player made the other choices without the network's policy.

3. The value. value = tanh(value_x) is the acting player's expected result in -1..1, and P(win) = sigmoid(2 * value_x). A temperature fitted on validation games (T = 1.607) is already folded into the value head's last layer.

4. The search in DraftZero's games. This is MageZero's tree search, run fresh at every decision with no tree reuse, on one sampled world (PIMC):

  • selection by PUCT with c = 1;
  • priors from the policy: softmax of the option logits at temperature 1.5, then 0.1 added to every option that isn't Pass or a mana ability (not renormalized: MageZero's setPriors);
  • uniform priors at the opponent's decisions;
  • leaves scored by the value head, discounted by 0.99 per ply;
  • the move with the most visits is played.

The code is java/mzbridge/src/mage/player/ai (BenchPlayer, BenchSearch, GraphNet, GraphMCTSPlayer) at danieljbrooks/draft-zero@2a461518. Playing the policy's top choice alone needs no search and no hidden-card sampling.

Checking a port with the goldens

goldens.jsonl.gz holds 300 decisions from the test split: 160 priority, 50 attack, 50 target and 40 block decisions. They come from games the network never trained on, encoded by the Java encoder above. Each line has:

  • state: the encoder's output in MageZero's graph-server layout: indices (raw feature id per node), values, edge_child, edge_parent, edge_label;
  • graph_type, options (node indices per legal option), option_is_pass, human_choice (the option the 17lands player took, for interest);
  • expected: option_logits, use, value and value_x.

The expected outputs come from DraftZero's games inference server (tools/imitation_scale/graph_server.py, loading the original checkpoint) on CPU in float32, one state at a time. python gnn_release.py check . loads this release and compares. A faithful port agrees to about 1e-5 and picks the same top option in all 300 (check.json).

The goldens check the network, its weights and the option mapping. They don't check an encoder, because states can't move between XMage versions as is; for that, use the vocab coverage above.

Limits

  • Imitation only. It learned from top players' moves and game results and has not trained by self-play yet.
  • Measured against one opponent: MageZero's heuristic bot, with PIMC and guessed decks. There is no Elo or human-play result yet.
  • XMage-specific input. The features are XMage's own strings, so other engines need either XMage as the encoder or a re-implementation that reproduces those strings exactly.
  • Search is not optional for strength. The policy alone lost to the heuristic bot (44.7%); 100 simulations or more are needed to beat it.

Sources

What Where
Network danieljbrooks/draft-zero@2a461518985e3b2bb393bddac4649e217f8fddfe, src/draftzero/gameplay/graph_net.py, a copy of MageZero's NetGraph (WillWroble/MageZero@de225045, branch graph-encoder)
Encoder WillWroble/mage@e4afc9c77ba7e4a6dbc24966cbdc6dc0819e0bff (branch graph-encoder)
Engine danieljbrooks/mage@48e4918413392523a5f9d7915e1f33941ddea21e (branch v0.2-generalist: DraftZero's FDN changes on MageZero v0.2)
Training docs/023 and docs/024
Data 17lands public game data, FDN Premier Draft, CC BY 4.0

The weights are released under CC BY 4.0, like the 17lands data they were trained on. The code (graph_net.py, gnn_release.py) is MIT, as are DraftZero and MageZero. The network and encoder are Will Wroble's MageZero design; DraftZero trained these weights.

Downloads last month
10
Safetensors
Model size
13.4M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support