mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
release: 2.2.6 – refresh bracket list JSONs; finalize brackets compliance docs and UI polish
This commit is contained in:
parent
4e03997923
commit
375349e56e
11 changed files with 734 additions and 16 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -12,6 +12,8 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [2.2.6] - 2025-09-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Bracket policy enforcement: global pool-level prune for disallowed categories when limits are 0 (e.g., Game Changers in Brackets 1–2). Applies to both Web and headless runs.
|
- Bracket policy enforcement: global pool-level prune for disallowed categories when limits are 0 (e.g., Game Changers in Brackets 1–2). Applies to both Web and headless runs.
|
||||||
- Inline enforcement UI: violations surface before the summary; Continue/Rerun disabled until you replace or remove flagged cards. Alternatives are role-consistent and exclude commander/locked/in-deck cards.
|
- Inline enforcement UI: violations surface before the summary; Continue/Rerun disabled until you replace or remove flagged cards. Alternatives are role-consistent and exclude commander/locked/in-deck cards.
|
||||||
|
@ -20,10 +22,29 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
|
||||||
### Changed
|
### Changed
|
||||||
- Spells and creatures phases apply bracket-aware pre-filters to reduce violations proactively.
|
- Spells and creatures phases apply bracket-aware pre-filters to reduce violations proactively.
|
||||||
- Compliance detection for Game Changers falls back to in-code constants when `config/card_lists/game_changers.json` is empty.
|
- Compliance detection for Game Changers falls back to in-code constants when `config/card_lists/game_changers.json` is empty.
|
||||||
|
- Data refresh: updated static lists used by bracket compliance/enforcement with current card names and metadata:
|
||||||
|
- `config/card_lists/extra_turns.json`
|
||||||
|
- `config/card_lists/game_changers.json`
|
||||||
|
- `config/card_lists/mass_land_denial.json`
|
||||||
|
- `config/card_lists/tutors_nonland.json`
|
||||||
|
Each list includes `list_version: "manual-2025-09-04"` and `generated_at`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Summary/export mismatch in headless JSON runs where disallowed cards could be pruned from exports but appear in summaries; global prune ensures consistent state across phases and reports.
|
- Summary/export mismatch in headless JSON runs where disallowed cards could be pruned from exports but appear in summaries; global prune ensures consistent state across phases and reports.
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- These lists underpin the bracket enforcement feature introduced in 2.2.5; shipping them as a follow-up release ensures consistent results across Web and headless runs.
|
||||||
|
|
||||||
|
## [2.2.5] - 2025-09-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Bracket WARN thresholds: `config/brackets.yml` supports optional `<category>_warn` keys (e.g., `tutors_nonland_warn`, `extra_turns_warn`). Compliance now returns PASS/WARN/FAIL; low brackets (1–2) conservatively WARN on presence of tutors/extra_turns when thresholds aren’t provided.
|
||||||
|
- Web UI compliance polish: the panel auto-opens on non-compliance (WARN/FAIL) and shows a colored overall status chip (green/WARN amber/red). WARN items now render as tiles with a subtle amber style and a WARN badge; tiles and enforcement actions remain FAIL-only.
|
||||||
|
- Tests: added coverage to ensure WARN thresholds from YAML are applied and that fallback WARN behavior appears for low brackets.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Web: flagged metadata now includes WARN categories with a `severity` field to support softer UI rendering for advisory cases.
|
||||||
|
|
||||||
## [2.2.4] - 2025-09-02
|
## [2.2.4] - 2025-09-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
BIN
README.md
BIN
README.md
Binary file not shown.
|
@ -100,10 +100,19 @@ def _canonicalize(name: str | None) -> str:
|
||||||
return s.casefold()
|
return s.casefold()
|
||||||
|
|
||||||
|
|
||||||
def _status_for(count: int, limit: Optional[int]) -> str:
|
def _status_for(count: int, limit: Optional[int], warn: Optional[int] = None) -> str:
|
||||||
|
# Unlimited hard limit -> always PASS (no WARN semantics without a cap)
|
||||||
if limit is None:
|
if limit is None:
|
||||||
return "PASS"
|
return "PASS"
|
||||||
return "PASS" if count <= int(limit) else "FAIL"
|
if count > int(limit):
|
||||||
|
return "FAIL"
|
||||||
|
# Soft guidance: if warn threshold provided and met, surface WARN
|
||||||
|
try:
|
||||||
|
if warn is not None and int(warn) > 0 and count >= int(warn):
|
||||||
|
return "WARN"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return "PASS"
|
||||||
|
|
||||||
|
|
||||||
def evaluate_deck(
|
def evaluate_deck(
|
||||||
|
@ -155,7 +164,13 @@ def evaluate_deck(
|
||||||
flagged_names_disp = sorted({deck_canon_to_display.get(cn, cn) for cn in flagged_set})
|
flagged_names_disp = sorted({deck_canon_to_display.get(cn, cn) for cn in flagged_set})
|
||||||
c = len(flagged_set)
|
c = len(flagged_set)
|
||||||
lim = limits.get(key)
|
lim = limits.get(key)
|
||||||
status = _status_for(c, lim)
|
# Optional warn thresholds live alongside limits as "<key>_warn"
|
||||||
|
try:
|
||||||
|
warn_key = f"{key}_warn"
|
||||||
|
warn_val = limits.get(warn_key)
|
||||||
|
except Exception:
|
||||||
|
warn_val = None
|
||||||
|
status = _status_for(c, lim, warn=warn_val)
|
||||||
cat: CategoryFinding = {
|
cat: CategoryFinding = {
|
||||||
"count": c,
|
"count": c,
|
||||||
"limit": lim,
|
"limit": lim,
|
||||||
|
@ -166,12 +181,27 @@ def evaluate_deck(
|
||||||
categories[key] = cat
|
categories[key] = cat
|
||||||
if status == "FAIL":
|
if status == "FAIL":
|
||||||
messages.append(f"{key.replace('_',' ').title()}: {c} exceeds limit {lim}")
|
messages.append(f"{key.replace('_',' ').title()}: {c} exceeds limit {lim}")
|
||||||
|
elif status == "WARN":
|
||||||
|
try:
|
||||||
|
if warn_val is not None:
|
||||||
|
messages.append(f"{key.replace('_',' ').title()}: {c} present (discouraged for this bracket)")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Conservative fallback: for low brackets (levels 1–2), tutors/extra-turns should WARN when present
|
||||||
|
# even if a warn threshold was not provided in YAML.
|
||||||
|
if status == "PASS" and level in (1, 2) and key in ("tutors_nonland", "extra_turns"):
|
||||||
|
try:
|
||||||
|
if (warn_val is None) and (lim is not None) and c > 0 and c <= int(lim):
|
||||||
|
categories[key]["status"] = "WARN"
|
||||||
|
messages.append(f"{key.replace('_',' ').title()}: {c} present (discouraged for this bracket)")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Two-card combos detection
|
# Two-card combos detection
|
||||||
combos = detect_combos(deck_cards.keys(), combos_path=combos_path)
|
combos = detect_combos(deck_cards.keys(), combos_path=combos_path)
|
||||||
cheap_early_pairs = [p for p in combos if p.cheap_early]
|
cheap_early_pairs = [p for p in combos if p.cheap_early]
|
||||||
c_limit = limits.get("two_card_combos")
|
c_limit = limits.get("two_card_combos")
|
||||||
combos_status = _status_for(len(cheap_early_pairs), c_limit)
|
combos_status = _status_for(len(cheap_early_pairs), c_limit, warn=None)
|
||||||
categories["two_card_combos"] = {
|
categories["two_card_combos"] = {
|
||||||
"count": len(cheap_early_pairs),
|
"count": len(cheap_early_pairs),
|
||||||
"limit": c_limit,
|
"limit": c_limit,
|
||||||
|
|
|
@ -51,3 +51,33 @@ def test_two_card_combination_detection_respects_cheap_early():
|
||||||
rep2 = evaluate_deck(deck, commander_name=None, bracket="optimized")
|
rep2 = evaluate_deck(deck, commander_name=None, bracket="optimized")
|
||||||
assert rep2["categories"]["two_card_combos"]["limit"] is None
|
assert rep2["categories"]["two_card_combos"]["limit"] is None
|
||||||
assert rep2["overall"] == "PASS"
|
assert rep2["overall"] == "PASS"
|
||||||
|
|
||||||
|
|
||||||
|
def test_warn_thresholds_in_yaml_are_applied():
|
||||||
|
# Exhibition: tutors_nonland_warn=1 -> WARN when a single tutor present (hard limit 3)
|
||||||
|
deck1 = {
|
||||||
|
# Use a non-"Game Changer" tutor to avoid hard fail in Exhibition
|
||||||
|
"Solve the Equation": _mk_card(["Bracket:TutorNonland"]),
|
||||||
|
"Cultivate": _mk_card([]),
|
||||||
|
}
|
||||||
|
rep1 = evaluate_deck(deck1, commander_name=None, bracket="exhibition")
|
||||||
|
assert rep1["level"] == 1
|
||||||
|
assert rep1["categories"]["tutors_nonland"]["status"] == "WARN"
|
||||||
|
assert rep1["overall"] == "WARN"
|
||||||
|
|
||||||
|
# Core: extra_turns_warn=1 -> WARN at 1, PASS at 0, FAIL above hard limit 3
|
||||||
|
deck2 = {
|
||||||
|
"Time Warp": _mk_card(["Bracket:ExtraTurn"]),
|
||||||
|
"Explore": _mk_card([]),
|
||||||
|
}
|
||||||
|
rep2 = evaluate_deck(deck2, commander_name=None, bracket="core")
|
||||||
|
assert rep2["level"] == 2
|
||||||
|
assert rep2["categories"]["extra_turns"]["limit"] == 3
|
||||||
|
assert rep2["categories"]["extra_turns"]["status"] in {"WARN", "PASS"}
|
||||||
|
# With two extra turns, still <= limit, but should at least WARN
|
||||||
|
deck3 = {
|
||||||
|
"Time Warp": _mk_card(["Bracket:ExtraTurn"]),
|
||||||
|
"Temporal Manipulation": _mk_card(["Bracket:ExtraTurn"]),
|
||||||
|
}
|
||||||
|
rep3 = evaluate_deck(deck3, commander_name=None, bracket="core")
|
||||||
|
assert rep3["categories"]["extra_turns"]["status"] == "WARN"
|
||||||
|
|
|
@ -2273,12 +2273,12 @@ async def build_compliance_panel(request: Request) -> HTMLResponse:
|
||||||
seen_lower: set[str] = set()
|
seen_lower: set[str] = set()
|
||||||
for key, cat in cats.items():
|
for key, cat in cats.items():
|
||||||
try:
|
try:
|
||||||
lim = cat.get('limit')
|
status = str(cat.get('status') or '').upper()
|
||||||
cnt = int(cat.get('count', 0) or 0)
|
# Only surface tiles for WARN and FAIL
|
||||||
if lim is None or cnt <= int(lim):
|
if status not in {"WARN", "FAIL"}:
|
||||||
continue
|
continue
|
||||||
# For two-card combos, split pairs into individual cards and skip commander
|
# For two-card combos, split pairs into individual cards and skip commander
|
||||||
if key == 'two_card_combos':
|
if key == 'two_card_combos' and status == 'FAIL':
|
||||||
# Prefer the structured combos list to ensure we only expand counted pairs
|
# Prefer the structured combos list to ensure we only expand counted pairs
|
||||||
pairs = []
|
pairs = []
|
||||||
try:
|
try:
|
||||||
|
@ -2316,6 +2316,7 @@ async def build_compliance_panel(request: Request) -> HTMLResponse:
|
||||||
'category': labels.get(key, key.replace('_',' ').title()),
|
'category': labels.get(key, key.replace('_',' ').title()),
|
||||||
'role': role,
|
'role': role,
|
||||||
'owned': (nm_l in owned_lower),
|
'owned': (nm_l in owned_lower),
|
||||||
|
'severity': status,
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
# Default handling for list/tag categories
|
# Default handling for list/tag categories
|
||||||
|
@ -2332,6 +2333,7 @@ async def build_compliance_panel(request: Request) -> HTMLResponse:
|
||||||
'category': labels.get(key, key.replace('_',' ').title()),
|
'category': labels.get(key, key.replace('_',' ').title()),
|
||||||
'role': role,
|
'role': role,
|
||||||
'owned': (nm_l in owned_lower),
|
'owned': (nm_l in owned_lower),
|
||||||
|
'severity': status,
|
||||||
})
|
})
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
{% if compliance %}
|
{% if compliance %}
|
||||||
<details id="compliance-panel" style="margin-top:.75rem;">
|
{% set non_compliant = compliance.overall is defined and (compliance.overall|string|lower != 'pass') %}
|
||||||
|
<details id="compliance-panel" style="margin-top:.75rem;" {% if non_compliant %}open{% endif %}>
|
||||||
<summary>Bracket compliance</summary>
|
<summary>Bracket compliance</summary>
|
||||||
<div class="muted" style="margin:.35rem 0;">Overall: <strong>{{ compliance.overall }}</strong> (Bracket: {{ compliance.bracket|title }}{{ ' #' ~ compliance.level if compliance.level is defined }})</div>
|
{% set ov = compliance.overall|string|lower %}
|
||||||
|
<div class="muted" style="margin:.35rem 0;">Overall:
|
||||||
|
{% if ov == 'fail' %}
|
||||||
|
<span class="chip" title="Overall bracket status"><span class="dot" style="background: var(--red-main);"></span> FAIL</span>
|
||||||
|
{% elif ov == 'warn' %}
|
||||||
|
<span class="chip" title="Overall bracket status"><span class="dot" style="background: var(--orange-main);"></span> WARN</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="chip" title="Overall bracket status"><span class="dot" style="background: var(--green-main);"></span> PASS</span>
|
||||||
|
{% endif %}
|
||||||
|
(Bracket: {{ compliance.bracket|title }}{{ ' #' ~ compliance.level if compliance.level is defined }})
|
||||||
|
</div>
|
||||||
{% if compliance.messages and compliance.messages|length > 0 %}
|
{% if compliance.messages and compliance.messages|length > 0 %}
|
||||||
<ul style="margin:.25rem 0; padding-left:1.25rem;">
|
<ul style="margin:.25rem 0; padding-left:1.25rem;">
|
||||||
{% for m in compliance.messages %}
|
{% for m in compliance.messages %}
|
||||||
|
@ -15,7 +26,8 @@
|
||||||
<h5 style="margin:.75rem 0 .35rem 0;">Flagged cards</h5>
|
<h5 style="margin:.75rem 0 .35rem 0;">Flagged cards</h5>
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
{% for f in flagged_meta %}
|
{% for f in flagged_meta %}
|
||||||
<div class="card-tile" data-card-name="{{ f.name }}" data-role="{{ f.role or '' }}">
|
{% set sev = (f.severity or 'FAIL')|upper %}
|
||||||
|
<div class="card-tile" data-card-name="{{ f.name }}" data-role="{{ f.role or '' }}" {% if sev == 'FAIL' %}style="border-color: var(--red-main);"{% elif sev == 'WARN' %}style="border-color: var(--orange-main);"{% endif %}>
|
||||||
<a href="https://scryfall.com/search?q={{ f.name|urlencode }}" target="_blank" rel="noopener" class="img-btn" title="{{ f.name }}">
|
<a href="https://scryfall.com/search?q={{ f.name|urlencode }}" target="_blank" rel="noopener" class="img-btn" title="{{ f.name }}">
|
||||||
<img class="card-thumb" src="https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=normal" alt="{{ f.name }} image" width="160" loading="lazy" decoding="async" data-lqip="1"
|
<img class="card-thumb" src="https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=normal" alt="{{ f.name }} image" width="160" loading="lazy" decoding="async" data-lqip="1"
|
||||||
srcset="https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=small 160w, https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=normal 488w, https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=large 672w"
|
srcset="https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=small 160w, https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=normal 488w, https://api.scryfall.com/cards/named?fuzzy={{ f.name|urlencode }}&format=image&version=large 672w"
|
||||||
|
@ -23,7 +35,14 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="owned-badge" title="{{ 'Owned' if f.owned else 'Not owned' }}" aria-label="{{ 'Owned' if f.owned else 'Not owned' }}">{% if f.owned %}✔{% else %}✖{% endif %}</div>
|
<div class="owned-badge" title="{{ 'Owned' if f.owned else 'Not owned' }}" aria-label="{{ 'Owned' if f.owned else 'Not owned' }}">{% if f.owned %}✔{% else %}✖{% endif %}</div>
|
||||||
<div class="name">{{ f.name }}</div>
|
<div class="name">{{ f.name }}</div>
|
||||||
<div class="muted" style="text-align:center; font-size:12px;">{{ f.category }}{% if f.role %} • {{ f.role }}{% endif %}</div>
|
<div class="muted" style="text-align:center; font-size:12px; display:flex; gap:.35rem; justify-content:center; align-items:center; flex-wrap:wrap;">
|
||||||
|
<span>{{ f.category }}{% if f.role %} • {{ f.role }}{% endif %}</span>
|
||||||
|
{% if sev == 'FAIL' %}
|
||||||
|
<span class="chip" title="Severity: FAIL"><span class="dot" style="background: var(--red-main);"></span> FAIL</span>
|
||||||
|
{% elif sev == 'WARN' %}
|
||||||
|
<span class="chip" title="Severity: WARN"><span class="dot" style="background: var(--orange-main);"></span> WARN</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div style="display:flex; justify-content:center; margin-top:.25rem;">
|
<div style="display:flex; justify-content:center; margin-top:.25rem;">
|
||||||
{# Role-aware alternatives: pass the flagged name; server will infer role and exclude in-deck/locked #}
|
{# Role-aware alternatives: pass the flagged name; server will infer role and exclude in-deck/locked #}
|
||||||
<button type="button" class="btn" hx-get="/build/alternatives" hx-vals='{"name": "{{ f.name }}"}' hx-target="#alts-flag-{{ loop.index0 }}" hx-swap="innerHTML" title="Suggest role-consistent replacements">Pick replacement…</button>
|
<button type="button" class="btn" hx-get="/build/alternatives" hx-vals='{"name": "{{ f.name }}"}' hx-target="#alts-flag-{{ loop.index0 }}" hx-swap="innerHTML" title="Suggest role-consistent replacements">Pick replacement…</button>
|
||||||
|
|
|
@ -9,6 +9,9 @@ exhibition:
|
||||||
extra_turns: 0
|
extra_turns: 0
|
||||||
tutors_nonland: 3
|
tutors_nonland: 3
|
||||||
two_card_combos: 0
|
two_card_combos: 0
|
||||||
|
# Soft-warning guidance (does not affect FAIL threshold):
|
||||||
|
# Tutors are discouraged at this level; warn if any are present.
|
||||||
|
tutors_nonland_warn: 1
|
||||||
core:
|
core:
|
||||||
level: 2
|
level: 2
|
||||||
name: Core
|
name: Core
|
||||||
|
@ -18,6 +21,10 @@ core:
|
||||||
extra_turns: 3
|
extra_turns: 3
|
||||||
tutors_nonland: 3
|
tutors_nonland: 3
|
||||||
two_card_combos: 0
|
two_card_combos: 0
|
||||||
|
# Soft-warning guidance: extra turns and tutors are allowed sparsely.
|
||||||
|
# Warn when any appear, fail only when exceeding the hard limit above.
|
||||||
|
extra_turns_warn: 1
|
||||||
|
tutors_nonland_warn: 1
|
||||||
upgraded:
|
upgraded:
|
||||||
level: 3
|
level: 3
|
||||||
name: Upgraded
|
name: Upgraded
|
||||||
|
|
|
@ -1 +1,56 @@
|
||||||
{"source_url": "test", "generated_at": "now", "cards": ["Time Warp"]}
|
{
|
||||||
|
"cards": [
|
||||||
|
"Alchemist's Gambit",
|
||||||
|
"Alrund's Epiphany",
|
||||||
|
"Beacon of Tomorrows",
|
||||||
|
"Capture of Jingzhou",
|
||||||
|
"Chance for Glory",
|
||||||
|
"Expropriate",
|
||||||
|
"Final Fortune",
|
||||||
|
"Gonti's Aether Heart",
|
||||||
|
"Ichormoon Gauntlet",
|
||||||
|
"Karn's Temporal Sundering",
|
||||||
|
"Last Chance",
|
||||||
|
"Lighthouse Chronologist",
|
||||||
|
"Lost Isle Calling",
|
||||||
|
"Magistrate's Scepter",
|
||||||
|
"Magosi, the Waterveil",
|
||||||
|
"Medomai the Ageless",
|
||||||
|
"Mu Yanling",
|
||||||
|
"Nexus of Fate",
|
||||||
|
"Notorious Throng",
|
||||||
|
"Part the Waterveil",
|
||||||
|
"Plea for Power",
|
||||||
|
"Ral Zarek",
|
||||||
|
"Regenerations Restored",
|
||||||
|
"Rise of the Eldrazi",
|
||||||
|
"Sage of Hours",
|
||||||
|
"Savor the Moment",
|
||||||
|
"Search the City",
|
||||||
|
"Second Chance",
|
||||||
|
"Seedtime",
|
||||||
|
"Stitch in Time",
|
||||||
|
"Teferi, Master of Time",
|
||||||
|
"Teferi, Timebender",
|
||||||
|
"Temporal Extortion",
|
||||||
|
"Temporal Manipulation",
|
||||||
|
"Temporal Mastery",
|
||||||
|
"Temporal Trespass",
|
||||||
|
"Time Sieve",
|
||||||
|
"Time Stretch",
|
||||||
|
"Time Warp",
|
||||||
|
"Timesifter",
|
||||||
|
"Timestream Navigator",
|
||||||
|
"Twice Upon a Time // Unlikely Meeting",
|
||||||
|
"Twice Upon a TimeUnlikely Meeting",
|
||||||
|
"Ugin's Nexus",
|
||||||
|
"Ultimecia, Time Sorceress",
|
||||||
|
"Ultimecia, Time Sorceress // Ultimecia, Omnipotent",
|
||||||
|
"Walk the Aeons",
|
||||||
|
"Wanderwine Prophets",
|
||||||
|
"Warrior's Oath",
|
||||||
|
"Wormfang Manta"
|
||||||
|
],
|
||||||
|
"list_version": "v1.0",
|
||||||
|
"generated_at": "2025-09-04"
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1,68 @@
|
||||||
{"source_url": "test", "generated_at": "now", "cards": []}
|
{
|
||||||
|
"cards": [
|
||||||
|
"Ad Nauseam",
|
||||||
|
"Ancient Tomb",
|
||||||
|
"Aura Shards",
|
||||||
|
"Bolas's Citadel",
|
||||||
|
"Braids, Cabal Minion",
|
||||||
|
"Chrome Mox",
|
||||||
|
"Coalition Victory",
|
||||||
|
"Consecrated Sphinx",
|
||||||
|
"Crop Rotation",
|
||||||
|
"Cyclonic Rift",
|
||||||
|
"Deflecting Swat",
|
||||||
|
"Demonic Tutor",
|
||||||
|
"Drannith Magistrate",
|
||||||
|
"Enlightened Tutor",
|
||||||
|
"Expropriate",
|
||||||
|
"Field of the Dead",
|
||||||
|
"Fierce Guardianship",
|
||||||
|
"Food Chain",
|
||||||
|
"Force of Will",
|
||||||
|
"Gaea's Cradle",
|
||||||
|
"Gamble",
|
||||||
|
"Gifts Ungiven",
|
||||||
|
"Glacial Chasm",
|
||||||
|
"Grand Arbiter Augustin IV",
|
||||||
|
"Grim Monolith",
|
||||||
|
"Humility",
|
||||||
|
"Imperial Seal",
|
||||||
|
"Intuition",
|
||||||
|
"Jeska's Will",
|
||||||
|
"Jin-Gitaxias, Core Augur",
|
||||||
|
"Kinnan, Bonder Prodigy",
|
||||||
|
"Lion's Eye Diamond",
|
||||||
|
"Mana Vault",
|
||||||
|
"Mishra's Workshop",
|
||||||
|
"Mox Diamond",
|
||||||
|
"Mystical Tutor",
|
||||||
|
"Narset, Parter of Veils",
|
||||||
|
"Natural Order",
|
||||||
|
"Necropotence",
|
||||||
|
"Notion Thief",
|
||||||
|
"Opposition Agent",
|
||||||
|
"Orcish Bowmasters",
|
||||||
|
"Panoptic Mirror",
|
||||||
|
"Rhystic Study",
|
||||||
|
"Seedborn Muse",
|
||||||
|
"Serra's Sanctum",
|
||||||
|
"Smothering Tithe",
|
||||||
|
"Survival of the Fittest",
|
||||||
|
"Sway of the Stars",
|
||||||
|
"Teferi's Protection",
|
||||||
|
"Tergrid, God of Fright",
|
||||||
|
"Tergrid, God of Fright // Tergrid's Lantern",
|
||||||
|
"Thassa's Oracle",
|
||||||
|
"The One Ring",
|
||||||
|
"The Tabernacle at Pendrell Vale",
|
||||||
|
"Underworld Breach",
|
||||||
|
"Urza, Lord High Artificer",
|
||||||
|
"Vampiric Tutor",
|
||||||
|
"Vorinclex, Voice of Hunger",
|
||||||
|
"Winota, Joiner of Forces",
|
||||||
|
"Worldly Tutor",
|
||||||
|
"Yuriko, the Tiger's Shadow"
|
||||||
|
],
|
||||||
|
"list_version": "v1.0",
|
||||||
|
"generated_at": "2025-09-04"
|
||||||
|
}
|
|
@ -1 +1,79 @@
|
||||||
{"source_url": "test", "generated_at": "now", "cards": ["Armageddon"]}
|
{
|
||||||
|
"cards": [
|
||||||
|
"Acid Rain",
|
||||||
|
"Apocalypse",
|
||||||
|
"Armageddon",
|
||||||
|
"Back to Basics",
|
||||||
|
"Bearer of the Heavens",
|
||||||
|
"Bend or Break",
|
||||||
|
"Blood Moon",
|
||||||
|
"Boil",
|
||||||
|
"Boiling Seas",
|
||||||
|
"Boom // Bust",
|
||||||
|
"BoomBust",
|
||||||
|
"Break the Ice",
|
||||||
|
"Burning of Xinye",
|
||||||
|
"Cataclysm",
|
||||||
|
"Catastrophe",
|
||||||
|
"Choke",
|
||||||
|
"Cleansing",
|
||||||
|
"Contamination",
|
||||||
|
"Conversion",
|
||||||
|
"Curse of Marit Lage",
|
||||||
|
"Death Cloud",
|
||||||
|
"Decree of Annihilation",
|
||||||
|
"Desolation Angel",
|
||||||
|
"Destructive Force",
|
||||||
|
"Devastating Dreams",
|
||||||
|
"Devastation",
|
||||||
|
"Dimensional Breach",
|
||||||
|
"Disciple of Caelus Nin",
|
||||||
|
"Epicenter",
|
||||||
|
"Fall of the Thran",
|
||||||
|
"Flashfires",
|
||||||
|
"Gilt-Leaf Archdruid",
|
||||||
|
"Glaciers",
|
||||||
|
"Global Ruin",
|
||||||
|
"Hall of Gemstone",
|
||||||
|
"Harbinger of the Seas",
|
||||||
|
"Hokori, Dust Drinker",
|
||||||
|
"Impending Disaster",
|
||||||
|
"Infernal Darkness",
|
||||||
|
"Jokulhaups",
|
||||||
|
"Keldon Firebombers",
|
||||||
|
"Land Equilibrium",
|
||||||
|
"Magus of the Balance",
|
||||||
|
"Magus of the Moon",
|
||||||
|
"Myojin of Infinite Rage",
|
||||||
|
"Naked Singularity",
|
||||||
|
"Natural Balance",
|
||||||
|
"Obliterate",
|
||||||
|
"Omen of Fire",
|
||||||
|
"Raiding Party",
|
||||||
|
"Ravages of War",
|
||||||
|
"Razia's Purification",
|
||||||
|
"Reality Twist",
|
||||||
|
"Realm Razer",
|
||||||
|
"Restore Balance",
|
||||||
|
"Rising Waters",
|
||||||
|
"Ritual of Subdual",
|
||||||
|
"Ruination",
|
||||||
|
"Soulscour",
|
||||||
|
"Stasis",
|
||||||
|
"Static Orb",
|
||||||
|
"Storm Cauldron",
|
||||||
|
"Sunder",
|
||||||
|
"Sway of the Stars",
|
||||||
|
"Tectonic Break",
|
||||||
|
"Thoughts of Ruin",
|
||||||
|
"Tsunami",
|
||||||
|
"Wildfire",
|
||||||
|
"Winter Moon",
|
||||||
|
"Winter Orb",
|
||||||
|
"Worldfire",
|
||||||
|
"Worldpurge",
|
||||||
|
"Worldslayer"
|
||||||
|
],
|
||||||
|
"list_version": "v1.0",
|
||||||
|
"generated_at": "2025-09-04"
|
||||||
|
}
|
|
@ -1 +1,410 @@
|
||||||
{"source_url": "test", "generated_at": "now", "cards": ["Demonic Tutor"]}
|
{
|
||||||
|
"cards": [
|
||||||
|
"Academy Rector",
|
||||||
|
"Aether Searcher",
|
||||||
|
"Altar of Bone",
|
||||||
|
"Amrou Scout",
|
||||||
|
"Analyze the Pollen",
|
||||||
|
"Anchor to Reality",
|
||||||
|
"Archdruid's Charm",
|
||||||
|
"Archmage Ascension",
|
||||||
|
"Arcum Dagsson",
|
||||||
|
"Arena Rector",
|
||||||
|
"Artificer's Intuition",
|
||||||
|
"Assembly Hall",
|
||||||
|
"Auratouched Mage",
|
||||||
|
"Aurochs Herd",
|
||||||
|
"Axgard Armory",
|
||||||
|
"Ayara's Oathsworn",
|
||||||
|
"Begin the Invasion",
|
||||||
|
"Behold the Beyond",
|
||||||
|
"Beseech the Mirror",
|
||||||
|
"Beseech the Queen",
|
||||||
|
"Bifurcate",
|
||||||
|
"Bilbo, Birthday Celebrant",
|
||||||
|
"Birthing Pod",
|
||||||
|
"Bitterheart Witch",
|
||||||
|
"Blightspeaker",
|
||||||
|
"Blood Speaker",
|
||||||
|
"Boggart Harbinger",
|
||||||
|
"Bog Glider",
|
||||||
|
"Boonweaver Giant",
|
||||||
|
"Brainspoil",
|
||||||
|
"Brightglass Gearhulk",
|
||||||
|
"Bringer of the Black Dawn",
|
||||||
|
"Bring to Light",
|
||||||
|
"Brutalizer Exarch",
|
||||||
|
"Buried Alive",
|
||||||
|
"Burning-Rune Demon",
|
||||||
|
"Call the Gatewatch",
|
||||||
|
"Captain Sisay",
|
||||||
|
"Caradora, Heart of Alacria",
|
||||||
|
"Case of the Stashed Skeleton",
|
||||||
|
"Cateran Brute",
|
||||||
|
"Cateran Enforcer",
|
||||||
|
"Cateran Kidnappers",
|
||||||
|
"Cateran Overlord",
|
||||||
|
"Cateran Persuader",
|
||||||
|
"Cateran Slaver",
|
||||||
|
"Cateran Summons",
|
||||||
|
"Central ElevatorPromising Stairs",
|
||||||
|
"Central Elevator // Promising Stairs",
|
||||||
|
"Chandra, Heart of Fire",
|
||||||
|
"Chord of Calling",
|
||||||
|
"Citanul Flute",
|
||||||
|
"Clarion Ultimatum",
|
||||||
|
"Cloud, Midgar Mercenary",
|
||||||
|
"Clutch of the Undercity",
|
||||||
|
"Conduit of Ruin",
|
||||||
|
"Conflux",
|
||||||
|
"Congregation at Dawn",
|
||||||
|
"Corpse Connoisseur",
|
||||||
|
"Corpse Harvester",
|
||||||
|
"Coveted Prize",
|
||||||
|
"Cruel Tutor",
|
||||||
|
"Curse of Misfortunes",
|
||||||
|
"Cynical Loner",
|
||||||
|
"Dark Petition",
|
||||||
|
"Deadeye Quartermaster",
|
||||||
|
"Deathbellow War Cry",
|
||||||
|
"Defense of the Heart",
|
||||||
|
"Defiant Falcon",
|
||||||
|
"Defiant Vanguard",
|
||||||
|
"Delivery Moogle",
|
||||||
|
"Demonic Bargain",
|
||||||
|
"Demonic Collusion",
|
||||||
|
"Demonic Consultation",
|
||||||
|
"Demonic Counsel",
|
||||||
|
"Demonic Tutor",
|
||||||
|
"Diabolic Intent",
|
||||||
|
"Diabolic Revelation",
|
||||||
|
"Diabolic Tutor",
|
||||||
|
"Dig Up",
|
||||||
|
"Dimir House Guard",
|
||||||
|
"Dimir Infiltrator",
|
||||||
|
"Dimir Machinations",
|
||||||
|
"Disciples of Gix",
|
||||||
|
"Distant Memories",
|
||||||
|
"Dizzy Spell",
|
||||||
|
"Djeru, With Eyes Open",
|
||||||
|
"Doomsday",
|
||||||
|
"Doubling Chant",
|
||||||
|
"Draconic Muralists",
|
||||||
|
"Dragon's Approach",
|
||||||
|
"Dragonstorm",
|
||||||
|
"Drift of Phantasms",
|
||||||
|
"Dwarven Recruiter",
|
||||||
|
"Ecological Appreciation",
|
||||||
|
"Eerie Procession",
|
||||||
|
"Eladamri's Call",
|
||||||
|
"Eldritch Evolution",
|
||||||
|
"Elvish Harbinger",
|
||||||
|
"Emergent Ultimatum",
|
||||||
|
"Enduring Ideal",
|
||||||
|
"Enigmatic Incarnation",
|
||||||
|
"Enlightened Tutor",
|
||||||
|
"Entomb",
|
||||||
|
"Ethereal Usher",
|
||||||
|
"Evolving Door",
|
||||||
|
"Eye of Ugin",
|
||||||
|
"Fabricate",
|
||||||
|
"Faerie Harbinger",
|
||||||
|
"Fang-Druid Summoner",
|
||||||
|
"Fauna Shaman",
|
||||||
|
"Fervent Mastery",
|
||||||
|
"Fiend Artisan",
|
||||||
|
"Fierce Empath",
|
||||||
|
"Fighter Class",
|
||||||
|
"Finale of Devastation",
|
||||||
|
"Final Parting",
|
||||||
|
"Firemind's Foresight",
|
||||||
|
"Flamekin Harbinger",
|
||||||
|
"Fleshwrither",
|
||||||
|
"Forerunner of the Coalition",
|
||||||
|
"Forerunner of the Empire",
|
||||||
|
"Forerunner of the Heralds",
|
||||||
|
"Forerunner of the Legion",
|
||||||
|
"Forging the Tyrite Sword",
|
||||||
|
"From Beyond",
|
||||||
|
"From Father to Son",
|
||||||
|
"Frostpyre Arcanist",
|
||||||
|
"Fugitive of the Judoon",
|
||||||
|
"Gamble",
|
||||||
|
"Garruk, Caller of Beasts",
|
||||||
|
"Garruk Relentless",
|
||||||
|
"Garruk Relentless // Garruk, the Veil-Cursed",
|
||||||
|
"Garruk, Unleashed",
|
||||||
|
"General Tazri",
|
||||||
|
"Giant Harbinger",
|
||||||
|
"Gifts Ungiven",
|
||||||
|
"Goblin Engineer",
|
||||||
|
"Goblin Matron",
|
||||||
|
"Goblin Recruiter",
|
||||||
|
"Godo, Bandit Warlord",
|
||||||
|
"Gravebreaker Lamia",
|
||||||
|
"Green Sun's Zenith",
|
||||||
|
"Grim Servant",
|
||||||
|
"Grim Tutor",
|
||||||
|
"Grozoth",
|
||||||
|
"Guardian Sunmare",
|
||||||
|
"Guidelight Pathmaker",
|
||||||
|
"Heliod's Pilgrim",
|
||||||
|
"Hibernation's End",
|
||||||
|
"Higure, the Still Wind",
|
||||||
|
"Hoarding Broodlord",
|
||||||
|
"Hoarding Dragon",
|
||||||
|
"Homing Sliver",
|
||||||
|
"Honored Knight-Captain",
|
||||||
|
"Hour of Victory",
|
||||||
|
"Huatli, Poet of Unity",
|
||||||
|
"Huatli, Poet of Unity // Roar of the Fifth People",
|
||||||
|
"Idyllic Tutor",
|
||||||
|
"Ignite the Beacon",
|
||||||
|
"Illicit Shipment",
|
||||||
|
"Imperial Hellkite",
|
||||||
|
"Imperial Recruiter",
|
||||||
|
"Imperial Seal",
|
||||||
|
"Iname as One",
|
||||||
|
"Iname, Death Aspect",
|
||||||
|
"Increasing Ambition",
|
||||||
|
"Infernal Tutor",
|
||||||
|
"Insatiable Avarice",
|
||||||
|
"Insidious Dreams",
|
||||||
|
"Instrument of the Bards",
|
||||||
|
"Intuition",
|
||||||
|
"Invasion of Arcavios",
|
||||||
|
"Invasion of Arcavios // Invocation of the Founders",
|
||||||
|
"Invasion of Ikoria",
|
||||||
|
"Invasion of Ikoria // Zilortha, Apex of Ikoria",
|
||||||
|
"Invasion of Theros",
|
||||||
|
"Invasion of Theros // Ephara, Ever-Sheltering",
|
||||||
|
"Inventors' Fair",
|
||||||
|
"InvertInvent",
|
||||||
|
"Invert // Invent",
|
||||||
|
"Iron Man, Titan of Innovation",
|
||||||
|
"Isperia the Inscrutable",
|
||||||
|
"Jarad's Orders",
|
||||||
|
"Kaho, Minamo Historian",
|
||||||
|
"Kaito Shizuki",
|
||||||
|
"Kasmina, Enigma Sage",
|
||||||
|
"Kellan, the Fae-BloodedBirthright Boon",
|
||||||
|
"Kellan, the Fae-Blooded // Birthright Boon",
|
||||||
|
"Kithkin Harbinger",
|
||||||
|
"Kuldotha Forgemaster",
|
||||||
|
"Lagomos, Hand of Hatred",
|
||||||
|
"Library of Lat-Nam",
|
||||||
|
"Lifespinner",
|
||||||
|
"Light-Paws, Emperor's Voice",
|
||||||
|
"Liliana Vess",
|
||||||
|
"Lim-Dûl's Vault",
|
||||||
|
"Lin Sivvi, Defiant Hero",
|
||||||
|
"Lively Dirge",
|
||||||
|
"Long-Term Plans",
|
||||||
|
"Lost Auramancers",
|
||||||
|
"Lotuslight Dancers",
|
||||||
|
"Loyal Inventor",
|
||||||
|
"Maelstrom of the Spirit Dragon",
|
||||||
|
"Magda, Brazen Outlaw",
|
||||||
|
"Magus of the Order",
|
||||||
|
"Mangara's Tome",
|
||||||
|
"Maralen of the Mornsong",
|
||||||
|
"March of Burgeoning Life",
|
||||||
|
"Mask of the Mimic",
|
||||||
|
"Mastermind's Acquisition",
|
||||||
|
"Mausoleum Secrets",
|
||||||
|
"Merchant Scroll",
|
||||||
|
"Merrow Harbinger",
|
||||||
|
"Micromancer",
|
||||||
|
"Mimeofacture",
|
||||||
|
"Mishra, Artificer Prodigy",
|
||||||
|
"Moggcatcher",
|
||||||
|
"Momir Vig, Simic Visionary",
|
||||||
|
"Moon-Blessed Cleric",
|
||||||
|
"Moonsilver Key",
|
||||||
|
"Muddle the Mixture",
|
||||||
|
"Mwonvuli Beast Tracker",
|
||||||
|
"Myr Kinsmith",
|
||||||
|
"Myr Turbine",
|
||||||
|
"Mystical Teachings",
|
||||||
|
"Mystical Tutor",
|
||||||
|
"Mythos of Brokkos",
|
||||||
|
"Nahiri, the Harbinger",
|
||||||
|
"Natural Order",
|
||||||
|
"Nature's Rhythm",
|
||||||
|
"Nazahn, Revered Bladesmith",
|
||||||
|
"Neoform",
|
||||||
|
"Netherborn Phalanx",
|
||||||
|
"Night Dealings",
|
||||||
|
"Nissa Revane",
|
||||||
|
"Noble Benefactor",
|
||||||
|
"Open the Armory",
|
||||||
|
"Opposition Agent",
|
||||||
|
"Oriq Loremage",
|
||||||
|
"Oswald Fiddlebender",
|
||||||
|
"Pack Hunt",
|
||||||
|
"Parallel Thoughts",
|
||||||
|
"Pattern Matcher",
|
||||||
|
"Pattern of Rebirth",
|
||||||
|
"Perplex",
|
||||||
|
"Personal Tutor",
|
||||||
|
"Phantom Carriage",
|
||||||
|
"Planar Bridge",
|
||||||
|
"Planar Portal",
|
||||||
|
"Plea for Guidance",
|
||||||
|
"Priest of the Wakening Sun",
|
||||||
|
"Primal Command",
|
||||||
|
"Prime Speaker Vannifar",
|
||||||
|
"Profane Tutor",
|
||||||
|
"Protean Hulk",
|
||||||
|
"Pyre of Heroes",
|
||||||
|
"Quest for the Holy Relic",
|
||||||
|
"Quiet Speculation",
|
||||||
|
"Ramosian Captain",
|
||||||
|
"Ramosian Commander",
|
||||||
|
"Ramosian Lieutenant",
|
||||||
|
"Ramosian Sergeant",
|
||||||
|
"Ramosian Sky Marshal",
|
||||||
|
"Ranger-Captain of Eos",
|
||||||
|
"Ranger of Eos",
|
||||||
|
"Ratcatcher",
|
||||||
|
"Rathi Assassin",
|
||||||
|
"Rathi Fiend",
|
||||||
|
"Rathi Intimidator",
|
||||||
|
"Razaketh's Rite",
|
||||||
|
"Razaketh, the Foulblooded",
|
||||||
|
"Reckless Handling",
|
||||||
|
"Recruiter of the Guard",
|
||||||
|
"Relic Seeker",
|
||||||
|
"Remembrance",
|
||||||
|
"Repurposing Bay",
|
||||||
|
"Reshape",
|
||||||
|
"Rhystic Tutor",
|
||||||
|
"Ring of Three Wishes",
|
||||||
|
"Ringsight",
|
||||||
|
"Rocco, Cabaretti Caterer",
|
||||||
|
"Rootless Yew",
|
||||||
|
"Runed Crown",
|
||||||
|
"Runeforge Champion",
|
||||||
|
"Rune-Scarred Demon",
|
||||||
|
"Rushed Rebirth",
|
||||||
|
"Saheeli Rai",
|
||||||
|
"Samut, the Tested",
|
||||||
|
"Sanctum of All",
|
||||||
|
"Sanctum of Ugin",
|
||||||
|
"Sarkhan, Dragonsoul",
|
||||||
|
"Sarkhan's Triumph",
|
||||||
|
"Sarkhan Unbroken",
|
||||||
|
"Savage Order",
|
||||||
|
"Sazh Katzroy",
|
||||||
|
"Scheming Symmetry",
|
||||||
|
"Scion of the Ur-Dragon",
|
||||||
|
"Scour for Scrap",
|
||||||
|
"Scrapyard Recombiner",
|
||||||
|
"Seahunter",
|
||||||
|
"Search for Glory",
|
||||||
|
"Secret Salvage",
|
||||||
|
"Self-Assembler",
|
||||||
|
"Servant of the Stinger",
|
||||||
|
"Shadowborn Apostle",
|
||||||
|
"Shadow-Rite Priest",
|
||||||
|
"Shared Summons",
|
||||||
|
"Shield-Wall Sentinel",
|
||||||
|
"Shred Memory",
|
||||||
|
"Shrine Steward",
|
||||||
|
"Sidisi, Undead Vizier",
|
||||||
|
"Signal the Clans",
|
||||||
|
"Sisay, Weatherlight Captain",
|
||||||
|
"Sivitri, Dragon Master",
|
||||||
|
"Skyship Weatherlight",
|
||||||
|
"Skyshroud Poacher",
|
||||||
|
"Sliver Overlord",
|
||||||
|
"Solve the Equation",
|
||||||
|
"Sovereigns of Lost Alara",
|
||||||
|
"Spellseeker",
|
||||||
|
"Sphinx Ambassador",
|
||||||
|
"Sphinx Summoner",
|
||||||
|
"Starfield Shepherd",
|
||||||
|
"Steelshaper Apprentice",
|
||||||
|
"Steelshaper's Gift",
|
||||||
|
"Step Through",
|
||||||
|
"Sterling Grove",
|
||||||
|
"Stoneforge Mystic",
|
||||||
|
"Stonehewer Giant",
|
||||||
|
"Summoner's Pact",
|
||||||
|
"Sunforger",
|
||||||
|
"SupplyDemand",
|
||||||
|
"Supply // Demand",
|
||||||
|
"Survival of the Fittest",
|
||||||
|
"Sylvan Tutor",
|
||||||
|
"Tainted Pact",
|
||||||
|
"Taj-Nar Swordsmith",
|
||||||
|
"Tallowisp",
|
||||||
|
"Tamiyo's Journal",
|
||||||
|
"Tempest Hawk",
|
||||||
|
"Templar Knight",
|
||||||
|
"Tezzeret, Artifice Master",
|
||||||
|
"Tezzeret, Cruel Captain",
|
||||||
|
"Tezzeret the Seeker",
|
||||||
|
"Thalia's Lancers",
|
||||||
|
"The Caves of Androzani",
|
||||||
|
"The Creation of Avacyn",
|
||||||
|
"The Cruelty of Gix",
|
||||||
|
"The Eleventh Hour",
|
||||||
|
"The Five Doctors",
|
||||||
|
"The Hunger Tide Rises",
|
||||||
|
"The Huntsman's Redemption",
|
||||||
|
"The Seriema",
|
||||||
|
"Thornvault Forager",
|
||||||
|
"Threats Undetected",
|
||||||
|
"Three Dreams",
|
||||||
|
"Tiamat",
|
||||||
|
"Time of Need",
|
||||||
|
"Tolaria West",
|
||||||
|
"Tooth and Nail",
|
||||||
|
"Totem-Guide Hartebeest",
|
||||||
|
"Transit Mage",
|
||||||
|
"Transmutation Font",
|
||||||
|
"Transmute Artifact",
|
||||||
|
"Trapmaker's Snare",
|
||||||
|
"Traverse the Ulvenwald",
|
||||||
|
"Treasure Chest",
|
||||||
|
"Treasure Mage",
|
||||||
|
"Treefolk Harbinger",
|
||||||
|
"Tribute Mage",
|
||||||
|
"Trinket Mage",
|
||||||
|
"Trophy Mage",
|
||||||
|
"Twice Upon a TimeUnlikely Meeting",
|
||||||
|
"Twice Upon a Time // Unlikely Meeting",
|
||||||
|
"Ugin, Eye of the Storms",
|
||||||
|
"Uncage the Menagerie",
|
||||||
|
"Unmarked Grave",
|
||||||
|
"Urza's Saga",
|
||||||
|
"Urza's Sylex",
|
||||||
|
"Vampiric Tutor",
|
||||||
|
"Varragoth, Bloodsky Sire",
|
||||||
|
"Vedalken Aethermage",
|
||||||
|
"Verdant Succession",
|
||||||
|
"Vexing Puzzlebox",
|
||||||
|
"Vile Entomber",
|
||||||
|
"Vivien, Monsters' Advocate",
|
||||||
|
"Vivien on the Hunt",
|
||||||
|
"Vizier of the Anointed",
|
||||||
|
"Wargate",
|
||||||
|
"War of the Last Alliance",
|
||||||
|
"Waterlogged Teachings",
|
||||||
|
"Waterlogged Teachings // Inundated Archive",
|
||||||
|
"Weird Harvest",
|
||||||
|
"Whir of Invention",
|
||||||
|
"Wild Pair",
|
||||||
|
"Wild Research",
|
||||||
|
"Wirewood Herald",
|
||||||
|
"Wishclaw Talisman",
|
||||||
|
"Woodland Bellower",
|
||||||
|
"Worldly Tutor",
|
||||||
|
"Yisan, the Wanderer Bard",
|
||||||
|
"Zirilan of the Claw",
|
||||||
|
"Zur the Enchanter"
|
||||||
|
],
|
||||||
|
"list_version": "v1.0",
|
||||||
|
"generated_at": "2025-09-04"
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue