Skip to content

fix(parser): preserve discard-this-way conditional outcomes - #6855

Merged
matthewevans merged 2 commits into
mainfrom
maintainer/rescue-5808-discard-this-way
Aug 2, 2026
Merged

fix(parser): preserve discard-this-way conditional outcomes#6855
matthewevans merged 2 commits into
mainfrom
maintainer/rescue-5808-discard-this-way

Conversation

@matthewevans

@matthewevans matthewevans commented Aug 2, 2026

Copy link
Copy Markdown
Member

Rescues closed contributor PR #5808 for echoed discard-this-way conditionals. Handles Chains of Mephistopheles and Magus forms while retaining the Great Desert Hellion optional-discard outcome gate.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of discard-dependent effects, ensuring follow-up actions occur only when the discard succeeds.
    • Corrected replacement-draw behavior so players draw when a discard is successful and mill instead when no discard can be made.
    • Preserved conditional effects tied to upkeep triggers and optional discard actions.
  • Tests

    • Added coverage for related card interactions and both successful and unsuccessful discard scenarios.

@matthewevans matthewevans added the bug Bug fix label Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@matthewevans, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3eafc426-ff8a-451e-8c16-eb92156e8022

📥 Commits

Reviewing files that changed from the base of the PR and between 3cbf437 and 454d0cc.

📒 Files selected for processing (6)
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/tests/fixtures/integration_cards.json
  • crates/engine/tests/integration/chains_of_mephistopheles_discard_draw_or_mill.rs
  • crates/engine/tests/integration/main.rs
📝 Walkthrough

Walkthrough

The parser now recognizes affirmative and negated “discard a card this way” conditions. Regression tests cover Great Desert Hellion and Chains of Mephistopheles. Integration tests verify draw and mill outcomes after successful or failed discard attempts.

Changes

Discard condition parsing

Layer / File(s) Summary
Discard condition parser support
crates/engine/src/parser/oracle_nom/condition.rs
The condition parser maps affirmative discard connectors to effect_performed() and negated connectors to Not(effect_performed()).
Card parser regression coverage
crates/engine/src/parser/oracle_effect/tests.rs, crates/engine/src/parser/oracle_replacement.rs
Parser tests verify discard-gated follow-up effects for Great Desert Hellion and Chains of Mephistopheles.
Discard replacement integration coverage
crates/engine/tests/integration/chains_of_mephistopheles_discard_draw_or_mill.rs, crates/engine/tests/integration/main.rs
Integration tests verify drawing after a successful discard, milling after a failed discard, and test-module registration.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: quality

Suggested reviewers: jacobwoodson

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the parser fix for discard-this-way conditional outcomes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch maintainer/rescue-5808-discard-this-way
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch maintainer/rescue-5808-discard-this-way

Comment @coderabbitai help to get the list of available commands.

nghetienhiep and others added 2 commits August 1, 2026 23:59
…f Mephistopheles

The replacement's trailing "if the player discards a card this way, they
draw a card. If the player doesn't discard a card this way, they mill a
card." was silently dropped — Discard, Draw, and Mill all ran unconditionally
instead of Draw/Mill being mutually exclusive on whether the discard
actually removed a card. The reflexive-connector recognizer only handled the
bare "if you do,"/"if the player does," forms, not the echoed-verb "discards
a card this way" phrasing this card uses.

Extends parse_affirmative_reflexive_connector / parse_negated_reflexive_connector
with the untyped echoed-discard form, mapping to the same effect_performed()
condition the bare connector already uses (and that the engine's mandatory-
rider seeding already resolves correctly for a failed discard).

Closes #5653

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/engine/src/parser/oracle_nom/condition.rs`:
- Around line 9425-9463: Refactor parse_discard_this_way_affirmative_connector
and parse_discard_this_way_negated_connector to compose the existing subject
alternatives from parse_affirmative_reflexive_connector and
parse_negated_reflexive_connector with the shared discard-this-way verb suffix,
preserving each subject’s correct verb form. Remove the duplicated full tag()
permutations so both bare and discard-specific connectors derive their
subject/anaphor coverage from one source.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9840f88d-7eed-4a0f-96dd-8ec0cd25ec08

📥 Commits

Reviewing files that changed from the base of the PR and between b902333 and 3cbf437.

📒 Files selected for processing (6)
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/tests/fixtures/integration_cards.json
  • crates/engine/tests/integration/chains_of_mephistopheles_discard_draw_or_mill.rs
  • crates/engine/tests/integration/main.rs

Comment on lines +9425 to 9463
parse_discard_this_way_affirmative_connector,
))
.parse(input)
}

/// CR 608.2c + CR 701.9a: the echoed-verb affirmative form — "if [subject]
/// discard(s) a card this way, " (Chains of Mephistopheles / Magus of the
/// Chains: "If the player discards a card this way, they draw a card."). This
/// is the untyped-object sibling of `parse_you_discard_this_way_clause` (which
/// requires a typed filter and lowers to `ZoneChangedThisWay`): an unqualified
/// "a card" carries no type information to check, so the condition collapses
/// to the same "did the preceding discard occur" gate as the bare "if you
/// do, " connector. The controller-specific "if you discard ..." form shares
/// that gate: its preceding optional discard publishes `OptionalEffectPerformed`,
/// so the follow-up stays attached to the discard outcome rather than executing
/// after the sacrifice alternative.
fn parse_discard_this_way_affirmative_connector(input: &str) -> OracleResult<'_, AbilityCondition> {
alt((
value(
AbilityCondition::effect_performed(),
tag("if you discard a card this way, "),
),
value(
AbilityCondition::effect_performed(),
tag("if a player discards a card this way, "),
),
value(
AbilityCondition::effect_performed(),
tag("if they discard a card this way, "),
),
value(
AbilityCondition::effect_performed(),
tag("if that player discards a card this way, "),
),
value(
AbilityCondition::effect_performed(),
tag("if the player discards a card this way, "),
),
))

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Compose the subject dimension instead of repeating full tag() strings per subject.

parse_discard_this_way_affirmative_connector and parse_discard_this_way_negated_connector each hard-code one tag() per subject ("you", "a player", "they", "that player", "the player"), duplicating the exact subject list already enumerated in parse_affirmative_reflexive_connector and parse_negated_reflexive_connector. This repeats the full compound phrase ("if a player discards a card this way, ") instead of composing an existing subject-alternative with a shared verb-phrase suffix.

If a new subject/anaphor form is added later, it must be updated in two independent places (the bare "does"/"doesn't" list and the "discard(s) a card this way" list), risking silent drift between them.

Compose the subject alternative (with its matching verb form) once, then append the fixed " discard(s)/discard(s)n't a card this way, " suffix, so both lists share one subject-to-verb-form source.

♻️ Suggested composition approach
-fn parse_discard_this_way_affirmative_connector(input: &str) -> OracleResult<'_, AbilityCondition> {
-    alt((
-        value(
-            AbilityCondition::effect_performed(),
-            tag("if you discard a card this way, "),
-        ),
-        value(
-            AbilityCondition::effect_performed(),
-            tag("if a player discards a card this way, "),
-        ),
-        value(
-            AbilityCondition::effect_performed(),
-            tag("if they discard a card this way, "),
-        ),
-        value(
-            AbilityCondition::effect_performed(),
-            tag("if that player discards a card this way, "),
-        ),
-        value(
-            AbilityCondition::effect_performed(),
-            tag("if the player discards a card this way, "),
-        ),
-    ))
-    .parse(input)
-}
+fn parse_discard_this_way_subject(input: &str) -> OracleResult<'_, &str> {
+    alt((
+        tag("you discard"),
+        tag("a player discards"),
+        tag("they discard"),
+        tag("that player discards"),
+        tag("the player discards"),
+    ))
+    .parse(input)
+}
+
+fn parse_discard_this_way_affirmative_connector(input: &str) -> OracleResult<'_, AbilityCondition> {
+    value(
+        AbilityCondition::effect_performed(),
+        (tag("if "), parse_discard_this_way_subject, tag(" a card this way, ")),
+    )
+    .parse(input)
+}

Based on learnings, the referenced documentation for this file states: "The discard-this-way condition additions should therefore be generalized across supported subject/anaphor forms and wired into the existing condition dispatcher, not implemented as card-name or full-string special cases." As per coding guidelines, crates/engine/src/parser/**/*.rs requires composing nom combinators across independent dimensions "instead of enumerating full-string permutations."

Also applies to: 9493-9523

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/engine/src/parser/oracle_nom/condition.rs` around lines 9425 - 9463,
Refactor parse_discard_this_way_affirmative_connector and
parse_discard_this_way_negated_connector to compose the existing subject
alternatives from parse_affirmative_reflexive_connector and
parse_negated_reflexive_connector with the shared discard-this-way verb suffix,
preserving each subject’s correct verb form. Remove the duplicated full tag()
permutations so both bare and discard-specific connectors derive their
subject/anaphor coverage from one source.

Sources: Coding guidelines, Path instructions

@matthewevans
matthewevans force-pushed the maintainer/rescue-5808-discard-this-way branch from 3cbf437 to 454d0cc Compare August 2, 2026 07:44
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

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

🟢 Added (1 signature)

  • 1 card · ➕ ability/Intensify · added: Intensify (conditional=previous effect outcome)
    • Affected (first 3): Great Desert Hellion

🔴 Removed (1 signature)

  • 1 card · ➖ ability/~ · removed: ~
    • Affected (first 3): Great Desert Hellion

🟡 Modified fields (3 signatures)

  • 2 cards · 🔄 ability/Draw · changed field conditional: previous effect outcome
    • Affected (first 3): Chains of Mephistopheles, Magus of the Chains
  • 2 cards · 🔄 ability/Mill · changed field conditional: not (previous effect outcome)
    • Affected (first 3): Chains of Mephistopheles, Magus of the Chains
  • 1 card · 🔄 ability/DamageAll · changed field conditional: previous effect outcome
    • Affected (first 3): Tainted Specter

1 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

@matthewevans
matthewevans added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 4b4e9e5 Aug 2, 2026
14 checks passed
@matthewevans
matthewevans deleted the maintainer/rescue-5808-discard-this-way branch August 2, 2026 08:40
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