Skip to content

Partial: Ultimate Nullification - #6940

Merged
matthewevans merged 9 commits into
phase-rs:mainfrom
JacobWoodson:card/ultimate-nullification
Aug 3, 2026
Merged

Partial: Ultimate Nullification#6940
matthewevans merged 9 commits into
phase-rs:mainfrom
JacobWoodson:card/ultimate-nullification

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds engine support for Ultimate Nullification.

Files changed

  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_effect\imperative.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_effect\mod.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_effect\tests.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_target.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\ultimate_nullification.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\main.rs

CR references

  • CR 108.2
  • CR 109.2
  • CR 205.2a
  • CR 205.3a
  • CR 400.3
  • CR 404.1
  • CR 406.2
  • CR 601.2f
  • CR 608.2f

Track

Developer

LLM

Model: claude-opus-4-8
Thinking: high

Tier: Frontier

Verification

  • cargo fmt --all — pass
  • ./scripts/check-parser-combinators.sh (Gate A) — pass (required env fix: default PATH resolved python3 to a broken WindowsApps stub returning Permission denied exit 126, which the gate mis-reported as 'cross-product detector test suite RED'; re-ran with /c/msys64/mingw64/bin prepended so python3=Python 3.9.7, detector self-test 10/10 OK, Gate G PASS + Gate A PASS)
  • cargo clippy-strict — incomplete (still compiling in background id biwckxqdo when StructuredOutput was force-required; last log: Compiling phase-engine + server-core, no errors yet but not finished)
  • cargo test -p phase-engine — not run (blocked behind clippy in the && chain)
  • ./scripts/gen-card-data.sh — not run
  • cargo coverage — not run (current data/card-data.json regenerated Aug 2 15:27 already shows the card is not fully supported)
  • cargo semantic-audit — not run

Scope Expansion

Added the recognizer at a second site (try_parse_verb_and_target compound-splitter probe) in addition to parse_exile_ast — the plan missed that the compound splitter intercepts "creatures and graveyards" first; both mirror the established try_parse_multi_zone_player_exile dual-site pattern. card-data.json left for the pipeline to regenerate.

Validation Failures

See review/cross-check notes.

CI Failures

  • Ultimate Nullification is NOT supported:true gap:0. Its parsed ability chain in data/card-data.json contains Effect::Unimplemented { name: 'graveyards', description: 'graveyards' } for the 'and graveyards' portion of the Oracle line 'Exile all creatures and graveyards.' The parser handles 'Exile all creatures' (ChangeZoneAll->Exile, Creature filter), the additional cost 'sacrifice a legendary creature' (Sacrifice + HasSupertype Legendary), and 'Put ~ on the bottom of its owner's library' (PutAtLibraryPosition Bottom), but the graveyard-exile clause is an unimplemented gap. The card cannot be placed in cardsSupported.
  • Verification pipeline did not complete: cargo clippy-strict was still building in the background and cargo test -p phase-engine, ./scripts/gen-card-data.sh, cargo coverage, and cargo semantic-audit had not run when StructuredOutput was force-required. Their statuses are unconfirmed.
  • Environment gap (not a code fault): no working Python on default PATH (only a permission-denied WindowsApps stub at C:/Users/jacob/AppData/Local/Microsoft/WindowsApps/python3). A working interpreter exists at C:/msys64/mingw64/bin/python3.exe; prepend that dir to PATH for any script that shells out to python3 (Gate A, cross-product detector), or Gate A will fail on every run.

Summary by CodeRabbit

  • New Features

    • Added support for mass-exile effects targeting permanent types across multiple zones, including the battlefield, graveyard, hand, library, and exile.
    • Supports combined battlefield and zone targets, multiple zone selections, and mixed permanent-type expressions.
    • Added more precise filtering for cards by type, owner, and controller.
  • Bug Fixes

    • Improved Ultimate Nullification’s sacrifice, exile, and resolution behavior.
    • Improved zone targeting and filtering for Thought Distortion.

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

coderabbitai Bot commented Aug 2, 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
📝 Walkthrough

Walkthrough

The parser now supports typed multi-zone exile and heterogeneous mass-exile expressions. It emits typed exile AST nodes and adds parser, synthesis, and integration tests for Thought Distortion and Ultimate Nullification.

Changes

Mass exile support

Layer / File(s) Summary
Zone and filter parsing
crates/engine/src/parser/oracle_effect/imperative.rs, crates/engine/src/parser/oracle_target.rs
Shared zone parsing handles singular and plural zone names. Mass-exile parsing combines battlefield permanent filters with whole-zone filters, removes duplicate zones, and validates full input consumption.
Exile AST wiring
crates/engine/src/parser/oracle_effect/imperative.rs, crates/engine/src/parser/oracle_effect/mod.rs
Multi-zone player exile preserves type filters. Heterogeneous mass exile emits one all-target exile AST node.
Parser, synthesis, and runtime validation
crates/engine/src/parser/oracle_effect/tests.rs, crates/engine/src/database/synthesis.rs, crates/engine/tests/integration/*, crates/engine/tests/integration/main.rs
Tests cover typed unions, multi-zone ownership, Thought Distortion targeting and exile, and Ultimate Nullification sacrifice and resolution behavior.

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

Possibly related PRs

  • phase-rs/phase#5871: Both changes modify exile-effect parsing in crates/engine/src/parser/oracle_effect/imperative.rs.
🚥 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 identifies the primary change, engine support for Ultimate Nullification, and accurately indicates that the implementation is partial.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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 commented Aug 2, 2026

Copy link
Copy Markdown

Generated for head 919094646178ec7c90e3a4d3809a3eba5d669387.

Parse changes introduced by this PR · 2 card(s), 6 signature(s) (baseline: main 7fa1fc4a106f)

🟢 Added (1 signature)

  • 1 card · ➕ ability/PutAtLibraryPosition · added: PutAtLibraryPosition (count=Fixed { value: 1 }, position=Bottom, target=self)
    • Affected (first 3): Ultimate Nullification

🔴 Removed (2 signatures)

  • 1 card · ➖ ability/graveyard · removed: graveyard
    • Affected (first 3): Thought Distortion
  • 1 card · ➖ ability/graveyards · removed: graveyards
    • Affected (first 3): Ultimate Nullification

🟡 Modified fields (3 signatures)

  • 1 card · 🔄 ability/ChangeZoneAll · changed field from: hand
    • Affected (first 3): Thought Distortion
  • 1 card · 🔄 ability/ChangeZoneAll · changed field target: creaturein battlefield creature or in graveyard card
    • Affected (first 3): Ultimate Nullification
  • 1 card · 🔄 ability/ChangeZoneAll · changed field target: in hand card non-creature non-landin hand/graveyard target player controls card non-creature non-land
    • Affected (first 3): Thought Distortion

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

🤖 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_effect/tests.rs`:
- Around line 24395-24405: Extend the existing filter assertion in the
multi-zone parser test to also require a Typed `TargetFilter` containing
`TypeFilter::Creature` and `FilterProp::InZone { zone: Zone::Battlefield }`.
Keep the current Card plus InAnyZone assertion, and ensure both expected legs
are validated from the same `filters` result.
- Around line 24245-24251: Correct the rules annotation above the
ultimate-nullification test: remove CR 608.2f and retain only verified
Comprehensive Rules citations whose rule text directly supports the mass exile
of creatures and graveyards and the self-return to the bottom of its owner’s
library, or remove unsupported citations. Keep the documented AST-shape
expectations unchanged.

In `@crates/engine/tests/integration/ultimate_nullification.rs`:
- Around line 177-204: Update the Err branch of the cast match in the
integration test to validate that the announcement failed specifically because
the mandatory legendary-creature sacrifice is unsatisfiable, rather than
accepting any error. Match or inspect the cast error using the concrete error
symbols available in the announcement flow, and assert the expected
sacrifice-related reason so unrelated parser, mana, or implementation failures
cannot satisfy the test.
- Around line 178-180: Update the explanatory comment in the announcement error
branch to cite CR 601.2h instead of CR 601.2f, keeping the existing description
of the engine rejecting an announcement whose total cost cannot be paid.
🪄 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: 1cb01115-e67a-40a7-a13f-3c1a443f9e11

📥 Commits

Reviewing files that changed from the base of the PR and between 9b97019 and 3dedb2f.

📒 Files selected for processing (6)
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_target.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/ultimate_nullification.rs

Comment thread crates/engine/src/parser/oracle_effect/tests.rs Outdated
Comment thread crates/engine/src/parser/oracle_effect/tests.rs
Comment thread crates/engine/tests/integration/ultimate_nullification.rs
Comment thread crates/engine/tests/integration/ultimate_nullification.rs Outdated
@matthewevans matthewevans self-assigned this Aug 3, 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.

[HIGH] The heterogeneous mass-exile recognizer claims type-qualified, owner-scoped origins that it cannot represent. Evidence: crates/engine/src/parser/oracle_effect/imperative.rs:2755-2788 accepts any leading type phrase, unconditionally scopes that leg to InZone(Battlefield), then appends an unscoped Card + InAnyZone leg. The current-head parse-diff reports Thought Distortion changing from in hand card non-creature non-land to in hand in battlefield card non-creature non-land or in graveyard card; Thought Distortion's authoritative Scryfall Oracle text is “Target opponent reveals their hand. Exile all noncreature, nonland cards from that player's hand and graveyard.” Why it matters: the hand leg becomes impossible after the injected battlefield constraint, while the graveyard leg loses both the target-player owner scope and the noncreature/nonland restriction, so resolving this spell exiles the wrong cards. Green CI does not refute this card-level semantic regression.

Please decline this form to the existing owner-scoped multi-zone parser until it can preserve both the parsed type restriction and that player's ownership across every origin, or generalize the ownership/type-origin representation at the correct seam. Add an exact Thought Distortion parser regression and a production-pipeline runtime regression that proves only the targeted opponent's noncreature, nonland cards in hand and graveyard are exiled (with opponent/other-player and creature/land controls). The Ultimate Nullification implementation can remain a separate bare-zone path.

@matthewevans matthewevans removed their assignment Aug 3, 2026
The heterogeneous "exile all <permanent types> and <zone(s)>" recognizer
(try_parse_mass_exile_permanents_and_zones) over-matched cards whose leading
leg already carries its own source-zone / owner scope, then injected
InZone(Battlefield) and rebuilt the zone leg as all-owners / all-cards. This
regressed two supported cards:

- Thought Distortion ("exile all noncreature, nonland cards from that player's
  hand and graveyard") gained an impossible hand-and-battlefield leg and lost
  both the "that player's" owner scope and the noncreature/nonland restriction
  on the graveyard leg.
- Worldfire ("exile all cards from all hands and graveyards") gained the same
  impossible battlefield injection on its hand leg.

Guard the recognizer with is_bare_battlefield_permanent_leg: only fire when the
leading permanent leg has no controller scope and no InZone/InAnyZone property
(a true battlefield permanent-type union per CR 109.2). Ultimate Nullification
("creatures and graveyards") is unaffected; Thought Distortion / Worldfire fall
back to their existing owner-scoped multi-zone parse.

Add a Thought Distortion parser regression pinning the fix.

Review nit cleanup:
- Remove unverified CR 608.2f from the parser-test annotation (a resolution-time
  simultaneity rule, not a parse-shape rule).
- Strengthen the multi-zone generalization test to also validate the
  Creature+InZone(Battlefield) leg from the same filters result.
- Tighten the integration Err branch to assert the specific
  ActionNotAllowed("...required additional cost") rejection so an unrelated
  parser/mana failure cannot satisfy it.
- Fix CR 601.2f -> CR 601.2h in the announcement-rejection comment.

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

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/engine/tests/integration/ultimate_nullification.rs (1)

41-43: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Model the printed mana cost in both fixtures.

Both tests currently cast a free spell with no seeded mana. The positive test does not verify mana payment. The negative test can report the sacrifice-specific failure only because the mana component is missing. Under CR 601.2f–h, mana and additional costs are both part of the total cost. (media.wizards.com)

  • crates/engine/tests/integration/ultimate_nullification.rs#L41-L43: set the printed cost and seed P0 with {4}{W} for the cast-and-resolve test.
  • crates/engine/tests/integration/ultimate_nullification.rs#L149-L151: set the same cost and mana pool before testing the unavailable legendary sacrifice.

Based on learnings, Oracle-text fixtures need an explicit CardBuilder::with_mana_cost call.

🤖 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/tests/integration/ultimate_nullification.rs` around lines 41 -
43, Update both fixtures in
crates/engine/tests/integration/ultimate_nullification.rs at lines 41-43 and
149-151: add an explicit CardBuilder::with_mana_cost for {4}{W} and seed P0’s
mana pool with {4}{W} before casting. Apply the same setup to both the
cast-and-resolve test and the unavailable legendary-sacrifice test so mana
payment is included alongside the additional cost.

Sources: Learnings, MCP tools

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

Outside diff comments:
In `@crates/engine/tests/integration/ultimate_nullification.rs`:
- Around line 41-43: Update both fixtures in
crates/engine/tests/integration/ultimate_nullification.rs at lines 41-43 and
149-151: add an explicit CardBuilder::with_mana_cost for {4}{W} and seed P0’s
mana pool with {4}{W} before casting. Apply the same setup to both the
cast-and-resolve test and the unavailable legendary-sacrifice test so mana
payment is included alongside the additional cost.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c2d3c821-416a-4b08-bacf-6a36b3ae0f07

📥 Commits

Reviewing files that changed from the base of the PR and between 3dedb2f and f6342d2.

📒 Files selected for processing (3)
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/tests/integration/ultimate_nullification.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs

@matthewevans matthewevans self-assigned this Aug 3, 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.

[HIGH] The Thought Distortion correction is not regression-proven at the production boundary. Evidence: crates/engine/src/parser/oracle_effect/tests.rs:24394-24470 parses only the isolated exile sentence and asserts only that some typed leg retains Hand plus the noncreature/nonland types; it never asserts the that player's owner/TargetPlayer binding, tests the complete “Target opponent reveals their hand. …” Oracle text, or resolves that spell against targeted-opponent, other-player, creature, and land controls. Why it matters: the prior defect was precisely a lost ownership restriction, so this test can pass if the fallback exiles qualifying cards from every player rather than only the targeted opponent. Please add an exact Thought Distortion parser assertion for the owner scope and a discriminating cast/resolve regression that proves only the targeted opponent's noncreature, nonland cards in hand and graveyard move.

[HIGH] The required current-head parser evidence is absent. Evidence: the comprehensive PR-comment scan contains the <!-- coverage-parse-diff --> artifact only for old head 3dedb2f35e6d5b7ea5c1f98a8aa79afd9b90ce30, while this review is for f6342d2d04337489515b08bcd6536d5220528d74; engine/parser source changed on this head. Why it matters: that artifact is the gate for confirming that the intended Thought Distortion/Worldfire reversion did not create further card-level parser regressions. Please publish a parse-diff bound to this head before re-review.

[MED] The Ultimate Nullification runtime fixtures cast a free spell instead of the printed {4}{W} spell. Evidence: crates/engine/tests/integration/ultimate_nullification.rs:41-44,151-154 call add_spell_to_hand_from_oracle(...).id() without with_mana_cost, whereas that scenario builder only creates and parses the object (crates/engine/src/game/scenario.rs:701-728) and existing spell-cast tests set a cost explicitly. Why it matters: the positive fixture therefore does not exercise payment of the real total cost, and the negative fixture can fail before the intended sacrifice-cost branch for the wrong reason. Set {4}{W} and seed matching mana in both fixtures.

@matthewevans matthewevans removed their assignment Aug 3, 2026
Addresses the second review round on PR phase-rs#6940.

Thought Distortion regression at the production boundary (was: isolated
`parse_effect_chain` on one sentence with weak assertions). The new test runs
the card's COMPLETE Oracle text through `build_oracle_face` — the same entry the
card-data pipeline uses — and pins the exact baseline shape my recognizer guard
restores:
- `RevealHand` targeting the Opponent (the owner binding),
- its `ChangeZoneAll` keeps `origin: Some(Hand)` — the ownership linkage the
  prior defect destroyed by rewriting it to `origin: None`,
- the exile filter keeps `Non(Creature)`/`Non(Land)` + `InZone(Hand)` with NO
  injected `InZone(Battlefield)`, as a single `Typed` (never an `Or`).
It also documents the boundary: the trailing "and graveyard" leg is a
pre-existing `Unimplemented` gap on main (`Effect:graveyard`) that this PR
neither closes nor worsens.

Ultimate Nullification runtime fixtures now cast the printed {4}{W} spell:
both fixtures set the real mana cost and seed matching mana. The positive
fixture therefore pays the true total cost (mana + the sacrifice additional
cost); the negative fixture has mana fully payable so the ONLY unpayable
component is the legendary sacrifice, which the tightened error assertion
confirms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@matthewevans matthewevans self-assigned this Aug 3, 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.

Actionable comments posted: 1

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

Inline comments:
In `@crates/engine/src/database/synthesis.rs`:
- Around line 10828-10829: Correct the comment near the reveal/exile logic by
removing the unsupported CR 601.2c citation and target-linked scope claim, or
replace it with a verified rule citation whose body explicitly describes the
written instructions resolving in order, using CR 608.2c only if that sequence
is what the code documents.
🪄 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: b128fbd1-161b-4434-84cf-ced499a2547b

📥 Commits

Reviewing files that changed from the base of the PR and between f6342d2 and 008c609.

📒 Files selected for processing (3)
  • crates/engine/src/database/synthesis.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/tests/integration/ultimate_nullification.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/tests/integration/ultimate_nullification.rs

Comment thread crates/engine/src/database/synthesis.rs Outdated
@matthewevans

Copy link
Copy Markdown
Member

Re-review is held for head 008c609d37a691aa22b3b2c634fcb145d79b6715: required Rust/card-data checks are still in progress, and no <!-- coverage-parse-diff --> artifact is yet bound to this head. The prior requested-changes review is stale; I will not approve or make a current-head correctness decision without the required parser evidence.

@matthewevans matthewevans removed their assignment Aug 3, 2026
…t Distortion)

Closes the last open item on PR phase-rs#6940's review: implement — not just decline —
the owner-scoped, type-restricted, two-zone exile so Thought Distortion fully
resolves, with a discriminating runtime regression.

Previously `try_parse_multi_zone_player_exile` claimed only the BARE "cards from
<owner> <zone> and <zone>" form (Identity Crisis, CR 108.2). A type-qualified
variant — Thought Distortion's "exile all noncreature, nonland cards from that
player's hand and graveyard" — fell through: the hand leg parsed as a single-zone
exile and "and graveyard" was orphaned into an `Unimplemented { "graveyard" }`
no-op (card marked unsupported, gap `Effect:graveyard`).

Generalize the recognizer to carry an optional leading type restriction
(CR 205.2a/205.3a): it now parses "[<types>] cards from <owner-possessive>
<zone-union>" and returns the card-type filters alongside the owner axis and the
zone union. The bare form is tried first and keeps empty type_filters, so its
representation (and Identity Crisis's parse) is byte-identical. Both dual-site
call sites (parse_exile_ast + the compound-splitter probe) build a single
`ChangeZoneAll { origin: None, Typed { <types>, controller: TargetPlayer,
InAnyZone([Hand, Graveyard]) } }`. Runtime resolution is inherited from the
existing multi-zone owner-exile path (Identity Crisis), so `TargetPlayer` binds
to the revealed opponent and the exile is confined to that player's zones.

Thought Distortion is now fully supported (no coverage gap). Tests:
- runtime regression (tests/integration/thought_distortion.rs): casting the real
  card exiles ONLY the targeted opponent's noncreature/nonland cards, from BOTH
  hand and graveyard — with owner-scope controls (the caster's own cards stay)
  and type controls (the target's creature/land cards stay). The graveyard
  assertions are the revert-failing authority.
- production-boundary parse test (synthesis): asserts the owner-scoped,
  type-restricted, InAnyZone([Hand, Graveyard]) shape and zero gaps.
- matcher unit test: bare (empty type_filters) and type-qualified cases.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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

Inline comments:
In `@crates/engine/tests/integration/thought_distortion.rs`:
- Around line 52-57: Add a P1-owned land card to the graveyard setup alongside
opp_gy_noncreature and opp_gy_creature, then extend the assertions in the
affected thought-distortion test to verify that this card remains in
Zone::Graveyard. Ensure the assertion exercises the Graveyard leg’s nonland
filtering rather than only validating creature and instant entries.
🪄 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: 242010f2-d3e5-4ae6-b42f-de1455efd156

📥 Commits

Reviewing files that changed from the base of the PR and between 008c609 and 9793b5f.

📒 Files selected for processing (6)
  • crates/engine/src/database/synthesis.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/thought_distortion.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/engine/tests/integration/main.rs
  • crates/engine/src/database/synthesis.rs
  • crates/engine/src/parser/oracle_effect/tests.rs

Comment thread crates/engine/tests/integration/thought_distortion.rs
@matthewevans matthewevans self-assigned this Aug 3, 2026
@matthewevans matthewevans added the bug Bug fix label Aug 3, 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.

[HIGH] The Thought Distortion runtime regression does not prove the nonland restriction for its graveyard origin. Evidence: crates/engine/tests/integration/thought_distortion.rs:52-57 creates only an instant and a creature in P1's graveyard, while :86-102 asserts only that the instant moves and the creature remains; the sole land control is in P1's hand at :49-50. Why it matters: a regression that preserves the type restriction for Hand but drops Non(Land) while collecting the Graveyard leg would still pass, even though this PR newly claims one filter over both origins. Suggested fix: add a P1-owned land card in the graveyard and assert that it remains there after the real cast/resolve pipeline.

[HIGH] The required parser coverage artifact is stale for this implementation head. Evidence: the only <!-- coverage-parse-diff --> PR comment is generated for 008c609d37a691aa22b3b2c634fcb145d79b6715, while this review covers parser changes at 9793b5feadd774df68c9ffa48ced360ae680e542; crates/engine/src/parser/oracle_effect/imperative.rs:2682-2772 changes the parser surface. Why it matters: without a head-bound card-level diff, this review cannot establish that the new type-qualified multi-zone recognizer has no unintended parser blast radius. Suggested fix: after the regression update, publish a completed coverage-parse-diff artifact bound to the resulting head and re-request review.

@matthewevans

Copy link
Copy Markdown
Member

[MED] The new Thought Distortion synthesis test documents the target linkage with an unverified CR 601.2c claim. Evidence: crates/engine/src/database/synthesis.rs:10826-10827 says the reveal “establishes the owner binding” and cites target selection. Why it matters: the test should assert the parser/runtime data flow without attaching a rule citation that does not document that anaphoric binding; this leaves a misleading CR annotation in rules-adjacent code. Suggested fix: remove that citation/claim or replace it only after verifying a CR section that directly supports the stated behavior.

@matthewevans matthewevans removed their assignment Aug 3, 2026
JacobWoodson and others added 4 commits August 2, 2026 22:15
The head-noun guard used .strip_suffix(" cards")/(" card") for parse dispatch,
which the nom-combinator mandate gate (Gate A) rejects. Drop it: parse_type_phrase
must FULLY consume the delimited head, and the owner-possessive + 2-zone-union
structure is already the discriminator, so the manual suffix guard was redundant.
Behavior and all type_filters are unchanged (parse_type_phrase output is identical).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review: the runtime regression only proved 'nonland' for the hand origin (the
sole land control was in P1's hand); a regression dropping Non(Land) for just the
graveyard leg would still pass. Add a P1-owned land card in the graveyard and
assert it stays, so the nonland restriction is proven on BOTH origins of the
single owner-scoped multi-zone filter. Adds a reusable add_land_to_graveyard
scenario helper mirroring add_creature_to_graveyard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@matthewevans matthewevans self-assigned this Aug 3, 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.

The revised runtime fixture now covers the target opponent’s graveyard land and the current parse-diff artifact reports the intended two signature additions. I found one remaining current-head issue before this can be approved.

MED — crates/engine/src/database/synthesis.rs:10826-10827: remove or correct the CR citation. The assertion is a useful check of the parser’s anaphoric target binding, but CR 601.2c governs choosing targets when casting; it does not establish the subsequent that player's binding claimed by this comment. Please either use a directly verified rule citation that supports the stated relationship or remove the rule annotation and describe this solely as parser-chain behavior. The same finding remains unresolved in the current review thread: #6940 (comment).

Evidence reviewed at 6c0cf885743fff6ca072bc89a0d7908cf1e7fea8: the exact-head coverage-parse-diff artifact, the new Thought Distortion runtime fixture, source diff, and all required CI checks (green). No local build was run.

@matthewevans matthewevans removed their assignment Aug 3, 2026
Per review: CR 601.2c governs choosing targets during casting, not the
subsequent "that player's" anaphoric binding the comment attached it to. Remove
the citation from both the doc comment and the inline reveal-assertion comment;
describe the reveal->exile owner linkage as parser-chain/anaphora resolution
(template behavior, not a numbered rule). Comment-only; no behavior change.

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

Copy link
Copy Markdown
Contributor Author

@matthewevans thanks — addressed the remaining MED.

CR 601.2c citation (synthesis.rs) — you're right that 601.2c governs choosing targets during casting, not the subsequent that player's anaphor. Removed the rule citation in both spots (the doc comment and the inline comment at the reveal assertion) and described it purely as parser-chain/anaphora resolution — the reveal names the target opponent and the exile's "that player" anaphor resolves to that same antecedent, which is template/parser behavior, not a numbered rule. No CR annotation now claims a relationship it doesn't support.

While there I re-verified the remaining CR citations in the new code against docs/MagicCompRules.txt: CR 400.3 (ownership), CR 402.1 (hand), CR 404.1 (graveyard), CR 205.2a (card types) — all accurate to the cited text.

This is a comment-only change; parser output, the parse-diff, and all tests are unaffected. Ready for re-review.

@matthewevans matthewevans self-assigned this Aug 3, 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 for the current head 919094646178ec7c90e3a4d3809a3eba5d669387.

The current parse-diff is bound to this head and matches the reviewed Ultimate Nullification and Thought Distortion changes. Required Rust and frontend checks are green; the prior owner/type/zone, graveyard-land, real-cost, and CR-annotation findings are resolved with production-boundary regressions.

@matthewevans
matthewevans added this pull request to the merge queue Aug 3, 2026
@matthewevans matthewevans removed their assignment Aug 3, 2026
Merged via the queue into phase-rs:main with commit 470ba2e Aug 3, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix 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