Overhaul infrastructure: replace GPU model with rack-centric system
CI / build-and-push (push) Successful in 33s
CI / build-and-push (push) Successful in 33s
Replace flat GPU buying with a realistic data center + rack pipeline: - 4 DC tiers (small/medium/large/mega) with construction time, dual capacity constraints (rack slots + power budget kW), and era/research gating - 10 predefined rack SKUs from consumer GPUs through custom ASICs, each with unique FLOPS, power draw, cost, and pipeline timings - 6-stage procurement pipeline (order → mfg → receive → install → test → production) with Kanban UI, talent-influenced speed bonuses - Test failures (5-25% base rate) reduced by cooling, ops talent, and QA research; auto-repair with cost and re-test cycle - Production failures at low per-tick rate, racks sent to repair pipeline - Cooling and redundancy upgrades per DC (reduce failure rates) - 4 new tech tree nodes (DC Engineering II/III/IV, Quality Assurance) - Save version bump (1→2) with migration that resets old saves - Updated economy system to account for rack repair costs - Redesigned Infrastructure page with pipeline Kanban, capacity bars, rack ordering, and DC upgrade panels Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,72 +1,275 @@
|
||||
import type { GameState, InfrastructureState } from '@ai-tycoon/shared';
|
||||
import type { GameState, InfrastructureState, DataCenter, RackOrder, Rack, PipelineStage } from '@ai-tycoon/shared';
|
||||
import {
|
||||
GPU_CONFIGS,
|
||||
LOCATION_CONFIGS,
|
||||
GPU_PRICE_VOLATILITY,
|
||||
GPU_FAILURE_RATE_BASE,
|
||||
REDUNDANCY_FAILURE_REDUCTION,
|
||||
RACK_SKU_CONFIGS,
|
||||
DC_TIER_CONFIGS,
|
||||
BASE_ENERGY_COST_PER_FLOP,
|
||||
BASE_MAINTENANCE_PER_GPU,
|
||||
BASE_MAINTENANCE_PER_RACK,
|
||||
COOLING_FAILURE_REDUCTION,
|
||||
REDUNDANCY_FAILURE_REDUCTION,
|
||||
RACK_REPAIR_BASE_TICKS,
|
||||
} from '@ai-tycoon/shared';
|
||||
import type { GpuType } from '@ai-tycoon/shared';
|
||||
import type { TickNotification } from '../tick';
|
||||
|
||||
export function processInfrastructure(state: GameState): InfrastructureState {
|
||||
const dataCenters = state.infrastructure.dataCenters.map(dc => {
|
||||
const location = LOCATION_CONFIGS[dc.location];
|
||||
export interface InfraTickResult {
|
||||
infrastructure: InfrastructureState;
|
||||
notifications: TickNotification[];
|
||||
repairCosts: number;
|
||||
}
|
||||
|
||||
const gpus = dc.gpus.map(inv => {
|
||||
const failureRate = GPU_FAILURE_RATE_BASE * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION);
|
||||
let newFailed = inv.failedCount;
|
||||
for (let i = 0; i < inv.healthyCount; i++) {
|
||||
if (Math.random() < failureRate) newFailed++;
|
||||
}
|
||||
const healthyCount = Math.max(0, inv.count - newFailed);
|
||||
return { ...inv, healthyCount, failedCount: newFailed };
|
||||
});
|
||||
const PIPELINE_ADVANCE_ORDER: PipelineStage[] = [
|
||||
'ordered', 'manufacturing', 'receiving', 'installation', 'testing',
|
||||
];
|
||||
|
||||
let totalFlops = 0;
|
||||
let totalPower = 0;
|
||||
let totalGpuCount = 0;
|
||||
for (const inv of gpus) {
|
||||
const config = GPU_CONFIGS[inv.type];
|
||||
totalFlops += inv.healthyCount * config.flopsPerUnit;
|
||||
totalPower += inv.healthyCount * config.basePowerDraw;
|
||||
totalGpuCount += inv.count;
|
||||
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;
|
||||
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': 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 };
|
||||
}
|
||||
|
||||
const energyCostPerTick = totalPower * BASE_ENERGY_COST_PER_FLOP * location.energyCostMultiplier;
|
||||
const maintenanceCostPerTick = totalGpuCount * BASE_MAINTENANCE_PER_GPU;
|
||||
const currentUptime = totalGpuCount > 0
|
||||
? gpus.reduce((s, inv) => s + inv.healthyCount, 0) / totalGpuCount
|
||||
: 1;
|
||||
|
||||
return { ...dc, gpus, energyCostPerTick, maintenanceCostPerTick, currentUptime };
|
||||
return { ...dc, constructionProgress: newProgress };
|
||||
});
|
||||
|
||||
const gpuMarketPrices = { ...state.infrastructure.gpuMarketPrices };
|
||||
for (const gpuType of Object.keys(gpuMarketPrices) as GpuType[]) {
|
||||
const basePrice = GPU_CONFIGS[gpuType].basePrice;
|
||||
const variation = (Math.random() - 0.5) * 2 * GPU_PRICE_VOLATILITY;
|
||||
const currentPrice = gpuMarketPrices[gpuType];
|
||||
const newPrice = currentPrice * (1 + variation);
|
||||
gpuMarketPrices[gpuType] = Math.max(basePrice * 0.7, Math.min(basePrice * 1.5, newPrice));
|
||||
// --- 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 === '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 dcCount = 0;
|
||||
for (const dc of dataCenters) {
|
||||
for (const inv of dc.gpus) {
|
||||
totalFlops += inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit;
|
||||
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;
|
||||
}
|
||||
totalUptime += dc.currentUptime;
|
||||
dcCount++;
|
||||
|
||||
const pipelineRacksForDc = rackPipeline.filter(o => o.dataCenterId === dc.id).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 {
|
||||
dataCenters,
|
||||
gpuMarketPrices,
|
||||
totalFlops,
|
||||
totalUptime: dcCount > 0 ? totalUptime / dcCount : 1,
|
||||
infrastructure: {
|
||||
dataCenters,
|
||||
rackPipeline,
|
||||
totalFlops,
|
||||
totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1,
|
||||
totalRackCount,
|
||||
},
|
||||
notifications,
|
||||
repairCosts,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user