diablo2-web/scripts/verify-dc6.ts

81 lines
3.7 KiB
TypeScript

/**
* Self-check for the DC6 decoder against the generated fixture.
*
* This proves the decoder and the encoder agree; it does *not* prove the
* format understanding is right. That second claim is what
* `scripts/verify-dc6.sh` establishes, by having the independent `dc6png`
* decoder read the same fixture and produce the same image.
*
* Usage: node scripts/verify-dc6.ts <fixture-directory>
*/
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import { decodeDc6 } from '../src/formats/dc6.ts'
interface ExpectedFrame {
readonly width: number
readonly height: number
readonly rows: readonly (readonly number[])[]
}
interface Expected {
readonly directions: number
readonly framesPerDirection: number
readonly frames: readonly (readonly ExpectedFrame[])[]
}
const dir = process.argv[2]
if (dir === undefined) {
console.error('usage: node scripts/verify-dc6.ts <fixture-directory>')
process.exit(2)
}
const expected = JSON.parse(await readFile(join(dir, 'expected.json'), 'utf8')) as Expected
const sheet = decodeDc6(new Uint8Array(await readFile(join(dir, 'fixture.dc6'))))
const problems: string[] = []
if (sheet.header.directions !== expected.directions) {
problems.push(`directions: ${String(sheet.header.directions)} != ${String(expected.directions)}`)
}
if (sheet.header.framesPerDirection !== expected.framesPerDirection) {
problems.push(`frames per direction: ${String(sheet.header.framesPerDirection)} != ${String(expected.framesPerDirection)}`)
}
if (sheet.header.version !== 6) problems.push(`version: ${String(sheet.header.version)} != 6`)
let frames = 0
for (let direction = 0; direction < expected.directions; direction += 1) {
const group = sheet.groups[direction]
if (group === undefined) { problems.push(`direction ${String(direction)} missing`); continue }
for (let frameIndex = 0; frameIndex < expected.framesPerDirection; frameIndex += 1) {
const want = expected.frames[direction]?.[frameIndex]
const got = group.frames[frameIndex]
if (want === undefined || got === undefined) { problems.push(`frame ${String(direction)}/${String(frameIndex)} missing`); continue }
frames += 1
if (got.width !== want.width || got.height !== want.height) {
problems.push(`frame ${String(direction)}/${String(frameIndex)} size ${String(got.width)}x${String(got.height)} != ${String(want.width)}x${String(want.height)}`)
continue
}
for (let y = 0; y < want.height; y += 1) {
for (let x = 0; x < want.width; x += 1) {
const at = y * want.width + x
const wantIndex = want.rows[y]?.[x] ?? 0
const gotIndex = got.indices[at] ?? 0
const wantOpaque = wantIndex === 0 ? 0 : 1
if (gotIndex !== wantIndex || (got.mask[at] ?? 0) !== wantOpaque) {
problems.push(`frame ${String(direction)}/${String(frameIndex)} px ${String(x)},${String(y)}: index ${String(gotIndex)}/mask ${String(got.mask[at] ?? 0)} != ${String(wantIndex)}/${String(wantOpaque)}`)
}
}
}
// Placement fields travel through untouched.
if (got.offsetX !== direction * 4 + frameIndex * 22 + 1 || got.offsetY !== -(direction * 4 + frameIndex * 22 + 1)) {
problems.push(`frame ${String(direction)}/${String(frameIndex)} anchors ${String(got.offsetX)},${String(got.offsetY)} are wrong`)
}
}
}
console.log(`fixture ${join(dir, 'fixture.dc6')}`)
console.log(`decoded ${String(frames)} frames, ${String(sheet.header.directions)} directions x ${String(sheet.header.framesPerDirection)}`)
console.log(`mismatches ${String(problems.length)}`)
for (const problem of problems.slice(0, 12)) console.log(` - ${problem}`)
console.log(problems.length === 0 ? 'RESULT decoder matches the encoded grid exactly' : 'RESULT FAILED')
process.exit(problems.length === 0 ? 0 : 1)