From b7d23c88726f8a51759bd8f4d7771f15ed3c7b8c Mon Sep 17 00:00:00 2001 From: josh Date: Sat, 25 Apr 2026 00:00:46 -0400 Subject: [PATCH] Fix Fill Capacity exceeding DC slot limit due to double-counted failed racks 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 --- apps/web/src/components/dev/EventTriggersTab.tsx | 14 +++++++++----- apps/web/src/pages/InfrastructurePage.tsx | 10 +++++----- apps/web/src/store/index.ts | 13 +++++++------ .../src/systems/infrastructureSystem.ts | 13 +++++++------ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/apps/web/src/components/dev/EventTriggersTab.tsx b/apps/web/src/components/dev/EventTriggersTab.tsx index 60865f5..672b4e3 100644 --- a/apps/web/src/components/dev/EventTriggersTab.tsx +++ b/apps/web/src/components/dev/EventTriggersTab.tsx @@ -71,11 +71,15 @@ function resetRackFailures() { ...cluster, campuses: cluster.campuses.map((campus) => ({ ...campus, - dataCenters: campus.dataCenters.map((dc) => ({ - ...dc, - computeRacksOnline: dc.computeRacksOnline + dc.computeRacksFailed, - computeRacksFailed: 0, - })), + dataCenters: campus.dataCenters.map((dc) => { + const repairCount = dc.deploymentCohorts.filter(c => c.stage === 'repair').reduce((sum, c) => sum + c.count, 0); + return { + ...dc, + computeRacksOnline: dc.computeRacksOnline + repairCount, + computeRacksFailed: 0, + deploymentCohorts: dc.deploymentCohorts.filter(c => c.stage !== 'repair'), + }; + }), })), })); return { infrastructure: { ...s.infrastructure, clusters: newClusters } }; diff --git a/apps/web/src/pages/InfrastructurePage.tsx b/apps/web/src/pages/InfrastructurePage.tsx index 13b7d33..91c7655 100644 --- a/apps/web/src/pages/InfrastructurePage.tsx +++ b/apps/web/src/pages/InfrastructurePage.tsx @@ -113,7 +113,7 @@ function DeploymentProgressBar({ dc }: { dc: DataCenter }) { const tierConfig = DC_TIER_CONFIGS[dc.tier]; const maxCompute = maxComputeRacks(tierConfig.rackSlots); 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; 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 maxCompute = maxComputeRacks(tierConfig.rackSlots); - const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed - + dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0); + const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((s, c) => s + c.count, 0); + const existingCompute = dc.computeRacksOnline + pipelineCount; const availableSlots = maxCompute - existingCompute; const sku = dc.rackSkuId ? RACK_SKU_CONFIGS[dc.rackSkuId] : null; const netSlots = networkSlotsRequired(existingCompute); @@ -814,7 +814,7 @@ function DataCenterDetailView({ clusterId, campusId, datacenterId }: { ) : ( <>

- Retrofit swaps all {dc.computeRacksOnline + dc.computeRacksFailed} {sku!.name} racks to a new SKU. + Retrofit swaps all {dc.computeRacksOnline + pipelineCount} {sku!.name} racks to a new SKU. The DC goes offline during retrofit.

@@ -906,7 +906,7 @@ function DataCenterDetailView({ clusterId, campusId, datacenterId }: { {confirmRetrofit && ( { retrofitDC(datacenterId, confirmRetrofit); setConfirmRetrofit(null); }} onCancel={() => setConfirmRetrofit(null)} diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts index 4c5a1ed..9581825 100644 --- a/apps/web/src/store/index.ts +++ b/apps/web/src/store/index.ts @@ -377,8 +377,8 @@ export const useGameStore = create()( const tierConfig = DC_TIER_CONFIGS[dc.tier]; const maxCompute = maxComputeRacks(tierConfig.rackSlots); - const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed - + dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); + const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); + const existingCompute = dc.computeRacksOnline + pipelineCount; const available = maxCompute - existingCompute; const actualQty = Math.min(quantity, available); if (actualQty <= 0) return s; @@ -424,14 +424,14 @@ export const useGameStore = create()( const dc = found.dc; const tierConfig = DC_TIER_CONFIGS[dc.tier]; const maxCompute = maxComputeRacks(tierConfig.rackSlots); - const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed - + dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); + const pipelineCount = dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0); + const existingCompute = dc.computeRacksOnline + pipelineCount; const available = maxCompute - existingCompute; if (available <= 0) return; const sku = RACK_SKU_CONFIGS[skuId]; 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); if (qty <= 0) return; @@ -498,7 +498,8 @@ export const useGameStore = create()( if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) 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; const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId]; diff --git a/packages/game-engine/src/systems/infrastructureSystem.ts b/packages/game-engine/src/systems/infrastructureSystem.ts index c74740d..f554ce3 100644 --- a/packages/game-engine/src/systems/infrastructureSystem.ts +++ b/packages/game-engine/src/systems/infrastructureSystem.ts @@ -195,7 +195,6 @@ export function processInfrastructure(state: GameState): InfraTickResult { } let computeRacksOnline = dc.computeRacksOnline; - let computeRacksFailed = dc.computeRacksFailed; let dcRepairCosts = 0; // Process retrofit @@ -349,7 +348,6 @@ export function processInfrastructure(state: GameState): InfraTickResult { const prodFailures = binomialSample(computeRacksOnline, effectiveRate); if (prodFailures > 0) { computeRacksOnline -= prodFailures; - computeRacksFailed += prodFailures; const repairCost = sku.baseCost * sku.repairCostFraction * prodFailures; dcRepairCosts += repairCost; @@ -400,12 +398,15 @@ export function processInfrastructure(state: GameState): InfraTickResult { // Compute aggregates for this DC const location = LOCATION_CONFIGS[cluster.locationId]; const tierConfig = DC_TIER_CONFIGS[dc.tier]; - const totalRacksInDc = computeRacksOnline + computeRacksFailed; - const netSlots = networkSlotsRequired(computeRacksOnline); const pipelineRacks = updatedCohorts - .filter(c => c.stage !== 'decommission' && c.stage !== 'repair') + .filter(c => c.stage !== 'decommission') .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 dcFlops = 0;