What this engine enforces, and how that is known
This is a rules engine for a trading card game, written so that the
claim "it enforces the rules" can be checked by somebody who does not
trust it. Every number below is generated by the thing it describes, and
the command that produces it is printed beside it. None is typed by hand.
1. What it enforces
Two reports, one per dimension. The rule report maps the rulebook's
nodes to the tests that exercise them; a rule counts as covered only when
a test cites it and genuinely exercises its content.
cargo test -p riftbound-conformance
# writes docs/coverage.md; a test asserts the committed file matches
Read the rule report — every covered node,
and every uncovered one.
The card report is the other direction: which cards the engine runs,
and for the ones it does not, what was missing. This server is serving a
pack right now, and these are its own numbers:
reading the pack…
cargo test -p riftbound-origins --test pack write_card_report -- --ignored
# writes docs/cards.md; a test asserts the committed file matches
Read the card report — the refusals come
first, because they are the more useful half.
2. What happens when it does not know
Every card is in exactly one of three buckets, and there is no fourth
state and no default. A card the engine runs has a test that plays it. A
card it cannot run is refused by name: a deck holding one will
not start a game unless both players override, and an override puts a
banner naming that card on screen for the whole game.
This is the stronger claim, and the one that survives contact with a
rules expert. The failure this design exists to prevent is a card that
silently does most of what it says. Partial is a lie here; there
is no arm for it.
Each refusal names what was missing in the engine's own terms. The
card report collates them: every refusal is filed under one named shape,
and the shapes are ranked by how many cards want each. So the refusals
are not a list of failures, they are a specification of what the engine
lacks — which is the honest answer to "what would it take to finish
this", and the part of this document a rules expert can argue with.
Each refusal is filed under the first shape its reason
names, so a count there is a floor and never a ceiling. The report says
so where the ranking is, rather than here.
3. Why believe the tests
Because each one has been shown to fail. Every card carries at least
one entry in a mutation ledger: weaken the card — its cost, a printed
number, a tag, a keyword, its program — and that card's own test must go
red, while the tests naming other cards stay green. A mutation that
reddens everything proves only that it compiles.
scripts/cardmut.py scripts/mutations/task-N.json
# every mutation names the tests it must BREAK and those it must leave GREEN
This is not decoration. It has caught tests that passed for the wrong
reason, a near miss that proved a different clause than it claimed, and a
card characteristic that no test could observe at all.
Two accidents are worth reporting, because together they are the
closest thing to a control this project has. On two separate occasions a
group of cards was transcribed and bucketed twice over, by
authors working from the same index who could not see each other's work:
four cards the first time, surfacing when two branches were merged, and
four more the second, where one branch was written from a commit that
predated the other and was never merged at all.
All eight agreed on which bucket the card belongs in. In both
accidents the refusals independently named the same missing shape in the
engine, in the engine's own terms — not the same words, but the same
absent mechanism, located in the same place in the code.
Eight is a sample and not a study. But bucketing is the judgement this
whole document rests on, and this is the only evidence available that the
judgement is reproducible rather than one author's taste.
4. Can a disagreement be settled
A game's record is its seed, the pack's hash, the engine build, and
every answer given, in order. Replaying it reproduces the position
exactly, and the record carries the fingerprint it reached so it can
check itself. A record that is not the record it claims to be is refused
rather than replayed into a different game and called faithful, and the
suite has tried these ways of making one:
16 of them, each its own case in the suite:
- a step dropped off the end
- no steps at all
- a step out of the middle
- two steps in the wrong order
- a step said twice
- a step under the wrong seat
- the override policy flipped
- an answer naming somewhere nobody offered
- a record that never says where it ended up
- a record carrying no digest of itself
- an action naming an object no game ever held
- a record that stops one step early and adds up
- a step the kernel says came back differently
- a record played against other card data
- a record played against another build of the engine
- a record handed over by a game still running
cargo test -p riftbound-play --test plain -- record::write_tamper_list --ignored
# rewrites that list above from the suite; a test asserts the page matches
The last one is not a tamper at all: a game still being played hands
over a record with the parts that would rebuild it held back, because
rebuilding it would rebuild the half neither player may see. The replay
route has to refuse that too, and say so rather than fail over a missing
field.
The interesting ones are the forgeries. A file edited in a text
editor no longer implies its own digest and is refused by arithmetic,
which is right and is also easy. A file edited and then made to add up
again can only be caught by replaying it and comparing where it lands —
which is what the fingerprint is for.
So a contested position is a file, not a story.
5. The licence
Three repositories with a firewall between them. Rules live in the
engine and card data lives in the pack; this server and its client hold
neither and know a card only by its identifier. Three scanners run over
every tracked file of all three and must read zero: verbatim containment
against the rulebook, ordered-subsequence echo against every rule
sentence, and containment against the card index.
RIFTBOUND_REFS=~/riftbound-refs ./scripts/gate.sh
The material those scanners read is not in any
repository and never will be.
What this does not claim
That the engine implements the whole game. It implements what the
reports say it implements, and refuses the rest by name. Of the four
verification layers this project specified, three are built: rule-cited
tests, property and fuzz testing, and determinism. The fourth — a corpus
of scenarios drawn from real judge rulings — is specified, has a harness,
and is empty, because its content is somebody's judgement and inventing
it would make it the one part of this project that is not evidence.