Skip to content

Fix Hawkeye, Avenging Archer - #6976

Merged
matthewevans merged 22 commits into
phase-rs:mainfrom
JacobWoodson:card/hawkeye-avenging-archer
Aug 9, 2026
Merged

Fix Hawkeye, Avenging Archer#6976
matthewevans merged 22 commits into
phase-rs:mainfrom
JacobWoodson:card/hawkeye-avenging-archer

Conversation

@JacobWoodson

@JacobWoodson JacobWoodson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a parse-fidelity defect on Hawkeye, Avenging Archer.

Issue: audit-flagged: DroppedCondition — a condition on one of its abilities is silently dropped from the parsed AST; find the dropped intervening-if/condition and restore it.

Files changed

  • 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_trigger_tests.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\parser\swallow_check.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\src\game\triggers.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\hawkeye_avenging_archer_dealt_damage_draw.rs
  • C:\Users\jacob\source\repos\phase-card-runs\crates\engine\tests\integration\main.rs

CR references

  • CR 603.4
  • CR 700.4
  • CR 120.1
  • CR 608.2i

Track

Developer

LLM

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

Tier: Frontier

Verification

  • cargo fmt --all — pass (exit 0)
  • ./scripts/check-parser-combinators.sh (Gate A) — pass (Gate G PASS + Gate A PASS; Family-D self-test and A-F scan both ran and passed via msys2 python3 after masking the WindowsApps python3 stub)
  • cargo clippy-strict — pass (exit 0, -D warnings, zero warnings)
  • cargo test -p phase-engine — fail (exit 101): 18468/18469 pass; the sole failure is a pre-existing, unrelated Windows path-separator artifact in an untouched census test; all 4 Hawkeye-specific tests pass (2 lib + 2 integration)
  • cargo export-cards data --stats --sidecar-dir client/public --output client/public/card-data.json && cp client/public/card-data.json data/card-data.json — pass (exit 0; added --output because the given recipe's --sidecar-dir does not write the main card-data.json and the cp would otherwise copy a stale file; both files fresh, Hawkeye key present)
  • cargo coverage — pass (exit 0; Hawkeye, Avenging Archer supported=true gap_count=0)
  • cargo semantic-audit — pass (exit 0; Hawkeye, Avenging Archer has 0 findings)
  • Re-verified at chunk-2 checkpoint with fresh card-data: supported:true gap:0, semantic-audit clean. The run-time 'partial' was a Windows census-test artifact (cargo test path-separator bug), not a code defect.

Scope Expansion

None. Misparse reproduced; fixed the class-level parser gap (dies-trigger "if dealt damage to it this turn" intervening-if) plus tests, no card-name special-casing.

Validation Failures

See review/cross-check notes.

CI Failures

  • cargo test -p phase-engine: 1 failing test 'game::engine::stage2_injector_tests::the_cr_603_5_prompt_census_is_pinned_so_a_sixth_producer_is_a_counted_event' (crates/engine/src/game/engine.rs:15185). This is a PRE-EXISTING, UNRELATED, Windows-only environmental artifact, NOT a Hawkeye regression: the test builds paths via Path::display().to_string() which yields backslash separators on Windows (e.g. 'game\effects\mod.rs:5996') but compares against hardcoded forward-slash literals ('game/effects/mod.rs:5996', lines 15209-15236). Both arrays contain the IDENTICAL 5 producer entries at the IDENTICAL file:line locations -- only the path separator differs -- proving the card change added zero producers. The module (stage2_injector_tests) was never touched by the Hawkeye work. Failure is deterministic (retry is futile) and passes on Linux/CI. Not fixed per multi-agent-safety rules: it is an actively-maintained census with an in-source drift-log, unrelated to card verification. All Hawkeye-specific verification is green (fmt, Gate A, clippy, 4/4 Hawkeye tests, coverage supported:true gap:0, semantic-audit 0 findings).

Summary by CodeRabbit

  • Bug Fixes
    • Improved death-trigger effects that check whether a creature was dealt damage during the current turn.
    • Damage conditions now correctly distinguish the specific source, including permanents that leave and re-enter play.
    • Fixed cases where these conditions were ignored or incorrectly applied, including Hawkeye’s card-draw ability.
    • Prevented damage-history conditions from affecting unrelated trigger types.
  • Tests
    • Added coverage for multiple damage sources, source changes, trigger parsing, and positive and negative Hawkeye scenarios.

@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

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 parser extracts damage-history clauses from dies triggers and maps them to source-specific conditions. Damage records retain optional source incarnations. Trigger evaluation matches the source, incarnation, and dying creature. Parser and Hawkeye tests cover positive and negative cases.

Changes

Dies-trigger damage history

Layer / File(s) Summary
Parse dies-trigger damage clauses
crates/engine/src/parser/oracle_trigger.rs, crates/engine/src/parser/oracle_effect/conditions.rs, crates/engine/src/parser/oracle_trigger_tests.rs, crates/engine/src/parser/swallow_check.rs
The parser extracts leading damage-history clauses only from battlefield-to-graveyard triggers. Trailing clauses remain resolution-time conditions. Tests cover source forms, non-dies triggers, trailing clauses, and swallow diagnostics.
Match damage records to trigger context
crates/engine/src/types/game_state.rs, crates/engine/src/types/ability.rs, crates/engine/src/game/effects/deal_damage.rs, crates/engine/src/game/triggers.rs, crates/engine/src/game/effects/mod.rs
Damage contexts and records store optional source incarnations. Trigger evaluation matches the source, incarnation, and dying creature. Tests cover multiple sources, legacy records, and re-entered objects.
Wire and validate the new condition
crates/engine/src/game/ability_rw.rs, crates/engine/src/game/ability_scan.rs, crates/engine/src/game/coverage.rs, crates/engine/src/game/engine.rs, crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs, crates/engine/tests/integration/main.rs, crates/engine/tests/integration/flame_spill_excess_damage.rs, crates/phase-ai/src/policies/tests/removal_lethality.rs
The condition is included in reference, scan, coverage, and resolver handling. Hawkeye integration tests verify draw and no-draw outcomes. Snapshot fixtures initialize the optional field.

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

Sequence Diagram(s)

sequenceDiagram
  participant GameState
  participant TriggerParser
  participant TriggerEvaluator
  participant Stack
  GameState->>TriggerParser: Parse Hawkeye dies trigger
  TriggerParser->>TriggerEvaluator: Return damage-history condition
  GameState->>TriggerEvaluator: Provide damage records and dying creature
  TriggerEvaluator->>Stack: Add draw trigger when source and creature match
  Stack->>GameState: Resolve card draw
Loading

Possibly related PRs

Suggested labels: needs-maintainer

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 identifies the primary change: fixing Hawkeye, Avenging Archer's intervening-condition parsing and behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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_trigger.rs`:
- Around line 5576-5585: Restrict the condition-hoisting branch in the trigger
parsing flow around scan_preceded and parse_dealt_damage_to_it_intervening_if to
cases where before contains no semantic effect text, ensuring only a leading
intervening-if clause is hoisted. Keep trailing forms such as “draw a card if …”
as resolution-time conditions, and add a regression test covering that form
while preserving CR 603.4 behavior.

In `@crates/engine/src/parser/swallow_check.rs`:
- Around line 4861-4872: Strengthen the swallow-detector test around the
assertions for “Condition_If” and “Duration_ThisTurn” by first asserting that
the parsed trigger contains the expected Hawkeye condition
“DealtDamageBySourceThisTurn” and that the tested trigger body reaches an
implemented parse path. Keep both existing negative diagnostic assertions,
pairing them with these positive reach guards so the test cannot pass by
bypassing the detector.

In
`@crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs`:
- Around line 46-56: Replace the direct damage mutation and manual SBA/trigger
calls in `kill_via_sba` and the `DamageRecord` insertion around the affected
test setup with a `GameAction` that activates Hawkeye’s `{T}` ability targeting
the victim. Let the normal ability resolution, damage handling, SBA checks,
trigger processing, and stack draining run through `GameRunner`’s production
pipeline, while preserving the test’s lethal-damage outcome.
🪄 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: ba136de1-7f2a-4480-9f55-260d1df07820

📥 Commits

Reviewing files that changed from the base of the PR and between 93da0ca and 4d1c01c.

📒 Files selected for processing (6)
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs
  • crates/engine/tests/integration/main.rs

Comment thread crates/engine/src/parser/oracle_trigger.rs Outdated
Comment thread crates/engine/src/parser/swallow_check.rs
Comment thread crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs Outdated
@matthewevans matthewevans self-assigned this 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.

Current head 4d1c01cb2019535910fa17214e4e436781e1d5a9 has four blocking correctness/test gaps.

[HIGH] Resolution-time trailing if clauses are promoted to an intervening-if. Evidence: crates/engine/src/parser/oracle_trigger.rs:5576-5585 uses scan_preceded over the entire dies trigger, so it accepts the matching damage clause wherever it appears. Why it matters: only the leading trigger condition is an intervening-if; a trailing resolution-time if must remain in the effect chain and be evaluated on resolution. Suggested fix: accept this grammar only in the leading condition position and add a trailing-if regression that proves it stays resolution-time.

[HIGH] The damage/death match can cross object incarnations. Evidence: crates/engine/src/game/triggers.rs:9524-9542 identifies both source and dying subject by ObjectId; DamageRecord at crates/engine/src/types/game_state.rs:1854-1903 has no source-incarnation identity and only an optional target incarnation. Why it matters: after a zone change/re-entry reuses an ID, historical damage can spuriously satisfy Hawkeye's condition and draw a card for a distinct object incarnation. Suggested fix: snapshot and compare the relevant source and target incarnations at damage time, then add a post-zone-change negative regression.

[HIGH] The runtime test bypasses the claimed activated-damage pipeline. Evidence: crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs:72-85 manually inserts a DamageRecord; it never activates Hawkeye, selects a target, or resolves the damage effect. Why it matters: the test cannot prove the new condition works with the real damage-recording path. Suggested fix: drive Hawkeye's actual activated ability through target selection and resolution, then kill that target and assert the draw.

[HIGH] The swallow-check coverage test proves only silence, not the typed semantic carrier. Evidence: the Condition_If checks assert absence of the diagnostic without asserting the parsed trigger condition/effect shape. Why it matters: a broad suppression or an unrelated carrier can make the diagnostic disappear while the Hawkeye condition is still absent or misclassified. Suggested fix: assert the parsed trigger has DealtDamageBySourceThisTurn and the draw effect, with a paired trailing-resolution-time condition case.

@matthewevans matthewevans added the enhancement New feature or request label Aug 4, 2026
@matthewevans matthewevans removed their assignment Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Generated for head 876fb18c730590d3a25e34e1a450f75c461ef336.

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

🟡 Modified fields (1 signature)

  • 1 card · 🔄 trigger/ChangesZone · changed field condition: dealt damage by source this turn
    • Affected (first 3): Hawkeye, Avenging Archer

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

Current head 562c5f457af6048c96662c2cc69b3cd6267e25c5 is a merge-from-main; the reviewed Hawkeye surfaces are unchanged, so these four blockers remain.

[HIGH] oracle_trigger.rs:5576-5585 scans the whole trigger and hoists a trailing resolution-time if as an intervening-if. Restrict this grammar to the leading trigger condition and add a trailing-if regression that remains resolution-time.

[HIGH] triggers.rs:9526-9541 matches source and dying object by ObjectId; DamageRecord (game_state.rs:1854-1864) has no source-incarnation snapshot. Zone-change/re-entry can therefore match historical damage to a different incarnation. Snapshot and compare source/target incarnations, with a post-zone-change negative test.

[HIGH] The integration test manually creates the decisive record: hawkeye_avenging_archer_dealt_damage_draw.rs:44-56 kills through SBA and :72-85 injects DamageRecord. It never activates Hawkeye, targets, or resolves the real damage effect. Drive that actual activation pipeline before asserting the draw.

[HIGH] swallow_check.rs:4853-4872 proves only that diagnostics disappeared. Assert the typed DealtDamageBySourceThisTurn trigger condition and draw effect, plus a paired trailing-resolution-time condition case, so suppression cannot make the test pass.

@matthewevans matthewevans removed their assignment Aug 4, 2026
…Hawkeye dies-trigger

Addresses the four review blockers on phase-rs#6976 (Hawkeye, Avenging Archer):

- CR 400.7: DamageRecord now snapshots source_incarnation, and the
  DealtDamageBySourceThisTurn match compares it (new
  damage_record_source_incarnation_matches) so a re-entered source -- same
  ObjectId, bumped incarnation, a new object per CR 400.7 -- no longer inherits
  a prior incarnation's damage. Captured in the production deal-damage resolver
  alongside target_incarnation. Adds a post-zone-change regression.

- CR 603.4: restrict the "if ~ dealt damage to it this turn" hoist to the
  leading effect position (before blank); a trailing resolution-time if
  ("draw a card if ~ dealt damage to it this turn") stays in the effect chain
  rather than being promoted to an intervening-if. Adds a trailing-if regression.

- The Hawkeye integration test now drives the real {T} activation through the
  production pipeline (activate -> target -> deal damage -> SBA death -> dies
  trigger -> draw) instead of hand-injecting a DamageRecord.

- The swallow-check test asserts the typed DealtDamageBySourceThisTurn
  condition and Draw effect before the negative diagnostic checks, and adds a
  paired trailing-resolution-time case.

Verification: cargo fmt; phase-engine lib + integration Hawkeye tests green;
clippy -D warnings clean.

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

@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/effects/deal_damage.rs`:
- Around line 737-749: The damage record currently derives source_incarnation
from the post-application live object, losing the original identity when the
source leaves or re-enters. Update DamageContext creation to capture the source
incarnation, carry it through DamageContextSnapshot, and have the DamageRecord
construction reuse that value, falling back to the LKI snapshot when no live
source exists. Add a regression test for a source leaving or re-entering before
the dies-trigger exact-source lookup.

In
`@crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs`:
- Around line 125-139: The drain_stack helper must fail instead of silently
accepting incomplete processing: continue only until WaitingFor::Priority with
an empty stack, panic or propagate any PassPriority error, and panic when the
200-iteration limit is reached. Add a reach guard confirming the death entered
trigger processing before asserting the negative hand result, using the existing
drain_stack and test state symbols.
🪄 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: d0badc8d-fa08-4fca-832d-c6612e304509

📥 Commits

Reviewing files that changed from the base of the PR and between 562c5f4 and 5ce359f.

📒 Files selected for processing (7)
  • crates/engine/src/game/effects/deal_damage.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs

Comment thread crates/engine/src/game/effects/deal_damage.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.

Changes requested — the current head still has two rules/architecture blockers and one non-discriminating regression test.

🔴 Blocker

crates/engine/src/parser/oracle_trigger.rs:5634-5648 leaves a trailing if in the trigger remainder, while crates/engine/src/parser/oracle_effect/conditions.rs:3223-3273 cannot represent the exact dealt-damage grammar that only oracle_trigger.rs:5757-5766 recognizes. The parser test at crates/engine/src/parser/oracle_trigger_tests.rs:18593-18618 therefore accepts an unconditioned Draw, which is false-green rather than proof of the conditional trigger.

CR 603.4 says: “this rule only applies to an ‘if’ that immediately follows a trigger condition.” The remaining if here is an effect condition, so it needs event-bound, resolution-time representation rather than the intervening-if path. Please thread a condition keyed to the trigger event/source through resolution, or retain an honest Effect::unimplemented; add runtime positive and negative tests that prove the condition is evaluated at resolution.

🔴 Blocker

The trigger path reads the live source (crates/engine/src/game/triggers.rs:9215, 9535-9542, 10362-10375) without an epoch/LKI captured in TriggerSourceContext; damage also records no source snapshot when the source is absent. A source that leaves and re-enters can therefore be treated as the original source. Thread a source epoch through the DamageContext snapshot and add production-pipeline regressions for source-gone and re-entry cases.

🟡 Non-blocking

crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs:125-139 swallows PassPriority errors/exhaustion before asserting only hand state. Tighten the helper to assert terminal priority/empty stack and add reach guards, so the test fails if the trigger never resolves.

The parse-diff sticky evidence is stale (it identifies head 562c…, not this 5ce359f…), so current engine/parser scope evidence is still missing.

Recommendation: request changes. Please implement the event-bound condition and LKI snapshot paths with discriminating runtime coverage, then refresh the parse-diff evidence for the resulting head.

@JacobWoodson JacobWoodson changed the title Partial: Hawkeye, Avenging Archer Fix Hawkeye, Avenging Archer Aug 5, 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

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

[HIGH] Compile blocker — should_resolve_subability_on_optional_decline has an exhaustive match ability.condition at crates/engine/src/game/effects/mod.rs:3189, but omits AbilityCondition::TriggerEventTargetDamagedBySourceThisTurn. CI run 31300016008 reports E0004 in lint, both Rust test shards, card-data, and WASM. Please add this condition to the ordinary => false arm: it is not an optional-decline branch selector.

[MED] Fixture update — the sole explicit DamageContextSnapshot fixture in crates/engine/tests/integration/flame_spill_excess_damage.rs:524-537 does not initialize the newly required source_incarnation field. Add the appropriate fixture value (None for this synthetic no-live-source snapshot) so the snapshot round-trip test remains complete.

After the compilation fixes, please provide fresh parse-diff evidence for the resulting exact head. The existing 5ce359 parse evidence predates these changes and is stale.

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

[HIGH] The sole explicit DamageContextSnapshot fixture in crates/engine/tests/integration/flame_spill_excess_damage.rs:524-537 still omits the newly required source_incarnation field. DamageContextSnapshot now declares that field in crates/engine/src/types/ability.rs:10223-10228; add source_incarnation: None for this synthetic no-live-source snapshot so the serialization round-trip covers the complete schema.

After that fix, please provide a current-head parser/coverage artifact. The available sticky evidence is for 5ce359…, while Card data is still pending for this head.

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

Verified the review hardening on 078cb88: the negative path now proves the SBA death and requires the trigger pipeline to settle. Parser gates and CI are green.

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

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

[HIGH] damage_record_source_incarnation_matches in crates/engine/src/game/triggers.rs:10759-10769 bypasses identity comparison whenever source_context.identity.expected_zone != Zone::Battlefield. That lets an old damage record match a later re-entry incarnation of the same object ID, contrary to CR 400.7. TriggerSourceContext is explicitly the pre-reset event-time projection (crates/engine/src/types/game_state.rs:441-452), and source_read correctly requires both its ObjectIncarnationRef and expected zone (:648-665); this helper must honor that same authority instead of treating every off-battlefield context as a match.

Compare against the pre-zone-change ObjectIncarnationRef and add a negative regression for a source that deals damage, changes zones/re-enters, then co-dies or otherwise reaches the off-battlefield trigger context. The old record must not satisfy the new incarnation's condition.

Please also regenerate parse/coverage evidence for the resulting exact head; the current artifact is stale (9e…).

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

🤖 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_rw.rs`:
- Around line 6005-6007: Update the annotation above
AbilityCondition::TriggerEventTargetDamagedBySourceThisTurn to remove the
incorrect CR 603.3b citation and replace it with a CR rule that directly
describes frozen event/source identity semantics, or state the behavior as an
engine invariant without a CR number. Keep the wording and citation consistent
with the canonical AbilityCondition documentation in ability.rs.

In `@crates/engine/src/game/effects/mod.rs`:
- Around line 3254-3256: Add verified “CR <number>: <description>” annotations
at both affected sites in crates/engine/src/game/effects/mod.rs: lines
3254-3256, documenting why the intervening trigger condition cannot select an
optional-effect decline branch; and lines 11514-11542, documenting the enforced
damage-history and intervening-condition behavior. Use authoritative,
behavior-specific Comprehensive Rules references and place each annotation with
the corresponding rules logic.

In `@crates/engine/src/parser/oracle_effect/conditions.rs`:
- Around line 3184-3188: Replace the full-clause all_consuming(tag(...)) branch
in the trigger-condition parser with a composable nom parser built from the
existing source, damage-action, event-target, and turn-qualifier parsers. Keep
all_consuming at the caller level and preserve the ctx.in_trigger guard, so
equivalent grammar variants are parsed through the typed condition components
rather than matched as one verbatim string.
🪄 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: 508a88d6-60dc-4e4a-87ef-0cd0b02d8ddc

📥 Commits

Reviewing files that changed from the base of the PR and between f8254b6 and f842161.

📒 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/deal_damage.rs
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/oracle_effect/conditions.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/types/ability.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/integration/flame_spill_excess_damage.rs
  • crates/engine/tests/integration/hawkeye_avenging_archer_dealt_damage_draw.rs
  • crates/engine/tests/integration/main.rs
  • crates/phase-ai/src/policies/tests/removal_lethality.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • crates/engine/tests/integration/main.rs
  • crates/engine/src/parser/oracle_trigger.rs
  • crates/engine/src/parser/oracle_trigger_tests.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/parser/swallow_check.rs
  • crates/engine/src/game/effects/deal_damage.rs
  • crates/engine/src/types/game_state.rs

Comment thread crates/engine/src/game/ability_rw.rs
Comment thread crates/engine/src/game/effects/mod.rs
Comment thread crates/engine/src/parser/oracle_effect/conditions.rs Outdated
@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 head f842161: Hawkeye source-incarnation handling preserves simultaneous-death LKI and rejects live re-entry; refreshed CI is green.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 9, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer conflict hold — current head f842161f5a6a331032942ec25192f2986be696b0.

GitHub reports this branch DIRTY. A controlled merge of current main produced non-mechanical conflicts in crates/engine/src/parser/oracle_trigger.rs (independent Hawkeye damage-history and current-main subtype intervening-if grammars) and crates/engine/src/game/engine.rs (the optional-prompt census coordinates). This needs a composed parser/coverage resolution and fresh exact-head verification, not a blind conflict choice.

The existing green checks and approval do not clear the pending source-incarnation re-entry concern: damage_record_source_incarnation_matches must continue to honor the captured ObjectIncarnationRef for off-battlefield trigger contexts. Maintainer conflict resolution will preserve that requirement, then regenerate CI and parse evidence. No author action is requested at this time.

@matthewevans
matthewevans enabled auto-merge August 9, 2026 10:49
@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.

Changes requested for head 9c8565ddae1304e1b56b05ba2c3a51f9262c318a.

[HIGH] The prior source-identity defect remains. damage_record_source_incarnation_matches (crates/engine/src/game/triggers.rs:10885-10894) accepts any recorded source incarnation whenever expected_zone != Battlefield; the Hawkeye trigger consumes that result at :10049-10066. An old damage record can therefore satisfy a later re-entry incarnation of the same object ID, violating CR 400.7.

TriggerSourceContext is the exact pre-reset source projection (crates/engine/src/types/game_state.rs:441-452) and source_read correctly demands both its ObjectIncarnationRef and expected zone (:648-665). Compare the damage record against that pre-move ObjectIncarnationRef regardless of the current/expected zone. Add a discriminating negative re-entry/co-death regression: damage by the old incarnation must not satisfy the later source incarnation's trigger.

Please regenerate parse/coverage evidence for the resulting exact head; the available artifact is for f842….

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 9, 2026
@matthewevans matthewevans added bug Bug fix and removed enhancement New feature or request labels Aug 9, 2026
@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold for current head fdaf88be68ee35dd14436dab5fe4423a9d2813bb.

The source-identity fix is now present and resolves the prior blocker. The current parse-diff artifact remains sticky on the earlier baseline/head, so it is not approval evidence for this revision. Rust/AI/performance gates are also still running.

Next step: once those required checks complete successfully, please refresh the parse-diff artifact for this exact head and current main; maintainer review can then proceed.

@matthewevans

Copy link
Copy Markdown
Member

Maintainer hold — current head a55eb5328baa6365e7f97e2b3d9d50c2c4080fd7.

Fresh maintainer review finds the implementation clean. Approval remains gated only on external evidence: Rust lint and both test shards, Paired-seed AI, and the Decision-cost performance gate are still in progress; the available coverage-parse-diff sticky is for older head f79eb870…, not this revision.

Next step: let those required checks complete successfully and publish/refresh the parse-diff artifact for this exact head against current main; maintainer approval/enqueue can then resume. 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.

Maintainer sign-off: exact source-incarnation checks now preserve simultaneous-death LKI while rejecting stale re-entries; the trailing damage rider uses composable shared nom grammar.

@matthewevans
matthewevans added this pull request to the merge queue Aug 9, 2026
@matthewevans matthewevans removed their assignment Aug 9, 2026
Merged via the queue into phase-rs:main with commit 8474cca 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

bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants