Skip to content

fix(engine): Sidisi, Regent of the Mire — bind sacrifice-cost X and reanimate at X+1 mana value#6027

Open
nghetienhiep wants to merge 2 commits into
phase-rs:mainfrom
nghetienhiep:fix/issue-5957
Open

fix(engine): Sidisi, Regent of the Mire — bind sacrifice-cost X and reanimate at X+1 mana value#6027
nghetienhiep wants to merge 2 commits into
phase-rs:mainfrom
nghetienhiep:fix/issue-5957

Conversation

@nghetienhiep

Copy link
Copy Markdown
Contributor

Summary

Sidisi, Regent of the Mire ({T}, Sacrifice a creature you control with mana value X other than Sidisi: Return target creature card with mana value X plus 1 from your graveyard to the battlefield. Activate only as a sorcery.)
was silently misparsed and, separately, could not actually be activated.

  • The parser dropped the plus 1 when it hit parse_quantity_expr_number
    (the filter-threshold quantity parser), so the reanimation target's mana
    value gate compiled to == X instead of == X + 1 — it would return a
    creature with the same mana value as the one sacrificed rather than one
    greater.
  • Independently, the sacrifice cost itself (with mana value X) is a
    Shoal-style pitch-bound X: X is defined by whichever creature the player
    chooses to sacrifice, not announced beforehand. That relaxation existed
    only for exile-as-cost (exile_cost_effective_filter), so sacrifice-cost
    eligibility evaluated the filter with X unresolved (== 0) and found no
    eligible permanents — the ability could never be activated at all.

Changes

  • parse_quantity_expr_number (oracle_nom/quantity.rs) now composes an
    optional plus N / minus N tail into QuantityExpr::Offset, mirroring
    the existing arithmetic arm in oracle_util::parse_count_expr. This is a
    general filter-threshold fix (mana value, power, toughness, counters),
    not a Sidisi-specific string match.
  • Renamed exile_cost_effective_filter to pitch_bound_x_effective_filter
    and applied it in find_eligible_sacrifice_targets, so any Shoal-style
    sacrifice cost (not just exile) is payable before X is known.
  • handle_sacrifice_for_cost binds chosen_x from the sacrificed
    permanent's own mana value when the cost filter is pitch-bound, mirroring
    the existing exile-pitch binding used for alternative casting costs.

Test plan

  • New integration test
    crates/engine/tests/integration/issue_5957_sidisi_regent_of_the_mire.rs
    drives the real parsed card through ActivateAbility → sacrifice →
    target selection → resolution, asserting the mana-value-3 graveyard
    creature is offered as a legal target, the mana-value-2 one is not, and
    the correct creature is reanimated.
  • cargo test -p engine --lib — 16746 passed, 0 failed
  • cargo test -p engine --test integration — 3205 passed, 0 failed
    (includes the existing Shoal-pitch-exile coverage, unaffected by the
    rename)
  • cargo clippy -p engine --all-targets --features cli -- -D warnings — clean
  • cargo fmt --all
  • Verified Oracle text against the Scryfall API before implementing

Closes #5957

…eanimate at X+1 mana value

The parser dropped "plus 1" when parsing "mana value X plus 1" filter
thresholds, so the reanimation target required mana value == X (the
sacrificed creature's own mana value) instead of X + 1. Separately, the
activated ability's sacrifice cost ("Sacrifice a creature you control
with mana value X") is a Shoal-style pitch-bound X — defined by whichever
creature is chosen, not a prior announcement — but that relaxation only
existed for exile costs, so the ability's own sacrifice requirement never
had any eligible permanents and couldn't be activated at all.

- parse_quantity_expr_number now composes an optional "plus N"/"minus N"
  tail into a QuantityExpr::Offset, mirroring the existing count-position
  arithmetic arm in parse_count_expr.
- Generalized exile_cost_effective_filter to pitch_bound_x_effective_filter
  and applied it to find_eligible_sacrifice_targets so Shoal-style sacrifice
  costs are payable before X is known.
- handle_sacrifice_for_cost now binds chosen_x from the sacrificed
  permanent's mana value when the sacrifice cost carries a pitch-bound X
  filter, mirroring the existing exile-pitch binding.

Closes phase-rs#5957

@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 Shoal-style 'with mana value X' sacrifice costs (such as on Sidisi, Regent of the Mire) and arithmetic offsets in quantity expressions ('X plus N' / 'X minus N') in accordance with CR 107.1b, CR 107.3a, and CR 118.9. It renames and generalizes exile_cost_effective_filter to pitch_bound_x_effective_filter to make it zone-agnostic, handles binding chosen_x during sacrifice cost resolution, and updates the quantity parser to handle offsets. An integration test and card fixture for Sidisi, Regent of the Mire are also added. There are no review comments, so we have no feedback to provide.

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.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 1 card(s), 1 signature(s) (baseline: main 9c80fcaad64d)

1 card(s) · ability/ChangeZone · field target: mv X in graveyard you control creaturemv X+1 in graveyard you control creature

Examples: Sidisi, Regent of the Mire

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Request changes — the selected-sacrifice X binding is annotated with rules that say the opposite.

🔴 Blocker

  • crates/engine/src/game/casting_costs.rs:2304-2309 says the selected creature defines X, then cites CR 107.3a + CR 118.9. The verified text of CR 107.3a applies only when X isn't defined by the spell or ability text and the controller announces it; CR 118.9 governs alternative costs for spells, not this activated-ability cost. Sidisi's sacrifice selection defines X, so this needs CR 107.3c; if documenting the activation sequence too, CR 602.2b is the applicable bridge to casting-cost steps. Please correct the annotation without changing the selected-object binding behavior.
  • crates/engine/src/parser/oracle_nom/quantity.rs:571-576 repeats the unsupported CR 107.3a attribution (and CR 107.1b, which is about negative numbers) for generic X plus N / X minus N grammar. Remove that incorrect CR claim, or cite only a verified rule that the code actually implements; the runtime X-definition annotation belongs at the binding seam above.

✅ Clean

The current parse-diff artifact now accounts for exactly one intended Sidisi target-filter change, and the required Rust checks are green on this head.

Recommendation: correct the two annotations on this head, keeping the verified runtime behavior intact, then request re-review.

CR 107.3a (X announced by controller) and CR 118.9 (spell alternative
costs) don't apply to the activated-ability sacrifice-cost seam where
Sidisi's mana value X is defined by the ability's own text rather than
announced — that's CR 107.3c, bridged to activation costs via CR
602.2b. Also drops the incorrect CR 107.1b/107.3a claim from the
generic "X plus N"/"X minus N" quantity-grammar combinator, which
implements no X-definition rule itself.
@matthewevans matthewevans self-assigned this Jul 16, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Request changes — this implements Sidisi with an impossible activation order.

🔴 Blocker

  • crates/engine/src/game/cost_payability.rs:187-193 and crates/engine/src/game/casting.rs:14963-14968 call this a text-defined X and relax the mana-value-X cost filter. Sidisi’s Oracle text has an {X}{B} activation cost; it does not define X from the sacrificed creature. The locally verified CR 107.3a therefore requires the controller to choose and announce X. CR 107.3c is inapplicable. Do not make the sacrifice pick bind chosen_x.
  • crates/engine/src/game/casting_costs.rs:2304-2312 then binds X when the sacrifice is paid. CR 602.2b applies the 601.2b–i sequence to activation: announce X under 601.2b, choose the return target under 601.2c, then pay costs. A sacrifice cannot retrospectively determine a target’s legality.
  • crates/engine/tests/integration/issue_5957_sidisi_regent_of_the_mire.rs:99-132 encodes the same reversed sequence by selecting the sacrifice before TargetSelection. It would accept the rules error above.

✅ Clean

The current parse-diff is limited to Sidisi’s intended X + 1 target-filter change, and the parser arithmetic portion is independently scoped.

Recommendation: model announced X before target selection and cost payment, with a test that exercises that order; otherwise remove the unsupported Sidisi activation support.

@matthewevans matthewevans removed their assignment Jul 16, 2026
@nghetienhiep

Copy link
Copy Markdown
Contributor Author

Re-verified the two blockers in the latest review against Scryfall — both rest on a premise that doesn't match the printed card.

Sidisi, Regent of the Mire's actual Oracle text (Scryfall, TDM #92, confirmed just now):

{T}, Sacrifice a creature you control with mana value X other than Sidisi: Return target creature card with mana value X plus 1 from your graveyard to the battlefield. Activate only as a sorcery.

There is no {X}{B} component anywhere in this ability's activation cost — the cost is exactly {T} + the sacrifice. X only ever appears as the mana value of the sacrificed creature, never as a mana symbol the controller pays or announces independently.

That puts this squarely under CR 107.3c ("If ... the value of X is defined by the text of that spell or ability, then that's the value of X ... The controller ... doesn't get to choose the value"), not CR 107.3a (which only governs X's the controller announces). So chosen_x being bound from the sacrificed permanent's own mana value — rather than announced up front — is the rules-correct behavior, not a bug. The CR annotations already reflect this (CR 107.3c + CR 602.2b at casting_costs.rs:2304 and cost_payability.rs), and that correction landed in the prior round.

On sequencing: CR 601.2b (bridged to activated abilities via CR 602.2b) directly addresses this exact shape —

If the spell has a variable cost ... If the value of that variable is defined in the text of the spell by a choice that player would make later in the announcement or resolution of the spell, that player makes that choice at this time instead of that later time.

That is, the rules themselves pull the X-defining choice (which creature to sacrifice) forward to before target selection (601.2c), precisely to avoid the "sacrifice retroactively determines target legality" problem raised in the review. So the engine presenting the sacrifice PayCost prompt before TargetSelection isn't a reversed/impossible order — it's what CR 601.2b requires for a cost that defines X via a later choice. The integration test doesn't hardcode this order either; it reacts to whichever WaitingFor state the engine actually produces.

Given both blockers are built on a cost shape ({X}{B}) the card doesn't have, and the actual card's structure is the one CR 601.2b explicitly covers, I don't think there's a correctness issue to fix here — the current implementation matches the printed card and the cited rules. Happy to be pointed to a different reading, but wanted to flag the premise mismatch before making a change that would move the engine away from the real card's behavior.

@matthewevans

Copy link
Copy Markdown
Member

Correction to my prior review: I am withdrawing only the announced-X / target-before-cost finding from the latest requested-changes review.

I incorrectly treated this as a {X}{B} activation. The current head remains 8b142db491dfc0639ded512c0da7e9ca10bb3b93, and the exact Oracle text for Sidisi, Regent of the Mire begins: {T}, Sacrifice a creature you control with mana value X other than Sidisi:. Here X is defined by the selected sacrifice, so that earlier finding does not apply.

An implementation re-review is underway. This correction does not approve or enqueue the PR, and it withdraws no other finding.

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Request changes — the parser change still has no discriminating test.

🔴 Blocker

  • [MED] The runtime regression at crates/engine/tests/integration/issue_5957_sidisi_regent_of_the_mire.rs:32-40 loads prebuilt QuantityExpr::Offset card data, so it remains green if the new parser arm at crates/engine/src/parser/oracle_nom/quantity.rs:561-603 is reverted. Evidence: the fixture is deserialized by CardDatabase::from_export, rather than parsing the Oracle text; the rest of the test only exercises the already-built ability. Why it matters: the claimed mana value X plus 1 grammar can regress silently while this test continues to validate only runtime resolution. Suggested fix: add parser-level assertions that fresh Oracle text yields QuantityExpr::Offset { inner: Variable("X"), offset: 1 } for mana value X plus 1, plus the corresponding minus threshold form (and retain the existing runtime Sidisi test).

✅ Clean

The current head's text-defined X behavior is not part of this finding. The required checks are green, and the parse-diff is limited to Sidisi's intended target-filter change.

Recommendation: add the discriminating parser coverage on this head, then request re-review.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sidisi, Regent of the Mire — After checking with the Alt key and not seeing anything about being unsupported I thought…

2 participants