Spaces:
Running
Running
| /** | |
| * Adaptive Tactics - Adaptation Tests | |
| * Tests for memory ledger, trigger evaluation, package selection | |
| */ | |
| import { | |
| evaluateMajorPackages, | |
| evaluateMinorModifiers, | |
| applyPackageEffects, | |
| applyModifierEffects, | |
| updateMemoryAfterCycle, | |
| getAdaptationHint | |
| } from '../js/adaptation.js'; | |
| // Test utilities | |
| function assert(condition, message) { | |
| if (!condition) { | |
| throw new Error(`FAILED: ${message}`); | |
| } | |
| console.log(`PASSED: ${message}`); | |
| } | |
| function createFreshMemory() { | |
| return { | |
| primary_lane_history: [], | |
| eilisia_kill_leads: 0, | |
| mirella_high_heal_cycles: 0, | |
| fast_clear_count: 0, | |
| fracture_line_cycles: 0, | |
| bait_pull_cycles: 0, | |
| throne_rush_count: 0, | |
| last_carry_unit: null, | |
| turns_to_clear_history: [] | |
| }; | |
| } | |
| const mockPackages = { | |
| western_denial: { | |
| id: 'western_denial', | |
| narrativeHint: 'The western path grows treacherous.', | |
| effect: { | |
| spawnBias: ['W1', 'W2'], | |
| preferredUnits: ['hollow_archer', 'fallen_soldier_spear'] | |
| } | |
| }, | |
| choke_hold: { | |
| id: 'choke_hold', | |
| narrativeHint: 'The center will not break so easily.', | |
| effect: { | |
| spawnBias: ['C1'], | |
| preferredUnits: ['fallen_soldier_spear', 'enslaved'] | |
| } | |
| }, | |
| mirellas_price: { | |
| id: 'mirellas_price', | |
| narrativeHint: 'His eyes turn toward your healer.', | |
| effect: { | |
| targetPriorityIncrease: 'mirella' | |
| } | |
| }, | |
| fast_pressure: { | |
| id: 'fast_pressure', | |
| narrativeHint: 'He will not wait for you this time.', | |
| effect: { | |
| engagementRadiusBonus: 1, | |
| forwardFlexPlacement: true | |
| } | |
| } | |
| }; | |
| const mockModifiers = { | |
| spear_wall: { | |
| id: 'spear_wall', | |
| availableFromCycle: 5, | |
| narrativeHint: 'The crossings bristle with spears.', | |
| effect: { | |
| spawnBias: 'crossings', | |
| preferredUnits: ['fallen_soldier_spear'] | |
| } | |
| }, | |
| cruel_recall: { | |
| id: 'cruel_recall', | |
| availableFromCycle: 5, | |
| narrativeHint: 'He remembers who struck hardest.', | |
| effect: { | |
| carryTargetBiasMultiplier: 1.5 | |
| } | |
| } | |
| }; | |
| // Test: No Package in Early Cycles | |
| function testNoPackageEarlyCycles() { | |
| const memory = createFreshMemory(); | |
| const package1 = evaluateMajorPackages(memory, mockPackages, 1); | |
| assert(package1 === null, 'Cycle 1 should have no package'); | |
| const package2 = evaluateMajorPackages(memory, mockPackages, 2); | |
| assert(package2 === null, 'Cycle 2 should have no package'); | |
| } | |
| // Test: Western Denial Trigger | |
| function testWesternDenialTrigger() { | |
| const memory = createFreshMemory(); | |
| memory.primary_lane_history = ['west', 'west']; | |
| const pkg = evaluateMajorPackages(memory, mockPackages, 3); | |
| assert(pkg === 'western_denial', `Should trigger western_denial, got ${pkg}`); | |
| } | |
| // Test: Choke Hold Trigger | |
| function testChokeHoldTrigger() { | |
| const memory = createFreshMemory(); | |
| memory.primary_lane_history = ['center', 'center']; | |
| const pkg = evaluateMajorPackages(memory, mockPackages, 3); | |
| assert(pkg === 'choke_hold', `Should trigger choke_hold, got ${pkg}`); | |
| } | |
| // Test: Mirella's Price Trigger | |
| function testMirellasPriceTrigger() { | |
| const memory = createFreshMemory(); | |
| memory.mirella_high_heal_cycles = 1; | |
| const pkg = evaluateMajorPackages(memory, mockPackages, 3); | |
| assert(pkg === 'mirellas_price', `Should trigger mirellas_price, got ${pkg}`); | |
| } | |
| // Test: Fast Pressure Trigger | |
| function testFastPressureTrigger() { | |
| const memory = createFreshMemory(); | |
| memory.fast_clear_count = 2; | |
| const pkg = evaluateMajorPackages(memory, mockPackages, 3); | |
| assert(pkg === 'fast_pressure', `Should trigger fast_pressure, got ${pkg}`); | |
| } | |
| // Test: No Modifier Before Cycle 5 | |
| function testNoModifierBeforeCycle5() { | |
| const memory = createFreshMemory(); | |
| memory.last_carry_unit = 'eilisia'; | |
| const mod3 = evaluateMinorModifiers(memory, mockModifiers, 3); | |
| assert(mod3 === null, 'Cycle 3 should have no modifier'); | |
| const mod4 = evaluateMinorModifiers(memory, mockModifiers, 4); | |
| assert(mod4 === null, 'Cycle 4 should have no modifier'); | |
| } | |
| // Test: Modifier Available at Cycle 5 | |
| function testModifierAtCycle5() { | |
| const memory = createFreshMemory(); | |
| memory.last_carry_unit = 'eilisia'; | |
| const mod = evaluateMinorModifiers(memory, mockModifiers, 5); | |
| assert(mod !== null, `Cycle 5 should have a modifier, got ${mod}`); | |
| } | |
| // Test: Package Effects Application | |
| function testPackageEffectsApplication() { | |
| const spawnConfig = {}; | |
| const aiConfig = {}; | |
| applyPackageEffects('western_denial', mockPackages, spawnConfig, aiConfig); | |
| assert(spawnConfig.biasNodes !== undefined, 'Should set spawn bias nodes'); | |
| assert(spawnConfig.biasNodes.includes('W1'), 'Should include W1 in bias'); | |
| assert(spawnConfig.preferredUnits !== undefined, 'Should set preferred units'); | |
| } | |
| // Test: Target Priority Effect | |
| function testTargetPriorityEffect() { | |
| const spawnConfig = {}; | |
| const aiConfig = {}; | |
| applyPackageEffects('mirellas_price', mockPackages, spawnConfig, aiConfig); | |
| assert(aiConfig.targetPriority !== undefined, 'Should set target priority'); | |
| assert(aiConfig.targetPriority.mirella > 1, 'Mirella should have elevated priority'); | |
| } | |
| // Test: Modifier Effects Application | |
| function testModifierEffectsApplication() { | |
| const spawnConfig = {}; | |
| const aiConfig = {}; | |
| applyModifierEffects('cruel_recall', mockModifiers, spawnConfig, aiConfig); | |
| assert(aiConfig.carryBias === 1.5, `Should set carry bias to 1.5, got ${aiConfig.carryBias}`); | |
| } | |
| // Test: Memory Update After Cycle | |
| function testMemoryUpdateAfterCycle() { | |
| const memory = createFreshMemory(); | |
| const cycleMetrics = { | |
| playerLaneMovement: { west: 5, center: 2, east: 1 }, | |
| eilisiaKills: 3, | |
| mirellaHealCount: 4, | |
| turnsElapsed: 7 | |
| }; | |
| updateMemoryAfterCycle(memory, cycleMetrics); | |
| assert(memory.primary_lane_history[0] === 'west', 'Should record west as primary lane'); | |
| assert(memory.eilisia_kill_leads === 1, 'Should increment Eilisia kill leads'); | |
| assert(memory.mirella_high_heal_cycles === 1, 'Should increment Mirella heal cycles'); | |
| assert(memory.fast_clear_count === 1, 'Should increment fast clear (7 <= 8)'); | |
| assert(memory.turns_to_clear_history[0] === 7, 'Should record turn count'); | |
| } | |
| // Test: Adaptation Hint Generation | |
| function testAdaptationHintGeneration() { | |
| const hint = getAdaptationHint('western_denial', null, mockPackages, mockModifiers); | |
| assert(hint.includes('western'), `Hint should mention western, got: ${hint}`); | |
| const combinedHint = getAdaptationHint('fast_pressure', 'cruel_recall', mockPackages, mockModifiers); | |
| assert(combinedHint.includes('wait') || combinedHint.includes('remembers'), | |
| `Combined hint should have both package and modifier content, got: ${combinedHint}`); | |
| } | |
| // Test: Priority of First Triggered Package | |
| function testPackagePriority() { | |
| const memory = createFreshMemory(); | |
| // Trigger multiple conditions | |
| memory.primary_lane_history = ['west', 'west']; | |
| memory.mirella_high_heal_cycles = 1; | |
| memory.fast_clear_count = 2; | |
| const pkg = evaluateMajorPackages(memory, mockPackages, 3); | |
| // Should return first triggered (western_denial comes first in code) | |
| assert(pkg === 'western_denial', `First triggered package should win, got ${pkg}`); | |
| } | |
| // Run all tests | |
| function runAllTests() { | |
| console.log('=== Running Adaptation Tests ===\n'); | |
| try { | |
| testNoPackageEarlyCycles(); | |
| testWesternDenialTrigger(); | |
| testChokeHoldTrigger(); | |
| testMirellasPriceTrigger(); | |
| testFastPressureTrigger(); | |
| testNoModifierBeforeCycle5(); | |
| testModifierAtCycle5(); | |
| testPackageEffectsApplication(); | |
| testTargetPriorityEffect(); | |
| testModifierEffectsApplication(); | |
| testMemoryUpdateAfterCycle(); | |
| testAdaptationHintGeneration(); | |
| testPackagePriority(); | |
| console.log('\n=== All Adaptation Tests Passed ==='); | |
| } catch (e) { | |
| console.error('\n=== Test Failed ==='); | |
| console.error(e.message); | |
| throw e; | |
| } | |
| } | |
| // Export for use in browser or Node | |
| if (typeof window !== 'undefined') { | |
| window.runAdaptationTests = runAllTests; | |
| } | |
| export { runAllTests }; | |