Check cheap block requirement before expensive one - #11949
Open
MostCromulent wants to merge 1 commit into
Open
MostCromulent wants to merge 1 commit into
MostCromulent wants to merge 1 commit into
Conversation
mustBlockAnAttacker skips an attacker when blocking it costs something, and again when its lure requirement is already met. The cost check ran first, and getBlockCost walks every card in the static ability source zones and rebuilds each one's static abilities, while the lure check only reads the attacker's own keywords and passes for any attacker without a lure requirement. Both are side-effect free, so running the cheap one first gives the same answer. The loop runs per attacker for every candidate blocker, so a wide board pays the scan for every pair. On a board of sixty distinct creatures a side, one turn with an attack: 80189 ms to 44800 ms, and calls to CardState.getStaticAbilities over two turns fall from 1007936214 to 362382774. A seeded four-player commander table plays the same games two to three percent faster. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Further testing, this time with go-wide token decks — AI-vs-AI games between mirrored Goblin token decks, rather than the sixty-creature board above. Three repetitions per arm, arms alternated and the order reversed between reps:
The percentages track game length as much as the size of the saving: the three ordinary games total under four seconds, against nearly two minutes for commander. Measured with the change applied to current master, which includes #11946. |
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.
CombatUtil.mustBlockAnAttackerbuilds the set of attackers a blocker is obliged to block. Two of its tests were in the wrong order, because they are not peers: one establishes whether an obligation exists at all, the other asks whether an existing obligation is excused.attackerLureSatisfiedanswers the first. If the attacker has no unmet must-be-blocked effect then nothing obliges this blocker, and the attacker is dismissed. It answers from the attacker's own keywords, and it dismisses almost every attacker, because hardly any creature has such an effect.getBlockCostanswers the second. A blocking requirement never forces a player to pay a cost, so where blocking would cost something the requirement is excused. Answering it means walking every card in the static-ability source zones and rebuilding each one's static abilities, and it excuses almost nothing, because block taxes are rare.The cost question only carries meaning once a requirement is known to exist, so asking it first computed an excuse for obligations that were not there — for every attacker, against every candidate blocker. Asking the cheap question first puts the two in the order the rules pose them, and since both are
continueguards over side-effect-free reads, the attackers reaching the later checks are unchanged. The same shape as #11916.The saving grows with the width of the combat. On sixty distinct creatures a side, the turn with the first attack takes 44800 ms instead of 80189 ms, and calls to
CardState.getStaticAbilitiesover two turns fall from 1007936214 to 362382774. A turn with no combat is unchanged. On a seeded four-player game with stock commander precons the difference is inside run-to-run variance, so no claim is made there beyond the game logs being identical.🤖 Generated with Claude Code