fix(engine): Metamorphic Alteration as-enters choose + host copy (#6013)#6539
Conversation
…se-rs#6013) Latch the chosen creature's copiable values on the Aura and install a Layer-1 CopyValues TCE on the enchanted host so the second choice works without BecomeCopying the Aura. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughMetamorphic Alteration now parses Aura-enter creature choices, stores the chosen creature’s copiable snapshot, applies it to the enchanted host, and preserves existing copy-choice behavior through purpose-tagged waiting states. Classification handling and integration tests cover resolution, lifecycle, legality, independence, token display, and parsing. ChangesMetamorphic Alteration copy-choice flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant OracleParser
participant ReplacementEngine
participant CopyTargetChoice
participant SnapshotHandler
participant LayerEngine
OracleParser->>ReplacementEngine: Parse Effect::ChoosePermanent
ReplacementEngine->>CopyTargetChoice: Create PersistChosenAttribute prompt
CopyTargetChoice->>SnapshotHandler: Submit selected creature
SnapshotHandler->>LayerEngine: Install latched Layer-1 copy effect
SnapshotHandler->>ReplacementEngine: Replay deferred Aura entry events
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/engine/src/game/effects/mod.rs (1)
3830-3843: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: add a release-visible diagnostic alongside the debug_assert.
If
Effect::ChoosePermanentever incorrectly reaches this dispatch arm in a release build, it silently no-ops with no trace — unlike theEffect::Unimplementedarm just below, which logs viaeprintln!even without assertions. Mirroring that pattern here would make a future violation of the "replacement-only" contract observable outside debug builds.🤖 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/mod.rs` around lines 3830 - 3843, Add a release-visible diagnostic to the Effect::ChoosePermanent dispatch arm alongside its existing debug_assert, mirroring the eprintln! behavior of the nearby Effect::Unimplemented arm. Preserve the current Ok(()) return and replacement-only handling contract.
🤖 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/engine_replacement.rs`:
- Around line 1393-1430: Update the `else` branch handling a missing `host`
after the `host` lookup to return `EngineError::InvalidAction` with the existing
diagnostic context, rather than relying on `debug_assert!` and continuing
dispatch. Preserve the successful `apply_precomputed_copy_values` path
unchanged.
---
Nitpick comments:
In `@crates/engine/src/game/effects/mod.rs`:
- Around line 3830-3843: Add a release-visible diagnostic to the
Effect::ChoosePermanent dispatch arm alongside its existing debug_assert,
mirroring the eprintln! behavior of the nearby Effect::Unimplemented arm.
Preserve the current Ok(()) return and replacement-only handling contract.
🪄 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: df474efb-30aa-4979-b5dd-b2c446c81dfb
📒 Files selected for processing (26)
client/src/adapter/types.tscrates/engine/src/analysis/ability_graph.rscrates/engine/src/game/ability_rw.rscrates/engine/src/game/ability_scan.rscrates/engine/src/game/coverage.rscrates/engine/src/game/effects/mod.rscrates/engine/src/game/engine_phase_trigger_regression_tests.rscrates/engine/src/game/engine_replacement.rscrates/engine/src/game/layers.rscrates/engine/src/game/printed_cards.rscrates/engine/src/game/quantity.rscrates/engine/src/parser/oracle_effect/lower.rscrates/engine/src/parser/oracle_effect/sequence.rscrates/engine/src/parser/oracle_ir/doc.rscrates/engine/src/parser/oracle_replacement.rscrates/engine/src/parser/oracle_static/dispatch.rscrates/engine/src/parser/oracle_static/shared.rscrates/engine/src/types/ability.rscrates/engine/src/types/game_state.rscrates/engine/src/types/layers.rscrates/engine/tests/integration/main.rscrates/engine/tests/integration/metamorphic_alteration.rscrates/phase-ai/src/policies/copy_value.rscrates/phase-ai/src/policies/effect_classify.rscrates/phase-ai/src/policies/redundancy_avoidance.rscrates/phase-ai/src/policies/x_reference.rs
Exhaustive match in keys_from_effect_kind must classify the new Metamorphic Alteration kind as a no-op EffectResolved key (same as Choose/BecomeCopy). Co-authored-by: Cursor <cursoragent@cursor.com>
Parse changes introduced by this PR · 7 card(s), 10 signature(s) (baseline: main
|
as_enchantment now strips Instant/Sorcery like other permanent converters, and token-donor setup mutates via GameRunner after build. Co-authored-by: Cursor <cursoragent@cursor.com>
Mid-entry CopyTargetChoice pauses delivery before CR 608.3c attach; stash PendingSpellResolution on that pause and apply it when PersistChosenAttribute answers. Also bump the ability-scan census count for ChoosePermanent. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Blocked — the submitted head does not parse or apply Metamorphic Alteration's as-enters copy choice.
🔴 Blocker
-
crates/engine/src/parser/oracle_replacement.rs:2297—scan_split_at_phrase(..., "choose ")supplies the suffix afterchoose(as shown by the existing named-choice path passing that value directly totry_parse_named_choice).parse_as_enters_choose_permanentthen requires a second leading"choose ", so it returnsNonefor the actual Oracle line. The current-head CI confirms this:as_enters_choose_a_creature_parses_to_choose_permanentfails, and every behavior test leaves the host as Grizzly Bears. Parse the suffix directly with the shared target descriptor, then keep the full-consumption guard. -
crates/engine/src/game/engine_replacement.rs:1376— after that parser fix, the proposed continuation still readsaura.attached_tobefore the Aura-spell attachment happens. The stack path drainsapply_pending_post_replacement_effectbefore its CR 608.3c Aura-attachment block, so this branch reaches the release-mode no-op and never installs the copy. Carry/establish the resolved Aura host through the continuation instead of relying on a debug assertion, and cover both normal spell resolution and a non-spell Aura entry.
The two exact census assertions also need updating for the new effect surface. CI run 29996478044 is red on this exact head (a6cf3ce6): both Rust test shards fail, including all six Metamorphic behavior tests.
Request changes: fix the suffix parser and entry/attachment sequencing, add discriminating spell and non-spell entry coverage, then rerun CI.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/engine/src/game/stack.rs (1)
1081-1135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCentralize the pending-resolution snapshot construction.
This duplicates the field-by-field snapshot assembly in Lines 1281-1334. Extract a shared builder so both choice paths preserve identical cast and cost metadata.
As per coding guidelines, “Reuse existing shared building blocks before adding utility or inline extraction logic.”
🤖 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/stack.rs` around lines 1081 - 1135, The pending-resolution snapshot assembly around push_spell_resolution is duplicated with the equivalent construction near the other choice path. Extract or reuse a shared builder for PendingSpellResolution that gathers cast_from_zone, kickers_paid, additional cost payment fields, and related metadata, then update both paths to use it so their snapshots remain identical.Source: Coding guidelines
🤖 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.
Nitpick comments:
In `@crates/engine/src/game/stack.rs`:
- Around line 1081-1135: The pending-resolution snapshot assembly around
push_spell_resolution is duplicated with the equivalent construction near the
other choice path. Extract or reuse a shared builder for PendingSpellResolution
that gathers cast_from_zone, kickers_paid, additional cost payment fields, and
related metadata, then update both paths to use it so their snapshots remain
identical.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4ba02f72-fc55-43fc-b164-8fbecf91f18e
📒 Files selected for processing (3)
crates/engine/src/game/ability_scan.rscrates/engine/src/game/engine_replacement.rscrates/engine/src/game/stack.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/engine/src/game/ability_scan.rs
- crates/engine/src/game/engine_replacement.rs
matthewevans
left a comment
There was a problem hiding this comment.
Re-reviewed current head c93e9596. The new stack continuation correctly addresses the earlier attachment-order observation and updates the census guards, but the parser blocker remains unchanged: scan_split_at_phrase(..., "choose ") hands parse_as_enters_choose_permanent the suffix after choose , while that helper still requires another tag("choose "). Thus As this Aura enters, choose a creature. still produces no replacement at all, and the requested parser/real-cast regressions cannot pass. Keeping this as requested changes on the new head.
…arse is_as_enters_choose_pattern only accepted named choices, so choose-a-creature never reached ChoosePermanent. Parse the choose suffix with the shared target descriptor, establish the Aura host from spell targets or CR 303.4f attach, and cover non-spell entry. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Blocked: the current head cannot compile its engine integration tests, and the new generic object-choice lowering assigns Metamorphic Alteration's copy-host semantics without proving that relation.
🔴 Blocker
-
[HIGH] Fix the non-spell integration fixture before this can merge. The fixture writes and reads
GameObject::replacements/statics, but those fields do not exist: the live fields arereplacement_definitionsandstatic_definitions, with matching base definitions. Both Rust test shards fail withE0609at these lines in run 30001032013. Stage the object through the existing fixture path or populate the live and base definition fields, then keep the non-spell regression assertion. -
[MED] Do not lower every parseable
As … enters, choose <permanent>clause toCopiableSnapshot. The new filter accepts any concreteTypedobject phrase and always constructsChoosePermanentPersist::CopiableSnapshot, while the resolver ignores the persistence discriminator and always issuesPersistChosenAttribute. That purpose is specifically a copy effect on an Aura's enchanted host, documented as Metamorphic-only here. This grammar admits unrelated selected-permanent effects; until the parser proves the accompanyingCopyChosenconsumer relation, preserve the strict unsupported marker (or model a general selected-permanent contract) rather than silently giving them copy-host behavior.
Request changes: address both blockers and re-run the PR checks.
Bare as-enters permanent choices stay unsupported until a CopyChosenHost document relation proves the companion static; stage the non-spell fixture via from_oracle_text so live and base definitions compile. Co-authored-by: Cursor <cursoragent@cursor.com>
is_some_and needs a closure over &Box, description is Option, and assignment targets the boxed AbilityDefinition. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Blocked — the current parser delta still has unexplained card scope.
🔴 Blocker
- [HIGH] Account for the non-Metamorphic parser blast radius. Evidence: the current-head parse-diff sticky comment reports replacement-shape changes for four cards, including Dauntless Bodyguard and Scheming Fence, while the PR summary claims Metamorphic Alteration. Why it matters: this generic as-enters chooser path changes unsupported-card structure beyond the stated card and needs a class-level proof that each residual stays explicitly unsupported rather than becoming supported by shape alone. Suggested fix: either narrow the parser entry to the proven CopyChosen relation, or enumerate the affected class and add a coverage-honesty test for representative non-CopyChosen cards.
🟡 Non-blocking
- CodeRabbit notes on earlier heads are superseded or optional; the current blocking concern is the required parse-diff reconciliation.
Recommendation: request changes; reconcile the four-card parse diff and add the corresponding honest-coverage proof before re-review.
Stop claiming Moved for bare as-enters permanent choices so Dauntless Bodyguard and Scheming Fence keep unsupported ability shape; inject ChoosePermanent only when a CopyChosen companion proves the relation. Co-authored-by: Cursor <cursoragent@cursor.com>
Classifier no longer routes object chooses through replacement parse; only as_enters_choose_permanent_filter remains for CopyChosenHost. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Reviewed current head 3a0e901261b0e2a61290bcb7aa9be5ec562ca6de.
The follow-up now keeps non-CopyChosen as-enters object choices as explicit unsupported abilities and injects ChoosePermanent only through the CopyChosenHost relation, addressing the prior scope finding. I found no additional code-level blocker in this follow-up.
Held: required CI is not yet green: Rust tests (shard 2/2) currently reports failure, while the CI workflow and Rust lint/card-data checks are still running. Please resolve and let the checks complete before an approval/enqueue pass.
Live replacement_definitions is Definitions<T>, not a slice. Co-authored-by: Cursor <cursoragent@cursor.com>
iter_all is crate-private; integration tests need the public API. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Verdict: held — this current-head follow-up resolves the fixture API issue, but approval is not yet supported by the required proof and verification evidence.
✅ Clean
crates/engine/tests/integration/metamorphic_alteration.rs:521 now uses the public Definitions::iter_unchecked() API, addressing the test-fixture compile-path correction without changing production behavior.
🔴 Blocker
crates/engine/tests/integration/metamorphic_alteration.rs still leaves the PR body’s runtime verification checklist unchecked (copy snapshot, host reversion, non-targetability, and independent-Aura isolation). The current required Rust checks are also still running. The implementation has a broad engine/parser surface, so this lacks the concrete current-head proof required before approval.
Recommendation: provide the completed discriminating runtime evidence and let the required checks settle; then it can return for approval.
CallerEpilogue discarded apply_pending_post_replacement_effect's CopyTargetChoice, so Metamorphic spell casts attached without copying. Surface the prompt, stash PendingSpellResolution, and return like the delivery NeedsChoice arm. Mark ChoosePermanent order-independent. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Held on current head 90f4179f. The spell-path continuation change addresses the previously identified dropped mid-entry choice, but required Rust/frontend checks are still pending. No approval or queue action while that external verification is incomplete.
Follow-up for held review (
|
| Required proof | Test |
|---|---|
| Copy snapshot (frozen) | copied_values_are_a_frozen_snapshot_of_the_chosen_creature |
| Host reversion on Aura leave | host_reverts_when_the_aura_leaves_play |
| Non-targetability (hexproof donor) | hexproof_opponent_creature_is_a_legal_copy_donor |
| Independent-Aura isolation | two_auras_install_independent_copies_on_their_own_hosts |
| Spell-path host copy + Aura unchanged | enchanted_creature_becomes_copy_of_chosen_and_aura_is_unchanged |
| Reach-guard (snapshot + CopyValues TCE) | host_is_the_only_creature_and_remains_a_legal_copy_choice |
All live in crates/engine/tests/integration/metamorphic_alteration.rs. Please re-check once Rust shards settle green on 90f4179fb.
…tions Honor mid-entry prompts from the spell drain (Metamorphic CopyTargetChoice) but continue the Aura-attach epilogue instead of early-returning with PendingSpellResolution on top — that buried Tribute/Siege AbilityContinuations and broke resume. Host comes from attached_to after epilogue, matching dig. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
Held on current head e5daa599. The CallerEpilogue follow-up removes the pending-spell wrapper that buried the active continuation and instead preserves the replacement choice while the normal Aura/cast epilogue completes. The existing Metamorphic integration coverage remains the relevant production-path evidence. Required Rust verification is still pending, with the current Rust shard 1 and Tauri jobs failed, so this is not ready for approval or queueing.
PersistChosenAttribute needs attached_to before CopyTargetChoice is answered. Move the honor-WaitingFor drain to after CR 608.3c attach so spell-path Metamorphic matches dig order without burying Tribute AbilityContinuations under SpellResolution. Co-authored-by: Cursor <cursoragent@cursor.com>
Follow-up
|
…oice CR 608.3c / CR 303.4a: stash PendingSpellResolution.spell_targets on the CallerEpilogue CopyTargetChoice pause instead of recovering the host via an ambiguous Enchant-filter consult. Fixture installs Enchant via MTGJSON hint; add a multi-host regression without SpecificObject pinning. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the Aura-host blocker on
Local MSVC |
CallerEpilogue CopyTargetChoice sits outside the permanent replace_event scope, so re-read cast_timing_permission and convoked_creatures from the battlefield object when building PendingSpellResolution. Co-authored-by: Cursor <cursoragent@cursor.com>
Attach the Aura before the CallerEpilogue ChoosePermanent drain, keep PendingSpellResolution for spell_targets without early-returning, and retire the paused drain before deferred-entry replay so a second cast is not blocked on a leftover CopyTargetChoice. Co-authored-by: Cursor <cursoragent@cursor.com>
PersistChosenAttribute left state.waiting_for as the inbound CopyTargetChoice, so the completion tail re-returned it and the cast driver re-answered until its loop cap — sequential casts stayed blocked while single-cast tests still passed. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Every prior blocker is resolved on Re-reviewed the delta since my last review head ( Card premise verified against Scryfall, not the PR description:
✅ CleanThe Aura-host blocker is properly fixed. The The regression is discriminating and honestly built. Keyword::Enchant(TargetFilter::Typed(tf))
if tf.type_filters.contains(&TypeFilter::Creature)Then asserts attachment lands on the cast target and that Coverage honesty is proven, not asserted. a.effect.unimplemented_description()
.is_some_and(|d| d.to_lowercase().contains("choose"))That proves the Dauntless Bodyguard / Scheming Fence choose lines actually reached the parser and were preserved as explicit Parse-diff scope reconciles. The sticky comment ( CR annotations grep-verify. All 25 CR numbers this PR adds resolve in Registration is real. 🟡 Non-blocking1. 2. 3. The completion tail diverges from its 4. 5. Inherited, not yours: the Prior requested changes, all resolved: the Recommendation: land fixups 1 and 2 — drop or exhaustively match |
…ll stash Drop the unread ChoosePermanentPersist discriminator (CopyTargetPurpose + CopyChosenHost already gate behavior), extract pending_spell_resolution_snapshot for the three stack stash sites, document why the PersistChosenAttribute completion tail omits BecomeCopy batch-capture steps, and restore CopyTargetChoice harness break uniformity. Co-authored-by: Cursor <cursoragent@cursor.com>
matthewevans
left a comment
There was a problem hiding this comment.
All three fixups landed by the author in a08ffb0a, plus the harness-uniformity item. Approving.
I had prepared a maintainer fixup for items 1 and 3 and discarded it — the author's a08ffb0a is the better version and arrived first. On item 1 they took the drop option rather than my exhaustive-match option; that is the cleaner of the two the review offered, since CopyTargetPurpose + LinkedChoiceKind::CopyChosenHost already carry the discrimination. Verified ChoosePermanentPersist is gone with zero dangling references repo-wide.
Item 2 verified safe — the part that actually needed proving
pending_spell_resolution_snapshot re-derives from state at call time, replacing two captures that were previously taken before replace_event (convoked_creatures, cast_timing_permission). Since reset_for_battlefield_entry clears all six of these fields (CR 400.7), a naive extraction here could have silently dropped convoke / kicker / cast-timing provenance through the pause detour. I traced it rather than assuming:
- Stack→Battlefield:
CastLinkSnapshotis captured pre-move (zone_pipeline.rs:2194) and restored at:2328— explicitly before the face-down / counter blocks, so it precedes everyNeedsChoicepark (:2499,:2530). The snapshot therefore reads restored values identical to the deleted pre-captures. - Stack→Graveyard:
cast_linkisNone(gate requiresto == Battlefield), butreset_for_battlefield_entrynever runs either — fields are untouched. Equal. - The one pre-restore early return (
:2143, merged-permanent leave) is gatedfrom == Zone::Battlefield, so it is unreachable from a Stack resolution. reset_for_battlefield_exit(the otherconvoked_creatures.clear()) is battlefield-exit only — not a Stack exit.
Conclusion: behavior-preserving at all three call sites. The extraction is a genuine DRY win — a new cast-metadata field can no longer be threaded into two of three sites.
Item 3's comment now answers both omissions (delivery-capture and the CR 702.49c batch drain) and is honest that the batch argument is reachability, not an enforced invariant.
Not addressed here, correctly: the CR 615.5 citation is wrong (it describes prevention effects) but is pre-existing on main in 5 places and inherited, not introduced. I'm fixing that cluster repo-wide separately — it is not this contributor's debt.
Required checks are re-running on a08ffb0a; enqueuing with --auto so the queue waits on them.
|
Temporarily dequeued by a maintainer — nothing is wrong with this PR, and it will be re-enqueued shortly.
That job is a The fix is #6568. It was queued behind the PRs it needs to unblock, which is a deadlock: the queue merges in order, so it could not land until those cleared, and they could not clear until it landed. Dequeuing the entries ahead of it is how that gets broken. No action needed from you. Your PR keeps its approval and its head is unchanged. Once #6568 merges, this will be re-enqueued and should go through on the same green it already had. Apologies for the churn — this was our breakage, not yours. |
|
Closing this PR, along with the other open PRs from this account, as a maintainer decision about contribution process rather than about any particular line of code here. The pattern across these submissions has been automated generation with our CI and review feedback acting as the correction loop — changes pushed before they are understood, then iterated against whatever comes back red. The PR histories show it directly: reverting a fix and pushing a That model doesn't work for this repository. Review capacity here is the scarce resource, and it is spent on rules-correctness questions that require someone to have reasoned about the Comprehensive Rules and the engine's layer architecture before pushing. When the correction loop is outsourced to maintainer review, each round consumes that capacity without transferring understanding, and the same class of defect returns on the next PR. To be clear about what this is not: it is not a verdict that the code in this PR is wrong. Some of this work was genuinely useful and several of these changes landed. Nor is it a judgement about using AI tooling — plenty of good contributions here are AI-assisted. The distinction is whether the author has verified and understood the change before it reaches review. If you want to contribute here again, the bar is a change you can explain end to end: which Comprehensive Rules section it implements, why that seam is the correct one, and what a test would have to do to fail if the change were reverted. Open an issue describing that first and we'll talk before any code is written. Thank you for the work that did land. |
|
Reopening this. The closure was a maintainer error, and the reasoning I posted was wrong on the facts. I closed this PR yesterday as part of a batch action against this account. That was my call and it was not correct. Here is the specific thing I got wrong, because you are owed the detail rather than a vague retraction. The trigger was
So the model use was compliant with the rules as published at the time, and I applied a rule retroactively that did not exist when you wrote the code. The grandfather clause now in I also want to separate two things I ran together in the closure comment. The behavioural concerns I raised — reverting a fix to push a diagnostic commit so CI prints a value, and removing a passing assertion under a commit message that did not mention it — are real observations and I stand behind raising them. They are review findings about specific changes. They were not grounds for a batch closure of unrelated PRs, and treating them as evidence of bad faith rather than as things to fix in review was the error. What happens now: this PR is reopened at its original head with its review history intact. It gets processed on its merits like any other. Where it already had an approval, that approval stands and it goes back toward the queue; where it had open findings, those findings are still open and unchanged. Two things worth knowing going forward:
Thanks for the work, and sorry for the disruption — you had a PR approved and in the merge queue when I closed it. |
Summary
Effect::ChoosePermanent) and enchanted-host copy static (CopyChosenmarker).CopyValuesTCE on the enchanted host viaapply_precomputed_copy_values(UntilHostLeavesPlay) — the Aura itself is neverBecomeCopy'd.WaitingFor::CopyTargetChoicewith aPersistChosenAttributepurpose discriminant (no new ObjectPick WaitingFor).ChoosePermanent { CopiableSnapshot }is injected only viaLinkedChoiceKind::CopyChosenHostwhen a companionCopyChosenstatic is present; bare as-enters permanent chooses (Dauntless Bodyguard, Scheming Fence) stay Unimplemented ability gaps.Fixes #6013
Test plan
enchanted_creature_becomes_copy_of_chosen_and_aura_is_unchanged— cast Aura, choose donor; host copies, Aura unchangedcopied_values_are_a_frozen_snapshot_of_the_chosen_creaturehost_reverts_when_the_aura_leaves_playhexproof_opponent_creature_is_a_legal_copy_donortwo_auras_install_independent_copies_on_their_own_hostshost_copying_a_token_donor_routes_token_displayCopiableSnapshot+ Aura-sourcedCopyValuesTCE —host_is_the_only_creature_and_remains_a_legal_copy_choicenon_spell_aura_entry_copies_chosen_creatureChoosePermanent+CopyChosen/EnchantedBy—as_enters_choose_a_creature_parses_to_choose_permanent,enchanted_is_a_copy_of_chosen_parses_to_copy_chosen_staticnon_copy_chosen_as_enters_permanent_choose_stays_unsupported(Dauntless Bodyguard, Scheming Fence)purpose: BecomeCopydefault) — covered by existing copy-choice regressions on this branche42fc8313; Rust shards + lint still settling)Validation notes
Discriminating runtime coverage lives in
crates/engine/tests/integration/metamorphic_alteration.rs. Plan/impl review completed via engine-implementer pipeline. Blast-radius narrowing: no line-local Moved claim for bare permanent chooses;CopyChosenHostis the sole ChoosePermanent injection path.