Skip to content

feat(engine): Susan Foreman planeswalk replacement arranges planar deck top#6070

Merged
matthewevans merged 9 commits into
phase-rs:mainfrom
andriypolanski:fix/5820-susan-foreman-planar-deck-arrange
Jul 17, 2026
Merged

feat(engine): Susan Foreman planeswalk replacement arranges planar deck top#6070
matthewevans merged 9 commits into
phase-rs:mainfrom
andriypolanski:fix/5820-susan-foreman-planar-deck-arrange

Conversation

@andriypolanski

Copy link
Copy Markdown
Contributor

Fixes #5820

Summary

Susan Foreman (Doctor Who):

If you would planeswalk, instead look at the top two cards of your planar deck, put one on the bottom of your planar deck and the other on top, then planeswalk.

This adds a building-block planeswalk replacement path: look at the top N cards of the planar deck, keep exactly keep_on_top on top (exact-count choice, unlike scry’s any-subset), then complete the chained Effect::Planeswalk via pending_continuation after the player submits SelectCards.

Changes

Engine types & state

  • Effect::ArrangePlanarDeckTop { count, keep_on_top } — typed arrange primitive for planar deck tops
  • WaitingFor::ArrangePlanarDeckTopChoice { player, cards, keep_on_top } — interactive exact-keep selection
  • Registrations across ability.rs, game_state.rs, ability_rw.rs, ability_scan.rs, coverage.rs, printed_cards.rs, trigger_index.rs, analysis/ability_graph.rs, oracle_effect/sequence.rs, oracle_ir/doc.rs, scenario.rs

Resolver & wiring

  • crates/engine/src/game/effects/arrange_planar_deck.rs — sets ArrangePlanarDeckTopChoice waiting state
  • engine_resolution_choices.rsvalidate_exact_keep_on_top_selection, reorder planar_deck, finish_with_continuation drain
  • replacement.rsProposedEvent::Planeswalk in player-scope gate (alongside Draw/Scry)
  • effects/planeswalk.rs — planar-die sentinel routes through replace_event; chained Planeswalk uses non-sentinel source to avoid infinite replacement loop

Parser

  • oracle_replacement.rs
    • parse_would_planeswalk_replacement()"if you would planeswalk, …"
    • parse_arrange_planar_deck_planeswalk_substitute()[ArrangePlanarDeckTop(2,1), Planeswalk]
    • Unit test: would_planeswalk_replacement_parses_susan_foreman

AI & frontend

  • ai_support/candidates.rsselect_cards_variants(..., Some(keep_on_top)) for exact keep count
  • phase-ai/decision_kind.rsArrangePlanarDeckTopChoice decision kind
  • client/src/adapter/types.ts, waitingForRegistry.ts, CardChoiceModal.tsx, libraryModals.tsxArrangePlanarDeckTopModal with exactKeepCount on ReorderableTopChoice

Tests

  • crates/engine/tests/integration/issue_5820_susan_foreman.rs — end-to-end: planar-die Planeswalk → replacement → arrange pause → SelectCards → planeswalk to chosen top card
  • engine_resolution_choices unit tests — reorder handler + stashed Planeswalk continuation drain
  • arrange_planar_deck unit test — waiting-for setup

Test setup note: planar deck objects must be inserted into objects without add_to_zone(Zone::Command) — only the active plane belongs in command_zone. Deck cards live only in planar_deck (face down), mirroring planechase_tests::create_planar_object.

Test plan

cargo fmt --all -- --check
cargo test -p engine susan_foreman_planeswalk_arranges
cargo test -p engine --lib arrange_planar_deck_top_choice
cargo test -p engine --lib arrange_planar_deck_top_sets
cargo test -p engine --lib would_planeswalk_replacement_parses

Tilt (when up): clippy, test-engine, check-frontend after merge.

Notes for reviewers

  • Exact keep vs scry: keep_on_top is enforced by validate_exact_keep_on_top_selection (not scry’s any-subset semantics).
  • Replacement loop safety: the chained Effect::Planeswalk sub-ability uses Susan’s object source (non-sentinel), so it planeswalks directly after arrange without re-entering the replacement pipeline.
  • Planar-die entry point: only the synthetic planar_ability_sentinel_id planeswalking ability is replaceable (Fixed Point in Time precedent); card/SBA planeswalks bypass replacement per CR 701.31c discrimination in planeswalk.rs.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label Jul 17, 2026
@superagent-security

Copy link
Copy Markdown

🚨 Contributor flagged. Click here for more info: Superagent Dashboard

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

Review — changes requested

Verdict: blocker

The new Effect::ArrangePlanarDeckTop and WaitingFor::ArrangePlanarDeckTopChoice variants are not integrated with phase-ai, so the PR cannot compile the workspace.

  • crates/phase-ai/src/policies/effect_classify.rs:52 does not match ArrangePlanarDeckTop.
  • crates/phase-ai/src/policies/redundancy_avoidance.rs:279 does not match ArrangePlanarDeckTop.
  • crates/phase-ai/src/search.rs:751 does not match ArrangePlanarDeckTopChoice.

Evidence: the required Decision-cost perf gate failed with E0004 for all three exhaustive matches (GitHub Actions run 29555228669, job 87805802188). Please add rules-appropriate AI classification and choice generation/handling, then rerun CI.

Clean: isolated diff review found no unsafe command/network or prompt-injection pattern; the failing contributor-trust comment is advisory only.

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

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

🟢 Added (1 signature)

  • 1 card · ➕ replacement/Planeswalk · added: Planeswalk (player scope=You)
    • Affected (first 3): Susan Foreman

🔴 Removed (1 signature)

  • 1 card · ➖ ability/replacement_structure · removed: replacement_structure
    • Affected (first 3): Susan Foreman

2 card(s) had Oracle-text changes (errata/reprint) — excluded as non-parser.

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

Verdict: changes requested — the phase-AI exhaustiveness fix is present, but the new replacement is still restricted to the wrong planeswalk source class.

🔴 Blocker

  • crates/engine/src/game/effects/planeswalk.rs:27-30 invokes planechase::planeswalk directly for every non-sentinel Effect::Planeswalk, so none of those events reaches the new ReplacementEvent::Planeswalk matcher. This contradicts Susan Foreman's Oracle text: “If you would planeswalk, instead look at the top two cards of your planar deck, put one on the bottom of your planar deck and the other on top, then planeswalk.” The effect has no “as a result of rolling the planar die” restriction. The same source file labels card-instruction and state-based planeswalks as nonreplaceable, while crates/engine/src/game/planechase.rs:426-430 and :463-466 identify direct phenomenon/SBA planeswalk paths.

    The new integration test only constructs the synthetic planar-die sentinel, so it cannot fail when a non-sentinel planeswalk bypasses the replacement. Route every planeswalk event through one replacement-aware authority, carrying the applied-replacement state so Susan's trailing “then planeswalk” does not reapply Susan while other eligible replacement effects still compose. Add runtime coverage for a non-sentinel Effect::Planeswalk and the existing planar-die path.

✅ Clean

  • 2dcc152 adds the three previously missing phase-ai exhaustive-match arms, addressing the earlier compile blocker.

Recommendation: request changes and route this through the shared planeswalk/replacement authority before re-review.

@matthewevans matthewevans added the enhancement New feature or request label Jul 17, 2026
@matthewevans matthewevans removed their assignment Jul 17, 2026
@matthewevans matthewevans self-assigned this Jul 17, 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.

Verdict: changes requested — the new head fixes Effect::Planeswalk, but the shared replacement authority still excludes two rules-defined planeswalk causes.

🔴 Blocker

  • crates/engine/src/game/planechase.rs:433 (encounter) and :469 (check_phenomenon_planeswalk_sba) still call planeswalk(...) directly. crates/engine/src/game/effects/planeswalk.rs:27-37 only consults planeswalk_through_replacements for Effect::Planeswalk; therefore Susan Foreman never applies when a phenomenon is encountered or when its state-based-action planeswalk occurs. The verified Oracle text is: “If you would planeswalk, instead look at the top two cards of your planar deck, put one on the bottom of your planar deck and the other on top, then planeswalk.” It has no planar-die or ability-instruction restriction. docs/MagicCompRules.txt:3574 (CR 701.31c) expressly lists the planeswalking ability, phenomenon/SBA paths, leaving-game path, and ability instructions as planeswalk causes; :3054-3056 (CR 614.1/614.1a) makes this an event replacement. The current PlaneswalkCause can distinguish only PlanarDie and Instruction, so the direct paths cannot preserve Fixed Point in Time's narrower scope while applying Susan, and they have no choice/continuation handling for Susan's arrangement prompt.

    Route every CR 701.31c cause through one replacement-aware planeswalk authority with an explicit non-planar-die cause, preserving the applied-replacement set and correctly pausing/resuming the phenomenon/SBA path. Keep Fixed Point limited to PlanarDie. Add runtime tests that Susan replaces encounter and the phenomenon SBA, alongside the existing planar-die and card-instruction coverage.

✅ Clean

  • 824540277 correctly routes non-sentinel Effect::Planeswalk through the replacement pipeline, carries the applied set into Susan's trailing planeswalk, and adds a card-instruction regression test; that resolves the prior review finding but does not cover the direct CR 701.31c callers.

Recommendation: request changes — complete the shared authority for every planeswalk cause, then re-request review.

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

Verdict: changes requested — #6070 is still rules-incomplete and the current head does not compile the workspace.

🔴 Blocker

  • crates/engine/src/game/planechase.rs:433 (encounter) and :469 (check_phenomenon_planeswalk_sba) call raw planeswalk(...), whereas crates/engine/src/game/effects/planeswalk.rs:30-36 is the only caller of planeswalk_through_replacements. Susan Foreman's verified text is “If you would planeswalk, instead … then planeswalk,” with no planar-die/instruction restriction. docs/MagicCompRules.txt:3574 (CR 701.31c) lists phenomenon and state-based paths among planeswalk causes, and CR 614.1a makes this a replacement event. Therefore Susan does not replace those two legal planeswalk causes. Route every CR 701.31c cause through the shared replacement-aware authority, preserve the applied set, and add runtime coverage for encounter and the phenomenon SBA while keeping Fixed Point restricted to planar-die causes.

  • CI run 29557534949 failed its Rust lint and Rust-tests shards: crates/mtgish-import/src/convert/replacement.rs has nine ReplacementDefinition initializers missing the new required planeswalk_scope field (for example lines 156, 222, 599, 737, 988, 1079, 1264, and 1383). Thread the new field through every construction or provide a constructor/default at the type authority; the current head cannot be enqueued.

✅ Clean

  • The head correctly changed ordinary Effect::Planeswalk to call planeswalk_through_replacements and retains the applied-replacement state for Susan's chained planeswalk.

Recommendation: request changes — complete the shared replacement authority and all ReplacementDefinition construction paths, add the two runtime regressions, then re-request review.

@matthewevans matthewevans removed their assignment Jul 17, 2026
@andriypolanski
andriypolanski force-pushed the fix/5820-susan-foreman-planar-deck-arrange branch from c434de0 to 944af17 Compare July 17, 2026 08:27
@matthewevans matthewevans self-assigned this Jul 17, 2026
@matthewevans matthewevans removed their assignment Jul 17, 2026
@matthewevans matthewevans self-assigned this Jul 17, 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.

Approved: the current head routes planar-die, card-instruction, encounter, and phenomenon-SBA planeswalks through the replacement-aware authority; current runtime coverage exercises each previously blocked path.

@matthewevans
matthewevans added this pull request to the merge queue Jul 17, 2026
@matthewevans matthewevans removed their assignment Jul 17, 2026
Merged via the queue into phase-rs:main with commit e216726 Jul 17, 2026
15 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:flagged Contributor flagged for review by trust analysis. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seam: planeswalk replacement + planar-deck top-2 arrange (Susan Foreman) — requesting accepted

3 participants