Skip to content

fix(parser): bind a dig-from-among "that creature" shared-type reference to the trigger subject (Conjurer's Mantle #5900) - #6528

Merged
matthewevans merged 3 commits into
phase-rs:mainfrom
rsnetworkinginc:fix/issue-5900
Jul 23, 2026
Merged

fix(parser): bind a dig-from-among "that creature" shared-type reference to the trigger subject (Conjurer's Mantle #5900)#6528
matthewevans merged 3 commits into
phase-rs:mainfrom
rsnetworkinginc:fix/issue-5900

Conversation

@rsnetworkinginc

@rsnetworkinginc rsnetworkinginc commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Conjurer's Mantle (#5900): its attack trigger — "Whenever equipped creature attacks, look at the top six cards of your library. You may reveal a card that shares a creature type with that creature from among them and put it into your hand." — never let you reveal a matching card, because the shared-creature-type filter matched nothing.

Root cause

Two layers of the same lost-context bug:

  1. parse_dig_from_among parsed the "from among them" reveal filter with the ctx-free parse_target, so the enclosing trigger subject never reached the filter's parse_shared_quality_reference.
  2. Even with the ctx in scope, parse_shared_quality_reference routed only the bare pronoun "it" through the ctx-aware resolve_pronoun_target; the singular demonstrative "that creature" fell through to the ctx-free parse_target, binding the shared-quality reference to ParentTarget.

In this attacks trigger there is no chosen target / recipient / effect-context object, so parent_target_shared_quality_values (game/filter.rs) returns None and the "shares a creature type" test matched no card — exactly the reported symptom (no card in the top six is ever selectable).

Sibling cards that reference the triggering creature with the pronoun "it" (Mana Echoes, the attacker anthem) already bind to TriggeringSource; only the demonstrative form in the dig-from-among reveal filter was missing.

Fix

  • Thread the enclosing ParseContext through parse_dig_from_among so the reveal filter parses with the trigger subject in scope.
  • Resolve the singular object demonstratives ("that creature" / "that permanent" / "that card") via the same resolve_pronoun_target path the bare pronoun "it" already used.

With a non-source trigger subject in scope the reference now binds to TriggeringSource (the attacking equipped creature); without one it stays ParentTarget, so non-trigger dig-from-among filters are unchanged. resolve_pronoun_target's existing subject discriminator (typed / AttachedTo subject → TriggeringSource; None / SelfRef / AnyParentTarget) contains the blast radius to genuine trigger-subject contexts.

Tests (RED pre-fix / GREEN post-fix, verified via stash)

  • conjurers_mantle_reveal_matches_shared_creature_type — drives the equipped Goblin's attack through the real combat + Dig reveal pipeline and asserts the shared-type Goblin card in the top six is selectable (empty pre-fix); non-creature fillers stay non-selectable; the revealed Goblin lands in hand.
  • conjurers_mantle_shared_type_reference_binds_to_trigger_subject — parser guard pinning the reference to TriggeringSource (ParentTarget pre-fix).

CI parity (isolated CARGO_TARGET_DIR)

  • cargo fmt --all -- --check — clean
  • cargo clippy --workspace --exclude phase-tauri --all-targets --features engine/proptest -- -D warnings — clean
  • cargo test -p engine --lib — 17565 passed, 0 failed
  • cargo test -p engine --test integration — 3837 passed, 0 failed

Closes #5900


Model: claude-opus-4
Tier: Standard
Thinking: high

Gate A: PASS — cargo fmt --all -- --check, cargo clippy --workspace --exclude phase-tauri --all-targets --features engine/proptest -- -D warnings, cargo test -p engine --lib (17565 passed / 0 failed), and cargo test -p engine --test integration (3837 passed / 0 failed), all clean in an isolated CARGO_TARGET_DIR, pre- and post-rebase onto latest origin/main.

Anchored on:

  • crates/engine/src/parser/oracle_effect/sequence.rsparse_dig_from_among threads ParseContext and parses the "from among them" filter via parse_target_with_ctx.
  • crates/engine/src/parser/oracle_target.rsparse_shared_quality_reference routes the singular object demonstratives through the existing ctx-aware resolve_pronoun_target.
  • crates/engine/src/game/filter.rsparent_target_shared_quality_values / object_shares_quality_with_reference_filter (unchanged) resolve TriggeringSource from state.current_trigger_events (the AttackersDeclared event), which is why the trigger-subject binding is the correct seam.

Summary by CodeRabbit

  • Bug Fixes

    • Improved interpretation of “from among them/those” filters so references resolve to the correct triggering object.
    • Added support for phrases such as “that creature,” “that permanent,” and “that card.”
    • Fixed card-selection behavior for effects like Conjurer’s Mantle, ensuring matching cards can be selected correctly.
  • Tests

    • Added regression coverage for parsing and gameplay behavior involving creature-type filters and contextual references.

… the trigger subject (Conjurer's Mantle phase-rs#5900)

Conjurer's Mantle's attack trigger ("Whenever equipped creature attacks,
look at the top six cards of your library. You may reveal a card that shares
a creature type with that creature from among them and put it into your
hand.") never let you reveal a matching card, because the shared-creature-type
filter matched nothing.

Root cause, two layers of the same lost-context bug:

1. `parse_dig_from_among` parsed the "from among them" reveal filter with the
   ctx-free `parse_target`, so the enclosing trigger subject never reached the
   filter's `parse_shared_quality_reference`.

2. Even with the ctx in scope, `parse_shared_quality_reference` routed only the
   bare pronoun "it" through the ctx-aware `resolve_pronoun_target`; the
   singular demonstrative "that creature" fell through to the ctx-free
   `parse_target`, binding the shared-quality reference to `ParentTarget`.

In this attacks trigger there is no chosen target / recipient / effect-context
object, so `parent_target_shared_quality_values` returns `None` and the
"shares a creature type" test matched no card — the reported symptom.

Fix: thread the `ParseContext` through `parse_dig_from_among`, and resolve the
singular object demonstratives ("that creature" / "that permanent" / "that
card") via the same `resolve_pronoun_target` path "it" already used. With a
non-source trigger subject in scope the reference now binds to
`TriggeringSource` (the attacking equipped creature); without one it stays
`ParentTarget`, so non-trigger dig-from-among filters are unchanged.

Regression: a runtime test drives the equipped Goblin's attack through the
real combat + Dig reveal pipeline and asserts the shared-type Goblin card is
selectable (empty pre-fix), plus a parser guard pinning the `TriggeringSource`
binding. Both RED pre-fix, GREEN post-fix (verified via stash).

Closes phase-rs#5900

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

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@matthewevans, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b19e5c36-cadc-485c-8bc9-b4e834d5869a

📥 Commits

Reviewing files that changed from the base of the PR and between c74a32d and cd60822.

📒 Files selected for processing (2)
  • crates/engine/src/parser/oracle_effect/sequence.rs
  • crates/engine/src/parser/oracle_target.rs
📝 Walkthrough

Walkthrough

The parser now threads ParseContext through Dig-from-among filters and resolves singular demonstrative references such as “that creature” contextually. New Conjurer’s Mantle integration tests validate both parsed target binding and runtime card selection.

Changes

Conjurer’s Mantle parser fix

Layer / File(s) Summary
Demonstrative target resolution
crates/engine/src/parser/oracle_target.rs
Singular “that creature”, “that permanent”, and “that card” references now use context-aware pronoun resolution.
Context-aware Dig filter parsing
crates/engine/src/parser/oracle_effect/sequence.rs, crates/engine/src/parser/oracle_effect/conditions.rs
Dig-from-among parsing accepts and propagates ParseContext, allowing “from among” filters to resolve references against the enclosing parse state.
Conjurer’s Mantle regression coverage
crates/engine/tests/integration/issue_5900_conjurers_mantle.rs, crates/engine/tests/integration/main.rs
Runtime and parser tests verify matching creature cards are selectable and that “that creature” binds to the triggering source.

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

Suggested labels: bug

Suggested reviewers: matthewevans, lgray, andriypolanski

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #5900 by making the dig-from-among filter context-aware and adding regression tests for Conjurer's Mantle.
Out of Scope Changes check ✅ Passed The PR stays focused on the reported parsing bug and supporting tests, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: context-aware parsing of a shared-type reference in Dig-from-among for Conjurer's Mantle #5900.
✨ 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.

🧹 Nitpick comments (2)
crates/engine/src/parser/oracle_target.rs (1)

6163-6178: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Missing "that token" demonstrative variant.

The loop covers "that creature", "that permanent", "that card" but omits "that token". The sibling anaphora combinator in crates/engine/src/parser/oracle_effect/counter.rs (counter_anaphor_created_token_binding) already treats "that token"/"the token"/"the permanent" as part of the same demonstrative family for an analogous binding, suggesting "that token" is a real Oracle form for shared-quality references (e.g. "a card that shares a card type with that token") that this arm would currently miss and fall through to the generic parse_target path.

Suggested fix
-    for demonstrative in ["that creature", "that permanent", "that card"] {
+    for demonstrative in ["that creature", "that permanent", "that card", "that token"] {
🤖 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_target.rs` around lines 6163 - 6178, Extend
the demonstrative list in the pronoun-resolution loop around
resolve_pronoun_target to include “that token”. Preserve the existing ctx-aware
binding and fallback behavior for the other singular demonstratives.
crates/engine/src/parser/oracle_effect/sequence.rs (1)

5275-5291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate filter-resolution block across both from among branches.

Both the "milled this way" branch and the "from among them/those" branch carry an identical empty/"card"/"cards"/"of them" guard plus the same multi-line CR-cited parse_target_with_ctx call and comment. This is the same class of duplication that caused issue #5900 (one branch had drifted to ctx-free parse_target while the other used parse_target_with_ctx). Extracting a small shared helper would make the two branches structurally impossible to desync again.

Suggested extraction
+fn resolve_dig_from_among_filter_text(filter_text: &str, ctx: &mut ParseContext) -> TargetFilter {
+    if filter_text.is_empty()
+        || filter_text == "card"
+        || filter_text == "cards"
+        || filter_text == "of them"
+    {
+        return TargetFilter::Any;
+    }
+    // CR 608.2c + CR 608.2k: parse the "from among them" reveal/put filter
+    // with the enclosing ctx so an anaphoric reference inside it binds to
+    // the trigger subject (`TriggeringSource`) when one exists (`#5900`).
+    let (parsed_filter, _) = parse_target_with_ctx(filter_text, ctx);
+    parsed_filter
+}

Then both call sites become let filter = resolve_dig_from_among_filter_text(filter_text, ctx);.

Also applies to: 5382-5398

🤖 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/sequence.rs` around lines 5275 - 5291,
Extract the duplicated filter-resolution logic from both “milled this way” and
“from among them/those” branches into a shared helper such as
resolve_dig_from_among_filter_text, preserving the empty/“card”/“cards”/“of
them” handling and ctx-aware parse_target_with_ctx behavior. Replace both local
blocks with calls to the helper, passing filter_text and ctx.
🤖 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/sequence.rs`:
- Around line 5275-5291: Extract the duplicated filter-resolution logic from
both “milled this way” and “from among them/those” branches into a shared helper
such as resolve_dig_from_among_filter_text, preserving the
empty/“card”/“cards”/“of them” handling and ctx-aware parse_target_with_ctx
behavior. Replace both local blocks with calls to the helper, passing
filter_text and ctx.

In `@crates/engine/src/parser/oracle_target.rs`:
- Around line 6163-6178: Extend the demonstrative list in the pronoun-resolution
loop around resolve_pronoun_target to include “that token”. Preserve the
existing ctx-aware binding and fallback behavior for the other singular
demonstratives.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0a55ef39-405d-4b51-b7fe-eadbfd5f2c46

📥 Commits

Reviewing files that changed from the base of the PR and between c3824d2 and c74a32d.

📒 Files selected for processing (5)
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_effect/sequence.rs
  • crates/engine/src/parser/oracle_target.rs
  • crates/engine/tests/integration/issue_5900_conjurers_mantle.rs
  • crates/engine/tests/integration/main.rs

@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

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

Approved: current-head parser context propagation uses the established target parser, and the registered combat-to-Dig regression is discriminating.

@matthewevans
matthewevans added this pull request to the merge queue Jul 23, 2026
@matthewevans matthewevans removed their assignment Jul 23, 2026
Merged via the queue into phase-rs:main with commit 1c64070 Jul 23, 2026
14 checks passed
jsdevninja pushed a commit to jsdevninja/phase that referenced this pull request Jul 24, 2026
…nce to the trigger subject (Conjurer's Mantle phase-rs#5900) (phase-rs#6528)

* fix(parser): bind a dig-from-among "that creature" shared-type ref to the trigger subject (Conjurer's Mantle phase-rs#5900)

Conjurer's Mantle's attack trigger ("Whenever equipped creature attacks,
look at the top six cards of your library. You may reveal a card that shares
a creature type with that creature from among them and put it into your
hand.") never let you reveal a matching card, because the shared-creature-type
filter matched nothing.

Root cause, two layers of the same lost-context bug:

1. `parse_dig_from_among` parsed the "from among them" reveal filter with the
   ctx-free `parse_target`, so the enclosing trigger subject never reached the
   filter's `parse_shared_quality_reference`.

2. Even with the ctx in scope, `parse_shared_quality_reference` routed only the
   bare pronoun "it" through the ctx-aware `resolve_pronoun_target`; the
   singular demonstrative "that creature" fell through to the ctx-free
   `parse_target`, binding the shared-quality reference to `ParentTarget`.

In this attacks trigger there is no chosen target / recipient / effect-context
object, so `parent_target_shared_quality_values` returns `None` and the
"shares a creature type" test matched no card — the reported symptom.

Fix: thread the `ParseContext` through `parse_dig_from_among`, and resolve the
singular object demonstratives ("that creature" / "that permanent" / "that
card") via the same `resolve_pronoun_target` path "it" already used. With a
non-source trigger subject in scope the reference now binds to
`TriggeringSource` (the attacking equipped creature); without one it stays
`ParentTarget`, so non-trigger dig-from-among filters are unchanged.

Regression: a runtime test drives the equipped Goblin's attack through the
real combat + Dig reveal pipeline and asserts the shared-type Goblin card is
selectable (empty pre-fix), plus a parser guard pinning the `TriggeringSource`
binding. Both RED pre-fix, GREEN post-fix (verified via stash).

Closes phase-rs#5900

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

* fix(PR-6528): resolve parser review findings

---------

Co-authored-by: rsnetworkinginc <rsnetworkinginc@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <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.

Conjurer's Mantle fails to recognize matching creature types — [[Conjurer's Mantle]] should identify creatures among 6…

2 participants