Skip to content

Partial: Batroc the Leaper - #7000

Merged
matthewevans merged 6 commits into
phase-rs:mainfrom
JacobWoodson:card/batroc-the-leaper
Aug 9, 2026
Merged

Partial: Batroc the Leaper#7000
matthewevans merged 6 commits into
phase-rs:mainfrom
JacobWoodson:card/batroc-the-leaper

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a parse-fidelity defect on Batroc the Leaper.

Issue: Enters-with-counter replacement parses count as Fixed 1 instead of dynamic KickerCount, so "a +1/+1 counter for each time he was kicked" wrongly gives exactly one counter regardless of kicks (parser flagged SwallowedClause/DynamicQty).

Files changed

  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_quantity.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_replacement.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\batroc_the_leaper_multikicker_counters.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\main.rs

CR references

  • CR 702.33c
  • CR 702.33d
  • CR 614.1c
  • CR 614.12
  • CR 201.5

Track

Developer

LLM

Model: claude-opus-4-8
Thinking: high

Tier: Frontier

Verification

  • cargo fmt --all — clean (exit 0)
  • ./scripts/check-parser-combinators.sh — pass with env-skip: Gate G PASS and families A/B/C/E/F verified clean on the parser diff (base 9169d8f); Family-D skipped because python3 is the Windows-Store stub (exit 126, Permission denied) — the documented env limitation, not a failure. The script's raw exit was 1 solely because the Family-D self-test precondition (D0) hard-exits when python3 is unavailable, before the (already-clean) A-F diff scan. My in-loop fixes touched only a test file and crates/engine/src/game/engine.rs (neither under crates/engine/src/parser), so the gate verdict is unaffected.
  • cargo clippy-strict — clean after in-loop fix (exit 0). Initial run failed with clippy::while_let_loop in crates/engine/tests/integration/batroc_the_leaper_multikicker_counters.rs:139 (the Batroc test under verification); converted the loop { match waiting_for {...} } to while let WaitingFor::OptionalCostChoice { .. } = ... (semantics preserved). Re-ran clean.
  • cargo test -p phase-engine — clean after in-loop fix (exit 0; 18489 lib + 4486 integration + doc-tests all pass). Initial run had 1 failure: game::engine::stage2_injector_tests::the_cr_603_5_prompt_census_is_pinned... — a pre-existing Windows path-separator bug UNRELATED to Batroc: actual vs expected producer lists had identical line coordinates and counts (5/7/25, total 37), differing only in \ (from Path::display() at engine.rs:15144) vs hardcoded /. Normalized the separator with .replace('\', "/") without touching the guarded line-coordinate pins. Re-ran clean.
  • cargo export-cards data --stats --sidecar-dir client/public && cp client/public/card-data.json data/card-data.json — clean. Regenerated 98MB card-data against THIS branch's engine (28154 cards scanned), sidecar written to client/public/card-data.json, copied to data/card-data.json (both 98176031 bytes).
  • cargo coverage — ran clean (exit 0; overall 88.94%, 31713/35657) BUT verification criterion FAILED: 'Batroc the Leaper' is supported:false, gap_count:1. NOTE the earlier apparent exit 101 was a SIGPIPE artifact from a head closing the pipe, not a coverage failure; the clean re-run to a file returned 0.
  • cargo semantic-audit — clean (exit 0; 32700 cards audited, 295 with findings). 'Batroc the Leaper' is not in flagged_cards -> 0 findings. semanticAuditClean=true.

Scope Expansion

None.

Validation Failures

See review/cross-check notes.

CI Failures

  • cargo coverage verification criterion NOT met: 'Batroc the Leaper' is supported:false with gap_count:1 (needs supported:true gap:0). The swallow-check flags one swallowed clause: handler=Swallow:DynamicQty, source_text='[warning:swallowed-clause] DynamicQty - Batroc enters with a +1/+1 counter on him for each time he was kicked.' The parser DID parse the enters-with-counter replacement (replacement 'Moved' -> ability 'PutCounter' counter='1 P1P1' target=self) but fixed it at exactly ONE +1/+1 counter and DROPPED the 'for each time he was kicked' kicker-count multiplier, so Batroc would enter with 1 counter instead of N. The other two abilities parse correctly and are supported:true: the ChangesZone/DealDamage trigger (targets='0-kicker payments for this spell', i.e. kicker-count is wired there) and the Multikicker {2} additional cost. NOT fixed in-loop: correctly wiring a dynamic PutCounter quantity keyed to kicker count on the enters-with-counters replacement is a class-level parser/engine change (build-for-the-class dynamic-quantity on the counter-count axis + CR annotation + card-test), which exceeds a 2-retry verification in-loop patch; additionally the relevant parser files (oracle_replacement.rs, oracle_effect/counter.rs, oracle_nom/quantity.rs, oracle_quantity.rs) currently carry uncommitted mid-implementation edits from the card-run pipeline, so per multi-agent safety I did not modify them. This gap should be routed back to the Batroc implementation step (engine-implementer) rather than patched by the verification gate.

Summary by CodeRabbit

  • Bug Fixes

    • Improved parsing of self-referential and object pronouns, including “it,” “him,” “her,” and “them.”
    • Fixed relative path handling across platforms.
    • Improved support for dynamic kicker-based counter quantities and replacement effects.
  • Tests

    • Added coverage for gendered and plural pronouns.
    • Added integration tests for multikicker counter behavior, including zero, multiple, and independent kicks.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 4, 2026
@coderabbitai

coderabbitai Bot commented Aug 4, 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: 1 minute

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: a08ccfc1-ebf4-4aa1-a5c5-9a492fdd1d01

📥 Commits

Reviewing files that changed from the base of the PR and between 0292903 and c5e70a7.

📒 Files selected for processing (11)
  • crates/engine/src/parser/oracle.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_effect/lower.rs
  • crates/engine/src/parser/oracle_effect/tests.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_nom/primitives.rs
  • crates/engine/src/parser/oracle_nom/quantity.rs
  • crates/engine/src/parser/oracle_quantity.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/tests/integration/batroc_the_leaper_multikicker_counters.rs
  • crates/engine/tests/integration/main.rs
📝 Walkthrough

Walkthrough

The PR centralizes object-recipient pronoun parsing across Oracle parsers, expands kicker-count and replacement parsing, adds Batroc multikicker integration tests, and normalizes relative path separators before pinned-coordinate comparisons.

Changes

Oracle parser updates

Layer / File(s) Summary
Shared recipient-pronoun parser
crates/engine/src/parser/oracle_nom/primitives.rs, crates/engine/src/parser/oracle.rs, crates/engine/src/parser/oracle_nom/condition.rs, crates/engine/src/parser/oracle_effect/counter.rs, crates/engine/src/parser/oracle_nom/quantity.rs
Adds parse_object_recipient_pronoun and uses it for it, them, him, and her parsing. Reflexive forms and existing ~ handling remain supported.
Replacement and kicker-count parsing
crates/engine/src/parser/oracle_replacement.rs, crates/engine/src/parser/oracle_quantity.rs
Expands self-referential replacement clauses and kicker-count subjects. Adds parser tests for gendered and plural pronouns, invalid verbs, trailing text, and existing creature-count parsing.
Batroc multikicker integration coverage
crates/engine/tests/integration/batroc_the_leaper_multikicker_counters.rs, crates/engine/tests/integration/main.rs
Adds real cast-pipeline tests for two kicks, zero kicks, and isolation from another permanent’s kicker count. Registers the integration test module.

Path normalization

Layer / File(s) Summary
Relative path separator normalization
crates/engine/src/game/engine.rs
Replaces backslashes with forward slashes before pinned-coordinate census comparisons.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: matthewevans

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title identifies the affected card and accurately indicates that the pull request provides a partial fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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.

Actionable comments posted: 2

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

3508-3535: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider sharing the "was/were kicked" verb-anchor scan with parse_where_x_kicker_count.

The comment above parse_for_each_kicker_count states it "mirrors the permissive subject match already used by oracle_effect::lower::parse_where_x_kicker_count." Two independent implementations of the same "subject was/were kicked" grammar can drift apart over time (for example, if one recognizer later needs a new subject form and the other does not get updated). Extract the shared take_until(" was kicked") / take_until(" were kicked") scan into one combinator in a shared primitives module, and have both recognizers call it.

This mirrors the single-authority principle this PR already applies to parse_object_recipient_pronoun.
[medium_effort_and_high_reward]

Run the following script to confirm the duplication and locate the sibling function:

#!/bin/bash
# Description: Locate parse_where_x_kicker_count and compare its verb-anchor grammar
# to parse_for_each_kicker_count.
rg -n -B3 -A25 'fn parse_where_x_kicker_count' crates/engine/src/parser
🤖 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_quantity.rs` around lines 3508 - 3535,
Extract the shared “was kicked”/“were kicked” subject scan from
parse_for_each_kicker_count and parse_where_x_kicker_count into a reusable
combinator in the shared parser primitives module. Update both recognizers to
call this single combinator while preserving full-consumption validation and
QuantityRef::KickerCount behavior.

4150-4154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix the CR 201.5 citation.

CR 201.5 governs name-based self-reference, while this test documents grammatical pronoun agreement for kicker state ("he was kicked" / "it was kicked"). No rule codifies pronoun gender/number as kicker-count equality, so keep the test comment limited to CR 702.33c-d, or cite the actual governing source if one exists.

🤖 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_quantity.rs` around lines 4150 - 4154, Update
the comment in for_each_time_gendered_pronoun_was_kicked_maps_to_kicker_count to
remove the incorrect CR 201.5 citation and describe the test as governed by CR
702.33c-d only; do not imply that the rules explicitly establish gendered/neuter
pronoun kicker-count equality.
🤖 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.

Inline comments:
In `@crates/engine/src/parser/oracle_nom/primitives.rs`:
- Around line 189-195: Update the documentation comment above the anaphoric
pronoun handling in the relevant parser code to remove the inaccurate CR
608.2c/608.2k citations and unsupported claims about earlier clauses and pronoun
categories. Either narrow the comment to behavior directly supported by a
verified Comprehensive Rules citation or replace it with a verified rule whose
text matches the implementation, while preserving the explanation that these
pronouns resolve to the established referent.
- Around line 203-205: Update parse_object_recipient_pronoun to require a word
boundary after each pronoun, preventing matches for prefixes such as item and
itself while preserving valid pronoun parsing. Ensure callers such as
parse_counter_object_scope still require full-clause consumption, and add
regression coverage for both rejected inputs.

---

Nitpick comments:
In `@crates/engine/src/parser/oracle_quantity.rs`:
- Around line 3508-3535: Extract the shared “was kicked”/“were kicked” subject
scan from parse_for_each_kicker_count and parse_where_x_kicker_count into a
reusable combinator in the shared parser primitives module. Update both
recognizers to call this single combinator while preserving full-consumption
validation and QuantityRef::KickerCount behavior.
- Around line 4150-4154: Update the comment in
for_each_time_gendered_pronoun_was_kicked_maps_to_kicker_count to remove the
incorrect CR 201.5 citation and describe the test as governed by CR 702.33c-d
only; do not imply that the rules explicitly establish gendered/neuter pronoun
kicker-count equality.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dd077b02-f69d-4079-bd47-81f6b6e6ff15

📥 Commits

Reviewing files that changed from the base of the PR and between b5ab421 and 84d10f5.

📒 Files selected for processing (10)
  • crates/engine/src/game/engine.rs
  • crates/engine/src/parser/oracle.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_nom/primitives.rs
  • crates/engine/src/parser/oracle_nom/quantity.rs
  • crates/engine/src/parser/oracle_quantity.rs
  • crates/engine/src/parser/oracle_replacement.rs
  • crates/engine/tests/integration/batroc_the_leaper_multikicker_counters.rs
  • crates/engine/tests/integration/main.rs

Comment thread crates/engine/src/parser/oracle_nom/primitives.rs Outdated
Comment thread crates/engine/src/parser/oracle_nom/primitives.rs
@matthewevans matthewevans self-assigned this Aug 4, 2026
@matthewevans matthewevans added the bug Bug fix label Aug 4, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High — Kicker-count grammar accepts unrelated or empty subjects

parse_for_each_kicker_count in crates/engine/src/parser/oracle_quantity.rs:3508-3534 discards every token between time and was/were kicked, then always emits QuantityRef::KickerCount. The resolver in crates/engine/src/game/quantity.rs:4395-4406 has no subject parameter: it reads only the trigger or self source's kickers_paid. Consequently, unsupported inputs such as for each time a creature was kicked (and an empty subject) silently resolve to the source's kick count instead of remaining explicit unsupported syntax or carrying an authoritative subject.

Please put this grammar in crates/engine/src/parser/oracle_nom/quantity.rs as an all-consuming, word-boundary-safe self-subject combinator, and delegate to it from the semantic entry point. It should admit only established self forms (it, this spell, ~, plus a correctly bound named-self pronoun if the existing architecture supports that); non-self and empty subjects must be rejected. Add negative reach-guard coverage for a generic creature and an empty subject, while retaining the Batroc runtime behavior for the real self form.

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MED — CR annotation overclaims the pronoun-reference rule

crates/engine/src/parser/oracle_nom/primitives.rs:189-195 attributes the broad statement that these pronouns bind objects introduced by a trigger, cost, or earlier clause to CR 608.2c + CR 608.2k. The verified rules do not support that scope: CR 608.2c governs instruction ordering and applying the rules of English, while CR 608.2k is limited to a specific untargeted object previously referred to by that ability's cost or trigger condition across characteristic changes.

Please remove or narrow the CR claim to its verified scope. The generic parser-grammar explanation may remain, but it should not assert that those CR sections define the full anaphora behavior implemented here.

@matthewevans matthewevans removed their assignment Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Generated for head c5e70a7319c654e86fed8fef2f023491bb3a7138.

Parse changes introduced by this PR · 4 card(s), 4 signature(s) (baseline: main 02929031f687)

🟡 Modified fields (4 signatures)

  • 1 card · 🔄 ability/Draw · changed field count: kicker payments for this spell
    • Affected (first 3): Spell Contortion
  • 1 card · 🔄 ability/PutCounter · changed field counter: 1 P1P1KickerCount P1P1
    • Affected (first 3): Batroc the Leaper
  • 1 card · 🔄 ability/PutCounter · changed field counter: 1 P1P1ObjectCount { filter: Typed(TypedFilter { type_filters: [Land], controller: Some(You), properties: [] }) } P1P1
    • Affected (first 3): Michelangelo, On the Scene
  • 1 card · 🔄 ability/PutCounter · changed field counter: 1 loyaltySpellsCastThisTurn { scope: Controller, filter: Some(Or { filters: [Typed(TypedFilter { type_filters: [Instant], contro…
    • Affected (first 3): Ral, Leyline Prodigy

@matthewevans matthewevans removed the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 6, 2026
@matthewevans matthewevans self-assigned this Aug 9, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head c5e70a7: feedback is addressed and the latest CI workflow is green.

@matthewevans
matthewevans added this pull request to the merge queue Aug 9, 2026
Merged via the queue into phase-rs:main with commit cebe9de Aug 9, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants