Skip to content

feat(parser+engine): Imprisoned in the Moon type-swap-with-granted-ability aura (#4770)#5142

Merged
matthewevans merged 3 commits into
phase-rs:mainfrom
minion1227:minion_4770
Jul 5, 2026
Merged

feat(parser+engine): Imprisoned in the Moon type-swap-with-granted-ability aura (#4770)#5142
matthewevans merged 3 commits into
phase-rs:mainfrom
minion1227:minion_4770

Conversation

@minion1227

Copy link
Copy Markdown
Contributor

Closes #4770.

Bug: Imprisoned in the Moon ("Enchanted permanent is a colorless land with "{T}: Add {C}" and loses all other card types and abilities.") parsed to only [SetCardTypes{Land}, SetColor{[]}] — the with "<ability>" clause and the trailing ability-strip were both silently dropped. So the enchanted permanent kept its own abilities (the #4770 report: an enchanted commander still had its abilities) and never gained the {T}: Add {C} mana ability.

Fix: parse_enchanted_becomes_type_with_ability in test-free oracle_static/type_change.rs, dispatched before parse_enchanted_is_type (whose base-P/T split does not model a with "<ability>" clause). It composes:

  • SetCardTypes (replace card types, CR 205.1a) + SetColor([]) (colorless, CR 105.2)
  • RemoveAllAbilities (strip the permanent's own abilities)
  • GrantAbility (the quoted ability, via the shared parse_quoted_ability_modifications authority — parses {T}: Add {C} to a mana ability)

RemoveAllAbilities is emitted before the grant so the granted ability survives the wipe (CR 613.7a written order — the same ordering the existing RemoveAllSubtypes → AddChosenSubtype composition relies on). The plain enchanted-is-type family (Song of the Dryads, Darksteel Mutation) has no quoted-ability clause and falls through unchanged.

No new variant, no new runtime — reuses existing Layer-4/Layer-6 modifications.

Verified: new imprisoned_in_the_moon_becomes_colorless_land_with_granted_mana_ability parser test (asserts the 4 modifications, RemoveAllAbilities before GrantAbility, EnchantedBy affected); live-parse confirms mods=[SetCardTypes{Land}, SetColor{[]}, RemoveAllAbilities, GrantAbility{"{T}: Add {C}", is_mana_ability:true}]; full oracle_static suite 946/946 green; parser-combinator gate + fmt clean.

Note for review: the modification order (RemoveAllAbilities before GrantAbility) is chosen so the granted ability survives the Layer-6 wipe; a cast-pipeline runtime test confirming the mooned permanent ends with exactly the {T}: Add {C} ability would be a good addition (I verified the parser AST but could not run the full cast pipeline in my environment).

🤖 Generated with Claude Code

@minion1227 minion1227 requested a review from matthewevans as a code owner July 5, 2026 14:18

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements parsing for the "Imprisoned in the Moon" style of type-changing aura effects to resolve issue #4770, adding a new parser function and associated unit tests. The review feedback highlights several incorrect Comprehensive Rules (CR) annotations that violate rule R6, and identifies a sibling coverage gap for the card "Sugar Coat" under rule L2 due to the parser not handling optional subtypes before the core card type.

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.

}
}

// CR 205.1a + CR 604.1: Imprisoned-in-the-Moon — "Enchanted <subject> is a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MED] Incorrect CR annotation for Layer 6. Evidence: crates/engine/src/parser/oracle_static/dispatch.rs:1051.
Why it matters: CR 604.1 defines static abilities generally, whereas Layer 6 (ability addition/removal) is governed by CR 613.1f.
Suggested fix: Update the comment to reference CR 613.1f instead of CR 604.1.

Suggested change
// CR 205.1a + CR 604.1: Imprisoned-in-the-Moon — "Enchanted <subject> is a
// CR 205.1a + CR 613.1f: Imprisoned-in-the-Moon — "Enchanted <subject> is a
References
  1. R6. CR annotations are mandatory and verified. Every rules-touching line of engine code must carry a comment of the form CR : . A CR annotation where the cited rule's body does not describe what the code is doing is a finding. (link)

///
/// Handles type-changing aura effects like Ensoul Artifact, Imprisoned in the Moon,
/// and Darksteel Mutation. Reuses nom type-word and P/T combinators.
/// CR 205.1a + CR 613.1d (Layer 4) + CR 604.1 (Layer 6): Imprisoned-in-the-Moon

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MED] Incorrect CR annotation for Layer 6. Evidence: crates/engine/src/parser/oracle_static/type_change.rs:555.
Why it matters: CR 604.1 defines static abilities generally rather than Layer 6 (CR 613.1f).
Suggested fix: Update the doc comment to reference CR 613.1f (Layer 6) instead of CR 604.1 (Layer 6).

Suggested change
/// CR 205.1a + CR 613.1d (Layer 4) + CR 604.1 (Layer 6): Imprisoned-in-the-Moon
/// CR 205.1a + CR 613.1d (Layer 4) + CR 613.1f (Layer 6): Imprisoned-in-the-Moon
References
  1. R6. CR annotations are mandatory and verified. Every rules-touching line of engine code must carry a comment of the form CR : . A CR annotation where the cited rule's body does not describe what the code is doing is a finding. (link)

/// `RemoveAllAbilities` (the permanent loses its own abilities), then the
/// `GrantAbility` produced by the shared `parse_quoted_ability_modifications`
/// authority. `RemoveAllAbilities` is emitted BEFORE the grant so the granted
/// ability SURVIVES the wipe (CR 613.7a intra-effect written order — the same

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MED] Incorrect CR annotation for intra-effect layer application. Evidence: crates/engine/src/parser/oracle_static/type_change.rs:568.
Why it matters: CR 613.7a governs timestamps of static abilities, whereas CR 613.6 governs how parts of a single continuous effect apply across different layers.
Suggested fix: Update the comment to reference CR 613.6 instead of CR 613.7a.

Suggested change
/// ability SURVIVES the wipe (CR 613.7a intra-effect written order — the same
/// ability SURVIVES the wipe (CR 613.6 intra-effect layer application — the same
References
  1. R6. CR annotations are mandatory and verified. Every rules-touching line of engine code must carry a comment of the form CR : . A CR annotation where the cited rule's body does not describe what the code is doing is a finding. (link)

.parse(r)
.ok()?;
let (r, _) = tag::<_, _, OracleError<'_>>("colorless ").parse(r).ok()?;
let (r, type_tf) = nom_target::parse_type_filter_word(r).ok()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

[MED] Sibling coverage gap for Sugar Coat. Evidence: crates/engine/src/parser/oracle_static/type_change.rs:593.
Why it matters: Sugar Coat is a sibling Aura in this class that turns a permanent into a "colorless Food artifact" with a granted ability, but it is not parsed because "Food" is a subtype prefix before the core type.
Suggested fix: Generalize the parser to handle optional subtypes before the core card type.

References
  1. L2. Sibling coverage: If a parser arm or string was extended, ensure sibling variants or related cards in the same class (such as Sugar Coat) are covered. (link)
  2. Avoid verbatim string equality for parsing Oracle phrases as it bypasses the robust nom-based parser and creates fragile matches. Instead, decompose compound phrases into modular, reusable parsers for constituent parts and compose them using idiomatic combinator aggregates to prevent combinatorial explosion and improve maintainability.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 4 card(s), 7 signature(s) (baseline: main a41c7537cdc7)

1 card(s) · static/Continuous · field mods: set card types artifact, set color , add subtype Foodset card types artifact, set color , remove all Artifact subtypes, add subtype Food, remove all abilities

Examples: Sugar Coat

1 card(s) · static/Continuous · field mods: set card types land, set color set card types land, set color , remove all abilities

Examples: Imprisoned in the Moon

1 card(s) · ability/Discard · added: Discard (count=1, target=controller)

Examples: Pollywog Symbiote

1 card(s) · trigger/Discarded · field condition: event object is with madness card

Examples: Anje Falkenrath

1 card(s) · ability/GainLife · added: GainLife (amount=3, kind=activated)

Examples: Sugar Coat

1 card(s) · ability/Mana · added: Mana (kind=activated, mana={C} x1)

Examples: Imprisoned in the Moon

1 card(s) · trigger/SpellCast · field condition: event object is with mutate card

Examples: Pollywog Symbiote

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

Thanks for the fix direction here. I’m holding this one for the current-head review findings already posted inline.

Blocking items:

  • The new parser handles colorless <core type> but misses the same Aura family when a subtype precedes the core type, as with Sugar Coat. The local card data already models that sibling as colorless + artifact subtype/card type + loses abilities + adds ability, so this should be generalized before we mark the class covered.
  • The CR annotations cite CR 604.1 for Layer 6 and CR 613.7a for intra-effect ordering. Local CR text confirms Layer 6 is CR 613.1f, and CR 613.6 is the relevant cross-layer continuation rule; CR 604.1 and CR 613.7a do not say what the comments claim.

Please update the parser to cover the subtype+core-type sibling shape, add a parser test for it, and correct the CR comments.

@matthewevans matthewevans added the bug Bug fix label Jul 5, 2026
minion1227 and others added 2 commits July 5, 2026 08:55
…ility aura (phase-rs#4770)

Imprisoned in the Moon ("Enchanted permanent is a colorless land with '{T}: Add
{C}' and loses all other card types and abilities.") parsed to only [SetCardTypes
{Land}, SetColor{[]}] — the `with "<ability>"` clause and the trailing ability
strip were both silently dropped, so the enchanted permanent kept its own
abilities (issue phase-rs#4770: an enchanted commander still had its abilities) and never
gained the "{T}: Add {C}" mana ability.

Add parse_enchanted_becomes_type_with_ability in oracle_static/type_change.rs,
dispatched before parse_enchanted_is_type (whose base-P/T split does not model
the with-quoted-ability clause). It composes SetCardTypes (CR 205.1a) + SetColor
([], CR 105.2) + RemoveAllAbilities + GrantAbility (the quoted ability, via the
shared parse_quoted_ability_modifications authority). RemoveAllAbilities is
emitted BEFORE the grant so the granted ability survives the wipe (CR 613.7a
written order — same ordering the RemoveAllSubtypes -> AddChosenSubtype
composition relies on). The plain enchanted-is-type family (Song of the Dryads,
Darksteel Mutation) has no quoted-ability clause and falls through unchanged.

No new variant, no new runtime — reuses existing Layer-4/Layer-6 modifications.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalize parse_enchanted_becomes_type_with_ability to accept optional
subtype(s) before the core card type, so the Imprisoned-in-the-Moon class also
covers Sugar Coat ("colorless Food artifact with \"...\" and loses all other
card types and abilities"). Each parsed subtype emits an AddSubtype (Layer 4)
placed with the other type-identity modifications, before the Layer-6 ability
wipe. Uses the case-insensitive parse_subtype building block; a core-type word
is never a subtype so the loop stops at the head noun. Adds the Sugar Coat
parser regression test.

Correct the CR annotations flagged in review (verified against
docs/MagicCompRules.txt):
- Layer 6 is CR 613.1f (ability add/remove), not CR 604.1 (static abilities
  generally) — dispatch.rs and the type_change.rs doc comment.
- The 'granted ability survives the wipe' ordering is CR 613.6 (parts of one
  continuous effect keep applying to the same object), not CR 613.7a
  (timestamps).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@minion1227

Copy link
Copy Markdown
Contributor Author

Addressed all three blocking items (pushed d34ca767c, rebased on current main):

1. Subtype-before-core-type sibling (Sugar Coat). Generalized parse_enchanted_becomes_type_with_ability to accept optional subtype(s) between colorless and the core card type, using the case-insensitive parse_subtype building block. Each subtype emits an AddSubtype (Layer 4) placed with the other type-identity mods, before the Layer-6 ability wipe. Sugar Coat now parses to SetCardTypes(Artifact) + SetColor([]) + AddSubtype(Food) + RemoveAllAbilities + GrantAbility(sac→3 life), matching the card-data model. A core-type word is never a subtype, so the loop stops at the head noun and Imprisoned ("colorless land", no subtype) is unchanged.

2. Parser test. Added sugar_coat_becomes_colorless_food_artifact_with_granted_ability asserting the artifact + colorless + Food-subtype identity and the RemoveAllAbilitiesGrantAbility ordering. Full oracle_static suite 998/998 green; parser-combinator gate clean.

3. CR annotation corrections (verified against docs/MagicCompRules.txt):

  • Layer 6 → CR 613.1f (ability add/remove), replacing the incorrect CR 604.1 (dispatch.rs + type_change.rs doc).
  • 'granted ability survives the wipe' → CR 613.6 (parts of one continuous effect keep applying to the same object), replacing the incorrect CR 613.7a (timestamps).

Ready for another look.

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

Thanks for the update. The Imprisoned in the Moon path is much closer, but the new Sugar Coat sibling still has a CR 205.1a subtype replacement issue.

[MED] Sugar Coat still preserves old artifact subtypes. Evidence: crates/engine/src/parser/oracle_static/type_change.rs:652 only appends AddSubtype { subtype: Food }, while crates/engine/src/game/layers.rs:4806 keeps all subtypes correlated with the new Artifact card type unless a RemoveAllSubtypes { Artifact } runs first. CR 205.1a says that when an effect sets one or more subtypes, the new subtypes replace any existing subtypes from the appropriate set. Why it matters: enchanting an artifact with another artifact subtype leaves it as, for example, Clue Food artifact instead of just Food artifact, so the Sugar Coat coverage is still rules-wrong. Suggested fix: insert RemoveAllSubtypes for each granted subtype set before the corresponding AddSubtype, and add a regression that an artifact subtype is wiped before AddSubtype(Food).

Setting a subtype replaces existing subtypes from the same set (CR 205.1a),
but AddSubtype alone is additive: a host that already carries an artifact
subtype (e.g. a Clue) enchanted by Sugar Coat would become 'Clue Food artifact'
instead of 'Food artifact'. Emit RemoveAllSubtypes for the core type's subtype
set before the AddSubtype(s) so the old subtypes are cleared first. The grammar
guarantees each parsed subtype belongs to the core type's set, so one
RemoveAllSubtypes covers them. Strengthen the Sugar Coat test to assert
RemoveAllSubtypes(Artifact) precedes AddSubtype(Food).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@minion1227

Copy link
Copy Markdown
Contributor Author

Fixed the CR 205.1a subtype-replacement issue (pushed 92a91ed4e). When a subtype is parsed, the parser now emits RemoveAllSubtypes for the core type's subtype set before the AddSubtype(s), so a host that already carries an artifact subtype (e.g. a Clue) becomes "Food artifact", not "Clue Food artifact". The grammar guarantees each parsed subtype belongs to the core type's set, so one RemoveAllSubtypes covers them. Strengthened the Sugar Coat test to assert RemoveAllSubtypes(Artifact) precedes AddSubtype(Food). Leaning on CI to confirm the build here.

@matthewevans matthewevans self-assigned this Jul 5, 2026
@matthewevans matthewevans added enhancement New feature or request and removed bug Bug fix labels Jul 5, 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.

Maintainer sweep: reviewed current head after the follow-up fixes. The parser/static type-change seam is clean on review, parse-diff evidence is present, and merge-when-ready can wait for remaining checks.

@matthewevans matthewevans enabled auto-merge July 5, 2026 18:30
@matthewevans matthewevans removed their assignment Jul 5, 2026
@matthewevans matthewevans added this pull request to the merge queue Jul 5, 2026
Merged via the queue into phase-rs:main with commit 6984869 Jul 5, 2026
11 checks passed
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.

Bug? — The Ai controls Strefan, Maurer Progenitor as commander, i used Imprisoned in the moon on it, and its abilities…

2 participants