Redesign infrastructure to hypercluster scale with 4-level hierarchy
CI / build-and-push (push) Successful in 43s

Replace flat DataCenter/Rack model with Cluster > Campus > Data Center > Racks
hierarchy. Individual rack entities eliminated in favor of statistical batch
simulation using deployment cohorts. Adds tiered network topology (ToR/agg/core)
with proportional outage model, DC retrofitting, bulk operations, and drill-down
UI navigation with breadcrumbs. First cluster and campus are free to preserve
early game flow. Rebalances starting economy ($600K), funding rounds, and
cohort scaling for hypercluster-scale gameplay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-24 23:15:41 -04:00
parent d36d9d61a8
commit c799f2e359
13 changed files with 1831 additions and 1267 deletions
@@ -19,7 +19,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.totalDataCenterCount);
const totalRacks = useGameStore((s) => s.infrastructure.totalRackCount); 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';
+6 -6
View File
@@ -12,7 +12,7 @@ export function DashboardPage() {
const revenuePerTick = useGameStore((s) => s.economy.revenuePerTick); const revenuePerTick = useGameStore((s) => s.economy.revenuePerTick);
const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick); const expensesPerTick = useGameStore((s) => s.economy.expensesPerTick);
const totalFlops = useGameStore((s) => s.infrastructure.totalFlops); const totalFlops = useGameStore((s) => s.infrastructure.totalFlops);
const dataCenters = useGameStore((s) => s.infrastructure.dataCenters); const totalDCs = useGameStore((s) => s.infrastructure.totalDataCenterCount);
const trainedModels = useGameStore((s) => s.models.trainedModels); const trainedModels = useGameStore((s) => s.models.trainedModels);
const activeTraining = useGameStore((s) => s.models.activeTraining); const activeTraining = useGameStore((s) => s.models.activeTraining);
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers); const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
@@ -27,13 +27,13 @@ export function DashboardPage() {
<div className="space-y-6"> <div className="space-y-6">
<h2 className="text-2xl font-bold">Dashboard</h2> <h2 className="text-2xl font-bold">Dashboard</h2>
{dataCenters.length === 0 && ( {totalDCs === 0 && (
<TutorialHint id="welcome"> <TutorialHint id="welcome">
Welcome to AI Tycoon! Start by building a data center in the Infrastructure tab, then order racks to begin training your first AI model. Welcome to AI Tycoon! Start by building a cluster in the Infrastructure tab, then add a campus and data center to deploy racks and train your first AI model.
</TutorialHint> </TutorialHint>
)} )}
{dataCenters.length > 0 && trainedModels.length === 0 && !activeTraining && ( {totalDCs > 0 && trainedModels.length === 0 && !activeTraining && (
<TutorialHint id="train-first-model"> <TutorialHint id="train-first-model">
You have compute available! Head to the Models tab to allocate compute for training and start your first model. You have compute available! Head to the Models tab to allocate compute for training and start your first model.
</TutorialHint> </TutorialHint>
@@ -58,7 +58,7 @@ export function DashboardPage() {
<StatCard <StatCard
icon={Server} icon={Server}
label="Data Centers" label="Data Centers"
value={dataCenters.length.toString()} value={totalDCs.toString()}
subValue={`${formatNumber(totalFlops)} FLOPS`} subValue={`${formatNumber(totalFlops)} FLOPS`}
color="text-blue-400" color="text-blue-400"
onClick={() => useGameStore.getState().setActivePage('infrastructure')} onClick={() => useGameStore.getState().setActivePage('infrastructure')}
@@ -194,7 +194,7 @@ export function DashboardPage() {
</div> </div>
</div> </div>
{dataCenters.length === 0 && ( {totalDCs === 0 && (
<div className="bg-surface-900 border border-accent/30 rounded-xl p-6 text-center"> <div className="bg-surface-900 border border-accent/30 rounded-xl p-6 text-center">
<h3 className="text-lg font-semibold mb-2">Get Started</h3> <h3 className="text-lg font-semibold mb-2">Get Started</h3>
<p className="text-surface-400 text-sm mb-4"> <p className="text-surface-400 text-sm mb-4">
+8 -3
View File
@@ -33,9 +33,14 @@ export function FinancePage() {
const burnRate = expensesPerTick > revenuePerTick ? expensesPerTick - revenuePerTick : 0; const burnRate = expensesPerTick > revenuePerTick ? expensesPerTick - revenuePerTick : 0;
const runway = burnRate > 0 ? money / burnRate : Infinity; const runway = burnRate > 0 ? money / burnRate : Infinity;
const infraCosts = infrastructure.dataCenters.reduce( let infraCosts = 0;
(s, dc) => s + dc.energyCostPerTick + dc.maintenanceCostPerTick, 0, for (const cluster of infrastructure.clusters) {
); for (const campus of cluster.campuses) {
for (const dc of campus.dataCenters) {
infraCosts += dc.energyCostPerTick + dc.maintenanceCostPerTick;
}
}
}
const talentCosts = talent.totalSalaryPerTick; const talentCosts = talent.totalSalaryPerTick;
return ( return (
File diff suppressed because it is too large Load Diff
+350 -78
View File
@@ -6,8 +6,10 @@ import type {
ResearchState, ModelsState, MarketState, ResearchState, ModelsState, MarketState,
CompetitorState, TalentState, DataState, CompetitorState, TalentState, DataState,
ReputationState, AchievementState, ReputationState, AchievementState,
DataCenter, DCTier, RackSkuId, TrainingJob, Cluster, Campus, DataCenter, DCTier, RackSkuId, TrainingJob,
ActiveResearch, OwnedDataset, LocationId, ActiveResearch, OwnedDataset, LocationId,
DeploymentCohort, PipelineStage,
NetworkHealthState,
} 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 {
@@ -18,8 +20,12 @@ import {
INITIAL_REPUTATION, INITIAL_ACHIEVEMENTS, INITIAL_REPUTATION, INITIAL_ACHIEVEMENTS,
DC_TIER_CONFIGS, RACK_SKU_CONFIGS, DC_TIER_CONFIGS, RACK_SKU_CONFIGS,
PIPELINE_ORDER_BASE_TICKS, DC_UPGRADE_COST_FRACTION, DC_UPGRADE_INCREMENT, PIPELINE_ORDER_BASE_TICKS, DC_UPGRADE_COST_FRACTION, DC_UPGRADE_INCREMENT,
CLUSTER_COST_CONFIG, CAMPUS_TIER_COSTS, FIRST_CAMPUS_BUILD_TICKS,
COHORT_SCALE_FACTOR,
FUNDING_ROUNDS, FUNDING_ROUNDS,
OPEN_SOURCE_REPUTATION_BOOST, OPEN_SOURCE_REPUTATION_BOOST,
LOCATION_CONFIGS,
networkSlotsRequired, maxComputeRacks,
uuid, uuid,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { INITIAL_RIVALS } from '@ai-tycoon/game-engine'; import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
@@ -27,9 +33,19 @@ import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models' export type ActivePage = 'dashboard' | 'infrastructure' | 'research' | 'models'
| 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'settings'; | 'market' | 'talent' | 'data' | 'competitors' | 'finance' | 'achievements' | 'leaderboard' | 'settings';
export type InfraNavLevel = 'clusters' | 'cluster' | 'campus' | 'datacenter';
export interface InfraNav {
level: InfraNavLevel;
clusterId?: string;
campusId?: string;
datacenterId?: string;
}
interface UIState { interface UIState {
activePage: ActivePage; activePage: ActivePage;
notifications: GameNotification[]; notifications: GameNotification[];
infraNav: InfraNav;
} }
export interface GameNotification { export interface GameNotification {
@@ -41,8 +57,13 @@ export interface GameNotification {
read: boolean; read: boolean;
} }
function emptyNetworkHealth(): NetworkHealthState {
return { tier1Required: 0, tier1Healthy: 0, tier2Required: 0, tier2Healthy: 0, tier3Required: 0, tier3Healthy: 0, racksDisconnected: 0 };
}
interface Actions { interface Actions {
setActivePage: (page: ActivePage) => void; setActivePage: (page: ActivePage) => void;
setInfraNav: (nav: InfraNav) => void;
addNotification: (n: Omit<GameNotification, 'id' | 'read'>) => void; addNotification: (n: Omit<GameNotification, 'id' | 'read'>) => void;
dismissNotification: (id: string) => void; dismissNotification: (id: string) => void;
markAllNotificationsRead: () => void; markAllNotificationsRead: () => void;
@@ -50,9 +71,14 @@ interface Actions {
setGameSpeed: (speed: GameSpeed) => void; setGameSpeed: (speed: GameSpeed) => void;
togglePause: () => void; togglePause: () => void;
setTrainingAllocation: (ratio: number) => void; setTrainingAllocation: (ratio: number) => void;
buildDataCenter: (name: string, location: LocationId, tier: DCTier) => void; buildCluster: (name: string, locationId: LocationId) => void;
orderRack: (dataCenterId: string, skuId: RackSkuId) => void; buildCampus: (name: string, clusterId: string, dcTier: DCTier) => void;
decommissionRack: (dataCenterId: string, rackId: string) => void; buildDataCenter: (name: string, campusId: string) => void;
deployRacks: (dataCenterId: string, skuId: RackSkuId, quantity: number) => void;
fillDCToCapacity: (dataCenterId: string, skuId: RackSkuId) => void;
addDCsToCampus: (campusId: string, count: number) => void;
retrofitDC: (dataCenterId: string, newSkuId: RackSkuId) => void;
cancelRetrofit: (dataCenterId: string) => void;
upgradeDataCenter: (dataCenterId: string, upgrade: 'cooling' | 'redundancy') => 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;
@@ -97,15 +123,73 @@ const initialGameState: GameState = {
achievements: INITIAL_ACHIEVEMENTS, achievements: INITIAL_ACHIEVEMENTS,
}; };
// --- Helper: find entities in the hierarchy ---
function findCluster(infra: InfrastructureState, clusterId: string): Cluster | undefined {
return infra.clusters.find(c => c.id === clusterId);
}
function findCampusInCluster(cluster: Cluster, campusId: string): Campus | undefined {
return cluster.campuses.find(c => c.id === campusId);
}
function findCampus(infra: InfrastructureState, campusId: string): { cluster: Cluster; campus: Campus } | undefined {
for (const cluster of infra.clusters) {
const campus = cluster.campuses.find(c => c.id === campusId);
if (campus) return { cluster, campus };
}
return undefined;
}
function findDC(infra: InfrastructureState, dcId: string): { cluster: Cluster; campus: Campus; dc: DataCenter } | undefined {
for (const cluster of infra.clusters) {
for (const campus of cluster.campuses) {
const dc = campus.dataCenters.find(d => d.id === dcId);
if (dc) return { cluster, campus, dc };
}
}
return undefined;
}
function updateDCInInfra(infra: InfrastructureState, dcId: string, updater: (dc: DataCenter) => DataCenter): InfrastructureState {
return {
...infra,
clusters: infra.clusters.map(cluster => ({
...cluster,
campuses: cluster.campuses.map(campus => ({
...campus,
dataCenters: campus.dataCenters.map(dc =>
dc.id === dcId ? updater(dc) : dc,
),
})),
})),
};
}
function updateCampusInInfra(infra: InfrastructureState, campusId: string, updater: (campus: Campus) => Campus): InfrastructureState {
return {
...infra,
clusters: infra.clusters.map(cluster => ({
...cluster,
campuses: cluster.campuses.map(campus =>
campus.id === campusId ? updater(campus) : campus,
),
})),
};
}
export const useGameStore = create<Store>()( export const useGameStore = create<Store>()(
persist( persist(
(set, get) => ({ (set, get) => ({
...initialGameState, ...initialGameState,
activePage: 'dashboard' as ActivePage, activePage: 'dashboard' as ActivePage,
notifications: [], notifications: [],
infraNav: { level: 'clusters' } as InfraNav,
setActivePage: (page) => set({ activePage: page }), setActivePage: (page) => set({ activePage: page }),
setInfraNav: (nav) => set({ infraNav: nav }),
addNotification: (n) => set((s) => ({ addNotification: (n) => set((s) => ({
notifications: [ notifications: [
{ ...n, id: uuid(), read: false }, { ...n, id: uuid(), read: false },
@@ -138,6 +222,7 @@ export const useGameStore = create<Store>()(
}, },
activePage: 'dashboard', activePage: 'dashboard',
notifications: [], notifications: [],
infraNav: { level: 'clusters' },
}), }),
setGameSpeed: (speed) => set((s) => ({ setGameSpeed: (speed) => set((s) => ({
@@ -152,121 +237,310 @@ export const useGameStore = create<Store>()(
compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio }, compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio },
})), })),
buildDataCenter: (name, location, tier) => set((s) => { // --- Infrastructure: Cluster ---
const tierConfig = DC_TIER_CONFIGS[tier];
if (s.economy.money < tierConfig.baseCost) return s;
buildCluster: (name, locationId) => set((s) => {
const loc = LOCATION_CONFIGS[locationId];
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(loc.availableAt)) return s;
const existingInRegion = s.infrastructure.clusters.find(c => c.locationId === locationId);
if (existingInRegion) return s;
const isFirst = s.infrastructure.clusters.length === 0;
const cost = isFirst ? 0 : CLUSTER_COST_CONFIG.baseCost;
if (s.economy.money < cost) return s;
const cluster: Cluster = {
id: uuid(),
name,
locationId,
campuses: [],
status: isFirst ? 'operational' : 'constructing',
constructionProgress: isFirst ? 0 : 0,
constructionTotal: isFirst ? 0 : CLUSTER_COST_CONFIG.buildTimeTicks,
};
return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: {
...s.infrastructure,
clusters: [...s.infrastructure.clusters, cluster],
},
};
}),
// --- Infrastructure: Campus ---
buildCampus: (name, clusterId, dcTier) => set((s) => {
const cluster = findCluster(s.infrastructure, clusterId);
if (!cluster || cluster.status !== 'operational') return s;
const tierConfig = DC_TIER_CONFIGS[dcTier];
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi']; const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(tierConfig.requiredEra)) return s; if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(tierConfig.requiredEra)) return s;
if (tierConfig.requiredResearch && !s.research.completedResearch.includes(tierConfig.requiredResearch)) return s; if (tierConfig.requiredResearch && !s.research.completedResearch.includes(tierConfig.requiredResearch)) return s;
const isFirstDC = s.infrastructure.dataCenters.length === 0; const campusCost = CAMPUS_TIER_COSTS[dcTier];
const isFirstCampus = s.infrastructure.clusters.every(c => c.campuses.length === 0);
const cost = isFirstCampus ? 0 : campusCost.baseCost;
if (s.economy.money < cost) return s;
const buildTime = isFirstCampus ? FIRST_CAMPUS_BUILD_TICKS : campusCost.buildTimeTicks;
const campus: Campus = {
id: uuid(),
name,
clusterId,
dcTier,
dataCenters: [],
status: 'constructing',
constructionProgress: 0,
constructionTotal: buildTime,
};
return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: {
...s.infrastructure,
clusters: s.infrastructure.clusters.map(c =>
c.id === clusterId
? { ...c, campuses: [...c.campuses, campus] }
: c,
),
},
};
}),
// --- Infrastructure: Data Center ---
buildDataCenter: (name, campusId) => set((s) => {
const found = findCampus(s.infrastructure, campusId);
if (!found || found.campus.status !== 'operational') return s;
const tierConfig = DC_TIER_CONFIGS[found.campus.dcTier];
if (s.economy.money < tierConfig.baseCost) return s;
const isFirstDC = s.infrastructure.clusters.every(cl =>
cl.campuses.every(ca => ca.dataCenters.length === 0),
);
const buildTime = isFirstDC ? tierConfig.firstBuildTimeTicks : tierConfig.buildTimeTicks; const buildTime = isFirstDC ? tierConfig.firstBuildTimeTicks : tierConfig.buildTimeTicks;
const dc: DataCenter = { const dc: DataCenter = {
id: uuid(), id: uuid(),
name, name,
location, campusId,
tier, tier: found.campus.dcTier,
status: 'constructing', status: 'constructing',
constructionProgress: 0, constructionProgress: 0,
constructionTotal: buildTime, constructionTotal: buildTime,
racks: [], rackSkuId: null,
computeRacksOnline: 0,
computeRacksFailed: 0,
networkHealth: emptyNetworkHealth(),
deploymentCohorts: [],
retrofitState: null,
coolingLevel: 0, coolingLevel: 0,
redundancyLevel: 0, redundancyLevel: 0,
currentUptime: 1, effectiveComputeRacks: 0,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
usedSlots: 0, usedSlots: 0,
usedPowerKW: 0, usedPowerKW: 0,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
currentUptime: 1,
}; };
return { return {
economy: { ...s.economy, money: s.economy.money - tierConfig.baseCost }, economy: { ...s.economy, money: s.economy.money - tierConfig.baseCost },
infrastructure: { infrastructure: updateCampusInInfra(s.infrastructure, campusId, (campus) => ({
...s.infrastructure, ...campus,
dataCenters: [...s.infrastructure.dataCenters, dc], dataCenters: [...campus.dataCenters, dc],
}, })),
}; };
}), }),
orderRack: (dataCenterId, skuId) => set((s) => { // --- Infrastructure: Deploy Racks ---
const sku = RACK_SKU_CONFIGS[skuId];
if (s.economy.money < sku.baseCost) return s;
deployRacks: (dataCenterId, skuId, quantity) => set((s) => {
if (quantity <= 0) return s;
const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'operational') return s;
const dc = found.dc;
if (dc.rackSkuId !== null && dc.rackSkuId !== skuId) return s;
const sku = RACK_SKU_CONFIGS[skuId];
const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi']; const eraOrder: Era[] = ['startup', 'scaleup', 'bigtech', 'agi'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) return s; if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(sku.era)) return s;
if (sku.requiredResearch && !s.research.completedResearch.includes(sku.requiredResearch)) 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]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const activePipeline = s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dataCenterId && o.stage !== 'decommission'); const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const actualUsedSlots = dc.racks.length + activePipeline.length; const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed
const pipelinePowerForDc = activePipeline + dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0);
.reduce((sum, o) => sum + RACK_SKU_CONFIGS[o.skuId].powerDrawKW, 0); const available = maxCompute - existingCompute;
const actualUsedPower = dc.racks.reduce((sum, r) => sum + RACK_SKU_CONFIGS[r.skuId].powerDrawKW, 0) + pipelinePowerForDc; const actualQty = Math.min(quantity, available);
if (actualUsedSlots >= tierConfig.rackSlots) return s; if (actualQty <= 0) return s;
if (actualUsedPower + sku.powerDrawKW > tierConfig.powerBudgetKW) return s;
const order = { const totalNetSlots = networkSlotsRequired(existingCompute + actualQty);
const totalSlotsNeeded = existingCompute + actualQty + totalNetSlots;
if (totalSlotsNeeded > tierConfig.rackSlots) return s;
const powerNeeded = (existingCompute + actualQty) * sku.powerDrawKW;
if (powerNeeded > tierConfig.powerBudgetKW) return s;
const totalCost = sku.baseCost * actualQty;
if (s.economy.money < totalCost) return s;
const baseTicks = PIPELINE_ORDER_BASE_TICKS;
const scaledTicks = Math.ceil(baseTicks * (1 + COHORT_SCALE_FACTOR * actualQty));
const cohort: DeploymentCohort = {
id: uuid(), id: uuid(),
count: actualQty,
skuId, skuId,
dataCenterId, stage: 'ordered',
stage: 'ordered' as const,
stageProgress: 0, stageProgress: 0,
stageTotal: PIPELINE_ORDER_BASE_TICKS, stageTotal: scaledTicks,
totalCost: sku.baseCost,
repairCount: 0, repairCount: 0,
}; };
return { return {
economy: { ...s.economy, money: s.economy.money - sku.baseCost }, economy: { ...s.economy, money: s.economy.money - totalCost },
infrastructure: { infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...s.infrastructure, ...d,
rackPipeline: [...s.infrastructure.rackPipeline, order], rackSkuId: skuId,
}, deploymentCohorts: [...d.deploymentCohorts, cohort],
})),
}; };
}), }),
decommissionRack: (dataCenterId, rackId) => set((s) => { fillDCToCapacity: (dataCenterId, skuId) => {
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId); const s = get();
if (!dc || dc.status !== 'operational') return s; const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'operational') return;
const rack = dc.racks.find(r => r.id === rackId); const dc = found.dc;
if (!rack) return s; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const maxCompute = maxComputeRacks(tierConfig.rackSlots);
const existingCompute = dc.computeRacksOnline + dc.computeRacksFailed
+ dc.deploymentCohorts.filter(c => c.stage !== 'decommission').reduce((sum, c) => sum + c.count, 0);
const available = maxCompute - existingCompute;
if (available <= 0) return;
const sku = RACK_SKU_CONFIGS[rack.skuId]; const sku = RACK_SKU_CONFIGS[skuId];
const dataCenters = s.infrastructure.dataCenters.map(d => { const affordableQty = Math.floor(s.economy.money / sku.baseCost);
if (d.id !== dataCenterId) return d; const powerLimit = Math.floor((tierConfig.powerBudgetKW - existingCompute * sku.powerDrawKW) / sku.powerDrawKW);
return { ...d, racks: d.racks.filter(r => r.id !== rackId) }; const qty = Math.min(available, affordableQty, powerLimit);
if (qty <= 0) return;
get().deployRacks(dataCenterId, skuId, qty);
},
addDCsToCampus: (campusId, count) => set((s) => {
if (count <= 0) return s;
const found = findCampus(s.infrastructure, campusId);
if (!found || found.campus.status !== 'operational') return s;
const tierConfig = DC_TIER_CONFIGS[found.campus.dcTier];
const totalCost = tierConfig.baseCost * count;
if (s.economy.money < totalCost) return s;
const newDCs: DataCenter[] = [];
for (let i = 0; i < count; i++) {
newDCs.push({
id: uuid(),
name: `${found.campus.name}-DC-${found.campus.dataCenters.length + i + 1}`,
campusId,
tier: found.campus.dcTier,
status: 'constructing',
constructionProgress: 0,
constructionTotal: tierConfig.buildTimeTicks,
rackSkuId: null,
computeRacksOnline: 0,
computeRacksFailed: 0,
networkHealth: emptyNetworkHealth(),
deploymentCohorts: [],
retrofitState: null,
coolingLevel: 0,
redundancyLevel: 0,
effectiveComputeRacks: 0,
usedSlots: 0,
usedPowerKW: 0,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
currentUptime: 1,
}); });
}
const order = {
id: rackId,
skuId: rack.skuId,
dataCenterId,
stage: 'decommission' as const,
stageProgress: 0,
stageTotal: sku.pipelineTimeTicks.installation,
totalCost: 0,
repairCount: 0,
};
return { return {
infrastructure: { economy: { ...s.economy, money: s.economy.money - totalCost },
...s.infrastructure, infrastructure: updateCampusInInfra(s.infrastructure, campusId, (campus) => ({
dataCenters, ...campus,
rackPipeline: [...s.infrastructure.rackPipeline, order], dataCenters: [...campus.dataCenters, ...newDCs],
}, })),
}; };
}), }),
// --- Infrastructure: Retrofit ---
retrofitDC: (dataCenterId, newSkuId) => set((s) => {
const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'operational') return s;
const dc = found.dc;
if (!dc.rackSkuId || dc.rackSkuId === newSkuId) return s;
const sku = RACK_SKU_CONFIGS[newSkuId];
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 totalRacksToRetrofit = dc.computeRacksOnline + dc.computeRacksFailed;
if (totalRacksToRetrofit <= 0) return s;
const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId];
const decommTicks = Math.ceil(oldSku.pipelineTimeTicks.installation * (1 + COHORT_SCALE_FACTOR * totalRacksToRetrofit));
return {
infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...d,
status: 'retrofitting' as const,
deploymentCohorts: [],
retrofitState: {
fromSkuId: d.rackSkuId!,
toSkuId: newSkuId,
phase: 'decommissioning' as const,
progress: 0,
total: decommTicks,
racksRemaining: totalRacksToRetrofit,
},
})),
};
}),
cancelRetrofit: (dataCenterId) => set((s) => {
const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'retrofitting') return s;
return {
infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...d,
status: 'operational' as const,
retrofitState: null,
})),
};
}),
// --- Infrastructure: Upgrades ---
upgradeDataCenter: (dataCenterId, upgrade) => set((s) => { upgradeDataCenter: (dataCenterId, upgrade) => set((s) => {
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId); const found = findDC(s.infrastructure, dataCenterId);
if (!dc || dc.status !== 'operational') return s; if (!found || found.dc.status !== 'operational') return s;
const dc = found.dc;
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const cost = tierConfig.baseCost * DC_UPGRADE_COST_FRACTION; const cost = tierConfig.baseCost * DC_UPGRADE_COST_FRACTION;
if (s.economy.money < cost) return s; if (s.economy.money < cost) return s;
@@ -274,21 +548,18 @@ export const useGameStore = create<Store>()(
const currentLevel = upgrade === 'cooling' ? dc.coolingLevel : dc.redundancyLevel; const currentLevel = upgrade === 'cooling' ? dc.coolingLevel : dc.redundancyLevel;
if (currentLevel >= 1.0) return s; if (currentLevel >= 1.0) return s;
const dataCenters = s.infrastructure.dataCenters.map(d => {
if (d.id !== dataCenterId) return d;
return { return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...d, ...d,
[upgrade === 'cooling' ? 'coolingLevel' : 'redundancyLevel']: [upgrade === 'cooling' ? 'coolingLevel' : 'redundancyLevel']:
Math.min(1.0, currentLevel + DC_UPGRADE_INCREMENT), Math.min(1.0, currentLevel + DC_UPGRADE_INCREMENT),
}; })),
});
return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: { ...s.infrastructure, dataCenters },
}; };
}), }),
// --- Non-infrastructure actions (unchanged) ---
startTraining: (job) => set((s) => ({ startTraining: (job) => set((s) => ({
models: { models: {
...s.models, ...s.models,
@@ -463,7 +734,7 @@ export const useGameStore = create<Store>()(
name: 'ai-tycoon-save', name: 'ai-tycoon-save',
version: SAVE_VERSION, version: SAVE_VERSION,
partialize: (state) => { partialize: (state) => {
const { activePage, notifications, ...rest } = state; const { activePage, notifications, infraNav, ...rest } = state;
return rest; return rest;
}, },
migrate: (_persisted, version) => { migrate: (_persisted, version) => {
@@ -474,11 +745,12 @@ export const useGameStore = create<Store>()(
notifications: [{ notifications: [{
id: uuid(), id: uuid(),
title: 'Save Reset', title: 'Save Reset',
message: 'Your save was reset due to a major infrastructure overhaul. Enjoy the new rack-based system!', message: 'Your save was reset due to a major infrastructure redesign — Hypercluster scale! Build clusters, campuses, and data centers.',
type: 'info' as const, type: 'info' as const,
tick: 0, tick: 0,
read: false, read: false,
}], }],
infraNav: { level: 'clusters' },
} as unknown as Store; } as unknown as Store;
} }
return _persisted as Store; return _persisted as Store;
@@ -6,7 +6,7 @@ export const ACHIEVEMENT_DEFINITIONS: AchievementDefinition[] = [
name: 'First Steps', name: 'First Steps',
description: 'Build your first data center.', description: 'Build your first data center.',
icon: 'Server', icon: 'Server',
condition: { field: 'infrastructure.dataCenters.length', operator: 'gte', value: 1 }, condition: { field: 'infrastructure.totalDataCenterCount', operator: 'gte', value: 1 },
}, },
{ {
id: 'first-model', id: 'first-model',
+33 -3
View File
@@ -65,7 +65,7 @@ export const TECH_TREE: ResearchNode[] = [
{ {
id: 'dc-engineering-ii', id: 'dc-engineering-ii',
name: 'DC Engineering II', name: 'DC Engineering II',
description: 'Advanced facility design unlocks Medium data centers (30 slots, 200kW).', description: 'Advanced facility design unlocks Medium data centers (500 slots, 3000kW).',
era: 'startup', era: 'startup',
category: 'infrastructure', category: 'infrastructure',
prerequisites: ['advanced-cooling'], prerequisites: ['advanced-cooling'],
@@ -75,7 +75,7 @@ export const TECH_TREE: ResearchNode[] = [
{ {
id: 'dc-engineering-iii', id: 'dc-engineering-iii',
name: 'DC Engineering III', name: 'DC Engineering III',
description: 'Large-scale facility design unlocks Large data centers (60 slots, 500kW).', description: 'Large-scale facility design unlocks Large data centers (1000 slots, 7000kW).',
era: 'scaleup', era: 'scaleup',
category: 'infrastructure', category: 'infrastructure',
prerequisites: ['dc-engineering-ii'], prerequisites: ['dc-engineering-ii'],
@@ -85,7 +85,7 @@ export const TECH_TREE: ResearchNode[] = [
{ {
id: 'dc-engineering-iv', id: 'dc-engineering-iv',
name: 'DC Engineering IV', name: 'DC Engineering IV',
description: 'Mega-scale campus design unlocks Mega data centers (120 slots, 1200kW).', description: 'Mega-scale campus design unlocks Mega data centers (1500 slots, 12000kW).',
era: 'bigtech', era: 'bigtech',
category: 'infrastructure', category: 'infrastructure',
prerequisites: ['dc-engineering-iii'], prerequisites: ['dc-engineering-iii'],
@@ -102,6 +102,36 @@ export const TECH_TREE: ResearchNode[] = [
cost: { researchPoints: 1, compute: 10, ticks: 90 }, cost: { researchPoints: 1, compute: 10, ticks: 90 },
effects: [{ type: 'cost_reduction', target: 'test_failure_rate', value: 0.25 }], effects: [{ type: 'cost_reduction', target: 'test_failure_rate', value: 0.25 }],
}, },
{
id: 'network-engineering-i',
name: 'Network Engineering I',
description: 'Improved network switching reduces Tier-1 failure rate by 40%.',
era: 'scaleup',
category: 'infrastructure',
prerequisites: ['redundancy-protocols'],
cost: { researchPoints: 2, compute: 20, ticks: 150 },
effects: [{ type: 'cost_reduction', target: 'network_failure_rate', value: 0.4 }],
},
{
id: 'network-engineering-ii',
name: 'Network Engineering II',
description: 'Spine-leaf architecture reduces all network failure rates by 50%.',
era: 'bigtech',
category: 'infrastructure',
prerequisites: ['network-engineering-i'],
cost: { researchPoints: 4, compute: 80, ticks: 360 },
effects: [{ type: 'cost_reduction', target: 'network_failure_rate', value: 0.5 }],
},
{
id: 'rapid-deployment',
name: 'Rapid Deployment',
description: 'Streamlined procurement pipelines reduce deployment times by 20%.',
era: 'scaleup',
category: 'infrastructure',
prerequisites: ['dc-engineering-ii'],
cost: { researchPoints: 2, compute: 25, ticks: 180 },
effects: [{ type: 'efficiency_boost', target: 'pipeline_speed', value: 0.2 }],
},
{ {
id: 'distributed-training', id: 'distributed-training',
name: 'Distributed Training', name: 'Distributed Training',
@@ -10,9 +10,14 @@ export function processEconomy(
): EconomyState { ): EconomyState {
const revenue = market.apiRevenue + market.subscriptionRevenue; const revenue = market.apiRevenue + market.subscriptionRevenue;
const infraExpenses = infrastructure.dataCenters.reduce((sum, dc) => { let infraExpenses = 0;
return sum + dc.energyCostPerTick + dc.maintenanceCostPerTick; for (const cluster of infrastructure.clusters) {
}, 0); for (const campus of cluster.campuses) {
for (const dc of campus.dataCenters) {
infraExpenses += dc.energyCostPerTick + dc.maintenanceCostPerTick;
}
}
}
const talentExpenses = state.talent.totalSalaryPerTick; const talentExpenses = state.talent.totalSalaryPerTick;
const dataExpenses = state.data.partnerships.reduce((sum, p) => sum + p.costPerTick, 0); const dataExpenses = state.data.partnerships.reduce((sum, p) => sum + p.costPerTick, 0);
@@ -1,4 +1,7 @@
import type { GameState, InfrastructureState, DataCenter, RackOrder, Rack, PipelineStage } from '@ai-tycoon/shared'; import type {
GameState, InfrastructureState, Cluster, Campus, DataCenter,
DeploymentCohort, NetworkHealthState, PipelineStage,
} from '@ai-tycoon/shared';
import { import {
LOCATION_CONFIGS, LOCATION_CONFIGS,
RACK_SKU_CONFIGS, RACK_SKU_CONFIGS,
@@ -8,6 +11,10 @@ import {
COOLING_FAILURE_REDUCTION, COOLING_FAILURE_REDUCTION,
REDUNDANCY_FAILURE_REDUCTION, REDUNDANCY_FAILURE_REDUCTION,
RACK_REPAIR_BASE_TICKS, RACK_REPAIR_BASE_TICKS,
NETWORK_TOPOLOGY,
COHORT_SCALE_FACTOR,
PIPELINE_ORDER_BASE_TICKS,
networkSlotsRequired,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import type { TickNotification } from '../tick'; import type { TickNotification } from '../tick';
@@ -27,18 +34,21 @@ function nextStage(stage: PipelineStage): PipelineStage | 'production' {
return PIPELINE_ADVANCE_ORDER[idx + 1]; return PIPELINE_ADVANCE_ORDER[idx + 1];
} }
function stageTotal(stage: PipelineStage, order: RackOrder): number { function cohortStageTotal(stage: PipelineStage, skuId: string, count: number): number {
const sku = RACK_SKU_CONFIGS[order.skuId]; const sku = RACK_SKU_CONFIGS[skuId as keyof typeof RACK_SKU_CONFIGS];
const timings = sku.pipelineTimeTicks; const timings = sku.pipelineTimeTicks;
let base: number;
switch (stage) { switch (stage) {
case 'manufacturing': return timings.manufacturing; case 'ordered': base = PIPELINE_ORDER_BASE_TICKS; break;
case 'receiving': return timings.receiving; case 'manufacturing': base = timings.manufacturing; break;
case 'installation': return timings.installation; case 'receiving': base = timings.receiving; break;
case 'testing': return timings.testing; case 'installation': base = timings.installation; break;
case 'repair': return RACK_REPAIR_BASE_TICKS; case 'testing': base = timings.testing; break;
case 'decommission': return timings.installation; case 'repair': base = RACK_REPAIR_BASE_TICKS; break;
default: return 0; case 'decommission': base = timings.installation; break;
default: base = 0;
} }
return Math.ceil(base * (1 + COHORT_SCALE_FACTOR * count));
} }
function stageSpeed(stage: PipelineStage, engEff: number, opsEff: number): number { function stageSpeed(stage: PipelineStage, engEff: number, opsEff: number): number {
@@ -52,19 +62,126 @@ function stageSpeed(stage: PipelineStage, engEff: number, opsEff: number): numbe
} }
} }
function binomialSample(n: number, p: number): number {
if (n <= 0 || p <= 0) return 0;
if (p >= 1) return n;
const expected = n * p;
const base = Math.floor(expected);
const frac = expected - base;
return base + (Math.random() < frac ? 1 : 0);
}
function computeNetworkHealth(computeRacksOnline: number): NetworkHealthState {
if (computeRacksOnline <= 0) {
return { tier1Required: 0, tier1Healthy: 0, tier2Required: 0, tier2Healthy: 0, tier3Required: 0, tier3Healthy: 0, racksDisconnected: 0 };
}
const tier1 = Math.ceil(computeRacksOnline / NETWORK_TOPOLOGY.tier1PerCompute);
const tier2 = Math.ceil(tier1 / NETWORK_TOPOLOGY.tier2PerTier1);
const tier3 = NETWORK_TOPOLOGY.tier3PerDC;
return {
tier1Required: tier1,
tier1Healthy: tier1,
tier2Required: tier2,
tier2Healthy: tier2,
tier3Required: tier3,
tier3Healthy: tier3,
racksDisconnected: 0,
};
}
function processNetworkFailures(
nh: NetworkHealthState,
computeRacksOnline: number,
networkResearchBonus: number,
): { networkHealth: NetworkHealthState; racksDisconnected: number } {
if (computeRacksOnline <= 0) {
return { networkHealth: nh, racksDisconnected: 0 };
}
let racksDisconnected = 0;
const t1Rate = NETWORK_TOPOLOGY.tier1FailureRate * (1 - networkResearchBonus);
const t1Failures = binomialSample(nh.tier1Required, t1Rate);
const tier1Healthy = nh.tier1Required - t1Failures;
racksDisconnected += t1Failures * NETWORK_TOPOLOGY.tier1BlastRadius;
const t2Rate = NETWORK_TOPOLOGY.tier2FailureRate * (1 - networkResearchBonus);
const t2Failures = binomialSample(nh.tier2Required, t2Rate);
const tier2Healthy = nh.tier2Required - t2Failures;
racksDisconnected += t2Failures * NETWORK_TOPOLOGY.tier1BlastRadius * NETWORK_TOPOLOGY.tier2BlastRadiusMultiplier;
const t3Rate = NETWORK_TOPOLOGY.tier3FailureRate * (1 - networkResearchBonus);
const t3Failures = binomialSample(nh.tier3Required, t3Rate);
const tier3Healthy = nh.tier3Required - t3Failures;
if (t3Failures > 0) {
racksDisconnected = computeRacksOnline;
}
racksDisconnected = Math.min(racksDisconnected, computeRacksOnline);
return {
networkHealth: {
...nh,
tier1Healthy,
tier2Healthy,
tier3Healthy,
racksDisconnected,
},
racksDisconnected,
};
}
export function processInfrastructure(state: GameState): InfraTickResult { export function processInfrastructure(state: GameState): InfraTickResult {
const notifications: TickNotification[] = []; const notifications: TickNotification[] = [];
let repairCosts = 0; let repairCosts = 0;
const engEff = state.talent.departments.engineering.effectiveness; const engEff = state.talent.departments.engineering.effectiveness;
const opsEff = state.talent.departments.operations.effectiveness; const opsEff = state.talent.departments.operations.effectiveness;
const qaResearchBonus = state.research.completedResearch.includes('quality-assurance') ? 0.25 : 0; const qaResearchBonus = state.research.completedResearch.includes('quality-assurance') ? 0.25 : 0;
const netResearch1 = state.research.completedResearch.includes('network-engineering-i') ? 0.4 : 0;
const netResearch2 = state.research.completedResearch.includes('network-engineering-ii') ? 0.5 : 0;
const networkResearchBonus = Math.min(0.8, netResearch1 + netResearch2);
// --- Phase 1: Advance DC Construction --- let totalFlops = 0;
const dataCenters: DataCenter[] = state.infrastructure.dataCenters.map(dc => { let totalUptime = 0;
if (dc.status !== 'constructing') return { ...dc }; let totalRackCount = 0;
let totalComputeRackCount = 0;
let totalDataCenterCount = 0;
let dcWithRacks = 0;
const clusters: Cluster[] = state.infrastructure.clusters.map(cluster => {
// Advance cluster construction
if (cluster.status === 'constructing') {
const newProgress = cluster.constructionProgress + 1;
if (newProgress >= cluster.constructionTotal) {
notifications.push({
title: 'Cluster Online',
message: `${cluster.name} cluster in ${LOCATION_CONFIGS[cluster.locationId].name} is now operational!`,
type: 'success',
});
return { ...cluster, constructionProgress: cluster.constructionTotal, status: 'operational' as const, campuses: cluster.campuses };
}
return { ...cluster, constructionProgress: newProgress };
}
const campuses: Campus[] = cluster.campuses.map(campus => {
// Advance campus construction
if (campus.status === 'constructing') {
const newProgress = campus.constructionProgress + 1;
if (newProgress >= campus.constructionTotal) {
notifications.push({
title: 'Campus Ready',
message: `Campus ${campus.name} is now operational!`,
type: 'success',
});
return { ...campus, constructionProgress: campus.constructionTotal, status: 'operational' as const, dataCenters: campus.dataCenters };
}
return { ...campus, constructionProgress: newProgress };
}
const dataCenters: DataCenter[] = campus.dataCenters.map(dc => {
// Advance DC construction
if (dc.status === 'constructing') {
const newProgress = dc.constructionProgress + 1; const newProgress = dc.constructionProgress + 1;
if (newProgress >= dc.constructionTotal) { if (newProgress >= dc.constructionTotal) {
notifications.push({ notifications.push({
@@ -75,86 +192,127 @@ export function processInfrastructure(state: GameState): InfraTickResult {
return { ...dc, constructionProgress: dc.constructionTotal, status: 'operational' as const }; return { ...dc, constructionProgress: dc.constructionTotal, status: 'operational' as const };
} }
return { ...dc, constructionProgress: newProgress }; return { ...dc, constructionProgress: newProgress };
});
// --- 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 === 'decommission') { let computeRacksOnline = dc.computeRacksOnline;
const sku = RACK_SKU_CONFIGS[order.skuId]; let computeRacksFailed = dc.computeRacksFailed;
let dcRepairCosts = 0;
// Process retrofit
if (dc.status === 'retrofitting' && dc.retrofitState) {
const rs = { ...dc.retrofitState };
rs.progress += (1 + opsEff * 0.1);
if (rs.progress >= rs.total) {
if (rs.phase === 'decommissioning') {
const installSku = RACK_SKU_CONFIGS[rs.toSkuId];
const installTotal = cohortStageTotal('installation', rs.toSkuId, rs.racksRemaining);
return {
...dc,
computeRacksOnline: 0,
computeRacksFailed: 0,
rackSkuId: rs.toSkuId,
deploymentCohorts: [{
id: `retrofit-${dc.id}-${Date.now()}`,
count: rs.racksRemaining,
skuId: rs.toSkuId,
stage: 'installation' as PipelineStage,
stageProgress: 0,
stageTotal: installTotal,
repairCount: 0,
}],
retrofitState: {
...rs,
phase: 'installing' as const,
progress: 0,
total: installTotal,
},
networkHealth: computeNetworkHealth(0),
effectiveComputeRacks: 0,
usedSlots: 0,
usedPowerKW: 0,
currentUptime: 0,
energyCostPerTick: DC_TIER_CONFIGS[dc.tier].baseEnergyCostPerTick * LOCATION_CONFIGS[cluster.locationId].energyCostMultiplier,
maintenanceCostPerTick: 0,
};
} else {
notifications.push({ notifications.push({
title: 'Rack Decommissioned', title: 'Retrofit Complete',
message: `${sku.name} rack has been fully decommissioned.`, message: `${dc.name} retrofit to ${RACK_SKU_CONFIGS[rs.toSkuId].name} is complete!`,
type: 'info', type: 'success',
}); });
return {
...dc,
status: 'operational' as const,
retrofitState: null,
};
}
}
return { ...dc, retrofitState: rs };
}
// Process deployment cohorts
const updatedCohorts: DeploymentCohort[] = [];
let racksJustOnlined = 0;
let racksFailedTesting = 0;
for (const cohort of dc.deploymentCohorts) {
const speed = stageSpeed(cohort.stage, engEff, opsEff);
const newProgress = cohort.stageProgress + speed;
if (newProgress < cohort.stageTotal) {
updatedCohorts.push({ ...cohort, stageProgress: newProgress });
continue; continue;
} }
if (order.stage === 'repair') { if (cohort.stage === 'decommission') {
const total = stageTotal('testing', order); continue;
rackPipeline.push({ }
...order,
if (cohort.stage === 'repair') {
const testTotal = cohortStageTotal('testing', cohort.skuId, cohort.count);
updatedCohorts.push({
...cohort,
stage: 'testing', stage: 'testing',
stageProgress: 0, stageProgress: 0,
stageTotal: total, stageTotal: testTotal,
}); });
continue; continue;
} }
const next = nextStage(order.stage); const next = nextStage(cohort.stage);
if (next === 'production') { if (next === 'production') {
const sku = RACK_SKU_CONFIGS[order.skuId]; const sku = RACK_SKU_CONFIGS[cohort.skuId];
const dc = dataCenters.find(d => d.id === order.dataCenterId);
const cooling = dc?.coolingLevel ?? 0;
const effectiveFailRate = sku.testFailureRate const effectiveFailRate = sku.testFailureRate
* (1 - cooling * COOLING_FAILURE_REDUCTION) * (1 - dc.coolingLevel * COOLING_FAILURE_REDUCTION)
* (1 - opsEff * 0.2) * (1 - opsEff * 0.2)
* (1 - qaResearchBonus); * (1 - qaResearchBonus);
if (Math.random() < effectiveFailRate) { const failed = binomialSample(cohort.count, effectiveFailRate);
const repairCost = sku.baseCost * sku.repairCostFraction; const passed = cohort.count - failed;
repairCosts += repairCost;
rackPipeline.push({ racksJustOnlined += passed;
...order,
if (failed > 0) {
racksFailedTesting += failed;
const repairCost = sku.baseCost * sku.repairCostFraction * failed;
dcRepairCosts += repairCost;
updatedCohorts.push({
id: `repair-${cohort.id}`,
count: failed,
skuId: cohort.skuId,
stage: 'repair', stage: 'repair',
stageProgress: 0, stageProgress: 0,
stageTotal: RACK_REPAIR_BASE_TICKS, stageTotal: cohortStageTotal('repair', cohort.skuId, failed),
repairCount: order.repairCount + 1, repairCount: cohort.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 { } else {
const total = stageTotal(next, order); const total = cohortStageTotal(next, cohort.skuId, cohort.count);
rackPipeline.push({ updatedCohorts.push({
...order, ...cohort,
stage: next, stage: next,
stageProgress: 0, stageProgress: 0,
stageTotal: total, stageTotal: total,
@@ -162,125 +320,145 @@ export function processInfrastructure(state: GameState): InfraTickResult {
} }
} }
// Add newly completed racks to their data centers computeRacksOnline += racksJustOnlined;
for (const rack of newRacks) {
const dcIdx = dataCenters.findIndex(d => d.id === rack.dataCenterId); if (racksFailedTesting > 0) {
if (dcIdx !== -1) { const skuName = dc.rackSkuId ? RACK_SKU_CONFIGS[dc.rackSkuId].name : 'Unknown';
dataCenters[dcIdx] = { notifications.push({
...dataCenters[dcIdx], title: 'Racks Failed Testing',
racks: [...dataCenters[dcIdx].racks, rack], message: `${dc.name}: ${racksFailedTesting} ${skuName} rack${racksFailedTesting > 1 ? 's' : ''} failed QA — repair batch created.`,
}; type: 'warning',
} });
} }
// --- Phase 3: Production Failures --- if (racksJustOnlined > 0 && updatedCohorts.filter(c => c.stage !== 'repair').length === 0) {
for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { notifications.push({
const dc = dataCenters[dcIdx]; title: 'Deployment Complete',
if (dc.status !== 'operational') continue; message: `${dc.name}: all racks deployed and online!`,
type: 'success',
const updatedRacks: Rack[] = []; });
for (const rack of dc.racks) {
if (!rack.isHealthy) {
updatedRacks.push(rack);
continue;
} }
const sku = RACK_SKU_CONFIGS[rack.skuId]; // Production failures (statistical)
if (computeRacksOnline > 0 && dc.rackSkuId) {
const sku = RACK_SKU_CONFIGS[dc.rackSkuId];
const effectiveRate = sku.productionFailureRate const effectiveRate = sku.productionFailureRate
* (1 - dc.coolingLevel * COOLING_FAILURE_REDUCTION) * (1 - dc.coolingLevel * COOLING_FAILURE_REDUCTION)
* (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION); * (1 - dc.redundancyLevel * REDUNDANCY_FAILURE_REDUCTION);
if (Math.random() < effectiveRate) { const prodFailures = binomialSample(computeRacksOnline, effectiveRate);
updatedRacks.push({ ...rack, isHealthy: false }); if (prodFailures > 0) {
const repairCost = sku.baseCost * sku.repairCostFraction; computeRacksOnline -= prodFailures;
repairCosts += repairCost; computeRacksFailed += prodFailures;
const repairCost = sku.baseCost * sku.repairCostFraction * prodFailures;
dcRepairCosts += repairCost;
rackPipeline.push({ updatedCohorts.push({
id: rack.id, id: `prodfail-${dc.id}-${Date.now()}`,
skuId: rack.skuId, count: prodFailures,
dataCenterId: dc.id, skuId: dc.rackSkuId,
stage: 'repair', stage: 'repair',
stageProgress: 0, stageProgress: 0,
stageTotal: RACK_REPAIR_BASE_TICKS, stageTotal: cohortStageTotal('repair', dc.rackSkuId, prodFailures),
totalCost: repairCost,
repairCount: 0, repairCount: 0,
}); });
notifications.push({ notifications.push({
title: 'Rack Failure', title: 'Production Failure',
message: `${sku.name} rack failed in ${dc.name}. Sent for repair.`, message: `${dc.name}: ${prodFailures} rack${prodFailures > 1 ? 's' : ''} failed in production — sent for repair.`,
type: 'danger', type: 'danger',
}); });
} else {
updatedRacks.push(rack);
} }
} }
// Remove failed racks from the DC (they're now in the repair pipeline) repairCosts += dcRepairCosts;
dataCenters[dcIdx] = {
...dc, // Network health
racks: updatedRacks.filter(r => r.isHealthy), const baseNetworkHealth = computeNetworkHealth(computeRacksOnline);
}; const { networkHealth, racksDisconnected } = processNetworkFailures(
baseNetworkHealth, computeRacksOnline, networkResearchBonus,
);
if (racksDisconnected > 0) {
if (networkHealth.tier3Healthy < networkHealth.tier3Required) {
notifications.push({
title: 'Core Network Failure',
message: `${dc.name}: Tier-3 core switch failure — entire DC disconnected!`,
type: 'danger',
});
} else if (racksDisconnected >= NETWORK_TOPOLOGY.tier1BlastRadius * NETWORK_TOPOLOGY.tier2BlastRadiusMultiplier) {
notifications.push({
title: 'Network Switch Failure',
message: `${dc.name}: Tier-2 aggregation failure — ${racksDisconnected} racks disconnected.`,
type: 'warning',
});
}
} }
// --- Phase 4: Compute Aggregates --- const effectiveComputeRacks = computeRacksOnline - racksDisconnected;
let totalFlops = 0;
let totalUptime = 0;
let totalRackCount = 0;
let dcWithRacks = 0;
for (let dcIdx = 0; dcIdx < dataCenters.length; dcIdx++) { // Compute aggregates for this DC
const dc = dataCenters[dcIdx]; const location = LOCATION_CONFIGS[cluster.locationId];
if (dc.status !== 'operational') continue;
const location = LOCATION_CONFIGS[dc.location];
const tierConfig = DC_TIER_CONFIGS[dc.tier]; const tierConfig = DC_TIER_CONFIGS[dc.tier];
const totalRacksInDc = computeRacksOnline + computeRacksFailed;
const netSlots = networkSlotsRequired(computeRacksOnline);
const pipelineRacks = updatedCohorts
.filter(c => c.stage !== 'decommission' && c.stage !== 'repair')
.reduce((sum, c) => sum + c.count, 0);
const usedSlots = totalRacksInDc + netSlots + pipelineRacks;
let dcFlops = 0;
let usedPowerKW = 0; let usedPowerKW = 0;
const repairingForDc = rackPipeline.filter(o => o.dataCenterId === dc.id && o.stage === 'repair').length; let dcFlops = 0;
const healthyCount = dc.racks.length; if (dc.rackSkuId && computeRacksOnline > 0) {
const totalInDc = dc.racks.length + repairingForDc; const sku = RACK_SKU_CONFIGS[dc.rackSkuId];
usedPowerKW = computeRacksOnline * sku.powerDrawKW;
for (const rack of dc.racks) { dcFlops = effectiveComputeRacks * sku.flopsPerRack;
const sku = RACK_SKU_CONFIGS[rack.skuId];
dcFlops += sku.flopsPerRack;
usedPowerKW += sku.powerDrawKW;
} }
const pipelineRacksForDc = rackPipeline.filter(o => o.dataCenterId === dc.id && o.stage !== 'decommission').length;
const usedSlots = totalInDc + pipelineRacksForDc;
const energyCostPerTick = (tierConfig.baseEnergyCostPerTick + usedPowerKW * BASE_ENERGY_COST_PER_FLOP) const energyCostPerTick = (tierConfig.baseEnergyCostPerTick + usedPowerKW * BASE_ENERGY_COST_PER_FLOP)
* location.energyCostMultiplier; * location.energyCostMultiplier;
const maintenanceCostPerTick = totalInDc * BASE_MAINTENANCE_PER_RACK; const maintenanceCostPerTick = totalRacksInDc * BASE_MAINTENANCE_PER_RACK;
const currentUptime = totalInDc > 0 ? healthyCount / totalInDc : 1; const currentUptime = totalRacksInDc > 0 ? effectiveComputeRacks / totalRacksInDc : 1;
totalFlops += dcFlops; totalFlops += dcFlops;
totalRackCount += totalInDc; totalRackCount += totalRacksInDc + netSlots;
if (totalInDc > 0) { totalComputeRackCount += totalRacksInDc;
totalDataCenterCount++;
if (totalRacksInDc > 0) {
totalUptime += currentUptime; totalUptime += currentUptime;
dcWithRacks++; dcWithRacks++;
} }
dataCenters[dcIdx] = { return {
...dataCenters[dcIdx], ...dc,
computeRacksOnline,
computeRacksFailed,
deploymentCohorts: updatedCohorts,
networkHealth,
effectiveComputeRacks,
usedSlots, usedSlots,
usedPowerKW, usedPowerKW,
energyCostPerTick, energyCostPerTick,
maintenanceCostPerTick, maintenanceCostPerTick,
currentUptime, currentUptime,
}; };
} });
return { ...campus, dataCenters };
});
return { ...cluster, campuses };
});
return { return {
infrastructure: { infrastructure: {
dataCenters, clusters,
rackPipeline,
totalFlops, totalFlops,
totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1, totalUptime: dcWithRacks > 0 ? totalUptime / dcWithRacks : 1,
totalRackCount, totalRackCount,
totalComputeRackCount,
totalDataCenterCount,
}, },
notifications, notifications,
repairCosts, repairCosts,
+63 -30
View File
@@ -1,4 +1,4 @@
import type { DCTier, DCTierConfig, RackSkuId, RackSkuConfig } from '../types/infrastructure'; import type { DCTier, DCTierConfig, RackSkuId, RackSkuConfig, NetworkTopologyConfig, CampusTierCost, ClusterCostConfig } 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;
@@ -9,7 +9,7 @@ export const FINANCIAL_SNAPSHOT_INTERVAL = 60;
export const MAX_FINANCIAL_HISTORY = 1000; export const MAX_FINANCIAL_HISTORY = 1000;
export const MAX_REPUTATION_HISTORY = 500; export const MAX_REPUTATION_HISTORY = 500;
export const STARTING_MONEY = 50_000; export const STARTING_MONEY = 600_000;
export const BASE_ENERGY_COST_PER_FLOP = 0.001; export const BASE_ENERGY_COST_PER_FLOP = 0.001;
export const TRAINING_BASE_TICKS = 120; export const TRAINING_BASE_TICKS = 120;
@@ -58,53 +58,84 @@ export const DC_TIER_CONFIGS: Record<DCTier, DCTierConfig> = {
small: { small: {
tier: 'small', tier: 'small',
name: 'Small Data Center', name: 'Small Data Center',
rackSlots: 12, rackSlots: 200,
powerBudgetKW: 60, powerBudgetKW: 1_000,
baseCost: 10_000, baseCost: 500_000,
buildTimeTicks: 300, buildTimeTicks: 600,
firstBuildTimeTicks: 10, firstBuildTimeTicks: 30,
requiredEra: 'startup', requiredEra: 'startup',
requiredResearch: null, requiredResearch: null,
baseEnergyCostPerTick: 5, baseEnergyCostPerTick: 50,
}, },
medium: { medium: {
tier: 'medium', tier: 'medium',
name: 'Medium Data Center', name: 'Medium Data Center',
rackSlots: 30, rackSlots: 500,
powerBudgetKW: 200, powerBudgetKW: 3_000,
baseCost: 50_000, baseCost: 2_000_000,
buildTimeTicks: 900, buildTimeTicks: 1200,
firstBuildTimeTicks: 900, firstBuildTimeTicks: 1200,
requiredEra: 'scaleup', requiredEra: 'scaleup',
requiredResearch: 'dc-engineering-ii', requiredResearch: 'dc-engineering-ii',
baseEnergyCostPerTick: 15, baseEnergyCostPerTick: 150,
}, },
large: { large: {
tier: 'large', tier: 'large',
name: 'Large Data Center', name: 'Large Data Center',
rackSlots: 60, rackSlots: 1000,
powerBudgetKW: 500, powerBudgetKW: 7_000,
baseCost: 200_000, baseCost: 8_000_000,
buildTimeTicks: 1800, buildTimeTicks: 2400,
firstBuildTimeTicks: 1800, firstBuildTimeTicks: 2400,
requiredEra: 'bigtech', requiredEra: 'bigtech',
requiredResearch: 'dc-engineering-iii', requiredResearch: 'dc-engineering-iii',
baseEnergyCostPerTick: 40, baseEnergyCostPerTick: 400,
}, },
mega: { mega: {
tier: 'mega', tier: 'mega',
name: 'Mega Data Center', name: 'Mega Data Center',
rackSlots: 120, rackSlots: 1500,
powerBudgetKW: 1200, powerBudgetKW: 12_000,
baseCost: 1_000_000, baseCost: 25_000_000,
buildTimeTicks: 3600, buildTimeTicks: 3600,
firstBuildTimeTicks: 3600, firstBuildTimeTicks: 3600,
requiredEra: 'agi', requiredEra: 'agi',
requiredResearch: 'dc-engineering-iv', requiredResearch: 'dc-engineering-iv',
baseEnergyCostPerTick: 100, baseEnergyCostPerTick: 1000,
}, },
}; };
// --- Campus Costs (scale with DC tier) ---
export const CAMPUS_TIER_COSTS: Record<DCTier, CampusTierCost> = {
small: { baseCost: 200_000, buildTimeTicks: 300 },
medium: { baseCost: 800_000, buildTimeTicks: 600 },
large: { baseCost: 3_000_000, buildTimeTicks: 900 },
mega: { baseCost: 10_000_000, buildTimeTicks: 1200 },
};
export const FIRST_CAMPUS_BUILD_TICKS = 15;
// --- Cluster Costs (first is free) ---
export const CLUSTER_COST_CONFIG: ClusterCostConfig = {
baseCost: 250_000,
buildTimeTicks: 600,
};
// --- Network Topology ---
export const NETWORK_TOPOLOGY: NetworkTopologyConfig = {
tier1PerCompute: 24,
tier2PerTier1: 6,
tier3PerDC: 2,
tier1FailureRate: 0.0001,
tier2FailureRate: 0.00005,
tier3FailureRate: 0.00002,
tier1BlastRadius: 24,
tier2BlastRadiusMultiplier: 6,
};
// --- Rack SKU Configs --- // --- Rack SKU Configs ---
export const RACK_SKU_CONFIGS: Record<RackSkuId, RackSkuConfig> = { export const RACK_SKU_CONFIGS: Record<RackSkuId, RackSkuConfig> = {
@@ -260,13 +291,15 @@ export const REDUNDANCY_FAILURE_REDUCTION = 0.5;
export const DC_UPGRADE_COST_FRACTION = 0.25; export const DC_UPGRADE_COST_FRACTION = 0.25;
export const DC_UPGRADE_INCREMENT = 0.1; export const DC_UPGRADE_INCREMENT = 0.1;
export const COHORT_SCALE_FACTOR = 0.0003;
export const FUNDING_ROUNDS = { export const FUNDING_ROUNDS = {
seed: { amount: 100_000, dilution: 0.10, requirements: { minRevenue: 100, minUsers: 0, minReputation: 0 } }, seed: { amount: 500_000, dilution: 0.10, requirements: { minRevenue: 500, minUsers: 0, minReputation: 0 } },
seriesA: { amount: 500_000, dilution: 0.15, requirements: { minRevenue: 500, minUsers: 100, minReputation: 20 } }, seriesA: { amount: 2_000_000, dilution: 0.15, requirements: { minRevenue: 2_500, minUsers: 100, minReputation: 20 } },
seriesB: { amount: 2_000_000, dilution: 0.12, requirements: { minRevenue: 5_000, minUsers: 1_000, minReputation: 30 } }, seriesB: { amount: 10_000_000, dilution: 0.12, requirements: { minRevenue: 25_000, minUsers: 1_000, minReputation: 30 } },
seriesC: { amount: 10_000_000, dilution: 0.10, requirements: { minRevenue: 50_000, minUsers: 10_000, minReputation: 40 } }, seriesC: { amount: 50_000_000, dilution: 0.10, requirements: { minRevenue: 250_000, minUsers: 10_000, minReputation: 40 } },
seriesD: { amount: 50_000_000, dilution: 0.08, requirements: { minRevenue: 500_000, minUsers: 50_000, minReputation: 50 } }, seriesD: { amount: 200_000_000, dilution: 0.08, requirements: { minRevenue: 2_500_000, minUsers: 50_000, minReputation: 50 } },
ipo: { amount: 200_000_000, dilution: 0.20, requirements: { minRevenue: 5_000_000, minUsers: 100_000, minReputation: 60 } }, ipo: { amount: 1_000_000_000, dilution: 0.20, requirements: { minRevenue: 25_000_000, minUsers: 100_000, minReputation: 60 } },
} as const; } as const;
export const OPEN_SOURCE_REPUTATION_BOOST = 8; export const OPEN_SOURCE_REPUTATION_BOOST = 8;
+2 -2
View File
@@ -46,7 +46,7 @@ export interface FinancialSnapshot {
} }
export const INITIAL_ECONOMY: EconomyState = { export const INITIAL_ECONOMY: EconomyState = {
money: 50_000, money: 600_000,
totalRevenue: 0, totalRevenue: 0,
totalExpenses: 0, totalExpenses: 0,
revenuePerTick: 0, revenuePerTick: 0,
@@ -56,7 +56,7 @@ export const INITIAL_ECONOMY: EconomyState = {
currentRound: null, currentRound: null,
completedRounds: [], completedRounds: [],
founderEquity: 1.0, founderEquity: 1.0,
valuation: 100_000, valuation: 1_000_000,
isPublic: false, isPublic: false,
}, },
financialHistory: [], financialHistory: [],
+1 -1
View File
@@ -58,4 +58,4 @@ export const INITIAL_SETTINGS: GameSettings = {
sfxVolume: 0.7, sfxVolume: 0.7,
}; };
export const SAVE_VERSION = 2; export const SAVE_VERSION = 3;
+122 -20
View File
@@ -1,9 +1,38 @@
import type { Era } from './gameState'; import type { Era } from './gameState';
// --- Cluster (regional container) ---
export type ClusterStatus = 'constructing' | 'operational';
export interface Cluster {
id: string;
name: string;
locationId: LocationId;
campuses: Campus[];
status: ClusterStatus;
constructionProgress: number;
constructionTotal: number;
}
// --- Campus (holds same-tier DCs) ---
export type CampusStatus = 'constructing' | 'operational';
export interface Campus {
id: string;
name: string;
clusterId: string;
dcTier: DCTier;
dataCenters: DataCenter[];
status: CampusStatus;
constructionProgress: number;
constructionTotal: number;
}
// --- Data Center --- // --- Data Center ---
export type DCTier = 'small' | 'medium' | 'large' | 'mega'; export type DCTier = 'small' | 'medium' | 'large' | 'mega';
export type DCStatus = 'constructing' | 'operational'; export type DCStatus = 'constructing' | 'operational' | 'retrofitting';
export interface DCTierConfig { export interface DCTierConfig {
tier: DCTier; tier: DCTier;
@@ -21,19 +50,71 @@ export interface DCTierConfig {
export interface DataCenter { export interface DataCenter {
id: string; id: string;
name: string; name: string;
location: LocationId; campusId: string;
tier: DCTier; tier: DCTier;
status: DCStatus; status: DCStatus;
constructionProgress: number; constructionProgress: number;
constructionTotal: number; constructionTotal: number;
racks: Rack[]; rackSkuId: RackSkuId | null;
computeRacksOnline: number;
computeRacksFailed: number;
networkHealth: NetworkHealthState;
deploymentCohorts: DeploymentCohort[];
retrofitState: RetrofitState | null;
coolingLevel: number; coolingLevel: number;
redundancyLevel: number; redundancyLevel: number;
currentUptime: number; effectiveComputeRacks: number;
energyCostPerTick: number;
maintenanceCostPerTick: number;
usedSlots: number; usedSlots: number;
usedPowerKW: number; usedPowerKW: number;
energyCostPerTick: number;
maintenanceCostPerTick: number;
currentUptime: number;
}
// --- Network Topology ---
export interface NetworkHealthState {
tier1Required: number;
tier1Healthy: number;
tier2Required: number;
tier2Healthy: number;
tier3Required: number;
tier3Healthy: number;
racksDisconnected: number;
}
export interface NetworkTopologyConfig {
tier1PerCompute: number;
tier2PerTier1: number;
tier3PerDC: number;
tier1FailureRate: number;
tier2FailureRate: number;
tier3FailureRate: number;
tier1BlastRadius: number;
tier2BlastRadiusMultiplier: number;
}
export function networkSlotsRequired(computeRacks: number): number {
if (computeRacks <= 0) return 0;
const tier1 = Math.ceil(computeRacks / 24);
const tier2 = Math.ceil(tier1 / 6);
const tier3 = 2;
return tier1 + tier2 + tier3;
}
export function maxComputeRacks(totalSlots: number): number {
if (totalSlots <= 2) return 0;
let lo = 0;
let hi = totalSlots;
while (lo < hi) {
const mid = Math.ceil((lo + hi) / 2);
if (mid + networkSlotsRequired(mid) <= totalSlots) {
lo = mid;
} else {
hi = mid - 1;
}
}
return lo;
} }
// --- Racks --- // --- Racks ---
@@ -70,43 +151,64 @@ export interface RackSkuConfig {
repairCostFraction: number; repairCostFraction: number;
} }
export interface Rack { // --- Deployment Cohort (batch pipeline) ---
id: string;
skuId: RackSkuId;
dataCenterId: string;
isHealthy: boolean;
}
export interface RackOrder { export interface DeploymentCohort {
id: string; id: string;
count: number;
skuId: RackSkuId; skuId: RackSkuId;
dataCenterId: string;
stage: PipelineStage; stage: PipelineStage;
stageProgress: number; stageProgress: number;
stageTotal: number; stageTotal: number;
totalCost: number;
repairCount: number; repairCount: number;
} }
// --- Retrofit ---
export interface RetrofitState {
fromSkuId: RackSkuId;
toSkuId: RackSkuId;
phase: 'decommissioning' | 'installing';
progress: number;
total: number;
racksRemaining: number;
}
// --- Campus Config ---
export interface CampusTierCost {
baseCost: number;
buildTimeTicks: number;
}
// --- Cluster Config ---
export interface ClusterCostConfig {
baseCost: number;
buildTimeTicks: number;
}
// --- Infrastructure State --- // --- Infrastructure State ---
export interface InfrastructureState { export interface InfrastructureState {
dataCenters: DataCenter[]; clusters: Cluster[];
rackPipeline: RackOrder[];
totalFlops: number; totalFlops: number;
totalUptime: number; totalUptime: number;
totalRackCount: number; totalRackCount: number;
totalComputeRackCount: number;
totalDataCenterCount: number;
} }
export const INITIAL_INFRASTRUCTURE: InfrastructureState = { export const INITIAL_INFRASTRUCTURE: InfrastructureState = {
dataCenters: [], clusters: [],
rackPipeline: [],
totalFlops: 0, totalFlops: 0,
totalUptime: 1, totalUptime: 1,
totalRackCount: 0, totalRackCount: 0,
totalComputeRackCount: 0,
totalDataCenterCount: 0,
}; };
// --- Locations (unchanged) --- // --- Locations ---
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';