AI perf follow-ups: fog check caching, tap/untap scan zones, chump block re-evaluation#11160
Open
Blackvipe99 wants to merge 3 commits into
Open
AI perf follow-ups: fog check caching, tap/untap scan zones, chump block re-evaluation#11160Blackvipe99 wants to merge 3 commits into
Blackvipe99 wants to merge 3 commits into
Conversation
…d redundant lifeInDanger re-evaluation Follow-up to Card-Forge#11157 / Card-Forge#3410. Caches isPreventCombatDamageThisTurn per game timestamp, restricts Tap/Untap replacement scans to zones such effects can be active from, and makes makeChumpBlocks re-run the full combat damage prediction only when a blocker was assigned since the last check. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tool4ever
reviewed
Jul 5, 2026
Review feedback: keep the engine free of caching/AI concerns. The engine-side timestamp cache is reverted; staticDamagePrevention gains an overload taking an optional precomputed answer, and the AI's predictDamageTo supplies it from AiCache (cleared per AI decision). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tool4ever
reviewed
Jul 7, 2026
| // tap/untap events can only be replaced by effects active from open zones; | ||
| // don't scan libraries and hands for them - this is a major hot path, as | ||
| // canTap/canUntap run a cantHappenCheck per mana source per AI cost check | ||
| if ((event == ReplacementType.Tap || event == ReplacementType.Untap) |
Contributor
There was a problem hiding this comment.
let's just guard it by Spell.performanceMode on the off chance some custom card needs it to work:
then you can also make the constraints even tighter since only Ood Sphere should care for it?
Contributor
Author
There was a problem hiding this comment.
Done in 0c9c6da — the skip now only applies when performance mode is on, and the allowed zones are tightened to Battlefield + Command (grepped cardsfolder: every Event$ Tap/Untap RE is ActiveZones$ Battlefield except Ood Sphere's goad effect and the Edge of Malacol / Imprison This Insolent Wretch planechase/archenemy ones, which all sit in Command).
…ield + command Co-Authored-By: Claude Fable 5 <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.
Follow-up to #11157, continuing the AI slowdown work from #3410. Three changes, all targeting full-game card scans and redundant recomputation in the AI's decision hot paths.
1. Cache
isPreventCombatDamageThisTurn()per game timestamp (ReplacementHandler)Even zone-restricted (#11157), the fog check still runs once per attacker per hypothetical combat per candidate spell ability. The set of active fog-type effects only changes together with events that advance the game timestamp (zone changes — fog effect cards entering/leaving the command zone — and ability-granting effects), so the result is now cached and recomputed only when
game.getTimestamp()changes. Fields arevolatile; a benign race recomputes the same value. Reviewer note: this assumes any event that adds/removes a matching replacement effect also advances the game timestamp — true for the Effect-card lifecycle that fog effects use, but please sanity-check against edge cases I may not know about.2. Skip hidden zones when scanning for Tap/Untap replacement effects (
ReplacementHandler.getReplacementList)Profiling AI timeouts (captured eval-thread stacks at the moment of timeout) showed the dominant remaining stall was not combat prediction but
Card.canTap/canUntap→cantHappenCheck→getReplacementList→forEachCardInGame, reached thousands of times per AI decision viaComputerUtilMana.groupSourcesByManaColor(per mana source per cost check) andCreatureEvaluator(per creature evaluation). Sample stack:For
ReplacementType.Tap/Untapevents the scan now skips cards in zones outsideSTATIC_ABILITIES_SOURCE_ZONES— no tap/untap-replacing effect functions from a library or hand, and the existingzonesCheckalready rejected them after visiting (and after payinggetReplacementEffects()'s rebuild cost per card). In multiplayer Commander this cuts each of those scans by the ~350+ cards sitting in libraries.3. Avoid redundant
lifeInDangerre-evaluation inmakeChumpBlocks(AiBlockController)makeChumpBlocksre-ranlifeInDanger(a full combat damage prediction over all attackers) at every recursion level — once per attacker — making block assignment O(attackers² × board). The recursion now only re-evaluates when a blocker was actually assigned since the last check; skipping an attacker leaves the combat unchanged, so the previous result still holds. Decisions are identical by construction.Testing
MATCH_AI_TIMEOUT=3: before these changes the AI hit the timeout during the match; after, zero timeouts across repeated full games.