The search
How one run turns a deck into a success rate β by exhaustively exploring each game's decision tree.
How a simulation runs
A run plays N independent games. Each game is one random shuffle of the deck (seeded, so runs are reproducible). For each game the engine then searches the whole decision tree:
- Mulligans. If you allow mulligans, every way of choosing which cards to bottom from the opening 7 becomes a separate root of the search.
- Lines of play. At every point the player has priority, every legal action
is a branch: in a main phase, playing a distinct land (with its enter modes β
a shockland can enter tapped or cost 2 life), casting a distinct affordable spell, activating
an ability, or passing. In the other steps (upkeep, combat sub-steps, end step) an
instant-speed window opens whenever an instant, a flash spell, or an
instant-speed ability is actually available β so lines like cracking a fetchland on the end
step, or holding up a counterspell, are explored too. Fetch targets, tutor targets, discards,
the value chosen for an
{X}spell, modal choices and attack declarations branch as well. - Mana is not a branch point. Tapping sources to pay a cost is solved deterministically by a payment planner, keeping the tree focused on genuinely meaningful decisions.
- Checkpoints. The game state is checked against your properties at every phase entry and after every action at a priority window.
A game is a success if some single line satisfies all properties at their respective trigger moments. Independently, per-property statistics count the games where each property was satisfiable in any line.
Pruning & bounds
- Viability pruning: a branch is dropped the moment any unsatisfied property can no longer be verified on it (its βat the end ofβ moment is past, or its βbeforeβ deadline reached) β no descendant could make the game a success.
- Sticky satisfaction: once a property holds at one of its checkpoints on a line, it stays satisfied on that line; the search stops as soon as every property is verified.
- Budget: each game runs until every property is satisfied, no branch can satisfy them anymore (the frontier drains), or a wall-clock timeout β there is no node cap. Timed-out games are reported as such (β± in the runs table).
- Bugs are logged, never hidden. If a card's behaviour raises mid-action the branch is recorded as an error (β in that game's tree) and the game keeps searching other lines; the number of bugs hit is surfaced per game in the runs table (π), with the full traceback one click away β so a buggy card can't silently swallow otherwise-viable lines.
Search strategies
All strategies are exhaustive β they visit the same states, only the order differs, so winning lines are found sooner and timeouts bite later:
| Mode | Order |
|---|---|
best_first default |
Greedy best-first on a board-progress score (satisfied properties, then permanents in play, cards seen, turn) β heads for the most developed states first. |
bfs | Breadth-first β shallowest lines first. |
Parallel tree exploration
Games run one at a time, in order β but within each game the search tree is explored across CPU cores (one worker process per core, keeping one core free for the UI). The trick is that no game state ever crosses a process boundary: everything about a game derives deterministically from its seed, so the master process expands the tree just deep enough to find a level with enough subtrees, and each worker rebuilds the same game from the seed, replays that same shallow expansion, and exhaustively explores only its share of the subtrees. The pieces are then grafted back into one tree β identical, state for state, to what a sequential search would have produced. The first worker to satisfy every property makes the others stop, and stopping a run reaches into every worker within a fraction of a second.
Fake shuffling
Distinct lines of play can shuffle the library differently (a fetch land cracked at different moments, say) β then each line effectively gets a fresh top of library, which can over-evaluate βfind Xβ probabilities. The Fake shuffling option (in the Simulation box) keeps the library near-constant instead: a βshuffleβ never reorders it, except that the cards whose position the player actually knows β put on top by a Brainstorm or a tutor, bottomed by a mulligan or a scry β are pulled out and reinserted at random places (after a real shuffle you wouldn't know where they went). Enabled by default; disable it to get real seeded shuffles.
Crash-safe runs & resume
A run's entry is persisted after every completed game, so if the app dies mid-run the games already searched are kept β the run shows as interrupted in βPrevious runsβ. Loading a stopped or interrupted run offers a Resume button next to Run/Stop: exactly the games that never completed are (re)run with their original seeds and settings, appending to the same entry.
The stack β how casting, activating and triggering are modelled step by step β is covered on the Architecture page.