From 03ef94c22ceccf2a0fbab350e584ca5be0d15978 Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 24 Apr 2026 20:05:11 -0400 Subject: [PATCH] Fix rack order race condition allowing slots/power to exceed DC limits Rapid clicks on order rack bypassed capacity checks because usedSlots and usedPowerKW were only updated during the tick. Now both the store action and UI compute slot/power usage live from racks + pipeline, so each order immediately reflects in the next check. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/pages/InfrastructurePage.tsx | 12 ++++++++---- apps/web/src/store/index.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/web/src/pages/InfrastructurePage.tsx b/apps/web/src/pages/InfrastructurePage.tsx index 459adff..0991ed5 100644 --- a/apps/web/src/pages/InfrastructurePage.tsx +++ b/apps/web/src/pages/InfrastructurePage.tsx @@ -103,6 +103,7 @@ function CapacityBar({ label, used, max, unit, icon: Icon }: { function DataCenterCard({ dcId }: { dcId: string }) { const dc = useGameStore((s) => s.infrastructure.dataCenters.find(d => d.id === dcId))!; + const pipelineForDc = useGameStore((s) => s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dcId)); const money = useGameStore((s) => s.economy.money); const era = useGameStore((s) => s.meta.currentEra); const completedResearch = useGameStore((s) => s.research.completedResearch); @@ -112,6 +113,9 @@ function DataCenterCard({ dcId }: { dcId: string }) { const tierConfig = DC_TIER_CONFIGS[dc.tier]; const currentEraIdx = ERA_ORDER.indexOf(era); + const liveUsedSlots = dc.racks.length + pipelineForDc.length; + const liveUsedPower = dc.racks.reduce((s, r) => s + RACK_SKU_CONFIGS[r.skuId].powerDrawKW, 0) + + pipelineForDc.reduce((s, o) => s + RACK_SKU_CONFIGS[o.skuId].powerDrawKW, 0); const availableSkus = Object.values(RACK_SKU_CONFIGS).filter(sku => { if (ERA_ORDER.indexOf(sku.era) > currentEraIdx) return false; @@ -163,8 +167,8 @@ function DataCenterCard({ dcId }: { dcId: string }) {
- - + +
{expanded && ( @@ -195,8 +199,8 @@ function DataCenterCard({ dcId }: { dcId: string }) {
{availableSkus.map(sku => { const canAfford = money >= sku.baseCost; - const hasSlot = dc.usedSlots < tierConfig.rackSlots; - const hasPower = dc.usedPowerKW + sku.powerDrawKW <= tierConfig.powerBudgetKW; + const hasSlot = liveUsedSlots < tierConfig.rackSlots; + const hasPower = liveUsedPower + sku.powerDrawKW <= tierConfig.powerBudgetKW; const disabled = !canAfford || !hasSlot || !hasPower; return ( diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts index 08a6618..8fe33e1 100644 --- a/apps/web/src/store/index.ts +++ b/apps/web/src/store/index.ts @@ -196,8 +196,14 @@ export const useGameStore = create()( if (!dc || dc.status !== 'operational') return s; const tierConfig = DC_TIER_CONFIGS[dc.tier]; - if (dc.usedSlots >= tierConfig.rackSlots) return s; - if (dc.usedPowerKW + sku.powerDrawKW > tierConfig.powerBudgetKW) return s; + const pipelineForDc = s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dataCenterId).length; + const actualUsedSlots = dc.racks.length + pipelineForDc; + const pipelinePowerForDc = s.infrastructure.rackPipeline + .filter(o => o.dataCenterId === dataCenterId) + .reduce((sum, o) => sum + RACK_SKU_CONFIGS[o.skuId].powerDrawKW, 0); + const actualUsedPower = dc.racks.reduce((sum, r) => sum + RACK_SKU_CONFIGS[r.skuId].powerDrawKW, 0) + pipelinePowerForDc; + if (actualUsedSlots >= tierConfig.rackSlots) return s; + if (actualUsedPower + sku.powerDrawKW > tierConfig.powerBudgetKW) return s; const order = { id: uuid(),