demobite 1.15.0 → 1.16.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
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// THE CAMERA'S CLOCK (2026-10-15). What a storyboard step really costs on the camera, so the
|
|
2
|
+
// planner's total is the take's total. Numbers from record.mjs: estimateSpeech = words / 2.6 + 0.4;
|
|
3
|
+
// the hold is speech + 0.5 s breath measured from arrival; a click or hover spends ~1 s on cursor
|
|
4
|
+
// flight and dwell before arrival; the first narrated beat carries STARTUP_LEAD_SLACK_S = 2.5;
|
|
5
|
+
// the take closes on CLOSING_BEAT_MS = 2200. Keep the two in step.
|
|
6
|
+
export const CLOSING_S = 2.2;
|
|
7
|
+
const ms = (v, fallback) => (v == null ? fallback : v < 60 ? v * 1000 : v);
|
|
8
|
+
const speech = (text) => { const n = String(text ?? "").trim().split(/\s+/).filter(Boolean).length; return n ? n / 2.6 + 0.4 + 0.5 : 0; };
|
|
9
|
+
export const secondsOf = (s, firstNarrated) => {
|
|
10
|
+
const text = typeof s.narration === "string" ? s.narration : s.narration?.text ?? "";
|
|
11
|
+
const say = speech(text);
|
|
12
|
+
const slack = say && firstNarrated ? 2.5 : 0;
|
|
13
|
+
switch (s.action) {
|
|
14
|
+
case "goto": return 0.1;
|
|
15
|
+
case "settle": return Math.max(ms(s.ms, 1200) / 1000, say) + slack;
|
|
16
|
+
case "scroll": return Math.max(ms(s.ms, 1900) / 1000, say) + slack;
|
|
17
|
+
case "click":
|
|
18
|
+
case "hover": return 1.0 + Math.max(ms(s.after, 2000) / 1000, say) + slack;
|
|
19
|
+
case "type": return 1.0 + String(s.text ?? "").length * 0.05 + Math.max(ms(s.after, 1500) / 1000, say) + slack;
|
|
20
|
+
case "expect": return 0.3;
|
|
21
|
+
default: return 1 + say;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export const estimateTake = (steps) => {
|
|
25
|
+
let first = true, total = CLOSING_S;
|
|
26
|
+
for (const s of steps ?? []) { total += secondsOf(s, first); if (s.narration) first = false; }
|
|
27
|
+
return total;
|
|
28
|
+
};
|
|
@@ -347,6 +347,10 @@ const wire = {
|
|
|
347
347
|
frame: { width: FW, height: FH },
|
|
348
348
|
// BROWSER HEADER (2026-09-16): true by default; DemoBites adds the dark macOS bar on top at ingest. A workspace rule "No browser header" sets it false.
|
|
349
349
|
...(typeof man.browserHeader === "boolean" ? { browserHeader: man.browserHeader } : {}),
|
|
350
|
+
// APPROVED STORY (founder 2026-10-15, bite 1228: "the storyteller is blabbing"): the lines came from the
|
|
351
|
+
// director and the person said Film it to THESE lines. DemoBites keeps them verbatim and never writes
|
|
352
|
+
// a filler into a silence or a longer line into a longer beat.
|
|
353
|
+
narration_approved: true,
|
|
350
354
|
duration,
|
|
351
355
|
steps,
|
|
352
356
|
camera,
|
package/skill/scripts/plan.mjs
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// reads is logged on the take (<takeDir>/code-shared.log) so the person can
|
|
19
19
|
// see exactly what left the machine.
|
|
20
20
|
import fs from "node:fs";
|
|
21
|
+
import { estimateTake } from "./clock.mjs";
|
|
21
22
|
import path from "node:path";
|
|
22
23
|
import { spawnSync } from "node:child_process";
|
|
23
24
|
import { fileURLToPath } from "node:url";
|
|
@@ -150,6 +151,10 @@ const validate = (sb) => {
|
|
|
150
151
|
if (s.frame !== undefined && s.frame !== "close" && s.frame !== "wide") return `${at}: frame must be close or wide`;
|
|
151
152
|
}
|
|
152
153
|
if (sb.steps.length > 14) return "more than 14 steps; 6 to 10 is the law";
|
|
154
|
+
// THE CAMERA'S CLOCK (founder 2026-10-15): the take may not run past 45 s. The estimate is the
|
|
155
|
+
// camera's own (clock.mjs); the model gets the number and the cure.
|
|
156
|
+
const est = estimateTake(sb.steps);
|
|
157
|
+
if (est > 45) return `the camera would run this take about ${Math.round(est)} s; the law is 30 to 45. Cut words (6 to 11 a line) or steps (5 to 8 narrated); never pad, never add.`;
|
|
153
158
|
return null;
|
|
154
159
|
};
|
|
155
160
|
|
|
@@ -23,11 +23,13 @@ if (!mode || !file || !["print", "words", "await"].includes(mode)) {
|
|
|
23
23
|
}
|
|
24
24
|
const sb = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
25
25
|
const steps = Array.isArray(sb.steps) ? sb.steps : [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
26
|
+
// THE CAMERA'S CLOCK (founder 2026-10-15, bite 1228 said 0:36 and ran 0:51): a beat is held until its
|
|
27
|
+
// line would have finished (words / 2.6 s + 0.4 s, record.mjs estimateSpeech) plus half a second of
|
|
28
|
+
// breath; a click or hover adds about a second of cursor flight and dwell; the first narrated settle
|
|
29
|
+
// carries 2.5 s of startup slack; the take closes on a 2.2 s beat. Same numbers as record.mjs.
|
|
30
|
+
import { secondsOf as cameraSeconds, CLOSING_S } from "./clock.mjs";
|
|
31
|
+
let firstNarrated = true;
|
|
32
|
+
const secondsOf = (s) => { const r = cameraSeconds(s, firstNarrated); if (s.narration) firstNarrated = false; return r; };
|
|
31
33
|
const mmss = (sec) => `${Math.floor(sec / 60)}:${String(Math.round(sec % 60)).padStart(2, "0")}`;
|
|
32
34
|
// A row per step that says or shows something; silent plumbing (a bare goto,
|
|
33
35
|
// an expect) rides inside the previous row's time.
|
|
@@ -39,7 +41,7 @@ for (const s of steps) {
|
|
|
39
41
|
if (text && !(s.action === "expect" && !s.narration)) rows.push({ at: clock, text: text.replace(/\s+/g, " "), pointed: s.pointed === true, step: s });
|
|
40
42
|
clock += dur;
|
|
41
43
|
}
|
|
42
|
-
const total = clock;
|
|
44
|
+
const total = clock + CLOSING_S;
|
|
43
45
|
const title = (sb.title ?? sb.app ?? "Demo").toString().trim();
|
|
44
46
|
|
|
45
47
|
if (mode === "words") {
|