Compare commits

...

4 Commits

Author SHA1 Message Date
CoreScope Bot b2b5913add revert(live): animation honors VCR.speed in BOTH modes (restore #922)
Operator wants slow-mo / fast-anim available in LIVE as well as REPLAY.
The mode-gated divisor removed that. Restore original #922 behavior:
both stepMs and DURATION_MS divide by VCR.speed unconditionally, and
the speed button stays visible in LIVE so it can be adjusted.

Updates test assertions to match: LIVE @ 0.25× → 132ms, LIVE @ 4× → 8.25ms.
2026-05-24 04:38:12 +00:00
openclaw-bot e610434434 fix(live): animation honors VCR.speed in REPLAY only; hide speed btn in LIVE
- stepMs / DURATION_MS divide by VCR.speed when mode===REPLAY (preserves
  #922 slow-mo @ 0.25× and fast-forward @ 4×/8×)
- LIVE mode always uses divisor=1 (fixes #1346 — persisted high speed no
  longer makes LIVE animation instantaneous)
- updateVCRUI hides speedBtn in LIVE (control is meaningless there)
- Tests cover LIVE@4/8, REPLAY@0.25/1/4 + inter-packet delay regression
  guard + UI hidden-in-LIVE assertion
2026-05-24 04:36:00 +00:00
openclaw-bot 122307fa15 fix(live): per-packet animation always 1× — decouple from VCR.speed (#1346)
VCR.speed is the inter-packet replay-gap multiplier, not a per-packet
animation cadence multiplier. Dividing the constants by VCR.speed made
LIVE mode appear instantaneous whenever a prior REPLAY had cycled to
4×/8× (the speed persists in localStorage across page loads).

Changes:
- drawAnimatedLine: stepMs = 33 (was 33 / VCR.speed)
- drawMatrixLine: DURATION_MS = 1100 (was 1100 / VCR.speed)

Inter-packet replay timing (delay = realGap / VCR.speed, line 507) is
left untouched — that is the legitimate slow-mo / fast-forward axis.

Fixes #1346
2026-05-24 04:30:19 +00:00
openclaw-bot 98dcf4c1e3 test(live): failing test for #1346 — per-packet anim must not divide by VCR.speed
RED commit. Test asserts:
- drawAnimatedLine stepMs is 33 (no /VCR.speed)
- drawMatrixLine DURATION_MS is 1100 (no /VCR.speed)
- inter-packet replay delay still uses VCR.speed (regression guard)
2026-05-24 04:29:54 +00:00
2 changed files with 112 additions and 3 deletions
+6 -3
View File
@@ -727,7 +727,10 @@
if (pauseBtn) { pauseBtn.textContent = '⏸'; pauseBtn.setAttribute('aria-label', 'Pause'); }
if (missedEl) missedEl.classList.add('hidden');
}
if (speedBtn) { speedBtn.textContent = speedLabel(VCR.speed); speedBtn.setAttribute('aria-label', 'Speed ' + speedLabel(VCR.speed)); }
if (speedBtn) {
speedBtn.textContent = speedLabel(VCR.speed);
speedBtn.setAttribute('aria-label', 'Speed ' + speedLabel(VCR.speed));
}
updateVCRLcd();
}
@@ -3231,7 +3234,7 @@
const matrixGreen = '#00ff41';
const TRAIL_LEN = Math.min(6, bytes.length);
const DURATION_MS = 1100 / VCR.speed;
const DURATION_MS = 1100 / VCR.speed; // #922: animation honors VCR.speed
const CHAR_INTERVAL = 0.06; // spawn a char every 6% of progress
const charMarkers = [];
let nextCharAt = CHAR_INTERVAL;
@@ -3363,7 +3366,7 @@
return;
}
const elapsed = now - lastStep;
const stepMs = 33 / VCR.speed;
const stepMs = 33 / VCR.speed; // #922: animation honors VCR.speed
if (elapsed >= stepMs) {
const ticks = Math.min(Math.floor(elapsed / stepMs), 4);
lastStep = now;
+106
View File
@@ -0,0 +1,106 @@
/* Tests for #1346 per-packet animation honors VCR.speed in BOTH modes.
*
* Bug history: PR #922 introduced `stepMs = 33 / VCR.speed` / `DURATION_MS = 1100 / VCR.speed`
* so slow-mo (0.25×) and fast-fwd (4×/8×) work for the per-packet animation. An interim fix
* mode-gated that to REPLAY only, which removed the ability to slow down / speed up LIVE
* animation. Operator wants the original #922 behavior restored: animation ALWAYS follows
* VCR.speed regardless of LIVE/REPLAY.
*
* Behavior:
* - LIVE & REPLAY both animation scaled by VCR.speed
* - Inter-packet replay delay `realGap / VCR.speed` unchanged
* - UI: speed button visible in BOTH modes (operator can adjust live-anim speed)
*/
'use strict';
const fs = require('fs');
const assert = require('assert');
const src = fs.readFileSync('public/live.js', 'utf8');
let passed = 0, failed = 0;
function test(name, fn) {
try { fn(); passed++; console.log(`${name}`); }
catch (e) { failed++; console.log(`${name}: ${e.message}`); }
}
console.log('\n=== #1346 — per-packet animation honors VCR.speed in BOTH modes ===');
function extractFn(name) {
const start = src.indexOf('function ' + name + '(');
assert.ok(start !== -1, `function ${name} not found`);
const next = src.indexOf('\n function ', start + 1);
return src.substring(start, next === -1 ? start + 4000 : next);
}
function evalWithVCR(expr, VCR) {
return new Function('VCR', `return (${expr});`)(VCR);
}
// --- drawAnimatedLine.stepMs ---
const stepExpr = extractFn('drawAnimatedLine').match(/const\s+stepMs\s*=\s*([^;]+);/)[1];
test('LIVE @ speed=0.25 → stepMs = 132 (slow-mo works in LIVE too)', () => {
const v = evalWithVCR(stepExpr, { mode: 'LIVE', speed: 0.25 });
assert.strictEqual(v, 132, `got ${v}`);
});
test('LIVE @ speed=4 → stepMs = 8.25 (fast-anim works in LIVE too)', () => {
const v = evalWithVCR(stepExpr, { mode: 'LIVE', speed: 4 });
assert.strictEqual(v, 8.25, `got ${v}`);
});
test('LIVE @ speed=1 → stepMs = 33 (baseline)', () => {
const v = evalWithVCR(stepExpr, { mode: 'LIVE', speed: 1 });
assert.strictEqual(v, 33, `got ${v}`);
});
test('REPLAY @ speed=4 → stepMs = 8.25 (fast-forward animation)', () => {
const v = evalWithVCR(stepExpr, { mode: 'REPLAY', speed: 4 });
assert.strictEqual(v, 8.25, `got ${v}`);
});
test('REPLAY @ speed=0.25 → stepMs = 132 (#922 slow-mo preserved)', () => {
const v = evalWithVCR(stepExpr, { mode: 'REPLAY', speed: 0.25 });
assert.strictEqual(v, 132, `got ${v}`);
});
test('REPLAY @ speed=1 → stepMs = 33 (baseline)', () => {
const v = evalWithVCR(stepExpr, { mode: 'REPLAY', speed: 1 });
assert.strictEqual(v, 33, `got ${v}`);
});
// --- drawMatrixLine.DURATION_MS ---
const durExpr = extractFn('drawMatrixLine').match(/const\s+DURATION_MS\s*=\s*([^;]+);/)[1];
test('LIVE @ speed=4 → DURATION_MS = 275 (fast-fwd in LIVE)', () => {
const v = evalWithVCR(durExpr, { mode: 'LIVE', speed: 4 });
assert.strictEqual(v, 275, `got ${v}`);
});
test('LIVE @ speed=0.25 → DURATION_MS = 4400 (slow-mo in LIVE)', () => {
const v = evalWithVCR(durExpr, { mode: 'LIVE', speed: 0.25 });
assert.strictEqual(v, 4400, `got ${v}`);
});
test('REPLAY @ speed=4 → DURATION_MS = 275 (fast-forward)', () => {
const v = evalWithVCR(durExpr, { mode: 'REPLAY', speed: 4 });
assert.strictEqual(v, 275, `got ${v}`);
});
test('REPLAY @ speed=0.25 → DURATION_MS = 4400 (#922 slow-mo)', () => {
const v = evalWithVCR(durExpr, { mode: 'REPLAY', speed: 0.25 });
assert.strictEqual(v, 4400, `got ${v}`);
});
// --- inter-packet replay delay regression guard ---
test('Inter-packet replay delay still divides realGap by VCR.speed', () => {
assert.ok(/delay\s*=\s*Math\.min\([^;]+?\/\s*VCR\.speed/.test(src),
'inter-packet replay delay must still divide realGap by VCR.speed');
});
// --- UI: speed button visible in BOTH modes ---
test('updateVCRUI does NOT hide speed button in LIVE', () => {
const start = src.indexOf('function updateVCRUI(');
assert.ok(start !== -1, 'updateVCRUI not found');
const end = src.indexOf('\n function ', start + 1);
const body = src.substring(start, end === -1 ? start + 4000 : end);
// No branch that adds 'hidden' class to speedBtn based on LIVE mode
assert.ok(!/speedBtn[\s\S]{0,200}VCR\.mode\s*===\s*['"]LIVE['"][\s\S]{0,200}classList\.add\(['"]hidden['"]\)/.test(body)
&& !/VCR\.mode\s*===\s*['"]LIVE['"][\s\S]{0,200}speedBtn[\s\S]{0,200}classList\.add\(['"]hidden['"]\)/.test(body),
'speedBtn must NOT be hidden when VCR.mode === LIVE — operator needs it to adjust live-anim speed');
});
console.log(`\n=== ${passed} passed, ${failed} failed ===`);
process.exit(failed === 0 ? 0 : 1);