189 lines
7.5 KiB
TypeScript
189 lines
7.5 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|