diablo2-web/scripts/make-pl2-fixture.ts

86 lines
2.8 KiB
TypeScript

/**
* PL2 fixture generator.
*
* PL2 is purely positional — no header, no counts, no directory — so the only
* way to test it without a real file is to write the bytes in the documented
* order and let an independent decoder read them back. Every table gets a
* pattern that depends on its *global index*, so a table read out of order, or a
* group with the wrong count, changes the digests the parity harness compares
* rather than passing silently.
*
* Usage: node scripts/make-pl2-fixture.ts <output-directory>
*/
import { mkdir, writeFile } from 'node:fs/promises'
import { join } from 'node:path'
import { pl2ExpectedSize } from '../src/formats/pl2.ts'
/** Palette entries per transform table. */
const PALETTE_COLORS = 256
/** Groups in file order: name → table count. */
const GROUPS: readonly (readonly [string, number])[] = [
['lightLevels', 32],
['inverseColors', 16],
['selectedUnitShift', 1],
['alphaBlend0', 256],
['alphaBlend1', 256],
['alphaBlend2', 256],
['additiveBlend', 256],
['multiplyBlend', 256],
['hueVariations', 111],
['redTones', 1],
['greenTones', 1],
['blueTones', 1],
['unknownVariations', 14],
['maxComponentBlend', 256],
['darkenedShift', 1],
['textShifts', 13],
]
const outDir = process.argv[2]
if (outDir === undefined) {
console.error('usage: node scripts/make-pl2-fixture.ts <output-directory>')
process.exit(2)
}
await mkdir(outDir, { recursive: true })
const size = pl2ExpectedSize()
const file = new Uint8Array(size)
let cursor = 0
// Base palette: r, g, b plus an unused byte that is deliberately not zero, so a
// decoder that reads it as colour data (or as alpha) is caught.
for (let index = 0; index < PALETTE_COLORS; index += 1) {
file[cursor++] = index
file[cursor++] = (index * 3) & 0xff
file[cursor++] = (index * 7) & 0xff
file[cursor++] = 0xab
}
// Transform tables: table `k` maps entry `i` to `(i + k) mod 256`, which makes
// every table distinct and its position observable.
let table = 0
for (const [, count] of GROUPS) {
for (let index = 0; index < count; index += 1) {
for (let entry = 0; entry < PALETTE_COLORS; entry += 1) {
file[cursor++] = (entry + table) & 0xff
}
table += 1
}
}
// Text palette (13 entries, three bytes each).
for (let index = 0; index < 13; index += 1) {
file[cursor++] = (10 + index) & 0xff
file[cursor++] = (20 + index) & 0xff
file[cursor++] = (30 + index) & 0xff
}
if (cursor !== size) {
throw new Error(`fixture wrote ${String(cursor)} bytes, expected ${String(size)}`)
}
const path = join(outDir, 'fixture.pl2')
await writeFile(path, file)
await writeFile(join(outDir, 'pl2-groups.json'), JSON.stringify(GROUPS, null, 2))
console.log(`wrote ${path} (${String(file.byteLength)} bytes, ${String(table)} transform tables + 13 text shifts)`)