/** * AUDIT TOOL (read-only, scratch): enumerate the REAL DT1 tile inventory. * * For every LevelType listed in /tmp/dt1_list.txt, open the DT1 straight out of * the MPQs and dump every tile record's (orientation, mainIndex, subIndex, * rarityFrameIndex). This is the authoritative ground truth for which * (style, sequence) pairs legally exist -- AutoMap.txt is only a proxy because * it lists tiles that have an automap glyph, not the full tile set. * * DT1 tile record is 96 bytes (Paul Siramy layout, corroborated by the port's own * src/formats/dt1.ts which reads subTileFlags at +40 and block pointers at +72/76/80): * +20 int32 orientation (0 == floor) * +24 int32 mainIndex (== "style") * +28 int32 subIndex (== "sequence") * +32 int32 rarityFrameIndex * * Output: TSV to stdout -> levelType, dt1File, orientation, mainIndex, subIndex, rarity */ import { readFileSync, writeFileSync } from 'node:fs' import { MpqArchive } from '../src/mpq/archive.ts' import { fileSource } from '../src/mpq/file-source.ts' const D2 = '/usr/local/google/home/taodao/d2-data' // Search order matters: Patch_D2 overrides base archives. const archives = [] for (const name of ['Patch_D2.mpq', 'd2exp.mpq', 'd2data.mpq']) { archives.push({ name, ar: await MpqArchive.open(await fileSource(`${D2}/${name}`)) }) } const i32 = (b: Uint8Array, o: number): number => new DataView(b.buffer, b.byteOffset, b.byteLength).getInt32(o, true) const lines = readFileSync('/tmp/dt1_list.txt', 'utf8').trim().split('\n') const out: string[] = ['levelType\tdt1\torientation\tmainIndex\tsubIndex\trarity'] const missing: string[] = [] let totalTiles = 0 for (const line of lines) { const [ltRaw, rel] = line.split('\t') if (ltRaw === undefined || rel === undefined) continue const internal = `data\\global\\tiles\\${rel.replace(/\//g, '\\')}` let data: Uint8Array | undefined let from = '' for (const { name, ar } of archives) { const f = ar.find(internal) if (f !== undefined) { data = await ar.read(f) from = name break } } if (data === undefined) { missing.push(`${ltRaw}\t${rel}`) continue } const major = i32(data, 0) const minor = i32(data, 4) if (major !== 7 || minor !== 6) { missing.push(`${ltRaw}\t${rel}\tBAD VERSION ${major}.${minor}`) continue } const tileCount = i32(data, 268) const tileStart = i32(data, 272) for (let k = 0; k < tileCount; k += 1) { const at = tileStart + k * 96 if (at + 96 > data.byteLength) break out.push( `${ltRaw}\t${rel}\t${i32(data, at + 20)}\t${i32(data, at + 24)}\t` + `${i32(data, at + 28)}\t${i32(data, at + 32)}`, ) totalTiles += 1 } console.error(` [${from.padEnd(12)}] lt=${ltRaw.padStart(2)} ${rel} -> ${tileCount} tiles`) } writeFileSync('/tmp/dt1_tiles.tsv', out.join('\n') + '\n') console.error(`\nwrote /tmp/dt1_tiles.tsv : ${totalTiles} tile records`) if (missing.length > 0) { console.error(`\nMISSING / UNREADABLE (${missing.length}):`) for (const m of missing) console.error(` ${m}`) }