Skip to content

fix(engine): honor the power gate on Goreclaw's spell cost reduction - #6626

Open
michiot05 wants to merge 5 commits into
phase-rs:mainfrom
michiot05:fix/goreclaw-power-gated-cost-reduction-6375
Open

fix(engine): honor the power gate on Goreclaw's spell cost reduction#6626
michiot05 wants to merge 5 commits into
phase-rs:mainfrom
michiot05:fix/goreclaw-power-gated-cost-reduction-6375

Conversation

@michiot05

@michiot05 michiot05 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Tier: Frontier
Model: claude-opus-4-8

Closes #6375.

Summary

Goreclaw, Terror of Qal Sisma"Creature spells you cast with power 4 or greater cost {2} less to cast." — reduced the cost of every spell you cast. It's a two-layer defect (the parser-only fix alone would have flipped it to reducing nothing):

1. Parser — the power gate was dropped. The cost-reduction static parsed with spell_filter: null over affected: Typed{Card}, so it matched all your spells. Fix #5606 added a "[type] spells with mana value N or greater/less" qualifier arm to the cost-modifier parser; there was no "with power N" counterpart, so the power gate fell through. Added a parallel parse_cost_mod_power_qualifier in oracle_static/static_helpers.rs, mirroring the mana-value arm and reusing the canonical parse_pt_comparison combinator (the same building block Goreclaw's own attack trigger already uses — no hand-rolled power parser). Goreclaw now parses spell_filter = Typed{ Creature, [PtComparison{ Power, Current, GE, Fixed(4) }] }.

2. Runtime — the cost-modifier matcher fail-closed on power. Even with a correct spell_filter, the cost path evaluates a candidate spell against a SpellCastRecord snapshot (spell_object_matches_propertyspell_record_matches_property) that carries no power/toughness, so FilterProp::PtComparison sat in the fail-closed => false group → Goreclaw would reduce nothing. Added a PtComparison arm to spell_object_matches_property (game/filter.rs) that reads the live spell object's printed P/T via the filter context (context.spell_object_id → object_pt_value, the same authority the battlefield matcher uses), evaluating the comparator. It is guarded to the context-bearing callers (cost / can't-cast / keyword-grant, which supply Some(spell_object_id)); every snapshot-only / None-context caller returns false exactly as before, so the fail-closed record path is unchanged. No SpellCastRecord/serde change, no new enum variant.

Result: a power-4+ creature spell is reduced; a power-3 creature spell and any noncreature spell are not (a noncreature is rejected by the type gate, and independently its power None → 0 fails GE 4 — CR 208.3). Scope Current is correct for an unresolved spell (= printed power; per Goreclaw's ruling, later +1/+1 counters don't retroactively count).

Implementation method (required)

Method: /engine-implementer — plan (/engine-planner, with a run-then-delete reproduction probe confirming power-3/noncreature spells were wrongly reduced at base) → /review-engine-plan → implement (tests-first). During implementation, driving the real production cost path (not a force-set power) empirically falsified the plan's "parser-only-safe" premise — the cost matcher reads a power-less snapshot — so the executor stopped rather than ship a fix that reduced nothing. A focused design-and-validation pass then pinned the runtime seam (traced the cost path decisively; ruled over-broadening safe by enumerating every caller of the shared matcher) → implement runtime arm → /review-impl (Maintainer-Simulation Gate PASS; two LOW notes, non-gating). Each step in a fresh agent context.

Gate A

./scripts/check-parser-combinators.shGate A PASS head=ddbeec86d (Gate G PASS). The parser peel is take_until + parse_pt_comparison (pure combinators).

Anchored on

  • The [Card Bug] The Scarlet Witch: Everything becomes cheaper to cast instead of only instants and sorceries #5606 mana-value cost-mod qualifier (oracle_static/static_helpers.rs: parse_cost_mod_mana_value_qualifier/strip_…/compose_…) — the exact peel-and-compose shape the power arm mirrors, one axis over (CmcPtComparison). compose_cost_mod_mana_value is renamed compose_cost_mod_qualifier_prop (already FilterProp-generic; one caller) and called for both qualifiers.
  • nom_filter::parse_pt_comparison (oracle_nom/filter.rs) — the canonical P/T-comparison combinator (self-consumes with /base , gives GE/LE/EQ), already used by Goreclaw's attack trigger and oracle_target::parse_power_suffix. No new power parser.
  • object_pt_value (game/filter.rs) — the single authority the live/battlefield matcher already uses to read an object's P/T; the new cost-path arm reuses it against the live spell object, so power evaluation is identical across matchers.

Verification

  • cargo fmt --all clean; cargo clippy -p engine --lib --tests -- -D warnings 0 warnings; cargo test -p engine --lib 17,701 passed / 0 failed (no drift from the shared filter.rs change); cargo test -p engine --test integration -- goreclaw 4 passed; cargo test -p engine --lib -- cost_mod_power 4 passed (parser shape + attack-trigger control).
  • Integration tests (crates/engine/tests/integration/goreclaw_power_gated_cost_reduction_6375.rs) drive the real display_spell_cost path over real card faces via add_real_card (printed power, never force-set), verbatim Goreclaw Oracle text:
    • power-5 creature (Fire Elemental) → reduced (generic 3→1); power-4 boundary (Onakke Ogre) → reduced (2→0); power-3 creature (Hill Giant) → not reduced (stays 3); noncreature (Arc Lightning sorcery) → not reduced (stays 2). Each negative is reach-guarded by a reduced sibling in the same test (non-vacuous).
  • Bidirectionally revert-sensitive: neutralizing the runtime arm re-fails the reduced/boundary tests (Goreclaw reduces nothing); reverting the parser spell_filter to null re-reduces the power-3 spell (the original "reduces all spells" bug, left: 1, right: 3).
  • Parser shape unit tests: GE / LE / bare-EQ power qualifiers, plus the attack-trigger control (still PtComparison{Power,GE,4} on Typed{Creature}, untouched).
  • CR annotations grep-verified against docs/MagicCompRules.txt: 208 / 208.1 / 208.3 (power is a creature characteristic; noncreatures have none), 601.2f (total cost), 202.3.

Claimed parse impact

Parser: among real cards, only Goreclaw's cost-modifier spell_filter changes (from null to the Creature + power-4 gate); the qualifier peel is all-or-nothing, so any subject lacking " with power " is byte-identical. Runtime: only the context-bearing cost / can't-cast / keyword-grant path gains PtComparison evaluation (all previously fail-closed false, so every flip is a bug fix); all snapshot/None-context callers are unchanged, confirmed by the full 17,701-test lib suite. The exact card-data parse-diff is deferred to CI's coverage-parse-diff artifact (local card-data/MTGJSON corpus unavailable) — I expect it to report only Goreclaw's cost-mod signature.

Scope Expansion

None. The runtime arm was necessary to make the parser fix actually correct (otherwise Goreclaw reduces nothing) and is minimal + guarded (no serde/type change). Honest limitation, out of class: a */* CDA creature spell reads power 0 off-battlefield (CR 208.3 / parse_pt maps *→0), so it wouldn't be reduced — a pre-existing off-battlefield CDA limitation, not introduced here and outside Goreclaw's fixed-power class.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed “power” gates to evaluate live spell objects’ effective P/T (including off-battlefield dynamic stats) with safe fail-closed behavior when the referenced spell object can’t be found.
    • Improved Oracle parsing for “with power N or greater/less” ModifyCost qualifiers (GE/LE/EQ) with correct handling order.
    • Prevented unparsed trailing qualifiers from widening the scope of static modifiers.
  • Tests
    • Added/expanded unit and integration tests for power-gated cost reduction (Goreclaw), including boundary, noncreature, dynamic P/T (graveyard-driven), and fixed P/T scenarios.
    • Added a regression test to ensure ModifyCost coverage signatures expose spell-filter differences.

"Creature spells you cast with power 4 or greater cost {2} less to cast"
reduced ALL spells: the cost-reduction static parsed with spell_filter:null
(the "creature ... with power 4+" gate dropped), and even once parsed the
cost-modifier path evaluated a power gate as fail-closed false.

Two layers:
- Parser (oracle_static/static_helpers.rs): add a "with power N or
  greater/less" cost-mod qualifier peel mirroring the mana-value qualifier
  (phase-rs#5606), reusing the canonical parse_pt_comparison combinator. Goreclaw now
  parses spell_filter = Typed{Creature, [PtComparison{Power,Current,GE,4}]}.
- Runtime (game/filter.rs): the cost-modifier matcher evaluates a spell
  against a SpellCastRecord snapshot that carries no power/toughness, so
  FilterProp::PtComparison was fail-closed. Add a PtComparison arm to
  spell_object_matches_property that reads the live spell object's printed P/T
  via the filter context (object_pt_value) — guarded to the context-bearing
  cost/prohibition/keyword-grant callers, so every snapshot-only caller stays
  byte-identical fail-closed. No SpellCastRecord/serde change, no new variant.

Now a power-4+ creature spell is reduced, power-3 and noncreature spells are
not (CR 208.1/208.3, 601.2f). Runtime honors spell_filter end-to-end.

Closes phase-rs#6375

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@michiot05
michiot05 requested a review from matthewevans as a code owner July 25, 2026 08:39
@coderabbitai

coderabbitai Bot commented Jul 25, 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: 5c634764-607d-4d10-a994-bdae36d3909b

📥 Commits

Reviewing files that changed from the base of the PR and between 18d7eb8 and 5de7550.

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

📝 Walkthrough

Walkthrough

Cost-modification parsing recognizes power-qualified creature spells, and live spell-object filters evaluate power comparisons using effective current P/T, including dynamic CDAs. Coverage signatures and unit/integration tests validate parsing, filtering, and Goreclaw cost reductions.

Changes

Power-gated cost filtering

Layer / File(s) Summary
Parse and compose power qualifiers
crates/engine/src/parser/oracle_static/static_helpers.rs, crates/engine/src/parser/oracle_static/tests.rs
Cost-modification parsing extracts trailing power qualifiers and combines them with mana-value and type restrictions. Tests cover comparator forms, rejected riders, and existing attack-trigger parsing.
Evaluate live spell power
crates/engine/src/game/filter.rs
Spell-object PtComparison filters resolve thresholds, locate live spell objects, and compute effective P/T through fixed, dynamic, and conditional CDAs before comparing values.
Expose cost-filter coverage signatures
crates/engine/src/game/coverage.rs
ModifyCost coverage details now include the spell filter and other cost-reduction fields, with regression coverage for filter-only differences.
Validate displayed cost reductions
crates/engine/tests/integration/goreclaw_power_gated_cost_reduction_6375.rs, crates/engine/tests/integration/main.rs
Integration tests cover static and dynamic P/T boundaries, noncreature exclusions, fixed-P/T controls, conditional CDA windows, displayed costs, and test registration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OracleStaticParser
  participant CostFilter
  participant LiveSpellObject
  participant EffectivePTResolver
  participant CostDisplay
  OracleStaticParser->>CostFilter: build creature filter with power gate
  CostFilter->>LiveSpellObject: evaluate spell-object properties
  LiveSpellObject->>EffectivePTResolver: compute effective P/T and dynamic CDAs
  EffectivePTResolver-->>LiveSpellObject: return effective power
  LiveSpellObject-->>CostFilter: return comparison result
  CostFilter->>CostDisplay: apply matching cost reduction
Loading

Suggested reviewers: matthewevans

🚥 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 is concise and accurately describes the main change: fixing Goreclaw’s power-gated spell cost reduction.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 2

🤖 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/src/parser/oracle_static/static_helpers.rs`:
- Around line 139-147: Update parse_cost_mod_power_qualifier to accept a power
qualifier only when the remainder after parse_pt_comparison(rest.trim_start())
is empty after trimming; otherwise treat it as invalid. Preserve the distinction
between no qualifier and an unparseable or partially parsed qualifier so
strip_cost_mod_power_qualifier and try_parse_cost_modification return None
instead of producing an unrestricted filter.

In `@crates/engine/tests/integration/goreclaw_power_gated_cost_reduction_6375.rs`:
- Around line 46-47: Update goreclaw_generics and its callers so
shared_card_db() unavailability cannot silently produce passing tests without
assertions. Require the fixture for this regression suite, or explicitly mark
the suite ignored and run it only through a configured fixture-backed test job,
while ensuring the tests exercise the parser and production cost path.
🪄 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: b7369bd6-0cb6-4382-b05f-1aca71a21fdc

📥 Commits

Reviewing files that changed from the base of the PR and between 712fe27 and ddbeec8.

📒 Files selected for processing (5)
  • crates/engine/src/game/filter.rs
  • crates/engine/src/parser/oracle_static/static_helpers.rs
  • crates/engine/src/parser/oracle_static/tests.rs
  • crates/engine/tests/integration/goreclaw_power_gated_cost_reduction_6375.rs
  • crates/engine/tests/integration/main.rs

Comment thread crates/engine/src/parser/oracle_static/static_helpers.rs Outdated
Comment thread crates/engine/tests/integration/goreclaw_power_gated_cost_reduction_6375.rs Outdated
@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR

Baseline pending for 7a35ef23ddfc7acb1c118eb83bb633bd4ea2d6f4 — this populates once main publishes its coverage snapshot (a few minutes after that commit landed).

@matthewevans matthewevans self-assigned this Jul 25, 2026
@matthewevans matthewevans added the bug Bug fix label Jul 25, 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.

[MED] Dynamic-P/T creature spells are rejected even when their CDA gives them power 4 or greater. Evidence: crates/engine/src/game/filter.rs:3495-3525 evaluates PtComparison from the candidate object's stored P/T, but crates/engine/src/game/printed_cards.rs:1876-1881 initializes PtValue::Variable and PtValue::Quantity to 0; crates/engine/src/game/layers.rs:1824-1854 resets only hand-zone keywords, not P/T. CR 208.2a requires a P/T characteristic-defining ability to function everywhere. Why it matters: Goreclaw must reduce an eligible CDA creature spell (for example, a Terravore/Tarmogoyf-like spell) rather than treating it as power 0. Suggested fix: resolve the candidate spell's off-battlefield characteristics through the CDA/layer authority before comparing power, with discriminating dynamic-P/T 3-vs-4+ production-path tests and a fixed-P/T control.

[MED] The required parse-diff is a false negative for this PR's claimed parser change. Evidence: the current-head parse-diff artifact/comment reports "No card-parse changes detected" despite this PR changing StaticMode::ModifyCost.spell_filter; crates/engine/src/game/coverage.rs:4352-4387 projects only static affected, modifications, condition, CDA, and zone, omitting ModifyCost payload fields including spell_filter. Why it matters: CI cannot establish that Goreclaw's generated card-data filter changed, or detect a future blast radius in this semantic axis. Suggested fix: project semantically relevant ModifyCost fields (including spell_filter) into the coverage parse signature, add a red/green parse-diff regression test, and regenerate CI so the sticky identifies Goreclaw.

@matthewevans matthewevans removed their assignment Jul 26, 2026
…ect ModifyCost.spell_filter into coverage (phase-rs#6375 review)

Address matthewevans's two MED findings on phase-rs#6626:

1. A dynamic-P/T characteristic-defining spell (Tarmogoyf: power = distinct
   card types among all graveyards) was read as power 0 off the battlefield —
   `printed_cards::parse_pt` maps `*` to 0 and the layer pass only re-derives
   P/T for battlefield objects — so Goreclaw's power gate wrongly skipped it,
   contrary to CR 208.2a (a P/T CDA functions in all zones). New helper
   `spell_object_effective_pt_value` gathers the spell object's own self-sourced
   continuous effects (active_continuous_effects_from_static_source), keeps only
   characteristic-defining SelfRef dynamic-P/T modifications, applies set-CDAs
   (layer 7a/7b, CR 613.4a) before add-CDAs (7c) resolving each magnitude via the
   layer pass's own resolve_quantity, and falls back to the stored value when the
   object sources no CDA. Called only from the cost-path PtComparison arm; the
   battlefield and zone-change matchers (whose objects already have computed P/T)
   are untouched.

2. The coverage parse-diff signature omitted StaticMode::ModifyCost payload
   fields, so Goreclaw's spell_filter change (None -> Typed{Creature,[PtComparison]})
   hashed identically and CI reported "No card-parse changes." static_details now
   projects the ModifyCost mode/amount/spell_filter/dynamic_count.

Tests: 3 discriminating production-path CDA cases (Tarmogoyf power 4 reduced /
power 3 not / fixed-P/T control) + a red/green coverage-signature test. Both
revert-sensitive.

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

Copy link
Copy Markdown
Contributor Author

Thanks — both fixed at head 18d7eb897.

MED 1 — dynamic-P/T CDA spells read as power 0 off-battlefield. Confirmed: object_pt_value returned the stored obj.power, which is Some(0) for a *-P/T CDA card (parse_pt maps Variable/Quantity → 0) and is only re-derived by the layer pass for battlefield objects. New helper spell_object_effective_pt_value (filter.rs) resolves the candidate spell's off-battlefield P/T through the existing layer authorityactive_continuous_effects_from_static_source to gather its self-sourced continuous effects, then the same continuous_modification_dynamic_quantityresolve_quantity/resolve_quantity_with_recipient path apply_continuous_effect uses — keeping only characteristic-defining SelfRef P/T modifications (CR 208.2a — a P/T CDA functions in all zones), applying set-CDAs (layer 7a/7b, CR 613.4a) before add-CDAs (7c). It falls back to the stored object_pt_value byte-identically when the object sources no self-CDA, so fixed-P/T spells are unchanged. It's called only from the cost-path PtComparison arm; the battlefield and zone-change matchers (whose objects already carry layer-computed P/T) are untouched.

Discriminating production-path tests via display_spell_cost over a real Tarmogoyf face (genuine * CDA, not force-set): a 4-distinct-type graveyard → CDA power 4 → reduced (generic 1→0); a 3-type graveyard → power 3 → not reduced; a fixed-power control (Fire Elemental) → still reduced regardless. Revert-proven (routing back to object_pt_value reads 0 → the power-4 test flips to not-reduced).

MED 2 — coverage parse-diff omitted ModifyCost.spell_filter. static_details never projected stat.mode, so StaticMode's Display collapsed ModifyCost{..} to a bare "ReduceCost" and Goreclaw's spell_filter: None → Some(Typed{Creature,[PtComparison]}) hashed identically. static_details now emits deterministic cost mode / cost amount / cost filter / cost per rows for ModifyCost statics, with a red/green regression (modify_cost_signature_exposes_spell_filter) proving the None-vs-filter signatures now differ. Heads-up for the parse-diff sticky: this is a coverage schema widening, so on regeneration the coverage-parse-diff comment will list the whole ModifyCost class as "changed" (every such card gains the new signature rows) — that's expected, not parser blast radius; Goreclaw's spell_filter change is the one now correctly surfaced.

Verified: cargo test -p engine --test integration --no-run clean against merged-main, cargo test -p engine --lib 17,707 passed / 0 failed, clippy 0, Gate A PASS. CR cites grep-verified (208.1/208.2a/601.2f/613.4a/613.4b/613.4c).

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

Verdict: blocked — the dynamic-P/T fix is present, but the new off-battlefield CDA evaluator still omits conditional fixed P/T.

🔴 Blocker

[HIGH] Off-battlefield CDA evaluation skips fixed power/toughness setters. Evidence: crates/engine/src/game/filter.rs:4182-4198 applies only dynamic Set* variants before returning the computed value, while crates/engine/src/parser/oracle_static/tests.rs:825-900 shows that the parsed conditional constant P/T CDA form uses ContinuousModification::SetPower { value: 2 } and SetToughness { value: 2 } (Angry Mob's off-turn clause). Why it matters: the new cost-filter path starts from an off-battlefield object's stored P/T and can therefore evaluate a card whose active constant CDA is 2/* as printed 0/*, incorrectly failing a power gate. Suggested fix: handle applicable self-CDA SetPower/SetToughness in the resolver's initial set stage, in the same ordering as the layer authority, and add a discriminating production display_spell_cost test for a conditional constant-P/T creature spell.

Recommendation: request changes for the missing fixed-setter CDA path.

@matthewevans matthewevans removed their assignment Jul 26, 2026
… SetPower/SetToughness (phase-rs#6375 review r2)

Address matthewevans's 2nd-round HIGH: the off-battlefield CDA evaluator
(spell_object_effective_pt_value) applied only the dynamic Set*/Set*Dynamic
CDAs and skipped the fixed-constant ContinuousModification::SetPower{value} /
SetToughness{value} setters (Angry Mob's conditional "are each N" clause), so a
creature spell whose active constant CDA is 4/* was read as printed 0/* and
wrongly failed Goreclaw's power gate.

Add the two fixed-constant setters to the same layer-7a/7b set stage as the
dynamic setters (before the 7c add stage), mirroring the layer authority
(layers.rs lists all six P/T-set variants together). Also harden the shared
self_cda filter with the layer pass's exact per-recipient condition gate
(effect.condition.is_none_or(evaluate_condition_with_recipient(.., obj.id)),
mirroring apply_continuous_effect_filtered) so a conditional CDA (fixed or
dynamic) is applied only inside its window; None-condition CDAs (Tarmogoyf,
fixed-P/T) pass unchanged. Fixed-P/T fallback is byte-identical.

Tests: two conditional-constant-P/T cases through the production display_spell_cost
path — a DuringYourTurn SetPower{4} spell is reduced on your turn; the
Not{DuringYourTurn} variant stays printed 3 and is not reduced (condition-gating
discriminator). CR 613.4a / 611.3a grep-verified.

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

Copy link
Copy Markdown
Contributor Author

Thanks — fixed at head 5de755085.

You're right: the off-battlefield evaluator applied only the dynamic Set*/Set*Dynamic CDAs and skipped the fixed-constant SetPower { value } / SetToughness { value } setters, so Angry Mob's constant off-turn clause (2/, or a 4/ form) was read as printed. Added both fixed setters to the same layer-7a/7b set stage as the dynamic setters (ahead of the 7c add stage), matching the layer authority (layers.rs lists all six P/T-set variants together).

While there I also hardened the shared self_cda filter with the layer pass's exact per-recipient condition gateeffect.condition.is_none_or(evaluate_condition_with_recipient(.., obj.id)), a byte-for-byte mirror of apply_continuous_effect_filtered (layers.rs:5838-5848), with the self-CDA's recipient = its source. active_continuous_effects_from_static_source already drops defs whose non-recipient-context condition fails at the source level (Angry Mob's DuringYourTurn/Not{DuringYourTurn} are pre-filtered there), so this is a no-op for that card; it additionally honors a retained recipient-context condition for both the fixed and dynamic paths, so a conditional CDA is never applied outside its window. None-condition CDAs (Tarmogoyf, fixed-P/T) pass through unchanged.

Discriminating tests through the production display_spell_cost path (real Hill Giant face, conditional constant-P/T CDA parsed via parse_static_line, attached post-rehydrate — no force-set power):

  • During your turn … are each 4 → active on your turn → effective power 4 → reduced (3→1).
  • During turns other than yours … are each 4Not{DuringYourTurn} false on your turn → CDA not applied → printed 3 → not reduced (the condition-gating discriminator).

Revert-proven: removing the two fixed arms flips the active case (reads printed 3, GE-4 rejects); restored → green. Tarmogoyf dynamic-CDA (power 4 reduced / 3 not) + Fire Elemental fixed control still pass. cargo test -p engine --lib 17,707 / 0; integration --no-run compiles; Goreclaw suite 9/9; clippy 0; Gate A PASS. CR 613.4a / 611.3a grep-verified.

@matthewevans matthewevans self-assigned this Jul 27, 2026
@matthewevans

Copy link
Copy Markdown
Member

Current-head disposition for 5de75508568f4e1614cb2540eb00b6fa78c17735: the prior fixed-setter CDA blocker is resolved, but this PR is not ready for approval or the merge queue.

[MED] The required parse-diff does not substantiate the PR's claimed one-card scope and is stale for the current head. Evidence: the PR says only Goreclaw and Scope Expansion: None, but the required <!-- coverage-parse-diff --> comment reports 555 cards / 249 signatures and was last updated 2026-07-26T09:09:11Z, before this head was pushed (2026-07-27T05:37:05Z). The broad migration is traceable to coverage.rs:4384, which begins projecting every ModifyCost payload field, but that makes the current artifact unable to prove the stated Goreclaw-only parser impact. Why it matters: parse-diff is the required blast-radius evidence for this engine/parser PR; the present artifact contradicts the scope claim and cannot be treated as current-head evidence. Suggested fix: publish a fresh current-head parse-diff and update the PR scope/claim to account for the signature-schema migration, or split that migration so Goreclaw's parser delta is independently reviewable.

[MED] The regression suite does not drive the actual cast action. Evidence: goreclaw_power_gated_cost_reduction_6375.rs:77, :191, and :322 call display_spell_cost; the production cast branch enters prepare_spell_cast in casting.rs:4570 with CastingMode::Actual, not a GameAction/payment flow. Why it matters: this bug fixes casting-cost behavior, so a display-only assertion does not prove that an actual cast charges the filtered cost. Suggested fix: add a scenario that submits the real cast action with sufficient mana and asserts the power-3 spell pays its full generic cost while the power-4+ spell pays the reduced cost.

The security pre-check is clean (only the six engine/parser/test paths in this PR); no maintainer fixup was pushed. Current CI remains pending on Rust lint and both Rust test shards. The bug label is present; no quality label or merge-queue entry has been added.

@matthewevans matthewevans removed their assignment Jul 27, 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.

Current-head disposition for 5de75508568f4e1614cb2540eb00b6fa78c17735: changes requested.

[MED] Fresh parse-diff / scope mismatch. The required <!-- coverage-parse-diff --> artifact does not substantiate this PR's claimed Goreclaw-only scope and is stale for the current head. The PR says only Goreclaw and Scope Expansion: None, while the artifact reports 555 cards / 249 signatures and predates this head. The broad result comes from projecting every ModifyCost payload field in coverage.rs, so it is a signature-schema migration as well as the Goreclaw filter change. Publish a fresh parse-diff for the current head and update the scope claim to account for that migration, or split the migration so Goreclaw's parser delta is independently reviewable.

[MED] Display-only cast-cost regression test. The Goreclaw regression suite asserts display_spell_cost, but does not drive the actual cast action / payment path (prepare_spell_cast with CastingMode::Actual). Because this fixes casting-cost behavior, add a scenario that submits the real cast action with sufficient mana and proves the power-3 spell pays its full generic cost while the power-4-or-greater spell pays the reduced cost.

These current-head blockers must be resolved before approval or merge-queue entry.

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

2 participants