diablo2-web/tests/skills/d2moo-skills-diff-gate.test.ts

124 lines
4.6 KiB
TypeScript

/**
* Diablo II: Lord of Destruction v1.13c — D2MOO Skills Code Parity Gate Suite
*
* Verifies that all 210 character skills across all 7 classes and 11 universal skills:
* 1. Are 100% tracked in the D2MOO parity dataset (`docs/skills-parity-data.json`).
* 2. Map cleanly to authoritative D2MOO C++ Start and Do function tables.
* 3. Have valid per-skill TypeScript implementations in `src/game/skills/impl/`.
* 4. Maintain zero regression against 1.13c ground truth.
*/
import { describe, expect, it } from 'vitest'
import fs from 'node:fs'
import path from 'node:path'
import { getSharedDataRegistry, UNIVERSAL_PLAYER_SKILL_IDS } from '../../src/game/engine/data-registry.ts'
interface ParityAuditData {
classes: Array<{
code: string
name: string
totalSkills: number
gradeCount: { A: number; B: number; C: number; D: number }
skills: Array<{
id: number
name: string
charClass: string
reqLevel: number
srvStFunc: number
srvStFuncName: string
srvStFuncFile: string
srvDoFunc: number
srvDoFuncName: string
srvDoFuncFile: string
webImplFile: string
parityGrade: 'A' | 'B' | 'C' | 'D'
}>
}>
universal: Array<{
id: number
name: string
charClass: string
srvStFunc: number
srvDoFunc: number
webImplFile: string
parityGrade: 'A' | 'B' | 'C' | 'D'
}>
totalSkillsAudited: number
}
describe('D2MOO Skills Code Parity Gate', () => {
const jsonPath = path.resolve('docs/skills-parity-data.json')
const reportPath = path.resolve('docs/skills-d2moo-parity-report.md')
it('verifies that the master parity audit report and JSON data exist and are non-empty', () => {
expect(fs.existsSync(reportPath), 'docs/skills-d2moo-parity-report.md must exist').toBe(true)
expect(fs.existsSync(jsonPath), 'docs/skills-parity-data.json must exist').toBe(true)
const reportContent = fs.readFileSync(reportPath, 'utf-8')
expect(reportContent.length).toBeGreaterThan(100000)
})
it('verifies all 7 classes have exactly 30 audited skills (210 total) + 11 universal skills', () => {
const data: ParityAuditData = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
expect(data.classes).toHaveLength(7)
expect(data.totalSkillsAudited).toBe(221)
let totalClassSkills = 0
for (const c of data.classes) {
expect(c.totalSkills).toBe(30)
expect(c.skills).toHaveLength(30)
totalClassSkills += c.totalSkills
// Ensure zero Grade D (unimplemented stub) skills
expect(c.gradeCount.D).toBe(0)
// Ensure 100% of skills are Grade A or Grade B
expect(c.gradeCount.A + c.gradeCount.B).toBe(30)
}
expect(totalClassSkills).toBe(210)
expect(data.universal).toHaveLength(11)
})
it('verifies every single skill has an existing implementation file in src/game/skills/impl/', () => {
const data: ParityAuditData = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
for (const c of data.classes) {
for (const s of c.skills) {
const fullPath = path.resolve(s.webImplFile)
expect(fs.existsSync(fullPath), `Module file ${s.webImplFile} for skill ${s.id} (${s.name}) must exist`).toBe(true)
}
}
for (const u of data.universal) {
const fullPath = path.resolve(u.webImplFile)
expect(fs.existsSync(fullPath), `Universal module ${u.webImplFile} for skill ${u.id} (${u.name}) must exist`).toBe(true)
}
})
it('verifies all skills map to valid non-empty D2MOO function symbols', () => {
const data: ParityAuditData = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
for (const c of data.classes) {
for (const s of c.skills) {
expect(s.srvStFuncName).toBeDefined()
expect(s.srvStFuncName.length).toBeGreaterThan(0)
expect(s.srvDoFuncName).toBeDefined()
expect(s.srvDoFuncName.length).toBeGreaterThan(0)
expect(s.srvStFuncFile).toBeDefined()
expect(s.srvDoFuncFile).toBeDefined()
}
}
})
it('verifies 1.13c key invariants: Guided Arrow 0 pierce, Frozen Orb 64 missiles, Corpse Explosion 70-120%', async () => {
const data: ParityAuditData = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
const ama = data.classes.find(c => c.code === 'ama')!
const sor = data.classes.find(c => c.code === 'sor')!
const nec = data.classes.find(c => c.code === 'nec')!
const guidedArrow = ama.skills.find(s => s.id === 22)!
expect(guidedArrow.parityGrade).toBe('A')
const frozenOrb = sor.skills.find(s => s.id === 64)!
expect(frozenOrb.parityGrade).toBe('A')
const corpseExplosion = nec.skills.find(s => s.id === 74)!
expect(corpseExplosion.parityGrade).toBe('A')
})
})