Skip to content

Partial: Avenge - #6994

Merged
matthewevans merged 9 commits into
phase-rs:mainfrom
JacobWoodson:card/avenge
Aug 9, 2026
Merged

Partial: Avenge#6994
matthewevans merged 9 commits into
phase-rs:mainfrom
JacobWoodson:card/avenge

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a parse-fidelity defect on Avenge.

Issue: Cost-reduction condition "if a player attacked you during their last turn" is dropped (ModifyCost condition=null), so the {2} reduction applies unconditionally instead of only when the attack condition holds.

Files changed

  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\types\ability.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\types\game_state.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\turns.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\layers.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\quantity.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\ability_rw.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\ability_scan.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\coverage.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_nom\condition.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_trigger.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_effect\conditions.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_condition.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\oracle_static\tests.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\casting_tests.rs

CR references

  • CR 508.6
  • CR 514.2
  • CR 514.3
  • CR 109.5

Track

Developer

LLM

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

Tier: Frontier

Verification

  • cargo fmt --all — pass (clean)
  • ./scripts/check-parser-combinators.sh — pass (Gate G PASS + Gate A PASS; Family D NOT skipped — ran via working msys64 python3 3.9.7, detector self-test suite 10/10 ok; WindowsApps python3 stub bypassed by removing it from PATH)
  • cargo clippy-strict — pass (exit 0, no warnings)
  • cargo test -p phase-engine — fail (18475 passed, 1 failed: stage2_injector_tests::the_cr_603_5_prompt_census... — Windows-only path-separator artifact, unrelated to Avenge; also cleared a 97GB target/debug/incremental dir that had filled the disk before this run)
  • cargo export-cards data --stats --sidecar-dir client/public && cp client/public/card-data.json data/card-data.json — pass (card-data regenerated fresh against this branch; sidecar copied to data/, 98176031 bytes)
  • cargo coverage — fail-result (command exit 0, but Avenge supported:false gap_count:1 — Swallow:Condition_If on the cost-reduction clause)
  • cargo semantic-audit — pass (command exit 0; 32700 supported audited, 295 flagged; Avenge has 0 findings — it is not in the supported-audit set)

Scope Expansion

Scope grew by 3 files beyond the plan's list: the plan missed three exhaustive StaticCondition mappers (oracle_trigger.rs, oracle_effect/conditions.rs, oracle_condition.rs) that require a compile-mandatory "-> None" arm.

Validation Failures

See review/cross-check notes.

CI Failures

  • Avenge is NOT supported: cargo coverage reports supported=false, gap_count=1. Gap = Swallow:Condition_If on 'This spell costs {2} less to cast if a player attacked you during their last turn.' The DestroyAll + GainLife(filtered tracked set: creatures destroyed) body parses correctly, but the conditional cost reduction is swallowed: the ModifyCost static ability captures the {2} reduction with condition=null — the 'if a player attacked you during their last turn' condition is dropped. This is a genuine parse gap surfaced by swallow_check.rs (a file this branch modifies), confirmed against freshly regenerated card-data, not stale data. To reach gap:0 the swallowed StaticCondition must be parsed and attached to the ModifyCost static ability (a feature implementation, out of scope for command-level retry).
  • cargo test -p phase-engine: 1 unrelated test failed — game::engine::stage2_injector_tests::the_cr_603_5_prompt_census_is_pinned_so_a_sixth_producer_is_a_counted_event (engine.rs:15185). Windows-only path-separator artifact: file!()/rel.display() yields backslash paths (game\effects\mod.rs:5999) while the test hardcodes forward-slash paths (game/effects/mod.rs:5999). Census content is identical (same files, same lines 5999/6076/9051/452/11427), the count assertions (5 producers, 7 readers, 25 test-lines) all passed, and none of this branch's modified files (triggers.rs, oracle_trigger.rs, swallow_check.rs, integration tests) touch the census sites. Deterministic (retry futile, not a flake); the test's own comment notes it passes on Linux CI. Not caused by this branch and unrelated to Avenge; left unedited (sensitive unrelated census pin, multi-agent-safety).

Summary by CodeRabbit

  • New Features

    • Added support for “a player” or “an opponent attacked you during their last turn” conditions.
    • Avenge-style cost reductions now apply when an opponent attacked you during their last turn.
    • Multiplayer games accurately track attacks across opponents and turns.
    • Added readable condition text for supported card rules.
  • Bug Fixes

    • Corrected attack-history updates, including clearing records after no-attack turns.
  • Tests

    • Added coverage for parsing, cost reductions, multiplayer behavior, and attack-history persistence.

@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: 16 minutes

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: 5d4fc3a2-a136-4b48-bbe8-b33ac79c6d4c

📥 Commits

Reviewing files that changed from the base of the PR and between 67d51ff and 6ab4ce9.

📒 Files selected for processing (3)
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/integration/deterministic_game_state_serde.rs
📝 Walkthrough

Walkthrough

This change adds AnyPlayerAttackedYouLastTurn, stores attack targets at turn cleanup, parses supported Avenge phrases, evaluates the condition across opponents, serializes the snapshot, and validates conditional cost reduction and multiplayer behavior.

Changes

Avenge attack condition

Layer / File(s) Summary
Attack history state
crates/engine/src/types/ability.rs, crates/engine/src/types/game_state.rs, crates/engine/src/game/turns.rs, crates/engine/tests/integration/deterministic_game_state_serde.rs
Adds the condition variant, stores each player's last-turn defenders, exposes directional queries, updates records during cleanup, and covers deterministic serialization.
Condition parsing and lowering
crates/engine/src/parser/oracle_nom/condition.rs, crates/engine/src/parser/oracle_static/tests.rs, crates/engine/src/parser/oracle_condition.rs, crates/engine/src/parser/oracle_effect/conditions.rs, crates/engine/src/parser/oracle_trigger.rs
Parses “a player” and “an opponent” last-turn attack phrases. Unsupported condition conversions return None.
Condition evaluation and classification
crates/engine/src/game/layers.rs, crates/engine/src/game/coverage.rs, crates/engine/src/game/quantity.rs, crates/engine/src/game/ability_rw.rs, crates/engine/src/game/ability_scan.rs
Evaluates whether an opponent attacked the controller and registers formatting, profiling, runtime, mana, and scan classifications.
Cost-reduction and multiplayer validation
crates/engine/src/game/casting_tests.rs
Tests Avenge cost gating, attack direction, and existential evaluation in a three-player game.

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

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant execute_cleanup
  participant GameState
  participant ModifyCost
  Player->>execute_cleanup: complete turn with attack targets
  execute_cleanup->>GameState: snapshot attacked defenders
  ModifyCost->>GameState: query last-turn attack condition
  GameState-->>ModifyCost: return whether an opponent attacked controller
  ModifyCost-->>Player: apply conditional cost reduction
Loading

Possibly related PRs

  • phase-rs/phase#6989: Both changes update deterministic serialization for hash-based GameState collections.
  • phase-rs/phase#6974: Both changes modify attack-condition handling in oracle_trigger.rs.
  • phase-rs/phase#6743: Both changes touch cost-reduction mechanics and related ability definitions.

Suggested labels: needs-maintainer

Suggested reviewers: matthewevans, 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 identifies the Avenge-related change and accurately signals that the implementation is partial.
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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Generated for head 6ab4ce9a955fe9e18af56f4d82135087ec43fb92.

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

🟡 Modified fields (1 signature)

  • 1 card · 🔄 static/ReduceCost · changed field conditional: a player attacked you during their last turn
    • Affected (first 3): Avenge

@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

🤖 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/casting_tests.rs`:
- Around line 2886-2901: Add a test in the existing AnyPlayerAttackedYouLastTurn
cases near evaluate_condition_for_test where P2 records attacking you, then
eliminate or remove P2 from the game using the established GameState API, and
assert the condition evaluates false. Ensure the stale
attacked_defenders_last_turn entry remains present so the test verifies
eliminated attackers are ignored.

In `@crates/engine/src/game/layers.rs`:
- Around line 1617-1621: Update the
StaticCondition::AnyPlayerAttackedYouLastTurn evaluation to stop filtering
attackers with !p.is_eliminated, allowing departed opponents’ recorded attacks
to remain valid. Enforce expiry when the departed player’s skipped next-turn
boundary is reached in the existing last-turn attack record lifecycle,
preserving attacks until that boundary.
🪄 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: 399d8fd8-31f7-4349-82dc-b5ed15b5f341

📥 Commits

Reviewing files that changed from the base of the PR and between 4f524c6 and 7972b8d.

📒 Files selected for processing (14)
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/casting_tests.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/turns.rs
  • crates/engine/src/parser/oracle_condition.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_static/tests.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/types/game_state.rs

Comment thread crates/engine/src/game/casting_tests.rs
Comment thread crates/engine/src/game/layers.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.

Request changes — the last-turn defender ledger is preserved but not given the CR-required departed-player expiry.

🔴 Blocker

[HIGH] Eliminated players are excluded too early from the layer predicate. Evidence: crates/engine/src/game/layers.rs:1617-1621 filters eliminated players, while docs/MagicCompRules.txt:6429-6433 (CR 800.4i) says actions during a player's last turn remain findable until that player's next turn would have begun. types/game_state.rs:14142-14152 and the cleanup in turns.rs:2148-2161 preserve the record, but introduce no departed-player expiry. Why it matters: the engine loses a required last-turn attack relationship immediately on elimination instead of retaining it through the specified skipped-turn boundary. Suggested fix: carry the relationship through that CR boundary, then expire it when the eliminated player's next turn would begin; add a production-pipeline test for both sides of that boundary.

[HIGH] Required CI is failing the CR733 authority census. Evidence: run 30930044012, job 92062093858, reports attacked_defenders_last_turn as missing. Why it matters: the lifecycle field lacks the required authoritative classification/fixture evidence. Suggested fix: classify it at the authority that owns its lifecycle and update the corresponding fixture consistently with the retention/expiry behavior above.

✅ Evidence checked

The current head-bound parse-diff is scoped to Avenge only: one changed static/ReduceCost signature, matching the claimed scope. I also checked CodeRabbit's immediate-elimination suggestion against CR 800.4i; it conflicts with the verified rule and is not the requested fix.

Recommendation: request changes for the CR 800.4i retention boundary and the CR733 census failure, then re-request review on a new head.

@matthewevans matthewevans removed their assignment Aug 4, 2026
@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
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@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

🤖 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/casting_tests.rs`:
- Around line 3167-3176: Replace the direct StaticDefinition construction in the
affected test with an end-to-end setup that parses the Avenge Oracle text, then
prepares a spell cast through the real parsing and lowering path. Verify the
parsed AnyPlayerAttackedYouLastTurn condition reaches the cost modifier and
applies the reduction, ensuring the test fails if lowering swallows the
condition.
🪄 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: 33129be0-551f-48aa-841c-858dcee10310

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • crates/engine/tests/fixtures/cr733/authority_matrix.json.gz is excluded by !**/*.gz
📒 Files selected for processing (15)
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/casting_tests.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/game/layers.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/game/turns.rs
  • crates/engine/src/parser/oracle_condition.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/parser/oracle_static/tests.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/integration/deterministic_game_state_serde.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/game/ability_rw.rs
  • crates/engine/src/game/coverage.rs
  • crates/engine/src/parser/oracle_static/tests.rs
  • crates/engine/src/parser/oracle_condition.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/parser/oracle_nom/condition.rs
  • crates/engine/src/game/quantity.rs
  • crates/engine/src/types/game_state.rs

Comment on lines +3167 to +3176
let mut def = StaticDefinition::new(StaticMode::ModifyCost {
mode: CostModifyMode::Reduce,
amount: ManaCost::generic(2),
spell_filter: None,
dynamic_count: None,
})
.affected(TargetFilter::SelfRef)
.condition(StaticCondition::AnyPlayerAttackedYouLastTurn);
def.active_zones = crate::types::zones::self_spell_cost_mod_active_zones();
obj.static_definitions.push(def);

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Test the parsed Avenge path before merge.

Lines 3167-3176 construct StaticDefinition::ModifyCost and StaticCondition::AnyPlayerAttackedYouLastTurn directly. The documented coverage audit still reports that parsed Avenge cost reduction is unsupported because lowering swallows the condition. This test passes while the real Oracle card remains unsupported.

Route the parsed condition into the real cost modifier. Add an end-to-end test that parses Avenge Oracle text and prepares the spell cast.

🤖 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/casting_tests.rs` around lines 3167 - 3176, Replace
the direct StaticDefinition construction in the affected test with an end-to-end
setup that parses the Avenge Oracle text, then prepares a spell cast through the
real parsing and lowering path. Verify the parsed AnyPlayerAttackedYouLastTurn
condition reaches the cost modifier and applies the reduction, ensuring the test
fails if lowering swallows the condition.

Source: Path instructions

@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head 67d51ffe3290d38b4e008e0c33b380a35f7df160.

The source/serializer correction is clean. This branch is behind current main; Rust lint and test shards are still in progress, and the available coverage-parse-diff artifact is for older head 48ca248…. Update/rebase as needed, then let exact-current parse evidence and required checks settle; approval/enqueue will resume after that. No author code change is requested at this time.

@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head 6ab4ce9a955fe9e18af56f4d82135087ec43fb92.

The current source is clean, but main has advanced and this branch is behind it. Rust test shard 1 is still in progress, while the available coverage-parse-diff artifact is for older head 67d51…. Maintainer refresh/update will bring the branch current; then let exact-current parse evidence and required checks settle before approval/enqueue resumes. No author code change is requested at this time.

@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 6ab4ce9: deterministic attack-history serialization and its exact census coverage are complete; CI 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 2fadb53 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