From 73f5f64c9796326e5895dca77deb81b315ca050f Mon Sep 17 00:00:00 2001 From: Chris Swenson <1323803+ninthworld@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:39:40 -0700 Subject: [PATCH] test(engine): gate the catalog against the card that was printed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `build.rs` validates a definition's shape — the file stem matches the identity, a creature carries both power and toughness, a planeswalker carries loyalty and nothing else does. None of that can know a number is wrong, and the behavioural tests assert against whatever the definition says, so a mis-transcribed mana cost is invisible at every gate and then cemented by the test written over it. Fifteen cards shipped that way (#819); twelve differed in a field a machine can check. This is that machine. A fixture transcribed from the printed set, compared to `CardDatabase::bundled()` — the embedded catalog the game is actually played with, not the JSON on disk. It names the card and the field when they disagree. The fixture carries name, mana cost, supertypes, types, subtypes, power, toughness, loyalty, and colours, and nothing else: the same functional data ADR 0009 already sources for the catalog, and nothing the licensing rule forbids. `deny_unknown_fields` refuses anything else structurally, and a test states the allowed set outright so the reason is not left to serde. The one rule that keeps this honest is that `scripts/printed-characteristics.py` reads a third-party set file and the catalog's *identities*, never a definition's characteristics. A fixture regenerated from the thing it checks is a gate that passes forever and proves nothing; that is the failure mode this is designed against, and it is stated where the fixture, the script, and the engine guide can all be read. A printed `*` is not a number, so the fixture keeps power as a string: Enigma Drake's `*` is satisfied by a `defined_power` ability rather than by a value, and a base of 0 with nothing to define it fails. Rules text is out of scope, and the limit is real: of the fifteen wrong cards this catches twelve. A missing {R} in a cost list, a life gain the card does not print, a token created tapped — those stay a human-review problem, held by the behavioural tests instead. Costs 0.02s in `make check`. Closes #822. Part of #819. Co-Authored-By: Claude Opus 5 (1M context) --- crates/sage-engine/AGENTS.md | 7 + .../fixtures/printed_characteristics.json | 4336 +++++++++++++++++ .../tests/printed_characteristics.rs | 321 ++ docs/card-schema.md | 31 + scripts/printed-characteristics.py | 92 + 5 files changed, 4787 insertions(+) create mode 100644 crates/sage-engine/tests/fixtures/printed_characteristics.json create mode 100644 crates/sage-engine/tests/printed_characteristics.rs create mode 100755 scripts/printed-characteristics.py diff --git a/crates/sage-engine/AGENTS.md b/crates/sage-engine/AGENTS.md index 33a96f12..06941c21 100644 --- a/crates/sage-engine/AGENTS.md +++ b/crates/sage-engine/AGENTS.md @@ -19,6 +19,13 @@ a functional definition per card under a stable `functional_id`, no presentation assets (the schema rejects them structurally), and code-defined behavior only via the declared `scripted` escape hatch. +- **A definition's printed characteristics are checked against the printed card, not + against the tests written over it.** `build.rs` validates *shape*; the accuracy gate is + `tests/printed_characteristics.rs`, reading a fixture transcribed from the set by + `scripts/printed-characteristics.py`. That script never reads `data/catalog/`, and it + must not: a fixture regenerated from the thing it checks is a gate that passes forever + and proves nothing. Rules text stays out of it — the project ships none, and a test + fixture is not an exception. - **Never write a `CardId` down.** `build.rs` assembles `data/catalog/` + `data/sets/` at compile time and interns `CardId(0..n)` from the sorted `FunctionalId`s (ADR 0008 §3), so authoring one card renumbers its neighbours. Name a card by its diff --git a/crates/sage-engine/tests/fixtures/printed_characteristics.json b/crates/sage-engine/tests/fixtures/printed_characteristics.json new file mode 100644 index 00000000..5ba4a774 --- /dev/null +++ b/crates/sage-engine/tests/fixtures/printed_characteristics.json @@ -0,0 +1,4336 @@ +[ + { + "functional_id": "abnormal_endurance", + "name": "Abnormal Endurance", + "mana_cost": "{1}{B}", + "types": [ + "instant" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "act_of_treason", + "name": "Act of Treason", + "mana_cost": "{2}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "aegis_of_the_heavens", + "name": "Aegis of the Heavens", + "mana_cost": "{1}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "aerial_engineer", + "name": "Aerial Engineer", + "mana_cost": "{2}{W}{U}", + "types": [ + "creature" + ], + "colors": [ + "white", + "blue" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "2", + "toughness": "4" + }, + { + "functional_id": "aether_tunnel", + "name": "Aether Tunnel", + "mana_cost": "{1}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "aethershield_artificer", + "name": "Aethershield Artificer", + "mana_cost": "{3}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Dwarf", + "Artificer" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "aggressive_mammoth", + "name": "Aggressive Mammoth", + "mana_cost": "{3}{G}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elephant" + ], + "power": "8", + "toughness": "8" + }, + { + "functional_id": "air_elemental", + "name": "Air Elemental", + "mana_cost": "{3}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Elemental" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "ajani_adversary_of_tyrants", + "name": "Ajani, Adversary of Tyrants", + "mana_cost": "{2}{W}{W}", + "types": [ + "planeswalker" + ], + "colors": [ + "white" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Ajani" + ], + "loyalty": "4" + }, + { + "functional_id": "ajani_s_influence", + "name": "Ajani's Influence", + "mana_cost": "{2}{W}{W}", + "types": [ + "sorcery" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "ajani_s_last_stand", + "name": "Ajani's Last Stand", + "mana_cost": "{2}{W}{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "ajani_s_pridemate", + "name": "Ajani's Pridemate", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Cat", + "Soldier" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "ajani_s_welcome", + "name": "Ajani's Welcome", + "mana_cost": "{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "ajani_wise_counselor", + "name": "Ajani, Wise Counselor", + "mana_cost": "{3}{W}{W}", + "types": [ + "planeswalker" + ], + "colors": [ + "white" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Ajani" + ], + "loyalty": "5" + }, + { + "functional_id": "alpine_moon", + "name": "Alpine Moon", + "mana_cost": "{R}", + "types": [ + "enchantment" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "amulet_of_safekeeping", + "name": "Amulet of Safekeeping", + "mana_cost": "{2}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "angel_of_the_dawn", + "name": "Angel of the Dawn", + "mana_cost": "{4}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Angel" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "anticipate", + "name": "Anticipate", + "mana_cost": "{1}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "apex_of_power", + "name": "Apex of Power", + "mana_cost": "{7}{R}{R}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "arcades_the_strategist", + "name": "Arcades, the Strategist", + "mana_cost": "{1}{G}{W}{U}", + "types": [ + "creature" + ], + "colors": [ + "white", + "blue", + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Elder", + "Dragon" + ], + "power": "3", + "toughness": "5" + }, + { + "functional_id": "arcane_encyclopedia", + "name": "Arcane Encyclopedia", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [], + "subtypes": [ + "Book" + ] + }, + { + "functional_id": "arisen_gorgon", + "name": "Arisen Gorgon", + "mana_cost": "{1}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie", + "Gorgon" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "aven_wind_mage", + "name": "Aven Wind Mage", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Bird", + "Wizard" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "aviation_pioneer", + "name": "Aviation Pioneer", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "1", + "toughness": "2" + }, + { + "functional_id": "banefire", + "name": "Banefire", + "mana_cost": "{X}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "befuddle", + "name": "Befuddle", + "mana_cost": "{2}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "blanchwood_armor", + "name": "Blanchwood Armor", + "mana_cost": "{2}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "blood_divination", + "name": "Blood Divination", + "mana_cost": "{3}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "boggart_brute", + "name": "Boggart Brute", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Warrior" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "bogstomper", + "name": "Bogstomper", + "mana_cost": "{4}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Beast" + ], + "power": "6", + "toughness": "5" + }, + { + "functional_id": "bone_dragon", + "name": "Bone Dragon", + "mana_cost": "{3}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Dragon", + "Skeleton" + ], + "power": "5", + "toughness": "4" + }, + { + "functional_id": "bone_to_ash", + "name": "Bone to Ash", + "mana_cost": "{2}{U}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "brawl_bash_ogre", + "name": "Brawl-Bash Ogre", + "mana_cost": "{2}{B}{R}", + "types": [ + "creature" + ], + "colors": [ + "black", + "red" + ], + "subtypes": [ + "Ogre", + "Warrior" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "bristling_boar", + "name": "Bristling Boar", + "mana_cost": "{3}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Boar" + ], + "power": "4", + "toughness": "3" + }, + { + "functional_id": "cancel", + "name": "Cancel", + "mana_cost": "{1}{U}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "catalyst_elemental", + "name": "Catalyst Elemental", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Elemental" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "cavalry_drillmaster", + "name": "Cavalry Drillmaster", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "centaur_courser", + "name": "Centaur Courser", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Centaur", + "Warrior" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "chaos_wand", + "name": "Chaos Wand", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "child_of_night", + "name": "Child of Night", + "mana_cost": "{1}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Vampire" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "chromium_the_mutable", + "name": "Chromium, the Mutable", + "mana_cost": "{4}{W}{U}{B}", + "types": [ + "creature" + ], + "colors": [ + "white", + "blue", + "black" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Elder", + "Dragon" + ], + "power": "7", + "toughness": "7" + }, + { + "functional_id": "cinder_barrens", + "name": "Cinder Barrens", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "cleansing_nova", + "name": "Cleansing Nova", + "mana_cost": "{3}{W}{W}", + "types": [ + "sorcery" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "colossal_dreadmaw", + "name": "Colossal Dreadmaw", + "mana_cost": "{4}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Dinosaur" + ], + "power": "6", + "toughness": "6" + }, + { + "functional_id": "colossal_majesty", + "name": "Colossal Majesty", + "mana_cost": "{2}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "court_cleric", + "name": "Court Cleric", + "mana_cost": "{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Cleric" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "crash_through", + "name": "Crash Through", + "mana_cost": "{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "crucible_of_worlds", + "name": "Crucible of Worlds", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "daggerback_basilisk", + "name": "Daggerback Basilisk", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Basilisk" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "dark_dweller_oracle", + "name": "Dark-Dweller Oracle", + "mana_cost": "{1}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Shaman" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "daybreak_chaplain", + "name": "Daybreak Chaplain", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Cleric" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "death_baron", + "name": "Death Baron", + "mana_cost": "{1}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie", + "Wizard" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "declare_dominance", + "name": "Declare Dominance", + "mana_cost": "{3}{G}{G}", + "types": [ + "sorcery" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "demanding_dragon", + "name": "Demanding Dragon", + "mana_cost": "{3}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "demon_of_catastrophes", + "name": "Demon of Catastrophes", + "mana_cost": "{2}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Demon" + ], + "power": "6", + "toughness": "6" + }, + { + "functional_id": "departed_deckhand", + "name": "Departed Deckhand", + "mana_cost": "{1}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Spirit", + "Pirate" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "desecrated_tomb", + "name": "Desecrated Tomb", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "detection_tower", + "name": "Detection Tower", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "diamond_mare", + "name": "Diamond Mare", + "mana_cost": "{2}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Horse" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "diregraf_ghoul", + "name": "Diregraf Ghoul", + "mana_cost": "{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "dismissive_pyromancer", + "name": "Dismissive Pyromancer", + "mana_cost": "{1}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "disperse", + "name": "Disperse", + "mana_cost": "{1}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "divination", + "name": "Divination", + "mana_cost": "{2}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "djinn_of_wishes", + "name": "Djinn of Wishes", + "mana_cost": "{3}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Djinn" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "doomed_dissenter", + "name": "Doomed Dissenter", + "mana_cost": "{1}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Human" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "doublecast", + "name": "Doublecast", + "mana_cost": "{R}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "draconic_disciple", + "name": "Draconic Disciple", + "mana_cost": "{1}{R}{G}", + "types": [ + "creature" + ], + "colors": [ + "red", + "green" + ], + "subtypes": [ + "Human", + "Shaman" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "dragon_egg", + "name": "Dragon Egg", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon", + "Egg" + ], + "power": "0", + "toughness": "2" + }, + { + "functional_id": "dragon_s_hoard", + "name": "Dragon's Hoard", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "druid_of_horns", + "name": "Druid of Horns", + "mana_cost": "{3}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Human", + "Druid" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "druid_of_the_cowl", + "name": "Druid of the Cowl", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Druid" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "dryad_greenseeker", + "name": "Dryad Greenseeker", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Dryad" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "duress", + "name": "Duress", + "mana_cost": "{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "dwarven_priest", + "name": "Dwarven Priest", + "mana_cost": "{3}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Dwarf", + "Cleric" + ], + "power": "2", + "toughness": "4" + }, + { + "functional_id": "dwindle", + "name": "Dwindle", + "mana_cost": "{2}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "electrify", + "name": "Electrify", + "mana_cost": "{3}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "elvish_clancaller", + "name": "Elvish Clancaller", + "mana_cost": "{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Druid" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "elvish_rejuvenator", + "name": "Elvish Rejuvenator", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Druid" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "enigma_drake", + "name": "Enigma Drake", + "mana_cost": "{1}{U}{R}", + "types": [ + "creature" + ], + "colors": [ + "blue", + "red" + ], + "subtypes": [ + "Drake" + ], + "power": "*", + "toughness": "4" + }, + { + "functional_id": "epicure_of_blood", + "name": "Epicure of Blood", + "mana_cost": "{4}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Vampire" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "essence_scatter", + "name": "Essence Scatter", + "mana_cost": "{1}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "exclusion_mage", + "name": "Exclusion Mage", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "explosive_apparatus", + "name": "Explosive Apparatus", + "mana_cost": "{1}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "fell_specter", + "name": "Fell Specter", + "mana_cost": "{3}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Specter" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "field_creeper", + "name": "Field Creeper", + "mana_cost": "{2}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Scarecrow" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "fiery_finish", + "name": "Fiery Finish", + "mana_cost": "{4}{R}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "fire_elemental", + "name": "Fire Elemental", + "mana_cost": "{3}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Elemental" + ], + "power": "5", + "toughness": "4" + }, + { + "functional_id": "forest", + "name": "Forest", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [], + "supertypes": [ + "basic" + ], + "subtypes": [ + "Forest" + ] + }, + { + "functional_id": "forsaken_sanctuary", + "name": "Forsaken Sanctuary", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "foul_orchard", + "name": "Foul Orchard", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "fountain_of_renewal", + "name": "Fountain of Renewal", + "mana_cost": "{1}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "fraying_omnipotence", + "name": "Fraying Omnipotence", + "mana_cost": "{3}{B}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "frilled_sea_serpent", + "name": "Frilled Sea Serpent", + "mana_cost": "{4}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Serpent" + ], + "power": "4", + "toughness": "6" + }, + { + "functional_id": "gallant_cavalry", + "name": "Gallant Cavalry", + "mana_cost": "{3}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "gargoyle_sentinel", + "name": "Gargoyle Sentinel", + "mana_cost": "{3}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Gargoyle" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "gearsmith_guardian", + "name": "Gearsmith Guardian", + "mana_cost": "{5}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Construct" + ], + "power": "3", + "toughness": "5" + }, + { + "functional_id": "gearsmith_prodigy", + "name": "Gearsmith Prodigy", + "mana_cost": "{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "1", + "toughness": "2" + }, + { + "functional_id": "ghastbark_twins", + "name": "Ghastbark Twins", + "mana_cost": "{5}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Treefolk" + ], + "power": "7", + "toughness": "7" + }, + { + "functional_id": "ghirapur_guide", + "name": "Ghirapur Guide", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Scout" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "ghostform", + "name": "Ghostform", + "mana_cost": "{1}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "giant_spider", + "name": "Giant Spider", + "mana_cost": "{3}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Spider" + ], + "power": "2", + "toughness": "4" + }, + { + "functional_id": "gift_of_paradise", + "name": "Gift of Paradise", + "mana_cost": "{2}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "gigantosaurus", + "name": "Gigantosaurus", + "mana_cost": "{G}{G}{G}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Dinosaur" + ], + "power": "10", + "toughness": "10" + }, + { + "functional_id": "goblin_instigator", + "name": "Goblin Instigator", + "mana_cost": "{1}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Rogue" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "goblin_motivator", + "name": "Goblin Motivator", + "mana_cost": "{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Warrior" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "goblin_trashmaster", + "name": "Goblin Trashmaster", + "mana_cost": "{2}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Warrior" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "goreclaw_terror_of_qal_sisma", + "name": "Goreclaw, Terror of Qal Sisma", + "mana_cost": "{3}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Bear" + ], + "power": "4", + "toughness": "3" + }, + { + "functional_id": "grasping_scoundrel", + "name": "Grasping Scoundrel", + "mana_cost": "{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Human", + "Pirate" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "gravedigger", + "name": "Gravedigger", + "mana_cost": "{3}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "gravewaker", + "name": "Gravewaker", + "mana_cost": "{4}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Bird", + "Spirit" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "graveyard_marshal", + "name": "Graveyard Marshal", + "mana_cost": "{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie", + "Soldier" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "greenwood_sentinel", + "name": "Greenwood Sentinel", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Scout" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "guttersnipe", + "name": "Guttersnipe", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Shaman" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "havoc_devils", + "name": "Havoc Devils", + "mana_cost": "{2}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Devil" + ], + "power": "4", + "toughness": "3" + }, + { + "functional_id": "herald_of_faith", + "name": "Herald of Faith", + "mana_cost": "{3}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Angel" + ], + "power": "4", + "toughness": "3" + }, + { + "functional_id": "heroic_reinforcements", + "name": "Heroic Reinforcements", + "mana_cost": "{2}{R}{W}", + "types": [ + "sorcery" + ], + "colors": [ + "white", + "red" + ] + }, + { + "functional_id": "hieromancer_s_cage", + "name": "Hieromancer's Cage", + "mana_cost": "{3}{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "highland_game", + "name": "Highland Game", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elk" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "highland_lake", + "name": "Highland Lake", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "hired_blade", + "name": "Hired Blade", + "mana_cost": "{2}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Human", + "Assassin" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "horizon_scholar", + "name": "Horizon Scholar", + "mana_cost": "{5}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Sphinx" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "hostile_minotaur", + "name": "Hostile Minotaur", + "mana_cost": "{3}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Minotaur" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "hungering_hydra", + "name": "Hungering Hydra", + "mana_cost": "{X}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Hydra" + ], + "power": "0", + "toughness": "0" + }, + { + "functional_id": "infectious_horror", + "name": "Infectious Horror", + "mana_cost": "{3}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie", + "Horror" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "infernal_reckoning", + "name": "Infernal Reckoning", + "mana_cost": "{B}", + "types": [ + "instant" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "infernal_scarring", + "name": "Infernal Scarring", + "mana_cost": "{1}{B}", + "types": [ + "enchantment" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "inferno_hellion", + "name": "Inferno Hellion", + "mana_cost": "{3}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Hellion" + ], + "power": "7", + "toughness": "3" + }, + { + "functional_id": "inspired_charge", + "name": "Inspired Charge", + "mana_cost": "{2}{W}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "invoke_the_divine", + "name": "Invoke the Divine", + "mana_cost": "{2}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "isareth_the_awakener", + "name": "Isareth the Awakener", + "mana_cost": "{1}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "island", + "name": "Island", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [], + "supertypes": [ + "basic" + ], + "subtypes": [ + "Island" + ] + }, + { + "functional_id": "isolate", + "name": "Isolate", + "mana_cost": "{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "kargan_dragonrider", + "name": "Kargan Dragonrider", + "mana_cost": "{1}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Human", + "Warrior" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "knight_of_the_tusk", + "name": "Knight of the Tusk", + "mana_cost": "{4}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "3", + "toughness": "7" + }, + { + "functional_id": "knight_s_pledge", + "name": "Knight's Pledge", + "mana_cost": "{1}{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "knightly_valor", + "name": "Knightly Valor", + "mana_cost": "{4}{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "lathliss_dragon_queen", + "name": "Lathliss, Dragon Queen", + "mana_cost": "{4}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Dragon" + ], + "power": "6", + "toughness": "6" + }, + { + "functional_id": "lava_axe", + "name": "Lava Axe", + "mana_cost": "{4}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "lena_selfless_champion", + "name": "Lena, Selfless Champion", + "mana_cost": "{4}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "leonin_vanguard", + "name": "Leonin Vanguard", + "mana_cost": "{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Cat", + "Soldier" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "leonin_warleader", + "name": "Leonin Warleader", + "mana_cost": "{2}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Cat", + "Soldier" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "lich_s_caress", + "name": "Lich's Caress", + "mana_cost": "{3}{B}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "lightning_mare", + "name": "Lightning Mare", + "mana_cost": "{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Elemental", + "Horse" + ], + "power": "3", + "toughness": "1" + }, + { + "functional_id": "lightning_strike", + "name": "Lightning Strike", + "mana_cost": "{1}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "liliana_s_contract", + "name": "Liliana's Contract", + "mana_cost": "{3}{B}{B}", + "types": [ + "enchantment" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "liliana_s_spoils", + "name": "Liliana's Spoils", + "mana_cost": "{3}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "liliana_the_necromancer", + "name": "Liliana, the Necromancer", + "mana_cost": "{3}{B}{B}", + "types": [ + "planeswalker" + ], + "colors": [ + "black" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Liliana" + ], + "loyalty": "4" + }, + { + "functional_id": "liliana_untouched_by_death", + "name": "Liliana, Untouched by Death", + "mana_cost": "{2}{B}{B}", + "types": [ + "planeswalker" + ], + "colors": [ + "black" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Liliana" + ], + "loyalty": "4" + }, + { + "functional_id": "llanowar_elves", + "name": "Llanowar Elves", + "mana_cost": "{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Druid" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "loxodon_line_breaker", + "name": "Loxodon Line Breaker", + "mana_cost": "{2}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Elephant", + "Soldier" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "luminous_bonds", + "name": "Luminous Bonds", + "mana_cost": "{2}{W}", + "types": [ + "enchantment" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "macabre_waltz", + "name": "Macabre Waltz", + "mana_cost": "{1}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "magistrate_s_scepter", + "name": "Magistrate's Scepter", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "make_a_stand", + "name": "Make a Stand", + "mana_cost": "{2}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "manalith", + "name": "Manalith", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "marauder_s_axe", + "name": "Marauder's Axe", + "mana_cost": "{2}", + "types": [ + "artifact" + ], + "colors": [], + "subtypes": [ + "Equipment" + ] + }, + { + "functional_id": "meandering_river", + "name": "Meandering River", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "mentor_of_the_meek", + "name": "Mentor of the Meek", + "mana_cost": "{2}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Soldier" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "metamorphic_alteration", + "name": "Metamorphic Alteration", + "mana_cost": "{1}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "meteor_golem", + "name": "Meteor Golem", + "mana_cost": "{7}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Golem" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "mighty_leap", + "name": "Mighty Leap", + "mana_cost": "{1}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "militia_bugler", + "name": "Militia Bugler", + "mana_cost": "{2}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Soldier" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "millstone", + "name": "Millstone", + "mana_cost": "{2}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "mind_rot", + "name": "Mind Rot", + "mana_cost": "{2}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "mirror_image", + "name": "Mirror Image", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Shapeshifter" + ], + "power": "0", + "toughness": "0" + }, + { + "functional_id": "mist_cloaked_herald", + "name": "Mist-Cloaked Herald", + "mana_cost": "{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Merfolk", + "Warrior" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "mistcaller", + "name": "Mistcaller", + "mana_cost": "{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Merfolk", + "Wizard" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "mountain", + "name": "Mountain", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [], + "supertypes": [ + "basic" + ], + "subtypes": [ + "Mountain" + ] + }, + { + "functional_id": "murder", + "name": "Murder", + "mana_cost": "{1}{B}{B}", + "types": [ + "instant" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "mystic_archaeologist", + "name": "Mystic Archaeologist", + "mana_cost": "{1}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "naturalize", + "name": "Naturalize", + "mana_cost": "{1}{G}", + "types": [ + "instant" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "nexus_of_fate", + "name": "Nexus of Fate", + "mana_cost": "{5}{U}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "nicol_bolas_the_ravager", + "name": "Nicol Bolas, the Ravager", + "mana_cost": "{1}{U}{B}{R}", + "types": [ + "creature" + ], + "colors": [ + "blue", + "black", + "red" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Elder", + "Dragon" + ], + "power": "4", + "toughness": "4", + "back_face": { + "name": "Nicol Bolas, the Arisen", + "mana_cost": "", + "types": [ + "planeswalker" + ], + "colors": [ + "blue", + "black", + "red" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Bolas" + ], + "loyalty": "7" + } + }, + { + "functional_id": "nightmare_s_thirst", + "name": "Nightmare's Thirst", + "mana_cost": "{B}", + "types": [ + "instant" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "novice_knight", + "name": "Novice Knight", + "mana_cost": "{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "oakenform", + "name": "Oakenform", + "mana_cost": "{2}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "omenspeaker", + "name": "Omenspeaker", + "mana_cost": "{1}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "omniscience", + "name": "Omniscience", + "mana_cost": "{7}{U}{U}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "onakke_ogre", + "name": "Onakke Ogre", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Ogre", + "Warrior" + ], + "power": "4", + "toughness": "2" + }, + { + "functional_id": "one_with_the_machine", + "name": "One with the Machine", + "mana_cost": "{3}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "open_the_graves", + "name": "Open the Graves", + "mana_cost": "{3}{B}{B}", + "types": [ + "enchantment" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "oreskos_swiftclaw", + "name": "Oreskos Swiftclaw", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Cat", + "Warrior" + ], + "power": "3", + "toughness": "1" + }, + { + "functional_id": "palladia_mors_the_ruiner", + "name": "Palladia-Mors, the Ruiner", + "mana_cost": "{3}{R}{G}{W}", + "types": [ + "creature" + ], + "colors": [ + "white", + "red", + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Elder", + "Dragon" + ], + "power": "6", + "toughness": "6" + }, + { + "functional_id": "patient_rebuilding", + "name": "Patient Rebuilding", + "mana_cost": "{3}{U}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "pegasus_courser", + "name": "Pegasus Courser", + "mana_cost": "{2}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Pegasus" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "pelakka_wurm", + "name": "Pelakka Wurm", + "mana_cost": "{4}{G}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Wurm" + ], + "power": "7", + "toughness": "7" + }, + { + "functional_id": "pendulum_of_patterns", + "name": "Pendulum of Patterns", + "mana_cost": "{2}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "phylactery_lich", + "name": "Phylactery Lich", + "mana_cost": "{B}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "plague_mare", + "name": "Plague Mare", + "mana_cost": "{1}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Nightmare", + "Horse" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "plains", + "name": "Plains", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [], + "supertypes": [ + "basic" + ], + "subtypes": [ + "Plains" + ] + }, + { + "functional_id": "plummet", + "name": "Plummet", + "mana_cost": "{1}{G}", + "types": [ + "instant" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "poison_tip_archer", + "name": "Poison-Tip Archer", + "mana_cost": "{2}{B}{G}", + "types": [ + "creature" + ], + "colors": [ + "black", + "green" + ], + "subtypes": [ + "Elf", + "Archer" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "prodigious_growth", + "name": "Prodigious Growth", + "mana_cost": "{4}{G}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "psychic_corrosion", + "name": "Psychic Corrosion", + "mana_cost": "{2}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "psychic_symbiont", + "name": "Psychic Symbiont", + "mana_cost": "{4}{U}{B}", + "types": [ + "creature" + ], + "colors": [ + "blue", + "black" + ], + "subtypes": [ + "Nightmare", + "Horror" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "rabid_bite", + "name": "Rabid Bite", + "mana_cost": "{1}{G}", + "types": [ + "sorcery" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "radiating_lightning", + "name": "Radiating Lightning", + "mana_cost": "{3}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "ravenous_harpy", + "name": "Ravenous Harpy", + "mana_cost": "{2}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Harpy" + ], + "power": "1", + "toughness": "2" + }, + { + "functional_id": "reassembling_skeleton", + "name": "Reassembling Skeleton", + "mana_cost": "{1}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Skeleton", + "Warrior" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "reclamation_sage", + "name": "Reclamation Sage", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Shaman" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "recollect", + "name": "Recollect", + "mana_cost": "{2}{G}", + "types": [ + "sorcery" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "regal_bloodlord", + "name": "Regal Bloodlord", + "mana_cost": "{3}{W}{B}", + "types": [ + "creature" + ], + "colors": [ + "white", + "black" + ], + "subtypes": [ + "Vampire", + "Soldier" + ], + "power": "2", + "toughness": "4" + }, + { + "functional_id": "reliquary_tower", + "name": "Reliquary Tower", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "remorseful_cleric", + "name": "Remorseful Cleric", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Spirit", + "Cleric" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "resplendent_angel", + "name": "Resplendent Angel", + "mana_cost": "{1}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Angel" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "revitalize", + "name": "Revitalize", + "mana_cost": "{1}{W}", + "types": [ + "instant" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "rhox_oracle", + "name": "Rhox Oracle", + "mana_cost": "{4}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Rhino", + "Monk" + ], + "power": "4", + "toughness": "2" + }, + { + "functional_id": "riddlemaster_sphinx", + "name": "Riddlemaster Sphinx", + "mana_cost": "{4}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Sphinx" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "rise_from_the_grave", + "name": "Rise from the Grave", + "mana_cost": "{4}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "rogue_s_gloves", + "name": "Rogue's Gloves", + "mana_cost": "{2}", + "types": [ + "artifact" + ], + "colors": [], + "subtypes": [ + "Equipment" + ] + }, + { + "functional_id": "root_snare", + "name": "Root Snare", + "mana_cost": "{1}{G}", + "types": [ + "instant" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "runic_armasaur", + "name": "Runic Armasaur", + "mana_cost": "{1}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Dinosaur" + ], + "power": "2", + "toughness": "5" + }, + { + "functional_id": "rupture_spire", + "name": "Rupture Spire", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "rustwing_falcon", + "name": "Rustwing Falcon", + "mana_cost": "{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Bird" + ], + "power": "1", + "toughness": "2" + }, + { + "functional_id": "sai_master_thopterist", + "name": "Sai, Master Thopterist", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "1", + "toughness": "4" + }, + { + "functional_id": "salvager_of_secrets", + "name": "Salvager of Secrets", + "mana_cost": "{3}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Merfolk", + "Wizard" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "sarkhan_dragonsoul", + "name": "Sarkhan, Dragonsoul", + "mana_cost": "{4}{R}{R}", + "types": [ + "planeswalker" + ], + "colors": [ + "red" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Sarkhan" + ], + "loyalty": "5" + }, + { + "functional_id": "sarkhan_fireblood", + "name": "Sarkhan, Fireblood", + "mana_cost": "{1}{R}{R}", + "types": [ + "planeswalker" + ], + "colors": [ + "red" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Sarkhan" + ], + "loyalty": "3" + }, + { + "functional_id": "sarkhan_s_dragonfire", + "name": "Sarkhan's Dragonfire", + "mana_cost": "{3}{R}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "sarkhan_s_unsealing", + "name": "Sarkhan's Unsealing", + "mana_cost": "{3}{R}", + "types": [ + "enchantment" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "sarkhan_s_whelp", + "name": "Sarkhan's Whelp", + "mana_cost": "{2}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "satyr_enchanter", + "name": "Satyr Enchanter", + "mana_cost": "{1}{G}{W}", + "types": [ + "creature" + ], + "colors": [ + "white", + "green" + ], + "subtypes": [ + "Satyr", + "Druid" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "scapeshift", + "name": "Scapeshift", + "mana_cost": "{2}{G}{G}", + "types": [ + "sorcery" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "scholar_of_stars", + "name": "Scholar of Stars", + "mana_cost": "{3}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "serra_s_guardian", + "name": "Serra's Guardian", + "mana_cost": "{4}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Angel" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "shield_mare", + "name": "Shield Mare", + "mana_cost": "{1}{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Horse" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "shivan_dragon", + "name": "Shivan Dragon", + "mana_cost": "{4}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "shock", + "name": "Shock", + "mana_cost": "{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "siegebreaker_giant", + "name": "Siegebreaker Giant", + "mana_cost": "{3}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Giant", + "Warrior" + ], + "power": "6", + "toughness": "3" + }, + { + "functional_id": "sift", + "name": "Sift", + "mana_cost": "{3}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "sigiled_sword_of_valeron", + "name": "Sigiled Sword of Valeron", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [], + "subtypes": [ + "Equipment" + ] + }, + { + "functional_id": "silverbeak_griffin", + "name": "Silverbeak Griffin", + "mana_cost": "{W}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Griffin" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "skalla_wolf", + "name": "Skalla Wolf", + "mana_cost": "{3}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Wolf", + "Spirit" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "skeleton_archer", + "name": "Skeleton Archer", + "mana_cost": "{3}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Skeleton", + "Archer" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "skilled_animator", + "name": "Skilled Animator", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Artificer" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "skymarch_bloodletter", + "name": "Skymarch Bloodletter", + "mana_cost": "{2}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Vampire", + "Soldier" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "skyrider_patrol", + "name": "Skyrider Patrol", + "mana_cost": "{2}{G}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue", + "green" + ], + "subtypes": [ + "Elf", + "Scout" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "skyscanner", + "name": "Skyscanner", + "mana_cost": "{3}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Thopter" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "sleep", + "name": "Sleep", + "mana_cost": "{2}{U}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "smelt", + "name": "Smelt", + "mana_cost": "{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "snapping_drake", + "name": "Snapping Drake", + "mana_cost": "{3}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Drake" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "sovereign_s_bite", + "name": "Sovereign's Bite", + "mana_cost": "{1}{B}", + "types": [ + "sorcery" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "sparktongue_dragon", + "name": "Sparktongue Dragon", + "mana_cost": "{3}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "spit_flame", + "name": "Spit Flame", + "mana_cost": "{2}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "star_crowned_stag", + "name": "Star-Crowned Stag", + "mana_cost": "{3}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Elk" + ], + "power": "3", + "toughness": "3" + }, + { + "functional_id": "stitcher_s_supplier", + "name": "Stitcher's Supplier", + "mana_cost": "{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "1", + "toughness": "1" + }, + { + "functional_id": "stone_quarry", + "name": "Stone Quarry", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "strangling_spores", + "name": "Strangling Spores", + "mana_cost": "{3}{B}", + "types": [ + "instant" + ], + "colors": [ + "black" + ] + }, + { + "functional_id": "submerged_boneyard", + "name": "Submerged Boneyard", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "sun_sentinel", + "name": "Sun Sentinel", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Soldier" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "suncleanser", + "name": "Suncleanser", + "mana_cost": "{1}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Cleric" + ], + "power": "1", + "toughness": "4" + }, + { + "functional_id": "supreme_phantom", + "name": "Supreme Phantom", + "mana_cost": "{1}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Spirit" + ], + "power": "1", + "toughness": "3" + }, + { + "functional_id": "sure_strike", + "name": "Sure Strike", + "mana_cost": "{1}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "surge_mare", + "name": "Surge Mare", + "mana_cost": "{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Horse", + "Fish" + ], + "power": "0", + "toughness": "5" + }, + { + "functional_id": "suspicious_bookcase", + "name": "Suspicious Bookcase", + "mana_cost": "{2}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Wall" + ], + "power": "0", + "toughness": "4" + }, + { + "functional_id": "swamp", + "name": "Swamp", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [], + "supertypes": [ + "basic" + ], + "subtypes": [ + "Swamp" + ] + }, + { + "functional_id": "switcheroo", + "name": "Switcheroo", + "mana_cost": "{4}{U}", + "types": [ + "sorcery" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "take_vengeance", + "name": "Take Vengeance", + "mana_cost": "{1}{W}", + "types": [ + "sorcery" + ], + "colors": [ + "white" + ] + }, + { + "functional_id": "talons_of_wildwood", + "name": "Talons of Wildwood", + "mana_cost": "{1}{G}", + "types": [ + "enchantment" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "tattered_mummy", + "name": "Tattered Mummy", + "mana_cost": "{1}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie", + "Jackal" + ], + "power": "1", + "toughness": "2" + }, + { + "functional_id": "tectonic_rift", + "name": "Tectonic Rift", + "mana_cost": "{3}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "tezzeret_artifice_master", + "name": "Tezzeret, Artifice Master", + "mana_cost": "{3}{U}{U}", + "types": [ + "planeswalker" + ], + "colors": [ + "blue" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Tezzeret" + ], + "loyalty": "5" + }, + { + "functional_id": "tezzeret_cruel_machinist", + "name": "Tezzeret, Cruel Machinist", + "mana_cost": "{4}{U}{U}", + "types": [ + "planeswalker" + ], + "colors": [ + "blue" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Tezzeret" + ], + "loyalty": "4" + }, + { + "functional_id": "tezzeret_s_gatebreaker", + "name": "Tezzeret's Gatebreaker", + "mana_cost": "{4}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "tezzeret_s_strider", + "name": "Tezzeret's Strider", + "mana_cost": "{3}", + "types": [ + "artifact", + "creature" + ], + "colors": [], + "subtypes": [ + "Golem" + ], + "power": "3", + "toughness": "1" + }, + { + "functional_id": "thorn_lieutenant", + "name": "Thorn Lieutenant", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elf", + "Warrior" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "thornhide_wolves", + "name": "Thornhide Wolves", + "mana_cost": "{4}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Wolf" + ], + "power": "4", + "toughness": "5" + }, + { + "functional_id": "thud", + "name": "Thud", + "mana_cost": "{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "timber_gorge", + "name": "Timber Gorge", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "titanic_growth", + "name": "Titanic Growth", + "mana_cost": "{1}{G}", + "types": [ + "instant" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "tolarian_scholar", + "name": "Tolarian Scholar", + "mana_cost": "{2}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Human", + "Wizard" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "tormenting_voice", + "name": "Tormenting Voice", + "mana_cost": "{1}{R}", + "types": [ + "sorcery" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "totally_lost", + "name": "Totally Lost", + "mana_cost": "{4}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "tranquil_expanse", + "name": "Tranquil Expanse", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + }, + { + "functional_id": "transmogrifying_wand", + "name": "Transmogrifying Wand", + "mana_cost": "{3}", + "types": [ + "artifact" + ], + "colors": [] + }, + { + "functional_id": "trumpet_blast", + "name": "Trumpet Blast", + "mana_cost": "{2}{R}", + "types": [ + "instant" + ], + "colors": [ + "red" + ] + }, + { + "functional_id": "trusty_packbeast", + "name": "Trusty Packbeast", + "mana_cost": "{2}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Beast" + ], + "power": "2", + "toughness": "3" + }, + { + "functional_id": "two_headed_zombie", + "name": "Two-Headed Zombie", + "mana_cost": "{3}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "4", + "toughness": "2" + }, + { + "functional_id": "uncomfortable_chill", + "name": "Uncomfortable Chill", + "mana_cost": "{2}{U}", + "types": [ + "instant" + ], + "colors": [ + "blue" + ] + }, + { + "functional_id": "ursine_champion", + "name": "Ursine Champion", + "mana_cost": "{1}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Human", + "Berserker" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "vaevictis_asmadi_the_dire", + "name": "Vaevictis Asmadi, the Dire", + "mana_cost": "{3}{B}{R}{G}", + "types": [ + "creature" + ], + "colors": [ + "black", + "red", + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Elder", + "Dragon" + ], + "power": "6", + "toughness": "6" + }, + { + "functional_id": "valiant_knight", + "name": "Valiant Knight", + "mana_cost": "{3}{W}", + "types": [ + "creature" + ], + "colors": [ + "white" + ], + "subtypes": [ + "Human", + "Knight" + ], + "power": "3", + "toughness": "4" + }, + { + "functional_id": "vampire_neonate", + "name": "Vampire Neonate", + "mana_cost": "{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Vampire" + ], + "power": "0", + "toughness": "3" + }, + { + "functional_id": "vampire_sovereign", + "name": "Vampire Sovereign", + "mana_cost": "{3}{B}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Vampire", + "Noble" + ], + "power": "3", + "toughness": "4" + }, + { + "functional_id": "viashino_pyromancer", + "name": "Viashino Pyromancer", + "mana_cost": "{1}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Lizard", + "Wizard" + ], + "power": "2", + "toughness": "1" + }, + { + "functional_id": "vigilant_baloth", + "name": "Vigilant Baloth", + "mana_cost": "{3}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Beast" + ], + "power": "5", + "toughness": "5" + }, + { + "functional_id": "vine_mare", + "name": "Vine Mare", + "mana_cost": "{2}{G}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Elemental", + "Horse" + ], + "power": "5", + "toughness": "3" + }, + { + "functional_id": "vivien_of_the_arkbow", + "name": "Vivien of the Arkbow", + "mana_cost": "{4}{G}{G}", + "types": [ + "planeswalker" + ], + "colors": [ + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Vivien" + ], + "loyalty": "5" + }, + { + "functional_id": "vivien_reid", + "name": "Vivien Reid", + "mana_cost": "{3}{G}{G}", + "types": [ + "planeswalker" + ], + "colors": [ + "green" + ], + "supertypes": [ + "legendary" + ], + "subtypes": [ + "Vivien" + ], + "loyalty": "5" + }, + { + "functional_id": "vivien_s_invocation", + "name": "Vivien's Invocation", + "mana_cost": "{5}{G}{G}", + "types": [ + "sorcery" + ], + "colors": [ + "green" + ] + }, + { + "functional_id": "vivien_s_jaguar", + "name": "Vivien's Jaguar", + "mana_cost": "{2}{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Cat", + "Spirit" + ], + "power": "3", + "toughness": "2" + }, + { + "functional_id": "volcanic_dragon", + "name": "Volcanic Dragon", + "mana_cost": "{4}{R}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Dragon" + ], + "power": "4", + "toughness": "4" + }, + { + "functional_id": "volley_veteran", + "name": "Volley Veteran", + "mana_cost": "{3}{R}", + "types": [ + "creature" + ], + "colors": [ + "red" + ], + "subtypes": [ + "Goblin", + "Warrior" + ], + "power": "4", + "toughness": "2" + }, + { + "functional_id": "walking_corpse", + "name": "Walking Corpse", + "mana_cost": "{1}{B}", + "types": [ + "creature" + ], + "colors": [ + "black" + ], + "subtypes": [ + "Zombie" + ], + "power": "2", + "toughness": "2" + }, + { + "functional_id": "wall_of_mist", + "name": "Wall of Mist", + "mana_cost": "{1}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Wall" + ], + "power": "0", + "toughness": "5" + }, + { + "functional_id": "wall_of_vines", + "name": "Wall of Vines", + "mana_cost": "{G}", + "types": [ + "creature" + ], + "colors": [ + "green" + ], + "subtypes": [ + "Plant", + "Wall" + ], + "power": "0", + "toughness": "3" + }, + { + "functional_id": "waterknot", + "name": "Waterknot", + "mana_cost": "{1}{U}{U}", + "types": [ + "enchantment" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Aura" + ] + }, + { + "functional_id": "windreader_sphinx", + "name": "Windreader Sphinx", + "mana_cost": "{5}{U}{U}", + "types": [ + "creature" + ], + "colors": [ + "blue" + ], + "subtypes": [ + "Sphinx" + ], + "power": "3", + "toughness": "7" + }, + { + "functional_id": "woodland_stream", + "name": "Woodland Stream", + "mana_cost": "", + "types": [ + "land" + ], + "colors": [] + } +] diff --git a/crates/sage-engine/tests/printed_characteristics.rs b/crates/sage-engine/tests/printed_characteristics.rs new file mode 100644 index 00000000..e59f1dec --- /dev/null +++ b/crates/sage-engine/tests/printed_characteristics.rs @@ -0,0 +1,321 @@ +//! The bundled catalog against the card that was actually printed (issue #822). +//! +//! `build.rs` validates a definition's *shape* — the file stem matches the identity, a +//! creature carries both power and toughness, a planeswalker carries loyalty and nothing +//! else does. None of that can know a number is **wrong**, and the behavioural suites +//! assert against whatever the definition says, so a mis-transcribed mana cost is +//! invisible at every gate and then cemented by the test written over it. Fifteen cards +//! shipped that way (#819, #820); twelve of them differed in a field a machine can check. +//! +//! This is that machine. It reads a fixture transcribed **from the printed set** and +//! compares it to `CardDatabase::bundled()` — the same embedded, validated catalog the +//! game is played with, not the JSON on disk. +//! +//! ## What the fixture may hold, and why +//! +//! Name, mana cost, supertypes, types, subtypes, power, toughness, loyalty, colours: the +//! functional characteristics ADR 0009 already sources for the catalog itself, and +//! nothing else. No rules text, no flavour text, no artist, no image, no set symbol — the +//! project ships none of those (`AGENTS.md`, Legal Considerations), and a fixture is not a +//! loophole in that rule. +//! +//! Rules text is therefore **out of scope for this gate**, and the limit is real: of the +//! fifteen wrong cards, this catches twelve. A missing `{R}` in a cost list, a `gain_life` +//! the card does not print, a token created tapped — those stay a human-review problem, +//! and the tests in `m19_activation_costs.rs` and `m19_attacking_tokens.rs` are where they +//! are held. +//! +//! ## The one rule that keeps this honest +//! +//! `crates/sage-engine/tests/fixtures/printed_characteristics.json` is transcribed from +//! the printed set by `scripts/printed-characteristics.py`, which reads a third-party set +//! file and **never** reads `data/catalog/`. Regenerating it the other way round would +//! leave this file green forever while proving nothing, which is the failure mode to +//! design against rather than to discover. +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] + +use std::collections::BTreeMap; + +use sage_engine::{Ability, CardData, CardDatabase, CardType, Color, Supertype}; +use serde::Deserialize; + +/// One printed **face**. Every field is optional in the source the way it is optional on +/// a card — a land has no mana cost, an instant has no power — and absent means the +/// catalog must not carry one either. +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Face { + name: String, + mana_cost: String, + types: Vec, + colors: Vec, + #[serde(default)] + supertypes: Vec, + #[serde(default)] + subtypes: Vec, + /// Kept as a **string**, because a printed power is not always a number: Enigma + /// Drake's is `*`, which the catalog says as a base of 0 plus a `defined_power` + /// ability, and a numeric field could not tell that from a card that really is a 0. + #[serde(default)] + power: Option, + #[serde(default)] + toughness: Option, + #[serde(default)] + loyalty: Option, +} + +/// One card as it was printed: its front face, the identity the catalog files it under, +/// and the second face when it has one (CR 712). +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct Printed { + functional_id: String, + #[serde(flatten)] + front: Face, + #[serde(default)] + back_face: Option, +} + +const FIXTURE: &str = include_str!("fixtures/printed_characteristics.json"); + +fn printed() -> Vec { + serde_json::from_str(FIXTURE).expect("the fixture parses") +} + +fn catalog() -> BTreeMap { + // Leaked once, so the map can borrow for the whole test: a `CardDatabase` owns its + // definitions and every test here wants them by identity rather than by handle. + let db: &'static CardDatabase = Box::leak(Box::new( + CardDatabase::bundled().expect("the bundled catalog loads"), + )); + db.all() + .into_iter() + .map(|(_, data)| (data.functional_id.as_str().to_string(), data)) + .collect() +} + +/// Whether `card` sets its own power from something it counts — the shape a printed `*` +/// takes in this catalog. +fn power_is_defined(card: &CardData) -> bool { + card.abilities + .iter() + .any(|ability| matches!(ability, Ability::DefinedPower { .. })) +} + +// ----- the two directions of the correspondence ----------------------------- + +#[test] +fn issue_822_every_catalog_card_has_a_printed_card_behind_it() { + let fixture: Vec = printed() + .into_iter() + .map(|card| card.functional_id) + .collect(); + let catalog = catalog(); + let unbacked: Vec<&String> = catalog.keys().filter(|id| !fixture.contains(id)).collect(); + assert!( + unbacked.is_empty(), + "a definition with no printed card behind it is a card nobody can check: {unbacked:?}" + ); +} + +#[test] +fn issue_822_every_printed_card_in_the_fixture_is_in_the_catalog() { + let catalog = catalog(); + let orphans: Vec = printed() + .into_iter() + .map(|card| card.functional_id) + .filter(|id| !catalog.contains_key(id)) + .collect(); + assert!( + orphans.is_empty(), + "a fixture row naming no definition is a row nothing checks: {orphans:?}" + ); +} + +// ----- the characteristics themselves ---------------------------------------- + +#[test] +fn issue_822_the_catalog_matches_the_printed_characteristics() { + let catalog = catalog(); + let mut wrong: Vec = Vec::new(); + + for card in printed() { + let Some(data) = catalog.get(&card.functional_id) else { + continue; // reported by the correspondence test above + }; + let mut note = |field: &str, found: String, want: String| { + wrong.push(format!( + "{}: {field} is {found}, printed {want}", + card.functional_id + )); + }; + + if data.name != card.front.name { + note( + "name", + format!("{:?}", data.name), + format!("{:?}", card.front.name), + ); + } + if data.mana_cost != card.front.mana_cost { + note( + "mana_cost", + format!("{:?}", data.mana_cost), + format!("{:?}", card.front.mana_cost), + ); + } + if data.types != card.front.types { + note( + "types", + format!("{:?}", data.types), + format!("{:?}", card.front.types), + ); + } + if data.supertypes != card.front.supertypes { + note( + "supertypes", + format!("{:?}", data.supertypes), + format!("{:?}", card.front.supertypes), + ); + } + if data.subtypes != card.front.subtypes { + note( + "subtypes", + format!("{:?}", data.subtypes), + format!("{:?}", card.front.subtypes), + ); + } + if data.colors != card.front.colors { + note( + "colors", + format!("{:?}", data.colors), + format!("{:?}", card.front.colors), + ); + } + + // A `*` is not a number the catalog stores: the card says "equal to the number + // of…", and the definition says that with an ability. What is checked is that + // the ability is there — a base of 0 with nothing to define it is a 0/4 wearing + // a `*`'s clothes. + match card.front.power.as_deref() { + Some("*") => { + if !power_is_defined(data) { + note( + "power", + format!("{:?} with no defined_power ability", data.power), + "* (a defined power)".to_string(), + ); + } + } + other => { + let found = data.power.map(|value| value.to_string()); + if found.as_deref() != other { + note("power", format!("{found:?}"), format!("{other:?}")); + } + } + } + let toughness = data.toughness.map(|value| value.to_string()); + if toughness.as_deref() != card.front.toughness.as_deref() { + note( + "toughness", + format!("{toughness:?}"), + format!("{:?}", card.front.toughness), + ); + } + let loyalty = data.loyalty.map(|value| value.to_string()); + if loyalty.as_deref() != card.front.loyalty.as_deref() { + note( + "loyalty", + format!("{loyalty:?}"), + format!("{:?}", card.front.loyalty), + ); + } + } + + assert!( + wrong.is_empty(), + "the catalog disagrees with the card that was printed:\n {}", + wrong.join("\n ") + ); +} + +#[test] +fn issue_822_a_second_face_matches_the_face_that_was_printed() { + let catalog = catalog(); + let mut wrong: Vec = Vec::new(); + + for card in printed() { + let Some(data) = catalog.get(&card.functional_id) else { + continue; + }; + match (&card.back_face, &data.back_face) { + (None, None) => {} + (Some(printed), Some(face)) => { + if face.name != printed.name + || face.types != printed.types + || face.supertypes != printed.supertypes + || face.subtypes != printed.subtypes + || face.colors != printed.colors + || face.power.map(|value| value.to_string()).as_deref() + != printed.power.as_deref() + || face.toughness.map(|value| value.to_string()).as_deref() + != printed.toughness.as_deref() + || face.loyalty.map(|value| value.to_string()).as_deref() + != printed.loyalty.as_deref() + { + wrong.push(format!("{}: back face is {face:?}", card.functional_id)); + } + } + (Some(_), None) => wrong.push(format!( + "{}: the printed card transforms and the definition has no back face", + card.functional_id + )), + (None, Some(_)) => wrong.push(format!( + "{}: the definition has a back face and the printed card has none", + card.functional_id + )), + } + } + + assert!( + wrong.is_empty(), + "a second face disagrees with the face that was printed:\n {}", + wrong.join("\n ") + ); +} + +// ----- the licensing constraint, enforced rather than remembered ------------- + +#[test] +fn issue_822_the_fixture_carries_nothing_but_printed_characteristics() { + // `deny_unknown_fields` on `Printed` already refuses a field this test does not know + // about, so a future regeneration cannot smuggle rules text past it silently. This + // states the allowed set outright as well, because the reason for the limit lives in + // `AGENTS.md` rather than in serde, and a reviewer should not have to infer it. + const ALLOWED: [&str; 11] = [ + "functional_id", + "name", + "mana_cost", + "types", + "colors", + "supertypes", + "subtypes", + "power", + "toughness", + "loyalty", + "back_face", + ]; + let rows: Vec> = + serde_json::from_str(FIXTURE).expect("the fixture parses"); + assert!(!rows.is_empty(), "the fixture is not empty"); + for row in &rows { + for key in row.keys() { + assert!( + ALLOWED.contains(&key.as_str()), + "{key:?} is not a printed characteristic — this project ships no rules \ + text, flavour text, art, or branding, and a test fixture is not an \ + exception (AGENTS.md)" + ); + } + } +} diff --git a/docs/card-schema.md b/docs/card-schema.md index cea9fb5c..07d4a38d 100644 --- a/docs/card-schema.md +++ b/docs/card-schema.md @@ -17,6 +17,37 @@ The Rust authorities are `CardData` and `Printing` in `build.rs` discovers, validates, sorts, and embeds both directories at compile time. The running engine performs no filesystem I/O. +### The accuracy fixture + +`build.rs` and `src/catalog/` check a definition's **shape** — the file stem matches the +identity, a creature carries both power and toughness, a planeswalker carries loyalty and +nothing else does. None of that can know a number is *wrong*, and the behavioural tests +assert against whatever the definition says, so a mis-transcribed mana cost passes every +gate and is then cemented by the test written over it. Fifteen cards shipped that way +(issue #819). + +`crates/sage-engine/tests/fixtures/printed_characteristics.json` is the independent copy +that closes that gap: one record per catalog card, carrying **only** name, mana cost, +supertypes, types, subtypes, power, toughness, loyalty, and colours — the same functional +data [ADR 0009](decisions/0009-real-functional-card-data.md) already sources, and nothing +the licensing rule forbids. `tests/printed_characteristics.rs` compares it to +`CardDatabase::bundled()` and names the card and the field when they disagree. + +Regenerate it when a set is added, from a third-party set file that is **not** committed: + +```sh +scripts/printed-characteristics.py path/to/M19.json [path/to/OTHER.json ...] +``` + +The script reads that file and the catalog's *identities*; it never reads a definition's +characteristics. Regenerating the fixture from `data/catalog/` instead would leave the +gate green forever while proving nothing, which is the one way this file can fail. + +Rules text is deliberately outside the fixture, and the limit is real: of the fifteen wrong +cards this catches the twelve that differed in a printed characteristic. A missing `{R}` in +a cost list or a token created tapped is still a human-review problem, held by the +behavioural tests instead. + ## Functional definition The bundled catalog's functional definitions are sourced from a real set (Core Set 2019); diff --git a/scripts/printed-characteristics.py b/scripts/printed-characteristics.py new file mode 100755 index 00000000..9f2dec5e --- /dev/null +++ b/scripts/printed-characteristics.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Transcribe a set's printed characteristics into the fixture the catalog is checked against. + +Usage: scripts/printed-characteristics.py [ ...] + +Writes crates/sage-engine/tests/fixtures/printed_characteristics.json: one record per +card the bundled catalog holds, carrying only what is printed in the type line and the +mana cost. No rules text, no flavour text, no artist, no image, no set symbol -- the +functional characteristics ADR 0009 already sources, and nothing the licensing rule in +AGENTS.md forbids. + +The point of this file is that it is transcribed from the *printed set* and never from +the catalog. Regenerating it from `crates/sage-engine/data/catalog/` would turn the gate +that reads it into a no-op. +""" +import json, glob, os, sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +CATALOG = os.path.join(ROOT, "crates", "sage-engine", "data", "catalog") +OUT = os.path.join(ROOT, "crates", "sage-engine", "tests", "fixtures", + "printed_characteristics.json") + +COLOR = {"W": "white", "U": "blue", "B": "black", "R": "red", "G": "green"} +WUBRG = ["white", "blue", "black", "red", "green"] + + +def faces(cards): + """Every printing in the source, indexed by the name a catalog entry would use.""" + by_name = {} + for card in cards: + by_name.setdefault(card["name"], []).append(card) + if card.get("faceName"): + by_name.setdefault(card["faceName"], []).append(card) + return by_name + + +def characteristics(card): + out = { + "name": card.get("faceName") or card["name"], + "mana_cost": card.get("manaCost", ""), + "types": [t.lower() for t in card.get("types", [])], + "colors": [c for c in WUBRG if c in {COLOR[x] for x in card.get("colors", [])}], + } + if card.get("supertypes"): + out["supertypes"] = [t.lower() for t in card["supertypes"]] + if card.get("subtypes"): + out["subtypes"] = list(card["subtypes"]) + if card.get("power") is not None: + out["power"] = card["power"] + if card.get("toughness") is not None: + out["toughness"] = card["toughness"] + if card.get("loyalty") is not None: + out["loyalty"] = card["loyalty"] + return out + + +def main(paths): + cards = [] + for path in paths: + cards.extend(json.load(open(path))["data"]["cards"]) + by_name = faces(cards) + + records = [] + missing = [] + for path in sorted(glob.glob(os.path.join(CATALOG, "*.json"))): + entry = json.load(open(path)) + printings = by_name.get(entry["name"]) + if not printings: + missing.append(entry["functional_id"]) + continue + front = next((c for c in printings if c.get("side") in (None, "a")), printings[0]) + record = {"functional_id": entry["functional_id"]} + record.update(characteristics(front)) + if front.get("layout") == "transform": + back = next(c for c in cards + if c["name"] == front["name"] and c.get("side") == "b") + record["back_face"] = characteristics(back) + records.append(record) + + if missing: + sys.exit(f"no printing found for: {', '.join(missing)}") + os.makedirs(os.path.dirname(OUT), exist_ok=True) + with open(OUT, "w") as handle: + json.dump(records, handle, indent=2) + handle.write("\n") + print(f"wrote {OUT} ({len(records)} cards)") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit(__doc__) + main(sys.argv[1:])