Skip to content

refactor(parser): typed AbilityIr shell + source-addressed builder key (unit 3b, byte-safe subset)#5513

Merged
matthewevans merged 2 commits into
mainfrom
ship/parser-ability-ir-shell-3b
Jul 10, 2026
Merged

refactor(parser): typed AbilityIr shell + source-addressed builder key (unit 3b, byte-safe subset)#5513
matthewevans merged 2 commits into
mainfrom
ship/parser-ability-ir-shell-3b

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Parser IR remediation — unit 3b (byte-safe subset)

Two byte-identical parser migrations from Plan 01 unit 3b. No behavior changes; all three lowered-parity oracles held (60 *_lowered.snap, 21 parser/snapshots/…pipeline_snapshot_tests…, and a data/card-data.json regen).

7c2b0dfabf — unit 3b-1: source-address the builder key

OracleDocBuilder.items re-keyed (first_line, ordinal_within_span)(first_line, start_byte, ordinal_within_span). conflicts_with rejects only overlapping sibling bytes, so two exact non-overlapping clauses on one physical line with the same ordinal passed the conflict check and then collided on the map key (second clause lost); the old key also ordered same-line siblings by ordinal rather than byte position. Inert today (every item spans the whole document, start_byte == 0) — fixed before unit-3b emission can fire it. Discriminating test two_exact_clauses_on_one_line_coexist_and_order_by_byte was watched go red against the reconstructed old key (sole failure among 18,620 tests).

20e5cf0eb7 — units 3b-2/3b-3: typed AbilityIr shell

  • AbilityShellIr { sub_link: Option<SubAbilityLink> } — the typed home for the one root AbilityDefinition field no ClauseIr/ParsedEffectClause can express (verified by auditing the return expressions of all 9 effect-side bypasses: they set only clause-expressible fields plus balance's root sub_link). Option<_> so a defaulted shell defers to the lowered stamp instead of overwriting it.
  • AbilityIr { source_text, body: EffectChainIr, shell } — no AbilityDefinition escape-hatch field. source_text (not OracleUnitSource) because the unit allocator is not threaded through ParseContext yet; minting a span here would fabricate precision.
  • ChainLoweringMode { Standalone, WithContext } (exhaustive, no _, no Default) types the 8-vs-9 bypass-set asymmetry between the two shims (ISSUES chore: update coverage stats and badges #9), dispatched by one try_parse_chain_bypass, preserved byte-for-byte — a parity migration never absorbs a bug fix.
  • lower_ability_ir is the single post-parse authority (lower → finalize → anchor → shell). finalize_effect_chain + apply_owner_library_reveal_anchor_from_text were relocated out of both shims into lower_ability_ir (oracle_effect/mod.rs) — the deletion site for unit 7's removal gate moved accordingly.

Deferred (out of this byte-safe migration)

3b-3′ / 3b-4 / 3b-5 as originally specified are deferred to a dedicated recognizer→IR bring-up plan. They require converting whole-body def-building recognizers (the 9 bypasses; the 8 TriggerBody::PreLowered producers — vote/pile/modal/reanimator) into IR. Those recognizers have no EffectChainIr sibling and deliberately skip the standard lowering transforms, so the conversion is behavioral, not byte-safe — blocked at source: oracle_trigger.rs:1155 ("chain parsing would fragment it into Unimplemented chunks"). Per the parity migration's #1 rule ("a moved byte is a stop"), it is handed off rather than absorbed. The byte-safe boundary landed here: clause-expressible bodies got AbilityShellIr; whole-body recognizers stay as-is.

…te, ordinal) (unit 3b-1)

`OracleDocBuilder::items` was keyed `(first_line, ordinal_within_span)`. Two
exact, NON-overlapping clauses on one physical line with the same
`ordinal_within_span` pass `conflicts_with` (which rejects only OVERLAPPING
sibling bytes) and then collide on the map key: `emit` returns
`DuplicateItemPosition` and the second clause is lost. A `(first_line, ordinal)`
key would also order same-line siblings by ordinal rather than by byte position,
so an emitter assigning ordinals out of byte order would have `finish()` return
them reversed and `lower_oracle_ir` would lower them reversed -- silently.

Add `start_byte` to the key. Inert today (every item spans the whole document,
so `start_byte == 0`), but unit 3b emits exact spans, at which point
"Destroy target creature. It can't be regenerated." is two clauses on one line.
Fixed before it can fire.

Discriminating test: `two_exact_clauses_on_one_line_coexist_and_order_by_byte`
emits the later clause first and asserts both coexist and come back in byte
order. Watched go red against the reconstructed `(first_line, ordinal)` key:
18620 tests run, exactly 1 failed, and it was this test.
…ape hatch (units 3b-2, 3b-3)

The two effect-chain entry points, `parse_effect_chain` and
`parse_effect_chain_with_context`, each opened with a hand-maintained stack of
whole-body `try_parse_* -> return AbilityDefinition` bypasses, then lowered the
IR and ran `finalize_effect_chain` + the owner-library anchor inline. Two latent
hazards lived here:

1. The two stacks are NOT the same set. `parse_effect_chain` runs 8 bypasses;
   `parse_effect_chain_with_context` runs the same 8 as a strict prefix plus
   `try_parse_exile_pile_shuffle_cloak`. Duplicating them as two `if let`
   ladders invited silent divergence. This is now typed data: `ChainLoweringMode
   { Standalone, WithContext }` (exhaustive, no `_` arm, no `Default`), dispatched
   by one `try_parse_chain_bypass`. The asymmetry is preserved byte-for-byte --
   it is a suspected-accidental pre-existing quirk (ISSUES.md #9), and a parity
   migration never absorbs a bug fix.

2. `try_parse_balance_equalization` sets `sub_link = SequentialSibling` on its
   ROOT def (CR 608.2e: three independent equalize steps, each resolving even
   when an optional parent is declined). No `ClauseIr`/`EffectChainIr` field can
   express a root `sub_link` -- `lower_effect_chain_ir` derives it from the
   previous clause boundary, and the root has none, so it is unconditionally
   `ContinuationStep`. Converting that bypass to return a bare `EffectChainIr`
   would silently flip its semantics. `AbilityShellIr` is the typed home for
   exactly the root fields no clause can hold; today that is `sub_link` alone
   (verified by auditing the return expressions -- not field names -- of all nine
   bypasses: they set only kind/effect/sub_ability/duration/player_scope, all
   clause-expressible, plus balance's root sub_link).

`lower_ability_ir` is now the single authority for the post-parse sequence:
lower the chain, finalize, anchor, then apply the shell. `finalize` and the
anchor stay here rather than in `lower_effect_chain_ir` so the ~7 production
callers that lower a chain without being a whole ability body are unaffected.

Parity: 191 snapshots byte-identical (0 .snap.new); clippy clean; test-engine
18620 run with the sole failure a pre-existing foreign integration-registration
guard; data/card-data.json byte-identical (H0==H_after==9fdef189), with a
positive control (min_x_value=77 injected into the broad lowering path) proving
the card-data probe discriminates (H_ctrl==48fbb42a).
@matthewevans
matthewevans enabled auto-merge July 10, 2026 16:14

@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 updates the OracleDocBuilder in doc.rs to key items by (first_line, start_byte, ordinal_within_span) instead of (first_line, ordinal_within_span). This ensures that multiple non-overlapping clauses on the same physical line do not collide and are correctly ordered by their byte position. A new unit test has been added to verify this behavior. Additionally, effect_chain.rs introduces AbilityShellIr and AbilityIr structs to model root-level metadata and encapsulate effect chains with their source text. I have no feedback to provide as there are no review comments to assess.

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 10, 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 166057c Jul 10, 2026
13 checks passed
@matthewevans
matthewevans deleted the ship/parser-ability-ir-shell-3b branch July 10, 2026 16:37
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