feat(parser): support “enter tapped this turn” (Due Respect, Nahiri’s Lithoforming)#5222
Conversation
… 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>
There was a problem hiding this comment.
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.
Parse changes introduced by this PR · 2 card(s), 3 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
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.
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 existingEffect::AddTargetReplacement { target: None }installer with anEndOfTurnexpiry; 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)
/engine-implementerpipeline (plan → review-plan → implement → review-impl → commit)Anchored on
crates/engine/src/parser/oracle_effect/mod.rs—try_parse_global_damage_modification_replacement: the sibling that wraps aparse_replacement_linedef inEffect::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.rs—parse_external_enters_tapped/build_external_entry_replacement: the existingChangeZone+SetTapState{SelfRef, Tap}builder reused as the detector.scan_contains_phrase(&original_lower, "this turn")arms (try_parse_play_from_exile,try_parse_additional_land_this_turn): the new arm copies theirpeel_ctxoptional/condition handling andattach_unless_slotsreturn.Gate A
scripts/check-parser-combinators.shpasses on the staged diff (no violations). The new detection delegates entirely tostrip_trailing_duration+parse_replacement_line(nom end-to-end) plus a typedmatches!shape guard — no string-method dispatch, match-arm string literals, chainedif let Ok = tag(...), verbatim-sentence equality, or hand-builtEffect::Unimplemented.CR references
EndOfTurnexpiry).AddTargetReplacement { target: None }, controller anchored at install).Verification
Built and tested with cargo (this contributor box has no Tilt):
crates/engine/tests/enters_tapped_this_turn.rs, typed fixtures, real cast/resolve pipeline): parser SHAPE for both cards (with a zero-Unimplementedreach-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 thesource_controller+ "Lands you control" filter).ChangeZone/SetTapStateshape, returnsNoneand the clause staysUnimplemented— coverage flips to supported only for what's genuinely handled.