You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
One effect says who and one payload says what changes, so a printed sentence about one
permanent is one effect whether that permanent is the source, a target, or a class — and the
four empty cells in the matrix fill themselves.
Context and evidence
Parent: #819, §2.2. Second of two halves; #826 collapses the amount axis and should land
first.
The subject × payload matrix is one variant per cell, and it is unevenly filled:
power/toughness
keyword
restriction
written-out ability
source
PumpSelf
AlterAbilitiesSelf.gain
RestrictSelf
✗
target
Pump
Pump.keywords / GrantKeyword
Pump.restrictions / Restrict
Pump.abilities
class
PumpAll
GrantKeywordAll
RestrictAll
✗
Nine variants for one idea, and three consequences visible in the shipped catalog:
The self row has no keyword grant of its own. The only way to add a keyword to the source
is AlterAbilitiesSelf.gain, whose sibling field is lose_all — reaching for the
lose-all-abilities verb to say "gains lifelink". resplendent_angel's third printed ability
({3}{W}{W}{W}: gets +2/+2 and gains lifelink) is missing from the catalog entirely, and
this is the most likely reason (#821).
One printed sentence becomes three effects.chromium_the_mutable's Discard a card: … becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn. is animate_self + alter_abilities_self + restrict_self. They share a CR 613.7 timestamp only by accident of being adjacent in a list.
Two effects naming the same target advertise two slots.Effect::target_groups is
collected with flat_map over the effect list (apply.rs:191, actions/targeting.rs:91) with
no dedup. docs/card-schema.md names the hazard —
One effect declares one target group, so two effects would advertise two independent slots
and let a player pump one creature while a different one gained flying.
— and prescribes folding the grant into the pump. That prescription only covers pump: PutCounters has no keywords field, which is why skyrider_patrol is authored the way the
document warns against and asks for two targets (#821). A single Subject field makes the
bug unrepresentable rather than merely discouraged.
Scope
One Subject { This, Target(TargetSpec, TargetCount), Class(PermanentFilter) } — the axis
that decides who, and the one place a target group is declared.
One modification payload — power/toughness, keywords, combat restrictions, written-out
abilities, and (where it belongs) counters — the axis that decides what changes.
The nine variants above collapsed onto it, with the four empty cells filled by construction.
PutCounters reconciled: either it joins the payload, or the reason it stays separate is
stated where the payload lives.
Durations stay explicit — until end of turn, until your next turn, for as long as the source
remains — since Animate already models three and the collapse must not flatten them.
Catalog JSON re-authored, including chromium_the_mutable as one effect per printed sentence.
The validator keeps refusing a subject/payload pairing that means nothing, and gains one for
the case that is now representable and still wrong.
Acceptance criteria
PumpSelf, PumpAll, GrantKeyword, GrantKeywordAll, Restrict, RestrictSelf, and RestrictAll no longer exist as separate variants.
A self-directed keyword grant is authorable without touching the lose-all-abilities verb,
and resplendent_angel's {3}{W}{W}{W} ability is in the catalog and works.
skyrider_patrol's trigger declares one target group; a test asserts the slot count
with two eligible creatures on the board.
No ability in the catalog can declare two target groups for one printed target — enforced
by the type or by a validator with a named Violation, not by review.
Granting a written-out ability to the source and to a class is authorable, and one
of the two is proven by a test.
chromium_the_mutable's activated ability is one effect and its halves share one
CR 613.7 timestamp.
Keyword grants stay idempotent and written-out ability grants stay additive, per
CR 613.1f — unchanged from today and tested.
alter_abilities_self's lose / lose_all half survives intact; this collapses the additive row only.
make compat regenerated; make verify green.
Test and validation evidence
Existing m19_* behavioural suites over the re-authored catalog — behaviour-preserving for
every card except the two that were wrong.
A catalog-wide test that no ability declares duplicate target groups.
Layer-6 tests: a granted keyword indistinguishable from a printed one, a granted dies trigger
still firing from the snapshot it was granted in.
Rules-text tests for each row of the matrix.
make verify.
Dependencies and sequencing
After #826 (one Amount first — the payload is much cleaner over verbs that already carry
one). After #824 if Subject::Class is to carry the shared filter rather than MassAffects. Supersedes #821 if scheduled before it; otherwise it collapses the narrow affordances #821 adds.
Estimated scope
Large — broad change requiring a split assessment. Split from #826 along the two independent
axes; splitting further would mean landing a half-collapsed matrix, which is harder to reason
about than either end state.
Risks and constraints
Layer 6 and CR 613.7 are the correctness surface. Merging three adjacent effects into one
changes how many timestamps exist. That is the fix for chromium_the_mutable, and a
regression anywhere it was load-bearing by accident. The layer tests are the net; run them
card by card during re-authoring, not once at the end.
A granted dies trigger is the one grant that outlives the grant, read from the snapshot
the permanent still carried it in (CR 603.6c/603.10a). The collapse must not disturb the
accessor path — abilities_of_permanent stays the only road, per the engine guide.
Idempotence differs by payload: duplicate keyword grants are redundant, duplicate ability
grants are additive. One payload carrying both must keep both rules.
Non-goals
The amount axis (#826). TargetSpec's internals (#825). AlterAbilitiesSelf's subtractive
half. The bundled one-card verbs (#828).
Documentation impact
docs/card-schema.md — "Granting keywords", "Granting a whole ability", and "Losing keywords
and losing all abilities" all describe the current per-row shape and are rewritten around the
matrix. "How far the vocabulary reaches" gains the four cells. An ADR is warranted here if
anywhere in this tranche: this changes how every future card is authored, and per AGENTS.md
it is written after the code proves the shape, not before.
Goal
One effect says who and one payload says what changes, so a printed sentence about one
permanent is one effect whether that permanent is the source, a target, or a class — and the
four empty cells in the matrix fill themselves.
Context and evidence
Parent: #819, §2.2. Second of two halves; #826 collapses the amount axis and should land
first.
The subject × payload matrix is one variant per cell, and it is unevenly filled:
PumpSelfAlterAbilitiesSelf.gainRestrictSelfPumpPump.keywords/GrantKeywordPump.restrictions/RestrictPump.abilitiesPumpAllGrantKeywordAllRestrictAllNine variants for one idea, and three consequences visible in the shipped catalog:
The self row has no keyword grant of its own. The only way to add a keyword to the source
is
AlterAbilitiesSelf.gain, whose sibling field islose_all— reaching for thelose-all-abilities verb to say "gains lifelink".
resplendent_angel's third printed ability(
{3}{W}{W}{W}: gets +2/+2 and gains lifelink) is missing from the catalog entirely, andthis is the most likely reason (#821).
One printed sentence becomes three effects.
chromium_the_mutable'sDiscard a card: … becomes a Human with base power and toughness 1/1, loses all abilities, and gains hexproof. It can't be blocked this turn.isanimate_self+alter_abilities_self+restrict_self. They share a CR 613.7 timestamp only by accident of being adjacent in a list.Two effects naming the same target advertise two slots.
Effect::target_groupsiscollected with
flat_mapover the effect list (apply.rs:191,actions/targeting.rs:91) withno dedup.
docs/card-schema.mdnames the hazard —— and prescribes folding the grant into the
pump. That prescription only coverspump:PutCountershas nokeywordsfield, which is whyskyrider_patrolis authored the way thedocument warns against and asks for two targets (#821). A single
Subjectfield makes thebug unrepresentable rather than merely discouraged.
Scope
Subject { This, Target(TargetSpec, TargetCount), Class(PermanentFilter) }— the axisthat decides who, and the one place a target group is declared.
abilities, and (where it belongs) counters — the axis that decides what changes.
PutCountersreconciled: either it joins the payload, or the reason it stays separate isstated where the payload lives.
remains — since
Animatealready models three and the collapse must not flatten them.chromium_the_mutableas one effect per printed sentence.the case that is now representable and still wrong.
Acceptance criteria
PumpSelf,PumpAll,GrantKeyword,GrantKeywordAll,Restrict,RestrictSelf, andRestrictAllno longer exist as separate variants.and
resplendent_angel's{3}{W}{W}{W}ability is in the catalog and works.skyrider_patrol's trigger declares one target group; a test asserts the slot countwith two eligible creatures on the board.
by the type or by a validator with a named
Violation, not by review.of the two is proven by a test.
chromium_the_mutable's activated ability is one effect and its halves share oneCR 613.7 timestamp.
CR 613.1f — unchanged from today and tested.
alter_abilities_self'slose/lose_allhalf survives intact; this collapses theadditive row only.
make compatregenerated;make verifygreen.Test and validation evidence
m19_*behavioural suites over the re-authored catalog — behaviour-preserving forevery card except the two that were wrong.
resplendent_angelandskyrider_patrol(see engine: two M19 cards the ability IR cannot currently say #821 — if that lands first,these are its tests, unchanged).
still firing from the snapshot it was granted in.
make verify.Dependencies and sequencing
After #826 (one
Amountfirst — the payload is much cleaner over verbs that already carryone). After #824 if
Subject::Classis to carry the shared filter rather thanMassAffects.Supersedes #821 if scheduled before it; otherwise it collapses the narrow affordances
#821 adds.
Estimated scope
Large — broad change requiring a split assessment. Split from #826 along the two independent
axes; splitting further would mean landing a half-collapsed matrix, which is harder to reason
about than either end state.
Risks and constraints
changes how many timestamps exist. That is the fix for
chromium_the_mutable, and aregression anywhere it was load-bearing by accident. The layer tests are the net; run them
card by card during re-authoring, not once at the end.
the permanent still carried it in (CR 603.6c/603.10a). The collapse must not disturb the
accessor path —
abilities_of_permanentstays the only road, per the engine guide.grants are additive. One payload carrying both must keep both rules.
Non-goals
The amount axis (#826).
TargetSpec's internals (#825).AlterAbilitiesSelf's subtractivehalf. The bundled one-card verbs (#828).
Documentation impact
docs/card-schema.md— "Granting keywords", "Granting a whole ability", and "Losing keywordsand losing all abilities" all describe the current per-row shape and are rewritten around the
matrix. "How far the vocabulary reaches" gains the four cells. An ADR is warranted here if
anywhere in this tranche: this changes how every future card is authored, and per
AGENTS.mdit is written after the code proves the shape, not before.