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:
@@ -20,6 +20,7 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) {
|
|||||||
const reputation = useGameStore((s) => s.reputation.score);
|
const reputation = useGameStore((s) => s.reputation.score);
|
||||||
const achievements = useGameStore((s) => s.achievements.unlocked.length);
|
const achievements = useGameStore((s) => s.achievements.unlocked.length);
|
||||||
const dataCenters = useGameStore((s) => s.infrastructure.dataCenters.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 eraLabel = era === 'startup' ? 'Startup' : era === 'scaleup' ? 'Scale-up' : era === 'bigtech' ? 'Big Tech' : 'AGI';
|
||||||
const hours = Math.floor(totalPlayTime / 3600);
|
const hours = Math.floor(totalPlayTime / 3600);
|
||||||
@@ -32,7 +33,7 @@ export function CompanyStatsCard({ onClose }: { onClose: () => void }) {
|
|||||||
`Valuation: ${formatMoney(valuation)}`,
|
`Valuation: ${formatMoney(valuation)}`,
|
||||||
`Subscribers: ${formatNumber(subscribers)} | Models: ${models}`,
|
`Subscribers: ${formatNumber(subscribers)} | Models: ${models}`,
|
||||||
`Best Model: ${bestModel.toFixed(1)}/100 | Reputation: ${reputation}/100`,
|
`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');
|
].join('\n');
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function DashboardPage() {
|
|||||||
|
|
||||||
{dataCenters.length === 0 && (
|
{dataCenters.length === 0 && (
|
||||||
<TutorialHint id="welcome">
|
<TutorialHint id="welcome">
|
||||||
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.
|
||||||
</TutorialHint>
|
</TutorialHint>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +1,342 @@
|
|||||||
import { useState } from 'react';
|
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 { useGameStore } from '@/store';
|
||||||
import { formatMoney, formatNumber, formatPercent, GPU_CONFIGS, LOCATION_CONFIGS } from '@ai-tycoon/shared';
|
import {
|
||||||
import type { GpuType, LocationId } from '@ai-tycoon/shared';
|
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 ERA_ORDER: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
|
||||||
const dataCenters = useGameStore((s) => s.infrastructure.dataCenters);
|
|
||||||
const gpuPrices = useGameStore((s) => s.infrastructure.gpuMarketPrices);
|
const STAGE_LABELS: Record<PipelineStage, string> = {
|
||||||
|
ordered: 'Ordered',
|
||||||
|
manufacturing: 'Manufacturing',
|
||||||
|
receiving: 'Receiving',
|
||||||
|
installation: 'Installation',
|
||||||
|
testing: 'Testing',
|
||||||
|
repair: 'Repair',
|
||||||
|
};
|
||||||
|
|
||||||
|
const STAGE_COLORS: Record<PipelineStage, string> = {
|
||||||
|
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 (
|
||||||
|
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||||
|
<h3 className="text-sm font-semibold text-surface-300 uppercase mb-3">Rack Pipeline</h3>
|
||||||
|
<div className="grid grid-cols-6 gap-2">
|
||||||
|
{grouped.map(({ stage, orders }) => (
|
||||||
|
<div key={stage}>
|
||||||
|
<div className="text-[10px] text-surface-400 uppercase mb-1.5 text-center">
|
||||||
|
{STAGE_LABELS[stage]} ({orders.length})
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5 min-h-[60px]">
|
||||||
|
{orders.map(order => (
|
||||||
|
<PipelineCard key={order.id} order={order} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PipelineCard({ order }: { order: RackOrder }) {
|
||||||
|
const sku = RACK_SKU_CONFIGS[order.skuId];
|
||||||
|
const progress = order.stageTotal > 0 ? order.stageProgress / order.stageTotal : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-surface-800 border border-surface-600 rounded p-1.5 text-[11px]">
|
||||||
|
<div className="font-medium truncate">{sku.name}</div>
|
||||||
|
<div className="w-full bg-surface-700 rounded-full h-1 mt-1">
|
||||||
|
<div
|
||||||
|
className={`h-1 rounded-full ${STAGE_COLORS[order.stage]}`}
|
||||||
|
style={{ width: `${Math.min(100, progress * 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{order.repairCount > 0 && (
|
||||||
|
<div className="text-danger mt-0.5 flex items-center gap-0.5">
|
||||||
|
<Wrench size={8} /> {order.repairCount}x
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center justify-between text-[11px] mb-0.5">
|
||||||
|
<span className="text-surface-400 flex items-center gap-1"><Icon size={12} />{label}</span>
|
||||||
|
<span className="text-surface-300">{formatNumber(used)}/{formatNumber(max)} {unit}</span>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-surface-700 rounded-full h-2">
|
||||||
|
<div className={`h-2 rounded-full ${color} transition-all`} style={{ width: `${Math.min(100, pct * 100)}%` }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 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 (
|
||||||
|
<div className="bg-surface-900 border border-amber-500/30 rounded-xl p-4">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold">{dc.name}</h3>
|
||||||
|
<div className="text-xs text-amber-400">Under Construction — {tierConfig.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-surface-400">
|
||||||
|
<MapPin size={12} />
|
||||||
|
{LOCATION_CONFIGS[dc.location].name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="w-full bg-surface-700 rounded-full h-3">
|
||||||
|
<div className="h-3 rounded-full bg-amber-500 transition-all" style={{ width: `${pct * 100}%` }} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-surface-400 mt-1 text-right">
|
||||||
|
{Math.round(pct * 100)}% — {dc.constructionTotal - dc.constructionProgress}s remaining
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="font-semibold text-lg">{dc.name}</h3>
|
||||||
|
<span className="text-[10px] px-1.5 py-0.5 rounded bg-accent/20 text-accent-light uppercase">{dc.tier}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 text-sm text-surface-400">
|
||||||
|
<span className="flex items-center gap-1"><MapPin size={12} />{LOCATION_CONFIGS[dc.location].name}</span>
|
||||||
|
<span>Uptime: {formatPercent(dc.currentUptime)}</span>
|
||||||
|
<span className="text-danger">Cost: {formatMoney(dc.energyCostPerTick + dc.maintenanceCostPerTick)}/s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setExpanded(!expanded)} className="text-surface-400 hover:text-surface-200">
|
||||||
|
{expanded ? <ChevronUp size={18} /> : <ChevronDown size={18} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4 mb-3">
|
||||||
|
<CapacityBar label="Slots" used={dc.usedSlots} max={tierConfig.rackSlots} unit="" icon={HardDrive} />
|
||||||
|
<CapacityBar label="Power" used={dc.usedPowerKW} max={tierConfig.powerBudgetKW} unit="kW" icon={Zap} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{expanded && (
|
||||||
|
<>
|
||||||
|
{dc.racks.length > 0 && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<h4 className="text-xs text-surface-400 uppercase mb-1.5">Production Racks ({dc.racks.length})</h4>
|
||||||
|
<div className="grid grid-cols-4 gap-1.5">
|
||||||
|
{dc.racks.map(rack => {
|
||||||
|
const sku = RACK_SKU_CONFIGS[rack.skuId];
|
||||||
|
return (
|
||||||
|
<div key={rack.id} className={`text-[11px] rounded p-1.5 border ${
|
||||||
|
rack.isHealthy
|
||||||
|
? 'bg-surface-800 border-surface-600'
|
||||||
|
: 'bg-danger/10 border-danger/30'
|
||||||
|
}`}>
|
||||||
|
<div className="font-medium truncate">{sku.name}</div>
|
||||||
|
<div className="text-surface-400">{formatNumber(sku.flopsPerRack)} FLOPS</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-3">
|
||||||
|
<h4 className="text-xs text-surface-400 uppercase mb-1.5">Order Racks</h4>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{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 (
|
||||||
|
<button
|
||||||
|
key={sku.id}
|
||||||
|
onClick={() => orderRack(dc.id, sku.id)}
|
||||||
|
disabled={disabled}
|
||||||
|
className="bg-surface-800 hover:bg-surface-700 border border-surface-600 rounded-lg px-2.5 py-1.5 text-xs disabled:opacity-40 disabled:cursor-not-allowed transition-colors text-left"
|
||||||
|
title={!hasSlot ? 'No slots available' : !hasPower ? 'Exceeds power budget' : ''}
|
||||||
|
>
|
||||||
|
<div className="font-medium">{sku.name}</div>
|
||||||
|
<div className="text-surface-400">{formatNumber(sku.flopsPerRack)} FLOPS · {sku.powerDrawKW}kW · {formatMoney(sku.baseCost)}</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xs text-surface-400 uppercase mb-1.5">Upgrades</h4>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => upgradeDataCenter(dc.id, 'cooling')}
|
||||||
|
disabled={dc.coolingLevel >= 1.0 || money < tierConfig.baseCost * 0.25}
|
||||||
|
className="flex items-center gap-1.5 bg-surface-800 hover:bg-surface-700 border border-surface-600 rounded-lg px-3 py-2 text-xs disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Thermometer size={14} />
|
||||||
|
Cooling Lv{Math.round(dc.coolingLevel * 10)}
|
||||||
|
<span className="text-surface-400">{formatMoney(tierConfig.baseCost * 0.25)}</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => upgradeDataCenter(dc.id, 'redundancy')}
|
||||||
|
disabled={dc.redundancyLevel >= 1.0 || money < tierConfig.baseCost * 0.25}
|
||||||
|
className="flex items-center gap-1.5 bg-surface-800 hover:bg-surface-700 border border-surface-600 rounded-lg px-3 py-2 text-xs disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
||||||
|
>
|
||||||
|
<Shield size={14} />
|
||||||
|
Redundancy Lv{Math.round(dc.redundancyLevel * 10)}
|
||||||
|
<span className="text-surface-400">{formatMoney(tierConfig.baseCost * 0.25)}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 buildDataCenter = useGameStore((s) => s.buildDataCenter);
|
||||||
const buyGpu = useGameStore((s) => s.buyGpu);
|
|
||||||
|
|
||||||
const [showNewDC, setShowNewDC] = useState(false);
|
const [name, setName] = useState('');
|
||||||
const [newDCName, setNewDCName] = useState('');
|
const [location, setLocation] = useState<LocationId>('us-west');
|
||||||
const [newDCLocation, setNewDCLocation] = useState<LocationId>('us-west');
|
const [tier, setTier] = useState<DCTier>('small');
|
||||||
|
|
||||||
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'];
|
const currentEraIdx = ERA_ORDER.indexOf(era);
|
||||||
const currentEraIdx = eraOrder.indexOf(era);
|
|
||||||
|
|
||||||
const availableLocations = Object.values(LOCATION_CONFIGS).filter(
|
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(
|
const availableTiers = Object.values(DC_TIER_CONFIGS).filter(t => {
|
||||||
gpu => eraOrder.indexOf(gpu.availableAt) <= currentEraIdx,
|
if (ERA_ORDER.indexOf(t.requiredEra) > currentEraIdx) return false;
|
||||||
);
|
if (t.requiredResearch && !completedResearch.includes(t.requiredResearch)) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const handleBuildDC = () => {
|
const tierConfig = DC_TIER_CONFIGS[tier];
|
||||||
if (!newDCName.trim()) return;
|
|
||||||
buildDataCenter(newDCName.trim(), newDCLocation);
|
const handleBuild = () => {
|
||||||
setNewDCName('');
|
if (!name.trim()) return;
|
||||||
setShowNewDC(false);
|
buildDataCenter(name.trim(), location, tier);
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="bg-surface-900 border border-accent/30 rounded-xl p-4 space-y-4">
|
||||||
|
<h3 className="font-semibold">Build New Data Center</h3>
|
||||||
|
<div className="grid grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-surface-400 mb-1">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => 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
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-surface-400 mb-1">Location</label>
|
||||||
|
<select
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value as LocationId)}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
{availableLocations.map(loc => (
|
||||||
|
<option key={loc.id} value={loc.id}>{loc.name} (Energy: {loc.energyCostMultiplier}x)</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-surface-400 mb-1">Tier</label>
|
||||||
|
<select
|
||||||
|
value={tier}
|
||||||
|
onChange={(e) => setTier(e.target.value as DCTier)}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
{availableTiers.map(t => (
|
||||||
|
<option key={t.tier} value={t.tier}>{t.name} ({t.rackSlots} slots, {t.powerBudgetKW}kW)</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-sm text-surface-400">
|
||||||
|
<span>Cost: {formatMoney(tierConfig.baseCost)} · Build time: {tierConfig.buildTimeTicks}s</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button onClick={onClose} className="px-4 py-2 rounded text-sm text-surface-400 hover:text-surface-200">Cancel</button>
|
||||||
|
<button
|
||||||
|
onClick={handleBuild}
|
||||||
|
disabled={money < tierConfig.baseCost || !name.trim()}
|
||||||
|
className="px-4 py-2 rounded bg-accent hover:bg-accent-dark text-white text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
Build
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InfrastructurePage() {
|
||||||
|
const dataCenters = useGameStore((s) => s.infrastructure.dataCenters);
|
||||||
|
const [showNewDC, setShowNewDC] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<h2 className="text-2xl font-bold">Infrastructure</h2>
|
<h2 className="text-2xl font-bold">Infrastructure</h2>
|
||||||
<button
|
<button
|
||||||
@@ -47,51 +348,11 @@ export function InfrastructurePage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showNewDC && (
|
{showNewDC && <BuildDCPanel onClose={() => setShowNewDC(false)} />}
|
||||||
<div className="bg-surface-900 border border-accent/30 rounded-xl p-4 space-y-4">
|
|
||||||
<h3 className="font-semibold">Build New Data Center</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs text-surface-400 mb-1">Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={newDCName}
|
|
||||||
onChange={(e) => 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
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs text-surface-400 mb-1">Location</label>
|
|
||||||
<select
|
|
||||||
value={newDCLocation}
|
|
||||||
onChange={(e) => setNewDCLocation(e.target.value as LocationId)}
|
|
||||||
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"
|
|
||||||
>
|
|
||||||
{availableLocations.map(loc => (
|
|
||||||
<option key={loc.id} value={loc.id}>{loc.name} (Energy: {loc.energyCostMultiplier}x)</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="text-sm text-surface-400">Cost: {formatMoney(10_000)}</span>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<button onClick={() => setShowNewDC(false)} className="px-4 py-2 rounded text-sm text-surface-400 hover:text-surface-200">Cancel</button>
|
|
||||||
<button
|
|
||||||
onClick={handleBuildDC}
|
|
||||||
disabled={money < 10_000 || !newDCName.trim()}
|
|
||||||
className="px-4 py-2 rounded bg-accent hover:bg-accent-dark text-white text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
>
|
|
||||||
Build
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{dataCenters.length === 0 ? (
|
<PipelineKanban />
|
||||||
|
|
||||||
|
{dataCenters.length === 0 && !showNewDC ? (
|
||||||
<div className="bg-surface-900 border border-surface-700 rounded-xl p-8 text-center text-surface-500">
|
<div className="bg-surface-900 border border-surface-700 rounded-xl p-8 text-center text-surface-500">
|
||||||
<Server size={48} className="mx-auto mb-4 opacity-50" />
|
<Server size={48} className="mx-auto mb-4 opacity-50" />
|
||||||
<p>No data centers yet. Build your first one to start hosting AI models.</p>
|
<p>No data centers yet. Build your first one to start hosting AI models.</p>
|
||||||
@@ -99,75 +360,10 @@ export function InfrastructurePage() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{dataCenters.map(dc => (
|
{dataCenters.map(dc => (
|
||||||
<div key={dc.id} className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
<DataCenterCard key={dc.id} dcId={dc.id} />
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-lg">{dc.name}</h3>
|
|
||||||
<div className="flex items-center gap-2 text-sm text-surface-400">
|
|
||||||
<MapPin size={14} />
|
|
||||||
{LOCATION_CONFIGS[dc.location].name}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-right text-sm">
|
|
||||||
<div className="text-surface-400">Uptime: <span className="text-surface-200">{formatPercent(dc.currentUptime)}</span></div>
|
|
||||||
<div className="text-surface-400">Cost: <span className="text-danger">{formatMoney(dc.energyCostPerTick + dc.maintenanceCostPerTick)}/s</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mb-4">
|
|
||||||
<h4 className="text-xs text-surface-400 uppercase mb-2">GPUs</h4>
|
|
||||||
{dc.gpus.length === 0 ? (
|
|
||||||
<p className="text-sm text-surface-500">No GPUs installed</p>
|
|
||||||
) : (
|
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{dc.gpus.map(inv => (
|
|
||||||
<div key={inv.type} className="bg-surface-800 rounded-lg p-2 text-sm">
|
|
||||||
<div className="font-medium">{GPU_CONFIGS[inv.type].name}</div>
|
|
||||||
<div className="text-surface-400 text-xs">
|
|
||||||
{inv.healthyCount}/{inv.count} healthy · {formatNumber(inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit)} FLOPS
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h4 className="text-xs text-surface-400 uppercase mb-2">Buy GPUs</h4>
|
|
||||||
<div className="flex gap-2 flex-wrap">
|
|
||||||
{availableGpus.map(gpu => (
|
|
||||||
<button
|
|
||||||
key={gpu.type}
|
|
||||||
onClick={() => buyGpu(dc.id, gpu.type, 1)}
|
|
||||||
disabled={money < gpuPrices[gpu.type]}
|
|
||||||
className="flex items-center gap-2 bg-surface-800 hover:bg-surface-700 border border-surface-600 rounded-lg px-3 py-2 text-sm disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
||||||
>
|
|
||||||
<Cpu size={14} />
|
|
||||||
{gpu.name}
|
|
||||||
<span className="text-surface-400">{formatMoney(gpuPrices[gpu.type])}</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
|
||||||
<h3 className="text-sm font-medium text-surface-400 mb-3">GPU Market Prices</h3>
|
|
||||||
<div className="grid grid-cols-3 gap-3">
|
|
||||||
{availableGpus.map(gpu => (
|
|
||||||
<div key={gpu.type} className="flex items-center justify-between bg-surface-800 rounded-lg p-3">
|
|
||||||
<div>
|
|
||||||
<div className="text-sm font-medium">{gpu.name}</div>
|
|
||||||
<div className="text-xs text-surface-400">{formatNumber(gpu.flopsPerUnit)} FLOPS/unit</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-sm font-mono">{formatMoney(gpuPrices[gpu.type])}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-37
@@ -6,8 +6,8 @@ import type {
|
|||||||
ResearchState, ModelsState, MarketState,
|
ResearchState, ModelsState, MarketState,
|
||||||
CompetitorState, TalentState, DataState,
|
CompetitorState, TalentState, DataState,
|
||||||
ReputationState, EventState, AchievementState,
|
ReputationState, EventState, AchievementState,
|
||||||
DataCenter, GpuType, GpuInventory, TrainingJob,
|
DataCenter, DCTier, RackSkuId, TrainingJob,
|
||||||
ActiveResearch, EventConsequence, OwnedDataset,
|
ActiveResearch, EventConsequence, OwnedDataset, LocationId,
|
||||||
} from '@ai-tycoon/shared';
|
} from '@ai-tycoon/shared';
|
||||||
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
|
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
|
||||||
import {
|
import {
|
||||||
@@ -16,7 +16,8 @@ import {
|
|||||||
INITIAL_RESEARCH, INITIAL_MODELS, INITIAL_MARKET,
|
INITIAL_RESEARCH, INITIAL_MODELS, INITIAL_MARKET,
|
||||||
INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA,
|
INITIAL_COMPETITORS, INITIAL_TALENT, INITIAL_DATA,
|
||||||
INITIAL_REPUTATION, INITIAL_EVENTS, INITIAL_ACHIEVEMENTS,
|
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,
|
FUNDING_ROUNDS,
|
||||||
OPEN_SOURCE_REPUTATION_BOOST,
|
OPEN_SOURCE_REPUTATION_BOOST,
|
||||||
uuid,
|
uuid,
|
||||||
@@ -48,8 +49,9 @@ interface Actions {
|
|||||||
setGameSpeed: (speed: GameSpeed) => void;
|
setGameSpeed: (speed: GameSpeed) => void;
|
||||||
togglePause: () => void;
|
togglePause: () => void;
|
||||||
setTrainingAllocation: (ratio: number) => void;
|
setTrainingAllocation: (ratio: number) => void;
|
||||||
buyGpu: (dataCenterId: string, gpuType: GpuType, count: number) => void;
|
buildDataCenter: (name: string, location: LocationId, tier: DCTier) => void;
|
||||||
buildDataCenter: (name: string, location: DataCenter['location']) => void;
|
orderRack: (dataCenterId: string, skuId: RackSkuId) => void;
|
||||||
|
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => void;
|
||||||
startTraining: (job: Omit<TrainingJob, 'progressTicks'>) => void;
|
startTraining: (job: Omit<TrainingJob, 'progressTicks'>) => void;
|
||||||
deployModel: (modelId: string) => void;
|
deployModel: (modelId: string) => void;
|
||||||
setProductPricing: (productLineId: string, field: string, value: number) => 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 },
|
compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio },
|
||||||
})),
|
})),
|
||||||
|
|
||||||
buyGpu: (dataCenterId, gpuType, count) => set((s) => {
|
buildDataCenter: (name, location, tier) => set((s) => {
|
||||||
const price = s.infrastructure.gpuMarketPrices[gpuType] * count;
|
const tierConfig = DC_TIER_CONFIGS[tier];
|
||||||
if (s.economy.money < price) return s;
|
if (s.economy.money < tierConfig.baseCost) return s;
|
||||||
|
|
||||||
const dataCenters = s.infrastructure.dataCenters.map(dc => {
|
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
|
||||||
if (dc.id !== dataCenterId) return dc;
|
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(tierConfig.requiredEra)) return s;
|
||||||
const existingIdx = dc.gpus.findIndex(g => g.type === gpuType);
|
if (tierConfig.requiredResearch && !s.research.completedResearch.includes(tierConfig.requiredResearch)) return s;
|
||||||
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 };
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
const isFirstDC = s.infrastructure.dataCenters.length === 0;
|
||||||
economy: { ...s.economy, money: s.economy.money - price },
|
const buildTime = isFirstDC ? tierConfig.firstBuildTimeTicks : tierConfig.buildTimeTicks;
|
||||||
infrastructure: { ...s.infrastructure, dataCenters },
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
|
|
||||||
buildDataCenter: (name, location) => set((s) => {
|
|
||||||
const buildCost = 10_000;
|
|
||||||
if (s.economy.money < buildCost) return s;
|
|
||||||
|
|
||||||
const dc: DataCenter = {
|
const dc: DataCenter = {
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
name,
|
name,
|
||||||
location,
|
location,
|
||||||
gpus: [],
|
tier,
|
||||||
maxCapacity: 100,
|
status: 'constructing',
|
||||||
coolingLevel: 0.5,
|
constructionProgress: 0,
|
||||||
redundancyLevel: 0.3,
|
constructionTotal: buildTime,
|
||||||
|
racks: [],
|
||||||
|
coolingLevel: 0,
|
||||||
|
redundancyLevel: 0,
|
||||||
currentUptime: 1,
|
currentUptime: 1,
|
||||||
energyCostPerTick: 0,
|
energyCostPerTick: 0,
|
||||||
maintenanceCostPerTick: 0,
|
maintenanceCostPerTick: 0,
|
||||||
|
usedSlots: 0,
|
||||||
|
usedPowerKW: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
economy: { ...s.economy, money: s.economy.money - buildCost },
|
economy: { ...s.economy, money: s.economy.money - tierConfig.baseCost },
|
||||||
infrastructure: {
|
infrastructure: {
|
||||||
...s.infrastructure,
|
...s.infrastructure,
|
||||||
dataCenters: [...s.infrastructure.dataCenters, dc],
|
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) => ({
|
startTraining: (job) => set((s) => ({
|
||||||
models: {
|
models: {
|
||||||
...s.models,
|
...s.models,
|
||||||
@@ -419,10 +468,28 @@ export const useGameStore = create<Store>()(
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'ai-tycoon-save',
|
name: 'ai-tycoon-save',
|
||||||
|
version: SAVE_VERSION,
|
||||||
partialize: (state) => {
|
partialize: (state) => {
|
||||||
const { activePage, notifications, ...rest } = state;
|
const { activePage, notifications, ...rest } = state;
|
||||||
return rest;
|
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;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -79,11 +79,11 @@ export const ACHIEVEMENT_DEFINITIONS: AchievementDefinition[] = [
|
|||||||
condition: { field: 'meta._eraIndex', operator: 'gte', value: 3 },
|
condition: { field: 'meta._eraIndex', operator: 'gte', value: 3 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'gpu-hoarder',
|
id: 'rack-hoarder',
|
||||||
name: 'GPU Hoarder',
|
name: 'Rack Hoarder',
|
||||||
description: 'Own 100 or more GPUs across all data centers.',
|
description: 'Have 50 or more production racks across all data centers.',
|
||||||
icon: 'Cpu',
|
icon: 'Cpu',
|
||||||
condition: { field: 'infrastructure._totalGpuCount', operator: 'gte', value: 100 },
|
condition: { field: 'infrastructure.totalRackCount', operator: 'gte', value: 50 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'research-pioneer',
|
id: 'research-pioneer',
|
||||||
|
|||||||
@@ -25,32 +25,32 @@ export const TECH_TREE: ResearchNode[] = [
|
|||||||
{
|
{
|
||||||
id: 'advanced-gpu-arch',
|
id: 'advanced-gpu-arch',
|
||||||
name: 'Advanced GPU Architecture',
|
name: 'Advanced GPU Architecture',
|
||||||
description: 'Unlocks procurement of NVIDIA A100 datacenter GPUs.',
|
description: 'Unlocks procurement of NVIDIA A100 rack configurations.',
|
||||||
era: 'startup',
|
era: 'startup',
|
||||||
category: 'infrastructure',
|
category: 'infrastructure',
|
||||||
prerequisites: [],
|
prerequisites: [],
|
||||||
cost: { researchPoints: 0, compute: 10, ticks: 90 },
|
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',
|
id: 'next-gen-gpu',
|
||||||
name: 'Next-Gen GPU Architecture',
|
name: 'Next-Gen GPU Architecture',
|
||||||
description: 'Unlocks procurement of NVIDIA H100 GPUs.',
|
description: 'Unlocks procurement of NVIDIA H100 rack configurations.',
|
||||||
era: 'scaleup',
|
era: 'scaleup',
|
||||||
category: 'infrastructure',
|
category: 'infrastructure',
|
||||||
prerequisites: ['advanced-gpu-arch'],
|
prerequisites: ['advanced-gpu-arch'],
|
||||||
cost: { researchPoints: 2, compute: 40, ticks: 240 },
|
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',
|
id: 'frontier-compute',
|
||||||
name: 'Frontier Compute',
|
name: 'Frontier Compute',
|
||||||
description: 'Unlocks procurement of NVIDIA B200 GPUs.',
|
description: 'Unlocks procurement of NVIDIA B200 rack configurations.',
|
||||||
era: 'bigtech',
|
era: 'bigtech',
|
||||||
category: 'infrastructure',
|
category: 'infrastructure',
|
||||||
prerequisites: ['next-gen-gpu'],
|
prerequisites: ['next-gen-gpu'],
|
||||||
cost: { researchPoints: 5, compute: 200, ticks: 480 },
|
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',
|
id: 'custom-silicon',
|
||||||
@@ -60,7 +60,47 @@ export const TECH_TREE: ResearchNode[] = [
|
|||||||
category: 'infrastructure',
|
category: 'infrastructure',
|
||||||
prerequisites: ['frontier-compute'],
|
prerequisites: ['frontier-compute'],
|
||||||
cost: { researchPoints: 10, compute: 500, ticks: 900 },
|
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',
|
id: 'distributed-training',
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ const ERA_INDEX: Record<string, number> = { startup: 0, scaleup: 1, bigtech: 2,
|
|||||||
function getFieldValue(state: GameState, field: string): number {
|
function getFieldValue(state: GameState, field: string): number {
|
||||||
if (field === 'meta._eraIndex') return ERA_INDEX[state.meta.currentEra] ?? 0;
|
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 === '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('.');
|
const parts = field.split('.');
|
||||||
let current: unknown = state;
|
let current: unknown = state;
|
||||||
for (const part of parts) {
|
for (const part of parts) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export function processEconomy(
|
|||||||
state: GameState,
|
state: GameState,
|
||||||
market: MarketTickResult,
|
market: MarketTickResult,
|
||||||
infrastructure: InfrastructureState,
|
infrastructure: InfrastructureState,
|
||||||
|
extraCosts: number = 0,
|
||||||
): EconomyState {
|
): EconomyState {
|
||||||
const revenue = market.apiRevenue + market.subscriptionRevenue;
|
const revenue = market.apiRevenue + market.subscriptionRevenue;
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export function processEconomy(
|
|||||||
const eraIdx = ['startup', 'scaleup', 'bigtech', 'agi'].indexOf(state.meta.currentEra);
|
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 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;
|
const money = state.economy.money + revenue - expenses;
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
import {
|
||||||
GPU_CONFIGS,
|
|
||||||
LOCATION_CONFIGS,
|
LOCATION_CONFIGS,
|
||||||
GPU_PRICE_VOLATILITY,
|
RACK_SKU_CONFIGS,
|
||||||
GPU_FAILURE_RATE_BASE,
|
DC_TIER_CONFIGS,
|
||||||
REDUNDANCY_FAILURE_REDUCTION,
|
|
||||||
BASE_ENERGY_COST_PER_FLOP,
|
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';
|
} from '@ai-tycoon/shared';
|
||||||
import type { GpuType } from '@ai-tycoon/shared';
|
import type { TickNotification } from '../tick';
|
||||||
|
|
||||||
export function processInfrastructure(state: GameState): InfrastructureState {
|
export interface InfraTickResult {
|
||||||
const dataCenters = state.infrastructure.dataCenters.map(dc => {
|
infrastructure: InfrastructureState;
|
||||||
const location = LOCATION_CONFIGS[dc.location];
|
notifications: TickNotification[];
|
||||||
|
repairCosts: number;
|
||||||
|
}
|
||||||
|
|
||||||
const gpus = dc.gpus.map(inv => {
|
const PIPELINE_ADVANCE_ORDER: PipelineStage[] = [
|
||||||
const failureRate = GPU_FAILURE_RATE_BASE * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION);
|
'ordered', 'manufacturing', 'receiving', 'installation', 'testing',
|
||||||
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 };
|
|
||||||
});
|
|
||||||
|
|
||||||
let totalFlops = 0;
|
function nextStage(stage: PipelineStage): PipelineStage | 'production' {
|
||||||
let totalPower = 0;
|
const idx = PIPELINE_ADVANCE_ORDER.indexOf(stage);
|
||||||
let totalGpuCount = 0;
|
if (idx === -1 || idx === PIPELINE_ADVANCE_ORDER.length - 1) return 'production';
|
||||||
for (const inv of gpus) {
|
return PIPELINE_ADVANCE_ORDER[idx + 1];
|
||||||
const config = GPU_CONFIGS[inv.type];
|
}
|
||||||
totalFlops += inv.healthyCount * config.flopsPerUnit;
|
|
||||||
totalPower += inv.healthyCount * config.basePowerDraw;
|
function stageTotal(stage: PipelineStage, order: RackOrder): number {
|
||||||
totalGpuCount += inv.count;
|
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 };
|
||||||
}
|
}
|
||||||
|
return { ...dc, constructionProgress: newProgress };
|
||||||
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 };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const gpuMarketPrices = { ...state.infrastructure.gpuMarketPrices };
|
// --- Phase 2: Advance Rack Pipeline ---
|
||||||
for (const gpuType of Object.keys(gpuMarketPrices) as GpuType[]) {
|
const rackPipeline: RackOrder[] = [];
|
||||||
const basePrice = GPU_CONFIGS[gpuType].basePrice;
|
const newRacks: Rack[] = [];
|
||||||
const variation = (Math.random() - 0.5) * 2 * GPU_PRICE_VOLATILITY;
|
|
||||||
const currentPrice = gpuMarketPrices[gpuType];
|
for (const order of state.infrastructure.rackPipeline) {
|
||||||
const newPrice = currentPrice * (1 + variation);
|
const speed = stageSpeed(order.stage, engEff, opsEff);
|
||||||
gpuMarketPrices[gpuType] = Math.max(basePrice * 0.7, Math.min(basePrice * 1.5, newPrice));
|
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 totalFlops = 0;
|
||||||
let totalUptime = 0;
|
let totalUptime = 0;
|
||||||
let dcCount = 0;
|
let totalRackCount = 0;
|
||||||
for (const dc of dataCenters) {
|
let dcWithRacks = 0;
|
||||||
for (const inv of dc.gpus) {
|
|
||||||
totalFlops += inv.healthyCount * GPU_CONFIGS[inv.type].flopsPerUnit;
|
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 {
|
return {
|
||||||
dataCenters,
|
infrastructure: {
|
||||||
gpuMarketPrices,
|
dataCenters,
|
||||||
totalFlops,
|
rackPipeline,
|
||||||
totalUptime: dcCount > 0 ? totalUptime / dcCount : 1,
|
totalFlops,
|
||||||
|
totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1,
|
||||||
|
totalRackCount,
|
||||||
|
},
|
||||||
|
notifications,
|
||||||
|
repairCosts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ export function setAchievementDefinitions(defs: AchievementDefinition[]) {
|
|||||||
export function processTick(state: GameState): Partial<GameState> {
|
export function processTick(state: GameState): Partial<GameState> {
|
||||||
const notifications: TickNotification[] = [];
|
const notifications: TickNotification[] = [];
|
||||||
|
|
||||||
const infrastructure = processInfrastructure(state);
|
const infraResult = processInfrastructure(state);
|
||||||
|
const infrastructure = infraResult.infrastructure;
|
||||||
|
notifications.push(...infraResult.notifications);
|
||||||
|
|
||||||
const stateWithInfra = { ...state, infrastructure };
|
const stateWithInfra = { ...state, infrastructure };
|
||||||
const modelResult = processModels(stateWithInfra);
|
const modelResult = processModels(stateWithInfra);
|
||||||
@@ -82,7 +84,7 @@ export function processTick(state: GameState): Partial<GameState> {
|
|||||||
type: 'danger',
|
type: 'danger',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const economy = processEconomy(stateWithTalent, market, infrastructure);
|
const economy = processEconomy(stateWithTalent, market, infrastructure, infraResult.repairCosts);
|
||||||
const data = processData(stateWithTalent);
|
const data = processData(stateWithTalent);
|
||||||
const competitors = processCompetitors(stateWithTalent);
|
const competitors = processCompetitors(stateWithTalent);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { DCTier, DCTierConfig, RackSkuId, RackSkuConfig } from '../types/infrastructure';
|
||||||
|
|
||||||
export const TICK_INTERVAL_MS = 1000;
|
export const TICK_INTERVAL_MS = 1000;
|
||||||
export const MAX_OFFLINE_TICKS = 86_400;
|
export const MAX_OFFLINE_TICKS = 86_400;
|
||||||
export const OFFLINE_EFFICIENCY = 0.8;
|
export const OFFLINE_EFFICIENCY = 0.8;
|
||||||
@@ -10,7 +12,6 @@ export const MAX_REPUTATION_HISTORY = 500;
|
|||||||
|
|
||||||
export const STARTING_MONEY = 50_000;
|
export const STARTING_MONEY = 50_000;
|
||||||
export const BASE_ENERGY_COST_PER_FLOP = 0.001;
|
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_BASE_TICKS = 120;
|
||||||
export const TRAINING_COMPUTE_MULTIPLIER = 0.8;
|
export const TRAINING_COMPUTE_MULTIPLIER = 0.8;
|
||||||
@@ -37,9 +38,213 @@ export const ERA_THRESHOLDS = {
|
|||||||
agi: { revenue: 100_000_000, capability: 90, reputation: 70 },
|
agi: { revenue: 100_000_000, capability: 90, reputation: 70 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GPU_PRICE_VOLATILITY = 0.02;
|
// --- Data Center Tier Configs ---
|
||||||
export const GPU_FAILURE_RATE_BASE = 0.0001;
|
|
||||||
|
export const DC_TIER_CONFIGS: Record<DCTier, DCTierConfig> = {
|
||||||
|
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<RackSkuId, RackSkuConfig> = {
|
||||||
|
'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 REDUNDANCY_FAILURE_REDUCTION = 0.5;
|
||||||
|
export const DC_UPGRADE_COST_FRACTION = 0.25;
|
||||||
|
export const DC_UPGRADE_INCREMENT = 0.1;
|
||||||
|
|
||||||
export const FUNDING_ROUNDS = {
|
export const FUNDING_ROUNDS = {
|
||||||
seed: { amount: 100_000, dilution: 0.10, requirements: { minRevenue: 100, minUsers: 0, minReputation: 0 } },
|
seed: { amount: 100_000, dilution: 0.10, requirements: { minRevenue: 100, minUsers: 0, minReputation: 0 } },
|
||||||
|
|||||||
@@ -60,4 +60,4 @@ export const INITIAL_SETTINGS: GameSettings = {
|
|||||||
sfxVolume: 0.7,
|
sfxVolume: 0.7,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SAVE_VERSION = 1;
|
export const SAVE_VERSION = 2;
|
||||||
|
|||||||
@@ -1,33 +1,112 @@
|
|||||||
import type { Era } from './gameState';
|
import type { Era } from './gameState';
|
||||||
|
|
||||||
export interface InfrastructureState {
|
// --- Data Center ---
|
||||||
dataCenters: DataCenter[];
|
|
||||||
gpuMarketPrices: Record<GpuType, number>;
|
export type DCTier = 'small' | 'medium' | 'large' | 'mega';
|
||||||
totalFlops: number;
|
export type DCStatus = 'constructing' | 'operational';
|
||||||
totalUptime: number;
|
|
||||||
|
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 {
|
export interface DataCenter {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
location: LocationId;
|
location: LocationId;
|
||||||
gpus: GpuInventory[];
|
tier: DCTier;
|
||||||
maxCapacity: number;
|
status: DCStatus;
|
||||||
|
constructionProgress: number;
|
||||||
|
constructionTotal: number;
|
||||||
|
racks: Rack[];
|
||||||
coolingLevel: number;
|
coolingLevel: number;
|
||||||
redundancyLevel: number;
|
redundancyLevel: number;
|
||||||
currentUptime: number;
|
currentUptime: number;
|
||||||
energyCostPerTick: number;
|
energyCostPerTick: number;
|
||||||
maintenanceCostPerTick: number;
|
maintenanceCostPerTick: number;
|
||||||
|
usedSlots: number;
|
||||||
|
usedPowerKW: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GpuInventory {
|
// --- Racks ---
|
||||||
type: GpuType;
|
|
||||||
count: number;
|
export type RackSkuId =
|
||||||
healthyCount: number;
|
| 'consumer-x4' | 't4-x4' | 't4-x8'
|
||||||
failedCount: number;
|
| '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';
|
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;
|
availableAt: Era;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GpuConfig {
|
|
||||||
type: GpuType;
|
|
||||||
name: string;
|
|
||||||
flopsPerUnit: number;
|
|
||||||
basePowerDraw: number;
|
|
||||||
basePrice: number;
|
|
||||||
availableAt: Era;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GPU_CONFIGS: Record<GpuType, GpuConfig> = {
|
|
||||||
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<LocationId, LocationConfig> = {
|
export const LOCATION_CONFIGS: Record<LocationId, LocationConfig> = {
|
||||||
'us-west': { id: 'us-west', name: 'US West (Oregon)', energyCostMultiplier: 1.0, latencyTier: 1, regulatoryStrictness: 0.3, politicalStability: 0.9, availableAt: 'startup' },
|
'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' },
|
'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<LocationId, LocationConfig> = {
|
|||||||
'asia-south': { id: 'asia-south', name: 'Asia South (Mumbai)', energyCostMultiplier: 0.6, latencyTier: 3, regulatoryStrictness: 0.2, politicalStability: 0.7, availableAt: 'bigtech' },
|
'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' },
|
'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,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ export interface ResearchNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ResearchEffect {
|
export interface ResearchEffect {
|
||||||
type: 'unlock_gpu' | 'unlock_model_tier' | 'efficiency_boost'
|
type: 'unlock_gpu' | 'unlock_rack' | 'unlock_dc_tier' | 'unlock_model_tier'
|
||||||
| 'capability_boost' | 'cost_reduction' | 'unlock_feature'
|
| 'efficiency_boost' | 'capability_boost' | 'cost_reduction'
|
||||||
| 'unlock_product_line' | 'safety_boost';
|
| 'unlock_feature' | 'unlock_product_line' | 'safety_boost';
|
||||||
target: string;
|
target: string;
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user