Skip to content

fix(parser): keep the card-type gate on "from among them" casts (#6880) - #6959

Merged
matthewevans merged 4 commits into
mainfrom
fix/6880-cast-type-gate
Aug 3, 2026
Merged

fix(parser): keep the card-type gate on "from among them" casts (#6880)#6959
matthewevans merged 4 commits into
mainfrom
fix/6880-cast-type-gate

Conversation

@matthewevans

@matthewevans matthewevans commented Aug 3, 2026

Copy link
Copy Markdown
Member

Fixes #6880.

The bug

Velomachus Lorehold: "…you may cast an instant or sorcery spell with mana value less than or equal to Velomachus Lorehold's power from among them without paying its mana cost."

The mana-value gate was enforced; the instant-or-sorcery gate was not, so any card type could be cast. The parsed CastFromZone carried the right constraint: ManaValue{LE, Power(Source)} but a bare target: ExiledBySource — an anaphor with no card-type leg.

The correct composition already existed in a sibling branch of the same function, which parses the type disjunction and ANDs it with the anaphor. The fix routes the bare-emitting branches through one shared helper.

Three branches, not two

The survey that scoped this named two bare-emitting branches. There are three: "from among them", the hand-bound sibling, and "from among those/the exiled cards". The third has type-gated members (Eager Flameguide, Kylox), so fixing only the first two would have left the class half-covered.

What is deliberately not copied from the sibling

The sibling composition includes FilterProp::InZone { zone: Zone::Exile }. Copying that leg would have been worse than the bug. Velomachus is Dig { keep_count: 0 } — the looked-at cards stay in the library and are published via last_revealed_ids. An exile-zone leg matches nothing there, converting a permissive filter into a total no-op: the card would become uncastable under a commit claiming a fix. The type gate is ANDed with ExiledBySource alone, and the reason is documented at the call site.

No engine change

Confirmed with the mechanism, not by assertion:

  • TargetFilter::references_exiled_by_source uses any over And legs, so the composed shape still enters the look-at fallback.
  • remap_exiled_by_source_for_looked_cards recurses into And, so the library remap survives.

Class — 37 anchor cards, 12 type-restricted, 10 fixed

The original scoping said 8 cards. That was wrong in both directions. Re-derived across all 35,657 entries: 41 cards carry the anchor with a bare ExiledBySource, of which 15 name a card type (10 fixed, 5 still bare) and 26 name none and are correctly bare. 10 + 5 + 26 = 41.

(An earlier revision of this description said 37/12/25. That was itself an undercount, caught by the same denominator discipline: the two anchor phrasings were censused separately, and a "cast " regex missed The Omenkeel, whose verb is "play lands". Corrected here.)

The original list also omitted four cards and was partly unverifiable by the method that produced it — 5 of its 8 have abilities: [], because the CastFromZone lives under .triggers. A census walking only .abilities silently undercounts.

Fixed (10 of the 15) — each checked against its real Oracle text:

card gate now emitted
Velomachus Lorehold, Jace's Mindseeker, Muse Vortex, Summons of Saruman, Talent of the Telepath AnyOf[Instant, Sorcery]
Chiss-Goria, Forge Tyrant Artifact
Herald of Amity Subtype(Aura)
Eager Flameguide Creature
Narset, Enlightened Master Card + Non(Creature)
The Omenkeel Land"you may play lands from among those cards" previously let any exiled card be played as a land

Plus one more card from outside this population — see the follow-up section: Mindclaw Shaman, corrected on the hand-bound branch. It is not a missing member of the 41/15 set; that set is defined by a bare ExiledBySource, which the hand-bound branch never emits. 11 cards are corrected in total.

Still permissive (5 of the 15) — pre-existing gap, not a regression, each blocked by a missing helper rather than by this change:

card clause why it still fails
Epic Experiment, Ral, Leyline Prodigy "instant and sorcery spells" helper handles " or " only
Kylox "any number of instant and/or sorcery spells" same
Collected Conjuring "up to two sorcery spells" leading count blocks the article parse
Sanwell, Avenger Ace "a Vehicle or artifact creature spell" Vehicle is a subtype, not a core type

Extending parse_cast_type_disjunction to "and" / "and/or" would reach three of them but changes two other call sites, so it is left for follow-up rather than widened here.

On Elder Brain — checked, and it is correctly bare. It reads "You may play lands and cast spells from among the exiled cards", which looks like it should carry a Land gate. It does not, and should not: the clause reaching the cast parser is "spells from among the exiled cards", which names no card type, so the bare binding is right. Verified against parsed card data — Elder Brain emits CastFromZone with a bare ExiledBySource and mode: Cast, whereas The Omenkeel's single "play lands from among those cards" emits mode: Play with And[Typed[Land], ExiledBySource]. Different clauses, correctly different results. Elder Brain is in the 26.

Separately, Elder Brain does have a real defect, filed rather than fixed here. It emits only one unit — the Cast half — with no Play unit and no Typed[Land] anywhere on the card. Its "play lands" permission is dropped outright, not merely under-restricted. That is a different defect in a different population and out of scope for this PR.

Verification

  • Red → green, attributed by test name rather than by resource colour, with the same 22,815 test count on both runs. At base: 22,813 passed / 2 failed (from_among_them_cast_retains_the_instant_or_sorcery_gate, velomachus_does_not_offer_a_creature_inside_its_mana_value_ceiling). After the fix: 22,815 / 22,815.
  • Two of the four tests are anti-swallow guards for the 26 untyped cards — they pass before and after, pinning that a bare "cast a spell" clause still emits a bare anaphor rather than acquiring a spurious type leg. They are guards, not discrimination, and are labelled as such.
  • clippy, test-engine, card-data all green. Parser combinator gate exit 0.
  • CR 601.3 and CR 109.2b added — grep-verified and read to confirm they describe the code.

Inertness — verified end to end

This is parser-side and therefore inert until card-data.json is regenerated and redeployed; it is invisible in the app on merge alone. Verified beyond the AST level: the card-data pipeline was rerun after the fix and the census re-taken against the regenerated file.

Does not close #3267

Sanwell's reported symptom there is rest-to-bottom behaviour, which is untouched — and Sanwell is one of the five whose type gate still does not parse. Please do not auto-close it.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed card-type restrictions in “cast from among” effects.
    • Typed permissions now correctly preserve instant/sorcery or other card-type limits when casting from exiled cards or cards in hand.
    • Unrestricted casting clauses remain unaffected.
    • Corrected gameplay behavior so ineligible card types are no longer offered as castable options.

Follow-up commit: hand-bound coverage (07163ac)

CodeRabbit flagged that the hand-bound branch gained a genuinely new code path — it previously always used a bare Card filter — and that none of the four original tests exercised it; they all drive the exile-bound branches.

The right first question was whether the path is reachable at all, since unreachable code should be documented or removed rather than tested. It is reachable, by a real card: Mindclaw Shaman"target opponent reveals their hand. You may cast an instant or sorcery spell from among those cards without paying its mana cost." Against regenerated card data it now parses to a Typed filter carrying AnyOf[Instant, Sorcery] alongside InZone { Hand } and controller: Opponent.

So it is an 11th corrected card, not merely an untested branch — this PR was undercounting its own impact. Fittingly, Mindclaw Shaman is another abilities: 0, triggers: 1 card, the same shape that made the original 8-card scoping unverifiable.

The new tests assert all three legs (type filter, zone, controller); a test pinning only the type filter would not catch a regression that dropped the zone or the controller. An untyped hand-bound guard is added alongside, as the hand-side counterpart of the exile-side guard.

The same commit sharpens parse_cast_type_gate's doc to state why non-type restrictions carried as properties — Chandra's "red spells", Meeting of the Five's "spells with exactly three colors", Perception Bobblehead's mana-value bound — also yield None: only the type axis gates, so the helper never invents a restriction the Oracle text did not state.

CodeRabbit's other nit — that a sibling branch still hand-rolls the same composition rather than calling the shared helper — is a real single-authority point but changes AST shape for cards currently parsing that way, so it needs its own card-data census. Deferred to #6960.

Why the 11th card was missed — the denominator, not the walk

Worth recording, because it is a different error from the .abilities-vs-.triggers one corrected above.

This description defines its population as "cards carrying the anchor with a bare ExiledBySource" — 41 cards, 15 type-restricted. But the defect lives in a function with three emitting branches, and the hand-bound branch never emits ExiledBySource at all; it emits a Typed hand filter. Mindclaw Shaman was therefore excluded by construction, not by an execution mistake. No amount of walking .triggers as well as .abilities would have surfaced it.

The tell is that the census predicate named a concrete emitted value rather than the behaviour under test (a cast permission that should carry a type gate). Whenever a population is defined by a value one code path emits, the question to ask is how many paths reach the defect and whether each emits that value.

No other hand-bound card is newly corrected

Verified with its own denominator: 35,657 MTGJSON entries → 50 carrying cast[^.]*from among (them|those cards). The hand-bound subset is 4: Mindclaw Shaman (typed → now fixed), and Silent-Blade Oni, Mindleech Mass, Extract Brain (all "cast a spell", untyped → unchanged, and covered by the new untyped guard). The 350-card complement carrying the anaphor outside those 50 was checked for any hand-reveal-plus-cast combination: empty.

Incidental, pinned but not changed: Silent-Blade Oni and Mindleech Mass bind controller: TriggeringPlayer rather than DefendingPlayer. Pre-existing and untouched here.

Velomachus Lorehold reads "you may cast an instant or sorcery spell with
mana value less than or equal to Velomachus Lorehold's power from among
them without paying its mana cost." The mana-value gate was enforced; the
instant-or-sorcery gate was not, so any card type could be cast.

The "from among them" branches emitted `target: TargetFilter::ExiledBySource`
unconditionally -- a bare anaphor with no card-type leg. The correct
composition already existed in a sibling branch in the same function, which
parses the type disjunction and ANDs it with the anaphor. Route all the
bare-emitting branches through one helper so the type gate survives.

Three branches emitted the bare filter, not two: "from among them", the
hand-bound sibling, and "from among those/the exiled cards". The third has
type-gated members (Eager Flameguide, Kylox), so fixing only the first two
would have left the class half-covered.

Deliberately NOT copying the sibling's `FilterProp::InZone { Zone::Exile }`
leg. Velomachus is `Dig { keep_count: 0 }` -- the looked-at cards stay in the
LIBRARY and are published via `last_revealed_ids`, so an exile-zone leg would
match nothing and turn a permissive filter into a total no-op. The type gate
is ANDed with `ExiledBySource` alone.

No engine change. `TargetFilter::references_exiled_by_source` uses `any` over
`And` legs, so the composed shape still enters the look-at fallback, and
`remap_exiled_by_source_for_looked_cards` recurses into `And`, so the library
remap survives.

Ten cards now carry a correct type gate, each verified against its real
Oracle text: Velomachus Lorehold, Jace's Mindseeker, Muse Vortex, Summons of
Saruman and Talent of the Telepath (instant-or-sorcery); Chiss-Goria
(artifact); Herald of Amity (Aura); Eager Flameguide (creature); Narset,
Enlightened Master (noncreature card); and The Omenkeel, whose "you may play
lands from among those cards" previously let any exiled card be played as a
land.

Five cards in the class stay permissive -- a pre-existing gap, not a
regression: Epic Experiment and Ral, Leyline Prodigy ("instant and sorcery
spells"), Kylox ("any number of instant and/or sorcery spells"), Collected
Conjuring ("up to two sorcery spells" -- the leading count blocks the article
parse), and Sanwell ("a Vehicle or artifact creature spell" -- Vehicle is a
subtype). Extending the disjunction helper to "and"/"and/or" would reach
three of them but changes two other call sites, so it is left for follow-up.

Two of the four tests are regression guards for the 31 untyped cards in the
class: they pass before and after, and pin that a bare "cast a spell" clause
still emits a bare anaphor rather than acquiring a spurious type leg.

This is parser-side and therefore inert until card data is regenerated and
redeployed. Verified end to end rather than at AST level: the card-data
pipeline was rerun after the fix and the census re-taken against the
regenerated file.

Does not address #3267 (Sanwell) -- its reported rest-to-bottom symptom is
untouched, and Sanwell is one of the five whose type gate still does not
parse.
@coderabbitai

coderabbitai Bot commented Aug 3, 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: 9 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: eea19a04-27a2-42ae-9044-178926e90a25

📥 Commits

Reviewing files that changed from the base of the PR and between 35be4e0 and fb4ae6f.

📒 Files selected for processing (2)
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/tests/integration/kiora_self_library_peek_cast.rs
📝 Walkthrough

Walkthrough

The parser now preserves card-type restrictions in cast-from-among clauses. It combines type filters with exile-source filters and applies them to hand and exile casts. Tests cover typed Velomachus permissions and unrestricted Svella permissions.

Changes

Cast-from-among card-type filtering

Layer / File(s) Summary
Build combined cast target filters
crates/engine/src/parser/oracle_effect/mod.rs
The parser combines meaningful card-type restrictions with ExiledBySource. Hand-bound and exile-bound cast permissions use the combined filter.
Validate typed and untyped cast permissions
crates/engine/tests/integration/kiora_self_library_peek_cast.rs
Tests verify instant-or-sorcery restrictions for Velomachus, Jace, and Talent. Runtime tests confirm that Velomachus excludes creatures and Svella still offers an eligible creature.
Estimated code review effort: 3 (Moderate) ~20 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: andriypolanski, jacobwoodson, jaytbarimbao-collab

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses Velomachus Lorehold in [#6880], but it does not address Sanwell’s rest-to-bottom requirement in [#3267]. Remove [#3267] from the linked issues or implement Sanwell’s uncast-card handling and type-gate requirements.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The parser changes and tests remain focused on card-type restrictions for casts from “from among them.”
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the parser fix that preserves card-type restrictions on "from among them" casts.
✨ 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 fix/6880-cast-type-gate

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.

🧹 Nitpick comments (2)
crates/engine/src/parser/oracle_effect/mod.rs (2)

21999-22053: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: align the sibling has_from_among_cards_exiled_with_self branch with the new parse_cast_type_gate skip-logic.

parse_cast_type_gate deliberately returns None when the parsed type filters are only TypeFilter::Card/TypeFilter::Any (Line 22028-22032), so an untyped clause stays unrestricted. The pre-existing sibling branch at Line 22991-23016 (has_from_among_cards_exiled_with_self) builds its typed leg with the same parse_cast_type_disjunction(rest).unwrap_or_else(|| parse_type_phrase(rest).0) composition but does not apply this Card/Any skip — an untyped clause there still wraps the target in TargetFilter::And{[Typed(Card), ExiledBySource]} instead of the bare ExiledBySource the new helper would produce. This is functionally harmless (Card matches everything) but is duplicated composition logic that could drift.

Consider reusing parse_cast_type_gate there too for one shared code path.

🤖 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_effect/mod.rs` around lines 21999 - 22053,
Update the has_from_among_cards_exiled_with_self branch to reuse
exiled_cast_target_with_type_gate or parse_cast_type_gate instead of duplicating
type parsing. Preserve the existing ExiledBySource behavior while ensuring
Card/Any-only clauses remain unrestricted and typed clauses are AND-combined
with the exile-source filter.

22900-22908: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the hand-bound branch's new type gate.

This new if let Some(typed) = parse_cast_type_gate(rest) { hand_filter.type_filters = typed.type_filters; } block is a genuinely new code path (the hand-reveal cast permission previously always used a bare Card filter). The paired test file (kiora_self_library_peek_cast.rs) only exercises the exile-bound branches (Velomachus, Jace's Mindseeker, Talent of the Telepath — all typed; Svella, Aetherworks Marvel, Apex of Power — all untyped). None of the new tests set ctx.chain_prior_hand_reveal_target together with a typed clause, so this specific overwrite (replacing hand_filter.type_filters entirely rather than appending) is not exercised by any test in this PR.

Confirm whether a real card combines a hand-reveal target (Silent-Blade Oni's shape) with a card-type restriction, and if so add a parser-level assertion (mirroring from_among_them_cast_retains_the_instant_or_sorcery_gate) that the hand-bound TargetFilter::Typed carries the parsed type filters alongside FilterProp::InZone { zone: Zone::Hand } and the revealed player's controller.

🤖 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_effect/mod.rs` around lines 22900 - 22908,
The hand-bound typed cast-gate path lacks parser-level coverage. In the tests
alongside from_among_them_cast_retains_the_instant_or_sorcery_gate, add a case
using chain_prior_hand_reveal_target with a typed cast clause, asserting the
resulting TargetFilter::Typed preserves the parsed type filters together with
FilterProp::InZone for Zone::Hand and the revealed player’s controller; first
confirm and use a real card pattern matching Silent-Blade Oni.
🤖 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.

Nitpick comments:
In `@crates/engine/src/parser/oracle_effect/mod.rs`:
- Around line 21999-22053: Update the has_from_among_cards_exiled_with_self
branch to reuse exiled_cast_target_with_type_gate or parse_cast_type_gate
instead of duplicating type parsing. Preserve the existing ExiledBySource
behavior while ensuring Card/Any-only clauses remain unrestricted and typed
clauses are AND-combined with the exile-source filter.
- Around line 22900-22908: The hand-bound typed cast-gate path lacks
parser-level coverage. In the tests alongside
from_among_them_cast_retains_the_instant_or_sorcery_gate, add a case using
chain_prior_hand_reveal_target with a typed cast clause, asserting the resulting
TargetFilter::Typed preserves the parsed type filters together with
FilterProp::InZone for Zone::Hand and the revealed player’s controller; first
confirm and use a real card pattern matching Silent-Blade Oni.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 73a6eb2f-2407-4478-a3a6-53f3e1e5614d

📥 Commits

Reviewing files that changed from the base of the PR and between c9daf66 and 35be4e0.

📒 Files selected for processing (2)
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/tests/integration/kiora_self_library_peek_cast.rs

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Generated for head fb4ae6f8dad887cd3c8e7c45a35b1b3777943136.

Parse changes introduced by this PR · 11 card(s), 7 signature(s) (baseline: main 9847b18acbde)

🟡 Modified fields (7 signatures)

  • 5 cards · 🔄 ability/CastFromZone · changed field target: cards exiled by sourceinstant or sorcery + cards exiled by source
    • Affected (first 3): Jace's Mindseeker, Muse Vortex, Summons of Saruman (+2 more)
  • 1 card · 🔄 ability/CastFromZone · changed field target: cards exiled by sourceAura + cards exiled by source
    • Affected (first 3): Herald of Amity
  • 1 card · 🔄 ability/CastFromZone · changed field target: cards exiled by sourceartifact + cards exiled by source
    • Affected (first 3): Chiss-Goria, Forge Tyrant
  • 1 card · 🔄 ability/CastFromZone · changed field target: cards exiled by sourcecard non-creature + cards exiled by source
    • Affected (first 3): Narset, Enlightened Master
  • 1 card · 🔄 ability/CastFromZone · changed field target: cards exiled by sourcecreature + cards exiled by source
    • Affected (first 3): Eager Flameguide
  • 1 card · 🔄 ability/CastFromZone · changed field target: cards exiled by sourceland + cards exiled by source
    • Affected (first 3): The Omenkeel
  • 1 card · 🔄 ability/CastFromZone · changed field target: in hand opponent controls cardin hand opponent controls instant or sorcery
    • Affected (first 3): Mindclaw Shaman

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

… doc

The hand-bound branch got a genuinely new code path -- it previously always
used a bare `Card` filter -- and nothing in the fix commit exercised it. All
four of those tests drive the exile-bound branches.

The path is reachable by a real card. Mindclaw Shaman reads "target opponent
reveals their hand. You may cast an instant or sorcery spell from among those
cards without paying its mana cost", and against regenerated card data it now
parses to a `Typed` filter carrying `AnyOf[Instant, Sorcery]` alongside
`InZone { Hand }` and `controller: Opponent`. So it is an additional card
corrected by this PR, not just an untested branch.

Assert all three legs. A test pinning only the type filter would not catch a
regression that dropped the zone or the controller.

Also add an untyped hand-bound guard, so a bare "cast a spell" hand clause
keeps its bare filter rather than acquiring a spurious type leg -- the
hand-side counterpart of the exile-side guard already present.

Sharpen `parse_cast_type_gate`'s doc to say why non-type restrictions carried
as properties (Chandra's "red spells", Meeting of the Five's "spells with
exactly three colors", Perception Bobblehead's mana-value bound) also yield
`None`: only the type axis gates, so the helper never invents a type
restriction the Oracle text did not state.

Raised by CodeRabbit on #6959.
The untyped guard covered three cards whose clause carries no qualifier at
all next to "spell". That leaves the interesting case untested: Oracle text
that DOES carry a restrictive qualifier immediately next to "spell", but
along an axis this filter does not model.

CR 601.3 genuinely restricts those casts -- just by color or mana value, not
by card type -- so `parse_cast_type_gate` must never mistake either for a
card type and synthesize a gate the text did not state. That is precisely
what its doc claims, and nothing pinned it.

Meeting of the Five ("spells with exactly three colors") probes the color
axis. Perception Bobblehead and Kiora ("a spell with mana value N or less")
probe the property axis, which rides on `CastPermissionConstraint` instead.
All three emit a bare `ExiledBySource` in regenerated card data, so the
assertion matches measured behaviour rather than an assumption.

The new const is byte-identical to the card's real Oracle text, verified
programmatically against card-data.json rather than by eye -- a paraphrased
fixture can take a different parser branch and quietly stop testing what it
names.
The doc said "the 25-card majority". The census it came from was superseded
twice while this PR was open, and the figure is now 26 -- so the comment
disagreed with the description of the change it documents.

A count in prose goes stale silently and nothing forces anyone to re-verify
it, which is the same reason the PlayerFilter variant count was removed from
analysis/resource.rs. Say "the untyped majority" instead: the property the
test asserts does not depend on the cardinality.
@matthewevans
matthewevans added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 1738d5c Aug 3, 2026
14 checks passed
@matthewevans
matthewevans deleted the fix/6880-cast-type-gate branch August 3, 2026 13:35
lgray added a commit to lgray/phase that referenced this pull request Aug 3, 2026
…itted

The CR 603.5 producer census pins `game/effects/mod.rs` line-exact. The phase-rs#6961 +
v0.44.0 uniform +78 shift moved the three producers to :5996/:6073/:9048, and
that re-pin was edited during the 851180c fold but never committed — it stayed
in the working tree, so the pushed tip 0b5a2bf still asserted :5918/:5995/:8970
and CI went red on it. This commit is that edit and nothing else.

Why the local gate was green while CI was red: the census test walks `src/` from
disk at runtime (`env!("CARGO_MANIFEST_DIR")` + `read_to_string`), so it measures
the WORKING TREE, never the commit. A dirty tree can never certify a commit —
"green at the exact tip" requires HEAD == sha AND a clean `git status` on the
gated paths, and that precondition is now part of the drift-log instructions.

Re-verified in the `refs/pull/<n>/merge` layout rather than locally, per the
drift log's own rule: `git merge-tree --write-tree HEAD upstream/main` (upstream
1738d5c) puts the producers at exactly :5996/:6073/:9048, with
`scoped_library_search.rs:452` and `engine.rs:11427` unmoved — all five
production producers accounted for, still no sixth. Upstream phase-rs#6959 is innocent.

Assisted-by: ClaudeCode:claude-opus-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant