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
+351 -79
View File
@@ -6,8 +6,10 @@ import type {
ResearchState, ModelsState, MarketState,
CompetitorState, TalentState, DataState,
ReputationState, AchievementState,
DataCenter, DCTier, RackSkuId, TrainingJob,
Cluster, Campus, DataCenter, DCTier, RackSkuId, TrainingJob,
ActiveResearch, OwnedDataset, LocationId,
DeploymentCohort, PipelineStage,
NetworkHealthState,
} from '@ai-tycoon/shared';
import type { FundingRoundType, OverloadPolicy, TuningPreset, ModelTuning } from '@ai-tycoon/shared';
import {
@@ -18,8 +20,12 @@ import {
INITIAL_REPUTATION, INITIAL_ACHIEVEMENTS,
DC_TIER_CONFIGS, RACK_SKU_CONFIGS,
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,
OPEN_SOURCE_REPUTATION_BOOST,
LOCATION_CONFIGS,
networkSlotsRequired, maxComputeRacks,
uuid,
} from '@ai-tycoon/shared';
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'
| '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 {
activePage: ActivePage;
notifications: GameNotification[];
infraNav: InfraNav;
}
export interface GameNotification {
@@ -41,8 +57,13 @@ export interface GameNotification {
read: boolean;
}
function emptyNetworkHealth(): NetworkHealthState {
return { tier1Required: 0, tier1Healthy: 0, tier2Required: 0, tier2Healthy: 0, tier3Required: 0, tier3Healthy: 0, racksDisconnected: 0 };
}
interface Actions {
setActivePage: (page: ActivePage) => void;
setInfraNav: (nav: InfraNav) => void;
addNotification: (n: Omit<GameNotification, 'id' | 'read'>) => void;
dismissNotification: (id: string) => void;
markAllNotificationsRead: () => void;
@@ -50,9 +71,14 @@ interface Actions {
setGameSpeed: (speed: GameSpeed) => void;
togglePause: () => void;
setTrainingAllocation: (ratio: number) => void;
buildDataCenter: (name: string, location: LocationId, tier: DCTier) => void;
orderRack: (dataCenterId: string, skuId: RackSkuId) => void;
decommissionRack: (dataCenterId: string, rackId: string) => void;
buildCluster: (name: string, locationId: LocationId) => void;
buildCampus: (name: string, clusterId: string, dcTier: DCTier) => 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;
startTraining: (job: Omit<TrainingJob, 'progressTicks'>) => void;
deployModel: (modelId: string) => void;
@@ -97,15 +123,73 @@ const initialGameState: GameState = {
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>()(
persist(
(set, get) => ({
...initialGameState,
activePage: 'dashboard' as ActivePage,
notifications: [],
infraNav: { level: 'clusters' } as InfraNav,
setActivePage: (page) => set({ activePage: page }),
setInfraNav: (nav) => set({ infraNav: nav }),
addNotification: (n) => set((s) => ({
notifications: [
{ ...n, id: uuid(), read: false },
@@ -138,6 +222,7 @@ export const useGameStore = create<Store>()(
},
activePage: 'dashboard',
notifications: [],
infraNav: { level: 'clusters' },
}),
setGameSpeed: (speed) => set((s) => ({
@@ -152,121 +237,310 @@ export const useGameStore = create<Store>()(
compute: { ...s.compute, trainingAllocation: ratio, inferenceAllocation: 1 - ratio },
})),
buildDataCenter: (name, location, tier) => set((s) => {
const tierConfig = DC_TIER_CONFIGS[tier];
if (s.economy.money < tierConfig.baseCost) return s;
// --- Infrastructure: Cluster ---
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'];
if (eraOrder.indexOf(s.meta.currentEra) < eraOrder.indexOf(tierConfig.requiredEra)) 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 dc: DataCenter = {
id: uuid(),
name,
location,
tier,
campusId,
tier: found.campus.dcTier,
status: 'constructing',
constructionProgress: 0,
constructionTotal: buildTime,
racks: [],
rackSkuId: null,
computeRacksOnline: 0,
computeRacksFailed: 0,
networkHealth: emptyNetworkHealth(),
deploymentCohorts: [],
retrofitState: null,
coolingLevel: 0,
redundancyLevel: 0,
currentUptime: 1,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
effectiveComputeRacks: 0,
usedSlots: 0,
usedPowerKW: 0,
energyCostPerTick: 0,
maintenanceCostPerTick: 0,
currentUptime: 1,
};
return {
economy: { ...s.economy, money: s.economy.money - tierConfig.baseCost },
infrastructure: {
...s.infrastructure,
dataCenters: [...s.infrastructure.dataCenters, dc],
},
infrastructure: updateCampusInInfra(s.infrastructure, campusId, (campus) => ({
...campus,
dataCenters: [...campus.dataCenters, dc],
})),
};
}),
orderRack: (dataCenterId, skuId) => set((s) => {
const sku = RACK_SKU_CONFIGS[skuId];
if (s.economy.money < sku.baseCost) return s;
// --- Infrastructure: Deploy Racks ---
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'];
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];
const activePipeline = s.infrastructure.rackPipeline.filter(o => o.dataCenterId === dataCenterId && o.stage !== 'decommission');
const actualUsedSlots = dc.racks.length + activePipeline.length;
const pipelinePowerForDc = activePipeline
.reduce((sum, o) => sum + RACK_SKU_CONFIGS[o.skuId].powerDrawKW, 0);
const actualUsedPower = dc.racks.reduce((sum, r) => sum + RACK_SKU_CONFIGS[r.skuId].powerDrawKW, 0) + pipelinePowerForDc;
if (actualUsedSlots >= tierConfig.rackSlots) return s;
if (actualUsedPower + sku.powerDrawKW > tierConfig.powerBudgetKW) return s;
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;
const actualQty = Math.min(quantity, available);
if (actualQty <= 0) 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(),
count: actualQty,
skuId,
dataCenterId,
stage: 'ordered' as const,
stage: 'ordered',
stageProgress: 0,
stageTotal: PIPELINE_ORDER_BASE_TICKS,
totalCost: sku.baseCost,
stageTotal: scaledTicks,
repairCount: 0,
};
return {
economy: { ...s.economy, money: s.economy.money - sku.baseCost },
infrastructure: {
...s.infrastructure,
rackPipeline: [...s.infrastructure.rackPipeline, order],
},
economy: { ...s.economy, money: s.economy.money - totalCost },
infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...d,
rackSkuId: skuId,
deploymentCohorts: [...d.deploymentCohorts, cohort],
})),
};
}),
decommissionRack: (dataCenterId, rackId) => set((s) => {
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId);
if (!dc || dc.status !== 'operational') return s;
fillDCToCapacity: (dataCenterId, skuId) => {
const s = get();
const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'operational') return;
const rack = dc.racks.find(r => r.id === rackId);
if (!rack) return s;
const dc = found.dc;
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 dataCenters = s.infrastructure.dataCenters.map(d => {
if (d.id !== dataCenterId) return d;
return { ...d, racks: d.racks.filter(r => r.id !== rackId) };
});
const sku = RACK_SKU_CONFIGS[skuId];
const affordableQty = Math.floor(s.economy.money / sku.baseCost);
const powerLimit = Math.floor((tierConfig.powerBudgetKW - existingCompute * sku.powerDrawKW) / sku.powerDrawKW);
const qty = Math.min(available, affordableQty, powerLimit);
if (qty <= 0) return;
const order = {
id: rackId,
skuId: rack.skuId,
dataCenterId,
stage: 'decommission' as const,
stageProgress: 0,
stageTotal: sku.pipelineTimeTicks.installation,
totalCost: 0,
repairCount: 0,
};
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,
});
}
return {
infrastructure: {
...s.infrastructure,
dataCenters,
rackPipeline: [...s.infrastructure.rackPipeline, order],
},
economy: { ...s.economy, money: s.economy.money - totalCost },
infrastructure: updateCampusInInfra(s.infrastructure, campusId, (campus) => ({
...campus,
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) => {
const dc = s.infrastructure.dataCenters.find(d => d.id === dataCenterId);
if (!dc || dc.status !== 'operational') return s;
const found = findDC(s.infrastructure, dataCenterId);
if (!found || found.dc.status !== 'operational') return s;
const dc = found.dc;
const tierConfig = DC_TIER_CONFIGS[dc.tier];
const cost = tierConfig.baseCost * DC_UPGRADE_COST_FRACTION;
if (s.economy.money < cost) return s;
@@ -274,21 +548,18 @@ export const useGameStore = create<Store>()(
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 {
return {
economy: { ...s.economy, money: s.economy.money - cost },
infrastructure: updateDCInInfra(s.infrastructure, dataCenterId, (d) => ({
...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 },
})),
};
}),
// --- Non-infrastructure actions (unchanged) ---
startTraining: (job) => set((s) => ({
models: {
...s.models,
@@ -463,7 +734,7 @@ export const useGameStore = create<Store>()(
name: 'ai-tycoon-save',
version: SAVE_VERSION,
partialize: (state) => {
const { activePage, notifications, ...rest } = state;
const { activePage, notifications, infraNav, ...rest } = state;
return rest;
},
migrate: (_persisted, version) => {
@@ -474,11 +745,12 @@ export const useGameStore = create<Store>()(
notifications: [{
id: uuid(),
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,
tick: 0,
read: false,
}],
infraNav: { level: 'clusters' },
} as unknown as Store;
}
return _persisted as Store;