Skip to content

Add Captain Marvel, Apex Avenger - #6938

Merged
matthewevans merged 16 commits into
phase-rs:mainfrom
JacobWoodson:card/captain-marvel-apex-avenger
Aug 9, 2026
Merged

Add Captain Marvel, Apex Avenger#6938
matthewevans merged 16 commits into
phase-rs:mainfrom
JacobWoodson:card/captain-marvel-apex-avenger

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds engine support for Captain Marvel, Apex Avenger.

Files changed

  • crates/engine/src/types/events.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/types/resolution.rs
  • crates/engine/src/game/trigger_matchers.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/game/targeting.rs
  • crates/engine/src/game/log.rs
  • crates/engine/src/game/effects/counters.rs
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/src/game/engine_payment_choices.rs
  • crates/engine/src/game/engine_phase_trigger_regression_tests.rs
  • crates/engine/src/game/sba.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/trigger_index.rs
  • crates/engine/src/game/printed_cards.rs
  • crates/engine/src/analysis/ability_graph.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/sequence.rs
  • crates/engine/src/parser/oracle_ir/ast.rs
  • crates/engine/src/parser/oracle_ir/doc.rs
  • crates/phase-ai/src/policies/effect_classify.rs
  • crates/phase-ai/src/policies/redundancy_avoidance.rs
  • crates/phase-ai/src/policies/sacrifice_value.rs
  • crates/phase-ai/src/policies/tests/effect_classify_snapshot.rs
  • crates/engine/tests/integration/captain_marvel_apex_avenger.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/cr733_resolved_frame_transition.rs
  • crates/engine/tests/integration/optional_effect_remember_legal_actions.rs

CR references

  • CR 111.1
  • CR 122.1
  • CR 205.3
  • CR 603.2c
  • CR 603.4
  • CR 608.2h
  • CR 614

Track

Developer

LLM

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

Tier: Frontier

Verification

  • cargo fmt --all — pass
  • ./scripts/check-parser-combinators.sh (Gate A) — pass
  • cargo clippy-strict — pass
  • cargo test -p phase-engine — pass
  • ./scripts/gen-card-data.sh — pass
  • cargo coverage — pass
  • cargo semantic-audit — pass

Scope Expansion

Beyond the plan: fixed a latent bug where the "you may" optional-trigger pause dropped the plural current_trigger_events batch (added OptionalEffectFrame.trigger_events + save/restore in handle_optional_effect_choice) — required for the reproduction resolver and benefiting all batched "you may" triggers; and added a ZoneCounterImperativeAst::ReproduceEventCounters AST variant for idiomatic parser plumbing.

Validation Failures

None.

CI Failures

None.

Summary by CodeRabbit

  • New Features
    • Added support for reproducing counters from triggering events, either matching their counts and types or placing one of each type.
    • Added parsing for counter-reproduction effects, including optional and multi-target choices.
    • Improved trigger handling for actor restrictions and per-recipient counter events.
  • Bug Fixes
    • Preserved complete event batches across optional effects.
    • Improved subtype-based trigger conditions and counter-placement attribution.
  • Tests
    • Added comprehensive coverage for parsing, filtering, batching, optionality, and counter resolution.

@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 2, 2026
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds Effect::ReproduceEventCounters, parses its Oracle text, preserves counter-placement actors and batched trigger events, resolves reproduced counters, updates exhaustive effect handling, and adds integration coverage.

Changes

Counter reproduction engine

Layer / File(s) Summary
Effect contract and Oracle parsing
crates/engine/src/types/ability.rs, crates/engine/src/parser/oracle_effect/*, crates/engine/src/parser/oracle_trigger.rs, crates/engine/src/parser/oracle_ir/*
Adds reproduction modes, effect and AST support, Oracle parsing, subtype intervening-if parsing, actor restrictions, and related traversal handling.
Trigger context and recipient batching
crates/engine/src/types/events.rs, crates/engine/src/types/resolution.rs, crates/engine/src/game/triggers.rs, crates/engine/src/game/trigger_matchers.rs, crates/engine/src/game/targeting.rs
Records counter-placement actors, preserves plural trigger events across optional continuations, applies actor filters, and groups batched CounterAdded events by recipient.
Counter reproduction resolution
crates/engine/src/game/effects/counters.rs, crates/engine/src/game/effects/mod.rs
Aggregates triggering CounterAdded deltas, applies exact or per-kind reproduction, resolves targets, and places counters through replacement-aware pending additions.
Engine and AI integration
crates/engine/src/analysis/*, crates/engine/src/game/ability_*, crates/engine/src/game/coverage.rs, crates/engine/src/game/trigger_index.rs, crates/phase-ai/src/policies/*
Adds exhaustive classifications, read/write profiling, coverage details, trigger indexing, and phase-AI policy handling for the new effect.
Integration validation
crates/engine/tests/integration/captain_marvel_apex_avenger.rs, crates/engine/tests/integration/*
Tests counter deltas, counter kinds, actor and recipient filters, optionality, batching, subtype conditions, target selection, and mixed recipients.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • phase-rs/phase#6310: Both PRs modify counter-effect target resolution and batched trigger handling.
  • phase-rs/phase#6539: Both PRs add exhaustive handling for new Effect variants across shared engine code.
  • phase-rs/phase#6686: Both PRs extend shared counter-effect parsing, targeting, scanning, coverage, and read/write handling.

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 clearly and concisely identifies the main change: adding engine support for Captain Marvel, Apex Avenger.
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.

@matthewevans matthewevans self-assigned this Aug 2, 2026

@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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/engine/src/parser/oracle_trigger.rs (1)

15801-15811: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale batching comment.

This comment states that "Tier-3 per-kind reproduction stays non-batched" after the lower_trigger_ir re-gate. The re-gate at lines 1777-1781 now clears batched only for Effect::Unimplemented, so reproduction effects stay batched. Align this comment with the new rule to avoid a wrong maintainer assumption.

🤖 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_trigger.rs` around lines 15801 - 15811, The
batching comment above the counter-filter check is stale: update it to state
that the lower_trigger_ir re-gate clears batched only for Effect::Unimplemented,
so Tier-3 reproduction effects remain batched. Preserve the existing explanation
of batched-by-phrasing and kind-filtered behavior.
🤖 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/game/ability_scan.rs`:
- Line 5373: Move ReproduceEventCounters out of the SnapshotOrEvent and
BoundedOrNoPopulation classifications, and add it to the LiveBoardCensus and
corresponding census partition in effect_census_role. Keep both classification
partitions byte-identical so census_partition_agrees_with_effect_target_ctx
remains valid.

In `@crates/engine/src/game/effects/mod.rs`:
- Around line 4154-4156: Add local CR annotations to the
Effect::ReproduceEventCounters dispatch arm and the grouped event-bookkeeping
arms near the corresponding reproduction handlers, using only verified CR
references already supported by the codebase. Match each citation to that arm’s
counter placement and batched-trigger behavior, and do not invent or reuse
unrelated CR numbers.

In `@crates/engine/src/game/engine_payment_choices.rs`:
- Around line 78-83: Add a production-pipeline regression test near the trigger
handling covered by the changed restoration logic, using a non-empty plural
CounterAdded batch. Pause the plural optional trigger, accept it through the
normal pipeline, and assert that Effect::ReproduceEventCounters observes every
event; do not rely on empty trigger_events fixtures.

In `@crates/engine/src/parser/oracle_effect/imperative.rs`:
- Around line 12743-12756: Preserve the `_multi_target` value returned by
`try_parse_reproduce_event_counters` when constructing
`ZoneCounterImperativeAst::ReproduceEventCounters`. Add or reuse a
`MultiTargetSpec` field on that AST variant and thread it through the normal
imperative-lowering path so `resolve_counter_placement_target` retains
zero-target and target-cardinality semantics without widening the parsed target
filter.

In `@crates/engine/src/parser/oracle_effect/mod.rs`:
- Around line 16136-16151: Preserve the multi-target cardinality returned by
try_parse_reproduce_event_counters when building
ZoneCounterImperativeAst::ReproduceEventCounters in try_parse_verb_and_target.
Propagate _multi_target through the targeted proxy or update
try_split_targeted_compound to recover and apply it, ensuring “up to one” and
“up to N” reproduction targets remain optional rather than mandatory.

In `@crates/engine/src/parser/oracle_trigger.rs`:
- Around line 5570-5586: Update the subtype-condition handling around
parse_event_object_subtype_intervening_if so zone-change triggers evaluate the
original ZoneChangeRecord object, using ZoneChangeObjectMatchesFilter or an
equivalent incarnation-aware matcher instead of EventObjectMatchesFilter.
Preserve current behavior for non-zone-change triggers and add a regression test
covering an object leaving and re-entering before resolution.
- Around line 9254-9268: The shared ParseContext allows a stale
relative_player_scope from counter-effect parsing to affect later general
subjects in parse_single_subject. Clear or isolate relative_player_scope before
general trigger-subject parsing, while preserving the scope only for the
intended counter-placement actor path and keeping parse_type_phrase_with_ctx
behavior there.

In `@crates/phase-ai/src/policies/effect_classify.rs`:
- Around line 79-84: Update the Effect::ReproduceEventCounters arm in the effect
polarity classifier to return EffectPolarity::Contextual instead of Beneficial.
Preserve event-derived counter and target handling without assuming self-buffs,
and add coverage for non-SelfRef targets, harmful counter kinds, and
EventCounterReproductionCount::PerKind.

---

Outside diff comments:
In `@crates/engine/src/parser/oracle_trigger.rs`:
- Around line 15801-15811: The batching comment above the counter-filter check
is stale: update it to state that the lower_trigger_ir re-gate clears batched
only for Effect::Unimplemented, so Tier-3 reproduction effects remain batched.
Preserve the existing explanation of batched-by-phrasing and kind-filtered
behavior.
🪄 Autofix (Beta)

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: d24a4e74-63e9-43bf-a7a3-763cfce43460

📥 Commits

Reviewing files that changed from the base of the PR and between 28d744a and be6b736.

📒 Files selected for processing (37)
  • crates/engine/data/known-tokens.toml
  • crates/engine/data/mtgjson-vintage
  • crates/engine/src/analysis/ability_graph.rs
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/effects/counters.rs
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/src/game/engine_payment_choices.rs
  • crates/engine/src/game/engine_phase_trigger_regression_tests.rs
  • crates/engine/src/game/log.rs
  • crates/engine/src/game/printed_cards.rs
  • crates/engine/src/game/sba.rs
  • crates/engine/src/game/targeting.rs
  • crates/engine/src/game/trigger_index.rs
  • crates/engine/src/game/trigger_matchers.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/mod.rs
  • crates/engine/src/parser/oracle_effect/sequence.rs
  • crates/engine/src/parser/oracle_ir/ast.rs
  • crates/engine/src/parser/oracle_ir/doc.rs
  • crates/engine/src/parser/oracle_static/mod.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/types/events.rs
  • crates/engine/src/types/resolution.rs
  • crates/engine/tests/integration/captain_marvel_apex_avenger.rs
  • crates/engine/tests/integration/cr733_resolved_frame_transition.rs
  • crates/engine/tests/integration/main.rs
  • crates/engine/tests/integration/optional_effect_remember_legal_actions.rs
  • crates/phase-ai/src/policies/effect_classify.rs
  • crates/phase-ai/src/policies/redundancy_avoidance.rs
  • crates/phase-ai/src/policies/sacrifice_value.rs
  • crates/phase-ai/src/policies/tests/effect_classify_snapshot.rs

Comment thread crates/engine/src/game/ability_scan.rs
Comment thread crates/engine/src/game/effects/mod.rs
Comment thread crates/engine/src/game/engine_payment_choices.rs
Comment thread crates/engine/src/parser/oracle_effect/imperative.rs
Comment thread crates/engine/src/parser/oracle_effect/mod.rs
Comment thread crates/engine/src/parser/oracle_trigger.rs
Comment thread crates/engine/src/parser/oracle_trigger.rs
Comment thread crates/phase-ai/src/policies/effect_classify.rs Outdated

@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] Remove the unrelated generated token-registry refresh from this card-mechanic PR. Evidence: crates/engine/data/known-tokens.toml:381 begins additions such as Bejeweled Warg; the file contributes 3,007 added lines, and crates/engine/data/mtgjson-vintage:1 advances the source snapshot. Why it matters: these unrelated corpus updates enlarge the review and regression surface of a 37-file engine/parser change without being required by Captain Marvel's counter-reproduction mechanic. Suggested fix: restore both generated-data paths to the PR base and land any registry refresh separately with its own generation and validation evidence.

[MED] Do not request merge approval until the current-head parser-impact artifact is available. Evidence: current-head packet for be6b73673a8f65d0ac2884a0f6786825db18ce6c reports parse_diff.present=false / state=absent, while crates/engine/src/parser/oracle_effect/counter.rs:638 and crates/engine/src/parser/oracle_trigger.rs:2390 change parser behavior. Why it matters: the required card-level diff is the only evidence that this broad parser change has no unintended coverage blast radius. Suggested fix: let/regenerate CI's <!-- coverage-parse-diff --> artifact for this exact head and reconcile every gained, lost, or changed card before resubmission.

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

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Generated for head 41d6f4813aa4a17d172ee60895f857907992722d.

Parse changes introduced by this PR · 26 card(s), 11 signature(s) (baseline: main f8254b60301a)

🟢 Added (2 signatures)

  • 2 cards · ➕ ability/ReproduceEventCounters · added: ReproduceEventCounters (reproduce counters=SameNumber, target=self)
    • Affected (first 3): Bold Plagiarist, Captain Marvel, Apex Avenger
  • 1 card · ➕ ability/ReproduceEventCounters · added: ReproduceEventCounters (reproduce counters=PerKind(1), target=another creature, targets=0-1)
    • Affected (first 3): Aragorn, Company Leader

🔴 Removed (2 signatures)

  • 2 cards · ➖ ability/put · removed: put
    • Affected (first 3): Aragorn, Company Leader, Bold Plagiarist
  • 1 card · ➖ ability/put · removed: put (conditional=not (target is Kree))
    • Affected (first 3): Captain Marvel, Apex Avenger

🟡 Modified fields (7 signatures)

  • 21 cards · 🔄 trigger/CounterAdded · changed field valid target: controller
    • Affected (first 3): Ant-Man, Colony Commander, Aragorn, Company Leader, Blaster, Combat DJ (+18 more)
  • 3 cards · 🔄 trigger/CounterAdded · changed field valid target: playercontroller
    • Affected (first 3): All Will Be One, Casey Jones, Back Alley Brute, The Great Goblin
  • 1 card · 🔄 ability/ChangeZone · changed field conditional: not (target is Spirit)
    • Affected (first 3): Otherworldly Escort
  • 1 card · 🔄 trigger/ChangesZone · changed field condition: not (object entering graveyard is creature Spirit)
    • Affected (first 3): Otherworldly Escort
  • 1 card · 🔄 trigger/CounterAdded · changed field condition: not (event object is creature Kree)
    • Affected (first 3): Captain Marvel, Apex Avenger
  • 1 card · 🔄 trigger/CounterAdded · changed field valid target: opponent
    • Affected (first 3): Bold Plagiarist
  • 1 card · 🔄 trigger/CounterAdded · changed field watches: you control creatureopponent controls creature
    • Affected (first 3): Bold Plagiarist

@matthewevans

Copy link
Copy Markdown
Member

Update: the required head-bound parse artifact arrived immediately after the review was submitted. It reports 27 changed cards / 13 signatures for be6b73673a8f65d0ac2884a0f6786825db18ce6c, including 21 CounterAdded actor-gate changes and changed zone-trigger conditions for Oathkeeper, Takeno's Daisho and Otherworldly Escort. Please reconcile those unrelated parser-impact changes as part of the resubmission; this supersedes only the artifact-availability portion of the review, not the changes-requested decision.

JacobWoodson and others added 2 commits August 2, 2026 18:13
…R (CR review [HIGH])

Restore known-tokens.toml and the mtgjson-vintage stamp to the PR base. The
+3,007-line token-registry refresh and the 2026-07-30 -> 2026-08-02 snapshot
bump are a broad multi-set catalog regeneration, unrelated to Captain Marvel's
counter-reproduction mechanic. The card corpus (client/public/card-data.json)
is generated at build time, so this does not remove any released card; the
registry refresh should land separately with its own generation/validation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…dition (CR 603.4)

The subtype intervening-if recognizer scanned the whole effect string via
scan_preceded, so it also matched a TRAILING resolution conditional
("... return that card ... if it's a Samurai card" -- Oathkeeper, Takeno's
Daisho) and wrongly promoted it to a CounterAdded/ChangesZone trigger
condition. That changes whether the ability triggers and goes on the stack,
rather than gating only the effect at resolution.

An intervening-if (CR 603.4) is defined by position: it sits at the head of
the effect text. Restrict the recognizer to a leading match (before empty,
modulo whitespace); a trailing conditional now falls through to the existing
effect-level ChangeZone gate as before. Leading cases (Captain Marvel's "if
it's not a Kree", Otherworldly Escort's "if it's not a Spirit") are unchanged.

Adds a regression test asserting the trailing form is not lifted.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@JacobWoodson

Copy link
Copy Markdown
Contributor Author

Thanks @matthewevans — both points addressed in 474acc58 and ac174eb2 (pushed).

[HIGH] Unrelated generated-data churn — reverted. Restored known-tokens.toml (−3,007) and the mtgjson-vintage stamp (2026-08-022026-07-30) to base. Confirmed this removes no released card: the stamp is only the monotonic ratchet floor for the tracked catalogs, and the card corpus (client/public/card-data.json) is gitignored/generated at build time. The token-registry refresh is a legitimate multi-set update (Marvel/TMNT/etc. are released) but belongs in its own PR with generation/validation evidence — will follow up separately.

[MED] Parser blast-radius — reconciled. Walked all 13 signatures against real Oracle text:

  • The 21+3 CounterAdded valid target → controller changes and Bold Plagiarist's watcher/target shift are intended and CR-correct (CR 603.2c). These "Whenever you put counters…" triggers previously ignored the actor and over-fired; the actor-gating the mechanic requires fixes them. E.g. Ant-Man, Colony Commander ("Whenever you put a +1/+1 counter on a creature…") is correctly gated to controller.
  • Otherworldly Escort"…dies, if it's not a Spirit, return it…" is a genuine leading intervening-if, correctly relocated to the trigger condition.
  • Oathkeeper / Takeno's Daisho — this one was a real regression, now fixed. "…return that card… if it's a Samurai card" is a trailing resolution conditional, not an intervening-if. The recognizer was scanning the whole effect string and promoting it to a trigger-firing gate (changing whether the ability hits the stack). Restricted the recognizer to the leading position (CR 603.4), so trailing conditionals fall back to the effect-level ChangeZone gate as before. Added a regression test; the intended Kree/Spirit leading cases still pass.

Note the parse-diff "affected" lists join names on commas, so a few entries (Ant-Man, Colony Commander, Aragorn, Company Leader) are single cards — the actual changed-card count is a bit lower than it reads.

@JacobWoodson

Copy link
Copy Markdown
Contributor Author

Follow-up on the [HIGH] item: the token/subtype catalog refresh is already on main — no separate PR needed. It landed via the automated #6928 (chore/refresh-card-data-catalogs, merged 2026-08-02 19:03 UTC, vintage 2026-08-02), which is owned by the daily refresh-card-data.yml workflow. Since I reverted this PR's two catalog files to the merge base and main advanced them via #6928, #6938 will inherit main's 2026-08-02 catalog on merge with no conflict and nothing missing. Considering the refresh item closed.

@matthewevans matthewevans self-assigned this Aug 3, 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] Preserve the optional target cardinality for counter reproduction. Evidence: crates/engine/src/parser/oracle_effect/imperative.rs:12743-12755 and crates/engine/src/parser/oracle_effect/mod.rs:16136-16151 bind the MultiTargetSpec returned by try_parse_reproduce_event_counters as _multi_target; ZoneCounterImperativeAst::ReproduceEventCounters at crates/engine/src/parser/oracle_ir/ast.rs:1730-1733 and its lowerer at imperative.rs:13113-13119 consequently have no place to retain it. try_split_targeted_compound only recovers this metadata for PutCounter (oracle_effect/mod.rs:16638-16650). Why it matters: Aragorn, Company Leader's printed “up to one other target creature” is parsed but then loses its zero-or-one bound, so the resulting ability is not rules-correctly optional. The present Aragorn checks assert only effect shape / a supplied target (captain_marvel_apex_avenger.rs:688-726, :734-767), and therefore do not prove that the parsed ability carries min=0,max=1 or that the production choice path can decline it.

Please thread the MultiTargetSpec through the AST and both imperative/targeted-proxy lowering routes (or centralize recovery for this effect alongside the existing PutCounter recovery), then add a regression that parses Aragorn and proves the resulting ability's optional cardinality plus a real cast/trigger path covering both zero and one selected reproduction target. Re-run the relevant Tilt resources before re-requesting review.

@matthewevans matthewevans added the enhancement New feature or request label Aug 3, 2026
@matthewevans matthewevans removed their assignment Aug 3, 2026
…ction (CR 115.1d)

Aragorn, Company Leader reproduces counters "on up to one other target
creature" (min=0, max=1). `try_parse_reproduce_event_counters` computes the
correct MultiTargetSpec::up_to(1), but both binding sites discard it and the
ZoneCounterImperativeAst carries no channel for it, so the AST->Effect lowering
dropped the bound and the reproduction target bound as MANDATORY.

PutCounter and ReproduceEventCounters share the identical target-side grammar
("...counter(s) on up to N target ..."), so recover the reproduction effect's
cardinality through the same dedicated `extract_put_counter_multi_target`
post-parse fixup in `lower_imperative_clause` -- the centralized-recovery route
alongside the existing PutCounter recovery.

Tests: adds a parse-level assertion that the reproduction ability stamps
MultiTargetSpec::up_to(1) (the mandatory-vs-optional discriminator the prior
runtime-only zero-target test could not prove); the existing 0- and 1-target
cast/trigger runtime paths still pass. Full captain_marvel_apex_avenger module
green (19/19); Tilt was down so verified via direct targeted test runs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@matthewevans matthewevans self-assigned this Aug 3, 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.

Changes requested — the single-clause recovery is a good step, but compound counter-reproduction still loses the optional target bound.

🔴 Blocker

crates/engine/src/parser/oracle_effect/mod.rs:15332 returns from try_split_targeted_compound before the later direct-clause recovery at :15375-15399. On that compound route, try_parse_verb_and_target binds the reproduction parser's MultiTargetSpec as _multi_target and lowers only the target/effect (:16145-16160); then the splitter's primary-cardinality recovery at :16647-16660 reparses only Effect::PutCounter. A compound such as Put one of each of those kinds of counters on up to one other target creature and draw a card. therefore retains the reproduction effect but returns no MultiTargetSpec, turning the printed zero-or-one target into a mandatory target.

The new Aragorn checks exercise the non-compound sentence (captain_marvel_apex_avenger.rs:679-844), so they do not reach this earlier compound return. Please carry the parsed cardinality through the reproduction AST/proxy path, or centralize the recovery so ReproduceEventCounters uses the same primary-clause path as PutCounter. Add a parser regression for the compound form plus production-pipeline zero-target and one-target regressions.

✅ Clean

The current head's direct-clause recovery and the Aragorn zero/one coverage address the original non-compound case; this request is limited to the compound route above.

Recommendation: request changes for the compound-cardinality path, then re-request review on a new head with the current-head parse-diff artifact.

@matthewevans matthewevans removed their assignment Aug 3, 2026
… reproduction (CR 115.1d)

Follow-up to the single-clause fix. When the reproduction is the PRIMARY clause
of a compound ("put one of each of those kinds of counters on up to one other
target creature AND draw a card"), the clause returns from
try_split_targeted_compound before the direct-clause lower_imperative_clause
fixup ever runs. That splitter's own primary-cardinality recovery only reparsed
Effect::PutCounter, so a compound reproduction primary dropped its "up to one"
bound and bound the target as mandatory.

Extend the splitter's primary-cardinality recovery to ReproduceEventCounters,
re-deriving the MultiTargetSpec from the consumed primary-clause prefix via
try_parse_reproduce_event_counters -- the same building block that produced the
effect, mirroring the existing PutCounter recovery exactly.

Tests: adds a compound parser regression (primary retains MultiTargetSpec::up_to(1)
and the trailing conjunct survives as a sub-ability) plus production-pipeline
zero-target and one-target runtime regressions proving the optional slot while the
mandatory second conjunct still draws. Module green (22/22); Tilt down, verified
via direct targeted runs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@JacobWoodson

Copy link
Copy Markdown
Contributor Author

Both addressed — single-clause in 56182677, compound in 761474b6.

Single-clause: confirmed the gap with a parse-level assertion (multi_target was None), then centralized recovery alongside the existing PutCounter fixup in lower_imperative_clause — they share the "…counter(s) on up to N target …" grammar, so both recover up_to(1) via extract_put_counter_multi_target.

Compound: extended try_split_targeted_compound's primary-cardinality recovery to ReproduceEventCounters (re-derived via try_parse_reproduce_event_counters), since the compound primary returns before the direct-clause fixup.

Added a compound parser regression plus 0- and 1-target production-pipeline runtime tests; the reproduction module is green (22/22). Tilt was down locally so I verified with direct targeted runs; CI on the prior head was green on all non-AI gates.

@matthewevans matthewevans self-assigned this Aug 3, 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] ReproduceEventCounters is not intrinsically beneficial. Evidence: crates/phase-ai/src/policies/effect_classify.rs:79-84 returns Beneficial for every variant, while crates/engine/src/parser/oracle_effect/counter.rs:648-675 intentionally preserves event-derived counter kinds and target shapes including Aragorn's up to one other target creature; crates/phase-ai/src/policies/anti_self_harm.rs:319-385 consumes this classification when evaluating creature-targeted effects. Why it matters: the triggering event can carry a harmful counter and the reproduced effect can target a non-self creature, so treating the whole effect class as a self-buff can make target-selection policy reason in the wrong direction. Suggested fix: classify ReproduceEventCounters as Contextual until the policy has both the selected target and triggering counter multiset, and replace the self-only snapshot expectation with discriminating coverage for SelfRef/another-target and SameNumber/PerKind forms.

@matthewevans matthewevans removed their assignment Aug 3, 2026
…l (CR 122.1)

The reproduced counter KIND is event-derived at resolution (the triggering event
can carry a harmful kind, e.g. -1/-1), and the TARGET is not necessarily self --
Aragorn, Company Leader reproduces onto "up to one other target creature". So the
effect's polarity depends on the selected target and the triggering counter
multiset, neither of which is known at classify time. Treating the whole class as
a static self-buff let anti_self_harm reason in the wrong direction on
creature-targeted reproductions.

Reclassify effect_polarity(ReproduceEventCounters) as Contextual so the call site
inspects the concrete target and multiset instead of assuming a self-buff.

Tests: replace the single self-only Beneficial snapshot expectation with
discriminating coverage across both axes -- target {SelfRef, other} x per-kind
{SameNumber, PerKind} -- all asserting Contextual (phase-ai effect_classify
suite green, 18/18).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@matthewevans matthewevans self-assigned this Aug 3, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head 30187323c7d7ac26b348e222915f2e24ee90b6b4 has addressed the AI classification request, but is awaiting current-head admission evidence.

The correction at crates/phase-ai/src/policies/effect_classify.rs:79-88 correctly treats event-derived counter reproduction as Contextual; its test covers both target (SelfRef/other) and count (SameNumber/PerKind) axes at crates/phase-ai/src/policies/tests/effect_classify_snapshot.rs:87-115.

Required Rust lint and test shards are still in progress, and the only <!-- coverage-parse-diff --> artifact is bound to prior head 761474b641a7f31b99646fb5f2c28fd4fb2343b6, not this one. Let those settle and regenerate the artifact before any approval/enqueue decision. This hold does not close or reject the PR.

@matthewevans matthewevans removed their assignment Aug 3, 2026
JacobWoodson and others added 2 commits August 3, 2026 00:08
…napshot evaluator (CR 603.4 + CR 603.10)

The "if it's [not] a <subtype>" recognizer emitted EventObjectMatchesFilter
unconditionally. That matcher is live-first (current state, then LKI fallback),
which is correct for a non-zone event (Captain Marvel's CounterAdded "if it's
not a Kree"), but wrong for a ZONE-CHANGE trigger: for a dies/leaves trigger
(Otherworldly Escort, "when this dies, if it's not a Spirit") the subject "it"
must be judged from the zone-change event snapshot, or a same-ID incarnation
that re-enters before the recheck would be judged instead of the object that
left.

Split the recognizer to return the typed filter + negation, and let the caller
choose the evaluator by trigger kind via build_event_object_subtype_condition:
ZoneChangeObjectMatchesFilter { origin, destination, filter } when
trigger_zone_change is present, EventObjectMatchesFilter otherwise. The zones
come from trigger_head_dies_zone_change (Battlefield -> Graveyard), matching the
dies_lookback_condition convention.

Tests: subtype_intervening_if_dispatches_by_trigger_kind proves the routing
(dies -> ZoneChangeObjectMatchesFilter, CounterAdded -> EventObjectMatchesFilter).
The destination evaluator's event-snapshot / re-entry-LKI authority is already
covered by zone_change_object_condition_uses_original_exit_lki_after_leave_and_
reentry and zone_change_object_condition_checks_dead_object_snapshot.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…storation (CR 603.2c + CR 608.2)

The OptionalEffectFrame restoration in engine_payment_choices.rs restores the
PLURAL trigger_events batch (not just the singular event) when a suspended
optional ReproduceEventCounters resumes, but the frame unit tests supply
trigger_events: Vec::new() and pass even if that restoration is removed.

Add optional_frame_restores_full_multi_event_batch_on_accept: a multi-kind
placement (a +1/+1 count-2 event AND a shield count-1 event) suspends Captain
Marvel's "may" reproduction and accepts it, then asserts BOTH kinds reproduce
onto Captain Marvel. Dropping the plural restoration leaves the resumed
resolution with only the singular event, so at most one kind survives and the
test fails.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@matthewevans matthewevans self-assigned this Aug 3, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — head e77ae43. The prior substantive review findings are resolved. Required CI is green; approval/enqueue remains on hold while the Paired-seed AI gate and Decision-cost perf gate are pending. Once both settle successfully, we will recheck the live head and resume approval/enqueue. No author action is requested at this time.

matthewevans and others added 2 commits August 3, 2026 12:09
Maintainer port for current main; preserve ReproduceEventCounters read/census/randomness handling while accepting main’s live-board resolution-prompt authority.

Co-authored-by: Jacob Woodson <[email protected]>
Port the new effect through main’s fail-closed resolution-prompt classifier. The branch predates that classifier; CI exposed the exhaustive-match omission after the maintainer merge.

Co-authored-by: Jacob Woodson <[email protected]>
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head 4870fa1227af0cfbf9de9585bb9d640a3f61f318.

The maintainer port resolved the conflict with current main, and its follow-up restored this new main-only exhaustive resolution_prompt classification for ReproduceEventCounters. The fresh WASM check is green. Approval/enqueue remains held until this head's Rust lint/test shards and card-data check settle, and the coverage-parse-diff artifact is regenerated for this exact head. No author action is requested.

Current main added this OptionalEffectFrame fixture after the contributor introduced its plural trigger_events field. The fixture has no trigger context, so an empty batch preserves its intended scenario.

Co-authored-by: Jacob Woodson <[email protected]>
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head e12d4a77f685c9f5b2fc0a31e1d3decc29aa2785.

The maintainer port added the empty trigger_events field to a current-main optional-effect test fixture; this fixes the sole current-head compile root cause. Approval/enqueue remains held until required CI and the coverage-parse-diff artifact complete for this exact head. No author action is requested.

@matthewevans matthewevans removed their assignment Aug 3, 2026
@matthewevans matthewevans removed the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Aug 6, 2026
# Conflicts:
#	crates/engine/src/game/printed_cards.rs

@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.

Changes requested for head 1a9a085971ae9cf5623de0527ed7ac8f14e738da.

[HIGH] Effect::ReproduceEventCounters is missing from two intentionally exhaustive Effect matches, which leaves the engine uncompilable. CI run 31300471390 reports E0004 at both sites:

  • crates/engine/src/types/ability.rs:15795, moves_card_to_or_from_library: classify this effect in the ordinary false arm—it does not move a card to or from a library.
  • crates/engine/src/types/ability_visit.rs:576, visit_effect_scoped: add ReproduceEventCounters to the leaf arm so the visitor remains exhaustively complete without inventing a nested carrier.

The Paired-seed AI and Decision-cost perf gates failed because code generation could not compile past these E0004s, not because they measured an AI or performance regression. After the exhaustive-match fixes, please provide current-head Rust/AI/perf results and a fresh parse/coverage artifact.

@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 5dfebc7: the visitor exhaustiveness fix completes the existing counter-reproduction path, feedback is addressed, and CI is green.

@matthewevans
matthewevans enabled auto-merge August 9, 2026 07:51
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head ef45a96bbf36733ecf4e785d7f421e73da6087bd.

The prior exhaustive-match compile blockers are resolved. This branch is behind current main, all required Rust/AI/performance checks are queued, and the available coverage-parse-diff artifact is for older head e12d4a…. Rebase/update the branch, then let current-head parse evidence and required checks settle; approval/enqueue will resume after that. No author code change is requested at this time.

@matthewevans
matthewevans added this pull request to the merge queue 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.

Re-reviewed current main-refreshed head 41d6f48: counter-reproduction fixes and merged-base validation are green.

Merged via the queue into phase-rs:main with commit dedbfd5 Aug 9, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants