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 Amount says how much, so a verb stops being duplicated once per way of counting and Creatures you control get +1/+1 for each … becomes authorable without two more variants.
Context and evidence
Parent: #819, §2.2. First of two halves; #827 collapses the subject axis.
Effect carries the same verb three times over, once per way its number is derived:
Verb
Printed number
Count of permanents
Derived amount
pump a target
Pump
PumpByCount
PumpByAmount
deal damage
DealDamage
DealDamageByCount
DealDamageByAmount
gain life
GainLife
GainLifeByCount
—
lose life
LoseLife
—
LoseLifeByAmount
draw
DrawCard
—
DrawCardsByAmount
discard
Discard
—
DiscardByAmount
pump a class
PumpAll
—
—
Eleven variants for six verbs, and the table's holes are the point: LoseLifeByCount and GainLifeByAmount do not exist because no M19 card needed them, so the next card that does
adds a variant plus an arm in each of ~6 exhaustive matches — including 86 arms in sage-server/src/rules_text/words.rs and 86 Effect:: arms in sage-server/src/rules_text/effects.rs. Effect is at 82 variants.
The codebase already solved this once, and its own reasoning is the argument. CreateToken takes count_of as a field, not a create_token_by_count twin:
A field rather than a create_token_by_count twin, which is where this departs from Effect::PumpByCount and its two siblings: a second variant would duplicate the four other
fields here … The field says where that number comes from; it does not add a second verb.
The counter-argument stated in PumpByAmount's docs is that two optional fields would make
"neither" and "both" authorable shapes that mean nothing. That is true of two Option fields
and false of a sum type:
Exactly one is present, by construction. CreateToken's field already proves the ergonomics.
The CR 608.2 semantics are identical across all three today — the amount is read once, as the
effect applies — which is what makes this a mechanical collapse rather than a behaviour change.
exclusions.json names one limit that is a limit of this shape: "Effects whose amount is
derived from a source outside the ones the IR names." Narrowing DerivedAmount is orthogonal
to this issue, but the entry should be re-read against the collapsed shape.
Scope
One Amount sum type covering the three derivations already modelled.
The eleven variants above collapsed to six verbs carrying it.
Resolution reads the amount at the one CR 608.2 seam, once, exactly as each variant does now.
The server's rules-text formatter renders the amount from one place instead of three arms per
verb.
Catalog JSON re-authored. No card's rules change.
The validator keeps refusing an amount a given verb cannot mean.
Acceptance criteria
PumpByCount, PumpByAmount, DealDamageByCount, DealDamageByAmount, GainLifeByCount, LoseLifeByAmount, DrawCardsByAmount, and DiscardByAmount no
longer exist as separate variants.
The four combinations the table is missing today — a life gain per derived amount, a life
loss per counted permanent, and the two on the class row — are authorable with no Rust
change, and at least one is proven by a test.
Every affected card behaves identically; the existing behavioural suites pass with the
re-authored catalog.
The amount is still read once, on resolution, and a "this way" amount still reads the
same resolution's earlier work (patient_rebuilding, scapeshift, liliana_untouched_by_death).
nightmare_s_thirst still reads life gained this turn and not life gained after it
resolves.
make compat regenerated; make verify green.
Test and validation evidence
Existing m19_* suites over re-authored JSON — this is behaviour-preserving.
A new test for one of the previously impossible combinations, card-shaped.
Rules-text tests for each verb's three renderings.
make verify.
Dependencies and sequencing
After #820 and #822 so the catalog is correct first. Sequence before#827: the subject
collapse is much cleaner over verbs that already carry one Amount. Overlaps #824/#825 in
files — pick an order.
Estimated scope
Medium — focused cross-layer change: one type, eleven variants to six, and the formatter.
Risks and constraints
The three derivations have subtly different reading rules per verb — LoseLifeByAmount reads of each named player ("half their life" is each seat's own half), while GainLifeByCount counts relative to the effect's controller. Collapsing the carrier must
not collapse that distinction; it belongs to the PlayerRef/CountScope already present, and
a test per verb should pin it.
Non-goals
The subject axis (#827). Widening DerivedAmount to new sources. CreateToken, which already
has the right shape.
Documentation impact
docs/card-schema.md — the amount vocabulary. exclusions.json — re-read the derived-amount
entry. No ADR: this is the shape ADR 0003 already chose, applied consistently.
Goal
One
Amountsays how much, so a verb stops being duplicated once per way of counting andCreatures you control get +1/+1 for each …becomes authorable without two more variants.Context and evidence
Parent: #819, §2.2. First of two halves; #827 collapses the subject axis.
Effectcarries the same verb three times over, once per way its number is derived:PumpPumpByCountPumpByAmountDealDamageDealDamageByCountDealDamageByAmountGainLifeGainLifeByCountLoseLifeLoseLifeByAmountDrawCardDrawCardsByAmountDiscardDiscardByAmountPumpAllEleven variants for six verbs, and the table's holes are the point:
LoseLifeByCountandGainLifeByAmountdo not exist because no M19 card needed them, so the next card that doesadds a variant plus an arm in each of ~6 exhaustive matches — including 86 arms in
sage-server/src/rules_text/words.rsand 86Effect::arms insage-server/src/rules_text/effects.rs.Effectis at 82 variants.The codebase already solved this once, and its own reasoning is the argument.
CreateTokentakescount_ofas a field, not acreate_token_by_counttwin:The counter-argument stated in
PumpByAmount's docs is that two optional fields would make"neither" and "both" authorable shapes that mean nothing. That is true of two
Optionfieldsand false of a sum type:
Exactly one is present, by construction.
CreateToken's field already proves the ergonomics.The CR 608.2 semantics are identical across all three today — the amount is read once, as the
effect applies — which is what makes this a mechanical collapse rather than a behaviour change.
exclusions.jsonnames one limit that is a limit of this shape: "Effects whose amount isderived from a source outside the ones the IR names." Narrowing
DerivedAmountis orthogonalto this issue, but the entry should be re-read against the collapsed shape.
Scope
Amountsum type covering the three derivations already modelled.verb.
Acceptance criteria
PumpByCount,PumpByAmount,DealDamageByCount,DealDamageByAmount,GainLifeByCount,LoseLifeByAmount,DrawCardsByAmount, andDiscardByAmountnolonger exist as separate variants.
loss per counted permanent, and the two on the class row — are authorable with no Rust
change, and at least one is proven by a test.
re-authored catalog.
same resolution's earlier work (
patient_rebuilding,scapeshift,liliana_untouched_by_death).nightmare_s_thirststill reads life gained this turn and not life gained after itresolves.
make compatregenerated;make verifygreen.Test and validation evidence
m19_*suites over re-authored JSON — this is behaviour-preserving.make verify.Dependencies and sequencing
After #820 and #822 so the catalog is correct first. Sequence before #827: the subject
collapse is much cleaner over verbs that already carry one
Amount. Overlaps #824/#825 infiles — pick an order.
Estimated scope
Medium — focused cross-layer change: one type, eleven variants to six, and the formatter.
Risks and constraints
The three derivations have subtly different reading rules per verb —
LoseLifeByAmountreadsof each named player ("half their life" is each seat's own half), while
GainLifeByCountcounts relative to the effect's controller. Collapsing the carrier mustnot collapse that distinction; it belongs to the
PlayerRef/CountScopealready present, anda test per verb should pin it.
Non-goals
The subject axis (#827). Widening
DerivedAmountto new sources.CreateToken, which alreadyhas the right shape.
Documentation impact
docs/card-schema.md— the amount vocabulary.exclusions.json— re-read the derived-amountentry. No ADR: this is the shape ADR 0003 already chose, applied consistently.