Check spell timing before scanning for cast-with-flash effects - #11963
Merged
tool4ever merged 1 commit intoSep 19, 2026
Merged
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
tool4ever
approved these changes
Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Priority passes with a non-empty stack are cheaper, because two board-wide scans for "cast as though it had flash" effects no longer run when their result cannot change the answer.
This complements #11946, which removed two other board-wide scans from wide token cascades like Scute Swarm and listed the AI's priority pass among the costs that remained. This change removes the part of that cost that came from these scans.
Cost
StaticAbilityCastWithFlash.anyWithFlashandanyWithFlashNeedsInfoeach build a collection of every card in the static-ability source zones and ask each card for its static abilities. Both are reached fromSpellAbilityRestriction.canPlay, ahead of the activator check, so they run for every ability a player's priority check looks at, including abilities on other players' permanents that the player could never activate:SpellAbility.canCastTimingcallswithFlash, and through itanyWithFlash, whenever the player cannot act at sorcery speed. That includes every pass with an object on the stack. For an ordinary activated ability the scan cannot change the result: activated abilities are instant speed by default, so the method returns true whether the scan finds anything or not.canPlayrunsanyWithFlashNeedsInfofirst and checkscanCastTimingsecond. The scan only matters when the timing check fails, but it ran for every ability, including the many whose timing already passes.The AI's priority decision calls
canPlayfor each ability on its hand and graveyard, the exile and command zones, and every battlefield, on every pass. Each of those calls scanned the whole board twice, so a pass cost grew with the number of abilities times the number of cards, and a long stack paid it once per resolution.Fix
canCastTimingreturns true for an activated ability that is neither a planeswalker ability nor sorcery speed, before it reacheswithFlash. Spells, planeswalker abilities and sorcery-speed abilities go through the scan as before.canPlaycheckscanCastTimingfirst and runsanyWithFlashNeedsInfoonly when the timing check fails.Why the result is unchanged
canCastTiming, the old code already returned true for such an ability on every path, either throughwithFlashor through the final!isPwAbility() && !isSorcerySpeed()line. Returning earlier skips only the scan. No ability is both a spell and an activated ability, so once that case has returned, the rest reduces tocanCastSorcery() || withFlash(...).canPlay,if (!needsInfo) { if (!timing) return false; }andif (!timing && !needsInfo) return false;are the same condition. Both calls only read game state, so evaluating them in the other order changes nothing.CastWithFlashstatics that target activated abilities (Teferi, The Wandering Emperor, Leonin Shikari and others) all target loyalty or equip abilities, which still go through the scan.Measurements
Four runs of each build, alternated, from the same seeds. Deltas are the change in the mean.
Sampling puts the two scans at 8.4% of a four-player commander game before the change and 3.1% after, and at 6.6% and 0.1% of the Scute Swarm runs. What remains in commander is spells and sorcery-speed abilities, where the scan still decides the answer.
Commander and ordinary constructed game logs are identical between the two builds in every run. Games built from token-making decks show no measurable change.
🤖 Generated with Claude Code