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
51 lines
2 KiB
Bash
Executable file
51 lines
2 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Batch Forge simulation for one deck variant.
|
|
#
|
|
# tools/run_forge.sh <deck_dir> [games] [seed] [variant]
|
|
#
|
|
# deck_dir is the deck's own directory, e.g. decks/reaper-king-no-more.
|
|
# variant defaults to "no-mikaeus". Writes into <deck_dir>/decks/<variant>/sim/:
|
|
# <stamp>.log full Forge output (every game)
|
|
# <stamp>.txt human report
|
|
# <stamp>.json machine-readable results
|
|
set -euo pipefail
|
|
|
|
DECK_DIR="${1:?usage: run_forge.sh <deck_dir> [games] [seed] [variant]}"
|
|
GAMES="${2:-24}"
|
|
SEED="${3:-20260907}"
|
|
VARIANT="${4:-no-mikaeus}"
|
|
FORGE="$HOME/forge"
|
|
FORGE_DECKS="$HOME/.forge/decks/commander"
|
|
TOOLS="$(cd "$(dirname "$0")" && pwd)"
|
|
DECK_DIR="$(cd "$DECK_DIR" && pwd)"
|
|
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
VDIR="$DECK_DIR/decks/$VARIANT"
|
|
OUT="$VDIR/sim/$STAMP"
|
|
|
|
[ -f "$VDIR/decklist.txt" ] || { echo "no decklist: $VDIR/decklist.txt" >&2; exit 1; }
|
|
mkdir -p "$VDIR/sim"
|
|
|
|
OPPONENTS=(
|
|
"Faceless Menace [C19] [2019].dck"
|
|
"Merciless Rage [C19] [2019].dck"
|
|
"Arcane Maelstrom [C20] [2020].dck"
|
|
)
|
|
|
|
# .dck filename and [metadata] Name= are derived from the deck (slug /
|
|
# config.yaml `name:`), never hardcoded -- ask to_dck.py what it wrote.
|
|
DCK_NAME="$(python3 "$TOOLS/to_dck.py" --deck "$DECK_DIR" "$VDIR/decklist.txt" \
|
|
-o "$FORGE_DECKS/$(basename "$DECK_DIR").dck" | sed -n 's/^wrote \(.*\) (.*/\1/p')"
|
|
DCK_NAME="$(basename "$DCK_NAME")"
|
|
|
|
echo "[$VARIANT] $GAMES games, 4-player Commander pod, seed $SEED"
|
|
# stdbuf keeps the log readable while the batch runs -- without it the pipe
|
|
# buffers everything until Forge exits and progress is invisible.
|
|
( cd "$FORGE" && timeout 7200 stdbuf -oL -eL sh forge.sh sim \
|
|
-d "$DCK_NAME" "${OPPONENTS[@]}" \
|
|
-f Commander -n "$GAMES" -s "$SEED" ) 2>&1 \
|
|
| stdbuf -oL grep -vE "WARNING:|not assigned to any set|Upcoming set|^Phase:|^Warning:|Correcting zone" \
|
|
| tee "$OUT.log" \
|
|
| stdbuf -oL grep -E "^Game Result:" || true
|
|
|
|
python3 "$TOOLS/forge_report.py" "$OUT.log" "$OUT.json" "$OUT.txt" \
|
|
"$GAMES" "$SEED" "$VDIR/decklist.txt"
|