eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1// Tests for the boot scene's pure timing/geometry (the light's band math, the
2// quote fade envelope, the staged cover's dwell guarantees). node test/bootscene.test.mjs
3import assert from "node:assert/strict";
4import { readFileSync } from "node:fs";
5import { BANDS, bandLight, quoteOpacity, finishWait, coverWait, validQuoteLib } from "../web/static/bootscene.js";
6
7let failures = 0;
8async function test(name, fn) {
9 try {
10 await fn();
11 console.log("PASS " + name);
12 } catch (e) {
13 console.log("FAIL " + name + " — " + (e && e.message));
14 failures++;
15 }
16}
17
18const sum = (l) => l[0] + l[1] + l[2] + l[3];
19
20test("bands cover the road in order: green, amber, red, eleven", () => {
21 assert.equal(BANDS.length, 5);
22 assert.equal(BANDS[0], 0);
23 assert.equal(BANDS[4], 1);
24 for (let i = 1; i < BANDS.length; i++) assert.ok(BANDS[i] > BANDS[i - 1]);
25});
26
27test("the soft frontier leads the level by a bounded margin", () => {
28 // The light fades out ~0.14 past the frontier instead of ending at a hard
29 // line. At level 0 that lead may kiss the very bottom of the road, but
30 // beyond it there must be nothing.
31 for (const u of [0.15, 0.2, 0.5, 0.9]) assert.equal(sum(bandLight(u, 0, 0)), 0);
32});
33
34test("the frontier gates the climb: light below it, none above", () => {
35 const l = 3; // frontier at 3/11
36 assert.ok(bandLight(0.1, l, 0)[0] > 0, "green near the source");
37 assert.equal(sum(bandLight(0.5, l, 0)), 0, "dark past the frontier");
38});
39
40test("the zen climb can never reach the red", () => {
41 // The climb asymptotes at level 6.2 (frontier 0.564 + soft lead 0.14 →
42 // light tops out below u=0.71): the frontier's bleed may warm the bottom
43 // of the amber, but red (from 9/11 = 0.818) only ever arrives with the
44 // punch.
45 for (const u of [0.71, 0.8, 0.9, 0.95]) {
46 assert.equal(sum(bandLight(u, 6.2, 0)), 0, `light found at u=${u} during climb`);
47 }
48});
49
50test("colour belongs to height, not to level: green stays green at ten", () => {
51 const low = bandLight(0.2, 10, 0);
52 assert.ok(low[0] > 0, "the bottom is still green at level ten");
53 assert.equal(low[1] + low[2] + low[3], 0, "no amber/red bleeding into the green band");
54 const mid = bandLight(0.7, 10, 0);
55 assert.ok(mid[1] > 0, "amber lives above the green");
56 const high = bandLight(0.88, 10, 0);
57 assert.ok(high[2] > 0, "red above the amber");
58});
59
60test("the eleventh band lights only with elevenGlow", () => {
61 const u = 0.96; // inside the eleven band
62 assert.equal(bandLight(u, 10, 0)[3], 0, "dark before the punch lands");
63 assert.ok(bandLight(u, 10, 1)[3] > 0, "lit at eleven");
64});
65
66test("light is hottest near the unseen source", () => {
67 const near = sum(bandLight(0.05, 10, 0));
68 const far = sum(bandLight(0.5, 10, 0));
69 assert.ok(near > far, "intensity must fall with distance up the road");
70});
71
72test("quote envelope: fade in, hold, fade out", () => {
73 const F = 2200, H = 7600;
74 assert.equal(quoteOpacity(0, F, H), 0);
75 assert.equal(quoteOpacity(F / 2, F, H), 0.5);
76 assert.equal(quoteOpacity(F, F, H), 1);
77 assert.equal(quoteOpacity(F + H / 2, F, H), 1);
78 assert.ok(Math.abs(quoteOpacity(F + H + F / 2, F, H) - 0.5) < 1e-9);
79 assert.equal(quoteOpacity(F + H + F, F, H), 0);
80 assert.equal(quoteOpacity(F + H + F + 500, F, H), 0, "stays 0 past the cycle");
81 assert.equal(quoteOpacity(-100, F, H), 0, "never negative-age light");
82});
83
84test("reading-time guarantee: a fresh quote delays the punch, an old one doesn't", () => {
85 assert.equal(finishWait(null, 4500), 0, "no quote on stage → punch immediately");
86 assert.equal(finishWait(0, 4500), 4500, "a quote that JUST appeared gets its full read");
87 assert.equal(finishWait(1000, 4500), 3500);
88 assert.equal(finishWait(4500, 4500), 0);
89 assert.equal(finishWait(60000, 4500), 0, "a long-displayed quote owes nothing");
90});
91
92test("staged cover: nothing assembled → no dwell owed", () => {
93 assert.equal(coverWait(null, null), 0, "a fast boot fades before any stage enters");
94});
95
96test("staged cover: an entered brand gets its fade plus a beat", () => {
97 assert.equal(coverWait(0, null), 850, "just entered: full fade (350) + dwell (500)");
98 assert.equal(coverWait(600, null), 250);
99 assert.equal(coverWait(850, null), 0, "dwell spent — exactly when the quote's stage may open");
100 assert.equal(coverWait(60000, null), 0);
101});
102
103test("staged cover: an entered quote gets its fade plus a read, subsuming the brand", () => {
104 assert.equal(coverWait(9999, 0), 3200, "just dealt: full fade (2200) + read (1000)");
105 assert.equal(coverWait(9999, 2200), 1000, "fully faded in — the read is still owed");
106 assert.equal(coverWait(9999, 3200), 0, "read spent — exactly when the smoke's stage may open");
107 assert.equal(coverWait(null, 0), 3200, "the quote's guarantee stands on its own");
108});
109
110test("validQuoteLib accepts only a real library", () => {
111 const good = Array.from({ length: 25 }, (_, i) => [`quote ${i}`, `who ${i}`]);
112 assert.ok(validQuoteLib(good));
113 assert.ok(!validQuoteLib(null), "null");
114 assert.ok(!validQuoteLib("[]"), "a string");
115 assert.ok(!validQuoteLib([]), "empty");
116 assert.ok(!validQuoteLib(good.slice(0, 10)), "too small to be the library");
117 assert.ok(!validQuoteLib(Array.from({ length: 25 }, () => ["text only"])), "pairs missing an attribution");
118 assert.ok(!validQuoteLib(Array.from({ length: 25 }, () => ["", "who"])), "empty quote text");
119});
120
121test("the shipped library is valid, generous, and deduped", () => {
122 const lib = JSON.parse(readFileSync(new URL("../web/static/quotes.json", import.meta.url), "utf8"));
123 assert.ok(validQuoteLib(lib), "shape: an array of [text, attribution] pairs");
124 assert.ok(lib.length >= 300, `to-a-human enormous: only ${lib.length} quotes`);
125 const texts = new Set(lib.map((q) => q[0]));
126 assert.equal(texts.size, lib.length, "no duplicate quote texts");
127 for (const [text, attr] of lib) {
128 assert.ok(text.length <= 240, `fits the card: "${text.slice(0, 50)}…" is ${text.length} chars`);
129 assert.ok(attr.length <= 120, `attribution fits: "${attr.slice(0, 50)}…"`);
130 }
131});
132
133process.exit(failures ? 1 : 0);
134