Skip to content

parser: compound-subject "don't untap" restriction strict-fails (Exhaustion, Icebreaker Kraken) #5412

Description

@claytonlin1110

Summary

Exhaustion and Icebreaker Kraken's compound-subject "don't untap" restriction —
"Creatures and lands target opponent controls don't untap during their next untap step."
and "...artifacts and creatures target opponent controls don't untap during that player's next untap step."
— currently strict-fail (verified against data/coverage-data.json: both supported: false,
gap_count: 1, with a bare partial-match label "creatures"/no label at all for the restriction
clause).

This is the same structural gap shape as #5219 (Life and Limb's compound-subject animation
static): a compound subject joined by "and" where each individual conjunct is already fully
supported on its own, but the combination is unhandled.

Verified card text (Scryfall API)

  • Exhaustion (sorcery): Creatures and lands target opponent controls don't untap during their next untap step.
  • Icebreaker Kraken (ETB trigger): When this creature enters, artifacts and creatures target opponent controls don't untap during that player's next untap step.

Proof the single-subject predicate is already fully correct

Two real, currently-supported: true cards confirm the single-subject sibling of this exact
predicate is complete end-to-end (verified against data/coverage-data.json):

  • Misstep: Creatures target player controls don't untap during that player's next untap step.
  • Mana Vapors: Lands target player controls don't untap during their next untap step.

Between them these are literally the two halves Exhaustion combines ("creatures ... don't
untap" + "lands ... don't untap" → "creatures and lands ... don't untap"). The predicate builder
(build_restriction_clause in crates/engine/src/parser/oracle_effect/subject.rs:4350) already
produces the correct StaticMode::CantUntap + Duration::UntilNextStepOf { step: Phase::Untap, player: PlayerScope::Controller } (CR 502.3, see the has_next_untap handling at
subject.rs:4397-4410). So this is not a predicate gap (unlike #5219, which needed two
predicate-halves merged) — it is purely a compound-subject gap.

Root cause (subject side)

The single-subject case reaches its filter through parse_subject_application's bare
plural-noun-phrase fallback (oracle_effect/subject.rs:2139-2165), which prepends "all " and
delegates whole to parse_target/parse_type_phrase_with_ctx
(oracle_target.rs:1909). That function does have a general "X and Y <shared suffix>"
core-type splitter (the TYPE_SEPARATORS recursion at oracle_target.rs:2440-2497), which
already Or-combines compound core-type nouns for other suffixes — e.g. the existing passing
test artifacts_and_creatures_your_opponents_control (oracle_target.rs:11048) confirms
"artifacts and creatures your opponents control"Or[Artifact+Opponent, Creature+Opponent]
via distribute_controller_to_or (oracle_target.rs:3723).

parse_zone_controller (oracle_nom/filter.rs:99) separately recognizes "target player controls"ControllerRef::TargetPlayer and "target opponent controls"
ControllerRef::TargetOpponent as its own suffix arms. Exhaustion/Icebreaker Kraken strict-fail
on the compound form of this specific controller-suffix family — needs tracing at
implementation time to confirm exactly which stage drops the second conjunct (candidates: the
starts_with_type_word/can_recurse gate on the right-hand leg, distribute_controller_to_or's
handling of the TargetOpponent/TargetPlayer variants specifically, or parse_subject_application's
subject_filter_application(filter, false) call not surfacing the two conjuncts' shared target-player
slot correctly for try_parse_subject_restriction_clause's predicate builder).

Proposed fix (mirrors #5219's structural shape)

Add a compound-subject dispatcher for this restriction-predicate class, following the same
shape as #5219 and its siblings (parse_compound_all_subjects_type_change /
try_parse_compound_all_subjects_become_clause):

  1. A new subject-splitter that recognizes "<X> and <Y>[ and <Z>...] target player|opponent controls", splits on " and ", resolves each conjunct's bare type word
    independently (reusing the existing single-conjunct path that already works for Misstep/Mana
    Vapors), and Or-combines them with the shared controller suffix distributed to every leg —
    reusing distribute_controller_to_or if it turns out to already generalize, or extending it
    if the TargetPlayer/TargetOpponent variants need explicit registration.
  2. Dispatched/reached before whatever currently causes the greedy single-conjunct
    drop, with a comment explaining the ordering (mirroring feat(parser): compound-subject animation static — Life and Limb (CR 611.3) #5219's dispatch-order comment
    discipline).
  3. Tests: positive parses for Exhaustion's and Icebreaker Kraken's exact verbatim predicate
    (asserting the Or of two Typed filters sharing ControllerRef::TargetOpponent, the
    CantUntap mode, and the UntilNextStepOf duration), plus confirmation that Misstep/Mana
    Vapors (single-subject) still route unchanged.

No new StaticMode, Duration variant, or runtime is needed — CantUntap +
Duration::UntilNextStepOf + TargetFilter::Or + ControllerRef::TargetOpponent all already
exist and are exercised end-to-end by existing supported cards.

CR references (grep-verified against docs/MagicCompRules.txt at implementation time)

  • CR 502.3 — Untap Step: effects that keep a permanent from untapping.
  • CR 611.3 — a continuous static effect applies to every object its text indicates (the
    Or-distributed affected set).
  • CR 109.4 / CR 115.1 — target-player/target-opponent controller references.

Scope

Parser-only (crates/engine/src/parser/oracle_target.rs and/or
crates/engine/src/parser/oracle_effect/subject.rs, plus their test files) — no engine
types/runtime changes expected, matching #5219's file footprint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions