Let the AI use Venser, the Sojourner (and two other mass-unblockable cards) - #11396
Open
liamiak wants to merge 5 commits into
Open
Let the AI use Venser, the Sojourner (and two other mass-unblockable cards)#11396liamiak wants to merge 5 commits into
liamiak wants to merge 5 commits into
Conversation
liamiak
force-pushed
the
let-ai-use-venser
branch
from
August 6, 2026 11:42
36e0699 to
c5c4944
Compare
Contributor
Author
|
Rebased onto master and cut this down. The lethal check now lives in +98/-4, of which 61 is the test. Description updated to match. |
Making everything unblockable is worth nothing unless the attack it enables wins the game, and EffectAi had no way to ask that, so every card with the effect declined on every board - including boards where it was lethal. Add an UnblockableForLethal logic that holds the ability until attacking with everything that can attack would take an opponent to zero with nothing able to block, which is precisely the state the ability creates. Blockers are therefore not counted. Applied to the three cards where using it is an optional choice - Venser, the Sojourner's -1, Jace, Arcane Strategist's -7 and Tezzeret's Gatebreaker. All three went from declining on every board to firing only on lethal ones. The others with the same effect are not decisions: Keeper of Keys and Veiling Oddity are mandatory triggers, Bedlam, Tazeem and Tanglewalker are static. Glaring Spotlight grants hexproof alongside it, so it is also a protection card and a lethal-only gate would be wrong for it. Reverse the Polarity offers it as a Charm mode, which CharmAi picks through a different path and would need its own verification. Venser also loses AI:RemoveDeck:All. His other two abilities already work - ChangeZoneAi's blink logic handles the +2 (skips tokens, prefers ETB triggers, waits for post-combat, and takes back anything an opponent has gained control of), and the -8 is already AILogic$ Always. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both directions on Venser's -1: fires at exactly lethal, holds one damage short. Calls the AI directly rather than playing a game out, so it does not depend on MyRandom. Drop this commit if you would rather not carry the test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move the check next to sumDamageIfUnblocked, where the rest of the combat maths lives, and give it the two guards AiAttackController.doAssault already has: ai.cantWin() and an opponent Fog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The earlier version of this test called EffectAi directly to keep MyRandom out of it. That skipped the part most likely to break: AiController strips every ability of an AI:RemoveDeck card from the candidate list, so Venser's -1 was unreachable in a real game no matter what EffectAi returned, and a direct call could not tell. Play the phase out instead, the way BlightAiTest does, and assert on loyalty: 5 to start, so 4 means the -1 was activated, 7 the +2, and 5 that Venser was left alone. Each case asserts an exact number rather than an absence, so a setup that quietly stopped working cannot pass. Both halves of the fix now fail the test when removed - restoring AI:RemoveDeck:All, or dropping the AILogic tag, each give 5 where 4 is expected. Neither was covered before. MyRandom is not a factor here: the branch sets randomReturn = true, so the decision does not consult it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RestrictBlocking asked the same question this branch just extracted - would
my whole team connecting kill them - but answered it with its own loop:
raw getCurrentPower() summed over creatures that can attack, against the
strongest opponent's life.
That loop has no ai.cantWin() check, no opp.cantLoseForZeroOrLessLife()
check and no Fog check, so War Cadence would pay {X} into a Fog where the
three cards above it now decline. getCurrentPower() also misreads double
strike and prevention, which damageIfUnblocked handles.
Point it at unblockedAttackIsLethal instead. Keeping getStrongestOpponent()
rather than the all-opponents overload leaves its choice of defender alone,
so the only changes are the three guards, the damage figure, and the
possibleBlockers.size() > 1 condition, which had been suppressing the
lethal branch against a lone blocker - the case where taxing that blocker
is most clearly right.
War Cadence is the only card using this logic, and had no test. The Fog
case fails without this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
liamiak
force-pushed
the
let-ai-use-venser
branch
from
August 25, 2026 15:20
c5c4944 to
4b683e7
Compare
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.
Venser, the Sojourner is
AI:RemoveDeck:All. Two of his three abilities already work — the flag was covering for one dead one, and that one is not his alone.What was dead
The -1 ("Creatures can't be blocked this turn") returned
CantPlayAion every board, including ones where it wins on the spot —EffectAi's no-AILogicbranch is a barereturn CantPlayAi, so nothing here narrows existing behaviour.AILogic$ UnblockableForLethalholds it until attacking with everything that can attack would take an opponent to zero with nothing able to block — the state the ability itself creates, so blockers are not counted.CantPlayAiCantPlayAiCantPlayAiSame tag on the other two cards where this is an optional choice, and both argue for it harder than Venser: Jace, Arcane Strategist spends seven loyalty, Tezzeret's Gatebreaker sacrifices itself. The rest are mandatory triggers, static, or wrong for a lethal-only gate — Glaring Spotlight also grants hexproof, and Reverse the Polarity is a Charm mode CharmAi reaches another way.
The check, and its second caller
It lives in
ComputerUtilCombatbesidesumDamageIfUnblocked, with the guardsdoAssault()already has:ai.cantWin()andhasAFogEffect, fog last since it walks several zones.RestrictBlocking(War Cadence) asked the same question with its own loop — rawgetCurrentPower(), nocantWin, nocantLoseForZeroOrLessLife, no Fog — so it pays {X} into a Fog where these three now decline. It calls the helper instead, keepinggetStrongestOpponent()so its defender choice is unchanged, and loses apossibleBlockers.size() > 1condition that had suppressed the lethal branch against a lone blocker.Testing
362 tests, 0 failures. Both tests play the phase out through
AiControllerrather than calling the ability AI directly. Removing either half of the Venser fix fails it; so does reverting the War Cadence change, on the Fog case.🤖 Implemented with the assistance of Claude Code (Opus 5).