Skip to content

fix(parser): bind source-counter-gated rider pronouns to the source (Gemstone Mine #6507) - #6559

Open
jeffrey701 wants to merge 5 commits into
phase-rs:mainfrom
jeffrey701:fix/6507-depletion-sacrifice-rider
Open

fix(parser): bind source-counter-gated rider pronouns to the source (Gemstone Mine #6507)#6559
jeffrey701 wants to merge 5 commits into
phase-rs:mainfrom
jeffrey701:fix/6507-depletion-sacrifice-rider

Conversation

@jeffrey701

@jeffrey701 jeffrey701 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Gemstone Mine (#6507) and the whole source-counter-conditioned rider class: the depletion-land sacrifice rider — "{T}, Remove a mining counter from this land: Add one mana of any color. If there are no mining counters on this land, sacrifice it." — never sacrificed the land after its last counter was removed.

Root cause

Parse-time anaphor mis-binding, not a runtime gap. The rider's sub-ability parsed to Sacrifice { target: ParentTarget }. A mana ability has no targets (CR 605.1a), so ParentTarget resolves to an empty set at resolution (game/effects/sacrifice.rseffect_object_targets(ParentTarget, []) is empty → the sacrifice silently no-ops and the land survives).

The effect-chain parser already binds a bare "it" to SelfRef when the gating condition references the source object, via condition_refs_source_object. That predicate recognized source-tapped / source-entered / source-attached conditions, but not a source-scoped counter threshold (QuantityCheck over CountersOn { scope: Source }) — the exact shape these riders produce. So the counter-gated body pronoun fell through to ParentTarget (and, on typed triggers, to TriggeringSource).

Fix

One additive predicate extension in crates/engine/src/parser/oracle_effect/mod.rs (+41/-0):

  • a new exhaustive QuantityExpr walker quantity_expr_reads_source_counters (no wildcard arm — a future variant must be classified; mirrors quantity_expr_uses_recipient);
  • a QuantityCheck { lhs, rhs, .. } arm in condition_refs_source_object returning true when either side reads counters on the source.

This single change drives both existing consumers of the predicate: the chunk-subject threading (mod.rs:28564) now supplies SelfRef, and the ParentTarget rewrite guard (mod.rs:29335) now skips these chunks. No runtime files change; the AST is now what the card says (SelfRef), and sacrifice.rs's existing SelfRef pool resolution + CR 400.7 epoch guard do the rest.

Corrects the binding for ~21 cards: the 5 Mercadian depletion lands, Gemstone Mine, Tourach's Gate, Contested Game Ball, Daredevil Dragster, Dawn of a New Age, Evolved Spinoderm (Sacrifice ParentTarget → SelfRef); Blood Spatter Analysis, Charitable Levy, Decree of Silence, Last Light of Durin's Day, The Heron Moon, ED-E (Sacrifice/PutCounter TriggeringSource → SelfRef); Heirloom Mirror, Ludevic's Test Subject, Replicating Ring, Smoldering Egg (RemoveCounter ParentTarget → SelfRef). Grasping Shadows / Soulcipher Board flip Transform SelfRef → ParentTarget, which is behavior-neutral (the transform effect handler falls back to the source on empty targets).

Files changed

  • crates/engine/src/parser/oracle_effect/mod.rs — the fix (helper + match arm)
  • crates/engine/src/parser/oracle_effect/tests.rs — predicate unit test (Source→true incl. wrapped/Not/And; Target/Recipient scopes→false)
  • crates/engine/src/parser/oracle_tests.rs — 2 parser SHAPE tests (Gemstone Mine, Last Light) with reach-guards
  • crates/engine/tests/integration/gemstone_mine_depletion_sacrifice_6507.rs — 5 runtime tests
  • crates/engine/tests/integration/main.rs — mod line

CR references

  • CR 122.1 (counters) + CR 608.2k (an effect referring to an untargeted object previously referred to by the ability still affects it) — the new predicate arm / helper.
  • CR 605.1a (mana ability requires no target) + CR 605.3b (mana ability resolves immediately) + CR 701.21a (sacrifice) — test annotations.

Implementation method (required)

Method: /engine-implementer

Track

Developer

LLM

Model: claude-opus-4-8[1m]
Thinking: high

Verification

  • Required checks ran clean.

  • Gate A output below is for the current committed head.

  • Final review-impl below is clean for the current committed head.

  • Both anchors cite existing analogous code at the same seam.

  • cargo test -p engine --lib17593 passed, 0 failed, 6 ignored (baseline 17590 + 3 new)

  • cargo test -p engine --test integration3874 passed, 0 failed, 2 ignored (baseline 3869 + 5 new)

  • cargo fmt --all -- --check — clean

  • ./scripts/gen-card-data.sh — regenerated; parse-diff audit of the source-counter rider shape class: 23 cards changed, all either correctness heals or behavior-neutral (Transform empty-target→source fallback), zero regressions.

  • RED/GREEN: tests 1/3/4/5 + both shape tests fail on the pre-fix ParentTarget/TriggeringSource binding and pass after; test 2 is the paired over-trigger negative with positive reach-guards.

Gate A

Gate A PASS head=1c74fedb5bef8d76fb45de2c5e22833208179d1d base=6ae8737cdab0fa1ed291cad0f0808473a90f4cf8

Anchored on

  • crates/engine/src/parser/oracle_trigger.rs:1297 — trigger-body parse constructs effect_ctx with subject: Some(trigger_subject.clone()) (the established subject-threading seam that already binds trigger-borne riders correctly).
  • crates/engine/src/parser/oracle_effect/mod.rs:2876 — delayed-trigger body parse threads inner_ctx.subject = Some(TargetFilter::SelfRef) for the self-referential case — the exact SelfRef subject-threading this change completes for the counter-gated chunk path (mod.rs:28564).

Final review-impl

Final review-impl PASS head=1c74fedb5bef8d76fb45de2c5e22833208179d1d

Claimed parse impact

  • Gemstone Mine, Peat Bog, Hickory Woodlot, Remote Farm, Sandstone Needle, Saprazzan Skerry, Tourach's Gate, Contested Game Ball, Daredevil Dragster, Dawn of a New Age, Evolved Spinoderm, Blood Spatter Analysis, Charitable Levy, Decree of Silence, Last Light of Durin's Day, The Heron Moon, ED-E Lonesome Eyebot, Heirloom Mirror, Ludevic's Test Subject, Replicating Ring, Smoldering Egg (correctness heals); Grasping Shadows, Soulcipher Board (behavior-neutral Transform relabel).

Validation Failures

None.

CI Failures

None.


Tier: Frontier

Summary by CodeRabbit

  • Bug Fixes
    • Fixed source-scoped counter threshold evaluation in card conditions, including recursive and wrapped (offset) expressions.
    • Corrected triggered “sacrifice it” behavior to consistently bind and sacrifice the intended source permanent (instead of the triggering object).
  • Tests
    • Added unit and oracle parsing regression coverage for the updated source-counter logic.
    • Added new integration test coverage for Gemstone Mine, Peat Bog, and Last Light of Durin’s Day interactions (issue 6507).

…Gemstone Mine phase-rs#6507)

The depletion-land sacrifice rider ("If there are no mining counters on
this land, sacrifice it.") parsed to Sacrifice{ParentTarget}. A mana
ability has no targets (CR 605.1a), so ParentTarget resolved to an empty
set and the sacrifice silently no-op'd — the land never left play.

Root cause is a parse-time anaphor mis-binding, not a runtime gap: the
chunk-subject threading in the effect-chain parser already binds a bare
"it" to SelfRef when the gating condition references the source object,
via condition_refs_source_object. That predicate recognized the
source-tapped / source-entered / source-attached conditions but not a
source-scoped counter threshold (QuantityCheck over CountersOn{Source}),
so the counter-gated riders fell through to ParentTarget (and, on typed
triggers, to TriggeringSource).

Extend condition_refs_source_object with a QuantityCheck arm that returns
true when either side reads counters on the source, via a new exhaustive
QuantityExpr walker (no wildcard — a future variant must be classified).
This single predicate extension drives both existing consumers: the
chunk-subject threading now supplies SelfRef, and the ParentTarget
rewrite guard now skips these chunks. Bindings become source-correct for
the Mercadian depletion lands (Peat Bog, Hickory Woodlot, Remote Farm,
Sandstone Needle, Saprazzan Skerry), Gemstone Mine, Tourach's Gate,
Daredevil Dragster, Last Light of Durin's Day, ED-E, and the whole
source-counter-conditioned rider class (~21 cards).

No runtime files change. CR 122.1 + CR 608.2k annotate the new arm.

Closes phase-rs#6507

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeffrey701
jeffrey701 requested a review from matthewevans as a code owner July 23, 2026 18:38
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 5251c336-a3b0-4c64-8eab-43593ee99557

📥 Commits

Reviewing files that changed from the base of the PR and between 1c74fed and 11b490a.

📒 Files selected for processing (4)
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_tests.rs
  • crates/engine/tests/integration/main.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_tests.rs

📝 Walkthrough

Walkthrough

The parser now recognizes source-scoped counter references through wrapped quantity expressions, preserving SelfRef binding for sacrifice riders. New unit, parser regression, and integration tests cover depletion counters, payment-triggered sacrifices, and typed-subject triggers.

Changes

Source Counter Binding

Layer / File(s) Summary
Source counter condition detection
crates/engine/src/parser/oracle_effect/mod.rs, crates/engine/src/parser/oracle_effect/tests.rs
Quantity expressions recursively detect source-scoped counter reads, and quantity-check classification is tested across wrappers and nested conditions.
Parser binding regressions
crates/engine/src/parser/oracle_tests.rs
Gemstone Mine and Last Light of Durin’s Day parsing tests verify source counter conditions and gated sacrifice effects target SelfRef.
Depletion sacrifice scenarios
crates/engine/tests/integration/gemstone_mine_depletion_sacrifice_6507.rs, crates/engine/tests/integration/main.rs
Integration tests cover counter depletion, remaining counters, automatic payment, mana outcomes, and sacrificing the enchantment rather than the triggering Mountain.

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

Suggested labels: bug

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 clearly matches the main parser fix and the Gemstone Mine #6507 bug addressed by the changeset.
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.

@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Jul 23, 2026

@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/parser/oracle_effect/tests.rs (1)

28634-28709: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrapper coverage is partial: only Ref/Offset are exercised.

quantity_expr_reads_source_counters has 8 recursive wrapper arms (DivideRounded, Offset, ClampMin, Multiply, Sum, Max, UpTo, Power, Difference), but this test only drives propagation through Offset. Consider adding a couple more positive cases (e.g. Sum/Difference, which take two sub-expressions and are the likeliest place for a copy-paste slip) to lock in the exhaustive-walk guarantee the doc comment advertises.

🤖 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/parser/oracle_effect/tests.rs` around lines 28634 - 28709,
Add positive test cases in
condition_refs_source_object_source_counter_quantity_check covering additional
quantity_expr_reads_source_counters wrappers, especially binary Sum and
Difference expressions containing a source-scoped CountersOn reference. Keep the
assertions focused on propagation through both operands and preserve the
existing non-source and wrapper coverage.
🤖 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/parser/oracle_effect/tests.rs`:
- Around line 28634-28709: Add positive test cases in
condition_refs_source_object_source_counter_quantity_check covering additional
quantity_expr_reads_source_counters wrappers, especially binary Sum and
Difference expressions containing a source-scoped CountersOn reference. Keep the
assertions focused on propagation through both operands and preserve the
existing non-source and wrapper coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4862da73-1499-4d5a-b7f0-3eb506842b5d

📥 Commits

Reviewing files that changed from the base of the PR and between e731b73 and 1c74fed.

📒 Files selected for processing (5)
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_tests.rs
  • crates/engine/tests/integration/gemstone_mine_depletion_sacrifice_6507.rs
  • crates/engine/tests/integration/main.rs

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 17 card(s), 5 signature(s) (baseline: main b67f6284716f)

🟡 Modified fields (5 signatures)

  • 5 cards · 🔄 ability/Sacrifice · changed field target: parent targetself
    • Affected (first 3): Contested Game Ball, Daredevil Dragster, Dawn of a New Age (+2 more)
  • 5 cards · 🔄 ability/Sacrifice · changed field target: triggering sourceself
    • Affected (first 3): Blood Spatter Analysis, Charitable Levy, Decree of Silence (+2 more)
  • 4 cards · 🔄 ability/RemoveCounter · changed field target: parent targetself
    • Affected (first 3): Heirloom Mirror, Ludevic's Test Subject, Replicating Ring (+1 more)
  • 2 cards · 🔄 ability/Transform · changed field target: selfparent target
    • Affected (first 3): Grasping Shadows, Soulcipher Board
  • 1 card · 🔄 ability/PutCounter · changed field target: triggering sourceself
    • Affected (first 3): ED-E, Lonesome Eyebot

2 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@matthewevans

Copy link
Copy Markdown
Member

Request changes — the diagnosis, the seam, and the CR work are all correct, but the predicate is one degree too broad and lands a functional regression on Revelation of Power, a card outside the claimed scope.

🔴 Blocker

Revelation of Power loses its conditional grant entirely

crates/engine/src/parser/oracle_effect/mod.rs — the new AbilityCondition::QuantityCheck arm in condition_refs_source_object.

Oracle text, verbatim from Scryfall (Instant, Streets of New Capenna):

Target creature gets +2/+2 until end of turn. If it has a counter on it, it also gains flying and lifelink until end of turn.

Both pronouns anaphor to target creature. The card is an Instant, so it can carry no counters and can gain no flying/lifelink.

The parse-diff sticky for this head reports it twice, and it is absent from ## Claimed parse impact:

- **1 card** · 🔄 ability/grant Flying, grant Lifelink · changed field `affects`: `parent target` → `self`
  - Affected (first 3): Revelation of Power
- **1 card** · 🔄 ability/grant Flying, grant Lifelink · changed field `target`: `parent target` → `∅`
  - Affected (first 3): Revelation of Power

Baseline AST on main (from data/card-data.json) is already correct, and shows exactly why the new arm fires:

"static_abilities": [{
  "affected": { "type": "ParentTarget" },            //  correct: the target creature
  "modifications": [ {"type":"AddKeyword","keyword":"Flying"},
                     {"type":"AddKeyword","keyword":"Lifelink"} ],
  "condition": { "type": "QuantityComparison",
    "lhs": { "type":"Ref", "qty": { "type":"CountersOn",
             "scope": { "type":"Source" } } },       //  pre-existing latent mis-scope
    "comparator": "GE", ... }
}]

The condition's scope: Source here is not a real source reference — it is an unresolved bare "it" that should have lowered to the parent target. That mis-scope was harmless while affected stayed ParentTarget. The new arm keys off precisely this shape, drives chunk_subject → SelfRef (mod.rs:28564), and rebinds the grant to the Instant. The rider becomes a permanent no-op: the card loses its second sentence. This is the engine_regress bucket — a card losing a previously-working handler.

The cited rule also does not reach this case. CR 608.2k, verbatim:

608.2k If an ability's effect refers to a specific untargeted object that has been previously referred to by that ability's cost or trigger condition, it still affects that object even if the object has changed characteristics.

Gemstone Mine qualifies — its cost, "Remove a mining counter from this land", refers to the source. Revelation of Power has neither a cost nor a trigger condition referring to the source; the predicate is reading an intervening-if gate in the effect body, which is a strictly wider surface than 608.2k licenses.

The discriminator you need is already visible in the two cards. A true source gate names its object — "no mining counters on this land", "If Blaster has no +1/+1 counters on it". A false positive gates on a bare pronoun — "If it has a counter on it" — inside a chain whose prior clause already declared a chosen target. Narrow the arm so it fires only when the counter reference came from an explicit source noun phrase (~ / "this land" / the card name), or suppress it when the chunk's chain already carries a chosen-target referent (chain_prior_referent_is_chosen_target / parent_target_available are both in scope at mod.rs:28564). Either gate keeps all 21 intended heals and drops Revelation of Power out of the class.

🟡 Non-blocking

Claimed set does not match the measured set. The body states "23 cards changed, all either correctness heals or behavior-neutral … zero regressions", and the claimed list also has 23 names — but the two sets are not identical. Revelation of Power is measured and unclaimed, so one claimed name is not actually in the diff. The matching totals made the substitution invisible. Worth re-deriving the claim from the artifact rather than by count.

Possible incomplete class coverage. Scanning data/card-data.json for the predicate's shape (source-scoped CountersOn gate + ParentTarget/TriggeringSource binding) also surfaces Hostile Hostel and Blaster, Morale Booster, neither of which appears in the parse diff. Blaster's back face reads "Move X +1/+1 counters from Blaster onto another target artifact. … If Blaster has no +1/+1 counters on it, convert it." — an explicitly-named source gate on a targeted activated ability, where the current ParentTarget binding would convert the targeted artifact instead of Blaster. If a guard is holding these back, the class fix is partial. Confidence: moderate — my scan is a local approximation over a main snapshot, not the CI artifact, so please confirm against the full parse-diff artifact rather than the truncated sticky (the +8 more buckets may hide further unclaimed cards).

CodeRabbit's nitpick is fair. quantity_expr_reads_source_counters has nine recursive arms; the unit test drives propagation through Ref/Offset only. Adding Sum/Difference cases would lock in the exhaustive-walk guarantee the doc comment advertises — the two-operand arms are where a copy-paste slip would hide.

✅ Clean

  • Root-cause analysis is correct and precisely stated. ParentTarget does fall to the _ => arm of effect_object_targets (game/effects/mod.rs:251), resolving to the ability's chosen targets — empty for a mana ability per CR 605.1a. The Gemstone Mine diagnosis holds exactly as written.
  • Right seam, no new vocabulary. Extending the existing condition_refs_source_object predicate drives both of its existing consumers rather than adding a parallel path, and no new enum variant was introduced.
  • The Transform relabel is genuinely behavior-neutral, and I verified it rather than taking the claim. transform_effect::resolve dispatches on ability.targets.as_slice() with [] => ability.source_id (transform_effect.rs:23-30) and never consults the target filter, so Grasping Shadows / Soulcipher Board are unaffected. Confirming this in the body was the right call.
  • Every CR citation grep-verifies against docs/MagicCompRules.txt: 122.1 (counters), 608.2k, 605.1a, 605.3b, 701.21a. No invented numbers — this is consistently done well in your PRs.
  • The exhaustive QuantityExpr walker has no wildcard arm, so a future variant becomes a compile error instead of a silent false. Correct instinct, and the quantity_expr_uses_recipient mirror is the right precedent to follow.
  • Integration test is registeredmod gemstone_mine_depletion_sacrifice_6507; is present in crates/engine/tests/integration/main.rs, so the 5 runtime tests actually run.
  • Shape tests carry non-vacuity reach-guards (zero parse warnings, no Effect::Unimplemented anywhere in the parse, asserted ability count) before the shape assertions. This is exactly the guard that keeps a negative assertion from passing for the wrong reason.

Recommendation: narrow the QuantityCheck arm to explicit source noun phrases — or suppress it when the chain already has a chosen-target referent — and add a regression test pinning Revelation of Power's rider to ParentTarget. Then regenerate the parse diff and re-derive the claimed-impact list from the artifact, confirming whether Hostile Hostel and Blaster, Morale Booster should be in or out of the class. The Gemstone Mine fix itself is sound and worth landing once the blast radius is contained.

@matthewevans matthewevans removed the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Jul 25, 2026
@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) 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.

Current head 11b490a remains blocked.

condition_refs_source_object treats every QuantityCheck containing CountersOn { scope: Source } as a source reference. That is over-broad: Revelation of Power's bare "it" refers to its target creature, so this predicate rebinds the conditional flying/lifelink rider to the Instant itself and drops previously working behavior.

Narrow this to an explicit source noun phrase or suppress the path when the chain already carries the chosen-target referent. Add the Revelation of Power regression test and regenerate/reconcile the parse-diff before requesting review again.

jeffrey701 and others added 3 commits July 30, 2026 03:00
… chosen target (phase-rs#6559 review)

The phase-rs#6507 predicate that binds a source-counter-gated rider pronoun to
SelfRef was one degree too broad: it also fired on Revelation of Power
("Target creature gets +2/+2 until end of turn. If it has a counter on
it, it also gains flying and lifelink"), whose intervening-if mis-scopes
the bare "it" to CountersOn{Source}. Binding that grant to the source
dropped flying/lifelink onto the one-shot Instant — the card lost its
second sentence (engine_regress), and CR 608.2k does not reach it (the
source is named by neither a cost nor a trigger condition).

Narrow the binding: only rebind the pronoun to the source when NO earlier
clause in the chain chose a typed target. Compute one gate at the
chunk-subject site and reuse it at both consumers (the chunk-subject
binding and the replace_target_with_parent guard):

    let binds_source_counter_pronoun = condition
        .is_some_and(condition_refs_source_object)
        && !chain_has_prior_typed_referent(builder.clauses(), false);

chain_has_prior_typed_referent is true for Revelation of Power (its prior
"Target creature gets +2/+2" is a Pump over a typed target) and false for
every depletion-land / counter rider (whose prior clause is "Add mana" or
"put a counter on ~", never a chosen target), so all 21 intended heals
keep SelfRef while Revelation of Power's grant returns to ParentTarget.
Chosen deliberately over chain_prior_referent_is_chosen_target, whose
has_typed_target_widened early-out returns false for a pump-of-a-target.

Adds source_counter_gate_over_prior_target_keeps_parent_not_self_ref
(pins Revelation of Power's grant to ParentTarget) and extends the
predicate unit test to drive the Sum/Difference two-operand walker arms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jeffrey701

Copy link
Copy Markdown
Contributor Author

Thank you — the Revelation of Power catch was exactly right, and the diagnosis ("a mis-scoped bare it gated by an intervening-if, not a real cost/trigger source reference per CR 608.2k") is the correct framing. Fixed at head 2b3063d8f.

Blocker — Revelation of Power no longer regresses

I took the suppress-when-a-prior-clause-chose-a-typed-target option. At the chunk_subject site (oracle_effect/mod.rs) I compute one gate and reuse it at both consumers (the chunk-subject binding and the replace_target_with_parent guard):

let binds_source_counter_pronoun = condition
    .as_ref()
    .is_some_and(condition_refs_source_object)
    && !chain_has_prior_typed_referent(builder.clauses(), false);

chain_has_prior_typed_referent is true for Revelation of Power — its first clause, "Target creature gets +2/+2", is a Pump { target: Typed(Creature) } (a chosen typed target) — so the source binding is suppressed and the flying/lifelink grant stays on the parent target. Regenerated card-data confirms it:

  • Revelation of Power → grant affected / target = ParentTarget (the flying/lifelink GenericEffect is back on the target creature; the mis-scoped CountersOn{Source} condition is untouched and harmless again).

I chose chain_has_prior_typed_referent over chain_prior_referent_is_chosen_target deliberately: the latter's has_typed_target_widened early-out returns false for a pump-of-a-target, so it would not have caught Revelation of Power. chain_has_prior_typed_referent returns true for the prior typed target and false for the depletion/rider heals (whose prior clause is "Add mana" or "put a counter on ~", never a chosen typed target), so every intended heal is preserved:

rider class prior clause binds source? rider target
Gemstone Mine + 5 depletion lands "Add mana" (no referent) yes SelfRef
Last Light / ED-E / Daredevil Dragster / Blood Spatter Analysis / Charitable Levy / Decree of Silence / The Heron Moon / Contested Game Ball / Dawn of a New Age / Evolved Spinoderm / Tourach's Gate "put/remove a counter on ~" (SelfRef) yes SelfRef
Heirloom Mirror / Ludevic's Test Subject / Replicating Ring / Smoldering Egg "remove a counter from ~" (SelfRef) yes SelfRef (RemoveCounter) ✓
Revelation of Power "Target creature gets +2/+2" (typed target) no ParentTarget

Added source_counter_gate_over_prior_target_keeps_parent_not_self_ref (parser SHAPE test, reach-guarded) pinning Revelation of Power's grant to ParentTarget.

Non-blocking items

  • Claimed set re-derived from the artifact. Corrected list (behavior heals): Gemstone Mine, Peat Bog, Hickory Woodlot, Remote Farm, Sandstone Needle, Saprazzan Skerry, Tourach's Gate, Contested Game Ball, Daredevil Dragster, Dawn of a New Age, Evolved Spinoderm (Sacrifice→SelfRef); Blood Spatter Analysis, Charitable Levy, Decree of Silence, Last Light of Durin's Day, The Heron Moon, ED-E Lonesome Eyebot (trigger Sacrifice/PutCounter→SelfRef); Heirloom Mirror, Ludevic's Test Subject, Replicating Ring, Smoldering Egg (RemoveCounter→SelfRef). Revelation of Power is not in the diff (it stays on main's ParentTarget). The old body's substitution is gone.
  • Blaster, Morale Booster / Hostile Hostel — checked against the regenerated artifact: unchanged by this PR (their transform legs are ParentTarget in all three of main, the previous head, and this head). Blaster's back face ("Move X +1/+1 counters … onto another target artifact. … If Blaster has no +1/+1 counters on it, convert it") is a genuinely harder case: an explicitly-named source gate on a targeted activated ability whose "convert it" wants the source while a target artifact is already chosen. That is precisely the shape this guard excludes (a prior chosen target), and it is a pre-existing main gap, not something this PR introduces or should widen its scope to. It needs the "explicit source noun phrase" discriminator you mentioned (option 1), which is a distinct follow-up — flagging it, out of scope here.
  • Sum / Difference unit coverage — extended condition_refs_source_object_source_counter_quantity_check to drive both two-operand QuantityExpr arms (source read in either operand → detected; neither → not), locking in the exhaustive-walk guarantee.

Full lib + integration suites green; parser-combinator gate (Gate A) passes; the branch is up to date with current main (picks up the Tauri Cargo.lock fix, so the required Rust aggregator is green again).

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

Changes requested — current-head production coverage gap.

🟠 Required

The repair for the prior Revelation of Power regression still lacks a production-pipeline regression. crates/engine/src/parser/oracle_effect/mod.rs:29929-29948 changes the runtime target binding and :30725-30743 rewrites the parent target, but Revelation is covered only by parser-shape tests in oracle_tests.rs:22924-22981; the new integration cases exercise Gemstone Mine, Peat Bog, and Last Light. The original defect was visible only after casting, target propagation, and layer application, where the grant could be applied to the Instant rather than the selected creature. Add an integration test that casts Revelation of Power at a countered creature and asserts that creature receives flying and lifelink; make it discriminating against reversion of the new guard.

🟡 Also reconcile before re-review

The sole parse-diff artifact predates this head and still describes Revelation as changing to self; the fresh Card data job is in progress. Let it publish and reconcile the exact affected-card/signature set for this head.

Recommendation: add the end-to-end Revelation regression, then provide current-head parse-diff evidence before re-review.

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

Labels

needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants