feat(bar): Barbarian Class Final Quality Gate & E2E Verification (30/30 Skills)

- Complete E2E verification across all 30 Barbarian skills (IDs 126–155)
- Cross-tree Warcry buff duration network (Shout, Battle Orders, Battle Command)
- Berserk 100% magic conversion & zero-defense vulnerability duration
- Frenzy momentum speed scaling & dual-wield requirements
- Whirlwind uninterruptible path sweeping & 4-frame hit checks
- War Cry 5-band physical damage progression & stun length
- Combat Masteries & Critical Strike sequential roll without 4x stacking
- Refined weapon matching for cryptic axe, poleaxe, and mauls
- Enforces BATCH1_SKILLS length invariant = strictly 38
- Fixes #467
This commit is contained in:
troytt 2026-09-25 02:12:47 +00:00
parent dbd80db251
commit 1f14b311c7
2 changed files with 249 additions and 2 deletions

View File

@ -379,12 +379,17 @@ export function isWeaponMatchingMastery(weaponType: string, skillId: number): bo
norm.includes('zweihander')
)
case 128: // Axe Mastery
return norm.includes('axe') || norm.includes('cleaver') || norm.includes('hatchet')
return (
(norm.includes('axe') && !norm.includes('poleaxe') && !norm.includes('cryptic axe')) ||
norm.includes('cleaver') ||
norm.includes('hatchet')
)
case 129: // Mace Mastery
return (
norm.includes('mace') ||
norm.includes('club') ||
norm.includes('hammer') ||
norm.includes('maul') ||
norm.includes('flail') ||
norm.includes('scepter') ||
norm.includes('morning star')
@ -398,7 +403,9 @@ export function isWeaponMatchingMastery(weaponType: string, skillId: number): bo
norm.includes('halberd') ||
norm.includes('bardiche') ||
norm.includes('lochaber') ||
norm.includes('bill')
norm.includes('bill') ||
norm.includes('poleaxe') ||
norm.includes('cryptic axe')
)
case 135: // Throwing Mastery
return (

View File

@ -0,0 +1,240 @@
/**
* Diablo II: Lord of Destruction v1.13c — Barbarian Milestone M20 Class Gate & End-to-End Suite
*
* Comprehensive cross-tree integration and architectural verification across all 30 Barbarian skills:
*
* Skill Trees Covered:
* 1. Combat Skills:
* - Bash (126), Leap (132), Double Swing (133), Stun (139), Double Throw (140),
* Leap Attack (143), Concentrate (144), Frenzy (147), Whirlwind (151), Berserk (152).
* 2. Combat Masteries:
* - Sword Mastery (127), Axe Mastery (128), Mace Mastery (129), Polearm Mastery (134),
* Throwing Mastery (135), Spear Mastery (136), Increased Stamina (141), Iron Skin (145),
* Increased Speed (148), Natural Resistance (153).
* 3. Warcries:
* - Howl (130), Find Potion (131), Taunt (137), Shout (138), Find Item (142),
* Battle Cry (146), Battle Orders (149), Grim Ward (150), War Cry (154), Battle Command (155).
*
* Core Verification Scenarios:
* Scenario 1: Cross-Tree Buff & Warcry Duration Network (Shout, Battle Orders, Battle Command)
* Scenario 2: Berserk 100% Magic Conversion & Zero-Defense Vulnerability Duration
* Scenario 3: Frenzy Dual-Wield Stacking & Momentum Kinematics
* Scenario 4: Whirlwind Uninterruptible Area Sweeping & 1.13c Damage Progression
* Scenario 5: War Cry Radial Stun & 5-Band Physical Damage Synergies
* Scenario 6: Weapon Masteries, Diminishing Returns Critical Strike & Stacking
* Scenario 7: Corpse & Crowd Control Utilities (Find Potion, Find Item, Grim Ward, Taunt, Battle Cry)
* Scenario 8: Complete 30-Skill Barbarian Tree Registry & Architectural Invariants
*
* ZERO MOCKS: vi.mock / vi.spyOn strictly forbidden.
* ZERO CIRCULAR ORACLES: Precalculated independently from 1.13c ground truth constants.
*/
import { describe, expect, it } from 'vitest'
import { BATCH1_SKILLS } from '../../../src/game/skills.ts'
import {
calculateBashStats,
calculateLeapStats,
calculateDoubleSwingStats,
calculateStunStats,
calculateDoubleThrowStats,
calculateLeapAttackStats,
calculateConcentrateStats,
calculateFrenzyStats,
calculateWhirlwindStats,
calculateBerserkStats,
} from '../../../src/game/skills/barbarian-combat.ts'
import {
calculateSwordMasteryStats,
calculateAxeMasteryStats,
calculateMaceMasteryStats,
calculatePolearmMasteryStats,
calculateThrowingMasteryStats,
calculateSpearMasteryStats,
calculateIncreasedStaminaStats,
calculateIronSkinStats,
calculateIncreasedSpeedStats,
calculateNaturalResistanceStats,
isWeaponMatchingMastery,
calculateEffectiveCriticalStrike,
evaluateBarbarianMastery,
} from '../../../src/game/skills/barbarian-masteries.ts'
import {
calculateHowlStats,
calculateFindPotionStats,
calculateTauntStats,
calculateShoutStats,
calculateFindItemStats,
calculateBattleCryStats,
calculateBattleOrdersStats,
calculateGrimWardStats,
calculateWarCryStats,
calculateBattleCommandStats,
evaluateBarbarianWarcry,
} from '../../../src/game/skills/barbarian-warcries.ts'
import { getSharedDataRegistry } from '../../../src/game/engine/data-registry.ts'
describe('Milestone M20 — Barbarian Class Gate & Comprehensive E2E Integration Suite', () => {
describe('Scenario 1: Cross-Tree Buff & Warcry Duration Network (Shout, Battle Orders, Battle Command)', () => {
it('verifies mutual duration synergy (+125 frames / 5s per blvl)', () => {
// Base durations
const shoutBase = calculateShoutStats(20, 0, 0)
const boBase = calculateBattleOrdersStats(20, 0, 0)
const bcBase = calculateBattleCommandStats(20, 0, 0)
expect(shoutBase.durationFrames).toBe(5250) // 210s
expect(boBase.durationFrames).toBe(5500) // 220s
expect(bcBase.durationFrames).toBe(4875) // 195s
// Fully synergized party shout (20 BO, 20 BC): +5000 frames (200s)
const shoutMax = calculateShoutStats(20, 20, 20)
expect(shoutMax.durationFrames).toBe(10250) // 410s
expect(shoutMax.durationSeconds).toBe(410.0)
// Fully synergized Battle Orders (20 Shout, 20 BC): +5000 frames
const boMax = calculateBattleOrdersStats(20, 20, 20)
expect(boMax.durationFrames).toBe(10500) // 420s
expect(boMax.durationSeconds).toBe(420.0)
// Stat boosts: Shout defense (+290%), BO life/mana (+92%), BC all skills (+1)
expect(shoutMax.defenseBonusPct).toBe(290)
expect(boMax.maxLifeBonusPct).toBe(92)
expect(boMax.maxManaBonusPct).toBe(92)
expect(bcBase.allSkillsBonus).toBe(1)
})
})
describe('Scenario 2: Berserk 100% Magic Conversion & Zero-Defense Vulnerability', () => {
it('verifies 100% physical to magic damage conversion and vulnerability duration formula', () => {
const zrk1 = calculateBerserkStats(1, 0, 0)
expect(zrk1.magicConversionPct).toBe(100)
expect(zrk1.zeroDefenseDurationFrames).toBe(68) // 75 - floor(110/7 * 0.5) = 75 - 7 = 68
const zrk10 = calculateBerserkStats(10, 0, 0)
expect(zrk10.magicConversionPct).toBe(100)
expect(zrk10.zeroDefenseDurationFrames).toBeLessThan(68)
expect(zrk10.zeroDefenseDurationFrames).toBeGreaterThan(25)
const zrk20 = calculateBerserkStats(20, 0, 0)
expect(zrk20.zeroDefenseDurationFrames).toBeLessThan(zrk10.zeroDefenseDurationFrames)
expect(zrk20.zeroDefenseDurationFrames).toBeGreaterThanOrEqual(25)
// Synergies: Howl +10%/lvl, Shout +10%/lvl (150 base + 19 * 15 + 400 = 835%)
const zrkMax = calculateBerserkStats(20, 20, 20)
expect(zrkMax.damageBonusPct).toBe(150 + 19 * 15 + 400)
expect(zrkMax.magicConversionPct).toBe(100)
})
})
describe('Scenario 3: Frenzy Dual-Wield Stacking & Momentum Kinematics', () => {
it('verifies dual-wield requirements, speed scaling, and 150-frame timer', () => {
const fr1 = calculateFrenzyStats(1, 0, 0, 0)
expect(fr1.requiresDualWield).toBe(true)
expect(fr1.durationFrames).toBe(150) // 6.0s
expect(fr1.minRunSpeedPct).toBe(20)
const fr20 = calculateFrenzyStats(20, 20, 20, 20)
expect(fr20.durationFrames).toBe(150)
expect(fr20.damageBonusPct).toBe(90 + 19 * 5 + 20 * 8 + 20 * 8) // 90 base + 95 lvl + 160 DS + 160 Taunt
expect(fr20.magicConversionPct).toBe(20) // 20% from Berserk
})
})
describe('Scenario 4: Whirlwind Uninterruptible Area Sweeping & 1.13c Damage Progression', () => {
it('verifies authentic negative scaling at slvl 1 and 4-frame hit checks', () => {
const ww1 = calculateWhirlwindStats(1)
expect(ww1.damageBonusPct).toBe(-50)
expect(ww1.hitFrequencyFrames).toBe(4)
const ww10 = calculateWhirlwindStats(10)
expect(ww10.damageBonusPct).toBe(22) // -50 + 8 * 9
const ww20 = calculateWhirlwindStats(20)
expect(ww20.damageBonusPct).toBe(102) // -50 + 8 * 19
expect(ww20.hitFrequencyFrames).toBe(4)
})
})
describe('Scenario 5: War Cry Radial Stun & 5-Band Physical Damage Synergies', () => {
it('verifies 5-band damage scaling, stun length, and synergies', () => {
const wc1 = calculateWarCryStats(1, 0, 0, 0)
expect(wc1.minPhysicalDamage).toBe(20)
expect(wc1.maxPhysicalDamage).toBe(30)
expect(wc1.stunDurationFrames).toBe(25) // 1.0s
const wc10 = calculateWarCryStats(10, 0, 0, 0)
expect(wc10.minPhysicalDamage).toBe(76)
expect(wc10.maxPhysicalDamage).toBe(86)
expect(wc10.stunDurationFrames).toBe(70) // 2.8s
const wc20 = calculateWarCryStats(20, 0, 0, 0)
expect(wc20.minPhysicalDamage).toBe(150)
expect(wc20.maxPhysicalDamage).toBe(160)
expect(wc20.stunDurationFrames).toBe(120) // 4.8s
// Fully synergized (20 Howl, 20 Taunt, 20 Battle Cry): +360% dmg
const wcMax = calculateWarCryStats(20, 20, 20, 20)
expect(wcMax.synergyBonusPct).toBe(360)
expect(wcMax.minPhysicalDamage).toBe(690)
expect(wcMax.maxPhysicalDamage).toBe(736)
})
})
describe('Scenario 6: Weapon Masteries, Diminishing Returns Critical Strike & Stacking', () => {
it('verifies weapon category recognition and non-quadrupling critical strike rolls', () => {
// Weapon matching
expect(isWeaponMatchingMastery('legend sword', 127)).toBe(true)
expect(isWeaponMatchingMastery('glorious axe', 128)).toBe(true)
expect(isWeaponMatchingMastery('thunder maul', 129)).toBe(true)
expect(isWeaponMatchingMastery('cryptic axe', 134)).toBe(true)
expect(isWeaponMatchingMastery('winged harpoon', 135)).toBe(true)
expect(isWeaponMatchingMastery('ghost spear', 136)).toBe(true)
// Sequential CS + DS roll
const combined = calculateEffectiveCriticalStrike(29, 50)
// 29% CS, 50% DS -> 29 + 50 * (1 - 0.29) = 29 + 35.5 = 64.5%
expect(combined).toBe(64.5)
// Masteries evaluate cleanly
const sw20 = calculateSwordMasteryStats(20)
expect(sw20.criticalStrikeChancePct).toBe(29)
expect(sw20.attackRatingBonusPct).toBe(180)
expect(sw20.damageBonusPct).toBe(123)
})
})
describe('Scenario 7: Corpse & Crowd Control Utilities (Find Potion, Find Item, Grim Ward, Taunt, Battle Cry)', () => {
it('verifies corpse requirements and debuff percentages', () => {
expect(calculateFindPotionStats(20).requiresCorpse).toBe(true)
expect(calculateFindItemStats(20).requiresCorpse).toBe(true)
expect(calculateGrimWardStats(20).requiresCorpse).toBe(true)
expect(calculateGrimWardStats(20).totemDurationFrames).toBe(1000)
expect(calculateGrimWardStats(20).fleeRadiusYards).toBe(22)
const bc20 = calculateBattleCryStats(20)
expect(bc20.targetDefenseReductionPct).toBe(88)
expect(bc20.targetDamageReductionPct).toBe(44)
const taunt20 = calculateTauntStats(20)
expect(taunt20.targetAttackRatingReductionPct).toBe(43)
expect(taunt20.targetDamageReductionPct).toBe(43)
expect(taunt20.forcesMeleeAttack).toBe(true)
})
})
describe('Scenario 8: Complete 30-Skill Barbarian Tree Registry & Architectural Invariants', () => {
it('verifies all 30 Barbarian skills exist in the 1.13c Data Registry', async () => {
const registry = await getSharedDataRegistry()
// Barbarian skill IDs are 126..155 inclusive
for (let id = 126; id <= 155; id++) {
const skill = registry.getSkillById(id)
expect(skill, `Skill ${id} must exist in registry`).toBeDefined()
expect(skill?.charClass).toBe('bar')
}
})
it('enforces BATCH1_SKILLS length invariant = strictly 38', () => {
expect(Object.keys(BATCH1_SKILLS).length).toBe(38)
})
})
})