Skip to content

Fix Aven Courier - #6686

Merged
matthewevans merged 18 commits into
phase-rs:mainfrom
mike-theDude:card/aven-courier
Jul 27, 2026
Merged

Fix Aven Courier#6686
matthewevans merged 18 commits into
phase-rs:mainfrom
mike-theDude:card/aven-courier

Conversation

@mike-theDude

@mike-theDude mike-theDude commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes Aven Courier by separating its stack-time destination target from its resolution-time counter-kind choice and applying the chosen counter only when the target lacks that kind.

The shared distinct-counter-kind population authority now also preserves explicit-zone, zone-change-ledger, and tracked-set domains through nested boolean filters.

Closes #6634.

Files changed

  • 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/choose_counter_kind.rs
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/src/game/effects/put_chosen_counter.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/targeting.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/lower.rs
  • crates/engine/src/parser/oracle_ir/ast.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/issue_6634_aven_courier.rs
  • crates/engine/tests/integration/main.rs

CR references

  • CR 115.1 / CR 115.1d
  • CR 608.2b / CR 608.2c / CR 608.2d
  • CR 122.1 / CR 122.6
  • CR 614.1a
  • CR 732.2a

Implementation method (required)

Method: /pipeline

Track

Developer

LLM

Model: codex-gpt-5
Thinking: high
Tier: Standard

Verification

  • Required checks ran clean.

  • Gate A output is for the current committed head.

  • Final review-impl is clean for the current committed head.

  • Anchors cite analogous code at the same seams.

  • cargo fmt --all -- --check — PASS

  • ./scripts/check-parser-combinators.sh — PASS

  • cargo clippy --all-targets -- -D warnings — PASS

  • cargo test -p engine — unit: 17,797 passed / 6 ignored; integration: 4,083 passed / 2 ignored; doc tests: 7 ignored; 0 failures

  • Focused quantity population tests — 10 passed; 0 failures

  • Focused distinct-counter-kind tests — 8 passed; 0 failures

  • Focused Aven Courier integration tests — 3 passed; 0 failures

  • Full Oracle export — Aven Courier supported with gap_count: 0 and no parse warnings

  • card-data-validate — PASS

  • Full semantic audit — zero findings for Aven Courier

Gate A

Gate G PASS (router/grant architecture: strict router vs permissive grant boundary intact)
Gate A PASS head=af07b50c0de408c7a4fe3a44a32833180cd66007 base=791927424572cf38371f29d6107ff9d908584b12

Anchored on

  • crates/engine/src/parser/oracle_effect/counter.rs:449 — existing nom-based typed counter-placement parser
  • crates/engine/src/game/effects/choose_counter_adjustment.rs:73 — resolution-time counter-kind enumeration and no-choice behavior
  • crates/engine/src/game/effects/counters.rs:1356 — replacement-aware counter-placement authority

Final review-impl

Final review-impl PASS head=af07b50c0de408c7a4fe3a44a32833180cd66007 base=791927424572cf38371f29d6107ff9d908584b12

Claimed parse impact

  • Aven Courier

Validation Failures

None.

CI Failures

None.

Summary by CodeRabbit

  • New Features

    • Added an optional “chosen counter count” condition to Put Chosen Counter for conditional resolution and no-op behavior.
    • Updated counter-kind choice to be resolution-local (no longer persists to the source), aligning timing for typed/contextual targeting.
    • Extended oracle parsing for “choose/put chosen counter” to support typed recipients and the optional absence/condition text.
  • Bug Fixes

    • Improved feasibility/offerability and effect/read classification when a target condition is present.
  • Tests

    • Added/expanded unit and integration coverage, including Aven Courier scenarios.

@coderabbitai

coderabbitai Bot commented Jul 26, 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 conditional chosen-counter placement, resolution-local counter-kind selection, typed and zone-aware counter discovery, corrected parent-slot resolution, parser support, effect-analysis updates, and Aven Courier integration coverage.

Changes

Chosen counter contracts and parsing

Layer / File(s) Summary
Counter contracts and parser lowering
crates/engine/src/types/ability.rs, crates/engine/src/parser/oracle_ir/ast.rs, crates/engine/src/parser/oracle_effect/*
Adds ChosenCounterCountCondition, propagates target_condition through parsing and lowering, supports typed recipients and absence predicates, and classifies resolution-time counter choices.

Population-aware counter discovery

Layer / File(s) Summary
Population-aware counter discovery and target resolution
crates/engine/src/game/quantity.rs, crates/engine/src/game/targeting.rs
Preserves candidate populations across boolean filters, zones, negations, tracked sets, and parent target slots when resolving matching objects and distinct counter kinds.

Resolution and placement

Layer / File(s) Summary
Resolution-local choice and conditional placement
crates/engine/src/game/effects/choose_counter_kind.rs, crates/engine/src/game/effects/put_chosen_counter.rs, crates/engine/src/game/effects/mod.rs
Stores the chosen kind only during resolution and suppresses PutChosenCounter when its optional count predicate is false before delegating valid placement.

Analysis and validation

Layer / File(s) Summary
Effect analysis and metadata propagation
crates/engine/src/game/ability_rw.rs, crates/engine/src/game/ability_scan.rs, crates/engine/src/game/coverage.rs
Updates read profiling, quantity scanning, legacy detection, census classification, and effect detail output for the new fields and reads.
Aven Courier end-to-end validation
crates/engine/tests/integration/issue_6634_aven_courier.rs, crates/engine/tests/integration/main.rs
Tests counter-kind options, successful placement, conditional no-op behavior, resolution-local state, and illegal-target resolution.

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

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant ChooseCounterKind
  participant ResolutionState
  participant PutChosenCounter
  participant PutCounter
  Player->>ChooseCounterKind: select a counter kind during resolution
  ChooseCounterKind->>ResolutionState: retain chosen kind locally
  ResolutionState->>PutChosenCounter: pass chosen kind to placement
  PutChosenCounter->>PutChosenCounter: evaluate target counter condition
  PutChosenCounter->>PutCounter: place counter when condition holds
Loading

Suggested labels: bug

Suggested reviewers: matthewevans, andriypolanski, lgray

🚥 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 is short and clearly describes the main Aven Courier fix.
Linked Issues check ✅ Passed The PR implements the Aven Courier counter-choice and conditional counter placement required by #6634.
Out of Scope Changes check ✅ Passed The additional parser, targeting, and quantity refactors support the same counter-choice flow and do not look unrelated.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@matthewevans matthewevans self-assigned this Jul 26, 2026
@matthewevans matthewevans added the enhancement New feature or request label Jul 26, 2026
@matthewevans

Copy link
Copy Markdown
Member

Implementation review is complete for current head af07b50c0de408c7a4fe3a44a32833180cd66007. Approval and merge-queue enrollment are held only for external gates: Rust lint (fmt, clippy, parser gate), Rust tests (shards 1/2 and 2/2), and the in-progress CodeRabbit review. Card-data and the other completed checks are green.

Once those remaining checks and the external review settle without a material finding, approval and auto-merge will resume.

@matthewevans matthewevans removed their assignment Jul 26, 2026
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR · 2 card(s), 5 signature(s) (baseline: main 6e0701b384a7)

🟢 Added (2 signatures)

  • 1 card · ➕ ability/ChooseCounterKind · added: ChooseCounterKind (target=you control permanent)
    • Affected (first 3): Aven Courier
  • 1 card · ➕ ability/PutCounter · added: PutCounter (conditional=cast during precombat main or postcombat main, counter=1 shield, target=you control creature)
    • Affected (first 3): Contractual Safeguard

🔴 Removed (3 signatures)

  • 1 card · ➖ ability/PutCounter · removed: PutCounter (conditional=cast during precombat main or postcombat main, counter=1 shield, target=you control creature)
    • Affected (first 3): Contractual Safeguard
  • 1 card · ➖ ability/TargetOnly · removed: TargetOnly (target=any target)
    • Affected (first 3): Contractual Safeguard
  • 1 card · ➖ ability/TargetOnly · removed: TargetOnly (target=you control permanent)
    • Affected (first 3): Aven Courier

10 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.
New cards in head: 39.

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

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/game/effects/put_chosen_counter.rs (1)

27-113: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

target_condition_is_satisfied checks only the first resolved target, but placement applies to the whole target filter.

target_condition_is_satisfied takes .next() from resolved_object_ids_for_filter(state, ability, target) (Line 37-43) and evaluates the condition against that single object's counter count. resolve() then gates the entire placement on this one check (Line 94-101) and, when satisfied, delegates to Effect::PutCounter using the same target filter (Line 106-112), which can resolve to multiple objects for a plain TargetFilter::ParentTarget outside a repeat_for iteration (see targeting.rs::resolved_targets_parent_target_for_attack_event_returns_all_attackers, which proves ParentTarget can carry several objects).

For Aven Courier (single target) this is harmless today, but this effect is explicitly documented as a reusable primitive mirroring PutCounter's general target-rebinding contract. If reused by a future card with a genuinely multi-object target outside a repeat_for iteration, the condition would silently gate/apply against only the first sampled object instead of per-object — either wrongly skipping the whole placement because one target fails, or wrongly placing on targets that were never checked.

Consider evaluating the condition per resolved object and restricting placement to only the objects that pass (e.g. resolve the id set once, filter by the condition, and hand the delegated PutCounter an explicit filtered object list/tracked set rather than the raw target filter).

🤖 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/game/effects/put_chosen_counter.rs` around lines 27 - 113,
Update target_condition_is_satisfied and resolve so the chosen-counter condition
is evaluated independently for every object resolved from target, rather than
only the first object. Resolve the target IDs once, retain only objects whose
counters satisfy the condition, and delegate PutCounter placement using an
explicit filtered target set so objects failing the condition are not modified;
preserve the existing no-condition and no-matching-target behavior.
🧹 Nitpick comments (2)
crates/engine/src/parser/oracle_effect/imperative.rs (2)

13435-13466: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the preserved anaphor branch (it / that permanent / that creature).

The two new tests only exercise the new typed-domain path and the target-rejection path. The anaphor→ParentTarget branch (lines 3865-3879) is untouched logic but now lives inside a fully rewritten function ("was rewritten from an anaphor-only parser… to a domain-based parser") — a rewrite of this shape carries real regression risk for the previously-shipped Doctor Who cards (The Caves of Androzani II/III) that this diff doesn't currently lock in with a test.

✅ Suggested additional test
#[test]
fn try_parse_choose_counter_kind_accepts_anaphor_forms() {
    for input in [
        "a counter on it",
        "a counter on that permanent",
        "a counter on that creature.",
    ] {
        assert_eq!(
            try_parse_choose_counter_kind(input),
            Some(ChooseImperativeAst::CounterKind {
                target: TargetFilter::ParentTarget,
            }),
            "anaphor form must still resolve to ParentTarget: {input}"
        );
    }
}
🤖 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/imperative.rs` around lines 13435 -
13466, Add a regression test alongside the existing
try_parse_choose_counter_kind tests covering “a counter on it”, “a counter on
that permanent”, and “a counter on that creature.” Assert each parses to
ChooseImperativeAst::CounterKind with TargetFilter::ParentTarget, preserving the
existing anaphor branch behavior.

3739-3746: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stale comment: "Anaphoric form only" no longer describes try_parse_choose_counter_kind.

This comment still claims the callee handles only the anaphoric form ("it"/"that permanent"), but the rewritten function (line 3854 onward) now also parses typed, untargeted domains via parse_target (e.g. Aven Courier's "a permanent you control"), returning CounterKind{Typed(..)}. Leaving this comment as-is could mislead a future reader into thinking typed-domain support lives elsewhere or is missing.

📝 Suggested comment update
-        // CR 608.2d + CR 122.1: "choose a counter on it / that permanent" —
-        // pick one of the distinct counter kinds on the anaphoric object
-        // (The Caves of Androzani II/III). Anaphoric form only; the declared-
-        // target form ("a counter on target permanent", Ichormoon Gauntlet)
-        // is handled by the target parser below.
+        // CR 608.2d + CR 122.1: "choose a counter on <domain>" — pick one of
+        // the distinct counter kinds on the anaphoric object (The Caves of
+        // Androzani II/III) or on a typed, untargeted population (Aven
+        // Courier's "a permanent you control"). The declared-target form
+        // ("a counter on target permanent", Ichormoon Gauntlet) is excluded
+        // and handled by the target parser below.
🤖 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/imperative.rs` around lines 3739 -
3746, Update the comment above try_parse_choose_counter_kind to remove the
inaccurate “anaphoric form only” claim and the implication that typed,
untargeted domains are handled elsewhere; describe that the helper parses both
anaphoric and typed untargeted counter domains, while preserving the distinction
for declared-target forms handled by the target parser.
🤖 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_effect/imperative.rs`:
- Around line 3858-3863: Update the target-designation guard in the surrounding
parser flow to also recognize the “another target” and “other target” prefixes
accepted by parse_target(), preventing those inputs from falling through to
ChooseCounterKind. Preserve the existing None return behavior for all recognized
target prefixes.

---

Outside diff comments:
In `@crates/engine/src/game/effects/put_chosen_counter.rs`:
- Around line 27-113: Update target_condition_is_satisfied and resolve so the
chosen-counter condition is evaluated independently for every object resolved
from target, rather than only the first object. Resolve the target IDs once,
retain only objects whose counters satisfy the condition, and delegate
PutCounter placement using an explicit filtered target set so objects failing
the condition are not modified; preserve the existing no-condition and
no-matching-target behavior.

---

Nitpick comments:
In `@crates/engine/src/parser/oracle_effect/imperative.rs`:
- Around line 13435-13466: Add a regression test alongside the existing
try_parse_choose_counter_kind tests covering “a counter on it”, “a counter on
that permanent”, and “a counter on that creature.” Assert each parses to
ChooseImperativeAst::CounterKind with TargetFilter::ParentTarget, preserving the
existing anaphor branch behavior.
- Around line 3739-3746: Update the comment above try_parse_choose_counter_kind
to remove the inaccurate “anaphoric form only” claim and the implication that
typed, untargeted domains are handled elsewhere; describe that the helper parses
both anaphoric and typed untargeted counter domains, while preserving the
distinction for declared-target forms handled by the target parser.
🪄 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: ff46d803-14ab-4948-b5fe-362fd135119f

📥 Commits

Reviewing files that changed from the base of the PR and between 7919274 and af07b50.

📒 Files selected for processing (17)
  • 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/choose_counter_kind.rs
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/src/game/effects/put_chosen_counter.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/targeting.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_effect/counter.rs
  • crates/engine/src/parser/oracle_effect/imperative.rs
  • crates/engine/src/parser/oracle_effect/lower.rs
  • crates/engine/src/parser/oracle_ir/ast.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/tests/integration/issue_6634_aven_courier.rs
  • crates/engine/tests/integration/main.rs

Comment thread crates/engine/src/parser/oracle_effect/imperative.rs Outdated
@matthewevans matthewevans self-assigned this Jul 26, 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 chosen-counter path has multi-target, declared-target, and coverage-honesty gaps.

🔴 Blocker

[HIGH] PutChosenCounter predicates only the first resolved target but then applies to the original multi-target filter. Evidence: crates/engine/src/game/effects/put_chosen_counter.rs:37-56 takes .next() from the resolved targets, while :94-113 passes the unfiltered original target onward for counter placement. Why it matters: mixed qualifying and nonqualifying targets can receive a counter contrary to the condition. Suggested fix: resolve and filter every candidate target before placement, with a mixed qualifying/nonqualifying multi-target regression.

[HIGH] Crystalline Giant is marked parse-supported without demonstrated binding of its random counter choice to the ChoiceType::CounterKind required by PutChosenCounter. Evidence: the current parse diff adds Crystalline Giant, but the random-counter choice does not show the required counter-kind binding. Why it matters: coverage can claim support while the runtime has no selected kind to apply. Suggested fix: retain strict-failure coverage until the binding exists, or implement the full random-choice binding with runtime coverage.

🟡 Non-blocking

[MED] The choose-counter parser excludes only the literal target prefix. Evidence: crates/engine/src/parser/oracle_effect/imperative.rs:3858-3889 rejects target but accepts other declared-target forms such as another target / other target as untargeted source populations. Why it matters: those forms bypass the existing target-designation path and receive the wrong binding semantics. Suggested fix: use a shared declared-target-prefix check and add a three-way reach test for target / another target / other target.

The earlier CI-hold comment is stale informational context only; this formal review supersedes it.

Recommendation: request changes — repair all-target predicate handling, declared-target parsing, and Crystalline Giant's counter-kind binding or coverage honesty before another review.

@matthewevans matthewevans removed their assignment Jul 26, 2026
@matthewevans matthewevans self-assigned this Jul 26, 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 prior all-target, target-prefix, and Crystalline strict-failure blockers are addressed; one new counter-kind-domain error remains.

🟡 Blocker

[MED] distinct_counter_kinds_among can include an unrelated battlefield object in a negated tracked-set domain. This PR now routes predicate domains through matching_object_ids_in_filter_universe at crates/engine/src/game/quantity.rs:4676-4684. For Not(And(TrackedSet(..), predicate)), filter_candidate_universe builds the Not universe from filter_zone_object_ids(state, filter) (battlefield default) plus the inner tracked-set population (:1936-1949), and the matcher complements against that universe (:2098-2106). A battlefield nonmember can therefore leak into the counter-kind set.

Make the complement universe the tracked/ledger population for this domain, and add a regression fixture with a tracked-set member plus an unrelated battlefield nonmember carrying a distinct counter kind. This is scoped to the newly routed counter-kind path, not a claim about object_count_matching_ids.

Fresh parse-diff and CI artifacts remain pending; this finding is from the current source and does not depend on them.

@matthewevans matthewevans removed their assignment Jul 26, 2026
@mike-theDude

Copy link
Copy Markdown
Collaborator Author

Addressed the requested changes in 1d78dc9:

  • evaluate PutChosenCounter predicates independently for every resolved object and apply counters only to qualifying objects; added a mixed-target regression
  • keep Crystalline Giant fail-closed: its random counter-kind producer is not yet bound to ChoiceType::CounterKind, so the source-self consumer remains Unimplemented
  • reject the complete declared-target prefix family (target, another target, other target) through a shared nom combinator
  • added the requested it / that permanent / that creature anaphor coverage and updated the stale parser comment

Verification on this head before push:

  • cargo fmt --all
  • scripts/check-parser-combinators.sh 791927424572cf38371f29d6107ff9d908584b12
  • focused chosen-counter and issue [Card Bug] Aven Courier's attack trigger does not put the chosen counter #6634 tests
  • cargo clippy -p engine --all-targets -- -D warnings
  • cargo test -p engine (17,806 unit + 4,083 integration tests passed)
  • filtered oracle-gen card check: Aven Courier remains typed/supported; Crystalline Giant has Unimplemented for both the random choice and its unbound put clause.

@mike-theDude

Copy link
Copy Markdown
Collaborator Author

Addressed the current-head tracked-set finding in 73171fe. distinct_counter_kinds_among now bounds Not(And(population, predicate)) to the conjunction's explicit tracked/ledger/zoned population before complementing. The general object_count_matching_ids path is unchanged, and a naked Not(TrackedSet) deliberately retains its broader exclusion semantics.

Added a regression with two tracked exiled members plus an unrelated battlefield nonmember carrying a distinct counter kind; only the nonmatching tracked member contributes. The same test locks the naked-negation boundary.

Verification:

  • cargo fmt --all
  • all 9 distinct_counter_kinds_among tests
  • cargo clippy -p engine --all-targets -- -D warnings
  • cargo test -p engine (17,807 unit + 4,083 integration tests passed)

@matthewevans matthewevans self-assigned this Jul 27, 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 negated tracked-set population fix still loses a disjunctive tracked-set domain.

🟡 Blocker

[MED] The complement can still include an unrelated battlefield object for Not(And(Or(TrackedSet A, TrackedSet B), predicate)). crates/engine/src/game/quantity.rs:2093-2110 derives a special population only for And; when the inner domain contains Or, the Not path (:2125-2133) falls back to filter_candidate_universe. Its Not universe includes the default battlefield (:1936-1948), so a nonmember's distinct counter kind leaks into the counter-kind result.

Carry the union population through Or before complementing, and add a regression with members in two tracked sets plus an unrelated battlefield nonmember carrying a different counter kind.

🟡 Evidence requested

The current parse diff reports an unexplained Contractual Safeguard change beyond the claimed Aven Courier impact. Restore it, or document and test the intended parser behavior.

@matthewevans matthewevans removed their assignment Jul 27, 2026
@mike-theDude

Copy link
Copy Markdown
Collaborator Author

Addressed the latest changes-requested review in c867657:

  • Not(And(Or(TrackedSet A, TrackedSet B), predicate)) now complements within the unioned tracked-set population. The regression uses members from two tracked sets plus an unrelated battlefield object with a distinct counter kind and proves the outsider cannot leak into the result. The naked Not(TrackedSet) broader-universe boundary remains pinned.
  • The Contractual Safeguard parse delta is intentional and now documented end to end. The counter-kind grammar accepts both “choose a counter on …” and “choose a kind of counter on …”; its Addendum shield-counter effect remains conditional, followed by a resolution-time ChooseCounterKind over creatures you control and PutChosenCounter on each other creature you control. Declared-target forms remain on the targeting path.

Verification: cargo fmt --all -- --check; parser combinator gate; focused Aven/Contractual/tracked-set tests; cargo clippy -p engine --all-targets -- -D warnings; full cargo test -p engine (17,812 unit tests and 4,085 integration tests passed; expected ignored tests only).

@matthewevans matthewevans self-assigned this Jul 27, 2026
@matthewevans matthewevans added the ai-contribution PR opened via docs/AI-CONTRIBUTOR.md flow label Jul 27, 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

[HIGH] Preserve the selected creature as the authority for “other” in Contractual Safeguard. Evidence: counter.rs:1737 parses the choice as only a TargetFilter, while choose_counter_kind.rs:53 carries only the resolved counter kind forward; filter.rs:4623 makes FilterProp::Another source-relative unless a recipient is supplied. Why it matters: the chosen creature is not the spell source or a per-recipient context here, so it is included in “each other creature you control” and receives the counter. Suggested fix: carry the object selected for the counter-kind choice through the resolution result and bind it as the recipient/exclusion context when resolving the downstream placement.

[MED] Retain TrackedSetFiltered’s nested filter/provenance when deriving a complement population. Evidence: quantity.rs:2062 and quantity.rs:2168 reduce TrackedSetFiltered to raw tracked-set membership, and quantity.rs:4746 uses that reduced population for Not. Why it matters: a complement can be evaluated over objects outside the nested filtered/provenance domain, producing the wrong counter-kind population for composed target filters. Suggested fix: represent and evaluate the full filtered domain when establishing the complement universe rather than discarding its nested predicate.

Please add production-pipeline regressions for both cases: one that resolves Contractual Safeguard with a chosen creature and confirms only the other controlled creatures receive the chosen kind, and one that exercises a complemented TrackedSetFiltered domain with a nested predicate/provenance constraint.

@matthewevans matthewevans removed their assignment Jul 27, 2026
@mike-theDude

Copy link
Copy Markdown
Collaborator Author

Addressed the latest requested changes in 409aa5c.

  • Counter-kind source selection now preserves the exact selected object separately from Aven Courier's independently declared destination target. Downstream PutChosenCounter evaluation binds that object as FilterContext.recipient_id, so Contractual Safeguard's each other creature excludes the chosen source creature.
  • Resolution-time source selection is concretized on the ChooseCounterKind continuation, preventing an older effect-context referent from suppressing a required new choice.
  • TrackedSetFiltered population anchors now retain the full nested predicate and producer provenance before complement evaluation.
  • Added production regressions for Contractual Safeguard across both interactive pauses and for a complemented TrackedSetFiltered domain with nested controller and Sacrificed provenance constraints. Updated Aven Courier's production tests to cover the separate source-object and destination-target choices.

Verification:

  • cargo fmt --all -- --check
  • parser combinator gate: PASS
  • cargo clippy -p engine --all-targets -- -D warnings
  • cargo test -p engine: 17,814 unit tests and 4,085 integration tests passed; zero failures
  • focused Contractual Safeguard and issue 6634 Aven Courier regressions passed

@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

[MED] Cover the repaired TrackedSetFiltered complement through a real production counter-kind consumer.

The new regression at crates/engine/src/game/quantity.rs:7521-7641 calls distinct_counter_kinds_among directly. That proves the helper’s local result, but it bypasses both non-concretized production consumers: QuantityRef::DistinctCounterKindsAmong (quantity.rs:3546-3548) and the counter-kind RepeatFor snapshot (effects/mod.rs:8999-9006).

Please add an integration/scenario regression that drives one of those consumers through the real ability-resolution pipeline using a nested Not(And(TrackedSetFiltered { filter, caused_by }, predicate)) population. Include the nested object predicate and producer-action provenance, plus an unrelated object with a distinct counter kind. Assert the externally observable consumer result excludes the outsider (for example, the resolved quantity or the RepeatFor iteration/binding contains only the in-domain kind). A direct helper invocation is not sufficient for this regression.

This is intentionally limited to production-consumer coverage; the prior selected-authority and disjunctive tracked-set blockers are addressed on this head.

@mike-theDude

Copy link
Copy Markdown
Collaborator Author

Addressed the production-consumer coverage request in d37e917.

The new integration regression builds a complemented TrackedSetFiltered domain with:

  • a nested controlled-creature predicate,
  • Sacrificed producer provenance,
  • a red exclusion predicate,
  • wrong-cause and wrong-controller tracked members, and
  • an unrelated battlefield object carrying its own distinct counter kind.

It drives the real resolve_ability_chain repeat_for: DistinctCounterKindsAmong path and asserts the externally visible source receives exactly one +1/+1 counter, proving only the in-domain Lore kind created an iteration.

Verification:

  • cargo fmt --all -- --check
  • git diff --check
  • cargo clippy -p engine --all-targets -- -D warnings
  • cargo test -p engine --test integration issue_6634_aven_courier: 4 passed, 0 failed

@matthewevans matthewevans self-assigned this Jul 27, 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.

Maintainer sign-off: current head rechecked; resolved blockers and the final production-pipeline regression are verified against the live diff.

@matthewevans
matthewevans added this pull request to the merge queue Jul 27, 2026
@matthewevans matthewevans removed their assignment Jul 27, 2026
Merged via the queue into phase-rs:main with commit 7a35ef2 Jul 27, 2026
13 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jul 29, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-contribution PR opened via docs/AI-CONTRIBUTOR.md flow enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Card Bug] Aven Courier's attack trigger does not put the chosen counter

3 participants