mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-17 22:35:13 +02:00
The binary protocol re-sent the full grid every frame. This adds an opt-in
per-frame codec that picks the smallest of three encodings and tags it in a
1-byte header, without changing the rendered output:
0 RAW framebuffer as-is (legacy)
1 ZLIB zlib(framebuffer)
2 DELTA only the cells changed since the previous frame, patched on top
Clients opt in via /ws?codec=adaptive; omitting it yields the original protocol
byte-for-byte, so existing clients are unaffected. A keyframe is forced
periodically for resync. codec.js is shared by the browser and the Node test,
so the shipped decode path is the tested one.
Optional --quality {lossless,high,balanced,low} enables lossy temporal delta
(conditional replenishment): a colour cell is only re-sent once it drifts past a
tolerance from what the viewer already sees; the character plane stays exact.
Default lossless = bit-exact.
Measured wire savings (mode 5, 200x80): static screen 0.3% of legacy (~375x),
pixel mode 11.6%, high-motion 63% (never worse). Encoder tuned (zlib level 3,
smart candidate selection) to stay well under the frame budget.
Verified bit-exact two independent ways: Python->Node vectors and a live
adaptive-vs-legacy WebSocket diff. (A fuller mutation + Autobahn conformance
harness exists on request.)
55 lines
2.2 KiB
JavaScript
55 lines
2.2 KiB
JavaScript
/**
|
|
* Decode the Python-generated test vectors with the SHIPPED codec.js and verify
|
|
* every frame matches the ground-truth framebuffer byte-for-byte.
|
|
*
|
|
* This exercises the real cross-language risk surface: zlib (Python) ->
|
|
* DecompressionStream (JS), little-endian delta indices, and delta patching.
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const codec = require('../codec.js');
|
|
|
|
function readChunks(buf) {
|
|
const out = [];
|
|
let off = 0;
|
|
while (off + 4 <= buf.length) {
|
|
const len = buf.readUInt32BE(off); off += 4;
|
|
out.push(new Uint8Array(buf.subarray(off, off + len))); off += len;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
async function checkDir(name) {
|
|
const dir = path.join(__dirname, 'vectors', name);
|
|
const meta = JSON.parse(fs.readFileSync(path.join(dir, 'meta.json')));
|
|
const msgs = readChunks(fs.readFileSync(path.join(dir, 'adaptive.bin')));
|
|
const truth = readChunks(fs.readFileSync(path.join(dir, 'truth.bin')));
|
|
const dec = codec.makeDecoder(meta.cellBytes);
|
|
|
|
let mismatches = 0, firstBad = null;
|
|
for (let i = 0; i < msgs.length; i++) {
|
|
const { frame } = await dec.decode(msgs[i]);
|
|
const want = truth[i];
|
|
if (frame.length !== want.length) { mismatches++; firstBad ??= [i, 'len', want.length, frame.length]; continue; }
|
|
for (let j = 0; j < want.length; j++) {
|
|
if (frame[j] !== want[j]) { mismatches++; firstBad ??= [i, 'byte@' + j, want[j], frame[j]]; break; }
|
|
}
|
|
}
|
|
const pct = (100 * meta.adaptiveBytes / meta.legacyBytes).toFixed(1);
|
|
const status = mismatches === 0 ? 'PASS bit-exact' : `FAIL (${mismatches})`;
|
|
console.log(
|
|
`${name.padEnd(20)} ${String(msgs.length).padStart(3)} frames ` +
|
|
`${status.padEnd(16)} wire ${pct}% of legacy` +
|
|
(firstBad ? ` firstBad=${JSON.stringify(firstBad)}` : '')
|
|
);
|
|
return mismatches === 0;
|
|
}
|
|
|
|
(async () => {
|
|
const names = fs.readdirSync(path.join(__dirname, 'vectors'));
|
|
console.log('Decoding with codec.js, comparing to ground truth:\n');
|
|
let allPass = true;
|
|
for (const n of names) allPass = (await checkDir(n)) && allPass;
|
|
console.log('\n' + (allPass ? 'ALL VECTORS BIT-EXACT' : 'SOME VECTORS FAILED'));
|
|
process.exit(allPass ? 0 : 1);
|
|
})().catch((e) => { console.error(e); process.exit(2); });
|