fix(engine): let Waterbend cost affordability check consider tap-to-help#6097
fix(engine): let Waterbend cost affordability check consider tap-to-help#6097tryeverything24 wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request resolves issue #4966 by correcting the affordability pre-check for the AbilityCost::Waterbend activated ability. Instead of using the standard auto-tap check which only considers mana-producing sources, it now utilizes can_feasibly_pay_mana_cost_with_tap_payment_mode to correctly account for the tap-to-help mechanic of Waterbend. Comprehensive integration tests have been added to verify this behavior under both pool-funded and tap-to-help payment scenarios. No review comments were provided, and the changes conform to the repository's architectural guidelines, so there is no additional 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.
matthewevans
left a comment
There was a problem hiding this comment.
Request changes — the activation-path regression is strong, but required current-head parse-diff evidence is absent.
🔴 Blocker
crates/engine/src/game/cost_payability.rs:637 changes engine source, but this PR has no <!-- coverage-parse-diff --> sticky comment for head d2cdc6b499e8c919109bf1360b90761e86491566. The Card data job completed successfully at 13:40 UTC, so a current card-level artifact is required to establish that the payability helper change did not alter parser/card coverage unexpectedly. The review protocol requires this evidence for engine/parser-surface PRs.
Regenerate or restore the current-head parse-diff artifact, then request re-review. No source rewrite is requested by this finding.
✅ Clean
The change uses the existing can_feasibly_pay_mana_cost_with_tap_payment_mode(..., ConvokeMode::Waterbend) authority, and issue_4966_waterbenders_ascension.rs drives the real activation, tap-to-help payment, target selection, resolution, and combat-legality path. CR 701.67a and CR 602.2b verify that a Waterbend activation may substitute controlled artifacts/creatures for its generic mana.
Recommendation: supply the current-head parse-diff, then request re-review.
matthewevans
left a comment
There was a problem hiding this comment.
Changes requested — the Waterbend helper path is correct to inspect, but this pre-check uses the wrong payment context for an activated ability.
🔴 Blocker
[MED] Waterbend activated-ability affordability is evaluated as a spell payment. Evidence: crates/engine/src/game/cost_payability.rs:637-644 calls can_feasibly_pay_mana_cost_with_tap_payment_mode, which constructs PaymentContext::Spell in crates/engine/src/game/casting.rs:13040-13074; the actual activated-ability payment builds PaymentContext::Activation in crates/engine/src/game/casting.rs:13700-13715 and 13833-13845. Why it matters: spell-only and activation-only mana restrictions can make the legal-action gate disagree with the later payment path, respectively offering an activation that cannot be paid or suppressing one that can. Suggested fix: make the tap-payment feasibility path accept an activation context (including the ability tag), or add an activation-specific sibling and thread the required source/type/tag data from the ability gate.
Please add discriminating runtime regressions for both an activation-only mana source that must make Waterbend available and a spell-only mana source that must not. The existing zero-floating-mana TapForConvoke scenario is useful coverage for the reported helper-counting bug, but it does not exercise this restriction-context boundary.
✅ Clean
The TapForConvoke test is registered and drives the real activation, payment, targeting, and resolution sequence. The review above does not challenge that coverage; it identifies a sibling payment class that the shared affordability gate also owns.
Recommendation: request changes, then re-review the new head before approval.
Parse changes introduced by this PR✓ No card-parse changes detected. |
d2cdc6b to
28ead6e
Compare
|
Addressed the payment-context blocker. The feasibility helper is now split into two context wrappers over a shared core: the existing spell-context entry point (kept for the spell-cast "additional cost: you may waterbend N" path) and a new Added both requested discriminating runtime regressions: (a) activation-only restricted mana ( This push also regenerates the current-head parse-diff artifact that the first review noted was missing. Verified locally in an isolated target dir: fmt clean, workspace clippy |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughChangesWaterbend activation affordability
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Player
participant AbilityCost
participant ActivationFeasibility
participant ManaPayment
participant WaterbenderAscension
Player->>AbilityCost: activate Waterbend ability
AbilityCost->>ActivationFeasibility: check tagged activation affordability
ActivationFeasibility->>ManaPayment: probe pool and tap-to-help payment
ManaPayment-->>ActivationFeasibility: return payment feasibility
ActivationFeasibility-->>AbilityCost: accept or reject activation
AbilityCost->>WaterbenderAscension: resolve targeted ability
WaterbenderAscension-->>Player: targeted creature cannot be blocked
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/engine/src/game/casting.rs (1)
13745-13796: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMinor duplication between the spell- and activation-mode wrappers.
can_feasibly_pay_activation_mana_cost_with_tap_payment_mode(13759-13796) andcan_feasibly_pay_mana_cost_with_tap_payment_mode(13712-13743) share an identical skeleton — X-cost recursion,state.clone()+flush_layers, then delegate to the shared core — differing only in how thePaymentContextis built. Since context construction differs by kind (spell metadata vs. activation source types + tag), consider extracting the shared skeleton into a private helper that takes a context-builder closure, leaving only the two context constructions distinct.🤖 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/casting.rs` around lines 13745 - 13796, Extract the duplicated X-cost recursion, cloned-state layer flushing, and shared payment delegation from can_feasibly_pay_activation_mana_cost_with_tap_payment_mode and can_feasibly_pay_mana_cost_with_tap_payment_mode into a private helper accepting a context-builder closure. Keep spell context construction and activation_source_types-based PaymentContext::Activation construction in their respective wrappers, and have both wrappers delegate through the shared helper without changing behavior.
🤖 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/casting.rs`:
- Around line 13745-13796: Extract the duplicated X-cost recursion, cloned-state
layer flushing, and shared payment delegation from
can_feasibly_pay_activation_mana_cost_with_tap_payment_mode and
can_feasibly_pay_mana_cost_with_tap_payment_mode into a private helper accepting
a context-builder closure. Keep spell context construction and
activation_source_types-based PaymentContext::Activation construction in their
respective wrappers, and have both wrappers delegate through the shared helper
without changing behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a9346c2-e1f8-4f08-b810-dbc1726ef005
📒 Files selected for processing (4)
crates/engine/src/game/casting.rscrates/engine/src/game/cost_payability.rscrates/engine/tests/integration/issue_4966_waterbenders_ascension.rscrates/engine/tests/integration/main.rs
matthewevans
left a comment
There was a problem hiding this comment.
Blocked — Waterbend’s early affordability check loses the activation tag.
🔴 Blocker
crates/engine/src/game/cost_payability.rs:653-660 always passes None for ability_tag when evaluating AbilityCost::Waterbend, although the actual activation gate receives ability_def.ability_tag in crates/engine/src/game/casting.rs:16718-16724. OnlyForTaggedActivation mana is consequently excluded by the early affordance check even though PaymentContext::Activation would allow it during payment.
Thread the ability tag through the activation-payability route and add a PowerUp-tagged Waterbend regression. The added tests cover generic activation and spell contexts, but not the tag-scoped sibling.
✅ Clean
The prior parse-artifact blocker is resolved: the current sticky reports no card-parse changes.
Recommendation: request changes for tag propagation and the tagged-Waterbend regression.
Fixes phase-rs#4966. Waterbender Ascension: "Waterbend {4}: Target creature can't be blocked this turn." ## Root cause & fix AbilityCost::Waterbend's is_payable affordability pre-check (consulted by casting.rs before an activated ability is even offered as a legal action) delegated to can_pay_cost_after_auto_tap, which only considers real mana-producing sources (lands, mana rocks) and has no notion of Waterbend's own tap-artifacts-or-creatures-to-help mechanic (CR 601.2b, the entire point of the keyword). A player with zero floating/land mana for the generic leg but plenty of untapped eligible creatures to tap was therefore told the ability wasn't payable at all -- the ability could never be activated, matching the reported "remains blockable" symptom. Fixed by delegating to can_feasibly_pay_mana_cost_with_tap_payment_mode (the existing, already-correct helper used by the spell-cast "additional cost: you may waterbend N" path), which falls back to the plain auto-tap check first, so pool/land-funded payment is unaffected. ## Testing Two new end-to-end tests: one pays the Waterbend {4} cost from a pre-funded mana pool (baseline), one pays it via real TapForConvoke tap-to-help with zero floating mana (the actual previously-broken path). Both confirm the targeted creature becomes unblockable. Verified locally: fmt clean, clippy clean (-D warnings), 3261 integration tests pass (3259 pre-existing + 2 new), 0 regressions.
…n payment context Review follow-up (phase-rs#4966): the affordability pre-check delegated to can_feasibly_pay_mana_cost_with_tap_payment_mode, which constructs PaymentContext::Spell — but the actual activated-ability payment builds PaymentContext::Activation, so spell-only vs activation-only mana restrictions made the legal-action gate disagree with the payment step in both directions (offering an unpayable activation on spell-only mana; suppressing a payable one on activation-only mana). Refactored the helper into two context wrappers over a shared feasibility core: the existing spell-context entry point (still used by the spell-cast 'additional cost: you may waterbend N' path) and a new can_feasibly_pay_activation_mana_cost_with_tap_payment_mode that builds PaymentContext::Activation from the source permanent's current types (mirroring the real payment path) and threads an ability_tag parameter for tag-scoped restrictions. The Waterbend is_payable arm now uses the activation sibling with tag None (a Waterbend-costed printed ability is not a tagged keyword ability; the exact tag-scoped gate runs at payment time per CR 601.2g, matching the documented preview behavior of can_pay_ability_cost_after_auto_tap). Added both requested discriminating runtime regressions: activation-only restricted mana (ManaRestriction::OnlyForActivation) as the sole funding must make the activation available and resolve to the unblockable effect; spell-only mana (OnlyForSpell) as the sole funding must reject ActivateAbility at the gate ('Cannot pay activation cost'). Each fails under the previous spell-context probe. Verified in an isolated CARGO_TARGET_DIR: cargo fmt --all --check clean; cargo clippy --workspace --exclude phase-tauri --all-targets --features engine/proptest -D warnings clean; engine lib 17515 passed; engine integration 3752 passed.
ff2651d to
7140e03
Compare
|
Addressed the blocking finding: the activation tag is now threaded through the affordability route, with a PowerUp-tagged Waterbend regression. Root of the blocker Fix (tag threaded, no duplicated traversal)
Regression (the tag-scoped sibling you asked for)
Full local parity (isolated CARGO_TARGET_DIR, rebased onto
|
|
Blocked — the activation tag now reaches one of the two activation affordability gates; the legal-action gate still drops it. The previously-blocking finding is genuinely addressed on the submit path, and the shape of the fix is right (single parameterized body, thin 🔴 Blocker[MED] Evidence — match scope {
PaymentScope::Activation { .. } => {
if !cost.is_payable(state, payer, source_id) {
return false;
}The pub(crate) enum PaymentScope<'a> {
Activation {
excluded_sources: &'a HashSet<ObjectId>,
/// CR 106.6: Keyword tag of the activated ability whose cost is being
/// paid. Threaded into `PaymentContext::Activation` so tag-scoped mana
/// spend restrictions (Quinjet → power-up) gate eligible mana. …
ability_tag: Option<crate::types::ability::AbilityTag>,
},The two activation gates therefore still disagree:
For a bare Waterbend cost, ManaRestriction::OnlyForTaggedActivation(required_tag) => {
ability_tag == Some(*required_tag)
}So the exact scenario your new unit test pins — tag-scoped mana as the sole funding — still yields "not activatable" at the offer gate: The same asymmetry falsifies the invariant your new doc comment asserts at Fix — one line, plus a discriminator: PaymentScope::Activation { ability_tag, .. } => {
if !cost.is_payable_for_activation(state, payer, source_id, *ability_tag) {
return false;
}[MED] The tag regression is unit-scoped, which is why the gap above survived. Evidence: 🟡 Non-blocking[LOW] The test module header states the Oracle text is "verified against ✅ Clean
Recommendation: thread |
Fixes #4966.
Waterbender Ascension: "Waterbend {4}: Target creature can't be blocked this turn."
Root cause & fix
AbilityCost::Waterbend'sis_payableaffordability pre-check (consulted bycasting.rsbefore an activated ability is even offered as a legal action) delegated tocan_pay_cost_after_auto_tap, which only considers real mana-producing sources (lands, mana rocks) and has no notion of Waterbend's own tap-artifacts-or-creatures-to-help mechanic (CR 601.2b, the entire point of the keyword). A player with zero floating/land mana for the generic leg but plenty of untapped eligible creatures to tap was therefore told the ability wasn't payable at all — the ability could never be activated, matching the reported "remains blockable" symptom.Fixed by delegating to
can_feasibly_pay_mana_cost_with_tap_payment_mode(the existing, already-correct helper used by the spell-cast "additional cost: you may waterbend N" path), which falls back to the plain auto-tap check first, so pool/land-funded payment is unaffected.Testing
Two new end-to-end tests: one pays the Waterbend {4} cost from a pre-funded mana pool (baseline), one pays it via real
TapForConvoketap-to-help with zero floating mana (the actual previously-broken path). Both confirm the targeted creature becomes unblockable.Verified locally: fmt clean, clippy clean (
-D warnings), 3261 integration tests pass (3259 pre-existing + 2 new), 0 regressions.Summary by CodeRabbit
Bug Fixes
Tests