| name | cube-wizard |
| description | Build and tune MTG cubes — curated card pools designed for drafting. Supports Vintage, Unpowered, Legacy, Modern, Pauper, Peasant, Set, Commander, and PDH cubes. |
| compatibility | Requires Python 3.12+ and uv. Shares mtg_utils package via symlink. |
| license | 0BSD |
Cube Wizard
Build MTG cubes from scratch (by cloning a well-known reference cube and
customizing) or tune existing ones. A cube is a curated card pool designed
for drafting, typically 360–720 cards. Unlike a deck, balance lives at the
collection level — archetype draftability, per-color-pair signal density,
fixing density, curve shape across the whole pool. Every recommendation MUST
be grounded in actual card oracle text from Scryfall — never from training
data.
The skill has two phases. Phase 1 (Cube Acquisition) determines how the
user gets a cube: parse an existing list (Path A) or clone a reference cube
(Path B). Both paths produce a cube JSON + hydrated cache. Phase 2
(Tuning) runs the same 10-step pipeline on that cube regardless of origin.
The Iron Rule
NEVER assume what a card does. Before referencing any card's abilities,
look up its oracle text via the helper scripts. Training data is not oracle
text.
Exception (archetype brainstorming only): During Phase 1 Path B's
customization-intent interview, when the user is brainstorming archetypes
and themes, you may use training data to suggest theme names (tokens,
sacrifice, +1/+1 counters, etc.). But every card you propose for the cube
MUST be verified against Scryfall oracle text before the user accepts it —
write all candidate names to a JSON list and batch-lookup in one call:
scryfall-lookup --batch <candidates.json> --bulk-data <bulk-path> --cache-dir <wd>/.cache
Supported Cube Formats
| Format | Default Size | Card Pool | Rarity Filter | Commander Pool | Notes |
|---|
| vintage | 540 | Full eternal | — | No | Powered (Power 9 allowed) |
| unpowered | 540 | Full eternal | — | No | Power 9 banned by default |
| legacy | 540 | Legacy-legal | — | No | Legacy ban list applies |
| modern | 540 | Modern-legal | — | No | Modern ban list applies |
| pauper | 540 | Full eternal | Commons only | No | — |
| peasant | 540 | Full eternal | Commons + uncommons | No | — |
| set | 360 | Single set | — | No | Auto-detected if all cards share a set |
| commander | 540 | Commander-legal | — | Yes | Dedicated commander pack drafted separately |
| pdh | 540 | Full eternal | Commons (main) | Yes | Commanders: uncommons |
Format is optional metadata, not a hard gate. A "cats-and-dogs tribal
cube" or a "budget-capped cube" works fine — layer constraints on top of
whichever format's rarity/legality filter fits. The designer_intent block
in the cube JSON captures the user's specific goals.
Progress Tracking
Before starting, create a TaskCreate list. The first question
determines the path; create tasks accordingly.
Phase 1: Path A (Parse Existing Cube)
- Fetch / Read Cube
- Parse to Canonical Cube JSON
- Hydrate Card Data
Phase 1: Path B (Clone Reference Cube)
- Interview
- Reference Cube Selection
- Fetch + Parse + Hydrate
- Baseline Review
Phase 2: Tuning (Both Paths)
- Baseline Metrics
- Designer Intent Confirmation
- Balance Dashboard
- Archetype Audit
- Power-Level Review
- Self-Grill
- Propose Changes
- Pack Simulation
- Finalize / Export
- Empirical Playtest (optional)
Mark each item in_progress the moment you begin it and completed the
moment it finishes — do not batch updates.
Setup (First Run)
uv sync --directory <skill-install-dir>
download-bulk --output-dir <skill-install-dir>
Subsequent runs skip uv sync if .venv exists and bulk data is fresh
(24h).
Tooling Notes
Human-Readable stdout + JSON Sidecar (load-bearing)
Every cube CLI emits a short human-readable summary to stdout and writes the
full structured output to a sidecar JSON file, with a Full JSON: <path>
footer. This pattern exists because cube JSON at 540+ cards would blow
context if streamed to stdout verbatim.
Script-to-script chaining uses file paths, not piped JSON. Read the
sidecar selectively with Read + offset/limit when you need specific
fields. Pass --json only when a downstream script needs the full payload
on stdout (rare).
JSON File Writing
Use the Write tool for any JSON file containing card names. Apostrophes
(e.g., "Ashnod's Altar") break shell quoting in heredocs and inline Python.
Write tool permissions are cacheable across the session; python3 -c "..."
re-prompts every time.
Scratch File Paths
Reuse these stable paths within a session: /tmp/cube-cuts.json,
/tmp/cube-adds.json, /tmp/cube-candidates.json, /tmp/cube-themes.json.
/tmp files persist across sessions. At session start, Read each scratch
path you plan to reuse before the first Write, OR run Write sequentially
and verify success before the dependent Bash call. Never batch
Write(/tmp/foo.json) + Bash(tool reading /tmp/foo.json) in a single
message.
Path Requirements
- Absolute paths only.
uv rebases the working directory to the skill
install; relative paths resolve against the wrong root.
- Cache directory: Always
<working-dir>/.cache, not the skill install.
- Re-hydration: After every cube edit, re-run
scryfall-lookup --batch on the new cube JSON. The hydrated cache is
SHA-keyed; old caches go stale silently.
CubeCobra Fetch Caveats
CubeCobra returns 403 to plain Python UAs for some endpoints. cubecobra-fetch
handles this transparently by falling back to curl via web_fetch. Fetch
priority: cubeJSON (richest — mainboard/maybeboard split, designer tags,
per-card overrides) → CSV → plain cubelist. If all three fail, the user can
export manually from the CubeCobra UI and pass the file to parse-cube.
Balance Checks are Informational, Not Pass/Fail
cube-balance reports observed metrics against conventional reference
ranges (e.g., "22–28% removal density"). It does NOT mark a cube as broken
if it falls outside the range. A mono-blue cube, a combo cube, or a
cats-and-dogs tribal cube may legitimately differ from conventional
expectations. Read the reference ranges as "typical" — not "required."
Reference ranges come from Riptide Lab and Star City Games consensus plus
Lucky Paper's "How Many Lands Should You Include in Your Cube" numerics.
Regex Scope Warning for cube-balance and archetype-audit
Both tools share a canonical preset library at mtg_utils.theme_presets
(PRESETS dict + get_preset(name) / matches(name, card) API). Each
preset carries test fixtures that pin its behavior against known cards,
so updates can't silently regress. cube-balance's removal check delegates
to the removal preset, and archetype-audit --preset <name> loads
presets by name.
Removal detection uses the removal preset (destroy/exile/counter/
damage/bounce/fight/-X). It's tuned to be generous — false positives are
acceptable for an informational metric. When the density feels off, spot-
check by running:
card-search --bulk-data <path> --oracle "<your regex>" --color-identity <ci>
For finer-grained removal density, six type-specific presets are
available alongside the broad removal umbrella: creature-removal,
artifact-removal, enchantment-removal, land-removal,
planeswalker-removal, and universal-removal (Vindicate / Beast
Within / Maelstrom Pulse / Nicol Bolas style). Type presets are strict
(they match cards that NAME the type); universal-removal is a
separate bucket — to count everything that can answer an artifact, sum
artifact-removal + universal-removal. Note: same-card overlap is
expected (Lightning Bolt matches both creature-removal and
planeswalker-removal via "any target" burn).
For archetype-audit, themes can come from --preset <name> (library,
tested) or --theme name=regex (custom, unvalidated). Custom regex is
exactly as precise as the regex the user writes.
Canonical Cube JSON Schema
{
"cube_format": "vintage|unpowered|legacy|modern|pauper|peasant|set|commander|pdh",
"target_size": 540,
"name": "Regular Cube",
"drafters": 12,
"pack_size": 15,
"packs_per_drafter": 3,
"source": "cubecobra:<id>",
"cloned_at": "2026-04-18T12:34:56Z",
"designer_intent": {
"description": "<from CubeCobra overview>",
"tags": ["unpowered", "vintage"],
"stated_archetypes": [
{"name": "tokens"},
{"name": "graveyard",
"members": ["reanimate", "self-mill", "graveyard-cast"]},
{"name": "Boros Equipment", "regex": "equip|attach"}
]
},
"pack_templates": {},
"balance_targets_override": {},
"commander_pool": [
{"name": "Atraxa, Praetors' Voice", "quantity": 1, "scryfall_id": "..."}
],
"cards": [
{"name": "Lightning Bolt", "quantity": 1,
"cube_color": "R", "cube_cmc": 1.0,
"tags": ["burn"], "scryfall_id": "..."}
],
"total_cards": 540
}
Each entry has one of three shapes:
{"name": NAME} — preset reference. NAME must be a key in
theme_presets.PRESETS (run archetype-audit --list-presets for
the full catalog).
{"name": NAME, "members": [PRESET, ...]} — archetype group. NAME
is the umbrella label; each members entry must be a known preset.
Tools that read stated_archetypes report on the group as one unit
while still tracking the constituent presets.
{"name": NAME, "regex": PATTERN} — custom matcher (legacy or
cube-specific themes that aren't covered by the preset library).
All cube CLIs accept this shape. Hydrated cache lives at
<working-dir>/.cache/<sha>.json, SHA-keyed so re-hydration is idempotent.
Decision Table
| Task | Tool |
|---|
| Pull a CubeCobra cube | cubecobra-fetch <id-or-url> --output-dir <wd> |
| Parse a cube list / CSV / JSON | parse-cube <path> --cube-format <fmt> --output <wd>/cube.json |
| Hydrate card data | scryfall-lookup --batch <cube.json> --bulk-data <path> --cache-dir <wd>/.cache |
| Top-line cube metrics | cube-stats <cube.json> <hyd.json> |
| Balance dashboard | cube-balance <cube.json> <hyd.json> [--check <name>] |
| Format legality + rarity audit | cube-legality-audit <cube.json> <hyd.json> |
| Archetype validation | archetype-audit <cube.json> <hyd.json> --theme name=regex |
| Archetype validation via preset library | archetype-audit <cube.json> <hyd.json> --preset <name> (repeatable; see --list-presets) |
| List available preset themes | archetype-audit --list-presets |
| Show which cards matched each theme | Add --show-matches to any archetype-audit invocation |
| Bridge cards | Included in archetype-audit output when ≥2 themes supplied |
| Compare cube revisions | cube-diff <old.json> <new.json> [--old-hydrated <h> --new-hydrated <h> --metrics] |
| Sample an opening pack | pack-simulate <cube.json> <hyd.json> --seed N --pack-size 15 |
| Stress-test draftability | pack-simulate ... --simulate-drafts 100 |
| Find candidate cards | card-search --bulk-data <path> --oracle "<regex>" --color-identity <ci> |
| Cube budget check | price-check <cube.json> --bulk-data <path> |
| Find accidental infinites | combo-search <cube.json> |
| Export to CubeCobra CSV | export-cube <cube.json> --format csv --output <wd>/cube.csv |
| Cite MTG Comprehensive Rules | rules-lookup --rule <n> / --term <keyword> / --grep "<regex>" |
| Fetch Scryfall per-card rulings | rulings-lookup --card "<name>" --bulk-data <path> |
| Deeper rules question (archetype legality, keyword interaction) | Invoke the rules-lawyer skill via the Skill tool |
Rules Lawyer
Cube design occasionally runs into rules questions — "does this combo actually work in the format?", "is this trigger mandatory?", "how does the layer system handle this static?". Run rules-lookup --term <keyword> for straightforward keyword/glossary questions. For anything nuanced (layers, replacement effects, multi-rule interactions), invoke the rules-lawyer skill via the Skill tool, which handles escalation to subagents loaded with the relevant CR section.
Run download-rules --output-dir <working-dir> once per session before the first rules-lookup call (24-hour freshness check, same pattern as download-bulk).
Phase 1: Cube Acquisition
Opening question: "Do you have an existing cube list you want to tune,
or do you want to build a new cube starting from a well-known reference?"
Path A: Parse Existing Cube
- Acquire the cube list. One of:
- CubeCobra ID or URL →
cubecobra-fetch <id> --output-dir <wd>
- Local file (CubeCobra CSV, plain text, etc.) — use as-is
- Parse to canonical cube JSON.
parse-cube <wd>/<cube>.json --cube-format <fmt> --output <wd>/cube.json
If the user didn't specify a format, parse-cube auto-detects
set cubes (all cards share a set code) and defaults to vintage
otherwise.
- Hydrate card data.
scryfall-lookup --batch <wd>/cube.json --bulk-data <wd>/default-cards.json --cache-dir <wd>/.cache
- Confirm scope. Read
cube.json.designer_intent.description and
tags — these provide the context the Phase 2 pipeline needs. If the
cube has stated archetypes, run archetype-audit --from-cube to process
preset references and groups directly. Only ask the user for regex
queries for themes that aren't covered by the preset library.
Path B: Clone Reference Cube
- Interview. In order:
- Cube format (see Supported Cube Formats table)
- Target size (360 / 450 / 540 / 630 / 720, default from format)
- Drafters / pack size (defaults from
cube_config.SIZE_TO_DRAFTERS)
- Reference cube selection: present the hardcoded options for the chosen
format from
cube_config.REFERENCE_CUBES, plus "Paste a CubeCobra URL"
for any cube the user knows. Use AskUserQuestion (respect the 4-option
cap — list all options in the text message, let the user pick any).
- Customization intent (open-ended): "What do you want to change from
this reference? Add an archetype? Cut specific cards? Tribal theme?
Budget cap? Or just tune the baseline?"
- Budget (optional): paper USD cap
- Restrictions (optional): no fast mana, no infinite combos, tribal
constraints, single-set, single-color, etc.
- Fetch + parse + hydrate. Same commands as Path A applied to the
reference cube's CubeCobra ID.
- Baseline review. Run
cube-stats and cube-balance on the
unmodified reference cube. Present the baseline to the user so they
confirm the starting point matches their intent before customization
begins.
Phase 2: Tuning Pipeline
The same 9 steps apply whether the cube came from Path A (existing) or Path
B (cloned reference). Use TaskCreate to track progress through them.
Step 1: Baseline Metrics
cube-stats <cube.json> <hyd.json>
cube-balance <cube.json> <hyd.json>
Capture the observed color distribution, curve, removal density, fixing
density, and commander-pool composition (if any). These are the before-
picture for Step 7's impact verification.
Step 2: Designer Intent Confirmation
Summarize the cube's stated intent back to the user:
- Format, size, drafters
- Designer description from CubeCobra overview (or the customization
intent from Path B's interview)
- Stated archetypes (if any)
- Explicit restrictions
Ask the user to confirm or adjust. Persist any clarifications back to
cube.json.designer_intent.stated_archetypes entries (preset, group, or
custom-regex shape) for Step 4.
Step 3: Balance Dashboard & Legality Audit
Already computed in Step 1; walk through each section with the user and
note any "outside typical range" items that are intentional (e.g., "yes,
the removal density is low because I want this cube to be creature-heavy").
Also run the mechanical legality audit:
cube-legality-audit <cube.json> <hyd.json>
Unlike cube-balance, this is a hard-constraint check: errors indicate
format rule violations that should be fixed before shipping. Checks:
- Rarity filter — e.g., a Pauper cube with a rare card (uses Scryfall's
legalities.pauper when available, falls back to default-printing rarity
as a warn for Peasant).
- Legality key — e.g., a Modern cube with a Modern-illegal card.
- Ban list — e.g., Power 9 cards in an
unpowered cube.
- Commander pool rarity — e.g., PDH commanders must be uncommons.
Any error-severity violation requires a fix (swap the card or change the
cube format). warn-severity items require manual verification (default-
printing rarity drift, missing legality data).
Step 4: Archetype Audit
Prefer --preset <name> over hand-written regex when the theme matches one
of the library presets. Available categories:
- Keyword abilities: flying, vigilance, trample, haste, deathtouch,
lifelink, first-strike, double-strike, reach, menace, defender, flash,
hexproof, indestructible, ward, protection, scry, surveil, cascade,
flashback, kicker, cycling, evoke, ninjutsu, exalted, prowess, revolt,
investigate, landfall, dredge, miracle, storm, infect, toxic, delve,
suspend, undying, persist, equip, mill.
- Removal (broad):
removal (umbrella), board-wipe.
- Removal by type:
creature-removal, artifact-removal,
enchantment-removal, land-removal, planeswalker-removal,
universal-removal (Vindicate / Beast Within / Maelstrom Pulse).
- Forced sacrifice (edicts):
edict (umbrella), creature-edict,
artifact-edict, enchantment-edict, land-edict,
planeswalker-edict, universal-edict (Vindicate-style on
opponent's choice).
- Turn manipulation:
extra-turns, extra-combats,
extra-upkeeps — the "take another of this step" payoff pile that
Obeka / Aurelia / Godo archetypes anchor.
- Functional: top-manipulation, self-mill, counterspell, bounce,
discard, tutors, tokens, sacrifice-outlet, burn, reanimate,
graveyard-return, cantrip, card-draw, lifegain, plus-one-counters,
blink.
Presets are tested against known cards and shared with cube-balance, so
results stay consistent across sessions. See the full catalog with
archetype-audit --list-presets. Add --show-matches to see which cards
matched each theme.
archetype-audit <cube.json> <hyd.json> \
--preset tokens \
--preset reanimate \
--preset counterspell \
--show-matches
For PDH cubes, pass --include-commanders to factor commander_pool
entries into theme density alongside the main-pool cards. Off by default
so non-PDH cubes keep their existing output unchanged.
For themes without a matching preset, fall back to --theme name=regex:
spells-matter=(instant|sorcery) you (control|cast)
energy=gets? \{E\}
counters=\+1/\+1 counter
archetype-audit <cube.json> <hyd.json> \
--preset tokens \
--theme "spells-matter=(instant|sorcery) you (control|cast)" \
--theme "counters=\\+1/\\+1 counter"
Output reports per-theme density, per-guild density, orphan signals
(payoffs only in one color), and bridge cards (cards supporting multiple
themes — design-priority connective tissue).
Close any theme that has fewer than 3–4 cards with the user — Lucky
Paper's FAQ says an archetype "needs as little as 3–4 cards" to be
draftable; below that threshold it's a non-archetype.
Step 5: Power-Level Review
Two checks:
- Price outliers (proxy for power): flag cards $50+ and $100+ tier
outliers via
price-check <cube.json> --bulk-data <path>.
- Accidental infinite combos — a cube's main worry is that unrelated
cards form a two-card infinite that warps drafts:
combo-search <cube.json>
combo-search is deck-scoped but works fine on cube JSON (both share the
cards[] shape). Review flagged combos with the user.
Step 6: Self-Grill (HARD GATE)
Run parallel Agent calls — one proposer, one challenger — following the
deck-wizard Step 8 pattern. Challenger prompt focuses on cube-specific
failure modes: archetype starvation (cutting enablers without replacing
payoffs), color imbalance introduced, signal dilution (new cards support 0
themes), power outliers, fixing disruption.
Brief both agents with:
- The cube's stated archetypes and designer intent
- Current
cube-stats + cube-balance baseline
- Current
archetype-audit per-theme density
- Proposed cuts/adds (from the tuning session)
Step 7: Propose Changes
Write cuts/adds to /tmp/cube-cuts.json and /tmp/cube-adds.json via
Write tool. Regenerate the cube JSON (by applying cuts/adds), re-hydrate,
and re-run the Phase 2 metrics:
cube-diff <old-cube.json> <new-cube.json> \
--old-hydrated <old-hyd.json> --new-hydrated <new-hyd.json> --metrics
Review the deltas with the user before proceeding.
Common swap criteria — drive cuts/adds from the diagnostic signals:
- Removal density off (Step 3): swap removal in/out by archetype tag.
- Archetype below LP minimum (Step 4): add enablers OR umbrella with
related archetypes via
stated_archetypes group.
- Power outliers (Step 5): cut the price-flagged card if it warps
drafts; combos identified by
combo-search may need one piece cut.
- High color-screw rate (from Step 10's playtest report, custom
formats): replace double-pip cards (
{C}{C}) with single-pip
equivalents in the same archetype. Use card-search --oracle "<archetype pattern>" --color-identity <C> to find candidates, then verify each
with scryfall-lookup.
- Archetype assembly below 30% (Step 10): add 2–3 enablers OR adjust
the archetype's group definition in the cube JSON.
When swap candidates would collide with cards already in the cube
(common with popular replacements), card-search with stricter filters
narrows alternatives. Verify every replacement's mana cost via
scryfall-lookup before committing — never trust card costs from
training data alone.
Step 8: Pack Simulation
pack-simulate <new-cube.json> <new-hyd.json> --seed 1 --pack-size 15
pack-simulate <new-cube.json> <new-hyd.json> --seed 1 --pack-size 15 --simulate-drafts 100
Spot-check a few opening packs visually, then look at the aggregate
distribution across 100 packs. If a category is severely under-represented
or the color distribution feels off, return to Step 7.
Step 9: Finalize / Export
export-cube <new-cube.json> --format csv --output <wd>/<cube-id>-updated.csv
Hand the CSV to the user with instructions to use CubeCobra's "Replace
with CSV Import" function to push the changes back upstream.
Step 10: Empirical Playtest
When to run:
- Standard cube formats: optional. Steps 1–9's static signals
already cover most decisions; run gauntlet/draft if you want
matchup-balance numbers.
- Custom format with a registered simulator module (i.e., the
format name is a key in
mtg_utils/_custom_format/__init__.py:FORMAT_REGISTRY):
mandatory. Static tools can't see format-specific dynamics;
the simulator is the only diagnostic for color-screw and archetype
assembly under the format's actual rules. Today the registry has
one entry: shared_library.
- Custom format with no simulator module: propose writing one
before continuing. Tell the user the format isn't yet simulated,
show them the format-module contract (
setup/run_turn/
is_terminal plus DEFAULT_PLAYERS / DEFAULT_TURNS /
SUPPORTS_ARCHETYPES; _custom_format/shared_library.py is the
reference implementation), and ask whether to add the module now
or fall back to Steps 1–9 with that limitation noted in the
proposal report. Don't write the module without confirmation —
format rules need to come from the user, not from training data.
Two complementary signals. Run both when invoked.
Gauntlet (matchup balance):
playtest-gauntlet cube.json --hydrated cube-hydrated.json \
--games-per-pair 100 --seed 1 --output playtest-gauntlet.json
Three precedence levels for which archetypes the gauntlet tests:
--gauntlet path/to/manifest.json — explicit override file
cube.designer_intent.stated_archetypes — when populated, the
gauntlet derives one deck per stated archetype, auto-inferring
colors and curve_target from the cube's actual card pool
(per-archetype theme matchers count cards by color identity and
CMC). To override the inference for a specific archetype, add an
optional gauntlet: block to its stated_archetype entry:
{
"name": "Aristocrats",
"members": ["sacrifice", "death-trigger"],
"gauntlet": {"colors": ["B", "W"], "shape": "midrange",
"curve_target": {"2": 5, "3": 7, "4": 6}}
}
shape is one of aggro|midrange|control|combo and adds
canonical scoring priors on top of theme matches; omit it to
rely solely on theme-driven scoring.
- Fallback when stated_archetypes is missing/empty: the per-format
default at
mtg_utils/data/gauntlets/<format>.json (the canonical
four shapes — Mono-Red Aggro, Azorius Control, Golgari Midrange,
Izzet Combo — with explicit shape field).
The report is a 4×4 win-rate matrix (or N×N when stated_archetypes has
N entries). Flag any cell that's > 60% / < 40% (10pp from balanced) —
at default M=100 the binomial 95% CI is ±10pp, so 10pp gaps are
statistically meaningful, smaller gaps are noise.
Draft (draft balance):
playtest-draft cube.json --hydrated cube-hydrated.json \
--pods 4 --players 8 --seed 1 --output playtest-draft.json
The report aggregates per-deck goldfish across 32 drafted decks and surfaces:
mean color-screw rate, mean lands at T4, archetype distribution, failed builds.
Mana-model caveat: the per-deck goldfish counts lands + every cast nonland
permanent with a Scryfall produced_mana (rocks, dorks, and mana-token makers
like Treasure/Gold/Eldrazi-Spawn generators), approximating token makers as a
rough ~1-mana/turn source rather than simulating their triggers/sacrifice. So
token ramp is captured roughly, not precisely. Note this when reporting draft
results.
Reporting back: if playtest-gauntlet flags imbalance, weave it into
the cuts-and-adds conversation. The wizard does NOT auto-cut from playtest output —
the user reads both reports and decides.
Custom format: if the cube targets a non-standard format whose
module is registered in FORMAT_REGISTRY (today: shared_library),
use playtest-custom-format instead of playtest-gauntlet /
playtest-draft. If the format isn't in the registry, follow the
"Custom format with no simulator module" guidance above before
proceeding.
If the cube has designer_intent.stated_archetypes populated (preset
references and/or {name, members} archetype groups), --from-cube
pulls them automatically:
playtest-custom-format cube.json --hydrated cube-hydrated.json \
--format-module shared_library --from-cube \
--turns 10 --games 1000 --seed 0 --output report.json
For ad-hoc archetype lists (or to layer on top of cube-declared ones),
pass --preset NAME (repeatable) and/or
--archetype-group NAME=PRESET1,PRESET2,... to declare umbrella
archetypes inline:
playtest-custom-format cube.json --hydrated cube-hydrated.json \
--format-module shared_library \
--preset top-manipulation --preset removal --preset cantrip \
--archetype-group graveyard=reanimate,flashback,graveyard-return,self-mill \
--turns 10 --games 1000 --seed 0 --output report.json
The simulator runs N games × T turns × P players, tracks marketplace
dynamics and per-archetype assembly, and reports rates without modeling
combat or full card resolution. v1 ships only --format-module shared_library; new formats are one Python module each.
Interpreting the report — two tiers of signal.
Always available (every custom-format module reports these):
-
Color-screw rate. The simulator uses a pip-count-aware mana model
({U}{U} requires 2 actual U mana, not just any 2 mana with U in the
pool). High rates indicate pip burden — too many cards requiring
multiple mana of one color for the format's color-fixing density.
Tuning knobs:
- Replace double-pip cards (
{U}{U} → {1}{U}) with single-pip
equivalents — typically the highest-leverage swap.
- Add more same-color land density (more dual lands of each color, or
mono-color basics in whatever zone the format uses).
- Identify candidates:
card-search --bulk-data <path> --oracle "<pattern>"
against the cube's archetype patterns to find lower-pip versions of
existing cards.
-
Per-archetype assembly rate. Archetypes below ~30% rarely come
together in actual play. Tuning knobs:
- Add more enabler density (Lucky Paper minimum is 3–4 cards;
simulator's K=4 threshold is consistent with this).
- For multi-mechanic archetypes (graveyard = reanimate + flashback +
graveyard-return + self-mill), declare an
--archetype-group or
{name, members} entry in stated_archetypes so the umbrella's
real density is visible. A sub-mechanic with 4 cards may look thin
while the umbrella with 20 cards is robust.
- Bridge cards (cards spanning 2+ archetypes) raise umbrella assembly
without adding card slots — track them via
archetype-audit's
bridge-cards output.
Format-specific (varies by module). Each format's report block
surfaces signals that only make sense for that format's mechanics. Read
the relevant module to see what it tracks. Examples:
shared_library reports marketplace dynamics — utilization,
cards exiled/discarded, library effects per turn. 65%+ utilization
is healthy; 30%–40% suggests the marketplace is rarely worth picking
from. Tuning knob: rebalance curve / color pool until utilization
climbs.
- A future format module would surface its own signals (e.g., a
command-zone format might report commander-cast turn distribution; a
cascading-pack format might report pack-to-pack signal carry-over).
When adding a new module, add a corresponding tuning-knob entry here.
First-run prereq: playtest-install-phase (~5–10 min, one-time)
required for gauntlet (uses phase-rs). Draft and custom-format are pure
Python, no install needed.
Step 11: Print Proxies (optional)
When the user wants to print the cube for physical drafting:
fetch-art --from-deck cube.json --by-name
proxy-print --kind cards --deck cube.json --out cube-cards.pdf
Cube JSON is shape-compatible with deck JSON for proxy-print's
purposes (commanders + cards, no sideboard). Skip --kind tokens
unless the cube intentionally includes token-producing payoffs you
want printed separately. See proxy-printer/SKILL.md for layout
details and catalog setup.
Cube vs Deck Differences
Some deck-wizard tools apply to cubes and some don't:
Apply as-is (share the same cards[] shape):
card-search, scryfall-lookup, download-bulk, price-check
combo-search — genuinely useful for finding accidental infinites
card-summary, mark-owned, web-fetch
Don't apply (deck-specific assumptions):
mana-audit — expects a 60-card or 100-card deck
legality-audit — expects format deck-construction rules
parse-deck — use parse-cube instead
cut-check — designed for commander combo/synergy detection in a deck
build-deck, deck-stats, deck-diff — cube versions exist
export-deck — use export-cube
set-commander, find-commanders, edhrec-lookup, mtga-import —
not relevant for cubes
References
Philosophy articles worth WebFetching during Step 2 (designer intent) or
Step 6 (self-grill). Lucky Paper has no API; the agent should fetch on
demand when the conversation touches a design question the article
addresses.
Attribution
Cube-specific logic (draft category classifier, pack templates for sizes
9/11/15) is ported from
dan-blanchard/cube-utils
(MIT-licensed, used with attribution).