pi-agent-flow 1.8.38 → 1.8.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/build.md +2 -2
- package/dist/batch/execute.js +4 -4
- package/dist/batch/execute.js.map +1 -1
- package/dist/cli-args.d.ts +1 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +9 -0
- package/dist/cli-args.js.map +1 -1
- package/dist/executor.d.ts +2 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +33 -4
- package/dist/executor.js.map +1 -1
- package/dist/flow-prompt.js +2 -2
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js +104 -16
- package/dist/flow.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/render.js +3 -3
- package/dist/render.js.map +1 -1
- package/dist/runner-events.d.ts +13 -1
- package/dist/runner-events.d.ts.map +1 -1
- package/dist/runner-events.js +15 -3
- package/dist/runner-events.js.map +1 -1
- package/dist/scramble.d.ts +18 -6
- package/dist/scramble.d.ts.map +1 -1
- package/dist/scramble.js +410 -193
- package/dist/scramble.js.map +1 -1
- package/dist/snapshot.d.ts +25 -2
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +214 -37
- package/dist/snapshot.js.map +1 -1
- package/dist/structured-output.d.ts.map +1 -1
- package/dist/structured-output.js +13 -0
- package/dist/structured-output.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -4
package/dist/scramble.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Mode 4 — ILLUMINATE: Neon glow ripple with depth-based esoteric char sets,
|
|
17
17
|
* ANSI truecolor, phrase-chunked msg streaming, and TPS hysteresis.
|
|
18
|
-
* Per-target color configs (
|
|
18
|
+
* Per-target color configs (sky aim, warm act, peach TPS, etc.).
|
|
19
19
|
*
|
|
20
20
|
* Line behavior (all modes):
|
|
21
21
|
* aim: — content stays still, no animation ever
|
|
@@ -70,16 +70,22 @@ export function hashNoise(seed, charIndex, tick, depth) {
|
|
|
70
70
|
// ---------------------------------------------------------------------------
|
|
71
71
|
// Character sets — depth-based esoteric scramble symbols (illuminate mode)
|
|
72
72
|
// ---------------------------------------------------------------------------
|
|
73
|
-
/** Deep glitch: fine dots,
|
|
74
|
-
const DEEP_GLITCH = '
|
|
75
|
-
/** Mid glitch: dots,
|
|
76
|
-
const MID_GLITCH = '
|
|
77
|
-
/** Shallow glitch:
|
|
78
|
-
const SHALLOW_GLITCH = '
|
|
79
|
-
/** Classic
|
|
80
|
-
const SCRAMBLE_CHARS = '
|
|
81
|
-
/**
|
|
82
|
-
const
|
|
73
|
+
/** Deep glitch: fine dots, sparse sparkle, dense braille for inner ripple depths (1–2) */
|
|
74
|
+
const DEEP_GLITCH = '·∘∙*˚。⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓';
|
|
75
|
+
/** Mid glitch: dots, light sparkles, medium braille for depth (3) */
|
|
76
|
+
const MID_GLITCH = '·∘∙~⋆˚。+×◇°⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋';
|
|
77
|
+
/** Shallow glitch: heavy sparkles + light braille for outer depths (4+) — the wavefront crest */
|
|
78
|
+
const SHALLOW_GLITCH = '·∘∙~×°+⠌⠡⠜';
|
|
79
|
+
/** Classic scramble set for stream/cascade/ripple fallback — balanced braille + sparkle mix */
|
|
80
|
+
const SCRAMBLE_CHARS = '·∘∙~⋆˚。+×◇°⠌⠡⠜⠣⠪⠹⠸⠷⠮⠯⠿⠾';
|
|
81
|
+
/** Sparkle and thin braille mix for afterglow "pop" */
|
|
82
|
+
const SPARK_CHARS = '·∘∙⋆˚。⠂⠄⠈⠐⠠⡀⢀⠃⠆⠉⠘⠰⡁⢂';
|
|
83
|
+
/** Backward-compat alias */
|
|
84
|
+
const THIN_BRAILLE_SPARK = SPARK_CHARS;
|
|
85
|
+
const DECORATIVE_ICON_RE = /[✔✅✖❌◐✓]/g;
|
|
86
|
+
function stripDecorativeIcons(text) {
|
|
87
|
+
return text.replace(DECORATIVE_ICON_RE, '');
|
|
88
|
+
}
|
|
83
89
|
function selectScrambleChar(depth, dist, elapsed, seed, textLen) {
|
|
84
90
|
const tickMs = (textLen !== undefined && textLen < 20) ? 300 : 150;
|
|
85
91
|
const tick = Math.floor(elapsed / tickMs);
|
|
@@ -130,38 +136,38 @@ function selectSparkChar(seed, charIndex, tick) {
|
|
|
130
136
|
// ANSI truecolor neon glow constants (illuminate mode)
|
|
131
137
|
// ---------------------------------------------------------------------------
|
|
132
138
|
const CYAN_GLOW = '\x1b[38;2;0;255;204m';
|
|
133
|
-
const WARM_GLOW = '\x1b[38;2;
|
|
134
|
-
const PEACH_GLOW = '\x1b[38;2;
|
|
139
|
+
const WARM_GLOW = '\x1b[38;2;255;140;120m';
|
|
140
|
+
const PEACH_GLOW = '\x1b[38;2;255;160;140m';
|
|
135
141
|
const ORANGE_GLOW = '\x1b[38;2;255;190;130m';
|
|
136
|
-
const SKY_GLOW = '\x1b[38;2;
|
|
142
|
+
const SKY_GLOW = '\x1b[38;2;80;170;255m';
|
|
137
143
|
const WHITE_GLOW = '\x1b[38;2;255;255;255m';
|
|
138
144
|
const RESET_COLOR = '\x1b[39m';
|
|
139
145
|
const BOLD_ON = '\x1b[1m';
|
|
140
146
|
const BOLD_OFF = '\x1b[22m';
|
|
141
147
|
const DIM_ON = '\x1b[2m';
|
|
142
148
|
const DIM_OFF = '\x1b[22m';
|
|
143
|
-
/** Illuminate close:
|
|
144
|
-
*
|
|
149
|
+
/** Illuminate close: resets foreground color only. No bg or bold/dim resets
|
|
150
|
+
* needed — bold is never applied, and enclosing dim context is preserved. */
|
|
145
151
|
const ILLUMINATE_CLOSE = '\x1b[39m';
|
|
146
152
|
const ILLUMINATE_CONFIGS = {
|
|
147
|
-
aimLabel: { color:
|
|
148
|
-
actLabel: { color: WARM_GLOW, duration:
|
|
149
|
-
msgLabel: { color: PEACH_GLOW, duration:
|
|
150
|
-
msgContent: { color: 'dynamic', duration:
|
|
151
|
-
flowMeta: { color: WARM_GLOW, duration:
|
|
152
|
-
tps: { color:
|
|
153
|
+
aimLabel: { color: SKY_GLOW, duration: 360, spread: 1.0, glowIntensity: 'high', crestOnly: false, spark: false },
|
|
154
|
+
actLabel: { color: WARM_GLOW, duration: 360, spread: 1.0, glowIntensity: 'high', crestOnly: false, spark: false },
|
|
155
|
+
msgLabel: { color: PEACH_GLOW, duration: 360, spread: 1.0, glowIntensity: 'high', crestOnly: false, spark: false },
|
|
156
|
+
msgContent: { color: 'dynamic', duration: 600, spread: 1.0, glowIntensity: 'variable', initialTimeOffset: 30, scramble: false },
|
|
157
|
+
flowMeta: { color: WARM_GLOW, duration: 380, spread: 0.8, glowIntensity: 'medium', crestOnly: false, spark: false },
|
|
158
|
+
tps: { color: WARM_GLOW, duration: 84, spread: 0.5, glowIntensity: 'medium', crestOnly: true, spark: false },
|
|
153
159
|
};
|
|
154
160
|
// ---------------------------------------------------------------------------
|
|
155
161
|
// Timing constants
|
|
156
162
|
// ---------------------------------------------------------------------------
|
|
157
|
-
const RIPPLE_DUR_DEFAULT =
|
|
163
|
+
const RIPPLE_DUR_DEFAULT = 520;
|
|
158
164
|
const RIPPLE_SPREAD_DEFAULT = 1;
|
|
159
|
-
const MIN_RIPPLE_INTERVAL =
|
|
165
|
+
const MIN_RIPPLE_INTERVAL = 300;
|
|
160
166
|
const DEPTH_BAND_MAX = 7;
|
|
161
167
|
const TPS_FLASH_DUR = 105;
|
|
162
168
|
const TPS_FLASH_SPREAD = 0.5;
|
|
163
|
-
const AFTERGLOW_MS =
|
|
164
|
-
const ECHO_AFTERGLOW_MS =
|
|
169
|
+
const AFTERGLOW_MS = 420;
|
|
170
|
+
const ECHO_AFTERGLOW_MS = 650;
|
|
165
171
|
const FLASH_AFTERGLOW_MS = 137; // shorter afterglow for TPS/KPI value flashes
|
|
166
172
|
const PULSE_WINDOW_MS = 600;
|
|
167
173
|
const PULSE_CYCLE_MS = 998;
|
|
@@ -176,7 +182,7 @@ const MIN_PHRASE_LENGTH = 60;
|
|
|
176
182
|
// Drain timeout: partial chunk ripples when text stops changing for this long.
|
|
177
183
|
// Tokens arrive ~200ms apart at 196 TPS; 350ms is long enough to avoid firing
|
|
178
184
|
// during active streaming but short enough to feel responsive when tool calls pause.
|
|
179
|
-
const MSG_CHUNK_DRAIN_MS =
|
|
185
|
+
const MSG_CHUNK_DRAIN_MS = 120;
|
|
180
186
|
// Resume gap: after a long pause (e.g. tool call), treat resumed chunks as a
|
|
181
187
|
// fresh stream and force a ripple effect.
|
|
182
188
|
const STREAMING_RESUME_GAP_MS = 2000;
|
|
@@ -192,6 +198,13 @@ const STREAM_SPEED_MSG = 35; // ms per char for msg: (~29 chars/sec)
|
|
|
192
198
|
const STREAM_SPEED_ACT = 25; // ms per char for act: (~40 chars/sec)
|
|
193
199
|
const STREAM_SCRAMBLE_WIDTH = 5; // scramble chars at cursor position
|
|
194
200
|
const STREAM_RERANDOMIZE_RATE = 0.28; // 28% chance to re-randomize (CodePen style)
|
|
201
|
+
const GLITCH_RERANDOMIZE = 0.28;
|
|
202
|
+
const GLITCH_MAX_START = 40;
|
|
203
|
+
const GLITCH_MAX_LENGTH = 40;
|
|
204
|
+
const GLITCH_SHORT_MAX_START = 10;
|
|
205
|
+
const GLITCH_SHORT_MAX_LENGTH = 10;
|
|
206
|
+
const GLITCH_COOLDOWN_MS = 1000;
|
|
207
|
+
const GLITCH_FADE_OUT_FRAMES = 18;
|
|
195
208
|
// ---------------------------------------------------------------------------
|
|
196
209
|
// Easing and interpolation helpers
|
|
197
210
|
// ---------------------------------------------------------------------------
|
|
@@ -275,7 +288,13 @@ function shouldFlushPhrase(text, displayed, lastFlushTime, now) {
|
|
|
275
288
|
newContent = text;
|
|
276
289
|
}
|
|
277
290
|
const boundaryPos = findPhraseBoundary(newContent);
|
|
278
|
-
|
|
291
|
+
if (boundaryPos >= 0)
|
|
292
|
+
return true;
|
|
293
|
+
// Force flush: if enough new content accumulated, flush regardless of boundary
|
|
294
|
+
const newContentLen = text.startsWith(displayed) ? text.length - displayed.length : text.length;
|
|
295
|
+
if (newContentLen >= 40)
|
|
296
|
+
return true;
|
|
297
|
+
return false;
|
|
279
298
|
}
|
|
280
299
|
// ---------------------------------------------------------------------------
|
|
281
300
|
// Pure helpers
|
|
@@ -381,7 +400,9 @@ export function renderStreamText(visibleText, visibleRevealed, scrambleWidth, cu
|
|
|
381
400
|
// ---------------------------------------------------------------------------
|
|
382
401
|
export function buildQueue(oldText, newText, maxStart = CASCADE_MAX_START, maxLength = CASCADE_MAX_LENGTH, rng) {
|
|
383
402
|
const queue = [];
|
|
384
|
-
const
|
|
403
|
+
const cleanOld = stripDecorativeIcons(oldText);
|
|
404
|
+
const cleanNew = stripDecorativeIcons(newText);
|
|
405
|
+
const length = Math.max(cleanOld.length, cleanNew.length);
|
|
385
406
|
const useRng = rng ?? new FastRNG(makeAnimationSeed(newText, Date.now()));
|
|
386
407
|
for (let i = 0; i < length; i++) {
|
|
387
408
|
const from = oldText[i] || '';
|
|
@@ -446,6 +467,79 @@ export function computeCascadeFrame(queue, frame, rng) {
|
|
|
446
467
|
result += DIM_OFF;
|
|
447
468
|
return result;
|
|
448
469
|
}
|
|
470
|
+
// ---------------------------------------------------------------------------
|
|
471
|
+
// Pure algorithm: GLITCH (TextScramble faithful port with Unicode braille)
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
export function buildGlitchQueue(oldText, newText, maxStart = GLITCH_MAX_START, maxLength = GLITCH_MAX_LENGTH) {
|
|
474
|
+
const queue = [];
|
|
475
|
+
const cleanOld = stripDecorativeIcons(oldText);
|
|
476
|
+
const cleanNew = stripDecorativeIcons(newText);
|
|
477
|
+
const length = Math.max(cleanOld.length, cleanNew.length);
|
|
478
|
+
for (let i = 0; i < length; i++) {
|
|
479
|
+
const from = cleanOld[i] || '';
|
|
480
|
+
const to = cleanNew[i] || '';
|
|
481
|
+
const start = Math.floor(Math.random() * maxStart);
|
|
482
|
+
const end = start + Math.floor(Math.random() * maxLength);
|
|
483
|
+
const fadeOutEnd = to === '' ? end + GLITCH_FADE_OUT_FRAMES : undefined;
|
|
484
|
+
queue.push({ from, to, start, end, fadeOutEnd, char: null });
|
|
485
|
+
}
|
|
486
|
+
return queue;
|
|
487
|
+
}
|
|
488
|
+
export function computeGlitchFrame(queue, frame, rng) {
|
|
489
|
+
let output = '';
|
|
490
|
+
let inDim = false;
|
|
491
|
+
for (let i = 0; i < queue.length; i++) {
|
|
492
|
+
const entry = queue[i];
|
|
493
|
+
const fadeOutEnd = entry.fadeOutEnd;
|
|
494
|
+
if (fadeOutEnd !== undefined && frame >= entry.end && frame < fadeOutEnd) {
|
|
495
|
+
if (!inDim) {
|
|
496
|
+
output += DIM_ON;
|
|
497
|
+
inDim = true;
|
|
498
|
+
}
|
|
499
|
+
if (!entry.char || Math.random() < GLITCH_RERANDOMIZE) {
|
|
500
|
+
entry.char = rng();
|
|
501
|
+
}
|
|
502
|
+
output += entry.char;
|
|
503
|
+
}
|
|
504
|
+
else if (frame >= (fadeOutEnd ?? entry.end)) {
|
|
505
|
+
if (inDim) {
|
|
506
|
+
output += DIM_OFF;
|
|
507
|
+
inDim = false;
|
|
508
|
+
}
|
|
509
|
+
output += entry.to;
|
|
510
|
+
}
|
|
511
|
+
else if (frame >= entry.start) {
|
|
512
|
+
if (inDim) {
|
|
513
|
+
output += DIM_OFF;
|
|
514
|
+
inDim = false;
|
|
515
|
+
}
|
|
516
|
+
if (!entry.char || Math.random() < GLITCH_RERANDOMIZE) {
|
|
517
|
+
entry.char = rng();
|
|
518
|
+
}
|
|
519
|
+
output += entry.char;
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
if (inDim) {
|
|
523
|
+
output += DIM_OFF;
|
|
524
|
+
inDim = false;
|
|
525
|
+
}
|
|
526
|
+
output += entry.from;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (inDim)
|
|
530
|
+
output += DIM_OFF;
|
|
531
|
+
return output;
|
|
532
|
+
}
|
|
533
|
+
export function isGlitchComplete(queue, frame) {
|
|
534
|
+
if (queue.length === 0)
|
|
535
|
+
return true;
|
|
536
|
+
return frame >= Math.max(...queue.map(e => e.fadeOutEnd ?? e.end));
|
|
537
|
+
}
|
|
538
|
+
function shouldStartGlitch(state, now, cooldownMs) {
|
|
539
|
+
if (state.glitchQueue.length > 0)
|
|
540
|
+
return false; // already animating
|
|
541
|
+
return now - state.lastGlitchTime >= cooldownMs;
|
|
542
|
+
}
|
|
449
543
|
function isCascadeComplete(queue, frame, maxEnd) {
|
|
450
544
|
const clampedFrame = Math.max(0, frame);
|
|
451
545
|
if (maxEnd !== undefined)
|
|
@@ -467,68 +561,47 @@ function illuminatePrefix(depth, elapsed, dur, config, combinedDepth) {
|
|
|
467
561
|
const heat = Math.min(1, depth / DEPTH_BAND_MAX);
|
|
468
562
|
const life = 1 - progress;
|
|
469
563
|
const intensity = heat * life * (1 - 0.25 * heat);
|
|
470
|
-
//
|
|
471
|
-
// with smooth sub-zone blends for liquid, band-free transitions
|
|
564
|
+
// 5-zone continuous truecolor gradient: deep sky → bright sky → sky-peach bridge → vivid peach → rich salmon → warm white peak
|
|
472
565
|
let r, g, b;
|
|
473
|
-
if (intensity < 0.
|
|
474
|
-
const t = smoothstep(0, 0.
|
|
475
|
-
r = lerp(0,
|
|
476
|
-
g = lerp(
|
|
477
|
-
b = lerp(
|
|
478
|
-
}
|
|
479
|
-
else if (intensity < 0.
|
|
480
|
-
const t = smoothstep(0.
|
|
481
|
-
r = lerp(
|
|
482
|
-
g = lerp(
|
|
483
|
-
b = lerp(
|
|
484
|
-
}
|
|
485
|
-
else if (intensity < 0.
|
|
486
|
-
const t = smoothstep(0.
|
|
487
|
-
r = lerp(
|
|
488
|
-
g = lerp(
|
|
489
|
-
b = lerp(
|
|
490
|
-
}
|
|
491
|
-
else if (intensity < 0.
|
|
492
|
-
const t = smoothstep(0.
|
|
493
|
-
r = lerp(
|
|
494
|
-
g = lerp(
|
|
495
|
-
b = lerp(
|
|
496
|
-
}
|
|
497
|
-
else if (intensity < 0.74) {
|
|
498
|
-
const t = smoothstep(0.58, 0.74, intensity);
|
|
499
|
-
r = lerp(250, 200, t);
|
|
500
|
-
g = lerp(190, 200, t);
|
|
501
|
-
b = lerp(183, 210, t);
|
|
502
|
-
}
|
|
503
|
-
else if (intensity < 0.88) {
|
|
504
|
-
const t = smoothstep(0.74, 0.88, intensity);
|
|
505
|
-
r = lerp(200, 152, t);
|
|
506
|
-
g = lerp(200, 203, t);
|
|
507
|
-
b = lerp(210, 250, t);
|
|
508
|
-
}
|
|
509
|
-
else if (intensity < 0.96) {
|
|
510
|
-
const t = smoothstep(0.88, 0.96, intensity);
|
|
511
|
-
r = lerp(152, 0, t);
|
|
512
|
-
g = lerp(203, 230, t);
|
|
513
|
-
b = lerp(250, 220, t);
|
|
566
|
+
if (intensity < 0.20) {
|
|
567
|
+
const t = smoothstep(0, 0.20, intensity);
|
|
568
|
+
r = lerp(0, 80, t);
|
|
569
|
+
g = lerp(80, 170, t);
|
|
570
|
+
b = lerp(255, 255, t);
|
|
571
|
+
}
|
|
572
|
+
else if (intensity < 0.40) {
|
|
573
|
+
const t = smoothstep(0.20, 0.40, intensity);
|
|
574
|
+
r = lerp(80, 180, t);
|
|
575
|
+
g = lerp(170, 170, t);
|
|
576
|
+
b = lerp(255, 210, t);
|
|
577
|
+
}
|
|
578
|
+
else if (intensity < 0.60) {
|
|
579
|
+
const t = smoothstep(0.40, 0.60, intensity);
|
|
580
|
+
r = lerp(180, 255, t);
|
|
581
|
+
g = lerp(170, 140, t);
|
|
582
|
+
b = lerp(210, 120, t);
|
|
583
|
+
}
|
|
584
|
+
else if (intensity < 0.80) {
|
|
585
|
+
const t = smoothstep(0.60, 0.80, intensity);
|
|
586
|
+
r = lerp(255, 255, t);
|
|
587
|
+
g = lerp(140, 90, t);
|
|
588
|
+
b = lerp(120, 70, t);
|
|
514
589
|
}
|
|
515
590
|
else {
|
|
516
|
-
const t = smoothstep(0.
|
|
517
|
-
r = lerp(
|
|
518
|
-
g = lerp(
|
|
519
|
-
b = lerp(
|
|
591
|
+
const t = smoothstep(0.80, 1.0, intensity);
|
|
592
|
+
r = lerp(255, 255, t);
|
|
593
|
+
g = lerp(90, 240, t);
|
|
594
|
+
b = lerp(70, 230, t);
|
|
520
595
|
}
|
|
521
|
-
// Interference boost: overlapping ripples
|
|
596
|
+
// Interference boost: overlapping ripples warm-white flash
|
|
522
597
|
const effectiveCombined = combinedDepth ?? depth;
|
|
523
598
|
const interferenceBoost = Math.max(0, (effectiveCombined - DEPTH_BAND_MAX * 0.6) / DEPTH_BAND_MAX);
|
|
524
599
|
if (interferenceBoost > 0) {
|
|
525
|
-
|
|
526
|
-
const targetR = 50, targetG = 255, targetB = 255;
|
|
600
|
+
const targetR = 255, targetG = 245, targetB = 240;
|
|
527
601
|
r = Math.min(255, Math.max(0, Math.round(r + interferenceBoost * (targetR - r))));
|
|
528
602
|
g = Math.min(255, Math.max(0, Math.round(g + interferenceBoost * (targetG - g))));
|
|
529
603
|
b = Math.min(255, Math.max(0, Math.round(b + interferenceBoost * (targetB - b))));
|
|
530
604
|
}
|
|
531
|
-
// No DIM/BOLD — truecolor RGB handles brightness smoothly
|
|
532
605
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
533
606
|
}
|
|
534
607
|
return config.color;
|
|
@@ -628,7 +701,7 @@ export function applyRipples(text, ripples, now, config, targetText, resolvedMas
|
|
|
628
701
|
const jitterTick = Math.floor(now / 42);
|
|
629
702
|
const depthJitter = (hashNoise(seed, bestDist, jitterTick, 99) * 2 - 1) * 0.15;
|
|
630
703
|
const jitteredDepth = Math.max(0.1, maxDepth + depthJitter);
|
|
631
|
-
const char = selectScrambleChar(jitteredDepth, bestDist, bestElapsed, seed, text.length);
|
|
704
|
+
const char = (config?.scramble === false) ? origChar : selectScrambleChar(jitteredDepth, bestDist, bestElapsed, seed, text.length);
|
|
632
705
|
if (config) {
|
|
633
706
|
const crestDepth = radii[bestIdx] - bestDist;
|
|
634
707
|
const isCrest = !config.crestOnly || (crestDepth > 0 && crestDepth < 2.0);
|
|
@@ -636,13 +709,12 @@ export function applyRipples(text, ripples, now, config, targetText, resolvedMas
|
|
|
636
709
|
if (isCrest) {
|
|
637
710
|
prefix = illuminatePrefix(maxDepth, bestElapsed, bestDur, config, combinedDepth);
|
|
638
711
|
if (config.color === 'dynamic' && crestDepth > 0 && crestDepth < 1.5) {
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
712
|
+
// Gradient peak: vivid salmon → warm white
|
|
713
|
+
const t = Math.min(1, crestDepth / 1.5);
|
|
714
|
+
const cr = Math.round(lerp(255, 255, t));
|
|
715
|
+
const cg = Math.round(lerp(90, 240, t));
|
|
716
|
+
const cb = Math.round(lerp(70, 230, t));
|
|
717
|
+
prefix = `\x1b[38;2;${cr};${cg};${cb}m`;
|
|
646
718
|
}
|
|
647
719
|
}
|
|
648
720
|
if (prefix) {
|
|
@@ -683,7 +755,7 @@ export function applyRipples(text, ripples, now, config, targetText, resolvedMas
|
|
|
683
755
|
const glitchRoll = bestAgIdx >= 0 ? hashNoise(agRipple.seed ?? 0, idx, agTick, 77) : 1;
|
|
684
756
|
const popTarget = Math.min(0.045, 4 / Math.max(1, text.length));
|
|
685
757
|
const shouldScramble = inInitialPopWindow && bestAgIdx >= 0 && afterglowRipples[bestAgIdx].dur >= 210 && glitchRoll < popTarget;
|
|
686
|
-
if (shouldScramble) {
|
|
758
|
+
if (shouldScramble && config?.scramble !== false) {
|
|
687
759
|
if (config) {
|
|
688
760
|
let agPrefix;
|
|
689
761
|
if (config.color === 'dynamic') {
|
|
@@ -691,8 +763,8 @@ export function applyRipples(text, ripples, now, config, targetText, resolvedMas
|
|
|
691
763
|
// Echo pops get minimum intensity so chars stay visible long after ripple
|
|
692
764
|
const effectiveIntensity = afterglowIntensity;
|
|
693
765
|
const emberR = Math.round(200 + 55 * effectiveIntensity);
|
|
694
|
-
const emberG = Math.round(
|
|
695
|
-
const emberB = Math.round(
|
|
766
|
+
const emberG = Math.round(130 + 80 * effectiveIntensity);
|
|
767
|
+
const emberB = Math.round(140 + 70 * effectiveIntensity);
|
|
696
768
|
agPrefix = `\x1b[38;2;${emberR};${emberG};${emberB}m`;
|
|
697
769
|
}
|
|
698
770
|
else {
|
|
@@ -735,8 +807,8 @@ export function applyRipples(text, ripples, now, config, targetText, resolvedMas
|
|
|
735
807
|
const settleRoll = hashNoise(42, idx, settleTick, 33);
|
|
736
808
|
if (settleRoll < 0.05) {
|
|
737
809
|
const settlePrefix = (hashNoise(42, idx, settleTick, 55) < 0.5)
|
|
738
|
-
? '\x1b[38;2;
|
|
739
|
-
: '\x1b[38;2;
|
|
810
|
+
? '\x1b[38;2;80;170;255m' // sky
|
|
811
|
+
: '\x1b[38;2;255;140;120m'; // warm
|
|
740
812
|
if (!inColor || currentPrefix !== settlePrefix) {
|
|
741
813
|
if (inColor)
|
|
742
814
|
segments[segCount++] = ILLUMINATE_CLOSE;
|
|
@@ -764,9 +836,9 @@ function spawnIlluminateRipple(pos, now, config, seed, contentChange) {
|
|
|
764
836
|
}
|
|
765
837
|
function getRippleDuration(textLength, baseDur = RIPPLE_DUR_DEFAULT) {
|
|
766
838
|
if (textLength <= 5)
|
|
767
|
-
return Math.max(baseDur,
|
|
839
|
+
return Math.max(baseDur, 950);
|
|
768
840
|
if (textLength <= 10)
|
|
769
|
-
return Math.max(baseDur,
|
|
841
|
+
return Math.max(baseDur, 850);
|
|
770
842
|
return baseDur;
|
|
771
843
|
}
|
|
772
844
|
function spawnSecondaryRipple(primary) {
|
|
@@ -791,34 +863,20 @@ function spawnIlluminateRippleForText(pos, now, config, textLength, seed, conten
|
|
|
791
863
|
const primary = spawnIlluminateRipple(pos, now, { ...config, duration: dur }, seed, contentChange);
|
|
792
864
|
return [primary, spawnSecondaryRipple(primary)];
|
|
793
865
|
}
|
|
794
|
-
function spawnTpsRipples(pos, now) {
|
|
795
|
-
// TPS flash is intentionally brief — no secondary ripple
|
|
796
|
-
return [spawnRipple(pos, now, TPS_FLASH_DUR, TPS_FLASH_SPREAD)];
|
|
797
|
-
}
|
|
798
|
-
function spawnTpsIlluminateRipples(pos, now) {
|
|
799
|
-
// TPS flash is intentionally brief — no secondary ripple
|
|
800
|
-
return [spawnIlluminateRipple(pos, now, ILLUMINATE_CONFIGS.tps)];
|
|
801
|
-
}
|
|
802
866
|
/**
|
|
803
867
|
* Compute a ripple spawn center with random jitter.
|
|
804
|
-
* The position is
|
|
805
|
-
*
|
|
868
|
+
* The position is chosen uniformly between 20% and 80% of the text
|
|
869
|
+
* length (or the center for very short strings), giving a varied
|
|
870
|
+
* but never edge-clamped ripple origin.
|
|
806
871
|
*/
|
|
807
872
|
function randomizedCenter(length, jitterRatio, rng) {
|
|
808
|
-
const
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
const maxJitter = Math.max(0, Math.min(rawJitter, base - 1, length - base - 2));
|
|
816
|
-
if (maxJitter <= 0)
|
|
817
|
-
return base;
|
|
818
|
-
const offset = rng
|
|
819
|
-
? rng.nextInt(maxJitter * 2 + 1) - maxJitter
|
|
820
|
-
: Math.floor(Math.random() * (maxJitter * 2 + 1)) - maxJitter;
|
|
821
|
-
return base + offset;
|
|
873
|
+
const min = Math.max(0, Math.floor(length * 0.2));
|
|
874
|
+
const max = Math.min(length - 1, Math.floor(length * 0.8));
|
|
875
|
+
if (max <= min)
|
|
876
|
+
return Math.floor(length / 2);
|
|
877
|
+
const range = max - min + 1;
|
|
878
|
+
const offset = rng ? rng.nextInt(range) : Math.floor(Math.random() * range);
|
|
879
|
+
return min + offset;
|
|
822
880
|
}
|
|
823
881
|
/**
|
|
824
882
|
* Find sentence-start character positions in text.
|
|
@@ -916,6 +974,15 @@ function applyScramble(text, state, now, mode, lineKey, rng) {
|
|
|
916
974
|
return computeCascadeFrame(state.queue, frame, rng);
|
|
917
975
|
}
|
|
918
976
|
else if (mode === 'illuminate') {
|
|
977
|
+
if (state.glitchQueue.length > 0) {
|
|
978
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
979
|
+
if (isGlitchComplete(state.glitchQueue, frame)) {
|
|
980
|
+
state.glitchQueue = [];
|
|
981
|
+
state.glitchFrame = 0;
|
|
982
|
+
return text;
|
|
983
|
+
}
|
|
984
|
+
return computeGlitchFrame(state.glitchQueue, frame, rng ?? poolRandomChar);
|
|
985
|
+
}
|
|
919
986
|
const config = lineKey === 'msg'
|
|
920
987
|
? ILLUMINATE_CONFIGS.msgContent
|
|
921
988
|
: lineKey === 'act'
|
|
@@ -965,74 +1032,111 @@ function processLine(state, newText, now, mode, lineKey) {
|
|
|
965
1032
|
state.ripples.length = keep;
|
|
966
1033
|
const hasActiveRipples = state.ripples.some(r => now - r.time < r.dur);
|
|
967
1034
|
const gap = now - state.lastTextChangeTime;
|
|
1035
|
+
const glitchCooledDown = now - state.lastGlitchTime >= GLITCH_COOLDOWN_MS;
|
|
1036
|
+
const previousText = state.lastText;
|
|
968
1037
|
if (textChanged) {
|
|
1038
|
+
const delta = Math.max(0, newText.length - state.lastText.length);
|
|
969
1039
|
state.lastText = newText;
|
|
970
1040
|
state.phraseBuffer = newText;
|
|
971
1041
|
state.lastTextChangeTime = now;
|
|
972
|
-
|
|
973
|
-
// Between ripples, displayedText stays as last rippled text for chunk detection
|
|
1042
|
+
state.charsSinceLastFlush += delta;
|
|
974
1043
|
}
|
|
975
|
-
//
|
|
976
|
-
if (
|
|
1044
|
+
// F1: accumulator — periodic ripples during dense streaming
|
|
1045
|
+
if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && state.charsSinceLastFlush >= 20 && newText !== state.displayedText) {
|
|
1046
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
977
1047
|
state.displayedText = newText;
|
|
978
1048
|
state.lastFlushTime = now;
|
|
979
1049
|
state.lastAnimTime = now;
|
|
980
|
-
state.
|
|
1050
|
+
state.charsSinceLastFlush = 0;
|
|
1051
|
+
state.ripples = [];
|
|
1052
|
+
if (glitchCooledDown) {
|
|
1053
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, newText);
|
|
1054
|
+
state.startTime = now;
|
|
1055
|
+
state.glitchFrame = 0;
|
|
1056
|
+
state.lastGlitchTime = now;
|
|
1057
|
+
}
|
|
981
1058
|
}
|
|
982
|
-
else if (
|
|
1059
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && shouldFlushPhrase(newText, state.displayedText, state.lastFlushTime, now)) {
|
|
1060
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
1061
|
+
state.displayedText = newText;
|
|
1062
|
+
state.lastFlushTime = now;
|
|
1063
|
+
state.lastAnimTime = now;
|
|
1064
|
+
state.charsSinceLastFlush = 0;
|
|
1065
|
+
state.ripples = [];
|
|
1066
|
+
if (glitchCooledDown) {
|
|
1067
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, newText);
|
|
1068
|
+
state.startTime = now;
|
|
1069
|
+
state.glitchFrame = 0;
|
|
1070
|
+
state.lastGlitchTime = now;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && newText !== state.displayedText && now - state.lastTextChangeTime > MSG_CHUNK_DRAIN_MS) {
|
|
983
1074
|
// Drain: text stopped arriving and we have unrippled content —
|
|
984
|
-
//
|
|
1075
|
+
// glitch it out so it doesn't sit plain indefinitely.
|
|
1076
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
985
1077
|
state.displayedText = newText;
|
|
986
1078
|
state.lastFlushTime = now;
|
|
987
1079
|
state.lastAnimTime = now;
|
|
988
|
-
state.
|
|
1080
|
+
state.charsSinceLastFlush = 0;
|
|
1081
|
+
state.ripples = [];
|
|
1082
|
+
if (glitchCooledDown) {
|
|
1083
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, newText);
|
|
1084
|
+
state.startTime = now;
|
|
1085
|
+
state.glitchFrame = 0;
|
|
1086
|
+
state.lastGlitchTime = now;
|
|
1087
|
+
}
|
|
989
1088
|
}
|
|
990
|
-
else if (
|
|
1089
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && newText !== state.displayedText && gap > STREAMING_RESUME_GAP_MS) {
|
|
991
1090
|
// Streaming resumed after a long pause (e.g., tool call) —
|
|
992
|
-
// force a fresh
|
|
1091
|
+
// force a fresh glitch on the accumulated content.
|
|
1092
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
993
1093
|
state.displayedText = newText;
|
|
994
1094
|
state.lastFlushTime = now;
|
|
995
1095
|
state.lastAnimTime = now;
|
|
996
|
-
state.
|
|
1096
|
+
state.charsSinceLastFlush = 0;
|
|
1097
|
+
state.ripples = [];
|
|
1098
|
+
if (glitchCooledDown) {
|
|
1099
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, newText);
|
|
1100
|
+
state.startTime = now;
|
|
1101
|
+
state.glitchFrame = 0;
|
|
1102
|
+
state.lastGlitchTime = now;
|
|
1103
|
+
}
|
|
997
1104
|
}
|
|
998
1105
|
return;
|
|
999
1106
|
}
|
|
1000
|
-
// act: and aim: —
|
|
1107
|
+
// act: and aim: — glitch animation
|
|
1001
1108
|
if (state.lastText === newText) {
|
|
1002
1109
|
return;
|
|
1003
1110
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1111
|
+
// Clear completed glitch queue so we can start a new one
|
|
1112
|
+
if (state.glitchQueue.length > 0) {
|
|
1113
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
1114
|
+
if (isGlitchComplete(state.glitchQueue, frame)) {
|
|
1115
|
+
state.glitchQueue = [];
|
|
1116
|
+
state.glitchFrame = 0;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
if (state.glitchQueue.length > 0) {
|
|
1010
1120
|
state.lastText = newText;
|
|
1011
1121
|
return;
|
|
1012
1122
|
}
|
|
1123
|
+
const hadRipples = state.ripples.length > 0;
|
|
1124
|
+
state.ripples = state.ripples.filter(r => now - r.time < r.dur + (r.contentChange ? ECHO_AFTERGLOW_MS : AFTERGLOW_MS));
|
|
1013
1125
|
const cooledDown = now - state.lastAnimTime >= MIN_RIPPLE_INTERVAL;
|
|
1014
|
-
if (!cooledDown && !
|
|
1126
|
+
if (!cooledDown && !hadRipples) {
|
|
1015
1127
|
state.lastText = newText;
|
|
1016
1128
|
return;
|
|
1017
1129
|
}
|
|
1130
|
+
const oldDisplayed = state.displayedText;
|
|
1018
1131
|
state.displayedText = newText;
|
|
1019
1132
|
state.lastText = newText;
|
|
1020
1133
|
state.lastFlushTime = now;
|
|
1021
1134
|
state.lastAnimTime = now;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
state.ripples.push(...spawnRippleForText(randomizedCenter(newText.length), now, newText.length, undefined, false));
|
|
1028
|
-
}
|
|
1029
|
-
let keep = 0;
|
|
1030
|
-
for (let i = 0; i < state.ripples.length; i++) {
|
|
1031
|
-
if (now - state.ripples[i].time < state.ripples[i].dur + (state.ripples[i].contentChange ? ECHO_AFTERGLOW_MS : AFTERGLOW_MS)) {
|
|
1032
|
-
state.ripples[keep++] = state.ripples[i];
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
state.ripples.length = keep;
|
|
1135
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed || '', newText);
|
|
1136
|
+
state.startTime = now;
|
|
1137
|
+
state.glitchFrame = 0;
|
|
1138
|
+
state.lastGlitchTime = now;
|
|
1139
|
+
state.ripples = [];
|
|
1036
1140
|
return;
|
|
1037
1141
|
}
|
|
1038
1142
|
// Standard modes (stream/cascade/ripple)
|
|
@@ -1110,10 +1214,14 @@ function createLineState() {
|
|
|
1110
1214
|
lastAccessTime: Date.now(),
|
|
1111
1215
|
lastTextChangeTime: 0,
|
|
1112
1216
|
lastRippleEndTime: 0,
|
|
1217
|
+
charsSinceLastFlush: 0,
|
|
1218
|
+
glitchQueue: [],
|
|
1219
|
+
glitchFrame: 0,
|
|
1220
|
+
lastGlitchTime: 0,
|
|
1113
1221
|
};
|
|
1114
1222
|
}
|
|
1115
1223
|
function createValueFlashState() {
|
|
1116
|
-
return { prev: '', ripples: [], queue: [], queueMaxEnd: 0, startTime: 0, lastValueChangeTime: 0, lastFlashTime: 0, completed: false, lastRippleEndTime: 0 };
|
|
1224
|
+
return { prev: '', ripples: [], queue: [], queueMaxEnd: 0, startTime: 0, lastValueChangeTime: 0, lastFlashTime: 0, completed: false, lastRippleEndTime: 0, glitchQueue: [], glitchFrame: 0, lastGlitchTime: 0 };
|
|
1117
1225
|
}
|
|
1118
1226
|
function createTypewriterState(speed) {
|
|
1119
1227
|
return {
|
|
@@ -1262,11 +1370,16 @@ export class ScrambleStateManager {
|
|
|
1262
1370
|
state.pendingText = '';
|
|
1263
1371
|
state.lastFlushTime = 0;
|
|
1264
1372
|
state.lastRippleEndTime = 0;
|
|
1373
|
+
state.charsSinceLastFlush = 0;
|
|
1374
|
+
state.glitchQueue = [];
|
|
1375
|
+
state.glitchFrame = 0;
|
|
1265
1376
|
}
|
|
1266
1377
|
if (isComplete) {
|
|
1267
1378
|
state.completed = true;
|
|
1268
1379
|
state.queue = [];
|
|
1269
1380
|
state.ripples = [];
|
|
1381
|
+
state.glitchQueue = [];
|
|
1382
|
+
state.glitchFrame = 0;
|
|
1270
1383
|
}
|
|
1271
1384
|
if (state.completed)
|
|
1272
1385
|
return { label: key, content: text, isAnimating: false };
|
|
@@ -1281,8 +1394,10 @@ export class ScrambleStateManager {
|
|
|
1281
1394
|
state.queueMaxEnd = state.queue.reduce((max, item) => Math.max(max, item.end), 0);
|
|
1282
1395
|
}
|
|
1283
1396
|
else if (this.mode === 'illuminate') {
|
|
1284
|
-
|
|
1285
|
-
state.
|
|
1397
|
+
state.glitchQueue = buildGlitchQueue('', text);
|
|
1398
|
+
state.startTime = now;
|
|
1399
|
+
state.lastGlitchTime = now;
|
|
1400
|
+
state.glitchFrame = 0;
|
|
1286
1401
|
}
|
|
1287
1402
|
else {
|
|
1288
1403
|
state.ripples.push(...spawnRippleForText(randomizedCenter(text.length), now, text.length, undefined, true));
|
|
@@ -1309,8 +1424,10 @@ export class ScrambleStateManager {
|
|
|
1309
1424
|
}
|
|
1310
1425
|
else if (this.mode === 'illuminate') {
|
|
1311
1426
|
state.ripples = [];
|
|
1312
|
-
|
|
1313
|
-
state.
|
|
1427
|
+
state.glitchQueue = buildGlitchQueue(state.displayedText || '', text);
|
|
1428
|
+
state.startTime = now;
|
|
1429
|
+
state.lastGlitchTime = now;
|
|
1430
|
+
state.glitchFrame = 0;
|
|
1314
1431
|
}
|
|
1315
1432
|
else {
|
|
1316
1433
|
state.ripples = [];
|
|
@@ -1348,11 +1465,16 @@ export class ScrambleStateManager {
|
|
|
1348
1465
|
state.pendingText = '';
|
|
1349
1466
|
state.lastFlushTime = 0;
|
|
1350
1467
|
state.lastRippleEndTime = 0;
|
|
1468
|
+
state.charsSinceLastFlush = 0;
|
|
1469
|
+
state.glitchQueue = [];
|
|
1470
|
+
state.glitchFrame = 0;
|
|
1351
1471
|
}
|
|
1352
1472
|
if (isComplete) {
|
|
1353
1473
|
state.completed = true;
|
|
1354
1474
|
state.queue = [];
|
|
1355
1475
|
state.ripples = [];
|
|
1476
|
+
state.glitchQueue = [];
|
|
1477
|
+
state.glitchFrame = 0;
|
|
1356
1478
|
}
|
|
1357
1479
|
if (state.completed)
|
|
1358
1480
|
return { label: 'aim:', content: text, isAnimating: false };
|
|
@@ -1371,7 +1493,10 @@ export class ScrambleStateManager {
|
|
|
1371
1493
|
state.queueMaxEnd = state.queue.reduce((max, item) => Math.max(max, item.end), 0);
|
|
1372
1494
|
}
|
|
1373
1495
|
else if (this.mode === 'illuminate') {
|
|
1374
|
-
state.
|
|
1496
|
+
state.glitchQueue = buildGlitchQueue('', text);
|
|
1497
|
+
state.startTime = now;
|
|
1498
|
+
state.lastGlitchTime = now;
|
|
1499
|
+
state.glitchFrame = 0;
|
|
1375
1500
|
}
|
|
1376
1501
|
else {
|
|
1377
1502
|
state.ripples.push(...spawnRippleForText(randomizedCenter(text.length), now, text.length, undefined, false));
|
|
@@ -1398,7 +1523,10 @@ export class ScrambleStateManager {
|
|
|
1398
1523
|
}
|
|
1399
1524
|
else if (this.mode === 'illuminate') {
|
|
1400
1525
|
state.ripples = [];
|
|
1401
|
-
state.
|
|
1526
|
+
state.glitchQueue = buildGlitchQueue(state.displayedText || '', text);
|
|
1527
|
+
state.startTime = now;
|
|
1528
|
+
state.lastGlitchTime = now;
|
|
1529
|
+
state.glitchFrame = 0;
|
|
1402
1530
|
}
|
|
1403
1531
|
else {
|
|
1404
1532
|
state.ripples = [];
|
|
@@ -1409,6 +1537,8 @@ export class ScrambleStateManager {
|
|
|
1409
1537
|
else if (!this.isLineAnimating(state, now)) {
|
|
1410
1538
|
state.queue = [];
|
|
1411
1539
|
state.ripples = [];
|
|
1540
|
+
state.glitchQueue = [];
|
|
1541
|
+
state.glitchFrame = 0;
|
|
1412
1542
|
}
|
|
1413
1543
|
}
|
|
1414
1544
|
else {
|
|
@@ -1440,11 +1570,16 @@ export class ScrambleStateManager {
|
|
|
1440
1570
|
state.pendingText = '';
|
|
1441
1571
|
state.lastFlushTime = 0;
|
|
1442
1572
|
state.lastRippleEndTime = 0;
|
|
1573
|
+
state.charsSinceLastFlush = 0;
|
|
1574
|
+
state.glitchQueue = [];
|
|
1575
|
+
state.glitchFrame = 0;
|
|
1443
1576
|
}
|
|
1444
1577
|
if (isComplete) {
|
|
1445
1578
|
state.completed = true;
|
|
1446
1579
|
state.queue = [];
|
|
1447
1580
|
state.ripples = [];
|
|
1581
|
+
state.glitchQueue = [];
|
|
1582
|
+
state.glitchFrame = 0;
|
|
1448
1583
|
}
|
|
1449
1584
|
if (state.completed)
|
|
1450
1585
|
return { label: 'act:', content: text, isAnimating: false };
|
|
@@ -1458,7 +1593,10 @@ export class ScrambleStateManager {
|
|
|
1458
1593
|
state.queueMaxEnd = state.queue.reduce((max, item) => Math.max(max, item.end), 0);
|
|
1459
1594
|
}
|
|
1460
1595
|
else if (this.mode === 'illuminate') {
|
|
1461
|
-
state.
|
|
1596
|
+
state.glitchQueue = buildGlitchQueue('', text);
|
|
1597
|
+
state.startTime = now;
|
|
1598
|
+
state.lastGlitchTime = now;
|
|
1599
|
+
state.glitchFrame = 0;
|
|
1462
1600
|
state.displayedText = text;
|
|
1463
1601
|
}
|
|
1464
1602
|
else {
|
|
@@ -1486,7 +1624,10 @@ export class ScrambleStateManager {
|
|
|
1486
1624
|
}
|
|
1487
1625
|
else if (this.mode === 'illuminate') {
|
|
1488
1626
|
state.ripples = [];
|
|
1489
|
-
state.
|
|
1627
|
+
state.glitchQueue = buildGlitchQueue(state.displayedText || '', text);
|
|
1628
|
+
state.startTime = now;
|
|
1629
|
+
state.lastGlitchTime = now;
|
|
1630
|
+
state.glitchFrame = 0;
|
|
1490
1631
|
}
|
|
1491
1632
|
else {
|
|
1492
1633
|
state.ripples = [];
|
|
@@ -1497,6 +1638,8 @@ export class ScrambleStateManager {
|
|
|
1497
1638
|
else if (!this.isLineAnimating(state, now)) {
|
|
1498
1639
|
state.queue = [];
|
|
1499
1640
|
state.ripples = [];
|
|
1641
|
+
state.glitchQueue = [];
|
|
1642
|
+
state.glitchFrame = 0;
|
|
1500
1643
|
}
|
|
1501
1644
|
}
|
|
1502
1645
|
else {
|
|
@@ -1529,11 +1672,15 @@ export class ScrambleStateManager {
|
|
|
1529
1672
|
state.pendingText = '';
|
|
1530
1673
|
state.lastFlushTime = 0;
|
|
1531
1674
|
state.lastRippleEndTime = 0;
|
|
1675
|
+
state.glitchQueue = [];
|
|
1676
|
+
state.glitchFrame = 0;
|
|
1532
1677
|
}
|
|
1533
1678
|
if (isComplete) {
|
|
1534
1679
|
state.completed = true;
|
|
1535
1680
|
state.queue = [];
|
|
1536
1681
|
state.ripples = [];
|
|
1682
|
+
state.glitchQueue = [];
|
|
1683
|
+
state.glitchFrame = 0;
|
|
1537
1684
|
}
|
|
1538
1685
|
if (state.completed)
|
|
1539
1686
|
return { label: 'msg:', content: visibleText, isAnimating: false };
|
|
@@ -1576,33 +1723,75 @@ export class ScrambleStateManager {
|
|
|
1576
1723
|
state.queue = [];
|
|
1577
1724
|
const hasActiveRipples = state.ripples.some(r => now - r.time < r.dur);
|
|
1578
1725
|
const gap = now - state.lastTextChangeTime;
|
|
1726
|
+
const glitchCooledDown = now - state.lastGlitchTime >= GLITCH_COOLDOWN_MS;
|
|
1727
|
+
const previousText = state.lastText;
|
|
1579
1728
|
if (textChanged) {
|
|
1729
|
+
const delta = Math.max(0, visibleText.length - state.lastText.length);
|
|
1580
1730
|
state.lastText = visibleText;
|
|
1581
1731
|
state.phraseBuffer = visibleText;
|
|
1582
1732
|
state.lastTextChangeTime = now;
|
|
1733
|
+
state.charsSinceLastFlush += delta;
|
|
1583
1734
|
}
|
|
1584
|
-
//
|
|
1585
|
-
if (
|
|
1735
|
+
// F1: accumulator — periodic ripples during dense streaming
|
|
1736
|
+
if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && state.charsSinceLastFlush >= 20 && visibleText !== state.displayedText) {
|
|
1737
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
1586
1738
|
state.displayedText = visibleText;
|
|
1587
1739
|
state.lastFlushTime = now;
|
|
1588
1740
|
state.lastAnimTime = now;
|
|
1589
|
-
state.
|
|
1741
|
+
state.charsSinceLastFlush = 0;
|
|
1742
|
+
state.ripples = [];
|
|
1743
|
+
if (glitchCooledDown) {
|
|
1744
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, visibleText);
|
|
1745
|
+
state.startTime = now;
|
|
1746
|
+
state.glitchFrame = 0;
|
|
1747
|
+
state.lastGlitchTime = now;
|
|
1748
|
+
}
|
|
1590
1749
|
}
|
|
1591
|
-
else if (
|
|
1750
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && shouldFlushPhrase(visibleText, state.displayedText, state.lastFlushTime, now)) {
|
|
1751
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
1752
|
+
state.displayedText = visibleText;
|
|
1753
|
+
state.lastFlushTime = now;
|
|
1754
|
+
state.lastAnimTime = now;
|
|
1755
|
+
state.charsSinceLastFlush = 0;
|
|
1756
|
+
state.ripples = [];
|
|
1757
|
+
if (glitchCooledDown) {
|
|
1758
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, visibleText);
|
|
1759
|
+
state.startTime = now;
|
|
1760
|
+
state.glitchFrame = 0;
|
|
1761
|
+
state.lastGlitchTime = now;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && visibleText !== state.displayedText && now - state.lastTextChangeTime > MSG_CHUNK_DRAIN_MS) {
|
|
1592
1765
|
// Drain: text stopped arriving and we have unrippled content —
|
|
1593
|
-
//
|
|
1766
|
+
// glitch it out so it doesn't sit plain indefinitely.
|
|
1767
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
1594
1768
|
state.displayedText = visibleText;
|
|
1595
1769
|
state.lastFlushTime = now;
|
|
1596
1770
|
state.lastAnimTime = now;
|
|
1597
|
-
state.
|
|
1771
|
+
state.charsSinceLastFlush = 0;
|
|
1772
|
+
state.ripples = [];
|
|
1773
|
+
if (glitchCooledDown) {
|
|
1774
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, visibleText);
|
|
1775
|
+
state.startTime = now;
|
|
1776
|
+
state.glitchFrame = 0;
|
|
1777
|
+
state.lastGlitchTime = now;
|
|
1778
|
+
}
|
|
1598
1779
|
}
|
|
1599
|
-
else if (
|
|
1780
|
+
else if ((state.ripples.length < 6 || state.charsSinceLastFlush >= 80) && visibleText !== state.displayedText && gap > STREAMING_RESUME_GAP_MS) {
|
|
1600
1781
|
// Streaming resumed after a long pause (e.g., tool call) —
|
|
1601
|
-
// force a fresh
|
|
1782
|
+
// force a fresh glitch on the accumulated content.
|
|
1783
|
+
const oldDisplayed = previousText || state.displayedText;
|
|
1602
1784
|
state.displayedText = visibleText;
|
|
1603
1785
|
state.lastFlushTime = now;
|
|
1604
1786
|
state.lastAnimTime = now;
|
|
1605
|
-
state.
|
|
1787
|
+
state.charsSinceLastFlush = 0;
|
|
1788
|
+
state.ripples = [];
|
|
1789
|
+
if (glitchCooledDown) {
|
|
1790
|
+
state.glitchQueue = buildGlitchQueue(oldDisplayed, visibleText);
|
|
1791
|
+
state.startTime = now;
|
|
1792
|
+
state.glitchFrame = 0;
|
|
1793
|
+
state.lastGlitchTime = now;
|
|
1794
|
+
}
|
|
1606
1795
|
}
|
|
1607
1796
|
}
|
|
1608
1797
|
else {
|
|
@@ -1619,6 +1808,8 @@ export class ScrambleStateManager {
|
|
|
1619
1808
|
const hadActiveRipplesBefore = state.ripples.some(r => now - r.time < r.dur);
|
|
1620
1809
|
state.ripples = state.ripples.filter(r => now - r.time < r.dur + (r.contentChange ? ECHO_AFTERGLOW_MS : AFTERGLOW_MS));
|
|
1621
1810
|
state.queue = [];
|
|
1811
|
+
state.glitchQueue = [];
|
|
1812
|
+
state.glitchFrame = 0;
|
|
1622
1813
|
const justExpired = hadRipples && !hadActiveRipplesBefore;
|
|
1623
1814
|
if (!textChanged) {
|
|
1624
1815
|
if (state.displayedText !== visibleText) {
|
|
@@ -1839,12 +2030,13 @@ export class ScrambleStateManager {
|
|
|
1839
2030
|
state.startTime = now;
|
|
1840
2031
|
state.queueMaxEnd = state.queue.reduce((max, item) => Math.max(max, item.end), 0);
|
|
1841
2032
|
}
|
|
1842
|
-
else if (this.mode === 'illuminate') {
|
|
1843
|
-
state.ripples = spawnTpsIlluminateRipples(randomizedCenter(value.length), now);
|
|
1844
|
-
state.startTime = now;
|
|
1845
|
-
}
|
|
1846
2033
|
else {
|
|
1847
|
-
state.
|
|
2034
|
+
state.glitchQueue = buildGlitchQueue(state.prev, value, GLITCH_SHORT_MAX_START, GLITCH_SHORT_MAX_LENGTH);
|
|
2035
|
+
state.startTime = now;
|
|
2036
|
+
state.lastGlitchTime = now;
|
|
2037
|
+
state.glitchFrame = 0;
|
|
2038
|
+
state.ripples = [];
|
|
2039
|
+
state.queue = [];
|
|
1848
2040
|
}
|
|
1849
2041
|
}
|
|
1850
2042
|
_renderValueFlash(state, value, now) {
|
|
@@ -1860,20 +2052,17 @@ export class ScrambleStateManager {
|
|
|
1860
2052
|
}
|
|
1861
2053
|
return value;
|
|
1862
2054
|
}
|
|
1863
|
-
else if (this.mode === 'illuminate') {
|
|
1864
|
-
if (state.ripples.some(r => now - r.time < r.dur + FLASH_AFTERGLOW_MS)) {
|
|
1865
|
-
return applyRipples(value, state.ripples, now, ILLUMINATE_CONFIGS.tps);
|
|
1866
|
-
}
|
|
1867
|
-
state.ripples = [];
|
|
1868
|
-
state.startTime = now;
|
|
1869
|
-
return value;
|
|
1870
|
-
}
|
|
1871
2055
|
else {
|
|
1872
|
-
if (state.
|
|
1873
|
-
|
|
2056
|
+
if (state.glitchQueue.length > 0) {
|
|
2057
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
2058
|
+
if (isGlitchComplete(state.glitchQueue, frame)) {
|
|
2059
|
+
state.glitchQueue = [];
|
|
2060
|
+
state.prev = value;
|
|
2061
|
+
return value;
|
|
2062
|
+
}
|
|
2063
|
+
return computeGlitchFrame(state.glitchQueue, frame, () => this.poolRandomChar());
|
|
1874
2064
|
}
|
|
1875
|
-
state.
|
|
1876
|
-
state.startTime = now;
|
|
2065
|
+
state.prev = value;
|
|
1877
2066
|
return value;
|
|
1878
2067
|
}
|
|
1879
2068
|
}
|
|
@@ -1908,6 +2097,8 @@ export class ScrambleStateManager {
|
|
|
1908
2097
|
state.startTime = 0;
|
|
1909
2098
|
state.lastRippleEndTime = 0;
|
|
1910
2099
|
state.lastFlashTime = 0;
|
|
2100
|
+
state.glitchQueue = [];
|
|
2101
|
+
state.glitchFrame = 0;
|
|
1911
2102
|
}
|
|
1912
2103
|
if (state.completed)
|
|
1913
2104
|
return state;
|
|
@@ -1922,6 +2113,9 @@ export class ScrambleStateManager {
|
|
|
1922
2113
|
else if (this.mode === 'cascade') {
|
|
1923
2114
|
state.queue = [];
|
|
1924
2115
|
}
|
|
2116
|
+
else {
|
|
2117
|
+
state.glitchQueue = [];
|
|
2118
|
+
}
|
|
1925
2119
|
state.prev = value;
|
|
1926
2120
|
}
|
|
1927
2121
|
if (isFirstCall && staticLine && state.startTime === 0 && cooldownElapsed) {
|
|
@@ -2016,6 +2210,10 @@ export class ScrambleStateManager {
|
|
|
2016
2210
|
return !isCascadeComplete(state.queue, frame, state.queueMaxEnd);
|
|
2017
2211
|
}
|
|
2018
2212
|
else {
|
|
2213
|
+
if (state.glitchQueue.length > 0) {
|
|
2214
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
2215
|
+
return !isGlitchComplete(state.glitchQueue, frame);
|
|
2216
|
+
}
|
|
2019
2217
|
return state.ripples.some((rp) => rp.time + rp.dur + (rp.contentChange ? ECHO_AFTERGLOW_MS : AFTERGLOW_MS) > now);
|
|
2020
2218
|
}
|
|
2021
2219
|
}
|
|
@@ -2082,8 +2280,11 @@ export class ScrambleStateManager {
|
|
|
2082
2280
|
}
|
|
2083
2281
|
}
|
|
2084
2282
|
else {
|
|
2085
|
-
if (state.
|
|
2086
|
-
|
|
2283
|
+
if (state.glitchQueue.length > 0) {
|
|
2284
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
2285
|
+
if (!isGlitchComplete(state.glitchQueue, frame))
|
|
2286
|
+
return true;
|
|
2287
|
+
}
|
|
2087
2288
|
}
|
|
2088
2289
|
}
|
|
2089
2290
|
for (const state of this.actKpiState.values()) {
|
|
@@ -2097,8 +2298,11 @@ export class ScrambleStateManager {
|
|
|
2097
2298
|
}
|
|
2098
2299
|
}
|
|
2099
2300
|
else {
|
|
2100
|
-
if (state.
|
|
2101
|
-
|
|
2301
|
+
if (state.glitchQueue.length > 0) {
|
|
2302
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
2303
|
+
if (!isGlitchComplete(state.glitchQueue, frame))
|
|
2304
|
+
return true;
|
|
2305
|
+
}
|
|
2102
2306
|
}
|
|
2103
2307
|
}
|
|
2104
2308
|
for (const state of this.msgKpiState.values()) {
|
|
@@ -2112,8 +2316,11 @@ export class ScrambleStateManager {
|
|
|
2112
2316
|
}
|
|
2113
2317
|
}
|
|
2114
2318
|
else {
|
|
2115
|
-
if (state.
|
|
2116
|
-
|
|
2319
|
+
if (state.glitchQueue.length > 0) {
|
|
2320
|
+
const frame = Math.floor((now - state.startTime) / CASCADE_FRAME_MS);
|
|
2321
|
+
if (!isGlitchComplete(state.glitchQueue, frame))
|
|
2322
|
+
return true;
|
|
2323
|
+
}
|
|
2117
2324
|
}
|
|
2118
2325
|
}
|
|
2119
2326
|
for (const state of this.genericCache.values()) {
|
|
@@ -2184,6 +2391,8 @@ export class ScrambleStateManager {
|
|
|
2184
2391
|
record[key].pendingText = '';
|
|
2185
2392
|
record[key].lastFlushTime = 0;
|
|
2186
2393
|
record[key].lastRippleEndTime = 0;
|
|
2394
|
+
record[key].glitchQueue = [];
|
|
2395
|
+
record[key].glitchFrame = 0;
|
|
2187
2396
|
}
|
|
2188
2397
|
}
|
|
2189
2398
|
const tpsState = this.tpsState.get(id);
|
|
@@ -2192,18 +2401,24 @@ export class ScrambleStateManager {
|
|
|
2192
2401
|
tpsState.queue = [];
|
|
2193
2402
|
tpsState.ripples = [];
|
|
2194
2403
|
tpsState.lastRippleEndTime = 0;
|
|
2404
|
+
tpsState.glitchQueue = [];
|
|
2405
|
+
tpsState.glitchFrame = 0;
|
|
2195
2406
|
}
|
|
2196
2407
|
const actKpiState = this.actKpiState.get(id);
|
|
2197
2408
|
if (actKpiState) {
|
|
2198
2409
|
actKpiState.completed = true;
|
|
2199
2410
|
actKpiState.queue = [];
|
|
2200
2411
|
actKpiState.ripples = [];
|
|
2412
|
+
actKpiState.glitchQueue = [];
|
|
2413
|
+
actKpiState.glitchFrame = 0;
|
|
2201
2414
|
}
|
|
2202
2415
|
const msgKpiState = this.msgKpiState.get(id);
|
|
2203
2416
|
if (msgKpiState) {
|
|
2204
2417
|
msgKpiState.completed = true;
|
|
2205
2418
|
msgKpiState.queue = [];
|
|
2206
2419
|
msgKpiState.ripples = [];
|
|
2420
|
+
msgKpiState.glitchQueue = [];
|
|
2421
|
+
msgKpiState.glitchFrame = 0;
|
|
2207
2422
|
}
|
|
2208
2423
|
const streamRecord = this.streamState.get(id);
|
|
2209
2424
|
if (streamRecord) {
|
|
@@ -2219,6 +2434,8 @@ export class ScrambleStateManager {
|
|
|
2219
2434
|
state.completed = true;
|
|
2220
2435
|
state.queue = [];
|
|
2221
2436
|
state.ripples = [];
|
|
2437
|
+
state.glitchQueue = [];
|
|
2438
|
+
state.glitchFrame = 0;
|
|
2222
2439
|
state.lastRippleEndTime = 0;
|
|
2223
2440
|
}
|
|
2224
2441
|
}
|