Skip to content

fix(parser): lower "if you put a [type] onto the battlefield this way" rider (Spelunking)#4704

Open
dean-alley wants to merge 1 commit into
phase-rs:mainfrom
dean-alley:card/spelunking-put-cave-rider
Open

fix(parser): lower "if you put a [type] onto the battlefield this way" rider (Spelunking)#4704
dean-alley wants to merge 1 commit into
phase-rs:mainfrom
dean-alley:card/spelunking-put-cave-rider

Conversation

@dean-alley

Copy link
Copy Markdown
Contributor

Summary

Fixes Spelunking (LCI, Modern 4-of). Its ETB rider — "If you put a Cave onto the battlefield this way, you gain 4 life." — was swallowed (Condition_If), which dropped the GainLife gate and marked the card supported: false. The rest of the card already worked on HEAD (the Lands you control enter untapped replacement half; the draw a card, then you may put a land card… stem).

Oracle text: When this enchantment enters, draw a card, then you may put a land card from your hand onto the battlefield. If you put a Cave onto the battlefield this way, you gain 4 life.\nLands you control enter untapped.

Root cause

The active-voice put-onto-battlefield gate combinator parse_you_put_onto_battlefield_this_way_clause already existed, but was only wired into strip_if_you_do_conditional's when branch — never the leading if path that Spelunking's rider takes. So you put a Cave onto the battlefield this way reached only the passive-voice recognizer (a Cave is/was put …) and failed to lower.

Fix

Compose the existing active-voice combinator into parse_outcome_this_way_condition — the shared recognizer reached by both the leading-if (strip_leading_general_conditionaltry_nom_condition_as_ability_condition) and trailing-suffix paths — alongside the passive-voice parse_zone_changed_this_way_clause it already delegated to. Both grammatical voices now lower to the identical AbilityCondition::ZoneChangedThisWay.

Parser-only, additive (a fallback alt branch), no change to the sacrificed/exiled/discarded or passive-voice paths.

Build for the class

This unlocks the whole "if you put a [type] onto the battlefield this way, [bonus]" fetch/ramp payoff rider class — the verb arm is the reusable unit and the type token (Cave) is the only card-specific part.

CR

  • CR 608.2c — later text on a card may reference an earlier instruction in the same effect ("this way").
  • CR 400.7j — if an effect causes an object to move to a public zone, other parts of that effect can find that object (how the rider finds the Cave the preceding put-land instruction moved to the battlefield).

Tests

  • leading_you_put_a_cave_onto_battlefield_spelunking — leading-if active-voice gate → ZoneChangedThisWay { Cave }.
  • outcome_you_put_a_creature_onto_battlefield_active_voice — suffix-form active-voice gate stays in lockstep.
  • spelunking_etb_put_cave_gains_life_conditionally — end-to-end parse_oracle_text of the full card: GainLife sub-ability carries ZoneChangedThisWay { Cave }, no Unimplemented node, replacement half still parses.

cargo fmt --all clean; cargo clippy -p engine --lib clean; the full parser::oracle_effect::conditions unit suite (95 tests) passes.

🤖 Generated with Claude Code

…" rider (Spelunking)

Spelunking's ETB rider "If you put a Cave onto the battlefield this way,
you gain 4 life." was swallowed (Condition_If), dropping the GainLife
gate and marking the Modern 4-of unsupported. The active-voice
put-onto-battlefield gate combinator
(parse_you_put_onto_battlefield_this_way_clause) already existed but was
only wired into strip_if_you_do_conditional's "when " branch, never the
leading "if " path that Spelunking's rider takes.

Compose the existing active-voice combinator into
parse_outcome_this_way_condition (the shared recognizer used by both the
leading-if and trailing-suffix paths), alongside the passive-voice
parse_zone_changed_this_way_clause it already delegates to. Both voices
now lower to the identical AbilityCondition::ZoneChangedThisWay.

This unlocks the whole "if you put a [type] onto the battlefield this
way, [bonus]" fetch/ramp payoff rider class; the type token is the only
card-specific part. The already-implemented "Lands you control enter
untapped" replacement half is unchanged.

CR 608.2c (later text references an earlier instruction; "this way") +
CR 400.7j (an effect can find an object it moved to a public zone).

Tests: unit coverage for the leading-if (Cave) and suffix (creature)
active-voice gates, plus an end-to-end parse of Spelunking asserting the
GainLife sub-ability carries ZoneChangedThisWay { Cave } with no
Unimplemented node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dean-alley dean-alley requested a review from matthewevans as a code owner July 1, 2026 13:38

@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 adds support for parsing active-voice "put onto the battlefield this way" conditions (such as on the card Spelunking) by updating the parser to recognize both active and passive grammatical voices, accompanied by comprehensive unit tests. The reviewer's feedback points out a violation of style guide rule R6, noting that the new parser implementation lacks mandatory CR annotations (CR 608.2c and CR 400.7j) in its documentation comments.

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.

Comment on lines +5024 to +5034
///
/// Both grammatical voices of the same event are recognized here so the leading
/// (`if …, [effect]`) and suffix (`[effect] if …`) forms produce identical
/// `ZoneChangedThisWay` conditions:
/// - passive: `a Cave (is|was) put onto the battlefield this way` —
/// `parse_zone_changed_this_way_clause`
/// - active: `you put a Cave onto the battlefield this way` —
/// `parse_you_put_onto_battlefield_this_way_clause` (Spelunking's ETB
/// rider "If you put a Cave onto the battlefield this way, you gain 4 life";
/// unlocks the whole "if you put a [type] onto the battlefield this way,
/// [bonus]" fetch/ramp payoff class).

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] Missing mandatory CR annotations on new game-rule parser implementation. Evidence: crates/engine/src/parser/oracle_effect/conditions.rs:5024.
Why it matters: The repository style guide (R6) requires all rules-touching engine code to carry verified CR annotations to ensure strict fidelity to the MTG Comprehensive Rules.
Suggested fix: Add CR 608.2c and CR 400.7j annotations to the doc comment.

///
/// CR 608.2c: Later text on a card may reference an earlier instruction in the same effect ("this way").
/// CR 400.7j: If an effect causes an object to move to a public zone, other parts of that effect can find that object.
///
/// Both grammatical voices of the same event are recognized here so the leading
/// (if ..., [effect]) and suffix ([effect] if ...) forms produce identical
/// ZoneChangedThisWay conditions:
///   - passive: a Cave (is|was) put onto the battlefield this way -
///     parse_zone_changed_this_way_clause
///   - active: you put a Cave onto the battlefield this way -
///     parse_you_put_onto_battlefield_this_way_clause (Spelunking's ETB
///     rider "If you put a Cave onto the battlefield this way, you gain 4 life";
///     unlocks the whole "if you put a [type] onto the battlefield this way,
///     [bonus]" fetch/ramp payoff class).
References
  1. Every rules-touching line of engine code must carry a comment of the form CR : . (link)

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

[LOW] Function-local imports violate the repo's no-inline-import rule. Evidence: crates/engine/src/parser/oracle_effect/conditions.rs:5036. Why it matters: the repository explicitly requires all imports at file scope, and this new parser path adds a local use inside rule-touching code. Suggested fix: move parse_you_put_onto_battlefield_this_way_clause and parse_zone_changed_this_way_clause imports to the module import section or call them through the module path.

Reviewed current head a440c57f36d693967a4e0cb08aaeaf54f21891c0.

@matthewevans matthewevans added the bug Bug fix label Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants