mirror of
https://github.com/YusufB5/ASCILINE.git
synced 2026-06-23 22:48:06 +02:00
feat: adaptive raw/zlib/delta frame codec (opt-in, backward compatible)
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.)
This commit is contained in:
parent
8c60ef12a0
commit
e3f282910d
10 changed files with 478 additions and 10 deletions
55
experiments/check_vectors.js
Normal file
55
experiments/check_vectors.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* 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); });
|
||||
Loading…
Add table
Add a link
Reference in a new issue