Skip to content

feat(engine): model "As ~ is turned face up" as a TurnFaceUp replacement#3694

Open
philluiz2323 wants to merge 1 commit into
phase-rs:mainfrom
philluiz2323:fix/as-turned-face-up-trigger
Open

feat(engine): model "As ~ is turned face up" as a TurnFaceUp replacement#3694
philluiz2323 wants to merge 1 commit into
phase-rs:mainfrom
philluiz2323:fix/as-turned-face-up-trigger

Conversation

@philluiz2323

Copy link
Copy Markdown
Contributor

Problem

Cards whose turned-face-up triggered ability is templated with "As ~ is turned face up, [effect]" (rather than "When …") were unsupported — the clause fell through to an Unimplemented effect: Hooded Hydra (put five +1/+1 counters), Bubble Smuggler (put four +1/+1 counters), Gift of Doom (may attach it to a creature), Crowd-Control Warden.

The trigger classifier (has_trigger_prefix) only accepted when/whenever/at, so the "As"-led line routed to effect parsing. The engine already models TriggerMode::TurnFaceUp and fires it at runtime on GameEvent::TurnedFaceUp (trigger_matchers.rs), and the "When ~ is turned face up" form already parses — only the "As" templating (CR 711 megamorph/disguise + CR 603.3) was missing.

Fix

Recognize the "As" lead, scoped to the "is turned face up" phrase so "As ~ enters" replacements are untouched:

  • has_trigger_prefix admits as … is turned face up, and
  • the trigger core rewrites the lead to the canonical "When " before parsing.

No new variant or runtime change — it reuses the existing, runtime-tested TurnFaceUp trigger path.

Verification

  • Test: the As-form parses to the same TurnFaceUp trigger + PutCounter effect as the When-form.
  • Full engine lib suite green (12420 passed, 0 regressions); clippy -D warnings + parser gate clean.
  • Local regen: 4 cards flip to supported, 0 regressions.

Closes #3691

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@matthewevans

Copy link
Copy Markdown
Member

VERDICT: request-changes

[HIGH] The PR routes “As … is turned face up” replacement text through triggered-ability parsing. Evidence: crates/engine/src/parser/oracle_trigger.rs rewrites the As lead to When, while the engine already has the correct replacement seam as ReplacementEvent::TurnFaceUp in crates/engine/src/types/replacements.rs; the replacement registry also notes that TurnFaceUp is currently a parser-emitted placeholder in crates/engine/src/game/replacement.rs. Why it matters: Hooded Hydra/Bubble Smuggler/Gift of Doom effects apply as the permanent is turned face up, not later as a stack trigger, so this gives players an illegal response window and models the class at the wrong seam. Suggested fix: parse this class in oracle_replacement.rs as ReplacementEvent::TurnFaceUp and wire the replacement handler/actions instead of classifying it as a trigger.

@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.

Formalizing my blocking review comment: “As this is turned face up” is being modeled as a trigger, but the rules-correct seam is the TurnFaceUp replacement path.

Per review (rules-correctness): the megamorph/disguise templating "As ~ is
turned face up, [effect]" (Hooded Hydra, Bubble Smuggler, Gift of Doom,
Crowd-Control Warden) applies its effect AS the permanent is turned face up
(CR 711 + CR 614.12) — a replacement effect, not a stack triggered ability. The
prior trigger modeling gave players an illegal response window.

- Parser: parse_turned_face_up_replacement (oracle_replacement.rs) emits a
  ReplacementDefinition(ReplacementEvent::TurnFaceUp){valid_card: SelfRef,
  execute: <effect>}, modeled on the as-enters / untap-step replacements.
- Runtime: add ProposedEvent::TurnFaceUp and wire a real applier (replacing the
  placeholder) — it runs the alternative effect chain bound to the permanent
  being turned up (source = that object so the it/SelfRef anaphor binds) and
  returns the event unchanged (the turn-up still happens). morph::turn_face_up
  raises it through replace_event after restoring the real face.

Tests: parser (As-form -> TurnFaceUp replacement + PutCounter execute) and an
end-to-end runtime test (a face-down megamorph turned face up gains five +1/+1
counters). Full engine lib suite green (12451 passed), 0 regressions.

Closes phase-rs#3691
@philluiz2323 philluiz2323 force-pushed the fix/as-turned-face-up-trigger branch from 24c774d to 95f51a2 Compare June 18, 2026 04:12
@philluiz2323

Copy link
Copy Markdown
Contributor Author

Re-modeled as a replacement per your review — thanks, you're right that the stack-trigger seam gave an illegal response window.

95f51a245 (branch reset to main, trigger approach discarded):

  • Parser (oracle_replacement.rs): parse_turned_face_up_replacement emits ReplacementDefinition(ReplacementEvent::TurnFaceUp){valid_card: SelfRef, execute} (modeled on the as-enters / untap-step replacements); is_replacement_pattern routes the As-led form (the When form stays a trigger).
  • Runtime: added ProposedEvent::TurnFaceUp and wired a real applier (replacing the placeholder) — it runs the alternative effect chain bound to the permanent being turned up (source = that object, so the it/SelfRef anaphor binds) and returns the event unchanged (the turn-up still happens). morph::turn_face_up raises it via replace_event after restoring the real face, so the effect applies AS the permanent is turned up — no stack window.
  • Tests: parser (As-form → TurnFaceUp replacement + PutCounter execute) and an end-to-end runtime test (a face-down megamorph turned face up gains five +1/+1 counters).

Local regen: 3 cards flip (Hooded Hydra, Bubble Smuggler, Gift of Doom), 0 regressions. Crowd-Control Warden is left for a follow-up — its compound "As ~ enters or is turned face up, put X …" (dual event + dynamic X) is a different pattern. Full engine lib suite green (12451 passed), clippy -D warnings + parser gate clean.

@philluiz2323 philluiz2323 changed the title feat(parser): recognize "As ~ is turned face up" megamorph/disguise trigger feat(engine): model "As ~ is turned face up" as a TurnFaceUp replacement Jun 18, 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.

VERDICT: request-changes

[HIGH] The new TurnFaceUp applier binds the turned-up permanent as a normal target slot, so the Gift of Doom class still resolves against the wrong object. Evidence: crates/engine/src/game/replacement.rs:2664-2668 builds every replacement execute ability with source_id = object_id and then sets ability.targets = [Object(object_id)]; Effect::Attach resolves the host from ability.targets before falling back to filter resolution in crates/engine/src/game/effects/attach.rs:31-39 and :177-187; and this parser path explicitly includes Gift of Doom in crates/engine/src/parser/oracle_replacement.rs:5032-5055, while the runtime test only covers the Hooded-Hydra counter case in crates/engine/src/game/morph.rs:615-650. Why it matters: As Gift of Doom is turned face up, you may attach it to a creature needs the Aura itself as SelfRef/source, but it still needs a real creature host target or choice; pre-seeding the Aura into explicit target slots makes the host resolver consume the Aura itself instead. Suggested fix: bind only the anaphoric self reference/source to the turned-up permanent, do not stuff it into ordinary target slots, and add a runtime Gift-of-Doom-style test with another legal creature host.

[MED] The new CR annotations cite the wrong rules. Evidence: the new comments use CR 711 + CR 614.12 in crates/engine/src/game/replacement.rs, crates/engine/src/parser/oracle_replacement.rs, crates/engine/src/game/morph.rs, crates/engine/src/types/proposed_event.rs, and related wiring; but the local rules text has CR 614.1e for “As [this permanent] is turned face up ...” replacement effects and CR 708.11 for applying them while the permanent is being turned face up, while CR 614.12 is enters-the-battlefield replacement effects and CR 711 is Leveler Cards. Suggested fix: replace these citations with the verified turn-face-up replacement rules, adding morph/disguise-specific refs only where the code is specifically about those special actions.

@matthewevans matthewevans added the enhancement New feature or request label Jun 18, 2026
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.

Parser: "As ~ is turned face up" megamorph/disguise triggers unsupported

2 participants