import type { GameState, RackSkuId } from '@ai-tycoon/shared'; import { RACK_SKU_CONFIGS } from '@ai-tycoon/shared'; import type { SimulationMetrics } from '../strategies/types'; export interface DeadZone { startTick: number; endTick: number; durationTicks: number; description: string; } function getCheapestSkuCost(state: GameState): number { const era = state.meta.currentEra; const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; const eraIdx = eraOrder.indexOf(era); let cheapest = Infinity; for (const [, sku] of Object.entries(RACK_SKU_CONFIGS)) { if (eraOrder.indexOf(sku.era) <= eraIdx) { cheapest = Math.min(cheapest, sku.baseCost); } } return cheapest === Infinity ? 100_000 : cheapest; } export function detectDeadZones( metrics: SimulationMetrics[], cheapestSkuCost: number, windowSize = 10, revenueTolerance = 0.02, capabilityTolerance = 0.02, ): DeadZone[] { const zones: DeadZone[] = []; let zoneStart: number | null = null; for (let i = windowSize; i < metrics.length; i++) { const current = metrics[i]; const past = metrics[i - windowSize]; const revFlat = past.revenue > 0 ? Math.abs(current.revenue - past.revenue) / past.revenue < revenueTolerance : current.revenue === 0; const capFlat = past.bestModelCapability > 0 ? Math.abs(current.bestModelCapability - past.bestModelCapability) / past.bestModelCapability < capabilityTolerance : current.bestModelCapability === 0; const isStuck = revFlat && capFlat && current.money < cheapestSkuCost * 2; if (isStuck) { if (zoneStart === null) zoneStart = past.tick; } else { if (zoneStart !== null) { const endTick = metrics[i - 1].tick; zones.push({ startTick: zoneStart, endTick, durationTicks: endTick - zoneStart, description: 'revenue flat, no affordable upgrades', }); zoneStart = null; } } } if (zoneStart !== null) { const endTick = metrics[metrics.length - 1].tick; zones.push({ startTick: zoneStart, endTick, durationTicks: endTick - zoneStart, description: 'revenue flat, no affordable upgrades (ongoing)', }); } return zones; } export { getCheapestSkuCost };