@ciphore/radiocli 0.2.4 → 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.
Files changed (145) hide show
  1. package/CHANGELOG.md +103 -2
  2. package/CONTRIBUTING.md +28 -0
  3. package/README.md +108 -253
  4. package/SECURITY.md +23 -1
  5. package/dist/agent/alarm-service.js +10 -7
  6. package/dist/agent/cli.js +16 -1
  7. package/dist/agent/command-schema.js +47 -0
  8. package/dist/agent/headless-host.js +226 -81
  9. package/dist/agent/launcher.js +2 -1
  10. package/dist/agent/mcp-server.js +10 -5
  11. package/dist/agent/service.js +75 -40
  12. package/dist/agent/session.js +214 -24
  13. package/dist/agent/tui-session.js +213 -0
  14. package/dist/alarms/cli.js +19 -3
  15. package/dist/alarms/occurrence-cache.js +25 -0
  16. package/dist/alarms/runner.js +87 -23
  17. package/dist/alarms/schedule.js +29 -1
  18. package/dist/alarms/scheduler.js +11 -3
  19. package/dist/alarms/system-volume-linux.js +160 -0
  20. package/dist/alarms/system-volume-ownership.js +76 -8
  21. package/dist/alarms/system-volume.js +44 -23
  22. package/dist/cli/arguments.js +212 -0
  23. package/dist/cli/completion.js +44 -0
  24. package/dist/cli/doctor.js +156 -0
  25. package/dist/cli/interactive.js +79 -0
  26. package/dist/cli/listener.js +227 -0
  27. package/dist/cli/output.js +121 -0
  28. package/dist/cli/render-tui.js +34 -0
  29. package/dist/cli.js +182 -199
  30. package/dist/platform/capabilities.js +8 -1
  31. package/dist/platform/desktop-media.js +61 -0
  32. package/dist/platform/diagnostics.js +273 -0
  33. package/dist/platform/json-worker.js +111 -0
  34. package/dist/platform/launch-command.js +9 -6
  35. package/dist/platform/mpris.js +356 -0
  36. package/dist/platform/network.js +69 -1
  37. package/dist/platform/omarchy.js +147 -0
  38. package/dist/platform/packages.js +7 -1
  39. package/dist/platform/runtime.js +1 -1
  40. package/dist/platform/terminals.js +24 -4
  41. package/dist/player/airplay-discovery.js +144 -61
  42. package/dist/player/airplay-policy.js +15 -0
  43. package/dist/player/airplay-process.js +63 -0
  44. package/dist/player/airplay-readiness.js +42 -0
  45. package/dist/player/airplay-sender-health.js +49 -49
  46. package/dist/player/airplay-sender-package.js +5 -0
  47. package/dist/player/airplay-sender-patch.js +102 -4
  48. package/dist/player/airplay-setup.js +51 -0
  49. package/dist/player/airplay-worker.js +97 -41
  50. package/dist/player/backend-install.js +9 -6
  51. package/dist/player/ffmpeg-input.js +37 -0
  52. package/dist/player/mpv-desktop-media.js +194 -0
  53. package/dist/player/mpv-ipc-client.js +51 -2
  54. package/dist/player/player-controller.js +390 -73
  55. package/dist/player/radio-engine.js +282 -0
  56. package/dist/playlists/playlist.js +18 -16
  57. package/dist/providers/cache.js +291 -75
  58. package/dist/providers/discovery-session.js +111 -0
  59. package/dist/providers/provider-manager.js +106 -28
  60. package/dist/providers/radio-browser.js +272 -41
  61. package/dist/providers/radio-garden.js +4 -2
  62. package/dist/setup-playback.js +152 -0
  63. package/dist/setup.js +88 -21
  64. package/dist/storage/file-lock.js +130 -0
  65. package/dist/storage/read-settings.js +33 -0
  66. package/dist/storage/replace-file.js +20 -0
  67. package/dist/storage/repository.js +247 -0
  68. package/dist/storage/station-identity.js +21 -0
  69. package/dist/storage/station-schema.js +27 -0
  70. package/dist/storage/store.js +314 -76
  71. package/dist/types.js +22 -1
  72. package/dist/ui/AdaptiveContent.js +21 -24
  73. package/dist/ui/App.js +708 -454
  74. package/dist/ui/AppContent.js +5 -5
  75. package/dist/ui/airplay-settings.js +12 -5
  76. package/dist/ui/bidi-ranges.js +45 -0
  77. package/dist/ui/components/AdaptiveMarquee.js +16 -9
  78. package/dist/ui/components/ReceiverVisualRow.js +39 -0
  79. package/dist/ui/format.js +37 -4
  80. package/dist/ui/help-content.js +5 -2
  81. package/dist/ui/rtl-output.js +137 -0
  82. package/dist/ui/screen-items.js +10 -4
  83. package/dist/ui/screens/AirPlaySettingsScreen.js +3 -3
  84. package/dist/ui/screens/AlarmsScreen.js +5 -2
  85. package/dist/ui/screens/HelpScreen.js +3 -3
  86. package/dist/ui/screens/NowPlayingScreen.js +2 -21
  87. package/dist/ui/screens/SettingsScreen.js +16 -16
  88. package/dist/ui/screens/StatsScreen.js +1 -1
  89. package/dist/ui/text-direction.js +9 -0
  90. package/dist/ui/theme.js +46 -0
  91. package/dist/ui/use-alarm-tui.js +24 -24
  92. package/dist/ui/use-app-input.js +42 -8
  93. package/dist/ui/use-command-executor.js +21 -14
  94. package/dist/ui/visualizers/boat-lake-kernel.js +325 -0
  95. package/dist/ui/visualizers/boat-lake.js +161 -0
  96. package/dist/ui/visualizers/gallop.js +2 -2
  97. package/dist/ui/visualizers/receiver-style-registry.js +3 -1
  98. package/dist/ui/visualizers/receiver-visualizers.js +6 -108
  99. package/dist/ui/visualizers/sumi-ocean.js +183 -0
  100. package/dist/ui/visualizers/terminal-cell.js +45 -0
  101. package/dist/update-check.js +2 -1
  102. package/docs/THIRD_PARTY_NOTICES.md +6 -4
  103. package/docs/boat-lake.md +88 -0
  104. package/docs/sumi-ocean.md +80 -0
  105. package/package.json +36 -12
  106. package/scripts/build-airplay-native.mjs +22 -0
  107. package/vendor/node-airtunes2/LICENSE +208 -0
  108. package/vendor/node-airtunes2/RADIOCLI_PATCH.md +29 -0
  109. package/vendor/node-airtunes2/README.md +225 -0
  110. package/vendor/node-airtunes2/UPSTREAM.json +50 -0
  111. package/vendor/node-airtunes2/binding.gyp +41 -0
  112. package/vendor/node-airtunes2/lib/ap2_test.js +4 -0
  113. package/vendor/node-airtunes2/lib/atv.js +307 -0
  114. package/vendor/node-airtunes2/lib/atvAuthenticator.js +136 -0
  115. package/vendor/node-airtunes2/lib/audio_out.js +69 -0
  116. package/vendor/node-airtunes2/lib/circular_buffer.js +133 -0
  117. package/vendor/node-airtunes2/lib/config.js +26 -0
  118. package/vendor/node-airtunes2/lib/device_airtunes.js +532 -0
  119. package/vendor/node-airtunes2/lib/devices.js +266 -0
  120. package/vendor/node-airtunes2/lib/ed25519.js +26 -0
  121. package/vendor/node-airtunes2/lib/homekit/credentials.js +86 -0
  122. package/vendor/node-airtunes2/lib/homekit/encryption.js +69 -0
  123. package/vendor/node-airtunes2/lib/homekit/number.js +44 -0
  124. package/vendor/node-airtunes2/lib/homekit/tlv.js +95 -0
  125. package/vendor/node-airtunes2/lib/http.js +189 -0
  126. package/vendor/node-airtunes2/lib/index.d.ts +141 -0
  127. package/vendor/node-airtunes2/lib/index.js +127 -0
  128. package/vendor/node-airtunes2/lib/ntp.js +37 -0
  129. package/vendor/node-airtunes2/lib/num_util.js +21 -0
  130. package/vendor/node-airtunes2/lib/packet_pool.js +43 -0
  131. package/vendor/node-airtunes2/lib/rtsp.js +1807 -0
  132. package/vendor/node-airtunes2/lib/srp.js +158 -0
  133. package/vendor/node-airtunes2/lib/udp_servers.js +214 -0
  134. package/vendor/node-airtunes2/lib/util.js +12 -0
  135. package/vendor/node-airtunes2/package.json +31 -0
  136. package/vendor/node-airtunes2/src/CADebugMacros.h +257 -0
  137. package/vendor/node-airtunes2/src/CAHostTimeBase.cpp +105 -0
  138. package/vendor/node-airtunes2/src/CAHostTimeBase.h +224 -0
  139. package/vendor/node-airtunes2/src/aes_utils.c +864 -0
  140. package/vendor/node-airtunes2/src/aes_utils.h +24 -0
  141. package/vendor/node-airtunes2/src/base64.c +136 -0
  142. package/vendor/node-airtunes2/src/base64.h +42 -0
  143. package/vendor/node-airtunes2/src/bindings.cc +29 -0
  144. package/vendor/node-airtunes2/src/codec.cc +170 -0
  145. package/vendor/node-airtunes2/src/coreaudio.cc +332 -0
@@ -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 => Buffer.from(frame, 'base64'));
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) {
@@ -11,7 +11,7 @@ export function buildGallop(pulse, width, height, theme) {
11
11
  const frame = frames[frameIndex];
12
12
  const usableW = w > 8 ? w - 4 : w;
13
13
  const usableH = h > 4 ? h - 2 : h;
14
- const scale = Math.min(usableW * 2 / horseStrideWidth, usableH * 4 / horseStrideHeight);
14
+ const scale = Math.min(usableW * 2 / horseStrideWidth, usableH * 4 / horseStrideHeight) * 0.9;
15
15
  const dotsW = Math.max(1, Math.floor(horseStrideWidth * scale));
16
16
  const dotsH = Math.max(1, Math.floor(horseStrideHeight * scale));
17
17
  const left = Math.floor((w - Math.ceil(dotsW / 2)) / 2) * 2;
@@ -62,12 +62,14 @@ 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);
68
- export const defaultReceiverStyle = 'gallop-fine';
69
+ export const defaultReceiverStyle = 'ultracode';
69
70
  export const receiverStyleMetadata = Object.fromEntries(receiverStyleRegistry.map(style => [style.id, style]));
70
71
  const retiredReceiverStyleNames = [
72
+ 'synthwave-sun', 'neon-alley', 'safari-dusk', 'hot-air-balloons', 'deep-sea-whale', 'meteor-shower', 'volcano', 'desert-highway', 'firefly-grove', 'cherry-blossom', 'event-horizon', 'lighthouse',
71
73
  'duplex-wave', 'color-drift', 'undertow', 'cube-tide', 'parallax-slabs', 'voxel-swell',
72
74
  'dot-current', 'flip-matrix', 'raster-harp',
73
75
  'spin-lattice', 'packet-storm', 'spark-chamber',