fix(engine): keep prepared-copy spells stack-resident through targeting - #7017
fix(engine): keep prepared-copy spells stack-resident through targeting#7017nishu-builder wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe change makes token and card-copy stack residency state-aware. Matching spell entries preserve announced objects, while activated, triggered, keyword, and unmatched entries do not. Regression tests cover cleanup, movement, resolution, and countered prepared copies. ChangesToken and copy residency
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant GameState
participant StateBasedActions
participant ZonePipeline
participant Zones
participant PreparedSpellTest
GameState->>StateBasedActions: evaluate token and copy cleanup
StateBasedActions->>Zones: inspect matching stack entry
GameState->>ZonePipeline: process token zone change
ZonePipeline->>Zones: validate state-aware residency
PreparedSpellTest->>GameState: cast prepared copy
GameState-->>PreparedSpellTest: retain or remove copy based on stack entry
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Generated for head Parse changes introduced by this PR✓ No card-parse changes detected. |
matthewevans
left a comment
There was a problem hiding this comment.
[MED] Scope prepared-copy stack residency to the production casting/SBA boundary rather than globally exempting same-ID placeholders. Evidence: crates/engine/src/game/zones.rs:25 now treats any matching StackItem::Spell as residency, while the new regression test manually calls check_state_based_actions during TargetSelection at crates/engine/tests/integration/issue_1312_prepared_spell_cast_triggers.rs:105; normal cast flow reaches state-based actions at the priority boundary (crates/engine/src/game/engine.rs:11051, crates/engine/src/game/engine_priority.rs:180). Why it matters: this global predicate lets an Exile token/copy survive whenever an unrelated same-ID spell placeholder exists, even outside the actual cast lifecycle; the test manufactures that condition before target finalization turns the object into Zone::Stack.
Please remove or rework the global exemption and add a production-path reproduction that advances through the real legal-action/cast flow to the normal SBA boundary. If debug/replay handling needs a special policy, keep it at that explicit API rather than changing the shared zone-residency rule. The revised test should demonstrate both that the prepared copy survives the real targeting path and that the synthetic non-stack case is still cleaned up.
Summary
Keeps a prepared-copy spell alive while its cast is paused for targeting.
CastPreparedCopycreated the copy object in Exile withis_token = trueand then paused for target selection; a state-based-action pass during the pause removed the token (outside battlefield/stack), and completingChooseTargetemittedSpellCastagainst a destroyed object — leaving a stack entry referencing an absent object. Found by an external deterministic-replay harness (11 distinct seeds; repro: a prepared back-face Regrowth copy targeting a graveyard card), with both actions drawn from the engine's own exact legal-action set. The fix aligns the prepared-copy path with ordinary announcement: the copy is stack-resident (same-idStackEntryKind::Spell) from cast start, so CR 704.5d/e token cleanup correctly ignores it, and it ceases to exist through the normal post-resolution route. The residency check requires the same-id entry to beStackEntryKind::Spellspecifically — same-id non-spell projections (virtual activated-ability overlays) neither shield an off-battlefield token from SBAs nor bypass the movement guards, with a hostile fixture proving both.Files changed
CR references
Implementation method (required)
Method: /engine-implementer
Track
Developer
LLM
Model: gpt-5.6-sol
Thinking: high
Tier: Frontier
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.
tilt get uiresource clippy— Tilt unavailable in this worktree; used the documented direct fallback.cargo fmt --all/cargo fmt --all -- --check— passed.git diff --check— passed.cargo clippy -p phase-engine --all-targets -- -D warnings— passed on headf569c3753289f763d95fc7ea5644bdf8ff7b6a5b.cargo test -p phase-engine— passed on headf569c3753289f763d95fc7ea5644bdf8ff7b6a5b(full suites, 0 failed).Plan verification matrix — passed individually: SBA-pass survival regression (production
CastPreparedCopy→ explicitsba::check_state_based_actionspass mid-targeting →ChooseTargetcompletes → stack object exists → normal resolution), countered-copy sibling (copy ceases correctly), hostile same-id non-spell fixture (virtualActivatedAbilityoverlay does not shield SBA removal nor pass movement guards), raw-movement and zone-pipeline hostile fixtures, three direct-to-stack sibling tests.Revert probe — with the raw-zone-only residency check restored, the survival regression fails; with the fix, passes.
./scripts/gen-card-data.sh— passed on headf569c3753289f763d95fc7ea5644bdf8ff7b6a5b.cargo coverage— passed on headf569c3753289f763d95fc7ea5644bdf8ff7b6a5b: timeless legal 15124/16180 fully supported (93.5%); vintage legal 29841/32268 fully supported (92.5%).cargo semantic-audit— passed on headf569c3753289f763d95fc7ea5644bdf8ff7b6a5b: 32732 cards audited, 297 existing findings.Gate A
Gate A PASS head=f569c3753289f763d95fc7ea5644bdf8ff7b6a5b base=6d7821dced9623609edea342b47dd9c704ff0b36
Anchored on
announce_spell_on_stackbinds the spell object's exact id toStackEntryKind::Spellat announcement; the prepared-copy path now establishes the same residency at the same point in its cast.cast_single_target_during_resolution's direct-to-stack copy casting is the analogous copy-cast seam whose object lifetime the fix mirrors.Final review-impl
Final review-impl PASS head=f569c3753289f763d95fc7ea5644bdf8ff7b6a5b
Claimed parse impact
None.
Validation Failures
Contributor-environment note per the engine-implementer skill: pipeline steps ran as isolated fresh contexts (Codex CLI sessions) with artifact-only handoffs rather than spawned Claude subagents. Plan review: 3 rounds to clean (1 → 1 → 0; round one tightened the residency contract to
StackEntryKind::Spellagainst same-id virtual ability overlays, round two corrected the rules rationale to CR 109.1/602.2a/603.3 after an invalid CR 111.1b citation was caught). Implementation review: 1 round, clean.CI Failures
None.
Related
Same contributor as #6997 (merged), #7007, #7008; independent concerns.
Summary by CodeRabbit
Bug Fixes
Tests