Fix extension

This commit is contained in:
mark rosenberg 2026-09-12 00:46:38 -04:00
parent 1e177058e0
commit 5f41bb9429
44 changed files with 65 additions and 65 deletions

View file

@ -56,7 +56,7 @@ Each variant is a hypothesis: change a few cards, then measure what it does to l
``` ```
decks/<variant>/ decks/<variant>/
decklist.mgt the 100 cards, "N Card Name" per line decklist.mtg the 100 cards, "N Card Name" per line
bracket.txt bracket-3 + house-rule check bracket.txt bracket-3 + house-rule check
combos.txt combos already in the deck, and one card away combos.txt combos already in the deck, and one card away
sim/ Forge runs, one triple per batch: sim/ Forge runs, one triple per batch:

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:37Z by `tools/variant.py`_ _generated 2026-09-07 23:37Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:37Z by `tools/variant.py`_ _generated 2026-09-07 23:37Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:51Z by `tools/variant.py`_ _generated 2026-09-07 15:51Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-08 02:07Z by `tools/variant.py`_ _generated 2026-09-08 02:07Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:37Z by `tools/variant.py`_ _generated 2026-09-07 23:37Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:38Z by `tools/variant.py`_ _generated 2026-09-07 23:38Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:51Z by `tools/variant.py`_ _generated 2026-09-07 15:51Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:51Z by `tools/variant.py`_ _generated 2026-09-07 15:51Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:52Z by `tools/variant.py`_ _generated 2026-09-07 15:52Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:51Z by `tools/variant.py`_ _generated 2026-09-07 15:51Z by `tools/variant.py`_
- **bracket 3: FAIL** (two-card vacuum combos: 7) - **bracket 3: FAIL** (two-card vacuum combos: 7)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 15:56Z by `tools/variant.py`_ _generated 2026-09-07 15:56Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:37Z by `tools/variant.py`_ _generated 2026-09-07 23:37Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -3,13 +3,13 @@
_generated 2026-09-07 23:38Z by `tools/variant.py`_ _generated 2026-09-07 23:38Z by `tools/variant.py`_
- **bracket 3: PASS** (two-card vacuum combos: 0) - **bracket 3: PASS** (two-card vacuum combos: 0)
- 86 lines in `decklist.mgt` - 86 lines in `decklist.mtg`
## files ## files
| file | what it is | | file | what it is |
| --- | --- | | --- | --- |
| `decklist.mgt` | the deck, `N Card Name` per line | | `decklist.mtg` | the deck, `N Card Name` per line |
| `bracket.txt` | bracket-3 + house-rule check | | `bracket.txt` | bracket-3 + house-rule check |
| `combos.txt` | combos in the deck, and one card away | | `combos.txt` | combos in the deck, and one card away |
| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data | | `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |

View file

@ -300,7 +300,7 @@ Card selection is offline and instant. Simulation is Forge, and it's the slow st
``` ```
make data (once/per set) ──► data/*.jsonl, variants.json, AtomicCards.json make data (once/per set) ──► data/*.jsonl, variants.json, AtomicCards.json
│ │
decklist.mgt ─────────────────────────► │ blacklist.txt ──┐ decklist.mtg ─────────────────────────► │ blacklist.txt ──┐
│ │ │ │ │ │
├─► bracket_check.py ── game changers, MLD, vacuum combos, blacklist ──┐ ├─► bracket_check.py ── game changers, MLD, vacuum combos, blacklist ──┐
├─► combos.py ── included / almostIncluded (offline) ──────────┤ ├─► combos.py ── included / almostIncluded (offline) ──────────┤
@ -313,13 +313,13 @@ Card selection is offline and instant. Simulation is Forge, and it's the slow st
Every simulation run writes a timestamped triple into `out/`: the full Forge `.log`, Every simulation run writes a timestamped triple into `out/`: the full Forge `.log`,
a human `.txt` report, and a `.json` artifact — and the report footer names a human `.txt` report, and a `.json` artifact — and the report footer names
`decklist.mgt`, the log, the JSON, and itself, so any number is traceable. `decklist.mtg`, the log, the JSON, and itself, so any number is traceable.
## 4. Repo layout ## 4. Repo layout
``` ```
reaper-king-no-more/ reaper-king-no-more/
decklist.mgt # working deck, "1 Card Name" per line decklist.mtg # working deck, "1 Card Name" per line
blacklist.txt # cards I won't play, "Card Name | reason" blacklist.txt # cards I won't play, "Card Name | reason"
plan.md plan.md
data/ # ~1.2GB, all gitignored data/ # ~1.2GB, all gitignored
@ -327,7 +327,7 @@ reaper-king-no-more/
variants.json AtomicCards.json embeddings.npz (generated) variants.json AtomicCards.json embeddings.npz (generated)
decks/ # RESULTS, one dir per archetype variant decks/ # RESULTS, one dir per archetype variant
README.md README.md
no-mikaeus/ # decklist.mgt, bracket.txt, combos.txt, no-mikaeus/ # decklist.mtg, bracket.txt, combos.txt,
mikaeus/ # README.md, sim/<stamp>.{log,txt,json} mikaeus/ # README.md, sim/<stamp>.{log,txt,json}
tools/ tools/
cards.py # shared index: oracle+tags+edhrec+combos+blacklist cards.py # shared index: oracle+tags+edhrec+combos+blacklist
@ -363,7 +363,7 @@ insists Commander decks live).
2. ~~`cards.py`~~ — **done.** oracle + 4,369 tags + edhrecRank + combos + blacklist. 2. ~~`cards.py`~~ — **done.** oracle + 4,369 tags + edhrecRank + combos + blacklist.
3. ~~`bracket_check.py`~~ — **done and passing.** 3. ~~`bracket_check.py`~~ — **done and passing.**
4. ~~`embed.py` / `similar.py` / `similar_text.py`~~ — **done** (Layer B). 4. ~~`embed.py` / `similar.py` / `similar_text.py`~~ — **done** (Layer B).
5. ~~Draft the 100~~ — **done**, `decklist.mgt`. 5. ~~Draft the 100~~ — **done**, `decklist.mtg`.
6. ~~Forge installed + `to_dck.py` + `run_forge.sh` + `forge_report.py`~~ — **done.** 6. ~~Forge installed + `to_dck.py` + `run_forge.sh` + `forge_report.py`~~ — **done.**
4-player pod vs precons, reproducible by seed. 4-player pod vs precons, reproducible by seed.
7. Iterate on the list using Forge results + the offline combo/tag tools. 7. Iterate on the list using Forge results + the offline combo/tag tools.
@ -375,7 +375,7 @@ with the real one is worse than having one. Forge is the simulator. (`goldfish.p
and its `out/goldfish-*` runs are removed.) and its `out/goldfish-*` runs are removed.)
**Report convention** (every tool): a timestamped `.txt` human report plus a `.json` **Report convention** (every tool): a timestamped `.txt` human report plus a `.json`
artifact in `out/`, with the report footer naming `decklist.mgt`, the raw log, the artifact in `out/`, with the report footer naming `decklist.mtg`, the raw log, the
JSON, and itself. JSON, and itself.
## 6. Deck skeleton to draft against ## 6. Deck skeleton to draft against

View file

@ -7,7 +7,7 @@
# correctly (the removal slot still does removal) even though the specific card # correctly (the removal slot still does removal) even though the specific card
# differs. # differs.
# #
# decklist.mgt remains the source of truth. This map applies ONLY when building # decklist.mtg remains the source of truth. This map applies ONLY when building
# the .dck for simulation, and every substitution is recorded in the run's JSON # the .dck for simulation, and every substitution is recorded in the run's JSON
# metadata and printed in reports. # metadata and printed in reports.
# #

View file

@ -53,7 +53,7 @@ def run_arm(ctx, label, variant, games, seed, profile, overlay, clock, workdir):
tools_dir = os.path.dirname(os.path.abspath(__file__)) tools_dir = os.path.dirname(os.path.abspath(__file__))
subprocess.run([sys.executable, os.path.join(tools_dir, "to_dck.py"), subprocess.run([sys.executable, os.path.join(tools_dir, "to_dck.py"),
"--deck", ctx.dir, "--deck", ctx.dir,
os.path.join(ctx.dir, "decks", variant, "decklist.mgt"), os.path.join(ctx.dir, "decks", variant, "decklist.mtg"),
"-o", os.path.join(dst, dck_name)], capture_output=True) "-o", os.path.join(dst, dck_name)], capture_output=True)
import shutil as sh import shutil as sh
for p in POD: for p in POD:

View file

@ -59,7 +59,7 @@ def variants(deck_root, only=None, resume=False):
out = [] out = []
for name in sorted(os.listdir(os.path.join(deck_root, "decks"))): for name in sorted(os.listdir(os.path.join(deck_root, "decks"))):
d = os.path.join(deck_root, "decks", name) d = os.path.join(deck_root, "decks", name)
if not os.path.isdir(d) or not os.path.exists(os.path.join(d, "decklist.mgt")): if not os.path.isdir(d) or not os.path.exists(os.path.join(d, "decklist.mtg")):
continue continue
if only and name not in only: if only and name not in only:
continue continue
@ -122,7 +122,7 @@ def run_variant(ctx, name, games, seed, wid, pod_idx):
# build this variant's .dck into the worker's own profile # build this variant's .dck into the worker's own profile
r = subprocess.run([sys.executable, os.path.join(TOOLS_ROOT, "tools", "to_dck.py"), r = subprocess.run([sys.executable, os.path.join(TOOLS_ROOT, "tools", "to_dck.py"),
"--deck", deck_root, "--deck", deck_root,
os.path.join(vdir, "decklist.mgt"), os.path.join(vdir, "decklist.mtg"),
"-o", os.path.join(dst, dck_name)], "-o", os.path.join(dst, dck_name)],
capture_output=True, text=True) capture_output=True, text=True)
if r.returncode != 0: if r.returncode != 0:
@ -160,7 +160,7 @@ def run_variant(ctx, name, games, seed, wid, pod_idx):
subprocess.run([sys.executable, os.path.join(TOOLS_ROOT, "tools", "forge_report.py"), subprocess.run([sys.executable, os.path.join(TOOLS_ROOT, "tools", "forge_report.py"),
out + ".log", out + ".json", out + ".txt", str(games), str(seed), out + ".log", out + ".json", out + ".txt", str(games), str(seed),
os.path.join(vdir, "decklist.mgt"), ctx.forge_metadata_name()], os.path.join(vdir, "decklist.mtg"), ctx.forge_metadata_name()],
capture_output=True) capture_output=True)
# Stamp the run config so tuned and stock arms are never silently compared. # Stamp the run config so tuned and stock arms are never silently compared.

View file

@ -2,7 +2,7 @@
"""Check a decklist against Commander Bracket 3 + our house rules. """Check a decklist against Commander Bracket 3 + our house rules.
python3 tools/bracket_check.py --deck decks/reaper-king-no-more python3 tools/bracket_check.py --deck decks/reaper-king-no-more
python3 tools/bracket_check.py --deck decks/reaper-king-no-more decks/reaper-king-no-more/decks/no-mikaeus/decklist.mgt python3 tools/bracket_check.py --deck decks/reaper-king-no-more decks/reaper-king-no-more/decks/no-mikaeus/decklist.mtg
Enforces: Enforces:
* <= 3 Game Changers (official) * <= 3 Game Changers (official)

View file

@ -103,7 +103,7 @@ def main():
# only cards actually in our decklist # only cards actually in our decklist
db = DB(blacklist_path=ctx.blacklist_path) db = DB(blacklist_path=ctx.blacklist_path)
variant_dir = os.path.dirname(os.path.dirname(log)) variant_dir = os.path.dirname(os.path.dirname(log))
deck_file = os.path.join(variant_dir, "decklist.mgt") deck_file = os.path.join(variant_dir, "decklist.mtg")
commander, deck = load_decklist(deck_file) commander, deck = load_decklist(deck_file)
deck_set = set(deck) | {commander} deck_set = set(deck) | {commander}

View file

@ -4,7 +4,7 @@ against the local variants.json. No API calls.
python3 tools/combos.py --deck decks/reaper-king-no-more python3 tools/combos.py --deck decks/reaper-king-no-more
python3 tools/combos.py --deck decks/reaper-king-no-more --almost 30 python3 tools/combos.py --deck decks/reaper-king-no-more --almost 30
python3 tools/combos.py --deck decks/reaper-king-no-more --deck-file some/decklist.mgt python3 tools/combos.py --deck decks/reaper-king-no-more --deck-file some/decklist.mtg
""" """
import sys, os, json import sys, os, json
sys.path.insert(0, os.path.dirname(__file__)) sys.path.insert(0, os.path.dirname(__file__))

View file

@ -23,7 +23,7 @@ def variant_order(ctx):
except ImportError: except ImportError:
pass pass
rest = sorted(os.path.basename(os.path.dirname(p)) rest = sorted(os.path.basename(os.path.dirname(p))
for p in glob.glob(ctx.path("decks", "*", "decklist.mgt")) for p in glob.glob(ctx.path("decks", "*", "decklist.mtg"))
if os.path.basename(os.path.dirname(p)) not in hand) if os.path.basename(os.path.dirname(p)) not in hand)
return hand + rest return hand + rest
@ -46,7 +46,7 @@ def main():
rows = [] rows = []
for name in order: for name in order:
vdir = os.path.join(DECKS, name) vdir = os.path.join(DECKS, name)
deck_f = os.path.join(vdir, "decklist.mgt") deck_f = os.path.join(vdir, "decklist.mtg")
if not os.path.exists(deck_f): if not os.path.exists(deck_f):
continue continue
cmd, deck = load_decklist(deck_f) cmd, deck = load_decklist(deck_f)
@ -107,7 +107,7 @@ def main():
slug = ctx.slug slug = ctx.slug
L += ["## layout", "", "```", "decks/<variant>/", L += ["## layout", "", "```", "decks/<variant>/",
" decklist.mgt the 100 cards, \"N Card Name\" per line", " decklist.mtg the 100 cards, \"N Card Name\" per line",
" bracket.txt bracket-3 + house-rule check", " bracket.txt bracket-3 + house-rule check",
" combos.txt combos already in the deck, and one card away", " combos.txt combos already in the deck, and one card away",
" sim/ Forge runs, one triple per batch:", " sim/ Forge runs, one triple per batch:",

View file

@ -17,7 +17,7 @@ Resolution order for the deck directory:
from deckctx import DeckContext from deckctx import DeckContext
ctx = DeckContext.from_args() # pops --deck out of sys.argv ctx = DeckContext.from_args() # pops --deck out of sys.argv
ctx.path("decklist.mgt") # -> decks/<name>/decklist.mgt ctx.path("decklist.mtg") # -> decks/<name>/decklist.mtg
ctx.commander # -> "The Reaper, King No More" ctx.commander # -> "The Reaper, King No More"
ctx.identity # -> frozenset("BRG"), from the DB ctx.identity # -> frozenset("BRG"), from the DB
ctx.slug # -> "reaper-king-no-more" ctx.slug # -> "reaper-king-no-more"
@ -30,7 +30,7 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def _looks_like_deck_dir(d): def _looks_like_deck_dir(d):
return os.path.exists(os.path.join(d, "config.yaml")) or \ return os.path.exists(os.path.join(d, "config.yaml")) or \
os.path.exists(os.path.join(d, "decklist.mgt")) os.path.exists(os.path.join(d, "decklist.mtg"))
def resolve_deck_dir(argv=None, pop=True): def resolve_deck_dir(argv=None, pop=True):
@ -103,7 +103,7 @@ class DeckContext:
@property @property
def decklist_path(self): def decklist_path(self):
return self.config.get("decklist") and self.path(self.config["decklist"]) \ return self.config.get("decklist") and self.path(self.config["decklist"]) \
or self.path("decklist.mgt") or self.path("decklist.mtg")
@property @property
def blacklist_path(self): def blacklist_path(self):

View file

@ -104,7 +104,7 @@ def parse(log_path):
return games return games
def report(games, n_req, seed, log, jsonp, txtp, deck_path="decklist.mgt", def report(games, n_req, seed, log, jsonp, txtp, deck_path="decklist.mtg",
tally=None, tally_total=0, timeouts=0): tally=None, tally_total=0, timeouts=0):
L, add = [], lambda s: L.append(s) L, add = [], lambda s: L.append(s)
# prefer the authoritative Match Result tally over per-game lines # prefer the authoritative Match Result tally over per-game lines
@ -209,7 +209,7 @@ def main():
log, jsonp, txtp = sys.argv[1], sys.argv[2], sys.argv[3] log, jsonp, txtp = sys.argv[1], sys.argv[2], sys.argv[3]
n_req = sys.argv[4] if len(sys.argv) > 4 else "?" n_req = sys.argv[4] if len(sys.argv) > 4 else "?"
seed = sys.argv[5] if len(sys.argv) > 5 else "?" seed = sys.argv[5] if len(sys.argv) > 5 else "?"
deck_path = sys.argv[6] if len(sys.argv) > 6 else "decklist.mgt" deck_path = sys.argv[6] if len(sys.argv) > 6 else "decklist.mtg"
ours_name = sys.argv[7] if len(sys.argv) > 7 else deck_name_for(deck_path) ours_name = sys.argv[7] if len(sys.argv) > 7 else deck_name_for(deck_path)
if ours_name: if ours_name:
OURS = ours_name OURS = ours_name

View file

@ -159,7 +159,7 @@ def main():
only = args[args.index("--only") + 1] if "--only" in args else None only = args[args.index("--only") + 1] if "--only" in args else None
stats = args[args.index("--from-stats") + 1] if "--from-stats" in args else None stats = args[args.index("--from-stats") + 1] if "--from-stats" in args else None
base_path = args[args.index("--base") + 1] if "--base" in args else \ base_path = args[args.index("--base") + 1] if "--base" in args else \
ctx.path(ctx.config.get("generate_base", "decks/no-mikaeus/decklist.mgt")) ctx.path(ctx.config.get("generate_base", "decks/no-mikaeus/decklist.mtg"))
recipes = load_recipes(ctx) recipes = load_recipes(ctx)
if "--list" in args: if "--list" in args:
@ -203,7 +203,7 @@ def main():
body = [f"1 {n}" for n in sorted(set(res["deck"]) - {"Swamp", "Forest", "Mountain"})] body = [f"1 {n}" for n in sorted(set(res["deck"]) - {"Swamp", "Forest", "Mountain"})]
basics = [f"{Counter(res['deck'])[b]} {b}" for b in ("Swamp", "Forest", "Mountain") basics = [f"{Counter(res['deck'])[b]} {b}" for b in ("Swamp", "Forest", "Mountain")
if Counter(res["deck"])[b]] if Counter(res["deck"])[b]]
open(os.path.join(vdir, "decklist.mgt"), "w").write( open(os.path.join(vdir, "decklist.mtg"), "w").write(
"\n".join(header + body + basics) + "\n") "\n".join(header + body + basics) + "\n")
print(f"{name:18} ok -{len(res['cuts'])} +{len(res['adds'])}") print(f"{name:18} ok -{len(res['cuts'])} +{len(res['adds'])}")
print(f"{'':18} cut: {', '.join(res['cuts'][:4])}{'...' if len(res['cuts'])>4 else ''}") print(f"{'':18} cut: {', '.join(res['cuts'][:4])}{'...' if len(res['cuts'])>4 else ''}")

View file

@ -53,7 +53,7 @@ def main():
from cards import DB from cards import DB
db = DB(blacklist_path=ctx.blacklist_path) db = DB(blacklist_path=ctx.blacklist_path)
commander = ctx.commander commander = ctx.commander
base_path = ctx.path("decks", "no-mikaeus", "decklist.mgt") base_path = ctx.path("decks", "no-mikaeus", "decklist.mtg")
if not os.path.exists(base_path): if not os.path.exists(base_path):
base_path = ctx.decklist_path base_path = ctx.decklist_path
base = load(base_path) base = load(base_path)
@ -87,7 +87,7 @@ def main():
os.makedirs(vdir, exist_ok=True) os.makedirs(vdir, exist_ok=True)
header = [f"# {name}", f"# {why}", "", "# Commander", header = [f"# {name}", f"# {why}", "", "# Commander",
f"1 {commander}", "", "# Main"] f"1 {commander}", "", "# Main"]
open(os.path.join(vdir, "decklist.mgt"), "w").write( open(os.path.join(vdir, "decklist.mtg"), "w").write(
"\n".join(header + lines) + "\n") "\n".join(header + lines) + "\n")
flags = [] flags = []

View file

@ -6,7 +6,7 @@ allocation. This module defines the roles (as verified Scryfall Tagger slugs plu
oracle-text fallbacks) and can classify any card or score a whole decklist. oracle-text fallbacks) and can classify any card or score a whole decklist.
python3 tools/roles.py --deck decks/reaper-king-no-more python3 tools/roles.py --deck decks/reaper-king-no-more
python3 tools/roles.py --deck decks/reaper-king-no-more decks/reaper-king-no-more/decks/fast-ramp/decklist.mgt python3 tools/roles.py --deck decks/reaper-king-no-more decks/reaper-king-no-more/decks/fast-ramp/decklist.mtg
""" """
import sys, os import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

View file

@ -22,7 +22,7 @@ STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
VDIR="$DECK_DIR/decks/$VARIANT" VDIR="$DECK_DIR/decks/$VARIANT"
OUT="$VDIR/sim/$STAMP" OUT="$VDIR/sim/$STAMP"
[ -f "$VDIR/decklist.mgt" ] || { echo "no decklist: $VDIR/decklist.mgt" >&2; exit 1; } [ -f "$VDIR/decklist.mtg" ] || { echo "no decklist: $VDIR/decklist.mtg" >&2; exit 1; }
mkdir -p "$VDIR/sim" mkdir -p "$VDIR/sim"
OPPONENTS=( OPPONENTS=(
@ -33,7 +33,7 @@ OPPONENTS=(
# .dck filename and [metadata] Name= are derived from the deck (slug / # .dck filename and [metadata] Name= are derived from the deck (slug /
# config.yaml `name:`), never hardcoded -- ask to_dck.py what it wrote. # config.yaml `name:`), never hardcoded -- ask to_dck.py what it wrote.
DCK_NAME="$(python3 "$TOOLS/to_dck.py" --deck "$DECK_DIR" "$VDIR/decklist.mgt" \ DCK_NAME="$(python3 "$TOOLS/to_dck.py" --deck "$DECK_DIR" "$VDIR/decklist.mtg" \
-o "$FORGE_DECKS/$(basename "$DECK_DIR").dck" | sed -n 's/^wrote \(.*\) (.*/\1/p')" -o "$FORGE_DECKS/$(basename "$DECK_DIR").dck" | sed -n 's/^wrote \(.*\) (.*/\1/p')"
DCK_NAME="$(basename "$DCK_NAME")" DCK_NAME="$(basename "$DCK_NAME")"
@ -48,4 +48,4 @@ echo "[$VARIANT] $GAMES games, 4-player Commander pod, seed $SEED"
| stdbuf -oL grep -E "^Game Result:" || true | stdbuf -oL grep -E "^Game Result:" || true
python3 "$TOOLS/forge_report.py" "$OUT.log" "$OUT.json" "$OUT.txt" \ python3 "$TOOLS/forge_report.py" "$OUT.log" "$OUT.json" "$OUT.txt" \
"$GAMES" "$SEED" "$VDIR/decklist.mgt" "$GAMES" "$SEED" "$VDIR/decklist.mtg"

View file

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Convert decklist.mgt -> Forge .dck format. """Convert decklist.mtg -> Forge .dck format.
python3 tools/to_dck.py --deck decks/reaper-king-no-more python3 tools/to_dck.py --deck decks/reaper-king-no-more
python3 tools/to_dck.py --deck decks/reaper-king-no-more [decklist.mgt] [-o ~/forge/decks/<slug>.dck] python3 tools/to_dck.py --deck decks/reaper-king-no-more [decklist.mtg] [-o ~/forge/decks/<slug>.dck]
Forge sections are [Commander], [Main], [Sideboard] (verified in DeckSection.java). Forge sections are [Commander], [Main], [Sideboard] (verified in DeckSection.java).
Card names must match Forge's own database, so this validates against Card names must match Forge's own database, so this validates against
@ -70,7 +70,7 @@ def main():
args = [a for a in sys.argv[1:]] args = [a for a in sys.argv[1:]]
out = args[args.index("-o") + 1] if "-o" in args else \ out = args[args.index("-o") + 1] if "-o" in args else \
os.path.join(FORGE, "decks", ctx.dck_filename()) os.path.join(FORGE, "decks", ctx.dck_filename())
src = next((a for a in args if a.endswith(".mgt")), ctx.decklist_path) src = next((a for a in args if a.endswith(".mtg")), ctx.decklist_path)
commander, deck = load_decklist(src) commander, deck = load_decklist(src)
@ -107,7 +107,7 @@ def main():
print(f"!! {len(missing)} card(s) Forge does not recognise:", file=sys.stderr) print(f"!! {len(missing)} card(s) Forge does not recognise:", file=sys.stderr)
for m in missing: for m in missing:
print(f" {m}", file=sys.stderr) print(f" {m}", file=sys.stderr)
print(" (fix the name in decklist.mgt, or Forge will drop it)\n", file=sys.stderr) print(" (fix the name in decklist.mtg, or Forge will drop it)\n", file=sys.stderr)
else: else:
print("warning: could not read Forge's card list; skipping validation", file=sys.stderr) print("warning: could not read Forge's card list; skipping validation", file=sys.stderr)

View file

@ -7,7 +7,7 @@
Each variant directory ends up self-contained: Each variant directory ends up self-contained:
<deck>/decks/<name>/ <deck>/decks/<name>/
decklist.mgt the 100 cards decklist.mtg the 100 cards
bracket.txt bracket-3 + house-rule check bracket.txt bracket-3 + house-rule check
combos.txt combos in the deck, and one card away combos.txt combos in the deck, and one card away
sim/ Forge runs: <stamp>.{log,txt,json} sim/ Forge runs: <stamp>.{log,txt,json}
@ -38,7 +38,7 @@ def main():
tools = os.path.join(TOOLS_ROOT, "tools") tools = os.path.join(TOOLS_ROOT, "tools")
vdir = ctx.path("decks", name) vdir = ctx.path("decks", name)
deck = os.path.join(vdir, "decklist.mgt") deck = os.path.join(vdir, "decklist.mtg")
if not os.path.exists(deck): if not os.path.exists(deck):
sys.exit(f"no decklist at {deck}") sys.exit(f"no decklist at {deck}")
os.makedirs(os.path.join(vdir, "sim"), exist_ok=True) os.makedirs(os.path.join(vdir, "sim"), exist_ok=True)
@ -90,13 +90,13 @@ def main():
f"_generated {stamp} by `tools/variant.py`_", f"_generated {stamp} by `tools/variant.py`_",
"", "",
f"- **bracket 3: {verdict}** (two-card vacuum combos: {vac})", f"- **bracket 3: {verdict}** (two-card vacuum combos: {vac})",
f"- {n_deck} lines in `decklist.mgt`", f"- {n_deck} lines in `decklist.mtg`",
"", "",
"## files", "## files",
"", "",
"| file | what it is |", "| file | what it is |",
"| --- | --- |", "| --- | --- |",
"| `decklist.mgt` | the deck, `N Card Name` per line |", "| `decklist.mtg` | the deck, `N Card Name` per line |",
"| `bracket.txt` | bracket-3 + house-rule check |", "| `bracket.txt` | bracket-3 + house-rule check |",
"| `combos.txt` | combos in the deck, and one card away |", "| `combos.txt` | combos in the deck, and one card away |",
"| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |", "| `sim/` | Forge runs — `.log` raw, `.txt` report, `.json` data |",