Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/engine/src/ai_support/payment_continuation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,11 @@ fn classify_parked_cost_move_root(state: &GameState) -> PaymentContinuationState
| PendingCostMoveResume::Foretell { .. }
| PendingCostMoveResume::UnlessBouncePayment { .. }
| PendingCostMoveResume::CounterAdditionUnlessPayment { .. }
// CR 701.9b: a parked random unless-discard holds no pending cast and
// no mana-ability cursor — the game picks the cards with no player
// input — so like its counter-addition sibling it affiliates with no
// payment-continuation root.
| PendingCostMoveResume::RandomDiscardUnlessPayment(..)
| PendingCostMoveResume::LoyaltyActivation { .. } => {
PaymentContinuationState::NotAffiliated
}
Expand Down Expand Up @@ -659,6 +664,8 @@ fn pending_cost_move_contains_root(
| Some(PendingCostMoveResume::DelveManaPayment { .. })
| Some(PendingCostMoveResume::UnlessBouncePayment { .. })
| Some(PendingCostMoveResume::CounterAdditionUnlessPayment { .. })
// CR 701.9b: holds no pending cast, so it can contain no root.
| Some(PendingCostMoveResume::RandomDiscardUnlessPayment(..))
| Some(PendingCostMoveResume::LoyaltyActivation { .. })
| None => false,
}
Expand Down
466 changes: 445 additions & 21 deletions crates/engine/src/game/effects/discard.rs

Large diffs are not rendered by default.

45 changes: 41 additions & 4 deletions crates/engine/src/game/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5781,6 +5781,7 @@ pub(crate) fn drain_pending_cost_move_resume(
| PendingCostMoveResume::ActivationMillPayment { .. }
| PendingCostMoveResume::LoyaltyActivation { .. }
| PendingCostMoveResume::CounterAdditionUnlessPayment { .. }
| PendingCostMoveResume::RandomDiscardUnlessPayment(..)
)
),
CostMoveDrainBoundary::PriorityBoundary => matches!(
Expand Down Expand Up @@ -5861,6 +5862,14 @@ pub(crate) fn drain_pending_cost_move_resume(
events,
matches!(boundary, CostMoveDrainBoundary::ReplacementDelivered { .. }),
)?
} else if matches!(
state.pending_cost_move_resume,
Some(PendingCostMoveResume::RandomDiscardUnlessPayment(..))
) {
// CR 118.12: random discard pauses only after its Moved replacement
// returns a replacement choice; the delivered boundary resumes the
// payment through its already-authorized paid epilogue.
engine_payment_choices::resume_random_discard_unless_payment(state, events)?
} else {
unreachable!("eligible cost-move root must remain parked")
};
Expand Down Expand Up @@ -16095,6 +16104,28 @@ mod stage2_injector_tests {
// with a delegation; it sits above this producer and below the first two.
// The merge tree therefore retains main's first two coordinates
// (`:6177`/`:6254`) and shifts this one by −16 to `:9442`.
// Random-discard-as-a-cost (#7320, review round 1): `engine.rs:12004 ⇒
// :12019`, +15, and ONLY the engine.rs entry moved — the four
// effects/mod.rs + scoped_library_search entries did not, which is the
// set-preservation evidence. `git diff -U0` on this file has exactly three
// hunks, ALL inside `drain_pending_cost_move_resume` at `:5761`/
// `:5865` (+1/+13 = +14, zero deletions), i.e. entirely ABOVE this
// producer; predicted `12004+14` equals the observed coordinate exactly.
// They add the `RandomDiscardUnlessPayment` delivery resume and its
// dispatch arm — a cost-payment continuation, not
// a prompt mint: it RESUMES an already-minted `UnlessPayment` rather than
// creating a recipient, so it is correctly absent from this census.
// Identity re-established, not assumed: the producer at `:12019` is the
// same announcement-time modal mint this row NAMES — an `Ok(Some(..))` of
// the optional-effect prompt over `player` / `source_id` /
// `trigger_description` / `may_trigger_key` — still inside
// `begin_pending_trigger_target_selection`. (Spelled out rather than
// quoted: the needle above is ASSEMBLED so this row cannot be counted by
// its own instrument, and a verbatim quote here re-introduces exactly the
// self-count that defends against — it inflates `in_test` and reds the
// TOTAL assert instead of this one.) The two asserts
// above this one fired GREEN on the run that caught it — total still 37,
// partition still 5/7/25 — so no producer was added or lost.
//
// ⚠ THIS ROW FAILS IN CI BEFORE IT FAILS LOCALLY, and that is not a bug in the
// row. CI checks out `refs/pull/<n>/merge` — this branch merged with CURRENT
Expand Down Expand Up @@ -16440,6 +16471,12 @@ mod stage2_injector_tests {
// SET PRESERVATION: unchanged. Upstream adds no line matching the needle to this file and
// neither does this branch — total still 37, partition still 5/7/25.
//
// #7303 shifts this producer from `:12004` to `:12003` (-1), while this
// PR's random-discard continuation adds +14 lines above it. Re-derived in the
// branch tree by content: `12004 - 1 + 14 = 12017`; the line below is the
// announcement-time optional-effect producer in
// `begin_pending_trigger_target_selection`.
//
// #7303 fix round 3: `:12004 ⇒ :12003`, −1, and ONLY this entry moved.
// Re-derived, not assumed. `git diff -U0` on this file has exactly ONE hunk,
// `@@ -9943,8 +9943,7 @@` inside `apply_action` — the `ReturnAsAuraTarget`
Expand All @@ -16456,10 +16493,10 @@ mod stage2_injector_tests {
// FIRST and both fired GREEN on the run that caught this — total still 37,
// partition still 5/7/25. The change constructs no `WaitingFor` of any kind;
// it threads an attachment-legality authority through an existing call.
// #4155 adds seven lines above this producer for abandoned-cast
// finalization, while its deferred-resume cleanup removes two;
// the net +5 moves this coordinate to `:12008`.
"game/engine.rs:12012".to_string(),
// The PR's random-discard continuation and main's abandoned-cast
// finalization both shift this source coordinate. The merged
// announcement-time producer is re-derived at `:12021`.
"game/engine.rs:12021".to_string(),
],
"the five production producers, NAMED: the CR 603.5 gate in `resolve_chain_body` \
plus the two repeated-optional-payment drivers, the per-player acceptance cursor \
Expand Down
Loading
Loading