pi-agent-flow 1.8.40 → 2.0.1

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 (331) hide show
  1. package/README.md +33 -37
  2. package/agents/audit.md +21 -22
  3. package/agents/build.md +23 -22
  4. package/agents/craft.md +23 -27
  5. package/agents/debug.md +24 -28
  6. package/agents/ideas.md +21 -101
  7. package/agents/scout.md +20 -19
  8. package/dist/batch/batch-bash.d.ts +2 -2
  9. package/dist/batch/batch-bash.d.ts.map +1 -1
  10. package/dist/batch/batch-bash.js +30 -7
  11. package/dist/batch/batch-bash.js.map +1 -1
  12. package/dist/batch/constants.d.ts +31 -5
  13. package/dist/batch/constants.d.ts.map +1 -1
  14. package/dist/batch/constants.js +50 -3
  15. package/dist/batch/constants.js.map +1 -1
  16. package/dist/batch/execute.d.ts +0 -1
  17. package/dist/batch/execute.d.ts.map +1 -1
  18. package/dist/batch/execute.js +210 -6
  19. package/dist/batch/execute.js.map +1 -1
  20. package/dist/batch/fuzzy-edit.d.ts +0 -6
  21. package/dist/batch/fuzzy-edit.d.ts.map +1 -1
  22. package/dist/batch/fuzzy-edit.js +1 -1
  23. package/dist/batch/fuzzy-edit.js.map +1 -1
  24. package/dist/batch/index.d.ts.map +1 -1
  25. package/dist/batch/index.js +87 -16
  26. package/dist/batch/index.js.map +1 -1
  27. package/dist/batch/render.d.ts +0 -1
  28. package/dist/batch/render.d.ts.map +1 -1
  29. package/dist/batch/render.js +7 -101
  30. package/dist/batch/render.js.map +1 -1
  31. package/dist/batch/shell-compress.d.ts +25 -0
  32. package/dist/batch/shell-compress.d.ts.map +1 -0
  33. package/dist/batch/shell-compress.js +602 -0
  34. package/dist/batch/shell-compress.js.map +1 -0
  35. package/dist/batch/summary.d.ts +5 -0
  36. package/dist/batch/summary.d.ts.map +1 -0
  37. package/dist/batch/summary.js +101 -0
  38. package/dist/batch/summary.js.map +1 -0
  39. package/dist/batch/symbols.d.ts.map +1 -1
  40. package/dist/batch/symbols.js +12 -7
  41. package/dist/batch/symbols.js.map +1 -1
  42. package/dist/{config.d.ts → config/config.d.ts} +39 -2
  43. package/dist/config/config.d.ts.map +1 -0
  44. package/dist/{config.js → config/config.js} +220 -9
  45. package/dist/config/config.js.map +1 -0
  46. package/dist/config/log.d.ts +27 -0
  47. package/dist/config/log.d.ts.map +1 -0
  48. package/dist/config/log.js +104 -0
  49. package/dist/config/log.js.map +1 -0
  50. package/dist/config/models.d.ts +2 -0
  51. package/dist/config/models.d.ts.map +1 -0
  52. package/dist/config/models.js +49 -0
  53. package/dist/config/models.js.map +1 -0
  54. package/dist/{settings-resolver.d.ts → config/settings-resolver.d.ts} +9 -2
  55. package/dist/config/settings-resolver.d.ts.map +1 -0
  56. package/dist/config/settings-resolver.js +275 -0
  57. package/dist/config/settings-resolver.js.map +1 -0
  58. package/dist/core/agents.d.ts.map +1 -0
  59. package/dist/{agents.js → core/agents.js} +13 -12
  60. package/dist/core/agents.js.map +1 -0
  61. package/dist/core/delegation.d.ts +24 -0
  62. package/dist/core/delegation.d.ts.map +1 -0
  63. package/dist/core/delegation.js +48 -0
  64. package/dist/core/delegation.js.map +1 -0
  65. package/dist/core/depth.d.ts.map +1 -0
  66. package/dist/{depth.js → core/depth.js} +9 -8
  67. package/dist/core/depth.js.map +1 -0
  68. package/dist/{executor.d.ts → core/executor.d.ts} +18 -3
  69. package/dist/core/executor.d.ts.map +1 -0
  70. package/dist/{executor.js → core/executor.js} +53 -14
  71. package/dist/core/executor.js.map +1 -0
  72. package/dist/{flow.d.ts → core/flow.d.ts} +13 -1
  73. package/dist/core/flow.d.ts.map +1 -0
  74. package/dist/{flow.js → core/flow.js} +125 -64
  75. package/dist/core/flow.js.map +1 -0
  76. package/dist/{session-mode.d.ts → core/session-mode.d.ts} +2 -1
  77. package/dist/core/session-mode.d.ts.map +1 -0
  78. package/dist/{session-mode.js → core/session-mode.js} +2 -1
  79. package/dist/core/session-mode.js.map +1 -0
  80. package/dist/core/session-registry.d.ts +16 -0
  81. package/dist/core/session-registry.d.ts.map +1 -0
  82. package/dist/core/session-registry.js +30 -0
  83. package/dist/core/session-registry.js.map +1 -0
  84. package/dist/core/transitions.d.ts.map +1 -0
  85. package/dist/{transitions.js → core/transitions.js} +1 -1
  86. package/dist/core/transitions.js.map +1 -0
  87. package/dist/flow/auto-warp.d.ts +12 -0
  88. package/dist/flow/auto-warp.d.ts.map +1 -0
  89. package/dist/flow/auto-warp.js +29 -0
  90. package/dist/flow/auto-warp.js.map +1 -0
  91. package/dist/flow/command.d.ts +8 -0
  92. package/dist/flow/command.d.ts.map +1 -0
  93. package/dist/flow/command.js +194 -0
  94. package/dist/flow/command.js.map +1 -0
  95. package/dist/flow/continuation.d.ts +16 -0
  96. package/dist/flow/continuation.d.ts.map +1 -0
  97. package/dist/flow/continuation.js +188 -0
  98. package/dist/flow/continuation.js.map +1 -0
  99. package/dist/flow/index.d.ts +18 -0
  100. package/dist/flow/index.d.ts.map +1 -0
  101. package/dist/flow/index.js +25 -0
  102. package/dist/flow/index.js.map +1 -0
  103. package/dist/flow/loop-command.d.ts +8 -0
  104. package/dist/flow/loop-command.d.ts.map +1 -0
  105. package/dist/flow/loop-command.js +99 -0
  106. package/dist/flow/loop-command.js.map +1 -0
  107. package/dist/flow/loop-templates.d.ts +7 -0
  108. package/dist/flow/loop-templates.d.ts.map +1 -0
  109. package/dist/flow/loop-templates.js +38 -0
  110. package/dist/flow/loop-templates.js.map +1 -0
  111. package/dist/flow/loop.d.ts +19 -0
  112. package/dist/flow/loop.d.ts.map +1 -0
  113. package/dist/flow/loop.js +95 -0
  114. package/dist/flow/loop.js.map +1 -0
  115. package/dist/flow/perform-warp.d.ts +28 -0
  116. package/dist/flow/perform-warp.d.ts.map +1 -0
  117. package/dist/flow/perform-warp.js +127 -0
  118. package/dist/flow/perform-warp.js.map +1 -0
  119. package/dist/flow/settings-command.d.ts +51 -0
  120. package/dist/flow/settings-command.d.ts.map +1 -0
  121. package/dist/flow/settings-command.js +937 -0
  122. package/dist/flow/settings-command.js.map +1 -0
  123. package/dist/flow/store.d.ts +26 -0
  124. package/dist/flow/store.d.ts.map +1 -0
  125. package/dist/flow/store.js +166 -0
  126. package/dist/flow/store.js.map +1 -0
  127. package/dist/flow/template-shared.d.ts +9 -0
  128. package/dist/flow/template-shared.d.ts.map +1 -0
  129. package/dist/flow/template-shared.js +13 -0
  130. package/dist/flow/template-shared.js.map +1 -0
  131. package/dist/flow/template-strings.d.ts +8 -0
  132. package/dist/flow/template-strings.d.ts.map +1 -0
  133. package/dist/flow/template-strings.js +36 -0
  134. package/dist/flow/template-strings.js.map +1 -0
  135. package/dist/flow/types.d.ts +61 -0
  136. package/dist/flow/types.d.ts.map +1 -0
  137. package/dist/flow/types.js +5 -0
  138. package/dist/flow/types.js.map +1 -0
  139. package/dist/flow/warp-command.d.ts +8 -0
  140. package/dist/flow/warp-command.d.ts.map +1 -0
  141. package/dist/flow/warp-command.js +144 -0
  142. package/dist/flow/warp-command.js.map +1 -0
  143. package/dist/flow/warp-utils.d.ts +11 -0
  144. package/dist/flow/warp-utils.d.ts.map +1 -0
  145. package/dist/flow/warp-utils.js +187 -0
  146. package/dist/flow/warp-utils.js.map +1 -0
  147. package/dist/index.d.ts +4 -1
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +115 -31
  150. package/dist/index.js.map +1 -1
  151. package/dist/{notify-state.d.ts → notify/notify-state.d.ts} +2 -1
  152. package/dist/notify/notify-state.d.ts.map +1 -0
  153. package/dist/notify/notify-state.js.map +1 -0
  154. package/dist/notify/notify.d.ts.map +1 -0
  155. package/dist/{notify.js → notify/notify.js} +3 -2
  156. package/dist/notify/notify.js.map +1 -0
  157. package/dist/{cli-args.d.ts → snapshot/cli-args.d.ts} +4 -2
  158. package/dist/snapshot/cli-args.d.ts.map +1 -0
  159. package/dist/{cli-args.js → snapshot/cli-args.js} +13 -1
  160. package/dist/snapshot/cli-args.js.map +1 -0
  161. package/dist/snapshot/index.d.ts +2 -0
  162. package/dist/snapshot/index.d.ts.map +1 -0
  163. package/dist/snapshot/index.js +2 -0
  164. package/dist/snapshot/index.js.map +1 -0
  165. package/dist/{reasoning-strip.d.ts → snapshot/reasoning-strip.d.ts} +0 -4
  166. package/dist/snapshot/reasoning-strip.d.ts.map +1 -0
  167. package/dist/{reasoning-strip.js → snapshot/reasoning-strip.js} +2 -2
  168. package/dist/snapshot/reasoning-strip.js.map +1 -0
  169. package/dist/snapshot/runner-events.d.ts.map +1 -0
  170. package/dist/{runner-events.js → snapshot/runner-events.js} +1 -1
  171. package/dist/snapshot/runner-events.js.map +1 -0
  172. package/dist/{snapshot.d.ts → snapshot/snapshot.d.ts} +24 -18
  173. package/dist/snapshot/snapshot.d.ts.map +1 -0
  174. package/dist/snapshot/snapshot.js +1791 -0
  175. package/dist/snapshot/snapshot.js.map +1 -0
  176. package/dist/{structured-output.d.ts → snapshot/structured-output.d.ts} +1 -1
  177. package/dist/snapshot/structured-output.d.ts.map +1 -0
  178. package/dist/snapshot/structured-output.js.map +1 -0
  179. package/dist/{flow-prompt.d.ts → steering/flow-prompt.d.ts} +2 -2
  180. package/dist/steering/flow-prompt.d.ts.map +1 -0
  181. package/dist/{flow-prompt.js → steering/flow-prompt.js} +1 -1
  182. package/dist/steering/flow-prompt.js.map +1 -0
  183. package/dist/{sliding-prompt.d.ts → steering/sliding-prompt.d.ts} +8 -7
  184. package/dist/steering/sliding-prompt.d.ts.map +1 -0
  185. package/dist/{sliding-prompt.js → steering/sliding-prompt.js} +18 -64
  186. package/dist/steering/sliding-prompt.js.map +1 -0
  187. package/dist/{tool-utils.d.ts → steering/tool-utils.d.ts} +1 -0
  188. package/dist/steering/tool-utils.d.ts.map +1 -0
  189. package/dist/{tool-utils.js → steering/tool-utils.js} +10 -3
  190. package/dist/steering/tool-utils.js.map +1 -0
  191. package/dist/{ask-user.d.ts → tools/ask-user.d.ts} +3 -15
  192. package/dist/tools/ask-user.d.ts.map +1 -0
  193. package/dist/tools/ask-user.js +778 -0
  194. package/dist/tools/ask-user.js.map +1 -0
  195. package/dist/{timed-bash.d.ts → tools/timed-bash.d.ts} +2 -7
  196. package/dist/tools/timed-bash.d.ts.map +1 -0
  197. package/dist/{timed-bash.js → tools/timed-bash.js} +11 -2
  198. package/dist/tools/timed-bash.js.map +1 -0
  199. package/dist/{web-tool.d.ts → tools/web-tool.d.ts} +1 -1
  200. package/dist/tools/web-tool.d.ts.map +1 -0
  201. package/dist/{web-tool.js → tools/web-tool.js} +8 -7
  202. package/dist/tools/web-tool.js.map +1 -0
  203. package/dist/tui/flow-colors.d.ts +55 -0
  204. package/dist/tui/flow-colors.d.ts.map +1 -0
  205. package/dist/tui/flow-colors.js +22 -0
  206. package/dist/tui/flow-colors.js.map +1 -0
  207. package/dist/{render-utils.d.ts → tui/render-utils.d.ts} +6 -2
  208. package/dist/tui/render-utils.d.ts.map +1 -0
  209. package/dist/{render-utils.js → tui/render-utils.js} +40 -12
  210. package/dist/tui/render-utils.js.map +1 -0
  211. package/dist/tui/render.d.ts +21 -0
  212. package/dist/tui/render.d.ts.map +1 -0
  213. package/dist/tui/render.js +786 -0
  214. package/dist/tui/render.js.map +1 -0
  215. package/dist/tui/scramble/algorithm.d.ts +7 -0
  216. package/dist/tui/scramble/algorithm.d.ts.map +1 -0
  217. package/dist/tui/scramble/algorithm.js +227 -0
  218. package/dist/tui/scramble/algorithm.js.map +1 -0
  219. package/dist/tui/scramble/constants.d.ts +99 -0
  220. package/dist/tui/scramble/constants.d.ts.map +1 -0
  221. package/dist/tui/scramble/constants.js +101 -0
  222. package/dist/tui/scramble/constants.js.map +1 -0
  223. package/dist/tui/scramble/index.d.ts +6 -0
  224. package/dist/tui/scramble/index.d.ts.map +1 -0
  225. package/dist/tui/scramble/index.js +6 -0
  226. package/dist/tui/scramble/index.js.map +1 -0
  227. package/dist/tui/scramble/manager.d.ts +44 -0
  228. package/dist/tui/scramble/manager.d.ts.map +1 -0
  229. package/dist/tui/scramble/manager.js +899 -0
  230. package/dist/tui/scramble/manager.js.map +1 -0
  231. package/dist/tui/scramble/utils.d.ts +18 -0
  232. package/dist/tui/scramble/utils.d.ts.map +1 -0
  233. package/dist/tui/scramble/utils.js +145 -0
  234. package/dist/tui/scramble/utils.js.map +1 -0
  235. package/dist/tui/single-select-layout.d.ts +17 -0
  236. package/dist/tui/single-select-layout.d.ts.map +1 -0
  237. package/dist/{single-select-layout.js → tui/single-select-layout.js} +8 -25
  238. package/dist/tui/single-select-layout.js.map +1 -0
  239. package/dist/types/flow.d.ts +112 -0
  240. package/dist/types/flow.d.ts.map +1 -0
  241. package/dist/{types.js → types/flow.js} +3 -54
  242. package/dist/types/flow.js.map +1 -0
  243. package/dist/types/index.d.ts +8 -0
  244. package/dist/types/index.d.ts.map +1 -0
  245. package/dist/types/index.js +7 -0
  246. package/dist/types/index.js.map +1 -0
  247. package/dist/types/output.d.ts +110 -0
  248. package/dist/types/output.d.ts.map +1 -0
  249. package/dist/types/output.js +5 -0
  250. package/dist/types/output.js.map +1 -0
  251. package/dist/types/ui.d.ts +24 -0
  252. package/dist/types/ui.d.ts.map +1 -0
  253. package/dist/types/ui.js +55 -0
  254. package/dist/types/ui.js.map +1 -0
  255. package/package.json +1 -1
  256. package/dist/agents.d.ts.map +0 -1
  257. package/dist/agents.js.map +0 -1
  258. package/dist/ask-user.d.ts.map +0 -1
  259. package/dist/ask-user.js +0 -1405
  260. package/dist/ask-user.js.map +0 -1
  261. package/dist/batch.d.ts +0 -12
  262. package/dist/batch.d.ts.map +0 -1
  263. package/dist/batch.js +0 -11
  264. package/dist/batch.js.map +0 -1
  265. package/dist/cli-args.d.ts.map +0 -1
  266. package/dist/cli-args.js.map +0 -1
  267. package/dist/config.d.ts.map +0 -1
  268. package/dist/config.js.map +0 -1
  269. package/dist/depth.d.ts.map +0 -1
  270. package/dist/depth.js.map +0 -1
  271. package/dist/executor.d.ts.map +0 -1
  272. package/dist/executor.js.map +0 -1
  273. package/dist/flow-prompt.d.ts.map +0 -1
  274. package/dist/flow-prompt.js.map +0 -1
  275. package/dist/flow.d.ts.map +0 -1
  276. package/dist/flow.js.map +0 -1
  277. package/dist/notify-state.d.ts.map +0 -1
  278. package/dist/notify-state.js.map +0 -1
  279. package/dist/notify.d.ts.map +0 -1
  280. package/dist/notify.js.map +0 -1
  281. package/dist/reasoning-strip.d.ts.map +0 -1
  282. package/dist/reasoning-strip.js.map +0 -1
  283. package/dist/render-utils.d.ts.map +0 -1
  284. package/dist/render-utils.js.map +0 -1
  285. package/dist/render.d.ts +0 -24
  286. package/dist/render.d.ts.map +0 -1
  287. package/dist/render.js +0 -592
  288. package/dist/render.js.map +0 -1
  289. package/dist/runner-events.d.ts.map +0 -1
  290. package/dist/runner-events.js.map +0 -1
  291. package/dist/scramble.d.ts +0 -183
  292. package/dist/scramble.d.ts.map +0 -1
  293. package/dist/scramble.js +0 -2478
  294. package/dist/scramble.js.map +0 -1
  295. package/dist/session-mode.d.ts.map +0 -1
  296. package/dist/session-mode.js.map +0 -1
  297. package/dist/settings-resolver.d.ts.map +0 -1
  298. package/dist/settings-resolver.js +0 -148
  299. package/dist/settings-resolver.js.map +0 -1
  300. package/dist/single-select-layout.d.ts +0 -20
  301. package/dist/single-select-layout.d.ts.map +0 -1
  302. package/dist/single-select-layout.js.map +0 -1
  303. package/dist/sliding-prompt.d.ts.map +0 -1
  304. package/dist/sliding-prompt.js.map +0 -1
  305. package/dist/snapshot.d.ts.map +0 -1
  306. package/dist/snapshot.js +0 -797
  307. package/dist/snapshot.js.map +0 -1
  308. package/dist/spec-mode.d.ts +0 -13
  309. package/dist/spec-mode.d.ts.map +0 -1
  310. package/dist/spec-mode.js +0 -90
  311. package/dist/spec-mode.js.map +0 -1
  312. package/dist/structured-output.d.ts.map +0 -1
  313. package/dist/structured-output.js.map +0 -1
  314. package/dist/timed-bash.d.ts.map +0 -1
  315. package/dist/timed-bash.js.map +0 -1
  316. package/dist/tool-utils.d.ts.map +0 -1
  317. package/dist/tool-utils.js.map +0 -1
  318. package/dist/transitions.d.ts.map +0 -1
  319. package/dist/transitions.js.map +0 -1
  320. package/dist/types.d.ts +0 -224
  321. package/dist/types.d.ts.map +0 -1
  322. package/dist/types.js.map +0 -1
  323. package/dist/web-tool.d.ts.map +0 -1
  324. package/dist/web-tool.js.map +0 -1
  325. /package/dist/{agents.d.ts → core/agents.d.ts} +0 -0
  326. /package/dist/{depth.d.ts → core/depth.d.ts} +0 -0
  327. /package/dist/{transitions.d.ts → core/transitions.d.ts} +0 -0
  328. /package/dist/{notify-state.js → notify/notify-state.js} +0 -0
  329. /package/dist/{notify.d.ts → notify/notify.d.ts} +0 -0
  330. /package/dist/{runner-events.d.ts → snapshot/runner-events.d.ts} +0 -0
  331. /package/dist/{structured-output.js → snapshot/structured-output.js} +0 -0
@@ -0,0 +1,899 @@
1
+ // Auto-generated from src/tui/scramble.ts split
2
+ import { tailText } from '../render-utils.js';
3
+ import { Text, truncateToWidth } from '@mariozechner/pi-tui';
4
+ import { GLITCH_FRAME_MS, GLITCH_SHORT_MAX_START, GLITCH_SHORT_MAX_LENGTH, GLITCH_COOLDOWN_MS, MIN_GLITCH_INTERVAL, TPS_HYSTERESIS_PCT, TPS_HYSTERESIS_MS, TPS_FLASH_COOLDOWN_MS, clearLiveText, MAX_FLOW_ENTRIES, MAX_CACHE_AGE_MS, RANDOM_POOL_SIZE, SCRAMBLE_CHARS, POOL_REFILL_THRESHOLD, } from './constants.js';
5
+ import { computeOverlapLen, isMinorStaticMutation, } from './utils.js';
6
+ import { buildGlitchQueue, buildMsgGlitchQueue, computeGlitchFrame, applyScramble, isGlitchComplete, } from './algorithm.js';
7
+ // ---------------------------------------------------------------------------
8
+ // processLine — unified change detection (glitch only)
9
+ // ---------------------------------------------------------------------------
10
+ function processLine(state, newText, now, lineKey, glitchEnabled = true) {
11
+ if (state.completed)
12
+ return;
13
+ if (!state.initialized) {
14
+ state.lastText = newText;
15
+ state.displayedText = newText;
16
+ state.initialized = true;
17
+ state.lastAnimTime = now;
18
+ return;
19
+ }
20
+ const textChanged = state.lastText !== newText;
21
+ if (!textChanged)
22
+ return;
23
+ const oldText = state.lastText;
24
+ state.lastText = newText;
25
+ const overlap = computeOverlapLen(oldText, newText);
26
+ const minLen = Math.min(oldText.length, newText.length);
27
+ const isExtension = newText.startsWith(oldText);
28
+ if (!isExtension && overlap > 0 && overlap >= minLen * 0.5) {
29
+ state.displayedText = newText;
30
+ return;
31
+ }
32
+ const cooldownMs = lineKey === 'msg' ? GLITCH_COOLDOWN_MS : MIN_GLITCH_INTERVAL;
33
+ const cooledDown = now - state.lastGlitchTime >= cooldownMs;
34
+ if (!cooledDown) {
35
+ if (lineKey !== 'msg') {
36
+ state.displayedText = newText;
37
+ }
38
+ return;
39
+ }
40
+ const oldDisplayed = state.displayedText || oldText;
41
+ if (lineKey === 'msg') {
42
+ state.targetText = newText;
43
+ }
44
+ else {
45
+ state.displayedText = newText;
46
+ }
47
+ state.lastAnimTime = now;
48
+ if (glitchEnabled) {
49
+ if (state.glitchQueue.length > 0) {
50
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
51
+ if (!isGlitchComplete(state.glitchQueue, frame)) {
52
+ state.pendingGlitch = lineKey === 'msg'
53
+ ? buildMsgGlitchQueue(oldDisplayed, newText)
54
+ : buildGlitchQueue(oldDisplayed, newText);
55
+ state.pendingOldDisplayed = oldDisplayed;
56
+ state.pendingNewDisplayed = newText;
57
+ state.pendingStartTime = now;
58
+ return;
59
+ }
60
+ }
61
+ state.glitchQueue = lineKey === 'msg'
62
+ ? buildMsgGlitchQueue(oldDisplayed, newText)
63
+ : buildGlitchQueue(oldDisplayed, newText);
64
+ state.targetText = newText;
65
+ state.startTime = now;
66
+ state.glitchFrame = 0;
67
+ state.lastGlitchTime = now;
68
+ }
69
+ else if (lineKey === 'msg') {
70
+ state.displayedText = newText;
71
+ }
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // ScrambleStateManager
75
+ // ---------------------------------------------------------------------------
76
+ function createLineState() {
77
+ return {
78
+ lastText: '',
79
+ displayedText: '',
80
+ targetText: '',
81
+ startTime: 0,
82
+ lastAnimTime: 0,
83
+ initialized: false,
84
+ completed: false,
85
+ lastAccessTime: Date.now(),
86
+ glitchQueue: [],
87
+ glitchFrame: 0,
88
+ lastGlitchTime: Number.NEGATIVE_INFINITY,
89
+ pendingGlitch: null,
90
+ pendingOldDisplayed: '',
91
+ pendingNewDisplayed: '',
92
+ pendingStartTime: 0,
93
+ };
94
+ }
95
+ function createValueFlashState() {
96
+ return {
97
+ prev: '',
98
+ startTime: 0,
99
+ lastValueChangeTime: 0,
100
+ lastFlashTime: 0,
101
+ completed: false,
102
+ glitchQueue: [],
103
+ glitchFrame: 0,
104
+ lastGlitchTime: 0,
105
+ };
106
+ }
107
+ export class ScrambleStateManager {
108
+ cache = new Map();
109
+ tpsState = new Map();
110
+ genericCache = new Map();
111
+ randomPool = [];
112
+ randomPoolIndex = 0;
113
+ animationConfig = { enabled: true, glitch: true };
114
+ fillRandomPool() {
115
+ this.randomPool = new Array(RANDOM_POOL_SIZE);
116
+ for (let i = 0; i < RANDOM_POOL_SIZE; i++) {
117
+ this.randomPool[i] = SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
118
+ }
119
+ this.randomPoolIndex = 0;
120
+ }
121
+ poolRandomChar() {
122
+ if (this.randomPoolIndex >= this.randomPool.length - POOL_REFILL_THRESHOLD) {
123
+ this.fillRandomPool();
124
+ }
125
+ return this.randomPool[this.randomPoolIndex++];
126
+ }
127
+ setAnimationConfig(config) {
128
+ this.animationConfig = config;
129
+ }
130
+ getState(id, key) {
131
+ let record = this.cache.get(id);
132
+ if (!record) {
133
+ record = { aim: createLineState(), act: createLineState(), msg: createLineState() };
134
+ this.cache.set(id, record);
135
+ }
136
+ return record[key];
137
+ }
138
+ getGenericState(id, key, now) {
139
+ const cacheKey = `${id}#${key}`;
140
+ let state = this.genericCache.get(cacheKey);
141
+ if (!state) {
142
+ state = createLineState();
143
+ this.genericCache.set(cacheKey, state);
144
+ }
145
+ state.lastAccessTime = now;
146
+ return state;
147
+ }
148
+ updateText(id, key, text, now, isComplete = false, staticLine = false) {
149
+ if (!this.animationConfig.enabled) {
150
+ return { label: key, content: text, isAnimating: false };
151
+ }
152
+ if (isComplete) {
153
+ const state = this.genericCache.get(`${id}#${key}`);
154
+ if (!state)
155
+ return { label: key, content: text, isAnimating: false };
156
+ }
157
+ const state = this.getGenericState(id, key, now);
158
+ if (!isComplete && state.completed) {
159
+ state.completed = false;
160
+ state.lastText = '';
161
+ state.displayedText = '';
162
+ state.targetText = '';
163
+ state.initialized = false;
164
+ state.glitchQueue = [];
165
+ state.glitchFrame = 0;
166
+ state.pendingGlitch = null;
167
+ state.pendingOldDisplayed = '';
168
+ state.pendingNewDisplayed = '';
169
+ state.pendingStartTime = 0;
170
+ }
171
+ if (isComplete) {
172
+ state.completed = true;
173
+ state.glitchQueue = [];
174
+ state.glitchFrame = 0;
175
+ state.pendingGlitch = null;
176
+ state.pendingOldDisplayed = '';
177
+ state.pendingNewDisplayed = '';
178
+ state.pendingStartTime = 0;
179
+ }
180
+ if (state.completed) {
181
+ if (state.glitchQueue.length > 0) {
182
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
183
+ if (!isGlitchComplete(state.glitchQueue, frame)) {
184
+ const content = computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar(), text);
185
+ return { label: key, content, isAnimating: true };
186
+ }
187
+ state.glitchQueue = [];
188
+ state.glitchFrame = 0;
189
+ }
190
+ state.displayedText = text;
191
+ state.lastText = text;
192
+ return { label: key, content: text, isAnimating: false };
193
+ }
194
+ if (!state.initialized) {
195
+ state.lastText = text;
196
+ state.displayedText = text;
197
+ state.initialized = true;
198
+ state.lastAnimTime = now;
199
+ if (this.animationConfig.glitch) {
200
+ state.glitchQueue = buildGlitchQueue('', text);
201
+ state.startTime = now;
202
+ state.lastGlitchTime = now;
203
+ state.glitchFrame = 0;
204
+ }
205
+ }
206
+ else if (staticLine && state.initialized) {
207
+ const oldText = state.lastText;
208
+ const textChanged = oldText !== text;
209
+ state.lastText = text;
210
+ const oldDisplayed = state.displayedText || '';
211
+ state.displayedText = text;
212
+ if (textChanged) {
213
+ if (isMinorStaticMutation(oldText, text)) {
214
+ // minor mutation — don't restart animation
215
+ }
216
+ else if (now - state.lastAnimTime >= MIN_GLITCH_INTERVAL) {
217
+ state.lastAnimTime = now;
218
+ if (this.animationConfig.glitch) {
219
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
220
+ const glitchComplete = isGlitchComplete(state.glitchQueue, frame);
221
+ if (glitchComplete) {
222
+ state.glitchQueue = buildGlitchQueue(oldDisplayed, text);
223
+ state.startTime = now;
224
+ state.lastGlitchTime = now;
225
+ state.glitchFrame = 0;
226
+ }
227
+ else if (state.glitchQueue.length > 0) {
228
+ state.pendingGlitch = buildGlitchQueue(oldDisplayed, text);
229
+ state.pendingOldDisplayed = oldDisplayed;
230
+ state.pendingNewDisplayed = text;
231
+ state.pendingStartTime = now;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ else if (!this.isLineAnimating(state, now)) {
237
+ state.glitchQueue = [];
238
+ state.glitchFrame = 0;
239
+ state.pendingGlitch = null;
240
+ state.pendingOldDisplayed = '';
241
+ state.pendingNewDisplayed = '';
242
+ state.pendingStartTime = 0;
243
+ }
244
+ }
245
+ else {
246
+ processLine(state, text, now, undefined, this.animationConfig.glitch);
247
+ }
248
+ const content = applyScramble(text, state, now, undefined, () => this.poolRandomChar(), this.animationConfig.glitch);
249
+ const isAnimating = this.isLineAnimating(state, now);
250
+ return { label: key, content, isAnimating };
251
+ }
252
+ updateAim(id, text, now, isComplete = false, staticLine = false) {
253
+ if (!this.animationConfig.enabled) {
254
+ return { label: 'aim:', content: text, isAnimating: false };
255
+ }
256
+ if (isComplete) {
257
+ const record = this.cache.get(id);
258
+ if (!record)
259
+ return { label: 'aim:', content: text, isAnimating: false };
260
+ }
261
+ const state = this.getState(id, 'aim');
262
+ if (!isComplete && state.completed) {
263
+ state.completed = false;
264
+ state.lastText = '';
265
+ state.displayedText = '';
266
+ state.targetText = '';
267
+ state.initialized = false;
268
+ state.glitchQueue = [];
269
+ state.glitchFrame = 0;
270
+ state.pendingGlitch = null;
271
+ state.pendingOldDisplayed = '';
272
+ state.pendingNewDisplayed = '';
273
+ state.pendingStartTime = 0;
274
+ }
275
+ if (isComplete) {
276
+ state.completed = true;
277
+ state.glitchQueue = [];
278
+ state.glitchFrame = 0;
279
+ state.pendingGlitch = null;
280
+ state.pendingOldDisplayed = '';
281
+ state.pendingNewDisplayed = '';
282
+ state.pendingStartTime = 0;
283
+ }
284
+ if (state.completed) {
285
+ if (state.glitchQueue.length > 0) {
286
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
287
+ if (!isGlitchComplete(state.glitchQueue, frame)) {
288
+ const content = computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar(), text);
289
+ return { label: 'aim:', content, isAnimating: true };
290
+ }
291
+ state.glitchQueue = [];
292
+ state.glitchFrame = 0;
293
+ }
294
+ state.displayedText = text;
295
+ state.lastText = text;
296
+ return { label: 'aim:', content: text, isAnimating: false };
297
+ }
298
+ if (!state.initialized) {
299
+ state.lastText = text;
300
+ state.initialized = true;
301
+ state.lastAnimTime = now;
302
+ if (this.animationConfig.glitch) {
303
+ state.glitchQueue = buildGlitchQueue('', text);
304
+ state.startTime = now;
305
+ state.lastGlitchTime = now;
306
+ state.glitchFrame = 0;
307
+ }
308
+ }
309
+ else if (staticLine && state.initialized) {
310
+ const oldText = state.lastText;
311
+ const textChanged = oldText !== text;
312
+ state.lastText = text;
313
+ const oldDisplayed = state.displayedText || '';
314
+ state.displayedText = text;
315
+ if (textChanged) {
316
+ if (isMinorStaticMutation(oldText, text)) {
317
+ // minor mutation — don't restart animation
318
+ }
319
+ else if (now - state.lastAnimTime >= MIN_GLITCH_INTERVAL) {
320
+ state.lastAnimTime = now;
321
+ if (this.animationConfig.glitch) {
322
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
323
+ const glitchComplete = isGlitchComplete(state.glitchQueue, frame);
324
+ if (glitchComplete) {
325
+ state.glitchQueue = buildGlitchQueue(oldDisplayed, text);
326
+ state.startTime = now;
327
+ state.lastGlitchTime = now;
328
+ state.glitchFrame = 0;
329
+ }
330
+ else if (state.glitchQueue.length > 0) {
331
+ state.pendingGlitch = buildGlitchQueue(oldDisplayed, text);
332
+ state.pendingOldDisplayed = oldDisplayed;
333
+ state.pendingNewDisplayed = text;
334
+ state.pendingStartTime = now;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ else if (!this.isLineAnimating(state, now)) {
340
+ state.glitchQueue = [];
341
+ state.glitchFrame = 0;
342
+ state.pendingGlitch = null;
343
+ state.pendingOldDisplayed = '';
344
+ state.pendingNewDisplayed = '';
345
+ state.pendingStartTime = 0;
346
+ }
347
+ }
348
+ else {
349
+ processLine(state, text, now, undefined, this.animationConfig.glitch);
350
+ }
351
+ const content = applyScramble(text, state, now, undefined, () => this.poolRandomChar(), this.animationConfig.glitch);
352
+ const isAnimating = this.isLineAnimating(state, now);
353
+ return { label: 'aim:', content, isAnimating };
354
+ }
355
+ updateAct(id, text, now, isComplete = false, staticLine = false) {
356
+ if (!this.animationConfig.enabled) {
357
+ return { label: 'act:', content: text, isAnimating: false };
358
+ }
359
+ if (isComplete) {
360
+ const record = this.cache.get(id);
361
+ if (!record)
362
+ return { label: 'act:', content: text, isAnimating: false };
363
+ }
364
+ const state = this.getState(id, 'act');
365
+ if (!isComplete && state.completed) {
366
+ state.completed = false;
367
+ state.lastText = '';
368
+ state.displayedText = '';
369
+ state.targetText = '';
370
+ state.initialized = false;
371
+ state.glitchQueue = [];
372
+ state.glitchFrame = 0;
373
+ state.pendingGlitch = null;
374
+ state.pendingOldDisplayed = '';
375
+ state.pendingNewDisplayed = '';
376
+ state.pendingStartTime = 0;
377
+ }
378
+ if (isComplete) {
379
+ state.completed = true;
380
+ state.glitchQueue = [];
381
+ state.glitchFrame = 0;
382
+ state.pendingGlitch = null;
383
+ state.pendingOldDisplayed = '';
384
+ state.pendingNewDisplayed = '';
385
+ state.pendingStartTime = 0;
386
+ }
387
+ if (state.completed) {
388
+ if (state.glitchQueue.length > 0) {
389
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
390
+ if (!isGlitchComplete(state.glitchQueue, frame)) {
391
+ const content = computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar(), text);
392
+ return { label: 'act:', content, isAnimating: true };
393
+ }
394
+ state.glitchQueue = [];
395
+ state.glitchFrame = 0;
396
+ }
397
+ state.displayedText = text;
398
+ state.lastText = text;
399
+ return { label: 'act:', content: text, isAnimating: false };
400
+ }
401
+ if (!state.initialized) {
402
+ state.lastText = text;
403
+ state.initialized = true;
404
+ state.lastAnimTime = now;
405
+ if (this.animationConfig.glitch) {
406
+ state.glitchQueue = buildGlitchQueue('', text);
407
+ state.startTime = now;
408
+ state.lastGlitchTime = now;
409
+ state.glitchFrame = 0;
410
+ }
411
+ }
412
+ else if (staticLine && state.initialized) {
413
+ const oldText = state.lastText;
414
+ const textChanged = oldText !== text;
415
+ state.lastText = text;
416
+ const oldDisplayed = state.displayedText || '';
417
+ state.displayedText = text;
418
+ if (textChanged) {
419
+ if (isMinorStaticMutation(oldText, text)) {
420
+ // minor mutation — don't restart animation
421
+ }
422
+ else if (now - state.lastAnimTime >= MIN_GLITCH_INTERVAL) {
423
+ state.lastAnimTime = now;
424
+ if (this.animationConfig.glitch) {
425
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
426
+ const glitchComplete = isGlitchComplete(state.glitchQueue, frame);
427
+ if (glitchComplete) {
428
+ state.glitchQueue = buildGlitchQueue(oldDisplayed, text);
429
+ state.startTime = now;
430
+ state.lastGlitchTime = now;
431
+ state.glitchFrame = 0;
432
+ }
433
+ else if (state.glitchQueue.length > 0) {
434
+ state.pendingGlitch = buildGlitchQueue(oldDisplayed, text);
435
+ state.pendingOldDisplayed = oldDisplayed;
436
+ state.pendingNewDisplayed = text;
437
+ state.pendingStartTime = now;
438
+ }
439
+ }
440
+ }
441
+ }
442
+ else if (!this.isLineAnimating(state, now)) {
443
+ state.glitchQueue = [];
444
+ state.glitchFrame = 0;
445
+ state.pendingGlitch = null;
446
+ state.pendingOldDisplayed = '';
447
+ state.pendingNewDisplayed = '';
448
+ state.pendingStartTime = 0;
449
+ }
450
+ }
451
+ else {
452
+ processLine(state, text, now, 'act', this.animationConfig.glitch);
453
+ }
454
+ const content = applyScramble(text, state, now, 'act', () => this.poolRandomChar(), this.animationConfig.glitch);
455
+ const isAnimating = this.isLineAnimating(state, now);
456
+ return { label: 'act:', content, isAnimating };
457
+ }
458
+ updateMsg(id, text, now, isComplete = false, budget, staticLine = false) {
459
+ const visibleText = budget !== undefined ? tailText(text, budget) : text;
460
+ if (!this.animationConfig.enabled) {
461
+ return { label: 'msg:', content: visibleText, isAnimating: false };
462
+ }
463
+ if (isComplete) {
464
+ const record = this.cache.get(id);
465
+ if (!record)
466
+ return { label: 'msg:', content: visibleText, isAnimating: false };
467
+ }
468
+ const state = this.getState(id, 'msg');
469
+ if (!isComplete && state.completed) {
470
+ state.completed = false;
471
+ state.lastText = '';
472
+ state.displayedText = '';
473
+ state.targetText = '';
474
+ state.initialized = false;
475
+ state.glitchQueue = [];
476
+ state.glitchFrame = 0;
477
+ state.pendingGlitch = null;
478
+ state.pendingOldDisplayed = '';
479
+ state.pendingNewDisplayed = '';
480
+ state.pendingStartTime = 0;
481
+ }
482
+ if (isComplete) {
483
+ state.completed = true;
484
+ state.glitchQueue = [];
485
+ state.glitchFrame = 0;
486
+ state.pendingGlitch = null;
487
+ state.pendingOldDisplayed = '';
488
+ state.pendingNewDisplayed = '';
489
+ state.pendingStartTime = 0;
490
+ }
491
+ if (state.completed) {
492
+ if (state.glitchQueue.length > 0) {
493
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
494
+ if (!isGlitchComplete(state.glitchQueue, frame)) {
495
+ const content = computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar(), visibleText);
496
+ return { label: 'msg:', content, isAnimating: true };
497
+ }
498
+ state.glitchQueue = [];
499
+ state.glitchFrame = 0;
500
+ }
501
+ state.displayedText = visibleText;
502
+ state.lastText = visibleText;
503
+ return { label: 'msg:', content: visibleText, isAnimating: false };
504
+ }
505
+ if (!state.initialized) {
506
+ state.lastText = visibleText;
507
+ state.initialized = true;
508
+ state.displayedText = visibleText;
509
+ state.lastAnimTime = now;
510
+ }
511
+ else if (staticLine && state.initialized) {
512
+ const oldText = state.lastText;
513
+ const textChanged = oldText !== visibleText;
514
+ state.lastText = visibleText;
515
+ const oldDisplayed = state.displayedText || '';
516
+ state.displayedText = visibleText;
517
+ if (textChanged) {
518
+ if (isMinorStaticMutation(oldText, visibleText)) {
519
+ // minor mutation — don't restart animation
520
+ }
521
+ else if (now - state.lastAnimTime >= MIN_GLITCH_INTERVAL) {
522
+ state.lastAnimTime = now;
523
+ if (this.animationConfig.glitch) {
524
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
525
+ const glitchComplete = isGlitchComplete(state.glitchQueue, frame);
526
+ if (glitchComplete) {
527
+ state.glitchQueue = buildMsgGlitchQueue(oldDisplayed, visibleText);
528
+ state.targetText = visibleText;
529
+ state.startTime = now;
530
+ state.lastGlitchTime = now;
531
+ state.glitchFrame = 0;
532
+ }
533
+ else if (state.glitchQueue.length > 0) {
534
+ state.pendingGlitch = buildMsgGlitchQueue(oldDisplayed, visibleText);
535
+ state.pendingOldDisplayed = oldDisplayed;
536
+ state.pendingNewDisplayed = visibleText;
537
+ state.pendingStartTime = now;
538
+ }
539
+ }
540
+ }
541
+ }
542
+ else if (!this.isLineAnimating(state, now)) {
543
+ state.glitchQueue = [];
544
+ state.glitchFrame = 0;
545
+ state.pendingGlitch = null;
546
+ state.pendingOldDisplayed = '';
547
+ state.pendingNewDisplayed = '';
548
+ state.pendingStartTime = 0;
549
+ }
550
+ }
551
+ else {
552
+ processLine(state, visibleText, now, 'msg', this.animationConfig.glitch);
553
+ }
554
+ let displayText;
555
+ if (staticLine && state.glitchQueue.length > 0) {
556
+ const frozenTarget = state.targetText || state.displayedText;
557
+ displayText = visibleText.length > frozenTarget.length ? frozenTarget : visibleText;
558
+ }
559
+ else {
560
+ const overlap = computeOverlapLen(state.displayedText, visibleText);
561
+ const minDispLen = Math.min(state.displayedText.length, visibleText.length);
562
+ const isTailSlide = overlap > 0 && overlap >= minDispLen * 0.5;
563
+ const suppressTailSlide = staticLine && !isComplete && state.displayedText !== '' && state.displayedText !== visibleText && isTailSlide;
564
+ displayText = suppressTailSlide ? state.displayedText : visibleText;
565
+ }
566
+ const content = applyScramble(displayText, state, now, 'msg', () => this.poolRandomChar(), this.animationConfig.glitch);
567
+ const isAnimating = this.isLineAnimating(state, now);
568
+ return { label: 'msg:', content, isAnimating };
569
+ }
570
+ // -----------------------------------------------------------------------
571
+ // Value flash helpers (shared by TPS, act KPI, msg KPI)
572
+ // -----------------------------------------------------------------------
573
+ _setupValueFlash(state, value, now) {
574
+ state.glitchQueue = buildGlitchQueue(state.prev, value, GLITCH_SHORT_MAX_START, GLITCH_SHORT_MAX_LENGTH);
575
+ state.startTime = now;
576
+ state.lastGlitchTime = now;
577
+ state.glitchFrame = 0;
578
+ }
579
+ _renderValueFlash(state, value, now) {
580
+ if (state.glitchQueue.length > 0) {
581
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
582
+ if (isGlitchComplete(state.glitchQueue, frame)) {
583
+ state.glitchQueue = [];
584
+ state.prev = value;
585
+ return value;
586
+ }
587
+ return computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar(), value);
588
+ }
589
+ state.prev = value;
590
+ return value;
591
+ }
592
+ _updateValueKpi(map, id, value, now, isComplete, staticLine) {
593
+ if (isComplete) {
594
+ const s = map.get(id);
595
+ if (!s) {
596
+ const newState = createValueFlashState();
597
+ newState.completed = true;
598
+ map.set(id, newState);
599
+ return newState;
600
+ }
601
+ s.completed = true;
602
+ s.glitchQueue = [];
603
+ return s;
604
+ }
605
+ let state = map.get(id);
606
+ const isFirstCall = !state;
607
+ if (!state) {
608
+ state = createValueFlashState();
609
+ state.prev = value;
610
+ state.lastValueChangeTime = now;
611
+ map.set(id, state);
612
+ }
613
+ if (!isComplete && state.completed) {
614
+ state.completed = false;
615
+ state.prev = '';
616
+ state.glitchQueue = [];
617
+ state.startTime = 0;
618
+ state.lastGlitchTime = 0;
619
+ state.lastFlashTime = 0;
620
+ state.glitchFrame = 0;
621
+ }
622
+ if (state.completed)
623
+ return state;
624
+ const cooldownElapsed = now - state.lastFlashTime >= TPS_FLASH_COOLDOWN_MS;
625
+ if (state.prev !== value) {
626
+ let shouldFlash = staticLine ? state.startTime === 0 : true;
627
+ state.lastValueChangeTime = now;
628
+ if (shouldFlash && cooldownElapsed) {
629
+ this._setupValueFlash(state, value, now);
630
+ state.lastFlashTime = now;
631
+ }
632
+ state.prev = value;
633
+ }
634
+ if (isFirstCall && staticLine && state.startTime === 0 && cooldownElapsed) {
635
+ this._setupValueFlash(state, value, now);
636
+ state.lastFlashTime = now;
637
+ }
638
+ return state;
639
+ }
640
+ // -----------------------------------------------------------------------
641
+ // TPS flash
642
+ // -----------------------------------------------------------------------
643
+ updateTps(id, tpsText, now, isComplete = false, staticLine = false) {
644
+ if (!this.animationConfig.enabled)
645
+ return tpsText;
646
+ if (!tpsText || tpsText.trim() === '-')
647
+ return tpsText;
648
+ if (isComplete) {
649
+ const s = this.tpsState.get(id);
650
+ if (!s)
651
+ return tpsText;
652
+ }
653
+ let state = this.tpsState.get(id);
654
+ const isFirstCall = !state;
655
+ if (!state) {
656
+ state = createValueFlashState();
657
+ state.prev = tpsText;
658
+ state.lastValueChangeTime = now;
659
+ this.tpsState.set(id, state);
660
+ }
661
+ if (!isComplete && state.completed) {
662
+ state.completed = false;
663
+ state.prev = '';
664
+ state.glitchQueue = [];
665
+ state.startTime = 0;
666
+ state.lastGlitchTime = 0;
667
+ state.lastFlashTime = 0;
668
+ }
669
+ if (isComplete) {
670
+ state.completed = true;
671
+ state.glitchQueue = [];
672
+ }
673
+ if (state.completed)
674
+ return tpsText;
675
+ const cooldownElapsed = now - state.lastFlashTime >= TPS_FLASH_COOLDOWN_MS;
676
+ if (state.prev !== tpsText) {
677
+ let shouldFlash = staticLine ? state.startTime === 0 : true;
678
+ const prevVal = parseFloat(state.prev);
679
+ const newVal = parseFloat(tpsText);
680
+ if (!isNaN(prevVal) && !isNaN(newVal) && prevVal !== 0) {
681
+ const deltaPct = Math.abs(newVal - prevVal) / prevVal;
682
+ const timeSinceLastChange = state.lastValueChangeTime > 0 ? now - state.lastValueChangeTime : 0;
683
+ shouldFlash = deltaPct > TPS_HYSTERESIS_PCT || timeSinceLastChange > TPS_HYSTERESIS_MS;
684
+ }
685
+ state.lastValueChangeTime = now;
686
+ if (shouldFlash && cooldownElapsed) {
687
+ this._setupValueFlash(state, tpsText, now);
688
+ state.lastFlashTime = now;
689
+ }
690
+ state.prev = tpsText;
691
+ }
692
+ if (isFirstCall && staticLine && state.startTime === 0 && cooldownElapsed) {
693
+ this._setupValueFlash(state, tpsText, now);
694
+ state.lastFlashTime = now;
695
+ }
696
+ return this._renderValueFlash(state, tpsText, now);
697
+ }
698
+ // -----------------------------------------------------------------------
699
+ // Animation status helpers
700
+ // -----------------------------------------------------------------------
701
+ isLineAnimating(state, now) {
702
+ if (state.completed)
703
+ return false;
704
+ if (state.glitchQueue.length > 0) {
705
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
706
+ if (!isGlitchComplete(state.glitchQueue, frame))
707
+ return true;
708
+ }
709
+ if (state.pendingGlitch && state.pendingGlitch.length > 0)
710
+ return true;
711
+ return false;
712
+ }
713
+ hasActiveAnimations(id, now) {
714
+ const prefix = `${id}#`;
715
+ const record = this.cache.get(id);
716
+ if (record) {
717
+ for (const key of ['aim', 'act', 'msg']) {
718
+ if (this.isLineAnimating(record[key], now))
719
+ return true;
720
+ }
721
+ }
722
+ for (const [key, rec] of this.cache) {
723
+ if (key.startsWith(prefix)) {
724
+ for (const lineKey of ['aim', 'act', 'msg']) {
725
+ if (this.isLineAnimating(rec[lineKey], now))
726
+ return true;
727
+ }
728
+ }
729
+ }
730
+ for (const [key, state] of this.genericCache) {
731
+ if (key.startsWith(prefix) && this.isLineAnimating(state, now))
732
+ return true;
733
+ }
734
+ const checkValueState = (map) => {
735
+ const exact = map.get(id);
736
+ if (exact && !exact.completed) {
737
+ if (exact.glitchQueue.length > 0) {
738
+ const frame = Math.floor((now - exact.startTime) / GLITCH_FRAME_MS);
739
+ if (!isGlitchComplete(exact.glitchQueue, frame))
740
+ return true;
741
+ }
742
+ }
743
+ for (const [key, state] of map) {
744
+ if (key.startsWith(prefix) && !state.completed) {
745
+ if (state.glitchQueue.length > 0) {
746
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
747
+ if (!isGlitchComplete(state.glitchQueue, frame))
748
+ return true;
749
+ }
750
+ }
751
+ }
752
+ return false;
753
+ };
754
+ if (checkValueState(this.tpsState))
755
+ return true;
756
+ return false;
757
+ }
758
+ hasAnyActiveAnimations(now) {
759
+ for (const record of this.cache.values()) {
760
+ for (const key of ['aim', 'act', 'msg']) {
761
+ if (this.isLineAnimating(record[key], now))
762
+ return true;
763
+ }
764
+ }
765
+ for (const state of this.tpsState.values()) {
766
+ if (state.completed)
767
+ continue;
768
+ if (state.glitchQueue.length > 0) {
769
+ const frame = Math.floor((now - state.startTime) / GLITCH_FRAME_MS);
770
+ if (!isGlitchComplete(state.glitchQueue, frame))
771
+ return true;
772
+ }
773
+ }
774
+ for (const state of this.genericCache.values()) {
775
+ if (this.isLineAnimating(state, now))
776
+ return true;
777
+ }
778
+ return false;
779
+ }
780
+ clear() {
781
+ this.cache.clear();
782
+ this.tpsState.clear();
783
+ this.genericCache.clear();
784
+ }
785
+ sweepCompletedEntries() {
786
+ if (this.cache.size <= MAX_FLOW_ENTRIES && this.tpsState.size <= MAX_FLOW_ENTRIES && this.genericCache.size <= MAX_FLOW_ENTRIES * 2) {
787
+ return;
788
+ }
789
+ for (const [id, record] of this.cache) {
790
+ if (record.aim.completed && record.act.completed && record.msg.completed) {
791
+ this.cache.delete(id);
792
+ }
793
+ }
794
+ for (const [id, state] of this.tpsState) {
795
+ if (state.completed) {
796
+ this.tpsState.delete(id);
797
+ }
798
+ }
799
+ for (const [key, state] of this.genericCache) {
800
+ if (state.completed) {
801
+ this.genericCache.delete(key);
802
+ }
803
+ }
804
+ const now = Date.now();
805
+ for (const [key, state] of this.genericCache) {
806
+ if (now - state.lastAccessTime > MAX_CACHE_AGE_MS) {
807
+ this.genericCache.delete(key);
808
+ }
809
+ }
810
+ }
811
+ completeFlow(id) {
812
+ clearLiveText(id);
813
+ const record = this.cache.get(id);
814
+ if (record) {
815
+ for (const key of ['aim', 'act', 'msg']) {
816
+ record[key].completed = true;
817
+ record[key].glitchQueue = [];
818
+ record[key].glitchFrame = 0;
819
+ record[key].pendingGlitch = null;
820
+ record[key].pendingOldDisplayed = '';
821
+ record[key].pendingNewDisplayed = '';
822
+ record[key].pendingStartTime = 0;
823
+ record[key].targetText = '';
824
+ }
825
+ }
826
+ const tpsState = this.tpsState.get(id);
827
+ if (tpsState) {
828
+ tpsState.completed = true;
829
+ tpsState.glitchQueue = [];
830
+ tpsState.glitchFrame = 0;
831
+ }
832
+ const prefix = `${id}#`;
833
+ for (const [key, state] of this.genericCache) {
834
+ if (key.startsWith(prefix)) {
835
+ state.completed = true;
836
+ state.glitchQueue = [];
837
+ state.glitchFrame = 0;
838
+ state.pendingGlitch = null;
839
+ state.pendingOldDisplayed = '';
840
+ state.pendingNewDisplayed = '';
841
+ state.pendingStartTime = 0;
842
+ state.targetText = '';
843
+ }
844
+ }
845
+ this.sweepCompletedEntries();
846
+ }
847
+ renderStatic(text) {
848
+ return text;
849
+ }
850
+ }
851
+ // ---------------------------------------------------------------------------
852
+ // Shared animation timer
853
+ // ---------------------------------------------------------------------------
854
+ export function runScrambleTimer(args, id) {
855
+ if (args?.invalidate && args?.state) {
856
+ const s = args.state.__scramble = args.state.__scramble || {};
857
+ const now = Date.now();
858
+ const hasActive = id ? scrambleManager.hasActiveAnimations(id, now) : scrambleManager.hasAnyActiveAnimations(now);
859
+ if (hasActive) {
860
+ if (!s.animTimer) {
861
+ s.animTimer = setTimeout(() => {
862
+ s.animTimer = undefined;
863
+ args.invalidate();
864
+ }, GLITCH_FRAME_MS);
865
+ }
866
+ }
867
+ else if (s.animTimer) {
868
+ clearTimeout(s.animTimer);
869
+ s.animTimer = undefined;
870
+ }
871
+ }
872
+ }
873
+ // ---------------------------------------------------------------------------
874
+ // DynamicScrambleText — TUI component that recomputes scramble on re-render
875
+ // ---------------------------------------------------------------------------
876
+ export class DynamicScrambleText {
877
+ getScrambleContent;
878
+ truncated;
879
+ base;
880
+ constructor(initialContent, getScrambleContent, truncated = false) {
881
+ this.getScrambleContent = getScrambleContent;
882
+ this.truncated = truncated;
883
+ this.base = new Text(initialContent, 0, 0);
884
+ }
885
+ invalidate() { this.base.invalidate(); }
886
+ render(width) {
887
+ const content = this.getScrambleContent();
888
+ const safeContent = content.replace(/[\r\n\t]+/g, ' ');
889
+ this.base.setText(this.truncated ? truncateToWidth(safeContent, width) : safeContent);
890
+ return this.base.render(width);
891
+ }
892
+ }
893
+ /** Standalone setter that delegates to the singleton manager. */
894
+ export function setAnimationConfig(config) {
895
+ scrambleManager.setAnimationConfig(config);
896
+ }
897
+ /** Module-level singleton for use across render calls. */
898
+ export const scrambleManager = new ScrambleStateManager();
899
+ //# sourceMappingURL=manager.js.map