@agentdeck/bridge 0.2.2 → 0.2.3

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 (383) hide show
  1. package/assets/fonts/IBMPlexSans-Bold.ttf +0 -0
  2. package/assets/fonts/IBMPlexSans-Regular.ttf +0 -0
  3. package/assets/fonts/JetBrainsMono-Bold.ttf +0 -0
  4. package/assets/fonts/JetBrainsMono-Regular.ttf +0 -0
  5. package/assets/fonts/LICENSES.md +22 -0
  6. package/dist/adapters/claude-code.d.ts +6 -0
  7. package/dist/adapters/claude-code.d.ts.map +1 -1
  8. package/dist/adapters/claude-code.js +8 -0
  9. package/dist/adapters/claude-code.js.map +1 -1
  10. package/dist/adapters/openclaw.d.ts +24 -0
  11. package/dist/adapters/openclaw.d.ts.map +1 -1
  12. package/dist/adapters/openclaw.js +291 -35
  13. package/dist/adapters/openclaw.js.map +1 -1
  14. package/dist/adapters/opencode-adapter.d.ts +59 -0
  15. package/dist/adapters/opencode-adapter.d.ts.map +1 -1
  16. package/dist/adapters/opencode-adapter.js +297 -35
  17. package/dist/adapters/opencode-adapter.js.map +1 -1
  18. package/dist/adb-reverse.d.ts +1 -1
  19. package/dist/adb-reverse.js +1 -1
  20. package/dist/antigravity-local.d.ts +1 -0
  21. package/dist/antigravity-local.d.ts.map +1 -1
  22. package/dist/antigravity-local.js +33 -0
  23. package/dist/antigravity-local.js.map +1 -1
  24. package/dist/apme/adapters/claude-hook.d.ts.map +1 -1
  25. package/dist/apme/adapters/claude-hook.js +5 -0
  26. package/dist/apme/adapters/claude-hook.js.map +1 -1
  27. package/dist/apme/adapters/codex-hook.d.ts +17 -0
  28. package/dist/apme/adapters/codex-hook.d.ts.map +1 -0
  29. package/dist/apme/adapters/codex-hook.js +95 -0
  30. package/dist/apme/adapters/codex-hook.js.map +1 -0
  31. package/dist/apme/adapters/codex-turn-manager.d.ts +86 -0
  32. package/dist/apme/adapters/codex-turn-manager.d.ts.map +1 -0
  33. package/dist/apme/adapters/codex-turn-manager.js +496 -0
  34. package/dist/apme/adapters/codex-turn-manager.js.map +1 -0
  35. package/dist/apme/adapters/openclaw-hook.d.ts +99 -0
  36. package/dist/apme/adapters/openclaw-hook.d.ts.map +1 -0
  37. package/dist/apme/adapters/openclaw-hook.js +234 -0
  38. package/dist/apme/adapters/openclaw-hook.js.map +1 -0
  39. package/dist/apme/adapters/opencode-hook.d.ts +56 -0
  40. package/dist/apme/adapters/opencode-hook.d.ts.map +1 -0
  41. package/dist/apme/adapters/opencode-hook.js +182 -0
  42. package/dist/apme/adapters/opencode-hook.js.map +1 -0
  43. package/dist/apme/adapters/timeline.d.ts.map +1 -1
  44. package/dist/apme/adapters/timeline.js +6 -1
  45. package/dist/apme/adapters/timeline.js.map +1 -1
  46. package/dist/apme/antigravity-transcript.d.ts +18 -0
  47. package/dist/apme/antigravity-transcript.d.ts.map +1 -0
  48. package/dist/apme/antigravity-transcript.js +94 -0
  49. package/dist/apme/antigravity-transcript.js.map +1 -0
  50. package/dist/apme/classify-turn.d.ts +13 -0
  51. package/dist/apme/classify-turn.d.ts.map +1 -0
  52. package/dist/apme/classify-turn.js +49 -0
  53. package/dist/apme/classify-turn.js.map +1 -0
  54. package/dist/apme/claude-transcript-reader.d.ts +10 -0
  55. package/dist/apme/claude-transcript-reader.d.ts.map +1 -1
  56. package/dist/apme/claude-transcript-reader.js +35 -0
  57. package/dist/apme/claude-transcript-reader.js.map +1 -1
  58. package/dist/apme/collector.d.ts +129 -6
  59. package/dist/apme/collector.d.ts.map +1 -1
  60. package/dist/apme/collector.js +470 -23
  61. package/dist/apme/collector.js.map +1 -1
  62. package/dist/apme/dashboard-html.d.ts.map +1 -1
  63. package/dist/apme/dashboard-html.js +87 -11
  64. package/dist/apme/dashboard-html.js.map +1 -1
  65. package/dist/apme/http.d.ts.map +1 -1
  66. package/dist/apme/http.js +43 -6
  67. package/dist/apme/http.js.map +1 -1
  68. package/dist/apme/index.d.ts +26 -5
  69. package/dist/apme/index.d.ts.map +1 -1
  70. package/dist/apme/index.js +135 -9
  71. package/dist/apme/index.js.map +1 -1
  72. package/dist/apme/pareto.d.ts +39 -0
  73. package/dist/apme/pareto.d.ts.map +1 -0
  74. package/dist/apme/pareto.js +59 -0
  75. package/dist/apme/pareto.js.map +1 -0
  76. package/dist/apme/recommend.d.ts +3 -0
  77. package/dist/apme/recommend.d.ts.map +1 -1
  78. package/dist/apme/recommend.js +43 -1
  79. package/dist/apme/recommend.js.map +1 -1
  80. package/dist/apme/runner.d.ts +92 -4
  81. package/dist/apme/runner.d.ts.map +1 -1
  82. package/dist/apme/runner.js +586 -70
  83. package/dist/apme/runner.js.map +1 -1
  84. package/dist/apme/sample-to-timeline.d.ts +26 -0
  85. package/dist/apme/sample-to-timeline.d.ts.map +1 -0
  86. package/dist/apme/sample-to-timeline.js +95 -0
  87. package/dist/apme/sample-to-timeline.js.map +1 -0
  88. package/dist/apme/scorers/index.d.ts +47 -0
  89. package/dist/apme/scorers/index.d.ts.map +1 -0
  90. package/dist/apme/scorers/index.js +96 -0
  91. package/dist/apme/scorers/index.js.map +1 -0
  92. package/dist/apme/settings.d.ts +14 -10
  93. package/dist/apme/settings.d.ts.map +1 -1
  94. package/dist/apme/settings.js +34 -8
  95. package/dist/apme/settings.js.map +1 -1
  96. package/dist/apme/store.d.ts +33 -1
  97. package/dist/apme/store.d.ts.map +1 -1
  98. package/dist/apme/store.js +334 -25
  99. package/dist/apme/store.js.map +1 -1
  100. package/dist/awaiting-overlay.d.ts +99 -0
  101. package/dist/awaiting-overlay.d.ts.map +1 -0
  102. package/dist/awaiting-overlay.js +148 -0
  103. package/dist/awaiting-overlay.js.map +1 -0
  104. package/dist/ble-sync-spawn.d.ts +63 -0
  105. package/dist/ble-sync-spawn.d.ts.map +1 -0
  106. package/dist/ble-sync-spawn.js +196 -0
  107. package/dist/ble-sync-spawn.js.map +1 -0
  108. package/dist/bridge-core.d.ts +24 -1
  109. package/dist/bridge-core.d.ts.map +1 -1
  110. package/dist/bridge-core.js +157 -24
  111. package/dist/bridge-core.js.map +1 -1
  112. package/dist/broadcast.d.ts +3 -0
  113. package/dist/broadcast.d.ts.map +1 -0
  114. package/dist/broadcast.js +122 -0
  115. package/dist/broadcast.js.map +1 -0
  116. package/dist/check-deps.d.ts.map +1 -1
  117. package/dist/check-deps.js +25 -9
  118. package/dist/check-deps.js.map +1 -1
  119. package/dist/cli.d.ts +15 -1
  120. package/dist/cli.d.ts.map +1 -1
  121. package/dist/cli.js +1021 -93
  122. package/dist/cli.js.map +1 -1
  123. package/dist/codex-auth.d.ts +21 -0
  124. package/dist/codex-auth.d.ts.map +1 -1
  125. package/dist/codex-auth.js +52 -1
  126. package/dist/codex-auth.js.map +1 -1
  127. package/dist/codex-output-parser.d.ts +8 -0
  128. package/dist/codex-output-parser.d.ts.map +1 -1
  129. package/dist/codex-output-parser.js +110 -25
  130. package/dist/codex-output-parser.js.map +1 -1
  131. package/dist/codex-rate-limits.d.ts +16 -0
  132. package/dist/codex-rate-limits.d.ts.map +1 -0
  133. package/dist/codex-rate-limits.js +197 -0
  134. package/dist/codex-rate-limits.js.map +1 -0
  135. package/dist/codex-rollout-response.d.ts +31 -0
  136. package/dist/codex-rollout-response.d.ts.map +1 -0
  137. package/dist/codex-rollout-response.js +135 -0
  138. package/dist/codex-rollout-response.js.map +1 -0
  139. package/dist/daemon-server.d.ts +85 -0
  140. package/dist/daemon-server.d.ts.map +1 -1
  141. package/dist/daemon-server.js +1627 -159
  142. package/dist/daemon-server.js.map +1 -1
  143. package/dist/daemon.js +15 -6
  144. package/dist/daemon.js.map +1 -1
  145. package/dist/display-dim.d.ts +9 -0
  146. package/dist/display-dim.d.ts.map +1 -0
  147. package/dist/display-dim.js +53 -0
  148. package/dist/display-dim.js.map +1 -0
  149. package/dist/display-monitor.d.ts +15 -2
  150. package/dist/display-monitor.d.ts.map +1 -1
  151. package/dist/display-monitor.js +104 -26
  152. package/dist/display-monitor.js.map +1 -1
  153. package/dist/esp32-serial.d.ts +160 -1
  154. package/dist/esp32-serial.d.ts.map +1 -1
  155. package/dist/esp32-serial.js +950 -153
  156. package/dist/esp32-serial.js.map +1 -1
  157. package/dist/fallback-task-timeline.d.ts +42 -0
  158. package/dist/fallback-task-timeline.d.ts.map +1 -0
  159. package/dist/fallback-task-timeline.js +112 -0
  160. package/dist/fallback-task-timeline.js.map +1 -0
  161. package/dist/foundation-models-helper.d.ts +11 -0
  162. package/dist/foundation-models-helper.d.ts.map +1 -0
  163. package/dist/foundation-models-helper.js +241 -0
  164. package/dist/foundation-models-helper.js.map +1 -0
  165. package/dist/hook-migration.js +1 -1
  166. package/dist/hook-migration.js.map +1 -1
  167. package/dist/hook-server.d.ts +2 -3
  168. package/dist/hook-server.d.ts.map +1 -1
  169. package/dist/hook-server.js +22 -10
  170. package/dist/hook-server.js.map +1 -1
  171. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts +33 -0
  172. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -0
  173. package/dist/idotmatrix/idotmatrix-daemon-sync.js +164 -0
  174. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -0
  175. package/dist/idotmatrix/idotmatrix-discover.d.ts +18 -0
  176. package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -0
  177. package/dist/idotmatrix/idotmatrix-discover.js +85 -0
  178. package/dist/idotmatrix/idotmatrix-discover.js.map +1 -0
  179. package/dist/idotmatrix/idotmatrix-settings.d.ts +20 -0
  180. package/dist/idotmatrix/idotmatrix-settings.d.ts.map +1 -0
  181. package/dist/idotmatrix/idotmatrix-settings.js +57 -0
  182. package/dist/idotmatrix/idotmatrix-settings.js.map +1 -0
  183. package/dist/index.d.ts.map +1 -1
  184. package/dist/index.js +215 -179
  185. package/dist/index.js.map +1 -1
  186. package/dist/log-stream.d.ts +15 -16
  187. package/dist/log-stream.d.ts.map +1 -1
  188. package/dist/log-stream.js +18 -96
  189. package/dist/log-stream.js.map +1 -1
  190. package/dist/logger.d.ts.map +1 -1
  191. package/dist/logger.js +12 -3
  192. package/dist/logger.js.map +1 -1
  193. package/dist/mdns.d.ts +17 -0
  194. package/dist/mdns.d.ts.map +1 -1
  195. package/dist/mdns.js +52 -18
  196. package/dist/mdns.js.map +1 -1
  197. package/dist/modules/adb-module.d.ts +1 -1
  198. package/dist/modules/adb-module.d.ts.map +1 -1
  199. package/dist/modules/adb-module.js +4 -3
  200. package/dist/modules/adb-module.js.map +1 -1
  201. package/dist/modules/broadcast-module.d.ts +17 -0
  202. package/dist/modules/broadcast-module.d.ts.map +1 -0
  203. package/dist/modules/broadcast-module.js +26 -0
  204. package/dist/modules/broadcast-module.js.map +1 -0
  205. package/dist/modules/idotmatrix-module.d.ts +9 -0
  206. package/dist/modules/idotmatrix-module.d.ts.map +1 -0
  207. package/dist/modules/idotmatrix-module.js +46 -0
  208. package/dist/modules/idotmatrix-module.js.map +1 -0
  209. package/dist/modules/index.d.ts +3 -1
  210. package/dist/modules/index.d.ts.map +1 -1
  211. package/dist/modules/index.js +9 -3
  212. package/dist/modules/index.js.map +1 -1
  213. package/dist/modules/pixoo-module.d.ts.map +1 -1
  214. package/dist/modules/pixoo-module.js +14 -4
  215. package/dist/modules/pixoo-module.js.map +1 -1
  216. package/dist/modules/serial-module.d.ts +4 -0
  217. package/dist/modules/serial-module.d.ts.map +1 -1
  218. package/dist/modules/serial-module.js +14 -12
  219. package/dist/modules/serial-module.js.map +1 -1
  220. package/dist/modules/timebox-module.d.ts +9 -0
  221. package/dist/modules/timebox-module.d.ts.map +1 -0
  222. package/dist/modules/timebox-module.js +46 -0
  223. package/dist/modules/timebox-module.js.map +1 -0
  224. package/dist/modules/types.d.ts +4 -2
  225. package/dist/modules/types.d.ts.map +1 -1
  226. package/dist/openclaw-session.d.ts +18 -0
  227. package/dist/openclaw-session.d.ts.map +1 -0
  228. package/dist/openclaw-session.js +34 -0
  229. package/dist/openclaw-session.js.map +1 -0
  230. package/dist/output-parser.d.ts +30 -0
  231. package/dist/output-parser.d.ts.map +1 -1
  232. package/dist/output-parser.js +125 -9
  233. package/dist/output-parser.js.map +1 -1
  234. package/dist/passive-observer.d.ts +26 -0
  235. package/dist/passive-observer.d.ts.map +1 -1
  236. package/dist/passive-observer.js +258 -49
  237. package/dist/passive-observer.js.map +1 -1
  238. package/dist/permission-resolver.d.ts +47 -0
  239. package/dist/permission-resolver.d.ts.map +1 -0
  240. package/dist/permission-resolver.js +103 -0
  241. package/dist/permission-resolver.js.map +1 -0
  242. package/dist/pixoo/micro-glyphs.d.ts +49 -0
  243. package/dist/pixoo/micro-glyphs.d.ts.map +1 -0
  244. package/dist/pixoo/micro-glyphs.js +276 -0
  245. package/dist/pixoo/micro-glyphs.js.map +1 -0
  246. package/dist/pixoo/pixoo-bridge.d.ts +2 -2
  247. package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
  248. package/dist/pixoo/pixoo-bridge.js +93 -25
  249. package/dist/pixoo/pixoo-bridge.js.map +1 -1
  250. package/dist/pixoo/pixoo-camera.d.ts +19 -3
  251. package/dist/pixoo/pixoo-camera.d.ts.map +1 -1
  252. package/dist/pixoo/pixoo-camera.js +62 -27
  253. package/dist/pixoo/pixoo-camera.js.map +1 -1
  254. package/dist/pixoo/pixoo-client.d.ts +13 -3
  255. package/dist/pixoo/pixoo-client.d.ts.map +1 -1
  256. package/dist/pixoo/pixoo-client.js +51 -22
  257. package/dist/pixoo/pixoo-client.js.map +1 -1
  258. package/dist/pixoo/pixoo-discover.d.ts +37 -0
  259. package/dist/pixoo/pixoo-discover.d.ts.map +1 -0
  260. package/dist/pixoo/pixoo-discover.js +105 -0
  261. package/dist/pixoo/pixoo-discover.js.map +1 -0
  262. package/dist/pixoo/pixoo-renderer.d.ts +8 -3
  263. package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
  264. package/dist/pixoo/pixoo-renderer.js +189 -60
  265. package/dist/pixoo/pixoo-renderer.js.map +1 -1
  266. package/dist/pixoo/pixoo-settings.d.ts +6 -0
  267. package/dist/pixoo/pixoo-settings.d.ts.map +1 -1
  268. package/dist/pixoo/pixoo-settings.js +9 -0
  269. package/dist/pixoo/pixoo-settings.js.map +1 -1
  270. package/dist/pixoo/pixoo-sprites.d.ts +60 -11
  271. package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
  272. package/dist/pixoo/pixoo-sprites.js +512 -209
  273. package/dist/pixoo/pixoo-sprites.js.map +1 -1
  274. package/dist/pty-manager.d.ts.map +1 -1
  275. package/dist/pty-manager.js +8 -2
  276. package/dist/pty-manager.js.map +1 -1
  277. package/dist/session-activity.d.ts +14 -0
  278. package/dist/session-activity.d.ts.map +1 -0
  279. package/dist/session-activity.js +160 -0
  280. package/dist/session-activity.js.map +1 -0
  281. package/dist/session-aggregator.d.ts +17 -0
  282. package/dist/session-aggregator.d.ts.map +1 -1
  283. package/dist/session-aggregator.js +48 -9
  284. package/dist/session-aggregator.js.map +1 -1
  285. package/dist/session-registry.d.ts +71 -3
  286. package/dist/session-registry.d.ts.map +1 -1
  287. package/dist/session-registry.js +137 -8
  288. package/dist/session-registry.js.map +1 -1
  289. package/dist/session-timeline-relay.d.ts.map +1 -1
  290. package/dist/session-timeline-relay.js +13 -4
  291. package/dist/session-timeline-relay.js.map +1 -1
  292. package/dist/session-transcript-timeline.d.ts +49 -0
  293. package/dist/session-transcript-timeline.d.ts.map +1 -0
  294. package/dist/session-transcript-timeline.js +276 -0
  295. package/dist/session-transcript-timeline.js.map +1 -0
  296. package/dist/state-machine.d.ts +6 -1
  297. package/dist/state-machine.d.ts.map +1 -1
  298. package/dist/state-machine.js +86 -28
  299. package/dist/state-machine.js.map +1 -1
  300. package/dist/timebox/timebox-daemon-sync.d.ts +19 -0
  301. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -0
  302. package/dist/timebox/timebox-daemon-sync.js +135 -0
  303. package/dist/timebox/timebox-daemon-sync.js.map +1 -0
  304. package/dist/timebox/timebox-discover.d.ts +19 -0
  305. package/dist/timebox/timebox-discover.d.ts.map +1 -0
  306. package/dist/timebox/timebox-discover.js +87 -0
  307. package/dist/timebox/timebox-discover.js.map +1 -0
  308. package/dist/timebox/timebox-settings.d.ts +32 -0
  309. package/dist/timebox/timebox-settings.d.ts.map +1 -0
  310. package/dist/timebox/timebox-settings.js +80 -0
  311. package/dist/timebox/timebox-settings.js.map +1 -0
  312. package/dist/timeline-store.d.ts +63 -3
  313. package/dist/timeline-store.d.ts.map +1 -1
  314. package/dist/timeline-store.js +292 -14
  315. package/dist/timeline-store.js.map +1 -1
  316. package/dist/timeline-summarizer.d.ts.map +1 -1
  317. package/dist/timeline-summarizer.js +29 -5
  318. package/dist/timeline-summarizer.js.map +1 -1
  319. package/dist/tui/ansi.d.ts +0 -4
  320. package/dist/tui/ansi.d.ts.map +1 -1
  321. package/dist/tui/ansi.js +14 -7
  322. package/dist/tui/ansi.js.map +1 -1
  323. package/dist/tui/dashboard.d.ts +1 -0
  324. package/dist/tui/dashboard.d.ts.map +1 -1
  325. package/dist/tui/dashboard.js +24 -7
  326. package/dist/tui/dashboard.js.map +1 -1
  327. package/dist/tui/renderer.d.ts +36 -0
  328. package/dist/tui/renderer.d.ts.map +1 -1
  329. package/dist/tui/renderer.js +303 -119
  330. package/dist/tui/renderer.js.map +1 -1
  331. package/dist/tui/terrarium.d.ts.map +1 -1
  332. package/dist/tui/terrarium.js +37 -49
  333. package/dist/tui/terrarium.js.map +1 -1
  334. package/dist/types.d.ts +2 -2
  335. package/dist/types.d.ts.map +1 -1
  336. package/dist/types.js +2 -2
  337. package/dist/types.js.map +1 -1
  338. package/dist/usage-api.d.ts.map +1 -1
  339. package/dist/usage-api.js +7 -1
  340. package/dist/usage-api.js.map +1 -1
  341. package/dist/usage-event.d.ts +10 -3
  342. package/dist/usage-event.d.ts.map +1 -1
  343. package/dist/usage-event.js +74 -12
  344. package/dist/usage-event.js.map +1 -1
  345. package/dist/utility-proxy.d.ts.map +1 -1
  346. package/dist/utility-proxy.js +8 -0
  347. package/dist/utility-proxy.js.map +1 -1
  348. package/dist/utils/project-name.d.ts +3 -0
  349. package/dist/utils/project-name.d.ts.map +1 -1
  350. package/dist/utils/project-name.js +44 -2
  351. package/dist/utils/project-name.js.map +1 -1
  352. package/dist/windows-service.d.ts +26 -0
  353. package/dist/windows-service.d.ts.map +1 -0
  354. package/dist/windows-service.js +145 -0
  355. package/dist/windows-service.js.map +1 -0
  356. package/dist/ws-server.d.ts +22 -1
  357. package/dist/ws-server.d.ts.map +1 -1
  358. package/dist/ws-server.js +89 -4
  359. package/dist/ws-server.js.map +1 -1
  360. package/fm-helper/AgentDeckFMHelper.swift +112 -0
  361. package/package.json +9 -3
  362. package/scripts/build-fm-helper.mjs +26 -0
  363. package/LICENSE +0 -21
  364. package/dist/apme/tuner.d.ts +0 -64
  365. package/dist/apme/tuner.d.ts.map +0 -1
  366. package/dist/apme/tuner.js +0 -297
  367. package/dist/apme/tuner.js.map +0 -1
  368. package/dist/d200h/hid-protocol.d.ts +0 -62
  369. package/dist/d200h/hid-protocol.d.ts.map +0 -1
  370. package/dist/d200h/hid-protocol.js +0 -124
  371. package/dist/d200h/hid-protocol.js.map +0 -1
  372. package/dist/d200h/image-renderer.d.ts +0 -36
  373. package/dist/d200h/image-renderer.d.ts.map +0 -1
  374. package/dist/d200h/image-renderer.js +0 -487
  375. package/dist/d200h/image-renderer.js.map +0 -1
  376. package/dist/modules/d200h-module.d.ts +0 -43
  377. package/dist/modules/d200h-module.d.ts.map +0 -1
  378. package/dist/modules/d200h-module.js +0 -360
  379. package/dist/modules/d200h-module.js.map +0 -1
  380. package/dist/modules/d200h-renderer.d.ts +0 -7
  381. package/dist/modules/d200h-renderer.d.ts.map +0 -1
  382. package/dist/modules/d200h-renderer.js +0 -321
  383. package/dist/modules/d200h-renderer.js.map +0 -1
@@ -23,6 +23,7 @@
23
23
  * zoom 2.0 → octopus ~14px wide, crayfish ~24px wide
24
24
  */
25
25
  import { worldToScreen, isVisible } from './pixoo-camera.js';
26
+ import { State, STATE_COLORS } from '@agentdeck/shared';
26
27
  // ===== Cell Types (shared with ESP32/Android) =====
27
28
  const EMPTY = 0;
28
29
  const BODY = 1;
@@ -32,132 +33,324 @@ const RIGHT_ARM = 4;
32
33
  const LEFT_LEG = 5;
33
34
  const RIGHT_LEG = 6;
34
35
  const ANTENNA = 7;
35
- // ===== Octopus 13×13Pixoo64 LED pixel art (square pixels, no PIXEL_ASPECT) =====
36
- //
37
- // Designed for LED matrix: solid filled pixels, eyes as negative space (black).
38
- // No outline needed LED pixel glow provides natural edge definition.
39
- // At zoom 3.2 with cellSize = zoom/3 1.07, sprite 14×14 screen pixels.
40
- //
41
- export const OCTOPUS_GRID = [
42
- [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // flat top (9) angular
43
- [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // (11)
44
- [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // (11)
45
- [0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 0], // eyes top col 4,8 (11)
46
- [0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 0], // eyes bottom 2px vertical (11)
47
- [3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4], // arms start + body (13)
48
- [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // arms thick (13) 2 rows, 2-3px wide
49
- [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // arms continue (13)
50
- [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // narrowing (9)
51
- [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // (9)
52
- [0, 0, 5, 0, 5, 0, 1, 0, 6, 0, 6, 0, 0], // tentacles + center
53
- [0, 0, 5, 0, 5, 0, 0, 0, 6, 0, 6, 0, 0], // tentacles
54
- [0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0], // outer tentacles only
36
+ // ===== Octopus HD 24×24Claude Code mascot aligned with SVG viewBox =====
37
+ export const OCTOPUS_GRID_HD = [
38
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
39
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
40
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
41
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
42
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
43
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
44
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
45
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
46
+ [0, 0, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0, 0, 0],
47
+ [0, 0, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0, 0, 0],
48
+ [0, 0, 0, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0, 0, 0],
49
+ [3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4],
50
+ [3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4],
51
+ [3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4],
52
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
53
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
54
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
55
+ [0, 0, 0, 0, 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0],
56
+ [0, 0, 0, 0, 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0],
57
+ [0, 0, 0, 0, 5, 5, 0, 0, 5, 5, 0, 0, 0, 0, 6, 6, 0, 0, 6, 6, 0, 0, 0, 0],
58
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
59
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
60
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
61
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
55
62
  ];
56
- const OCTO_COLS = 13;
57
- const OCTO_ROWS = 13;
63
+ const OCTO_HD_COLS = 24;
64
+ const OCTO_HD_ROWS = 24;
65
+ // ===== Octopus MD 14×13 — Claude Code boxy mascot =====
66
+ export const OCTOPUS_GRID_MD = [
67
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // Flat top (12)
68
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // (14)
69
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // (14)
70
+ [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1], // Wide eye holes (2px)
71
+ [1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1], // Eye holes bottom
72
+ [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // Arms + body (14)
73
+ [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // Arms + body (14)
74
+ [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // Arms + body (14)
75
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // Slightly tapered waist (12)
76
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0], // (12)
77
+ [0, 5, 5, 0, 5, 5, 0, 0, 6, 6, 0, 6, 6, 0], // Thick 2px legs for distortion-free downscaling
78
+ [0, 5, 5, 0, 5, 5, 0, 0, 6, 6, 0, 6, 6, 0],
79
+ [0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0]
80
+ ];
81
+ const OCTO_GRID_MD_COLS = 14;
82
+ const OCTO_GRID_MD_ROWS = 13;
83
+ export const OCTOPUS_GRID = OCTOPUS_GRID_HD; // alias for compatibility
84
+ const OCTO_COLS = OCTO_HD_COLS;
85
+ const OCTO_ROWS = OCTO_HD_ROWS;
58
86
  /** World width of octopus in normalized coords. */
59
87
  export const OCTO_WORLD_W = 7 / 64;
60
88
  // ===== Octopus LOD 7×7 — chunky grid for zoom < 1.3 =====
61
- const OCTOPUS_LOD = [
62
- [0, 1, 1, 1, 1, 1, 0], // flat top (5) — angular
63
- [1, 1, 1, 1, 1, 1, 1], // full width (7)
64
- [1, 1, 2, 1, 2, 1, 1], // eyes (7)
65
- [1, 1, 1, 1, 1, 1, 1], // body (7)
66
- [0, 1, 1, 1, 1, 1, 0], // waist (5)
67
- [0, 0, 5, 1, 6, 0, 0], // tentacles + center
68
- [0, 0, 5, 0, 6, 0, 0], // tentacle tips
89
+ export const OCTOPUS_LOD = [
90
+ [0, 1, 1, 1, 1, 1, 0],
91
+ [1, 1, 1, 1, 1, 1, 1],
92
+ [1, 2, 2, 1, 2, 2, 1], // Bold eye holes
93
+ [3, 1, 1, 1, 1, 1, 4], // Arms + body
94
+ [0, 1, 1, 1, 1, 1, 0],
95
+ [0, 5, 5, 1, 6, 6, 0], // Thick limbs
96
+ [0, 5, 0, 0, 0, 6, 0],
69
97
  ];
70
98
  const OCTO_LOD_COLS = 7;
71
99
  const OCTO_LOD_ROWS = 7;
72
- // ===== Crayfish 12×8front-facing, matches OpenClaw SVG =====
73
- //
74
- // OpenClaw SVG: rounded body, compact side claws, antennae from top,
75
- // dark eyes with teal (#00e5cc) highlights, red gradient body.
76
- // Body shape: oval (narrow top wide middle narrow bottom).
77
- //
78
- export const CRAYFISH_GRID = [
79
- [0, 0, 7, 0, 0, 0, 0, 0, 0, 7, 0, 0], // antennae tips (wide spread)
80
- [0, 0, 0, 7, 0, 0, 0, 0, 7, 0, 0, 0], // antennae shafts
81
- [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], // head dome top (6w rounder)
82
- [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // head + claw tips (8w body + 2+2 claws)
83
- [0, 3, 1, 1, 2, 1, 1, 2, 1, 1, 4, 0], // eyes + claw arms (8w body)
84
- [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // thorax widest (8w)
85
- [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], // abdomen (6w)
86
- [0, 0, 5, 0, 0, 1, 1, 0, 0, 6, 0, 0], // tail + walking legs
100
+ // ===== Crayfish HD 24×24OpenClaw mascot, round full body matching design/brand/openclaw.svg =====
101
+ // Round body, antennae curving to the top corners, two separated side-claw blobs (3/4), two leg
102
+ // stubs (5/6). Eyes are NOT in the grid drawn as a teal+black overlay at eyeRow/eyeCols below.
103
+ export const CRAYFISH_GRID_HD = [
104
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
105
+ [0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0],
106
+ [0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0],
107
+ [0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0],
108
+ [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
109
+ [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
110
+ [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
111
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
112
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
113
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0], // eyes overlaid at cols 8,14
114
+ [0, 3, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 4, 0],
115
+ [3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 4, 4, 4],
116
+ [3, 3, 3, 3, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 4, 4, 4, 4],
117
+ [0, 3, 3, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 4, 4, 0],
118
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
119
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
120
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
121
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
122
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
123
+ [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
124
+ [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
125
+ [0, 0, 0, 0, 0, 0, 0, 1, 5, 5, 1, 1, 1, 1, 6, 6, 1, 0, 0, 0, 0, 0, 0, 0],
126
+ [0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 1, 1, 1, 1, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0],
127
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
128
+ ];
129
+ const CF_HD_COLS = 24;
130
+ const CF_HD_ROWS = 24;
131
+ // ===== Crayfish MD 12×8 — OpenClaw mascot, round body (eyes overlaid at row 3, cols 4/7) =====
132
+ export const CRAYFISH_GRID_MD = [
133
+ [0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0], // Antennae tips at the corners
134
+ [0, 0, 7, 0, 0, 1, 1, 0, 0, 7, 0, 0], // Antennae + head crown
135
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], // Rounded head dome
136
+ [0, 3, 3, 1, 1, 1, 1, 1, 1, 4, 4, 0], // Claws + body (eyes overlay row 3)
137
+ [3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4], // Wide claws + body
138
+ [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // Thorax
139
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0], // Lower body
140
+ [0, 0, 0, 5, 5, 0, 0, 6, 6, 0, 0, 0] // Walking legs
87
141
  ];
88
- const CF_COLS = 12;
89
- const CF_ROWS = 8;
90
- /** World width of crayfish in normalized coords (12 world-pixels / 64). */
142
+ const CF_GRID_MD_COLS = 12;
143
+ const CF_GRID_MD_ROWS = 8;
144
+ export const CRAYFISH_GRID = CRAYFISH_GRID_HD; // alias for compatibility
145
+ const CF_COLS = CF_HD_COLS;
146
+ const CF_ROWS = CF_HD_ROWS;
147
+ /** World width of crayfish in normalized coords. */
91
148
  export const CF_WORLD_W = 12 / 64;
92
- // ===== Crayfish LOD 8×6 — compact for zoom < 1.3 =====
93
- const CRAYFISH_LOD = [
94
- [0, 7, 0, 0, 0, 0, 7, 0], // antennae (wider spread)
95
- [0, 0, 1, 1, 1, 1, 0, 0], // head dome (4w — rounder)
96
- [3, 1, 1, 1, 1, 1, 1, 4], // claws + body (6w wider oval)
97
- [0, 1, 2, 1, 1, 2, 1, 0], // eyes at col 2,5 (spaced for 3×3 rendering)
98
- [0, 0, 1, 1, 1, 1, 0, 0], // lower body (4w)
99
- [0, 5, 0, 1, 1, 0, 6, 0], // legs + tail
149
+ // ===== Crayfish LOD 8×6 — compact round body (eyes overlaid at row 2, cols 2/5) =====
150
+ export const CRAYFISH_LOD = [
151
+ [0, 7, 0, 0, 0, 0, 7, 0], // Antennae at corners
152
+ [0, 0, 1, 1, 1, 1, 0, 0], // Head crown
153
+ [3, 3, 1, 1, 1, 1, 4, 4], // Claws + body (eyes overlay row 2)
154
+ [0, 1, 1, 1, 1, 1, 1, 0], // Body
155
+ [0, 1, 1, 1, 1, 1, 1, 0], // Body
156
+ [0, 0, 5, 0, 0, 6, 0, 0], // Legs
100
157
  ];
101
158
  const CF_LOD_COLS = 8;
102
159
  const CF_LOD_ROWS = 6;
103
- // ===== Jellyfish 10×86-lobe cloud (matches TUI/Android CloudCreature) =====
104
- //
105
- // Codex CLI creature: clover-shaped cloud with >_ terminal prompt marking.
106
- // Cell types: 0=empty, 1=body, 2=marking(>_), 3=edge(breathe/contract)
107
- //
108
- export const JELLYFISH_GRID = [
109
- [0, 0, 1, 1, 0, 0, 1, 1, 0, 0], // top lobes
110
- [0, 1, 1, 1, 1, 1, 1, 1, 1, 0], // upper merge
111
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // widest side lobes
112
- [3, 1, 2, 2, 1, 1, 2, 1, 1, 3], // >_ marking + breathe edges
113
- [3, 1, 1, 1, 1, 1, 1, 1, 1, 3], // center + breathe edges
114
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], // widest
115
- [0, 1, 1, 1, 1, 1, 1, 1, 1, 0], // taper
116
- [0, 0, 1, 1, 0, 0, 1, 1, 0, 0], // bottom lobes
160
+ // ===== Jellyfish HD 24×24Codex cloud mascot aligned with SVG viewBox =====
161
+ export const JELLYFISH_GRID_HD = [
162
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
163
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
164
+ [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
165
+ [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
166
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
167
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
168
+ [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
169
+ [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
170
+ [0, 0, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], // prompt `>`
171
+ [0, 0, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
172
+ [0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
173
+ [0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
174
+ [0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
175
+ [0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
176
+ [0, 0, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
177
+ [0, 0, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0], // prompt `_`
178
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0],
179
+ [0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
180
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
181
+ [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
182
+ [0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0], // bottom lobes are BREATHE_EDGE
183
+ [0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0],
184
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
185
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
117
186
  ];
118
- const JF_COLS = 10;
119
- const JF_ROWS = 8;
187
+ const JF_HD_COLS = 24;
188
+ const JF_HD_ROWS = 24;
189
+ // ===== Jellyfish MD 13×11 — Codex mascot =====
190
+ export const JELLYFISH_GRID_MD = [
191
+ [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0], // Rounded top lobe
192
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
193
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
194
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
195
+ [3, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3], // `>` top stroke (2px thick)
196
+ [3, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 3], // `>` point + long `_` prompt (4px)
197
+ [3, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 3], // `>` bottom stroke
198
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
199
+ [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
200
+ [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
201
+ [0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0] // Bottom cloud lobe, not dangling legs
202
+ ];
203
+ const JF_GRID_MD_COLS = 13;
204
+ const JF_GRID_MD_ROWS = 11;
205
+ export const JELLYFISH_GRID = JELLYFISH_GRID_HD; // alias for compatibility
206
+ const JF_COLS = JF_HD_COLS;
207
+ const JF_ROWS = JF_HD_ROWS;
120
208
  /** World width of jellyfish in normalized coords. */
121
- export const JF_WORLD_W = 10 / 64;
122
- // ===== Jellyfish LOD 7×5 — compact for zoom < 1.3 =====
123
- const JELLYFISH_LOD = [
124
- [0, 1, 1, 0, 1, 1, 0], // top lobes
125
- [1, 1, 1, 1, 1, 1, 1], // widest
126
- [1, 1, 2, 1, 2, 1, 1], // marking
127
- [1, 1, 1, 1, 1, 1, 1], // widest
128
- [0, 1, 1, 0, 1, 1, 0], // bottom lobes
209
+ export const JF_WORLD_W = 13 / 64;
210
+ // ===== Jellyfish LOD 7 — compact for zoom < 1.3 =====
211
+ export const JELLYFISH_LOD = [
212
+ [0, 0, 1, 1, 1, 1, 1, 0, 0],
213
+ [0, 1, 1, 1, 1, 1, 1, 1, 0],
214
+ [1, 2, 2, 1, 1, 1, 1, 1, 1], // `>` upper stroke (2px)
215
+ [1, 1, 2, 2, 1, 2, 2, 2, 1], // `>` point + `_` prompt (3px)
216
+ [1, 2, 2, 1, 1, 1, 1, 1, 1], // `>` lower stroke
217
+ [0, 1, 1, 1, 1, 1, 1, 1, 0],
218
+ [0, 0, 1, 1, 1, 1, 1, 0, 0],
129
219
  ];
130
- const JF_LOD_COLS = 7;
131
- const JF_LOD_ROWS = 5;
220
+ const JF_LOD_COLS = 9;
221
+ const JF_LOD_ROWS = 7;
132
222
  // Cell type constants for jellyfish
133
223
  const MARKING = 2;
134
224
  const BREATHE_EDGE = 3;
135
- // ===== OpenCode 10×9nested-square logo sprite (simulator SSOT) =====
225
+ // ===== OpenCode HD 24×24OpenCode mascot aligned with SVG viewBox =====
226
+ export const OPENCODE_GRID_HD = [
227
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
228
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
229
+ [0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0],
230
+ [0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0],
231
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0],
232
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0],
233
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0],
234
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0],
235
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
236
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
237
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
238
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
239
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
240
+ [0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 8, 8, 9, 9, 9, 9, 9, 9],
241
+ [0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9],
242
+ [0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9],
243
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
244
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
245
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
246
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
247
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
248
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
249
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
250
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
251
+ ];
252
+ const OC_HD_COLS = 24;
253
+ const OC_HD_ROWS = 24;
254
+ // ===== OpenCode MD 10×10 — nested-square logo =====
136
255
  const OPENCODE_FRAME = 8;
137
256
  const OPENCODE_CORE = 9;
138
- const OPENCODE_GRID = [
139
- [8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
140
- [8, 0, 0, 0, 0, 0, 0, 0, 0, 8],
141
- [8, 0, 9, 9, 9, 9, 9, 9, 0, 8],
142
- [8, 0, 9, 0, 0, 0, 0, 9, 0, 8],
143
- [8, 0, 9, 0, 0, 0, 0, 9, 0, 8],
144
- [8, 0, 9, 0, 0, 0, 0, 9, 0, 8],
145
- [8, 0, 9, 9, 9, 9, 9, 9, 0, 8],
146
- [8, 0, 0, 0, 0, 0, 0, 0, 0, 8],
147
- [8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
257
+ export const OPENCODE_GRID_MD = [
258
+ [8, 8, 8, 8, 8, 8, 0, 0, 0, 0],
259
+ [8, 0, 0, 0, 0, 8, 0, 0, 0, 0],
260
+ [8, 0, 0, 0, 0, 8, 0, 0, 0, 0],
261
+ [8, 0, 0, 0, 0, 8, 0, 0, 0, 0],
262
+ [8, 0, 0, 0, 9, 8, 9, 9, 9, 9],
263
+ [8, 8, 8, 8, 8, 8, 9, 9, 9, 9],
264
+ [0, 0, 0, 0, 9, 9, 9, 9, 9, 9],
265
+ [0, 0, 0, 0, 9, 9, 9, 9, 9, 9],
266
+ [0, 0, 0, 0, 9, 9, 9, 9, 9, 9],
267
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
148
268
  ];
149
- const OC_COLS = 10;
150
- const OC_ROWS = 9;
151
- const OPENCODE_LOD = [
152
- [8, 8, 8, 8, 8, 8],
153
- [8, 0, 0, 0, 0, 8],
154
- [8, 0, 9, 9, 0, 8],
155
- [8, 0, 9, 9, 0, 8],
156
- [8, 0, 0, 0, 0, 8],
157
- [8, 8, 8, 8, 8, 8],
269
+ const OC_GRID_MD_COLS = 10;
270
+ const OC_GRID_MD_ROWS = 10;
271
+ export const OPENCODE_GRID = OPENCODE_GRID_HD; // alias for compatibility
272
+ const OC_COLS = OC_HD_COLS;
273
+ const OC_ROWS = OC_HD_ROWS;
274
+ export const OPENCODE_LOD = [
275
+ [8, 8, 8, 8, 0, 0],
276
+ [8, 0, 0, 8, 0, 0],
277
+ [8, 0, 9, 8, 9, 9],
278
+ [8, 8, 8, 8, 9, 9],
279
+ [0, 0, 9, 9, 9, 9],
280
+ [0, 0, 9, 9, 9, 9],
158
281
  ];
159
282
  const OC_LOD_COLS = 6;
160
283
  const OC_LOD_ROWS = 6;
284
+ // ===== Antigravity — rainbow peak/arc mark =====
285
+ const ANTIGRAVITY_GRID = [
286
+ '....YOO....',
287
+ '....YOO....',
288
+ '...LYOOR...',
289
+ '...LTORR...',
290
+ '..LLTVPP...',
291
+ '..TTKKVPP..',
292
+ '.TQQK.KVU..',
293
+ '.QQK...KUU.',
294
+ 'NQK.....KUU',
295
+ 'NN.......UU',
296
+ '...........',
297
+ ];
298
+ const AG_COLS = 11;
299
+ const AG_ROWS = 11;
300
+ function selectGrid(zoom, canvasWidth, hd, hdCols, hdRows, md, mdCols, mdRows, lod, lodCols, lodRows) {
301
+ if (canvasWidth <= 32) {
302
+ if (zoom < 1.3) {
303
+ return { grid: lod, cols: lodCols, rows: lodRows };
304
+ }
305
+ else {
306
+ // Zoomed-in on a 32px display (iDotMatrix): use the full HD grid, not MD. Now that cell size
307
+ // is resolution-aware (creatureCellSize), HD fits the 32px frame (~60% width) and keeps the
308
+ // creature's true silhouette instead of the coarser MD approximation. MD/LOD stay for the
309
+ // wide (zoom<1.3) shots where the creature is small and detail would just alias.
310
+ return { grid: hd, cols: hdCols, rows: hdRows };
311
+ }
312
+ }
313
+ else {
314
+ if (zoom < 1.3) {
315
+ return { grid: md, cols: mdCols, rows: mdRows };
316
+ }
317
+ else {
318
+ return { grid: hd, cols: hdCols, rows: hdRows };
319
+ }
320
+ }
321
+ }
322
+ /**
323
+ * On-screen creature width as a fraction of (zoom × output width), independent of LOD grid.
324
+ *
325
+ * A sprite spans `SPRITE_W_FRAC × zoom × w` pixels regardless of which HD/MD/LOD grid is selected
326
+ * (cellSz = span / cols, so the `cols` term cancels). Multiplying by `w` (32 or 64) is what makes
327
+ * sizing resolution-aware — at 32px a creature is exactly half the pixels it is at 64px, so it stays
328
+ * inside the iDotMatrix frame instead of overflowing. 0.1875 reproduces the legacy 64px HD size
329
+ * (24 cols × zoom×0.5 = 12×zoom px = 0.1875 × zoom × 64). See drawOpenCode for the original pattern.
330
+ */
331
+ const SPRITE_W_FRAC = 0.1875;
332
+ /** Resolution-aware square cell size for a creature sprite.
333
+ *
334
+ * Rounded to a whole pixel so every grid cell is a uniform integer block. A
335
+ * fractional cell size makes adjacent cells round to alternating 1/2px widths,
336
+ * and on the 2×2 eye holes that intermittently opens a 1px body-coloured seam
337
+ * between the two eye rows while the creature moves — the eyes appear to "break"
338
+ * into dots. Integer cells render the eye as a solid block every frame.
339
+ *
340
+ * The sizing zoom is itself quantized to a coarse 0.25 step first. The raw zoom
341
+ * lerps continuously during camera transitions, so a cell size sitting right on
342
+ * a rounding threshold (e.g. zoom where `…/cols` ≈ x.5) flickers 1↔2px frame to
343
+ * frame — and since the eye sits at a fixed column, a cellSz change jumps it by
344
+ * `eyeCol` pixels. Snapping the zoom to discrete steps holds cellSz stable
345
+ * through a transition so the eye only ever moves by whole-creature translation. */
346
+ export function creatureCellSize(zoom, canvasW, cols) {
347
+ const zq = Math.round(zoom * 4) / 4; // 0.25 steps — kills threshold-straddle flicker
348
+ return Math.max(1, Math.round((SPRITE_W_FRAC * zq * canvasW) / cols));
349
+ }
350
+ /** Canonical `#rrggbb` → RGB tuple (LED frame buffers take tuples, not hex). */
351
+ function hexRgb(hex) {
352
+ return [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
353
+ }
161
354
  export const COLORS = {
162
355
  // Octopus (terracotta — Android match)
163
356
  octopusBody: [0xC0, 0x70, 0x58],
@@ -181,7 +374,7 @@ export const COLORS = {
181
374
  // Jellyfish / Codex CLI (indigo — TUI/Android match)
182
375
  jellyfishBody: [0x63, 0x66, 0xF1], // indigo #6366F1
183
376
  jellyfishEdge: [0x4F, 0x46, 0xE5], // darker edge (breathe cells)
184
- jellyfishMarking: [0xA5, 0xB4, 0xFC], // glow #A5B4FC (>_ prompt)
377
+ jellyfishMarking: [0xF5, 0xF7, 0xFF], // near-white (>_ prompt)
185
378
  jellyfishGlow: [0x31, 0x33, 0x78], // dim indigo glow halo
186
379
  jellyfishPulse: [0xA5, 0xB4, 0xFC], // bioluminescent pulse
187
380
  jellyfishSleeping: [0x3A, 0x3C, 0x90], // dimmed indigo
@@ -190,6 +383,17 @@ export const COLORS = {
190
383
  opencodeInner: [0x4B, 0x46, 0x46], // dark core
191
384
  opencodePulse: [0xCF, 0xCE, 0xCD], // pulse state
192
385
  opencodeSleeping: [0x8A, 0x84, 0x84], // sleep dim
386
+ // Antigravity (reference rainbow peak/arc)
387
+ antigravityLime: [0x5C, 0xD6, 0x4D],
388
+ antigravityTeal: [0x1F, 0xC6, 0xB3],
389
+ antigravityCyan: [0x3A, 0xC7, 0xEB],
390
+ antigravityYellow: [0xF5, 0xCB, 0x24],
391
+ antigravityOrange: [0xFF, 0x84, 0x10],
392
+ antigravityRed: [0xFF, 0x52, 0x41],
393
+ antigravityPink: [0xB7, 0x5C, 0xB6],
394
+ antigravityViolet: [0x66, 0x6F, 0xE1],
395
+ antigravityBlue: [0x24, 0x7E, 0xFF],
396
+ antigravitySky: [0x29, 0xB8, 0xEE],
193
397
  // Tetra (neon)
194
398
  tetraNeon: [0x00, 0xE5, 0xFF],
195
399
  tetraBody: [0x1E, 0x40, 0xAF],
@@ -235,11 +439,11 @@ export const COLORS = {
235
439
  // Tank walls (above water)
236
440
  tankWall: [0x06, 0x0A, 0x10],
237
441
  tankWallEdge: [0x10, 0x14, 0x1C],
238
- // State shimmer
239
- stateIdle: [0x22, 0xC5, 0x5E],
240
- stateProcessing: [0x3B, 0x82, 0xF6],
241
- stateAwaiting: [0xF5, 0x9E, 0x0B],
242
- stateError: [0xEF, 0x44, 0x44],
442
+ // State shimmer — derived from the canonical shared palette
443
+ stateIdle: hexRgb(STATE_COLORS[State.IDLE]),
444
+ stateProcessing: hexRgb(STATE_COLORS[State.PROCESSING]),
445
+ stateAwaiting: hexRgb(STATE_COLORS[State.AWAITING_PERMISSION]),
446
+ stateError: [0xEF, 0x44, 0x44], // red — no canonical error state color exists
243
447
  white: [0xFF, 0xFF, 0xFF],
244
448
  black: [0x00, 0x00, 0x00],
245
449
  };
@@ -288,27 +492,45 @@ export function getOpenCodePaletteForSession(sessionIndex = 0) {
288
492
  pulse: scaleColor(COLORS.opencodePulse, tone),
289
493
  };
290
494
  }
495
+ export function getAntigravityPaletteForSession(sessionIndex = 0) {
496
+ const tone = SESSION_TONE_FACTORS[Math.max(0, Math.min(SESSION_TONE_FACTORS.length - 1, sessionIndex))];
497
+ return {
498
+ lime: scaleColor(COLORS.antigravityLime, tone),
499
+ teal: scaleColor(COLORS.antigravityTeal, tone),
500
+ cyan: scaleColor(COLORS.antigravityCyan, tone),
501
+ yellow: scaleColor(COLORS.antigravityYellow, tone),
502
+ orange: scaleColor(COLORS.antigravityOrange, tone),
503
+ red: scaleColor(COLORS.antigravityRed, tone),
504
+ pink: scaleColor(COLORS.antigravityPink, tone),
505
+ violet: scaleColor(COLORS.antigravityViolet, tone),
506
+ blue: scaleColor(COLORS.antigravityBlue, tone),
507
+ sky: scaleColor(COLORS.antigravitySky, tone),
508
+ cutout: COLORS.black,
509
+ };
510
+ }
291
511
  // ===== Pixel Operations =====
292
- /** Set a pixel in the 64×64 RGB buffer. */
512
+ /** Set a pixel in the dynamic RGB buffer. */
293
513
  export function setPixel(buf, x, y, color) {
294
- if (x < 0 || x >= 64 || y < 0 || y >= 64)
514
+ const w = Math.sqrt(buf.length / 3);
515
+ if (x < 0 || x >= w || y < 0 || y >= w)
295
516
  return;
296
517
  const ix = Math.round(x);
297
518
  const iy = Math.round(y);
298
- if (ix < 0 || ix >= 64 || iy < 0 || iy >= 64)
519
+ if (ix < 0 || ix >= w || iy < 0 || iy >= w)
299
520
  return;
300
- const idx = (iy * 64 + ix) * 3;
521
+ const idx = (iy * w + ix) * 3;
301
522
  buf[idx] = color[0];
302
523
  buf[idx + 1] = color[1];
303
524
  buf[idx + 2] = color[2];
304
525
  }
305
526
  /** Alpha-blend a pixel onto existing buffer content. */
306
527
  export function blendPixel(buf, x, y, color, alpha) {
528
+ const w = Math.sqrt(buf.length / 3);
307
529
  const ix = Math.round(x);
308
530
  const iy = Math.round(y);
309
- if (ix < 0 || ix >= 64 || iy < 0 || iy >= 64 || alpha <= 0)
531
+ if (ix < 0 || ix >= w || iy < 0 || iy >= w || alpha <= 0)
310
532
  return;
311
- const idx = (iy * 64 + ix) * 3;
533
+ const idx = (iy * w + ix) * 3;
312
534
  const a = Math.min(1, alpha);
313
535
  const inv = 1 - a;
314
536
  buf[idx] = Math.min(255, Math.round(buf[idx] * inv + color[0] * a));
@@ -317,11 +539,12 @@ export function blendPixel(buf, x, y, color, alpha) {
317
539
  }
318
540
  /** Additive-blend (glow) a pixel. */
319
541
  export function glowPixel(buf, x, y, color, intensity) {
542
+ const w = Math.sqrt(buf.length / 3);
320
543
  const ix = Math.round(x);
321
544
  const iy = Math.round(y);
322
- if (ix < 0 || ix >= 64 || iy < 0 || iy >= 64 || intensity <= 0)
545
+ if (ix < 0 || ix >= w || iy < 0 || iy >= w || intensity <= 0)
323
546
  return;
324
- const idx = (iy * 64 + ix) * 3;
547
+ const idx = (iy * w + ix) * 3;
325
548
  buf[idx] = Math.min(255, buf[idx] + Math.round(color[0] * intensity));
326
549
  buf[idx + 1] = Math.min(255, buf[idx + 1] + Math.round(color[1] * intensity));
327
550
  buf[idx + 2] = Math.min(255, buf[idx + 2] + Math.round(color[2] * intensity));
@@ -336,45 +559,52 @@ export function fillRect(buf, x, y, w, h, color) {
336
559
  }
337
560
  /** Fill a scaled cell (variable size rectangle) — edge rounding for correct PIXEL_ASPECT. */
338
561
  function fillCell(buf, x, y, w, h, color) {
339
- const ix = Math.floor(x);
340
- const iy = Math.floor(y);
341
- const iw = Math.max(1, Math.round(ix + w) - ix); // right edge - left edge
342
- const ih = Math.max(1, Math.round(iy + h) - iy);
562
+ const ix1 = Math.round(x);
563
+ const iy1 = Math.round(y);
564
+ const ix2 = Math.round(x + w);
565
+ const iy2 = Math.round(y + h);
566
+ const iw = Math.max(1, ix2 - ix1);
567
+ const ih = Math.max(1, iy2 - iy1);
343
568
  for (let dy = 0; dy < ih; dy++) {
344
569
  for (let dx = 0; dx < iw; dx++) {
345
- setPixel(buf, ix + dx, iy + dy, color);
570
+ setPixel(buf, ix1 + dx, iy1 + dy, color);
346
571
  }
347
572
  }
348
573
  }
349
574
  /** Fill a scaled cell and track drawn pixels for outline generation. */
350
575
  function fillCellTracked(buf, x, y, w, h, color, pixels) {
351
- const ix = Math.floor(x);
352
- const iy = Math.floor(y);
353
- const iw = Math.max(1, Math.round(ix + w) - ix);
354
- const ih = Math.max(1, Math.round(iy + h) - iy);
576
+ const canvasW = Math.sqrt(buf.length / 3);
577
+ const ix1 = Math.round(x);
578
+ const iy1 = Math.round(y);
579
+ const ix2 = Math.round(x + w);
580
+ const iy2 = Math.round(y + h);
581
+ const iw = Math.max(1, ix2 - ix1);
582
+ const ih = Math.max(1, iy2 - iy1);
355
583
  for (let dy = 0; dy < ih; dy++) {
356
584
  for (let dx = 0; dx < iw; dx++) {
357
- const px = ix + dx;
358
- const py = iy + dy;
359
- if (px >= 0 && px < 64 && py >= 0 && py < 64) {
360
- const idx = (py * 64 + px) * 3;
585
+ const px = ix1 + dx;
586
+ const py = iy1 + dy;
587
+ if (px >= 0 && px < canvasW && py >= 0 && py < canvasW) {
588
+ const idx = (py * canvasW + px) * 3;
361
589
  buf[idx] = color[0];
362
590
  buf[idx + 1] = color[1];
363
591
  buf[idx + 2] = color[2];
364
- pixels.add(py * 64 + px);
592
+ pixels.add(py * canvasW + px);
365
593
  }
366
594
  }
367
595
  }
368
596
  }
369
597
  /** Glow-fill a scaled cell — edge rounding. */
370
598
  function glowCell(buf, x, y, w, h, color, intensity) {
371
- const ix = Math.floor(x);
372
- const iy = Math.floor(y);
373
- const iw = Math.max(1, Math.round(ix + w) - ix);
374
- const ih = Math.max(1, Math.round(iy + h) - iy);
599
+ const ix1 = Math.round(x);
600
+ const iy1 = Math.round(y);
601
+ const ix2 = Math.round(x + w);
602
+ const iy2 = Math.round(y + h);
603
+ const iw = Math.max(1, ix2 - ix1);
604
+ const ih = Math.max(1, iy2 - iy1);
375
605
  for (let dy = 0; dy < ih; dy++) {
376
606
  for (let dx = 0; dx < iw; dx++) {
377
- glowPixel(buf, ix + dx, iy + dy, color, intensity);
607
+ glowPixel(buf, ix1 + dx, iy1 + dy, color, intensity);
378
608
  }
379
609
  }
380
610
  }
@@ -408,6 +638,7 @@ function drawCreatureGlow(buf, cx, cy, rx, ry, glowColor, intensity = 0.15, isLO
408
638
  // ===== Creature Outline =====
409
639
  /** 8-directional colored outline around tracked creature pixels. */
410
640
  function drawCreatureOutline(buf, creaturePixels, bodyColor, alpha = 0.8) {
641
+ const w = Math.sqrt(buf.length / 3);
411
642
  const outlineColor = [
412
643
  Math.round(bodyColor[0] * 0.5),
413
644
  Math.round(bodyColor[1] * 0.5),
@@ -415,17 +646,17 @@ function drawCreatureOutline(buf, creaturePixels, bodyColor, alpha = 0.8) {
415
646
  ];
416
647
  const neighbors = [-1, 0, 1];
417
648
  for (const key of creaturePixels) {
418
- const cx = key % 64;
419
- const cy = Math.floor(key / 64);
649
+ const cx = key % w;
650
+ const cy = Math.floor(key / w);
420
651
  for (const ndx of neighbors) {
421
652
  for (const ndy of neighbors) {
422
653
  if (ndx === 0 && ndy === 0)
423
654
  continue;
424
655
  const nx = cx + ndx;
425
656
  const ny = cy + ndy;
426
- if (nx < 0 || nx >= 64 || ny < 0 || ny >= 64)
657
+ if (nx < 0 || nx >= w || ny < 0 || ny >= w)
427
658
  continue;
428
- if (!creaturePixels.has(ny * 64 + nx)) {
659
+ if (!creaturePixels.has(ny * w + nx)) {
429
660
  blendPixel(buf, nx, ny, outlineColor, alpha);
430
661
  }
431
662
  }
@@ -499,26 +730,37 @@ export function drawOctopus(buf, worldX, worldY, state, animFrame, cam, palette
499
730
  if (!isVisible(worldX, worldY, cam, 0.15))
500
731
  return;
501
732
  const [scx, scy] = worldToScreen(worldX, worldY, cam);
733
+ const w = Math.sqrt(buf.length / 3);
502
734
  // LOD selection
503
- const useLOD = cam.zoom < 1.3;
504
- const grid = useLOD ? OCTOPUS_LOD : OCTOPUS_GRID;
505
- const cols = useLOD ? OCTO_LOD_COLS : OCTO_COLS;
506
- const rows = useLOD ? OCTO_LOD_ROWS : OCTO_ROWS;
507
- // Fixed 1px per cell LED pixel art, no fractional sizes, no tearing
508
- const cellSz = 1;
509
- const spriteW = cols;
510
- const spriteH = rows;
735
+ const select = selectGrid(cam.zoom, w, OCTOPUS_GRID_HD, OCTO_HD_COLS, OCTO_HD_ROWS, OCTOPUS_GRID_MD, OCTO_GRID_MD_COLS, OCTO_GRID_MD_ROWS, OCTOPUS_LOD, OCTO_LOD_COLS, OCTO_LOD_ROWS);
736
+ const grid = select.grid;
737
+ const cols = select.cols;
738
+ const rows = select.rows;
739
+ // Resolution-aware cell sizesprite spans a fixed fraction of (zoom × output width),
740
+ // so it scales down on the 32px iDotMatrix instead of overflowing the frame.
741
+ const cellSz = creatureCellSize(cam.zoom, w, cols);
742
+ const spriteW = cols * cellSz;
743
+ const spriteH = rows * cellSz;
744
+ // Snap the sprite origin to whole pixels. The cells tile from this origin via
745
+ // round(base + col*cellSz); if `base` is fractional, a swimming creature's
746
+ // sub-pixel screen drift shifts the rounding phase every frame, so each cell
747
+ // oscillates ±1px (shimmer). On the small high-contrast eye holes that reads
748
+ // as the eyes "breaking up" while the octopus moves. Rounding the origin makes
749
+ // the cell pattern frame-stable (only an integer translation changes), so the
750
+ // creature moves in clean 1px steps with rock-steady eyes.
511
751
  const baseX = Math.round(scx - spriteW / 2);
512
752
  const baseY = Math.round(scy - spriteH / 2);
513
- // Working: gentle vertical bob (integer pixels only)
753
+ // Working: gentle vertical bob (scaled by cellSz)
514
754
  const breathPx = state === 'working'
515
- ? Math.round(Math.sin(animFrame * 0.3) * 1.5)
755
+ ? Math.round(Math.sin(animFrame * 0.3) * cellSz * 1.5)
516
756
  : 0;
517
757
  // Body color by state
518
758
  const bodyColor = state === 'working' ? palette.starburst
519
759
  : state === 'sleeping' ? palette.sleeping
520
760
  : palette.body;
521
- // Draw all cells solid fill, no outline
761
+ // Track drawn pixels for outline generation
762
+ const trackedPixels = new Set();
763
+ // Draw all cells
522
764
  for (let row = 0; row < rows; row++) {
523
765
  for (let col = 0; col < cols; col++) {
524
766
  const cellType = grid[row][col];
@@ -541,16 +783,18 @@ export function drawOctopus(buf, worldX, worldY, state, animFrame, cam, palette
541
783
  // Tentacle animation only — arms stay fixed (attached to body)
542
784
  let dx = 0;
543
785
  if (state !== 'sleeping' && (cellType === LEFT_LEG || cellType === RIGHT_LEG)) {
544
- dx = Math.round(Math.sin(animFrame * 0.2 + col * 1.8) * 1.5);
786
+ dx = Math.round(Math.sin(animFrame * 0.2 + col * 1.8) * cellSz * 1.5);
545
787
  }
546
- fillCell(buf, baseX + col * cellSz + dx, baseY + row * cellSz + breathPx, cellSz, cellSz, color);
788
+ fillCellTracked(buf, baseX + col * cellSz + dx, baseY + row * cellSz + breathPx, cellSz, cellSz, color, trackedPixels);
547
789
  }
548
790
  }
791
+ // Draw outline to separate octopus from blue water (matches crayfish style)
792
+ drawCreatureOutline(buf, trackedPixels, bodyColor, 0.8);
549
793
  // "?" bubble when asking
550
794
  if (state === 'asking') {
551
795
  const bobY = Math.round(Math.sin(animFrame * 0.25));
552
796
  const bx = scx;
553
- const by = baseY - 3 + bobY;
797
+ const by = baseY - Math.round(cellSz * 3) + bobY;
554
798
  const r = 3;
555
799
  for (let dy = -r; dy <= r; dy++) {
556
800
  for (let dx = -r; dx <= r; dx++) {
@@ -569,7 +813,7 @@ export function drawOctopus(buf, worldX, worldY, state, animFrame, cam, palette
569
813
  // Starburst particles when working
570
814
  if (state === 'working') {
571
815
  const sparkPhase = animFrame * 0.35;
572
- const dist = 5 + Math.sin(animFrame * 0.25) * 3;
816
+ const dist = (5 + Math.sin(animFrame * 0.25) * 3) * cellSz;
573
817
  for (let i = 0; i < 6; i++) {
574
818
  const angle = sparkPhase + (i * Math.PI * 2 / 6);
575
819
  const sx = scx + Math.cos(angle) * dist;
@@ -589,28 +833,29 @@ export function drawJellyfish(buf, worldX, worldY, state, animFrame, cam, palett
589
833
  if (!isVisible(worldX, worldY, cam, 0.15))
590
834
  return;
591
835
  const [scx, scy] = worldToScreen(worldX, worldY, cam);
836
+ const w = Math.sqrt(buf.length / 3);
592
837
  // LOD selection
593
- const useLOD = cam.zoom < 1.3;
594
- const grid = useLOD ? JELLYFISH_LOD : JELLYFISH_GRID;
595
- const cols = useLOD ? JF_LOD_COLS : JF_COLS;
596
- const rows = useLOD ? JF_LOD_ROWS : JF_ROWS;
597
- // Fixed 1px per cellLED pixel art
598
- const cellSz = 1;
599
- const spriteW = cols;
600
- const spriteH = rows;
601
- const baseX = Math.round(scx - spriteW / 2);
602
- const baseY = Math.round(scy - spriteH / 2);
838
+ const select = selectGrid(cam.zoom, w, JELLYFISH_GRID_HD, JF_HD_COLS, JF_HD_ROWS, JELLYFISH_GRID_MD, JF_GRID_MD_COLS, JF_GRID_MD_ROWS, JELLYFISH_LOD, JF_LOD_COLS, JF_LOD_ROWS);
839
+ const grid = select.grid;
840
+ const cols = select.cols;
841
+ const rows = select.rows;
842
+ // Resolution-aware cell size (see creatureCellSize) scales with output width.
843
+ const cellSz = creatureCellSize(cam.zoom, w, cols);
844
+ const spriteW = cols * cellSz;
845
+ const spriteH = rows * cellSz;
846
+ const baseX = scx - spriteW / 2;
847
+ const baseY = scy - spriteH / 2;
603
848
  // Bell pulse: contraction/expansion
604
849
  const pulseSpeed = state === 'working' ? 0.25 : 0.06;
605
850
  const pulsePhase = Math.sin(animFrame * pulseSpeed);
606
851
  const contracting = pulsePhase < 0;
607
- // Vertical bob when working
852
+ // Vertical bob when working (scaled by cellSz)
608
853
  const breathPx = state === 'working'
609
- ? Math.round(Math.sin(animFrame * 0.3) * 1.5)
854
+ ? Math.round(Math.sin(animFrame * 0.3) * cellSz * 1.5)
610
855
  : 0;
611
856
  // Bioluminescent body color pulse when working
612
857
  const bodyColor = state === 'working'
613
- ? lerpColor(palette.body, palette.pulse, (Math.sin(animFrame * 0.2) + 1) * 0.5)
858
+ ? lerpColor(palette.body, palette.pulse, 0.18 + ((Math.sin(animFrame * 0.2) + 1) * 0.18))
614
859
  : state === 'sleeping' ? palette.sleeping
615
860
  : palette.body;
616
861
  // >_ marking blink
@@ -646,7 +891,7 @@ export function drawJellyfish(buf, worldX, worldY, state, animFrame, cam, palett
646
891
  if (state === 'asking') {
647
892
  const bobY = Math.round(Math.sin(animFrame * 0.25));
648
893
  const bx = scx;
649
- const by = baseY - 3 + bobY;
894
+ const by = baseY - Math.round(cellSz * 3) + bobY;
650
895
  const r = 3;
651
896
  for (let dy = -r; dy <= r; dy++) {
652
897
  for (let dx = -r; dx <= r; dx++) {
@@ -665,7 +910,7 @@ export function drawJellyfish(buf, worldX, worldY, state, animFrame, cam, palett
665
910
  // Orbiting glow particles when working (bioluminescent)
666
911
  if (state === 'working') {
667
912
  const orbitPhase = animFrame * 0.2;
668
- const dist = 5 + Math.sin(animFrame * 0.15) * 2;
913
+ const dist = (5 + Math.sin(animFrame * 0.15) * 2) * cellSz;
669
914
  for (let i = 0; i < 4; i++) {
670
915
  const angle = orbitPhase + (i * Math.PI * 2 / 4);
671
916
  const sx = scx + Math.cos(angle) * dist;
@@ -676,48 +921,103 @@ export function drawJellyfish(buf, worldX, worldY, state, animFrame, cam, palett
676
921
  }
677
922
  // ===== OpenCode — nested-square logo (simulator SSOT) =====
678
923
  export function drawOpenCode(buf, worldX, worldY, state, animFrame, camera, palette) {
679
- const scx = Math.round(((worldX - camera.cx) * camera.zoom + 0.5) * 64);
680
- const scy = Math.round(((worldY - camera.cy) * camera.zoom + 0.5) * 64);
924
+ const w = Math.sqrt(buf.length / 3);
925
+ const [scx, scy] = worldToScreen(worldX, worldY, camera);
926
+ // Canonical opencode mark: a single-color vertical rectangular RING (16:20) with a
927
+ // HOLLOW center — drawn procedurally so it scales cleanly to any panel (64px, 32px,
928
+ // iDotMatrix) instead of grid sprites that overflowed 32px or drew a filled shadow.
681
929
  const worldW = 10 / 64;
682
- const pixW = Math.max(1, Math.round(worldW * camera.zoom * 64 / OC_COLS));
683
- const pixH = pixW;
930
+ const unit = Math.max(1, Math.round((worldW * camera.zoom * w) / OC_GRID_MD_COLS));
931
+ const outerW = Math.max(5, 6 * unit);
932
+ const outerH = Math.max(6, Math.round(outerW * 1.25)); // slightly tall (16:20)
933
+ const thick = Math.max(1, Math.round(outerW * 0.28)); // frame thickness ~ brand 4/16
684
934
  const breathPx = state === 'working'
685
935
  ? Math.round(Math.sin(animFrame * 0.3) * 1.5)
686
936
  : state === 'idle' ? Math.round(Math.sin(animFrame * 0.08) * 0.7) : 0;
687
- const useLod = camera.zoom < 1.3;
688
- const grid = useLod ? OPENCODE_LOD : OPENCODE_GRID;
689
- const gc = useLod ? OC_LOD_COLS : OC_COLS;
690
- const gr = useLod ? OC_LOD_ROWS : OC_ROWS;
691
- const baseX = scx - Math.round((gc * pixW) / 2);
692
- const baseY = scy + breathPx - Math.round((gr * pixH) / 2);
693
- const outerColor = state === 'working'
937
+ const color = state === 'working'
694
938
  ? lerpColor(palette.outer, palette.pulse, 0.5 + Math.sin(animFrame * 0.2) * 0.5)
695
939
  : state === 'sleeping' ? palette.sleeping : palette.outer;
696
- const innerColor = state === 'sleeping'
697
- ? scaleColor(palette.inner, 0.6) : palette.inner;
698
- for (let r = 0; r < gr; r++) {
699
- for (let c = 0; c < gc; c++) {
700
- const cell = grid[r][c];
701
- if (cell === 0)
702
- continue;
703
- const color = (cell === OPENCODE_CORE) ? innerColor : outerColor;
704
- for (let dy = 0; dy < pixH; dy++) {
705
- for (let dx = 0; dx < pixW; dx++) {
706
- setPixel(buf, baseX + c * pixW + dx, baseY + r * pixH + dy, color);
707
- }
708
- }
940
+ const x0 = scx - Math.round(outerW / 2);
941
+ const y0 = scy + breathPx - Math.round(outerH / 2);
942
+ for (let dy = 0; dy < outerH; dy++) {
943
+ for (let dx = 0; dx < outerW; dx++) {
944
+ const onFrame = dx < thick || dx >= outerW - thick || dy < thick || dy >= outerH - thick;
945
+ if (!onFrame)
946
+ continue; // hollow center
947
+ setPixel(buf, x0 + dx, y0 + dy, color);
709
948
  }
710
949
  }
711
950
  if (state === 'asking') {
712
- // Amber three-dot asking indicator
713
- const dotCx = scx + Math.round(gc / 2) + 3;
714
- const dotCy = baseY - 2 + Math.round(Math.sin(animFrame * 0.26) * 1.4);
951
+ // Amber three-dot asking indicator above the mark
952
+ const dotCx = scx + Math.round(outerW / 2) + 3;
953
+ const dotCy = y0 - 2 + Math.round(Math.sin(animFrame * 0.26) * 1.4);
715
954
  for (let i = -1; i <= 1; i++) {
716
955
  blendPixel(buf, dotCx + i * 3, dotCy, COLORS.stateAwaiting, 0.75);
717
956
  blendPixel(buf, dotCx + i * 3 + 1, dotCy, COLORS.stateAwaiting, 0.45);
718
957
  }
719
958
  }
720
959
  }
960
+ function antigravityCellColor(ch, palette) {
961
+ switch (ch) {
962
+ case 'L': return palette.lime;
963
+ case 'T': return palette.teal;
964
+ case 'Q': return palette.cyan;
965
+ case 'Y': return palette.yellow;
966
+ case 'O': return palette.orange;
967
+ case 'R': return palette.red;
968
+ case 'P': return palette.pink;
969
+ case 'V': return palette.violet;
970
+ case 'U': return palette.blue;
971
+ case 'N': return palette.sky;
972
+ case 'K': return palette.cutout;
973
+ default: return null;
974
+ }
975
+ }
976
+ function drawQuestionMark(buf, x, y, color) {
977
+ setPixel(buf, x, y, color);
978
+ setPixel(buf, x + 1, y, color);
979
+ setPixel(buf, x + 1, y + 1, color);
980
+ setPixel(buf, x, y + 2, color);
981
+ setPixel(buf, x, y + 4, color);
982
+ }
983
+ export function drawAntigravity(buf, worldX, worldY, state, animFrame, camera, palette) {
984
+ if (!isVisible(worldX, worldY, camera, 0.15))
985
+ return;
986
+ const [scx, scy] = worldToScreen(worldX, worldY, camera);
987
+ const w = Math.sqrt(buf.length / 3);
988
+ const cellSz = creatureCellSize(camera.zoom, w, AG_COLS);
989
+ const spriteW = AG_COLS * cellSz;
990
+ const spriteH = AG_ROWS * cellSz;
991
+ const breathPx = state === 'working'
992
+ ? Math.round(Math.sin(animFrame * 0.28) * cellSz)
993
+ : state === 'idle' ? Math.round(Math.sin(animFrame * 0.08) * 0.5) : 0;
994
+ const cameraNudgeX = state === 'working' && ((animFrame >> 3) & 1) ? cellSz : 0;
995
+ const cameraNudgeY = state !== 'idle' && ((animFrame >> 2) & 1) ? -cellSz : 0;
996
+ const baseX = Math.round(scx - spriteW / 2) + cameraNudgeX;
997
+ const baseY = Math.round(scy - spriteH / 2) + breathPx + cameraNudgeY;
998
+ const trackedPixels = new Set();
999
+ for (let row = 0; row < AG_ROWS; row++) {
1000
+ const line = ANTIGRAVITY_GRID[row];
1001
+ for (let col = 0; col < AG_COLS; col++) {
1002
+ const color = antigravityCellColor(line[col], palette);
1003
+ if (!color)
1004
+ continue;
1005
+ fillCellTracked(buf, baseX + col * cellSz, baseY + row * cellSz, cellSz, cellSz, color, trackedPixels);
1006
+ }
1007
+ }
1008
+ drawCreatureOutline(buf, trackedPixels, palette.violet, 0.45);
1009
+ if (state === 'asking') {
1010
+ drawQuestionMark(buf, Math.round(scx + spriteW * 0.48), Math.round(baseY + spriteH * 0.18), COLORS.stateAwaiting);
1011
+ }
1012
+ if (state === 'working') {
1013
+ const sparkle = lerpColor(palette.yellow, COLORS.white, 0.35);
1014
+ const dist = Math.max(2, cellSz * 3);
1015
+ for (let i = 0; i < 4; i++) {
1016
+ const t = animFrame * 0.22 + i * Math.PI / 2;
1017
+ setPixel(buf, Math.round(scx + Math.cos(t) * dist), Math.round(baseY - 1 + Math.sin(t) * dist * 0.35), sparkle);
1018
+ }
1019
+ }
1020
+ }
721
1021
  /**
722
1022
  * Draw crayfish scaled by camera zoom.
723
1023
  * @param worldX Crayfish center in normalized world X (0~1)
@@ -727,14 +1027,16 @@ export function drawCrayfish(buf, worldX, worldY, routing, animFrame, cam, sick
727
1027
  if (!isVisible(worldX, worldY, cam, 0.15))
728
1028
  return;
729
1029
  const [scx, scy] = worldToScreen(worldX, worldY, cam);
1030
+ const w = Math.sqrt(buf.length / 3);
730
1031
  // LOD selection — matches octopus threshold
731
- const useLOD = cam.zoom < 1.3;
732
- const grid = useLOD ? CRAYFISH_LOD : CRAYFISH_GRID;
733
- const cols = useLOD ? CF_LOD_COLS : CF_COLS;
734
- const rows = useLOD ? CF_LOD_ROWS : CF_ROWS;
735
- // Cell size: detail cellW = Z, LOD cellW = 12*Z/8 = 1.5*Z (bigger cells for fewer cols)
736
- const cellW = useLOD ? cam.zoom * 1.5 : cam.zoom;
737
- const cellH = cellW * 1.5;
1032
+ const select = selectGrid(cam.zoom, w, CRAYFISH_GRID_HD, CF_HD_COLS, CF_HD_ROWS, CRAYFISH_GRID_MD, CF_GRID_MD_COLS, CF_GRID_MD_ROWS, CRAYFISH_LOD, CF_LOD_COLS, CF_LOD_ROWS);
1033
+ const grid = select.grid;
1034
+ const cols = select.cols;
1035
+ const rows = select.rows;
1036
+ // Resolution-aware square cells (see creatureCellSize). Square (cellH = cellW) the old
1037
+ // cellH = cellW*1.5 stretched the round OpenClaw body into a tall pointed shape.
1038
+ const cellW = creatureCellSize(cam.zoom, w, cols);
1039
+ const cellH = cellW;
738
1040
  const spriteW = cols * cellW;
739
1041
  const spriteH = rows * cellH;
740
1042
  const baseX = scx - spriteW / 2;
@@ -752,7 +1054,7 @@ export function drawCrayfish(buf, worldX, worldY, routing, animFrame, cam, sick
752
1054
  // 1. Glow halo (before creature — stronger for LOD)
753
1055
  const glowRx = spriteW / 2 + 2;
754
1056
  const glowRy = spriteH / 2 + 2;
755
- drawCreatureGlow(buf, scx, scy + breathPx, glowRx, glowRy, COLORS.crayfishGlow, 0.12, useLOD);
1057
+ drawCreatureGlow(buf, scx, scy + breathPx, glowRx, glowRy, COLORS.crayfishGlow, 0.12, cols === 8);
756
1058
  // 2. Draw cells with pixel tracking
757
1059
  const trackedPixels = new Set();
758
1060
  for (let row = 0; row < rows; row++) {
@@ -769,7 +1071,7 @@ export function drawCrayfish(buf, worldX, worldY, routing, animFrame, cam, sick
769
1071
  const spd = routing ? 0.35 : 0.15;
770
1072
  const wiggle = Math.sin(animFrame * spd + col * 3) * cellW * 1.5;
771
1073
  // Random-ish twitch: sharp spike every ~30 frames
772
- const twitch = ((animFrame + col * 17) % 60) < 4 ? cellW * 2 * (col < 6 ? -1 : 1) : 0;
1074
+ const twitch = ((animFrame + col * 17) % 60) < 4 ? cellW * 2 * (col < cols / 2 ? -1 : 1) : 0;
773
1075
  dx = ensureMinAmplitude(wiggle + twitch, 1);
774
1076
  }
775
1077
  // Claw animation — routing: slower, wider clap; idle: gentle sway
@@ -800,11 +1102,11 @@ export function drawCrayfish(buf, worldX, worldY, routing, animFrame, cam, sick
800
1102
  }
801
1103
  // 3. Colored outline — moderate alpha for LOD
802
1104
  const bodyColor = routing ? COLORS.crayfishRouting : COLORS.crayfishBody;
803
- const outlineAlpha = useLOD ? 0.6 : 0.8;
1105
+ const outlineAlpha = cols === 8 ? 0.6 : 0.8;
804
1106
  drawCreatureOutline(buf, trackedPixels, bodyColor, outlineAlpha);
805
1107
  // Fixed 3×3 eyes (teal center + black ring) — drawn at grid eye positions
806
- const eyeRow = useLOD ? 2 : 3;
807
- const eyeCols = useLOD ? [2, 5] : [4, 7];
1108
+ const eyeRow = cols === 24 ? 9 : (cols === 8 ? 2 : 3);
1109
+ const eyeCols = cols === 24 ? [8, 14] : (cols === 8 ? [2, 5] : [4, 7]);
808
1110
  const sickEyeCenter = [0x44, 0x66, 0x60]; // dim teal for sick
809
1111
  const eyeCenter = sick ? sickEyeCenter : COLORS.crayfishEye;
810
1112
  const eyeRing = sick ? [0x44, 0x33, 0x33] : COLORS.crayfishEyeRing;
@@ -851,7 +1153,8 @@ export function drawTetra(buf, worldX, worldY, heading, cam) {
851
1153
  if (!isVisible(worldX, worldY, cam, 0.08))
852
1154
  return;
853
1155
  const [sx, sy] = worldToScreen(worldX, worldY, cam);
854
- const px = cam.zoom; // pixel scale
1156
+ const w = Math.sqrt(buf.length / 3);
1157
+ const px = cam.zoom * (w / 64); // resolution-aware pixel scale (half-size on 32px iDotMatrix)
855
1158
  // Body (2px-equivalent)
856
1159
  const bw = Math.max(1, Math.round(px * 2));
857
1160
  const bh = Math.max(1, Math.round(px));