diablo2-web/tests/skills/ama/adv-amazon-assets-audit.tes...

374 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest'
import fs from 'node:fs'
import path from 'node:path'
import zlib from 'node:zlib'
import {
MISSILE_METAS,
MAGICARROW_META,
FIREARROW_META,
ICEARROW_META,
ARROW_META,
LIGHTNINGJAVELIN_META,
BOLT_META,
COLDARROW_META,
EXPLODINGARROW_META,
EXPLODINGARROWEXP_META,
ICEARROWEXPLODE_META,
FREEZINGARROW_META,
GUIDEDARROW_META,
STRAFEARROW_META,
STRAFEBOLT_META,
IMMOLATIONARROW_META,
IMMOLATIONARROWEXP_META,
IMMOLATIONFIRE_META,
POISONJAV_META,
POISONJAVCLOUD_META,
CHARGEDSTRIKEBOLT_META,
PLAGUEJAVELIN_META,
PLAGUEJAVCLOUD_META,
LIGHTNINGFURY_META,
FURYLIGHTNING_META,
type MissileMeta,
} from '../../../src/render/missiles-meta.ts'
import {
OVERLAY_METAS,
ALL_OVERLAY_NAMES,
CAST_INNERSIGHT_OVERLAY_META,
INNERSIGHT_OVERLAY_META,
CAST_SLOWMISSILES_OVERLAY_META,
HANDOFATHENA_OVERLAY_META,
VALKYRIESTART_OVERLAY_META,
VALKYRIE_OVERLAY_META,
DOPLLEZON_APPEAR_OVERLAY_META,
DOUBLEDAMAGE1_OVERLAY_META,
DOUBLEDAMAGE2_OVERLAY_META,
DOUBLEDAMAGE3_OVERLAY_META,
type OverlayMeta,
} from '../../../src/render/overlays-meta.ts'
import { BATCH1_SKILLS, ISO_GROUND_ASPECT_RATIO } from '../../../src/game/skills.ts'
// Strict PNG RGBA Decoder
function decodeRgbaPng(buf: Buffer) {
const width = buf.readUInt32BE(16)
const height = buf.readUInt32BE(20)
const bitDepth = buf[24]!
const colorType = buf[25]!
const idatParts: Buffer[] = []
let offset = 8
while (offset < buf.length) {
const len = buf.readUInt32BE(offset)
const type = buf.subarray(offset + 4, offset + 8).toString('ascii')
if (type === 'IDAT') {
idatParts.push(buf.subarray(offset + 8, offset + 8 + len))
}
offset += 12 + len
}
const idat = Buffer.concat(idatParts)
const raw = zlib.inflateSync(idat)
const bpp = 4
const rowBytes = width * bpp
const pixels = new Uint8Array(width * height * 4)
let srcAt = 0
let dstAt = 0
for (let y = 0; y < height; y++) {
const filter = raw[srcAt++]
const row = raw.subarray(srcAt, srcAt + rowBytes)
srcAt += rowBytes
const prevRow = y > 0 ? pixels.subarray((y - 1) * rowBytes, y * rowBytes) : null
const curRow = pixels.subarray(dstAt, dstAt + rowBytes)
for (let x = 0; x < rowBytes; x++) {
const left = x >= bpp ? curRow[x - bpp]! : 0
const up = prevRow ? prevRow[x]! : 0
const upLeft = prevRow && x >= bpp ? prevRow[x - bpp]! : 0
let val = row[x]!
if (filter === 0) {
// None
} else if (filter === 1) {
val = (val + left) & 0xff
} else if (filter === 2) {
val = (val + up) & 0xff
} else if (filter === 3) {
val = (val + Math.floor((left + up) / 2)) & 0xff
} else if (filter === 4) {
const p = left + up - upLeft
const pa = Math.abs(p - left)
const pb = Math.abs(p - up)
const pc = Math.abs(p - upLeft)
let pr = upLeft
if (pa <= pb && pa <= pc) pr = left
else if (pb <= pc) pr = up
val = (val + pr) & 0xff
}
curRow[x] = val
}
dstAt += rowBytes
}
return { width, height, bitDepth, colorType, pixels }
}
describe('Adv Coverage Audit — Amazon Assets & Metadata Integrity', () => {
const publicMissilesDir = path.resolve(__dirname, '../../../public/missiles')
const packMissilesDir = path.resolve(__dirname, '../../../samples/d2-packs/missiles')
const publicOverlaysDir = path.resolve(__dirname, '../../../public/overlays')
const packOverlaysDir = path.resolve(__dirname, '../../../samples/d2-packs/overlays')
const entitiesDir = path.resolve(__dirname, '../../../samples/d2-packs/entities')
const AMAZON_SKILL_MATRIX = [
{ id: 6, name: 'Magic Arrow', anim: 'A1', weapon: 'bow', missile: 'magicarrow' },
{ id: 7, name: 'Fire Arrow', anim: 'A1', weapon: 'bow', missile: 'firearrow' },
{ id: 8, name: 'Inner Sight', anim: 'SC', overlay: 'cast_innersight' },
{ id: 9, name: 'Critical Strike', type: 'passive', overlay: 'doubledamage1' },
{ id: 10, name: 'Jab', anim: 'A1', weapon: '1ht' },
{ id: 11, name: 'Cold Arrow', anim: 'A1', weapon: 'bow', missile: 'coldarrow' },
{ id: 12, name: 'Multiple Shot', anim: 'A1', weapon: 'bow', missile: 'arrow' },
{ id: 13, name: 'Dodge', anim: 'S1', type: 'passive' },
{ id: 14, name: 'Power Strike', anim: 'A1', weapon: '1ht' },
{ id: 15, name: 'Poison Javelin', anim: 'TH', weapon: '1ht', missile: 'poisonjav' },
{ id: 16, name: 'Exploding Arrow', anim: 'A1', weapon: 'bow', missile: 'explodingarrow' },
{ id: 17, name: 'Slow Missiles', anim: 'SC', overlay: 'cast_slowmissiles' },
{ id: 18, name: 'Avoid', anim: 'S1', type: 'passive' },
{ id: 19, name: 'Impale', anim: 'A1', weapon: '1ht' },
{ id: 20, name: 'Lightning Bolt', anim: 'TH', weapon: '1ht', missile: 'lightningjavelin' },
{ id: 21, name: 'Ice Arrow', anim: 'A1', weapon: 'bow', missile: 'icearrow' },
{ id: 22, name: 'Guided Arrow', anim: 'A1', weapon: 'bow', missile: 'guidedarrow' },
{ id: 23, name: 'Penetrate', type: 'passive' },
{ id: 24, name: 'Charged Strike', anim: 'A1', weapon: '1ht', missile: 'chargedstrikebolt' },
{ id: 25, name: 'Plague Javelin', anim: 'TH', weapon: '1ht', missile: 'plaguejavelin' },
{ id: 26, name: 'Strafe', anim: 'A1', weapon: 'bow', missile: 'strafearrow' },
{ id: 27, name: 'Immolation Arrow', anim: 'A1', weapon: 'bow', missile: 'immolationarrow' },
{ id: 28, name: 'Decoy', anim: 'SC', overlay: 'dopllezon_appear' },
{ id: 29, name: 'Evade', anim: 'S1', type: 'passive' },
{ id: 30, name: 'Fend', anim: 'A1', weapon: '1ht' },
{ id: 31, name: 'Freezing Arrow', anim: 'A1', weapon: 'bow', missile: 'freezingarrow' },
{ id: 32, name: 'Valkyrie', anim: 'SC', overlay: 'valkyriestart', minion: 'valkyrie' },
{ id: 33, name: 'Pierce', type: 'passive' },
{ id: 34, name: 'Lightning Strike', anim: 'A1', weapon: '1ht', missile: 'lightningstrike' },
{ id: 35, name: 'Lightning Fury', anim: 'TH', weapon: '1ht', missile: 'lightningfury' },
] as const
const ALL_AMAZON_MISSILES: readonly { readonly name: string; readonly meta: MissileMeta; readonly trans: number }[] = [
{ name: 'magicarrow', meta: MAGICARROW_META, trans: 1 },
{ name: 'firearrow', meta: FIREARROW_META, trans: 1 },
{ name: 'icearrow', meta: ICEARROW_META, trans: 1 },
{ name: 'arrow', meta: ARROW_META, trans: 0 },
{ name: 'lightningjavelin', meta: LIGHTNINGJAVELIN_META, trans: 1 },
{ name: 'bolt', meta: BOLT_META, trans: 0 },
{ name: 'coldarrow', meta: COLDARROW_META, trans: 1 },
{ name: 'explodingarrow', meta: EXPLODINGARROW_META, trans: 1 },
{ name: 'explodingarrowexp', meta: EXPLODINGARROWEXP_META, trans: 1 },
{ name: 'icearrowexplode', meta: ICEARROWEXPLODE_META, trans: 1 },
{ name: 'freezingarrow', meta: FREEZINGARROW_META, trans: 1 },
{ name: 'guidedarrow', meta: GUIDEDARROW_META, trans: 1 },
{ name: 'strafearrow', meta: STRAFEARROW_META, trans: 0 },
{ name: 'strafebolt', meta: STRAFEBOLT_META, trans: 0 },
{ name: 'immolationarrow', meta: IMMOLATIONARROW_META, trans: 1 },
{ name: 'immolationarrowexp', meta: IMMOLATIONARROWEXP_META, trans: 1 },
{ name: 'immolationfire', meta: IMMOLATIONFIRE_META, trans: 1 },
{ name: 'poisonjav', meta: POISONJAV_META, trans: 0 },
{ name: 'poisonjavcloud', meta: POISONJAVCLOUD_META, trans: 1 },
{ name: 'chargedstrikebolt', meta: CHARGEDSTRIKEBOLT_META, trans: 1 },
{ name: 'plaguejavelin', meta: PLAGUEJAVELIN_META, trans: 0 },
{ name: 'plaguejavcloud', meta: PLAGUEJAVCLOUD_META, trans: 1 },
{ name: 'lightningfury', meta: LIGHTNINGFURY_META, trans: 1 },
{ name: 'furylightning', meta: FURYLIGHTNING_META, trans: 1 },
]
const ALL_AMAZON_OVERLAYS: readonly { readonly name: string; readonly meta: OverlayMeta }[] = [
{ name: 'cast_innersight', meta: CAST_INNERSIGHT_OVERLAY_META },
{ name: 'innersight', meta: INNERSIGHT_OVERLAY_META },
{ name: 'cast_slowmissiles', meta: CAST_SLOWMISSILES_OVERLAY_META },
{ name: 'handofathena', meta: HANDOFATHENA_OVERLAY_META },
{ name: 'valkyriestart', meta: VALKYRIESTART_OVERLAY_META },
{ name: 'valkyrie', meta: VALKYRIE_OVERLAY_META },
{ name: 'dopllezon_appear', meta: DOPLLEZON_APPEAR_OVERLAY_META },
{ name: 'doubledamage1', meta: DOUBLEDAMAGE1_OVERLAY_META },
{ name: 'doubledamage2', meta: DOUBLEDAMAGE2_OVERLAY_META },
{ name: 'doubledamage3', meta: DOUBLEDAMAGE3_OVERLAY_META },
]
// =========================================================================
// Dimension 1: Feature Matrix & 30-Skill Requirements Coverage
// =========================================================================
describe('Dimension 1: Feature Matrix & 30-Skill Requirements Coverage', () => {
it('1.1 Verifies all 30 Amazon skills (IDs 6–35) are mapped in requirement matrix', () => {
expect(AMAZON_SKILL_MATRIX.length).toBe(30)
for (let i = 0; i < 30; i++) {
expect(AMAZON_SKILL_MATRIX[i]!.id).toBe(6 + i)
}
})
it('1.2 Verifies every missile skill has a registered missile meta in MISSILE_METAS', () => {
for (const skill of AMAZON_SKILL_MATRIX) {
if ('missile' in skill && skill.missile) {
expect(MISSILE_METAS[skill.missile], `Missing missile meta for ${skill.name} (${skill.missile})`).toBeDefined()
}
}
})
it('1.3 Verifies every overlay skill has a registered overlay meta in OVERLAY_METAS', () => {
for (const skill of AMAZON_SKILL_MATRIX) {
if ('overlay' in skill && skill.overlay) {
expect(OVERLAY_METAS[skill.overlay], `Missing overlay meta for ${skill.name} (${skill.overlay})`).toBeDefined()
}
}
})
})
// =========================================================================
// Dimension 2: Zero Null / Missing / NaN Frame Arrays & Geometry
// =========================================================================
describe('Dimension 2: Zero Null / Missing / NaN Frame Arrays & Geometry', () => {
it('2.1 Asserts no NaN or Infinite coordinates in all Amazon missile frame bounding boxes', () => {
for (const m of ALL_AMAZON_MISSILES) {
expect(m.meta.groups.length).toBeGreaterThanOrEqual(1)
for (const grp of m.meta.groups) {
expect(grp.length).toBeGreaterThanOrEqual(1)
for (const [x, y, w, h, ax, ay] of grp) {
expect(Number.isFinite(x) && !Number.isNaN(x)).toBe(true)
expect(Number.isFinite(y) && !Number.isNaN(y)).toBe(true)
expect(Number.isFinite(w) && !Number.isNaN(w) && w > 0).toBe(true)
expect(Number.isFinite(h) && !Number.isNaN(h) && h > 0).toBe(true)
expect(Number.isFinite(ax) && !Number.isNaN(ax)).toBe(true)
expect(Number.isFinite(ay) && !Number.isNaN(ay)).toBe(true)
}
}
}
})
it('2.2 Asserts no NaN or Infinite coordinates in all Amazon overlay frame bounding boxes', () => {
for (const o of ALL_AMAZON_OVERLAYS) {
expect(o.meta.groups.length).toBeGreaterThanOrEqual(1)
for (const grp of o.meta.groups) {
expect(grp.length).toBeGreaterThanOrEqual(1)
for (const [x, y, w, h, ax, ay] of grp) {
expect(Number.isFinite(x) && !Number.isNaN(x)).toBe(true)
expect(Number.isFinite(y) && !Number.isNaN(y)).toBe(true)
expect(Number.isFinite(w) && !Number.isNaN(w) && w > 0).toBe(true)
expect(Number.isFinite(h) && !Number.isNaN(h) && h > 0).toBe(true)
expect(Number.isFinite(ax) && !Number.isNaN(ax)).toBe(true)
expect(Number.isFinite(ay) && !Number.isNaN(ay)).toBe(true)
}
}
}
})
})
// =========================================================================
// Dimension 3: Boundary & Coordinate Geometric Stress
// =========================================================================
describe('Dimension 3: Boundary & Coordinate Geometric Stress', () => {
it('3.1 Asserts anchor coordinates are bounded within valid sprite offset range [-1000, 1000]', () => {
for (const m of ALL_AMAZON_MISSILES) {
for (const grp of m.meta.groups) {
for (const [, , , , ax, ay] of grp) {
expect(ax).toBeGreaterThanOrEqual(-1000)
expect(ax).toBeLessThanOrEqual(1000)
expect(ay).toBeGreaterThanOrEqual(-1000)
expect(ay).toBeLessThanOrEqual(1000)
}
}
}
for (const o of ALL_AMAZON_OVERLAYS) {
for (const grp of o.meta.groups) {
for (const [, , , , ax, ay] of grp) {
expect(ax).toBeGreaterThanOrEqual(-1000)
expect(ax).toBeLessThanOrEqual(1000)
expect(ay).toBeGreaterThanOrEqual(-1000)
expect(ay).toBeLessThanOrEqual(1000)
}
}
}
})
it('3.2 Every Amazon missile exists in public/missiles and samples/d2-packs/missiles with valid PNG header', () => {
for (const m of ALL_AMAZON_MISSILES) {
for (const dir of [publicMissilesDir, packMissilesDir]) {
const pngPath = path.join(dir, `${m.name}.png`)
const jsonPath = path.join(dir, `${m.name}.json`)
expect(fs.existsSync(pngPath), `Missing ${pngPath}`).toBe(true)
expect(fs.existsSync(jsonPath), `Missing ${jsonPath}`).toBe(true)
const stat = fs.statSync(pngPath)
expect(stat.size).toBeGreaterThan(500)
const buf = fs.readFileSync(pngPath)
expect(buf.readUInt32BE(0)).toBe(0x89504e47) // \x89PNG
}
}
})
it('3.3 Every Amazon overlay exists in public/overlays and samples/d2-packs/overlays with valid PNG header', () => {
for (const o of ALL_AMAZON_OVERLAYS) {
for (const dir of [publicOverlaysDir, packOverlaysDir]) {
const pngPath = path.join(dir, `${o.name}.png`)
const jsonPath = path.join(dir, `${o.name}.json`)
expect(fs.existsSync(pngPath), `Missing ${pngPath}`).toBe(true)
expect(fs.existsSync(jsonPath), `Missing ${jsonPath}`).toBe(true)
const stat = fs.statSync(pngPath)
expect(stat.size).toBeGreaterThan(500)
const buf = fs.readFileSync(pngPath)
expect(buf.readUInt32BE(0)).toBe(0x89504e47) // \x89PNG
}
}
})
})
// =========================================================================
// Dimension 4: State Invariants & Trans/PreDraw Flags
// =========================================================================
describe('Dimension 4: State Invariants & Trans/PreDraw Flags', () => {
it('4.1 Valkyrie glow overlay has preDraw === true, all other Amazon overlays preDraw === false', () => {
expect(VALKYRIE_OVERLAY_META.preDraw).toBe(true)
expect(CAST_INNERSIGHT_OVERLAY_META.preDraw).toBe(false)
expect(INNERSIGHT_OVERLAY_META.preDraw).toBe(false)
expect(CAST_SLOWMISSILES_OVERLAY_META.preDraw).toBe(false)
expect(HANDOFATHENA_OVERLAY_META.preDraw).toBe(false)
expect(VALKYRIESTART_OVERLAY_META.preDraw).toBe(false)
expect(DOPLLEZON_APPEAR_OVERLAY_META.preDraw).toBe(false)
expect(DOUBLEDAMAGE1_OVERLAY_META.preDraw).toBe(false)
expect(DOUBLEDAMAGE2_OVERLAY_META.preDraw).toBe(false)
expect(DOUBLEDAMAGE3_OVERLAY_META.preDraw).toBe(false)
})
it('4.2 All Amazon overlays enforce trans === 3 (additive blending mode)', () => {
for (const o of ALL_AMAZON_OVERLAYS) {
expect(o.meta.trans).toBe(3)
}
})
})
// =========================================================================
// Dimension 5: System Regression, Cross-Class Isolation & Zero Runtime MPQ
// =========================================================================
describe('Dimension 5: System Regression, Cross-Class Isolation & Zero Runtime MPQ', () => {
it('5.1 Verifies metadata files contain zero runtime MPQ, DLL or fs imports', () => {
const missilesMetaPath = path.resolve(__dirname, '../../../src/render/missiles-meta.ts')
const overlaysMetaPath = path.resolve(__dirname, '../../../src/render/overlays-meta.ts')
for (const p of [missilesMetaPath, overlaysMetaPath]) {
const content = fs.readFileSync(p, 'utf-8')
expect(content).not.toContain('node:fs')
expect(content).not.toContain('src/mpq')
expect(content).not.toContain('.mpq')
expect(content).not.toContain('.dll')
}
})
it('5.2 Verifies BATCH1_SKILLS length invariant strictly maintained at 38', () => {
expect(Object.keys(BATCH1_SKILLS).length).toBe(38)
})
})
})