@agentdeck/bridge 0.1.0 → 0.2.0
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/LICENSE +21 -0
- package/dist/adapters/claude-code.d.ts +11 -27
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +29 -123
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex-cli.d.ts +21 -0
- package/dist/adapters/codex-cli.d.ts.map +1 -0
- package/dist/adapters/codex-cli.js +53 -0
- package/dist/adapters/codex-cli.js.map +1 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +13 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/monitor.d.ts +34 -0
- package/dist/adapters/monitor.d.ts.map +1 -0
- package/dist/adapters/monitor.js +86 -0
- package/dist/adapters/monitor.js.map +1 -0
- package/dist/adapters/openclaw.d.ts +36 -2
- package/dist/adapters/openclaw.d.ts.map +1 -1
- package/dist/adapters/openclaw.js +329 -59
- package/dist/adapters/openclaw.js.map +1 -1
- package/dist/adapters/opencode-adapter.d.ts +48 -0
- package/dist/adapters/opencode-adapter.d.ts.map +1 -0
- package/dist/adapters/opencode-adapter.js +345 -0
- package/dist/adapters/opencode-adapter.js.map +1 -0
- package/dist/adapters/pty-adapter.d.ts +56 -0
- package/dist/adapters/pty-adapter.d.ts.map +1 -0
- package/dist/adapters/pty-adapter.js +143 -0
- package/dist/adapters/pty-adapter.js.map +1 -0
- package/dist/adb-reverse.d.ts +25 -0
- package/dist/adb-reverse.d.ts.map +1 -0
- package/dist/adb-reverse.js +129 -0
- package/dist/adb-reverse.js.map +1 -0
- package/dist/antigravity-local.d.ts +7 -0
- package/dist/antigravity-local.d.ts.map +1 -0
- package/dist/antigravity-local.js +153 -0
- package/dist/antigravity-local.js.map +1 -0
- package/dist/apme/adapters/claude-hook.d.ts +15 -0
- package/dist/apme/adapters/claude-hook.d.ts.map +1 -0
- package/dist/apme/adapters/claude-hook.js +85 -0
- package/dist/apme/adapters/claude-hook.js.map +1 -0
- package/dist/apme/adapters/claude-pty.d.ts +28 -0
- package/dist/apme/adapters/claude-pty.d.ts.map +1 -0
- package/dist/apme/adapters/claude-pty.js +88 -0
- package/dist/apme/adapters/claude-pty.js.map +1 -0
- package/dist/apme/adapters/timeline.d.ts +16 -0
- package/dist/apme/adapters/timeline.d.ts.map +1 -0
- package/dist/apme/adapters/timeline.js +66 -0
- package/dist/apme/adapters/timeline.js.map +1 -0
- package/dist/apme/classifier.d.ts +53 -0
- package/dist/apme/classifier.d.ts.map +1 -0
- package/dist/apme/classifier.js +266 -0
- package/dist/apme/classifier.js.map +1 -0
- package/dist/apme/claude-transcript-reader.d.ts +38 -0
- package/dist/apme/claude-transcript-reader.d.ts.map +1 -0
- package/dist/apme/claude-transcript-reader.js +127 -0
- package/dist/apme/claude-transcript-reader.js.map +1 -0
- package/dist/apme/collector.d.ts +108 -0
- package/dist/apme/collector.d.ts.map +1 -0
- package/dist/apme/collector.js +659 -0
- package/dist/apme/collector.js.map +1 -0
- package/dist/apme/dashboard-html.d.ts +9 -0
- package/dist/apme/dashboard-html.d.ts.map +1 -0
- package/dist/apme/dashboard-html.js +416 -0
- package/dist/apme/dashboard-html.js.map +1 -0
- package/dist/apme/http.d.ts +19 -0
- package/dist/apme/http.d.ts.map +1 -0
- package/dist/apme/http.js +210 -0
- package/dist/apme/http.js.map +1 -0
- package/dist/apme/hw-sampler.d.ts +30 -0
- package/dist/apme/hw-sampler.d.ts.map +1 -0
- package/dist/apme/hw-sampler.js +106 -0
- package/dist/apme/hw-sampler.js.map +1 -0
- package/dist/apme/index.d.ts +43 -0
- package/dist/apme/index.d.ts.map +1 -0
- package/dist/apme/index.js +75 -0
- package/dist/apme/index.js.map +1 -0
- package/dist/apme/outcome.d.ts +48 -0
- package/dist/apme/outcome.d.ts.map +1 -0
- package/dist/apme/outcome.js +232 -0
- package/dist/apme/outcome.js.map +1 -0
- package/dist/apme/recommend.d.ts +32 -0
- package/dist/apme/recommend.d.ts.map +1 -0
- package/dist/apme/recommend.js +41 -0
- package/dist/apme/recommend.js.map +1 -0
- package/dist/apme/runner.d.ts +94 -0
- package/dist/apme/runner.d.ts.map +1 -0
- package/dist/apme/runner.js +812 -0
- package/dist/apme/runner.js.map +1 -0
- package/dist/apme/settings.d.ts +65 -0
- package/dist/apme/settings.d.ts.map +1 -0
- package/dist/apme/settings.js +97 -0
- package/dist/apme/settings.js.map +1 -0
- package/dist/apme/store.d.ts +99 -0
- package/dist/apme/store.d.ts.map +1 -0
- package/dist/apme/store.js +911 -0
- package/dist/apme/store.js.map +1 -0
- package/dist/apme/tuner.d.ts +64 -0
- package/dist/apme/tuner.d.ts.map +1 -0
- package/dist/apme/tuner.js +297 -0
- package/dist/apme/tuner.js.map +1 -0
- package/dist/apme/types.d.ts +11 -0
- package/dist/apme/types.d.ts.map +1 -0
- package/dist/apme/types.js +10 -0
- package/dist/apme/types.js.map +1 -0
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +15 -5
- package/dist/auth.js.map +1 -1
- package/dist/bridge-core.d.ts +162 -0
- package/dist/bridge-core.d.ts.map +1 -0
- package/dist/bridge-core.js +627 -0
- package/dist/bridge-core.js.map +1 -0
- package/dist/check-deps.d.ts +3 -1
- package/dist/check-deps.d.ts.map +1 -1
- package/dist/check-deps.js +34 -13
- package/dist/check-deps.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1235 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-auth.d.ts +10 -0
- package/dist/codex-auth.d.ts.map +1 -0
- package/dist/codex-auth.js +63 -0
- package/dist/codex-auth.js.map +1 -0
- package/dist/codex-output-parser.d.ts +21 -0
- package/dist/codex-output-parser.d.ts.map +1 -0
- package/dist/codex-output-parser.js +253 -0
- package/dist/codex-output-parser.js.map +1 -0
- package/dist/d200h/hid-protocol.d.ts +62 -0
- package/dist/d200h/hid-protocol.d.ts.map +1 -0
- package/dist/d200h/hid-protocol.js +124 -0
- package/dist/d200h/hid-protocol.js.map +1 -0
- package/dist/d200h/image-renderer.d.ts +36 -0
- package/dist/d200h/image-renderer.d.ts.map +1 -0
- package/dist/d200h/image-renderer.js +487 -0
- package/dist/d200h/image-renderer.js.map +1 -0
- package/dist/daemon-server.d.ts +19 -0
- package/dist/daemon-server.d.ts.map +1 -0
- package/dist/daemon-server.js +1168 -0
- package/dist/daemon-server.js.map +1 -0
- package/dist/daemon-ws-client.d.ts +82 -0
- package/dist/daemon-ws-client.d.ts.map +1 -0
- package/dist/daemon-ws-client.js +179 -0
- package/dist/daemon-ws-client.js.map +1 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +56 -0
- package/dist/daemon.js.map +1 -0
- package/dist/display-monitor.d.ts +25 -0
- package/dist/display-monitor.d.ts.map +1 -0
- package/dist/display-monitor.js +143 -0
- package/dist/display-monitor.js.map +1 -0
- package/dist/esp32-serial.d.ts +112 -0
- package/dist/esp32-serial.d.ts.map +1 -0
- package/dist/esp32-serial.js +502 -0
- package/dist/esp32-serial.js.map +1 -0
- package/dist/gateway-probe.d.ts +12 -0
- package/dist/gateway-probe.d.ts.map +1 -0
- package/dist/gateway-probe.js +39 -0
- package/dist/gateway-probe.js.map +1 -0
- package/dist/hook-server.d.ts +29 -0
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +366 -8
- package/dist/hook-server.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1305 -562
- package/dist/index.js.map +1 -1
- package/dist/log-stream.d.ts +26 -0
- package/dist/log-stream.d.ts.map +1 -0
- package/dist/log-stream.js +111 -0
- package/dist/log-stream.js.map +1 -0
- package/dist/logger.d.ts +9 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +36 -5
- package/dist/logger.js.map +1 -1
- package/dist/mdns.d.ts +15 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +143 -28
- package/dist/mdns.js.map +1 -1
- package/dist/mlx-probe.d.ts +14 -0
- package/dist/mlx-probe.d.ts.map +1 -0
- package/dist/mlx-probe.js +44 -0
- package/dist/mlx-probe.js.map +1 -0
- package/dist/model-catalog.d.ts +5 -3
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +45 -29
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/adb-module.d.ts +14 -0
- package/dist/modules/adb-module.d.ts.map +1 -0
- package/dist/modules/adb-module.js +36 -0
- package/dist/modules/adb-module.js.map +1 -0
- package/dist/modules/d200h-module.d.ts +43 -0
- package/dist/modules/d200h-module.d.ts.map +1 -0
- package/dist/modules/d200h-module.js +360 -0
- package/dist/modules/d200h-module.js.map +1 -0
- package/dist/modules/d200h-renderer.d.ts +7 -0
- package/dist/modules/d200h-renderer.d.ts.map +1 -0
- package/dist/modules/d200h-renderer.js +321 -0
- package/dist/modules/d200h-renderer.js.map +1 -0
- package/dist/modules/index.d.ts +23 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +65 -0
- package/dist/modules/index.js.map +1 -0
- package/dist/modules/mdns-module.d.ts +12 -0
- package/dist/modules/mdns-module.d.ts.map +1 -0
- package/dist/modules/mdns-module.js +21 -0
- package/dist/modules/mdns-module.js.map +1 -0
- package/dist/modules/pixoo-module.d.ts +8 -0
- package/dist/modules/pixoo-module.d.ts.map +1 -0
- package/dist/modules/pixoo-module.js +24 -0
- package/dist/modules/pixoo-module.js.map +1 -0
- package/dist/modules/serial-module.d.ts +18 -0
- package/dist/modules/serial-module.d.ts.map +1 -0
- package/dist/modules/serial-module.js +47 -0
- package/dist/modules/serial-module.js.map +1 -0
- package/dist/modules/types.d.ts +39 -0
- package/dist/modules/types.d.ts.map +1 -0
- package/dist/modules/types.js +2 -0
- package/dist/modules/types.js.map +1 -0
- package/dist/ollama-probe.d.ts +13 -0
- package/dist/ollama-probe.d.ts.map +1 -0
- package/dist/ollama-probe.js +46 -0
- package/dist/ollama-probe.js.map +1 -0
- package/dist/opencode-client.d.ts +126 -0
- package/dist/opencode-client.d.ts.map +1 -0
- package/dist/opencode-client.js +181 -0
- package/dist/opencode-client.js.map +1 -0
- package/dist/output-parser.d.ts +3 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +29 -3
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +38 -0
- package/dist/passive-observer.d.ts.map +1 -0
- package/dist/passive-observer.js +632 -0
- package/dist/passive-observer.js.map +1 -0
- package/dist/pixoo/pixoo-bridge.d.ts +50 -0
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -0
- package/dist/pixoo/pixoo-bridge.js +280 -0
- package/dist/pixoo/pixoo-bridge.js.map +1 -0
- package/dist/pixoo/pixoo-camera.d.ts +80 -0
- package/dist/pixoo/pixoo-camera.d.ts.map +1 -0
- package/dist/pixoo/pixoo-camera.js +271 -0
- package/dist/pixoo/pixoo-camera.js.map +1 -0
- package/dist/pixoo/pixoo-client.d.ts +67 -0
- package/dist/pixoo/pixoo-client.d.ts.map +1 -0
- package/dist/pixoo/pixoo-client.js +335 -0
- package/dist/pixoo/pixoo-client.js.map +1 -0
- package/dist/pixoo/pixoo-font.d.ts +24 -0
- package/dist/pixoo/pixoo-font.d.ts.map +1 -0
- package/dist/pixoo/pixoo-font.js +121 -0
- package/dist/pixoo/pixoo-font.js.map +1 -0
- package/dist/pixoo/pixoo-renderer.d.ts +32 -0
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -0
- package/dist/pixoo/pixoo-renderer.js +696 -0
- package/dist/pixoo/pixoo-renderer.js.map +1 -0
- package/dist/pixoo/pixoo-settings.d.ts +10 -0
- package/dist/pixoo/pixoo-settings.d.ts.map +1 -0
- package/dist/pixoo/pixoo-settings.js +47 -0
- package/dist/pixoo/pixoo-settings.js.map +1 -0
- package/dist/pixoo/pixoo-sprites.d.ts +187 -0
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -0
- package/dist/pixoo/pixoo-sprites.js +926 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -0
- package/dist/pty-manager.d.ts +1 -1
- package/dist/pty-manager.d.ts.map +1 -1
- package/dist/pty-manager.js +36 -15
- package/dist/pty-manager.js.map +1 -1
- package/dist/session-aggregator.d.ts +41 -0
- package/dist/session-aggregator.d.ts.map +1 -0
- package/dist/session-aggregator.js +76 -0
- package/dist/session-aggregator.js.map +1 -0
- package/dist/session-focus-relay.d.ts +35 -0
- package/dist/session-focus-relay.d.ts.map +1 -0
- package/dist/session-focus-relay.js +127 -0
- package/dist/session-focus-relay.js.map +1 -0
- package/dist/session-registry.d.ts +88 -1
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +222 -15
- package/dist/session-registry.js.map +1 -1
- package/dist/session-timeline-relay.d.ts +27 -0
- package/dist/session-timeline-relay.d.ts.map +1 -0
- package/dist/session-timeline-relay.js +124 -0
- package/dist/session-timeline-relay.js.map +1 -0
- package/dist/state-machine.d.ts +1 -0
- package/dist/state-machine.d.ts.map +1 -1
- package/dist/state-machine.js +14 -0
- package/dist/state-machine.js.map +1 -1
- package/dist/terminal-status.d.ts +28 -0
- package/dist/terminal-status.d.ts.map +1 -0
- package/dist/terminal-status.js +152 -0
- package/dist/terminal-status.js.map +1 -0
- package/dist/timeline-store.d.ts +22 -0
- package/dist/timeline-store.d.ts.map +1 -0
- package/dist/timeline-store.js +79 -0
- package/dist/timeline-store.js.map +1 -0
- package/dist/timeline-summarizer.d.ts +14 -0
- package/dist/timeline-summarizer.d.ts.map +1 -0
- package/dist/timeline-summarizer.js +154 -0
- package/dist/timeline-summarizer.js.map +1 -0
- package/dist/tts.d.ts +25 -0
- package/dist/tts.d.ts.map +1 -0
- package/dist/tts.js +99 -0
- package/dist/tts.js.map +1 -0
- package/dist/tui/ansi.d.ts +92 -0
- package/dist/tui/ansi.d.ts.map +1 -0
- package/dist/tui/ansi.js +181 -0
- package/dist/tui/ansi.js.map +1 -0
- package/dist/tui/dashboard.d.ts +35 -0
- package/dist/tui/dashboard.d.ts.map +1 -0
- package/dist/tui/dashboard.js +436 -0
- package/dist/tui/dashboard.js.map +1 -0
- package/dist/tui/gauge.d.ts +15 -0
- package/dist/tui/gauge.d.ts.map +1 -0
- package/dist/tui/gauge.js +82 -0
- package/dist/tui/gauge.js.map +1 -0
- package/dist/tui/renderer.d.ts +15 -0
- package/dist/tui/renderer.d.ts.map +1 -0
- package/dist/tui/renderer.js +758 -0
- package/dist/tui/renderer.js.map +1 -0
- package/dist/tui/screen.d.ts +27 -0
- package/dist/tui/screen.d.ts.map +1 -0
- package/dist/tui/screen.js +109 -0
- package/dist/tui/screen.js.map +1 -0
- package/dist/tui/terrarium.d.ts +96 -0
- package/dist/tui/terrarium.d.ts.map +1 -0
- package/dist/tui/terrarium.js +912 -0
- package/dist/tui/terrarium.js.map +1 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -2
- package/dist/types.js.map +1 -1
- package/dist/usage-api.d.ts +9 -2
- package/dist/usage-api.d.ts.map +1 -1
- package/dist/usage-api.js +224 -42
- package/dist/usage-api.js.map +1 -1
- package/dist/usage-event.d.ts +12 -0
- package/dist/usage-event.d.ts.map +1 -0
- package/dist/usage-event.js +78 -0
- package/dist/usage-event.js.map +1 -0
- package/dist/usage-tracker.d.ts +4 -0
- package/dist/usage-tracker.d.ts.map +1 -1
- package/dist/usage-tracker.js +17 -0
- package/dist/usage-tracker.js.map +1 -1
- package/dist/utility-proxy.d.ts +30 -0
- package/dist/utility-proxy.d.ts.map +1 -0
- package/dist/utility-proxy.js +217 -0
- package/dist/utility-proxy.js.map +1 -0
- package/dist/utils/project-name.d.ts +22 -0
- package/dist/utils/project-name.d.ts.map +1 -0
- package/dist/utils/project-name.js +72 -0
- package/dist/utils/project-name.js.map +1 -0
- package/dist/version-check.d.ts +26 -0
- package/dist/version-check.d.ts.map +1 -0
- package/dist/version-check.js +225 -0
- package/dist/version-check.js.map +1 -0
- package/dist/voice-assistant.d.ts +63 -0
- package/dist/voice-assistant.d.ts.map +1 -0
- package/dist/voice-assistant.js +406 -0
- package/dist/voice-assistant.js.map +1 -0
- package/dist/voice.d.ts +2 -0
- package/dist/voice.d.ts.map +1 -1
- package/dist/voice.js +26 -1
- package/dist/voice.js.map +1 -1
- package/dist/wake-word.d.ts +35 -0
- package/dist/wake-word.d.ts.map +1 -0
- package/dist/wake-word.js +225 -0
- package/dist/wake-word.js.map +1 -0
- package/dist/whisper-server-manager.d.ts +1 -1
- package/dist/whisper-server-manager.js +1 -1
- package/dist/wifi-config.d.ts +24 -0
- package/dist/wifi-config.d.ts.map +1 -0
- package/dist/wifi-config.js +84 -0
- package/dist/wifi-config.js.map +1 -0
- package/dist/ws-server.d.ts +12 -0
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +81 -3
- package/dist/ws-server.js.map +1 -1
- package/package.json +22 -13
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI Terrarium — Unicode Braille aquarium animation.
|
|
3
|
+
* Creature behavior matches Android/iOS/ESP32:
|
|
4
|
+
* - IDLE/SLEEPING: octopus rests on sea floor (touching sand), gentle bob
|
|
5
|
+
* - PROCESSING: octopus swims upward with starburst, tetra school converges
|
|
6
|
+
* - AWAITING: octopus mid-water with "?" bubble
|
|
7
|
+
* - Crayfish: larger than octopus, right side, heartbeat(sitting)/active(routing)
|
|
8
|
+
* - Tetra: 2 schools (5 each), Lissajous centers, boids cohesion
|
|
9
|
+
* - Scaling: small (default) / large (2×) based on terminal size
|
|
10
|
+
*/
|
|
11
|
+
import { fg, bg, RESET, DIM, colors } from './ansi.js';
|
|
12
|
+
// ===== Braille Renderer =====
|
|
13
|
+
const BRAILLE_BASE = 0x2800;
|
|
14
|
+
const BRAILLE_MAP = [
|
|
15
|
+
[0x01, 0x02, 0x04, 0x40],
|
|
16
|
+
[0x08, 0x10, 0x20, 0x80],
|
|
17
|
+
];
|
|
18
|
+
function gridToBraille(grid, width, height) {
|
|
19
|
+
const charRows = Math.ceil(height / 4);
|
|
20
|
+
const charCols = Math.ceil(width / 2);
|
|
21
|
+
const result = [];
|
|
22
|
+
for (let cr = 0; cr < charRows; cr++) {
|
|
23
|
+
let row = '';
|
|
24
|
+
for (let cc = 0; cc < charCols; cc++) {
|
|
25
|
+
let code = 0;
|
|
26
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
27
|
+
for (let dy = 0; dy < 4; dy++) {
|
|
28
|
+
const gx = cc * 2 + dx;
|
|
29
|
+
const gy = cr * 4 + dy;
|
|
30
|
+
if (gy < height && gx < width && grid[gy]?.[gx]) {
|
|
31
|
+
code |= BRAILLE_MAP[dx][dy];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
row += String.fromCharCode(BRAILLE_BASE + code);
|
|
36
|
+
}
|
|
37
|
+
result.push(row);
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function getSpriteScale(width, height) {
|
|
42
|
+
if (width >= 160 && height >= 35)
|
|
43
|
+
return 'xlarge';
|
|
44
|
+
if (width >= 100 && height >= 20)
|
|
45
|
+
return 'large';
|
|
46
|
+
return 'small';
|
|
47
|
+
}
|
|
48
|
+
function scaleGridN(grid, n) {
|
|
49
|
+
const scaled = [];
|
|
50
|
+
for (const row of grid) {
|
|
51
|
+
const scaledRow = [];
|
|
52
|
+
for (const cell of row) {
|
|
53
|
+
for (let i = 0; i < n; i++)
|
|
54
|
+
scaledRow.push(cell);
|
|
55
|
+
}
|
|
56
|
+
for (let i = 0; i < n; i++)
|
|
57
|
+
scaled.push([...scaledRow]);
|
|
58
|
+
}
|
|
59
|
+
return scaled;
|
|
60
|
+
}
|
|
61
|
+
// ===== Octopus Sprite =====
|
|
62
|
+
// Small: 14×5 pixel → 7×2 braille, Large: 28×10 → 14×3 braille
|
|
63
|
+
const OCTOPUS_GRID_SMALL = [
|
|
64
|
+
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
|
65
|
+
[0, 0, 0, 1, 1, 2, 1, 1, 2, 1, 1, 0, 0, 0],
|
|
66
|
+
[0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 4, 0, 0],
|
|
67
|
+
[0, 0, 0, 5, 1, 1, 1, 1, 1, 1, 6, 0, 0, 0],
|
|
68
|
+
[0, 0, 0, 0, 5, 0, 5, 6, 0, 6, 0, 0, 0, 0],
|
|
69
|
+
];
|
|
70
|
+
function renderOctopus(inst, frame, scale) {
|
|
71
|
+
const f = frame + inst.phaseOffset;
|
|
72
|
+
const srcGrid = scale === 'xlarge' ? OCTOPUS_GRID_XLARGE :
|
|
73
|
+
scale === 'large' ? OCTOPUS_GRID_LARGE : OCTOPUS_GRID_SMALL;
|
|
74
|
+
const gh = srcGrid.length;
|
|
75
|
+
const gw = srcGrid[0].length;
|
|
76
|
+
const grid = [];
|
|
77
|
+
for (let y = 0; y < gh; y++) {
|
|
78
|
+
grid[y] = [];
|
|
79
|
+
for (let x = 0; x < gw; x++) {
|
|
80
|
+
const cell = srcGrid[y][x];
|
|
81
|
+
if (cell === 0) {
|
|
82
|
+
grid[y][x] = false;
|
|
83
|
+
}
|
|
84
|
+
else if (cell === 2) {
|
|
85
|
+
grid[y][x] = (f % 40) > 3;
|
|
86
|
+
}
|
|
87
|
+
else if (cell === 3 || cell === 4) {
|
|
88
|
+
if (inst.state === 'processing') {
|
|
89
|
+
const armPhase = cell === 3 ? 0 : Math.PI;
|
|
90
|
+
grid[y][x] = Math.sin(f * 0.3 + armPhase) > -0.3;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
grid[y][x] = true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (cell === 5 || cell === 6) {
|
|
97
|
+
const legPhase = cell === 5 ? 0 : Math.PI * 0.5;
|
|
98
|
+
grid[y][x] = Math.sin(f * 0.1 + legPhase) > -0.7;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
grid[y][x] = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const braille = gridToBraille(grid, gw, gh);
|
|
106
|
+
const color = inst.state === 'disconnected' ? DIM + colors.octopus : colors.octopus;
|
|
107
|
+
return { braille, color };
|
|
108
|
+
}
|
|
109
|
+
// ===== Crayfish Sprite =====
|
|
110
|
+
// Small: 16×8 pixel → 8×2 braille, Large: 32×16 → 16×4 braille
|
|
111
|
+
const CRAYFISH_GRID_SMALL = [
|
|
112
|
+
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], // antennae
|
|
113
|
+
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], // antenna stems
|
|
114
|
+
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0], // claws open
|
|
115
|
+
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], // body top
|
|
116
|
+
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], // body mid
|
|
117
|
+
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], // body bottom
|
|
118
|
+
[0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], // legs
|
|
119
|
+
[0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], // tail legs
|
|
120
|
+
];
|
|
121
|
+
// Pre-compute scaled grids (2× and 3× each axis)
|
|
122
|
+
const OCTOPUS_GRID_LARGE = scaleGridN(OCTOPUS_GRID_SMALL, 2);
|
|
123
|
+
const OCTOPUS_GRID_XLARGE = scaleGridN(OCTOPUS_GRID_SMALL, 3);
|
|
124
|
+
const CRAYFISH_GRID_LARGE = scaleGridN(CRAYFISH_GRID_SMALL, 2);
|
|
125
|
+
const CRAYFISH_GRID_XLARGE = scaleGridN(CRAYFISH_GRID_SMALL, 3);
|
|
126
|
+
function renderCrayfish(state, frame, scale) {
|
|
127
|
+
const srcGrid = scale === 'xlarge' ? CRAYFISH_GRID_XLARGE :
|
|
128
|
+
scale === 'large' ? CRAYFISH_GRID_LARGE : CRAYFISH_GRID_SMALL;
|
|
129
|
+
const gh = srcGrid.length;
|
|
130
|
+
const gw = srcGrid[0].length;
|
|
131
|
+
const grid = [];
|
|
132
|
+
for (let y = 0; y < gh; y++) {
|
|
133
|
+
grid[y] = [];
|
|
134
|
+
for (let x = 0; x < gw; x++) {
|
|
135
|
+
const cell = srcGrid[y][x];
|
|
136
|
+
if (!cell) {
|
|
137
|
+
grid[y][x] = false;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Map back to original row/col for animation logic
|
|
141
|
+
const sf = scale === 'xlarge' ? 3 : scale === 'large' ? 2 : 1;
|
|
142
|
+
const origY = Math.floor(y / sf);
|
|
143
|
+
const origX = Math.floor(x / sf);
|
|
144
|
+
// Antennae wiggle (original rows 0-1)
|
|
145
|
+
if (origY <= 1 && (origX === 3 || origX === 10)) {
|
|
146
|
+
grid[y][x] = state.routing || Math.sin(frame * 0.1 + origX) > -0.3;
|
|
147
|
+
}
|
|
148
|
+
// Claw clap when routing (original row 2)
|
|
149
|
+
else if (origY === 2 && (origX === 1 || origX === 12)) {
|
|
150
|
+
grid[y][x] = !state.routing || (frame % 20 > 5);
|
|
151
|
+
}
|
|
152
|
+
// Leg movement (original rows 6-7)
|
|
153
|
+
else if (origY >= 6) {
|
|
154
|
+
const legPhase = origX * 0.5;
|
|
155
|
+
grid[y][x] = Math.sin(frame * 0.08 + legPhase) > -0.5;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
grid[y][x] = true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const braille = gridToBraille(grid, gw, gh);
|
|
163
|
+
let color;
|
|
164
|
+
if (state.sick) {
|
|
165
|
+
color = DIM + fg(180, 140, 140); // desaturated dim red
|
|
166
|
+
}
|
|
167
|
+
else if (state.routing) {
|
|
168
|
+
color = colors.crayfish;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Heartbeat: double-pulse every ~50 frames
|
|
172
|
+
const t = frame % 50;
|
|
173
|
+
const pulse = (t < 5 || (t > 8 && t < 13));
|
|
174
|
+
color = pulse ? colors.crayfish : DIM + colors.crayfish;
|
|
175
|
+
}
|
|
176
|
+
return { braille, color };
|
|
177
|
+
}
|
|
178
|
+
// ===== Jellyfish Sprite =====
|
|
179
|
+
// Codex CLI creature — 6-lobe cloud (matches Android CloudCreature / Apple JellyfishCreature)
|
|
180
|
+
// Small: 10×8 pixel → 5×2 braille, Large: 20×16 → 10×4 braille
|
|
181
|
+
// Cell types: 0=empty, 1=cloud body, 2=marking(>_), 3=cloud edge(breathe)
|
|
182
|
+
const JELLYFISH_GRID_SMALL = [
|
|
183
|
+
[0, 0, 1, 1, 0, 0, 1, 1, 0, 0], // top-left + top-right lobes
|
|
184
|
+
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0], // upper body merge
|
|
185
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // widest — side lobes
|
|
186
|
+
[3, 1, 2, 2, 1, 1, 2, 1, 1, 3], // center with >_ + breathe edges
|
|
187
|
+
[3, 1, 1, 1, 1, 1, 1, 1, 1, 3], // center body + breathe edges
|
|
188
|
+
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // widest — side lobes
|
|
189
|
+
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0], // lower body taper
|
|
190
|
+
[0, 0, 1, 1, 0, 0, 1, 1, 0, 0], // bottom-left + bottom-right lobes
|
|
191
|
+
];
|
|
192
|
+
const JELLYFISH_GRID_LARGE = scaleGridN(JELLYFISH_GRID_SMALL, 2);
|
|
193
|
+
const JELLYFISH_GRID_XLARGE = scaleGridN(JELLYFISH_GRID_SMALL, 3);
|
|
194
|
+
function renderJellyfish(inst, frame, scale) {
|
|
195
|
+
const f = frame + inst.phaseOffset;
|
|
196
|
+
const srcGrid = scale === 'xlarge' ? JELLYFISH_GRID_XLARGE :
|
|
197
|
+
scale === 'large' ? JELLYFISH_GRID_LARGE : JELLYFISH_GRID_SMALL;
|
|
198
|
+
const gh = srcGrid.length;
|
|
199
|
+
const gw = srcGrid[0].length;
|
|
200
|
+
const sf = scale === 'xlarge' ? 3 : scale === 'large' ? 2 : 1;
|
|
201
|
+
const grid = [];
|
|
202
|
+
// Bell pulse: contracts/expands based on state
|
|
203
|
+
const pulseSpeed = inst.state === 'processing' ? 0.25 : 0.06;
|
|
204
|
+
const pulsePhase = Math.sin(f * pulseSpeed);
|
|
205
|
+
const contracting = pulsePhase < 0;
|
|
206
|
+
for (let y = 0; y < gh; y++) {
|
|
207
|
+
grid[y] = [];
|
|
208
|
+
for (let x = 0; x < gw; x++) {
|
|
209
|
+
const cell = srcGrid[y][x];
|
|
210
|
+
if (cell === 0) {
|
|
211
|
+
grid[y][x] = false;
|
|
212
|
+
}
|
|
213
|
+
else if (cell === 2) {
|
|
214
|
+
// >_ marking — blinks subtly
|
|
215
|
+
grid[y][x] = (f % 60) > 5;
|
|
216
|
+
}
|
|
217
|
+
else if (cell === 3) {
|
|
218
|
+
// Cloud edge — contracts during pulse
|
|
219
|
+
grid[y][x] = !contracting;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
grid[y][x] = true; // cloud body
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const braille = gridToBraille(grid, gw, gh);
|
|
227
|
+
// Color: dim when disconnected, glow when processing
|
|
228
|
+
let color;
|
|
229
|
+
if (inst.state === 'disconnected') {
|
|
230
|
+
color = DIM + colors.jellyfish;
|
|
231
|
+
}
|
|
232
|
+
else if (inst.state === 'processing') {
|
|
233
|
+
// Bioluminescent pulse
|
|
234
|
+
const glow = Math.sin(f * 0.2) > 0;
|
|
235
|
+
color = glow ? colors.jellyfishGlow : colors.jellyfish;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
color = colors.jellyfish;
|
|
239
|
+
}
|
|
240
|
+
return { braille, color };
|
|
241
|
+
}
|
|
242
|
+
function initSchool(count, seed) {
|
|
243
|
+
const fish = [];
|
|
244
|
+
for (let i = 0; i < count; i++) {
|
|
245
|
+
const angle = (i / count) * Math.PI * 2;
|
|
246
|
+
fish.push({
|
|
247
|
+
x: 0.4 + seed * 0.2 + Math.cos(angle) * 0.08,
|
|
248
|
+
y: 0.3 + Math.sin(angle) * 0.06,
|
|
249
|
+
vx: (seed % 2 === 0 ? 1 : -1) * (0.003 + Math.random() * 0.002),
|
|
250
|
+
vy: (Math.random() - 0.5) * 0.002,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return { fish, centerX: 0.4 + seed * 0.2, centerY: 0.3 + seed * 0.1 };
|
|
254
|
+
}
|
|
255
|
+
function updateSchool(school, frame, seed, attractTarget) {
|
|
256
|
+
const t = frame * 0.015;
|
|
257
|
+
school.centerX = 0.3 + 0.25 * Math.sin(t * (1.0 + seed * 0.4));
|
|
258
|
+
school.centerY = 0.2 + 0.18 * Math.cos(t * (0.7 + seed * 0.3));
|
|
259
|
+
for (const f of school.fish) {
|
|
260
|
+
let targetX = school.centerX;
|
|
261
|
+
let targetY = school.centerY;
|
|
262
|
+
if (attractTarget) {
|
|
263
|
+
targetX = targetX * 0.7 + attractTarget.x * 0.3;
|
|
264
|
+
targetY = targetY * 0.7 + attractTarget.y * 0.3;
|
|
265
|
+
}
|
|
266
|
+
f.vx += (targetX - f.x) * 0.008;
|
|
267
|
+
f.vy += (targetY - f.y) * 0.008;
|
|
268
|
+
for (const other of school.fish) {
|
|
269
|
+
if (other === f)
|
|
270
|
+
continue;
|
|
271
|
+
const sx = f.x - other.x, sy = f.y - other.y;
|
|
272
|
+
const dist = Math.sqrt(sx * sx + sy * sy);
|
|
273
|
+
if (dist < 0.05 && dist > 0) {
|
|
274
|
+
f.vx += (sx / dist) * 0.002;
|
|
275
|
+
f.vy += (sy / dist) * 0.002;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const speed = Math.sqrt(f.vx * f.vx + f.vy * f.vy);
|
|
279
|
+
if (speed > 0.008) {
|
|
280
|
+
f.vx = (f.vx / speed) * 0.008;
|
|
281
|
+
f.vy = (f.vy / speed) * 0.008;
|
|
282
|
+
}
|
|
283
|
+
f.x += f.vx;
|
|
284
|
+
f.y += f.vy;
|
|
285
|
+
if (f.x > 0.92) {
|
|
286
|
+
f.x = 0.92;
|
|
287
|
+
f.vx *= -0.5;
|
|
288
|
+
}
|
|
289
|
+
if (f.x < 0.03) {
|
|
290
|
+
f.x = 0.03;
|
|
291
|
+
f.vx *= -0.5;
|
|
292
|
+
}
|
|
293
|
+
if (f.y > 0.62) {
|
|
294
|
+
f.y = 0.62;
|
|
295
|
+
f.vy *= -0.5;
|
|
296
|
+
}
|
|
297
|
+
if (f.y < 0.08) {
|
|
298
|
+
f.y = 0.08;
|
|
299
|
+
f.vy *= -0.5;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// ===== Environment =====
|
|
304
|
+
const WAVE_CHARS = ['~', '\u2248', '\u223F', '~', '\u2248'];
|
|
305
|
+
const BUBBLE_CHARS = ['\u00B0', '\u00B7', '\u25CB', '\u25E6'];
|
|
306
|
+
function renderOpenCode(_inst, frame, scale) {
|
|
307
|
+
const isSleeping = _inst.state === 'sleeping' || _inst.state === 'paused';
|
|
308
|
+
const isProcessing = _inst.state === 'processing';
|
|
309
|
+
const outerColor = isSleeping ? DIM + fg(160, 158, 158) : fg(241, 236, 236);
|
|
310
|
+
const innerColor = isSleeping ? DIM + fg(60, 56, 56) : fg(75, 70, 70);
|
|
311
|
+
const pulse = isProcessing && ((frame + _inst.phaseOffset) % 30 < 15);
|
|
312
|
+
const oc = pulse ? fg(207, 206, 205) : outerColor;
|
|
313
|
+
const ic = innerColor;
|
|
314
|
+
if (scale === 'xlarge') {
|
|
315
|
+
return { lines: [
|
|
316
|
+
`${oc}\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510${RESET}`,
|
|
317
|
+
`${oc}\u2502${RESET} ${oc}\u2502${RESET}`,
|
|
318
|
+
`${oc}\u2502${RESET} ${ic}\u250c\u2500\u2500\u2500\u2510${RESET} ${oc}\u2502${RESET}`,
|
|
319
|
+
`${oc}\u2502${RESET} ${ic}\u2502 \u2502${RESET} ${oc}\u2502${RESET}`,
|
|
320
|
+
`${oc}\u2502${RESET} ${ic}\u2514\u2500\u2500\u2500\u2518${RESET} ${oc}\u2502${RESET}`,
|
|
321
|
+
`${oc}\u2502${RESET} ${oc}\u2502${RESET}`,
|
|
322
|
+
`${oc}\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518${RESET}`,
|
|
323
|
+
], color: outerColor };
|
|
324
|
+
}
|
|
325
|
+
if (scale === 'large') {
|
|
326
|
+
return { lines: [
|
|
327
|
+
`${oc}\u250c\u2500\u2500\u2500\u2500\u2500\u2510${RESET}`,
|
|
328
|
+
`${oc}\u2502${RESET} ${oc}\u2502${RESET}`,
|
|
329
|
+
`${oc}\u2502${RESET} ${ic}\u250c\u2500\u2510${RESET} ${oc}\u2502${RESET}`,
|
|
330
|
+
`${oc}\u2502${RESET} ${oc}\u2502${RESET}`,
|
|
331
|
+
`${oc}\u2514\u2500\u2500\u2500\u2500\u2500\u2518${RESET}`,
|
|
332
|
+
], color: outerColor };
|
|
333
|
+
}
|
|
334
|
+
return { lines: [
|
|
335
|
+
`${oc}\u250c\u2500\u2500\u2500\u2510${RESET}`,
|
|
336
|
+
`${oc}\u2502${ic}\u250c\u2500\u2510${oc}\u2502${RESET}`,
|
|
337
|
+
`${oc}\u2514\u2500\u2500\u2500\u2518${RESET}`,
|
|
338
|
+
], color: outerColor };
|
|
339
|
+
}
|
|
340
|
+
export function initTerrarium() {
|
|
341
|
+
const bubbles = [];
|
|
342
|
+
for (let i = 0; i < 8; i++) {
|
|
343
|
+
bubbles.push({
|
|
344
|
+
x: 0.1 + Math.random() * 0.8,
|
|
345
|
+
y: 0.3 + Math.random() * 0.6,
|
|
346
|
+
char: BUBBLE_CHARS[Math.floor(Math.random() * BUBBLE_CHARS.length)],
|
|
347
|
+
speed: 0.008 + Math.random() * 0.015,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
bubbles,
|
|
352
|
+
schools: [initSchool(5, 0), initSchool(5, 1)],
|
|
353
|
+
octopi: [],
|
|
354
|
+
jellyfish: [],
|
|
355
|
+
opencode: [],
|
|
356
|
+
crayfish: { visible: false, routing: false, sick: false, x: 0.75, y: 0.88 },
|
|
357
|
+
voiceAssistantState: 'disabled',
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
export function updateTerrarium(ctx, frame) {
|
|
361
|
+
for (const b of ctx.bubbles) {
|
|
362
|
+
b.y -= b.speed;
|
|
363
|
+
b.x += Math.sin(frame * 0.1 + b.x * 10) * 0.003;
|
|
364
|
+
if (b.y < 0.02) {
|
|
365
|
+
b.y = 0.85 + Math.random() * 0.1;
|
|
366
|
+
b.x = 0.1 + Math.random() * 0.8;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Attract target: processing octopus > processing jellyfish > routing crayfish > none
|
|
370
|
+
const activeOct = ctx.octopi.find(o => o.state === 'processing');
|
|
371
|
+
const activeJelly = ctx.jellyfish.find(j => j.state === 'processing');
|
|
372
|
+
let attractTarget;
|
|
373
|
+
if (activeOct) {
|
|
374
|
+
attractTarget = { x: activeOct.x, y: activeOct.y };
|
|
375
|
+
}
|
|
376
|
+
else if (activeJelly) {
|
|
377
|
+
attractTarget = { x: activeJelly.x, y: activeJelly.y };
|
|
378
|
+
}
|
|
379
|
+
else if (ctx.crayfish.visible && ctx.crayfish.routing) {
|
|
380
|
+
attractTarget = { x: ctx.crayfish.x, y: ctx.crayfish.y };
|
|
381
|
+
}
|
|
382
|
+
for (let i = 0; i < ctx.schools.length; i++) {
|
|
383
|
+
updateSchool(ctx.schools[i], frame, i, attractTarget);
|
|
384
|
+
}
|
|
385
|
+
// Animate octopi Y — IDLE touches the floor (0.88), PROCESSING swims up
|
|
386
|
+
for (const oct of ctx.octopi) {
|
|
387
|
+
// Target Y: idle/disconnected/sleeping → floor, processing → swimming, awaiting → mid
|
|
388
|
+
const targetY = oct.state === 'processing' ? 0.30 :
|
|
389
|
+
oct.state.startsWith('awaiting') ? 0.50 :
|
|
390
|
+
0.88; // idle, disconnected → flush with sand
|
|
391
|
+
oct.y += (targetY - oct.y) * 0.05;
|
|
392
|
+
// Bob: small for idle (resting), larger for swimming
|
|
393
|
+
const bobAmp = oct.state === 'processing' ? 0.02 : 0.005;
|
|
394
|
+
const bobFreq = oct.state === 'processing' ? 0.15 : 0.04;
|
|
395
|
+
oct.y += Math.sin((frame + oct.phaseOffset) * bobFreq) * bobAmp;
|
|
396
|
+
}
|
|
397
|
+
// Animate jellyfish — near surface when processing, floor when idle
|
|
398
|
+
for (const jf of ctx.jellyfish) {
|
|
399
|
+
const isProcessing = jf.state === 'processing';
|
|
400
|
+
const targetY = isProcessing ? 0.10 :
|
|
401
|
+
jf.state.startsWith('awaiting') ? 0.50 :
|
|
402
|
+
0.85;
|
|
403
|
+
jf.y += (targetY - jf.y) * 0.03;
|
|
404
|
+
const bobAmp = isProcessing ? 0.015 : 0.01;
|
|
405
|
+
const bobFreq = isProcessing ? 0.12 : 0.03;
|
|
406
|
+
jf.y += Math.sin((frame + jf.phaseOffset) * bobFreq) * bobAmp;
|
|
407
|
+
// Processing: wide side-to-side drift near surface
|
|
408
|
+
const driftAmp = isProcessing ? 0.06 : 0.002;
|
|
409
|
+
const driftSpeed = isProcessing ? 0.02 : 0.02;
|
|
410
|
+
jf.x += Math.sin((frame + jf.phaseOffset) * driftSpeed) * driftAmp;
|
|
411
|
+
jf.x = Math.max(0.08, Math.min(0.65, jf.x));
|
|
412
|
+
}
|
|
413
|
+
// OpenCode Y — same state-Y mapping as octopus
|
|
414
|
+
for (const oc of ctx.opencode) {
|
|
415
|
+
const targetY = oc.state === 'processing' ? 0.30 :
|
|
416
|
+
oc.state.startsWith('awaiting') ? 0.50 : 0.88;
|
|
417
|
+
oc.y += (targetY - oc.y) * 0.04;
|
|
418
|
+
if (oc.state === 'processing') {
|
|
419
|
+
oc.y += Math.sin((frame + oc.phaseOffset) * 0.08) * 0.006;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Crayfish Y: routing swims up, sitting rests on floor
|
|
423
|
+
const crayfishTargetY = ctx.crayfish.routing ? 0.50 : 0.85;
|
|
424
|
+
ctx.crayfish.y += (crayfishTargetY - ctx.crayfish.y) * 0.04;
|
|
425
|
+
}
|
|
426
|
+
export function setOctopi(ctx, sessions) {
|
|
427
|
+
const octSessions = sessions.filter(s => s.agentType !== 'daemon' &&
|
|
428
|
+
s.agentType !== 'openclaw' &&
|
|
429
|
+
s.agentType !== 'codex-cli' &&
|
|
430
|
+
s.agentType !== 'opencode');
|
|
431
|
+
const count = octSessions.length;
|
|
432
|
+
// Count name occurrences to number duplicates
|
|
433
|
+
const nameCounts = new Map();
|
|
434
|
+
for (const s of octSessions) {
|
|
435
|
+
const n = s.name || '';
|
|
436
|
+
nameCounts.set(n, (nameCounts.get(n) || 0) + 1);
|
|
437
|
+
}
|
|
438
|
+
const nameSeq = new Map();
|
|
439
|
+
const newOctopi = [];
|
|
440
|
+
for (let i = 0; i < count; i++) {
|
|
441
|
+
const s = octSessions[i];
|
|
442
|
+
const sid = s.id || `oct-${i}`;
|
|
443
|
+
const baseName = s.name || '';
|
|
444
|
+
const seq = (nameSeq.get(baseName) || 0) + 1;
|
|
445
|
+
nameSeq.set(baseName, seq);
|
|
446
|
+
const displayName = (nameCounts.get(baseName) || 0) > 1
|
|
447
|
+
? `${baseName} #${seq}` : baseName;
|
|
448
|
+
const homeX = count === 1 ? 0.28 : 0.12 + (i * 0.40) / Math.max(1, count - 1);
|
|
449
|
+
const existing = ctx.octopi.find(o => o.id === sid);
|
|
450
|
+
if (existing) {
|
|
451
|
+
existing.state = s.state;
|
|
452
|
+
existing.name = displayName || undefined;
|
|
453
|
+
existing.homeX = homeX;
|
|
454
|
+
existing.x = homeX;
|
|
455
|
+
newOctopi.push(existing);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
newOctopi.push({
|
|
459
|
+
id: sid,
|
|
460
|
+
x: homeX, y: 0.88, homeX,
|
|
461
|
+
state: s.state, name: displayName || undefined,
|
|
462
|
+
phaseOffset: Math.floor(Math.random() * 40),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
ctx.octopi = newOctopi;
|
|
467
|
+
}
|
|
468
|
+
export function setCrayfish(ctx, visible, routing, name, sick) {
|
|
469
|
+
ctx.crayfish.visible = visible;
|
|
470
|
+
ctx.crayfish.routing = routing;
|
|
471
|
+
ctx.crayfish.sick = sick || false;
|
|
472
|
+
if (name !== undefined)
|
|
473
|
+
ctx.crayfish.name = name;
|
|
474
|
+
}
|
|
475
|
+
export function setJellyfish(ctx, sessions) {
|
|
476
|
+
const jellySessions = sessions.filter(s => s.agentType === 'codex-cli');
|
|
477
|
+
const count = jellySessions.length;
|
|
478
|
+
const nameCounts = new Map();
|
|
479
|
+
for (const s of jellySessions) {
|
|
480
|
+
const n = s.name || '';
|
|
481
|
+
nameCounts.set(n, (nameCounts.get(n) || 0) + 1);
|
|
482
|
+
}
|
|
483
|
+
const nameSeq = new Map();
|
|
484
|
+
const newJellyfish = [];
|
|
485
|
+
for (let i = 0; i < count; i++) {
|
|
486
|
+
const s = jellySessions[i];
|
|
487
|
+
const sid = s.id || `jf-${i}`;
|
|
488
|
+
const baseName = s.name || '';
|
|
489
|
+
const seq = (nameSeq.get(baseName) || 0) + 1;
|
|
490
|
+
nameSeq.set(baseName, seq);
|
|
491
|
+
const displayName = (nameCounts.get(baseName) || 0) > 1
|
|
492
|
+
? `${baseName} #${seq}` : baseName;
|
|
493
|
+
// Jellyfish home: right of center, between octopi and crayfish
|
|
494
|
+
const homeX = count === 1 ? 0.50 : 0.38 + (i * 0.25) / Math.max(1, count - 1);
|
|
495
|
+
const existing = ctx.jellyfish.find(j => j.id === sid);
|
|
496
|
+
if (existing) {
|
|
497
|
+
existing.state = s.state;
|
|
498
|
+
existing.name = displayName || undefined;
|
|
499
|
+
existing.homeX = homeX;
|
|
500
|
+
existing.x = homeX;
|
|
501
|
+
newJellyfish.push(existing);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
newJellyfish.push({
|
|
505
|
+
id: sid,
|
|
506
|
+
x: homeX, y: 0.55, homeX,
|
|
507
|
+
state: s.state, name: displayName || undefined,
|
|
508
|
+
phaseOffset: Math.floor(Math.random() * 60),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
ctx.jellyfish = newJellyfish;
|
|
513
|
+
}
|
|
514
|
+
export function setOpenCode(ctx, sessions) {
|
|
515
|
+
const ocSessions = sessions.filter(s => s.agentType === 'opencode');
|
|
516
|
+
const count = ocSessions.length;
|
|
517
|
+
const nameCounts = new Map();
|
|
518
|
+
for (const s of ocSessions) {
|
|
519
|
+
const n = s.name || '';
|
|
520
|
+
nameCounts.set(n, (nameCounts.get(n) || 0) + 1);
|
|
521
|
+
}
|
|
522
|
+
const nameSeq = new Map();
|
|
523
|
+
const newOc = [];
|
|
524
|
+
for (let i = 0; i < count; i++) {
|
|
525
|
+
const s = ocSessions[i];
|
|
526
|
+
const sid = s.id || `oc-${i}`;
|
|
527
|
+
const baseName = s.name || '';
|
|
528
|
+
const seq = (nameSeq.get(baseName) || 0) + 1;
|
|
529
|
+
nameSeq.set(baseName, seq);
|
|
530
|
+
const displayName = (nameCounts.get(baseName) || 0) > 1 ? `${baseName} #${seq}` : baseName;
|
|
531
|
+
const homeX = count === 1 ? 0.55 : 0.48 + (i * 0.20) / Math.max(1, count - 1);
|
|
532
|
+
const existing = ctx.opencode.find(o => o.id === sid);
|
|
533
|
+
if (existing) {
|
|
534
|
+
existing.state = s.state;
|
|
535
|
+
existing.name = displayName || undefined;
|
|
536
|
+
existing.homeX = homeX;
|
|
537
|
+
existing.x = homeX;
|
|
538
|
+
newOc.push(existing);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
newOc.push({ id: sid, x: homeX, y: 0.88, homeX, state: s.state, name: displayName || undefined, phaseOffset: Math.floor(Math.random() * 40) });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
ctx.opencode = newOc;
|
|
545
|
+
}
|
|
546
|
+
export function setVoiceAssistantState(ctx, state) {
|
|
547
|
+
ctx.voiceAssistantState = state;
|
|
548
|
+
}
|
|
549
|
+
// ===== Render Frame =====
|
|
550
|
+
export function renderTerrariumFrame(ctx, width, height, frame) {
|
|
551
|
+
if (height < 3 || width < 20)
|
|
552
|
+
return [];
|
|
553
|
+
const scale = getSpriteScale(width, height);
|
|
554
|
+
const scaleFactor = scale === 'xlarge' ? 3 : scale === 'large' ? 2 : 1;
|
|
555
|
+
const lines = [];
|
|
556
|
+
const sandRow = height - 2; // sand starts at this row
|
|
557
|
+
for (let row = 0; row < height; row++) {
|
|
558
|
+
const t = row / height;
|
|
559
|
+
const r = Math.floor(10 + t * 20);
|
|
560
|
+
const g = Math.floor(22 + t * 36);
|
|
561
|
+
const bv = Math.floor(40 + t * 55);
|
|
562
|
+
const bgColor = bg(r, g, bv);
|
|
563
|
+
const chars = new Array(width).fill(' ');
|
|
564
|
+
const charColors = new Array(width).fill('');
|
|
565
|
+
// Water surface wave (row 0)
|
|
566
|
+
if (row === 0) {
|
|
567
|
+
for (let x = 0; x < width; x++) {
|
|
568
|
+
chars[x] = WAVE_CHARS[(x + frame) % WAVE_CHARS.length];
|
|
569
|
+
charColors[x] = fg(100, 149, 237);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// Sand/gravel bottom (last 2 rows)
|
|
573
|
+
if (row >= sandRow) {
|
|
574
|
+
for (let x = 0; x < width; x++) {
|
|
575
|
+
const sandChars = row === height - 1 ? '░▒░░░░▒▒░░' : '░░░░▒░░░░░';
|
|
576
|
+
chars[x] = sandChars[(x * 7 + 3) % sandChars.length];
|
|
577
|
+
charColors[x] = colors.sand;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// Seaweed
|
|
581
|
+
if (row >= height - 5 && row < sandRow) {
|
|
582
|
+
const positions = [0.04, 0.10, 0.18, 0.85, 0.92, 0.97];
|
|
583
|
+
for (const pos of positions) {
|
|
584
|
+
const sx = Math.floor(pos * width);
|
|
585
|
+
if (sx >= 0 && sx < width) {
|
|
586
|
+
const depth = sandRow - row;
|
|
587
|
+
if (depth <= 1)
|
|
588
|
+
chars[sx] = '\u2502';
|
|
589
|
+
else
|
|
590
|
+
chars[sx] = Math.sin(frame * 0.05 + pos * 15) > 0 ? '\u2571' : '\u2572';
|
|
591
|
+
charColors[sx] = colors.seaweed;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Bubbles
|
|
596
|
+
for (const b of ctx.bubbles) {
|
|
597
|
+
const bx = Math.floor(b.x * width);
|
|
598
|
+
const by = Math.floor(b.y * height);
|
|
599
|
+
if (by === row && bx >= 0 && bx < width) {
|
|
600
|
+
chars[bx] = b.char;
|
|
601
|
+
charColors[bx] = colors.bubble;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// Fish (scale-aware: small=3, large=5, xlarge=7 chars)
|
|
605
|
+
for (const school of ctx.schools) {
|
|
606
|
+
for (const f of school.fish) {
|
|
607
|
+
const fx = Math.floor(f.x * width);
|
|
608
|
+
const fy = Math.floor(f.y * height);
|
|
609
|
+
const fishStrs = scale === 'xlarge'
|
|
610
|
+
? (f.vx > 0 ? '>>><>>>' : '<<<><<<')
|
|
611
|
+
: scale === 'large'
|
|
612
|
+
? (f.vx > 0 ? '>><>>' : '<<><>')
|
|
613
|
+
: (f.vx > 0 ? '><>' : '<><');
|
|
614
|
+
const fishLen = fishStrs.length;
|
|
615
|
+
const midIdx = Math.floor(fishLen / 2);
|
|
616
|
+
if (fy === row && fx >= 1 && fx < width - fishLen + 1) {
|
|
617
|
+
for (let c = 0; c < fishLen; c++) {
|
|
618
|
+
if (fx + c < width) {
|
|
619
|
+
chars[fx + c] = fishStrs[c];
|
|
620
|
+
charColors[fx + c] = c === midIdx ? colors.tetraStripe : colors.tetraNeon;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Octopi (scale-aware braille)
|
|
627
|
+
for (const oct of ctx.octopi) {
|
|
628
|
+
const { braille, color } = renderOctopus(oct, frame, scale);
|
|
629
|
+
const octHalfW = Math.floor(braille[0]?.length / 2) || 3;
|
|
630
|
+
const ox = Math.floor(oct.x * width) - octHalfW;
|
|
631
|
+
const oy = Math.floor(oct.y * height) - Math.floor(braille.length / 2);
|
|
632
|
+
for (let br = 0; br < braille.length; br++) {
|
|
633
|
+
if (oy + br === row) {
|
|
634
|
+
for (let bc = 0; bc < braille[br].length; bc++) {
|
|
635
|
+
const px = ox + bc;
|
|
636
|
+
if (px >= 0 && px < width) {
|
|
637
|
+
chars[px] = braille[br][bc];
|
|
638
|
+
charColors[px] = color;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Name tag — directly above braille sprite
|
|
644
|
+
if (oct.name && oy - 1 === row) {
|
|
645
|
+
const name = oct.name.length > 12 ? oct.name.slice(0, 11) + '\u2026' : oct.name;
|
|
646
|
+
const nx = Math.floor(oct.x * width) - Math.floor(name.length / 2);
|
|
647
|
+
for (let nc = 0; nc < name.length; nc++) {
|
|
648
|
+
const px = nx + nc;
|
|
649
|
+
if (px >= 0 && px < width) {
|
|
650
|
+
chars[px] = name[nc];
|
|
651
|
+
charColors[px] = fg(180, 180, 180);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// "?" bubble — below sprite (not on name tag row, to avoid overlap)
|
|
656
|
+
if (oct.state.startsWith('awaiting') && oy + braille.length === row) {
|
|
657
|
+
const qx = Math.floor(oct.x * width) + octHalfW + 1;
|
|
658
|
+
if (qx >= 0 && qx < width) {
|
|
659
|
+
chars[qx] = '?';
|
|
660
|
+
charColors[qx] = fg(255, 255, 100);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
// Voice assistant indicator — above active octopus (first octopus or processing one)
|
|
664
|
+
if (ctx.voiceAssistantState !== 'disabled' && ctx.voiceAssistantState !== 'idle') {
|
|
665
|
+
const isVoiceTarget = ctx.octopi.length <= 1 || oct === ctx.octopi.find(o => o.state === 'processing') || oct === ctx.octopi[0];
|
|
666
|
+
if (isVoiceTarget) {
|
|
667
|
+
if (ctx.voiceAssistantState === 'listening') {
|
|
668
|
+
// Musical note above octopus
|
|
669
|
+
if (oy - 1 === row) {
|
|
670
|
+
const mX = Math.floor(oct.x * width) + octHalfW + 2;
|
|
671
|
+
if (mX >= 0 && mX < width) {
|
|
672
|
+
chars[mX] = '\u266A';
|
|
673
|
+
charColors[mX] = fg(0, 220, 220);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Expanding listening circles (radio wave visualization)
|
|
677
|
+
for (let ring = 0; ring < 2; ring++) {
|
|
678
|
+
const ringPhase = (frame * 0.15 + ring * 2.5) % 5;
|
|
679
|
+
const radius = (1.5 + ringPhase) * scaleFactor * 0.6;
|
|
680
|
+
for (let p = 0; p < 6; p++) {
|
|
681
|
+
const angle = (p / 6) * Math.PI * 2;
|
|
682
|
+
const px = Math.floor(oct.x * width + Math.cos(angle) * radius);
|
|
683
|
+
const py = Math.floor(oct.y * height + Math.sin(angle) * radius * 0.5);
|
|
684
|
+
if (py === row && px >= 0 && px < width && chars[px] === ' ') {
|
|
685
|
+
chars[px] = '\u00B7';
|
|
686
|
+
charColors[px] = fg(0, 200, 200);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else if (ctx.voiceAssistantState === 'processing') {
|
|
692
|
+
// Animated "..." dots above octopus
|
|
693
|
+
if (oy - 1 === row) {
|
|
694
|
+
const dotCount = (Math.floor(frame / 5) % 3) + 1;
|
|
695
|
+
const dots = '.'.repeat(dotCount);
|
|
696
|
+
const dX = Math.floor(oct.x * width) + octHalfW + 2;
|
|
697
|
+
for (let d = 0; d < dots.length; d++) {
|
|
698
|
+
const px = dX + d;
|
|
699
|
+
if (px >= 0 && px < width) {
|
|
700
|
+
chars[px] = '.';
|
|
701
|
+
charColors[px] = fg(220, 180, 50);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
else if (ctx.voiceAssistantState === 'speaking') {
|
|
707
|
+
// Musical double note above octopus
|
|
708
|
+
if (oy - 1 === row) {
|
|
709
|
+
const mX = Math.floor(oct.x * width) + octHalfW + 2;
|
|
710
|
+
if (mX >= 0 && mX < width) {
|
|
711
|
+
chars[mX] = '\u266C';
|
|
712
|
+
charColors[mX] = fg(50, 220, 50);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Wave pattern around octopus
|
|
716
|
+
for (let w = 0; w < 4; w++) {
|
|
717
|
+
const wAngle = (w / 4) * Math.PI * 2 + frame * 0.2;
|
|
718
|
+
const wR = (2.0 + Math.sin(frame * 0.1) * 0.5) * scaleFactor * 0.5;
|
|
719
|
+
const px = Math.floor(oct.x * width + Math.cos(wAngle) * wR);
|
|
720
|
+
const py = Math.floor(oct.y * height + Math.sin(wAngle) * wR * 0.5);
|
|
721
|
+
if (py === row && px >= 0 && px < width && chars[px] === ' ') {
|
|
722
|
+
chars[px] = '\u223F';
|
|
723
|
+
charColors[px] = fg(50, 200, 50);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// Starburst particles
|
|
730
|
+
if (oct.state === 'processing') {
|
|
731
|
+
const burstR = (2 + (frame % 8) * 0.3) * (scaleFactor * 0.75);
|
|
732
|
+
for (let p = 0; p < 6; p++) {
|
|
733
|
+
const angle = (p / 6) * Math.PI * 2 + frame * 0.15;
|
|
734
|
+
const px = Math.floor(oct.x * width + Math.cos(angle) * burstR);
|
|
735
|
+
const py = Math.floor(oct.y * height + Math.sin(angle) * burstR * 0.5);
|
|
736
|
+
if (py === row && px >= 0 && px < width) {
|
|
737
|
+
chars[px] = '\u2727';
|
|
738
|
+
charColors[px] = fg(255, 200, 100);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Jellyfish (scale-aware braille)
|
|
744
|
+
for (const jf of ctx.jellyfish) {
|
|
745
|
+
const { braille, color } = renderJellyfish(jf, frame, scale);
|
|
746
|
+
const jfHalfW = Math.floor(braille[0]?.length / 2) || 3;
|
|
747
|
+
const jx = Math.floor(jf.x * width) - jfHalfW;
|
|
748
|
+
const jy = Math.floor(jf.y * height) - Math.floor(braille.length / 2);
|
|
749
|
+
for (let br = 0; br < braille.length; br++) {
|
|
750
|
+
if (jy + br === row) {
|
|
751
|
+
for (let bc = 0; bc < braille[br].length; bc++) {
|
|
752
|
+
const px = jx + bc;
|
|
753
|
+
if (px >= 0 && px < width) {
|
|
754
|
+
chars[px] = braille[br][bc];
|
|
755
|
+
charColors[px] = color;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Name tag
|
|
761
|
+
if (jf.name && jy - 1 === row) {
|
|
762
|
+
const name = jf.name.length > 12 ? jf.name.slice(0, 11) + '\u2026' : jf.name;
|
|
763
|
+
const nx = Math.floor(jf.x * width) - Math.floor(name.length / 2);
|
|
764
|
+
for (let nc = 0; nc < name.length; nc++) {
|
|
765
|
+
const px = nx + nc;
|
|
766
|
+
if (px >= 0 && px < width) {
|
|
767
|
+
chars[px] = name[nc];
|
|
768
|
+
charColors[px] = fg(180, 180, 180);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// "?" bubble when awaiting
|
|
773
|
+
if (jf.state.startsWith('awaiting') && jy + braille.length === row) {
|
|
774
|
+
const qx = Math.floor(jf.x * width) + jfHalfW + 1;
|
|
775
|
+
if (qx >= 0 && qx < width) {
|
|
776
|
+
chars[qx] = '?';
|
|
777
|
+
charColors[qx] = fg(255, 255, 100);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
// Bioluminescent glow particles when processing
|
|
781
|
+
if (jf.state === 'processing') {
|
|
782
|
+
const glowR = (1.5 + Math.sin(frame * 0.1) * 0.5) * (scaleFactor * 0.75);
|
|
783
|
+
for (let p = 0; p < 4; p++) {
|
|
784
|
+
const angle = (p / 4) * Math.PI * 2 + frame * 0.08;
|
|
785
|
+
const px = Math.floor(jf.x * width + Math.cos(angle) * glowR);
|
|
786
|
+
const py = Math.floor(jf.y * height + Math.sin(angle) * glowR * 0.6);
|
|
787
|
+
if (py === row && px >= 0 && px < width && chars[px] === ' ') {
|
|
788
|
+
chars[px] = '\u2022'; // •
|
|
789
|
+
charColors[px] = colors.jellyfishGlow;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
// OpenCode (box-drawing nested square)
|
|
795
|
+
for (const oc of ctx.opencode) {
|
|
796
|
+
const { lines, color } = renderOpenCode(oc, frame, scale);
|
|
797
|
+
const ocHalfW = Math.floor((lines[0]?.replace(/\x1b\[[^m]*m/g, '').length ?? 5) / 2);
|
|
798
|
+
const ox = Math.floor(oc.x * width) - ocHalfW;
|
|
799
|
+
const oy = Math.floor(oc.y * height) - Math.floor(lines.length / 2);
|
|
800
|
+
for (let lr = 0; lr < lines.length; lr++) {
|
|
801
|
+
if (oy + lr === row) {
|
|
802
|
+
const stripped = lines[lr].replace(/\x1b\[[^m]*m/g, '');
|
|
803
|
+
for (let ci = 0; ci < stripped.length; ci++) {
|
|
804
|
+
const px = ox + ci;
|
|
805
|
+
if (px >= 0 && px < width) {
|
|
806
|
+
chars[px] = stripped[ci];
|
|
807
|
+
charColors[px] = color;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (oc.name && oy - 1 === row) {
|
|
813
|
+
const name = oc.name.length > 12 ? oc.name.slice(0, 11) + '\u2026' : oc.name;
|
|
814
|
+
const nx = Math.floor(oc.x * width) - Math.floor(name.length / 2);
|
|
815
|
+
for (let nc = 0; nc < name.length; nc++) {
|
|
816
|
+
const px = nx + nc;
|
|
817
|
+
if (px >= 0 && px < width) {
|
|
818
|
+
chars[px] = name[nc];
|
|
819
|
+
charColors[px] = fg(180, 180, 180);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (oc.state.startsWith('awaiting') && oy + lines.length === row) {
|
|
824
|
+
const qx = Math.floor(oc.x * width) + ocHalfW + 1;
|
|
825
|
+
if (qx >= 0 && qx < width) {
|
|
826
|
+
chars[qx] = '?';
|
|
827
|
+
charColors[qx] = fg(255, 255, 100);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Crayfish (scale-aware braille)
|
|
832
|
+
if (ctx.crayfish.visible) {
|
|
833
|
+
const { braille, color } = renderCrayfish(ctx.crayfish, frame, scale);
|
|
834
|
+
const cfHalfW = Math.floor(braille[0]?.length / 2) || 4;
|
|
835
|
+
const cx = Math.floor(ctx.crayfish.x * width) - cfHalfW;
|
|
836
|
+
const cy = Math.floor(ctx.crayfish.y * height) - Math.floor(braille.length / 2);
|
|
837
|
+
for (let br = 0; br < braille.length; br++) {
|
|
838
|
+
if (cy + br === row) {
|
|
839
|
+
for (let bc = 0; bc < braille[br].length; bc++) {
|
|
840
|
+
const px = cx + bc;
|
|
841
|
+
if (px >= 0 && px < width) {
|
|
842
|
+
chars[px] = braille[br][bc];
|
|
843
|
+
charColors[px] = color;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Crayfish name tag — directly above braille sprite
|
|
849
|
+
const cfBaseName = ctx.crayfish.name || 'OpenClaw';
|
|
850
|
+
const cfName = ctx.crayfish.sick ? `\u26A0 ${cfBaseName}` : cfBaseName;
|
|
851
|
+
const cfNameColor = ctx.crayfish.sick ? fg(200, 120, 120) : fg(180, 180, 180);
|
|
852
|
+
if (cy - 1 === row) {
|
|
853
|
+
const nx = Math.floor(ctx.crayfish.x * width) - Math.floor(cfName.length / 2);
|
|
854
|
+
for (let nc = 0; nc < cfName.length; nc++) {
|
|
855
|
+
const px = nx + nc;
|
|
856
|
+
if (px >= 0 && px < width) {
|
|
857
|
+
chars[px] = cfName[nc];
|
|
858
|
+
charColors[px] = cfNameColor;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
// Signal wave rings + orbiting dots when ROUTING
|
|
863
|
+
if (ctx.crayfish.routing) {
|
|
864
|
+
const cfCenterX = ctx.crayfish.x * width;
|
|
865
|
+
const cfCenterY = ctx.crayfish.y * height;
|
|
866
|
+
const waveScale = scaleFactor * 0.75;
|
|
867
|
+
const waveChars = ['\u25E6', '\u00B7', '\u2219']; // ◦ · ∙
|
|
868
|
+
// 3 concentric signal wave rings, expanding outward
|
|
869
|
+
for (let ring = 0; ring < 3; ring++) {
|
|
870
|
+
const ringPhase = (frame * 0.12 + ring * 2.1) % 6;
|
|
871
|
+
const radius = (2 + ringPhase) * waveScale;
|
|
872
|
+
// Semi-circle (upper half — signals radiate upward/outward)
|
|
873
|
+
for (let p = 0; p < 8; p++) {
|
|
874
|
+
const angle = (p / 8) * Math.PI + Math.PI; // upper semicircle
|
|
875
|
+
const px = Math.floor(cfCenterX + Math.cos(angle) * radius);
|
|
876
|
+
const py = Math.floor(cfCenterY + Math.sin(angle) * radius * 0.5);
|
|
877
|
+
if (py === row && px >= 0 && px < width && chars[px] === ' ') {
|
|
878
|
+
chars[px] = waveChars[ring];
|
|
879
|
+
// Fade opacity with distance
|
|
880
|
+
const fade = Math.max(0, 1 - ringPhase / 6);
|
|
881
|
+
const r = Math.floor(255 * fade);
|
|
882
|
+
const g = Math.floor(107 * fade);
|
|
883
|
+
const b = Math.floor(107 * fade);
|
|
884
|
+
charColors[px] = fg(Math.max(r, 60), Math.max(g, 30), Math.max(b, 30));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// 4 orbiting signal dots (cyan ✦ — contrasts with octopus gold ✧)
|
|
889
|
+
for (let d = 0; d < 4; d++) {
|
|
890
|
+
const orbitAngle = (d / 4) * Math.PI * 2 + frame * 0.2;
|
|
891
|
+
const orbitRx = (3.5 + Math.sin(frame * 0.08) * 0.5) * waveScale;
|
|
892
|
+
const orbitRy = (1.8 + Math.cos(frame * 0.08) * 0.3) * waveScale;
|
|
893
|
+
const px = Math.floor(cfCenterX + Math.cos(orbitAngle) * orbitRx);
|
|
894
|
+
const py = Math.floor(cfCenterY + Math.sin(orbitAngle) * orbitRy);
|
|
895
|
+
if (py === row && px >= 0 && px < width && chars[px] === ' ') {
|
|
896
|
+
chars[px] = '\u2726'; // ✦
|
|
897
|
+
charColors[px] = colors.tetraNeon;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Build line
|
|
903
|
+
let line = bgColor;
|
|
904
|
+
for (let x = 0; x < width; x++) {
|
|
905
|
+
line += charColors[x] ? charColors[x] + chars[x] : chars[x];
|
|
906
|
+
}
|
|
907
|
+
line += RESET;
|
|
908
|
+
lines.push(line);
|
|
909
|
+
}
|
|
910
|
+
return lines;
|
|
911
|
+
}
|
|
912
|
+
//# sourceMappingURL=terrarium.js.map
|