Skip to content

test(engine): positively discriminate the assimilate target filter - #7108

Merged
matthewevans merged 2 commits into
mainfrom
ship/assimilate-target-filter-discrimination
Aug 8, 2026
Merged

test(engine): positively discriminate the assimilate target filter#7108
matthewevans merged 2 commits into
mainfrom
ship/assimilate-target-filter-discrimination

Conversation

@matthewevans

@matthewevans matthewevans commented Aug 8, 2026

Copy link
Copy Markdown
Member

Follow-up items from #7096 (the assimilate keyword action). Test-strengthening plus a CR-citation
correction; no production behavior changes.

What changed

1. The assimilate target-filter tests now discriminate positively. Tests 3a/3b previously declared
zero targets and asserted only that nothing moved. That passes for the right reason and also for two
wrong ones, so it could not distinguish a correct filter from a filter that matches nothing. Both now
declare a bait object first and assert the split:

  • the legal card is taken (positive leg — proves the production fired on this fixture), and
  • the illegal one is not, despite being declared first (negative leg).

3a discriminates a creature card from a land in the same graveyard; 3b discriminates an opponent's
graveyard from the controller's own.

Declaration order is load-bearing here, and the shipped doc comment says so explicitly, because the
target-slot machinery has three states and only the middle one is sensitive to order:

legal set behavior
exactly one auto_select_targets_for_ability returns Some -> AutoAssigned, no prompt, order irrelevant
two or more NeedsPlayerChoice -> pick_slot_target takes the first-declared legal object — order decides
empty build_target_slots errors, so no slot is ever built; CR 603.3d removes the ability from the stack

That third row is why declaration order is documented as a regression-only instrument rather than a
guarantee: an under-matching filter never reaches pick_slot_target at all, so the negative leg alone
cannot catch it — the paired positive leg is what does.

2. Recruit CR citations corrected. imperative.rs cited CR 701.9a (Discard) for Recruit. Recruit
is CR 701.70a, verified against docs/MagicCompRules.txt:3877:

701.70a "Recruit" means "Draw a card, then discard a card. If you discarded a nonland card this
way, create a 1/1 white Human Soldier creature token."

The two sites get different treatment, which that rule text justifies directly:

  • the keyword-action comment was simply wrong -> 701.9a replaced by 701.70a;
  • the contingent-token comment describes a genuine interaction, so it now cites both: the discard
    is 701.9a, and the nonland contingency that makes the discard result load-bearing is 701.70a.

Every other CR 701.9a in the file was checked and is a genuine Discard site, left alone.

3. A parser-only test moved to the parser suite. The assimilate-without-a-graveyard-target
fail-closed test asserted on parse_effect output and did not need the runtime harness, so it now
lives in parser/oracle_effect/tests.rs beside the existing shape test. Engine integration test count
drops 8 -> 7, and two now-dead imports were removed. The second commit fixes two comment incoherences
this relocation introduced (the integration module doc named only one of the two parser tests, and the
relocated test kept a 3c. ordinal indexing a scheme in a different file).

Validation

  • test-engine: green on exactly these bytes (23251/23251 on the first candidate; re-verified
    green after the comment fixes, with build start timestamps confirmed to post-date every edit).
  • Parse-neutrality: measured EMPTY, not argued. A two-sided oracle-gen + coverage-report
    projection from a pinned AtomicCards.json produced a byte-identical card-data.json, and a
    negative control (one card's parse_details mutated) produced 2 clusters, proving the empty result
    discriminates rather than being vacuous.
  • CR numbers grep-verified against docs/MagicCompRules.txt with a positive control, per the
    project's mandatory-verification rule.

Validation gap, stated explicitly

Local clippy could not return a verdict on this change. The shared checkout currently fails to
compile phase-engine (lib) because of an unrelated concurrent change
(crates/engine/src/types/ability_visit.rs: visit_ability_def_scoped referenced at four sites with
no definition — another agent mid-refactor, not touched here). That failure stops upstream of the
test "integration" target where this PR's doc comments live, so it is neither a pass nor a fail for
this change. CI builds from the committed tree, which does not contain that broken file, so CI's
clippy run is the authoritative — and uncontaminated — gate here.

The residual risk is confined to doc lints. doc_lazy_continuation (which did fire on this file
earlier in development, so the lint demonstrably reaches this surface) requires a preceding markdown
list item; the comment edits in the second commit are plain prose with no list marker, and one of them
removes a 3c. token that was never a valid ordered-list marker.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Assimilate handling so only eligible cards matching the required creature type and ownership can be moved from a graveyard.
    • Invalid non-graveyard Assimilate phrasing is now safely rejected instead of being interpreted as a valid effect.
  • Tests

    • Added regression coverage for valid and invalid Assimilate scenarios, including cases involving opponent-owned cards and nonmatching creature types.
  • Documentation

    • Updated rule references and parser behavior notes for greater accuracy.

Tests 3a and 3b asserted only that an illegal candidate stayed in its
graveyard, which is equally consistent with "the filter rejected it" and
"the ETB production never fired at all". Seed a legal AND an illegal
candidate in one fixture and declare the illegal one FIRST, so the filter
is observable across all three reachable states:

  correct     the legal set is a singleton, so
              auto_select_targets_for_ability assigns it and no prompt is
              raised; the legal card enters, the illegal one stays
  over-match  two legal candidates, auto-selection declines, a required
              slot IS created, and pick_slot_target fills it from the
              DECLARED order -- taking the bait, flipping both legs
  under-match empty legal set, so CR 603.3d removes the trigger before
              any slot exists and the POSITIVE leg fails

Declaration order is a regression-only instrument: it converts the
over-match case from pick_slot_target's no-declared-target panic into a
clean assertion failure. Each test holds two filter legs constant and
varies one, so 3a isolates the Creature type leg and 3b the ownership leg
(CR 109.4: a graveyard card has no controller, so "an opponent's
graveyard" rides as ownership). InZone { Graveyard } is isolated by
neither and keeps only its parser-level coverage.

Both tests are now revert-failing. 3b previously passed with the lowering
reverted, because nothing moved at all, and its doc comment claimed only
forward-guard status; that claim is updated rather than left stale.

Correct Recruit's mis-cited keyword-action rule. CR 701.9 is Discard;
Recruit is CR 701.70a. The "standalone keyword action" comment is purely
a 701.70a claim, so it is replaced. The discard-result comment genuinely
involves both rules -- the discard is 701.9a, the nonland contingency is
701.70a -- so it gains 701.70a instead. Two further Recruit-tagged 701.9a
sites are deliberately left alone: they assert snapshot and provenance
semantics already covered by 701.9a + 608.2c + 400.7, and assert nothing
that 701.70a supplies.

Relocate the parser-only fail-closed test beside its sibling shape test
in the parser module, and drop the two imports it was the last user of.

Replace the ephemeral BASE_SHA run identifier in three doc comments with
a stable PR reference. BASE_SHA is an implementation-run artifact whose
meaning changes every run and is undefined to a reader of the repository.
Two comment incoherences introduced by the preceding commit, which moved the
parser-only fail-closed test out of the integration suite and into
`parser/oracle_effect/tests.rs`.

`borg_queen_assimilate.rs`'s module doc pointed at the parser file but named
only `borg_queen_assimilate_lowers_to_reanimate_then_retype_chain`, so the
relocated `assimilate_without_a_graveyard_target_stays_unimplemented` was
referenced from nowhere in the tree — the one thing the relocation was supposed
to preserve was the ability to find it. Both are now named, and labelled by what
they assert (positive lowering / fail-closed negative) rather than listed.

The relocated test also kept its `3c.` ordinal, which indexed the integration
file's 3a/3b/3c scheme. In its new home that prefix points at nothing. Dropped;
the doc comment already states what the test discriminates.

Comment-only: 0 non-comment lines change. Verified via `git diff -U0` with
comment lines filtered out, so this cannot alter parse output, and
`crates/engine/src/parser/oracle_effect/tests.rs` is gated behind
`#[cfg(test)] mod tests;` and is never compiled into the `oracle-gen` binary
that generates card data.
@matthewevans
matthewevans enabled auto-merge August 8, 2026 20:05
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The parser tests now cover fail-closed assimilate phrasing and valid graveyard lowering. Integration tests verify creature-type and opponent-ownership filtering. Recruit comments include CR 701.70a references.

Changes

Assimilate regression coverage

Layer / File(s) Summary
Parser references and assimilate coverage
crates/engine/src/parser/oracle_effect/imperative.rs, crates/engine/src/parser/oracle_effect/tests.rs
Recruit comments now reference CR 701.70a. Parser tests cover unsupported non-graveyard assimilate phrasing and valid opponent-graveyard lowering.
Integration candidate filtering
crates/engine/tests/integration/borg_queen_assimilate.rs
Integration tests verify creature-type and opponent-ownership filtering. Documentation and revert-failure notes now describe the parser coverage.

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

Possibly related PRs

  • phase-rs/phase#7003: Adds related fail-closed parser regression tests for Effect::Unimplemented.

Suggested labels: bug

Suggested reviewers: andriypolanski, jsdevninja

🚥 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 summarizes the main change: adding positive discrimination to the assimilate target-filter tests.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ship/assimilate-target-filter-discrimination

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 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/parser/oracle_effect/imperative.rs`:
- Line 9816: Remove the unrelated CR 608.2c citation from the comment describing
Recruit as a standalone keyword action, retaining only CR 701.70a. Leave the
ordered-resolution comment near the later instruction-resolution logic
unchanged.
🪄 Autofix

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: 961600e6-7470-4725-8201-574f3b60ed3d

📥 Commits

Reviewing files that changed from the base of the PR and between 42712cd and 442aaf9.

📒 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/borg_queen_assimilate.rs

}

// CR 701.9a + CR 608.2c: Recruit is a standalone keyword action. Keep this
// CR 701.70a + CR 608.2c: Recruit is a standalone keyword action. Keep this

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the unrelated CR citation.

Line 9816 documents recognition of Recruit as a standalone keyword action. It does not document written instructions resolving in order. Remove CR 608.2c and retain CR 701.70a. The ordered-resolution comment at Lines 11945-11948 can retain CR 608.2c.

Based on learnings, cite CR 608.2c only when the comment documents written instructions resolving in order.

🤖 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/imperative.rs` at line 9816, Remove
the unrelated CR 608.2c citation from the comment describing Recruit as a
standalone keyword action, retaining only CR 701.70a. Leave the
ordered-resolution comment near the later instruction-resolution logic
unchanged.

Sources: Coding guidelines, Path instructions, Learnings

@matthewevans
matthewevans added this pull request to the merge queue Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Generated for head 442aaf910c79192d1c18d0c68a4e1d833542fa23.

Parse changes introduced by this PR

✓ No card-parse changes detected.

Merged via the queue into main with commit d46667f Aug 8, 2026
13 checks passed
@matthewevans
matthewevans deleted the ship/assimilate-target-filter-discrimination branch August 8, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant