Skip to content

Fix True-Name Nemesis chosen-player protection - #7213

Open
traemyn wants to merge 4 commits into
phase-rs:mainfrom
traemyn:fix/issue-5941-true-name-protection
Open

Fix True-Name Nemesis chosen-player protection#7213
traemyn wants to merge 4 commits into
phase-rs:mainfrom
traemyn:fix/issue-5941-true-name-protection

Conversation

@traemyn

@traemyn traemyn commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #5941. Fixes the True-Name Nemesis protection misparse by representing “the chosen player” as a typed protection target and resolving it from the protected permanent’s persisted choice, so objects controlled by that player cannot target it.

Files changed

  • crates/engine/src/game/keywords.rs
  • crates/engine/src/game/static_abilities.rs
  • crates/engine/src/types/keywords.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs

Track

Developer

LLM

Model: GitHub Copilot (via GitHub Copilot; canonical id not exposed)
Tier: Frontier
Thinking: high

Implementation method (required)

Method: /engine-implementer

CR references

  • CR 702.16
  • CR 109.4

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.

  • 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 phase-engine parse_protection_target_chosen_player — passed: test types::keywords::tests::parse_protection_target_chosen_player ... ok

  • cargo test -p phase-engine --test integration issue_5941_true_name_chosen_player_protection — passed: test issue_5941_true_name_chosen_player_protection::true_name_protection_uses_the_protected_objects_chosen_player ... ok

  • cargo test -p phase-engine --lib --quiet — passed: 18833 passed; 0 failed; 6 ignored; finished in 26.65s

  • cargo clippy -p phase-engine --all-targets -- -D warnings — passed.

  • cargo fmt --all — passed.

  • git diff --check — passed.

  • Independent final review-impl — LGTM.

Gate A

Gate G PASS (router/grant architecture: strict router vs permissive grant boundary intact)
Gate A PASS head=b71eec325dc0c7a1a46dbcbd4cf9a1ba5be5973f base=97591656218103d8e8c7315725b24cfe64645dd4

Anchored on

  • crates/engine/src/game/keywords.rs:533 — existing chosen-color and chosen-card-type protection matching reads durable choices from the protected object.
  • crates/engine/src/types/keywords.rs:2877 — existing chosen-color and chosen-card-type parser arms use typed protection variants for runtime resolution.
  • crates/engine/tests/integration/serras_emissary_chosen_card_type_protection.rs — existing production-path choice-to-targeting regression structure.

Final review-impl

Final review-impl PASS head=b71eec325dc0c7a1a46dbcbd4cf9a1ba5be5973f

Claimed parse impact

True-Name Nemesis — expected parser classification changes from the stringly typed CardType("the chosen player") form to ProtectionTarget::ChosenPlayer when generated card data is regenerated. The generated card-data artifact was not available in this worktree, so no artifact parse-diff was produced.

Scope Expansion

None.

Validation Failures

  • cargo clippy --all-targets -- -D warnings — blocked before linting because openssl-sys v0.9.116 could not find an OpenSSL installation and requires OPENSSL_DIR.
  • cargo test -p phase-engine — started successfully with many passing tests but exceeded the 120-second safety timeout and was killed while loop_shortcut::an_evicting_beat_mints_without_growing_the_ring was still running.

CI Failures

None.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed protection effects that target a chosen player.
    • Sources controlled by the chosen player can no longer interact with protected permanents.
    • Other players’ sources remain eligible to interact as expected.
  • Tests

    • Added coverage for chosen-player selection and protection behavior.

@traemyn
traemyn requested a review from matthewevans as a code owner August 10, 2026 17:41
@coderabbitai

coderabbitai Bot commented Aug 10, 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: 33 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: dd6c060f-af37-4b85-bd15-c36c4b54d4f8

📥 Commits

Reviewing files that changed from the base of the PR and between 3c6294a and 67d9d45.

📒 Files selected for processing (2)
  • crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs
  • crates/engine/tests/integration/main.rs
📝 Walkthrough

Walkthrough

The engine parses ProtectionTarget::ChosenPlayer, stores the selected player, and blocks sources controlled by that player from targeting the protected permanent. Unit and integration tests cover parsing and target legality.

Changes

Chosen-player protection

Layer / File(s) Summary
Protection contract and parsing
crates/engine/src/types/keywords.rs
Adds ProtectionTarget::ChosenPlayer, parses both chosen-player text forms, and tests Keyword::from_str integration.
Protection matching and integration
crates/engine/src/game/keywords.rs, crates/engine/src/game/static_abilities.rs, crates/engine/tests/integration/*
Matches source controllers against the chosen player, excludes chosen-player protection from unsupported player-level checks, and verifies target filtering in the True-Name Nemesis scenario.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

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 describes the fix for True-Name Nemesis's chosen-player protection.
Linked Issues check ✅ Passed The changes implement chosen-player protection and add regression coverage for issue #5941.
Out of Scope Changes check ✅ Passed All changes support the linked issue through parser, runtime, and integration-test updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

Inline comments:
In `@crates/engine/src/game/keywords.rs`:
- Around line 545-549: Update ProtectionTarget::ChosenPlayer in
crates/engine/src/game/keywords.rs:545-549 to match the chosen player against
source.owner when source.controller is absent, while retaining controller
matching for controlled sources and using owner for nonbattlefield player-scoped
queries. Update the documentation in crates/engine/src/types/keywords.rs:505-507
to cite CR 702.16k and describe both controller and ownership protection
behavior.

In
`@crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs`:
- Around line 35-41: Update the test setup around the direct
WaitingFor::NamedChoice assignment to enter True-Name Nemesis through the
production enter-the-battlefield path instead of prepopulating post-dispatch
state. Assert that this path produces the expected WaitingFor::NamedChoice, then
submit GameAction::ChooseOption and retain the existing target-legality
assertions.
🪄 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: bae2b917-67e3-4060-b068-437d4d432a3e

📥 Commits

Reviewing files that changed from the base of the PR and between 1bb6c1d and b71eec3.

📒 Files selected for processing (5)
  • crates/engine/src/game/keywords.rs
  • crates/engine/src/game/static_abilities.rs
  • crates/engine/src/types/keywords.rs
  • crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs
  • crates/engine/tests/integration/main.rs

Comment thread crates/engine/src/game/keywords.rs Outdated
Comment on lines +545 to +549
// CR 702.16 + CR 109.4: the source is protected against objects
// controlled by the player chosen for the protected permanent.
ProtectionTarget::ChosenPlayer => protected
.chosen_player()
.is_some_and(|player| source.controller == player),

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

Implement the ownership leg of protection from the chosen player.

CR 702.16k covers objects controlled by the chosen player and objects owned by that player that another player does not control. The current comparison only checks source.controller, so nonbattlefield sources owned by the chosen player can bypass protection. CR 109.4 also states that objects outside the stack and battlefield have no controller. (media.wizards.com)

  • crates/engine/src/game/keywords.rs#L545-L549: Match the chosen player against the source owner when the source has no controller, while retaining controller matching for controlled sources.
  • crates/engine/src/types/keywords.rs#L505-L507: Replace the CR 109.4 citation with the applicable CR 702.16k behavior and document the ownership case.

As per path instructions, player-scoped queries on nonbattlefield zones must filter by obj.owner, not controller.

📍 Affects 2 files
  • crates/engine/src/game/keywords.rs#L545-L549 (this comment)
  • crates/engine/src/types/keywords.rs#L505-L507
🤖 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/game/keywords.rs` around lines 545 - 549, Update
ProtectionTarget::ChosenPlayer in crates/engine/src/game/keywords.rs:545-549 to
match the chosen player against source.owner when source.controller is absent,
while retaining controller matching for controlled sources and using owner for
nonbattlefield player-scoped queries. Update the documentation in
crates/engine/src/types/keywords.rs:505-507 to cite CR 702.16k and describe both
controller and ownership protection behavior.

Sources: Path instructions, MCP tools

Comment thread crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs Outdated
@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

Generated for head 67d9d4506154098ead179d8638099c5b29b5ffe0.

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans matthewevans self-assigned this Aug 10, 2026
@matthewevans matthewevans added the bug Bug fix label Aug 10, 2026
@matthewevans

Copy link
Copy Markdown
Member

Held pending current-head CI. I pushed maintainer commit 3c6294a50bfdc307b981ba70ebce890c6a2d2191 to make the regression exercise the actual cast → as-enters → named-choice path. Please let the required checks and the head-bound parse-diff artifact complete; I will resume approval and enqueue once that evidence is green for this head.

@matthewevans

Copy link
Copy Markdown
Member

Held pending current-head validation. I pushed 67d9d4506154098ead179d8638099c5b29b5ffe0, which merges current main and advances the regression to the existing legal main-phase test helper before its real cast → as-enters → named-choice path. CI and the head-bound parse-diff artifact are queued for that exact head.

I also confirmed the remaining unresolved CodeRabbit finding is rules-valid: CR 702.16k protects from objects the chosen player controls and objects that player owns but no other player controls. The present ChosenPlayer matcher checks only source.controller; resolving that semantic gap needs a deliberately scoped engine fix and discriminating coverage, so this PR is not approved or enqueued on the current head.

@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] ChosenPlayer implements only the controller half of protection from a player. Evidence: crates/engine/src/game/keywords.rs:547-549 compares only source.controller, while CR 702.16k also covers an object the chosen player owns when no other player controls it. This is not theoretical in this engine: crates/engine/src/game/effects/change_zone.rs:1698-1719 and its regression at :4582-4620 document that a stolen permanent retains its former controller after moving to its owner's graveyard, where player-scoped semantics must use owner. Why it matters: a chosen player's off-battlefield source can incorrectly bypass protection, and a stale controller can make an object owned by someone else incorrectly match. Suggested fix: make the ChosenPlayer arm use the engine's owner-vs-controller-by-zone authority (controller for controlled sources; owner where the object has no controller), document CR 702.16k on the variant, and add discriminating tests for both a selected owner's stolen-then-died source and the inverse stale-controller case.

Current-head CI also fails before the claimed production regression reaches its as-enters path: Rust tests (shard 2/4) reports InvalidAction("apply_as_current: no authorized submitter (game over?)") at crates/engine/tests/integration/issue_5941_true_name_chosen_player_protection.rs:46. This is supporting evidence only; it does not replace the rules-correctness fix above.

@matthewevans matthewevans removed their assignment Aug 11, 2026
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.

true name nemesis — True name nemesis was able to be targeted by my opponent's Song of the Dryads even though i named p…

2 participants