Skip to content

refactor(parser): hoist Class-H replacement producers to the oracle document IR seam - #6123

Merged
matthewevans merged 1 commit into
mainfrom
ship/u2-doc-seam-hoist
Jul 18, 2026
Merged

refactor(parser): hoist Class-H replacement producers to the oracle document IR seam#6123
matthewevans merged 1 commit into
mainfrom
ship/u2-doc-seam-hoist

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Replacement producers now emit typed OracleNodeIr::Replacement document
nodes instead of PreLoweredReplacement, giving the document seam a single
authority for replacement lowering. Relation discovery (item_replacement)
accepts both representations so ID-keyed LinkedChoice relations survive
the hoist: choose + chosen-dependent ETB counters still compose into one
replacement and chosen-type cost reducers keep their linkage.

Six not-yet-constructed IR/index APIs are re-homed to the Class-B bring-up
under dated PLAN-05 DEBT item-level allows. The four oracle_ir snapshots
move representation-only (PreLoweredReplacement -> Replacement).

Full-corpus card-data export verified byte-identical to the pre-hoist
baseline (sha256 18fe47ae07...aac7e8b5, 35424 faces).

…ocument IR seam

Replacement producers now emit typed OracleNodeIr::Replacement document
nodes instead of PreLoweredReplacement, giving the document seam a single
authority for replacement lowering. Relation discovery (item_replacement)
accepts both representations so ID-keyed LinkedChoice relations survive
the hoist: choose + chosen-dependent ETB counters still compose into one
replacement and chosen-type cost reducers keep their linkage.

Six not-yet-constructed IR/index APIs are re-homed to the Class-B bring-up
under dated PLAN-05 DEBT item-level allows. The four oracle_ir snapshots
move representation-only (PreLoweredReplacement -> Replacement).

Full-corpus card-data export verified byte-identical to the pre-hoist
baseline (sha256 18fe47ae07...aac7e8b5, 35424 faces).
@matthewevans
matthewevans enabled auto-merge July 18, 2026 00:32

@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 refactors the Oracle parser to transition from pre-lowered definitions to typed intermediate representations (IR), specifically introducing ReplacementIr and OracleNodeIr::Spell(EffectChainIr) to represent parsed elements before lowering. It updates several parsing functions, the DocEmitter helper, and the item resolution logic to process these IR nodes uniformly. The single review comment regarding a missing #[allow(dead_code)] attribute has been removed, as the repository style guide explicitly instructs reviewers not to duplicate Clippy or compiler warnings that are already covered by the CI gate. Consequently, there is no additional feedback to provide.

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.

@matthewevans
matthewevans added this pull request to the merge queue Jul 18, 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 61e597a Jul 18, 2026
13 checks passed
@matthewevans
matthewevans deleted the ship/u2-doc-seam-hoist branch July 18, 2026 00:59
andriypolanski pushed a commit to andriypolanski/phase that referenced this pull request Jul 28, 2026
…se-rs#6722)

* fix(parser): Plan 05b T9a — route U0-39 through lower_ability_ir

The instant/sorcery prevention recognizer (CR 615.1a: "Effects that use the
word 'prevent' are prevention effects") was the only spell path in the parser
that lowered a whole ability body without `finalize_effect_chain`, the
owner-library reveal anchor, and the `WithContext` whole-body recognizer set.
It now goes through `parse_ability_ir_with_context` + `ability_ir_at`, i.e.
`lower_ability_ir` — which is what `parse_effect_chain_with_context` has
always been.

phase-rs#6123 introduced the skip. Its hunk at oracle.rs:5108 replaced

    let def = parse_effect_chain_with_context(&line, AbilityKind::Spell, &mut ctx);

with the raw pair `parse_effect_chain_ir` + `lower_effect_chain_ir` while
hoisting the Class-H replacement producers. At 61e597a^,
`parse_effect_chain_with_context` was `try_parse_chain_bypass(..)` followed by
`lower_ability_ir(..)`, so the conversion dropped THREE things, not the two the
recensus records: finalize, the anchor, and the bypass set. The skip was
incidental — the commit body describes only the replacement hoist and never
mentions a spell path, and its byte-identity claim is why it passed review.

Measured over the real pool (data/mtgjson/AtomicCards.json, 34,739 cards /
35,516 faces, generated from the repo root): card-data.json is byte-identical
before and after, sha256
c3f8431c494c1a9710e08fc15161eab75203a2dc98a5aa35e3f7a993ec43c245,
99,201,978 bytes.

Zero delta is a semantic no-op, not an unapplied probe. The site is reached 155
times across 153 cards; disabling it moves the export (probe went red), and the
compiler independently witnesses the conversion — `OracleNodeIr::Spell` lost
its only producer. Per-mechanism reach over those 155 reaches:

  * finalize_effect_chain (6 passes): no-op — lower_ability_ir(&ir) ==
    lower_effect_chain_ir(&ir.body) on all 155
  * owner-library reveal anchor: 0 cards — no instant/sorcery prevention line
    carries both "shuffles it into their library" and "reveals the top card of
    their library"
  * WithContext whole-body bypasses: 0 cards — no bypass fires on any of the 155
  * CR 707.9a printed-slot stamping: 0 cards — all 7 pool-wide carriers of
    RetainPrintedAbilityFromSource are permanents, so none reaches an is_spell site
  * has_unimplemented gate: 0 flips — 148 emit and 7 decline, identically both ways

So this is a latent-defect fix with no present-day behavioral change: it closes
the CR 707.9a mis-index window recorded as recensus harvest item 4 (now provably
unreachable rather than merely "plausible/latent"), and makes U0-39 conform to
phase A, which is the precondition `ability_ir_at`'s doc block names for T9b's
payload swap.

The `OracleNodeIr::Spell` payload is untouched — that swap is T9b.

* test(parser): Plan 05b T9a — two-layer guards for the prevention recognizer

153 cards in the pool reach the instant/sorcery prevention recognizer (U0-39)
and, before this commit, NOT ONE of them was snapshotted. The only spell path
that lowered a whole ability body without finalize, the owner-library anchor and
the WithContext bypass set was also the one with no two-layer guard — which is
how phase-rs#6123 narrowed it without any test noticing, and why T9a's full-pool zero
delta had nothing local to pin it to.

These are the §5.3 remediation for that: T9b's payload swap lands on this exact
recognizer, so it must not be able to move it silently.

  * Fog — the canonical single-clause case. The whole card is the prevention
    sentence, so the chain is one clause with no sub_ability.
  * Blunt the Assault — the multi-clause case the recognizer exists for. The
    site's own comment cites this shape verbatim ("preserve any preceding
    clauses ('You gain 1 life for each ...')") because the prevention marker
    sits in the SECOND sentence, so a replacement classifier reaching the line
    first would drop the life gain. It lowers to GainLife{ObjectCount(Creature)}
    with the PreventDamage chained as sub_link: SequentialSibling — two
    independent instructions in the order written, CR 608.2c. This is the
    non-degenerate fixture: a one-clause body would take the same path either
    way and prove nothing about chain assembly.

Both texts are verbatim MTGJSON, not paraphrases — a paraphrase can take a
different parser branch and go green while the real card stays broken.

Snapshots read and verified correct before acceptance, not merely accepted as
current: both nodes are PreLoweredSpell (the visible witness of T9a's routing),
Fog is PreventDamage{All, Any, CombatDamage} with UntilEndOfTurn, and Blunt the
Assault retains BOTH clauses.

---------

Co-authored-by: matthewevans <matthewevans@users.noreply.github.com>
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