Fix AI X sizing that left Curse of the Swine and Distorting Wake uncastable#11338
Open
liamiak wants to merge 1 commit into
Open
Fix AI X sizing that left Curse of the Swine and Distorting Wake uncastable#11338liamiak wants to merge 1 commit into
liamiak wants to merge 1 commit into
Conversation
…stable On spells whose target count is X, ComputerUtilCost.setMaxXValue sizes X against every legal target via CardUtil.getValidCardsToTarget. Later in ChangeZoneAi.isPreferredTarget the candidate list is narrowed to what the AI actually wants, which for exile and bounce effects is the opponents' permanents only. X has already been fixed by then, so the min-target check below it fails and the AI declines the spell entirely. The practical effect is that controlling a single targetable permanent of its own is enough to make these spells uncastable. Distorting Wake targets any nonland permanent, so any creature, artifact, enchantment or planeswalker will do it: own nonland permanents | 0 | 1 | 2 | 3 | 5 X chosen | 2 | 3 | 4 | 5 | 7 casts? | y | n | n | n | n against a fixed two permanents on the opponent's side. It is not gated on having spare mana - the same happens on four lands. Re-cap X to the size of the narrowed list before the check. The AI then pays for exactly the permanents it wants and casts normally. Verified before and after on Curse of the Swine, Aether Tide and Distorting Wake, all of which go from declining to casting for the right X. Alexi, Zephyr Mage and Vengeful Dreams still decline for unrelated reasons and are untouched by this. This is the non-simulation AI only. The simulation AI reaches a similarly oversized X by a different route - SpellAbilityChoicesIterator.announceX always takes the maximum, with a standing TODO about iterating - and shares no decision code with these AI classes, so it is unaffected either way. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tool4ever
approved these changes
Jul 25, 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.
On spells whose target count is X, the AI sizes X against every legal target and then narrows the targets — leaving X too big to satisfy, so it declines the spell entirely.
ComputerUtilCost.setMaxXValuecaps X usingCardUtil.getValidCardsToTarget, which counts everything legal. Later inChangeZoneAi.isPreferredTargetthe candidate list is narrowed to what the AI actually wants, which for exile and bounce effects is the opponents' permanents only. X is already fixed by then, so thelist.size() < sa.getMinTargets()check below it fails.The practical effect: controlling one targetable permanent of its own is enough to make these spells uncastable.
Distorting Wake, before this change
It targets any nonland permanent, so any creature, artifact, enchantment or planeswalker triggers it. Opponent held two permanents throughout:
X visibly tracks the whole battlefield rather than the opponent's side. It is not gated on having spare mana either — the same happens on four lands.
Distorting Wake carries no
AI:RemoveDeckflag, so this is a card the AI is allowed to run today and cannot cast.The change
Re-cap X to the size of the narrowed list, immediately before that check. The AI then pays for exactly the permanents it wants and casts normally. It targets only the opponents' permanents, as it did before.
Verified
Before and after, by reverting the change and re-running:
AllAllAllAllThe last two are untouched by this and remain broken for their own reasons.
245 tests green across
ChangeZoneAiTest, the simulation suites, and the AI ability and combat tests.Scope
I have deliberately not removed any
AI:RemoveDeck:Allflags. Making these castable is a separate question from whether the AI plays them well, and Curse of the Swine still has a wart worth judging in real games first: when an opponent's only creatures are small, it will still exile them, and a 2/2 Boar is close to an even swap.This is the non-simulation AI only. The simulation AI arrives at a similarly oversized X by a different route —
SpellAbilityChoicesIterator.announceXalways takes the maximum, with a standing TODO about iterating over values for performance reasons — and shares no decision code with these AI classes, so it is unaffected either way. Fixing that means widening a combinatorial search and belongs in its own change.Written with Claude Code (Opus 4.8), per the AI coding agent guidance in CONTRIBUTING.md; also recorded as a commit co-author.