Files
AIHostingTycoon/packages/game-engine/src/systems/computeSystem.test.ts
T
josh a8746246f8
Balance Check / balance-simulation (push) Successful in 7m0s
Balance Check / multi-run-balance (push) Failing after 20m5s
CI / build-and-push (push) Successful in 1m18s
Add Vitest test suite with 184 tests covering all game engine systems
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 09:41:56 -04:00

345 lines
12 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { createTestState } from '../__test-utils__';
import { computeCapacity, finalizeCompute } from './computeSystem';
import type { InfrastructureState } from '@ai-tycoon/shared';
import { INITIAL_INFRASTRUCTURE, FLOPS_TO_TOKENS_MULTIPLIER, COMPUTE_SNAPSHOT_INTERVAL, MAX_COMPUTE_HISTORY } from '@ai-tycoon/shared';
function createInfrastructure(overrides: Partial<InfrastructureState> = {}): InfrastructureState {
return { ...INITIAL_INFRASTRUCTURE, ...overrides };
}
describe('computeCapacity', () => {
it('splits allocation between training and inference', () => {
const state = createTestState({
compute: { trainingAllocation: 0.7 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 500,
});
const result = computeCapacity(state, infra);
expect(result.trainingAllocation).toBe(0.7);
expect(result.inferenceAllocation).toBeCloseTo(0.3);
});
it('calculates effectiveTrainingFlops with cross-hardware contribution', () => {
const state = createTestState({
compute: { trainingAllocation: 0.6 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 400,
});
const result = computeCapacity(state, infra);
// effectiveTraining = 1000 * 0.6 + 400 * 0.6 * 0.3 = 600 + 72 = 672
expect(result.effectiveTrainingFlops).toBeCloseTo(672);
});
it('calculates effectiveInferenceFlops with cross-hardware contribution', () => {
const state = createTestState({
compute: { trainingAllocation: 0.6 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 400,
});
const result = computeCapacity(state, infra);
// inferenceAlloc = 0.4
// effectiveInference = (400 * 0.4 + 1000 * 0.4 * 0.5) * 1 = (160 + 200) * 1 = 360
expect(result.effectiveInferenceFlops).toBeCloseTo(360);
});
it('applies research bonuses to inference calculation', () => {
const state = createTestState({
compute: { trainingAllocation: 0.5 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 1000,
});
const bonuses = {
tokensPerFlopBonus: 0.3,
inferenceEfficiencyBonus: 0.15,
energyCostReduction: 0,
pipelineSpeedBonus: 0,
trainingSpeedBonus: 0,
dataQualityBonus: 0,
sdkCoverageBonus: 0,
globalCapabilityBonus: 0,
reasoningBonus: 0,
codingBonus: 0,
creativeBonus: 0,
multimodalBonus: 0,
agentsBonus: 0,
reputationBonus: 0,
safetyBonus: 0,
autoScalingBonus: 0,
};
const result = computeCapacity(state, infra, bonuses);
// inferenceBoost = 1 + 0.3 + 0.15 = 1.45
// effectiveInference = (1000 * 0.5 + 1000 * 0.5 * 0.5) * 1.45 = (500 + 250) * 1.45 = 1087.5
expect(result.effectiveInferenceFlops).toBeCloseTo(1087.5);
});
it('does not apply research bonuses to training calculation', () => {
const state = createTestState({
compute: { trainingAllocation: 0.5 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 1000,
});
const bonuses = {
tokensPerFlopBonus: 0.5,
inferenceEfficiencyBonus: 0.5,
energyCostReduction: 0,
pipelineSpeedBonus: 0,
trainingSpeedBonus: 0,
dataQualityBonus: 0,
sdkCoverageBonus: 0,
globalCapabilityBonus: 0,
reasoningBonus: 0,
codingBonus: 0,
creativeBonus: 0,
multimodalBonus: 0,
agentsBonus: 0,
reputationBonus: 0,
safetyBonus: 0,
autoScalingBonus: 0,
};
const result = computeCapacity(state, infra, bonuses);
// effectiveTraining = 1000 * 0.5 + 1000 * 0.5 * 0.3 = 500 + 150 = 650
// (same with or without bonuses)
expect(result.effectiveTrainingFlops).toBeCloseTo(650);
});
it('calculates tokensPerSecondCapacity from effectiveInferenceFlops', () => {
const state = createTestState({
compute: { trainingAllocation: 0.0 },
});
const infra = createInfrastructure({
totalTrainingFlops: 0,
totalInferenceFlops: 100,
});
const result = computeCapacity(state, infra);
// inferenceAlloc = 1.0
// effectiveInference = (100 * 1.0 + 0 * 1.0 * 0.5) * 1 = 100
// tokensPerSecond = 100 * 26 = 2600
expect(result.tokensPerSecondCapacity).toBe(100 * FLOPS_TO_TOKENS_MULTIPLIER);
});
it('reports totalFlops as sum of training and inference', () => {
const state = createTestState();
const infra = createInfrastructure({
totalTrainingFlops: 750,
totalInferenceFlops: 250,
});
const result = computeCapacity(state, infra);
expect(result.totalFlops).toBe(1000);
});
it('passes through totalVramGB from infrastructure', () => {
const state = createTestState();
const infra = createInfrastructure({
totalVramGB: 512,
});
const result = computeCapacity(state, infra);
expect(result.totalVramGB).toBe(512);
});
it('returns all zeros when infrastructure has no hardware', () => {
const state = createTestState({
compute: { trainingAllocation: 0.5 },
});
const infra = createInfrastructure({
totalTrainingFlops: 0,
totalInferenceFlops: 0,
totalVramGB: 0,
});
const result = computeCapacity(state, infra);
expect(result.totalFlops).toBe(0);
expect(result.effectiveTrainingFlops).toBe(0);
expect(result.effectiveInferenceFlops).toBe(0);
expect(result.tokensPerSecondCapacity).toBe(0);
});
it('handles full training allocation (1.0)', () => {
const state = createTestState({
compute: { trainingAllocation: 1.0 },
});
const infra = createInfrastructure({
totalTrainingFlops: 1000,
totalInferenceFlops: 500,
});
const result = computeCapacity(state, infra);
// effectiveTraining = 1000 * 1.0 + 500 * 1.0 * 0.3 = 1000 + 150 = 1150
expect(result.effectiveTrainingFlops).toBeCloseTo(1150);
// inferenceAlloc = 0, so effectiveInference = 0
expect(result.effectiveInferenceFlops).toBe(0);
expect(result.tokensPerSecondCapacity).toBe(0);
});
});
describe('finalizeCompute', () => {
it('calculates inferenceUtilization as demand / capacity', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0 } }),
createInfrastructure({ totalInferenceFlops: 100 }),
);
// tokensPerSecondCapacity = 100 * 26 = 2600
const result = finalizeCompute(capacity, 1300, [], 1);
expect(result.inferenceUtilization).toBeCloseTo(0.5);
});
it('clamps utilization to 1 when demand exceeds capacity', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0 } }),
createInfrastructure({ totalInferenceFlops: 100 }),
);
const result = finalizeCompute(capacity, 999999, [], 1);
expect(result.inferenceUtilization).toBe(1);
});
it('sets utilization to 1 when capacity is 0 but demand > 0', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 0, totalInferenceFlops: 0 }),
);
const result = finalizeCompute(capacity, 100, [], 1);
expect(result.inferenceUtilization).toBe(1);
});
it('sets utilization to 0 when both capacity and demand are 0', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 0, totalInferenceFlops: 0 }),
);
const result = finalizeCompute(capacity, 0, [], 1);
expect(result.inferenceUtilization).toBe(0);
});
it('adds a history snapshot when tickCount is a multiple of COMPUTE_SNAPSHOT_INTERVAL', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 500, totalInferenceFlops: 500 }),
);
const tick = COMPUTE_SNAPSHOT_INTERVAL; // 60
const result = finalizeCompute(capacity, 200, [], tick);
expect(result.computeHistory).toHaveLength(1);
expect(result.computeHistory[0].tick).toBe(tick);
expect(result.computeHistory[0].totalFlops).toBe(capacity.totalFlops);
expect(result.computeHistory[0].tokensPerSecondDemand).toBe(200);
});
it('does not add a snapshot when tickCount is not a multiple of COMPUTE_SNAPSHOT_INTERVAL', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 500, totalInferenceFlops: 500 }),
);
const result = finalizeCompute(capacity, 200, [], 1);
expect(result.computeHistory).toHaveLength(0);
});
it('preserves existing history entries', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 500, totalInferenceFlops: 500 }),
);
const existingHistory = [
{
tick: 60,
totalFlops: 800,
effectiveTrainingFlops: 400,
effectiveInferenceFlops: 400,
inferenceUtilization: 0.5,
tokensPerSecondCapacity: 10400,
tokensPerSecondDemand: 5200,
},
];
const result = finalizeCompute(capacity, 200, existingHistory, COMPUTE_SNAPSHOT_INTERVAL * 2);
expect(result.computeHistory).toHaveLength(2);
expect(result.computeHistory[0].tick).toBe(60);
expect(result.computeHistory[1].tick).toBe(COMPUTE_SNAPSHOT_INTERVAL * 2);
});
it('trims history when it exceeds MAX_COMPUTE_HISTORY', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.5 } }),
createInfrastructure({ totalTrainingFlops: 100, totalInferenceFlops: 100 }),
);
// Create a history that is exactly at the limit
const fullHistory = Array.from({ length: MAX_COMPUTE_HISTORY }, (_, i) => ({
tick: (i + 1) * COMPUTE_SNAPSHOT_INTERVAL,
totalFlops: 200,
effectiveTrainingFlops: 100,
effectiveInferenceFlops: 100,
inferenceUtilization: 0.5,
tokensPerSecondCapacity: 2600,
tokensPerSecondDemand: 1300,
}));
const nextSnapshotTick = (MAX_COMPUTE_HISTORY + 1) * COMPUTE_SNAPSHOT_INTERVAL;
const result = finalizeCompute(capacity, 1300, fullHistory, nextSnapshotTick);
expect(result.computeHistory).toHaveLength(MAX_COMPUTE_HISTORY);
// Oldest entry should have been shifted out
expect(result.computeHistory[0].tick).toBe(2 * COMPUTE_SNAPSHOT_INTERVAL);
// Newest entry should be our new snapshot
expect(result.computeHistory[result.computeHistory.length - 1].tick).toBe(nextSnapshotTick);
});
it('carries capacity fields into the returned ComputeState', () => {
const capacity = computeCapacity(
createTestState({ compute: { trainingAllocation: 0.3 } }),
createInfrastructure({ totalTrainingFlops: 200, totalInferenceFlops: 800, totalVramGB: 256 }),
);
const result = finalizeCompute(capacity, 500, [], 1);
expect(result.totalFlops).toBe(capacity.totalFlops);
expect(result.totalTrainingFlops).toBe(capacity.totalTrainingFlops);
expect(result.totalInferenceFlops).toBe(capacity.totalInferenceFlops);
expect(result.totalVramGB).toBe(capacity.totalVramGB);
expect(result.effectiveTrainingFlops).toBe(capacity.effectiveTrainingFlops);
expect(result.effectiveInferenceFlops).toBe(capacity.effectiveInferenceFlops);
expect(result.tokensPerSecondCapacity).toBe(capacity.tokensPerSecondCapacity);
expect(result.tokensPerSecondDemand).toBe(500);
});
});