Skip to content

Fix Emperor of Bones - #7311

Open
traemyn wants to merge 7 commits into
phase-rs:mainfrom
traemyn:card/emperor-of-bones-regression
Open

Fix Emperor of Bones#7311
traemyn wants to merge 7 commits into
phase-rs:mainfrom
traemyn:card/emperor-of-bones-regression

Conversation

@traemyn

@traemyn traemyn commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes Emperor of Bones so Adapt places counters without self-sacrificing when no creature was exiled, binds Haste and delayed Sacrifice to the returned creature, and preserves independent sequential instructions when an empty forward_result move has later dependent riders.

Files changed

  • crates/engine/src/game/effects/mod.rs
  • crates/engine/tests/integration/issue_1515_emperor_of_bones.rs

Track

Developer

LLM

Model: GitHub Copilot (via GitHub Copilot; canonical id not exposed)
Tier: Frontier
Thinking: high

Implementation method (required)

Method: /engine-implementer

CR references

  • CR 608.2b — an ability with remaining legal independent instructions still resolves those instructions when an earlier target becomes illegal.
  • CR 608.2c — instructions resolve in written order; dependent "it" riders require the referent established by the preceding instruction.
  • CR 603.7c — delayed triggers snapshot their object referent at creation.
  • CR 122.1h — a finality counter replaces a battlefield-to-graveyard move with exile.

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.

  • Gate A output below is for the current committed head.

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

  • Both anchors cite existing analogous code at the same seam.

  • cargo fmt --all -- --check — exit 0.

  • cargo clippy --all-targets -- -D warnings — exit 0 on the final head.

  • cargo test -p phase-engine — exit 0.

  • cargo test -p phase-engine --test integration emperor_of_bones -- --nocapture — 8 passed, 0 failed.

  • cargo coverage — exit 0 on the final head.

  • cargo semantic-audit — final tool-profile link was terminated by the environment with SIGKILL after sustained resource pressure; no semantic finding was emitted.

  • git diff --check — exit 0.

Pipeline-reviewed head: 6894ba1
Current branch head: 6894ba1
Pipeline status: current
Current-head review: clean at 6894ba1

Gate A

Gate G PASS (router/grant architecture: strict router vs permissive grant boundary intact)
Gate A PASS head=6894ba18816a76f0af8a48e1e185e31e9c577c21 base=b620a33408db7cdf8807b767632494cdb1100df8

Anchored on

  • crates/engine/src/game/effects/mod.rs:10891 — existing SequentialSibling traversal for independent instructions after a failed dependent branch.
  • crates/engine/src/game/effects/mod.rs:11574 — recursive ParentTarget dependency authority, including delayed-trigger payloads.

Final review-impl

Final review-impl PASS head=6894ba18816a76f0af8a48e1e185e31e9c577c21

Claimed parse impact

None.

Scope Expansion

None.

Validation Failures

The final semantic-audit tool-profile link was terminated by the environment with SIGKILL after sustained resource pressure. Coverage, full engine tests, clippy, formatting, Gate A, and focused regressions passed.

CI Failures

None.

@traemyn
traemyn requested a review from matthewevans as a code owner August 12, 2026 19:46
@coderabbitai

coderabbitai Bot commented Aug 12, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e76811d3-23bb-4693-acd9-cd310bfec2f7

📥 Commits

Reviewing files that changed from the base of the PR and between d3e8810 and ae316a2.

📒 Files selected for processing (1)
  • crates/engine/src/game/effects/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/engine/src/game/effects/mod.rs

📝 Walkthrough

Walkthrough

The change adds recursive ParentTarget dependency detection and updates forward-result continuation handling. It adds Adapt integration tests and a Yawgmoth’s Vile Offering regression test for independent sequential effects.

Changes

Emperor of Bones resolution

Layer / File(s) Summary
Forward-result continuation guard
crates/engine/src/game/effects/mod.rs
Detects nested ParentTarget dependencies and skips dependent siblings when no objects are forwarded. Independent siblings continue resolving.
Adapt activation integration tests
crates/engine/tests/integration/issue_1515_emperor_of_bones.rs
Tests linked-creature return with delayed sacrifice and counter placement without a linked exiled creature.
Sequential sibling effect regression
crates/engine/tests/integration/issue_1515_emperor_of_bones.rs
Tests that an invalidated reanimation target does not block the independent destroy effect or the spell’s self-exile instruction.

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

Mergeability Score: 🔵 Low · up to ae316

The PR prevents dependent effects from falling back to Emperor when no creature was exiled, avoiding unintended counter or trigger behavior. It is mergeable with owner awareness because the regression test may not exercise the production target-invalidation path used during zone changes.

Possibly related issues

  • phase-rs/phase#7100: Both changes address ParentTarget handling in delayed or dependent effects.

Possibly related PRs

Suggested labels: test

Suggested reviewers: matthewevans

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: fixing Emperor of Bones behavior and its related engine handling.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@matthewevans matthewevans self-assigned this Aug 12, 2026
@matthewevans

Copy link
Copy Markdown
Member

Current-head CI hold for d79c5eb: Rust tests shard 4 failed in mozilla-actions/sccache-action@v0.0.10 setup/post-run before Run tests began (run 31634317625, job 94240690224). The remaining test shards and lint/card-data checks are passing or still settling. No contributor change is requested; I’ll recheck once the current run settles and retry the sccache job if it remains the only failure.

@matthewevans matthewevans removed their assignment Aug 12, 2026
@traemyn
traemyn marked this pull request as draft August 12, 2026 20:06
@matthewevans matthewevans self-assigned this Aug 12, 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

[HIGH] Preserve independent sequential siblings when a forward_result move yields no object. Evidence: crates/engine/src/game/effects/mod.rs:11208 returns from the whole subchain whenever ability.forward_result && forwarded_objects.is_empty(). forward_result is stamped on an instruction when a descendant needs its result; it does not mean every later instruction depends on that result. For example, Yawgmoth's Vile Offering is parsed as an optional graveyard-to-battlefield ChangeZone with forward_result, followed by the independently targeted Destroy, then self-exile. If the controller chooses no first target (or that move cannot occur) but retains a legal destroy target, this return suppresses the destroy and self-exile. Why it matters: CR 608.2b says an ability with at least one remaining legal target resolves normally and other applicable parts can still affect it; CR 608.2c requires its instructions to be followed in written order. Suggested fix: skip only the result-anchored dependent continuation(s), preserving independent sibling instructions, and add a runtime regression that declines/fails the first optional move while destroying a legal second target (and verifies Yawgmoth's Vile Offering is exiled).

@matthewevans matthewevans removed their assignment Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 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/mod.rs`:
- Around line 11208-11214: Update the forward-result handling around
ability.forward_result and forwarded_objects so a missing forwarded object
resolves the dependent rider as a no-op without terminating the continuation
chain. Preserve execution of subsequent SubAbilityLink::SequentialSibling
instructions, and add a regression covering this chain shape.

In `@crates/engine/tests/integration/issue_1515_emperor_of_bones.rs`:
- Around line 313-317: The no-result test must also verify that Emperor did not
gain haste. In the test around the delayed-trigger length assertion, add a
direct assertion against Emperor’s haste state while preserving the existing
delayed-trigger counter as the positive reach guard.
🪄 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: 6198db61-785d-4442-95a8-9b93cc7d4644

📥 Commits

Reviewing files that changed from the base of the PR and between d79c5eb and bd36bec.

📒 Files selected for processing (2)
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/tests/integration/issue_1515_emperor_of_bones.rs

Comment thread crates/engine/src/game/effects/mod.rs Outdated
Comment thread crates/engine/tests/integration/issue_1515_emperor_of_bones.rs
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

Generated for head 6894ba18816a76f0af8a48e1e185e31e9c577c21.

Parse changes introduced by this PR

✓ No card-parse changes detected.

@traemyn
traemyn marked this pull request as ready for review August 12, 2026 20:29

@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/tests/integration/issue_1515_emperor_of_bones.rs`:
- Around line 380-385: Replace the direct zones::move_to_zone call for
graveyard_creature with the production replacement-aware apply or GameAction
path that emits ProposedEvent::ZoneChange before resolution. Preserve the test’s
intended move to Zone::Battlefield while ensuring replacement effects and normal
zone-change handling execute.
🪄 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: c368faa9-8c20-4561-8871-56827d406c0b

📥 Commits

Reviewing files that changed from the base of the PR and between bd36bec and b8d5c24.

📒 Files selected for processing (2)
  • crates/engine/src/game/effects/mod.rs
  • crates/engine/tests/integration/issue_1515_emperor_of_bones.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/engine/src/game/effects/mod.rs

Comment on lines +380 to +385
engine::game::zones::move_to_zone(
runner.state_mut(),
graveyard_creature,
Zone::Battlefield,
&mut Vec::new(),
);

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

Use the replacement-aware zone-change pipeline.

Line 380 moves graveyard_creature with zones::move_to_zone. This bypasses ProposedEvent::ZoneChange, replacement effects, and production zone-change handling. The regression can pass while a real pre-resolution move produces different target validity or forwarded results.

Cause the move through the production apply or GameAction path before resolution.

As per path instructions: “Zone changes must route through the replacement-aware pipeline (ProposedEvent::ZoneChange), not a direct zones::move_to_zone” and integration tests must drive the production pipeline.

🤖 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/tests/integration/issue_1515_emperor_of_bones.rs` around lines
380 - 385, Replace the direct zones::move_to_zone call for graveyard_creature
with the production replacement-aware apply or GameAction path that emits
ProposedEvent::ZoneChange before resolution. Preserve the test’s intended move
to Zone::Battlefield while ensuring replacement effects and normal zone-change
handling execute.

Source: Path instructions

@matthewevans matthewevans self-assigned this Aug 12, 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 is blocked by one resolver regression.

🔴 Blocker

[HIGH] Recursive missing-result detection suppresses an independent sequential instruction. resolve_chain_body returns from the entire chain when ability_chain_refs_parent_target(sub) finds a ParentTarget. That helper recursively traverses later sub_ability and else_ability branches, so it also matches a dependent rider after an independent sibling. This bypasses SubAbilityLink::SequentialSibling, whose contract is the next printed instruction, independent of its parent, and which must resolve even when an optional parent was declined.

For a forward-result move that yields no objects, followed by an independent sequential instruction and then a nested ParentTarget/delayed-trigger rider, the outer return skips the independent instruction. That violates the ordered-instruction rule (CR 608.2c) and contradicts the nearby claim that independent sequential siblings continue through the ordinary chain walker. The previous Yawgmoth-style direct independent tail is preserved, but it does not cover this later-nested dependency boundary.

Please model result dependency per continuation segment: no-op only the dependent continuation while continuing through SequentialSibling tails, and add a runtime regression with an empty forward result, an independent sibling, and a later nested dependent rider.

✅ Confirmed

I rechecked the prior empty-forward failure class and the current direct independent tail; the remaining issue is the new recursive look-ahead across that ordering boundary.

Recommendation: request changes; do not enqueue this shared resolver change until the sibling-order regression is covered.

@matthewevans matthewevans added the bug Bug fix label Aug 12, 2026
@matthewevans matthewevans removed their assignment Aug 12, 2026
@matthewevans matthewevans self-assigned this Aug 13, 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 still has a sibling-order blocker.

🔴 Blocker

[HIGH] The new loop skips an independent sequential instruction whenever a later descendant is dependent. resolve_chain_body calls ability_chain_refs_parent_target(sibling) before deciding to resolve a SequentialSibling; that helper recursively traverses sub_ability and else_ability (lines 11574–11604). Consequently, an independent action followed by a later nested ParentTarget/delayed rider is skipped wholesale at lines 11222–11224. SequentialSibling is explicitly the next independent printed instruction and must resolve (ability.rs:19342–19346); CR 608.2c requires the instructions in written order.

The added Yawgmoth test only has independent Destroy/self-exile tails (issue_1515_emperor_of_bones.rs:331–405); neither tail carries a later ParentTarget, so it cannot exercise the new recursive branch.

Please traverse the continuation by segment: execute each independent sibling's own effect, suppress only the dependent continuation node, then resume scanning. Add a runtime regression with an empty forward result, an independent sequential action, and a subsequent nested ParentTarget/delayed rider; the independent action must occur while the rider remains a no-op.

✅ Confirmed

The direct Yawgmoth empty-forward regression is now covered and preserves its independent tail. The unresolved case is the later-dependent boundary introduced by this head.

Recommendation: request changes; do not enqueue this shared resolver change until that ordering boundary has discriminating coverage.

@matthewevans matthewevans removed their assignment Aug 13, 2026
@traemyn
traemyn requested a review from matthewevans August 13, 2026 00:36
@matthewevans matthewevans self-assigned this Aug 13, 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 resolves the prior sub-chain case, but still has one missing-result fallback path.

🔴 Blocker

[HIGH] A sequential sibling's dependent else_ability is executed instead of suppressed. The walker enters this path because ability_chain_refs_parent_target correctly recurses through else_ability, but at resolve_chain_body:11222–11234 the per-sibling check looks only at sibling.effect and clears only sub_ability. It then resolves the clone with its else_ability intact. If the sibling's own condition is false, a ParentTarget effect in that else branch runs with no forwarded object and can fall back to the original source.

This is the same ordered-instruction/referent defect through the conditional branch: the independent sibling's own effect should be allowed, while only the dependent branch is a no-op. CR 608.2c requires the written instructions in order; an absent antecedent must not retarget a later rider to the source.

The new synthetic regression covers an independent sibling followed by a dependent sub_ability, but has no conditional else_ability branch (issue_1515_emperor_of_bones.rs:405–495).

Please apply the same segment boundary to both continuation edges: when executing an independent sibling, retain only an else branch that is itself referent-independent (or selectively strip/suppress its dependent branch), and add a false-condition runtime regression with a ParentTarget else rider.

✅ Confirmed

The prior blocker is fixed for the sibling's own effect plus a later sub_ability: the walker now executes independent segments and the new regression asserts that shape. CI, the current no-parse-change artifact, and security scan are green.

Recommendation: request changes; do not enqueue until the conditional dependent branch is handled and covered.

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