AI: only untap a permanent when it makes a concrete difference - #11373
Open
liamiak wants to merge 4 commits into
Open
AI: only untap a permanent when it makes a concrete difference#11373liamiak wants to merge 4 commits into
liamiak wants to merge 4 commits into
Conversation
When no priority target (Time Vault-style "won't auto-untap") was present,
UntapAi fell through to getMostExpensivePermanentAI - untapping the priciest
tapped permanent whether or not untapping it achieved anything. For a card like
Kiora's Follower that meant tapping it every turn to untap a land that untaps
itself next turn regardless: motion with no value, which is why ~40 plain
{T}: untap cards carry AI:RemoveDeck:All.
For free ({T}-only) untap abilities, pick a benefit-directed target instead, and
untap the exact card the benefit points to:
- ramp (own main): untap a mana source iff untapping that specific source turns
a spell in hand we cannot currently afford into one we can - checked with the
real mana-payment routine (canPayManaCost), so it respects the actual cost,
colours included, not just mana value;
- blocker (opponent's turn): untap our best creature only while under attack;
- otherwise: hold.
Costed / triggered / Always-logic untaps are a deliberate investment and keep
their existing behaviour unchanged.
With the crude waste gone, drop AI:RemoveDeck:All from six clean {T}-untap cards
verified to untap only for genuine benefit: Aphetto Alchemist, Kiora's Follower,
Unbender Tine, Vizier of Tumbling Sands, Krosan Restorer and Tidewater Minion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tool4ever
reviewed
Jul 25, 2026
| * check ({@link ComputerUtilMana#canPayManaCost}) against the source temporarily untapped, so it | ||
| * accounts for the actual cost - colours included - not just mana value. | ||
| */ | ||
| private static Card pickRampUntap(final Player ai, final CardCollection untapList) { |
Contributor
There was a problem hiding this comment.
code looks too unclean
also just redundant to doPoolExtraManaLogic...?
Contributor
Author
There was a problem hiding this comment.
Good calls, both — addressed in the last three commits.
- Unclean: the smell was a board poke — it flipped a card's tapped state to probe affordability and restored it in a
finally. Replaced with a small shared helper,untappingEnablesACast, that models the freed mana by shaving generic off a copy of the cost (ManaCostBeingPaid#decreaseShard) and askingcanPayManaCostagainst usable sources only. No tap/untap of real permanents. - Redundant: you were right that it overlapped
doPoolExtraManaLogic, so I routed the dorks through the same helper. That also surfaced a latent bug on the dork side: its old!hasEnoughManaSourcesToCastgate counts tapped sources, so when you're tapped out but source-rich it decided it had "enough" and declined to ramp — exactly the spot where untapping a land is what lets the spell resolve. The helper asks "can I pay with usable sources now, and would one more mana close the gap?" instead, which is a strict superset of the old firing (nothing that ramped before stops), and the wholeforge.aisuite stays green.
Split into three commits — helper + generic path, then the dork routing/fix, then an optional test — so the fix and the test are separable.
The free ({T}-only) untap's ramp check temporarily flipped a card's tapped
state to probe affordability, restoring it in a finally block. Poking live game
state to answer a hypothetical is fragile, and it re-implemented a question the
mana logic already answers by arithmetic.
Replace it with a shared helper, untappingEnablesACast, that models the freed
mana by shaving generic off a copy of the cost (ManaCostBeingPaid#decreaseShard)
and asking ComputerUtilMana#canPayManaCost against usable sources only - no
tap/untap of the board. The generic ramp path now calls it; the mana-dorks keep
their own inline copy for now (routed through the helper in the next commit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
doPoolExtraManaLogic now shares the untappingEnablesACast helper instead of duplicating the check, removing the redundancy the free-untap path already resolved. Sharing it also fixes a latent bug the dork side never exposed. The old gate was !hasEnoughManaSourcesToCast, which counts *all* mana sources, tapped included. So with, say, five lands (three already tapped from casting this turn) and a four-drop in hand, it decided it had "enough sources" and declined to untap - exactly the tapped-out spot where untapping a land is what lets the four-drop resolve. The helper instead asks "can I pay with my *usable* sources now, and would one more mana close the gap?", so the dorks fire in that case. It is a strict superset of the old firing (source-poor still can't pay now), so nothing that ramped before stops; whole forge.ai suite stays green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Optional guard for the shared ramp check: Kiora's Follower untaps a tapped land when one more mana reaches a spell in hand it otherwise can't afford, and holds when nothing becomes castable. Kept as its own commit so it can be dropped without touching the fix. 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.
The problem
When no priority target (Time Vault-style "won't auto-untap") is present,
UntapAifell through togetMostExpensivePermanentAI— untapping the priciest tapped permanent whether or not untapping it achieved anything. For a card like Kiora's Follower that meant tapping it every turn to untap a land that untaps itself next turn regardless: motion with no value. That crudeness is why ~40 cards with a plain{T}: untapability carryAI:RemoveDeck:All, which removes them from AI decks entirely.The change
For free (
{T}-only) untap abilities, pick a benefit-directed target — untap as late as possible, only for a concrete reason, and untap the exact card that reason points to:canPayManaCost), so it respects the actual cost, colours included, not just mana value.Costed / triggered /
Always-logic untaps are a deliberate investment and keep their existing behaviour byte-for-byte.Behaviour, free untappers (before → after)
Cards taken off
AI:RemoveDeck:AllThe improved logic makes six clean
{T}-untap cards playable, so this drops the flag on all of them: Aphetto Alchemist, Kiora's Follower, Unbender Tine, Vizier of Tumbling Sands, Krosan Restorer, Tidewater Minion. Each was verified to untap only for genuine ramp/blocker benefit and hold otherwise.Kept flagged: combo/return-cost pieces, and Ith, High Arcanist, whose untap is a combat damage-prevention trick rather than the ramp/blocker pattern.
Validation
Full desktop suite green (286). A characterization pass across free and costed untappers confirms every free-untap waste case now holds, every genuine benefit still fires, and costed untappers are unchanged.
Follow-up (not in this PR)
Two things are deliberately deferred to a follow-up:
{X}{T},{1}{T}, and other non-free untappers still flagged). This change only affects free{T}-only untaps, so their flags are left in place until the follow-up gives them their own benefit logic — untapping is worth real mana under a different, tighter bar than a free tap.🤖 Implemented with the assistance of Claude Code (Opus).