Fix Fill Capacity exceeding DC slot limit due to double-counted failed racks
CI / build-and-push (push) Successful in 38s

computeRacksFailed was incremented on production failure and never decremented
when repaired racks came back online, while repair cohorts also tracked the
same racks. This caused usedSlots to inflate past the DC capacity over time.

Fix: derive computeRacksFailed from repair cohorts each tick instead of
maintaining it as a running counter. Include repair cohorts in pipeline slot
accounting so all racks are counted exactly once. Also fixes power limit in
fillDCToCapacity to only count online racks (pipeline racks don't draw power).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 00:00:46 -04:00
parent 9c49a10b31
commit b7d23c8872
4 changed files with 28 additions and 22 deletions
@@ -71,11 +71,15 @@ function resetRackFailures() {
...cluster, ...cluster,
campuses: cluster.campuses.map((campus) => ({ campuses: cluster.campuses.map((campus) => ({
...campus, ...campus,
dataCenters: campus.dataCenters.map((dc) => ({ dataCenters: campus.dataCenters.map((dc) => {
const repairCount = dc.deploymentCohorts.filter(c => c.stage === 'repair').reduce((sum, c) => sum + c.count, 0);
return {
...dc, ...dc,
computeRacksOnline: dc.computeRacksOnline + dc.computeRacksFailed, computeRacksOnline: dc.computeRacksOnline + repairCount,
computeRacksFailed: 0, computeRacksFailed: 0,
})), deploymentCohorts: dc.deploymentCohorts.filter(c => c.stage !== 'repair'),
};
}),
})), })),
})); }));
return { infrastructure: { ...s.infrastructure, clusters: newClusters } }; return { infrastructure: { ...s.infrastructure, clusters: newClusters } };
+5 -5
View File
@@ -113,7 +113,7 @@ function DeploymentProgressBar({ dc }: { dc: DataCenter }) {
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const maxCompute = maxComputeRacks(tierConfig.rackSlots); const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const pipelineRacks = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0); const pipelineRacks = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0);
const totalTarget = dc.computeRacksOnline + dc.computeRacksFailed + pipelineRacks; const totalTarget = dc.computeRacksOnline + pipelineRacks;
const pct = totalTarget > 0 ? (dc.computeRacksOnline / totalTarget) * 100 : 0; const pct = totalTarget > 0 ? (dc.computeRacksOnline / totalTarget) * 100 : 0;
if (totalTarget === 0 && dc.status === 'operational') return null; if (totalTarget === 0 && dc.status === 'operational') return null;
@@ -642,8 +642,8 @@ function DataCenterDetailView({ clusterId, campusId, datacenterId }: {
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const maxCompute = maxComputeRacks(tierConfig.rackSlots); const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0);
+ dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0); const existingCompute = dc.computeRacksOnline + pipelineCount;
const availableSlots = maxCompute - existingCompute; const availableSlots = maxCompute - existingCompute;
const sku = dc.rackSkuId ? RACK_SKU_CONFIGS[dc.rackSkuId] : null; const sku = dc.rackSkuId ? RACK_SKU_CONFIGS[dc.rackSkuId] : null;
const netSlots = networkSlotsRequired(existingCompute); const netSlots = networkSlotsRequired(existingCompute);
@@ -814,7 +814,7 @@ function DataCenterDetailView({ clusterId, campusId, datacenterId }: {
) : ( ) : (
<> <>
<p className="text-sm text-surface-400"> <p className="text-sm text-surface-400">
Retrofit swaps all {dc.computeRacksOnline + dc.computeRacksFailed} <span className="text-surface-200">{sku!.name}</span> racks to a new SKU. Retrofit swaps all {dc.computeRacksOnline + pipelineCount} <span className="text-surface-200">{sku!.name}</span> racks to a new SKU.
The DC goes offline during retrofit. The DC goes offline during retrofit.
</p> </p>
<div className="space-y-2"> <div className="space-y-2">
@@ -906,7 +906,7 @@ function DataCenterDetailView({ clusterId, campusId, datacenterId }: {
{confirmRetrofit && ( {confirmRetrofit && (
<ConfirmModal <ConfirmModal
title="Confirm Retrofit" title="Confirm Retrofit"
message={`This will decommission all ${dc.computeRacksOnline + dc.computeRacksFailed} ${sku?.name} racks and install ${RACK_SKU_CONFIGS[confirmRetrofit].name}. The DC will go offline during this process.`} message={`This will decommission all ${dc.computeRacksOnline + pipelineCount} ${sku?.name} racks and install ${RACK_SKU_CONFIGS[confirmRetrofit].name}. The DC will go offline during this process.`}
confirmLabel="Start Retrofit" confirmLabel="Start Retrofit"
onConfirm={() => { retrofitDC(datacenterId, confirmRetrofit); setConfirmRetrofit(null); }} onConfirm={() => { retrofitDC(datacenterId, confirmRetrofit); setConfirmRetrofit(null); }}
onCancel={() => setConfirmRetrofit(null)} onCancel={() => setConfirmRetrofit(null)}
+7 -6
View File
@@ -377,8 +377,8 @@ export const useGameStore = create<Store>()(
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const maxCompute = maxComputeRacks(tierConfig.rackSlots); const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0);
+ dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); const existingCompute = dc.computeRacksOnline + pipelineCount;
const available = maxCompute - existingCompute; const available = maxCompute - existingCompute;
const actualQty = Math.min(quantity, available); const actualQty = Math.min(quantity, available);
if (actualQty <= 0) return s; if (actualQty <= 0) return s;
@@ -424,14 +424,14 @@ export const useGameStore = create<Store>()(
const dc = found.dc; const dc = found.dc;
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const maxCompute = maxComputeRacks(tierConfig.rackSlots); const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0);
+ dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); const existingCompute = dc.computeRacksOnline + pipelineCount;
const available = maxCompute - existingCompute; const available = maxCompute - existingCompute;
if (available <= 0) return; if (available <= 0) return;
const sku = RACK_SKU_CONFIGS[skuId]; const sku = RACK_SKU_CONFIGS[skuId];
const affordableQty = Math.floor(s.economy.money / sku.baseCost); const affordableQty = Math.floor(s.economy.money / sku.baseCost);
const powerLimit = Math.floor((tierConfig.powerBudgetKW - existingCompute * sku.powerDrawKW) / sku.powerDrawKW); const powerLimit = Math.floor((tierConfig.powerBudgetKW - dc.computeRacksOnline * sku.powerDrawKW) / sku.powerDrawKW);
const qty = Math.min(available, affordableQty, powerLimit); const qty = Math.min(available, affordableQty, powerLimit);
if (qty <= 0) return; if (qty <= 0) return;
@@ -498,7 +498,8 @@ export const useGameStore = create<Store>()(
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) return s; if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) return s;
if (sku.requiredResearch && !s.research.completedResearch.includes(sku.requiredResearch)) return s; if (sku.requiredResearch && !s.research.completedResearch.includes(sku.requiredResearch)) return s;
const totalRacksToRetrofit = dc.computeRacksOnline + dc.computeRacksFailed; const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0);
const totalRacksToRetrofit = dc.computeRacksOnline + pipelineCount;
if (totalRacksToRetrofit <= 0) return s; if (totalRacksToRetrofit <= 0) return s;
const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId]; const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId];
@@ -195,7 +195,6 @@ export function processInfrastructure(state: GameState): InfraTickResult {
} }
let computeRacksOnline = dc.computeRacksOnline; let computeRacksOnline = dc.computeRacksOnline;
let computeRacksFailed = dc.computeRacksFailed;
let dcRepairCosts = 0; let dcRepairCosts = 0;
// Process retrofit // Process retrofit
@@ -349,7 +348,6 @@ export function processInfrastructure(state: GameState): InfraTickResult {
const prodFailures = binomialSample(computeRacksOnline, effectiveRate); const prodFailures = binomialSample(computeRacksOnline, effectiveRate);
if (prodFailures > 0) { if (prodFailures > 0) {
computeRacksOnline -= prodFailures; computeRacksOnline -= prodFailures;
computeRacksFailed += prodFailures;
const repairCost = sku.baseCost * sku.repairCostFraction * prodFailures; const repairCost = sku.baseCost * sku.repairCostFraction * prodFailures;
dcRepairCosts += repairCost; dcRepairCosts += repairCost;
@@ -400,12 +398,15 @@ export function processInfrastructure(state: GameState): InfraTickResult {
// Compute aggregates for this DC // Compute aggregates for this DC
const location = LOCATION_CONFIGS[cluster.locationId]; const location = LOCATION_CONFIGS[cluster.locationId];
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const totalRacksInDc = computeRacksOnline + computeRacksFailed;
const netSlots = networkSlotsRequired(computeRacksOnline);
const pipelineRacks = updatedCohorts const pipelineRacks = updatedCohorts
.filter(c => c.stage !== 'decommission' && c.stage !== 'repair') .filter(c => c.stage !== 'decommission')
.reduce((sum, c) => sum + c.count, 0); .reduce((sum, c) => sum + c.count, 0);
const usedSlots = totalRacksInDc + netSlots + pipelineRacks; const computeRacksFailed = updatedCohorts
.filter(c => c.stage === 'repair')
.reduce((sum, c) => sum + c.count, 0);
const totalRacksInDc = computeRacksOnline + pipelineRacks;
const netSlots = networkSlotsRequired(computeRacksOnline + pipelineRacks);
const usedSlots = computeRacksOnline + pipelineRacks + netSlots;
let usedPowerKW = 0; let usedPowerKW = 0;
let dcFlops = 0; let dcFlops = 0;