fix(effects): offer optional tap on Charismatic Conqueror (#4963)#5014
fix(effects): offer optional tap on Charismatic Conqueror (#4963)#5014bohdansolovie wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements support for 'they may tap' zone-change observer triggers (such as Charismatic Conqueror) by updating the optional prompt player resolution and the clause shell parser, along with adding corresponding tests. The feedback identifies incorrect Magic Comprehensive Rules (CR) citations in the newly added comments, specifically recommending the removal of the reference to CR 603.7c as it is not applicable to standard triggered abilities.
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.
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on two blockers.
First, the runtime behavior introduced in optional_prompt_player needs runtime coverage. The current test only proves that the parser marks the clause optional; it does not prove that the WaitingFor prompt is offered to the entering permanent's controller, nor that accept/decline resolves correctly for Charismatic Conqueror. Please add a GameScenario-style regression that puts an opponent's untapped artifact/creature onto the battlefield and asserts the prompt player is the opponent.
Second, the new comments cite CR 603.7c, but that rule is for delayed triggered abilities. I verified the local CR text: 603.7c is not applicable to this ordinary zone-change trigger. CR 608.2d covers choices during resolution; the trigger context should use the normal trigger rules instead. Please remove or correct that citation.
Parse changes introduced by this PR · 3 card(s), 3 signature(s) (baseline: main
|
Peel only "they may tap" so Gonti-style "they may play" grants stay non-optional, drop Volo-only parser test from this branch, and cite CR 608.2d. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on the current head. The prior runtime blocker is still unresolved: the new test only asserts that the parser marked the execute effect optional; it still does not drive the trigger to WaitingFor and prove the prompt player is the entering permanent's controller.
The parse-diff is also far broader than the Charismatic Conqueror class, including Gonti/Razor Demon/Royal Booster/Tibalt's Trickery/Mishra's Command/Tarnation and other unrelated cards. Please narrow the clause-shell route and add a runtime scenario for the optional tap prompt controller.
phase-rs#4963) Peel only the "they may " optional marker (keeping the "tap" verb) so Charismatic Conqueror and Bow to My Command keep their tap effects while unrelated "they may play/cast" grants are untouched. Lift SetTapState's ParentTarget to TriggeringSource on zone-change triggers so the tap and its optional-prompt player resolve off the entering object, and add a runtime scenario proving the opponent is prompted and accept taps / decline makes the Vampire token. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on current head 9ece812226e6f896ccb10c305633c059c3d5f2c5.
[MED] The current parse-diff is still far outside the Charismatic Conqueror optional-tap fix. Evidence: the current coverage-parse-diff sticky changes broad unrelated signatures (ReduceAbilityCost affects fields, static_structure, orphaned_copy_retarget, Ravenous Trap, Suppression Field/Zirda, Teyo), and for the target card it changes Charismatic Conqueror from a concrete Tap (target=parent target) to that. Why it matters: the new runtime tests cover the prompt-player path, but they do not justify or guard this card-data/parser blast radius; the PR can still false-green while rewriting unrelated card parses. Suggested fix: constrain the parser change so the parse-diff is limited to the intended Charismatic Conqueror shape, and add production parse/card-data coverage that would fail if the target card regresses to the vague that/unrelated-signature diff.
…phase-rs#4963) Add a production parse_details regression (the exact tree the coverage-parse-diff sticky is built from) asserting the optional-tap trigger lowers to a concrete Tap ability bound to the entering object ("triggering source"), never the vague `that` bare noun phrase. Confirms the clause-shell peel keeps the tap verb; the peel's blast radius is only the two production cards with "they may tap" (Charismatic Conqueror, Bow to My Command). Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks @matthewevans — addressed on the new head ( The
Both are a concrete, supported Blast radius is bounded to the intended class. Only two production cards contain New production coverage: |
matthewevans
left a comment
There was a problem hiding this comment.
[MED] SetTapState anaphor lifting now rewrites targeted/reflexive tap effects to the trigger source. Evidence: crates/engine/src/parser/oracle_trigger.rs:1623; PR parse-diff reports Snaremaster Sprite ability/Tap target changed from parent target to triggering source, but its source text is “When you do, tap target creature an opponent controls...” in data/mtgish-cards.json:25417. Why it matters: targeted tap effects can now act on the entering/triggering object instead of the player-chosen target. Suggested fix: stop the lift when the current AbilityDefinition has its own chosen target (valid_target / optional targeting), or restrict SetTapState lifting to the no-target “that permanent” anaphor class; add a Snaremaster parse regression.
…conqueror-optional-tap
…ent" tap (phase-rs#4963) The zone-change anaphor lift rewrote *every* SetTapState ParentTarget to TriggeringSource, which retargeted player-chosen taps onto the entering object. Restrict the SetTapState lift to the trigger's OWN top-level "that permanent" tap anaphor (Charismatic Conqueror): - skip it below the top link (reflexive "you may pay {2}. When you do, tap target creature an opponent controls" — Snaremaster Sprite), - skip it when the link carries its own multi_target/optional_targeting slot, and - only lift TapStateChange::Tap — an entering permanent enters untapped, so "untap that creature" (Howl of the Hunt = the enchanted creature) never refers to the trigger source. Charismatic Conqueror and Dragon Turtle's genuine "that entering permanent" self-tap still lift. Add parse regressions for Snaremaster, Howl of the Hunt, and Charismatic Conqueror. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Thanks @matthewevans — fixed on the new head ( Root cause. The zone-change anaphor lift rewrote every Fix (
Parse-diff is now tight and fully justified (baseline main
Snaremaster Sprite and Howl of the Hunt both dropped out of the diff (back to Regression |
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on current head 84a6dc017e558fe2e08be6c84d413dda9180f203.
[MED] they may tap drops the actor scope. Evidence: crates/engine/src/parser/clause_shell.rs:308. Why it matters: Bow to My Command-style text is now parsed as optional tap, but the peel returns no PlayerFilter, so target choice/prompting can fall back to the scheme controller instead of the opponents named by "they." Suggested fix: thread the "they" actor as a scoped player/triggering player through the optional peel and add a Bow regression.
[MED] Charismatic Conqueror resolves the prompt player from the entering permanent's live controller. Evidence: crates/engine/src/game/effects/mod.rs:4688. Why it matters: if the entering permanent changes controllers before the trigger resolves, "they" should still be the event-time controller recorded by the zone-change trigger context, not whoever controls the object later. Suggested fix: carry/resolve the optional actor from the ZoneChangeRecord/trigger context authority and add a control-change-before-resolution regression.
[MED] Current parse-diff evidence still includes unrelated SpellCast watcher changes. Evidence: the coverage-parse-diff sticky updated 2026-07-03T21:05:08Z still changes Menagerie Curator and Volo's trigger/SpellCast.watches signature in addition to the tap-related signatures. Why it matters: that is unexplained parser blast radius for a PR scoped to optional tap handling. Suggested fix: narrow the parser diff, or explain and test why the SpellCast watch changes are intentional in this PR.
…yer (phase-rs#4963) Address matthewevans' review on the optional-tap fix: - clause_shell "they may tap" peel now threads the trigger's subject player out as PlayerFilter::TriggeringPlayer, so a Bow to My Command-style tap of "any number of untapped creatures they control" routes its optional prompt and target choice to "they" (the triggering opponent), not the scheme's controller. The bare "that permanent"/"it" self-tap anaphor (Charismatic Conqueror) deliberately keeps no player_scope: it lifts to SetTapState { TriggeringSource } and would otherwise leak the triggering player onto the "if they don't, you create ..." tail. - optional_prompt_player resolves the SetTapState { TriggeringSource } actor from the EVENT-TIME controller on the trigger's ZoneChangeRecord (TriggeringPlayer -> record.controller, CR 603.10a) instead of the object's live controller, so a control change between ETB and the trigger resolving still prompts the player who controlled the permanent when it entered. TriggeringSourceController remains a fallback. Regressions: peel-level Bow vs Charismatic Conqueror actor-scope tests, an end-to-end Bow parse asserting player_scope: TriggeringPlayer, and a control-change-before-resolution runtime scenario. Co-authored-by: Cursor <cursoragent@cursor.com>
…conqueror-optional-tap
|
Thanks @matthewevans — all three addressed on the new head ( [MED] [MED] Charismatic Conqueror resolved the prompt from the live controller ( [MED] parse-diff included unrelated SpellCast watcher changes (Menagerie Curator, Volo). Those were baseline pollution, not this PR: the branch had merged an older Full engine lib suite (14,879) is green; fmt, clippy, the parser-combinator gate, and the engine-authority gate all pass. |
matthewevans
left a comment
There was a problem hiding this comment.
Current-head review on 69e675b068ecb887c9a03e602719dffc069f75f2:
The prior implementation blockers appear addressed in the current code, but the parser blast-radius proof is still stale for this head. The coverage-parse-diff sticky predates the current push and still includes unrelated trigger signature changes, while the current card-data/coverage check had not completed when reviewed. Please wait for/regenerate the parse-diff sticky on this exact head and confirm that only the intended tap signatures remain.
|
Thanks @matthewevans — confirmed on this exact head All three signatures are
All CI checks are green on this head (card-data/coverage, Rust lint, both Rust test shards, WASM/Tauri/frontend). Happy to re-run CI if you'd like a second regenerated sticky to double-confirm. |
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on current head 69e675b068ecb887c9a03e602719dffc069f75f2.
[MED] The new optional_prompt_player comment still cites CR 603.10a for the Charismatic Conqueror ETB optional-tap path. Evidence: crates/engine/src/game/effects/mod.rs:4715; the local Comprehensive Rules text for 603.10a covers look-back zone-change triggers such as leaves-the-battlefield, sacrifice, leaves-graveyard, and visible-object-to-hand/library triggers. It does not describe an object entering the battlefield.
The implementation may still be using the right engine provenance (ZoneChangeRecord event-time controller), and CR 608.2d is still relevant for making choices during resolution. Please remove CR 603.10a from this ETB prompt-controller comment and describe the event-time controller lookup as engine trigger-context provenance rather than citing that rule here.
Remove the inapplicable CR 603.10a citation from optional_prompt_player's Charismatic Conqueror comment. CR 608.2d was verified locally as the relevant choices-during-resolution rule; the event-time controller lookup remains described as engine trigger-context provenance.
# Conflicts: # crates/engine/src/parser/oracle_trigger.rs # crates/engine/src/parser/oracle_trigger_tests.rs
matthewevans
left a comment
There was a problem hiding this comment.
Requesting changes on the current head.
[HIGH] The new they may tap parser branch still uses ad-hoc string dispatch and allocations. Evidence: crates/engine/src/parser/clause_shell.rs:307-330 calls to_lowercase() twice and classifies the remainder with starts_with() over a string array. Why it matters: parser dispatch must use the existing nom combinator building blocks; this also adds avoidable allocation to a shared clause-peeling path. Suggested fix: compose the anaphor alternatives with tag/alt (through the existing lowercase bridge) and return the original remainder without manual prefix scanning.
[HIGH] Wrong Comprehensive Rules citation remains in the ordinary-trigger path. Evidence: crates/engine/src/game/effects/mod.rs:415 and crates/engine/src/parser/oracle_trigger.rs:1660 cite CR 603.7c, which the current CR text defines for delayed triggered abilities; these functions handle normal trigger-event provenance. Suggested fix: remove the delayed-trigger citation and use the verified ordinary trigger/zone-change/anaphor rules (CR 603.2, CR 603.6, and/or CR 608.2k) that describe the implemented behavior.
[MED] Bow to My Command has only parser-shape coverage for the new triggering-player scope. Evidence: crates/engine/src/parser/oracle_trigger_tests.rs:22827-22871 asserts PlayerFilter::TriggeringPlayer, but no runtime scenario drives the optional prompt and target selection through WaitingFor/GameAction. Why it matters: the generalized player_scope at crates/engine/src/parser/clause_shell.rs:329 can be structurally correct while the resolving prompt or selected creatures still use the scheme controller. Suggested fix: add a runtime regression with an opponent-triggered Bow-like ability that verifies the opponent receives the prompt and controls the selected creatures.
Summary
they may tap that permanentas optional in trigger effect parsingTriggeringSourceController)Fixes #4963
Test plan
cargo test -p engine --lib trigger_etb_subject_enters_untapped