Skip to content

fix(engine): auto-tap self-sacrifice mana abilities like Gold tokens#6230

Merged
matthewevans merged 9 commits into
phase-rs:mainfrom
bitfathers94:fix/issue-6157
Jul 23, 2026
Merged

fix(engine): auto-tap self-sacrifice mana abilities like Gold tokens#6230
matthewevans merged 9 commits into
phase-rs:mainfrom
bitfathers94:fix/issue-6157

Conversation

@bitfathers94

@bitfathers94 bitfathers94 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

fix(engine): auto-tap self-sacrifice mana abilities like Gold tokens

Auto-tap source discovery required every scanned mana ability to have a
{T} cost component, so Gold's "Sacrifice this token: Add one mana of any
color." (no {T}) was invisible to CastPaymentMode::Auto even though the
sacrifice targets only the token itself and needs no player choice —
exactly as deterministic as a {T} cost. Treasure worked only because its
cost happens to also include {T}.

Extend the single-authority eligibility check to also accept an
unambiguous self-sacrifice cost (Sacrifice targeting SelfRef, count 1),
and exclude that same shape from the "needs manual payment" fallback path
so it isn't double-counted. Ambiguous sacrifice costs (Krark-Clan
Ironworks' "Sacrifice an artifact") still require a manual choice and are
unaffected.

Closes #6157

Summary by CodeRabbit

  • Bug Fixes

    • Improved automatic mana payment for Gold and Treasure when their mana abilities use choice-free self-sacrifice costs, including tapless variants.
    • Automatic selection now preserves eligible mana sources even when tapped or summoning-sick, while continuing to enforce normal tap gating for other abilities.
    • Refined eligibility to avoid auto-using composite mana abilities that include player-choice elements (LED-style), preventing incorrect auto-tap/auto-pay behavior.
  • Tests

    • Added integration coverage for auto-payment success with Gold and Treasure, including tapped/summoning-sick eligibility.
    • Added regression coverage confirming choice-containing composite abilities are not auto-selected.

@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 #6157 by enabling automatic mana payment to utilize Gold tokens, which feature a self-sacrifice cost without a tap component. It introduces a helper to identify unambiguous self-sacrifice costs so they can be processed on the auto-tap path. The review feedback highlights a correctness bug where tapped or summoning-sick Gold tokens might be incorrectly excluded from auto-tap due to the order of checks in is_active_tap_mana_ability. Additionally, the feedback points out incorrect MTG Comprehensive Rules citations (referencing CR 701.21 instead of CR 701.16 for Sacrifice) in both casting_costs.rs and mana_sources.rs.

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 on lines +1782 to 1785
if !has_tap_component(&ability.cost) && !has_unambiguous_self_sacrifice_component(&ability.cost)
{
return false;
}

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.

high

[HIGH] Correctness bug with tapped/summoning-sick Gold tokens. Evidence: crates/engine/src/game/mana_sources.rs:1782.

Why it matters: If is_active_tap_mana_ability performs obj.tapped or summoning sickness checks before verifying if the ability actually has a {T} component, then tapped or summoning-sick Gold tokens (especially when animated) will be incorrectly excluded from auto-tap, violating CR 302.6 and CR 701.16.

Suggested fix: Restructure the function to check has_tap_component and has_unambiguous_self_sacrifice_component first, and only apply the tapped and summoning sickness checks if the ability actually has a {T} component.

References
  1. Rule R6: Strict fidelity to the MTG Comprehensive Rules (CR) — every game rule, validation, and computed value matches the CR exactly. (link)

Comment thread crates/engine/src/game/mana_sources.rs Outdated
cost_has_component(cost, |c| matches!(c, AbilityCost::Untap))
}

/// CR 605.3a + CR 701.21: True when the cost has a `Sacrifice` component that

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 annotation citation. Evidence: crates/engine/src/game/mana_sources.rs:273.

Why it matters: The MTG Comprehensive Rules section for Sacrifice is CR 701.16, whereas CR 701.21 refers to Meld, violating the requirement for verified CR annotations (Rule R6).

Suggested fix: Change CR 701.21 to CR 701.16.

Suggested change
/// CR 605.3a + CR 701.21: True when the cost has a `Sacrifice` component that
/// CR 605.3a + CR 701.16: True when the cost has a Sacrifice component that
References
  1. Rule R6: 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)

.cost(AbilityCost::Tap),
);
}
// CR 605.3a + CR 701.21: an ambiguous sacrifice cost (choosing among

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 annotation citation. Evidence: crates/engine/src/game/casting_costs.rs:13218.

Why it matters: The MTG Comprehensive Rules section for Sacrifice is CR 701.16, whereas CR 701.21 refers to Meld, violating the requirement for verified CR annotations (Rule R6).

Suggested fix: Change CR 701.21 to CR 701.16.

Suggested change
// CR 605.3a + CR 701.21: an ambiguous sacrifice cost (choosing among
// CR 605.3a + CR 701.16: an ambiguous sacrifice cost (choosing among
References
  1. Rule R6: 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)

@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.

The new self-sacrifice predicate is never reached for a tapped source. auto_tap_mana_options still returns early on obj.tapped before scan_mana_abilities can call is_active_tap_mana_ability; that early gate is only valid for a {T} cost. A tapped Gold token can legally pay “Sacrifice this token: Add one mana of any color”, but the proposed auto-payment path offers no source and the spell remains unpayable. Please make the untapped/summoning-sickness prefilter conditional on the selected ability requiring {T} (or let the existing activation/payability predicate make that decision), and add a tapped-Gold regression test.

@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

bitfathers94 and others added 2 commits July 22, 2026 01:21
Auto-tap source discovery required every scanned mana ability to have a
{T} cost component, so Gold's "Sacrifice this token: Add one mana of any
color." (no {T}) was invisible to CastPaymentMode::Auto even though the
sacrifice targets only the token itself and needs no player choice —
exactly as deterministic as a {T} cost. Treasure worked only because its
cost happens to also include {T}.

Extend the single-authority eligibility check to also accept an
unambiguous self-sacrifice cost (Sacrifice targeting SelfRef, count 1),
and exclude that same shape from the "needs manual payment" fallback path
so it isn't double-counted. Ambiguous sacrifice costs (Krark-Clan
Ironworks' "Sacrifice an artifact") still require a manual choice and are
unaffected.

Closes phase-rs#6157
Address PR review on phase-rs#6230: the self-sacrifice predicate was never reached
for a tapped source. Both auto-tap source gates (`auto_tap_mana_options` and
`collect_sorted_auto_tap_source_options`) returned early on `obj.tapped` /
summoning-sickness before `scan_mana_abilities` could evaluate the ability,
so a tapped Gold token ("Sacrifice this token: Add one mana of any color.",
no `{T}`) offered no source and the spell stayed unpayable.

Make the object-level tapped/summoning-sick prefilter conditional on the
source requiring `{T}`: a source is only dropped up front when it lacks a
*tapless* unambiguous self-sacrifice mana ability (CR 106.12 / CR 302.6 gate
`{T}`/`{Q}` costs only). Sources carrying such an ability fall through to
`scan_mana_abilities`, where a cheap per-ability cost/field guard on the
auto-tap path still excludes any `{T}`/`{Q}` ability of a tapped or
summoning-sick source — so no `{T}` source leaks through and no legality
simulation (readiness call) is added, preserving the bulk-activation
linearity invariant. Treasure (`{T}, Sacrifice this artifact`) is
deliberately excluded from the bypass: once tapped it genuinely cannot
activate.

Add regression test `tapped_gold_token_still_auto_taps_for_self_sacrifice_mana`
proving a tapped, summoning-sick Gold token is still auto-tapped via its
self-sacrifice mana ability. Fails on the pre-fix path, passes after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6868645e-b08c-4c25-8153-80ce5d82008a

📥 Commits

Reviewing files that changed from the base of the PR and between b951f28 and 4656be6.

📒 Files selected for processing (1)
  • crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs

📝 Walkthrough

Walkthrough

Automatic mana payment now recognizes deterministic tapless self-sacrifice abilities, including Gold tokens, while preserving restrictions for tapped or summoning-sick tap abilities and choice-bearing composite costs. Integration tests cover Gold, Treasure, and LED-shaped sources.

Changes

Gold token automatic payment

Layer / File(s) Summary
Tapless self-sacrifice eligibility
crates/engine/src/game/mana_abilities.rs, crates/engine/src/game/mana_sources.rs
Adds shared choice-free cost visibility and detects unambiguous self-sacrifice mana abilities while excluding composite costs requiring player choices.
Auto-tap and casting integration
crates/engine/src/game/mana_sources.rs, crates/engine/src/game/casting_costs.rs
Preserves eligible tapless self-sacrifice sources during auto-tap filtering, enforces tap and summoning-sickness restrictions, and updates the artifact-sacrifice scenario.
Gold, Treasure, and LED validation
crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs, crates/engine/tests/integration/main.rs
Adds automatic-payment coverage for Gold, Treasure, tapped summoning-sick Gold, and LED-shaped composite costs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant CastingCosts
  participant ManaSources
  participant GoldToken
  Player->>CastingCosts: cast spell with automatic payment
  CastingCosts->>ManaSources: scan payable mana abilities
  ManaSources->>GoldToken: select tapless self-sacrifice ability
  GoldToken-->>CastingCosts: produce mana and sacrifice
  CastingCosts-->>Player: resolve spell and draw a card
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: auto-tapping self-sacrifice mana abilities such as Gold tokens.
Linked Issues check ✅ Passed Gold-token auto-payment, Treasure parity, and regression coverage are implemented, while ambiguous LED-shaped costs stay on the manual path.
Out of Scope Changes check ✅ Passed The visibility and test harness updates support the same mana-source eligibility work and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@bitfathers94

Copy link
Copy Markdown
Contributor Author

Thanks for the precise catch, @matthewevans — you were exactly right that the new predicate was never reached for a tapped source. Fixed and rebased on latest main.

What changed
The untapped/summoning-sickness prefilter is now conditional on the selected ability requiring {T}. There were two object-level early returns dropping a tapped/summoning-sick permanent before scan_mana_abilities could evaluate it:

  • mana_sources.rs::auto_tap_mana_options (the Gold-token path)
  • casting_costs.rs::collect_sorted_auto_tap_source_options (the outer board sweep)

Both now gate the tapped/summoning-sick/can't-tap exclusion behind a new helper object_has_tapless_self_sacrifice_mana_ability (a self-sacrifice mana ability with no {T}/{Q} component, per CR 106.12 / CR 302.6). A tapless self-sac source (Gold) now falls through to the payability predicate; a {T}-gated source (e.g. Treasure's {T}, Sacrifice) is still correctly excluded when tapped. I also added a cheap per-ability guard inside scan_mana_abilities on the auto-tap path so no {T}/{Q} ability of a tapped source leaks through.

Regression test
tapped_gold_token_still_auto_taps_for_self_sacrifice_mana (integration/issue_6157): a Gold token with tapped=true and summoning_sick=true must still auto-pay via self-sacrifice. Revert-verified — it panics (spell unpayable) on the pre-fix code and passes after.

Green locally: cargo build/clippy clean (0 warnings), the 3 issue_6157 integration tests pass, and the full mana lib suite (1241 tests) is green. Ready for another look.

@matthewevans matthewevans added the bug Bug fix label Jul 22, 2026
@matthewevans

Copy link
Copy Markdown
Member

Current-head review completed. The tapped/summoning-sick Gold regression now reaches the per-ability gate, while {T}/{Q} abilities remain gated there; the integration test exercises the reported path. I also verified the Sacrifice citations against Wizards' current Comprehensive Rules, where Sacrifice is CR 701.21, so the older bot citation finding is not applicable.

No approval or enqueue yet: the current Rust and frontend required checks are still running, and CodeRabbit's current-head review is in progress. I will leave this held for those external gates rather than override them.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
crates/engine/src/game/mana_sources.rs (1)

273-316: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Predicate only checks presence of a self-sacrifice component, not exclusivity.

has_unambiguous_self_sacrifice_component / object_has_tapless_self_sacrifice_mana_ability treat a cost as "unambiguous"/deterministic whenever a Sacrifice(SelfRef, count:1) component exists anywhere in the cost — via cost_has_component's costs.iter().any(&pred) — without checking that every other component of a Composite cost is also choice-free (e.g. Tap). A hypothetical future mana ability shaped Composite{ Sacrifice(SelfRef,1), Discard{ any card } } (or Sacrifice(SelfRef,1) alongside a second, ambiguous Sacrifice target) would be classified as deterministic by all three call sites (object_has_tapless_self_sacrifice_mana_ability, has_activatable_non_tap_mana_ability_for_payment, is_active_tap_mana_ability) and pushed onto the auto-tap fast path even though it still requires a player choice (which card to discard / which permanent to sacrifice).

Today's two shipped shapes (Gold: bare Sacrifice(SelfRef,1); Treasure: Composite{Tap, Sacrifice(SelfRef,1)}) are unaffected, but per the "build reusable building blocks for a class of cards" guideline this predicate should assert that the self-sacrifice component is the only non-Tap component before treating the whole cost as choice-free.

♻️ Proposed tightening
 pub(crate) fn has_unambiguous_self_sacrifice_component(cost: &Option<AbilityCost>) -> bool {
-    cost_has_component(cost, |c| {
-        matches!(
-            c,
-            AbilityCost::Sacrifice(SacrificeCost {
-                target: TargetFilter::SelfRef,
-                requirement: SacrificeRequirement::Count { count: 1 },
-            })
-        )
-    })
+    match cost {
+        Some(AbilityCost::Composite { costs }) => {
+            costs.iter().any(|c| is_self_sacrifice(c))
+                && costs
+                    .iter()
+                    .all(|c| matches!(c, AbilityCost::Tap) || is_self_sacrifice(c))
+        }
+        Some(c) => is_self_sacrifice(c),
+        None => false,
+    }
 }

As per path instructions, "Build reusable card-class building blocks rather than one-card special cases... new enum variants, parsers, effects, and filters must support a category of cards."

Also applies to: 1276-1282, 1820-1825

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/engine/src/game/mana_sources.rs` around lines 273 - 316, Update
has_unambiguous_self_sacrifice_component to validate the entire AbilityCost
structure, not merely detect a matching component: require exactly one
Sacrifice(SelfRef, count: 1) and allow only choice-free Tap alongside it,
rejecting any additional discard or sacrifice components. Preserve the existing
Gold and Treasure cost shapes, so object_has_tapless_self_sacrifice_mana_ability
and all callers classify only genuinely deterministic costs.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/engine/src/game/mana_sources.rs`:
- Around line 273-316: Update has_unambiguous_self_sacrifice_component to
validate the entire AbilityCost structure, not merely detect a matching
component: require exactly one Sacrifice(SelfRef, count: 1) and allow only
choice-free Tap alongside it, rejecting any additional discard or sacrifice
components. Preserve the existing Gold and Treasure cost shapes, so
object_has_tapless_self_sacrifice_mana_ability and all callers classify only
genuinely deterministic costs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 07198f57-8863-42c4-b55c-48807f401ecb

📥 Commits

Reviewing files that changed from the base of the PR and between 23824af and 249a857.

📒 Files selected for processing (4)
  • crates/engine/src/game/casting_costs.rs
  • crates/engine/src/game/mana_sources.rs
  • crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs
  • crates/engine/tests/integration/main.rs

@matthewevans matthewevans self-assigned this Jul 22, 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.

Blocking: the auto-tap eligibility predicate currently accepts a cost that still requires a player choice.

🔴 Blocker

crates/engine/src/game/mana_sources.rs:282-291 uses cost_has_component, whose Composite implementation is costs.iter().any(...). Consequently, a mana ability with Composite[Discard, Sacrifice(SelfRef, 1)] is classified as an “unambiguous” self-sacrifice merely because one component matches. That classification is consumed both by the manual-payment exclusion at mana_sources.rs:1276-1283 and by the auto-tap eligibility gate at mana_sources.rs:1820-1824, so the ability can be selected automatically even though it still needs a discard choice.

This is a real existing cost shape, not a hypothetical: the Lion's Eye Diamond activation test builds exactly Composite[Discard { selection: Chosen }, Sacrifice(SelfRef, 1)] at crates/engine/src/game/mana_abilities.rs:6209-6223; the activation pipeline then exposes WaitingFor::PayCost { kind: Discard } at mana_abilities.rs:1450-1468 and asserts that prompt at 6240-6256. Auto-tap must not bypass that prompt.

The established authority already models the needed invariant: mana_abilities.rs:3027-3041 is a deny-by-default full-tree whitelist, allowing only Tap, Sacrifice(SelfRef, 1), and composites whose every component is choice-free. Please make the auto-tap predicate share/extract that full-cost authority (or apply the same full-tree invariant), then add a regression proving the LED-shaped composite remains on the manual-payment path while bare Gold and {T} + self-sacrifice Treasure remain eligible.

✅ Clean

The current head fixes the prior tapped/summoning-sick Gold reachability finding; that issue is independent of this composite-cost blocker.

Recommendation: request changes. Tighten eligibility to the full, choice-free cost tree and cover the existing LED shape before approval.

@matthewevans matthewevans removed their assignment Jul 22, 2026
bitfathers94 and others added 2 commits July 22, 2026 07:14
… eligibility (LED composite)

has_unambiguous_self_sacrifice_component matched a self-sacrifice via
cost_has_component (Composite = any), so Lion's Eye Diamond's
Composite[Discard{Chosen}, Sacrifice(SelfRef,1)] was misclassified as
unambiguous self-sacrifice. That let the auto-tap eligibility gate
(mana_sources.rs:1820) and the manual-payment exclusion
(mana_sources.rs:1281) auto-select LED, bypassing its Discard prompt.

Reuse the existing deny-by-default full-tree authority
mana_abilities::cost_component_choice_free (Tap + single-token
self-sacrifice + Composites built solely from those) by making it
pub(crate) and sharing it: the predicate now requires the whole cost
tree be choice-free AND actually contain a self-sacrifice component.
Gold (bare self-sac) and Treasure ({T}+self-sac) stay auto-tap eligible;
LED-shaped composites are rejected and keep their discard prompt.

Add regression led_shaped_discard_sacrifice_stays_off_auto_tap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ent path

Drive CastPaymentMode::Auto with a LED-shaped mana source
(Composite[Discard{Chosen}, Sacrifice(SelfRef,1)]) alongside a Gold
token: auto-tap must skip the choice-bearing LED and pay the generic-1
spell with Gold, leaving LED on the battlefield (manual-payment path),
while a LED-only board is not auto-payable. Complements the predicate
unit test with a behavioral regression; fails on the pre-fix any-match
predicate (spell becomes unpayable) and passes with the whole-tree
choice-free check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bitfathers94

Copy link
Copy Markdown
Contributor Author

Good catch, @matthewevans — you're right that cost_has_component's any-over-Composite let a partial match through, so an LED-shaped Composite[Discard{Chosen}, Sacrifice(SelfRef,1)] was misclassified as deterministic. Fixed by reusing the authority you pointed to rather than adding a special case.

What changed

  • mana_abilities.rs: exposed cost_component_choice_free as pub(crate) (the existing deny-by-default full-tree whitelist: only Tap, Sacrifice(SelfRef,1), and Composites whose every component is choice-free).
  • mana_sources.rs: has_unambiguous_self_sacrifice_component now requires both (1) the whole cost tree passes that shared cost_component_choice_free authority, and (2) a Sacrifice(SelfRef,1) component is actually present. Both consumers — the manual-payment exclusion (:1276-1283) and the auto-tap eligibility gate (:1820-1824) — go through this one predicate, so they're fixed together.

Result: LED (Composite[Discard, Sacrifice]) is rejected by the full-tree whitelist → stays on the manual-payment path and keeps its WaitingFor::PayCost{ Discard } prompt. Bare Gold (Sacrifice(SelfRef,1)) and Treasure (Composite{Tap, Sacrifice(SelfRef,1)}) still pass → remain auto-tap eligible.

Regression (end-to-end)
led_shaped_discard_sacrifice_not_auto_tapped_stays_manual: board holds an LED-shaped source and a Gold token; auto-resolving a {1} spell pays with Gold, drives to WaitingFor::Priority (no bypassed prompt), and LED remains on the battlefield. Revert-verified — reverting the predicate to the any-match makes LED contaminate the auto-tap plan and the payment fails, so the test genuinely pins the invariant. (Kept the predicate-level unit test too.)

Green locally: cargo build/clippy clean (0 warnings under -D warnings), all 4 issue_6157 integration tests pass, full mana lib suite 1242 green. Ready for another look.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs`:
- Around line 255-268: The sanity-floor assertion around
runner.cast(spell).try_resolve() must verify that resolution pauses in the
expected ManaPayment state rather than asserting an error. Replace the is_err()
check with final_waiting_for() or an equivalent positive reach-guard, preserving
the scenario’s intended outcome that the spell remains unresolved because mana
payment requires handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 530ae4bb-7f71-4eae-803b-8adfa8a430e9

📥 Commits

Reviewing files that changed from the base of the PR and between 249a857 and f3c6e89.

📒 Files selected for processing (3)
  • crates/engine/src/game/mana_abilities.rs
  • crates/engine/src/game/mana_sources.rs
  • crates/engine/tests/integration/issue_6157_gold_token_auto_mana_payment.rs

@matthewevans matthewevans self-assigned this Jul 22, 2026
@matthewevans matthewevans removed their assignment Jul 22, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer fixup 3ef7451 replaces the LED-only test's vacuous try_resolve().is_err() assertion with a positive WaitingFor::ManaPayment reach guard and assertions that the pending cast and LED source are preserved.

The current head is held pending its required CI run and CodeRabbit review. No approval or merge-queue enrollment has been performed.

@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.

Approved on current head 4656be6: the LED-only regression now drives the real CastSpell action and asserts the manual ManaPayment boundary; Gold/Treasure/tapped-Gold and LED cases remain covered. Current CodeRabbit completed successfully; auto-merge will wait for the remaining required checks.

@matthewevans
matthewevans enabled auto-merge July 23, 2026 12:42
@matthewevans matthewevans removed their assignment Jul 23, 2026
@matthewevans
matthewevans added this pull request to the merge queue Jul 23, 2026
Merged via the queue into phase-rs:main with commit c754a08 Jul 23, 2026
15 checks passed
jsdevninja pushed a commit to jsdevninja/phase that referenced this pull request Jul 24, 2026
…hase-rs#6230)

* fix(engine): auto-tap self-sacrifice mana abilities like Gold tokens

Auto-tap source discovery required every scanned mana ability to have a
{T} cost component, so Gold's "Sacrifice this token: Add one mana of any
color." (no {T}) was invisible to CastPaymentMode::Auto even though the
sacrifice targets only the token itself and needs no player choice —
exactly as deterministic as a {T} cost. Treasure worked only because its
cost happens to also include {T}.

Extend the single-authority eligibility check to also accept an
unambiguous self-sacrifice cost (Sacrifice targeting SelfRef, count 1),
and exclude that same shape from the "needs manual payment" fallback path
so it isn't double-counted. Ambiguous sacrifice costs (Krark-Clan
Ironworks' "Sacrifice an artifact") still require a manual choice and are
unaffected.

Closes phase-rs#6157

* fix(engine): honor tapped/summoning-sick self-sacrifice mana on auto-tap

Address PR review on phase-rs#6230: the self-sacrifice predicate was never reached
for a tapped source. Both auto-tap source gates (`auto_tap_mana_options` and
`collect_sorted_auto_tap_source_options`) returned early on `obj.tapped` /
summoning-sickness before `scan_mana_abilities` could evaluate the ability,
so a tapped Gold token ("Sacrifice this token: Add one mana of any color.",
no `{T}`) offered no source and the spell stayed unpayable.

Make the object-level tapped/summoning-sick prefilter conditional on the
source requiring `{T}`: a source is only dropped up front when it lacks a
*tapless* unambiguous self-sacrifice mana ability (CR 106.12 / CR 302.6 gate
`{T}`/`{Q}` costs only). Sources carrying such an ability fall through to
`scan_mana_abilities`, where a cheap per-ability cost/field guard on the
auto-tap path still excludes any `{T}`/`{Q}` ability of a tapped or
summoning-sick source — so no `{T}` source leaks through and no legality
simulation (readiness call) is added, preserving the bulk-activation
linearity invariant. Treasure (`{T}, Sacrifice this artifact`) is
deliberately excluded from the bypass: once tapped it genuinely cannot
activate.

Add regression test `tapped_gold_token_still_auto_taps_for_self_sacrifice_mana`
proving a tapped, summoning-sick Gold token is still auto-tapped via its
self-sacrifice mana ability. Fails on the pre-fix path, passes after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(engine): require full choice-free cost tree for self-sac auto-tap eligibility (LED composite)

has_unambiguous_self_sacrifice_component matched a self-sacrifice via
cost_has_component (Composite = any), so Lion's Eye Diamond's
Composite[Discard{Chosen}, Sacrifice(SelfRef,1)] was misclassified as
unambiguous self-sacrifice. That let the auto-tap eligibility gate
(mana_sources.rs:1820) and the manual-payment exclusion
(mana_sources.rs:1281) auto-select LED, bypassing its Discard prompt.

Reuse the existing deny-by-default full-tree authority
mana_abilities::cost_component_choice_free (Tap + single-token
self-sacrifice + Composites built solely from those) by making it
pub(crate) and sharing it: the predicate now requires the whole cost
tree be choice-free AND actually contain a self-sacrifice component.
Gold (bare self-sac) and Treasure ({T}+self-sac) stay auto-tap eligible;
LED-shaped composites are rejected and keep their discard prompt.

Add regression led_shaped_discard_sacrifice_stays_off_auto_tap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(engine): end-to-end LED composite stays off auto-tap manual-payment path

Drive CastPaymentMode::Auto with a LED-shaped mana source
(Composite[Discard{Chosen}, Sacrifice(SelfRef,1)]) alongside a Gold
token: auto-tap must skip the choice-bearing LED and pay the generic-1
spell with Gold, leaving LED on the battlefield (manual-payment path),
while a LED-only board is not auto-payable. Complements the predicate
unit test with a behavioral regression; fails on the pre-fix any-match
predicate (spell becomes unpayable) and passes with the whole-tree
choice-free check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(PR-6230): assert manual payment pause

* test(PR-6230): preserve manual mana payment assertion

* test(PR-6230): fix manual payment assertion

---------

Co-authored-by: bitfathers94 <237535319+bitfathers94@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: matthewevans <matthewevans@users.noreply.github.com>
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.

Gold tokens are not used by automatic mana payment

2 participants