Skip to content

AI: fetch the land colour it is actually short of - #11504

Open
liamiak wants to merge 2 commits into
Card-Forge:masterfrom
liamiak:ai-land-color-need
Open

AI: fetch the land colour it is actually short of#11504
liamiak wants to merge 2 commits into
Card-Forge:masterfrom
liamiak:ai-land-color-need

Conversation

@liamiak

@liamiak liamiak commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Land searches picked by list order. basicManaFixing chose the basic type the player had fewest
of, then took list.get(0) from whatever survived that filter; getBestLandAI ended in
Aggregates.random. Neither asked which colours were actually blocking anything.

Every fetchland comes through here — areAllBasics("Plains,Island") is true — and a "Plains" search
matches every dual carrying the Plains type. Measured over 12 seeded AI-vs-AI games (deck 260613,
three seeds), basicManaFixing fires 46 times, 44 with a real choice, once over 31 candidates.
One observed decision, minType=Island, offered:

Tundra(WU)  Underground Sea(UB)  Volcanic Island(UR)  Tropical Island(UG)
Raffine's Tower(WUB)  Ketria Triome(URG)  Breeding Pool(UG)  ...

All carry Island; all differ beside it. It took Tundra because Tundra was first.

The metric. getColorFixingNeed counts how many cards go from unpayable to payable if that
player had this land, across their hand and the activatable abilities on their permanents. It reuses
ComputerUtilCost.getAvailableManaColors, which already takes an "if I also had this land"
argument, and canBePaidWithAvailable.

Ordering turned out to matter more than the metric. Colour need is asked before the basic-type
count, because that count cannot tell a colour that is missing from one that is merely uncommon:
with three Islands and a hand wanting black, it concluded it needed Plains — having none — and
fetched a Plains-Island. Asking first also means the whole candidate list is still in front of it
rather than the remains of a filter. That is the case the fetchland test pins.

Where a measure cannot separate the candidates it returns null and the caller keeps what it was
already doing, so nothing decides while blind: evaluateLand is never asked to rank a utility land
against a basic (Maze of Ith scores 53, below a Forest at 106), and the existing fallbacks stand.

Result: of the 44 real decisions, colour need has signal in 33 and changes the pick in 25.

This also fixes what the comment above the old call site suspected — basicManaFixing read the
decider's board while searching someone else's library. It now works from the owner's side and
inverts every layer when an opponent is the one choosing. To be straight about it: that inversion is
covered by a unit test but never ran in the measured games, since deck 260613 has no Chooser$
cards, so it is the least exercised part of this.

getBestAI's all-lands branch has no player in scope, so it passes null, skips the colour layer and
falls to land value — better than random, but not colour-aware. That is the Vesuva path.

Full desktop suite: 357 tests, 0 failures. The regression tests are a separate commit and can be
dropped on their own.

Written with Claude Opus 5 (also recorded in the commit co-authors).

liamiak1 and others added 2 commits August 3, 2026 19:43
Land searches picked by list order. basicManaFixing chose the basic type the player
had fewest of, then took list.get(0) from whatever survived that filter, and
getBestLandAI ended in Aggregates.random. Neither asked which colours were actually
blocking anything.

Every fetchland comes through here - areAllBasics("Plains,Island") is true - and a
"Plains" search matches every dual carrying the Plains type. Measured over 12 seeded
AI-vs-AI games (deck 260613, three seeds), basicManaFixing fires 46 times, 44 of them
with a real choice, once over 31 candidates. One observed decision offered Tundra,
Underground Sea, Volcanic Island, Tropical Island, Raffine's Tower and Ketria Triome
among others - all carrying Island, all different beside it - and it took Tundra
because Tundra was first.

getColorFixingNeed counts how many cards go from unpayable to payable if that player
had this land, across their hand and the activatable abilities on their permanents.
It reuses ComputerUtilCost.getAvailableManaColors, which already takes an "if I also
had this land" argument, and canBePaidWithAvailable.

Ordering turned out to matter more than the metric. Colour need is asked before the
basic-type count, because that count cannot tell a colour that is missing from one
that is merely uncommon: with three Islands and a hand wanting black it concluded it
needed Plains, having none, and fetched a Plains-Island. Asking first also means the
whole candidate list is still in front of it rather than the remains of a filter.

Where a measure cannot separate the candidates it returns null and the caller keeps
what it was already doing, so nothing decides while blind - evaluateLand is never
asked to rank a utility land against a basic, and the existing fallbacks stand.

Of the 44 real decisions, colour need has signal in 33 and changes the pick in 25.

Also fixes what the comment above the old call site suspected: basicManaFixing read
the decider's board while searching someone else's library. It now works from the
owner's side, and inverts every layer when an opponent is the one choosing. That
inversion is covered by a unit test but never ran in the measured games - deck 260613
has no Chooser$ cards - so it is the least exercised part of this.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Covers the decision directly and through a real Flooded Strand activation, which is
how it is reached in a game: two duals both carrying the searched-for type, only one
of which unblocks the hand. Also pins the two ways it declines to act - an opponent
choosing gives the least useful land, and identical candidates leave the caller's own
ordering alone.

The fetchland case fails without the fix.

Drop this commit if you would rather not carry the tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants