Compare commits

...

3 Commits

Author SHA1 Message Date
josh 19f652b43a Replace per-switch network simulation with aggregate per-DC statistical model
Balance Check / balance-simulation (push) Successful in 48s
Balance Check / multi-run-balance (push) Successful in 1m24s
CI / build-and-push (push) Successful in 43s
Eliminates the 22K-object switchRegistry that caused O(n×m) scans 4x per tick.
Network health is now tracked as aggregate counts per tier (totalByTier/healthyByTier)
with RepairBatch timers, cutting late-game tick cost from ~50ms to ~0.3ms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 20:06:40 -04:00
josh 57a81be769 Cache serving pipeline fleet to eliminate per-tick rebuilds and reduce GC pressure
Fleet template is now rebuilt only when deploymentVersion changes (~68 times per
28,800-tick run instead of every tick). Reuses module-level Maps, arrays, and
utilization objects instead of allocating new ones each tick. Replaces 4x
Object.values().reduce() with single-pass aggregation and sorts fleet in-place.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 19:51:13 -04:00
josh bbb69a315c Remove benchmark evaluation system, use training capabilities directly
Model quality for market segments and product lines now derives from deployed
model capabilities (coding, reasoning, agents, etc.) instead of requiring a
separate manual benchmark evaluation step. This eliminates an unbounded
benchmarkResults[] array that was scanned 5x per tick and removes ~480 lines
of dead-weight UI, types, and engine code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 19:28:59 -04:00
16 changed files with 428 additions and 999 deletions
+2 -1
View File
@@ -162,7 +162,8 @@ function CohortStageBreakdown({ cohorts }: { cohorts: DeploymentCohort[] }) {
function NetworkHealthIndicator({ dc }: { dc: DataCenter }) { function NetworkHealthIndicator({ dc }: { dc: DataCenter }) {
const ns = dc.networkSummary; const ns = dc.networkSummary;
if (ns.switchIds.length === 0) return null; const torTotal = ns.totalByTier?.tor ?? 0;
if (torTotal === 0) return null;
const hasDisconnected = ns.racksDisconnected > 0; const hasDisconnected = ns.racksDisconnected > 0;
const hasDegraded = ns.racksDegraded > 0; const hasDegraded = ns.racksDegraded > 0;
+6 -249
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { Play, Rocket, Globe, ChevronDown, ChevronUp, Beaker, Shield, Zap, BarChart3 } from 'lucide-react'; import { Play, Rocket, Globe, ChevronDown, ChevronUp, Beaker, Shield, Zap } from 'lucide-react';
import { TutorialHint } from '@/components/game/TutorialHint'; import { TutorialHint } from '@/components/game/TutorialHint';
import { ConfirmModal } from '@/components/common/ConfirmModal'; import { ConfirmModal } from '@/components/common/ConfirmModal';
import { useGameStore } from '@/store'; import { useGameStore } from '@/store';
@@ -16,10 +16,9 @@ import {
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import type { import type {
ModelArchitecture, DataMixAllocation, SFTSpecialization, AlignmentMethod, ModelArchitecture, DataMixAllocation, SFTSpecialization, AlignmentMethod,
DataDomain, QuantizationLevel, BaseModel, ModelVariant, BenchmarkResult, DataDomain, QuantizationLevel, BaseModel, ModelVariant,
SizeTier, ModelFamily, SizeTier, ModelFamily,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { BENCHMARKS } from '@ai-tycoon/game-engine';
const DATA_MIX_PRESETS: Record<string, { label: string; mix: DataMixAllocation }> = { const DATA_MIX_PRESETS: Record<string, { label: string; mix: DataMixAllocation }> = {
balanced: { label: 'Balanced', mix: DEFAULT_DATA_MIX }, balanced: { label: 'Balanced', mix: DEFAULT_DATA_MIX },
@@ -52,8 +51,6 @@ export function ModelsPage() {
const families = useGameStore((s) => s.models.families); const families = useGameStore((s) => s.models.families);
const pipelines = useGameStore((s) => s.models.activeTrainingPipelines); const pipelines = useGameStore((s) => s.models.activeTrainingPipelines);
const variantJobs = useGameStore((s) => s.models.variantJobs); const variantJobs = useGameStore((s) => s.models.variantJobs);
const evalJobs = useGameStore((s) => s.models.evalJobs);
const benchmarkResults = useGameStore((s) => s.models.benchmarkResults);
const productLines = useGameStore((s) => s.models.productLines); const productLines = useGameStore((s) => s.models.productLines);
const totalFlops = useGameStore((s) => s.compute.totalFlops); const totalFlops = useGameStore((s) => s.compute.totalFlops);
const totalVramGB = useGameStore((s) => s.compute.totalVramGB); const totalVramGB = useGameStore((s) => s.compute.totalVramGB);
@@ -64,7 +61,6 @@ export function ModelsPage() {
const deployModel = useGameStore((s) => s.deployModel); const deployModel = useGameStore((s) => s.deployModel);
const deployVariant = useGameStore((s) => s.deployVariant); const deployVariant = useGameStore((s) => s.deployVariant);
const createQuantization = useGameStore((s) => s.createQuantization); const createQuantization = useGameStore((s) => s.createQuantization);
const startEvaluation = useGameStore((s) => s.startEvaluation);
const setTrainingAllocation = useGameStore((s) => s.setTrainingAllocation); const setTrainingAllocation = useGameStore((s) => s.setTrainingAllocation);
const openSourceModel = useGameStore((s) => s.openSourceModel); const openSourceModel = useGameStore((s) => s.openSourceModel);
const openSourcedModels = useGameStore((s) => s.market.openSourcedModels); const openSourcedModels = useGameStore((s) => s.market.openSourcedModels);
@@ -96,15 +92,12 @@ export function ModelsPage() {
const activePipelines = pipelines.filter(p => p.status === 'active' || p.status === 'stalled'); const activePipelines = pipelines.filter(p => p.status === 'active' || p.status === 'stalled');
const activeVariantJobs = variantJobs.filter(j => j.status === 'active'); const activeVariantJobs = variantJobs.filter(j => j.status === 'active');
const activeEvalJobs = evalJobs.filter(j => j.status === 'active');
const undeployedCount = baseModels.filter(m => !m.isDeployed).length; const undeployedCount = baseModels.filter(m => !m.isDeployed).length;
const hasActiveJobs = activePipelines.length > 0 || activeVariantJobs.length > 0 || activeEvalJobs.length > 0; const hasActiveJobs = activePipelines.length > 0 || activeVariantJobs.length > 0;
const noModelDeployed = baseModels.length > 0 && !baseModels.some(m => m.isDeployed); const noModelDeployed = baseModels.length > 0 && !baseModels.some(m => m.isDeployed);
const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'] as const; const eraOrder = ['startup', 'scaleup', 'bigtech', 'agi'] as const;
const currentEraIdx = eraOrder.indexOf(currentEra); const currentEraIdx = eraOrder.indexOf(currentEra);
const availableBenchmarks = BENCHMARKS.filter(b => eraOrder.indexOf(b.unlockedAtEra) <= currentEraIdx);
const hasAlignmentResearch = completedResearch.some(r => const hasAlignmentResearch = completedResearch.some(r =>
r === 'alignment-research' || r === 'interpretability' || r === 'constitutional-ai', r === 'alignment-research' || r === 'interpretability' || r === 'constitutional-ai',
); );
@@ -186,7 +179,6 @@ export function ModelsPage() {
{ id: 'overview' as const, label: 'Overview' }, { id: 'overview' as const, label: 'Overview' },
{ id: 'train' as const, label: 'Train New' }, { id: 'train' as const, label: 'Train New' },
{ id: 'models' as const, label: `Families${families.length > 0 ? ` (${families.length})` : ''}` }, { id: 'models' as const, label: `Families${families.length > 0 ? ` (${families.length})` : ''}` },
{ id: 'benchmarks' as const, label: 'Benchmarks' },
{ id: 'products' as const, label: 'Products' }, { id: 'products' as const, label: 'Products' },
]).map(tab => ( ]).map(tab => (
<button <button
@@ -347,28 +339,6 @@ export function ModelsPage() {
</div> </div>
)} )}
{/* Active Eval Jobs */}
{modelsTab === 'overview' && activeEvalJobs.length > 0 && (
<div className="space-y-3">
<h3 className="font-semibold">Running Evaluations</h3>
{activeEvalJobs.map(job => {
const model = baseModels.find(m => m.id === job.modelId) ?? families.flatMap(f => f.variants).find(v => v.id === job.modelId);
const progress = job.progressTicks / job.totalTicks;
return (
<div key={job.id} className="bg-surface-900 border border-surface-700 rounded-xl p-3">
<div className="flex items-center justify-between mb-1">
<span className="text-sm">{model?.name ?? 'Unknown'} {job.benchmarkIds.length} benchmarks</span>
<span className="text-xs text-surface-400">{formatPercent(progress)}</span>
</div>
<div className="h-1.5 bg-surface-800 rounded-full overflow-hidden">
<div className="h-full bg-blue-500 rounded-full transition-all" style={{ width: `${progress * 100}%` }} />
</div>
</div>
);
})}
</div>
)}
{/* Train New Model */} {/* Train New Model */}
{modelsTab === 'train' && <div className="bg-surface-900 border border-surface-700 rounded-xl p-4 space-y-4"> {modelsTab === 'train' && <div className="bg-surface-900 border border-surface-700 rounded-xl p-4 space-y-4">
<h3 className="font-semibold">Train New Model</h3> <h3 className="font-semibold">Train New Model</h3>
@@ -716,9 +686,8 @@ export function ModelsPage() {
{familyModels.map(model => ( {familyModels.map(model => (
<div key={model.id} className="space-y-3"> <div key={model.id} className="space-y-3">
<h5 className="text-sm font-medium text-surface-300">{model.name}</h5> <h5 className="text-sm font-medium text-surface-300">{model.name}</h5>
<ModelDetails model={model} benchmarkResults={benchmarkResults} /> <ModelDetails model={model} />
<QuantizationCreator model={model} completedResearch={completedResearch} onQuantize={createQuantization} /> <QuantizationCreator model={model} completedResearch={completedResearch} onQuantize={createQuantization} />
<BenchmarkEvaluator modelId={model.id} modelName={model.name} availableBenchmarks={availableBenchmarks} benchmarkResults={benchmarkResults} evalJobs={evalJobs} onStartEval={startEvaluation} />
</div> </div>
))} ))}
@@ -730,11 +699,7 @@ export function ModelsPage() {
key={variant.id} key={variant.id}
variant={variant} variant={variant}
familyId={family.id} familyId={family.id}
benchmarkResults={benchmarkResults}
availableBenchmarks={availableBenchmarks}
evalJobs={evalJobs}
onDeploy={() => deployVariant(family.id, variant.id)} onDeploy={() => deployVariant(family.id, variant.id)}
onStartEval={startEvaluation}
/> />
))} ))}
</div> </div>
@@ -747,21 +712,6 @@ export function ModelsPage() {
</div> </div>
)} )}
{/* Benchmark Leaderboard */}
{modelsTab === 'benchmarks' && benchmarkResults.length > 0 && (
<BenchmarkLeaderboard
benchmarkResults={benchmarkResults}
baseModels={baseModels}
families={families}
availableBenchmarks={availableBenchmarks}
/>
)}
{modelsTab === 'benchmarks' && benchmarkResults.length === 0 && (
<div className="bg-surface-900 border border-surface-700 rounded-xl p-8 text-center text-surface-500 text-sm">
No benchmark results yet. Run evaluations from the Models tab.
</div>
)}
{/* Product Lines */} {/* Product Lines */}
{modelsTab === 'products' && <div className="space-y-3"> {modelsTab === 'products' && <div className="space-y-3">
<h3 className="font-semibold">Product Lines</h3> <h3 className="font-semibold">Product Lines</h3>
@@ -865,9 +815,7 @@ function ModelActions({ model, isOpenSourced, onDeploy, onOpenSource }: {
); );
} }
function ModelDetails({ model, benchmarkResults }: { model: BaseModel; benchmarkResults: BenchmarkResult[] }) { function ModelDetails({ model }: { model: BaseModel }) {
const modelResults = benchmarkResults.filter(r => r.modelId === model.id);
return ( return (
<div className="space-y-3"> <div className="space-y-3">
<div className="grid grid-cols-3 gap-3 text-xs"> <div className="grid grid-cols-3 gap-3 text-xs">
@@ -907,22 +855,6 @@ function ModelDetails({ model, benchmarkResults }: { model: BaseModel; benchmark
</div> </div>
</div> </div>
{modelResults.length > 0 && (
<div>
<span className="text-xs font-medium text-surface-300">Benchmark Scores</span>
<div className="grid grid-cols-3 gap-2 mt-1">
{modelResults.map(r => {
const bench = BENCHMARKS.find(b => b.id === r.benchmarkId);
return (
<div key={r.benchmarkId} className="bg-surface-800 rounded-lg p-2 text-xs">
<span className="text-surface-400">{bench?.name ?? r.benchmarkId}</span>
<div className="font-mono mt-0.5 text-accent-light">{r.score.toFixed(1)}</div>
</div>
);
})}
</div>
</div>
)}
</div> </div>
); );
} }
@@ -981,91 +913,12 @@ function QuantizationCreator({ model, completedResearch, onQuantize }: {
); );
} }
function BenchmarkEvaluator({ modelId, modelName, availableBenchmarks, benchmarkResults, evalJobs, onStartEval }: { function VariantCard({ variant, familyId, onDeploy }: {
modelId: string;
modelName: string;
availableBenchmarks: typeof BENCHMARKS;
benchmarkResults: BenchmarkResult[];
evalJobs: { id: string; modelId: string; status: string }[];
onStartEval: (modelId: string, benchmarkIds: string[]) => void;
}) {
const [showEval, setShowEval] = useState(false);
const [selectedBenchmarks, setSelectedBenchmarks] = useState<string[]>([]);
const existingResults = benchmarkResults.filter(r => r.modelId === modelId);
const evaluatedIds = new Set(existingResults.map(r => r.benchmarkId));
const isEvaluating = evalJobs.some(j => j.modelId === modelId && j.status === 'active');
const unevaluated = availableBenchmarks.filter(b => !evaluatedIds.has(b.id));
if (unevaluated.length === 0 && !showEval) {
return null;
}
if (!showEval) {
return (
<button onClick={() => { setShowEval(true); setSelectedBenchmarks(unevaluated.map(b => b.id)); }}
disabled={isEvaluating}
className="flex items-center gap-1 text-xs text-blue-400 hover:text-blue-300 disabled:opacity-50">
<BarChart3 size={12} /> Run Benchmarks ({unevaluated.length} available)
</button>
);
}
return (
<div className="bg-surface-800/50 rounded-lg p-3 space-y-2">
<div className="flex items-center justify-between">
<span className="text-xs font-medium text-surface-300">Run Evaluation</span>
<button onClick={() => setShowEval(false)} className="text-xs text-surface-500 hover:text-surface-300">Close</button>
</div>
<div className="flex flex-wrap gap-1">
{availableBenchmarks.map(bench => {
const alreadyDone = evaluatedIds.has(bench.id);
const selected = selectedBenchmarks.includes(bench.id);
return (
<button key={bench.id}
disabled={alreadyDone}
onClick={() => setSelectedBenchmarks(prev =>
prev.includes(bench.id) ? prev.filter(id => id !== bench.id) : [...prev, bench.id]
)}
className={`px-2 py-0.5 rounded text-[10px] border ${
alreadyDone ? 'bg-success/10 border-success/30 text-success cursor-default' :
selected ? 'bg-blue-500/20 border-blue-500 text-blue-300' :
'bg-surface-800 border-surface-600 text-surface-400'
}`}
title={bench.description}
>
{bench.name} {alreadyDone ? `(${existingResults.find(r => r.benchmarkId === bench.id)?.score.toFixed(0)})` : ''}
</button>
);
})}
</div>
{selectedBenchmarks.length > 0 && (
<div className="flex items-center justify-between">
<span className="text-[10px] text-surface-500">
{selectedBenchmarks.length} benchmark{selectedBenchmarks.length > 1 ? 's' : ''} · ~{availableBenchmarks.filter(b => selectedBenchmarks.includes(b.id)).reduce((s, b) => s + b.ticksToRun, 0)} ticks
</span>
<button onClick={() => { onStartEval(modelId, selectedBenchmarks); setShowEval(false); }}
disabled={isEvaluating}
className="bg-blue-600 hover:bg-blue-700 text-white rounded px-3 py-1 text-xs disabled:opacity-50">
Evaluate
</button>
</div>
)}
</div>
);
}
function VariantCard({ variant, familyId, benchmarkResults, availableBenchmarks, evalJobs, onDeploy, onStartEval }: {
variant: ModelVariant; variant: ModelVariant;
familyId: string; familyId: string;
benchmarkResults: BenchmarkResult[];
availableBenchmarks: typeof BENCHMARKS;
evalJobs: { id: string; modelId: string; status: string }[];
onDeploy: () => void; onDeploy: () => void;
onStartEval: (modelId: string, benchmarkIds: string[]) => void;
}) { }) {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const variantResults = benchmarkResults.filter(r => r.modelId === variant.id);
return ( return (
<div className="bg-surface-800/50 rounded-lg p-3 ml-4 border-l-2 border-surface-600"> <div className="bg-surface-800/50 rounded-lg p-3 ml-4 border-l-2 border-surface-600">
@@ -1106,104 +959,8 @@ function VariantCard({ variant, familyId, benchmarkResults, availableBenchmarks,
</div> </div>
))} ))}
</div> </div>
{variantResults.length > 0 && (
<div className="grid grid-cols-3 gap-2">
{variantResults.map(r => {
const bench = BENCHMARKS.find(b => b.id === r.benchmarkId);
return (
<div key={r.benchmarkId} className="bg-surface-800 rounded p-1.5 text-xs">
<span className="text-surface-400 text-[10px]">{bench?.name ?? r.benchmarkId}</span>
<div className="font-mono text-accent-light text-[11px]">{r.score.toFixed(1)}</div>
</div>
);
})}
</div> </div>
)} )}
<BenchmarkEvaluator
modelId={variant.id}
modelName={variant.name}
availableBenchmarks={availableBenchmarks}
benchmarkResults={benchmarkResults}
evalJobs={evalJobs}
onStartEval={onStartEval}
/>
</div>
)}
</div>
);
}
function BenchmarkLeaderboard({ benchmarkResults, baseModels, families, availableBenchmarks }: {
benchmarkResults: BenchmarkResult[];
baseModels: BaseModel[];
families: { id: string; name: string; variants: ModelVariant[] }[];
availableBenchmarks: typeof BENCHMARKS;
}) {
const allModels: (BaseModel | ModelVariant)[] = [
...baseModels,
...families.flatMap(f => f.variants),
];
const modelNames = new Map(allModels.map(m => [m.id, m.name]));
const benchmarksWithResults = availableBenchmarks.filter(b =>
benchmarkResults.some(r => r.benchmarkId === b.id),
);
if (benchmarksWithResults.length === 0) return null;
const modelIds = [...new Set(benchmarkResults.map(r => r.modelId))];
return (
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
<h3 className="font-semibold mb-3 flex items-center gap-2">
<BarChart3 size={16} /> Benchmark Leaderboard
</h3>
<div className="overflow-x-auto">
<table className="w-full text-xs">
<thead>
<tr className="border-b border-surface-700">
<th className="text-left py-1.5 pr-3 text-surface-400 font-medium">Model</th>
{benchmarksWithResults.map(b => (
<th key={b.id} className="text-center py-1.5 px-2 text-surface-400 font-medium">{b.name}</th>
))}
<th className="text-center py-1.5 px-2 text-surface-400 font-medium">Avg</th>
</tr>
</thead>
<tbody>
{modelIds.map(modelId => {
const results = benchmarkResults.filter(r => r.modelId === modelId);
const scores = benchmarksWithResults.map(b => {
const r = results.find(r => r.benchmarkId === b.id);
return r?.score ?? null;
});
const validScores = scores.filter((s): s is number => s !== null);
const avg = validScores.length > 0 ? validScores.reduce((a, b) => a + b, 0) / validScores.length : 0;
return (
<tr key={modelId} className="border-b border-surface-800">
<td className="py-1.5 pr-3 font-medium">{modelNames.get(modelId) ?? 'Unknown'}</td>
{scores.map((score, i) => (
<td key={i} className="text-center py-1.5 px-2 font-mono">
{score !== null ? (
<span className={score >= 80 ? 'text-success' : score >= 50 ? 'text-accent-light' : 'text-surface-400'}>
{score.toFixed(1)}
</span>
) : (
<span className="text-surface-600"></span>
)}
</td>
))}
<td className="text-center py-1.5 px-2 font-mono font-medium text-accent-light">
{avg > 0 ? avg.toFixed(1) : '—'}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div> </div>
); );
} }
+4 -30
View File
@@ -15,7 +15,6 @@ import type {
TrainingPipeline, ModelFamily, DataMixAllocation, TrainingPipeline, ModelFamily, DataMixAllocation,
ModelArchitecture, AlignmentMethod, SizeTier, ModelArchitecture, AlignmentMethod, SizeTier,
SFTSpecialization, QuantizationLevel, VariantCreationJob, SFTSpecialization, QuantizationLevel, VariantCreationJob,
EvalJob,
ConsumerTierId, ApiTierId, ConsumerTierId, ApiTierId,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { import {
@@ -43,7 +42,7 @@ import {
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { import {
emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary, emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary,
BENCHMARKS, TECH_TREE, onModelDeployed, TECH_TREE, onModelDeployed,
} from '@ai-tycoon/game-engine'; } from '@ai-tycoon/game-engine';
import { INITIAL_RIVALS } from '@ai-tycoon/game-engine'; import { INITIAL_RIVALS } from '@ai-tycoon/game-engine';
@@ -59,7 +58,7 @@ export interface InfraNav {
datacenterId?: string; datacenterId?: string;
} }
type ModelsTab = 'overview' | 'train' | 'models' | 'benchmarks' | 'products'; type ModelsTab = 'overview' | 'train' | 'models' | 'products';
interface UIState { interface UIState {
activePage: ActivePage; activePage: ActivePage;
@@ -132,7 +131,6 @@ interface Actions {
}) => void; }) => void;
startPointRelease: (baseModelId: string) => void; startPointRelease: (baseModelId: string) => void;
createQuantization: (baseModelId: string, level: QuantizationLevel, variantName: string) => void; createQuantization: (baseModelId: string, level: QuantizationLevel, variantName: string) => void;
startEvaluation: (modelId: string, benchmarkIds: string[]) => void;
deployModel: (modelId: string) => void; deployModel: (modelId: string) => void;
deployVariant: (familyId: string, variantId: string) => void; deployVariant: (familyId: string, variantId: string) => void;
setProductPricing: (productLineId: string, field: string, value: number) => void; setProductPricing: (productLineId: string, field: string, value: number) => void;
@@ -1076,32 +1074,6 @@ export const useGameStore = create<Store>()(
} }
}, },
startEvaluation: (modelId, benchmarkIds) => {
let created = false;
set((s) => {
const benchmarks = BENCHMARKS.filter(b => benchmarkIds.includes(b.id));
if (benchmarks.length === 0) return s;
created = true;
const totalTicks = benchmarks.reduce((sum, b) => sum + b.ticksToRun, 0);
const computeCost = benchmarks.reduce((sum, b) => sum + b.computeCost, 0);
const job: EvalJob = {
id: uuid(),
modelId,
benchmarkIds,
progressTicks: 0,
totalTicks,
computeAllocated: computeCost,
status: 'active',
results: [],
};
return { models: { ...s.models, evalJobs: [...s.models.evalJobs, job] } };
});
if (created) {
get().addNotification({ title: 'Evaluation Started', message: `${benchmarkIds.length} benchmark${benchmarkIds.length > 1 ? 's' : ''} queued.`, type: 'info', tick: get().meta.tickCount });
set({ modelsTab: 'overview' as ModelsTab });
}
},
deployModel: (modelId) => { deployModel: (modelId) => {
const modelName = get().models.baseModels.find(m => m.id === modelId)?.name ?? 'Model'; const modelName = get().models.baseModels.find(m => m.id === modelId)?.name ?? 'Model';
set((s) => ({ set((s) => ({
@@ -1113,6 +1085,7 @@ export const useGameStore = create<Store>()(
productLines: s.models.productLines.map(pl => ({ productLines: s.models.productLines.map(pl => ({
...pl, modelId, isActive: true, ...pl, modelId, isActive: true,
})), })),
deploymentVersion: s.models.deploymentVersion + 1,
}, },
market: { market: {
...s.market, ...s.market,
@@ -1132,6 +1105,7 @@ export const useGameStore = create<Store>()(
? { ...f, variants: f.variants.map(v => v.id === variantId ? { ...v, isDeployed: true } : v) } ? { ...f, variants: f.variants.map(v => v.id === variantId ? { ...v, isDeployed: true } : v) }
: f, : f,
), ),
deploymentVersion: s.models.deploymentVersion + 1,
}, },
})); }));
get().addNotification({ title: 'Variant Deployed', message: 'Variant is now live.', type: 'success', tick: get().meta.tickCount }); get().addNotification({ title: 'Variant Deployed', message: 'Variant is now live.', type: 'success', tick: get().meta.tickCount });
@@ -8,10 +8,10 @@ import type { DeepPartial } from './createTestState';
function emptyDCNetwork(): DCNetworkSummary { function emptyDCNetwork(): DCNetworkSummary {
return { return {
switchIds: [],
networkRackCount: 0,
totalByTier: {}, totalByTier: {},
healthyByTier: {}, healthyByTier: {},
repairBatches: [],
networkRackCount: 0,
racksDisconnected: 0, racksDisconnected: 0,
racksDegraded: 0, racksDegraded: 0,
averageBandwidth: 1, averageBandwidth: 1,
@@ -20,11 +20,11 @@ function emptyDCNetwork(): DCNetworkSummary {
} }
function emptyCampusNetwork(): CampusNetworkSummary { function emptyCampusNetwork(): CampusNetworkSummary {
return { switchIds: [], totalT4: 0, healthyT4: 0, crossDCBandwidth: 1 }; return { totalT4: 0, healthyT4: 0, crossDCBandwidth: 1 };
} }
function emptyClusterNetwork(): ClusterNetworkSummary { function emptyClusterNetwork(): ClusterNetworkSummary {
return { switchIds: [], totalT5: 0, healthyT5: 0, crossCampusBandwidth: 1 }; return { totalT5: 0, healthyT5: 0, crossCampusBandwidth: 1 };
} }
export function createTestDataCenter(overrides?: DeepPartial<DataCenter>): DataCenter { export function createTestDataCenter(overrides?: DeepPartial<DataCenter>): DataCenter {
@@ -171,7 +171,6 @@ export function createTestBaseModel(overrides?: Partial<BaseModel>): BaseModel {
sizeTier: 'small', sizeTier: 'small',
isPointRelease: false, isPointRelease: false,
sourceModelId: null, sourceModelId: null,
benchmarkResults: {},
dataMix: { web: 0.4, code: 0.2, books: 0.15, academic: 0.1, conversational: 0.1, specialized: 0.05 }, dataMix: { web: 0.4, code: 0.2, books: 0.15, academic: 0.1, conversational: 0.1, specialized: 0.05 },
}; };
return overrides ? { ...base, ...overrides } : base; return overrides ? { ...base, ...overrides } : base;
@@ -181,9 +180,10 @@ export function createTestModelFamily(overrides?: Partial<ModelFamily>): ModelFa
const base: ModelFamily = { const base: ModelFamily = {
id: uuid(), id: uuid(),
name: 'Test Family', name: 'Test Family',
baseModels: [], generation: 1,
baseModelIds: [],
variants: [], variants: [],
activeEvals: [], createdAtTick: 0,
}; };
return overrides ? { ...base, ...overrides } : base; return overrides ? { ...base, ...overrides } : base;
} }
-111
View File
@@ -1,111 +0,0 @@
import type { BenchmarkDefinition } from '@ai-tycoon/shared';
export const BENCHMARKS: BenchmarkDefinition[] = [
{
id: 'arc-challenge',
name: 'ARC Challenge',
category: 'reasoning',
description: 'Advanced reasoning and comprehension tasks requiring multi-step inference.',
primaryCapability: 'reasoning',
secondaryCapability: 'knowledge',
computeCost: 0.001,
ticksToRun: 8,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.3, enterprise: 0.5, developer: 0.4, research: 0.8 },
},
{
id: 'codeforce',
name: 'CodeForce',
category: 'coding',
description: 'Competitive programming and software engineering benchmarks.',
primaryCapability: 'coding',
secondaryCapability: 'reasoning',
computeCost: 0.001,
ticksToRun: 8,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.2, enterprise: 0.7, developer: 0.9, research: 0.5 },
},
{
id: 'mathquest',
name: 'MathQuest',
category: 'math',
description: 'Mathematical problem-solving from algebra to graduate-level proofs.',
primaryCapability: 'math',
secondaryCapability: 'reasoning',
computeCost: 0.001,
ticksToRun: 8,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.1, enterprise: 0.6, developer: 0.5, research: 0.9 },
},
{
id: 'worldfacts',
name: 'WorldFacts',
category: 'knowledge',
description: 'Broad factual knowledge across science, history, culture, and current events.',
primaryCapability: 'knowledge',
secondaryCapability: 'reasoning',
computeCost: 0.001,
ticksToRun: 6,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.5, enterprise: 0.4, developer: 0.3, research: 0.6 },
},
{
id: 'chatrank',
name: 'ChatRank',
category: 'chat',
description: 'Human preference evaluation of conversational quality, helpfulness, and creativity.',
primaryCapability: 'creative',
secondaryCapability: 'knowledge',
computeCost: 0.002,
ticksToRun: 10,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.9, enterprise: 0.3, developer: 0.2, research: 0.2 },
},
{
id: 'harmguard',
name: 'HarmGuard',
category: 'safety',
description: 'Safety evaluation measuring harm avoidance, truthfulness, and responsible behavior.',
primaryCapability: 'reasoning',
computeCost: 0.001,
ticksToRun: 8,
unlockedAtEra: 'startup',
marketRelevance: { consumer: 0.4, enterprise: 0.9, developer: 0.3, research: 0.7 },
},
{
id: 'visionbench',
name: 'VisionBench',
category: 'multimodal',
description: 'Image understanding, visual reasoning, and multimodal comprehension.',
primaryCapability: 'multimodal',
secondaryCapability: 'reasoning',
computeCost: 0.003,
ticksToRun: 12,
unlockedAtEra: 'scaleup',
marketRelevance: { consumer: 0.5, enterprise: 0.6, developer: 0.6, research: 0.7 },
},
{
id: 'agentarena',
name: 'AgentArena',
category: 'agents',
description: 'Autonomous agent tasks: tool use, multi-step planning, and environment interaction.',
primaryCapability: 'agents',
secondaryCapability: 'coding',
computeCost: 0.005,
ticksToRun: 15,
unlockedAtEra: 'bigtech',
marketRelevance: { consumer: 0.3, enterprise: 0.8, developer: 0.7, research: 0.6 },
},
{
id: 'frontier-eval',
name: 'Frontier Eval',
category: 'reasoning',
description: 'Cutting-edge capability evaluation at the frontier of AI research.',
primaryCapability: 'reasoning',
secondaryCapability: 'math',
computeCost: 0.01,
ticksToRun: 20,
unlockedAtEra: 'agi',
marketRelevance: { consumer: 0.2, enterprise: 0.5, developer: 0.5, research: 1.0 },
},
];
+1 -1
View File
@@ -3,6 +3,7 @@ export { processTick, setAchievementDefinitions } from './tick';
export type { TickNotification } from './tick'; export type { TickNotification } from './tick';
export { getAvailableResearch, getResearchNode } from './systems/researchSystem'; export { getAvailableResearch, getResearchNode } from './systems/researchSystem';
export { getResearchBonuses, resetResearchBonusCache } from './systems/researchBonuses'; export { getResearchBonuses, resetResearchBonusCache } from './systems/researchBonuses';
export { resetFleetCache } from './systems/market/servingPipeline';
export type { ResearchBonuses } from './systems/researchBonuses'; export type { ResearchBonuses } from './systems/researchBonuses';
export { emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary } from './systems/infrastructureSystem'; export { emptyDCNetworkSummary, emptyCampusNetworkSummary, emptyClusterNetworkSummary } from './systems/infrastructureSystem';
export { onModelDeployed } from './systems/market/obsolescenceSystem'; export { onModelDeployed } from './systems/market/obsolescenceSystem';
@@ -11,4 +12,3 @@ export { TECH_TREE } from './data/techTree';
export { INITIAL_RIVALS } from './data/competitors'; export { INITIAL_RIVALS } from './data/competitors';
export { KEY_HIRE_POOL } from './data/keyHires'; export { KEY_HIRE_POOL } from './data/keyHires';
export { ACHIEVEMENT_DEFINITIONS } from './data/achievements'; export { ACHIEVEMENT_DEFINITIONS } from './data/achievements';
export { BENCHMARKS } from './data/benchmarks';
@@ -1,8 +1,8 @@
import type { import type {
GameState, InfrastructureState, Cluster, Campus, DataCenter, GameState, InfrastructureState, Cluster, Campus, DataCenter,
DeploymentCohort, PipelineStage, RackSkuId, NetworkSwitch, DeploymentCohort, PipelineStage, RackSkuId,
SwitchTier, DCNetworkSummary, CampusNetworkSummary, ClusterNetworkSummary, SwitchTier, DCNetworkSummary, CampusNetworkSummary, ClusterNetworkSummary,
CampusRetrofitQueue, DCTier, IntraNodeInterconnect, NetworkFabric, RackSkuConfig, RepairBatch, CampusRetrofitQueue, DCTier, IntraNodeInterconnect, NetworkFabric, RackSkuConfig,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { import {
LOCATION_CONFIGS, LOCATION_CONFIGS,
@@ -83,357 +83,202 @@ function binomialSample(n: number, p: number): number {
return base + (Math.random() < frac ? 1 : 0); return base + (Math.random() < frac ? 1 : 0);
} }
// --- Network Topology Construction --- // --- Aggregate Network Model ---
let switchIdCounter = 0; const DC_TIERS: SwitchTier[] = ['tor', 't1', 't2', 't3'];
function createSwitch(
tier: SwitchTier,
dcId: string | null,
campusId: string | null,
clusterId: string | null,
): NetworkSwitch {
const config = SWITCH_TIER_CONFIGS[tier];
return {
id: `${tier}-${dcId ?? campusId ?? clusterId ?? 'x'}-${switchIdCounter++}`,
tier,
status: 'healthy',
dcId, campusId, clusterId,
uplinkIds: [],
downlinkIds: [],
activeUplinks: config.uplinkCount,
totalUplinks: config.uplinkCount,
effectiveBandwidth: 1.0,
repairProgress: 0,
repairTotal: 0,
};
}
function wireUplinks(child: NetworkSwitch, parents: NetworkSwitch[], count: number): void {
if (parents.length === 0) return;
for (let i = 0; i < count; i++) {
const parent = parents[i % parents.length];
child.uplinkIds.push(parent.id);
if (!parent.downlinkIds.includes(child.id)) {
parent.downlinkIds.push(child.id);
}
}
child.activeUplinks = count;
child.effectiveBandwidth = 1.0;
}
export function emptyDCNetworkSummary(): DCNetworkSummary { export function emptyDCNetworkSummary(): DCNetworkSummary {
return { return {
switchIds: [], networkRackCount: 0,
totalByTier: {}, healthyByTier: {}, totalByTier: {}, healthyByTier: {},
repairBatches: [], networkRackCount: 0,
racksDisconnected: 0, racksDegraded: 0, racksDisconnected: 0, racksDegraded: 0,
averageBandwidth: 1, effectiveFlopsFraction: 1, averageBandwidth: 1, effectiveFlopsFraction: 1,
}; };
} }
export function emptyCampusNetworkSummary(): CampusNetworkSummary { export function emptyCampusNetworkSummary(): CampusNetworkSummary {
return { switchIds: [], totalT4: 0, healthyT4: 0, crossDCBandwidth: 1 }; return { totalT4: 0, healthyT4: 0, crossDCBandwidth: 1 };
} }
export function emptyClusterNetworkSummary(): ClusterNetworkSummary { export function emptyClusterNetworkSummary(): ClusterNetworkSummary {
return { switchIds: [], totalT5: 0, healthyT5: 0, crossCampusBandwidth: 1 }; return { totalT5: 0, healthyT5: 0, crossCampusBandwidth: 1 };
} }
export function buildDCTopology( function computeTopologyCounts(
computeRackCount: number, computeRackCount: number,
dcTier: DCTier, dcTier: DCTier,
dcId: string, ): Partial<Record<SwitchTier, number>> {
registry: Record<string, NetworkSwitch>, if (computeRackCount <= 0) return {};
): DCNetworkSummary {
if (computeRackCount <= 0) return emptyDCNetworkSummary();
const switchIds: string[] = [];
const t3Count = T3_COUNT_PER_DC_TIER[dcTier];
const t3s: NetworkSwitch[] = [];
for (let i = 0; i < t3Count; i++) {
const sw = createSwitch('t3', dcId, null, null);
sw.totalUplinks = 0;
sw.activeUplinks = 0;
t3s.push(sw);
registry[sw.id] = sw;
switchIds.push(sw.id);
}
const t1Count = Math.ceil(computeRackCount / SWITCH_TIER_CONFIGS.t1.fanOut); const t1Count = Math.ceil(computeRackCount / SWITCH_TIER_CONFIGS.t1.fanOut);
const t2Count = Math.ceil(t1Count / SWITCH_TIER_CONFIGS.t2.fanOut); const t2Count = Math.ceil(t1Count / SWITCH_TIER_CONFIGS.t2.fanOut);
const t3Count = T3_COUNT_PER_DC_TIER[dcTier];
const t2s: NetworkSwitch[] = []; return { tor: computeRackCount, t1: t1Count, t2: t2Count, t3: t3Count };
for (let i = 0; i < t2Count; i++) {
const sw = createSwitch('t2', dcId, null, null);
wireUplinks(sw, t3s, SWITCH_TIER_CONFIGS.t2.uplinkCount);
t2s.push(sw);
registry[sw.id] = sw;
switchIds.push(sw.id);
} }
const t1s: NetworkSwitch[] = []; export function buildDCNetworkSummary(
for (let i = 0; i < t1Count; i++) { computeRackCount: number,
const sw = createSwitch('t1', dcId, null, null);
wireUplinks(sw, t2s, SWITCH_TIER_CONFIGS.t1.uplinkCount);
t1s.push(sw);
registry[sw.id] = sw;
switchIds.push(sw.id);
}
for (let i = 0; i < computeRackCount; i++) {
const sw = createSwitch('tor', dcId, null, null);
const primary = t1s[Math.floor(i / SWITCH_TIER_CONFIGS.t1.fanOut)];
const altIdx = (Math.floor(i / SWITCH_TIER_CONFIGS.t1.fanOut) + 1) % t1s.length;
const alt = t1s[altIdx];
if (t1s.length >= 2 && primary !== alt) {
wireUplinks(sw, [primary, alt], 2);
} else {
wireUplinks(sw, [primary], 2);
}
registry[sw.id] = sw;
switchIds.push(sw.id);
}
const networkRackCount = estimateNetworkSlots(computeRackCount, dcTier);
return buildDCSummary(switchIds, networkRackCount, registry);
}
export function expandDCTopology(
existing: DCNetworkSummary,
newRackCount: number,
dcTier: DCTier, dcTier: DCTier,
dcId: string,
registry: Record<string, NetworkSwitch>,
): DCNetworkSummary { ): DCNetworkSummary {
if (newRackCount <= 0) return existing; if (computeRackCount <= 0) return emptyDCNetworkSummary();
const totalByTier = computeTopologyCounts(computeRackCount, dcTier);
const currentTorCount = existing.totalByTier?.tor ?? 0; const healthyByTier = { ...totalByTier };
const targetTorCount = currentTorCount + newRackCount;
const t1s = existing.switchIds.map(id => registry[id]).filter((s): s is NetworkSwitch => !!s && s.tier === 't1');
const t2s = existing.switchIds.map(id => registry[id]).filter((s): s is NetworkSwitch => !!s && s.tier === 't2');
const t3s = existing.switchIds.map(id => registry[id]).filter((s): s is NetworkSwitch => !!s && s.tier === 't3');
const newIds = [...existing.switchIds];
const neededT1 = Math.ceil(targetTorCount / SWITCH_TIER_CONFIGS.t1.fanOut);
const neededT2 = Math.ceil(neededT1 / SWITCH_TIER_CONFIGS.t2.fanOut);
while (t2s.length < neededT2) {
const sw = createSwitch('t2', dcId, null, null);
wireUplinks(sw, t3s, SWITCH_TIER_CONFIGS.t2.uplinkCount);
t2s.push(sw);
registry[sw.id] = sw;
newIds.push(sw.id);
}
while (t1s.length < neededT1) {
const sw = createSwitch('t1', dcId, null, null);
wireUplinks(sw, t2s, SWITCH_TIER_CONFIGS.t1.uplinkCount);
t1s.push(sw);
registry[sw.id] = sw;
newIds.push(sw.id);
}
for (let i = 0; i < newRackCount; i++) {
const torIdx = currentTorCount + i;
const sw = createSwitch('tor', dcId, null, null);
const primary = t1s[Math.floor(torIdx / SWITCH_TIER_CONFIGS.t1.fanOut)];
const altIdx = (Math.floor(torIdx / SWITCH_TIER_CONFIGS.t1.fanOut) + 1) % t1s.length;
const alt = t1s[altIdx];
if (t1s.length >= 2 && primary !== alt) {
wireUplinks(sw, [primary, alt], 2);
} else {
wireUplinks(sw, [primary], 2);
}
registry[sw.id] = sw;
newIds.push(sw.id);
}
const networkRackCount = estimateNetworkSlots(targetTorCount, dcTier);
return buildDCSummary(newIds, networkRackCount, registry);
}
export function shrinkDCTopology(
existing: DCNetworkSummary,
removeCount: number,
dcTier: DCTier,
registry: Record<string, NetworkSwitch>,
): DCNetworkSummary {
if (removeCount <= 0) return existing;
const torIds = existing.switchIds.filter(id => registry[id]?.tier === 'tor');
const toRemove = new Set(torIds.slice(-removeCount));
for (const torId of toRemove) {
const tor = registry[torId];
if (!tor) continue;
for (const upId of tor.uplinkIds) {
const parent = registry[upId];
if (parent) parent.downlinkIds = parent.downlinkIds.filter(id => id !== torId);
}
delete registry[torId];
}
const remainingIds = existing.switchIds.filter(id => !toRemove.has(id));
const remainingTors = remainingIds.filter(id => registry[id]?.tier === 'tor').length;
return buildDCSummary(remainingIds, estimateNetworkSlots(remainingTors, dcTier), registry);
}
function computeRackBandwidth(tor: NetworkSwitch, registry: Record<string, NetworkSwitch>): number {
if (tor.status !== 'healthy') return 0;
let minBW = tor.totalUplinks > 0 ? tor.activeUplinks / tor.totalUplinks : 1;
if (minBW === 0) return 0;
const visited = new Set<string>();
let current = tor.uplinkIds.filter(id => {
const sw = registry[id];
return sw && sw.status === 'healthy';
});
while (current.length > 0) {
let tierBW = 1;
const next: string[] = [];
for (const sid of current) {
if (visited.has(sid)) continue;
visited.add(sid);
const sw = registry[sid];
if (!sw || sw.status !== 'healthy') continue;
const bw = sw.totalUplinks > 0 ? sw.activeUplinks / sw.totalUplinks : 1;
tierBW = Math.min(tierBW, bw);
for (const upId of sw.uplinkIds) {
if (registry[upId]?.status === 'healthy') next.push(upId);
}
}
minBW = Math.min(minBW, tierBW);
if (minBW === 0) return 0;
current = next;
}
return minBW;
}
function buildDCSummary(
switchIds: string[],
networkRackCount: number,
registry: Record<string, NetworkSwitch>,
): DCNetworkSummary {
const totalByTier: Partial<Record<SwitchTier, number>> = {};
const healthyByTier: Partial<Record<SwitchTier, number>> = {};
let disconnected = 0;
let degraded = 0;
let bwSum = 0;
let torCount = 0;
for (const sid of switchIds) {
const sw = registry[sid];
if (!sw) continue;
totalByTier[sw.tier] = (totalByTier[sw.tier] ?? 0) + 1;
if (sw.status === 'healthy') healthyByTier[sw.tier] = (healthyByTier[sw.tier] ?? 0) + 1;
if (sw.tier === 'tor') {
torCount++;
const bw = computeRackBandwidth(sw, registry);
bwSum += bw;
if (bw === 0) disconnected++;
else if (bw < 1) degraded++;
}
}
const avgBW = torCount > 0 ? bwSum / torCount : 1;
return { return {
switchIds, networkRackCount, totalByTier, healthyByTier, totalByTier, healthyByTier,
racksDisconnected: disconnected, racksDegraded: degraded, repairBatches: [],
averageBandwidth: avgBW, effectiveFlopsFraction: avgBW, networkRackCount: estimateNetworkSlots(computeRackCount, dcTier),
racksDisconnected: 0, racksDegraded: 0,
averageBandwidth: 1, effectiveFlopsFraction: 1,
}; };
} }
// --- Network Tick (failure rolls + repair) --- export function expandDCNetwork(
existing: DCNetworkSummary,
addedRacks: number,
dcTier: DCTier,
): DCNetworkSummary {
if (addedRacks <= 0) return existing;
const oldTor = existing.totalByTier.tor ?? 0;
const newTor = oldTor + addedRacks;
const newTotal = computeTopologyCounts(newTor, dcTier);
const healthyByTier: Partial<Record<SwitchTier, number>> = {};
for (const tier of DC_TIERS) {
const oldTotal = existing.totalByTier[tier] ?? 0;
const oldHealthy = existing.healthyByTier[tier] ?? 0;
const added = (newTotal[tier] ?? 0) - oldTotal;
healthyByTier[tier] = oldHealthy + Math.max(0, added);
}
const summary: DCNetworkSummary = {
...existing,
totalByTier: newTotal,
healthyByTier,
networkRackCount: estimateNetworkSlots(newTor, dcTier),
};
return recomputeBandwidth(summary);
}
function processNetworkTick( export function shrinkDCNetwork(
registry: Record<string, NetworkSwitch>, existing: DCNetworkSummary,
removedRacks: number,
dcTier: DCTier,
): DCNetworkSummary {
if (removedRacks <= 0) return existing;
const oldTor = existing.totalByTier.tor ?? 0;
const newTor = Math.max(0, oldTor - removedRacks);
if (newTor === 0) return emptyDCNetworkSummary();
const newTotal = computeTopologyCounts(newTor, dcTier);
const healthyByTier: Partial<Record<SwitchTier, number>> = {};
for (const tier of DC_TIERS) {
const nt = newTotal[tier] ?? 0;
const oh = existing.healthyByTier[tier] ?? 0;
healthyByTier[tier] = Math.min(oh, nt);
}
const repairBatches = existing.repairBatches.filter(b => {
const nt = newTotal[b.tier] ?? 0;
const nh = healthyByTier[b.tier] ?? 0;
return nh < nt;
});
const summary: DCNetworkSummary = {
...existing,
totalByTier: newTotal,
healthyByTier,
repairBatches,
networkRackCount: estimateNetworkSlots(newTor, dcTier),
};
return recomputeBandwidth(summary);
}
function computeAggregateBandwidth(
summary: DCNetworkSummary,
redundancyBonus: number,
): number {
let minBW = 1;
for (const tier of DC_TIERS) {
const total = summary.totalByTier[tier] ?? 0;
if (total === 0) continue;
const healthy = summary.healthyByTier[tier] ?? 0;
const tierBW = Math.min(1, (healthy + redundancyBonus) / total);
if (tierBW < minBW) minBW = tierBW;
}
return minBW;
}
function recomputeBandwidth(summary: DCNetworkSummary, redundancyBonus = 0): DCNetworkSummary {
const avgBW = computeAggregateBandwidth(summary, redundancyBonus);
const torTotal = summary.totalByTier.tor ?? 0;
const torHealthy = summary.healthyByTier.tor ?? 0;
const torFailed = torTotal - torHealthy;
const disconnected = avgBW === 0 ? torTotal : torFailed;
const degraded = avgBW > 0 && avgBW < 1 ? Math.ceil(torTotal * (1 - avgBW)) - disconnected : 0;
return {
...summary,
averageBandwidth: avgBW,
effectiveFlopsFraction: avgBW,
racksDisconnected: Math.max(0, disconnected),
racksDegraded: Math.max(0, degraded),
};
}
function processNetworkForDC(
summary: DCNetworkSummary,
networkResearchBonus: number, networkResearchBonus: number,
opsEff: number, opsEff: number,
repairSpeedBonus: number, repairSpeedBonus: number,
hotStandbyTicks: number, hotStandbyTicks: number,
redundancyBonus: number, redundancyBonus: number,
): { switchRepairCosts: number; notifications: TickNotification[]; dirtyDCs: Set<string> } { ): { summary: DCNetworkSummary; costs: number; notifications: TickNotification[] } {
const torTotal = summary.totalByTier.tor ?? 0;
if (torTotal === 0) return { summary, costs: 0, notifications: [] };
let costs = 0;
const notifications: TickNotification[] = []; const notifications: TickNotification[] = [];
let switchRepairCosts = 0; const healthyByTier = { ...summary.healthyByTier };
const dirtyDCs = new Set<string>(); let dirty = false;
const healthyByTier: Partial<Record<SwitchTier, NetworkSwitch[]>> = {}; for (const tier of DC_TIERS) {
const repairing: NetworkSwitch[] = []; const healthy = healthyByTier[tier] ?? 0;
if (healthy <= 0) continue;
for (const sw of Object.values(registry)) {
if (sw.status === 'healthy') {
(healthyByTier[sw.tier] ??= []).push(sw);
} else if (sw.status === 'repairing') {
repairing.push(sw);
}
}
const tiers: SwitchTier[] = ['tor', 't1', 't2', 't3', 't4', 't5'];
const newlyFailed: NetworkSwitch[] = [];
for (const tier of tiers) {
const healthy = healthyByTier[tier];
if (!healthy || healthy.length === 0) continue;
const rate = SWITCH_TIER_CONFIGS[tier].failureRatePerTick * (1 - networkResearchBonus); const rate = SWITCH_TIER_CONFIGS[tier].failureRatePerTick * (1 - networkResearchBonus);
const count = binomialSample(healthy.length, rate); const failed = binomialSample(healthy, rate);
if (count > 0) { if (failed > 0) {
const shuffled = [...healthy].sort(() => Math.random() - 0.5); healthyByTier[tier] = healthy - failed;
for (let i = 0; i < count; i++) {
const sw = shuffled[i];
const baseRepair = SWITCH_TIER_CONFIGS[tier].repairBaseTicks; const baseRepair = SWITCH_TIER_CONFIGS[tier].repairBaseTicks;
const repairTime = hotStandbyTicks > 0 const repairTime = hotStandbyTicks > 0
? hotStandbyTicks ? hotStandbyTicks
: baseRepair * (1 - repairSpeedBonus); : baseRepair * (1 - repairSpeedBonus);
sw.status = 'repairing'; summary.repairBatches.push({ tier, count: failed, ticksRemaining: repairTime });
sw.repairProgress = 0; costs += SWITCH_TIER_CONFIGS[tier].baseCost * SWITCH_REPAIR_COST_FRACTION * failed;
sw.repairTotal = repairTime; dirty = true;
newlyFailed.push(sw);
if (sw.dcId) dirtyDCs.add(sw.dcId);
switchRepairCosts += SWITCH_TIER_CONFIGS[tier].baseCost * SWITCH_REPAIR_COST_FRACTION;
}
}
}
for (const sw of repairing) { if (tier === 't3') {
sw.repairProgress += 1 + opsEff * 0.05;
if (sw.repairProgress >= sw.repairTotal) {
sw.status = 'healthy';
sw.repairProgress = 0;
sw.repairTotal = 0;
if (sw.dcId) dirtyDCs.add(sw.dcId);
}
}
if (dirtyDCs.size > 0) {
for (const sw of Object.values(registry)) {
if (sw.uplinkIds.length === 0) continue;
if (sw.dcId && !dirtyDCs.has(sw.dcId)) continue;
let active = 0;
for (const upId of sw.uplinkIds) {
if (registry[upId]?.status === 'healthy') active++;
}
sw.activeUplinks = active;
sw.effectiveBandwidth = sw.totalUplinks > 0 ? Math.min(1, (active + redundancyBonus) / sw.totalUplinks) : 1;
}
}
for (const sw of newlyFailed) {
if (sw.tier === 't3') {
notifications.push({ title: 'Core Network Failure', message: `Tier-3 core switch failed — potential DC disconnect!`, type: 'danger' }); notifications.push({ title: 'Core Network Failure', message: `Tier-3 core switch failed — potential DC disconnect!`, type: 'danger' });
} else if (sw.tier === 't4') { } else if (tier === 't2') {
notifications.push({ title: 'Campus Network Failure', message: `Tier-4 campus switch failed — cross-DC degradation!`, type: 'danger' });
} else if (sw.tier === 't2') {
notifications.push({ title: 'Network Switch Failure', message: `Tier-2 spine switch failed — racks may be degraded.`, type: 'warning' }); notifications.push({ title: 'Network Switch Failure', message: `Tier-2 spine switch failed — racks may be degraded.`, type: 'warning' });
} }
} }
}
return { switchRepairCosts, notifications, dirtyDCs }; const remainingBatches: RepairBatch[] = [];
for (const batch of summary.repairBatches) {
const newTicks = batch.ticksRemaining - (1 + opsEff * 0.05);
if (newTicks <= 0) {
healthyByTier[batch.tier] = Math.min(
summary.totalByTier[batch.tier] ?? 0,
(healthyByTier[batch.tier] ?? 0) + batch.count,
);
dirty = true;
} else {
remainingBatches.push({ ...batch, ticksRemaining: newTicks });
}
}
if (!dirty) return { summary: { ...summary, repairBatches: remainingBatches }, costs, notifications };
const updated: DCNetworkSummary = {
...summary,
healthyByTier,
repairBatches: remainingBatches,
};
return { summary: recomputeBandwidth(updated, redundancyBonus), costs, notifications };
} }
// --- Interconnect Training Multiplier --- // --- Interconnect Training Multiplier ---
@@ -476,14 +321,6 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
const hotStandbyTicks = state.research.completedResearch.includes('network-hot-standby') ? 5 : 0; const hotStandbyTicks = state.research.completedResearch.includes('network-hot-standby') ? 5 : 0;
const redundancyBonus = state.research.completedResearch.includes('network-redundancy') ? 1 : 0; const redundancyBonus = state.research.completedResearch.includes('network-redundancy') ? 1 : 0;
// Mutate registry in-place — infrastructure returns a new state anyway
const registry = state.infrastructure.switchRegistry;
// Process network failures/repairs globally
const netResult = processNetworkTick(registry, networkResearchBonus, opsEff, repairSpeedBonus, hotStandbyTicks, redundancyBonus);
repairCosts += netResult.switchRepairCosts;
if (netResult.notifications.length > 0) notifications.push(...netResult.notifications);
let totalFlops = 0; let totalFlops = 0;
let totalTrainingFlops = 0; let totalTrainingFlops = 0;
let totalInferenceFlops = 0; let totalInferenceFlops = 0;
@@ -541,8 +378,6 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
if (rs.progress >= rs.total) { if (rs.progress >= rs.total) {
if (rs.phase === 'decommissioning') { if (rs.phase === 'decommissioning') {
const installTotal = cohortStageTotal('installation', rs.toSkuId, rs.racksRemaining); const installTotal = cohortStageTotal('installation', rs.toSkuId, rs.racksRemaining);
// Clear DC topology on retrofit
for (const sid of dc.networkSummary.switchIds) delete registry[sid];
return { return {
...dc, ...dc,
computeRacksOnline: 0, computeRacksOnline: 0,
@@ -636,10 +471,11 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
// Expand topology for newly onlined racks // Expand topology for newly onlined racks
let networkSummary = dc.networkSummary; let networkSummary = dc.networkSummary;
if (racksJustOnlined > 0) { if (racksJustOnlined > 0) {
if (networkSummary.switchIds.length === 0) { const torTotal = networkSummary.totalByTier.tor ?? 0;
networkSummary = buildDCTopology(computeRacksOnline, dc.tier, dc.id, registry); if (torTotal === 0) {
networkSummary = buildDCNetworkSummary(computeRacksOnline, dc.tier);
} else { } else {
networkSummary = expandDCTopology(networkSummary, racksJustOnlined, dc.tier, dc.id, registry); networkSummary = expandDCNetwork(networkSummary, racksJustOnlined, dc.tier);
} }
} }
@@ -660,18 +496,20 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
stageTotal: cohortStageTotal('repair', dc.rackSkuId, prodFailures), stageTotal: cohortStageTotal('repair', dc.rackSkuId, prodFailures),
repairCount: 0, repairCount: 0,
}); });
networkSummary = shrinkDCTopology(networkSummary, prodFailures, dc.tier, registry); networkSummary = shrinkDCNetwork(networkSummary, prodFailures, dc.tier);
} }
} }
repairCosts += dcRepairCosts; repairCosts += dcRepairCosts;
// Recompute DC network summary after failures/repairs (only if this DC's switches changed) // Process per-DC network failures and repairs (aggregate model)
if (netResult.dirtyDCs.has(dc.id) && networkSummary.switchIds.length > 0) { const netResult = processNetworkForDC(
networkSummary = buildDCSummary( networkSummary, networkResearchBonus, opsEff,
networkSummary.switchIds, networkSummary.networkRackCount, registry, repairSpeedBonus, hotStandbyTicks, redundancyBonus,
); );
} networkSummary = netResult.summary;
repairCosts += netResult.costs;
if (netResult.notifications.length > 0) notifications.push(...netResult.notifications);
// Rackdown: detect recovery (previously disconnected racks now have connectivity) // Rackdown: detect recovery (previously disconnected racks now have connectivity)
const prevDisconnected = dc.networkSummary.racksDisconnected; const prevDisconnected = dc.networkSummary.racksDisconnected;
@@ -680,7 +518,7 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
if (currDisconnected < prevDisconnected && dc.rackSkuId) { if (currDisconnected < prevDisconnected && dc.rackSkuId) {
const recovered = prevDisconnected - currDisconnected; const recovered = prevDisconnected - currDisconnected;
computeRacksOnline -= recovered; computeRacksOnline -= recovered;
networkSummary = shrinkDCTopology(networkSummary, recovered, dc.tier, registry); networkSummary = shrinkDCNetwork(networkSummary, recovered, dc.tier);
updatedCohorts.push({ updatedCohorts.push({
id: `netrecovery-${dc.id}-${Date.now()}`, id: `netrecovery-${dc.id}-${Date.now()}`,
count: recovered, skuId: dc.rackSkuId, count: recovered, skuId: dc.rackSkuId,
@@ -688,10 +526,6 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
stageTotal: cohortStageTotal('testing', dc.rackSkuId, recovered), stageTotal: cohortStageTotal('testing', dc.rackSkuId, recovered),
repairCount: 0, repairCount: 0,
}); });
// Recompute summary after shrink
networkSummary = buildDCSummary(
networkSummary.switchIds, networkSummary.networkRackCount, registry,
);
} }
// Compute DC aggregates // Compute DC aggregates
@@ -789,8 +623,6 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
if (totalRacks <= 0) return dc; if (totalRacks <= 0) return dc;
const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId as RackSkuId]; const oldSku = RACK_SKU_CONFIGS[dc.rackSkuId as RackSkuId];
const decommTicks = Math.ceil(oldSku.pipelineTimeTicks.installation * (1 + COHORT_SCALE_FACTOR * totalRacks)); const decommTicks = Math.ceil(oldSku.pipelineTimeTicks.installation * (1 + COHORT_SCALE_FACTOR * totalRacks));
// Clear topology on retrofit start
for (const sid of dc.networkSummary.switchIds) delete registry[sid];
return { return {
...dc, ...dc,
status: 'retrofitting' as const, status: 'retrofitting' as const,
@@ -827,7 +659,6 @@ export function processInfrastructure(state: GameState, researchBonuses?: Resear
return { return {
infrastructure: { infrastructure: {
clusters, clusters,
switchRegistry: registry,
totalFlops, totalFlops,
totalTrainingFlops, totalTrainingFlops,
totalInferenceFlops, totalInferenceFlops,
@@ -1,7 +1,6 @@
import type { GameState, MarketState, BenchmarkResult } from '@ai-tycoon/shared'; import type { GameState, MarketState, ModelCapabilities } from '@ai-tycoon/shared';
import { CONSUMER_TOKENS_PER_SUBSCRIBER, API_TOKENS_PER_DEVELOPER_PER_TICK, BATCH_API_DEMAND_PER_DEV, makeInitialServingMetrics } from '@ai-tycoon/shared'; import { CONSUMER_TOKENS_PER_SUBSCRIBER, API_TOKENS_PER_DEVELOPER_PER_TICK, BATCH_API_DEMAND_PER_DEV, makeInitialServingMetrics } from '@ai-tycoon/shared';
import type { TrafficPriority, TierServingMetrics } from '@ai-tycoon/shared'; import type { TrafficPriority, TierServingMetrics } from '@ai-tycoon/shared';
import { BENCHMARKS } from '../../data/benchmarks';
import { computeSeasonal } from './seasonalSystem'; import { computeSeasonal } from './seasonalSystem';
import { updateObsolescence } from './obsolescenceSystem'; import { updateObsolescence } from './obsolescenceSystem';
import { buildPlayerProfile, buildCompetitorProfile, computeMarketShares, updateTAMGrowth } from './tamSystem'; import { buildPlayerProfile, buildCompetitorProfile, computeMarketShares, updateTAMGrowth } from './tamSystem';
@@ -21,31 +20,30 @@ export interface MarketTickResult {
totalTokenDemand: number; totalTokenDemand: number;
} }
const SEGMENT_CAPABILITY_WEIGHTS: Record<string, Partial<Record<keyof ModelCapabilities, number>>> = {
consumer: { creative: 0.35, knowledge: 0.25, reasoning: 0.15, multimodal: 0.15, coding: 0.05, agents: 0.05 },
enterprise: { reasoning: 0.25, coding: 0.20, agents: 0.20, knowledge: 0.15, math: 0.10, multimodal: 0.10 },
developer: { coding: 0.35, reasoning: 0.20, agents: 0.20, math: 0.15, knowledge: 0.10 },
research: { reasoning: 0.30, math: 0.30, knowledge: 0.20, coding: 0.10, agents: 0.10 },
};
function getSegmentQuality( function getSegmentQuality(
segment: 'consumer' | 'enterprise' | 'developer' | 'research', segment: 'consumer' | 'enterprise' | 'developer' | 'research',
benchmarkResults: BenchmarkResult[], capabilities: ModelCapabilities,
fallbackScore: number, fallbackScore: number,
): number { ): number {
if (benchmarkResults.length === 0) return fallbackScore / 100; const weights = SEGMENT_CAPABILITY_WEIGHTS[segment];
if (!weights) return fallbackScore / 100;
const bestByBenchmark = new Map<string, number>();
for (const r of benchmarkResults) {
const prev = bestByBenchmark.get(r.benchmarkId) ?? 0;
if (r.score > prev) bestByBenchmark.set(r.benchmarkId, r.score);
}
let weightedSum = 0; let weightedSum = 0;
let totalWeight = 0; let totalWeight = 0;
for (const bench of BENCHMARKS) { for (const [cap, weight] of Object.entries(weights)) {
const score = bestByBenchmark.get(bench.id); const score = capabilities[cap as keyof ModelCapabilities] ?? 0;
if (score == null) continue; if (score > 0) {
const weight = bench.marketRelevance[segment];
weightedSum += (score / 100) * weight; weightedSum += (score / 100) * weight;
totalWeight += weight; totalWeight += weight;
} }
}
if (totalWeight === 0) return fallbackScore / 100; return totalWeight > 0 ? weightedSum / totalWeight : fallbackScore / 100;
return weightedSum / totalWeight;
} }
export function processMarketV2( export function processMarketV2(
@@ -54,9 +52,11 @@ export function processMarketV2(
effectiveInferenceFlops?: number, effectiveInferenceFlops?: number,
researchBonuses?: ResearchBonuses, researchBonuses?: ResearchBonuses,
): MarketTickResult { ): MarketTickResult {
const consumerQuality = getSegmentQuality('consumer', state.models.benchmarkResults, state.models.bestDeployedModelScore); const caps = state.models.bestDeployedCapabilities;
const enterpriseQuality = getSegmentQuality('enterprise', state.models.benchmarkResults, state.models.bestDeployedModelScore); const hasDeployed = state.models.bestDeployedModelScore > 0;
const modelQuality = state.models.benchmarkResults.length > 0 const consumerQuality = getSegmentQuality('consumer', caps, state.models.bestDeployedModelScore);
const enterpriseQuality = getSegmentQuality('enterprise', caps, state.models.bestDeployedModelScore);
const modelQuality = hasDeployed
? (consumerQuality + enterpriseQuality) / 2 ? (consumerQuality + enterpriseQuality) / 2
: state.models.bestDeployedModelScore / 100; : state.models.bestDeployedModelScore / 100;
@@ -115,7 +115,7 @@ export function processMarketV2(
const productResult = processProductLines( const productResult = processProductLines(
state.market.codeAssistant, state.market.codeAssistant,
state.market.agentsPlatform, state.market.agentsPlatform,
state.models.benchmarkResults, caps,
playerDevCustomers, playerDevCustomers,
playerEntCustomers, playerEntCustomers,
seasonal.multipliers.consumer, seasonal.multipliers.consumer,
@@ -1,4 +1,4 @@
import type { CodeAssistantState, AgentsPlatformState, BenchmarkResult } from '@ai-tycoon/shared'; import type { CodeAssistantState, AgentsPlatformState, ModelCapabilities } from '@ai-tycoon/shared';
import { import {
CODE_ASSISTANT_MIN_CODING_SCORE, CODE_ASSISTANT_MIN_CODING_SCORE,
CODE_ASSISTANT_BASE_ADOPTION_RATE, CODE_ASSISTANT_BASE_ADOPTION_RATE,
@@ -7,27 +7,6 @@ import {
AGENTS_PLATFORM_BASE_ADOPTION_RATE, AGENTS_PLATFORM_BASE_ADOPTION_RATE,
AGENTS_PLATFORM_CHURN_RATE, AGENTS_PLATFORM_CHURN_RATE,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { BENCHMARKS } from '../../data/benchmarks';
function getBenchmarkScore(benchmarkId: string, results: BenchmarkResult[]): number {
let best = 0;
for (const r of results) {
if (r.benchmarkId === benchmarkId && r.score > best) best = r.score;
}
return best;
}
function getCodingScore(results: BenchmarkResult[]): number {
const codeBench = BENCHMARKS.find(b => b.id === 'codeforce');
if (!codeBench) return 0;
return getBenchmarkScore(codeBench.id, results);
}
function getAgentsScore(results: BenchmarkResult[]): number {
const agentBench = BENCHMARKS.find(b => b.id === 'agentarena');
if (!agentBench) return 0;
return getBenchmarkScore(agentBench.id, results);
}
export interface ProductLineResult { export interface ProductLineResult {
codeAssistant: CodeAssistantState; codeAssistant: CodeAssistantState;
@@ -41,7 +20,7 @@ export interface ProductLineResult {
export function processProductLines( export function processProductLines(
ca: CodeAssistantState, ca: CodeAssistantState,
ap: AgentsPlatformState, ap: AgentsPlatformState,
benchmarkResults: BenchmarkResult[], capabilities: ModelCapabilities,
playerDevCustomers: number, playerDevCustomers: number,
playerEntCustomers: number, playerEntCustomers: number,
seasonalConsumerMult: number, seasonalConsumerMult: number,
@@ -53,7 +32,7 @@ export function processProductLines(
let apRevenue = 0; let apRevenue = 0;
// --- Code Assistant --- // --- Code Assistant ---
updatedCA.qualityScore = getCodingScore(benchmarkResults); updatedCA.qualityScore = capabilities.coding;
if (updatedCA.isUnlocked && updatedCA.isActive && updatedCA.qualityScore >= CODE_ASSISTANT_MIN_CODING_SCORE) { if (updatedCA.isUnlocked && updatedCA.isActive && updatedCA.qualityScore >= CODE_ASSISTANT_MIN_CODING_SCORE) {
const qualityFactor = updatedCA.qualityScore / 100; const qualityFactor = updatedCA.qualityScore / 100;
const priceAttr = Math.max(0.1, 1 - updatedCA.pricePerSeat / 50); const priceAttr = Math.max(0.1, 1 - updatedCA.pricePerSeat / 50);
@@ -70,7 +49,7 @@ export function processProductLines(
} }
// --- Agents Platform --- // --- Agents Platform ---
updatedAP.qualityScore = getAgentsScore(benchmarkResults); updatedAP.qualityScore = capabilities.agents;
if (updatedAP.isUnlocked && updatedAP.isActive && updatedAP.qualityScore >= AGENTS_PLATFORM_MIN_AGENTS_SCORE) { if (updatedAP.isUnlocked && updatedAP.isActive && updatedAP.qualityScore >= AGENTS_PLATFORM_MIN_AGENTS_SCORE) {
const qualityFactor = updatedAP.qualityScore / 100; const qualityFactor = updatedAP.qualityScore / 100;
const priceAttr = Math.max(0.1, 1 - updatedAP.pricePerSeat / 250); const priceAttr = Math.max(0.1, 1 - updatedAP.pricePerSeat / 250);
@@ -6,7 +6,7 @@ import type {
ModelUtilizationEntry, ModelUtilizationEntry,
BatchApiState, BatchApiState,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import type { BaseModel, ModelVariant, ModelFamily, ModelsState, SizeTier } from '@ai-tycoon/shared'; import type { BaseModel, ModelsState, SizeTier } from '@ai-tycoon/shared';
import { import {
MODEL_SIZE_THROUGHPUT_SCALER, MODEL_SIZE_THROUGHPUT_SCALER,
MOE_SPEED_MULTIPLIER, MOE_SPEED_MULTIPLIER,
@@ -62,73 +62,133 @@ export interface ServingPipelineResult {
batchRevenue: number; batchRevenue: number;
} }
interface CachedSlot {
modelId: string;
modelName: string;
sizeTier: SizeTier;
isVariant: boolean;
quantization: string | null;
qualityScore: number;
speedMultiplier: number;
throughputMultiplier: number;
isMoE: boolean;
}
let cachedDeploymentVersion = -1;
let cachedSlots: CachedSlot[] = [];
const fleetOutput: ModelServingSlot[] = [];
const mainRemaining = new Map<string, number>();
const mainUsed = new Map<string, number>();
const entRemaining = new Map<string, number>();
const entUsed = new Map<string, number>();
let cachedUtilization: ModelUtilizationEntry[] = [];
export function resetFleetCache(): void {
cachedDeploymentVersion = -1;
cachedSlots.length = 0;
fleetOutput.length = 0;
mainRemaining.clear();
mainUsed.clear();
entRemaining.clear();
entUsed.clear();
cachedUtilization.length = 0;
}
function buildModelFleet( function buildModelFleet(
modelsState: ModelsState, modelsState: ModelsState,
effectiveInferenceFlops: number, effectiveInferenceFlops: number,
): ModelServingSlot[] { ): ModelServingSlot[] {
const slots: ModelServingSlot[] = []; const version = modelsState.deploymentVersion;
if (version !== cachedDeploymentVersion) {
cachedSlots.length = 0;
const deployedBases: BaseModel[] = [];
const baseModelById = new Map<string, BaseModel>(); const baseModelById = new Map<string, BaseModel>();
for (const m of modelsState.baseModels) { for (const m of modelsState.baseModels) {
if (m.isDeployed) deployedBases.push(m);
baseModelById.set(m.id, m); baseModelById.set(m.id, m);
if (!m.isDeployed) continue;
const sizeFactor = MODEL_SIZE_THROUGHPUT_SCALER[m.sizeTier] ?? 1.0;
const moeFactor = m.architecture.type === 'moe' ? MOE_SPEED_MULTIPLIER : 1.0;
cachedSlots.push({
modelId: m.id,
modelName: m.name,
sizeTier: m.sizeTier,
isVariant: false,
quantization: null,
qualityScore: m.rawCapability / 100,
speedMultiplier: moeFactor,
throughputMultiplier: FLOPS_TO_TOKENS_MULTIPLIER * sizeFactor * moeFactor,
isMoE: m.architecture.type === 'moe',
});
} }
const deployedVariants: { variant: ModelVariant; baseModel: BaseModel }[] = [];
for (const family of modelsState.families) { for (const family of modelsState.families) {
for (const variant of family.variants) { for (const variant of family.variants) {
if (!variant.isDeployed) continue; if (!variant.isDeployed) continue;
const base = baseModelById.get(variant.baseModelId); const base = baseModelById.get(variant.baseModelId);
if (base) deployedVariants.push({ variant, baseModel: base }); if (!base) continue;
} const sizeFactor = MODEL_SIZE_THROUGHPUT_SCALER[base.sizeTier] ?? 1.0;
}
const totalDeployed = deployedBases.length + deployedVariants.length;
if (totalDeployed === 0 || effectiveInferenceFlops <= 0) return slots;
const flopsPerModel = effectiveInferenceFlops / totalDeployed;
for (const model of deployedBases) {
const sizeFactor = MODEL_SIZE_THROUGHPUT_SCALER[model.sizeTier] ?? 1.0;
const moeFactor = model.architecture.type === 'moe' ? MOE_SPEED_MULTIPLIER : 1.0;
const throughput = flopsPerModel * FLOPS_TO_TOKENS_MULTIPLIER * sizeFactor * moeFactor;
slots.push({
modelId: model.id,
modelName: model.name,
sizeTier: model.sizeTier,
isVariant: false,
quantization: null,
qualityScore: model.rawCapability / 100,
speedMultiplier: moeFactor,
throughputCapacity: throughput,
isMoE: model.architecture.type === 'moe',
});
}
for (const { variant, baseModel } of deployedVariants) {
const sizeFactor = MODEL_SIZE_THROUGHPUT_SCALER[baseModel.sizeTier] ?? 1.0;
const moeFactor = variant.architecture.type === 'moe' ? MOE_SPEED_MULTIPLIER : 1.0; const moeFactor = variant.architecture.type === 'moe' ? MOE_SPEED_MULTIPLIER : 1.0;
const quantConfig = variant.quantization ? QUANTIZATION_CONFIGS[variant.quantization] : null; const quantConfig = variant.quantization ? QUANTIZATION_CONFIGS[variant.quantization] : null;
const quantSpeedFactor = quantConfig?.speedMultiplier ?? 1.0; const quantSpeedFactor = quantConfig?.speedMultiplier ?? 1.0;
const qualityRetention = quantConfig?.qualityRetention ?? 1.0; const qualityRetention = quantConfig?.qualityRetention ?? 1.0;
const throughput = flopsPerModel * FLOPS_TO_TOKENS_MULTIPLIER * sizeFactor * moeFactor * quantSpeedFactor; cachedSlots.push({
slots.push({
modelId: variant.id, modelId: variant.id,
modelName: variant.name, modelName: variant.name,
sizeTier: baseModel.sizeTier, sizeTier: base.sizeTier,
isVariant: true, isVariant: true,
quantization: variant.quantization ?? null, quantization: variant.quantization ?? null,
qualityScore: (baseModel.rawCapability / 100) * qualityRetention, qualityScore: (base.rawCapability / 100) * qualityRetention,
speedMultiplier: moeFactor * quantSpeedFactor, speedMultiplier: moeFactor * quantSpeedFactor,
throughputCapacity: throughput, throughputMultiplier: FLOPS_TO_TOKENS_MULTIPLIER * sizeFactor * moeFactor * quantSpeedFactor,
isMoE: variant.architecture.type === 'moe', isMoE: variant.architecture.type === 'moe',
}); });
} }
}
return slots; cachedDeploymentVersion = version;
}
const totalDeployed = cachedSlots.length;
if (totalDeployed === 0 || effectiveInferenceFlops <= 0) {
fleetOutput.length = 0;
return fleetOutput;
}
const flopsPerModel = effectiveInferenceFlops / totalDeployed;
fleetOutput.length = totalDeployed;
for (let i = 0; i < totalDeployed; i++) {
const cs = cachedSlots[i];
const existing = fleetOutput[i];
if (existing) {
existing.modelId = cs.modelId;
existing.modelName = cs.modelName;
existing.sizeTier = cs.sizeTier;
existing.isVariant = cs.isVariant;
existing.quantization = cs.quantization;
existing.qualityScore = cs.qualityScore;
existing.speedMultiplier = cs.speedMultiplier;
existing.throughputCapacity = flopsPerModel * cs.throughputMultiplier;
existing.isMoE = cs.isMoE;
} else {
fleetOutput[i] = {
modelId: cs.modelId,
modelName: cs.modelName,
sizeTier: cs.sizeTier,
isVariant: cs.isVariant,
quantization: cs.quantization,
qualityScore: cs.qualityScore,
speedMultiplier: cs.speedMultiplier,
throughputCapacity: flopsPerModel * cs.throughputMultiplier,
isMoE: cs.isMoE,
};
}
}
return fleetOutput;
} }
function sortFleetByStrategy( function sortFleetByStrategy(
@@ -136,24 +196,23 @@ function sortFleetByStrategy(
strategy: string, strategy: string,
overallUtilization: number, overallUtilization: number,
): ModelServingSlot[] { ): ModelServingSlot[] {
const sorted = [...fleet];
switch (strategy) { switch (strategy) {
case 'quality-first': case 'quality-first':
sorted.sort((a, b) => b.qualityScore - a.qualityScore); fleet.sort((a, b) => b.qualityScore - a.qualityScore);
break; break;
case 'speed-first': case 'speed-first':
sorted.sort((a, b) => b.throughputCapacity - a.throughputCapacity); fleet.sort((a, b) => b.throughputCapacity - a.throughputCapacity);
break; break;
case 'balanced': case 'balanced':
default: default:
if (overallUtilization > 0.8) { if (overallUtilization > 0.8) {
sorted.sort((a, b) => b.throughputCapacity - a.throughputCapacity); fleet.sort((a, b) => b.throughputCapacity - a.throughputCapacity);
} else { } else {
sorted.sort((a, b) => b.qualityScore - a.qualityScore); fleet.sort((a, b) => b.qualityScore - a.qualityScore);
} }
break; break;
} }
return sorted; return fleet;
} }
interface FleetState { interface FleetState {
@@ -250,7 +309,8 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
const { modelsState, effectiveInferenceFlops, overloadPolicy, demandByTier, batchApi, modelQuality, researchUnlocks } = input; const { modelsState, effectiveInferenceFlops, overloadPolicy, demandByTier, batchApi, modelQuality, researchUnlocks } = input;
const fleet = buildModelFleet(modelsState, effectiveInferenceFlops); const fleet = buildModelFleet(modelsState, effectiveInferenceFlops);
const totalFleetCapacity = fleet.reduce((sum, s) => sum + s.throughputCapacity, 0); let totalFleetCapacity = 0;
for (const s of fleet) totalFleetCapacity += s.throughputCapacity;
if (fleet.length === 0 || totalFleetCapacity <= 0) { if (fleet.length === 0 || totalFleetCapacity <= 0) {
const metrics = makeInitialServingMetrics(); const metrics = makeInitialServingMetrics();
@@ -275,7 +335,7 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
}; };
} }
const totalDemand = Object.values(demandByTier).reduce((s, v) => s + v, 0); const totalDemand = demandByTier.enterprise + demandByTier['api-paid'] + demandByTier['consumer-paid'] + demandByTier['api-free'] + demandByTier['consumer-free'];
const overallUtilization = totalFleetCapacity > 0 ? totalDemand / totalFleetCapacity : 0; const overallUtilization = totalFleetCapacity > 0 ? totalDemand / totalFleetCapacity : 0;
const effectiveStrategy = researchUnlocks.servingRoutingUnlocked const effectiveStrategy = researchUnlocks.servingRoutingUnlocked
@@ -284,10 +344,13 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
const sortedFleet = sortFleetByStrategy(fleet, effectiveStrategy, overallUtilization); const sortedFleet = sortFleetByStrategy(fleet, effectiveStrategy, overallUtilization);
const fleetState: FleetState = { mainRemaining.clear();
remaining: new Map(fleet.map(s => [s.modelId, s.throughputCapacity])), mainUsed.clear();
used: new Map(fleet.map(s => [s.modelId, 0])), for (const s of fleet) {
}; mainRemaining.set(s.modelId, s.throughputCapacity);
mainUsed.set(s.modelId, 0);
}
const fleetState: FleetState = { remaining: mainRemaining, used: mainUsed };
const reservedCapacity = totalFleetCapacity * overloadPolicy.enterpriseReservation; const reservedCapacity = totalFleetCapacity * overloadPolicy.enterpriseReservation;
const enterpriseDemand = demandByTier['enterprise'] ?? 0; const enterpriseDemand = demandByTier['enterprise'] ?? 0;
@@ -310,10 +373,13 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
const nonEnterpriseTiers = effectivePriorityOrder.filter(t => t !== 'enterprise'); const nonEnterpriseTiers = effectivePriorityOrder.filter(t => t !== 'enterprise');
if (enterpriseDemand > 0) { if (enterpriseDemand > 0) {
const enterpriseFleetState: FleetState = { entRemaining.clear();
remaining: new Map(fleet.map(s => [s.modelId, s.throughputCapacity])), entUsed.clear();
used: new Map(fleet.map(s => [s.modelId, 0])), for (const s of fleet) {
}; entRemaining.set(s.modelId, s.throughputCapacity);
entUsed.set(s.modelId, 0);
}
const enterpriseFleetState: FleetState = { remaining: entRemaining, used: entUsed };
const reserveLimit = reservedCapacity > 0 ? reservedCapacity : totalFleetCapacity; const reserveLimit = reservedCapacity > 0 ? reservedCapacity : totalFleetCapacity;
let budgetLeft = reserveLimit; let budgetLeft = reserveLimit;
@@ -334,10 +400,10 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
); );
for (const slot of fleet) { for (const slot of fleet) {
const entUsed = enterpriseFleetState.used.get(slot.modelId) ?? 0; const entUsedForModel = enterpriseFleetState.used.get(slot.modelId) ?? 0;
const mainRemaining = fleetState.remaining.get(slot.modelId) ?? 0; const mainRemainingForModel = fleetState.remaining.get(slot.modelId) ?? 0;
fleetState.remaining.set(slot.modelId, Math.max(0, mainRemaining - entUsed + (reservedCapacity > 0 ? reservedCapacity / fleet.length : 0))); fleetState.remaining.set(slot.modelId, Math.max(0, mainRemainingForModel - entUsedForModel + (reservedCapacity > 0 ? reservedCapacity / fleet.length : 0)));
fleetState.used.set(slot.modelId, entUsed); fleetState.used.set(slot.modelId, entUsedForModel);
} }
} else { } else {
tierResults['enterprise'] = { demandTokens: 0, servedTokens: 0, queuedTokens: 0, rejectedTokens: 0, degradedTokens: 0, avgQualityDelivered: 1 }; tierResults['enterprise'] = { demandTokens: 0, servedTokens: 0, queuedTokens: 0, rejectedTokens: 0, degradedTokens: 0, avgQualityDelivered: 1 };
@@ -390,34 +456,50 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
updatedBatchApi.revenue = batchRevenue; updatedBatchApi.revenue = batchRevenue;
} }
const totalServed = Object.values(tierResults).reduce((s, t) => s + t.servedTokens, 0); let totalServed = 0;
const totalQueued = Object.values(tierResults).reduce((s, t) => s + t.queuedTokens, 0); let totalQueued = 0;
const totalRejected = Object.values(tierResults).reduce((s, t) => s + t.rejectedTokens, 0); let totalRejected = 0;
const totalDegraded = Object.values(tierResults).reduce((s, t) => s + t.degradedTokens, 0); let totalDegraded = 0;
let effectiveQuality = modelQuality;
if (totalServed > 0) {
let qualitySum = 0; let qualitySum = 0;
for (const t of Object.values(tierResults)) { for (const tier of effectivePriorityOrder) {
const t = tierResults[tier];
if (!t) continue;
totalServed += t.servedTokens;
totalQueued += t.queuedTokens;
totalRejected += t.rejectedTokens;
totalDegraded += t.degradedTokens;
qualitySum += t.avgQualityDelivered * t.servedTokens; qualitySum += t.avgQualityDelivered * t.servedTokens;
} }
effectiveQuality = qualitySum / totalServed; const effectiveQuality = totalServed > 0 ? qualitySum / totalServed : modelQuality;
}
const queuedFraction = totalDemand > 0 ? totalQueued / totalDemand : 0; const queuedFraction = totalDemand > 0 ? totalQueued / totalDemand : 0;
const avgLatencyMs = BASE_LATENCY_MS + queuedFraction * 100 * QUEUE_LATENCY_MS_PER_PERCENT; const avgLatencyMs = BASE_LATENCY_MS + queuedFraction * 100 * QUEUE_LATENCY_MS_PER_PERCENT;
const modelUtilization: ModelUtilizationEntry[] = fleet.map(slot => ({ cachedUtilization.length = fleet.length;
for (let i = 0; i < fleet.length; i++) {
const slot = fleet[i];
const used = fleetState.used.get(slot.modelId) ?? 0;
const existing = cachedUtilization[i];
if (existing) {
existing.modelId = slot.modelId;
existing.modelName = slot.modelName;
existing.quantization = slot.quantization;
existing.qualityScore = slot.qualityScore;
existing.throughputCapacity = slot.throughputCapacity;
existing.throughputUsed = used;
existing.utilization = slot.throughputCapacity > 0 ? Math.min(1, used / slot.throughputCapacity) : 0;
} else {
cachedUtilization[i] = {
modelId: slot.modelId, modelId: slot.modelId,
modelName: slot.modelName, modelName: slot.modelName,
quantization: slot.quantization, quantization: slot.quantization,
qualityScore: slot.qualityScore, qualityScore: slot.qualityScore,
throughputCapacity: slot.throughputCapacity, throughputCapacity: slot.throughputCapacity,
throughputUsed: fleetState.used.get(slot.modelId) ?? 0, throughputUsed: used,
utilization: slot.throughputCapacity > 0 utilization: slot.throughputCapacity > 0 ? Math.min(1, used / slot.throughputCapacity) : 0,
? Math.min(1, (fleetState.used.get(slot.modelId) ?? 0) / slot.throughputCapacity) };
: 0, }
})); }
const autoScaleBoost = researchUnlocks.autoScalingBonus; const autoScaleBoost = researchUnlocks.autoScalingBonus;
if (autoScaleBoost > 0) { if (autoScaleBoost > 0) {
@@ -443,7 +525,7 @@ export function processServingPipeline(input: ServingPipelineInput): ServingPipe
totalDegraded, totalDegraded,
effectiveQuality, effectiveQuality,
avgLatencyMs, avgLatencyMs,
modelUtilization, modelUtilization: cachedUtilization,
batchApiTokensServed: batchTokensServed, batchApiTokensServed: batchTokensServed,
batchApiRevenue: batchRevenue, batchApiRevenue: batchRevenue,
}, },
+16 -48
View File
@@ -1,10 +1,8 @@
import type { import type {
GameState, ModelsState, BaseModel, ModelCapabilities, SafetyProfile, GameState, ModelsState, BaseModel, ModelCapabilities, SafetyProfile,
TrainingPipeline, TrainingEvent, TrainingEventType, TrainingPipeline, TrainingEvent, TrainingEventType,
ModelVariant, VariantCreationJob, EvalJob, BenchmarkResult, ModelVariant, VariantCreationJob,
BenchmarkDefinition,
} from '@ai-tycoon/shared'; } from '@ai-tycoon/shared';
import { BENCHMARKS } from '../data/benchmarks';
import { import {
uuid, VRAM_REQUIREMENTS_BY_GENERATION, uuid, VRAM_REQUIREMENTS_BY_GENERATION,
MOE_CAPABILITY_MULTIPLIER, MOE_SPEED_MULTIPLIER, MOE_CAPABILITY_MULTIPLIER, MOE_SPEED_MULTIPLIER,
@@ -154,14 +152,21 @@ export function processModels(state: GameState, researchBonuses?: ResearchBonuse
}); });
} }
const updatedEvalJobs = processEvalJobs(state); const bestDeployedCapabilities: ModelCapabilities = {
reasoning: 0, coding: 0, creative: 0, math: 0,
knowledge: 0, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0,
};
let bestDeployedModelScore = 0; let bestDeployedModelScore = 0;
let bestDeployedSafetyScore = 0; let bestDeployedSafetyScore = 0;
for (const m of baseModels) { for (const m of baseModels) {
if (!m.isDeployed) continue; if (!m.isDeployed) continue;
if (m.rawCapability > bestDeployedModelScore) bestDeployedModelScore = m.rawCapability; if (m.rawCapability > bestDeployedModelScore) bestDeployedModelScore = m.rawCapability;
if (m.safetyProfile.overallSafety > bestDeployedSafetyScore) bestDeployedSafetyScore = m.safetyProfile.overallSafety; if (m.safetyProfile.overallSafety > bestDeployedSafetyScore) bestDeployedSafetyScore = m.safetyProfile.overallSafety;
for (const key of Object.keys(bestDeployedCapabilities) as (keyof ModelCapabilities)[]) {
if ((m.capabilities[key] ?? 0) > bestDeployedCapabilities[key]) {
bestDeployedCapabilities[key] = m.capabilities[key];
}
}
} }
for (const f of families) { for (const f of families) {
for (const v of f.variants) { for (const v of f.variants) {
@@ -169,6 +174,11 @@ export function processModels(state: GameState, researchBonuses?: ResearchBonuse
const score = computeVariantScore(v); const score = computeVariantScore(v);
if (score > bestDeployedModelScore) bestDeployedModelScore = score; if (score > bestDeployedModelScore) bestDeployedModelScore = score;
if (v.safetyProfile.overallSafety > bestDeployedSafetyScore) bestDeployedSafetyScore = v.safetyProfile.overallSafety; if (v.safetyProfile.overallSafety > bestDeployedSafetyScore) bestDeployedSafetyScore = v.safetyProfile.overallSafety;
for (const key of Object.keys(bestDeployedCapabilities) as (keyof ModelCapabilities)[]) {
if ((v.capabilities[key] ?? 0) > bestDeployedCapabilities[key]) {
bestDeployedCapabilities[key] = v.capabilities[key];
}
}
} }
} }
@@ -179,10 +189,9 @@ export function processModels(state: GameState, researchBonuses?: ResearchBonuse
families, families,
activeTrainingPipelines: updatedPipelines, activeTrainingPipelines: updatedPipelines,
variantJobs: updatedVariantJobs.jobs, variantJobs: updatedVariantJobs.jobs,
evalJobs: updatedEvalJobs.jobs,
benchmarkResults: [...state.models.benchmarkResults, ...updatedEvalJobs.newResults],
bestDeployedModelScore, bestDeployedModelScore,
bestDeployedSafetyScore, bestDeployedSafetyScore,
bestDeployedCapabilities,
}, },
completedModels, completedModels,
notifications, notifications,
@@ -490,47 +499,6 @@ function createVariant(job: VariantCreationJob, base: BaseModel): ModelVariant {
}; };
} }
function processEvalJobs(state: GameState): { jobs: EvalJob[]; newResults: BenchmarkResult[] } {
const newResults: BenchmarkResult[] = [];
const allModels: (BaseModel | ModelVariant)[] = [
...state.models.baseModels,
...state.models.families.flatMap(f => f.variants),
];
const jobs = state.models.evalJobs.map(job => {
if (job.status !== 'active') return job;
const newProgress = job.progressTicks + 1;
if (newProgress >= job.totalTicks) {
const model = allModels.find(m => m.id === job.modelId);
if (model) {
const results = computeBenchmarkScores(model, job.benchmarkIds, state.meta.tickCount);
newResults.push(...results);
return { ...job, status: 'completed' as const, progressTicks: job.totalTicks, results };
}
return { ...job, status: 'completed' as const, progressTicks: job.totalTicks };
}
return { ...job, progressTicks: newProgress };
});
return { jobs, newResults };
}
function computeBenchmarkScores(
model: BaseModel | ModelVariant,
benchmarkIds: string[],
tick: number,
): BenchmarkResult[] {
const benchmarkMap = new Map(BENCHMARKS.map(b => [b.id, b]));
return benchmarkIds.map(id => {
const bench = benchmarkMap.get(id);
if (!bench) return { benchmarkId: id, modelId: model.id, score: 0, ranAtTick: tick };
const primary = model.capabilities[bench.primaryCapability] ?? 0;
const secondary = bench.secondaryCapability ? (model.capabilities[bench.secondaryCapability] ?? 0) : 0;
const noise = (Math.random() - 0.5) * 6;
const score = clamp(primary * 0.7 + secondary * 0.3 + noise);
return { benchmarkId: id, modelId: model.id, score, ranAtTick: tick };
});
}
function computeVariantScore(variant: ModelVariant): number { function computeVariantScore(variant: ModelVariant): number {
const c = variant.capabilities; const c = variant.capabilities;
return (c.reasoning * 0.25 + c.coding * 0.2 + c.creative * 0.15 + c.math * 0.15 + c.knowledge * 0.15 + c.agents * 0.1); return (c.reasoning * 0.25 + c.coding * 0.2 + c.creative * 0.15 + c.math * 0.15 + c.knowledge * 0.15 + c.agents * 0.1);
+2 -1
View File
@@ -3,12 +3,14 @@ import { processTick, setAchievementDefinitions } from './tick';
import { createTestState, createSeededRNG } from './__test-utils__'; import { createTestState, createSeededRNG } from './__test-utils__';
import { ACHIEVEMENT_DEFINITIONS } from './data/achievements'; import { ACHIEVEMENT_DEFINITIONS } from './data/achievements';
import { resetResearchBonusCache } from './systems/researchBonuses'; import { resetResearchBonusCache } from './systems/researchBonuses';
import { resetFleetCache } from './systems/market/servingPipeline';
const rng = createSeededRNG(42); const rng = createSeededRNG(42);
beforeEach(() => { beforeEach(() => {
rng.install(); rng.install();
resetResearchBonusCache(); resetResearchBonusCache();
resetFleetCache();
setAchievementDefinitions(ACHIEVEMENT_DEFINITIONS); setAchievementDefinitions(ACHIEVEMENT_DEFINITIONS);
}); });
afterEach(() => rng.uninstall()); afterEach(() => rng.uninstall());
@@ -66,7 +68,6 @@ describe('processTick', () => {
isDeployed: true, trainedAtTick: 0, trainingCostTotal: 0, trainingStagesCompleted: ['pretraining' as const], isDeployed: true, trainedAtTick: 0, trainingCostTotal: 0, trainingStagesCompleted: ['pretraining' as const],
sizeTier: 'small' as const, version: 1.0, sftSpecializations: ['general' as const], alignmentMethod: 'rlhf' as const, sizeTier: 'small' as const, version: 1.0, sftSpecializations: ['general' as const], alignmentMethod: 'rlhf' as const,
dataMix: { web: 0.4, code: 0.2, books: 0.15, academic: 0.1, conversational: 0.1, specialized: 0.05 }, dataMix: { web: 0.4, code: 0.2, books: 0.15, academic: 0.1, conversational: 0.1, specialized: 0.05 },
benchmarkResults: {},
}; };
const state = createTestState({ const state = createTestState({
meta: { currentEra: 'startup' }, meta: { currentEra: 'startup' },
@@ -119,6 +119,7 @@ export function deployModel(state: GameState, modelId: string): boolean {
if (!model) return false; if (!model) return false;
model.isDeployed = true; model.isDeployed = true;
state.models.deploymentVersion = (state.models.deploymentVersion ?? 0) + 1;
for (const pl of state.models.productLines) { for (const pl of state.models.productLines) {
pl.modelId = modelId; pl.modelId = modelId;
+2 -1
View File
@@ -1,5 +1,5 @@
import type { GameState } from '@ai-tycoon/shared'; import type { GameState } from '@ai-tycoon/shared';
import { processTick, setAchievementDefinitions, ACHIEVEMENT_DEFINITIONS, resetResearchBonusCache } from '@ai-tycoon/game-engine'; import { processTick, setAchievementDefinitions, ACHIEVEMENT_DEFINITIONS, resetResearchBonusCache, resetFleetCache } from '@ai-tycoon/game-engine';
import type { TickNotification } from '@ai-tycoon/game-engine'; import type { TickNotification } from '@ai-tycoon/game-engine';
import type { Strategy, SimulationMetrics } from './strategies/types'; import type { Strategy, SimulationMetrics } from './strategies/types';
import { collectMetrics } from './analysis/metrics'; import { collectMetrics } from './analysis/metrics';
@@ -78,6 +78,7 @@ export function runSimulation(config: SimulationConfig): SimulationResult {
resetIds(); resetIds();
resetResearchBonusCache(); resetResearchBonusCache();
resetFleetCache();
let rng: ReturnType<typeof createSeededRNG> | null = null; let rng: ReturnType<typeof createSeededRNG> | null = null;
if (config.seed !== undefined) { if (config.seed !== undefined) {
+8 -24
View File
@@ -91,24 +91,6 @@ export interface DataCenter {
// --- Network Topology (6-Tier Clos) --- // --- Network Topology (6-Tier Clos) ---
export type SwitchTier = 'tor' | 't1' | 't2' | 't3' | 't4' | 't5'; export type SwitchTier = 'tor' | 't1' | 't2' | 't3' | 't4' | 't5';
export type SwitchStatus = 'healthy' | 'failed' | 'repairing';
export interface NetworkSwitch {
id: string;
tier: SwitchTier;
status: SwitchStatus;
dcId: string | null;
campusId: string | null;
clusterId: string | null;
uplinkIds: string[];
downlinkIds: string[];
activeUplinks: number;
totalUplinks: number;
effectiveBandwidth: number;
repairProgress: number;
repairTotal: number;
}
export interface SwitchTierConfig { export interface SwitchTierConfig {
tier: SwitchTier; tier: SwitchTier;
name: string; name: string;
@@ -121,11 +103,17 @@ export interface SwitchTierConfig {
powerDrawKW: number; powerDrawKW: number;
} }
export interface RepairBatch {
tier: SwitchTier;
count: number;
ticksRemaining: number;
}
export interface DCNetworkSummary { export interface DCNetworkSummary {
switchIds: string[];
networkRackCount: number;
totalByTier: Partial<Record<SwitchTier, number>>; totalByTier: Partial<Record<SwitchTier, number>>;
healthyByTier: Partial<Record<SwitchTier, number>>; healthyByTier: Partial<Record<SwitchTier, number>>;
repairBatches: RepairBatch[];
networkRackCount: number;
racksDisconnected: number; racksDisconnected: number;
racksDegraded: number; racksDegraded: number;
averageBandwidth: number; averageBandwidth: number;
@@ -133,14 +121,12 @@ export interface DCNetworkSummary {
} }
export interface CampusNetworkSummary { export interface CampusNetworkSummary {
switchIds: string[];
totalT4: number; totalT4: number;
healthyT4: number; healthyT4: number;
crossDCBandwidth: number; crossDCBandwidth: number;
} }
export interface ClusterNetworkSummary { export interface ClusterNetworkSummary {
switchIds: string[];
totalT5: number; totalT5: number;
healthyT5: number; healthyT5: number;
crossCampusBandwidth: number; crossCampusBandwidth: number;
@@ -262,7 +248,6 @@ export interface ClusterCostConfig {
export interface InfrastructureState { export interface InfrastructureState {
clusters: Cluster[]; clusters: Cluster[];
switchRegistry: Record<string, NetworkSwitch>;
totalFlops: number; totalFlops: number;
totalTrainingFlops: number; totalTrainingFlops: number;
totalInferenceFlops: number; totalInferenceFlops: number;
@@ -276,7 +261,6 @@ export interface InfrastructureState {
export const INITIAL_INFRASTRUCTURE: InfrastructureState = { export const INITIAL_INFRASTRUCTURE: InfrastructureState = {
clusters: [], clusters: [],
switchRegistry: {},
totalFlops: 0, totalFlops: 0,
totalTrainingFlops: 0, totalTrainingFlops: 0,
totalInferenceFlops: 0, totalInferenceFlops: 0,
+4 -43
View File
@@ -182,45 +182,6 @@ export interface QuantizationConfig {
variantName: string; variantName: string;
} }
export type BenchmarkCategory = 'reasoning' | 'coding' | 'math' | 'knowledge' | 'safety' | 'chat' | 'multimodal' | 'agents';
export interface BenchmarkDefinition {
id: string;
name: string;
category: BenchmarkCategory;
description: string;
primaryCapability: keyof ModelCapabilities;
secondaryCapability?: keyof ModelCapabilities;
computeCost: number;
ticksToRun: number;
unlockedAtEra: Era;
marketRelevance: {
consumer: number;
enterprise: number;
developer: number;
research: number;
};
}
export interface BenchmarkResult {
benchmarkId: string;
modelId: string;
score: number;
ranAtTick: number;
rank?: number;
}
export interface EvalJob {
id: string;
modelId: string;
benchmarkIds: string[];
progressTicks: number;
totalTicks: number;
computeAllocated: number;
status: 'active' | 'completed';
results: BenchmarkResult[];
}
export type ProductLineType = 'text-api' | 'chat-product' | 'chat-free' | 'chat-enterprise' | 'code-api' | 'image' | 'agents-api'; export type ProductLineType = 'text-api' | 'chat-product' | 'chat-free' | 'chat-enterprise' | 'code-api' | 'image' | 'agents-api';
export interface ProductPricing { export interface ProductPricing {
@@ -246,11 +207,11 @@ export interface ModelsState {
baseModels: BaseModel[]; baseModels: BaseModel[];
activeTrainingPipelines: TrainingPipeline[]; activeTrainingPipelines: TrainingPipeline[];
variantJobs: VariantCreationJob[]; variantJobs: VariantCreationJob[];
evalJobs: EvalJob[];
benchmarkResults: BenchmarkResult[];
productLines: ProductLine[]; productLines: ProductLine[];
bestDeployedModelScore: number; bestDeployedModelScore: number;
bestDeployedSafetyScore: number; bestDeployedSafetyScore: number;
bestDeployedCapabilities: ModelCapabilities;
deploymentVersion: number;
} }
export const DEFAULT_DATA_MIX: DataMixAllocation = { export const DEFAULT_DATA_MIX: DataMixAllocation = {
@@ -271,8 +232,6 @@ export const INITIAL_MODELS: ModelsState = {
baseModels: [], baseModels: [],
activeTrainingPipelines: [], activeTrainingPipelines: [],
variantJobs: [], variantJobs: [],
evalJobs: [],
benchmarkResults: [],
productLines: [ productLines: [
{ {
id: 'text-api', id: 'text-api',
@@ -307,4 +266,6 @@ export const INITIAL_MODELS: ModelsState = {
], ],
bestDeployedModelScore: 0, bestDeployedModelScore: 0,
bestDeployedSafetyScore: 0, bestDeployedSafetyScore: 0,
bestDeployedCapabilities: { reasoning: 0, coding: 0, creative: 0, math: 0, knowledge: 0, multimodal: 0, agents: 0, speed: 0, contextUtilization: 0 },
deploymentVersion: 0,
}; };