ship/t104 special action x#5788
Merged
Merged
Conversation
matthewevans
commented
Jul 14, 2026
Member
- refactor(engine): rename activated_ability_x -> announced_source_x (CR 107.3a + CR 107.3d)
- fix(engine): announce, pay, and carry X for the turn-face-up special action (CR 107.3d + CR 702.37f)
…R 107.3a + CR 107.3d) The source-keyed announced-X carrier is about to gain its SECOND announce surface: a turn-face-up SPECIAL ACTION (CR 116.2b), whose X is fixed by CR 107.3d rather than by CR 107.3a. The old name would then be a lie -- the field carries "the X announced for an in-flight cost of this source", not specifically an activated ability's. Behaviour-free on its own: the activated-ability publication and the trigger-instantiation consumer are unchanged. Doc widened to name both CR announce surfaces. serde: #[serde(alias = "activated_ability_x")]. GameState sets no deny_unknown_fields, so WITHOUT the alias a mid-activation save written by a pre-rename binary would be accepted and its live X SILENTLY DROPPED -- the worst failure mode, since it looks like a clean load. The save-compat test gains a third direction (a pre-rename key fixture) that pins this; deleting the alias makes it fail with `left: None`, watched.
…action (CR 107.3d + CR 702.37f)
Turning a face-down permanent face up is a SPECIAL ACTION (CR 116.2b): it uses no stack and
never passes through push_ability_entry, so neither existing X channel could reach it by
construction. Three faces in the pool take their X from that action -- Warbreak Trumpeter
(Morph {X}{X}{R}), Bane of the Living (Morph {X}{B}{B}) and Aurelia's Vindicator (Disguise
{X}{3}{W}) -- and all three were LYING GREENS: zero Unimplemented, 100% "supported", and
every one resolving X to 0.
MEASURED on pristine main, through the real GameAction::TurnFaceUp path: flipping Warbreak
Trumpeter cost 1 mana and created 0 goblins. TWO defects, not one:
(A) THE COST. GameAction::TurnFaceUp carried no X at all, so the player never chose one and
the raw {X}{X}{R} reached mana payment with its ManaCostShard::X shards unconcretized --
where they are not payable and were dropped outright. The permanent flipped for its
non-X remainder ({R}) alone. Fixing (B) without this would be strictly WORSE than the
status quo: pay {R}, collect X goblins.
(B) THE CARRIER. Nothing recorded the announced X, so the turn-face-up trigger had nothing
to bind. (cost_x_paid is the CR 107.3m *cast* channel and reads 0 here -- a face-down
permanent was cast for {3}, CR 702.37c.)
CR 107.3d: X is "chosen by the player taking the special action immediately before they pay
that cost" -- with no priority window between the choice and the payment. So X rides on the
action (#[serde(default)]; an x-less client announces X=0, itself a legal choice) rather
than on a WaitingFor round-trip, which would model a pause the rules do not have.
CR 702.37f (morph) / CR 702.168e (disguise) then bind it: "other abilities of that permanent
may also refer to X ... equal to the value of X chosen as the morph special action was
taken." The three faces spend X in three DIFFERENT slots -- Token.count, PtValue::Variable
("-X"), and multi_target.max -- but all three read ResolvedAbility::chosen_x, so publishing
to the existing source-keyed carrier binds every one of them through the existing consumer
(triggers::build_triggered_ability). No new carrier, no new enum variant, no parser change,
no read-path change.
The stamp must land at trigger INSTANTIATION, not resolution: Aurelia's Vindicator spends X
in multi_target.max ("exile up to X other target creatures"), consumed during TARGET
SELECTION -- a resolution-time stamp would arrive too late.
Scoping: published ONLY when the cost actually has an {X} (CR 107.3d grants a choice only
then). A no-X flip leaves the carrier untouched rather than clobbering it with Some((..,0)):
an unrelated activated ability of ANOTHER object may have its own announced X in flight.
Witnesses (crates/engine/tests/integration/special_action_x_runtime.rs, all watched red ->
green, cards synthesized from VERBATIM Oracle text):
* Warbreak Trumpeter X=3 -> 3 Goblins (effect slot)
* Bane of the Living X=2 -> all creatures -2/-2 (negated PtValue slot)
* Aurelia's Vindicator X=2 -> 2 creatures exiled (target-count slot)
* payment: {X}{X}{R} at X=3 charges 2*3 + {R} = 7 mana, not 1
* X=0 announces a REAL zero (chosen_x == Some(0) on the stack, not None) and costs {R}
* unpayable X and a non-zero X on a no-X cost are both REJECTED
* CONTROL Hooded Hydra (Morph {3}{G}{G}, no X): still flips, still gets its five +1/+1
counters (non-vacuity), and publishes NOTHING
Reds watched independently: disabling the publication fails exactly the 4 carrier witnesses
while all 4 controls stay green; disabling concretize_x fails exactly the payment witness.
#96/#97 pins (11/11) and #99 pins (5/5) stay green.
NOTE (filed separately): the turn-face-up special action is not enumerated by
ai_support::legal_actions and is dispatched nowhere in the client, so it is currently
reachable only from engine tests. That reachability gap is pre-existing and independent of X.
matthewevans
enabled auto-merge
July 14, 2026 05:56
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Parse changes introduced by this PR✓ No card-parse changes detected. |
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.