Skip to content

Fix AI X sizing that left Curse of the Swine and Distorting Wake uncastable#11338

Open
liamiak wants to merge 1 commit into
Card-Forge:masterfrom
liamiak:fix-ai-curse-of-the-swine
Open

Fix AI X sizing that left Curse of the Swine and Distorting Wake uncastable#11338
liamiak wants to merge 1 commit into
Card-Forge:masterfrom
liamiak:fix-ai-curse-of-the-swine

Conversation

@liamiak

@liamiak liamiak commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

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.setMaxXValue caps X using CardUtil.getValidCardsToTarget, which counts everything legal. 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 is already fixed by then, so the list.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:

AI's own nonland permanents 0 1 2 3 5
X chosen 2 3 4 5 7
casts? yes no no no no

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:RemoveDeck flag, 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:

Card Flagged Before (AI controls 3 permanents) After
Distorting Wake no declines, X=5 casts, X=2
Curse of the Swine All declines, X=5 casts, X=2
Aether Tide All declines, X=5 casts, X=2
Alexi, Zephyr Mage All declines still declines — unrelated
Vengeful Dreams All declines still declines — unrelated

The 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:All flags. 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.announceX always 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.

…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants