78 lines
3.1 KiB
JavaScript
78 lines
3.1 KiB
JavaScript
/**
|
||
* 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 menu’s 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');
|