### One-time setup on the MacBook Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/REBEL_TRAIN_RUNBOOK.md Clone the repo and install numpy, the only third-party dependency for training. Verify numpy installation. ```bash git clone mtg && cd mtg # or pull latest on an existing clone python3 -m pip install numpy # the only third-party dep for training python3 -c "import numpy; print('numpy', numpy.__version__)" ``` -------------------------------- ### Quickstart: create game, inspect legal moves, push/pop, and loop Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows the basic usage of the mtg.Game class: creating a game, inspecting legal moves, pushing and popping moves, and running a simple game loop. The game is initialized with a demo matchup and seed 0. ```python import mtg g = mtg.Game() # the demo Gruul-vs-Dimir matchup (real cards), seed 0 g.legal_moves # the action tuples you may play now g.push(g.legal_moves[0]) # make a move; advances to the next decision point g.pop() # take it back (no snapshots needed — env.step is pure) g.turn # whose decision it is g.life() # {'alice': 20, 'bob': 20} g.battlefield() # {permanent: controller} g.hand('alice') # ['grizzly_bears_6', ...] g.is_game_over(), g.outcome(), g.result() while not g.is_game_over(): # a trivial "first legal option" policy g.push(g.legal_moves[0]) ``` -------------------------------- ### Install dependencies on ARM Mac via Homebrew Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Install all required packages. mcpp is available as a prebuilt binary, so skip the Linux from-source fixes. ```bash brew install autoconf automake libtool mcpp bison flex gcc ncurses sqlite libffi ``` -------------------------------- ### Bash: Install dev dependencies and run tests Source: https://github.com/yevbar/witchcraft/blob/master/packages/inthearena/README.md Install the package in editable mode (requires pydantic>=2) and run the test suite with PYTHONPATH set to src. ```bash pip install -e . # needs pydantic>=2 PYTHONPATH=src python3 tests/test_mtga.py ``` -------------------------------- ### Install dependencies on Fedora (Linux/aarch64) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Install required packages from Fedora repos. bison, flex, m4, g++, and pkg-config are already present. ```bash dnf install autoconf automake libtool ncurses-devel zlib-ng-devel sqlite-devel libffi-devel ``` -------------------------------- ### Install python-mtg package variants Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/INTERPRETER_DECOUPLE_PLAN.md Commands to install the mtg engine driver with various optional dependencies. ```bash pip install python-mtg # a COMPLETE shim + binary: the engine driver + python-chess API, # the datalog/ artifact, and the compiled souffle backend. # imports ZERO interpreter Python. pip install python-mtg[analysis] # + the interpreter-based card/deck analysis (card_spacy, card_synergy) pip install python-mtg[parser] # + the interpreter itself (regenerate the datalog build from card text) pip install python-mtg[learn] # + torch value nets ``` -------------------------------- ### Install prerequisites on Fedora/RHEL Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/RUNBOOK.md Install required build dependencies for the elastic incremental engine on Fedora/RHEL Linux distributions. Includes git, cmake, bison, flex, g++, python3, and development libraries. ```bash sudo dnf install -y git cmake bison flex g++ python3 libffi-devel ncurses-devel zlib-ng-devel ``` -------------------------------- ### Sanity check for JDK, Forge fatjar, and Python engine imports Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/RUNNING.md Run these commands to verify the required external artifacts are installed and accessible before a real run. The first checks the JDK version (must be 17.x), the second confirms the Forge fatjar exists, and the third verifies the Python engine modules are importable. ```bash "$JDK/bin/java" -version # -> openjdk 17.x ls -la "$FORGE/forge-gui-desktop/target/"*jar-with-dependencies.jar python3 -c "import deck_evaluator, interaction_evaluator, forge_bridge; print('engine OK')" ``` -------------------------------- ### Install prerequisites on Debian/Ubuntu Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/RUNBOOK.md Install required build dependencies for the elastic incremental engine on Debian/Ubuntu Linux distributions. Includes git, cmake, bison, flex, g++, python3, and development libraries. ```bash sudo apt-get install -y git cmake bison flex g++ python3 libffi-dev libncurses-dev zlib1g-dev ``` -------------------------------- ### Engine architecture file layout Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows the file layout and roles of the engine components: datalog rules, driver, env, game setup, and the mtg.Game wrapper. This is a conceptual overview, not executable code. ```text datalog/engine_rules.dl derives the consequences of a state — the rules driver.py applies them, owns chance/choice — the shim env.py enumerates legal actions, steps PURELY — the referee game.py (top level) builds a real game (decks, mulligan) — the setup mtg.Game a stateful object wrapping all of it — this package ``` -------------------------------- ### Build mcpp 2.7.2 from source for aarch64 Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Compiles mcpp 2.7.2 into ~/.local without root, applying fixes for GCC 15/aarch64: aarch64 config.guess, CFLAGS for C23 keywords and GCC14 permerrors, and an LL_FORM define. After installation, add ~/.local/bin to PATH so Soufflé can find the mcpp binary. ```bash curl -fsSL -o /tmp/mcpp-2.7.2.tar.gz \ https://downloads.sourceforge.net/project/mcpp/mcpp/V.2.7.2/mcpp-2.7.2.tar.gz cd /tmp && tar xf mcpp-2.7.2.tar.gz && cd mcpp-2.7.2 ./configure --prefix=$HOME/.local \ --build=aarch64-unknown-linux-gnu --host=aarch64-unknown-linux-gnu \ # 2008 config.guess can't detect aarch64 CFLAGS="-std=gnu11 -fcommon -fpermissive -w" # C23 true/false keywords + GCC14 permerrors sed -i 's|/\* #undef LL_FORM \*/|#define LL_FORM "ll"|' src/config.h # long-long printf fmt left undefined here make && make install # -> ~/.local/bin/mcpp export PATH=$HOME/.local/bin:$PATH ``` -------------------------------- ### Load Decks and Create a Game Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to list bundled decks, load a deck by name or file path, and use them in Game and benchmark. Deck lists are plain text with comments and blank lines ignored. ```python import mtg mtg.bundled_decks() # ['izzet_prowess', 'mono_black_zombies', # 'mono_green_landfall', 'selesnya_landfall'] deck = mtg.load_deck("mono_green_landfall") # bundled name -> flat list ['Forest', 'Forest', ...] deck = mtg.load_deck("/path/to/my.txt") # ...or any file path g = mtg.Game({"alice": mtg.load_deck("izzet_prowess"), "bob": mtg.load_deck("mono_black_zombies")}) mtg.benchmark(MyBot(), decks={"alice": mtg.load_deck("selesnya_landfall"), "bob": mtg.load_deck("selesnya_landfall")}) ``` -------------------------------- ### Run smoke test (1 game, ~20 min) Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/RUNNING.md Runs a quick commander tournament with one game. Requires JDK and FORGE environment variables. On a desktop host with a display, prepend FORGE_HEADLESS=false to avoid HeadlessException. Watch /tmp/cedh_smoke.out and /tmp/cedh_tournament_logs/game_p9300.log for success. ```bash cd JDK=/path/to/jdk-17 FORGE=/path/to/forge \ python3 forge_integration/run_commander_tournament.py --quick \ > /tmp/cedh_smoke.out 2>&1 ``` -------------------------------- ### Reproduce mtg and Forge benchmarks Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/SIMULATION_BENCHMARK.md Commands to reproduce the benchmark. The first runs the mtg benchmark. The second prepares Forge decks, compiles ForgeBench.java against the fatjar, and runs the Forge benchmark with two deck paths. Requires JDK 17 and the fatjar. ```bash python3 experiments/bench_mtg.py ``` ```bash python3 -c "import sys; sys.path.insert(0,'forge_integration'); from run_commander_tournament import write_decks; write_decks()" FATJAR=$FORGE/forge-gui-desktop/target/forge-gui-desktop-2.0.13-SNAPSHOT-jar-with-dependencies.jar $JDK/bin/javac -cp "$FATJAR" -d /tmp/forge_bench_out forge_integration/ForgeBench.java FORGE_ASSETS="$FORGE/forge-gui/" $JDK/bin/java \ -Ddeck0=/tmp/cedh_decks/kinnan.dck -Ddeck1=/tmp/cedh_decks/bluefarm.dck \ -cp "$FATJAR:/tmp/forge_bench_out" ForgeBench ``` -------------------------------- ### Build Souffle and run toy oracle with incremental/build_souffle.sh Source: https://github.com/yevbar/witchcraft/blob/master/incremental/SOUFFLE_FORK_NOTES.md Run this command from the project root to build the third_party/souffle directory and execute the toy oracle. The comment indicates the build and run steps are combined in this script. ```bash bash incremental/build_souffle.sh # builds third_party/souffle, runs the toy oracle ``` -------------------------------- ### Build Soufflé fork from source (Linux/aarch64) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Bootstrap, configure with a local prefix, and build with memory-capped parallelism. Produces src/souffle. ```bash ./bootstrap && ./configure --prefix=$PWD/install && make -j2 ``` -------------------------------- ### Build the souffle fork with incremental/build_souffle.sh Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/RUNBOOK.md Runs CMake + make (Release) and prints a toy sanity check (transitive closure with a multi-support retraction). Produces the binary at third_party/souffle/build/src/souffle. Build takes a few minutes; fails on Bison if the system Bison is < 3.2. ```bash bash incremental/build_souffle.sh ``` ```text third_party/souffle/build/src/souffle ``` -------------------------------- ### Game.serialize() Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Serializes the current position to a JSON string, preserving the RNG state so that future play is identical. Does not include move history or installed policies. ```APIDOC ## Game.serialize() ### Description Serializes the current position to a JSON string, preserving the RNG state so that future play is identical. Does not include move history or installed policies. ### Method Method ### Endpoint Game.serialize() ### Parameters None ### Request Example ```python blob = g.serialize() ``` ### Response #### Success Response Returns a JSON string representing the game state. ``` -------------------------------- ### Configure the engine backend Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to create a Game with the incremental backend and check which engine is available. The incremental backend is byte-identical to the default, ~2× faster on large states, and degrades gracefully if the souffle fork isn't built. ```python g = mtg.Game(incremental=True) # in-process incremental update backend; g.incremental reports if it engaged mtg.engine_available() # 'incremental' | 'native' | 'inproc' | 'interpreter' ``` -------------------------------- ### Inspecting cards: card() and permanents() Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to get derived characteristics of a card and filter permanents by controller or type. The card() method returns a dict with derived values like power/toughness and printed types/colors. ```python g.card("grizzly_bears_6") # {'id':..., 'zone':'battlefield', 'controller':'alice', 'types':['creature'], 'subtypes':['bear'], # 'colors':['green'], 'is_creature':True, 'power':2, 'toughness':2, 'keywords':[], # 'tapped':False, 'summoning_sick':False} g.permanents() # views for everything on the battlefield (one engine eval for all) g.permanents(player="alice") # filter by controller g.permanents(type="creature") # filter by printed type ``` -------------------------------- ### Run 1v1 tournament (laptop-survivable fallback) Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/RUNNING.md Runs a 1v1 tournament with a single bot. The --quick variant runs combos plus one bo1 pairing; the full variant runs a full 1v1 tournament. On a desktop host with a display, prepend FORGE_HEADLESS=false to avoid HeadlessException. ```bash python3 forge_integration/run_tournament.py --quick # combos + one bo1 pairing python3 forge_integration/run_tournament.py # full 1v1 tournament ``` -------------------------------- ### mtg.Game() Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Creates a new game instance. The default constructor initializes a demo Gruul-vs-Dimir matchup with real cards and a fixed seed. ```APIDOC ## mtg.Game() ### Description Creates a new game instance. The default constructor initializes a demo Gruul-vs-Dimir matchup with real cards and a fixed seed. ### Method Constructor ### Endpoint mtg.Game() ### Parameters None ### Request Example ```python import mtg g = mtg.Game() ``` ### Response #### Success Response Returns a Game object representing the initial game state. #### Response Example ```python g = mtg.Game() ``` ``` -------------------------------- ### Readable names: name(), card()['name'], describe() Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to recover the printed card name from an engine id/slug, and how to get a human-readable description of a move. The name() method uses corpus-correct casing, not naive title-case. ```python g.name("grizzly_bears_6") # 'Grizzly Bears' g.name(commander_id) # 'Magda, Brazen Outlaw' (corpus-correct, not naive title-case) g.card(id)["name"] # card()/permanents() views include a 'name' field g.describe(move) # 'alice: cast Grizzly Bears' (vs Game.describe_move(move), id-form) ``` -------------------------------- ### Define a custom Player subclass Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to create a custom heuristic player by subclassing Player and overriding choose_move. The example implements an aggressive strategy that attacks when possible. Optionally override decide() to steer sub-choices. ```python class Aggro(Player): def choose_move(self, game): atk = [m for m in game.legal_moves if m[0] == "attack"] return max(atk, key=lambda m: len(m[1])) if atk else game.legal_moves[0] # (optionally also override decide() to steer targets/modes/blocks) ``` -------------------------------- ### Run commander tournament smoke test Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/SMOKE_FINDINGS.md Run the commander tournament smoke test on a desktop host. Requires JDK 17 (Temurin) and a Forge installation path. The FORGE_HEADLESS=false flag enables GUI mode, and output is redirected to /tmp/cedh_smoke.out. ```bash JDK=/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home \ FORGE=/path/to/forge FORGE_HEADLESS=false \ python3 forge_integration/run_commander_tournament.py --quick > /tmp/cedh_smoke.out 2>&1 ``` -------------------------------- ### Play a game with built-in players Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to run a full game using RandomPlayer and GreedyPlayer. The result includes the outcome. RandomPlayer can take an optional seed for reproducibility. ```python from mtg import Player, RandomPlayer, GreedyPlayer, play result = play({"alice": RandomPlayer(), "bob": GreedyPlayer()}, seed=7) result.outcome() # ('alice', 'bob lost') ``` -------------------------------- ### Initialize submodule and run toy sanity test Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Update the souffle-elastic submodule, build, then run the toy test: compile tc.dl with --incremental, bootstrap, remove edge(2,4), commit, and compare out/path.csv to the recompute oracle. ```bash git submodule update --init third_party/souffle-elastic ``` ```bash souffle --incremental -o tc tc.dl ``` ```bash remove edge(2, 4) ``` ```bash commit ``` -------------------------------- ### Create an observation from a seat Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to get a redacted, read-only Game from a specific seat. Observations hide hidden information like opponents' hands and library contents, but provide true counts and public info. Attempting to push moves raises a RuntimeError. ```python obs = g.observation("alice") # a redacted, READ-ONLY Game from alice's seat (§103) obs.hand("alice") # alice's real hand obs.hand("bob") # [] — hidden; but obs.hand_count("bob") gives the true size obs.library_size("bob") # true count (rows hidden, count carried) obs.battlefield(); obs.life() # public info kept obs.library_top() # cards this seat scried/looked at, in order (else []) obs.push(...) # RuntimeError — observations are read-only (legality lives on the true game) ``` -------------------------------- ### Put keg-only bison and flex on PATH (ARM Mac) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Export PATH to include brew's bison and flex binaries before running ./configure, because Apple's system bison is too old. ```bash export PATH="$(brew --prefix bison)/bin:$(brew --prefix flex)/bin:$PATH" ``` -------------------------------- ### Run a Commander Variant Game Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Shows how to create a Game with the 'commander' variant, specifying commanders and seed. Also demonstrates self_play and a play call with RandomPlayer instances. ```python import game as _setup g = mtg.Game(_setup.COMMANDER_DECKS, variant="commander", seed=1, commanders=_setup.COMMANDERS) # §903: 40 life, command zone mtg.self_play(seed=7) # full random game -> winner result = play({"alice": RandomPlayer(), "bob": RandomPlayer()}, _setup.COMMANDER_DECKS, variant="commander", commanders=_setup.COMMANDERS) # players, any variant ``` -------------------------------- ### Build Soufflé-elastic fork with autotools and mcpp Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Compiles the elastic fork after mcpp is on PATH. Runs bootstrap and configure, then builds with a memory cap via systemd-run to avoid OOM. The resulting binary is src/souffle with --incremental and --provenance=...subtreeHeights. ```bash git submodule update --init third_party/souffle-elastic cd third_party/souffle-elastic && export PATH=$HOME/.local/bin:$PATH ./bootstrap # autotools chain (non-fatal obsolete-macro warnings) ./configure --prefix=$PWD/install # finds mcpp + ncurses/zlib/sqlite/libffi systemd-run --user --scope -p MemoryMax=5G -p MemorySwapMax=2G make -j2 # memory-capped; ~5 min, no OOM # -> src/souffle (0.0.3-4408-g540cf8d33; has --incremental AND --provenance=...subtreeHeights) ``` -------------------------------- ### Benchmark against Forge with benchmark_vs_forge Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Benchmark a bot against Forge, which acts as the source of truth and runs a mirror. Requires Forge installed and spins up one JVM per game (heavy). The returned dictionary includes bot_win_rate, avg_turns, mirror_modeled_frac, and mirror_endorsed_frac, which indicate how much of Forge's game mtg could model and independently endorse as legal. ```python from mtg.benchmark import benchmark_vs_forge benchmark_vs_forge("engine", games=10) # -> {'bot_win_rate': 0.3, 'avg_turns': 23, # 'mirror_modeled_frac': 0.93, # how much of Forge's real game mtg could MODEL # 'mirror_endorsed_frac': 0.17, # how much it could independently ENDORSE as legal (the engine gaps) # 'source_of_truth': 'forge', ...} ``` -------------------------------- ### Run benchmark scripts (bash) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/RUNBOOK.md Run the benchmark scripts to measure single-move speedup, search crossover, and per-stratum recompute cost. These are optional and provide performance insights. ```bash python3 incremental/harness/bench.py # single-move speedup vs recompute (~2x at 100–500 facts) python3 incremental/harness/search_bench.py # where it helps the real search (crossover ~250 facts) python3 incremental/harness/profile_strata.py # per-stratum recompute cost (the hotspot) ``` -------------------------------- ### Run full 4-player FFA tournament (4 games, rotating seats) Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/RUNNING.md Runs a full commander tournament with 4 games, each ~20-30 min, totaling ~1.5-2 h wall time. Requires JDK and FORGE environment variables. On a desktop host with a display, prepend FORGE_HEADLESS=false to avoid HeadlessException. ```bash cd JDK=/path/to/jdk-17 FORGE=/path/to/forge \ python3 forge_integration/run_commander_tournament.py \ > /tmp/cedh_full.out 2>&1 ``` -------------------------------- ### Run a training loop with periodic Forge benchmarks Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Runs a training loop over 12 rounds with fixed decks, benchmarking every 4 rounds. The output history contains data sizes and benchmark results including win rate vs random and Forge result. ```python from mtg.rebel_train import train_loop out = train_loop(rounds=12, train_decks=("mono_green_landfall", "mono_white_soldiers"), benchmark_every=4, save_path="rebel_vnet") # alice=green (trains), bob=white (random) out["history"] # every round: data size; benchmark rounds also carry win_rate_vs_random + the Forge result ``` -------------------------------- ### Build Forge with Maven Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/README.md Commands to clone and build the Forge repository, producing the fat jar required by the integration scripts. Use the matching Maven profile for your OS. ```bash git clone https://github.com/Card-Forge/forge cd forge mvn -U -B clean -P windows-linux install # use the matching profile for your OS ``` -------------------------------- ### Python usage of engine_incremental Source: https://github.com/yevbar/witchcraft/blob/master/incremental/README.md Shows how to use the incremental backend: bootstrap with evaluate, then push/pop for game-tree search. The branch context manager combines push and pop-on-exit. ```python import engine_incremental as E # the in-process backend (fork --incremental .so, ctypes) E.evaluate(fkey) # bootstrap once, then incremental updates (diff from last state) # game-tree search primitives (O(diff) descend/ascend, no snapshot): result = E.push(child_fkey) # apply a move, remember the parent E.pop() # roll back to the parent via the inverse diff with E.branch(child_fkey) as result: ... # push + pop-on-exit ``` -------------------------------- ### Use incremental engine API (Python) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/RUNBOOK.md Import the incremental engine and use evaluate, push, pop, and branch for game-tree search. The backend is opt-in via MTG_INCREMENTAL=1; default is engine_inproc. Both are byte-identical. ```python import engine_incremental as E # in-process fork --incremental .so, ctypes bridge E.evaluate(fkey) # bootstrap once, then incremental updates (auto-diffs from last state) # game-tree search primitives (O(diff), no snapshot): result = E.push(child_fkey) # apply a move, remember the parent E.pop() # roll back via the inverse diff with E.branch(child_fkey) as result: ... # push + auto-pop on exit ``` -------------------------------- ### Run Forge integration scripts Source: https://github.com/yevbar/witchcraft/blob/master/forge_integration/README.md Commands to run the headless Forge games: engine vs AI, combo kill, and with recording. Set JDK and FORGE environment variables to your paths. ```bash # mtg (engine-driven) vs the Forge AI, headless JDK=/path/to/jdk17 FORGE=/path/to/forge ./forge_integration/run.sh # a stacked-hand, lookahead-driven turn-1 combo kill (Forge confirms the win) JDK=/path/to/jdk17 FORGE=/path/to/forge ./forge_integration/run_combo.sh # also dump the board per phase and render an mp4 (needs Pillow + ffmpeg) RECORD=1 JDK=… FORGE=… ./forge_integration/run.sh ``` -------------------------------- ### Player Interface and play() Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Define custom players by subclassing Player and run full games with the play() function. ```APIDOC ## Player Interface and play() ### Description Players answer engine decisions: top-level moves via `choose_move(game)` and internal sub-choices via `decide(view, key, options, default)`. The `play()` function runs a full game with given players. ### Methods - `play(players, seed=None, variant=None)` - Runs a full game with the given players. - `Player.choose_move(game)` - Override to implement custom move selection. - `Player.decide(view, key, options, default)` - Override to steer sub-choices (optional). ### Parameters - **players** (dict) - Required - Mapping of seat names to Player instances. - **seed** (int) - Optional - Seed for reproducibility. - **variant** (string) - Optional - Game variant (e.g., 'two-player'). ### Built-in Players - `RandomPlayer(seed=None)` - Uniform-random player; seed=None uses game's RNG. - `GreedyPlayer` - Takes engine's default at every decision. ### Example ```python from mtg import Player, RandomPlayer, GreedyPlayer, play result = play({"alice": RandomPlayer(), "bob": GreedyPlayer()}, seed=7) result.outcome() class Aggro(Player): def choose_move(self, game): atk = [m for m in game.legal_moves if m[0] == "attack"] return max(atk, key=lambda m: len(m[1])) if atk else game.legal_moves[0] play({"alice": Aggro(), "bob": RandomPlayer(seed=1)}, variant="two-player", seed=3) ``` ``` -------------------------------- ### Benchmark a trained value net against Forge Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Loads a saved value net and benchmarks it against Forge using a NetValue wrapper. Forge games are heavy and may stall, so a generous timeout is recommended. ```python from mtg.rebel_train import TinyValueNet, NetValue from mtg.benchmark import benchmark_vs_forge benchmark_vs_forge(NetValue(TinyValueNet.load("rebel_vnet")), games=5, timeout=300) ``` -------------------------------- ### Engine Backend Selection Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Create a game with an optional incremental update backend and check which engine backend is available. ```APIDOC ## Engine Backend ### Description Creates a Game instance with an optional incremental update backend, and provides a function to check available engine backends. ### Methods - `mtg.Game(incremental=True)` - Creates a game with the incremental backend if available. - `mtg.engine_available()` - Returns a string indicating the available engine backend: 'incremental', 'native', 'inproc', or 'interpreter'. ### Parameters - **incremental** (boolean) - Optional - If True, uses the incremental update backend (process-global selection). Defaults to False if not specified. ### Example ```python g = mtg.Game(incremental=True) mtg.engine_available() ``` ``` -------------------------------- ### Play a game with ReBeLPlayer and RandomPlayer Source: https://github.com/yevbar/witchcraft/blob/master/packages/mtg/README.md Sets up a two-player game where Alice uses ReBeLPlayer with specified parameters and Bob uses RandomPlayer. The play function runs the game. ```python from mtg import ReBeLPlayer, RandomPlayer, play play({"alice": ReBeLPlayer(worlds=4, iterations=100, depth=3, time_budget=5.0), "bob": RandomPlayer()}) ``` -------------------------------- ### Unverified build commands for mac mini Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Intended build steps for the mac mini, unverified here. Keep parallelism low with -j1 due to the large C++ compile. Provenance is on by default. ```shell cd third_party/souffle-elastic ./bootstrap ./configure --prefix=$PWD/install # add --disable-64bit-domain etc. as needed; provenance on by default make -j1 # KEEP LOW PARALLELISM; this is a big C++ compile make install ``` -------------------------------- ### Compile and run elastic fork with REPL commands Source: https://github.com/yevbar/witchcraft/blob/master/third_party/toy_elastic_test/README.md Shows how to compile tc.dl with incremental support and drive the REPL to remove an edge and commit. The commit runs executeSubroutine("incremental_update_clear_diffs") then ("update"). Note: pre-2.0 flags; -c = compile. ```bash souffle --incremental -c tc.dl -o tc # NB: pre-2.0 flags; -c = compile echo 'edge 1 2 ... ' > facts/edge.facts # bootstrap EDB (annotated; see notes) ./tc # bootstraps, then enters the incremental REPL: > remove edge(2, 4) > commit # runs executeSubroutine("incremental_update_clear_diffs") then ("update") # dump path and diff path == oracle above ``` -------------------------------- ### Use brew's g++-15 as CXX (ARM Mac) Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/SOUFFLE_ELASTIC_BUILD_NOTES.md Set CXX to g++-15 to avoid divergence with Apple clang. Expect at least as bad a blocker #2 under clang/libc++. ```bash CXX=g++-15 ``` -------------------------------- ### Run smoke test with guard and watchdog Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/MAGEZERO_BRAIN_PLAN.md Run this smoke test before any real experiment. It uses a single process, no Pool, 2 games, max_moves=400, one rung, guard on, and a 60-second watchdog. Expected to complete in well under 60 seconds, print a score dict, and keep peak RSS below 4 GB; if it hangs or the watchdog fires, stop and do not scale up. ```python import _safe; _safe.clamp(mem_gb=4); _safe.watchdog(60) from mtg.ladder import compare from mtg.heuristic import HeuristicPlayer # brain = the Stage-N candidate (Stage 0: GreedyPlayer; Stage 1+: PolicyPlayer(load(...))) print(compare(brain, HeuristicPlayer(), games=2, seed=1, max_moves=400, explicit_lands=True)) ``` -------------------------------- ### Build oracle corpus, card coverage, and cards Datalog Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/ORACLE.md Runs the Python scripts to generate the oracle corpus, report current card coverage percentage, and build the cards Datalog file. The cards.dl file is gitignored and can be checked with Souffle. ```bash python3 interpreter/build_oracle_corpus.py # -> mtgjson/oracle_corpus.json python3 experiments/card_coverage.py # current % python3 interpreter/build_cards.py # -> datalog/cards.dl (gitignored; souffle-checkable) ``` -------------------------------- ### Run ReBeL training script Source: https://github.com/yevbar/witchcraft/blob/master/markdowns/REBEL_TRAIN_RUNBOOK.md Commands to run the training script from the repo root. Includes a smoke test, a real run, and a run that skips Forge. ```bash # Smoke test first (~30s): tiny rounds/games, no Forge. python3 experiments/run_rebel_train.py --quick --out rebel_runs/smoke ``` ```bash # Real run: 20 rounds, benchmark every 4, self-eval at the end. python3 experiments/run_rebel_train.py --rounds 20 --out rebel_runs/green_vnet ``` ```bash # Skip Forge even if installed: python3 experiments/run_rebel_train.py --rounds 20 --no-forge ``` -------------------------------- ### Run local sanity check commands Source: https://github.com/yevbar/witchcraft/blob/master/RULES_UPDATE_NOTES.md Run these commands in order to build the project and execute the test suite. The environment variables OMP_NUM_THREADS, MKL_NUM_THREADS, and OPENBLAS_NUM_THREADS are set to 1 to limit threading, and MTG_NO_SPACY=1 disables spaCy for tests that don't need it. The build script is run first, followed by the test scripts. ```sh OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 python3 build.py OMP_NUM_THREADS=1 MTG_NO_SPACY=1 python3 tests/test_rules_2026.py OMP_NUM_THREADS=1 MKL_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 python3 tests/test_rules_build.py OMP_NUM_THREADS=1 MTG_NO_SPACY=1 python3 tests/test_power_up.py OMP_NUM_THREADS=1 MTG_NO_SPACY=1 python3 tests/test_teamwork.py OMP_NUM_THREADS=1 MTG_NO_SPACY=1 python3 tests/test_nonmana_costs.py ```