orbit/dev/decode.test.mjs

78 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ScrambleDecode test (dev tool, run with Node — no browser):
*
* node dev/decode.test.mjs
*
* The shared "decode" effect (js/utils/Decode.js) — the type-out the
* menu uses on the Galaxy Seed and the game uses on the system dossier
* HUD. Checks the effect's contract:
* - '' before the start time, the exact target once finished;
* - the revealed prefix always matches the target's;
* - unrevealed slots are drawn from the decode alphabet;
* - the reveal never goes backwards over time;
* - display length is stable through the window;
* - decodeDur floors short lines, caps long ones, grows in between;
* - the default window is the menu's 620 ms.
*/
import { pathToFileURL } from 'node:url';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const { ScrambleDecode, DECODE_CHARS, DECODE_DURATION, decodeDur } = await import(
pathToFileURL(join(__dirname, '../js/utils/Decode.js')).href
);
let failures = 0;
const check = (label, cond) => {
console.log(`${cond ? '✔' : '✘ FAIL'} ${label}`);
if (!cond) failures++;
};
const VALUE = 'Kepler-9 · 17 planets';
const T0 = 1000;
const dur = DECODE_DURATION;
const dec = new ScrambleDecode(VALUE, T0, dur);
check('empty before the start time', dec.display(T0 - 1) === '');
check('exact target after the window', dec.display(T0 + dur + 1) === VALUE);
check('started()/finished() agree with display()', dec.started(T0 - 1) === false && dec.finished(T0 + dur) === true);
check('empty target stays empty', new ScrambleDecode('', T0, dur).display(T0 + dur / 2) === '');
// Through the window: length stable, prefix exact, rest from the alphabet,
// and the reveal monotonically non-decreasing.
let lenOk = true;
let prefixOk = true;
let charsOk = true;
let monotonic = true;
let prev = 0;
for (let t = T0; t <= T0 + dur; t += 16) {
const s = dec.display(t);
if (s.length !== VALUE.length) lenOk = false;
let n = 0;
while (n < VALUE.length && s[n] === VALUE[n]) n++;
if (s.slice(0, n) !== VALUE.slice(0, n)) prefixOk = false; // revealed part is exact
for (let i = n; i < s.length; i++) if (DECODE_CHARS.indexOf(s[i]) === -1) charsOk = false;
if (n < prev) monotonic = false;
prev = n;
}
check('display length stays constant through the window', lenOk);
check('revealed prefix always matches the target', prefixOk);
check('unrevealed slots come from the decode alphabet', charsOk);
check('reveal never goes backwards', monotonic);
// decodeDur: floored, capped, growing in between.
check('decodeDur floors short lines', decodeDur(0) >= 420 && decodeDur(1) >= 420);
check('decodeDur caps long lines', decodeDur(500) <= 880);
check('decodeDur grows with length (in range)', decodeDur(4) < decodeDur(12) && decodeDur(12) < decodeDur(40));
// The default window is the menu's 620 ms (the canonical decode timing).
check('default window is the menus 620 ms', new ScrambleDecode('x', 0).dur === 620);
if (failures > 0) {
console.error(`\n${failures} decode test(s) FAILED`);
process.exit(1);
}
console.log('\ndecode: all checks passed');