import { describe, it, expect, beforeEach } from 'vitest'; import { getResearchBonuses, resetResearchBonusCache } from './researchBonuses'; import { TECH_TREE } from '../data/techTree'; beforeEach(() => { resetResearchBonusCache(); }); describe('getResearchBonuses', () => { it('returns all-zero bonuses for empty completedResearch', () => { const bonuses = getResearchBonuses([]); expect(bonuses.energyCostReduction).toBe(0); expect(bonuses.pipelineSpeedBonus).toBe(0); expect(bonuses.trainingSpeedBonus).toBe(0); expect(bonuses.inferenceEfficiencyBonus).toBe(0); expect(bonuses.tokensPerFlopBonus).toBe(0); expect(bonuses.dataQualityBonus).toBe(0); expect(bonuses.sdkCoverageBonus).toBe(0); expect(bonuses.globalCapabilityBonus).toBe(0); expect(bonuses.reasoningBonus).toBe(0); expect(bonuses.codingBonus).toBe(0); expect(bonuses.creativeBonus).toBe(0); expect(bonuses.multimodalBonus).toBe(0); expect(bonuses.agentsBonus).toBe(0); expect(bonuses.reputationBonus).toBe(0); expect(bonuses.safetyBonus).toBe(0); expect(bonuses.autoScalingBonus).toBe(0); }); it('ignores unknown research IDs without crashing', () => { const bonuses = getResearchBonuses(['nonexistent-research', 'also-fake']); expect(bonuses.energyCostReduction).toBe(0); expect(bonuses.globalCapabilityBonus).toBe(0); }); it('accumulates energy cost reduction from advanced-cooling', () => { // advanced-cooling has: { type: 'cost_reduction', target: 'energy', value: 0.25 } const bonuses = getResearchBonuses(['advanced-cooling']); expect(bonuses.energyCostReduction).toBe(0.25); }); it('accumulates inference efficiency from quantization', () => { // quantization has: { type: 'efficiency_boost', target: 'inference', value: 0.15 } const bonuses = getResearchBonuses(['quantization']); expect(bonuses.inferenceEfficiencyBonus).toBe(0.15); }); it('accumulates global capability bonus from transformer-v2', () => { // transformer-v2 has: { type: 'capability_boost', target: 'all', value: 10 } const bonuses = getResearchBonuses(['transformer-v2']); expect(bonuses.globalCapabilityBonus).toBe(10); }); it('accumulates safety and reputation from alignment-research', () => { // alignment-research has: // { type: 'safety_boost', target: 'models', value: 10 } // { type: 'capability_boost', target: 'reputation', value: 5 } const bonuses = getResearchBonuses(['alignment-research']); expect(bonuses.safetyBonus).toBe(10); expect(bonuses.reputationBonus).toBe(5); }); it('accumulates reasoning bonus from reasoning-enhancement', () => { // reasoning-enhancement has: { type: 'capability_boost', target: 'reasoning', value: 15 } const bonuses = getResearchBonuses(['reasoning-enhancement']); expect(bonuses.reasoningBonus).toBe(15); }); it('accumulates coding bonus from code-generation', () => { // code-generation has: { type: 'capability_boost', target: 'coding', value: 15 } const bonuses = getResearchBonuses(['code-generation']); expect(bonuses.codingBonus).toBe(15); }); it('accumulates pipeline speed from rapid-deployment', () => { // rapid-deployment has: { type: 'efficiency_boost', target: 'pipeline_speed', value: 0.2 } const bonuses = getResearchBonuses(['rapid-deployment']); expect(bonuses.pipelineSpeedBonus).toBe(0.2); }); it('accumulates data quality from data-pipeline', () => { // data-pipeline has: { type: 'efficiency_boost', target: 'data_quality', value: 0.2 } const bonuses = getResearchBonuses(['data-pipeline']); expect(bonuses.dataQualityBonus).toBe(0.2); }); it('accumulates auto-scaling from auto-scaling node', () => { // auto-scaling has: { type: 'efficiency_boost', target: 'auto_scaling', value: 0.2 } const bonuses = getResearchBonuses(['auto-scaling']); expect(bonuses.autoScalingBonus).toBe(0.2); }); it('additively accumulates bonuses from multiple research nodes', () => { // distillation: { type: 'capability_boost', target: 'all', value: 5 } // transformer-v2: { type: 'capability_boost', target: 'all', value: 10 } const bonuses = getResearchBonuses(['transformer-v2', 'distillation']); expect(bonuses.globalCapabilityBonus).toBe(15); }); it('accumulates safety additively across multiple safety nodes', () => { // alignment-research: safety 10, reputation 5 // interpretability: safety 10, reputation 5 // constitutional-ai: safety 15, reputation 10 const bonuses = getResearchBonuses([ 'alignment-research', 'interpretability', 'constitutional-ai', ]); expect(bonuses.safetyBonus).toBe(35); expect(bonuses.reputationBonus).toBe(20); }); it('accumulates multiple different bonus types from a diverse set', () => { const bonuses = getResearchBonuses([ 'advanced-cooling', // energyCostReduction: 0.25 'quantization', // inferenceEfficiencyBonus: 0.15 'transformer-v2', // globalCapabilityBonus: 10 'reasoning-enhancement', // reasoningBonus: 15 'alignment-research', // safetyBonus: 10, reputationBonus: 5 ]); expect(bonuses.energyCostReduction).toBe(0.25); expect(bonuses.inferenceEfficiencyBonus).toBe(0.15); expect(bonuses.globalCapabilityBonus).toBe(10); expect(bonuses.reasoningBonus).toBe(15); expect(bonuses.safetyBonus).toBe(10); expect(bonuses.reputationBonus).toBe(5); // Other fields remain zero expect(bonuses.codingBonus).toBe(0); expect(bonuses.creativeBonus).toBe(0); }); }); describe('cache behavior', () => { it('returns cached result when called with same-length array', () => { const first = getResearchBonuses(['advanced-cooling']); // Call with a different ID but same length -- cache triggers on length match const second = getResearchBonuses(['quantization']); // Because the cache keys on array length, second call returns the cached first result expect(second).toBe(first); expect(second.energyCostReduction).toBe(0.25); }); it('recomputes when array length changes', () => { const first = getResearchBonuses(['advanced-cooling']); expect(first.energyCostReduction).toBe(0.25); const second = getResearchBonuses(['advanced-cooling', 'quantization']); expect(second.energyCostReduction).toBe(0.25); expect(second.inferenceEfficiencyBonus).toBe(0.15); // Different reference since length changed expect(second).not.toBe(first); }); it('resetResearchBonusCache forces recompute on next call', () => { const first = getResearchBonuses(['advanced-cooling']); expect(first.energyCostReduction).toBe(0.25); resetResearchBonusCache(); // Same length, but cache was cleared so it recomputes with new input const second = getResearchBonuses(['quantization']); expect(second.energyCostReduction).toBe(0); expect(second.inferenceEfficiencyBonus).toBe(0.15); }); }); describe('TECH_TREE consistency', () => { it('all tested node IDs exist in TECH_TREE', () => { const testedIds = [ 'advanced-cooling', 'quantization', 'transformer-v2', 'reasoning-enhancement', 'code-generation', 'alignment-research', 'interpretability', 'constitutional-ai', 'distillation', 'rapid-deployment', 'data-pipeline', 'auto-scaling', ]; const treeIds = new Set(TECH_TREE.map(n => n.id)); for (const id of testedIds) { expect(treeIds.has(id), `Expected TECH_TREE to contain '${id}'`).toBe(true); } }); });