143 lines
5.7 KiB
TypeScript
143 lines
5.7 KiB
TypeScript
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<string, unknown>)['_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<string, unknown>)['_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<string, unknown>)['_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);
|
|
});
|
|
});
|