// Golden-vector tests: wire frames must serialize byte-identically to the TS
// implementation's (fixtures generated while the TS was canon — recorded off
// the real hostTick/sendSnap/broadcastRoster code paths).
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";
const vectors = JSON.parse(readFileSync(new URL("./vectors/frames.json", import.meta.url), "utf8"));
const byName = Object.fromEntries(vectors.map((v) => [v.name, v]));
// Each builder is called with the values the fixture recorded, so a key-order
// or omitted-field regression shows up as a byte diff.
const built = {
"ro-idle": () => lib.mkRo("12D3KooWA", [["12D3KooWA", "zed", 0, 2], ["12D3KooWB", "rex", 1, 0]], false),
"ro-live": () => lib.mkRo("12D3KooWA", [["12D3KooWA", "zed", 0, 2], ["12D3KooWB", "rex", 1, 0]], true),
hi: () => lib.mkHi("zed", 3),
in: () => lib.mkIn(3),
fu: () => lib.mkFu(),
st: () => lib.mkSt(2, 125, 360, ["12D3KooWA", "12D3KooWB"], [[20, 4, 2], [20, 26, 0]]),
tk: () => lib.mkTk(1, [[0, 20, 5], [1, 20, 25]], null, []),
"tk-newbloom": () => lib.mkTk(24, [[0, 20, 6], [1, 20, 24]], [[21, 4]], []),
"tk-splash": () => lib.mkTk(6, [[0, 20, 7], [1, 20, 23]], null, [0]),
ov: () => lib.mkOv("12D3KooWA", [31, 11], [["12D3KooWA", 1], ["12D3KooWB", 0]]),
"ov-draw": () => lib.mkOv(null, [1, 1], [["12D3KooWA", 0], ["12D3KooWB", 0]]),
ma: () => lib.mkMa("12D3KooWA", [["12D3KooWA", 3], ["12D3KooWB", 0]]),
lo: () => lib.mkLo(),
sn: () => {
const v = byName.sn.frame;
return lib.mkSn(v.round, v.tickMs, v.ticks, v.n, v.order, v.ps, v.runs, v.bl);
},
};
test("every recorded frame type has a builder under test", () => {
assert.deepEqual(Object.keys(built).sort(), vectors.map((v) => v.name).sort());
});
for (const [name, make] of Object.entries(built)) {
test(`frame ${name} serializes byte-identically to TS`, () => {
assert.equal(JSON.stringify(make()), byName[name].json);
});
}
test("tk omits nb and sp when empty, exactly as the TS spread did", () => {
assert.equal(JSON.stringify(lib.mkTk(4, [[0, 1, 1]], null, [])), '{"t":"tk","n":4,"h":[[0,1,1]]}');
assert.equal(JSON.stringify(lib.mkTk(4, [], null, null)), '{"t":"tk","n":4,"h":[]}');
// and carries them, in wire order t,n,h,nb,sp, when present
assert.equal(
JSON.stringify(lib.mkTk(4, [[0, 1, 1]], [[2, 3]], [0])),
'{"t":"tk","n":4,"h":[[0,1,1]],"nb":[[2,3]],"sp":[0]}',
);
});
test("ro omits live unless the sender is host mid-round", () => {
assert.ok(!JSON.stringify(lib.mkRo("h", [], false)).includes("live"));
assert.ok(JSON.stringify(lib.mkRo("h", [], true)).includes('"live":1'));
});
test("isGameFrame accepts frames, rejects junk", () => {
for (const v of vectors) assert.equal(lib.isGameFrame(v.frame), true, v.name);
for (const junk of [null, undefined, 42, "hi", [], {}, { t: 7 }]) {
assert.equal(Boolean(lib.isGameFrame(junk)), false, `junk: ${JSON.stringify(junk)}`);
}
});