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 { 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); }); });