Fix AI implementation of Toxic Deluge and Flowstone Slide#11337
Open
liamiak wants to merge 1 commit into
Open
Conversation
Both cards carried AI:RemoveDeck:All because the AI could not cast them at all: their X is announced as a cost, and nothing ever chose it. PumpAll has an AI class, so the generic X handling in AiController is skipped, and Toxic Deluge's X lives in a PayLife<X> cost that path would not have seen anyway. With X unset both pump amounts read 0, so the sweep evaluated as -0/-0 and was always declined. Toxic Deluge was also missing IsCurse$ True, which meant PumpAllAi never reached its sweeper branch and instead asked whether to pump the AI's own creatures. PumpAllAi now picks X for a -X/-X sweep when the ability announces its own X, scoring candidates by the creatures each value destroys, and records it with setXManaCostPaid. Candidates are the toughnesses actually on the battlefield rather than every point up to the maximum, so the AI does not walk a 40 point life total one step at a time. Alongside that: - Life spent is priced against how much of the total is going, and a sweep is skipped if what survives it could then kill the AI. - +X/-X hands survivors power until end of turn, so Flowstone Slide is measured against whoever still has a combat coming: on an opponent's turn their survivors can swing back, on the AI's own turn that boost has expired long before they untap. - If some X clears an opponent's blockers and leaves enough power to finish them off this turn, that X is taken regardless of card value, mirroring the existing shortcut in DamageAllAi. - A mass pump lands on every opponent at once, so the evaluation now weighs all of them instead of just the strongest. Two player games are unaffected. The "dies to -X/-X" test existed in three copies; they now share one predicate on PumpAiBase. Triggers and sub-abilities are deliberately excluded, since X was already paid when the spell was cast. The Meathook Massacre would otherwise be refused, and the test covers that. Bane of the Living has the same underlying problem but is not fixed here: its X belongs to a morph cost handled by SetStateAi, which returns early for TurnFaceUp before choosing X, and sizing it needs the trigger's effect to be evaluated from there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Toxic Deluge and Flowstone Slide both carry
AI:RemoveDeck:All. The reason is that the AI cannot cast either of them at all — not that it plays them badly.Why
Their X is announced as a cost, and nothing chooses it.
PumpAllhas an AI class, so the generic X handling inAiController(which only fires for abilities with no AI class, and only for X in the mana cost) is skipped. Toxic Deluge's X lives in aPayLife<X>cost, which that path would not have covered anyway. With X unset, both pump amounts read 0 inPumpAllAi.checkApiLogic, the sweep evaluates as -0/-0, and it is always declined.Toxic Deluge was also missing
IsCurse$ True.SpellAbility.isCurse()is justhasParam("IsCurse"), so without itPumpAllAinever reached its sweeper branch and instead asked whether to pump the AI's own creatures. The majority of negative-NumDefPumpAll cards already carry the param.What changed
PumpAllAichooses X for a -X/-X sweep when the ability announces its own X, scores candidates by what each value destroys, and records it viasetXManaCostPaid. The existing curse evaluation still makes the final call. The X ceiling already worked —ComputerUtilCost.setMaxXValuereachesCostPayLife.getMaxAmountX— so nothing new was needed in forge-game.Candidates are the toughnesses actually on the battlefield rather than every point up to the maximum, so the AI does not walk a 40 point life total one step at a time.
Also:
+X/-Xhands survivors power until end of turn, so Flowstone Slide is measured against whoever still has a combat coming. On an opponent's turn their survivors can swing back; on the AI's own turn that boost expires long before they untap, so it is not counted against the cast.DamageAllAi.The "dies to -X/-X" filter existed in three copies; they now share one predicate on
PumpAiBase, which is why that file shows more deletions than additions.Deliberately out of scope
Triggers and sub-abilities are excluded from X selection, because X was already paid when the spell was cast. Without that guard The Meathook Massacre gets refused outright; there is a test for it.
Bane of the Living has the same underlying problem and is not fixed here. Its X belongs to a morph cost handled by
SetStateAi, which returns early forTurnFaceUpbefore reaching any X selection, and sizing it properly means evaluating the turn-face-up trigger's effect from there. That is a different subsystem and belongs in its own change.Testing
To see the original bug: put Toxic Deluge in an AI deck, give the opponent a few creatures, and watch it never get cast.
PumpAllAiTestcovers the four seams — that the AI casts the card at all, that a mass sweep counts every opponent's board, that an expiring power boost does not scare it off a good sweep, and that an already-paid X on a trigger is left alone. Each was confirmed to fail without its corresponding fix.Full run against current master: 244 tests green across
PumpAllAiTest, the simulation suites, and the AI ability and combat tests.Written with Claude Code (Opus 4.8), per the AI coding agent guidance in CONTRIBUTING.md; also recorded as a commit co-author.