fix: AI only plays lands — sync card enrichment, mana pool check, field alignment

- Add _enrichCardFromCache() in precon-loader.js for synchronous card
  enrichment at draw time (typeLine, cmc, oracle roles available before
  async Scryfall batch completes)
- Call _enrichCardFromCache in drawCard() so AI has complete card data
  on its first turn
- Improve _canAfford in ai-bridge.js: check actual mana pool before
  falling back to untapped land count; free spells (cmc 0) always castable
- Fix totalCards field name mismatch in ui.js and engine.js (lab.py
  returns totalCards, not card_count/total_cards)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rgriffithandClaude Opus 4.6 committed 2026-04-05 01:43:29 -04:00
1 parent 9eaa41a4ae
commit 44123f7622
4 files changed
+40 -3

No files matched your search

+13 -1
View File
@@ -895,10 +895,22 @@ class MacroActionMapper {
static _canAfford(playerIdx, card, gameState) {
if (!gameState) return false;
const cmc = MacroActionMapper._cmc(card);
if (cmc === 0) return true; // Free spells always castable
// Prefer actual mana pool if populated
const player = gameState.players[playerIdx];
const pool = player?.manaPool;
if (pool) {
const total = Object.values(pool).reduce((s, v) => s + (v || 0), 0);
if (total > 0) return total >= cmc;
}
// Fallback: count untapped lands
const available = typeof aiCountUntappedLands === 'function'
? aiCountUntappedLands(playerIdx)
: 0;
return available >= MacroActionMapper._cmc(card);
return available >= cmc;
}
static _creatureScore(card) {
+2 -1
View File
@@ -8241,6 +8241,7 @@ function drawCard(playerId) {
}
const card = player.zones.library.shift(); // Take from top (index 0)
if (typeof _enrichCardFromCache === 'function') _enrichCardFromCache(card);
player.zones.hand.push(card);
addLogEntry('<strong>' + player.name + '</strong> drew a card (' + player.zones.hand.length + ' in hand)');
updatePlayerZones();
@@ -12604,7 +12605,7 @@ function _loadUserDecksForSetup() {
var html = '';
for (var di = 0; di < decks.length; di++) {
var d = decks[di];
var cardCount = d.card_count || d.total_cards || 0;
var cardCount = d.totalCards || d.card_count || d.total_cards || 0;
var cmdr = d.commander || '';
html += '<button class="setup-custom-deck-btn" id="setup-custom-deck-btn-' + i + '-' + di + '"' +
' onclick="selectSetupUserDeck(' + i + ',' + d.deck_id + ',\'' + (d.name || 'Untitled').replace(/'/g, "\\'") + '\')">' +
+24
View File
@@ -281,6 +281,30 @@ function _applyScryfallDataToAllCards() {
if (typeof updateZoneCounts === 'function') updateZoneCounts();
}
/**
* Synchronously enrich a single card from the Scryfall cache.
* Called at draw time so cards have typeLine/cmc/oracle data
* before the AI's first turn, even if async enrichment hasn't completed.
*/
function _enrichCardFromCache(card) {
if (!card || !card.name) return;
const data = _scryfallCache[card.name];
if (!data) return;
card.imageUrl = data.imageUrl || card.imageUrl || '';
card.typeLine = data.typeLine || card.typeLine || '';
card.oracleText = data.oracleText || card.oracleText || '';
card.manaCost = data.manaCost || card.manaCost || '';
card.cmc = data.cmc || card.cmc || 0;
if (data.power) card.power = data.power;
if (data.toughness) card.toughness = data.toughness;
// Tag oracle roles
const ot = (card.oracleText || '').toLowerCase();
if (!card.isRamp) card.isRamp = /search your library for a.*land|add \{|adds? one mana/i.test(ot);
if (!card.isRemoval) card.isRemoval = /destroy target|exile target|counter target spell/i.test(ot);
if (!card.isBoardWipe) card.isBoardWipe = /destroy all|exile all|all creatures get -/i.test(ot);
}
/**
* Enrich a single card object on-demand (useful for cards drawn or played after initial load).
* Returns a Promise that resolves when the card's imageUrl is set.
+1 -1
View File
@@ -1434,7 +1434,7 @@ function _loadMyDecksTab(playerIdx) {
decks.forEach(function(d) {
var name = d.name || 'Untitled';
var cmdr = d.commander || d.commander_name || '';
var count = d.card_count || d.total_cards || 0;
var count = d.totalCards || d.card_count || d.total_cards || 0;
html += '<button class="deck-precon-item" onclick="selectUserDeck(' + playerIdx + ',' + d.deck_id + ',\'' + name.replace(/'/g, "\\'") + '\')">' +
'<span class="deck-precon-name">' + name.replace(/</g, '&lt;') + '</span>' +
'<span class="deck-precon-cmdr">' + (cmdr ? cmdr.replace(/</g, '&lt;') + ' \u2014 ' : '') + count + ' cards</span>' +