/** * Milestone M24 — Druid Summoning Skills Parity & Adversarial Stress Suite * * Exhaustive 4-dimension verification suite for the 10 Druid Summoning Skills: * - Skill 221: Raven * - Skill 222: Poison Creeper (Plague Poppy) * - Skill 226: Oak Sage * - Skill 227: Summon Spirit Wolf * - Skill 231: Carrion Vine (Cycle of Life) * - Skill 236: Heart of Wolverine * - Skill 237: Summon Dire Wolf (Fenris) * - Skill 241: Solar Creeper (Vines) * - Skill 246: Spirit of Barbs * - Skill 247: Summon Grizzly * * Verifies: * - Dimension 1: Exact 1.13c numerical scaling, level progressions, and mana curves * - Dimension 2: PetType.txt group mutual exclusivity, species eviction, and FIFO limits * - Dimension 3: Kinematics, corpse consumption cycles, raven hit counters, and wolf/grizzly passive synergies * - Dimension 4: Invariants (BATCH1_SKILLS=38), DataRegistry, and SkillModule integration */ import { describe, expect, it } from 'vitest' import { DRUID_SUMMON_SKILL_IDS, DruidPetGroup, calculateRavenStats, calculatePoisonCreeperStats, calculateOakSageStats, calculateSpiritWolfStats, calculateCarrionVineStats, calculateHeartOfWolverineStats, calculateDireWolfStats, calculateSolarCreeperStats, calculateSpiritOfBarbsStats, calculateGrizzlyStats, calculateDruidSummonSynergyProfile, resolvePetSummon, simulateRavenPeck, simulateVineConsumption, simulateDireWolfCorpseFrenzy, type ActivePetRecord, } from '../../../src/game/skills/druid-summon.ts' import { BATCH1_SKILLS } from '../../../src/game/skills.ts' import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts' import { UnitStatList } from '../../../src/game/engine/stat-list.ts' import { evaluateSkill113c, getRegisteredSkillModule } from '../../../src/game/skills/registry.ts' describe('Milestone M24 — Druid Summoning Skills Parity & Adversarial Stress Suite', () => { // ========================================================================= // Dimension 1: Exact 1.13c Numerical Scaling & Level Progressions // ========================================================================= describe('Dimension 1: Exact 1.13c Numerical Scaling & Formulas', () => { it('1.1 Raven (221): Evaluates hits scaling (11 + slvl), max ravens, and 6 mana', () => { // slvl 1 const r1 = calculateRavenStats(1) expect(r1.maxRavens).toBe(1) expect(r1.hitsRemaining).toBe(12) expect(r1.minDamage).toBe(2) expect(r1.maxDamage).toBe(4) expect(r1.attackRating).toBe(100) expect(r1.manaCost).toBe(6) expect(r1.manaCost256).toBe(1536) // slvl 5 const r5 = calculateRavenStats(5) expect(r5.maxRavens).toBe(5) expect(r5.hitsRemaining).toBe(16) expect(r5.minDamage).toBe(6) expect(r5.maxDamage).toBe(8) expect(r5.attackRating).toBe(160) // slvl 10: max ravens capped at 5 const r10 = calculateRavenStats(10) expect(r10.maxRavens).toBe(5) expect(r10.hitsRemaining).toBe(21) expect(r10.minDamage).toBe(11) expect(r10.maxDamage).toBe(13) expect(r10.attackRating).toBe(235) // slvl 20 const r20 = calculateRavenStats(20) expect(r20.maxRavens).toBe(5) expect(r20.hitsRemaining).toBe(31) expect(r20.minDamage).toBe(21) expect(r20.maxDamage).toBe(23) expect(r20.attackRating).toBe(385) }) it('1.2 Poison Creeper (222): Evaluates HP scaling (+25%/lvl) and Rabies synergy', () => { const pc1 = calculatePoisonCreeperStats(1, 0) expect(pc1.maxPets).toBe(1) expect(pc1.hpBonusPct).toBe(0) expect(pc1.minPoisonDamage).toBe(4) expect(pc1.maxPoisonDamage).toBe(6) expect(pc1.manaCost).toBe(8) expect(pc1.manaCost256).toBe(2048) const pc10 = calculatePoisonCreeperStats(10, 0) expect(pc10.hpBonusPct).toBe(225) // 25 * 9% expect(pc10.minPoisonDamage).toBe(40) expect(pc10.maxPoisonDamage).toBe(51) // With 10 blvl in Rabies (+180% synergy) const pc10Syn = calculatePoisonCreeperStats(10, 10) expect(pc10Syn.synergyBonusPct).toBe(180) expect(pc10Syn.minPoisonDamage).toBe(Math.trunc((40 * 280) / 100)) // 112 }) it('1.3 Oak Sage (226): Evaluates party Max Life% aura (25 + 5*slvl) and mana progression', () => { // slvl 1: 30% life, radius 30 subtiles (20 yards), mana 15 const os1 = calculateOakSageStats(1) expect(os1.maxPets).toBe(1) expect(os1.lifeBonusPct).toBe(30) expect(os1.radiusSubtiles).toBe(30) expect(os1.radiusYards).toBe(20.0) expect(os1.hpBonusPct).toBe(0) expect(os1.manaCost).toBe(15) expect(os1.manaCost256).toBe(3840) // slvl 10: 75% life, radius 48 subtiles (32 yards), mana 24 const os10 = calculateOakSageStats(10) expect(os10.lifeBonusPct).toBe(75) expect(os10.radiusSubtiles).toBe(48) expect(os10.radiusYards).toBe(32.0) expect(os10.hpBonusPct).toBe(270) // 9 * 30% expect(os10.manaCost).toBe(24) expect(os10.manaCost256).toBe(6144) // slvl 20: 125% life, radius 68 subtiles (45.3 yards), mana 34 const os20 = calculateOakSageStats(20) expect(os20.lifeBonusPct).toBe(125) expect(os20.radiusSubtiles).toBe(68) expect(os20.radiusYards).toBe(45.3) expect(os20.hpBonusPct).toBe(570) expect(os20.manaCost).toBe(34) expect(os20.manaCost256).toBe(8704) }) it('1.4 Summon Spirit Wolf (227): Evaluates 5-band damage scaling and passive AR/Def bonuses', () => { // slvl 1: 2-6 damage, passive +50% AR, +50% Def, max 1 wolf const sw1 = calculateSpiritWolfStats(1) expect(sw1.maxWolves).toBe(1) expect(sw1.minDamage).toBe(2) expect(sw1.maxDamage).toBe(6) expect(sw1.passiveArBonusPct).toBe(50) expect(sw1.passiveDefBonusPct).toBe(50) expect(sw1.manaCost).toBe(15) expect(sw1.coldDamagePct).toBe(50) // slvl 5: max 5 wolves const sw5 = calculateSpiritWolfStats(5) expect(sw5.maxWolves).toBe(5) // slvl 10: base: 2+7*1+2*2 = 13 min, 6+7*1+2*2 = 17 max const sw10 = calculateSpiritWolfStats(10) expect(sw10.maxWolves).toBe(5) expect(sw10.minDamage).toBe(13) expect(sw10.maxDamage).toBe(17) expect(sw10.passiveArBonusPct).toBe(275) // 50 + 9*25 expect(sw10.passiveDefBonusPct).toBe(140) // 50 + 9*10 // slvl 20: base: 13+6*2+4*4 = 41 min, 17+6*2+4*4 = 45 max const sw20 = calculateSpiritWolfStats(20) expect(sw20.minDamage).toBe(41) expect(sw20.maxDamage).toBe(45) // With synergies: 10 hard points in Dire Wolf (+275% life) and 10 in Grizzly (+115% dmg) const sw20Syn = calculateSpiritWolfStats(20, { direWolfBlvl: 10, grizzlyBlvl: 10 }) expect(sw20Syn.passiveLifeBonusPct).toBe(275) expect(sw20Syn.passiveDmgBonusPct).toBe(115) expect(sw20Syn.minDamage).toBe(Math.trunc((41 * 215) / 100)) // 88 expect(sw20Syn.maxDamage).toBe(Math.trunc((45 * 215) / 100)) // 96 }) it('1.5 Carrion Vine (231): Evaluates corpse heal % diminishing returns and fixed 10 mana', () => { // slvl 1: heal 4% max HP (dm12 with 3, 12) const cv1 = calculateCarrionVineStats(1) expect(cv1.maxPets).toBe(1) expect(cv1.healLifePct).toBe(4) expect(cv1.searchRadiusSubtiles).toBe(10) expect(cv1.manaCost).toBe(10) expect(cv1.manaCost256).toBe(2560) // slvl 10: diminishing returns between 3 and 12 -> 9% const cv10 = calculateCarrionVineStats(10) expect(cv10.healLifePct).toBe(9) expect(cv10.hpBonusPct).toBe(225) // 9 * 25% // slvl 20: 10% const cv20 = calculateCarrionVineStats(20) expect(cv20.healLifePct).toBe(10) }) it('1.6 Heart of Wolverine (236): Evaluates party Enhanced Damage% and Attack Rating% aura', () => { // slvl 1: +20% ED, +25% AR, radius 30 subtiles (20 yards), mana 20 const hw1 = calculateHeartOfWolverineStats(1) expect(hw1.maxPets).toBe(1) expect(hw1.damageBonusPct).toBe(20) expect(hw1.attackRatingBonusPct).toBe(25) expect(hw1.radiusSubtiles).toBe(30) expect(hw1.manaCost).toBe(20) expect(hw1.manaCost256).toBe(5120) // slvl 10: 20 + 9*7 = 83% ED, 25 + 9*7 = 88% AR, radius 48 subtiles, mana 29 const hw10 = calculateHeartOfWolverineStats(10) expect(hw10.damageBonusPct).toBe(83) expect(hw10.attackRatingBonusPct).toBe(88) expect(hw10.radiusSubtiles).toBe(48) expect(hw10.manaCost).toBe(29) expect(hw10.manaCost256).toBe(7424) // slvl 20: 20 + 19*7 = 153% ED, 25 + 19*7 = 158% AR, radius 68 subtiles, mana 39 const hw20 = calculateHeartOfWolverineStats(20) expect(hw20.damageBonusPct).toBe(153) expect(hw20.attackRatingBonusPct).toBe(158) expect(hw20.radiusSubtiles).toBe(68) expect(hw20.manaCost).toBe(39) }) it('1.7 Summon Dire Wolf (237): Evaluates 5-band damage scaling, passive Life% bonus, and frenzy', () => { // slvl 1: 7-12 damage, +50% life bonus, max 1 wolf, 20 mana const dw1 = calculateDireWolfStats(1) expect(dw1.maxWolves).toBe(1) expect(dw1.minDamage).toBe(7) expect(dw1.maxDamage).toBe(12) expect(dw1.passiveLifeBonusPct).toBe(50) expect(dw1.corpseFrenzyDamageBonusPct).toBe(100) expect(dw1.corpseFrenzyDurationFrames).toBe(500) expect(dw1.manaCost).toBe(20) expect(dw1.manaCost256).toBe(5120) // slvl 3: max 3 wolves const dw3 = calculateDireWolfStats(3) expect(dw3.maxWolves).toBe(3) // slvl 10: base: 7+7*2+2*3 = 27 min, 12+7*2+2*3 = 32 max const dw10 = calculateDireWolfStats(10) expect(dw10.maxWolves).toBe(3) expect(dw10.minDamage).toBe(27) expect(dw10.maxDamage).toBe(32) expect(dw10.passiveLifeBonusPct).toBe(275) // 50 + 9*25 // slvl 20: base: 27+6*3+4*6 = 69 min, 32+6*3+4*6 = 74 max const dw20 = calculateDireWolfStats(20) expect(dw20.minDamage).toBe(69) expect(dw20.maxDamage).toBe(74) expect(dw20.passiveLifeBonusPct).toBe(525) // 50 + 19*25 // With Grizzly synergy (+115% damage) const dw20Syn = calculateDireWolfStats(20, { grizzlyBlvl: 10 }) expect(dw20Syn.minDamage).toBe(Math.trunc((69 * 215) / 100)) // 148 expect(dw20Syn.maxDamage).toBe(Math.trunc((74 * 215) / 100)) // 159 }) it('1.8 Solar Creeper (241): Evaluates corpse mana % diminishing returns and mana scaling', () => { // slvl 1: restore 2% max mana, mana 14 (dm12 with 1, 8) const sc1 = calculateSolarCreeperStats(1) expect(sc1.maxPets).toBe(1) expect(sc1.restoreManaPct).toBe(2) expect(sc1.searchRadiusSubtiles).toBe(10) expect(sc1.manaCost).toBe(14) expect(sc1.manaCost256).toBe(3584) // slvl 10: 5% mana, mana 23 const sc10 = calculateSolarCreeperStats(10) expect(sc10.restoreManaPct).toBe(5) expect(sc10.hpBonusPct).toBe(180) // 9 * 20% expect(sc10.manaCost).toBe(23) expect(sc10.manaCost256).toBe(5888) // slvl 20: 6% mana, mana 33 const sc20 = calculateSolarCreeperStats(20) expect(sc20.restoreManaPct).toBe(6) expect(sc20.manaCost).toBe(33) }) it('1.9 Spirit of Barbs (246): Evaluates thorns reflection % scaling and mana progression', () => { // slvl 1: 50% thorns, radius 30 subtiles (20 yards), mana 25 const sb1 = calculateSpiritOfBarbsStats(1) expect(sb1.maxPets).toBe(1) expect(sb1.thornsReturnPct).toBe(50) expect(sb1.radiusSubtiles).toBe(30) expect(sb1.radiusYards).toBe(20.0) expect(sb1.manaCost).toBe(25) expect(sb1.manaCost256).toBe(6400) // slvl 10: 50 + 9*10 = 140% thorns, mana 34 const sb10 = calculateSpiritOfBarbsStats(10) expect(sb10.thornsReturnPct).toBe(140) expect(sb10.manaCost).toBe(34) expect(sb10.manaCost256).toBe(8704) // slvl 20: 50 + 19*10 = 240% thorns, mana 44 const sb20 = calculateSpiritOfBarbsStats(20) expect(sb20.thornsReturnPct).toBe(240) expect(sb20.manaCost).toBe(44) }) it('1.10 Summon Grizzly (247): Evaluates apex solo pet damage, Smite stun, and passive synergy', () => { // slvl 1: 30-60 damage, +25% passive damage, smite stun 15 frames, 40 mana const gz1 = calculateGrizzlyStats(1) expect(gz1.maxPets).toBe(1) expect(gz1.minDamage).toBe(30) expect(gz1.maxDamage).toBe(60) expect(gz1.passiveDmgBonusPct).toBe(25) expect(gz1.smiteStunFrames).toBe(15) expect(gz1.smiteKnockback).toBe(true) expect(gz1.manaCost).toBe(40) expect(gz1.manaCost256).toBe(10240) // slvl 10: base: 30+7*10+2*15 = 130 min, 60+7*10+2*15 = 160 max const gz10 = calculateGrizzlyStats(10) expect(gz10.minDamage).toBe(130) expect(gz10.maxDamage).toBe(160) expect(gz10.passiveDmgBonusPct).toBe(115) // 25 + 9*10 expect(gz10.smiteStunFrames).toBe(60) // 15 + 9*5 // slvl 20: base: 130+6*15+4*20 = 300 min, 160+6*15+4*20 = 330 max const gz20 = calculateGrizzlyStats(20) expect(gz20.minDamage).toBe(300) expect(gz20.maxDamage).toBe(330) expect(gz20.passiveDmgBonusPct).toBe(215) // 25 + 19*10 expect(gz20.smiteStunFrames).toBe(110) // 15 + 19*5 }) }) // ========================================================================= // Dimension 2: Pet Group Exclusivity & Slot Eviction Matrix // ========================================================================= describe('Dimension 2: Pet Group Exclusivity & Slot Eviction Matrix (5)', () => { it('2.1 Group 1 (Wolf & Bear): Grizzly and wolves mutually evict one another', () => { let activePets: ActivePetRecord[] = [] // Summon 3 Spirit Wolves for (let i = 1; i <= 3; i++) { const res = resolvePetSummon(activePets, { id: `wolf_${i}`, skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_SPIRIT_WOLF, slvl: 5, }) activePets = res.newActivePets } expect(activePets.length).toBe(3) expect(activePets.every(p => p.petType === 'spiritwolf')).toBe(true) // Summoning Grizzly MUST evict all 3 Spirit Wolves! const gzRes = resolvePetSummon(activePets, { id: 'grizzly_1', skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_GRIZZLY, slvl: 1, }) expect(gzRes.evictedPetIds).toEqual(['wolf_1', 'wolf_2', 'wolf_3']) expect(gzRes.newActivePets.length).toBe(1) expect(gzRes.newActivePets[0]!.petType).toBe('grizzly') activePets = gzRes.newActivePets // Summoning Dire Wolf MUST evict Grizzly! const dwRes = resolvePetSummon(activePets, { id: 'dire_1', skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_DIRE_WOLF, slvl: 1, }) expect(dwRes.evictedPetIds).toEqual(['grizzly_1']) expect(dwRes.newActivePets.length).toBe(1) expect(dwRes.newActivePets[0]!.petType).toBe('fenris') }) it('2.2 Group 2 (Spirit Totems): Only 1 spirit totem can be active at a time', () => { let activePets: ActivePetRecord[] = [] // Summon Oak Sage const r1 = resolvePetSummon(activePets, { id: 'spirit_oak', skillId: DRUID_SUMMON_SKILL_IDS.OAK_SAGE, slvl: 1, }) activePets = r1.newActivePets expect(activePets.length).toBe(1) expect(activePets[0]!.skillId).toBe(DRUID_SUMMON_SKILL_IDS.OAK_SAGE) // Summon Heart of Wolverine -> evicts Oak Sage const r2 = resolvePetSummon(activePets, { id: 'spirit_wolverine', skillId: DRUID_SUMMON_SKILL_IDS.HEART_OF_WOLVERINE, slvl: 1, }) expect(r2.evictedPetIds).toEqual(['spirit_oak']) expect(r2.newActivePets.length).toBe(1) expect(r2.newActivePets[0]!.skillId).toBe(DRUID_SUMMON_SKILL_IDS.HEART_OF_WOLVERINE) activePets = r2.newActivePets // Summon Spirit of Barbs -> evicts Heart of Wolverine const r3 = resolvePetSummon(activePets, { id: 'spirit_barbs', skillId: DRUID_SUMMON_SKILL_IDS.SPIRIT_OF_BARBS, slvl: 1, }) expect(r3.evictedPetIds).toEqual(['spirit_wolverine']) expect(r3.newActivePets.length).toBe(1) expect(r3.newActivePets[0]!.skillId).toBe(DRUID_SUMMON_SKILL_IDS.SPIRIT_OF_BARBS) }) it('2.3 Group 3 (Vines): Only 1 vine creature can be active at a time', () => { let activePets: ActivePetRecord[] = [] // Summon Poison Creeper const r1 = resolvePetSummon(activePets, { id: 'vine_poison', skillId: DRUID_SUMMON_SKILL_IDS.POISON_CREEPER, slvl: 1, }) activePets = r1.newActivePets expect(activePets.length).toBe(1) // Summon Carrion Vine -> evicts Poison Creeper const r2 = resolvePetSummon(activePets, { id: 'vine_carrion', skillId: DRUID_SUMMON_SKILL_IDS.CARRION_VINE, slvl: 1, }) expect(r2.evictedPetIds).toEqual(['vine_poison']) expect(r2.newActivePets.length).toBe(1) expect(r2.newActivePets[0]!.skillId).toBe(DRUID_SUMMON_SKILL_IDS.CARRION_VINE) activePets = r2.newActivePets // Summon Solar Creeper -> evicts Carrion Vine const r3 = resolvePetSummon(activePets, { id: 'vine_solar', skillId: DRUID_SUMMON_SKILL_IDS.SOLAR_CREEPER, slvl: 1, }) expect(r3.evictedPetIds).toEqual(['vine_carrion']) expect(r3.newActivePets.length).toBe(1) expect(r3.newActivePets[0]!.skillId).toBe(DRUID_SUMMON_SKILL_IDS.SOLAR_CREEPER) }) it('2.4 Group 0 (Ravens): Ravens can coexist with wolf/bear, spirit, and vine simultaneously', () => { let activePets: ActivePetRecord[] = [] // Summon Grizzly (Group 1) activePets = resolvePetSummon(activePets, { id: 'bear', skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_GRIZZLY, slvl: 1, }).newActivePets // Summon Oak Sage (Group 2) activePets = resolvePetSummon(activePets, { id: 'spirit', skillId: DRUID_SUMMON_SKILL_IDS.OAK_SAGE, slvl: 1, }).newActivePets // Summon Carrion Vine (Group 3) activePets = resolvePetSummon(activePets, { id: 'vine', skillId: DRUID_SUMMON_SKILL_IDS.CARRION_VINE, slvl: 1, }).newActivePets // Summon 5 Ravens (Group 0) for (let i = 1; i <= 5; i++) { const res = resolvePetSummon(activePets, { id: `raven_${i}`, skillId: DRUID_SUMMON_SKILL_IDS.RAVEN, slvl: 5, }) expect(res.evictedPetIds).toEqual([]) // None evicted! activePets = res.newActivePets } // Total active pets = 1 Grizzly + 1 Spirit + 1 Vine + 5 Ravens = 8 pets! expect(activePets.length).toBe(8) expect(activePets.filter(p => p.petGroup === DruidPetGroup.RAVEN).length).toBe(5) expect(activePets.filter(p => p.petGroup === DruidPetGroup.WOLF_BEAR).length).toBe(1) expect(activePets.filter(p => p.petGroup === DruidPetGroup.SPIRIT).length).toBe(1) expect(activePets.filter(p => p.petGroup === DruidPetGroup.VINE).length).toBe(1) }) it('2.5 FIFO Eviction: Enforces species caps and replaces oldest pet', () => { let activePets: ActivePetRecord[] = [] // Summon 5 Spirit Wolves for (let i = 1; i <= 5; i++) { activePets = resolvePetSummon(activePets, { id: `sw_${i}`, skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_SPIRIT_WOLF, slvl: 5, }).newActivePets } expect(activePets.length).toBe(5) // Summon 6th Spirit Wolf -> evicts sw_1 (FIFO) const res6 = resolvePetSummon(activePets, { id: 'sw_6', skillId: DRUID_SUMMON_SKILL_IDS.SUMMON_SPIRIT_WOLF, slvl: 5, }) expect(res6.evictedPetIds).toEqual(['sw_1']) expect(res6.newActivePets.length).toBe(5) expect(res6.newActivePets.map(p => p.id)).toEqual(['sw_2', 'sw_3', 'sw_4', 'sw_5', 'sw_6']) }) }) // ========================================================================= // Dimension 3: Kinematics, Corpse Cycles & Combat Synergies // ========================================================================= describe('Dimension 3: Kinematics, Corpse Cycles & Combat Synergies (4)', () => { it('3.1 Raven peck: Decrements hits counter, inflicts blind, and departs at 0 hits', () => { let hits = 3 const target = { isImmuneToCurse: false } // Peck 1 const p1 = simulateRavenPeck(hits, target) expect(p1.hitsRemainingAfter).toBe(2) expect(p1.blindInflicted).toBe(true) expect(p1.dismissed).toBe(false) hits = p1.hitsRemainingAfter // Peck 2 const p2 = simulateRavenPeck(hits, target) expect(p2.hitsRemainingAfter).toBe(1) expect(p2.dismissed).toBe(false) hits = p2.hitsRemainingAfter // Peck 3 (Final) const p3 = simulateRavenPeck(hits, target) expect(p3.hitsRemainingAfter).toBe(0) expect(p3.dismissed).toBe(true) // Curse immune target (Boss) const bossTarget = { isImmuneToCurse: true } const pBoss = simulateRavenPeck(5, bossTarget) expect(pBoss.blindInflicted).toBe(false) }) it('3.2 Vine corpse consumption: Carrion heals life, Solar restores mana, ignores consumed/boss corpses', () => { const owner = { maxHp: 1000, maxMana: 500 } // Carrion Vine slvl 10 (9% max HP = 90 HP) const corpse1 = { consumed: false, isBoss: false } const cRes = simulateVineConsumption(DRUID_SUMMON_SKILL_IDS.CARRION_VINE, 10, corpse1, owner) expect(cRes.corpseConsumed).toBe(true) expect(cRes.lifeHealed).toBe(90) expect(cRes.manaRestored).toBe(0) expect(corpse1.consumed).toBe(true) // Trying to consume already consumed corpse fails const cRes2 = simulateVineConsumption(DRUID_SUMMON_SKILL_IDS.CARRION_VINE, 10, corpse1, owner) expect(cRes2.corpseConsumed).toBe(false) expect(cRes2.lifeHealed).toBe(0) // Solar Creeper slvl 10 (5% max Mana = 25 Mana) const corpse2 = { consumed: false, isBoss: false } const sRes = simulateVineConsumption(DRUID_SUMMON_SKILL_IDS.SOLAR_CREEPER, 10, corpse2, owner) expect(sRes.corpseConsumed).toBe(true) expect(sRes.manaRestored).toBe(25) expect(sRes.lifeHealed).toBe(0) // Boss corpses cannot be eaten by vines const bossCorpse = { consumed: false, isBoss: true } const bRes = simulateVineConsumption(DRUID_SUMMON_SKILL_IDS.CARRION_VINE, 10, bossCorpse, owner) expect(bRes.corpseConsumed).toBe(false) expect(bossCorpse.consumed).toBe(false) }) it('3.3 Dire Wolf corpse frenzy: Consumes combat corpse to activate 500-frame +100% damage rage', () => { const corpse = { consumed: false, isBoss: false } const out = simulateDireWolfCorpseFrenzy(corpse) expect(out.consumed).toBe(true) expect(out.rageActivated).toBe(true) expect(out.rageDurationFrames).toBe(500) expect(out.damageMultiplier).toBe(2.0) // +100% expect(corpse.consumed).toBe(true) // Already consumed corpse cannot re-trigger frenzy const out2 = simulateDireWolfCorpseFrenzy(corpse) expect(out2.consumed).toBe(false) expect(out2.rageActivated).toBe(false) }) it('3.4 Wolf & Grizzly cross-species synergy network correctly calculates mutual bonuses', () => { const syn = calculateDruidSummonSynergyProfile({ spiritWolf: 10, // +275% AR & Def direWolf: 10, // +275% Life grizzly: 10, // +115% Damage }) expect(syn.spiritWolfArDefBonusPct).toBe(275) expect(syn.direWolfLifeBonusPct).toBe(275) expect(syn.grizzlyDmgBonusPct).toBe(115) }) }) // ========================================================================= // Dimension 4: Invariants, DataRegistry & SkillModule Architecture // ========================================================================= describe('Dimension 4: Invariants, DataRegistry & SkillModule Architecture (4)', () => { it('4.1 BATCH1_SKILLS length invariant = 38 is strictly preserved', () => { const batch1Keys = Object.keys(BATCH1_SKILLS) expect(batch1Keys.length).toBe(38) }) it('4.2 All 10 Druid Summoning skills are properly defined in SkillModule architecture', async () => { const summonIds = [221, 222, 226, 227, 231, 236, 237, 241, 246, 247] const { skillModule: m221 } = await import('../../../src/game/skills/impl/dru/skill-221-raven.ts') const { skillModule: m222 } = await import('../../../src/game/skills/impl/dru/skill-222-plague-poppy.ts') const { skillModule: m226 } = await import('../../../src/game/skills/impl/dru/skill-226-oak-sage.ts') const { skillModule: m227 } = await import('../../../src/game/skills/impl/dru/skill-227-summon-spirit-wolf.ts') const { skillModule: m231 } = await import('../../../src/game/skills/impl/dru/skill-231-cycle-of-life.ts') const { skillModule: m236 } = await import('../../../src/game/skills/impl/dru/skill-236-heart-of-wolverine.ts') const { skillModule: m237 } = await import('../../../src/game/skills/impl/dru/skill-237-summon-fenris.ts') const { skillModule: m241 } = await import('../../../src/game/skills/impl/dru/skill-241-vines.ts') const { skillModule: m246 } = await import('../../../src/game/skills/impl/dru/skill-246-spirit-of-barbs.ts') const { skillModule: m247 } = await import('../../../src/game/skills/impl/dru/skill-247-summon-grizzly.ts') const modules = [m221, m222, m226, m227, m231, m236, m237, m241, m246, m247] for (let i = 0; i < summonIds.length; i++) { const id = summonIds[i] const mod = modules[i] expect(mod).toBeDefined() expect(mod.skillId).toBe(id) expect(mod.charClass).toBe('dru') } }) it('4.3 DataRegistry loads all 10 Druid Summoning skills from Patch_D2.mpq with exact attributes', async () => { const registry = await getSharedDataRegistry() const druSummonIds = [221, 222, 226, 227, 231, 236, 237, 241, 246, 247] for (const id of druSummonIds) { const rec = registry.getSkillById(id) expect(rec).toBeDefined() expect(rec!.charClass).toBe('dru') expect(rec!.summon).toBeTruthy() } }) it('4.4 evaluateSkill113c evaluates all 10 Druid Summoning skills against UnitStatList with exact attributes', async () => { const registry = await getSharedDataRegistry() const stats = new UnitStatList() // Raven (221) const rRaven = evaluateSkill113c({ registry, skill: registry.getSkillById(221)!, slvl: 10, blvl: 10, statList: stats, }) expect(rRaven.manaCost).toBe(6) expect(rRaven.minPhysDmg).toBe(11) expect(rRaven.maxPhysDmg).toBe(13) expect(rRaven.petMax).toBe(5) // Oak Sage (226) const rOak = evaluateSkill113c({ registry, skill: registry.getSkillById(226)!, slvl: 10, blvl: 10, statList: stats, }) expect(rOak.manaCost).toBe(24) expect(rOak.radiusSubtiles).toBe(75) expect(rOak.petMax).toBe(1) // Spirit Wolf (227) const rSW = evaluateSkill113c({ registry, skill: registry.getSkillById(227)!, slvl: 10, blvl: 10, statList: stats, }) expect(rSW.manaCost).toBe(15) expect(rSW.minPhysDmg).toBe(13) expect(rSW.maxPhysDmg).toBe(17) expect(rSW.petMax).toBe(5) // Grizzly (247) const rGz = evaluateSkill113c({ registry, skill: registry.getSkillById(247)!, slvl: 10, blvl: 10, statList: stats, }) expect(rGz.manaCost).toBe(40) expect(rGz.minPhysDmg).toBe(130) expect(rGz.maxPhysDmg).toBe(160) expect(rGz.petMax).toBe(1) }) }) })