Files
AIHostingTycoon/packages/game-engine/src/systems/economySystem.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

331 lines
10 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { processEconomy } from './economySystem';
import { createTestState, createTestCluster } from '../__test-utils__';
import type { MarketTickResult } from './marketSystem';
import type { InfrastructureState } from '@ai-tycoon/shared';
function createMarketResult(
overrides: Partial<MarketTickResult> = {},
): MarketTickResult {
return {
marketState: {} as MarketTickResult['marketState'],
apiRevenue: 0,
subscriptionRevenue: 0,
totalTokenDemand: 0,
...overrides,
};
}
function createInfraWithCosts(
energyCosts: number[],
maintenanceCosts: number[],
): InfrastructureState {
const clusters = energyCosts.map((energy, i) => {
const cluster = createTestCluster();
const dc = cluster.campuses[0].dataCenters[0];
dc.energyCostPerTick = energy;
dc.maintenanceCostPerTick = maintenanceCosts[i];
return cluster;
});
const state = createTestState();
return { ...state.infrastructure, clusters };
}
describe('processEconomy', () => {
it('computes revenue as apiRevenue + subscriptionRevenue', () => {
const state = createTestState();
const market = createMarketResult({
apiRevenue: 200,
subscriptionRevenue: 300,
});
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.revenuePerTick).toBe(500);
});
it('includes infrastructure energy and maintenance in expenses', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([10, 20], [5, 15]);
const result = processEconomy(state, market, infra);
// 10 + 5 + 20 + 15 = 50
expect(result.expensesPerTick).toBe(50);
});
it('includes talent salary in expenses', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 100 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(100);
});
it('includes data partnership costs in expenses', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 0 },
data: {
partnerships: [
{ costPerTick: 25 },
{ costPerTick: 75 },
] as any,
},
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(100);
});
it('includes compliance cost when bestCapability > 30', () => {
// compliance = bestCapability * 50 * (1 + eraIdx * 0.5) / 100
// bestCapability = 60, era = startup (idx 0)
// 60 * 50 * (1 + 0) / 100 = 30
const state = createTestState({
meta: { currentEra: 'startup' },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 60 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(30);
});
it('scales compliance cost with era index', () => {
// bestCapability = 60, era = bigtech (idx 2)
// 60 * 50 * (1 + 2 * 0.5) / 100 = 60
const state = createTestState({
meta: { currentEra: 'bigtech' },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 60 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(60);
});
it('has zero compliance cost when bestCapability <= 30', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 30 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(0);
});
it('includes devRel spending in expenses', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 42 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.expensesPerTick).toBe(42);
});
it('includes extraCosts in expenses', () => {
const state = createTestState({
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra, 200);
expect(result.expensesPerTick).toBe(200);
});
it('computes money as previousMoney + revenue - expenses', () => {
const state = createTestState({
economy: { money: 1000 },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult({
apiRevenue: 300,
subscriptionRevenue: 200,
});
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.money).toBe(1500);
});
it('floors money at zero', () => {
const state = createTestState({
economy: { money: 100 },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult({ apiRevenue: 0, subscriptionRevenue: 0 });
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra, 500);
expect(result.money).toBe(0);
});
it('accumulates totalRevenue and totalExpenses', () => {
const state = createTestState({
economy: { money: 10_000, totalRevenue: 5000, totalExpenses: 2000 },
talent: { totalSalaryPerTick: 50 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult({
apiRevenue: 100,
subscriptionRevenue: 200,
});
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.totalRevenue).toBe(5300);
expect(result.totalExpenses).toBe(2050);
});
it('adds financial history snapshot when tickCount % 60 === 0', () => {
const state = createTestState({
meta: { tickCount: 120 },
economy: { money: 5000, financialHistory: [] },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult({
apiRevenue: 100,
subscriptionRevenue: 0,
});
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.financialHistory).toHaveLength(1);
expect(result.financialHistory[0]).toMatchObject({
tick: 120,
revenue: 100,
expenses: 0,
});
});
it('does not add financial history snapshot when tickCount % 60 !== 0', () => {
const state = createTestState({
meta: { tickCount: 61 },
economy: { money: 5000, financialHistory: [] },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
expect(result.financialHistory).toHaveLength(0);
});
it('trims financial history when it exceeds 1000 entries', () => {
const existingHistory = Array.from({ length: 1000 }, (_, i) => ({
tick: i * 60,
money: 1000,
revenue: 10,
expenses: 5,
valuation: 1_000_000,
}));
const state = createTestState({
meta: { tickCount: 60000 },
economy: { money: 5000, financialHistory: existingHistory },
talent: { totalSalaryPerTick: 0 },
data: { partnerships: [] },
models: { bestDeployedModelScore: 0 },
market: { developerEcosystem: { devRelSpending: 0 } },
});
const market = createMarketResult();
const infra = createInfraWithCosts([], []);
const result = processEconomy(state, market, infra);
// 1000 existing + 1 new = 1001 -> shift -> 1000
expect(result.financialHistory).toHaveLength(1000);
// The oldest entry (tick 0) should have been shifted off
expect(result.financialHistory[0].tick).toBe(60);
});
it('sums all expense categories together', () => {
// infra: 10+5 = 15, talent: 20, data: 30, compliance (cap=50, era=scaleup idx=1): 50*50*(1+0.5)/100 = 37.5, devRel: 10, extra: 8
// total = 15 + 20 + 30 + 37.5 + 10 + 8 = 120.5
const state = createTestState({
meta: { currentEra: 'scaleup' },
talent: { totalSalaryPerTick: 20 },
data: {
partnerships: [{ costPerTick: 30 }] as any,
},
models: { bestDeployedModelScore: 50 },
market: { developerEcosystem: { devRelSpending: 10 } },
});
const market = createMarketResult({
apiRevenue: 500,
subscriptionRevenue: 500,
});
const infra = createInfraWithCosts([10], [5]);
const result = processEconomy(state, market, infra, 8);
expect(result.expensesPerTick).toBe(120.5);
expect(result.revenuePerTick).toBe(1000);
});
});