fix(parser): scope count-form "would draw N or more" draw replacements (Alms Collector #5678)#5867
Conversation
There was a problem hiding this comment.
Code Review
This pull request improves the draw replacement parser to recognize count-form antecedents (e.g., "would draw or more cards") and dynamically determine the replacement scope (IndividualDraw vs. InstructionCount) based on the substitute's behavior rather than the antecedent's grammatical number. Feedback focuses on ensuring that the newly added comments strictly adhere to Rule R6's formatting requirements for CR annotations to prevent breaking automated regex verification.
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.
Parse changes introduced by this PR · 1 card(s), 2 signature(s) (baseline: main
|
matthewevans
left a comment
There was a problem hiding this comment.
Verdict: changes requested — the parser recognizes the count form, but the resulting definition cannot enforce its N or more threshold at runtime.
🔴 Blocker
crates/engine/src/parser/oracle_replacement.rs:426-443 parses the numeric antecedent but retains only IndividualDraw; :526-542 then derives the scope solely from the substitute. No field on ReplacementDefinition carries the parsed N, and draw_scope only selects instruction versus individual-draw matching. Consequently, Alms Collector's two or more condition is discarded: an individual-draw matcher sees one card at a time, while an instruction matcher has no typed >= 2 predicate to enforce. This would either never apply to the intended two-card instruction or apply to the wrong draw class.
The earlier #5678 investigation reached the same implementation boundary: issue comment. CR 121.2a applies the relevant replacement before individual draws; CR 121.6b then governs resuming the individual sequence. The current parser-only test proves AST recognition and chosen scope, not that a pending two-card draw is gated correctly or that a one-card draw is left alone.
Please carry the antecedent through a typed replacement condition (for example, EventContextAmount >= N), teach the census classifier to recognize that typed signal as InstructionCount, and verify the replacement matcher resolves it against the pending instruction count. Add an engine-pipeline regression that proves: opponent draw 1 is unchanged; opponent draw 2 is replaced; and the replacement's two draws occur for the correct players.
🟡 Non-blocking
The fixed-substitute versus count-modifier distinction is real, but it does not eliminate the antecedent threshold. It decides what the substitute does; the typed antecedent still decides whether the instruction is eligible.
✅ Clean
parse_number is the right reusable combinator for the open-ended N or more grammar axis, rather than a two or more special case.
Recommendation: rework this through the draw-replacement model/runtime path, then request re-review on the new head.
|
Thanks for the detailed review — the blocker is right: the parsed
So an I'm happy to build it end-to-end: parser ( |
|
@Yurii214 yes, we just refactored how draw works in the engine. If you rebase you should be able to leverage the new approach to correctly complete this PR
|
…ments Recognize the count-form draw antecedent "would draw <N> or more cards" (parse_number -- build for the class, not a "two or more" special case) and, for N >= 2, retain N as a typed ReplacementCondition::OnlyIfQuantity over the event's draw count (EventContextAmount >= N) -- reusing the existing condition, no new variant -- composed (And) with any as-long-as / while gate. Scope is InstructionCount. Teach the census classifier that a Draw whose condition references EventContextAmount is InstructionCount (the instruction-count signal lives in the antecedent threshold, not just the execute count); re-freeze the corpus (+1: Alms Collector). The instruction-stage runtime enforcement (an InstructionCount replace_event offer at the draw seam, the draw_scope match-gate, and EventContextAmount threading into condition evaluation) is a separate core-draw change tracked in the PR discussion. Refs phase-rs#5678 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4e541d7 to
c88f2b6
Compare
|
Rebased onto the new draw engine and pushed the parser + condition + census layer:
What's left is the instruction-stage runtime, and I'd like to match your intended design before touching the core seam. Tracing the new flow: Since you just refactored this and it's high blast radius, how would you prefer the instruction offer wired — e.g. a |
matthewevans
left a comment
There was a problem hiding this comment.
Verdict: changes requested — the parser/census work now models the threshold correctly, but it advertises a supported instruction-stage replacement before the runtime has the required instruction-stage authority.
🔴 Blocker
crates/engine/src/parser/oracle_replacement.rs:424-460 lowers Alms Collector to InstructionCount with OnlyIfQuantity(EventContextAmount >= N), while crates/engine/src/game/effects/draw.rs:248-307 still decomposes the frame and consults only ProposedEvent::Draw { count: 1 }. The full parse-diff consequently reports Alms Collector as added support, but no candidate can see its count-two instruction and the condition cannot be true in the per-unit consultation. This is coverage-incorrect rather than a parser-only intermediate.
The intended runtime shape is the Plan 03 draw authority: keep DrawReplacementScope as the definition-side classifier, add a separate explicit event-side DrawEventStage (instruction-count versus individual draw), and consult scope-compatible definitions at the matching stage. The instruction consultation must happen once against the full count before units are created; each surviving unit then gets its own individual-draw consultation. Thread that stage event's amount into condition quantity resolution, rather than adding a post-filter or inferring scope from the execute body. This is the design needed for both Alms Collector and Quantum Riddler and preserves CR 121.2a's ordering.
03-draw-and-zone-authority.md is also clear that this belongs in the centralized, pause-safe draw sequence and should follow its Plan-02 preflight, not as a parser-only acceptance change. Please either complete that scoped runtime work with one-card/two-card end-to-end GameScenario coverage (including the correct replacement draws), or keep Alms Collector honestly unsupported until the Plan-03 implementation lands.
✅ Clean
The new antecedent lowering is at the right parser seam: parse_number plus a typed OnlyIfQuantity condition represents the full N or more class without a card-specific branch.
Recommendation: retain the typed parser direction, but land it only with the planned staged runtime authority and discriminating runtime coverage; do not add a generic candidate post-filter.
|
Clarification to my prior review: you do not have access to the internal remediation plan I referenced, and I am not asking you to implement a hidden core-pipeline design. Your diagnosis is correct: the current engine has no public, complete instruction-stage replacement contract, so this PR cannot safely finish the runtime portion from the available contributor context alone. Please do not add a speculative candidate post-filter or a new ad-hoc draw discriminator. We will own the centralized staged draw work and publish the relevant contract before requesting contributor changes in that area. For this PR, the actionable state is simply that parser/census acceptance cannot merge while the runtime is absent, because it would mark Alms Collector supported without enforcing its threshold. We will follow up after the shared runtime lands; no additional implementation is expected from you now. |
matthewevans
left a comment
There was a problem hiding this comment.
Changes requested — parser coverage claims a runtime capability that the draw pipeline does not implement.
🔴 Blocker
crates/engine/src/parser/oracle_replacement.rs:408-580parses Alms Collector asInstructionCountand recordsEventContextAmount >= 2;scripts/draw_replacement_census.py:304-326then freezes that parser classification. But the draw execution authority is still the per-unit sequence driver incrates/engine/src/game/effects/draw.rs:138-221; this PR adds no instruction-stage consult/apply path forDrawReplacementScope::InstructionCount. The new test atoracle_replacement.rs:17076-17120asserts only the AST/coverage shape, so it cannot prove that an opponent's two-card draw becomes the required one-card-for-each substitute (or that a one-card draw remains unchanged).
Please hold this parser/coverage change until the core draw-replacement contract has a real instruction-stage authority, then land it with registered runtime regressions for 1-card and 2+-card opponent draws.
✅ Clean
- The antecedent grammar is compositional and the threshold is typed rather than a card-name special case.
Recommendation: request changes — do not mark Alms Collector supported before the instruction-count runtime path exists.
|
Understood, and thanks for the clear guidance — I'll hold on the runtime and won't add a speculative post-filter or draw discriminator. Glad the threshold modeling (parser + Happy to keep this PR open so that layer is ready to combine once the shared staged-draw runtime + contract land, or to close it and re-open against the published contract — whichever keeps your queue cleaner. Just let me know. Appreciate the detailed engagement. |
Closes #5678.
Problem
parse_replacement_line's draw-antecedentaltmatched only"would draw a card"and a hardcoded"would draw one or more cards". Alms Collector's antecedent is the count-form "would draw two or more cards", so it matched neither and the line produced no replacement at all — the card silently did nothing.Fix
Two parts, both at the parser seam (
crates/engine/src/parser/oracle_replacement.rs):Recognize the count-form antecedent as a class. Replace the hardcoded
"one or more cards"tag with"would draw " + parse_number + " or more cards", so every<N> or morethreshold (one, two, three, …) is recognized — build-for-the-class rather than a per-card tag.parse_numberalready covers digit and word forms.Derive
draw_scopefrom the substitute, not the antecedent. This is the subtle part, and it corrects the issue's stated expectation. The issue predictedInstructionCount, but that contradictsDrawReplacementScope's own doc andscripts/draw_replacement_census.py::classify_scope:InstructionCountis reserved for count-modifier substitutes that read the replaced instruction's own count ("draw that many cards plus one instead" — Quantum Riddler, aQuantityRef::EventContextAmount; the only such card). Alms Collector's substitute is a fixed substitution ("instead you and that player each draw a card") — the Notion Thief / Hullbreacher class the enum doc explicitly scopes asIndividualDraw(CR 121.6b: cards are drawn one at a time).So the scope is finalized after the execute chain is parsed. Both antecedent forms seed the
IndividualDrawdefault;execute_draw_reads_replaced_countthen inspects the execute's top-levelDraweffect — the exact surfaceclassify_scopereads (execute["effect"]) — for a count that referencesEventContextAmount, and promotes toInstructionCountonly then. Emitting the scope from the same signal the census reads keeps producer and cross-check in agreement by construction: Alms Collector →IndividualDraw, Quantum Riddler →InstructionCount, and no existing card's scope changes.Why the corpus baseline moves
scripts/draw-replacement-corpus.tsvgains exactly one row (alms collector … IndividualDraw) now that the card produces a Draw replacement. Re-frozen withscripts/draw_replacement_census.py --corpus --writein this commit;--corpus --checkis green (51 rows), and the diff is that single added row — no existing row moved.Verification
count_form_draw_antecedent_is_recognized_and_scope_follows_the_substitutedrives the real entry (parse_replacement_line) and asserts scope follows the substitute: Alms Collector + a fixed "three or more" →IndividualDraw; a "that many … plus one" count-modifier →InstructionCount; singular "a card" →IndividualDraw.cargo test -p engine --lib(the new test) — green.draw_replacement_census.py --corpus --check— green after re-freeze (against a freshly regeneratedcard-data.json).cargo clippy -p engine --lib -- -D warnings— clean.docs/MagicCompRules.txt.Model: claude-opus-4-8[1m]