import type { GameState, InfrastructureState, DataCenter, RackOrder, Rack, PipelineStage } from '@ai-tycoon/shared'; import { LOCATION_CONFIGS, RACK_SKU_CONFIGS, DC_TIER_CONFIGS, BASE_ENERGY_COST_PER_FLOP, BASE_MAINTENANCE_PER_RACK, COOLING_FAILURE_REDUCTION, REDUNDANCY_FAILURE_REDUCTION, RACK_REPAIR_BASE_TICKS, } from '@ai-tycoon/shared'; import type { TickNotification } from '../tick'; export interface InfraTickResult { infrastructure: InfrastructureState; notifications: TickNotification[]; repairCosts: number; } const PIPELINE_ADVANCE_ORDER: PipelineStage[] = [ 'ordered', 'manufacturing', 'receiving', 'installation', 'testing', ]; function nextStage(stage: PipelineStage): PipelineStage | 'production' { const idx = PIPELINE_ADVANCE_ORDER.indexOf(stage); if (idx === -1 || idx === PIPELINE_ADVANCE_ORDER.length - 1) return 'production'; return PIPELINE_ADVANCE_ORDER[idx + 1]; } function stageTotal(stage: PipelineStage, order: RackOrder): number { const sku = RACK_SKU_CONFIGS[order.skuId]; const timings = sku.pipelineTimeTicks; switch (stage) { case 'manufacturing': return timings.manufacturing; case 'receiving': return timings.receiving; case 'installation': return timings.installation; case 'testing': return timings.testing; case 'repair': return RACK_REPAIR_BASE_TICKS; case 'decommission': return timings.installation; default: return 0; } } function stageSpeed(stage: PipelineStage, engEff: number, opsEff: number): number { switch (stage) { case 'manufacturing': return 1 + engEff * 0.1; case 'installation': case 'testing': case 'decommission': return 1 + opsEff * 0.1; case 'repair': return 1 + opsEff * 0.05; default: return 1; } } export function processInfrastructure(state: GameState): InfraTickResult { const notifications: TickNotification[] = []; let repairCosts = 0; const engEff = state.talent.departments.engineering.effectiveness; const opsEff = state.talent.departments.operations.effectiveness; const qaResearchBonus = state.research.completedResearch.includes('quality-assurance') ? 0.25 : 0; // --- Phase 1: Advance DC Construction --- const dataCenters: DataCenter[] = state.infrastructure.dataCenters.map(dc => { if (dc.status !== 'constructing') return { ...dc }; const newProgress = dc.constructionProgress + 1; if (newProgress >= dc.constructionTotal) { notifications.push({ title: 'Data Center Online', message: `${dc.name} is now operational!`, type: 'success', }); return { ...dc, constructionProgress: dc.constructionTotal, status: 'operational' as const }; } return { ...dc, constructionProgress: newProgress }; }); // --- Phase 2: Advance Rack Pipeline --- const rackPipeline: RackOrder[] = []; const newRacks: Rack[] = []; for (const order of state.infrastructure.rackPipeline) { const speed = stageSpeed(order.stage, engEff, opsEff); const newProgress = order.stageProgress + speed; if (newProgress < order.stageTotal) { rackPipeline.push({ ...order, stageProgress: newProgress }); continue; } if (order.stage === 'decommission') { const sku = RACK_SKU_CONFIGS[order.skuId]; notifications.push({ title: 'Rack Decommissioned', message: `${sku.name} rack has been fully decommissioned.`, type: 'info', }); continue; } if (order.stage === 'repair') { const total = stageTotal('testing', order); rackPipeline.push({ ...order, stage: 'testing', stageProgress: 0, stageTotal: total, }); continue; } const next = nextStage(order.stage); if (next === 'production') { const sku = RACK_SKU_CONFIGS[order.skuId]; const dc = dataCenters.find(d => d.id === order.dataCenterId); const cooling = dc?.coolingLevel ?? 0; const effectiveFailRate = sku.testFailureRate * (1 - cooling * COOLING_FAILURE_REDUCTION) * (1 - opsEff * 0.2) * (1 - qaResearchBonus); if (Math.random() < effectiveFailRate) { const repairCost = sku.baseCost * sku.repairCostFraction; repairCosts += repairCost; rackPipeline.push({ ...order, stage: 'repair', stageProgress: 0, stageTotal: RACK_REPAIR_BASE_TICKS, repairCount: order.repairCount + 1, }); notifications.push({ title: 'Rack Failed Testing', message: `${sku.name} rack failed QA (attempt ${order.repairCount + 1}). Repair cost: $${repairCost.toLocaleString()}`, type: 'warning', }); } else { newRacks.push({ id: order.id, skuId: order.skuId, dataCenterId: order.dataCenterId, isHealthy: true, }); notifications.push({ title: 'Rack Online', message: `${sku.name} rack is now in production at ${dc?.name ?? 'data center'}.`, type: 'success', }); } } else { const total = stageTotal(next, order); rackPipeline.push({ ...order, stage: next, stageProgress: 0, stageTotal: total, }); } } // Add newly completed racks to their data centers for (const rack of newRacks) { const dcIdx = dataCenters.findIndex(d => d.id === rack.dataCenterId); if (dcIdx !== -1) { dataCenters[dcIdx] = { ...dataCenters[dcIdx], racks: [...dataCenters[dcIdx].racks, rack], }; } } // --- Phase 3: Production Failures --- for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { const dc = dataCenters[dcIdx]; if (dc.status !== 'operational') continue; const updatedRacks: Rack[] = []; for (const rack of dc.racks) { if (!rack.isHealthy) { updatedRacks.push(rack); continue; } const sku = RACK_SKU_CONFIGS[rack.skuId]; const effectiveRate = sku.productionFailureRate * (1 - dc.coolingLevel * COOLING_FAILURE_REDUCTION) * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION); if (Math.random() < effectiveRate) { updatedRacks.push({ ...rack, isHealthy: false }); const repairCost = sku.baseCost * sku.repairCostFraction; repairCosts += repairCost; rackPipeline.push({ id: rack.id, skuId: rack.skuId, dataCenterId: dc.id, stage: 'repair', stageProgress: 0, stageTotal: RACK_REPAIR_BASE_TICKS, totalCost: repairCost, repairCount: 0, }); notifications.push({ title: 'Rack Failure', message: `${sku.name} rack failed in ${dc.name}. Sent for repair.`, type: 'danger', }); } else { updatedRacks.push(rack); } } // Remove failed racks from the DC (they're now in the repair pipeline) dataCenters[dcIdx] = { ...dc, racks: updatedRacks.filter(r => r.isHealthy), }; } // --- Phase 4: Compute Aggregates --- let totalFlops = 0; let totalUptime = 0; let totalRackCount = 0; let dcWithRacks = 0; for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { const dc = dataCenters[dcIdx]; if (dc.status !== 'operational') continue; const location = LOCATION_CONFIGS[dc.location]; const tierConfig = DC_TIER_CONFIGS[dc.tier]; let dcFlops = 0; let usedPowerKW = 0; const healthyCount = dc.racks.length; const totalInDc = dc.racks.length; for (const rack of dc.racks) { const sku = RACK_SKU_CONFIGS[rack.skuId]; dcFlops += sku.flopsPerRack; usedPowerKW += sku.powerDrawKW; } const pipelineRacksForDc = rackPipeline.filter(o => o.dataCenterId === dc.id && o.stage !== 'decommission').length; const usedSlots = totalInDc + pipelineRacksForDc; const energyCostPerTick = (tierConfig.baseEnergyCostPerTick + usedPowerKW * BASE_ENERGY_COST_PER_FLOP) * location.energyCostMultiplier; const maintenanceCostPerTick = totalInDc * BASE_MAINTENANCE_PER_RACK; const currentUptime = totalInDc > 0 ? healthyCount / totalInDc : 1; totalFlops += dcFlops; totalRackCount += totalInDc; if (totalInDc > 0) { totalUptime += currentUptime; dcWithRacks++; } dataCenters[dcIdx] = { ...dataCenters[dcIdx], usedSlots, usedPowerKW, energyCostPerTick, maintenanceCostPerTick, currentUptime, }; } return { infrastructure: { dataCenters, rackPipeline, totalFlops, totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1, totalRackCount, }, notifications, repairCosts, }; }