diablo2-web/tests/packed-entity-art.test.ts

86 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { existsSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { ACT_MONSTER_SPECS, MONSTER_ART_MAP } from '../src/game/monster-mapping.ts'
describe('offline pre-baked entity atlases & full Act 1..5 pack index (Issues #30 & #33)', () => {
const packRoot = join(process.cwd(), 'samples', 'd2-packs')
const indexJsonPath = join(packRoot, 'index.json')
const hasPacks = existsSync(indexJsonPath) && existsSync(join(packRoot, 'act1', 'entities', 'char-so.json'))
it('maps 700+ monster IDs across Acts 1..5 in MONSTER_ART_MAP', () => {
expect(Object.keys(MONSTER_ART_MAP).length).toBeGreaterThanOrEqual(700)
expect(MONSTER_ART_MAP.fallen).toEqual({ token: 'FA', weapon: 'hth' })
expect(MONSTER_ART_MAP.hellbovine).toEqual({ token: 'CW', weapon: 'hth' })
expect(MONSTER_ART_MAP.skeleton).toEqual({ token: 'SK', weapon: 'hth' })
})
it.skipIf(!hasPacks)('includes all 5 acts and 350+ map variants in samples/d2-packs/index.json (Issue #33)', () => {
const index = JSON.parse(readFileSync(indexJsonPath, 'utf8'))
expect(Array.isArray(index.levels)).toBe(true)
expect(index.levels.length).toBeGreaterThanOrEqual(350)
const actsPresent = new Set(index.levels.map((l: { act: number }) => l.act))
expect(actsPresent.has(1)).toBe(true)
expect(actsPresent.has(2)).toBe(true)
expect(actsPresent.has(3)).toBe(true)
expect(actsPresent.has(4)).toBe(true)
expect(actsPresent.has(5)).toBe(true)
})
it.skipIf(!hasPacks)('bakes Sorceress char-so PNG + JSON for Acts 1..5', () => {
for (let act = 1; act <= 5; act += 1) {
const dir = join(packRoot, `act${act}`, 'entities')
const jsonPath = join(dir, 'char-so.json')
const pngPath = join(dir, 'char-so.png')
expect(existsSync(jsonPath)).toBe(true)
expect(existsSync(pngPath)).toBe(true)
const meta = JSON.parse(readFileSync(jsonPath, 'utf8'))
expect(meta.file).toBe('char-so.png')
expect(meta.directions).toBe(16)
expect(meta.walk).toBe(0)
expect(meta.stand).toBe(16)
expect(meta.members).toBeGreaterThanOrEqual(16)
expect(meta.groups.length).toBe(32) // 16 walk + 16 stand
const png = readFileSync(pngPath)
expect(png[0]).toBe(137)
expect(png[1]).toBe(80) // P
expect(png[2]).toBe(78) // N
expect(png[3]).toBe(71) // G
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
expect(view.getUint32(16)).toBe(meta.width)
expect(view.getUint32(20)).toBe(meta.height)
}
})
it.skipIf(!hasPacks)('bakes monster PNG + JSON atlases for all per-Act monster specs across Acts 1..5', () => {
for (let act = 1; act <= 5; act += 1) {
const dir = join(packRoot, `act${act}`, 'entities')
const specs = ACT_MONSTER_SPECS[act] ?? []
expect(specs.length).toBeGreaterThan(0)
for (const spec of specs) {
const slug = `monster-${spec.token.toLowerCase()}-${spec.weapon.toLowerCase()}`
const jsonPath = join(dir, `${slug}.json`)
const pngPath = join(dir, `${slug}.png`)
expect(existsSync(jsonPath), `missing ${jsonPath}`).toBe(true)
expect(existsSync(pngPath), `missing ${pngPath}`).toBe(true)
const meta = JSON.parse(readFileSync(jsonPath, 'utf8'))
expect(meta.token).toBe(spec.token)
expect(meta.weapon).toBe(spec.weapon)
expect([1, 4, 8, 16]).toContain(meta.directions)
expect(meta.walkOffset).toBe(0)
expect(meta.standOffset).toBe(meta.directions)
expect(meta.groups.length).toBe(meta.directions * 2)
const png = readFileSync(pngPath)
const view = new DataView(png.buffer, png.byteOffset, png.byteLength)
expect(view.getUint32(16)).toBe(meta.width)
expect(view.getUint32(20)).toBe(meta.height)
}
}
})
})