Overhaul infrastructure: replace GPU model with rack-centric system
CI / build-and-push (push) Successful in 33s
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:
+104
-37
@@ -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;
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user