Skip to content

Detect attack-punisher enchantments by trigger instead of by name - #11456

Open
omraj21 wants to merge 2 commits into
Card-Forge:masterfrom
omraj21:ai-attack-punisher-detection
Open

Detect attack-punisher enchantments by trigger instead of by name#11456
omraj21 wants to merge 2 commits into
Card-Forge:masterfrom
omraj21:ai-attack-punisher-detection

Conversation

@omraj21

@omraj21 omraj21 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

AiAttackController only recognised Revenge of Ravens, by name, when working out whether attacking would cost the AI life:

// TODO: detect Revenge of Ravens by the trigger instead of by name
revengeOfRavens = !CardLists.filter(player.getCardsIn(ZoneType.Battlefield),
        CardPredicates.nameEquals("Revenge of Ravens")).isEmpty();

Several cards are printed with the same trigger, and the AI walked straight into all of them:

card trigger
Hissing Miasma Whenever a creature attacks you, its controller loses 1 life
Blood Reckoning same, for creatures you don't control
Marchesa's Decree same, alongside the monarch ability

This does what the TODO asks and looks for the trigger: an Attacks trigger on the defending player's battlefield whose effect is LoseLife aimed at TriggeredAttackerController.

With the old name check, the added test shows the AI sending all three attackers into lethal life loss:

holdsBackAgainstHissingMiasma  AI should not attack into lethal life loss expected:<0> but was:<3>
holdsBackAgainstBloodReckoning AI should not attack into lethal life loss expected:<0> but was:<3>

What deliberately does not count

Cards that punish the attacking creature rather than its controller — Circle of Flame dealing 1 damage to the attacker, Raking Canopy, Palazzo Archers — are excluded. The creature pays that cost, not the player, and normal combat evaluation already accounts for it. There's a test asserting the AI still attacks into Circle of Flame, so the check can't quietly widen to cards it shouldn't cover.

Two smaller corrections in the same logic

  • The life total is compared against the actual life lost per attacker rather than assuming 1, so a card draining 2 each is handled correctly.
  • The "not worth attacking" filter uses that same number instead of a hardcoded getNetCombatDamage() <= 1.

Amounts that aren't plain numbers are skipped rather than guessed at, since overestimating here would make the AI refuse to attack for no reason.

Verification

5 tests: no-punisher control, Revenge of Ravens (the case the name check already covered), Hissing Miasma, Blood Reckoning, and the Circle of Flame exclusion. Two of them fail on the old code.

Full mvn -U -B clean test: 12 modules, 308 tests, 0 failures, run three times.


Written with the help of GitHub Copilot CLI; the commit carries a Co-authored-by trailer for it.

@tool4ever tool4ever added the AI General AI tag label Jul 29, 2026
AiAttackController only recognised Revenge of Ravens, by name, when working out
whether attacking would cost the AI life. The code carried a TODO to detect it by
its trigger instead. Several cards are printed with the same trigger and the AI
walked straight into all of them:

  Hissing Miasma     Whenever a creature attacks you, its controller loses 1 life
  Blood Reckoning    same, for creatures you don't control
  Marchesa's Decree  same, alongside the monarch ability

Look for the trigger instead: an Attacks trigger on the defending player's
battlefield whose effect is LoseLife aimed at TriggeredAttackerController. A test
with the old name check shows the AI sending all three attackers into lethal life
loss against Hissing Miasma and Blood Reckoning.

Cards that punish the attacking creature rather than its controller, such as
Circle of Flame dealing damage to the attacker, deliberately do not count. The
creature pays that cost, not the player, and normal combat evaluation already
handles it. There is a test for that too, so the check can't quietly widen.

The life total is now compared against the actual life lost rather than assuming
one per attacker, and the "not worth attacking" filter uses the same number
instead of a hardcoded 1, so a card draining 2 per attacker is treated correctly.
Amounts that aren't plain numbers are skipped rather than guessed, since
overestimating here would make the AI refuse to attack for no reason.

Full mvn -U -B clean test: 12 modules, 0 failures, run three times.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@omraj21
omraj21 force-pushed the ai-attack-punisher-detection branch from bdbdf27 to 051a647 Compare August 2, 2026 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI General AI tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants