import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { processTick, setAchievementDefinitions } from './tick'; import { createTestState, createSeededRNG } from './__test-utils__'; import { ACHIEVEMENT_DEFINITIONS } from './data/achievements'; import { resetResearchBonusCache } from './systems/researchBonuses'; const rng = createSeededRNG(42); beforeEach(() => { rng.install(); resetResearchBonusCache(); setAchievementDefinitions(ACHIEVEMENT_DEFINITIONS); }); afterEach(() => rng.uninstall()); describe('processTick', () => { it('returns a valid partial state from initial state', () => { const state = createTestState(); const result = processTick(state); expect(result.meta).toBeDefined(); expect(result.economy).toBeDefined(); expect(result.infrastructure).toBeDefined(); expect(result.compute).toBeDefined(); expect(result.research).toBeDefined(); expect(result.models).toBeDefined(); expect(result.market).toBeDefined(); expect(result.talent).toBeDefined(); expect(result.reputation).toBeDefined(); expect(result.data).toBeDefined(); expect(result.competitors).toBeDefined(); expect(result.achievements).toBeDefined(); }); it('increments tickCount by 1', () => { const state = createTestState({ meta: { tickCount: 100 } }); const result = processTick(state); expect(result.meta!.tickCount).toBe(101); }); it('increments totalPlayTime by 1', () => { const state = createTestState({ meta: { totalPlayTime: 500 } }); const result = processTick(state); expect(result.meta!.totalPlayTime).toBe(501); }); it('preserves money when no revenue or expenses', () => { const state = createTestState({ economy: { money: 600_000 } }); const result = processTick(state); expect(result.economy!.money).toBeGreaterThanOrEqual(0); }); it('attaches notifications array', () => { const state = createTestState(); const result = processTick(state); const notifications = (result as Record)['_notifications']; expect(Array.isArray(notifications)).toBe(true); }); it('generates era transition notification when thresholds met', () => { const deployedModel = { id: 'model-1', familyId: 'fam-1', name: 'Test v1', architecture: { type: 'dense' as const, totalParameters: 7e9, activeParameters: 7e9, contextWindow: 8192, vocabularySize: 32000 }, rawCapability: 20, capabilities: { reasoning: 20, coding: 20, creative: 20, math: 20, knowledge: 20, multimodal: 0, agents: 10, speed: 50, contextUtilization: 50 }, safetyProfile: { overallSafety: 50, refusalRate: 0.05, harmAvoidance: 50, instructionFollowing: 50, honesty: 50 }, 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, dataMix: { web: 0.4, code: 0.2, books: 0.15, academic: 0.1, conversational: 0.1, specialized: 0.05 }, benchmarkResults: {}, }; const state = createTestState({ meta: { currentEra: 'startup' }, economy: { totalRevenue: 10_000 }, models: { bestDeployedModelScore: 20, bestDeployedSafetyScore: 50, baseModels: [deployedModel], families: [] }, reputation: { score: 50, safetyRecord: 60, publicPerception: 50, employeeSatisfaction: 50, regulatoryStanding: 50 }, }); const result = processTick(state); expect(result.meta!.currentEra).toBe('scaleup'); const notifications = (result as Record)['_notifications'] as Array<{ title: string }>; expect(notifications.some(n => n.title === 'Era Transition!')).toBe(true); }); it('does not transition era when thresholds not met', () => { const state = createTestState({ meta: { currentEra: 'startup' }, economy: { totalRevenue: 100 }, models: { bestDeployedModelScore: 5 }, reputation: { score: 20 }, }); const result = processTick(state); expect(result.meta!.currentEra).toBe('startup'); }); it('recomputes valuation each tick', () => { const state = createTestState({ economy: { revenuePerTick: 10 }, market: { consumerTiers: { totalUsers: 100 } }, models: { bestDeployedModelScore: 30 }, }); const result = processTick(state); expect(result.economy!.funding.valuation).toBeGreaterThan(0); }); it('research completion generates notification', () => { const state = createTestState({ research: { activeResearch: { researchId: 'advanced-cooling', progressTicks: 59, totalTicks: 60, allocatedResearchers: 1, allocatedCompute: 5, }, researchQueue: [], completedResearch: [], researchPoints: 0, }, talent: { departments: { research: { headcount: 1, effectiveness: 0.5 }, }, }, }); const result = processTick(state); const notifications = (result as Record)['_notifications'] as Array<{ title: string; message: string }>; const researchNotif = notifications.find(n => n.title === 'Research Complete'); expect(researchNotif).toBeDefined(); expect(researchNotif!.message).toContain('Advanced Cooling'); expect(researchNotif!.message).not.toContain('advanced-cooling'); expect(result.research!.completedResearch).toContain('advanced-cooling'); }); it('multiple ticks are stable (no crash on sequential processing)', () => { let state = createTestState(); for (let i = 0; i < 10; i++) { const result = processTick(state); state = { ...state, ...result }; } expect(state.meta.tickCount).toBe(10); expect(state.economy.money).toBeGreaterThanOrEqual(0); }); });