Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/engine/systems/ai/AIBuildOrderExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,20 @@ export class AIBuildOrderExecutor {
switch (action.type) {
case 'train':
if (action.targetId) {
// Pre-check resources before attempting to train
if (!this.canAffordUnit(ai, action.targetId)) {
return false;
}
return this.tryTrainUnit(ai, action.targetId);
} else if (action.options) {
// Weighted random selection
const totalWeight = action.options.reduce((sum: number, opt: { id: string; weight: number }) => sum + opt.weight, 0);
// Filter options to only those we can afford, then do weighted random selection
const affordableOptions = action.options.filter(opt => this.canAffordUnit(ai, opt.id));
if (affordableOptions.length === 0) {
return false;
}
const totalWeight = affordableOptions.reduce((sum: number, opt: { id: string; weight: number }) => sum + opt.weight, 0);
let random = this.coordinator.getRandom().next() * totalWeight;
for (const option of action.options) {
for (const option of affordableOptions) {
random -= option.weight;
if (random <= 0) {
return this.tryTrainUnit(ai, option.id);
Expand Down Expand Up @@ -624,16 +632,14 @@ export class AIBuildOrderExecutor {
const shouldLog = currentTick % 100 === 0;

if (ai.minerals < unitDef.mineralCost || ai.vespene < unitDef.vespeneCost) {
// Log resource failures with console.warn to ensure visibility
if (shouldLog) {
console.warn(`[AIBuildOrder] ${ai.playerId}: INSUFFICIENT RESOURCES for ${unitType} (need ${unitDef.mineralCost}M/${unitDef.vespeneCost}G, have ${Math.floor(ai.minerals)}M/${Math.floor(ai.vespene)}G)`);
debugAI.warn(`[AIBuildOrder] ${ai.playerId}: INSUFFICIENT RESOURCES for ${unitType} (need ${unitDef.mineralCost}M/${unitDef.vespeneCost}G, have ${Math.floor(ai.minerals)}M/${Math.floor(ai.vespene)}G)`);
}
return false;
}
if (ai.supply + unitDef.supplyCost > ai.maxSupply) {
// Log supply failures with console.warn to ensure visibility
if (shouldLog) {
console.warn(`[AIBuildOrder] ${ai.playerId}: SUPPLY BLOCKED for ${unitType} (${ai.supply}+${unitDef.supplyCost} > ${ai.maxSupply})`);
debugAI.warn(`[AIBuildOrder] ${ai.playerId}: SUPPLY BLOCKED for ${unitType} (${ai.supply}+${unitDef.supplyCost} > ${ai.maxSupply})`);
}
return false;
}
Expand Down Expand Up @@ -679,6 +685,23 @@ export class AIBuildOrderExecutor {
return false;
}

/**
* Check if the AI can afford to train a unit (resources and supply).
* Used as a pre-check before attempting training to reduce log spam.
*/
private canAffordUnit(ai: AIPlayer, unitType: string): boolean {
const unitDef = UNIT_DEFINITIONS[unitType];
if (!unitDef) return false;

if (ai.minerals < unitDef.mineralCost || ai.vespene < unitDef.vespeneCost) {
return false;
}
if (ai.supply + unitDef.supplyCost > ai.maxSupply) {
return false;
}
return true;
}

/**
* Check if a unit type requires a research module addon.
*/
Expand Down
21 changes: 7 additions & 14 deletions src/engine/systems/ai/AICoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,10 @@ export class AICoordinator extends System {
): void {
// Idempotency check: prevent duplicate registrations that would reset AI state
if (this.aiPlayers.has(playerId)) {
console.log(`[AICoordinator] AI ${playerId} already registered, skipping duplicate registration`);
debugAI.log(`[AICoordinator] AI ${playerId} already registered, skipping duplicate registration`);
return;
}

// Always log AI registration (bypasses debug settings for diagnostics)
console.log(`[AICoordinator] Registering AI: ${playerId}, faction: ${faction}, difficulty: ${difficulty}`);
debugAI.log(`[AICoordinator] Registering AI: ${playerId}, faction: ${faction}, difficulty: ${difficulty}`);

const factionConfig = getFactionAIConfig(faction);
Expand Down Expand Up @@ -650,9 +648,7 @@ export class AICoordinator extends System {
this.random.reseed(currentTick * 31337 + 42);

if (currentTick === 1) {
// Always log on first tick for diagnostics
console.log(`[AICoordinator] Tick 1: Registered AI players: ${Array.from(this.aiPlayers.keys()).join(', ') || '(none)'}`);
debugAI.log(`[AICoordinator] Registered AI players: ${Array.from(this.aiPlayers.keys()).join(', ')}`);
debugAI.log(`[AICoordinator] Tick 1: Registered AI players: ${Array.from(this.aiPlayers.keys()).join(', ') || '(none)'}`);
}

for (const [playerId, ai] of this.aiPlayers) {
Expand All @@ -665,26 +661,23 @@ export class AICoordinator extends System {

const totalBuildings = Array.from(ai.buildingCounts.values()).reduce((a, b) => a + b, 0);
if (totalBuildings === 0) {
// Critical diagnostic: always log when AI has no buildings (this blocks all AI logic)
if (currentTick % 100 === 0) {
console.warn(`[AICoordinator] ${playerId} has NO buildings detected! AI logic SKIPPED. This is likely a bug.`);
debugAI.warn(`[AICoordinator] ${playerId} has no buildings detected! buildingCounts:`, Object.fromEntries(ai.buildingCounts));
debugAI.warn(`[AICoordinator] ${playerId} has NO buildings detected! AI logic SKIPPED. buildingCounts:`, Object.fromEntries(ai.buildingCounts));
}
continue;
}

// Periodic status log (always enabled for diagnostics)
// Periodic status log
if (currentTick % 200 === 0) {
// Use console.log for critical diagnostics to ensure visibility
console.log(`[AICoordinator] ${playerId}: workers=${ai.workerCount}, buildings=${totalBuildings}, minerals=${Math.floor(ai.minerals)}, vespene=${Math.floor(ai.vespene)}, supply=${ai.supply}/${ai.maxSupply}, buildOrderStep=${ai.buildOrderIndex}/${ai.buildOrder.length}, state=${ai.state}`);
debugAI.log(`[AICoordinator] ${playerId}: workers=${ai.workerCount}, buildings=${totalBuildings}, minerals=${Math.floor(ai.minerals)}, vespene=${Math.floor(ai.vespene)}, supply=${ai.supply}/${ai.maxSupply}, buildOrderStep=${ai.buildOrderIndex}/${ai.buildOrder.length}, state=${ai.state}`);
}

this.updateMaxSupply(ai);

// Diagnostic: warn if AI is supply blocked with resources available
// Warn if AI is supply blocked with resources available
if (ai.supply >= ai.maxSupply && ai.minerals >= 50) {
if (currentTick % 100 === 0) {
console.warn(`[AICoordinator] ${playerId} is SUPPLY BLOCKED: supply=${ai.supply}/${ai.maxSupply}, minerals=${Math.floor(ai.minerals)}`);
debugAI.warn(`[AICoordinator] ${playerId} is SUPPLY BLOCKED: supply=${ai.supply}/${ai.maxSupply}, minerals=${Math.floor(ai.minerals)}`);
}
}

Expand Down