Skip to content

refactor(parser): retain nom dispatch results as IR - #6776

Merged
matthewevans merged 5 commits into
mainfrom
ship/parser-nom-dispatch-ir
Jul 29, 2026
Merged

refactor(parser): retain nom dispatch results as IR#6776
matthewevans merged 5 commits into
mainfrom
ship/parser-nom-dispatch-ir

Conversation

@matthewevans

@matthewevans matthewevans commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

  • retain generic nom-dispatch success as AbilityIr through the Oracle document lowering seam
  • preserve exact unsupported residual category, fragment, root description, X floor, and printed ability slot accounting
  • cover Forked Bolt distribution metadata and update affected lossless-IR snapshots

Verification

  • cargo clippy -p engine --lib -- -D warnings
  • cargo nextest run -p engine --lib (17,922 passed; 6 skipped)
  • cargo nextest run -p engine --test integration (4,164 passed; 2 skipped)
  • full MTGJSON export parity: actual base → C1 → C2 identical SHA-256 a788a27b43682f98c595ea7ae0a0de7ff997923cf90104d33a782fa464968ad8
  • parser combinator, PreLowered ratchet, and skill-doc gates

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of unsupported spell and ability text, preserving names, fragments, descriptions, and level requirements.
    • More consistently reports unimplemented effects when oracle text cannot be fully parsed.
    • Preserves distribution and multi-target details for supported effects such as Forked Bolt.
  • Tests

    • Added coverage for unsupported ability details and multi-target distribution metadata.

@matthewevans
matthewevans enabled auto-merge July 29, 2026 04:36
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Unsupported spell and ability residuals now use structured UnsupportedAbilityIr data. Dispatch, document emission, oracle lowering, fallback parsing, and tests were updated to preserve residual names, fragments, descriptions, and minimum values.

Changes

Structured unsupported residual flow

Layer / File(s) Summary
Structured unsupported IR contract
crates/engine/src/parser/oracle_ir/doc.rs
Introduces UnsupportedAbilityIr and replaces raw unsupported text fields in oracle and spell payload IR.
Dispatch, emission, and lowering
crates/engine/src/parser/oracle_dispatch.rs, crates/engine/src/parser/oracle.rs
Dispatch returns NomDispatchIr, routes spell and unsupported results separately, and lowers structured residuals into unimplemented ability definitions.
Fallbacks and regression coverage
crates/engine/src/parser/oracle_class.rs, crates/engine/src/parser/oracle_dispatch.rs, crates/engine/src/parser/oracle_ir/snapshot_tests.rs, crates/engine/src/parser/oracle_tests.rs
Updates fallback construction and verifies unsupported payload preservation and Forked Bolt distribution metadata.

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

Sequence Diagram(s)

sequenceDiagram
  participant OracleParser
  participant dispatch_line_nom
  participant DocEmitter
  participant lower_unsupported_node
  OracleParser->>dispatch_line_nom: dispatch line
  dispatch_line_nom-->>OracleParser: NomDispatchIr::Spell or Unsupported
  OracleParser->>DocEmitter: emit spell or unsupported IR
  DocEmitter-->>OracleParser: OracleNodeIr
  OracleParser->>lower_unsupported_node: lower UnsupportedAbilityIr
  lower_unsupported_node-->>OracleParser: AbilityDefinition with Effect::Unimplemented
Loading

Possibly related PRs

  • phase-rs/phase#6714: Adds or refactors the ability-IR parsing infrastructure used by the new spell dispatch path.
  • phase-rs/phase#6733: Refactors the unsupported spell residual flow and related oracle IR emission and lowering.

Suggested labels: quality

Suggested reviewers: nghetienhiep, nickmopen, andriypolanski

🚥 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 summarizes the main change: retaining nom dispatch results as IR in the parser.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ship/parser-nom-dispatch-ir

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

@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: 2

🤖 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_ir/doc.rs`:
- Around line 42-68: Replace UnsupportedAbilityIr.name’s String with a closed
enum covering Unknown and the known trigger/static/replacement/effect structure
categories, and update its constructors to accept or produce those variants.
Propagate the typed category through all UnsupportedAbilityIr producers and
convert it to the legacy string only at Effect::unimplemented, preserving
existing output names.

In `@crates/engine/src/parser/oracle.rs`:
- Around line 6604-6610: Update the fallback dispatch match around
dispatch_line_nom so the previously computed min_x_value is threaded into both
NomDispatchIr arms: apply max with the spell IR shell’s existing floor before
emitting Spell, and pass the same floor into the Unsupported residual node
emitter instead of allowing it to seed zero. Preserve the CR 601.2b announced-X
floor for both fallback paths.
🪄 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: 7104e1f6-6987-49d9-ae21-2e52192e83b7

📥 Commits

Reviewing files that changed from the base of the PR and between 32a6a98 and 59e80f7.

⛔ Files ignored due to path filters (21)
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__arni_brokenbrow_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__baneslayer_angel_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__barbarian_class_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__birds_of_paradise_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__bomat_courier_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__brazen_borrower_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__changeling_outcast_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__experiment_one_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__goblin_guide_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__leonin_arbiter_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__lumen_class_frigate_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__monastery_swiftspear_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__murderous_rider_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__questing_beast_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__reckless_bushwhacker_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__serra_angel_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__slippery_bogle_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__smugglers_copter_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__snapcaster_mage_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__thalia_guardian_of_thraben_ir.snap is excluded by !**/*.snap, !**/snapshots/**
  • crates/engine/src/parser/oracle_ir/snapshots/engine__parser__oracle_ir__snapshot_tests__wolfir_silverheart_ir.snap is excluded by !**/*.snap, !**/snapshots/**
📒 Files selected for processing (6)
  • crates/engine/src/parser/oracle.rs
  • crates/engine/src/parser/oracle_class.rs
  • crates/engine/src/parser/oracle_dispatch.rs
  • crates/engine/src/parser/oracle_ir/doc.rs
  • crates/engine/src/parser/oracle_ir/snapshot_tests.rs
  • crates/engine/src/parser/oracle_tests.rs

Comment on lines +42 to +68
pub(crate) struct UnsupportedAbilityIr {
pub(crate) name: String,
pub(crate) fragment: String,
pub(crate) description: String,
}

impl UnsupportedAbilityIr {
pub(crate) fn unknown(text: impl Into<String>) -> Self {
let text = text.into();
Self {
name: "unknown".to_string(),
fragment: text.clone(),
description: text,
}
}

pub(crate) fn new(
name: impl Into<String>,
fragment: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
name: name.into(),
fragment: fragment.into(),
description: description.into(),
}
}

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

Use a closed type for the residual category.

name: String makes coverage keys typo-prone and lets future producers create unrecognized categories without compiler enforcement. Model the known categories (Unknown, trigger/static/replacement/effect structure) as an enum, converting to the legacy string only at Effect::unimplemented.

As per coding guidelines, “prefer enums over stringly typed data”; as per path instructions, CLAUDE.md requires unsupported residuals to remain typed.

🤖 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_ir/doc.rs` around lines 42 - 68, Replace
UnsupportedAbilityIr.name’s String with a closed enum covering Unknown and the
known trigger/static/replacement/effect structure categories, and update its
constructors to accept or produce those variants. Propagate the typed category
through all UnsupportedAbilityIr producers and convert it to the legacy string
only at Effect::unimplemented, preserving existing output names.

Sources: Coding guidelines, Path instructions

Comment on lines +6604 to +6610
// Priority 14a: the dispatcher parses once and retains successful spell IR.
// Priority 15: its exact unsupported payload reaches final lowering unchanged.
match dispatch_line_nom(&line, card_name, ctx.host_self_reference.clone()) {
NomDispatchIr::Spell(ir) => emitter.ability_ir_at(item_line, ir),
NomDispatchIr::Unsupported(unsupported) => {
emitter.unsupported_ir_at(item_line, unsupported)
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the announced-X floor through fallback dispatch.

line arrives after its X can't be 0 suffix was stripped, but neither arm carries the already-computed min_x_value: Spell(ir) retains the default shell floor and Unsupported calls an emitter that seeds zero. A fallback-dispatched spell can therefore be cast with X = 0. Thread the floor into both arms, using max for the IR shell and the residual node.

As per path instructions, engine rules must preserve the CR 601.2b announced-X floor.

🤖 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.rs` around lines 6604 - 6610, Update the
fallback dispatch match around dispatch_line_nom so the previously computed
min_x_value is threaded into both NomDispatchIr arms: apply max with the spell
IR shell’s existing floor before emitting Spell, and pass the same floor into
the Unsupported residual node emitter instead of allowing it to seed zero.
Preserve the CR 601.2b announced-X floor for both fallback paths.

Source: Path instructions

@matthewevans
matthewevans added this pull request to the merge queue Jul 29, 2026
@github-actions

Copy link
Copy Markdown

Parse changes introduced by this PR

✓ No card-parse changes detected.

Merged via the queue into main with commit fb2c912 Jul 29, 2026
15 checks passed
@matthewevans
matthewevans deleted the ship/parser-nom-dispatch-ir branch July 29, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant