test(engine): gate the catalog against the card that was printed - #830
Merged
Merged
Conversation
`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) <noreply@anthropic.com>
ninthworld
force-pushed
the
test/catalog-printed-data-gate
branch
from
August 8, 2026 01:37
97242d5 to
73f5f64
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #822. Part of #819. Stacked on #829 — review that first; this PR's diff against
mainwill collapse to its own once #829 merges.build.rsvalidates a definition's shape. It cannot know a number is wrong, and the behavioural tests assert against whatever the definition says — so they cement the bug rather than catch it. Fifteen cards shipped that way (#820); twelve differed in a field a machine can check.What lands
crates/sage-engine/tests/fixtures/printed_characteristics.json— one record per catalog card: name, mana cost, supertypes, types, subtypes, power, toughness, loyalty, colours. 72 KB.crates/sage-engine/tests/printed_characteristics.rs— five tests: both directions of the catalog↔fixture correspondence, the characteristics themselves, second faces, and the licensing constraint.scripts/printed-characteristics.py— regenerates the fixture from a third-party set file.The one rule that keeps it honest
The script reads the set file and the catalog's identities (
functional_id,name). It never reads a definition's characteristics. A fixture regenerated from the thing it checks is a gate that passes forever and proves nothing — that's the failure mode this is designed against, and it's stated in the script, in the test's module doc, indocs/card-schema.md, and in the engineAGENTS.md.Falsification
Reverting two of #829's fixes and running the gate:
Card and field, named.
Licensing
AGENTS.mdforbids shipping rules text, flavour text, art, symbols, or branding. The fixture carries the same functional data ADR 0009 already sources for the catalog itself and nothing else.deny_unknown_fieldsrefuses anything else structurally; a test also states the allowed set outright, because the reason lives inAGENTS.mdrather than in serde.What it does not catch, stated plainly
Rules text is out of scope — it cannot be checked without storing it, and storing it is the one thing the rule forbids. Of #820's fifteen wrong cards this catches twelve. A missing
{R}in a cost list, again_lifethe card does not print, a token created tapped: those stay a human-review problem, held by the behavioural tests instead. The module doc says so rather than implying broader coverage.*is not a numberEnigma Drake's printed power is
*. The fixture keeps power as a string, and a*is satisfied by adefined_powerability rather than by a value — so a base of 0 with nothing to define it fails, which is a 0/4 wearing a*'s clothes.Costs 0.02s in
make check.make verifygreen.🤖 Generated with Claude Code