pi-agent-flow 1.8.39 → 2.0.0

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