diff --git a/apps/web/src/components/game/CompanyStatsCard.tsx b/apps/web/src/components/game/CompanyStatsCard.tsx index c7333cf..5200a31 100644 --- a/apps/web/src/components/game/CompanyStatsCard.tsx +++ b/apps/web/src/components/game/CompanyStatsCard.tsx @@ -20,6 +20,7 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) { const reputation = useGameStore((s) => s.reputation.score); const achievements = useGameStore((s) => s.achievements.unlocked.length); const dataCenters = useGameStore((s) => s.infrastructure.dataCenters.length); + const totalRacks = useGameStore((s) => s.infrastructure.totalRackCount); const eraLabel = era === 'startup' ? 'Startup' : era === 'scaleup' ? 'Scale-up' : era === 'bigtech' ? 'Big Tech' : 'AGI'; const hours = Math.floor(totalPlayTime / 3600); @@ -32,7 +33,7 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) { `Valuation: ${formatMoney(valuation)}`, `Subscribers: ${formatNumber(subscribers)} | Models: ${models}`, `Best Model: ${bestModel.toFixed(1)}/100 | Reputation: ${reputation}/100`, - `Data Centers: ${dataCenters} | Achievements: ${achievements}/${ACHIEVEMENT_DEFINITIONS.length}`, + `Data Centers: ${dataCenters} | Racks: ${totalRacks} | Achievements: ${achievements}/${ACHIEVEMENT_DEFINITIONS.length}`, ].join('\n'); const handleCopy = () => { diff --git a/apps/web/src/pages/DashboardPage.tsx b/apps/web/src/pages/DashboardPage.tsx index 8693aa9..be9225d 100644 --- a/apps/web/src/pages/DashboardPage.tsx +++ b/apps/web/src/pages/DashboardPage.tsx @@ -29,7 +29,7 @@ export function DashboardPage() { {dataCenters.length === 0 && ( - Welcome to AI Tycoon! Start by building a data center in the Infrastructure tab, then buy GPUs to begin training your first AI model. + Welcome to AI Tycoon! Start by building a data center in the Infrastructure tab, then order racks to begin training your first AI model. )} diff --git a/apps/web/src/pages/InfrastructurePage.tsx b/apps/web/src/pages/InfrastructurePage.tsx index 45037f3..459adff 100644 --- a/apps/web/src/pages/InfrastructurePage.tsx +++ b/apps/web/src/pages/InfrastructurePage.tsx @@ -1,41 +1,342 @@ import { useState } from 'react'; -import { Plus, Server, Cpu, MapPin } from 'lucide-react'; +import { Plus, Server, MapPin, Zap, HardDrive, Wrench, ChevronDown, ChevronUp, Thermometer, Shield } from 'lucide-react'; import { useGameStore } from '@/store'; -import { formatMoney, formatNumber, formatPercent, GPU_CONFIGS, LOCATION_CONFIGS } from '@ai-tycoon/shared'; -import type { GpuType, LocationId } from '@ai-tycoon/shared'; +import { + formatMoney, formatNumber, formatPercent, + LOCATION_CONFIGS, DC_TIER_CONFIGS, RACK_SKU_CONFIGS, +} from '@ai-tycoon/shared'; +import type { DCTier, RackSkuId, LocationId, RackOrder, PipelineStage, Era } from '@ai-tycoon/shared'; -export function InfrastructurePage() { - const dataCenters = useGameStore((s) => s.infrastructure.dataCenters); - const gpuPrices = useGameStore((s) => s.infrastructure.gpuMarketPrices); +const ERA_ORDER: Era[] = ['startup', 'scaleup', 'bigtech', 'agi']; + +const STAGE_LABELS: Record = { + ordered: 'Ordered', + manufacturing: 'Manufacturing', + receiving: 'Receiving', + installation: 'Installation', + testing: 'Testing', + repair: 'Repair', +}; + +const STAGE_COLORS: Record = { + ordered: 'bg-surface-600', + manufacturing: 'bg-blue-500', + receiving: 'bg-cyan-500', + installation: 'bg-violet-500', + testing: 'bg-amber-500', + repair: 'bg-danger', +}; + +function PipelineKanban() { + const pipeline = useGameStore((s) => s.infrastructure.rackPipeline); + + if (pipeline.length === 0) return null; + + const stages: PipelineStage[] = ['ordered', 'manufacturing', 'receiving', 'installation', 'testing', 'repair']; + const grouped = stages.map(stage => ({ + stage, + orders: pipeline.filter(o => o.stage === stage), + })); + + return ( +
+

Rack Pipeline

+
+ {grouped.map(({ stage, orders }) => ( +
+
+ {STAGE_LABELS[stage]} ({orders.length}) +
+
+ {orders.map(order => ( + + ))} +
+
+ ))} +
+
+ ); +} + +function PipelineCard({ order }: { order: RackOrder }) { + const sku = RACK_SKU_CONFIGS[order.skuId]; + const progress = order.stageTotal > 0 ? order.stageProgress / order.stageTotal : 0; + + return ( +
+
{sku.name}
+
+
+
+ {order.repairCount > 0 && ( +
+ {order.repairCount}x +
+ )} +
+ ); +} + +function CapacityBar({ label, used, max, unit, icon: Icon }: { + label: string; used: number; max: number; unit: string; + icon: typeof HardDrive; +}) { + const pct = max > 0 ? used / max : 0; + const color = pct > 0.9 ? 'bg-danger' : pct > 0.7 ? 'bg-amber-500' : 'bg-accent'; + + return ( +
+
+ {label} + {formatNumber(used)}/{formatNumber(max)} {unit} +
+
+
+
+
+ ); +} + +function DataCenterCard({ dcId }: { dcId: string }) { + const dc = useGameStore((s) => s.infrastructure.dataCenters.find(d => d.id === dcId))!; const money = useGameStore((s) => s.economy.money); const era = useGameStore((s) => s.meta.currentEra); + const completedResearch = useGameStore((s) => s.research.completedResearch); + const orderRack = useGameStore((s) => s.orderRack); + const upgradeDataCenter = useGameStore((s) => s.upgradeDataCenter); + const [expanded, setExpanded] = useState(true); + + const tierConfig = DC_TIER_CONFIGS[dc.tier]; + const currentEraIdx = ERA_ORDER.indexOf(era); + + const availableSkus = Object.values(RACK_SKU_CONFIGS).filter(sku => { + if (ERA_ORDER.indexOf(sku.era) > currentEraIdx) return false; + if (sku.requiredResearch && !completedResearch.includes(sku.requiredResearch)) return false; + return true; + }); + + if (dc.status === 'constructing') { + const pct = dc.constructionTotal > 0 ? dc.constructionProgress / dc.constructionTotal : 0; + return ( +
+
+
+

{dc.name}

+
Under Construction — {tierConfig.name}
+
+
+ + {LOCATION_CONFIGS[dc.location].name} +
+
+
+
+
+
+ {Math.round(pct * 100)}% — {dc.constructionTotal - dc.constructionProgress}s remaining +
+
+ ); + } + + return ( +
+
+
+
+

{dc.name}

+ {dc.tier} +
+
+ {LOCATION_CONFIGS[dc.location].name} + Uptime: {formatPercent(dc.currentUptime)} + Cost: {formatMoney(dc.energyCostPerTick + dc.maintenanceCostPerTick)}/s +
+
+ +
+ +
+ + +
+ + {expanded && ( + <> + {dc.racks.length > 0 && ( +
+

Production Racks ({dc.racks.length})

+
+ {dc.racks.map(rack => { + const sku = RACK_SKU_CONFIGS[rack.skuId]; + return ( +
+
{sku.name}
+
{formatNumber(sku.flopsPerRack)} FLOPS
+
+ ); + })} +
+
+ )} + +
+

Order Racks

+
+ {availableSkus.map(sku => { + const canAfford = money >= sku.baseCost; + const hasSlot = dc.usedSlots < tierConfig.rackSlots; + const hasPower = dc.usedPowerKW + sku.powerDrawKW <= tierConfig.powerBudgetKW; + const disabled = !canAfford || !hasSlot || !hasPower; + + return ( + + ); + })} +
+
+ +
+

Upgrades

+
+ + +
+
+ + )} +
+ ); +} + +function BuildDCPanel({ onClose }: { onClose: () => void }) { + const money = useGameStore((s) => s.economy.money); + const era = useGameStore((s) => s.meta.currentEra); + const completedResearch = useGameStore((s) => s.research.completedResearch); const buildDataCenter = useGameStore((s) => s.buildDataCenter); - const buyGpu = useGameStore((s) => s.buyGpu); - const [showNewDC, setShowNewDC] = useState(false); - const [newDCName, setNewDCName] = useState(''); - const [newDCLocation, setNewDCLocation] = useState('us-west'); + const [name, setName] = useState(''); + const [location, setLocation] = useState('us-west'); + const [tier, setTier] = useState('small'); - const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi']; - const currentEraIdx = eraOrder.indexOf(era); + const currentEraIdx = ERA_ORDER.indexOf(era); const availableLocations = Object.values(LOCATION_CONFIGS).filter( - loc => eraOrder.indexOf(loc.availableAt) <= currentEraIdx, + loc => ERA_ORDER.indexOf(loc.availableAt) <= currentEraIdx, ); - const availableGpus = Object.values(GPU_CONFIGS).filter( - gpu => eraOrder.indexOf(gpu.availableAt) <= currentEraIdx, - ); + const availableTiers = Object.values(DC_TIER_CONFIGS).filter(t => { + if (ERA_ORDER.indexOf(t.requiredEra) > currentEraIdx) return false; + if (t.requiredResearch && !completedResearch.includes(t.requiredResearch)) return false; + return true; + }); - const handleBuildDC = () => { - if (!newDCName.trim()) return; - buildDataCenter(newDCName.trim(), newDCLocation); - setNewDCName(''); - setShowNewDC(false); + const tierConfig = DC_TIER_CONFIGS[tier]; + + const handleBuild = () => { + if (!name.trim()) return; + buildDataCenter(name.trim(), location, tier); + onClose(); }; return ( -
+
+

Build New Data Center

+
+
+ + setName(e.target.value)} + placeholder="DC-West-01" + className="w-full bg-surface-800 border border-surface-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" + autoFocus + /> +
+
+ + +
+
+ + +
+
+
+ Cost: {formatMoney(tierConfig.baseCost)} · Build time: {tierConfig.buildTimeTicks}s +
+ + +
+
+
+ ); +} + +export function InfrastructurePage() { + const dataCenters = useGameStore((s) => s.infrastructure.dataCenters); + const [showNewDC, setShowNewDC] = useState(false); + + return ( +

Infrastructure

- {showNewDC && ( -
-

Build New Data Center

-
-
- - setNewDCName(e.target.value)} - placeholder="DC-West-01" - className="w-full bg-surface-800 border border-surface-600 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-accent/50" - autoFocus - /> -
-
- - -
-
-
- Cost: {formatMoney(10_000)} -
- - -
-
-
- )} + {showNewDC && setShowNewDC(false)} />} - {dataCenters.length === 0 ? ( + + + {dataCenters.length === 0 && !showNewDC ? (

No data centers yet. Build your first one to start hosting AI models.

@@ -99,75 +360,10 @@ export function InfrastructurePage() { ) : (
{dataCenters.map(dc => ( -
-
-
-

{dc.name}

-
- - {LOCATION_CONFIGS[dc.location].name} -
-
-
-
Uptime: {formatPercent(dc.currentUptime)}
-
Cost: {formatMoney(dc.energyCostPerTick + dc.maintenanceCostPerTick)}/s
-
-
- -
-

GPUs

- {dc.gpus.length === 0 ? ( -

No GPUs installed

- ) : ( -
- {dc.gpus.map(inv => ( -
-
{GPU_CONFIGS[inv.type].name}
-
- {inv.healthyCount}/{inv.count} healthy · {formatNumber(inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit)} FLOPS -
-
- ))} -
- )} -
- -
-

Buy GPUs

-
- {availableGpus.map(gpu => ( - - ))} -
-
-
+ ))}
)} - -
-

GPU Market Prices

-
- {availableGpus.map(gpu => ( -
-
-
{gpu.name}
-
{formatNumber(gpu.flopsPerUnit)} FLOPS/unit
-
-
{formatMoney(gpuPrices[gpu.type])}
-
- ))} -
-
); } diff --git a/apps/web/src/store/index.ts b/apps/web/src/store/index.ts index 4508b36..7b29690 100644 --- a/apps/web/src/store/index.ts +++ b/apps/web/src/store/index.ts @@ -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) => void; deployModel: (modelId: string) => void; setProductPricing: (productLineId: string, field: string, value: number) => void; @@ -146,51 +148,37 @@ export const useGameStore = create()( 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()( }; }), + 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()( }), { 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; + }, }, ), ); diff --git a/packages/game-engine/src/data/achievements.ts b/packages/game-engine/src/data/achievements.ts index 31fdc5e..a65af51 100644 --- a/packages/game-engine/src/data/achievements.ts +++ b/packages/game-engine/src/data/achievements.ts @@ -79,11 +79,11 @@ export const ACHIEVEMENT_DEFINITIONS: AchievementDefinition[] = [ condition: { field: 'meta._eraIndex', operator: 'gte', value: 3 }, }, { - id: 'gpu-hoarder', - name: 'GPU Hoarder', - description: 'Own 100 or more GPUs across all data centers.', + id: 'rack-hoarder', + name: 'Rack Hoarder', + description: 'Have 50 or more production racks across all data centers.', icon: 'Cpu', - condition: { field: 'infrastructure._totalGpuCount', operator: 'gte', value: 100 }, + condition: { field: 'infrastructure.totalRackCount', operator: 'gte', value: 50 }, }, { id: 'research-pioneer', diff --git a/packages/game-engine/src/data/techTree.ts b/packages/game-engine/src/data/techTree.ts index 0abe52b..e479dad 100644 --- a/packages/game-engine/src/data/techTree.ts +++ b/packages/game-engine/src/data/techTree.ts @@ -25,32 +25,32 @@ export const TECH_TREE: ResearchNode[] = [ { id: 'advanced-gpu-arch', name: 'Advanced GPU Architecture', - description: 'Unlocks procurement of NVIDIA A100 datacenter GPUs.', + description: 'Unlocks procurement of NVIDIA A100 rack configurations.', era: 'startup', category: 'infrastructure', prerequisites: [], cost: { researchPoints: 0, compute: 10, ticks: 90 }, - effects: [{ type: 'unlock_gpu', target: 'a100', value: 1 }], + effects: [{ type: 'unlock_rack', target: 'a100', value: 1 }], }, { id: 'next-gen-gpu', name: 'Next-Gen GPU Architecture', - description: 'Unlocks procurement of NVIDIA H100 GPUs.', + description: 'Unlocks procurement of NVIDIA H100 rack configurations.', era: 'scaleup', category: 'infrastructure', prerequisites: ['advanced-gpu-arch'], cost: { researchPoints: 2, compute: 40, ticks: 240 }, - effects: [{ type: 'unlock_gpu', target: 'h100', value: 1 }], + effects: [{ type: 'unlock_rack', target: 'h100', value: 1 }], }, { id: 'frontier-compute', name: 'Frontier Compute', - description: 'Unlocks procurement of NVIDIA B200 GPUs.', + description: 'Unlocks procurement of NVIDIA B200 rack configurations.', era: 'bigtech', category: 'infrastructure', prerequisites: ['next-gen-gpu'], cost: { researchPoints: 5, compute: 200, ticks: 480 }, - effects: [{ type: 'unlock_gpu', target: 'b200', value: 1 }], + effects: [{ type: 'unlock_rack', target: 'b200', value: 1 }], }, { id: 'custom-silicon', @@ -60,7 +60,47 @@ export const TECH_TREE: ResearchNode[] = [ category: 'infrastructure', prerequisites: ['frontier-compute'], cost: { researchPoints: 10, compute: 500, ticks: 900 }, - effects: [{ type: 'unlock_gpu', target: 'custom', value: 1 }], + effects: [{ type: 'unlock_rack', target: 'custom', value: 1 }], + }, + { + id: 'dc-engineering-ii', + name: 'DC Engineering II', + description: 'Advanced facility design unlocks Medium data centers (30 slots, 200kW).', + era: 'startup', + category: 'infrastructure', + prerequisites: ['advanced-cooling'], + cost: { researchPoints: 1, compute: 15, ticks: 120 }, + effects: [{ type: 'unlock_dc_tier', target: 'medium', value: 1 }], + }, + { + id: 'dc-engineering-iii', + name: 'DC Engineering III', + description: 'Large-scale facility design unlocks Large data centers (60 slots, 500kW).', + era: 'scaleup', + category: 'infrastructure', + prerequisites: ['dc-engineering-ii'], + cost: { researchPoints: 3, compute: 60, ticks: 300 }, + effects: [{ type: 'unlock_dc_tier', target: 'large', value: 1 }], + }, + { + id: 'dc-engineering-iv', + name: 'DC Engineering IV', + description: 'Mega-scale campus design unlocks Mega data centers (120 slots, 1200kW).', + era: 'bigtech', + category: 'infrastructure', + prerequisites: ['dc-engineering-iii'], + cost: { researchPoints: 6, compute: 150, ticks: 600 }, + effects: [{ type: 'unlock_dc_tier', target: 'mega', value: 1 }], + }, + { + id: 'quality-assurance', + name: 'Quality Assurance', + description: 'Advanced QA processes reduce rack test failure rate by 25%.', + era: 'startup', + category: 'infrastructure', + prerequisites: ['redundancy-protocols'], + cost: { researchPoints: 1, compute: 10, ticks: 90 }, + effects: [{ type: 'cost_reduction', target: 'test_failure_rate', value: 0.25 }], }, { id: 'distributed-training', diff --git a/packages/game-engine/src/systems/achievementSystem.ts b/packages/game-engine/src/systems/achievementSystem.ts index fce60b9..5ccfad4 100644 --- a/packages/game-engine/src/systems/achievementSystem.ts +++ b/packages/game-engine/src/systems/achievementSystem.ts @@ -10,12 +10,6 @@ const ERA_INDEX: Record = { startup: 0, scaleup: 1, bigtech: 2, function getFieldValue(state: GameState, field: string): number { if (field === 'meta._eraIndex') return ERA_INDEX[state.meta.currentEra] ?? 0; if (field === 'meta._deployedModelCount') return state.models.trainedModels.filter(m => m.isDeployed).length; - if (field === 'infrastructure._totalGpuCount') { - return state.infrastructure.dataCenters.reduce( - (sum, dc) => sum + dc.gpus.reduce((s, g) => s + g.count, 0), 0, - ); - } - const parts = field.split('.'); let current: unknown = state; for (const part of parts) { diff --git a/packages/game-engine/src/systems/economySystem.ts b/packages/game-engine/src/systems/economySystem.ts index 4ebab07..86c27fc 100644 --- a/packages/game-engine/src/systems/economySystem.ts +++ b/packages/game-engine/src/systems/economySystem.ts @@ -6,6 +6,7 @@ export function processEconomy( state: GameState, market: MarketTickResult, infrastructure: InfrastructureState, + extraCosts: number = 0, ): EconomyState { const revenue = market.apiRevenue + market.subscriptionRevenue; @@ -20,7 +21,7 @@ export function processEconomy( const eraIdx = ['startup', 'scaleup', 'bigtech', 'agi'].indexOf(state.meta.currentEra); const complianceCost = bestCapability > 30 ? bestCapability * REGULATION_COMPLIANCE_PER_CAPABILITY * (1 + eraIdx * 0.5) / 100 : 0; - const expenses = infraExpenses + talentExpenses + dataExpenses + complianceCost; + const expenses = infraExpenses + talentExpenses + dataExpenses + complianceCost + extraCosts; const money = state.economy.money + revenue - expenses; diff --git a/packages/game-engine/src/systems/infrastructureSystem.ts b/packages/game-engine/src/systems/infrastructureSystem.ts index 0ae2efd..f09cdad 100644 --- a/packages/game-engine/src/systems/infrastructureSystem.ts +++ b/packages/game-engine/src/systems/infrastructureSystem.ts @@ -1,72 +1,275 @@ -import type { GameState, InfrastructureState } from '@ai-tycoon/shared'; +import type { GameState, InfrastructureState, DataCenter, RackOrder, Rack, PipelineStage } from '@ai-tycoon/shared'; import { - GPU_CONFIGS, LOCATION_CONFIGS, - GPU_PRICE_VOLATILITY, - GPU_FAILURE_RATE_BASE, - REDUNDANCY_FAILURE_REDUCTION, + RACK_SKU_CONFIGS, + DC_TIER_CONFIGS, BASE_ENERGY_COST_PER_FLOP, - BASE_MAINTENANCE_PER_GPU, + BASE_MAINTENANCE_PER_RACK, + COOLING_FAILURE_REDUCTION, + REDUNDANCY_FAILURE_REDUCTION, + RACK_REPAIR_BASE_TICKS, } from '@ai-tycoon/shared'; -import type { GpuType } from '@ai-tycoon/shared'; +import type { TickNotification } from '../tick'; -export function processInfrastructure(state: GameState): InfrastructureState { - const dataCenters = state.infrastructure.dataCenters.map(dc => { - const location = LOCATION_CONFIGS[dc.location]; +export interface InfraTickResult { + infrastructure: InfrastructureState; + notifications: TickNotification[]; + repairCosts: number; +} - const gpus = dc.gpus.map(inv => { - const failureRate = GPU_FAILURE_RATE_BASE * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION); - let newFailed = inv.failedCount; - for (let i = 0; i < inv.healthyCount; i++) { - if (Math.random() < failureRate) newFailed++; - } - const healthyCount = Math.max(0, inv.count - newFailed); - return { ...inv, healthyCount, failedCount: newFailed }; - }); +const PIPELINE_ADVANCE_ORDER: PipelineStage[] = [ + 'ordered', 'manufacturing', 'receiving', 'installation', 'testing', +]; - let totalFlops = 0; - let totalPower = 0; - let totalGpuCount = 0; - for (const inv of gpus) { - const config = GPU_CONFIGS[inv.type]; - totalFlops += inv.healthyCount * config.flopsPerUnit; - totalPower += inv.healthyCount * config.basePowerDraw; - totalGpuCount += inv.count; +function nextStage(stage: PipelineStage): PipelineStage | 'production' { + const idx = PIPELINE_ADVANCE_ORDER.indexOf(stage); + if (idx === -1 || idx === PIPELINE_ADVANCE_ORDER.length - 1) return 'production'; + return PIPELINE_ADVANCE_ORDER[idx + 1]; +} + +function stageTotal(stage: PipelineStage, order: RackOrder): number { + const sku = RACK_SKU_CONFIGS[order.skuId]; + const timings = sku.pipelineTimeTicks; + switch (stage) { + case 'manufacturing': return timings.manufacturing; + case 'receiving': return timings.receiving; + case 'installation': return timings.installation; + case 'testing': return timings.testing; + case 'repair': return RACK_REPAIR_BASE_TICKS; + default: return 0; + } +} + +function stageSpeed(stage: PipelineStage, engEff: number, opsEff: number): number { + switch (stage) { + case 'manufacturing': return 1 + engEff * 0.1; + case 'installation': + case 'testing': return 1 + opsEff * 0.1; + case 'repair': return 1 + opsEff * 0.05; + default: return 1; + } +} + +export function processInfrastructure(state: GameState): InfraTickResult { + const notifications: TickNotification[] = []; + let repairCosts = 0; + + const engEff = state.talent.departments.engineering.effectiveness; + const opsEff = state.talent.departments.operations.effectiveness; + + const qaResearchBonus = state.research.completedResearch.includes('quality-assurance') ? 0.25 : 0; + + // --- Phase 1: Advance DC Construction --- + const dataCenters: DataCenter[] = state.infrastructure.dataCenters.map(dc => { + if (dc.status !== 'constructing') return { ...dc }; + + const newProgress = dc.constructionProgress + 1; + if (newProgress >= dc.constructionTotal) { + notifications.push({ + title: 'Data Center Online', + message: `${dc.name} is now operational!`, + type: 'success', + }); + return { ...dc, constructionProgress: dc.constructionTotal, status: 'operational' as const }; } - - const energyCostPerTick = totalPower * BASE_ENERGY_COST_PER_FLOP * location.energyCostMultiplier; - const maintenanceCostPerTick = totalGpuCount * BASE_MAINTENANCE_PER_GPU; - const currentUptime = totalGpuCount > 0 - ? gpus.reduce((s, inv) => s + inv.healthyCount, 0) / totalGpuCount - : 1; - - return { ...dc, gpus, energyCostPerTick, maintenanceCostPerTick, currentUptime }; + return { ...dc, constructionProgress: newProgress }; }); - const gpuMarketPrices = { ...state.infrastructure.gpuMarketPrices }; - for (const gpuType of Object.keys(gpuMarketPrices) as GpuType[]) { - const basePrice = GPU_CONFIGS[gpuType].basePrice; - const variation = (Math.random() - 0.5) * 2 * GPU_PRICE_VOLATILITY; - const currentPrice = gpuMarketPrices[gpuType]; - const newPrice = currentPrice * (1 + variation); - gpuMarketPrices[gpuType] = Math.max(basePrice * 0.7, Math.min(basePrice * 1.5, newPrice)); + // --- Phase 2: Advance Rack Pipeline --- + const rackPipeline: RackOrder[] = []; + const newRacks: Rack[] = []; + + for (const order of state.infrastructure.rackPipeline) { + const speed = stageSpeed(order.stage, engEff, opsEff); + const newProgress = order.stageProgress + speed; + + if (newProgress < order.stageTotal) { + rackPipeline.push({ ...order, stageProgress: newProgress }); + continue; + } + + if (order.stage === 'repair') { + const total = stageTotal('testing', order); + rackPipeline.push({ + ...order, + stage: 'testing', + stageProgress: 0, + stageTotal: total, + }); + continue; + } + + const next = nextStage(order.stage); + + if (next === 'production') { + const sku = RACK_SKU_CONFIGS[order.skuId]; + const dc = dataCenters.find(d => d.id === order.dataCenterId); + const cooling = dc?.coolingLevel ?? 0; + + const effectiveFailRate = sku.testFailureRate + * (1 - cooling * COOLING_FAILURE_REDUCTION) + * (1 - opsEff * 0.2) + * (1 - qaResearchBonus); + + if (Math.random() < effectiveFailRate) { + const repairCost = sku.baseCost * sku.repairCostFraction; + repairCosts += repairCost; + rackPipeline.push({ + ...order, + stage: 'repair', + stageProgress: 0, + stageTotal: RACK_REPAIR_BASE_TICKS, + repairCount: order.repairCount + 1, + }); + notifications.push({ + title: 'Rack Failed Testing', + message: `${sku.name} rack failed QA (attempt ${order.repairCount + 1}). Repair cost: $${repairCost.toLocaleString()}`, + type: 'warning', + }); + } else { + newRacks.push({ + id: order.id, + skuId: order.skuId, + dataCenterId: order.dataCenterId, + isHealthy: true, + }); + notifications.push({ + title: 'Rack Online', + message: `${sku.name} rack is now in production at ${dc?.name ?? 'data center'}.`, + type: 'success', + }); + } + } else { + const total = stageTotal(next, order); + rackPipeline.push({ + ...order, + stage: next, + stageProgress: 0, + stageTotal: total, + }); + } } + // Add newly completed racks to their data centers + for (const rack of newRacks) { + const dcIdx = dataCenters.findIndex(d => d.id === rack.dataCenterId); + if (dcIdx !== -1) { + dataCenters[dcIdx] = { + ...dataCenters[dcIdx], + racks: [...dataCenters[dcIdx].racks, rack], + }; + } + } + + // --- Phase 3: Production Failures --- + for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { + const dc = dataCenters[dcIdx]; + if (dc.status !== 'operational') continue; + + const updatedRacks: Rack[] = []; + for (const rack of dc.racks) { + if (!rack.isHealthy) { + updatedRacks.push(rack); + continue; + } + + const sku = RACK_SKU_CONFIGS[rack.skuId]; + const effectiveRate = sku.productionFailureRate + * (1 - dc.coolingLevel * COOLING_FAILURE_REDUCTION) + * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION); + + if (Math.random() < effectiveRate) { + updatedRacks.push({ ...rack, isHealthy: false }); + const repairCost = sku.baseCost * sku.repairCostFraction; + repairCosts += repairCost; + + rackPipeline.push({ + id: rack.id, + skuId: rack.skuId, + dataCenterId: dc.id, + stage: 'repair', + stageProgress: 0, + stageTotal: RACK_REPAIR_BASE_TICKS, + totalCost: repairCost, + repairCount: 0, + }); + + notifications.push({ + title: 'Rack Failure', + message: `${sku.name} rack failed in ${dc.name}. Sent for repair.`, + type: 'danger', + }); + } else { + updatedRacks.push(rack); + } + } + + // Remove failed racks from the DC (they're now in the repair pipeline) + dataCenters[dcIdx] = { + ...dc, + racks: updatedRacks.filter(r => r.isHealthy), + }; + } + + // --- Phase 4: Compute Aggregates --- let totalFlops = 0; let totalUptime = 0; - let dcCount = 0; - for (const dc of dataCenters) { - for (const inv of dc.gpus) { - totalFlops += inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit; + let totalRackCount = 0; + let dcWithRacks = 0; + + for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { + const dc = dataCenters[dcIdx]; + if (dc.status !== 'operational') continue; + + const location = LOCATION_CONFIGS[dc.location]; + const tierConfig = DC_TIER_CONFIGS[dc.tier]; + + let dcFlops = 0; + let usedPowerKW = 0; + const healthyCount = dc.racks.length; + const totalInDc = dc.racks.length; + + for (const rack of dc.racks) { + const sku = RACK_SKU_CONFIGS[rack.skuId]; + dcFlops += sku.flopsPerRack; + usedPowerKW += sku.powerDrawKW; } - totalUptime += dc.currentUptime; - dcCount++; + + const pipelineRacksForDc = rackPipeline.filter(o => o.dataCenterId === dc.id).length; + const usedSlots = totalInDc + pipelineRacksForDc; + + const energyCostPerTick = (tierConfig.baseEnergyCostPerTick + usedPowerKW * BASE_ENERGY_COST_PER_FLOP) + * location.energyCostMultiplier; + const maintenanceCostPerTick = totalInDc * BASE_MAINTENANCE_PER_RACK; + + const currentUptime = totalInDc > 0 ? healthyCount / totalInDc : 1; + + totalFlops += dcFlops; + totalRackCount += totalInDc; + if (totalInDc > 0) { + totalUptime += currentUptime; + dcWithRacks++; + } + + dataCenters[dcIdx] = { + ...dataCenters[dcIdx], + usedSlots, + usedPowerKW, + energyCostPerTick, + maintenanceCostPerTick, + currentUptime, + }; } return { - dataCenters, - gpuMarketPrices, - totalFlops, - totalUptime: dcCount > 0 ? totalUptime / dcCount : 1, + infrastructure: { + dataCenters, + rackPipeline, + totalFlops, + totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1, + totalRackCount, + }, + notifications, + repairCosts, }; } diff --git a/packages/game-engine/src/tick.ts b/packages/game-engine/src/tick.ts index 60c8a42..cfcdebe 100644 --- a/packages/game-engine/src/tick.ts +++ b/packages/game-engine/src/tick.ts @@ -39,7 +39,9 @@ export function setAchievementDefinitions(defs: AchievementDefinition[]) { export function processTick(state: GameState): Partial { const notifications: TickNotification[] = []; - const infrastructure = processInfrastructure(state); + const infraResult = processInfrastructure(state); + const infrastructure = infraResult.infrastructure; + notifications.push(...infraResult.notifications); const stateWithInfra = { ...state, infrastructure }; const modelResult = processModels(stateWithInfra); @@ -82,7 +84,7 @@ export function processTick(state: GameState): Partial { type: 'danger', }); } - const economy = processEconomy(stateWithTalent, market, infrastructure); + const economy = processEconomy(stateWithTalent, market, infrastructure, infraResult.repairCosts); const data = processData(stateWithTalent); const competitors = processCompetitors(stateWithTalent); diff --git a/packages/shared/src/constants/gameBalance.ts b/packages/shared/src/constants/gameBalance.ts index 1652227..2fdc6bb 100644 --- a/packages/shared/src/constants/gameBalance.ts +++ b/packages/shared/src/constants/gameBalance.ts @@ -1,3 +1,5 @@ +import type { DCTier, DCTierConfig, RackSkuId, RackSkuConfig } from '../types/infrastructure'; + export const TICK_INTERVAL_MS = 1000; export const MAX_OFFLINE_TICKS = 86_400; export const OFFLINE_EFFICIENCY = 0.8; @@ -10,7 +12,6 @@ export const MAX_REPUTATION_HISTORY = 500; export const STARTING_MONEY = 50_000; export const BASE_ENERGY_COST_PER_FLOP = 0.001; -export const BASE_MAINTENANCE_PER_GPU = 0.005; export const TRAINING_BASE_TICKS = 120; export const TRAINING_COMPUTE_MULTIPLIER = 0.8; @@ -37,9 +38,213 @@ export const ERA_THRESHOLDS = { agi: { revenue: 100_000_000, capability: 90, reputation: 70 }, }; -export const GPU_PRICE_VOLATILITY = 0.02; -export const GPU_FAILURE_RATE_BASE = 0.0001; +// --- Data Center Tier Configs --- + +export const DC_TIER_CONFIGS: Record = { + small: { + tier: 'small', + name: 'Small Data Center', + rackSlots: 12, + powerBudgetKW: 60, + baseCost: 10_000, + buildTimeTicks: 300, + firstBuildTimeTicks: 10, + requiredEra: 'startup', + requiredResearch: null, + baseEnergyCostPerTick: 5, + }, + medium: { + tier: 'medium', + name: 'Medium Data Center', + rackSlots: 30, + powerBudgetKW: 200, + baseCost: 50_000, + buildTimeTicks: 900, + firstBuildTimeTicks: 900, + requiredEra: 'scaleup', + requiredResearch: 'dc-engineering-ii', + baseEnergyCostPerTick: 15, + }, + large: { + tier: 'large', + name: 'Large Data Center', + rackSlots: 60, + powerBudgetKW: 500, + baseCost: 200_000, + buildTimeTicks: 1800, + firstBuildTimeTicks: 1800, + requiredEra: 'bigtech', + requiredResearch: 'dc-engineering-iii', + baseEnergyCostPerTick: 40, + }, + mega: { + tier: 'mega', + name: 'Mega Data Center', + rackSlots: 120, + powerBudgetKW: 1200, + baseCost: 1_000_000, + buildTimeTicks: 3600, + firstBuildTimeTicks: 3600, + requiredEra: 'agi', + requiredResearch: 'dc-engineering-iv', + baseEnergyCostPerTick: 100, + }, +}; + +// --- Rack SKU Configs --- + +export const RACK_SKU_CONFIGS: Record = { + 'consumer-x4': { + id: 'consumer-x4', + name: 'Consumer GPU x4', + era: 'startup', + gpuCount: 4, + flopsPerRack: 4, + powerDrawKW: 0.4, + baseCost: 3_200, + requiredResearch: null, + pipelineTimeTicks: { manufacturing: 20, receiving: 10, installation: 15, testing: 15 }, + testFailureRate: 0.05, + productionFailureRate: 0.0002, + repairCostFraction: 0.10, + }, + 't4-x4': { + id: 't4-x4', + name: 'NVIDIA T4 x4', + era: 'startup', + gpuCount: 4, + flopsPerRack: 32, + powerDrawKW: 1.2, + baseCost: 20_000, + requiredResearch: null, + pipelineTimeTicks: { manufacturing: 30, receiving: 15, installation: 25, testing: 20 }, + testFailureRate: 0.07, + productionFailureRate: 0.0003, + repairCostFraction: 0.12, + }, + 't4-x8': { + id: 't4-x8', + name: 'NVIDIA T4 x8', + era: 'scaleup', + gpuCount: 8, + flopsPerRack: 64, + powerDrawKW: 2.4, + baseCost: 38_000, + requiredResearch: null, + pipelineTimeTicks: { manufacturing: 40, receiving: 20, installation: 30, testing: 30 }, + testFailureRate: 0.08, + productionFailureRate: 0.0003, + repairCostFraction: 0.12, + }, + 'a100-x4': { + id: 'a100-x4', + name: 'NVIDIA A100 x4', + era: 'scaleup', + gpuCount: 4, + flopsPerRack: 160, + powerDrawKW: 4.0, + baseCost: 60_000, + requiredResearch: 'advanced-gpu-arch', + pipelineTimeTicks: { manufacturing: 60, receiving: 25, installation: 50, testing: 45 }, + testFailureRate: 0.10, + productionFailureRate: 0.0004, + repairCostFraction: 0.15, + }, + 'a100-x8': { + id: 'a100-x8', + name: 'NVIDIA A100 x8', + era: 'scaleup', + gpuCount: 8, + flopsPerRack: 320, + powerDrawKW: 8.0, + baseCost: 115_000, + requiredResearch: 'advanced-gpu-arch', + pipelineTimeTicks: { manufacturing: 70, receiving: 30, installation: 55, testing: 55 }, + testFailureRate: 0.12, + productionFailureRate: 0.0004, + repairCostFraction: 0.15, + }, + 'h100-x4': { + id: 'h100-x4', + name: 'NVIDIA H100 x4', + era: 'bigtech', + gpuCount: 4, + flopsPerRack: 480, + powerDrawKW: 5.6, + baseCost: 140_000, + requiredResearch: 'next-gen-gpu', + pipelineTimeTicks: { manufacturing: 80, receiving: 30, installation: 65, testing: 65 }, + testFailureRate: 0.15, + productionFailureRate: 0.0005, + repairCostFraction: 0.18, + }, + 'h100-x8': { + id: 'h100-x8', + name: 'NVIDIA H100 x8', + era: 'bigtech', + gpuCount: 8, + flopsPerRack: 960, + powerDrawKW: 11.2, + baseCost: 270_000, + requiredResearch: 'next-gen-gpu', + pipelineTimeTicks: { manufacturing: 90, receiving: 35, installation: 75, testing: 80 }, + testFailureRate: 0.18, + productionFailureRate: 0.0005, + repairCostFraction: 0.18, + }, + 'b200-x4': { + id: 'b200-x4', + name: 'NVIDIA B200 x4', + era: 'bigtech', + gpuCount: 4, + flopsPerRack: 1600, + powerDrawKW: 8.0, + baseCost: 200_000, + requiredResearch: 'frontier-compute', + pipelineTimeTicks: { manufacturing: 100, receiving: 40, installation: 80, testing: 80 }, + testFailureRate: 0.20, + productionFailureRate: 0.0006, + repairCostFraction: 0.20, + }, + 'b200-x8': { + id: 'b200-x8', + name: 'NVIDIA B200 x8', + era: 'agi', + gpuCount: 8, + flopsPerRack: 3200, + powerDrawKW: 16.0, + baseCost: 380_000, + requiredResearch: 'frontier-compute', + pipelineTimeTicks: { manufacturing: 120, receiving: 45, installation: 95, testing: 100 }, + testFailureRate: 0.22, + productionFailureRate: 0.0006, + repairCostFraction: 0.20, + }, + 'custom-x8': { + id: 'custom-x8', + name: 'Custom ASIC x8', + era: 'agi', + gpuCount: 8, + flopsPerRack: 6400, + powerDrawKW: 20.0, + baseCost: 640_000, + requiredResearch: 'custom-silicon', + pipelineTimeTicks: { manufacturing: 140, receiving: 50, installation: 100, testing: 110 }, + testFailureRate: 0.25, + productionFailureRate: 0.0008, + repairCostFraction: 0.20, + }, +}; + +// --- Pipeline & Infrastructure Constants --- + +export const PIPELINE_ORDER_BASE_TICKS = 15; +export const RACK_REPAIR_BASE_TICKS = 30; +export const BASE_MAINTENANCE_PER_RACK = 0.02; +export const COOLING_FAILURE_REDUCTION = 0.5; export const REDUNDANCY_FAILURE_REDUCTION = 0.5; +export const DC_UPGRADE_COST_FRACTION = 0.25; +export const DC_UPGRADE_INCREMENT = 0.1; export const FUNDING_ROUNDS = { seed: { amount: 100_000, dilution: 0.10, requirements: { minRevenue: 100, minUsers: 0, minReputation: 0 } }, diff --git a/packages/shared/src/types/gameState.ts b/packages/shared/src/types/gameState.ts index 5bf90eb..484cee9 100644 --- a/packages/shared/src/types/gameState.ts +++ b/packages/shared/src/types/gameState.ts @@ -60,4 +60,4 @@ export const INITIAL_SETTINGS: GameSettings = { sfxVolume: 0.7, }; -export const SAVE_VERSION = 1; +export const SAVE_VERSION = 2; diff --git a/packages/shared/src/types/infrastructure.ts b/packages/shared/src/types/infrastructure.ts index abc9464..62fa074 100644 --- a/packages/shared/src/types/infrastructure.ts +++ b/packages/shared/src/types/infrastructure.ts @@ -1,33 +1,112 @@ import type { Era } from './gameState'; -export interface InfrastructureState { - dataCenters: DataCenter[]; - gpuMarketPrices: Record; - totalFlops: number; - totalUptime: number; +// --- Data Center --- + +export type DCTier = 'small' | 'medium' | 'large' | 'mega'; +export type DCStatus = 'constructing' | 'operational'; + +export interface DCTierConfig { + tier: DCTier; + name: string; + rackSlots: number; + powerBudgetKW: number; + baseCost: number; + buildTimeTicks: number; + firstBuildTimeTicks: number; + requiredEra: Era; + requiredResearch: string | null; + baseEnergyCostPerTick: number; } export interface DataCenter { id: string; name: string; location: LocationId; - gpus: GpuInventory[]; - maxCapacity: number; + tier: DCTier; + status: DCStatus; + constructionProgress: number; + constructionTotal: number; + racks: Rack[]; coolingLevel: number; redundancyLevel: number; currentUptime: number; energyCostPerTick: number; maintenanceCostPerTick: number; + usedSlots: number; + usedPowerKW: number; } -export interface GpuInventory { - type: GpuType; - count: number; - healthyCount: number; - failedCount: number; +// --- Racks --- + +export type RackSkuId = + | 'consumer-x4' | 't4-x4' | 't4-x8' + | 'a100-x4' | 'a100-x8' + | 'h100-x4' | 'h100-x8' + | 'b200-x4' | 'b200-x8' | 'custom-x8'; + +export type PipelineStage = + | 'ordered' | 'manufacturing' | 'receiving' + | 'installation' | 'testing' | 'repair'; + +export interface PipelineTimings { + manufacturing: number; + receiving: number; + installation: number; + testing: number; } -export type GpuType = 'consumer' | 't4' | 'a100' | 'h100' | 'b200' | 'custom'; +export interface RackSkuConfig { + id: RackSkuId; + name: string; + era: Era; + gpuCount: number; + flopsPerRack: number; + powerDrawKW: number; + baseCost: number; + requiredResearch: string | null; + pipelineTimeTicks: PipelineTimings; + testFailureRate: number; + productionFailureRate: number; + repairCostFraction: number; +} + +export interface Rack { + id: string; + skuId: RackSkuId; + dataCenterId: string; + isHealthy: boolean; +} + +export interface RackOrder { + id: string; + skuId: RackSkuId; + dataCenterId: string; + stage: PipelineStage; + stageProgress: number; + stageTotal: number; + totalCost: number; + repairCount: number; +} + +// --- Infrastructure State --- + +export interface InfrastructureState { + dataCenters: DataCenter[]; + rackPipeline: RackOrder[]; + totalFlops: number; + totalUptime: number; + totalRackCount: number; +} + +export const INITIAL_INFRASTRUCTURE: InfrastructureState = { + dataCenters: [], + rackPipeline: [], + totalFlops: 0, + totalUptime: 1, + totalRackCount: 0, +}; + +// --- Locations (unchanged) --- export type LocationId = 'us-west' | 'us-east' | 'eu-west' | 'eu-north' | 'asia-east' | 'asia-south' | 'middle-east'; @@ -41,24 +120,6 @@ export interface LocationConfig { availableAt: Era; } -export interface GpuConfig { - type: GpuType; - name: string; - flopsPerUnit: number; - basePowerDraw: number; - basePrice: number; - availableAt: Era; -} - -export const GPU_CONFIGS: Record = { - consumer: { type: 'consumer', name: 'Consumer GPU', flopsPerUnit: 1, basePowerDraw: 0.3, basePrice: 800, availableAt: 'startup' }, - t4: { type: 't4', name: 'NVIDIA T4', flopsPerUnit: 8, basePowerDraw: 0.5, basePrice: 5_000, availableAt: 'startup' }, - a100: { type: 'a100', name: 'NVIDIA A100', flopsPerUnit: 40, basePowerDraw: 1.0, basePrice: 15_000, availableAt: 'scaleup' }, - h100: { type: 'h100', name: 'NVIDIA H100', flopsPerUnit: 120, basePowerDraw: 1.2, basePrice: 35_000, availableAt: 'scaleup' }, - b200: { type: 'b200', name: 'NVIDIA B200', flopsPerUnit: 400, basePowerDraw: 1.5, basePrice: 50_000, availableAt: 'bigtech' }, - custom: { type: 'custom', name: 'Custom ASIC', flopsPerUnit: 800, basePowerDraw: 1.0, basePrice: 80_000, availableAt: 'agi' }, -}; - export const LOCATION_CONFIGS: Record = { 'us-west': { id: 'us-west', name: 'US West (Oregon)', energyCostMultiplier: 1.0, latencyTier: 1, regulatoryStrictness: 0.3, politicalStability: 0.9, availableAt: 'startup' }, 'us-east': { id: 'us-east', name: 'US East (Virginia)', energyCostMultiplier: 1.1, latencyTier: 1, regulatoryStrictness: 0.3, politicalStability: 0.9, availableAt: 'startup' }, @@ -68,17 +129,3 @@ export const LOCATION_CONFIGS: Record = { 'asia-south': { id: 'asia-south', name: 'Asia South (Mumbai)', energyCostMultiplier: 0.6, latencyTier: 3, regulatoryStrictness: 0.2, politicalStability: 0.7, availableAt: 'bigtech' }, 'middle-east': { id: 'middle-east', name: 'Middle East (UAE)', energyCostMultiplier: 0.5, latencyTier: 3, regulatoryStrictness: 0.1, politicalStability: 0.6, availableAt: 'bigtech' }, }; - -export const INITIAL_INFRASTRUCTURE: InfrastructureState = { - dataCenters: [], - gpuMarketPrices: { - consumer: 800, - t4: 5_000, - a100: 15_000, - h100: 35_000, - b200: 50_000, - custom: 80_000, - }, - totalFlops: 0, - totalUptime: 1, -}; diff --git a/packages/shared/src/types/research.ts b/packages/shared/src/types/research.ts index 7846a34..4e9bff9 100644 --- a/packages/shared/src/types/research.ts +++ b/packages/shared/src/types/research.ts @@ -31,9 +31,9 @@ export interface ResearchNode { } export interface ResearchEffect { - type: 'unlock_gpu' | 'unlock_model_tier' | 'efficiency_boost' - | 'capability_boost' | 'cost_reduction' | 'unlock_feature' - | 'unlock_product_line' | 'safety_boost'; + type: 'unlock_gpu' | 'unlock_rack' | 'unlock_dc_tier' | 'unlock_model_tier' + | 'efficiency_boost' | 'capability_boost' | 'cost_reduction' + | 'unlock_feature' | 'unlock_product_line' | 'safety_boost'; target: string; value: number; }