Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
14 changes: 0 additions & 14 deletions crates/engine/data/known-tokens.toml
Original file line number Diff line number Diff line change
Expand Up @@ -386,11 +386,9 @@ source_card_names = [
"Bank Job",
"Battle Angels of Tyr",
"Beamtown Beatstick",
"Bejeweled Warg",
"Beza, the Bounding Spring",
"Big Score",
"Big Spender",
"Bilbo's Gambit",
"Bilbo, Retired Burglar",
"Bill Ferny, Bree Swindler",
"Black Market Connections",
Expand Down Expand Up @@ -448,8 +446,6 @@ source_card_names = [
"Dockside Extortionist",
"Don Andres, the Renegade",
"Done for the Day",
"Dori, Bearer of Friends",
"Dragon-Cursed Halls",
"Dungeon of the Mad Mage",
"Dungeoneer's Pack",
"Edward Kenway",
Expand Down Expand Up @@ -482,7 +478,6 @@ source_card_names = [
"Gilded Pinions",
"Gimli of the Glittering Caves",
"Gleaming Barrier",
"Gleaming Splendor",
"Glittermonger",
"Gluntch, the Bestower",
"Glóin, Dwarf Emissary",
Expand Down Expand Up @@ -548,7 +543,6 @@ source_card_names = [
"Life Insurance",
"Lobelia Sackville-Baggins",
"Locke, Treasure Hunter",
"Long-Bodied Grey Dog",
"Loot Dispute",
"Lost Mine of Phandelver",
"Lotho, Corrupt Shirriff",
Expand Down Expand Up @@ -591,7 +585,6 @@ source_card_names = [
"Old Gnawbone",
"Old Rutstein",
"Olivia, Opulent Outlaw",
"Orcrist, Goblin-cleaver",
"Orochi Soul-Reaver",
"Pain Distributor",
"Patient Naturalist",
Expand Down Expand Up @@ -659,9 +652,6 @@ source_card_names = [
"Skullport Merchant",
"Smashing Success",
"Smaug",
"Smaug the Impenetrable",
"Smaug the Magnificent",
"Smaug, Wicked Worm",
"Smoke Blessing",
"Smoke Spirits' Aid",
"Smothering Tithe",
Expand Down Expand Up @@ -695,15 +685,11 @@ source_card_names = [
"The Gold Saucer",
"The Golden City of Orazca",
"The Matrix of Time",
"The Misty Mountains Cold",
"The Reaver Cleaver",
"The Sackville-Bagginses",
"The Third Doctor",
"The Western Cloud",
"There and Back Again",
"Thieves' Tools",
"Thorin, Company's Leader",
"Thorin, King of Durin's Folk",
"Ticket Tortoise",
"Tireless Provisioner",
"Tivit, Seller of Secrets",
Expand Down
19 changes: 19 additions & 0 deletions crates/engine/src/game/casting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,25 @@ fn restriction_scope_matches_player(
RestrictionPlayerScope::OpponentsOfSourceController => {
source_controller.is_some_and(|controller| controller != caster)
}
// CR 109.5 + CR 611.2a: the affected "you" ("you can't cast additional
// spells this turn" — Conduit of Worlds) is the player who activated the
// ability (CR 109.5: an activated ability's "you" is the activator), fixed
// at resolution, and the resulting continuous effect lasts until end of
// turn independent of its source (CR 611.2a). `add_restriction` lowers
// `SourceController` to `SpecificPlayer` at creation so the ban stays with
// the activator even after the source leaves play or changes controller —
// reading it live here would silently drop the ban when the source is
// gone (`source_controller == None`). An unresolved scope here is a bug:
// a corrupt/forged snapshot is scrubbed of it on restore by
// `GameState::drop_unresolved_source_controller_restrictions`, so a raw
// scope reaching this arm means the invariant was violated in a live state.
RestrictionPlayerScope::SourceController => {
debug_assert!(
false,
"SourceController should be resolved by add_restriction"
);
false
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand Down
22 changes: 22 additions & 0 deletions crates/engine/src/game/casting_costs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9616,6 +9616,28 @@ fn finalize_cast_with_phyrexian_choices_inner(
object_id,
});

// CR 608.2c + CR 608.2g + CR 601.2i: A paid during-resolution cast is the
// "performed optional" the moment its spell is on the stack and its mana is
// paid — this line is reached only on full payment completion (a pause
// returns earlier, and a cancelled/rewound cast never emits SpellCast), so an
// unpayable or declined cast never latches. When the granting ability parked
// an "If you do, …" rider as a continuation (Conduit of Worlds: "you may cast
// that card. If you do, you can't cast additional spells this turn."), that
// rider's `EffectOutcome { OptionalEffectPerformed }` gate must now evaluate
// true, so propagate the signal into the stashed continuation. Gated on the
// gate's presence so the shared finalize path does not misfire: a normal hand
// cast is announced only at a priority window (no `AbilityContinuation` frame
// is stack-top there), and a during-resolution cast with no "if you do" rider
// (Cascade, Discover) carries no such gate, so neither is latched.
if let Some(frame) = state.active_ability_continuation_frame_mut() {
if frame.pending.chain.has_optional_effect_performed_gate() {
frame
.pending
.chain
.set_optional_effect_performed_recursive(true);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

// CR 601.2a + CR 601.2b + CR 110.4: Record permission usage when the spell
// is finalized onto the stack. This prevents casting a second spell via the
// same source/slot before the first resolves. Only frequency-bounded
Expand Down
Loading
Loading