c1cc70eeb9
Full rebrand: UI display text, package scope (@ai-tycoon/* -> @token-empire/*), localStorage keys, Docker/CI image paths, database names, and documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
374 lines
13 KiB
TypeScript
374 lines
13 KiB
TypeScript
import type { SimulationMetrics } from '../strategies/types';
|
|
import type { TickNotification } from '@token-empire/game-engine';
|
|
|
|
export interface SystemConnection {
|
|
from: string;
|
|
to: string;
|
|
score: number;
|
|
evidence: string;
|
|
diagnosis: string;
|
|
events: number;
|
|
eventLabel: string;
|
|
}
|
|
|
|
export interface InterconnectionResult {
|
|
connections: SystemConnection[];
|
|
overallScore: number;
|
|
weakLinks: SystemConnection[];
|
|
deadLinks: SystemConnection[];
|
|
}
|
|
|
|
function findMetricAtTick(metrics: SimulationMetrics[], tick: number): SimulationMetrics | undefined {
|
|
for (let i = metrics.length - 1; i >= 0; i--) {
|
|
if (metrics[i].tick <= tick) return metrics[i];
|
|
}
|
|
return metrics[0];
|
|
}
|
|
|
|
function findMetricAfterTick(metrics: SimulationMetrics[], tick: number, windowTicks: number): SimulationMetrics | undefined {
|
|
const target = tick + windowTicks;
|
|
for (const m of metrics) {
|
|
if (m.tick >= target) return m;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function measureDelta(
|
|
metrics: SimulationMetrics[],
|
|
eventTicks: number[],
|
|
getter: (m: SimulationMetrics) => number,
|
|
windowTicks = 300,
|
|
): { totalDelta: number; events: number } {
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of eventTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, windowTicks);
|
|
if (before && after) {
|
|
totalDelta += after[getter.name as keyof SimulationMetrics] !== undefined
|
|
? getter(after) - getter(before)
|
|
: 0;
|
|
events++;
|
|
}
|
|
}
|
|
return { totalDelta, events };
|
|
}
|
|
|
|
function scoreFromDelta(totalDelta: number, events: number, scale: number): number {
|
|
if (events === 0) return 0;
|
|
const avgDelta = totalDelta / events;
|
|
return Math.min(10, Math.max(0, Math.round((avgDelta / scale) * 10)));
|
|
}
|
|
|
|
function detectResearchCompletionTicks(metrics: SimulationMetrics[]): number[] {
|
|
const ticks: number[] = [];
|
|
for (let i = 1; i < metrics.length; i++) {
|
|
if (metrics[i].researchCount > metrics[i - 1].researchCount) {
|
|
ticks.push(metrics[i].tick);
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
function detectModelDeploymentTicks(metrics: SimulationMetrics[]): number[] {
|
|
const ticks: number[] = [];
|
|
for (let i = 1; i < metrics.length; i++) {
|
|
if (metrics[i].modelsDeployed > metrics[i - 1].modelsDeployed) {
|
|
ticks.push(metrics[i].tick);
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
function detectFundingTicks(metrics: SimulationMetrics[]): number[] {
|
|
const ticks: number[] = [];
|
|
for (let i = 1; i < metrics.length; i++) {
|
|
if (metrics[i].fundingRoundsCompleted > metrics[i - 1].fundingRoundsCompleted) {
|
|
ticks.push(metrics[i].tick);
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
function detectHiringSpikes(metrics: SimulationMetrics[]): number[] {
|
|
const ticks: number[] = [];
|
|
for (let i = 1; i < metrics.length; i++) {
|
|
if (metrics[i].headcount - metrics[i - 1].headcount >= 3) {
|
|
ticks.push(metrics[i].tick);
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
function detectComputeGrowthTicks(metrics: SimulationMetrics[]): number[] {
|
|
const ticks: number[] = [];
|
|
for (let i = 1; i < metrics.length; i++) {
|
|
const prev = metrics[i - 1].totalFlops;
|
|
const curr = metrics[i].totalFlops;
|
|
if (prev > 0 && (curr - prev) / prev > 0.1) {
|
|
ticks.push(metrics[i].tick);
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
|
|
export function analyzeSystemInterconnections(
|
|
metrics: SimulationMetrics[],
|
|
_notifications: TickNotification[],
|
|
): InterconnectionResult {
|
|
const connections: SystemConnection[] = [];
|
|
|
|
if (metrics.length < 10) {
|
|
return { connections: [], overallScore: 0, weakLinks: [], deadLinks: [] };
|
|
}
|
|
|
|
const researchTicks = detectResearchCompletionTicks(metrics);
|
|
const deployTicks = detectModelDeploymentTicks(metrics);
|
|
const fundingTicks = detectFundingTicks(metrics);
|
|
const hiringTicks = detectHiringSpikes(metrics);
|
|
const computeGrowthTicks = detectComputeGrowthTicks(metrics);
|
|
|
|
// Research → Capability
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of researchTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 600);
|
|
if (before && after) {
|
|
totalDelta += after.bestModelCapability - before.bestModelCapability;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 ? scoreFromDelta(totalDelta, events, 5) : 0;
|
|
const avgDelta = events > 0 ? (totalDelta / events).toFixed(1) : '0';
|
|
connections.push({
|
|
from: 'Research', to: 'Model Capability', score, events,
|
|
eventLabel: 'research completions',
|
|
evidence: events > 0
|
|
? `${events} research completions, avg capability delta: ${avgDelta}`
|
|
: 'No research completions observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No research completions observed'
|
|
: `Research completions did not improve model capability (avg delta: ${avgDelta})`,
|
|
});
|
|
}
|
|
|
|
// Research → Infrastructure
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of researchTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 600);
|
|
if (before && after) {
|
|
totalDelta += after.totalFlops - before.totalFlops;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 && totalDelta > 0 ? Math.min(10, Math.round((totalDelta / events / 100) * 10)) : 0;
|
|
connections.push({
|
|
from: 'Research', to: 'Infrastructure', score, events,
|
|
eventLabel: 'research completions',
|
|
evidence: events > 0
|
|
? `${events} research completions, avg FLOPS delta: ${(totalDelta / events).toFixed(0)}`
|
|
: 'No research completions observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No research completions observed'
|
|
: `Research completions did not increase compute FLOPS`,
|
|
});
|
|
}
|
|
|
|
// Talent → Training (hiring → more training pipelines or faster progress)
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of hiringTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 300);
|
|
if (before && after) {
|
|
totalDelta += after.bestModelCapability - before.bestModelCapability;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 ? scoreFromDelta(totalDelta, events, 3) : 0;
|
|
const avgChange = events > 0 ? (totalDelta / events).toFixed(1) : '0';
|
|
connections.push({
|
|
from: 'Talent', to: 'Training', score, events,
|
|
eventLabel: 'hiring spikes',
|
|
evidence: events > 0
|
|
? `${events} hiring spikes, avg capability change: ${avgChange}`
|
|
: 'No significant hiring events observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No significant hiring events observed'
|
|
: `Hiring had no effect on training capability (avg delta: ${avgChange})`,
|
|
});
|
|
}
|
|
|
|
// Talent → Enterprise
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of hiringTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 600);
|
|
if (before && after) {
|
|
totalDelta += after.enterpriseContracts - before.enterpriseContracts;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 && totalDelta > 0 ? Math.min(10, Math.round((totalDelta / events) * 5)) : 0;
|
|
connections.push({
|
|
from: 'Talent', to: 'Enterprise', score, events,
|
|
eventLabel: 'hiring spikes',
|
|
evidence: events > 0
|
|
? `${events} hiring spikes, avg enterprise contract delta: ${(totalDelta / events).toFixed(1)}`
|
|
: 'No hiring events observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No hiring events observed'
|
|
: `Hiring did not lead to enterprise contracts`,
|
|
});
|
|
}
|
|
|
|
// Infrastructure → Revenue
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of computeGrowthTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 300);
|
|
if (before && after && before.revenue > 0) {
|
|
totalDelta += (after.revenue - before.revenue) / before.revenue;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 ? Math.min(10, Math.max(0, Math.round((totalDelta / events) * 20))) : 0;
|
|
connections.push({
|
|
from: 'Infrastructure', to: 'Revenue', score, events,
|
|
eventLabel: 'compute expansions',
|
|
evidence: events > 0
|
|
? `${events} compute growth events, avg revenue growth: ${((totalDelta / events) * 100).toFixed(1)}%`
|
|
: 'No compute growth events observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No compute growth events observed'
|
|
: `Compute growth did not increase revenue`,
|
|
});
|
|
}
|
|
|
|
// Models → Revenue
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of deployTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 300);
|
|
if (before && after) {
|
|
const revDelta = before.revenue > 0
|
|
? (after.revenue - before.revenue) / before.revenue
|
|
: (after.revenue > 0 ? 1 : 0);
|
|
totalDelta += revDelta;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 ? Math.min(10, Math.max(0, Math.round((totalDelta / events) * 10))) : 0;
|
|
connections.push({
|
|
from: 'Models', to: 'Revenue', score, events,
|
|
eventLabel: 'model deployments',
|
|
evidence: events > 0
|
|
? `${events} model deployments, avg revenue change: ${((totalDelta / events) * 100).toFixed(1)}%`
|
|
: 'No model deployments observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No model deployments observed'
|
|
: `Model deployments did not increase revenue`,
|
|
});
|
|
}
|
|
|
|
// Models → Enterprise
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of deployTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 600);
|
|
if (before && after) {
|
|
totalDelta += after.enterpriseContracts - before.enterpriseContracts;
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 && totalDelta > 0 ? Math.min(10, Math.round((totalDelta / events) * 5)) : 0;
|
|
connections.push({
|
|
from: 'Models', to: 'Enterprise', score, events,
|
|
eventLabel: 'model deployments',
|
|
evidence: events > 0
|
|
? `${events} deployments, avg enterprise contract delta: ${(totalDelta / events).toFixed(1)}`
|
|
: 'No model deployments observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No model deployments observed'
|
|
: `Model deployments did not attract enterprise contracts`,
|
|
});
|
|
}
|
|
|
|
// Funding → Growth
|
|
{
|
|
let totalDelta = 0;
|
|
let events = 0;
|
|
for (const tick of fundingTicks) {
|
|
const before = findMetricAtTick(metrics, tick);
|
|
const after = findMetricAfterTick(metrics, tick, 600);
|
|
if (before && after) {
|
|
const growthBefore = before.revenue;
|
|
const growthAfter = after.revenue;
|
|
totalDelta += growthBefore > 0
|
|
? (growthAfter - growthBefore) / growthBefore
|
|
: (growthAfter > 0 ? 1 : 0);
|
|
events++;
|
|
}
|
|
}
|
|
const score = events > 0 ? Math.min(10, Math.max(0, Math.round((totalDelta / events) * 10))) : 0;
|
|
connections.push({
|
|
from: 'Funding', to: 'Growth', score, events,
|
|
eventLabel: 'funding rounds',
|
|
evidence: events > 0
|
|
? `${events} funding rounds, avg revenue growth: ${((totalDelta / events) * 100).toFixed(1)}%`
|
|
: 'No funding rounds observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: events === 0 ? 'No funding rounds observed'
|
|
: `Funding rounds did not boost revenue`,
|
|
});
|
|
}
|
|
|
|
// Compute → Serving quality (utilization tracking)
|
|
{
|
|
let wellUtilizedCount = 0;
|
|
let totalSamples = 0;
|
|
for (const m of metrics) {
|
|
if (m.tokensPerSecondCapacity > 0) {
|
|
totalSamples++;
|
|
const util = m.inferenceUtilization;
|
|
if (util > 0.2 && util < 0.95) wellUtilizedCount++;
|
|
}
|
|
}
|
|
const score = totalSamples > 0 ? Math.min(10, Math.round((wellUtilizedCount / totalSamples) * 10)) : 0;
|
|
const pct = totalSamples > 0 ? ((wellUtilizedCount / totalSamples) * 100).toFixed(1) : '0';
|
|
connections.push({
|
|
from: 'Compute', to: 'Serving', score, events: totalSamples,
|
|
eventLabel: 'utilization samples',
|
|
evidence: totalSamples > 0
|
|
? `${wellUtilizedCount}/${totalSamples} samples with healthy utilization (20-95%)`
|
|
: 'No compute capacity observed',
|
|
diagnosis: score >= 7 ? 'Strong link'
|
|
: totalSamples === 0 ? 'No compute capacity observed'
|
|
: `Only ${wellUtilizedCount}/${totalSamples} samples (${pct}%) in healthy utilization range (20-95%)`,
|
|
});
|
|
}
|
|
|
|
const overallScore = connections.length > 0
|
|
? Math.round(connections.reduce((sum, c) => sum + c.score, 0) / connections.length * 10) / 10
|
|
: 0;
|
|
|
|
const weakLinks = connections.filter(c => c.score > 0 && c.score < 3);
|
|
const deadLinks = connections.filter(c => c.score === 0);
|
|
|
|
return { connections, overallScore, weakLinks, deadLinks };
|
|
}
|