/** * Adversarial Stress & Ground Truth Parity Test Suite — Assassin Martial Arts (10 skills) * * Validates 1.13c Diablo II mechanics: * 1. Charge orb progression (1..3 tiers, 375-frame duration, refresh on hit, timeout expiry). * 2. Multi-skill charge coexistence (simultaneous Tiger, Cobra, and Phoenix charges). * 3. Finisher release mechanics: * - Simultaneous full discharge of all active charge-ups. * - Elemental charge-ups release cumulative tiers (FoF, CoT, BoI). * - Phoenix Strike releases single targeted tier (1: Meteor, 2: Chain Lightning, 3: Chaos Ice). * 4. Dragon Talon kick count progression: 1 + floor(slvl / 6). * 5. Dragon Claw dual-claw equipment validation and 2-hit strike. * 6. Dragon Tail physical-to-fire explosion conversion: 50 + 10 * (slvl - 1)%. * 7. Dragon Flight teleport kick with 4-frame NextDelay and +100..+575% ED. * * Gitea Milestone: M27 (Issue #474) */ import { describe, expect, it } from 'vitest' import { ASSASSIN_MARTIAL_SKILL_IDS, MAX_CHARGES_PER_SKILL, CHARGE_DURATION_FRAMES, ChargeOrbManager, resolveFinisherDischarge, canCastDragonClaw, evaluateTigerStrikeStats, evaluateDragonTalonStats, evaluateFistsOfFireStats, evaluateDragonClawStats, evaluateCobraStrikeStats, evaluateClawsOfThunderStats, evaluateDragonTailStats, evaluateBladesOfIceStats, evaluateDragonFlightStats, evaluatePhoenixStrikeStats, } from '../../../src/server/skills/assassin-martial-arts.ts' import '../../../src/baker/mpq-archive-source.ts' import '../../../src/server/skills/registry.ts' describe('Assassin Martial Arts Ground Truth & Adversarial Suite (Milestone M27)', () => { describe('1. Charge Orb Progression & Expiration Mechanics', () => { it('accumulates charges from 1 to 3 and caps at 3', () => { const manager = new ChargeOrbManager() const skillId = ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE // Hit 1 at frame 10 const c1 = manager.addCharge(skillId, 20, 10) expect(c1.previousCount).toBe(0) expect(c1.currentCount).toBe(1) expect(manager.getChargeLevel(skillId, 10)).toBe(1) // Hit 2 at frame 30 const c2 = manager.addCharge(skillId, 20, 30) expect(c2.previousCount).toBe(1) expect(c2.currentCount).toBe(2) expect(manager.getChargeLevel(skillId, 30)).toBe(2) // Hit 3 at frame 50 const c3 = manager.addCharge(skillId, 20, 50) expect(c3.previousCount).toBe(2) expect(c3.currentCount).toBe(3) expect(manager.getChargeLevel(skillId, 50)).toBe(3) // Hit 4 at frame 70 -> stays at 3, but refreshes duration const c4 = manager.addCharge(skillId, 20, 70) expect(c4.previousCount).toBe(3) expect(c4.currentCount).toBe(3) expect(manager.getChargeLevel(skillId, 70)).toBe(3) }) it('enforces 375-frame (15.0s) duration expiration', () => { const manager = new ChargeOrbManager() const skillId = ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE // Add charge at frame 100 -> expires at 100 + 375 = 475 manager.addCharge(skillId, 20, 100) // Valid within duration window expect(manager.getChargeLevel(skillId, 200)).toBe(1) expect(manager.getChargeLevel(skillId, 474)).toBe(1) // Expired at frame 475 expect(manager.getChargeLevel(skillId, 475)).toBe(0) expect(manager.getActiveCharges(475)).toHaveLength(0) }) it('refreshes expiration when a charge is added before timeout', () => { const manager = new ChargeOrbManager() const skillId = ASSASSIN_MARTIAL_SKILL_IDS.CLAWS_OF_THUNDER // Charge 1 at frame 0 -> expires at 375 manager.addCharge(skillId, 20, 0) expect(manager.getChargeLevel(skillId, 300)).toBe(1) // Charge 2 at frame 350 -> expires at 350 + 375 = 725 manager.addCharge(skillId, 20, 350) expect(manager.getChargeLevel(skillId, 350)).toBe(2) // Frame 400 (> 375 original expiry) is still active! expect(manager.getChargeLevel(skillId, 400)).toBe(2) expect(manager.getChargeLevel(skillId, 724)).toBe(2) // Frame 725 is expired expect(manager.getChargeLevel(skillId, 725)).toBe(0) }) it('supports simultaneous multi-skill charge orb coexistence', () => { const manager = new ChargeOrbManager() // Charge Tiger Strike to 3 manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 0) manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 10) manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 20) // Charge Cobra Strike to 2 manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, 20, 25) manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, 20, 35) // Charge Phoenix Strike to 1 manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.PHOENIX_STRIKE, 20, 40) const active = manager.getActiveCharges(50) expect(active).toHaveLength(3) expect(manager.getChargeLevel(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 50)).toBe(3) expect(manager.getChargeLevel(ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, 50)).toBe(2) expect(manager.getChargeLevel(ASSASSIN_MARTIAL_SKILL_IDS.PHOENIX_STRIKE, 50)).toBe(1) }) }) describe('2. Finisher Release & Discharge Resolution Engine', () => { it('discharges all active charges and clears container on finisher impact', () => { const manager = new ChargeOrbManager() manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 0) manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 10) manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 20, 20) // 3 charges manager.addCharge(ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, 20, 30) // 1 charge const discharged = manager.dischargeAll(50) expect(discharged).toHaveLength(2) // Manager is now completely empty expect(manager.getActiveCharges(50)).toHaveLength(0) expect(manager.getChargeLevel(ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, 50)).toBe(0) expect(manager.getChargeLevel(ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, 50)).toBe(0) }) it('resolves Tiger Strike 3-tier Enhanced Damage bonus (+1440% at slvl 20 Tier 3)', () => { // Tier 1: 480% ED const p1 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, count: 1, slvl: 20, expiresAtFrame: 1000 }]) expect(p1[0]?.enhancedDamagePct).toBe(480) // Tier 2: 960% ED const p2 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, count: 2, slvl: 20, expiresAtFrame: 1000 }]) expect(p2[0]?.enhancedDamagePct).toBe(960) // Tier 3: 1440% ED const p3 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.TIGER_STRIKE, count: 3, slvl: 20, expiresAtFrame: 1000 }]) expect(p3[0]?.enhancedDamagePct).toBe(1440) }) it('resolves Cobra Strike Life and Mana steal progression', () => { // Tier 1: Life Steal only (135% at slvl 20) const p1 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, count: 1, slvl: 20, expiresAtFrame: 1000 }]) expect(p1[0]?.lifeStealPct).toBe(135) expect(p1[0]?.manaStealPct).toBe(0) // Tier 2: Life Steal (135%) + Mana Steal (135%) const p2 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, count: 2, slvl: 20, expiresAtFrame: 1000 }]) expect(p2[0]?.lifeStealPct).toBe(135) expect(p2[0]?.manaStealPct).toBe(135) // Tier 3: 2x Life Steal (270%) + 2x Mana Steal (270%) const p3 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.COBRA_STRIKE, count: 3, slvl: 20, expiresAtFrame: 1000 }]) expect(p3[0]?.lifeStealPct).toBe(270) expect(p3[0]?.manaStealPct).toBe(270) }) it('resolves Claws of Thunder cumulative elemental tiers (Tier 3 releases Nova + 4 Bolts)', () => { // Tier 1: direct damage only const p1 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.CLAWS_OF_THUNDER, count: 1, slvl: 20, expiresAtFrame: 1000 }]) expect(p1[0]?.ltngDamage?.hasNova).toBe(false) expect(p1[0]?.ltngDamage?.chargedBoltsCount).toBeUndefined() // Tier 2: direct damage + Nova const p2 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.CLAWS_OF_THUNDER, count: 2, slvl: 20, expiresAtFrame: 1000 }]) expect(p2[0]?.ltngDamage?.hasNova).toBe(true) expect(p2[0]?.ltngDamage?.chargedBoltsCount).toBeUndefined() // Tier 3: direct damage + Nova + 4 Charged Bolts const p3 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.CLAWS_OF_THUNDER, count: 3, slvl: 20, expiresAtFrame: 1000 }]) expect(p3[0]?.ltngDamage?.hasNova).toBe(true) expect(p3[0]?.ltngDamage?.chargedBoltsCount).toBe(4) }) it('resolves Phoenix Strike single-tier payload selection', () => { // Charge 1: Meteor const p1 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.PHOENIX_STRIKE, count: 1, slvl: 20, expiresAtFrame: 1000 }]) expect(p1[0]?.phoenixPayload?.tier).toBe(1) expect(p1[0]?.phoenixPayload?.type).toBe('meteor') expect(p1[0]?.phoenixPayload?.meteorDamage).toBeDefined() expect(p1[0]?.phoenixPayload?.chainLightningDamage).toBeUndefined() expect(p1[0]?.phoenixPayload?.chaosIceDamage).toBeUndefined() // Charge 2: Chain Lightning const p2 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.PHOENIX_STRIKE, count: 2, slvl: 20, expiresAtFrame: 1000 }]) expect(p2[0]?.phoenixPayload?.tier).toBe(2) expect(p2[0]?.phoenixPayload?.type).toBe('chain_lightning') expect(p2[0]?.phoenixPayload?.chainLightningDamage?.maxTargets).toBe(10) expect(p2[0]?.phoenixPayload?.meteorDamage).toBeUndefined() // Charge 3: Chaos Ice Orb const p3 = resolveFinisherDischarge([{ skillId: ASSASSIN_MARTIAL_SKILL_IDS.PHOENIX_STRIKE, count: 3, slvl: 20, expiresAtFrame: 1000 }]) expect(p3[0]?.phoenixPayload?.tier).toBe(3) expect(p3[0]?.phoenixPayload?.type).toBe('chaos_ice') expect(p3[0]?.phoenixPayload?.chaosIceDamage?.boltsCount).toBe(16) expect(p3[0]?.phoenixPayload?.chaosIceDamage?.freezeFrames).toBeGreaterThan(0) }) }) describe('3. Dragon Talon Multi-Kick Finisher', () => { it('evaluates kick count scaling: 1 + floor(slvl / 6)', () => { expect(evaluateDragonTalonStats(1).kickCount).toBe(1) expect(evaluateDragonTalonStats(5).kickCount).toBe(1) expect(evaluateDragonTalonStats(6).kickCount).toBe(2) expect(evaluateDragonTalonStats(11).kickCount).toBe(2) expect(evaluateDragonTalonStats(12).kickCount).toBe(3) expect(evaluateDragonTalonStats(18).kickCount).toBe(4) expect(evaluateDragonTalonStats(24).kickCount).toBe(5) expect(evaluateDragonTalonStats(30).kickCount).toBe(6) }) it('evaluates Attack Rating progression: 20 + 35 * (slvl - 1)%', () => { expect(evaluateDragonTalonStats(1).attackRatingBonusPct).toBe(20) expect(evaluateDragonTalonStats(10).attackRatingBonusPct).toBe(335) expect(evaluateDragonTalonStats(20).attackRatingBonusPct).toBe(685) }) }) describe('4. Dragon Claw Dual-Claw Finisher', () => { it('enforces dual-claw equipment requirement', () => { expect(canCastDragonClaw(false, false)).toBe(false) expect(canCastDragonClaw(true, false)).toBe(false) expect(canCastDragonClaw(false, true)).toBe(false) expect(canCastDragonClaw(true, true)).toBe(true) }) it('evaluates 2-strike count, ED% and Claw Mastery synergy', () => { const s0 = evaluateDragonClawStats(20, 0) expect(s0.strikesCount).toBe(2) // 50 + 5 * 19 = 145% ED expect(s0.enhancedDamagePct).toBe(145) expect(s0.attackRatingBonusPct).toBe(515) // 40 + 25 * 19 = 515% // With Claw Mastery blvl 20: +80% ED (4% per blvl) const s20 = evaluateDragonClawStats(20, 20) expect(s20.enhancedDamagePct).toBe(145 + 80) }) }) describe('5. Dragon Tail Explosive Kick Finisher', () => { it('evaluates physical-to-fire conversion: 50 + 10 * (slvl - 1)%', () => { expect(evaluateDragonTailStats(1).fireExplosionPct).toBe(50) expect(evaluateDragonTailStats(10).fireExplosionPct).toBe(140) expect(evaluateDragonTailStats(20).fireExplosionPct).toBe(240) }) it('applies 6 subtiles (4 yards) explosion radius', () => { expect(evaluateDragonTailStats(20).radiusSubtiles).toBe(6) }) }) describe('6. Dragon Flight Teleport Kick Finisher', () => { it('evaluates teleport kick ED progression: 100 + 25 * (slvl - 1)% and NextDelay', () => { const df1 = evaluateDragonFlightStats(1) expect(df1.enhancedDamagePct).toBe(100) expect(df1.attackRatingBonusPct).toBe(60) expect(df1.nextDelayFrames).toBe(4) const df20 = evaluateDragonFlightStats(20) expect(df20.enhancedDamagePct).toBe(575) // 100 + 25 * 19 = 575% expect(df20.attackRatingBonusPct).toBe(535) // 60 + 25 * 19 = 535% expect(df20.nextDelayFrames).toBe(4) expect(df20.manaCost).toBe(15) }) }) describe( '7. Challenger 1 Runtime Execution & Differential Stress Suite (executeSkill113c / evaluateSkill113c)', { timeout: 30_000 }, () => { it('Dimension 1: Missed charge-up attacks (Tiger, Cobra, FoF, CoT, BoI, Phoenix) do NOT grant or advance charges', async () => { const { getSharedDataRegistry } = await import('../../../src/baker/mpq-archive-source.ts') const { FIXED_ONE, UnitStatList } = await import('../../../src/server/engine/stat-list.ts') const { StateBus } = await import('../../../src/server/engine/state-bus.ts') const { MissileEngine } = await import('../../../src/server/engine/missile-engine.ts') const { AuraScanner } = await import('../../../src/server/engine/aura-scanner.ts') const { SummonManager } = await import('../../../src/server/ai/summon-manager.ts') const { evaluateSkill113c, executeSkill113c } = await import('../../../src/server/skills/registry.ts') const registry = await getSharedDataRegistry() const chargeUpSkills = [ { id: 254, state: 'progressive_damage' }, { id: 259, state: 'progressive_fire' }, { id: 265, state: 'progressive_steal' }, { id: 269, state: 'progressive_lightning' }, { id: 274, state: 'progressive_cold' }, { id: 280, state: 'progressive_other' }, ] for (const { id, state } of chargeUpSkills) { const skill = registry.getSkillById(id)! const casterStats = new UnitStatList() casterStats.setBaseStat('level', 80) casterStats.setBaseStat('tohit', 1) // 5% minimum hit chance -> roll=25 misses! casterStats.setBaseSkillLevel(id, 20) const casterBus = new StateBus(casterStats, registry) const caster = { id: 'caster_ass', name: 'Assassin', statList: casterStats, stateBus: casterBus, weaponMinPhys: 50, weaponMaxPhys: 50, } const targetStats = new UnitStatList() targetStats.setBaseStat('level', 80) targetStats.setBaseStat('armorclass', 100000) targetStats.setBaseStat('maxhp', 100000 * FIXED_ONE) targetStats.setHp256(100000 * FIXED_ONE) const target = { id: 'target_mon', name: 'Monster', statList: targetStats, stateBus: new StateBus(targetStats, registry), } const evalResult = evaluateSkill113c({ registry, skill, slvl: 20, blvl: 20, statList: casterStats }) const missRes = executeSkill113c({ registry, skill, evalResult, caster, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 100, targetX: 10, targetY: 10, }) expect(missRes.totalDamageDealt).toBe(0) expect(casterBus.getState(state)).toBeUndefined() // Now give caster high AR so attacks connect: 1 -> 2 -> 3 -> 3 (capped at 3) and refresh 375-frame timer casterStats.setBaseStat('tohit', 100000) targetStats.setBaseStat('armorclass', 10) for (const [stepIdx, tick, expectedCharge] of [ [1, 100, 1], [2, 200, 2], [3, 300, 3], [4, 400, 3], ] as const) { executeSkill113c({ registry, skill, evalResult, caster, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: tick, targetX: 10, targetY: 10, }) const activeSt = casterBus.getState(state) expect(activeSt, `Skill ${id} step ${stepIdx}`).toBeDefined() expect(activeSt?.stats?.charge).toBe(expectedCharge) expect(activeSt?.expireFrame).toBe(tick + 375) } // Advance stateBus past 400 + 375 = 775 frames -> charge expires casterBus.tick(774) expect(casterBus.getState(state)).toBeDefined() casterBus.tick(775) expect(casterBus.getState(state)).toBeUndefined() } }) it('Dimension 2: Finishers on miss do NOT discharge charges; on first connecting hit, all charges discharge and reset to 0', async () => { const { getSharedDataRegistry } = await import('../../../src/baker/mpq-archive-source.ts') const { FIXED_ONE, UnitStatList } = await import('../../../src/server/engine/stat-list.ts') const { StateBus } = await import('../../../src/server/engine/state-bus.ts') const { MissileEngine } = await import('../../../src/server/engine/missile-engine.ts') const { AuraScanner } = await import('../../../src/server/engine/aura-scanner.ts') const { SummonManager } = await import('../../../src/server/ai/summon-manager.ts') const { evaluateSkill113c, executeSkill113c } = await import('../../../src/server/skills/registry.ts') const registry = await getSharedDataRegistry() const finisherIds = [255, 260, 270, 275, 0] for (const finId of finisherIds) { const skill = registry.getSkillById(finId)! const casterStats = new UnitStatList() casterStats.setBaseStat('level', 80) casterStats.setBaseStat('tohit', 1) // Force miss first casterStats.setHp256(500 * FIXED_ONE) casterStats.setMana256(500 * FIXED_ONE) if (finId > 0) casterStats.setBaseSkillLevel(finId, 12) const casterBus = new StateBus(casterStats, registry) // Pre-load Charge 3 Tiger Strike + Charge 3 Cobra Strike + Charge 2 Phoenix Strike casterBus.applyState({ stateNameOrId: 'progressive_damage', sourceUnitId: 'caster', sourceSkillId: 254, slvl: 20, durationFrames: 375, currentFrame: 10, stats: { charge: 3, progressive_charges: 3 }, }) casterBus.applyState({ stateNameOrId: 'progressive_steal', sourceUnitId: 'caster', sourceSkillId: 265, slvl: 20, durationFrames: 375, currentFrame: 10, stats: { charge: 3, progressive_charges: 3 }, }) casterBus.applyState({ stateNameOrId: 'progressive_other', sourceUnitId: 'caster', sourceSkillId: 280, slvl: 20, durationFrames: 375, currentFrame: 10, stats: { charge: 2, progressive_charges: 2 }, }) const caster = { id: 'caster', name: 'Assassin', statList: casterStats, stateBus: casterBus, isDualClaw: true, weaponMinPhys: 100, weaponMaxPhys: 100, } const targetStats = new UnitStatList() targetStats.setBaseStat('level', 80) targetStats.setBaseStat('armorclass', 100000) targetStats.setBaseStat('maxhp', 50000 * FIXED_ONE) targetStats.setHp256(50000 * FIXED_ONE) const target = { id: 'target', name: 'Target', statList: targetStats, stateBus: new StateBus(targetStats, registry), } const evalResult = evaluateSkill113c({ registry, skill, slvl: 12, blvl: 12, statList: casterStats }) const missOut = executeSkill113c({ registry, skill, evalResult, caster, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 20, targetX: 10, targetY: 10, }) // Missed finisher must NOT discharge any charge! expect(missOut.notes).not.toContain('finisher_discharged') expect(casterBus.getState('progressive_damage')?.stats?.charge).toBe(3) expect(casterBus.getState('progressive_steal')?.stats?.charge).toBe(3) expect(casterBus.getState('progressive_other')?.stats?.charge).toBe(2) // Now force hit: all charges discharge on first connecting strike and reset to 0 casterStats.setBaseStat('tohit', 100000) targetStats.setBaseStat('armorclass', 10) const hpBefore = casterStats.getHp256() const manaBefore = casterStats.getMana256() const hitOut = executeSkill113c({ registry, skill, evalResult, caster, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 30, targetX: 10, targetY: 10, }) expect(hitOut.notes).toContain('finisher_discharged') expect(hitOut.totalDamageDealt).toBeGreaterThan(1000) // Base + 1440% Tiger Strike + Phoenix CL expect(casterStats.getHp256()).toBeGreaterThan(hpBefore) // Cobra Strike life leech applied expect(casterStats.getMana256()).toBeGreaterThan(manaBefore) // Cobra Strike mana leech applied expect(casterBus.getState('progressive_damage')).toBeUndefined() expect(casterBus.getState('progressive_steal')).toBeUndefined() expect(casterBus.getState('progressive_other')).toBeUndefined() } }) it('Dimension 2b: FoF, CoT, and BoI fire cumulative payloads (prgstack=1) while Phoenix Strike fires strictly single payload (prgstack=0)', () => { // FoF Tier 1, 2, 3 cumulative properties const fof1 = resolveFinisherDischarge([{ skillId: 259, count: 1, slvl: 20, expiresAtFrame: 500 }])[0]! const fof2 = resolveFinisherDischarge([{ skillId: 259, count: 2, slvl: 20, expiresAtFrame: 500 }])[0]! const fof3 = resolveFinisherDischarge([{ skillId: 259, count: 3, slvl: 20, expiresAtFrame: 500 }])[0]! expect(fof1.fireDamage?.explosionRadiusPx).toBeUndefined() expect(fof2.fireDamage?.explosionRadiusPx).toBe(64) expect(fof2.fireDamage?.burningGroundFrames).toBeUndefined() expect(fof3.fireDamage?.explosionRadiusPx).toBe(64) expect(fof3.fireDamage?.burningGroundFrames).toBe(60) // BoI Tier 1, 2, 3 cumulative properties (1.13c Missiles.txt bladesoficecubes: 100 base + 10/lvl = 290 frames at slvl 20) const boi1 = resolveFinisherDischarge([{ skillId: 274, count: 1, slvl: 20, expiresAtFrame: 500 }])[0]! const boi2 = resolveFinisherDischarge([{ skillId: 274, count: 2, slvl: 20, expiresAtFrame: 500 }])[0]! const boi3 = resolveFinisherDischarge([{ skillId: 274, count: 3, slvl: 20, expiresAtFrame: 500 }])[0]! expect(boi1.coldDamage?.explosionRadiusPx).toBeUndefined() expect(boi2.coldDamage?.explosionRadiusPx).toBe(64) expect(boi2.coldDamage?.freezeDurationFrames).toBeUndefined() expect(boi3.coldDamage?.explosionRadiusPx).toBe(64) expect(boi3.coldDamage?.freezeDurationFrames).toBe(290) }) it('Dimension 3: Dragon Claw runtime rejects non-dual-claw caster, executes 2 strikes when dual-claw, and does not double-count Claw Mastery synergy', async () => { const { getSharedDataRegistry } = await import('../../../src/baker/mpq-archive-source.ts') const { FIXED_ONE, UnitStatList } = await import('../../../src/server/engine/stat-list.ts') const { StateBus } = await import('../../../src/server/engine/state-bus.ts') const { MissileEngine } = await import('../../../src/server/engine/missile-engine.ts') const { AuraScanner } = await import('../../../src/server/engine/aura-scanner.ts') const { SummonManager } = await import('../../../src/server/ai/summon-manager.ts') const { evaluateSkill113c, executeSkill113c } = await import('../../../src/server/skills/registry.ts') const registry = await getSharedDataRegistry() const skill = registry.getSkillById(260)! const casterStats = new UnitStatList() casterStats.setBaseStat('level', 80) casterStats.setBaseStat('tohit', 100000) casterStats.setBaseSkillLevel(260, 20) const targetStats = new UnitStatList() targetStats.setBaseStat('level', 80) targetStats.setBaseStat('armorclass', 10) targetStats.setBaseStat('maxhp', 10000 * FIXED_ONE) targetStats.setHp256(10000 * FIXED_ONE) const target = { id: 'target', name: 'Target', statList: targetStats, stateBus: new StateBus(targetStats, registry), } const evalResult = evaluateSkill113c({ registry, skill, slvl: 20, blvl: 20, statList: casterStats }) // Rejects when isDualClaw === false const failRes = executeSkill113c({ registry, skill, evalResult, caster: { id: 'caster', name: 'Assassin', statList: casterStats, stateBus: new StateBus(casterStats, registry), isDualClaw: false, weaponMinPhys: 100, weaponMaxPhys: 100, }, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 10, targetX: 0, targetY: 0, }) expect(failRes.executed).toBe(false) expect(failRes.error).toBe('requires_dual_claws') expect(failRes.totalDamageDealt).toBe(0) // Succeeds and lands 2 strikes when isDualClaw === true const okRes = executeSkill113c({ registry, skill, evalResult, caster: { id: 'caster', name: 'Assassin', statList: casterStats, stateBus: new StateBus(casterStats, registry), isDualClaw: true, weaponMinPhys: 100, weaponMaxPhys: 100, }, targets: [target], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 10, targetX: 0, targetY: 0, }) expect(okRes.executed).toBe(true) // Each strike deals floor(100 * (100 + 145) / 100) = 245 -> 2 strikes = 490 expect(okRes.totalDamageDealt).toBe(490) // With Claw Mastery (252) blvl = 20 (+80% ED): total physDamagePct must be 145 + 80 = 225% (NOT 305% double-counted!) casterStats.setBaseSkillLevel(252, 20) const evalWithSyn = evaluateSkill113c({ registry, skill, slvl: 20, blvl: 20, statList: casterStats }) expect(evalWithSyn.synergyBonusPct).toBe(80) expect(evalWithSyn.physDamagePct).toBe(225) }) it('Dimension 4: Dragon Tail deals 0 Fire AoE explosion damage when primary target is 100% Physical Immune', async () => { const { getSharedDataRegistry } = await import('../../../src/baker/mpq-archive-source.ts') const { FIXED_ONE, UnitStatList } = await import('../../../src/server/engine/stat-list.ts') const { StateBus } = await import('../../../src/server/engine/state-bus.ts') const { MissileEngine } = await import('../../../src/server/engine/missile-engine.ts') const { AuraScanner } = await import('../../../src/server/engine/aura-scanner.ts') const { SummonManager } = await import('../../../src/server/ai/summon-manager.ts') const { evaluateSkill113c, executeSkill113c } = await import('../../../src/server/skills/registry.ts') const registry = await getSharedDataRegistry() const skill = registry.getSkillById(270)! const casterStats = new UnitStatList() casterStats.setBaseStat('level', 80) casterStats.setBaseStat('tohit', 100000) casterStats.setBaseSkillLevel(270, 20) const caster = { id: 'caster', name: 'Assassin', statList: casterStats, stateBus: new StateBus(casterStats, registry), weaponMinPhys: 100, weaponMaxPhys: 100, } // Primary target: 100% Physical Immune (damageresist = 100) const immunePrimaryStats = new UnitStatList() immunePrimaryStats.setBaseStat('level', 80) immunePrimaryStats.setBaseStat('armorclass', 10) immunePrimaryStats.setBaseStat('damageresist', 100) immunePrimaryStats.setBaseStat('maxhp', 10000 * FIXED_ONE) immunePrimaryStats.setHp256(10000 * FIXED_ONE) const immunePrimary = { id: 'primary_immune', name: 'Ghost', statList: immunePrimaryStats, stateBus: new StateBus(immunePrimaryStats, registry), } // Secondary target right next to primary target (0% fire resist) const bystanderStats = new UnitStatList() bystanderStats.setBaseStat('level', 80) bystanderStats.setBaseStat('armorclass', 10) bystanderStats.setBaseStat('maxhp', 10000 * FIXED_ONE) bystanderStats.setHp256(10000 * FIXED_ONE) const bystander = { id: 'bystander', name: 'Fallen', statList: bystanderStats, stateBus: new StateBus(bystanderStats, registry), } const evalResult = evaluateSkill113c({ registry, skill, slvl: 20, blvl: 20, statList: casterStats }) const targetPositions = new Map([ ['primary_immune', { x: 0, y: 0 }], ['bystander', { x: 16, y: 0 }], // 1 subtile away (< 6 subtiles radius) ]) const immuneRes = executeSkill113c({ registry, skill, evalResult, caster, targets: [immunePrimary, bystander], targetPositions, corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 10, targetX: 0, targetY: 0, }) // Because primary target took 0 physical damage, 0 fire explosion damage is dealt to bystander! expect(immuneRes.totalDamageDealt).toBe(0) expect(bystanderStats.getHp256()).toBe(10000 * FIXED_ONE) // Now set primary target damageresist to 0: primary takes 100 phys, bystander takes floor(100 * 240 / 100) = 240 fire immunePrimaryStats.setBaseStat('damageresist', 0) const normalRes = executeSkill113c({ registry, skill, evalResult, caster, targets: [immunePrimary, bystander], targetPositions, corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 20, targetX: 0, targetY: 0, }) expect(normalRes.totalDamageDealt).toBe(100 + 240) expect(bystanderStats.getHp256()).toBe((10000 - 240) * FIXED_ONE) }) it('Dimension 5 & 6: Dragon Flight teleports and kicks with +575% ED at slvl 20; single-target melee skills have radiusSubtiles === 0', async () => { const { getSharedDataRegistry } = await import('../../../src/baker/mpq-archive-source.ts') const { FIXED_ONE, UnitStatList } = await import('../../../src/server/engine/stat-list.ts') const { StateBus } = await import('../../../src/server/engine/state-bus.ts') const { MissileEngine } = await import('../../../src/server/engine/missile-engine.ts') const { AuraScanner } = await import('../../../src/server/engine/aura-scanner.ts') const { SummonManager } = await import('../../../src/server/ai/summon-manager.ts') const { evaluateSkill113c, executeSkill113c } = await import('../../../src/server/skills/registry.ts') const registry = await getSharedDataRegistry() // Dimension 6: Single-target melee skills (Tiger Strike 254, Dragon Talon 255, Dragon Claw 260, Cobra Strike 265) have radius = 0 for (const id of [254, 255, 260, 265]) { const skill = registry.getSkillById(id)! const stats = new UnitStatList() stats.setBaseSkillLevel(id, 20) const ev = evaluateSkill113c({ registry, skill, slvl: 20, blvl: 20, statList: stats }) expect(ev.radiusSubtiles, `Skill ${id} (${skill.name}) radiusSubtiles must be 0`).toBe(0) } // Dimension 5: Dragon Flight (275) teleports to target and deals kick damage with 100 + 25*(slvl-1) = +575% ED at slvl 20 const dfSkill = registry.getSkillById(275)! const casterStats = new UnitStatList() casterStats.setBaseStat('level', 80) casterStats.setBaseStat('tohit', 100000) casterStats.setBaseSkillLevel(275, 20) const targetStats = new UnitStatList() targetStats.setBaseStat('level', 80) targetStats.setBaseStat('armorclass', 10) targetStats.setBaseStat('maxhp', 10000 * FIXED_ONE) targetStats.setHp256(10000 * FIXED_ONE) const dfEval = evaluateSkill113c({ registry, skill: dfSkill, slvl: 20, blvl: 20, statList: casterStats }) const dfOut = executeSkill113c({ registry, skill: dfSkill, evalResult: dfEval, caster: { id: 'caster', name: 'Assassin', statList: casterStats, stateBus: new StateBus(casterStats, registry), weaponMinPhys: 100, weaponMaxPhys: 100, }, targets: [{ id: 'target', name: 'Target', statList: targetStats, stateBus: new StateBus(targetStats, registry), }], targetPositions: new Map(), corpses: [], missileEngine: new MissileEngine(registry), auraScanner: new AuraScanner(registry), summonManager: new SummonManager(registry), currentTick: 10, targetX: 144, targetY: 288, }) expect(dfOut.executed).toBe(true) expect(dfOut.notes).toContain('teleported:144,288') expect(dfOut.totalDamageDealt).toBe(675) // 100 * (100 + 575) / 100 }) it('Differential Fuzzing (1000 random states): ChargeOrbManager & resolveFinisherDischarge vs independent 1.13c oracle', () => { let seed = 0x1337beef const nextRand = (max: number) => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0 return seed % max } for (let iter = 0; iter < 1000; iter++) { const slvl = 1 + nextRand(60) // slvls 1..60 const dt = evaluateDragonTalonStats(slvl) expect(dt.kickCount).toBe(1 + Math.floor(slvl / 6)) expect(dt.attackRatingBonusPct).toBe(20 + 35 * (slvl - 1)) const dtail = evaluateDragonTailStats(slvl) expect(dtail.fireExplosionPct).toBe(50 + 10 * (slvl - 1)) expect(dtail.radiusSubtiles).toBe(6) const df = evaluateDragonFlightStats(slvl) expect(df.enhancedDamagePct).toBe(100 + 25 * (slvl - 1)) expect(df.nextDelayFrames).toBe(4) const tier = (1 + nextRand(3)) as 1 | 2 | 3 const tigerDischarge = resolveFinisherDischarge([ { skillId: 254, count: tier, slvl, expiresAtFrame: 1000 }, ])[0]! const expectedTigerEd = (100 + 20 * (slvl - 1)) * tier expect(tigerDischarge.enhancedDamagePct).toBe(expectedTigerEd) const psDischarge = resolveFinisherDischarge([ { skillId: 280, count: tier, slvl, expiresAtFrame: 1000 }, ])[0]! expect(psDischarge.phoenixPayload?.tier).toBe(tier) expect(psDischarge.phoenixPayload?.type).toBe( tier === 1 ? 'meteor' : tier === 2 ? 'chain_lightning' : 'chaos_ice', ) } }) }) })