demobites 1.28.0 → 1.29.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.
- package/package.json +1 -1
- package/scripts/record.mjs +35 -32
- package/skill/scripts/clock.mjs +4 -2
package/package.json
CHANGED
package/scripts/record.mjs
CHANGED
|
@@ -148,7 +148,8 @@ fs.mkdirSync(profileDir, { recursive: true });
|
|
|
148
148
|
// a customer's own uploaded voice. Holding a shot to cover an estimated line
|
|
149
149
|
// is what produced a 60-second take with the cursor parked for 12 seconds.
|
|
150
150
|
const DEFAULT_SETTLE_MS = 2200;
|
|
151
|
-
|
|
151
|
+
// 1.6 s (was 3.2 s; the planner's clock budgets 2 s for the beat — founder 2026-10-18: planned 31 s, filmed 46 s).
|
|
152
|
+
const DEFAULT_HOVER_DWELL = 1600;
|
|
152
153
|
const DEFAULT_CLICK_DWELL = 700;
|
|
153
154
|
const DEFAULT_CLICK_AFTER = 2000;
|
|
154
155
|
const CLOSING_BEAT_MS = 2200;
|
|
@@ -681,9 +682,13 @@ async function revealedBox(step) {
|
|
|
681
682
|
// Pace matched to the reference: a 350px move lands in ~0.75s INCLUDING its
|
|
682
683
|
// deceleration tail. The ballistic shape covers most distance early, so the
|
|
683
684
|
// same wall duration reads far snappier than min-jerk did.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
685
|
+
// THE GLIDE IS PACED BY THE CLOCK (founder's take of 2026-10-18, bite 1234: four glides took 7.2 s, 3.6 s, 3.5 s and
|
|
686
|
+
// 4.8 s against this cap — the loop awaited a browser round trip on each of its ~60 sub-steps, so its pace was the
|
|
687
|
+
// protocol's latency, not the design). Now the recorded cursor advances by wall time at 60 Hz, and the real mouse
|
|
688
|
+
// follows with at most one move in flight, skipping points it cannot keep up with.
|
|
689
|
+
const GLIDE_MIN_MS = 420;
|
|
690
|
+
const GLIDE_MAX_MS = 900;
|
|
691
|
+
const GLIDE_PER_PX = 0.85;
|
|
687
692
|
|
|
688
693
|
// LAW (ballistic motion, measured off the founder's reference hero-demo,
|
|
689
694
|
// 2026-08-09): the life is in the VELOCITY PROFILE, not the path. Frame-by-
|
|
@@ -720,7 +725,6 @@ async function glide(x, y) {
|
|
|
720
725
|
// not the 2x capture space, or every move doubles in duration and bow.
|
|
721
726
|
const designDist = dist / SUPERSAMPLE;
|
|
722
727
|
const dur = Math.min(GLIDE_MAX_MS, Math.max(GLIDE_MIN_MS, designDist * GLIDE_PER_PX));
|
|
723
|
-
const steps = Math.max(12, Math.round(dur / 16));
|
|
724
728
|
// Near-straight trace: the reference hero-demo's moves are straight to
|
|
725
729
|
// within 2% (path/chord 1.02) — a big swoop reads as fake, a trace of
|
|
726
730
|
// curvature reads as a hand. 4.5% capped 20px, DETERMINISTIC.
|
|
@@ -731,36 +735,30 @@ async function glide(x, y) {
|
|
|
731
735
|
const c1y = y0 + (y - y0) * 0.3 + perpY * arcMag * sign;
|
|
732
736
|
const c2x = x0 + (x - x0) * 0.75 + perpX * arcMag * 0.35 * sign;
|
|
733
737
|
const c2y = y0 + (y - y0) * 0.75 + perpY * arcMag * 0.35 * sign;
|
|
734
|
-
|
|
735
|
-
|
|
738
|
+
const started = Date.now();
|
|
739
|
+
let inflight = null;
|
|
740
|
+
for (;;) {
|
|
741
|
+
const f = Math.min(1, (Date.now() - started) / dur);
|
|
742
|
+
const e = ballistic(f);
|
|
736
743
|
const u = 1 - e;
|
|
737
744
|
const nx = u * u * u * x0 + 3 * u * u * e * c1x + 3 * u * e * e * c2x + e * e * e * x;
|
|
738
745
|
const ny = u * u * u * y0 + 3 * u * u * e * c1y + 3 * u * e * e * c2y + e * e * e * y;
|
|
739
|
-
// LAW (record what you animate, founder 2026-08-09):
|
|
740
|
-
//
|
|
741
|
-
// OLD position for the entire glide and then teleported. 98% of a real
|
|
742
|
-
// take's events were duplicates and the cursor jumped 458px in 0ms. Every
|
|
743
|
-
// reported symptom (rigid, jumping, too fast, missing where the action is,
|
|
744
|
-
// appearing and reappearing) came from that one omission — at a 3x zoom
|
|
745
|
-
// the visible frame is 640x360, so a cursor parked at the previous target
|
|
746
|
-
// is off-frame entirely. Advance the live position EVERY frame and record
|
|
747
|
-
// it, so the event track is the motion the viewer actually sees.
|
|
746
|
+
// LAW (record what you animate, founder 2026-08-09): advance the live position EVERY frame and record it, so
|
|
747
|
+
// the event track is the motion the viewer actually sees.
|
|
748
748
|
cx = nx; cy = ny;
|
|
749
749
|
pushMouse("move", nx, ny);
|
|
750
|
-
// The burned-in cursor only exists in the STANDALONE ending.
|
|
751
|
-
|
|
752
|
-
//
|
|
753
|
-
//
|
|
754
|
-
if (
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
// LAW (mouse-coordinate clicks): the real mouse tracks the drawn cursor,
|
|
758
|
-
// so hover states fire naturally and the click lands where the pulse is.
|
|
759
|
-
await page.mouse.move(nx, ny);
|
|
760
|
-
await page.waitForTimeout(dur / steps);
|
|
750
|
+
// The burned-in cursor only exists in the STANDALONE ending.
|
|
751
|
+
if (BURN_CURSOR) await page.evaluate(([a, b]) => window.__recSetCursor?.(a, b), [nx, ny]);
|
|
752
|
+
// LAW (mouse-coordinate clicks): the real mouse tracks the drawn cursor, so hover states fire naturally and the
|
|
753
|
+
// click lands where the pulse is. One move in flight at a time: a slow round trip skips points, never the clock.
|
|
754
|
+
if (!inflight) inflight = page.mouse.move(nx, ny).catch(() => {}).finally(() => { inflight = null; });
|
|
755
|
+
if (f >= 1) break;
|
|
756
|
+
await page.waitForTimeout(16);
|
|
761
757
|
}
|
|
758
|
+
if (inflight) await inflight;
|
|
762
759
|
cx = x; cy = y;
|
|
763
760
|
pushMouse("move", x, y);
|
|
761
|
+
await page.mouse.move(x, y).catch(() => {});
|
|
764
762
|
}
|
|
765
763
|
|
|
766
764
|
async function smoothScroll(dy, ms = 1400, within = null) {
|
|
@@ -873,7 +871,7 @@ try {
|
|
|
873
871
|
// then stamp the window so manifest.mjs cuts it out with a fade —
|
|
874
872
|
// the viewer sees page one, a fade, page two. No zoom or narration
|
|
875
873
|
// may live inside that window (manifest.mjs keeps them out).
|
|
876
|
-
await page.waitForLoadState("networkidle", { timeout:
|
|
874
|
+
await page.waitForLoadState("networkidle", { timeout: 2500 }).catch(() => {});
|
|
877
875
|
const paintDeadline = Date.now() + 10000;
|
|
878
876
|
while (Date.now() < paintDeadline) {
|
|
879
877
|
const painted = await page.evaluate(() => {
|
|
@@ -888,7 +886,7 @@ try {
|
|
|
888
886
|
if (painted) break;
|
|
889
887
|
await page.waitForTimeout(200);
|
|
890
888
|
}
|
|
891
|
-
await settledPicture(
|
|
889
|
+
await settledPicture(3000, "navigation");
|
|
892
890
|
await page.waitForTimeout(350);
|
|
893
891
|
rec.nav = { from: navIssuedAt, to: t() };
|
|
894
892
|
}
|
|
@@ -1127,7 +1125,7 @@ try {
|
|
|
1127
1125
|
// the dialog opened in the middle of the screen. A step framed wide
|
|
1128
1126
|
// already frames its result; a click that changed the URL leaves the
|
|
1129
1127
|
// landing to the settle that follows (framed wide there).
|
|
1130
|
-
if (page.url() !== urlBeforeClick) { landingPending = true; await settledPicture(
|
|
1128
|
+
if (page.url() !== urlBeforeClick) { landingPending = true; await settledPicture(2500, "landing"); }
|
|
1131
1129
|
if (step.reveals !== false && !stepWide) {
|
|
1132
1130
|
const opened = await revealedBox(step);
|
|
1133
1131
|
if (opened) {
|
|
@@ -1222,8 +1220,9 @@ try {
|
|
|
1222
1220
|
// the rest of the take.
|
|
1223
1221
|
await page.waitForLoadState("domcontentloaded", { timeout: 30000 }).catch(() => {});
|
|
1224
1222
|
if (page.url() !== urlBefore) {
|
|
1225
|
-
|
|
1226
|
-
await
|
|
1223
|
+
// The still gate replaces a networkidle that long-pollers never let end (6 s of dead air, 2026-10-18).
|
|
1224
|
+
await page.waitForLoadState("domcontentloaded", { timeout: 6000 }).catch(() => {});
|
|
1225
|
+
await settledPicture(2500, "landing");
|
|
1227
1226
|
await ensureCursor();
|
|
1228
1227
|
await applyHideCss();
|
|
1229
1228
|
await page.evaluate(([a, b]) => window.__recSetCursor?.(a, b), [x, y]);
|
|
@@ -1287,6 +1286,10 @@ try {
|
|
|
1287
1286
|
}
|
|
1288
1287
|
// Closing beat so the last action breathes before the cut ends.
|
|
1289
1288
|
await page.waitForTimeout(CLOSING_BEAT_MS);
|
|
1289
|
+
// THE TAKE SAYS WHAT IT COST (2026-10-18): per step, so a slow take explains itself.
|
|
1290
|
+
const glides = manifest.camera.filter((c) => c.glide).map((c) => c.glide.t_end - c.glide.t_start);
|
|
1291
|
+
manifest.timing = { filmed_s: Math.round(t() * 100) / 100, steps: manifest.steps.map((st) => ({ n: st.n, action: st.action, s: Math.round(((st.t_end ?? 0) - (st.t_start ?? 0)) * 100) / 100 })), glide_s: Math.round(glides.reduce((a, b) => a + b, 0) * 100) / 100, glides: glides.length };
|
|
1292
|
+
console.log(`timing: filmed ${manifest.timing.filmed_s}s; steps ${manifest.timing.steps.map((st) => `${st.n}:${st.s}s`).join(" ")}; glides ${glides.length} totalling ${manifest.timing.glide_s}s`);
|
|
1290
1293
|
} catch (e) {
|
|
1291
1294
|
failure = e;
|
|
1292
1295
|
}
|
package/skill/scripts/clock.mjs
CHANGED
|
@@ -14,8 +14,10 @@ export const secondsOf = (s, firstNarrated) => {
|
|
|
14
14
|
case "goto": return 0.1;
|
|
15
15
|
case "settle": return Math.max(ms(s.ms, 1200) / 1000, say) + slack;
|
|
16
16
|
case "scroll": return Math.max(ms(s.ms, 1900) / 1000, say) + slack;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// A click: the glide (~0.7 s), the dwell and the press (0.9 s), then the beat; a click that opens another page
|
|
18
|
+
// lands first (~1 s). A hover: the glide, then the beat (1.6 s at least). Numbers of record.mjs 1.29.0.
|
|
19
|
+
case "click": return 0.7 + 0.9 + Math.max(ms(s.after, 2000) / 1000, say) + (/href|^a\b|url/i.test(String(s.selector ?? "")) || s.url ? 1.0 : 0.3) + slack;
|
|
20
|
+
case "hover": return 0.7 + Math.max(ms(s.dwell, 1600) / 1000, say) + slack;
|
|
19
21
|
case "type": return 1.0 + String(s.text ?? "").length * 0.05 + Math.max(ms(s.after, 1500) / 1000, say) + slack;
|
|
20
22
|
case "expect": return 0.3;
|
|
21
23
|
default: return 1 + say;
|