diablo2-web/scripts/verify-tbl.ts

60 lines
2.8 KiB
TypeScript

/**
* Construction check for the TBL decoder.
*
* The classic TBL layout has no independent decoder available (the community Go
* package implements the later hash-table variant), so this is a two-sided test
* rather than a differential one: the script *writes* a table whose entries are
* known — including empty strings, unused indices and non-ASCII text — and then
* requires the decoder to reproduce exactly that. It catches layout and
* decoding mistakes (off-by-one offsets, byte/character confusion, UTF-16
* endianness) but cannot catch a wrong reading shared by writer and reader;
* that is what a real `string.tbl` is for.
*
* Usage: node scripts/verify-tbl.ts
*/
import { decodeTbl, tblLookup } from '../src/formats/tbl.ts'
import { encodeTbl } from './lib/tbl-writer.ts'
import type { TblEntry } from './lib/tbl-writer.ts'
/** Entries the decoder must reproduce, in order. */
const entries: TblEntry[] = [
'Stamina Potion', // plain ASCII
'', // empty string is a value, not a hole
null, // unused index
'Tome of Town Portal',
null,
'完美宝石', // CJK: multi-byte UTF-16, but one unit each
'Straße', // Latin-1 range
null,
'🌀 Emoji beyond the BMP', // surrogate pair: two units for one character
'x'.repeat(300), // long entry
]
const encoded = encodeTbl(entries)
const decoded = decodeTbl(encoded)
const lookup = tblLookup(decoded)
const problems: string[] = []
if (encoded.byteLength !== decoded.length * 0 + encoded.byteLength) {
/* v8 ignore next -- placeholder to keep the shape obvious. */
problems.push('unreachable')
}
if (decoded.length !== entries.length) problems.push(`entry count ${String(decoded.length)} != ${String(entries.length)}`)
entries.forEach((want, index) => {
const got = decoded[index]
if (want === null) {
if (got !== undefined) problems.push(`index ${String(index)}: expected an unused index, got ${JSON.stringify(got)}`)
return
}
if (got !== want) problems.push(`index ${String(index)}: ${JSON.stringify(got)} != ${JSON.stringify(want)}`)
})
console.log(`table ${String(entries.length)} indices (${String(lookup.size)} used)`)
console.log(`encoded ${String(encoded.byteLength)} bytes`)
console.log(`unicode CJK=${JSON.stringify(lookup.get(5))} latin1=${JSON.stringify(lookup.get(6))} astral=${JSON.stringify(lookup.get(8))}`)
console.log(`long ${String(lookup.get(9)?.length ?? 0)} characters round-tripped`)
console.log(`problems ${String(problems.length)}`)
for (const problem of problems.slice(0, 8)) console.log(` - ${problem}`)
console.log(problems.length === 0 ? 'RESULT every index round-trips exactly' : 'RESULT FAILED')
process.exit(problems.length === 0 ? 0 : 1)