Skip to content

fix(engine): thread the "Pay X {E}" X-announcement through Chthonian Nightmare's activated ability#5870

Open
tryeverything24 wants to merge 2 commits into
phase-rs:mainfrom
tryeverything24:fix/issue-1092
Open

fix(engine): thread the "Pay X {E}" X-announcement through Chthonian Nightmare's activated ability#5870
tryeverything24 wants to merge 2 commits into
phase-rs:mainfrom
tryeverything24:fix/issue-1092

Conversation

@tryeverything24

Copy link
Copy Markdown
Contributor

Fixes #1092.

Chthonian Nightmare: "Pay X {E}, Sacrifice a creature, Return this enchantment to its owner's hand: Return target creature card with mana value X from your graveyard to the battlefield. Activate only as a sorcery." — the ability could never be found/activated at all.

Root cause (two independent gaps)

  1. Parser: try_parse_energy_cost had no variable-X branch, so "Pay X {E}" silently parsed as PayEnergy { amount: Fixed(0) } instead of Ref(Variable("X")), unlike the neighboring "pay x life"/"pay x speed" branches. With X pinned to 0, the reanimation target filter ("mana value X") could only ever match a mana-value-0 graveyard creature — no legal target on any realistic board.
  2. Runtime: even after fixing the parser, the X-announcement machinery (cost_needs_activation_x_announcement, additional_cost_x_max, concretize_chosen_x_cost) only recognized RemoveCounter-shaped variable costs, and the resumed-cost re-surfacing for the sibling Sacrifice/ReturnToHand costs needed an ActivationResidual::ManaLeg tag that was never applied for this combination.

Testing

New end-to-end integration test driving the real activation pipeline: announces X=2 bounded by energy, sacrifices a creature, chooses between two identical-mana-value graveyard creatures (plus a wrong-mana-value decoy to prove X is genuinely threaded into the target filter), verifies energy/zones land correctly.

Verified locally (rebased onto main): fmt clean, clippy clean, 16672 lib tests pass, 3121 integration tests pass.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request resolves issue #1092 by adding support for variable energy costs (e.g., "Pay X {E}" on Chthonian Nightmare) during parsing and ability activation. The parser was updated to return a QuantityExpr for energy costs, and the engine now correctly handles X-announcement, cost concretization, and energy caps for variable energy costs. Additionally, integration tests were added to verify this behavior. The feedback identifies several incorrect Magic Comprehensive Rules (CR) citations across the modified files, violating the repository's strict rule R6 on verified CR annotations, and suggests replacing CR 107.9 with CR 107.14 and CR 107.3c.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/engine/src/game/casting_costs.rs Outdated
zone: Some(Zone::Graveyard),
filter: filter.clone(),
},
// CR 107.9 + CR 601.2b: once X is announced, a variable "Pay X {E}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Incorrect CR citation for energy and X-announcement. Evidence: crates/engine/src/game/casting_costs.rs:3624. Why it matters: CR 107.9 describes negative values, whereas CR 107.14 defines energy counters and CR 107.3c defines choosing X. Suggested fix: Replace CR 107.9 with CR 107.14 + CR 107.3c.

Suggested change
// CR 107.9 + CR 601.2b: once X is announced, a variable "Pay X {E}"
// CR 107.14 + CR 107.3c + CR 601.2b: once X is announced, a variable "Pay X {E}"
References
  1. R6. CR annotations are mandatory and verified: Every rules-touching line of engine code must carry a comment of the form CR : . The number must be verified against docs/MagicCompRules.txt before writing. (link)

Comment thread crates/engine/src/game/casting_costs.rs Outdated
AbilityCost::PayLife { amount } if amount.contains_x() => {
Some(max_pay_life_x(state, player))
}
// CR 107.9 + CR 601.2b: X in a variable "Pay X {E}" activation cost

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Incorrect CR citation for energy and X-announcement. Evidence: crates/engine/src/game/casting_costs.rs:5718. Why it matters: CR 107.9 describes negative values, whereas CR 107.14 defines energy counters and CR 107.3c defines choosing X. Suggested fix: Replace CR 107.9 with CR 107.14 + CR 107.3c.

Suggested change
// CR 107.9 + CR 601.2b: X in a variable "Pay X {E}" activation cost
// CR 107.14 + CR 107.3c + CR 601.2b: X in a variable "Pay X {E}" activation cost
References
  1. R6. CR annotations are mandatory and verified: Every rules-touching line of engine code must carry a comment of the form CR : . The number must be verified against docs/MagicCompRules.txt before writing. (link)

Comment thread crates/engine/src/game/casting_costs.rs Outdated
fn cost_has_symbolic_counter_removal(cost: &AbilityCost) -> bool {
/// True when an activated ability's cost carries a symbolic X that must be
/// announced before payment: a variable counter-removal count (CR 601.2b) or a
/// variable `{E}` amount (CR 107.9 + CR 601.2b, e.g. "Pay X {E}" — Chthonian

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Incorrect CR citation for energy and X-announcement. Evidence: crates/engine/src/game/casting_costs.rs:5828. Why it matters: CR 107.9 describes negative values, whereas CR 107.14 defines energy counters and CR 107.3c defines choosing X. Suggested fix: Replace CR 107.9 with CR 107.14 + CR 107.3c.

Suggested change
/// variable `{E}` amount (CR 107.9 + CR 601.2b, e.g. "Pay X {E}" — Chthonian
/// variable {E} amount (CR 107.14 + CR 107.3c + CR 601.2b, e.g. "Pay X {E}" — Chthonian
References
  1. R6. CR annotations are mandatory and verified: Every rules-touching line of engine code must carry a comment of the form CR : . The number must be verified against docs/MagicCompRules.txt before writing. (link)

Comment thread crates/engine/src/parser/oracle_cost.rs Outdated

/// CR 107.9: Parse energy costs like "{E}", "{E}{E}", "pay N {e}", "pay eight {e}".
fn try_parse_energy_cost(lower: &str) -> Option<u32> {
/// CR 107.9: Parse energy costs like "{E}", "{E}{E}", "pay N {e}", "pay eight {e}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MEDIUM] Incorrect CR citation for energy and X-announcement. Evidence: crates/engine/src/parser/oracle_cost.rs:1628. Why it matters: CR 107.9 describes negative values, whereas CR 107.14 defines energy counters and CR 107.3c defines choosing X. Suggested fix: Replace CR 107.9 with CR 107.14 + CR 107.3c.

Suggested change
/// CR 107.9: Parse energy costs like "{E}", "{E}{E}", "pay N {e}", "pay eight {e}",
/// CR 107.14 + CR 107.3c: Parse energy costs like "{E}", "{E}{E}", "pay N {e}", "pay eight {e}",
References
  1. R6. CR annotations are mandatory and verified: Every rules-touching line of engine code must carry a comment of the form CR : . The number must be verified against docs/MagicCompRules.txt before writing. (link)

@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans matthewevans self-assigned this Jul 15, 2026
tryeverything24 and others added 2 commits July 15, 2026 11:18
…worked (phase-rs#1092)

Root cause (two independent gaps, both required for the fix):

1. Parser: `try_parse_energy_cost` (parser/oracle_cost.rs) had no variable-X
   branch, so "Pay X {E}" silently parsed as `PayEnergy { amount: Fixed(0) }`
   instead of `PayEnergy { amount: Ref(Variable("X")) }` — mirroring the
   existing "pay x life" / "pay x speed" branches it sat next to. With X
   hard-pinned to 0, the ability's own effect ("mana value X") could only
   ever match a mana-value-0 graveyard creature, so on any realistic board
   the ability had no legal target and was never activatable — exactly the
   reported symptom.

2. Runtime: even after the parser fix, the generic X-announcement machinery
   in casting_costs.rs (`cost_needs_activation_x_announcement`, formerly
   `cost_has_symbolic_counter_removal`; `additional_cost_x_max`;
   `concretize_chosen_x_cost`) only recognized `RemoveCounter`-shaped X
   costs, not `PayEnergy`. Extending just those three functions surfaced
   `ChooseXValue` correctly, but `handle_activate_ability`'s X-announcement
   branch (casting.rs) never tagged the resumed `PendingCast` with
   `ActivationResidual::ManaLeg`, so `push_activated_ability_to_stack`'s
   existing hand-rolled Sacrifice/Exile/ReturnToHand re-surfacing detour
   (already correct, written for the mana-leg case) was skipped — the
   residual Composite[Sacrifice, ReturnToHand] silently no-op'd via the
   same class of bug already documented for the XMana residual path
   ("silent Paid no-op" for a non-self battlefield-removal cost). No
   existing card combines a symbolic-X activation cost with a sibling
   Sacrifice/Exile/ReturnToHand sub-cost, so this gap was never exercised.

Fix: tag the X-announcement resume with `ActivationResidual::ManaLeg`
whenever a non-self battlefield-removal cost remains outstanding, so the
already-correct re-surfacing logic actually runs.

Added `issue_1092_chthonian_nightmare.rs`, driving the real
parse_oracle_text + activation pipeline end to end: announce X=2 (bounded
by 3 energy counters), sacrifice a creature, choose between two
mana-value-2 graveyard creatures (a mana-value-0 decoy proves X is
threaded into the target filter, not pinned to 0), reanimate, and confirm
energy/zones all land correctly.

Verified: cargo fmt --check, cargo clippy -p engine --all-targets -D
warnings (clean), cargo test -p engine --lib (16659 passed), cargo test
-p engine --test integration (3118 passed) — all green.

Closes phase-rs#1092
@matthewevans

Copy link
Copy Markdown
Member

@gemini-code-assist review

@matthewevans matthewevans added the bug Bug fix label Jul 15, 2026
@matthewevans matthewevans removed their assignment Jul 15, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements support for variable-X energy costs (e.g., "Pay X {E}") in activated abilities, specifically addressing issue #1092 with Chthonian Nightmare. It updates the oracle cost parser to parse variable energy costs into a QuantityExpr, ensures the variable cost is capped by the player's current energy counters, and handles the announcement and concretization of X during activation. Additionally, it ensures that non-self battlefield-removal sub-costs (like sacrifice or bounce) are correctly surfaced interactively after X-announcement. A comprehensive integration test has been added to verify this behavior. I have no feedback to provide as the implementation is clean and aligns with the repository's architectural guidelines.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Chthonian nightmare — Can't make it work; there's no point where to activate the ability of the card or start by paying…

2 participants