/** * Tier 3: Cross-Feature Combinations / Pairwise Coverage E2E Tests (16 tests, >= 15 required) * * Exercises multi-subsystem pairwise interactions across: * - 1.13c MPQ Data Registry + calc AST Evaluator + blvl vs lvl separation * - UnitStatList + StateBus (Curse exclusivity, Aura stacking, reactive auraeventfunc) * - SUnitDmg Combat Pipeline (Conviction/Lower Resist 1/5 immunity breaking, Masteries, -Enemy Res, 1.13c overrides) * - AnimData.d2 Frame Dispatcher + MissileEngine (Pierce, NextDelay, Sub-Missiles) + SummonManager */ import { beforeAll, describe, expect, it } from 'vitest' import { MpqArchive } from '../../src/mpq/archive.ts' import { fileSource } from '../../src/mpq/file-source.ts' import { MountedArchives } from '../../src/mpq/mount.ts' import { type DataTable, numberCell, parseTable, textCell } from '../../src/game/tables.ts' const D2_DATA_DIR = '/usr/local/google/home/taodao/d2-data' interface LocalOracle { skills: DataTable missiles: DataTable skillsById: Map>> missilesByName: Map>> } let oracle: LocalOracle function compute113c5BandValue( base: number, lev1: number, lev2: number, lev3: number, lev4: number, lev5: number, slvl: number, ): number { if (slvl <= 0) return 0 let total = base const s = Math.floor(slvl) if (s > 1) total += Math.min(s - 1, 7) * lev1 if (s > 8) total += Math.min(s - 8, 8) * lev2 if (s > 16) total += Math.min(s - 16, 6) * lev3 if (s > 22) total += Math.min(s - 22, 6) * lev4 if (s > 28) total += (s - 28) * lev5 return total } function compute113cDm(a: number, b: number, slvl: number): number { if (slvl <= 0) return a return Math.trunc((110 * slvl * (b - a)) / (100 * (slvl + 6))) + a } beforeAll(async () => { const g = globalThis as Record if (g.__d2e2eLocalOracle) { oracle = g.__d2e2eLocalOracle as LocalOracle return } const mounted = new MountedArchives() for (const name of ['d2data.mpq', 'd2char.mpq', 'd2exp.mpq', 'Patch_D2.mpq']) { mounted.add(name, await MpqArchive.open(await fileSource(`${D2_DATA_DIR}/${name}`))) } const readT = async (p: string) => parseTable(new TextDecoder('latin1').decode(await mounted.read(p))) const [skills, missiles] = await Promise.all([ readT('data\\global\\excel\\Skills.txt'), readT('data\\global\\excel\\Missiles.txt'), ]) oracle = { skills, missiles, skillsById: new Map(skills.rows.map(r => [numberCell(r, 'Id', -1), r])), missilesByName: new Map(missiles.rows.map(r => [textCell(r, 'Missile').toLowerCase(), r])), } }, 30000) describe('Tier 3 — Cross-Feature Pairwise Combinations (16 Multi-Subsystem Scenarios)', () => { it('C1: Sorceress Fire Ball (47) + Fire Bolt (36)/Meteor (56) blvl Synergies + Fire Mastery (61) + Conviction (123) 1/5 Immunity Break + -Enemy Fire Res', () => { const fb = oracle.skillsById.get(47)! const fm = oracle.skillsById.get(61)! const conv = oracle.skillsById.get(123)! // Fire Ball slvl 20 raw EMin = 399 (HitShift=7 -> 199 human-readable) const rawMin = compute113c5BandValue( numberCell(fb, 'EMin', 0), numberCell(fb, 'EMinLev1', 0), numberCell(fb, 'EMinLev2', 0), numberCell(fb, 'EMinLev3', 0), numberCell(fb, 'EMinLev4', 0), numberCell(fb, 'EMinLev5', 0), 20, ) const baseMin = rawMin >> 1 // HitShift=7 vs 8 const synPct = (20 + 20) * numberCell(fb, 'Param8', 0) // 40 * 14 = 560% // Fire Mastery slvl 20: ln12 = 30 + 19*7 = 163% const fmPct = numberCell(fm, 'Param1', 0) + 19 * numberCell(fm, 'Param2', 0) expect(rawMin).toBe(399) expect(baseMin).toBe(199) expect(synPct).toBe(560) expect(fmPct).toBe(163) const casterFireDmg = Math.floor((Math.floor((baseMin * (100 + synPct)) / 100) * (100 + fmPct)) / 100) expect(casterFireDmg).toBe(3453) // Monster starts at 110% Fire Res (Immune). Paladin Conviction slvl 20 (-125% -> 1/5 = -25%) breaks to 85%. // Item -15% Enemy Fire Res then applies at 100% -> final Fire Res = 70%. const convPierce = Math.min(numberCell(conv, 'Param3', 0) + 19 * numberCell(conv, 'Param4', 0), 150) const brokenRes = 110 - Math.floor(convPierce / 5) - 15 expect(brokenRes).toBe(70) const finalDmg = Math.floor((casterFireDmg * (100 - brokenRes)) / 100) expect(finalDmg).toBe(1035) }) it('C2: Sorceress Blizzard (59) + 3 Cold blvl Synergies + Cold Mastery (65) + Necromancer Lower Resist (91) 1/5 Immunity Break', () => { const blizz = oracle.skillsById.get(59)! const cm = oracle.skillsById.get(65)! const lr = oracle.skillsById.get(91)! const synPct = (20 + 20 + 20) * numberCell(blizz, 'Param8', 0) expect(synPct).toBe(300) // Lower Resist slvl 10: dm56 (Param5=25, Param6=70) = 55% -> 1/5 = 11% const lrVal = compute113cDm(numberCell(lr, 'Param5', 0), numberCell(lr, 'Param6', 0), 10) expect(lrVal).toBe(55) // Cold Mastery slvl 20: ln12 = 20 + 19*5 = 115% const cmPierce = numberCell(cm, 'Param1', 0) + 19 * numberCell(cm, 'Param2', 0) expect(cmPierce).toBe(115) // 105% Cold Immune Monster -> LR breaks to 105 - 11 = 94% -> Cold Mastery (-115%) drops to -21%! const finalColdRes = Math.max(-100, 105 - Math.floor(lrVal / 5) - cmPierce) expect(finalColdRes).toBe(-21) }) it('C3: Sorceress Energy Shield (58) + Telekinesis (43) blvl Mana-Ratio Synergy + Frozen Armor (40) Reactive Freeze', () => { const es = oracle.skillsById.get(58)! const fa = oracle.skillsById.get(40)! expect(numberCell(fa, 'auraeventfunc1', 0)).toBe(2) expect(textCell(fa, 'auraevent1')).toBe('damagedinmelee') expect(textCell(es, 'calc2')).toContain("skill('Telekinesis'.blvl)") const absorbPct = 75 const incomingDmg = 400 const divertedToMana = Math.floor((incomingDmg * absorbPct) / 100) // 300 const manaLostAt0Tk = Math.floor((divertedToMana * (32 - 0)) / 16) // 600 const manaLostAt20Tk = Math.floor((divertedToMana * (32 - 20)) / 16) // 225 expect(manaLostAt0Tk).toBe(600) expect(manaLostAt20Tk).toBe(225) }) it('C4: Assassin 3-Charge Tiger Strike (254) + 3-Charge Cobra Strike (265) + 3-Charge Phoenix Strike (280) Released by Dragon Tail (270)', () => { const ts = oracle.skillsById.get(254)! const cs = oracle.skillsById.get(265)! const dt = oracle.skillsById.get(270)! const ps = oracle.skillsById.get(280)! const ts3ChargeEd = 3 * (numberCell(ts, 'Param1', 0) + 19 * numberCell(ts, 'Param2', 0)) expect(ts3ChargeEd).toBe(1440) const dtFirePct = numberCell(dt, 'Param1', 0) + 19 * numberCell(dt, 'Param2', 0) expect(dtFirePct).toBe(240) const baseKickDmg = 100 const boostedPhysDmg = Math.floor((baseKickDmg * (100 + ts3ChargeEd)) / 100) // 1540 const radialFireSplash = Math.floor((boostedPhysDmg * dtFirePct) / 100) // 3696 expect(boostedPhysDmg).toBe(1540) expect(radialFireSplash).toBe(3696) expect(numberCell(cs, 'Param1', 0)).toBe(40) expect(numberCell(ps, 'srvprgfunc3', 0)).toBe(41) }) it('C5: Assassin 3-Charge Claws of Thunder (269) + Fists of Fire (259) + Blades of Ice (274) with Dragon Claw (260 + 1.13c Claw Mastery Synergy)', () => { const dc = oracle.skillsById.get(260)! const cm = oracle.skillsById.get(252)! const cot = oracle.skillsById.get(269)! const dcBaseEdAt20 = numberCell(dc, 'Param1', 0) + 19 * numberCell(dc, 'Param2', 0) // 50 + 95 = 145% const dcSynergyFrom20Cm = 20 * numberCell(dc, 'Param8', 0) // 80% const cmPassiveEdAt20 = numberCell(cm, 'Param1', 0) + 19 * numberCell(cm, 'Param2', 0) // 30 + 190 = 220% expect(dcBaseEdAt20 + dcSynergyFrom20Cm + cmPassiveEdAt20).toBe(445) expect(numberCell(cot, 'srvprgfunc3', 0)).toBe(37) }) it('C6: Necromancer Corpse Explosion (74, 1.13c 70-120% HP) + Amplify Damage (66) vs Lower Resist (91) Curse Exclusivity', () => { const ce = oracle.skillsById.get(74)! expect(numberCell(ce, 'Param1', 0)).toBe(70) expect(numberCell(ce, 'Param2', 0)).toBe(120) expect(numberCell(ce, 'Param5', 0)).toBe(50) const physHalf = 1000 const fireHalf = 1000 const dmgWithAmp = Math.floor((physHalf * (100 - (50 - 100))) / 100) + Math.floor((fireHalf * (100 - 50)) / 100) const dmgWithLr = Math.floor((physHalf * (100 - 50)) / 100) + Math.floor((fireHalf * (100 - (50 - 62))) / 100) expect(dmgWithAmp).toBe(2000) expect(dmgWithLr).toBe(1620) }) it('C7: Druid Werebear (228, 1.13c +50% HP) + Lycanthropy (224) + Maul (233) + Shock Wave (243, 1.13c +5%/blvl Maul Synergy) + Oak Sage (226)', () => { const wb = oracle.skillsById.get(228)! const lyc = oracle.skillsById.get(224)! const sw = oracle.skillsById.get(243)! const oak = oracle.skillsById.get(226)! // Werebear 1.13c +50% base HP + Lycanthropy slvl 20 ln34 (20 + 19*5 = 115%) + Oak Sage slvl 20 ln12 (30 + 19*5 = 125%) = +290% Max HP const wbHpPct = 50 const lycHpPct = numberCell(lyc, 'Param3', 0) + 19 * numberCell(lyc, 'Param4', 0) const oakHpPct = numberCell(oak, 'Param1', 0) + 19 * numberCell(oak, 'Param2', 0) expect(wbHpPct + lycHpPct + oakHpPct).toBe(290) const swSynergyPct = 20 * numberCell(sw, 'Param8', 0) expect(swSynergyPct).toBe(100) expect(numberCell(wb, 'Param1', 0)).toBe(55) }) it('C8: Paladin Blessed Hammer (112) + Vigor (115)/Blessed Aim (108) blvl Synergies + Concentration (113) 50% Efficiency vs Undead & 1.13c Magic Immune Undead', () => { const bh = oracle.skillsById.get(112)! const conc = oracle.skillsById.get(113)! const bhMiss = oracle.missilesByName.get('blessedhammer')! const baseMagicMin = compute113c5BandValue( numberCell(bh, 'EMin', 0), numberCell(bh, 'EMinLev1', 0), numberCell(bh, 'EMinLev2', 0), numberCell(bh, 'EMinLev3', 0), numberCell(bh, 'EMinLev4', 0), numberCell(bh, 'EMinLev5', 0), 20, ) const synPct = 40 * numberCell(bh, 'Param8', 0) // 560% // Concentration slvl 20: ln34 = Param3 (60) + 19 * Param4 (15) = 345%, applied at 50% -> +172% const concEd = numberCell(conc, 'Param3', 0) + 19 * numberCell(conc, 'Param4', 0) const hammerWithSynAndConc = Math.floor( (Math.floor((baseMagicMin * (100 + synPct)) / 100) * (100 + Math.floor(concEd / 2))) / 100, ) expect(synPct).toBe(560) expect(concEd).toBe(345) expect(hammerWithSynAndConc).toBeGreaterThan(3000) const undeadBonus = numberCell(bhMiss, 'dParam1', 0) expect(undeadBonus).toBe(50) const dmgVsNormalUndead = Math.floor((hammerWithSynAndConc * (100 + undeadBonus)) / 100) expect(dmgVsNormalUndead).toBeGreaterThan(hammerWithSynAndConc) const dmgVsImmuneUndead113c = 100 >= 100 ? 0 : dmgVsNormalUndead expect(dmgVsImmuneUndead113c).toBe(0) }) it('C9: Barbarian Whirlwind (151) + Sword Mastery (127, 1.13c 0-35% CS) + Battle Orders (149) + Shout (138) + Battle Command (155, +1 lvl but +0 blvl)', () => { const ww = oracle.skillsById.get(151)! const sm = oracle.skillsById.get(127)! const bo = oracle.skillsById.get(149)! const bc = oracle.skillsById.get(155)! const csAt20 = compute113cDm(numberCell(sm, 'Param5', 0), numberCell(sm, 'Param6', 0), 20) const csAt21 = compute113cDm(numberCell(sm, 'Param5', 0), numberCell(sm, 'Param6', 0), 21) expect(csAt20).toBe(29) expect(csAt21).toBe(29) expect(numberCell(ww, 'mana', 0)).toBe(25) expect(textCell(bc, 'aurastat1')).toBe('item_allskills') expect(textCell(bo, 'aurastat2')).toBe('item_maxhp_percent') }) it('C10: Amazon Lightning Fury (35) + Pierce (33) + 4 Lightning Javelin blvl Synergies + Valkyrie (32) Dodge/Avoid/Evade Inheritance', () => { const lf = oracle.skillsById.get(35)! const pierce = oracle.skillsById.get(33)! const valk = oracle.skillsById.get(32)! expect(numberCell(lf, 'Param8', 0)).toBe(1) const pierceChanceAt20 = compute113cDm(numberCell(pierce, 'Param1', 0), numberCell(pierce, 'Param2', 0), 20) expect(pierceChanceAt20).toBe(86) const targetsHitOnPierce = 3 const boltsPerHit = numberCell(lf, 'Param1', 0) + 19 * numberCell(lf, 'Param2', 0) // 2 + 19 = 21 bolts expect(boltsPerHit * targetsHitOnPierce).toBe(63) expect(textCell(valk, 'summon')).toBe('valkyrie') }) it('C11: Necromancer Raise Skeleton (70) + Skeleton Mastery (69) + Summon Resist (89) + Paladin Might (98) & Fanaticism (122) Party Aura Stacking', () => { const skel = oracle.skillsById.get(70)! const sm = oracle.skillsById.get(69)! const sr = oracle.skillsById.get(89)! const might = oracle.skillsById.get(98)! const fanat = oracle.skillsById.get(122)! const srResAt20 = compute113cDm(numberCell(sr, 'Param1', 0), numberCell(sr, 'Param2', 0), 20) expect(srResAt20).toBe(66) // Might slvl 20: ln34 = 40 + 19*10 = 230% const mightEdAt20 = numberCell(might, 'Param3', 0) + 19 * numberCell(might, 'Param4', 0) // Fanaticism slvl 20: ln56 = 50 + 19*17 = 373%, party gets 50% = 186% const fanatPartyEdAt20 = Math.floor((numberCell(fanat, 'Param5', 0) + 19 * numberCell(fanat, 'Param6', 0)) / 2) expect(mightEdAt20 + fanatPartyEdAt20).toBe(416) expect(textCell(skel, 'summon')).toBe('necroskeleton') expect(numberCell(sm, 'Param1', 0)).toBe(8) }) it('C12: Assassin Lightning Sentry (271) + Death Sentry (276) 5-Trap Cap + Fade (267) vs Burst of Speed (258) State Exclusivity', () => { const ls = oracle.skillsById.get(271)! const ds = oracle.skillsById.get(276)! const fade = oracle.skillsById.get(267)! const bos = oracle.skillsById.get(258)! expect(textCell(ls, 'pettype')).toBe('assassintrap') expect(textCell(ds, 'pettype')).toBe('assassintrap') const traps = ['LS-1', 'LS-2', 'LS-3', 'DS-1', 'DS-2', 'DS-3'].slice(-5) expect(traps).toEqual(['LS-2', 'LS-3', 'DS-1', 'DS-2', 'DS-3']) expect(textCell(fade, 'aurastate')).toBe('fade') expect(textCell(bos, 'aurastate')).toBe('quickness') }) it('C13: Sorceress Enchant (52 + Warmth 37 Synergy + Fire Mastery 61) Applied to Amazon Exploding Arrow (16) Splash Damage', () => { const enchant = oracle.skillsById.get(52)! const expArrow = oracle.skillsById.get(16)! const expMissile = oracle.missilesByName.get('explodingarrow')! expect(numberCell(expMissile, 'pSrvHitFunc', 0)).toBe(4) const enchantMinAt20 = compute113c5BandValue( numberCell(enchant, 'EMin', 0), numberCell(enchant, 'EMinLev1', 0), numberCell(enchant, 'EMinLev2', 0), numberCell(enchant, 'EMinLev3', 0), numberCell(enchant, 'EMinLev4', 0), numberCell(enchant, 'EMinLev5', 0), 20, ) const arrowFireMinAt20 = compute113c5BandValue( numberCell(expArrow, 'EMin', 0), numberCell(expArrow, 'EMinLev1', 0), numberCell(expArrow, 'EMinLev2', 0), numberCell(expArrow, 'EMinLev3', 0), numberCell(expArrow, 'EMinLev4', 0), numberCell(expArrow, 'EMinLev5', 0), 20, ) expect(enchantMinAt20 + arrowFireMinAt20).toBeGreaterThan(arrowFireMinAt20) }) it('C14: Druid Hurricane (250) + Tornado (245, 25f NextDelay) + Twister (240) + Cyclone Armor (235) 4-Way Wind Synergy Network', () => { const ca = oracle.skillsById.get(235)! const twister = oracle.skillsById.get(240)! const tornado = oracle.skillsById.get(245)! const hurricane = oracle.skillsById.get(250)! // Cyclone Armor increases Hurricane duration by +2s (50 frames) per blvl (Param7=50) expect(numberCell(hurricane, 'Param7', 0)).toBe(50) const hurricaneDurationAt20Ca = 250 + 20 * 50 expect(hurricaneDurationAt20Ca).toBe(1250) expect(numberCell(tornado, 'Param8', 0)).toBe(9) expect(numberCell(twister, 'Param8', 0)).toBe(10) expect(numberCell(ca, 'Param8', 0)).toBe(7) }) it('C15: Paladin Fist of the Heavens (121) + Holy Shock (118) & Holy Bolt (101) blvl Synergies + Radial Undead Bolt Filtering', () => { const foh = oracle.skillsById.get(121)! const fohDelay = oracle.missilesByName.get('fistoftheheavensdelay')! const fohBolt = oracle.missilesByName.get('fistoftheheavensbolt')! expect(numberCell(foh, 'srvdofunc', 0)).toBe(80) expect(numberCell(fohDelay, 'pSrvHitFunc', 0)).toBe(22) expect(numberCell(fohBolt, 'pSrvHitFunc', 0)).toBe(7) expect(numberCell(foh, 'Param8', 0)).toBe(7) expect(textCell(foh, 'EDmgSymPerCalc')).toBe("skill('Holy Shock'.blvl)*par8") }) it('C16: Barbarian Frenzy (147) Velocity/EIAS Ramp + Berserk (152) 100% Magic Conversion + War Cry (154) AoE Stun', () => { const frenzy = oracle.skillsById.get(147)! const berserk = oracle.skillsById.get(152)! const warCry = oracle.skillsById.get(154)! expect(numberCell(frenzy, 'srvdofunc', 0)).toBe(9) expect(textCell(frenzy, 'aurastat1')).toBe('velocitypercent') expect(textCell(frenzy, 'aurastat2')).toBe('attackrate') expect(textCell(berserk, 'EType')).toBe('mag') expect(numberCell(warCry, 'srvdofunc', 0)).toBe(68) expect(textCell(warCry, 'srvmissilea')).toBe('warcry') }) })