Redesign infrastructure to hypercluster scale with 4-level hierarchy
CI / build-and-push (push) Successful in 43s
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:
@@ -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';
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
@@ -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',
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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: [],
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user