703 lines
29 KiB
TypeScript
703 lines
29 KiB
TypeScript
/**
|
|
* Diablo II: Lord of Destruction v1.13c — Cohort 5 Barbarian Warcries & Corpse Interactions
|
|
* Challenger 2 Adversarial Stress Testing & Empirical Vulnerability Probe Suite
|
|
*
|
|
* Ground Truth:
|
|
* - D2Game/src/SKILLS/SkillBar.cpp & SkillBarDo.cpp (D2MOO decompiled binary assembly)
|
|
* - Patch_D2.mpq: Skills.txt, Missiles.txt, States.txt
|
|
*
|
|
* Adversarial Testing Targets:
|
|
* 1. Battle Orders Life / Mana / Stamina Scaling & Party Buff Dissemination
|
|
* 2. Battle Command Skill Bonus & Duration / Radius Scaling
|
|
* 3. War Cry Radial Physical Damage & Stun Duration Application
|
|
* 4. Battle Cry Defense & Damage Debuffs, Duration Scaling & Curse Overwriting
|
|
* 5. Taunt & Howl AI Flee/Taunt States, Debuff Modifiers & Boss Immunities
|
|
* 6. Find Item & Find Potion Corpse Target Validity, Diminishing Returns & Loot Execution
|
|
* 7. Grim Ward Fear Totem Corpse Consumption, Entity Spawning & Boss Fear Immunity
|
|
*/
|
|
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import {
|
|
calculateHowlStats,
|
|
calculateFindPotionStats,
|
|
calculateTauntStats,
|
|
calculateShoutStats,
|
|
calculateFindItemStats,
|
|
calculateBattleCryStats,
|
|
calculateBattleOrdersStats,
|
|
calculateGrimWardStats,
|
|
calculateWarCryStats,
|
|
calculateBattleCommandStats,
|
|
evaluateBarbarianWarcry,
|
|
validateBarbarianCorpseTarget,
|
|
} from '../../../src/game/skills/barbarian-warcries.ts'
|
|
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
|
|
import { UnitStatList } from '../../../src/game/engine/stat-list.ts'
|
|
import { StateBus } from '../../../src/game/engine/state-bus.ts'
|
|
import {
|
|
evaluateSkill113c,
|
|
executeSkill113c,
|
|
registerSkillModule,
|
|
} from '../../../src/game/skills/registry.ts'
|
|
import { skillModule as findPotionModule } from '../../../src/game/skills/impl/bar/skill-131-find-potion.ts'
|
|
import { skillModule as findItemModule } from '../../../src/game/skills/impl/bar/skill-142-find-item.ts'
|
|
import { skillModule as grimWardModule } from '../../../src/game/skills/impl/bar/skill-150-grim-ward.ts'
|
|
import type { CombatUnitContext } from '../../../src/game/engine/combat-pipeline.ts'
|
|
|
|
describe('Challenger 2 — Barbarian Warcries & Corpse Interactions Stress Suite', () => {
|
|
|
|
// =========================================================================
|
|
// Section 1: Battle Orders Life / Mana / Stamina Scaling & Party Buff
|
|
// =========================================================================
|
|
describe('1. Battle Orders Life / Mana / Stamina Scaling & Party Dissemination', () => {
|
|
it('1.1 verifies +35% base life/mana/stamina + 3% per slvl formula across slvl 1..30', () => {
|
|
// slvl 1: +35%
|
|
const s1 = calculateBattleOrdersStats(1)
|
|
expect(s1.maxLifeBonusPct).toBe(35)
|
|
expect(s1.maxManaBonusPct).toBe(35)
|
|
expect(s1.maxStaminaBonusPct).toBe(35)
|
|
expect(s1.durationFrames).toBe(750) // 30.0s
|
|
|
|
// slvl 2: +38%
|
|
const s2 = calculateBattleOrdersStats(2)
|
|
expect(s2.maxLifeBonusPct).toBe(38)
|
|
expect(s2.maxManaBonusPct).toBe(38)
|
|
expect(s2.maxStaminaBonusPct).toBe(38)
|
|
|
|
// slvl 10: 35 + 3 * 9 = 62%
|
|
const s10 = calculateBattleOrdersStats(10)
|
|
expect(s10.maxLifeBonusPct).toBe(62)
|
|
expect(s10.maxManaBonusPct).toBe(62)
|
|
expect(s10.maxStaminaBonusPct).toBe(62)
|
|
|
|
// slvl 20: 35 + 3 * 19 = 92%
|
|
const s20 = calculateBattleOrdersStats(20)
|
|
expect(s20.maxLifeBonusPct).toBe(92)
|
|
expect(s20.maxManaBonusPct).toBe(92)
|
|
expect(s20.maxStaminaBonusPct).toBe(92)
|
|
|
|
// slvl 30: 35 + 3 * 29 = 122%
|
|
const s30 = calculateBattleOrdersStats(30)
|
|
expect(s30.maxLifeBonusPct).toBe(122)
|
|
expect(s30.maxManaBonusPct).toBe(122)
|
|
expect(s30.maxStaminaBonusPct).toBe(122)
|
|
})
|
|
|
|
it('1.2 empirical probe: checks whether party members receive Battle Orders buff during execution', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const boRec = registry.getSkillById(149)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const allyStats = new UnitStatList(registry)
|
|
const allyBus = new StateBus(allyStats, registry)
|
|
const ally: CombatUnitContext = { id: 'ally', name: 'Mercenary', statList: allyStats, stateBus: allyBus }
|
|
|
|
const evalBO = evaluateSkill113c({ registry, skill: boRec, slvl: 10, blvl: 10, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: boRec,
|
|
evalResult: evalBO,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map([['hero', { x: 0, y: 0 }], ['ally', { x: 10, y: 10 }]]),
|
|
allies: [ally],
|
|
corpses: [],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
// Caster receives buff
|
|
expect(heroBus.hasState('battleorders')).toBe(true)
|
|
|
|
// In D2 1.13c, party members within aura radius receive Battle Orders!
|
|
const allyReceivedBuff = allyBus.hasState('battleorders')
|
|
expect(allyReceivedBuff).toBe(true)
|
|
})
|
|
|
|
it('1.3 empirical probe: checks whether stamina bonus is applied in state stats', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const boRec = registry.getSkillById(149)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const evalBO = evaluateSkill113c({ registry, skill: boRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: boRec,
|
|
evalResult: evalBO,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
const entry = heroBus.getState('battleorders')
|
|
expect(entry).toBeDefined()
|
|
expect(entry?.stats?.['item_maxhp_percent']).toBe(35)
|
|
expect(entry?.stats?.['item_maxmana_percent']).toBe(35)
|
|
|
|
// Battle Orders grants +35% max stamina
|
|
expect(entry?.stats?.['item_maxstamina_percent']).toBe(35)
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 2: Battle Command Skill Bonus & Duration / Radius
|
|
// =========================================================================
|
|
describe('2. Battle Command Skill Bonus & Duration Scaling', () => {
|
|
it('2.1 evaluates +1 to all skills buff and duration scaling', () => {
|
|
const s1 = calculateBattleCommandStats(1)
|
|
expect(s1.allSkillsBonus).toBe(1)
|
|
// 1.13c Skills.txt param1=125 frames (5.0s), param2=250 frames (10.0s/lvl)
|
|
expect(s1.durationFrames).toBe(125)
|
|
expect(s1.durationSeconds).toBe(5.0)
|
|
|
|
const s10 = calculateBattleCommandStats(10)
|
|
expect(s10.allSkillsBonus).toBe(1)
|
|
expect(s10.durationFrames).toBe(2375) // 125 + 250 * 9 = 95s
|
|
|
|
const s20 = calculateBattleCommandStats(20)
|
|
expect(s20.allSkillsBonus).toBe(1)
|
|
expect(s20.durationFrames).toBe(4875) // 125 + 250 * 19 = 195s
|
|
})
|
|
|
|
it('2.2 empirical probe: evaluates radiusSubtiles calculation in registry', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const bcRec = registry.getSkillById(155)!
|
|
const stats = new UnitStatList(registry)
|
|
const evalBC = evaluateSkill113c({ registry, skill: bcRec, slvl: 20, blvl: 20, statList: stats })
|
|
|
|
expect(evalBC.durationFrames).toBe(4875)
|
|
expect(evalBC.radiusSubtiles).toBe(20)
|
|
})
|
|
|
|
it('2.3 empirical probe: verifies whether party members receive Battle Command +1 all skills', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const bcRec = registry.getSkillById(155)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const allyStats = new UnitStatList(registry)
|
|
const allyBus = new StateBus(allyStats, registry)
|
|
const ally: CombatUnitContext = { id: 'ally', name: 'Mercenary', statList: allyStats, stateBus: allyBus }
|
|
|
|
const evalBC = evaluateSkill113c({ registry, skill: bcRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: bcRec,
|
|
evalResult: evalBC,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map([['hero', { x: 0, y: 0 }], ['ally', { x: 5, y: 5 }]]),
|
|
allies: [ally],
|
|
corpses: [],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
expect(heroBus.hasState('battlecommand')).toBe(true)
|
|
expect(heroBus.getState('battlecommand')?.stats?.['item_allskills']).toBe(1)
|
|
|
|
// Ally receives Battle Command state
|
|
expect(allyBus.hasState('battlecommand')).toBe(true)
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 3: War Cry Radial Stun & Damage
|
|
// =========================================================================
|
|
describe('3. War Cry Radial Stun & Damage Scaling', () => {
|
|
it('3.1 verifies 5-band physical damage and stun duration scaling formulas', () => {
|
|
// slvl 1: 20..30 dmg, 25 frames (1.0s) stun, 10 mana
|
|
const wc1 = calculateWarCryStats(1)
|
|
expect(wc1.minPhysicalDamage).toBe(20)
|
|
expect(wc1.maxPhysicalDamage).toBe(30)
|
|
expect(wc1.stunDurationFrames).toBe(25)
|
|
expect(wc1.stunDurationSeconds).toBe(1.0)
|
|
expect(wc1.manaCost).toBe(10)
|
|
|
|
// slvl 10: 76..86 dmg, 70 frames (2.8s) stun, 19 mana
|
|
const wc10 = calculateWarCryStats(10)
|
|
expect(wc10.minPhysicalDamage).toBe(76)
|
|
expect(wc10.maxPhysicalDamage).toBe(86)
|
|
expect(wc10.stunDurationFrames).toBe(70)
|
|
expect(wc10.stunDurationSeconds).toBe(2.8)
|
|
expect(wc10.manaCost).toBe(19)
|
|
|
|
// slvl 20: 150..160 dmg, 120 frames (4.8s) stun, 29 mana
|
|
const wc20 = calculateWarCryStats(20)
|
|
expect(wc20.minPhysicalDamage).toBe(150)
|
|
expect(wc20.maxPhysicalDamage).toBe(160)
|
|
expect(wc20.stunDurationFrames).toBe(120)
|
|
expect(wc20.stunDurationSeconds).toBe(4.8)
|
|
expect(wc20.manaCost).toBe(29)
|
|
})
|
|
|
|
it('3.2 empirical probe: checks whether War Cry actually stuns enemies during runtime execution', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const wcRec = registry.getSkillById(154)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const dummyStats = new UnitStatList(registry)
|
|
const dummyBus = new StateBus(dummyStats, registry)
|
|
const dummy: CombatUnitContext = { id: 'dummy', name: 'Monster Dummy', statList: dummyStats, stateBus: dummyBus }
|
|
|
|
const evalWC = evaluateSkill113c({ registry, skill: wcRec, slvl: 10, blvl: 10, statList: heroStats })
|
|
const outcome = executeSkill113c({
|
|
registry,
|
|
skill: wcRec,
|
|
evalResult: evalWC,
|
|
caster: hero,
|
|
targets: [dummy],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: { spawnMissile: () => ({ name: 'warcry' }) } as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
// Damage was dealt
|
|
expect(outcome.totalDamageDealt).toBeGreaterThan(0)
|
|
|
|
// In 1.13c, War Cry applies radial stun
|
|
const dummyIsStunned = dummyBus.hasState('stun')
|
|
expect(dummyIsStunned).toBe(true)
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 4: Battle Cry Debuff & Overwriting Rules
|
|
// =========================================================================
|
|
describe('4. Battle Cry Debuff & Curse Overwriting Rules', () => {
|
|
it('4.1 verifies formula for -Enemy Defense % and -Enemy Damage % across levels', () => {
|
|
// slvl 1: -50% def, -25% dmg, 300 frames (12.0s)
|
|
const bc1 = calculateBattleCryStats(1)
|
|
expect(bc1.targetDefenseReductionPct).toBe(50)
|
|
expect(bc1.targetDamageReductionPct).toBe(25)
|
|
expect(bc1.durationFrames).toBe(300)
|
|
|
|
// slvl 10: -68% def, -34% dmg, 840 frames (33.6s)
|
|
const bc10 = calculateBattleCryStats(10)
|
|
expect(bc10.targetDefenseReductionPct).toBe(68)
|
|
expect(bc10.targetDamageReductionPct).toBe(34)
|
|
expect(bc10.durationFrames).toBe(840)
|
|
|
|
// slvl 20: -88% def, -44% dmg, 1440 frames (57.6s)
|
|
const bc20 = calculateBattleCryStats(20)
|
|
expect(bc20.targetDefenseReductionPct).toBe(88)
|
|
expect(bc20.targetDamageReductionPct).toBe(44)
|
|
expect(bc20.durationFrames).toBe(1440)
|
|
})
|
|
|
|
it('4.2 empirical probe: checks applied stats and duration in runtime registry execution', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const bcRec = registry.getSkillById(146)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const dummyStats = new UnitStatList(registry)
|
|
const dummyBus = new StateBus(dummyStats, registry)
|
|
const dummy: CombatUnitContext = { id: 'dummy', name: 'Monster', statList: dummyStats, stateBus: dummyBus }
|
|
|
|
const evalBC1 = evaluateSkill113c({ registry, skill: bcRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: bcRec,
|
|
evalResult: evalBC1,
|
|
caster: hero,
|
|
targets: [dummy],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
const entry = dummyBus.getState('battlecry')
|
|
expect(entry).toBeDefined()
|
|
|
|
// 1.13c: Battle Cry reduces defense by -50% at slvl 1
|
|
expect(entry?.stats?.['item_armor_percent']).toBe(-50)
|
|
|
|
// 1.13c: Battle Cry reduces damage by -25% at slvl 1
|
|
const hasDamageReduction = entry?.stats?.['damage_percent'] !== undefined || entry?.stats?.['item_mindamage_percent'] !== undefined
|
|
expect(hasDamageReduction).toBe(true)
|
|
expect(entry?.stats?.['item_mindamage_percent']).toBe(-25)
|
|
|
|
// 1.13c: Duration scales to 840 frames at slvl 10
|
|
const evalBC10 = evaluateSkill113c({ registry, skill: bcRec, slvl: 10, blvl: 10, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: bcRec,
|
|
evalResult: evalBC10,
|
|
caster: hero,
|
|
targets: [dummy],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 10,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
const entry10 = dummyBus.getState('battlecry')
|
|
expect(entry10?.durationFrames).toBe(840)
|
|
})
|
|
|
|
it('4.3 empirical probe: verifies curse overwriting rules (curseType 1 mutual exclusion)', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const bcRec = registry.getSkillById(146)!
|
|
const ampRec = registry.getSkillById(66)! // Amplify Damage (NEC)
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const dummyStats = new UnitStatList(registry)
|
|
const dummyBus = new StateBus(dummyStats, registry)
|
|
const dummy: CombatUnitContext = { id: 'dummy', name: 'Monster', statList: dummyStats, stateBus: dummyBus }
|
|
|
|
// Apply Battle Cry
|
|
const evalBC = evaluateSkill113c({ registry, skill: bcRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry, skill: bcRec, evalResult: evalBC, caster: hero, targets: [dummy],
|
|
targetPositions: new Map(), corpses: [], missileEngine: {} as any, auraScanner: {} as any, summonManager: {} as any, currentTick: 1, targetX: 0, targetY: 0,
|
|
})
|
|
expect(dummyBus.hasState('battlecry')).toBe(true)
|
|
|
|
// Apply Amplify Damage: should evict Battle Cry because both are curseType 1
|
|
const evalAmp = evaluateSkill113c({ registry, skill: ampRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry, skill: ampRec, evalResult: evalAmp, caster: hero, targets: [dummy],
|
|
targetPositions: new Map(), corpses: [], missileEngine: {} as any, auraScanner: {} as any, summonManager: {} as any, currentTick: 2, targetX: 0, targetY: 0,
|
|
})
|
|
expect(dummyBus.hasState('amplifydamage')).toBe(true)
|
|
expect(dummyBus.hasState('battlecry')).toBe(false) // Battle Cry was evicted
|
|
|
|
// Apply Battle Cry back: should evict Amplify Damage
|
|
executeSkill113c({
|
|
registry, skill: bcRec, evalResult: evalBC, caster: hero, targets: [dummy],
|
|
targetPositions: new Map(), corpses: [], missileEngine: {} as any, auraScanner: {} as any, summonManager: {} as any, currentTick: 3, targetX: 0, targetY: 0,
|
|
})
|
|
expect(dummyBus.hasState('battlecry')).toBe(true)
|
|
expect(dummyBus.hasState('amplifydamage')).toBe(false) // Amplify was evicted
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 5: Taunt & Howl AI Flee/Taunt States & Boss Immunity
|
|
// =========================================================================
|
|
describe('5. Taunt & Howl AI Flee/Taunt States & Immunities', () => {
|
|
it('5.1 empirical probe: verifies Howl state application and boss immunity bypass', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const howlRec = registry.getSkillById(130)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
// Normal monster
|
|
const minionStats = new UnitStatList(registry)
|
|
const minionBus = new StateBus(minionStats, registry)
|
|
const minion: CombatUnitContext = { id: 'minion', name: 'Fallen', statList: minionStats, stateBus: minionBus }
|
|
|
|
// Boss monster (e.g. Diablo)
|
|
const bossStats = new UnitStatList(registry)
|
|
const bossBus = new StateBus(bossStats, registry)
|
|
const boss: CombatUnitContext = { id: 'boss', name: 'Diablo', statList: bossStats, stateBus: bossBus, isBoss: true, rank: 'boss' }
|
|
|
|
const evalHowl = evaluateSkill113c({ registry, skill: howlRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: howlRec,
|
|
evalResult: evalHowl,
|
|
caster: hero,
|
|
targets: [minion, boss],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: { spawnMissile: () => ({ name: 'howl' }) } as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
// Minion receives 'terror'
|
|
expect(minionBus.hasState('terror')).toBe(true)
|
|
|
|
// In 1.13c, Bosses are strictly immune to mental flee (Howl)
|
|
const bossWasTerrified = bossBus.hasState('terror')
|
|
expect(bossWasTerrified).toBe(false)
|
|
})
|
|
|
|
it('5.2 empirical probe: verifies Taunt state application and debuff stats emptiness', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const tauntRec = registry.getSkillById(137)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const dummyStats = new UnitStatList(registry)
|
|
const dummyBus = new StateBus(dummyStats, registry)
|
|
const dummy: CombatUnitContext = { id: 'dummy', name: 'Monster', statList: dummyStats, stateBus: dummyBus }
|
|
|
|
const evalTaunt = evaluateSkill113c({ registry, skill: tauntRec, slvl: 5, blvl: 5, statList: heroStats })
|
|
executeSkill113c({
|
|
registry,
|
|
skill: tauntRec,
|
|
evalResult: evalTaunt,
|
|
caster: hero,
|
|
targets: [dummy],
|
|
targetPositions: new Map(),
|
|
corpses: [],
|
|
missileEngine: { spawnMissile: () => ({ name: 'taunt' }) } as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
expect(dummyBus.hasState('taunt')).toBe(true)
|
|
const entry = dummyBus.getState('taunt')
|
|
|
|
// In 1.13c, Taunt slvl 5 reduces Attack Rating (-13%) and Damage (-13%)
|
|
expect(entry?.stats?.['tohit_percent']).toBe(-13)
|
|
expect(entry?.stats?.['item_mindamage_percent']).toBe(-13)
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 6: Find Item & Find Potion Corpse Validity & Horking Success Roll
|
|
// =========================================================================
|
|
describe('6. Find Item & Find Potion Corpse Target Validity & Horking Rolls', () => {
|
|
it('6.1 verifies 1.13c corpse rejection guards for shattered, consumed, and boss corpses', () => {
|
|
registerSkillModule(findPotionModule)
|
|
registerSkillModule(findItemModule)
|
|
|
|
for (const skillId of [131, 142]) {
|
|
// Missing corpse
|
|
expect(validateBarbarianCorpseTarget(skillId, undefined).valid).toBe(false)
|
|
// Consumed
|
|
expect(validateBarbarianCorpseTarget(skillId, { consumed: true }).valid).toBe(false)
|
|
// Shattered by cold death
|
|
expect(validateBarbarianCorpseTarget(skillId, { consumed: false, shattered: true }).valid).toBe(false)
|
|
// Boss corpse
|
|
expect(validateBarbarianCorpseTarget(skillId, { consumed: false, isBoss: true }).valid).toBe(false)
|
|
// Clean valid corpse
|
|
expect(validateBarbarianCorpseTarget(skillId, { consumed: false, shattered: false, isBoss: false }).valid).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('6.2 empirical probe: verifies Find Item / Find Potion execution ignores success chance and always succeeds', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const fiRec = registry.getSkillById(142)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
registerSkillModule(findItemModule)
|
|
const evalFI = evaluateSkill113c({ registry, skill: fiRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
|
|
// At slvl 1, Find Item success chance is 13%
|
|
expect(calculateFindItemStats(1).dropChancePct).toBe(13)
|
|
|
|
let successes = 0
|
|
const totalTrials = 25
|
|
for (let i = 0; i < totalTrials; i++) {
|
|
const corpse = { id: `corpse-${i}`, consumed: false, shattered: false, isBoss: false, baseMaxHp: 500, x: 0, y: 0 }
|
|
const outcome = executeSkill113c({
|
|
registry,
|
|
skill: fiRec,
|
|
evalResult: evalFI,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map(),
|
|
corpses: [corpse as any],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
if (outcome.notes.includes('item_found')) {
|
|
successes++
|
|
}
|
|
}
|
|
|
|
// In 1.13c, Find Item rolls against dropChancePct (13% at slvl 1)
|
|
expect(successes).toBeLessThan(totalTrials)
|
|
})
|
|
|
|
it('6.3 empirical probe: verifies loot roll execution (actual item generation) is absent', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const fiRec = registry.getSkillById(142)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
registerSkillModule(findItemModule)
|
|
const evalFI = evaluateSkill113c({ registry, skill: fiRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
const corpse = { id: 'c-loot', consumed: false, shattered: false, isBoss: false, baseMaxHp: 500, x: 0, y: 0 }
|
|
|
|
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.05)
|
|
const outcome = executeSkill113c({
|
|
registry,
|
|
skill: fiRec,
|
|
evalResult: evalFI,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map(),
|
|
corpses: [corpse as any],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
randomSpy.mockRestore()
|
|
|
|
// Find Item pushes string 'item_found' to outcome.notes;
|
|
// It does NOT invoke executeDropPipeline or produce physical ground items.
|
|
expect(outcome.notes).toContain('item_found')
|
|
const hasDropPipelineCall = outcome.notes.some(n => n.startsWith('drop:') || n.startsWith('item_dropped'))
|
|
expect(hasDropPipelineCall).toBe(false)
|
|
})
|
|
})
|
|
|
|
// =========================================================================
|
|
// Section 7: Grim Ward Fear Totem
|
|
// =========================================================================
|
|
describe('7. Grim Ward Fear Totem & Boss Immunity', () => {
|
|
it('7.1 verifies corpse consumption on Grim Ward placement', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const gwRec = registry.getSkillById(150)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
registerSkillModule(grimWardModule)
|
|
const evalGW = evaluateSkill113c({ registry, skill: gwRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
const corpse = { id: 'corpse-gw', consumed: false, shattered: false, isBoss: false, baseMaxHp: 500, x: 0, y: 0 }
|
|
|
|
const outcome = executeSkill113c({
|
|
registry,
|
|
skill: gwRec,
|
|
evalResult: evalGW,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map(),
|
|
corpses: [corpse as any],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
expect(outcome.executed).toBe(true)
|
|
expect(outcome.corpsesConsumed).toBe(1)
|
|
expect(corpse.consumed).toBe(true)
|
|
})
|
|
|
|
it('7.2 empirical probe: checks whether Grim Ward spawns a persistent totem entity (1000 frames)', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const gwRec = registry.getSkillById(150)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
registerSkillModule(grimWardModule)
|
|
const evalGW = evaluateSkill113c({ registry, skill: gwRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
const corpse = { id: 'corpse-gw-pet', consumed: false, shattered: false, isBoss: false, baseMaxHp: 500, x: 0, y: 0 }
|
|
|
|
const outcome = executeSkill113c({
|
|
registry,
|
|
skill: gwRec,
|
|
evalResult: evalGW,
|
|
caster: hero,
|
|
targets: [],
|
|
targetPositions: new Map(),
|
|
corpses: [corpse as any],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
// CHALLENGER PROBE: In registry.ts line 512-518, Grim Ward does NOT spawn any summon or totem missile!
|
|
// petsSummoned and missilesSpawned are empty!
|
|
expect(outcome.petsSummoned.length).toBe(0)
|
|
expect(outcome.missilesSpawned.length).toBe(0)
|
|
})
|
|
|
|
it('7.3 empirical probe: checks whether Grim Ward fear aura respects boss immunity', async () => {
|
|
const registry = await getSharedDataRegistry()
|
|
const gwRec = registry.getSkillById(150)!
|
|
const heroStats = new UnitStatList(registry)
|
|
const heroBus = new StateBus(heroStats, registry)
|
|
const hero: CombatUnitContext = { id: 'hero', name: 'Barbarian', statList: heroStats, stateBus: heroBus }
|
|
|
|
const bossStats = new UnitStatList(registry)
|
|
const bossBus = new StateBus(bossStats, registry)
|
|
const boss: CombatUnitContext = { id: 'boss', name: 'Diablo', statList: bossStats, stateBus: bossBus, isBoss: true, rank: 'boss' }
|
|
|
|
registerSkillModule(grimWardModule)
|
|
const evalGW = evaluateSkill113c({ registry, skill: gwRec, slvl: 1, blvl: 1, statList: heroStats })
|
|
const corpse = { id: 'corpse-gw-boss', consumed: false, shattered: false, isBoss: false, baseMaxHp: 500, x: 0, y: 0 }
|
|
|
|
executeSkill113c({
|
|
registry,
|
|
skill: gwRec,
|
|
evalResult: evalGW,
|
|
caster: hero,
|
|
targets: [boss],
|
|
targetPositions: new Map(),
|
|
corpses: [corpse as any],
|
|
missileEngine: {} as any,
|
|
auraScanner: {} as any,
|
|
summonManager: {} as any,
|
|
currentTick: 1,
|
|
targetX: 0,
|
|
targetY: 0,
|
|
})
|
|
|
|
// In 1.13c, Bosses are strictly immune to Grim Ward radial fear!
|
|
const bossFled = bossBus.hasState('terror')
|
|
expect(bossFled).toBe(false)
|
|
})
|
|
})
|
|
})
|