Overhaul infrastructure: replace GPU model with rack-centric system
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:
2026-04-24 19:41:55 -04:00
parent 1af9408c87
commit 0005e580a7
14 changed files with 1051 additions and 295 deletions
+104 -37
View File
@@ -6,8 +6,8 @@ import type {
ResearchState, ModelsState, MarketState,
CompetitorState, TalentState, DataState,
ReputationState, EventState, AchievementState,
DataCenter, GpuType, GpuInventory, TrainingJob,
ActiveResearch, EventConsequence, OwnedDataset,
DataCenter, DCTier, RackSkuId, TrainingJob,
ActiveResearch, EventConsequence, OwnedDataset, LocationId,
} from '@ai-tycoon/shared';
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
import {
@@ -16,7 +16,8 @@ import {
INITIAL_RESEARCH, INITIAL_MODELS, INITIAL_MARKET,
INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA,
INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS,
GPU_CONFIGS,
DC_TIER_CONFIGS, RACK_SKU_CONFIGS,
PIPELINE_ORDER_BASE_TICKS, DC_UPGRADE_COST_FRACTION, DC_UPGRADE_INCREMENT,
FUNDING_ROUNDS,
OPEN_SOURCE_REPUTATION_BOOST,
uuid,
@@ -48,8 +49,9 @@ interface Actions {
setGameSpeed: (speed: GameSpeed) => void;
togglePause: () => void;
setTrainingAllocation: (ratio: number) => void;
buyGpu: (dataCenterId: string, gpuType: GpuType, count: number) => void;
buildDataCenter: (name: string, location: DataCenter['location']) => void;
buildDataCenter: (name: string, location: LocationId, tier: DCTier) => void;
orderRack: (dataCenterId: string, skuId: RackSkuId) => void;
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => void;
startTraining: (job: Omit<TrainingJob, 'progressTicks'>) => void;
deployModel: (modelId: string) => void;
setProductPricing: (productLineId: string, field: string, value: number) => void;
@@ -146,51 +148,37 @@ export const useGameStore = create<Store>()(
compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio },
})),
buyGpu: (dataCenterId, gpuType, count) => set((s) => {
const price = s.infrastructure.gpuMarketPrices[gpuType] * count;
if (s.economy.money < price) return s;
buildDataCenter: (name, location, tier) => set((s) => {
const tierConfig = DC_TIER_CONFIGS[tier];
if (s.economy.money < tierConfig.baseCost) return s;
const dataCenters = s.infrastructure.dataCenters.map(dc => {
if (dc.id !== dataCenterId) return dc;
const existingIdx = dc.gpus.findIndex(g => g.type === gpuType);
let gpus: GpuInventory[];
if (existingIdx >= 0) {
gpus = dc.gpus.map((g, i) =>
i === existingIdx
? { ...g, count: g.count + count, healthyCount: g.healthyCount + count }
: g,
);
} else {
gpus = [...dc.gpus, { type: gpuType, count, healthyCount: count, failedCount: 0 }];
}
return { ...dc, gpus };
});
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(tierConfig.requiredEra)) return s;
if (tierConfig.requiredResearch && !s.research.completedResearch.includes(tierConfig.requiredResearch)) return s;
return {
economy: { ...s.economy, money: s.economy.money - price },
infrastructure: { ...s.infrastructure, dataCenters },
};
}),
buildDataCenter: (name, location) => set((s) => {
const buildCost = 10_000;
if (s.economy.money < buildCost) return s;
const isFirstDC = s.infrastructure.dataCenters.length === 0;
const buildTime = isFirstDC ? tierConfig.firstBuildTimeTicks : tierConfig.buildTimeTicks;
const dc: DataCenter = {
id: uuid(),
name,
location,
gpus: [],
maxCapacity: 100,
coolingLevel: 0.5,
redundancyLevel: 0.3,
tier,
status: 'constructing',
constructionProgress: 0,
constructionTotal: buildTime,
racks: [],
coolingLevel: 0,
redundancyLevel: 0,
currentUptime: 1,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
usedSlots: 0,
usedPowerKW: 0,
};
return {
economy: { ...s.economy, money: s.economy.money - buildCost },
economy: { ...s.economy, money: s.economy.money - tierConfig.baseCost },
infrastructure: {
...s.infrastructure,
dataCenters: [...s.infrastructure.dataCenters, dc],
@@ -198,6 +186,67 @@ export const useGameStore = create<Store>()(
};
}),
orderRack: (dataCenterId, skuId) => set((s) => {
const sku = RACK_SKU_CONFIGS[skuId];
if (s.economy.money < sku.baseCost) return s;
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) return s;
if (sku.requiredResearch && !s.research.completedResearch.includes(sku.requiredResearch)) return s;
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId);
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 order = {
id: uuid(),
skuId,
dataCenterId,
stage: 'ordered' as const,
stageProgress: 0,
stageTotal: PIPELINE_ORDER_BASE_TICKS,
totalCost: sku.baseCost,
repairCount: 0,
};
return {
economy: { ...s.economy, money: s.economy.money - sku.baseCost },
infrastructure: {
...s.infrastructure,
rackPipeline: [...s.infrastructure.rackPipeline, order],
},
};
}),
upgradeDataCenter: (dataCenterId, upgrade) => set((s) => {
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId);
if (!dc || dc.status !== 'operational') return s;
const tierConfig = DC_TIER_CONFIGS[dc.tier];
const cost = tierConfig.baseCost * DC_UPGRADE_COST_FRACTION;
if (s.economy.money < cost) return s;
const currentLevel = upgrade === 'cooling' ? dc.coolingLevel : dc.redundancyLevel;
if (currentLevel >= 1.0) return s;
const dataCenters = s.infrastructure.dataCenters.map(d => {
if (d.id !== dataCenterId) return d;
return {
...d,
[upgrade === 'cooling' ? 'coolingLevel' : 'redundancyLevel']:
Math.min(1.0, currentLevel + DC_UPGRADE_INCREMENT),
};
});
return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: { ...s.infrastructure, dataCenters },
};
}),
startTraining: (job) => set((s) => ({
models: {
...s.models,
@@ -419,10 +468,28 @@ export const useGameStore = create<Store>()(
}),
{
name: 'ai-tycoon-save',
version: SAVE_VERSION,
partialize: (state) => {
const { activePage, notifications, ...rest } = state;
return rest;
},
migrate: (_persisted, version) => {
if (version < SAVE_VERSION) {
return {
...initialGameState,
activePage: 'dashboard' as const,
notifications: [{
id: uuid(),
title: 'Save Reset',
message: 'Your save was reset due to a major infrastructure overhaul. Enjoy the new rack-based system!',
type: 'info' as const,
tick: 0,
read: false,
}],
} as unknown as Store;
}
return _persisted as Store;
},
},
),
);