Fix Winter Orb / Static Orb#5394
Conversation
…02.3)
MaxUntapPerType's dispatch branch required an EOF-anchored suffix match,
so when the general inverted-"as long as" rewrite recursed into it with
a trailing " as long as <condition>" clause, the branch never matched
and the effect silently fell through to a no-op Continuous static.
Winter Orb and Static Orb both print this leading-condition form
("As long as this artifact is untapped, players can't untap more than
...") and currently do nothing in-game as a result. Fixes both by
giving MaxUntapPerType the same trailing-condition tolerance
parse_continuous_gets_has already has, reusing parse_static_condition
and rewrite_self_pronoun_subject rather than a one-off check. The
unconditional family (Smoke, Damping Field, Imi Statue, Stoic Angel)
is unaffected and covered by regression assertions.
Verification: cargo fmt, check-parser-combinators.sh, cargo clippy
--all-targets -D warnings, and cargo test -p engine --lib (15758
passed) all clean. New parser tests assert both cards' verbatim
Oracle text parse to MaxUntapPerType with the condition populated;
new turns.rs tests drive the fix through the real
active_static_definitions/evaluate_condition/max_untap_restrictions
pipeline, including a multi-authority fixture proving two Winter Orbs
gate independently on their own tapped state.
Found via AI-CONTRIBUTOR.md's Old-School (93/94) misparse-backlog
cross-reference. Plan review and implementation review both passed
clean on the first round.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMa6DrxXyFHBdGxz3uLgvM
Both cards now parse correctly per the preceding fix (verified by the new discriminating parser and runtime tests) — list hygiene per AI-CONTRIBUTOR.md §3.1: remove fixed cards from their root-cause lists in the same PR. Updates the two affected section counts and the top summary totals; neither list emptied so no section was deleted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DMa6DrxXyFHBdGxz3uLgvM
There was a problem hiding this comment.
Code Review
This pull request implements parsing and engine support for conditional max-untap static abilities (such as Winter Orb and Static Orb) gated by their own tapped state, aligning with CR 502.3, CR 611.3a, and CR 604.1. It introduces the parse_max_untap_per_type_static helper to handle trailing conditions and updates the parser backlog documentation. The feedback identifies a minor improvement opportunity in dispatch.rs to remove redundant double references when calling parse_max_untap_per_type_static.
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.
| // because the restriction applies to whoever is the active player, not | ||
| // the source's controller. See `parse_max_untap_per_type_static` for the | ||
| // trailing-condition tolerance shared with `parse_continuous_gets_has`. | ||
| if let Some(def) = parse_max_untap_per_type_static(&tp, &text) { |
There was a problem hiding this comment.
[MEDIUM] Redundant double references in function call.
Why it matters: tp is already of type &TextPair<'_> and text is already of type &str. Passing &tp and &text creates unnecessary double references (&&TextPair<'_> and &&str) which are then coerced by the compiler.
Suggested fix: Pass tp and text directly without the redundant reference operators.
| if let Some(def) = parse_max_untap_per_type_static(&tp, &text) { | |
| if let Some(def) = parse_max_untap_per_type_static(tp, text) { |
|
Holding current head |
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: current head parses Winter Orb / Static Orb into conditional MaxUntapPerType, verifies own-source tapped-state gating at runtime, and the parse diff is limited to those two cards.
Summary
Fixes the "Granted/quoted ability or continuous modification dropped" misparse for Winter Orb, and the same underlying bug (independently confirmed, tracked in the backlog under a different, less-precise root-cause label) for Static Orb. Both currently parse to a no-op
StaticMode::Continuouswithmodifications: []— the leading "As long as this artifact is untapped, ..." condition breaksMaxUntapPerType's dispatch branch, so the untap-cap effect is silently dropped and both cards currently do nothing in-game.Root cause:
MaxUntapPerType's branch required its suffix parser to hit end-of-string. The general inverted-"as long as" rewrite machinery recurses into it with trailing text after the match point, so the branch never fires and a generic fallback swallows the effect. Fix givesMaxUntapPerTypethe same trailing-condition toleranceparse_continuous_gets_hasalready has, reusing existing building blocks — zero runtime changes needed, the condition-gating machinery already worked generically once the parser emits the right mode+condition.Backlog entries for both cards removed per §3.1 list hygiene (second commit).
Files changed
CR references
Track
Developer
LLM
Model: claude-sonnet-5
Thinking: high
Verification
cargo fmt --all -- --check— clean./scripts/check-parser-combinators.sh— clean (no output, exit 0)cargo clippy --all-targets -- -D warnings— clean, full workspacecargo test -p engine --lib— 15758 passed, 0 failed, 6 ignoredstatic_max_untap_winter_orb_conditional,static_max_untap_static_orb_conditional) assert both cards' verbatim Oracle text parse toMaxUntapPerTypewith the correct filter/max and a populatedNot(SourceIsTapped)conditionwinter_orb_max_untap_cap_gated_by_own_tapped_state,two_winter_orbs_gate_independently_on_own_tapped_state) drive the fix through the realactive_static_definitions/evaluate_condition/max_untap_restrictionspipeline, including a multi-authority fixture proving two Winter Orbs gate independently on their own tapped statecargo coverage— not run locally (MTGJSON/card-data.json not present in this environment); the primary coverage gate's "supported" boolean for these cards is unaffected by this fix either way (see judgment note below);cargo semantic-auditlikewise not run locally for the same reasonGate A
Anchored on
crates/engine/src/parser/oracle_static/anthem.rs:782—parse_continuous_gets_has's own" as long as "split-recurse-reattach shape, the established precedent this fix's newparse_max_untap_per_type_static(dispatch.rs:170) mirrorscrates/engine/src/parser/oracle_static/anthem.rs:729—rewrite_self_pronoun_subject, reused (visibility bumped topub(crate)) rather than re-implemented, for self-pronoun condition normalizationTier: Standard
Scope Expansion
None.
Validation Failures
None.
CI Failures
None.