mtg/README.md
mark rosenberg d4a8a587f0 Restructure into a multi-deck engine; generalize tools/ off Jund/Reaper hardcodes
Migrates the reaper-king-no-more proof of concept into decks/reaper-king-no-more/
as the first deck under a shared tools/ engine, so the repo can hold many
Commander decks instead of one implicit one.

- tools/deckctx.py: new shared DeckContext, resolves --deck / $MTG_DECK / cwd,
  loads a deck's config.yaml, and derives color identity from the commander's
  own card data (never a hardcoded color combination).
- cards.py: drop the JUND=frozenset("BRG") constant; jund_legal() renamed to
  identity_legal(card, identity); DB() takes an explicit blacklist_path since
  the blacklist is per-deck, not shared.
- combos.py, roles.py, similar.py, similar_text.py, bracket_check.py: take
  --deck, use the deck's real identity instead of Jund/BRG.
- power.py, card_stats.py, forge_report.py: OURS deck-name constant now comes
  from the deck's config (name: / commander), matched via DeckContext instead
  of hardcoded "Reaper King No More".
- to_dck.py, batch_run.py, ab_test.py: .dck filename and [metadata] Name= are
  derived from the deck (slug / config name), not a hardcoded "reaper.dck".
- generate.py: RECIPES and make_variants.py's VARIANTS (deck-specific
  hypotheses) moved out of Python into decks/reaper-king-no-more/variants.yaml;
  protected cards now come from config.yaml's protected: list instead of a
  hardcoded set.
- New tools/mtg dispatcher: `tools/mtg decks/<name> <tool> [args...]` runs any
  tool against any deck without cd-ing into its directory.
- data/ (Scryfall/MTGJSON/Spellbook bulk, ~1.2GB, gitignored) and embeddings.npz
  move to the repo root as shared, commander-format-wide data.
- docs/howto.md: moved from the deck dir (it documents the tool, not the deck)
  and updated its example commands to the real generalized CLI.
- README.md: rewritten to describe the multi-deck repo and point at
  decks/reaper-king-no-more as the reference example.

Verified after the change: bracket_check, combos, and roles against
reaper-king-no-more reproduce the exact PASS/11-combos/role-profile numbers
from before the move; generate.py --list now loads recipes from YAML;
make_variants.py reproduces all 7 hand-curated variants including the
mikaeus FAIL control case; to_dck.py derives the right Name= and filename;
variant.py and compare.py reproduce decks/README.md's original numbers
exactly. The original POC at ~/mtg/reaper-king-no-more is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPKgLYpMR8szWNFXyK1XNu
2026-09-12 00:37:42 -04:00

4.4 KiB

mtg

A generic toolkit for building, testing, and simulating Magic: The Gathering Commander decks — evidenced by real games played in a real rules engine (Forge), not vibes.

Give it four things about a deck — a commander, a card pool, a blacklist of cards you refuse to play, and a few constraints (bracket, land count, role budget) — and it derives everything else from offline card databases: color identity, legality, Game Changers, Scryfall Tagger tags (so it knows what a "sac outlet" or "drain payoff" is), and the Commander Spellbook combo database (108k+ combos). It can then generate deck variants as falsifiable hypotheses, check them against Commander Bracket rules and house rules, and simulate them in Forge to get card-level and deck-level statistics — cast rate, win-rate contribution, synergy pairs — instead of guesses.

See docs/howto.md for the full methodology: what you supply, how pool size and game count determine whether results mean anything, how the Forge AI's weaknesses get worked around, and how to read the output honestly.

Layout

mtg/
  tools/                      generic engine — works on ANY deck given its config.yaml
    mtg                         single dispatcher: tools/mtg decks/<name> <tool> [args...]
    deckctx.py                  resolves --deck / $MTG_DECK, loads config.yaml
    cards.py                    shared index: oracle + tags + edhrecRank + combos + blacklist
    bracket_check.py            Bracket 3 + house-rule check (game changers, MLD, vacuum combos)
    combos.py                   offline combo finder (included / one-card-away)
    roles.py                    role classification + budget profile
    generate.py                 role-budget variant generator (recipes in <deck>/variants.yaml)
    make_variants.py            hand-curated cut/add variant generator (same file)
    to_dck.py                   decklist -> Forge .dck
    batch_run.py / run_forge.sh / ab_test.py    Forge simulation, in bulk or A/B
    forge_report.py / card_stats.py / power.py  turn Forge logs into reports
    compare.py                   rebuilds a deck's decks/README.md summary table
    similar.py / similar_text.py / embed.py     semantic "cards like this" search
    fetch_data.py                downloads the shared bulk data below

  decks/
    reaper-king-no-more/       one deck: data + config, the reference example
      config.yaml               commander, pool, constraints, role budget, protected cards
      decklist.txt               the 100 cards
      blacklist.txt              cards you refuse to play, with reasons
      variants.yaml              variant hypotheses (role-budget recipes + hand-curated cut/add)
      plan.md / README.md        this deck's own design notes
      forge-overrides/           AI tuning for cards Forge's default AI can't pilot
      sim_substitutions.yaml     simulation-only stand-ins for AI-unpilotable cards
      decks/<variant>/           RESULTS: one dir per archetype variant (generated)

  data/                       gitignored, ~1.2GB — Scryfall/MTGJSON/Spellbook bulk downloads.
                               Shared across every deck: this is Commander-format-wide data,
                               not deck-specific. One copy at the repo root.
  out/                       gitignored scratch output
  docs/howto.md              the full methodology (pool size, game count, AI tuning, caveats)

Quickstart

# one-time, shared across all decks
python3 tools/fetch_data.py            # ~1.2GB: Scryfall, MTGJSON, Spellbook
python3 tools/embed.py                 # -> data/embeddings.npz
# install Forge 2.0.14 to ~/forge (needs Java 17+)

# check the reference deck
python3 tools/bracket_check.py --deck decks/reaper-king-no-more
python3 tools/combos.py --deck decks/reaper-king-no-more
python3 tools/roles.py --deck decks/reaper-king-no-more

# or via the single dispatcher
tools/mtg decks/reaper-king-no-more bracket_check
tools/mtg decks/reaper-king-no-more variant no-mikaeus --sim 24

To add a new deck: copy the shape of decks/reaper-king-no-more/ (at minimum config.yaml, decklist.txt, blacklist.txt), point --deck at it, and every tool in tools/ works on it immediately — nothing in tools/ is hardcoded to a specific commander, color identity, or deck name. See docs/howto.md for what each config field does and why, and decks/reaper-king-no-more/plan.md / README.md for a worked example of building one deck end to end.