@ciphore/radiocli 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +18 -0
- package/README.md +3 -3
- package/dist/cli.js +4 -0
- package/dist/storage/file-lock.js +26 -9
- package/dist/storage/store.js +108 -43
- package/dist/types.js +22 -1
- package/dist/ui/AdaptiveContent.js +3 -14
- package/dist/ui/components/ReceiverVisualRow.js +39 -0
- package/dist/ui/screens/NowPlayingScreen.js +2 -21
- package/dist/ui/theme.js +46 -0
- package/dist/ui/visualizers/boat-lake-kernel.js +325 -0
- package/dist/ui/visualizers/boat-lake.js +161 -0
- package/dist/ui/visualizers/gallop.js +1 -1
- package/dist/ui/visualizers/receiver-style-registry.js +1 -0
- package/dist/ui/visualizers/receiver-visualizers.js +6 -108
- package/dist/ui/visualizers/sumi-ocean.js +183 -0
- package/dist/ui/visualizers/terminal-cell.js +45 -0
- package/docs/boat-lake.md +88 -0
- package/docs/sumi-ocean.md +80 -0
- package/package.json +3 -2
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Scene kernel extracted from https://boat.dev/ on 2026-10-28.
|
|
3
|
+
// Upstream procedural artwork; isolated from React, workers, canvas, fonts and telemetry.
|
|
4
|
+
// See docs/boat-lake.md for provenance and the local extraction.
|
|
5
|
+
// output/boat-lake/scene-0.js
|
|
6
|
+
var e = ["#FEFEFE", "#F8F9FC", "#F1F3F9", "#E9EBF6", "#E0E3F2", "#D1D6EC", "#C2C9E6", "#AAB4DD", "#929FD3", "#7F8ECC", "#6B7CC4", "#5A6DBE", "#495EB7", "#384FB0", "#263FA9", "#223895", "#1E3180", "#1A2A6C", "#162358", "#131D46", "#0F1634"], r = e.map((t2) => (-16777216 | parseInt(t2.slice(5, 7), 16) << 16 | parseInt(t2.slice(3, 5), 16) << 8 | parseInt(t2.slice(1, 3), 16)) >>> 0);
|
|
7
|
+
var m97596_z = class {
|
|
8
|
+
w;
|
|
9
|
+
h;
|
|
10
|
+
ch;
|
|
11
|
+
fg;
|
|
12
|
+
bg;
|
|
13
|
+
constructor(t2, e2, r22 = -1) {
|
|
14
|
+
this.w = t2, this.h = e2, this.ch = Array.from({ length: e2 }, () => Array(t2).fill(" ")), this.fg = Array.from({ length: e2 }, () => Array(t2).fill(-1)), this.bg = Array.from({ length: e2 }, () => Array(t2).fill(r22));
|
|
15
|
+
}
|
|
16
|
+
in(t2, e2) {
|
|
17
|
+
return t2 >= 0 && e2 >= 0 && t2 < this.w && e2 < this.h;
|
|
18
|
+
}
|
|
19
|
+
set(t2, e2, r22, n2, l3) {
|
|
20
|
+
this.in(t2, e2) && (this.ch[e2][t2] = r22, this.fg[e2][t2] = n2, l3 !== void 0 && (this.bg[e2][t2] = l3));
|
|
21
|
+
}
|
|
22
|
+
paint(t2, e2, r22) {
|
|
23
|
+
this.in(t2, e2) && (this.bg[e2][t2] = r22);
|
|
24
|
+
}
|
|
25
|
+
get(t2, e2) {
|
|
26
|
+
return this.in(t2, e2) ? this.ch[e2][t2] : "";
|
|
27
|
+
}
|
|
28
|
+
put(t2, e2, r22, n2, l3) {
|
|
29
|
+
[...r22].forEach((r3, a3) => {
|
|
30
|
+
r3 !== " " ? this.set(t2 + a3, e2, r3, n2, l3) : l3 !== void 0 && this.paint(t2 + a3, e2, l3);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
blit(t2, e2, r22) {
|
|
34
|
+
for (let n2 = 0; n2 < t2.h; n2++)
|
|
35
|
+
for (let l3 = 0; l3 < t2.w; l3++)
|
|
36
|
+
t2.ch[n2][l3] === " " && t2.bg[n2][l3] < 0 || this.set(e2 + l3, r22 + n2, t2.ch[n2][l3], t2.fg[n2][l3], t2.bg[n2][l3] >= 0 ? t2.bg[n2][l3] : void 0);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
// output/boat-lake/scene-2.js
|
|
40
|
+
var r2 = e.map((t2) => [parseInt(t2.slice(1, 3), 16), parseInt(t2.slice(3, 5), 16), parseInt(t2.slice(5, 7), 16)]), n = (t2) => (t2 /= 255) <= 0.04045 ? t2 / 12.92 : ((t2 + 0.055) / 1.055) ** 2.4, l = (t2) => 255 * (t2 <= 31308e-7 ? 12.92 * t2 : 1.055 * t2 ** (1 / 2.4) - 0.055), a = r2.map((t2) => t2.map(n)), o = [];
|
|
41
|
+
r2.forEach((t2, e2) => o.push([t2, " ", -1, e2]));
|
|
42
|
+
var h = [[0.25, "\u2591"], [0.5, "\u2592"], [0.75, "\u2593"]];
|
|
43
|
+
for (let t2 = 0; t2 < r2.length; t2++)
|
|
44
|
+
for (let e2 = 0; e2 < r2.length; e2++)
|
|
45
|
+
if (!(t2 === e2 || Math.abs(t2 - e2) > 1))
|
|
46
|
+
for (let [r22, n2] of h) {
|
|
47
|
+
let h22 = a[t2].map((t3, n3) => l(t3 * (1 - r22) + a[e2][n3] * r22));
|
|
48
|
+
o.push([h22, n2, e2, t2]);
|
|
49
|
+
}
|
|
50
|
+
var m = (t2) => r2[t2];
|
|
51
|
+
function b(t2, e2, r22) {
|
|
52
|
+
let n2 = 374761393 * t2 + 668265263 * e2 + 2147483647 * r22 | 0;
|
|
53
|
+
return (((n2 = Math.imul(n2 ^ n2 >>> 13, 1274126177)) ^ n2 >>> 16) >>> 0) / 4294967295;
|
|
54
|
+
}
|
|
55
|
+
function L(t2, e2, r22 = 0) {
|
|
56
|
+
let n2 = Math.floor(t2), l22 = Math.floor(e2), a22 = t2 - n2, o22 = e2 - l22, h22 = a22 * a22 * (3 - 2 * a22), i2 = o22 * o22 * (3 - 2 * o22), f2 = b(n2, l22, r22), u2 = b(n2 + 1, l22, r22), M2 = b(n2, l22 + 1, r22);
|
|
57
|
+
return f2 + (u2 - f2) * h22 + (M2 - f2) * i2 + (f2 - u2 - M2 + b(n2 + 1, l22 + 1, r22)) * h22 * i2;
|
|
58
|
+
}
|
|
59
|
+
var z = (t2, e2, r22 = 0) => 0.55 * L(t2, e2, r22) + 0.3 * L(2.1 * t2, 2.1 * e2, r22 + 1) + 0.15 * L(4.3 * t2, 4.3 * e2, r22 + 2);
|
|
60
|
+
var H = [[43, 338], [240, 0], [322, 240], [244, 338]], W = [[282, 338], [444, 126], [455, 132], [498, 338]], A = [[0, 361], [498, 361], [498, 412], [465, 412], [465, 462], [84, 462], [8, 412], [0, 404]];
|
|
61
|
+
function R(t2, e2, r22) {
|
|
62
|
+
let n2 = !1;
|
|
63
|
+
for (let l22 = 0, a22 = t2.length - 1; l22 < t2.length; a22 = l22++) {
|
|
64
|
+
let [o22, h22] = t2[l22], [i2, f2] = t2[a22];
|
|
65
|
+
h22 > r22 != f2 > r22 && e2 < (i2 - o22) * (r22 - h22) / (f2 - h22) + o22 && (n2 = !n2);
|
|
66
|
+
}
|
|
67
|
+
return n2;
|
|
68
|
+
}
|
|
69
|
+
function E(t2, e2, r22) {
|
|
70
|
+
let n2 = t2.h / 462, l22 = (e2 - t2.x) / n2 + 249, a22 = (r22 - t2.y) / n2 + 462;
|
|
71
|
+
return l22 < 0 || l22 > 498 || a22 < 0 || a22 > 462 ? 0 : R(A, l22, a22) ? 3 : R(H, l22, a22) ? 1 : 2 * !!R(W, l22, a22);
|
|
72
|
+
}
|
|
73
|
+
var F = [[0, 0, 0], m(0), m(5), m(3)];
|
|
74
|
+
function k(t2, e2, r22) {
|
|
75
|
+
let n2 = 8 * t2, l22 = 16 * e2, a22 = r22 != null ? l22 - 16 * r22 : Math.round(0.42 * l22);
|
|
76
|
+
return { W: n2, H: l22, hz: a22, mx: Math.round(n2 - Math.max(0.2 * n2, Math.min(340, 0.26 * n2))), my: Math.round(a22 * (a22 < 200 ? 0.6 : 0.36)) };
|
|
77
|
+
}
|
|
78
|
+
function v(t2) {
|
|
79
|
+
let e2 = 2 * t2 - 3, r22 = Math.round(0.7 * e2), n2 = e2 + 3 + r22, l22 = n2 + 3, a22 = [];
|
|
80
|
+
for (let t3 = 0; t3 < e2; t3++) {
|
|
81
|
+
let o22 = Array(l22).fill("."), h22 = Math.max(0, Math.round((e2 - 1) * ((t3 + 1) / e2) ** 0.78));
|
|
82
|
+
for (let t4 = e2 - h22; t4 <= e2; t4++)
|
|
83
|
+
o22[t4] = "L";
|
|
84
|
+
let i2 = t3 - (e2 - r22);
|
|
85
|
+
if (i2 >= 0)
|
|
86
|
+
for (let t4 = n2 - i2; t4 <= n2; t4++)
|
|
87
|
+
o22[t4] = "R";
|
|
88
|
+
a22.push(o22.join(""));
|
|
89
|
+
}
|
|
90
|
+
return a22.push("H".repeat(l22), ".." + "H".repeat(l22 - 4) + "..", ".".repeat(l22)), a22;
|
|
91
|
+
}
|
|
92
|
+
var I = ["", "\u2598", "\u259D", "\u2580", "\u2596", "\u258C", "\u259E", "\u259B", "\u2597", "\u259A", "\u2590", "\u259C", "\u2584", "\u2599", "\u259F", "\u2588"];
|
|
93
|
+
function C(t2, r22) {
|
|
94
|
+
let n2 = Math.ceil(Math.max(...t2.map((t3) => t3.length)) / 2), l22 = Math.ceil(t2.length / 2), a22 = new m97596_z(n2, l22);
|
|
95
|
+
for (let e2 = 0; e2 < l22; e2++)
|
|
96
|
+
for (let l3 = 0; l3 < n2; l3++) {
|
|
97
|
+
let n3 = [t2[2 * e2]?.[2 * l3], t2[2 * e2]?.[2 * l3 + 1], t2[2 * e2 + 1]?.[2 * l3], t2[2 * e2 + 1]?.[2 * l3 + 1]].map((t3) => t3 && t3 in r22 ? r22[t3] : -1), o22 = /* @__PURE__ */ new Map();
|
|
98
|
+
if (n3.forEach((t3) => {
|
|
99
|
+
t3 >= 0 && o22.set(t3, (o22.get(t3) ?? 0) + 1);
|
|
100
|
+
}), !o22.size)
|
|
101
|
+
continue;
|
|
102
|
+
let [h22, i2] = [...o22.entries()].sort((t3, e3) => e3[1] - t3[1]).map((t3) => t3[0]), f2 = n3.reduce((t3, e3, r3) => t3 | (e3 === h22 || e3 >= 0 && e3 !== i2 ? 1 << r3 : 0), 0);
|
|
103
|
+
a22.set(l3, e2, I[f2], h22, i2 ?? void 0);
|
|
104
|
+
}
|
|
105
|
+
return a22;
|
|
106
|
+
}
|
|
107
|
+
var D = { 2: ["...L....", "..LL.R..", ".HHHHHH.", "........"], 3: [".....L....", "....LL....", "...LLL.R..", "..LLLL.RR.", ".HHHHHHHHH", ".........."], 4: ["......L.....", ".....LL.....", "....LLL.R...", "...LLLL.RR..", "..LLLLL.RRR.", "HHHHHHHHHHHH", ".HHHHHHHHHH.", "............"] };
|
|
108
|
+
function P(t2, e2, r22, n2, l22 = 0) {
|
|
109
|
+
let a22 = D[t2] ?? v(t2), o22 = a22.filter((t3) => /[LR]/.test(t3)).length, h22 = 0, i2 = a22.map((t3) => {
|
|
110
|
+
if (!/[LR]/.test(t3))
|
|
111
|
+
return t3;
|
|
112
|
+
let e3 = h22++ / Math.max(1, o22);
|
|
113
|
+
return t3.replace(/L/g, "abc"[Math.min(2, Math.floor(3 * e3))]).replace(/R/g, "de"[Math.min(1, Math.floor(2 * e3))]);
|
|
114
|
+
}), f2 = (t3) => Math.max(0, Math.min(20, t3 + l22));
|
|
115
|
+
return C(i2, { a: f2(e2[0]), b: f2(e2[1]), c: f2(e2[2]), d: f2(r22[0]), e: f2(r22[1]), H: f2(n2) });
|
|
116
|
+
}
|
|
117
|
+
var m89369_U = function (t2, e2) {
|
|
118
|
+
let r22 = v(t2), n2 = 2 * t2 - 3, l22 = r22.filter((t3) => /[LR]/.test(t3)).length;
|
|
119
|
+
return C(r22.map((t3, e3) => t3.includes("H") || e3 >= l22 ? t3 : [...t3].map((t4, r3) => r3 === n2 ? "M" : e3 >= l22 - 2 && r3 >= n2 - Math.round(0.8 * l22) && r3 <= n2 ? "L" : ".").join("")), { M: e2[0], L: e2[1], R: Math.min(20, e2[1] + 2), H: e2[2] });
|
|
120
|
+
};
|
|
121
|
+
var m89369_V = function (t2) {
|
|
122
|
+
let e2 = [], r22 = Math.ceil(t2.hz / 16), n2 = t2.H / 16;
|
|
123
|
+
return [{ row: r22 + 4, h: 3, n: Math.max(2, Math.round(t2.W / 420)) }, { row: r22 + 9, h: 4, n: Math.max(1, Math.round(t2.W / 600)) }, { row: n2 - 1 - (t2.fade ?? 0), h: 5, n: Math.max(1, Math.round(t2.W / 1e3)) }].forEach((r3, n3) => {
|
|
124
|
+
for (let l22 = 0; l22 < r3.n; l22++) {
|
|
125
|
+
let a22 = t2.W * (0.2 + 0.62 * ((l22 + 0.5 + 0.37 * n3 + (L(l22, n3, 21) - 0.5) * 0.4) / r3.n % 1));
|
|
126
|
+
Math.abs(a22 - t2.mx) < 16 * r3.h && n3 < 2 || e2.push({ x: 8 * Math.round(a22 / 8), y: (r3.row - (n3 < 2 ? Math.round(2 * L(l22, n3, 23)) : 0)) * 16, h: 16 * r3.h });
|
|
127
|
+
}
|
|
128
|
+
}), e2;
|
|
129
|
+
};
|
|
130
|
+
// output/boat-lake/scene-1.js
|
|
131
|
+
var l2 = 2 * Math.PI, a2 = [[-1, { zen: 16.6, hor: 12.5, horFar: 14, band: 0.16, glow: 0, glowR: 0.3, cloud: 17, cloudLit: 14, star: 1, land: 17.5, landLit: 15.5, pine: 19, pineLit: 17, deep: 18.6, far: 16, fres: 0.55, crest: -2.4, trough: 1, road: 4, roadA: 0.8, sail: 6, hull: 17 }], [-0.2, { zen: 16.6, hor: 11, horFar: 14, band: 0.14, glow: 2, glowR: 0.4, cloud: 16.5, cloudLit: 13, star: 0.85, land: 17.5, landLit: 16, pine: 19, pineLit: 17.5, deep: 18.4, far: 15, fres: 0.6, crest: -2.4, trough: 1, road: 4.5, roadA: 0.8, sail: 6, hull: 17 }], [-0.06, { zen: 16.2, hor: 5, horFar: 12.5, band: 0.08, glow: 7, glowR: 0.45, cloud: 15, cloudLit: 6, star: 0.25, land: 18, landLit: 16.5, pine: 19, pineLit: 18, deep: 17.6, far: 11, fres: 0.75, crest: -2.6, trough: 1, road: 3, roadA: 0.6, sail: 5, hull: 16.5 }], [0.03, { zen: 15.4, hor: 1, horFar: 10.5, band: 0.06, glow: 9, glowR: 0.4, cloud: 14, cloudLit: 2, star: 0, land: 17.5, landLit: 13.5, pine: 18.8, pineLit: 15.5, deep: 16.6, far: 7, fres: 0.8, crest: -2.8, trough: 1.2, road: 0, roadA: 1, sail: 3, hull: 16 }], [0.16, { zen: 14.6, hor: 1.5, horFar: 8, band: 0.08, glow: 8, glowR: 0.35, cloud: 12, cloudLit: 3, star: 0, land: 15.5, landLit: 10.5, pine: 18, pineLit: 14, deep: 15.6, far: 6.5, fres: 0.75, crest: -2.8, trough: 1.2, road: 0, roadA: 1, sail: 2, hull: 15.5 }], [0.42, { zen: 13.9, hor: 4, horFar: 5.5, band: 0.12, glow: 5, glowR: 0.25, cloud: 9, cloudLit: 3, star: 0, land: 13.5, landLit: 9, pine: 16.5, pineLit: 12.5, deep: 14, far: 7, fres: 0.7, crest: -2.6, trough: 1.2, road: 1, roadA: 0.9, sail: 1, hull: 14.5 }], [1, { zen: 13.6, hor: 4.5, horFar: 5, band: 0.14, glow: 4, glowR: 0.2, cloud: 8, cloudLit: 3, star: 0, land: 13, landLit: 8.5, pine: 16, pineLit: 12, deep: 13.6, far: 7, fres: 0.7, crest: -2.6, trough: 1.2, road: 1.5, roadA: 0.9, sail: 1, hull: 14.5 }]], o2 = (t2) => t2 * t2 * (3 - 2 * t2), h2 = (t2, e2 = 0, r22 = 1) => t2 < e2 ? e2 : t2 > r22 ? r22 : t2, i = (t2, e2, r22) => o2(h2((r22 - t2) / (e2 - t2))), f = (t2, e2, r22) => t2 + (e2 - t2) * r22, u = /* @__PURE__ */ new WeakMap();
|
|
132
|
+
function M(t2, e2) {
|
|
133
|
+
let n2 = u.get(t2);
|
|
134
|
+
if (!n2) {
|
|
135
|
+
n2 = new Float32Array(t2.W + 200);
|
|
136
|
+
for (let e3 = 0; e3 < n2.length; e3++)
|
|
137
|
+
n2[e3] = Math.max(t2.hz - 48, t2.hz + 4 - (z((e3 - 100) / 300, 0.5, 5) - 0.45) * 180);
|
|
138
|
+
u.set(t2, n2);
|
|
139
|
+
}
|
|
140
|
+
let l22 = Math.round(e2) + 100;
|
|
141
|
+
return n2[l22 < 0 ? 0 : l22 >= n2.length ? n2.length - 1 : l22];
|
|
142
|
+
}
|
|
143
|
+
var s = /* @__PURE__ */ new WeakMap();
|
|
144
|
+
function c(t2) {
|
|
145
|
+
let e2 = s.get(t2);
|
|
146
|
+
if (e2)
|
|
147
|
+
return e2;
|
|
148
|
+
if (e2 = [], !t2.full)
|
|
149
|
+
return s.set(t2, e2), e2;
|
|
150
|
+
let n2 = (n3, l22, a22, o22, h22, i2) => {
|
|
151
|
+
let f2 = l22;
|
|
152
|
+
for (let l3 = 0; l3 < n3; l3++) {
|
|
153
|
+
// Receiver adaptation: keep pine tips inside short scenes instead of clipping a fixed 64px tree.
|
|
154
|
+
let u2 = Math.max(Math.min(64, t2.hz * (0.54 + 0.3 * L(l3 + 0.5, 2, h22))), o22 * (0.62 + 0.38 * L(l3 + 0.5, 2, h22)) * (1 - l3 / n3 * 0.55));
|
|
155
|
+
e2.push({ x: f2, top: t2.hz + 10 - u2, base: t2.hz + 12, w: i2 }), f2 += a22 * u2 * (0.18 + 0.22 * L(l3 + 0.5, 3, h22));
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
return n2(Math.max(3, Math.round(t2.W / 360)), 10, 1, Math.min(0.36 * t2.hz, 130), 7, 9), n2(Math.max(4, Math.round(t2.W / 240)), t2.W - 12, -1, 0.66 * Math.min(t2.hz, 300), 8, 11), s.set(t2, e2), e2;
|
|
159
|
+
}
|
|
160
|
+
function d(t2, e2, r22, n2) {
|
|
161
|
+
if (r22 < t2.top || r22 > t2.base)
|
|
162
|
+
return 0;
|
|
163
|
+
let l22 = r22 - t2.top, a22 = 0.72 + 0.28 * (l22 / (1.1 * t2.w) % 1), o22 = 0.24 * l22 * a22 + 0.6, h22 = e2 - t2.x;
|
|
164
|
+
return Math.abs(h22) > o22 ? +(1 > Math.abs(h22) && r22 > t2.base - 6) : (n2 > t2.x ? h22 > 0.15 * o22 : h22 < -(0.15 * o22)) && Math.abs(h22) > o22 - 3.2 ? 2 : 1;
|
|
165
|
+
}
|
|
166
|
+
var p = /* @__PURE__ */ new WeakMap();
|
|
167
|
+
function w(t2, e2, n2, l22, a22 = !1) {
|
|
168
|
+
let o22 = h2(1 - l22 / t2.hz), u2 = Math.exp(-(((n2 - e2.sx) / (0.42 * t2.W)) ** 2)), M2 = f(e2.horFar, e2.hor, u2), s2 = e2.zen - 1.4 * (1 - o22);
|
|
169
|
+
s2 -= (s2 - M2) * Math.exp(-o22 / e2.band);
|
|
170
|
+
let c2 = (n2 - e2.sx) / t2.W, d2 = (l22 - e2.sy) / t2.hz;
|
|
171
|
+
if (s2 -= e2.glow * Math.exp(-(c2 * c2 * 2.2 + d2 * d2) / (e2.glowR * e2.glowR)) * (0.35 + 0.65 * i(0.5, 0, o22)), e2.moonEl > -0.1) {
|
|
172
|
+
let r22 = (n2 - e2.mx) / t2.W, a3 = (l22 - e2.my) / t2.hz;
|
|
173
|
+
s2 -= 2.6 * e2.night * Math.exp(-(r22 * r22 * 3 + a3 * a3) / 0.03);
|
|
174
|
+
}
|
|
175
|
+
if (t2.full || t2.W > 0) {
|
|
176
|
+
let a3 = i(0.5 * t2.W, 0.62 * t2.W, n2);
|
|
177
|
+
if (a3 > 0 && o22 > 0.12 && o22 < 0.7) {
|
|
178
|
+
let h22 = i(0.6, 0.76, z((n2 - 4 * e2.t) / 230, l22 / 15, 3)) * i(0.12, 0.22, o22) * i(0.7, 0.5, o22) * a3;
|
|
179
|
+
if (h22 > 0) {
|
|
180
|
+
let r22 = Math.exp(-(((n2 - e2.lx) / t2.W) ** 2 * 6 + ((l22 - e2.ly) / t2.hz) ** 2 * 2));
|
|
181
|
+
s2 = f(s2, f(e2.cloud, e2.cloudLit, r22), 0.85 * h22);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (t2.full || (s2 = f(14, s2, i(80, 200, l22))), !a22) {
|
|
186
|
+
if (e2.star > 0 && n2 % 8 < 4 && l22 % 16 < 8 && o22 > 0.3) {
|
|
187
|
+
let a3 = Math.floor(n2 / 8), h22 = Math.floor(l22 / 16), i2 = L(1.37 * a3 + 0.5, 2.11 * h22 + 0.5, 91);
|
|
188
|
+
(a3 + h22) % 3 == 0 && i2 > 0.86 && n2 > t2.copyW && L(a3 + 0.5, h22 + 0.5, 93) < e2.star * (o22 - 0.2) && Math.hypot((n2 - e2.mx) / t2.W * 2, (l22 - e2.my) / t2.hz) > 0.22 && (s2 = Math.min(s2, 4 + 6 * L(a3 + 0.5, h22 + 0.5, 95)));
|
|
189
|
+
}
|
|
190
|
+
if (t2.full && n2 < t2.copyW + 520) {
|
|
191
|
+
let e3 = i(t2.copyW + 520, t2.copyW - 60, n2) * i(0.1, 0.22, o22);
|
|
192
|
+
s2 < 13 && (s2 = f(s2, 13, e3));
|
|
193
|
+
}
|
|
194
|
+
e2.sunEl > -0.2 && (s2 = g(s2, n2, l22, e2.sx, e2.sy, 21, 0, 1.5, 0)), e2.moonEl > -0.2 && (s2 = g(s2, n2, l22, e2.mx, e2.my, 18, 1, 3, 1));
|
|
195
|
+
}
|
|
196
|
+
return h2(s2, 0, 20);
|
|
197
|
+
}
|
|
198
|
+
function g(t2, e2, r22, n2, l22, a22, o22, u2, M2) {
|
|
199
|
+
let s2 = Math.hypot(e2 - n2, r22 - l22);
|
|
200
|
+
if (s2 > a22 + 16)
|
|
201
|
+
return t2;
|
|
202
|
+
t2 = Math.min(t2, f(t2, u2 + 2.5, 0.85 * Math.exp(-(((s2 - a22) / 7) ** 2))));
|
|
203
|
+
let c2 = h2((a22 - s2) / 8 + 0.5);
|
|
204
|
+
if (c2 <= 0)
|
|
205
|
+
return t2;
|
|
206
|
+
let d2 = o22 + (u2 - o22) * i(0.5 * a22, a22, s2);
|
|
207
|
+
return M2 && 7 > Math.hypot(e2 - n2 + 6, r22 - l22 + 2) && (d2 = 3.5), f(t2, Math.min(t2, d2), c2);
|
|
208
|
+
}
|
|
209
|
+
function y(t2, e2, n2, l22) {
|
|
210
|
+
let a22 = (function (t3, e3, n3) {
|
|
211
|
+
if (n3 >= t3.hz)
|
|
212
|
+
return 0;
|
|
213
|
+
let l3 = Math.round(e3 / 16), a3 = 16 * l3, o3 = t3.hz - M(t3, a3);
|
|
214
|
+
if (o3 < 6 || t3.hz - M(t3, e3) < 6)
|
|
215
|
+
return 0;
|
|
216
|
+
let i3 = (10 + 12 * L(l3, 1, 11)) * h2((o3 - 3) / 18), f2 = Math.min(M(t3, e3), M(t3, a3) - i3 + 1.4 * Math.abs(e3 - a3));
|
|
217
|
+
return n3 > f2 ? 1 + n3 - f2 : 0;
|
|
218
|
+
})(t2, n2, l22);
|
|
219
|
+
if (!a22)
|
|
220
|
+
return -1;
|
|
221
|
+
if (l22 > t2.hz - 5)
|
|
222
|
+
return f(e2.land, e2.landLit, 0.5);
|
|
223
|
+
let o22 = M(t2, n2 + 40) - M(t2, n2 - 40), i2 = h2((e2.lx > n2 ? o22 : -o22) / 30, -1, 1), u2 = f(e2.land, e2.landLit, 0.25 + 0.25 * i2);
|
|
224
|
+
return a22 < 5 && (u2 = f(u2, e2.landLit, 0.7)), u2;
|
|
225
|
+
}
|
|
226
|
+
var b2 = (t2, e2, r22, n2) => Math.sin(1.6 / (h2((r22 - t2.hz) / (t2.H - t2.hz)) + 0.12) + e2 / t2.W * 2.1 + 1e3 * n2 / 4200 * l2);
|
|
227
|
+
function L2(t2, e2, r22, n2) {
|
|
228
|
+
if (n2 < t2.hz) {
|
|
229
|
+
for (let l22 of c(t2)) {
|
|
230
|
+
let t3 = d(l22, r22, n2, e2.lx);
|
|
231
|
+
if (t3)
|
|
232
|
+
return t3 === 2 ? e2.pineLit : e2.pine;
|
|
233
|
+
}
|
|
234
|
+
return -1;
|
|
235
|
+
}
|
|
236
|
+
for (let [l22, a22] of (function (t3) {
|
|
237
|
+
let e3 = p.get(t3);
|
|
238
|
+
if (!e3) {
|
|
239
|
+
let r3 = c(t3);
|
|
240
|
+
e3 = [r3.filter((e4) => e4.x < t3.W / 2).map((t4) => t4.x), r3.filter((e4) => e4.x >= t3.W / 2).map((t4) => t4.x)].filter((t4) => t4.length).map((t4) => [Math.min(...t4), Math.max(...t4)]), p.set(t3, e3);
|
|
241
|
+
}
|
|
242
|
+
return e3;
|
|
243
|
+
})(t2))
|
|
244
|
+
if (r22 > l22 - 26 + (n2 - t2.hz) * 1.4 && r22 < a22 + 26 - (n2 - t2.hz) * 1.4 && n2 < t2.hz + 16)
|
|
245
|
+
return e2.pine + 0.5;
|
|
246
|
+
return -1;
|
|
247
|
+
}
|
|
248
|
+
function z2(t2, e2, l22, a22, o22) {
|
|
249
|
+
var u2;
|
|
250
|
+
let M2, s2, p2, m2, x2;
|
|
251
|
+
if (o22 < t2.hz)
|
|
252
|
+
return -1;
|
|
253
|
+
let g2 = c(t2), L22 = e2.t, z22 = h2((o22 - t2.hz) / (t2.H - t2.hz)), H22 = b2(t2, a22, o22, L22) * i(0.02, 0.3, z22), W22 = H22 * (1.5 + 6 * z22), A22 = 2 * t2.hz - o22 + H22 * (3 + 10 * z22), R22 = w(t2, e2, a22 + W22, A22, !0), E22 = y(t2, e2, a22 + W22, A22);
|
|
254
|
+
for (let t3 of (E22 >= 0 && (R22 = E22), g2))
|
|
255
|
+
if (d(t3, a22 + W22, A22, e2.lx)) {
|
|
256
|
+
R22 = f(e2.pine, R22, 0.3);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
for (let t3 of l22) {
|
|
260
|
+
if (t3.off || o22 < t3.y || o22 > t3.y + t3.h || Math.abs(a22 - t3.x) > 0.7 * t3.h)
|
|
261
|
+
continue;
|
|
262
|
+
let l3 = E(t3, a22 + W22, 2 * t3.y - o22);
|
|
263
|
+
if (l3 && Math.sin(0.55 * o22 + 3 * L(a22 / 40, o22 / 8, 5)) > -0.2) {
|
|
264
|
+
R22 = f(R22, l3 === 3 ? e2.hull : e2.sail + 2 * (l3 === 2), 0.5);
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
let F22 = f(e2.far, e2.deep, Math.pow(z22, 0.7)), k22 = f(F22, R22, e2.fres * Math.pow(1 - z22, 1.3));
|
|
269
|
+
k22 -= 0.4 * H22;
|
|
270
|
+
let Q2 = (u2 = 0.78 - 0.03 * z22 - 0.05 * H22, M2 = Math.floor(o22 / 8), s2 = 16 + 60 * z22, m2 = (p2 = (t3) => L((a22 + L22 * (4 + 10 * z22) * (1 & t3 ? 1 : -0.6)) / s2, 0.05 * L22 + (7 & t3) * 3.1, 131 + 17 * t3))(M2), x2 = p2(M2 - 1), m2 > u2 && x2 <= u2 ? 1 : x2 > u2 && p2(M2 - 2) <= u2 ? -1 : 0), v22 = t2.full ? 1 : 0.6;
|
|
271
|
+
if (Q2 > 0 ? k22 += e2.crest * (0.4 + 0.3 * z22) * v22 : Q2 < 0 && (k22 += 0.6 * e2.trough * v22), e2.lEl > -0.05) {
|
|
272
|
+
let t3 = Math.max(0, e2.lEl), n2 = (7 + 70 * z22) * (0.7 + 0.9 * t3), l3 = Math.exp(-(((a22 - e2.lx) / n2) ** 2));
|
|
273
|
+
if (l3 > 0.02) {
|
|
274
|
+
let n3 = l3 * i(0, 0.35, z22 + 0.3 - 0.5 * t3) * e2.roadA * (e2.sunEl > -0.05 ? 1 : 0.85) * i(-0.05, 0.03, e2.lEl), h22 = Q2 > 0 ? 1 : 0.6 * (Q2 === 0 && L(a22 / 10, 1.3 * Math.floor(o22 / 8) + 0.3 * L22, 9) > 0.62);
|
|
275
|
+
k22 = f(k22, e2.road, n3 * h22);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return t2.fade && (k22 = f(k22, 0, i(t2.H - (t2.fade + 1) * 16, t2.H - 40, Math.min(t2.H - 40, o22 + (L(a22 / 160, 3.3, 17) - 0.5) * 22 + (L(a22 / 36, 7.1, 19) - 0.5) * 24)))), h2(k22, 0, 20);
|
|
279
|
+
}
|
|
280
|
+
function H2(t2, e2, r22 = 1) {
|
|
281
|
+
let n2 = (t3) => Math.max(0, Math.min(20, Math.floor(t3 + e2))), l22 = t2.back * i(0.5, 0.15, r22) > 0.5 ? 15 : t2.sail;
|
|
282
|
+
return [n2(l22), n2(l22 + 1.6), n2(l22 + 3.2), n2(l22 + 1), n2(l22 + 2.4), n2(t2.hull)];
|
|
283
|
+
}
|
|
284
|
+
var W2 = /* @__PURE__ */ new Map();
|
|
285
|
+
function A2(t2, e2) {
|
|
286
|
+
let r22 = `${t2.h},${+!!t2.off},${e2.join(",")}`, l22 = W2.get(r22);
|
|
287
|
+
return l22 || (l22 = t2.off ? m89369_U(t2.h / 16, [e2[1], e2[0], e2[5]]) : P(t2.h / 16, e2.slice(0, 3), e2.slice(3, 5), e2[5]), W2.size > 300 && W2.clear(), W2.set(r22, l22)), l22;
|
|
288
|
+
}
|
|
289
|
+
var R2 = e.map((t2) => {
|
|
290
|
+
let e2 = [1, 3, 5].map((e3) => parseInt(t2.slice(e3, e3 + 2), 16) / 255).map((t3) => t3 <= 0.04045 ? t3 / 12.92 : ((t3 + 0.055) / 1.055) ** 2.4);
|
|
291
|
+
return 0.2126 * e2[0] + 0.7152 * e2[1] + 0.0722 * e2[2];
|
|
292
|
+
}), E2 = e.length, F2 = [];
|
|
293
|
+
for (let t2 = 0; t2 < E2; t2++)
|
|
294
|
+
F2.push({ pos: t2, g: 0, f: -1, b: t2 }), t2 < E2 - 1 && [0.25, 0.5, 0.75].forEach((e2, r22) => F2.push({ pos: (function (t3) {
|
|
295
|
+
for (let e3 = 0; e3 < E2 - 1; e3++)
|
|
296
|
+
if (t3 >= R2[e3 + 1])
|
|
297
|
+
return e3 + (R2[e3] - t3) / (R2[e3] - R2[e3 + 1]);
|
|
298
|
+
return E2 - 1;
|
|
299
|
+
})(R2[t2] * (1 - e2) + R2[t2 + 1] * e2), g: 16 + r22, f: t2 + 1, b: t2 }));
|
|
300
|
+
var k2 = (() => {
|
|
301
|
+
let t2 = [1, 0, 3, 2], e2 = [2, 3, 0, 1], r22 = [];
|
|
302
|
+
for (let n2 = 0; n2 < 4; n2++)
|
|
303
|
+
for (let l22 = 0; l22 < 4; l22++)
|
|
304
|
+
r22.push(l22 + n2 & 1 ? l22 === t2[n2] ? 0.17 : 0.33 : l22 === e2[n2] ? 0.67 : 0.83);
|
|
305
|
+
return r22;
|
|
306
|
+
})(), Q = new Float64Array(4), v2 = new Int8Array(4);
|
|
307
|
+
var m59908_F = function (t2, e2, r22) {
|
|
308
|
+
return r22.map((r3, n2) => {
|
|
309
|
+
let l22 = A2(r3, H2(e2, (0.618 * n2 + 0.13) % 1, h2((r3.y - t2.hz) / (t2.H - t2.hz)))), a22 = r3.h >= 64 ? 4 : 2;
|
|
310
|
+
return { g: l22, cx: Math.round(r3.x / 8 - l22.w / 2), cy: (r3.y - r3.h) / 16, dy: -b2(t2, r3.x, r3.y, e2.t) * a22, y: r3.y };
|
|
311
|
+
}).sort((t3, e3) => t3.y - e3.y);
|
|
312
|
+
};
|
|
313
|
+
var m59908_z = function (t2, e2, r22) {
|
|
314
|
+
let n2, u2, M2, s2 = l2 * (n2 = (e2 % 1 + 1) % 1) - 0.25 * Math.sin(2 * l2 * n2), { cx: c2, rx: d2, ry: p2 } = (u2 = Math.min(t2.copyW + 40, 0.6 * t2.W), M2 = t2.full ? u2 + (t2.W - u2) * 0.3 : 0.5 * t2.W, { cx: M2, rx: t2.full ? (t2.W - u2) * 0.24 : 0.36 * t2.W, ry: t2.full ? 0.72 * t2.hz : Math.max(16, t2.hz - 96 - 21 - 4) }), m2 = Math.sin(s2), x2 = c2 - d2 * Math.cos(s2), w2 = t2.hz - p2 * m2, g2 = c2 + d2 * Math.cos(s2), y2 = t2.hz + p2 * m2, b22 = (function (t3) {
|
|
315
|
+
let e3 = 1;
|
|
316
|
+
for (; e3 < a2.length - 1 && t3 > a2[e3][0];)
|
|
317
|
+
e3++;
|
|
318
|
+
let [r3, n3] = a2[e3 - 1], [l22, i2] = a2[e3], u3 = o2(h2((t3 - r3) / (l22 - r3))), M3 = {};
|
|
319
|
+
for (let t4 in n3)
|
|
320
|
+
M3[t4] = f(n3[t4], i2[t4], u3);
|
|
321
|
+
return M3;
|
|
322
|
+
})(m2), L22 = m2 > -0.05;
|
|
323
|
+
return { ...b22, p: e2, t: r22, sx: x2, sy: w2, sunEl: m2, mx: g2, my: y2, moonEl: -m2, lx: L22 ? x2 : g2, ly: L22 ? w2 : y2, lEl: L22 ? m2 : -m2, night: i(-0.02, -0.25, m2), back: i(0.4, 0.12, m2) * i(-0.1, 0, m2) };
|
|
324
|
+
};
|
|
325
|
+
export { E as boatShape, m89369_V as fleet, y as land, k as layout, m59908_z as lighting, e as palette, L2 as pines, w as sky, z2 as water };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { encodeTerminalCell } from './terminal-cell.js';
|
|
2
|
+
import * as kernel from './boat-lake-kernel.js';
|
|
3
|
+
const colors = kernel.palette;
|
|
4
|
+
const mono = colors.map((_, index) => {
|
|
5
|
+
const level = Math.round(248 - index * 10.8).toString(16).padStart(2, '0');
|
|
6
|
+
return `#${level}${level}${level}`;
|
|
7
|
+
});
|
|
8
|
+
// Bounded by viewport count, independent of playback duration. No frame history.
|
|
9
|
+
const scenes = new Map();
|
|
10
|
+
function landscape(layout, light, x, y) {
|
|
11
|
+
const pine = kernel.pines(layout, light, x, y);
|
|
12
|
+
if (pine >= 0)
|
|
13
|
+
return pine;
|
|
14
|
+
const land = kernel.land(layout, light, x, y);
|
|
15
|
+
return land >= 0 ? land : kernel.sky(layout, light, x, y);
|
|
16
|
+
}
|
|
17
|
+
function sceneFor(width, height) {
|
|
18
|
+
const key = `${width}:${height}`;
|
|
19
|
+
const cached = scenes.get(key);
|
|
20
|
+
if (cached)
|
|
21
|
+
return cached;
|
|
22
|
+
// Keep native-sized artwork, rather than shrinking a 160×42 scene into
|
|
23
|
+
// every receiver. Short receivers need larger sails and less empty sky.
|
|
24
|
+
const columns = Math.max(20, width);
|
|
25
|
+
const rows = Math.max(8, height);
|
|
26
|
+
const layout = { ...kernel.layout(columns, rows, Math.round(rows * 0.6)), full: true, copyW: 0, fade: 0 };
|
|
27
|
+
layout.hz = Math.round(layout.H * (height <= 7 ? 0.36 : 0.45));
|
|
28
|
+
const light = kernel.lighting(layout, 0.44, 0);
|
|
29
|
+
const samples = new Float32Array(width * height * 8).fill(-1);
|
|
30
|
+
for (let y = 0; y < height * 4; y++) {
|
|
31
|
+
const vy = (y + 0.5) * layout.H / (height * 4);
|
|
32
|
+
for (let x = 0; x < width * 2; x++) {
|
|
33
|
+
const vx = (x + 0.5) * layout.W / (width * 2);
|
|
34
|
+
const pine = kernel.pines(layout, light, vx, vy);
|
|
35
|
+
if (pine >= 0 || vy < layout.hz)
|
|
36
|
+
samples[y * width * 2 + x] = pine >= 0 ? pine : landscape(layout, light, vx, vy);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const water = layout.H - layout.hz;
|
|
40
|
+
const size = (fraction, minimum) => Math.round(Math.max(minimum, layout.H * fraction) / 16) * 16;
|
|
41
|
+
const fleet = [{ x: layout.W * 0.41, y: layout.H - 16, h: size(0.18, width < 24 ? 48 : 64) }];
|
|
42
|
+
if (width >= 50 && height >= 7)
|
|
43
|
+
fleet.unshift({ x: layout.W * 0.30, y: layout.hz + water * 0.37, h: size(0.11, 32) });
|
|
44
|
+
if (width >= 70 && height >= 7)
|
|
45
|
+
fleet.unshift({ x: layout.W * 0.81, y: layout.hz + water * 0.18, h: 32 });
|
|
46
|
+
// The expensive reflected scene is stable at fixed lighting. Preserve its
|
|
47
|
+
// eight-sample detail, then animate small horizontal refractions at runtime.
|
|
48
|
+
const reflected = new Float32Array(samples.length);
|
|
49
|
+
for (let y = 0; y < height * 4; y++)
|
|
50
|
+
for (let x = 0; x < width * 2; x++) {
|
|
51
|
+
const index = y * width * 2 + x;
|
|
52
|
+
reflected[index] = samples[index] >= 0 ? samples[index] : kernel.water(layout, light, fleet, (x + 0.5) * layout.W / (width * 2), (y + 0.5) * layout.H / (height * 4));
|
|
53
|
+
}
|
|
54
|
+
const cells = new Array(width * height);
|
|
55
|
+
const cellSamples = new Float32Array(8);
|
|
56
|
+
for (let y = 0; y < height; y++)
|
|
57
|
+
for (let x = 0; x < width; x++) {
|
|
58
|
+
for (let q = 0; q < 8; q++)
|
|
59
|
+
cellSamples[q] = samples[(y * 4 + (q >> 1)) * width * 2 + x * 2 + (q & 1)];
|
|
60
|
+
if (cellSamples.every(value => value >= 0))
|
|
61
|
+
cells[y * width + x] = encodeTerminalCell(cellSamples, x, y, 20);
|
|
62
|
+
}
|
|
63
|
+
const scene = { layout, light, fleet, samples, cells, water: reflected };
|
|
64
|
+
if (scenes.size >= 4)
|
|
65
|
+
scenes.delete(scenes.keys().next().value);
|
|
66
|
+
scenes.set(key, scene);
|
|
67
|
+
return scene;
|
|
68
|
+
}
|
|
69
|
+
/** At four rows, subpixel averages cannot retain a sail's silhouette. Draw
|
|
70
|
+
* its two sails and hull on the terminal grid, with transparent water behind. */
|
|
71
|
+
function compactBoat(x, y, width, height) {
|
|
72
|
+
if (width < 12 || height < 3 || height > 6)
|
|
73
|
+
return undefined;
|
|
74
|
+
const left = Math.round(width * 0.41) - 3;
|
|
75
|
+
const column = x - left;
|
|
76
|
+
const hull = height - 1;
|
|
77
|
+
if (y === hull && column >= 0 && column <= 6)
|
|
78
|
+
return { glyph: column === 0 ? '◥' : column === 6 ? '◤' : '▀', fg: 18 };
|
|
79
|
+
if (y === hull - 1 && column >= 1 && column <= 5)
|
|
80
|
+
return { glyph: column === 1 ? '◢' : column === 5 ? '◣' : column === 4 ? '▌' : '█', fg: column === 5 ? 5 : 1 };
|
|
81
|
+
if (height >= 4 && y === hull - 2 && (column === 3 || column === 4))
|
|
82
|
+
return { glyph: column === 3 ? '◢' : '▌', fg: 1 };
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function compactPine(x, y, width, height) {
|
|
86
|
+
if (width < 12 || height < 3 || height > 6 || y > 1)
|
|
87
|
+
return undefined;
|
|
88
|
+
const local = x < width / 2 ? x : x - width + 4;
|
|
89
|
+
if (y === 0 && (local === 1 || local === 2))
|
|
90
|
+
return { glyph: local === 1 ? '◢' : '◣', fg: 18, bg: 14 };
|
|
91
|
+
if (height >= 4 && y === 1 && local >= 0 && local <= 3)
|
|
92
|
+
return { glyph: local === 0 ? '◢' : local === 3 ? '◣' : '█', fg: 18, bg: 14 };
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
/** Boat's lake artwork with viewport-sized scenery and legible compact sails. */
|
|
96
|
+
export function buildBoatLake(pulse, width, height, theme) {
|
|
97
|
+
const scene = sceneFor(width, height);
|
|
98
|
+
const { layout } = scene;
|
|
99
|
+
const time = pulse * 0.08;
|
|
100
|
+
const light = scene.light;
|
|
101
|
+
const fleet = scene.fleet.map((boat, index) => ({ ...boat, y: boat.y + Math.sin(time * 0.6 + index) * 1.5 }));
|
|
102
|
+
const shifts = new Float32Array(height * 4);
|
|
103
|
+
for (let y = 0; y < shifts.length; y++) {
|
|
104
|
+
const depth = Math.max(0, ((y + 0.5) * layout.H / shifts.length - layout.hz) / (layout.H - layout.hz));
|
|
105
|
+
shifts[y] = Math.sin(time * 0.7 + y * 0.65) * (0.3 + 1.2 * depth);
|
|
106
|
+
}
|
|
107
|
+
const palette = theme === 'mono' ? mono : colors;
|
|
108
|
+
const rows = [];
|
|
109
|
+
const samples = new Float32Array(8);
|
|
110
|
+
for (let y = 0; y < height; y++) {
|
|
111
|
+
let text = '';
|
|
112
|
+
const segments = [];
|
|
113
|
+
for (let x = 0; x < width; x++) {
|
|
114
|
+
const vx0 = x * layout.W / width, vy0 = y * layout.H / height;
|
|
115
|
+
const cached = scene.cells[y * width + x];
|
|
116
|
+
const overlapsBoat = height > 6 && fleet.some(boat => vx0 < boat.x + boat.h * 0.55 && vx0 + layout.W / width > boat.x - boat.h * 0.55 && vy0 < boat.y && vy0 + layout.H / height > boat.y - boat.h);
|
|
117
|
+
let cell = cached;
|
|
118
|
+
if (!cell || overlapsBoat) {
|
|
119
|
+
for (let dy = 0; dy < 4; dy++)
|
|
120
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
121
|
+
const sx = x * 2 + dx;
|
|
122
|
+
const sy = y * 4 + dy;
|
|
123
|
+
const vx = (sx + 0.5) * layout.W / (width * 2);
|
|
124
|
+
const vy = (sy + 0.5) * layout.H / (height * 4);
|
|
125
|
+
let value = scene.samples[sy * width * 2 + sx];
|
|
126
|
+
if (value < 0) {
|
|
127
|
+
const sampleX = Math.max(0, Math.min(width * 2 - 1, sx + shifts[sy]));
|
|
128
|
+
const left = Math.floor(sampleX), fraction = sampleX - left;
|
|
129
|
+
const a = scene.water[sy * width * 2 + left];
|
|
130
|
+
const b = scene.water[sy * width * 2 + Math.min(width * 2 - 1, left + 1)];
|
|
131
|
+
value = a + (b - a) * fraction;
|
|
132
|
+
}
|
|
133
|
+
// Rasterize the original sail/hull polygons at the target resolution.
|
|
134
|
+
// Sampling an already quantized sprite erased small sails a second time.
|
|
135
|
+
if (overlapsBoat)
|
|
136
|
+
for (const boat of fleet) {
|
|
137
|
+
if (vy < boat.y - boat.h || vy > boat.y || Math.abs(vx - boat.x) > boat.h * 0.55)
|
|
138
|
+
continue;
|
|
139
|
+
const shape = kernel.boatShape(boat, vx, vy);
|
|
140
|
+
if (shape)
|
|
141
|
+
value = shape === 3 ? light.hull : light.sail + (shape === 2 ? 2 : 0);
|
|
142
|
+
}
|
|
143
|
+
samples[dy * 2 + dx] = value;
|
|
144
|
+
}
|
|
145
|
+
cell = encodeTerminalCell(samples, x, y, 20);
|
|
146
|
+
}
|
|
147
|
+
const detail = compactBoat(x, y, width, height) ?? compactPine(x, y, width, height);
|
|
148
|
+
const { glyph, fg, bg } = detail ? { ...cell, ...detail } : cell;
|
|
149
|
+
const color = palette[fg];
|
|
150
|
+
const backgroundColor = palette[bg];
|
|
151
|
+
text += glyph;
|
|
152
|
+
const previous = segments.at(-1);
|
|
153
|
+
if (previous?.color === color && previous.backgroundColor === backgroundColor)
|
|
154
|
+
previous.text += glyph;
|
|
155
|
+
else
|
|
156
|
+
segments.push({ text: glyph, color, backgroundColor });
|
|
157
|
+
}
|
|
158
|
+
rows.push({ text, color: palette[14], segments });
|
|
159
|
+
}
|
|
160
|
+
return rows;
|
|
161
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { themeAccent } from '../theme.js';
|
|
2
2
|
import { horseStrideFrames, horseStrideHeight, horseStrideWidth } from './horse-stride.js';
|
|
3
|
-
const frames = horseStrideFrames.map(frame =>
|
|
3
|
+
const frames = horseStrideFrames.map(frame => Uint8Array.from(atob(frame), char => char.charCodeAt(0)));
|
|
4
4
|
const brailleBits = [[1, 8], [2, 16], [4, 32], [64, 128]];
|
|
5
5
|
/** One pose per receiver tick. The same scale and torso anchor are used for every pose. */
|
|
6
6
|
export function buildGallop(pulse, width, height, theme) {
|
|
@@ -62,6 +62,7 @@ export const receiverStyleRegistry = [
|
|
|
62
62
|
{ id: 'golden-gate', maxRows: 14, minRows: 7 },
|
|
63
63
|
{ id: 'manhattan', maxRows: 14, minRows: 7 },
|
|
64
64
|
{ id: 'alpine', maxRows: 14, minRows: 7 },
|
|
65
|
+
{ id: 'boat-lake', maxRows: 18, minRows: 7 },
|
|
65
66
|
{ id: 'crossfade', maxRows: 14, minRows: 7 },
|
|
66
67
|
];
|
|
67
68
|
export const receiverStyleNames = receiverStyleRegistry.map(style => style.id);
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { themeAccent, themeContributionColors } from '../theme.js';
|
|
2
|
+
import { buildBoatLake } from './boat-lake.js';
|
|
3
|
+
import { buildSumiOcean } from './sumi-ocean.js';
|
|
2
4
|
import { retroReceiverBuilders } from './retro-receivers.js';
|
|
3
5
|
import { receiverStyleMetadata } from './receiver-style-registry.js';
|
|
4
6
|
const receiverStyleBuilders = {
|
|
5
7
|
...retroReceiverBuilders,
|
|
8
|
+
'boat-lake': buildBoatLake,
|
|
6
9
|
ultracode: buildUltracode,
|
|
7
10
|
'motion-contour': buildMotionContour,
|
|
8
11
|
leds: buildLeds,
|
|
@@ -97,7 +100,9 @@ const microNativeScaleStyles = new Set([
|
|
|
97
100
|
'galaxy',
|
|
98
101
|
'cyclone',
|
|
99
102
|
'fire',
|
|
100
|
-
'stormfront'
|
|
103
|
+
'stormfront',
|
|
104
|
+
'boat-lake',
|
|
105
|
+
'sumi-ocean'
|
|
101
106
|
]);
|
|
102
107
|
function resampleVisualizer(rows, sourceWidth, sourceHeight, targetWidth, targetHeight, fallbackColor) {
|
|
103
108
|
const source = Array.from({ length: sourceHeight }, (_, rowIndex) => visualCellsForLine(rows[rowIndex], sourceWidth, fallbackColor));
|
|
@@ -4078,12 +4083,6 @@ function buildPlanetarium(pulse, width, height, theme) {
|
|
|
4078
4083
|
}
|
|
4079
4084
|
return grid.map(row => lineFromCells(row, accent));
|
|
4080
4085
|
}
|
|
4081
|
-
function receiverArtPalette(theme, colors) {
|
|
4082
|
-
if (theme !== 'mono') {
|
|
4083
|
-
return colors;
|
|
4084
|
-
}
|
|
4085
|
-
return ['#181818', '#303030', '#505050', '#777777', '#9a9a9a', '#bcbcbc', '#dddddd', '#ffffff'];
|
|
4086
|
-
}
|
|
4087
4086
|
function receiverBandEnergy(pulse, startBand, samples = 6) {
|
|
4088
4087
|
let total = 0;
|
|
4089
4088
|
for (let index = 0; index < samples; index += 1) {
|
|
@@ -4091,107 +4090,6 @@ function receiverBandEnergy(pulse, startBand, samples = 6) {
|
|
|
4091
4090
|
}
|
|
4092
4091
|
return total / Math.max(1, samples);
|
|
4093
4092
|
}
|
|
4094
|
-
/** A deep-perspective indigo ocean rendered with restrained sumi marks. */
|
|
4095
|
-
function buildSumiOcean(pulse, width, height, theme) {
|
|
4096
|
-
const accent = themeAccent(theme);
|
|
4097
|
-
const palette = receiverArtPalette(theme, ['#020817', '#06172c', '#092743', '#0d3c60', '#15567d', '#2777a1', '#66a9cc', '#d7edf5']);
|
|
4098
|
-
const grid = emptyMotionGrid(width, height, palette[0] ?? '#020817');
|
|
4099
|
-
const moonX = width * (0.73 + Math.sin(pulse * 0.006) * 0.015);
|
|
4100
|
-
const moonY = height * 0.22;
|
|
4101
|
-
const moonRadius = Math.max(1.1, height * 0.11);
|
|
4102
|
-
for (let y = 0; y < height; y += 1) {
|
|
4103
|
-
for (let x = 0; x < width; x += 1) {
|
|
4104
|
-
const dx = (x - moonX) / 1.9;
|
|
4105
|
-
const dy = y - moonY;
|
|
4106
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
4107
|
-
if (distance <= moonRadius) {
|
|
4108
|
-
grid[y][x] = { text: distance < moonRadius * 0.72 ? '█' : '▓', color: palette[7] ?? accent, bold: distance < moonRadius * 0.55 };
|
|
4109
|
-
}
|
|
4110
|
-
else if (hashNoise(x, y, 0) > 0.992) {
|
|
4111
|
-
grid[y][x] = { text: '·', color: palette[5] ?? accent };
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
}
|
|
4115
|
-
const horizon = 0.4;
|
|
4116
|
-
const layers = [
|
|
4117
|
-
{ base: 0.42, amp: 0.035, cycles: 15, speed: 0.009, color: 2, glyph: '·' },
|
|
4118
|
-
{ base: 0.47, amp: 0.038, cycles: 12, speed: 0.012, color: 2, glyph: '░' },
|
|
4119
|
-
{ base: 0.53, amp: 0.042, cycles: 10, speed: 0.016, color: 3, glyph: '░' },
|
|
4120
|
-
{ base: 0.61, amp: 0.05, cycles: 8, speed: 0.021, color: 3, glyph: '▒' },
|
|
4121
|
-
{ base: 0.71, amp: 0.06, cycles: 6.5, speed: 0.027, color: 4, glyph: '▒' },
|
|
4122
|
-
{ base: 0.83, amp: 0.075, cycles: 5.2, speed: 0.034, color: 5, glyph: '▓' },
|
|
4123
|
-
{ base: 0.96, amp: 0.1, cycles: 4.2, speed: 0.042, color: 5, glyph: '█' }
|
|
4124
|
-
];
|
|
4125
|
-
const bass = receiverBandEnergy(pulse, 2);
|
|
4126
|
-
// The water plane grows darker and more textured toward the viewer. Far
|
|
4127
|
-
// water is quiet and compressed at the horizon; nearby water occupies more
|
|
4128
|
-
// screen space, producing a clear vanishing-depth read.
|
|
4129
|
-
const horizonRow = Math.max(0, Math.round(height * horizon));
|
|
4130
|
-
for (let y = horizonRow; y < height; y += 1) {
|
|
4131
|
-
const depth = (y - horizonRow) / Math.max(1, height - 1 - horizonRow);
|
|
4132
|
-
const colorIndex = Math.min(5, 2 + Math.floor(depth * 4));
|
|
4133
|
-
for (let x = 0; x < width; x += 1) {
|
|
4134
|
-
const texture = hashNoise(x, y, 41);
|
|
4135
|
-
const glyph = texture > 0.975
|
|
4136
|
-
? ' '
|
|
4137
|
-
: depth > 0.68
|
|
4138
|
-
? texture > 0.48 ? '▓' : '▒'
|
|
4139
|
-
: depth > 0.32
|
|
4140
|
-
? texture > 0.62 ? '▒' : '░'
|
|
4141
|
-
: texture > 0.76 ? '░' : '·';
|
|
4142
|
-
grid[y][x] = { text: glyph, color: palette[colorIndex] ?? accent };
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4145
|
-
layers.forEach((layer, layerIndex) => {
|
|
4146
|
-
for (let x = 0; x < width; x += 1) {
|
|
4147
|
-
const nx = x / Math.max(1, width - 1);
|
|
4148
|
-
const phase = nx * layer.cycles * Math.PI * 2 + pulse * layer.speed + layerIndex * 1.9;
|
|
4149
|
-
const ridgeNoise = clampNumber(0.5 +
|
|
4150
|
-
Math.sin(phase) * 0.27 +
|
|
4151
|
-
Math.sin(phase * 1.83 + 0.9) * 0.16 +
|
|
4152
|
-
Math.cos(phase * 3.2 - 0.4) * 0.07, 0, 1);
|
|
4153
|
-
const near = layerIndex / Math.max(1, layers.length - 1);
|
|
4154
|
-
const ridgeY = Math.round(height * (layer.base + layer.amp * (1 - ridgeNoise * 2) - (near > 0.7 ? bass * 0.018 : 0)));
|
|
4155
|
-
const crest = layerIndex < 3 ? '─' : '━';
|
|
4156
|
-
paintCell(grid, x, ridgeY, crest, palette[Math.min(6, layer.color + 1)] ?? accent);
|
|
4157
|
-
if (ridgeY + 1 < height && layerIndex > 1) {
|
|
4158
|
-
paintCell(grid, x, ridgeY + 1, layer.glyph, palette[layer.color] ?? accent);
|
|
4159
|
-
}
|
|
4160
|
-
if (layerIndex > 3 && ridgeY > 0 && (x + layerIndex * 3) % 13 < 3 + Math.floor(near * 3)) {
|
|
4161
|
-
paintCell(grid, x, ridgeY - 1, layerIndex > 5 ? '▀' : '·', palette[6] ?? accent);
|
|
4162
|
-
}
|
|
4163
|
-
}
|
|
4164
|
-
});
|
|
4165
|
-
// A narrow moon path widens toward the foreground, reinforcing the same
|
|
4166
|
-
// perspective even in short terminal viewports.
|
|
4167
|
-
for (let y = horizonRow; y < height; y += 1) {
|
|
4168
|
-
const depth = (y - horizonRow) / Math.max(1, height - 1 - horizonRow);
|
|
4169
|
-
const reflectionHalfWidth = 1 + Math.floor(depth * Math.max(2, width * 0.045));
|
|
4170
|
-
const center = Math.round(moonX + Math.sin(y * 2.1 + pulse * 0.025) * reflectionHalfWidth * 0.45);
|
|
4171
|
-
for (let offset = -reflectionHalfWidth; offset <= reflectionHalfWidth; offset += 1) {
|
|
4172
|
-
if (hashNoise(offset + 30, y, Math.floor(pulse / 12)) > 0.48 + depth * 0.14) {
|
|
4173
|
-
paintCell(grid, center + offset, y, depth > 0.65 ? '━' : '─', palette[6] ?? accent);
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
}
|
|
4177
|
-
// Two distant seabirds keep the horizon alive without making it busy.
|
|
4178
|
-
const birdX = Math.round(width * (0.24 + 0.04 * Math.sin(pulse * 0.01)));
|
|
4179
|
-
const birdY = Math.max(1, Math.round(height * 0.2));
|
|
4180
|
-
paintCell(grid, birdX - 1, birdY, '⌁', palette[6] ?? accent);
|
|
4181
|
-
paintCell(grid, birdX + 2, birdY + 1, '⌁', palette[5] ?? accent);
|
|
4182
|
-
// A blue water plane beneath the block texture removes isolated black gaps
|
|
4183
|
-
// while retaining the foreground-to-horizon depth bands.
|
|
4184
|
-
for (let y = horizonRow; y < height; y += 1) {
|
|
4185
|
-
const depth = (y - horizonRow) / Math.max(1, height - 1 - horizonRow);
|
|
4186
|
-
const backgroundIndex = depth > 0.68 ? 3 : depth > 0.28 ? 2 : 1;
|
|
4187
|
-
const backgroundColor = palette[backgroundIndex] ?? palette[1] ?? accent;
|
|
4188
|
-
for (let x = 0; x < width; x += 1) {
|
|
4189
|
-
const cell = grid[y][x];
|
|
4190
|
-
grid[y][x] = { ...cell, backgroundColor };
|
|
4191
|
-
}
|
|
4192
|
-
}
|
|
4193
|
-
return grid.map(row => lineFromCells(row, accent));
|
|
4194
|
-
}
|
|
4195
4093
|
function rgbColor(red, green, blue) {
|
|
4196
4094
|
const channel = (value) => Math.round(clampNumber(value, 0, 255)).toString(16).padStart(2, '0');
|
|
4197
4095
|
return `#${channel(red)}${channel(green)}${channel(blue)}`;
|