Skip to content

feat(parser): support “enter tapped this turn” (Due Respect, Nahiri’s Lithoforming)#5222

Merged
matthewevans merged 1 commit into
phase-rs:mainfrom
codevector96:fix/enters-tapped-this-turn
Jul 7, 2026
Merged

feat(parser): support “enter tapped this turn” (Due Respect, Nahiri’s Lithoforming)#5222
matthewevans merged 1 commit into
phase-rs:mainfrom
codevector96:fix/enters-tapped-this-turn

Conversation

@codevector96

Copy link
Copy Markdown
Contributor

Summary

Implements the previously-unsupported "enter tapped this turn" replacement, so Due Respect ("Permanents enter tapped this turn. Draw a card.") and Nahiri's Lithoforming ("… Lands you control enter tapped this turn.") stop dropping that clause to Effect::Unimplemented.

The clause is a turn-duration floating replacement: on resolution it installs a global replacement that makes a filtered set of permanents enter the battlefield tapped, expiring at cleanup. It reuses the existing external "[objects] enter tapped" building block (the same ReplacementDefinition{ event: ChangeZone, execute: SetTapState{Tap} } shape that Authority of the Consuls already uses) — this is just its turn-scoped, spell-installed variant. No new engine surface: one new private parser function lifts the clause into the existing Effect::AddTargetReplacement { target: None } installer with an EndOfTurn expiry; the resolver, floating-replacement scan, controller-anchoring, and cleanup pruning are all unchanged.

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

Implementation method (required)

  • Produced via the /engine-implementer pipeline (plan → review-plan → implement → review-impl → commit)

Anchored on

  • crates/engine/src/parser/oracle_effect/mod.rstry_parse_global_damage_modification_replacement: the sibling that wraps a parse_replacement_line def in Effect::AddTargetReplacement { target: None, expiry: EndOfTurn }; the new function mirrors its shape (different seam: the pre-peel "this turn" scan block, since the duration is trailing).
  • crates/engine/src/parser/oracle_replacement.rsparse_external_enters_tapped / build_external_entry_replacement: the existing ChangeZone + SetTapState{SelfRef, Tap} builder reused as the detector.
  • The existing scan_contains_phrase(&original_lower, "this turn") arms (try_parse_play_from_exile, try_parse_additional_land_this_turn): the new arm copies their peel_ctx optional/condition handling and attach_unless_slots return.

Gate A

scripts/check-parser-combinators.sh passes on the staged diff (no violations). The new detection delegates entirely to strip_trailing_duration + parse_replacement_line (nom end-to-end) plus a typed matches! shape guard — no string-method dispatch, match-arm string literals, chained if let Ok = tag(...), verbatim-sentence equality, or hand-built Effect::Unimplemented.

CR references

  • CR 614.1d — "[Objects] enter [the battlefield] …" replacement effects (the external enters-tapped class; distinct from the self form in 614.1c).
  • CR 514.2 — "this turn" effects end during cleanup (the EndOfTurn expiry).
  • CR 611.2a — a resolution-fixed continuous effect (AddTargetReplacement { target: None }, controller anchored at install).
  • CR 109.4 — controller-relative object references ("Lands you control").

Verification

Built and tested with cargo (this contributor box has no Tilt):

tests/enters_tapped_this_turn.rs ... 5 passed; 0 failed
engine --lib parser::oracle_effect ... 2496 passed; 0 failed
  • New tests (crates/engine/tests/enters_tapped_this_turn.rs, typed fixtures, real cast/resolve pipeline): parser SHAPE for both cards (with a zero-Unimplemented reach-guard); a permanent entering after Due Respect resolves enters tapped while one that entered before is unaffected; the replacement expires at cleanup (later-turn entries untapped); and the load-bearing Nahiri multi-authority test — P0's land enters tapped while P1's land and P0's non-land do not (proving the source_controller + "Lands you control" filter).
  • Fail-closed: any duration other than end-of-turn, or any non-ChangeZone/SetTapState shape, returns None and the clause stays Unimplemented — coverage flips to supported only for what's genuinely handled.

… Lithoforming)

"Permanents enter tapped this turn." (Due Respect) and "Lands you control enter
tapped this turn." (Nahiri's Lithoforming) previously dropped that clause to
Effect::Unimplemented.

Lift the clause into a turn-duration floating replacement: a new parser helper
strips the trailing "this turn" duration, hands the bare "[subject] enter tapped"
body to the existing external-replacement parser (the same ChangeZone +
SetTapState{Tap} shape Authority of the Consuls uses), and emits it as
Effect::AddTargetReplacement { target: None } with an EndOfTurn expiry. The
resolver installs it globally, anchors source_controller to the caster (so
Nahiri's "Lands you control" taps only your lands), and cleanup prunes it so
later-turn entries are unaffected. No new engine surface — reuses
AddTargetReplacement, ChangeZone, SetTapState, RestrictionExpiry::EndOfTurn.

Fail-closed: any non-end-of-turn duration or non-enters-tapped shape stays
Unimplemented, so coverage flips to supported only for what is genuinely handled.

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

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request adds a new integration test suite in crates/engine/tests/enters_tapped_this_turn.rs to verify the parser shape and runtime behavior of turn-duration floating replacement effects, specifically testing cards like Due Respect and Nahiri's Lithoforming. The tests cover parsing verification, replacement execution, cleanup-phase expiry, and controller-scoped filtering. There are no review comments, and I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 2 card(s), 3 signature(s) (baseline: main 3d347a3fc7cf)

2 card(s) · ability/AddTargetReplacement · added: AddTargetReplacement (destination=Battlefield, event=ChangeZone, expiry=EndOfTurn)

Examples: Due Respect, Nahiri's Lithoforming

1 card(s) · ability/lands · removed: lands (duration=until end of turn)

Examples: Nahiri's Lithoforming

1 card(s) · ability/permanents · removed: permanents (duration=until end of turn)

Examples: Due Respect

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

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

Maintainer review: parser change is narrowly scoped, reuses the existing external-entry replacement parser and AddTargetReplacement resolver, has current parse-diff evidence for only Due Respect and Nahiri’s Lithoforming, and CI is green.

@matthewevans matthewevans added the enhancement New feature or request label Jul 7, 2026
@matthewevans matthewevans added this pull request to the merge queue Jul 7, 2026
@matthewevans matthewevans removed their assignment Jul 7, 2026
Merged via the queue into phase-rs:main with commit 2c83a94 Jul 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants