Skip to content

chore: update coverage stats and badges - #9

Merged
matthewevans merged 2 commits into
mainfrom
chore/update-coverage-badges
Apr 8, 2026
Merged

chore: update coverage stats and badges#9
matthewevans merged 2 commits into
mainfrom
chore/update-coverage-badges

Conversation

@matthewevans

Copy link
Copy Markdown
Member

Automated update of README coverage badges from latest card data.

@matthewevans
matthewevans enabled auto-merge (squash) April 8, 2026 14:39
@matthewevans
matthewevans force-pushed the chore/update-coverage-badges branch from 58ef9f6 to a1d4e9b Compare April 8, 2026 14:49
@matthewevans
matthewevans merged commit 9ffd088 into main Apr 8, 2026
2 checks passed
@matthewevans
matthewevans deleted the chore/update-coverage-badges branch April 8, 2026 14:56
andriypolanski pushed a commit to andriypolanski/phase that referenced this pull request Jul 10, 2026
…y (unit 3b, byte-safe subset) (phase-rs#5513)

* refactor(parser): key OracleDocBuilder items by (first_line, start_byte, 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.

* refactor(parser): typed AbilityIr shell replaces the effect-chain escape 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 phase-rs#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).

---------

Co-authored-by: matthewevans <matthewevans@users.noreply.github.com>
nickmopen pushed a commit to nickmopen/phase that referenced this pull request Jul 12, 2026
…hase-rs#5646)

Both cards fall under misparse-backlog root-cause category phase-rs#9 (wrong
player/controller scope):

- Mercenaries ("Any player may activate this ability" + a one-shot
  "would deal damage to you" prevention shield): the shield's recipient
  scope was hardcoded to `Any` rather than binding to the activator
  (CR 602.2a: an activated ability's controller is whoever activated
  it). This also affected ~27 sibling cards using the same one-shot
  prevention shape (Circle/Rune of Protection and others), all of which
  previously had zero recipient restriction on their shield at runtime.
  Fixed by adding a "to you" recognizer to the shared damage-recipient
  combinator and threading it through the prevention branch instead of
  discarding it.

- Total War ("whenever a player attacks... destroy all ... creatures
  that player controls..."): the destroy effect's controller filter was
  hardcoded to the enchantment's own controller instead of the
  attacking player who triggered it. Fixed by adding a new dispatch arm
  recognizing "a player attacks with" as introducing the existing
  ControllerRef::TriggeringPlayer scope for the effect body's "that
  player" anaphor.

- Typhoon ("deals damage to each opponent equal to the number of
  Islands that player controls"): verified already correct end-to-end
  (per-opponent ScopedPlayer binding resolves correctly in the runtime
  fan-out); added regression tests only, no code change.

Total War's separate "except for creatures the player hasn't
controlled continuously since the beginning of the turn" exemption
clause remains unimplemented (a distinct dropped-modifier-clause root
cause, not category phase-rs#9) and is documented as a known gap at the parse
site for follow-up.


Claude-Session: https://claude.ai/code/session_01XbgwGxbU9NHN9kou9isp8K

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
lgray added a commit to lgray/phase that referenced this pull request Jul 14, 2026
…oop, current board only)

ROUND-4 ADVERSARIAL REVIEW broke five more claims (Appendix B now has ten). RC-2
SURVIVED -- the reviewer could not break it and confirmed the full chain.

New refutations, all measured:
- B0/phase-rs#6: 'Combo B is ONE activation' is FALSE. drive_offline_kilo_freed_relic
  (corpus.rs:1556) takes TWO ActivateAbility actions; its comment: 'Relic has two
  mana abilities; the tap-self one would not fire Kilo's trigger.' The CR 605.3a
  nesting story is rules-legal but engine-false -- a mana ability in a ManaPayment
  window is still its own GameAction (engine.rs:4867). A single-action arming latch
  cannot capture it.
- phase-rs#7: 'generalizing normalize_recast_frame lifts all 13 ObjectReentry rows' is
  FALSE -- it lifts ZERO. 6 rows are blocked by R6/RC-1/RC-3, not id churn; the
  other 7 need id-CANONICALIZATION, because stripping the churned object does not
  fix STABLE objects whose paired_with/attached_to point at the dead id (Deadeye
  Navigator never moves and still fails equality). Demoted from 'Phase 2.5 quick
  win' to P6, its own PR with its own soundness proof.
- phase-rs#8: 'C3 is the arm no review broke' is FALSE -- ability_scan.rs:2454 sets
  sibling:true for ANY typed filter, so the predicate rejects Intruder Alarm, which
  is CR 732.2a's OWN worked example.
- phase-rs#9: RC-1's 'measured trips, in order' is the wrong provenance -- board_covers runs
  first and returns false before the firewall is ever reached.
- phase-rs#10: the Hum of the Radix fixture is UNSATISFIABLE ('each ARTIFACT spell'; Sprout
  Swarm is a green instant). The card is Damping Sphere.
- 6 of 15 verification rows were VACUOUS, dominated by the ARMING gate. Arming (P1)
  is therefore a PREREQUISITE, not the last phase. Re-sequenced.
- The 'Cryptolith Rite vs Earthcraft' crux pair was vacuous: Cryptolith + Squirrel
  Nest is NOT A LOOP AT ALL, so it declined for the wrong reason. Redesigned to hold
  the loop fixed and vary only the cost shape.
- Corpus partition has THREE terminals (L-OFFER / L-AUTOWIN / WAIVED), and the
  bi-implication must fix the offline/live asymmetry UPWARD or it will pressure us
  into making the only game-ending path LESS sound.

THE GOVERNING CONSTRAINT (user steer, now S4.6 and the spine of the plan): the
player proposes a FIXED loop impactable only by the CURRENT board -- and we DRIVE
that sequence on a clone. So every board ability that fires ALREADY LANDS IN DELTA.
The firewall's only legitimate job is what the drive is structurally blind to:
  (1) monotone depletion outside the drive window  -> C2
  (2) a DISCONTINUITY (a threshold tripping later) -> C3
Everything else is measured: an effect that SCALES moves delta (C1); an effect that
merely READS the growing axis yields constant delta and is HARMLESS -- which is
exactly why today's predicate rejects the rulebook's own example.

=> C3 collapses from a REWRITE into a DELETION. The condition scan already exists at
gate (4) (resource.rs:1524, inspects def.condition); the defect is gate (1) scanning
EFFECTS. Delete gate (1), R3, R5, R6; narrow gate (4) to a Comparator against the
growing axis. Deleting R6 alone is worth 2 corpus rows (Kiki-Jiki, Splinter Twin).
New surface is now honestly TWO subsystems (P1 driver, P2 CR 113.6 predicate) + C2 --
not three.

Assisted-by: ClaudeCode:claude-opus-4.8
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