cosmonarchy-bw-prerelease/tools/tbl/test/roundtrip.test.ts
Kenny Udovic 36cc26b464 25 August 2026 -- tbl encoder decoder and image.tbl.txt loader
Ported from cosmonarchy-bw-release fe42f19. The .tbl.txt sources were
regenerated from this repo's own .tbl binaries rather than copied, so
images.tbl.txt and unitnames.tbl.txt carry the prerelease strings
(Askosi Sons of Ash, Faction Selection Askosi, Terran Fleshthief,
Forerunner Lumen) instead of the release ones.

tools/tbl is committed as real files here; in the release repo it is a
bare gitlink with no .gitmodules, so it comes out empty on clone.
2026-08-25 20:08:20 -04:00

85 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "bun:test"
import * as Tbl from "../src/Tbl.ts"
import * as TblText from "../src/TblText.ts"
import { show, succeed } from "./helpers.ts"
const repo = `${import.meta.dir}/../../..`
const FIXTURES = [
"mpq/arr/images.tbl",
"mpq/arr/portdata.tbl",
"mpq/rez/gluHist.tbl",
"mpq/rez/help_txt.tbl",
"mpq/rez/network.tbl",
"mpq/rez/stat_txt.tbl",
"mpq/rez/tips.tbl",
"mpq/rez/unitnames.tbl"
]
describe("round trip against the shipped game data", () => {
for (const fixture of FIXTURES) {
it(`${fixture} survives .tbl -> .tbl.txt -> .tbl byte for byte`, async () => {
const original = new Uint8Array(await Bun.file(`${repo}/${fixture}`).arrayBuffer())
const table = await succeed(Tbl.decode(original))
const text = TblText.encode(table)
const reparsed = await succeed(TblText.decode(text))
const rebuilt = await succeed(Tbl.encode(reparsed))
expect(rebuilt.length).toBe(original.length)
expect(rebuilt).toEqual(original)
})
it(`${fixture} decompiles to pure ASCII`, async () => {
const original = new Uint8Array(await Bun.file(`${repo}/${fixture}`).arrayBuffer())
const text = TblText.encode(await succeed(Tbl.decode(original)))
const offender = [...text].find((char) => char.charCodeAt(0) > 0x7e)
expect(offender).toBeUndefined()
})
}
it("renders images.tbl as plain, unescaped GRP paths", async () => {
const original = new Uint8Array(await Bun.file(`${repo}/mpq/arr/images.tbl`).arrayBuffer())
const table = await succeed(Tbl.decode(original))
const lines = TblText.encode(table, { header: false }).split("\n")
expect(lines[0]).toBe("zerg\\avenger.grp")
expect(lines[1]).toBe("protoss\\mshield.los")
})
it("renders unitnames.tbl multi-part entries with <0> separators", async () => {
const original = new Uint8Array(await Bun.file(`${repo}/mpq/rez/unitnames.tbl`).arrayBuffer())
const table = await succeed(Tbl.decode(original))
const lines = TblText.encode(table, { header: false }).split("\n")
expect(lines[0]).toBe("Terran Maverick<0>*<0>Ground Units")
expect(lines[5]).toBe("Terran Phalanx<0>Tank Mode<0>Ground Units")
})
it("renders stat_txt.tbl colour codes and newlines as <N>", async () => {
const original = new Uint8Array(await Bun.file(`${repo}/mpq/rez/stat_txt.tbl`).arrayBuffer())
const table = await succeed(Tbl.decode(original))
const line = TblText.encode(table, { header: false }).split("\n")[338]!
expect(line).toBe(
"e<0><4>Tank Mode <1>(<3>E<1>)<10>Returns the Phalanx's weapon and mobility to normal."
)
})
it("dedupe reclaims stat_txt.tbl headroom without changing a single string", async () => {
const original = new Uint8Array(await Bun.file(`${repo}/mpq/rez/stat_txt.tbl`).arrayBuffer())
const table = await succeed(Tbl.decode(original))
const deduped = await succeed(Tbl.encode(table, { dedupe: true }))
expect(deduped.length).toBeLessThan(original.length)
expect(show(await succeed(Tbl.decode(deduped)))).toEqual(show(table))
})
it("reports how little offset headroom stat_txt.tbl has left", async () => {
const original = new Uint8Array(await Bun.file(`${repo}/mpq/rez/stat_txt.tbl`).arrayBuffer())
const view = new DataView(original.buffer)
const count = view.getUint16(0, true)
let highest = 0
for (let i = 0; i < count; i++) highest = Math.max(highest, view.getUint16(2 + i * 2, true))
expect(highest).toBeLessThanOrEqual(Tbl.MAX_OFFSET)
expect(Tbl.MAX_OFFSET - highest).toBeLessThan(64) // 26 bytes at time of writing
})
})