Fix rack order race condition allowing slots/power to exceed DC limits
CI / build-and-push (push) Successful in 47s
CI / build-and-push (push) Successful in 47s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,7 @@ function CapacityBar({ label, used, max, unit, icon: Icon }: {
|
|||||||
|
|
||||||
function DataCenterCard({ dcId }: { dcId: string }) {
|
function DataCenterCard({ dcId }: { dcId: string }) {
|
||||||
const dc = useGameStore((s) => s.infrastructure.dataCenters.find(d => d.id === dcId))!;
|
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 money = useGameStore((s) => s.economy.money);
|
||||||
const era = useGameStore((s) => s.meta.currentEra);
|
const era = useGameStore((s) => s.meta.currentEra);
|
||||||
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
const completedResearch = useGameStore((s) => s.research.completedResearch);
|
||||||
@@ -112,6 +113,9 @@ function DataCenterCard({ dcId }: { dcId: string }) {
|
|||||||
|
|
||||||
const tierConfig = DC_TIER_CONFIGS[dc.tier];
|
const tierConfig = DC_TIER_CONFIGS[dc.tier];
|
||||||
const currentEraIdx = ERA_ORDER.indexOf(era);
|
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 => {
|
const availableSkus = Object.values(RACK_SKU_CONFIGS).filter(sku => {
|
||||||
if (ERA_ORDER.indexOf(sku.era) > currentEraIdx) return false;
|
if (ERA_ORDER.indexOf(sku.era) > currentEraIdx) return false;
|
||||||
@@ -163,8 +167,8 @@ function DataCenterCard({ dcId }: { dcId: string }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-4 mb-3">
|
<div className="flex gap-4 mb-3">
|
||||||
<CapacityBar label="Slots" used={dc.usedSlots} max={tierConfig.rackSlots} unit="" icon={HardDrive} />
|
<CapacityBar label="Slots" used={liveUsedSlots} max={tierConfig.rackSlots} unit="" icon={HardDrive} />
|
||||||
<CapacityBar label="Power" used={dc.usedPowerKW} max={tierConfig.powerBudgetKW} unit="kW" icon={Zap} />
|
<CapacityBar label="Power" used={liveUsedPower} max={tierConfig.powerBudgetKW} unit="kW" icon={Zap} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{expanded && (
|
{expanded && (
|
||||||
@@ -195,8 +199,8 @@ function DataCenterCard({ dcId }: { dcId: string }) {
|
|||||||
<div className="flex gap-2 flex-wrap">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{availableSkus.map(sku => {
|
{availableSkus.map(sku => {
|
||||||
const canAfford = money >= sku.baseCost;
|
const canAfford = money >= sku.baseCost;
|
||||||
const hasSlot = dc.usedSlots < tierConfig.rackSlots;
|
const hasSlot = liveUsedSlots < tierConfig.rackSlots;
|
||||||
const hasPower = dc.usedPowerKW + sku.powerDrawKW <= tierConfig.powerBudgetKW;
|
const hasPower = liveUsedPower + sku.powerDrawKW <= tierConfig.powerBudgetKW;
|
||||||
const disabled = !canAfford || !hasSlot || !hasPower;
|
const disabled = !canAfford || !hasSlot || !hasPower;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -196,8 +196,14 @@ export const useGameStore = create<Store>()(
|
|||||||
if (!dc || dc.status !== 'operational') return s;
|
if (!dc || dc.status !== 'operational') return s;
|
||||||
|
|
||||||
const tierConfig = DC_TIER_CONFIGS[dc.tier];
|
const tierConfig = DC_TIER_CONFIGS[dc.tier];
|
||||||
if (dc.usedSlots >= tierConfig.rackSlots) return s;
|
const pipelineForDc = s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dataCenterId).length;
|
||||||
if (dc.usedPowerKW + sku.powerDrawKW > tierConfig.powerBudgetKW) return s;
|
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 = {
|
const order = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
|
|||||||
Reference in New Issue
Block a user