summer-engine 4.3.0 → 4.5.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 (218) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +3 -1
  3. package/.codex-plugin/plugin.json +5 -3
  4. package/.cursor-plugin/plugin.json +4 -2
  5. package/.factory-plugin/plugin.json +4 -2
  6. package/AGENTS.md +2 -2
  7. package/CHANGELOG.md +87 -0
  8. package/README.md +67 -27
  9. package/commands/start.md +19 -15
  10. package/commands/summer.md +1 -0
  11. package/dist/cli/command-usage.d.ts +3 -1
  12. package/dist/cli/command-usage.js +10 -4
  13. package/dist/cli/commands/create.d.ts +1 -25
  14. package/dist/cli/commands/create.js +10 -138
  15. package/dist/cli/commands/dev.d.ts +66 -0
  16. package/dist/cli/commands/dev.js +234 -0
  17. package/dist/cli/commands/install.d.ts +15 -58
  18. package/dist/cli/commands/install.js +31 -481
  19. package/dist/cli/commands/login.js +1 -1
  20. package/dist/cli/commands/ops.d.ts +17 -1
  21. package/dist/cli/commands/ops.js +97 -9
  22. package/dist/cli/commands/preview.d.ts +58 -0
  23. package/dist/cli/commands/preview.js +181 -0
  24. package/dist/cli/commands/run.d.ts +1 -17
  25. package/dist/cli/commands/run.js +17 -206
  26. package/dist/cli/index.js +5 -0
  27. package/dist/core/agent-host.d.ts +3 -0
  28. package/dist/core/agent-host.js +1 -1
  29. package/dist/core/api-client.d.ts +10 -2
  30. package/dist/core/api-client.js +13 -8
  31. package/dist/core/capabilities/agent-playbook.js +1 -0
  32. package/dist/core/capabilities/apps.d.ts +9 -9
  33. package/dist/core/capabilities/asset-import.d.ts +4 -1
  34. package/dist/core/capabilities/asset-import.js +15 -6
  35. package/dist/core/capabilities/capture-gameplay.js +1 -1
  36. package/dist/core/capabilities/connect-signal.d.ts +2 -2
  37. package/dist/core/capabilities/content-make/index.d.ts +81 -0
  38. package/dist/core/capabilities/content-make/index.js +706 -0
  39. package/dist/core/capabilities/content-make/media.d.ts +57 -0
  40. package/dist/core/capabilities/content-make/media.js +172 -0
  41. package/dist/core/capabilities/content-make/plan.d.ts +130 -0
  42. package/dist/core/capabilities/content-make/plan.js +433 -0
  43. package/dist/core/capabilities/content-make/render.d.ts +64 -0
  44. package/dist/core/capabilities/content-make/render.js +370 -0
  45. package/dist/core/capabilities/content-make/study.d.ts +13 -0
  46. package/dist/core/capabilities/content-make/study.js +165 -0
  47. package/dist/core/capabilities/content-make/types.d.ts +173 -0
  48. package/dist/core/capabilities/content-make/types.js +6 -0
  49. package/dist/core/capabilities/content-make/voice.d.ts +7 -0
  50. package/dist/core/capabilities/content-make/voice.js +27 -0
  51. package/dist/core/capabilities/events.d.ts +2 -2
  52. package/dist/core/capabilities/export-game.d.ts +4 -1
  53. package/dist/core/capabilities/export-game.js +4 -3
  54. package/dist/core/capabilities/export-presets.d.ts +2 -1
  55. package/dist/core/capabilities/export-presets.js +2 -1
  56. package/dist/core/capabilities/go-live.d.ts +14 -0
  57. package/dist/core/capabilities/go-live.js +408 -0
  58. package/dist/core/capabilities/image-generation.d.ts +6 -6
  59. package/dist/core/capabilities/item-manifest.d.ts +92 -0
  60. package/dist/core/capabilities/item-manifest.js +247 -0
  61. package/dist/core/capabilities/placement.d.ts +7 -7
  62. package/dist/core/capabilities/projects-local.d.ts +68 -0
  63. package/dist/core/capabilities/projects-local.js +172 -0
  64. package/dist/core/capabilities/publish-build.d.ts +13 -2
  65. package/dist/core/capabilities/publish-build.js +87 -21
  66. package/dist/core/capabilities/publish-download.js +1 -1
  67. package/dist/core/capabilities/runtime-control.d.ts +7 -2
  68. package/dist/core/capabilities/runtime-control.js +27 -1
  69. package/dist/core/capabilities/seeing/args.d.ts +2 -2
  70. package/dist/core/capabilities/store-checks.d.ts +45 -0
  71. package/dist/core/capabilities/store-checks.js +85 -0
  72. package/dist/core/capabilities/store-details.d.ts +37 -0
  73. package/dist/core/capabilities/store-details.js +94 -0
  74. package/dist/core/capabilities/summer-bundle.d.ts +21 -2
  75. package/dist/core/capabilities/summer-bundle.js +54 -7
  76. package/dist/core/capabilities/sync-items.d.ts +89 -0
  77. package/dist/core/capabilities/sync-items.js +352 -0
  78. package/dist/core/capabilities/tool-dispatch.js +66 -6
  79. package/dist/core/capabilities/video-ogv.d.ts +49 -0
  80. package/dist/core/capabilities/video-ogv.js +260 -0
  81. package/dist/core/capability-skew.js +2 -2
  82. package/dist/core/cli-usage-beacon.js +26 -2
  83. package/dist/core/cli-usage.d.ts +5 -2
  84. package/dist/core/cli-usage.js +8 -2
  85. package/dist/core/engine-install.d.ts +53 -0
  86. package/dist/core/engine-install.js +93 -0
  87. package/dist/core/engine-installer.d.ts +134 -0
  88. package/dist/core/engine-installer.js +760 -0
  89. package/dist/core/engine-launch.d.ts +126 -0
  90. package/dist/core/engine-launch.js +398 -0
  91. package/dist/core/engine-self-start.d.ts +63 -0
  92. package/dist/core/engine-self-start.js +282 -0
  93. package/dist/core/engine.d.ts +7 -1
  94. package/dist/core/engine.js +9 -2
  95. package/dist/core/error-codes.d.ts +4 -1
  96. package/dist/core/error-codes.js +7 -3
  97. package/dist/core/library-read.d.ts +2 -2
  98. package/dist/core/library-search.d.ts +2 -2
  99. package/dist/core/local-project.d.ts +50 -0
  100. package/dist/core/local-project.js +234 -0
  101. package/dist/core/ops/areas.js +9 -3
  102. package/dist/core/ops/catalog.d.ts +9 -0
  103. package/dist/core/ops/catalog.js +56 -4
  104. package/dist/core/ops/engine-answer.d.ts +4 -1
  105. package/dist/core/ops/engine-answer.js +16 -3
  106. package/dist/core/ops/engine.js +4 -4
  107. package/dist/core/ops/save-files.d.ts +6 -1
  108. package/dist/core/ops/save-files.js +67 -50
  109. package/dist/core/ops/search.js +9 -2
  110. package/dist/core/preview/input.d.ts +35 -0
  111. package/dist/core/preview/input.js +105 -0
  112. package/dist/core/preview/launch-config.d.ts +52 -0
  113. package/dist/core/preview/launch-config.js +137 -0
  114. package/dist/core/preview/page.d.ts +22 -0
  115. package/dist/core/preview/page.js +232 -0
  116. package/dist/core/preview/preview-source.d.ts +179 -0
  117. package/dist/core/preview/preview-source.js +350 -0
  118. package/dist/core/preview/server.d.ts +26 -0
  119. package/dist/core/preview/server.js +218 -0
  120. package/dist/core/telemetry.d.ts +20 -16
  121. package/dist/core/telemetry.js +92 -16
  122. package/dist/core/templates.d.ts +20 -0
  123. package/dist/core/templates.js +300 -3
  124. package/dist/core/tool-profiles.d.ts +2 -2
  125. package/dist/core/tool-profiles.js +1 -0
  126. package/dist/core/usage-fields.d.ts +41 -0
  127. package/dist/core/usage-fields.js +150 -0
  128. package/dist/core/util/sleep.d.ts +3 -0
  129. package/dist/core/util/sleep.js +12 -0
  130. package/dist/mcp/ops-surface.js +28 -2
  131. package/dist/mcp/server.d.ts +14 -4
  132. package/dist/mcp/server.js +42 -8
  133. package/dist/mcp/tool-surface.d.ts +8 -0
  134. package/dist/mcp/tool-surface.js +19 -2
  135. package/dist/mcp/tools/creator-tools.js +92 -14
  136. package/dist/mcp/tools/debug-tools.js +2 -0
  137. package/dist/mcp/tools/local-project-tools.d.ts +6 -0
  138. package/dist/mcp/tools/local-project-tools.js +33 -0
  139. package/dist/mcp/tools/navigation-tools.js +2 -1
  140. package/dist/mcp/tools/project-tools.js +5 -8
  141. package/dist/mcp/tools/seeing-tools.js +1 -1
  142. package/dist/mcp/tools/visual-tools.js +1 -1
  143. package/dist/mcp/tools/with-engine.js +1 -1
  144. package/gemini-extension.json +3 -1
  145. package/library/references/mcp-tools-reference/mcp-tools-reference.md +18 -5
  146. package/library/skills/README.md +12 -8
  147. package/library/skills/animated-loop/SKILL.md +10 -16
  148. package/library/skills/cinematic-cutscene/SKILL.md +7 -18
  149. package/library/skills/headless-scripting/SKILL.md +7 -4
  150. package/library/skills/in-game-purchases/SKILL.md +25 -8
  151. package/library/skills/in-game-purchases/resource.yaml +2 -1
  152. package/library/skills/launch-ready/SKILL.md +351 -0
  153. package/library/skills/launch-ready/resource.yaml +45 -0
  154. package/library/skills/make-content/SKILL.md +202 -0
  155. package/library/skills/make-content/resource.yaml +32 -0
  156. package/library/skills/make-game/SKILL.md +2 -0
  157. package/library/skills/make-game/resource.yaml +2 -1
  158. package/library/skills/navigate-summer/SKILL.md +4 -2
  159. package/library/skills/new-project/SKILL.md +15 -22
  160. package/library/skills/new-project/resource.yaml +2 -1
  161. package/library/skills/painted-25d-world/SKILL.md +366 -0
  162. package/library/skills/painted-25d-world/_building-blocks/blender-route.md +33 -0
  163. package/library/skills/painted-25d-world/_building-blocks/blockout_builder.gd +149 -0
  164. package/library/skills/painted-25d-world/_building-blocks/capture_paint_view.gd +32 -0
  165. package/library/skills/painted-25d-world/_building-blocks/paint_projection.gdshader +23 -0
  166. package/library/skills/painted-25d-world/_building-blocks/paintcam_check.gd +25 -0
  167. package/library/skills/painted-25d-world/_building-blocks/painted_door.gd +17 -0
  168. package/library/skills/painted-25d-world/_building-blocks/painted_location.gd +56 -0
  169. package/library/skills/painted-25d-world/_building-blocks/painted_player.gd +125 -0
  170. package/library/skills/painted-25d-world/_building-blocks/painted_world.gd +111 -0
  171. package/library/skills/painted-25d-world/_building-blocks/tools/cam.py +57 -0
  172. package/library/skills/painted-25d-world/_building-blocks/tools/detail4k.py +81 -0
  173. package/library/skills/painted-25d-world/_building-blocks/tools/overlay.py +17 -0
  174. package/library/skills/painted-25d-world/_building-blocks/tools/preview.py +60 -0
  175. package/library/skills/painted-25d-world/resource.yaml +39 -0
  176. package/library/skills/publish-your-game/SKILL.md +13 -14
  177. package/library/skills/publish-your-game/resource.yaml +4 -3
  178. package/library/skills/release-checklist/SKILL.md +188 -0
  179. package/library/skills/release-checklist/resource.yaml +40 -0
  180. package/library/skills/release-readiness/SKILL.md +1 -1
  181. package/library/skills/release-readiness/resource.yaml +1 -1
  182. package/library/skills/store-art/SKILL.md +1 -1
  183. package/library/skills/store-art/resource.yaml +1 -1
  184. package/library/skills/store-listing/SKILL.md +1 -1
  185. package/library/skills/store-listing/resource.yaml +1 -1
  186. package/library/skills/summer-cli/SKILL.md +2 -0
  187. package/library/skills/summer-store/SKILL.md +295 -39
  188. package/library/skills/summer-store/resource.yaml +9 -3
  189. package/library/skills/trailer-shot/SKILL.md +3 -3
  190. package/library/templates/README.md +5 -0
  191. package/library/tools/content-make/resource.yaml +132 -0
  192. package/library/tools/frame-shot/GUIDE.md +1 -1
  193. package/library/tools/go-live/resource.yaml +67 -0
  194. package/library/tools/import-from-url/GUIDE.md +1 -0
  195. package/library/tools/import-from-url-batch/GUIDE.md +1 -1
  196. package/library/tools/play/GUIDE.md +2 -0
  197. package/library/tools/projects-create-local/resource.yaml +55 -0
  198. package/library/tools/projects-open-local/resource.yaml +51 -0
  199. package/library/tools/publish-build/resource.yaml +3 -3
  200. package/library/tools/screenshot/GUIDE.md +1 -1
  201. package/library/tools/store-set-details/resource.yaml +114 -0
  202. package/library/tools/sync-items/resource.yaml +72 -0
  203. package/package.json +1 -1
  204. package/registry/generated/aliases.json +6 -0
  205. package/registry/generated/counts.json +3 -3
  206. package/registry/generated/gemini-extension.json +3 -1
  207. package/registry/generated/index.json +463 -37
  208. package/registry/generated/marketplace.claude.json +1 -1
  209. package/registry/generated/plugin.claude-directory.json +4 -2
  210. package/registry/generated/plugin.claude.json +3 -1
  211. package/registry/generated/plugin.codex.json +5 -3
  212. package/registry/generated/plugin.cursor.json +4 -2
  213. package/registry/generated/plugin.factory.json +4 -2
  214. package/registry/generated/server.json +2 -2
  215. package/registry/generated/skills-index.md +12 -8
  216. package/registry/generated/skills-registry.json +62 -5
  217. package/registry/operations/legacy-names.json +40 -0
  218. package/registry/operations/public-catalog.json +10144 -387
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The media tools content.make runs on the person's computer: ffmpeg and
3
+ * ffprobe (cuts, motion, render, loudness), ImageMagick (captions) and,
4
+ * for a voice-over, a Whisper (word times). Nothing is installed here: a
5
+ * missing tool is a clear failure that names it.
6
+ */
7
+ export declare const FPS = 30;
8
+ export declare const W = 1080;
9
+ export declare const H = 1920;
10
+ /** Where a command is on PATH, or null. */
11
+ export declare function which(command: string, path?: string): string | null;
12
+ export declare function requireTools(names: readonly string[]): void;
13
+ export interface RunResult {
14
+ stdout: string;
15
+ stderr: string;
16
+ }
17
+ /** Runs a command to the end; a non-zero exit is an error with the last lines of its output. */
18
+ export declare function run(command: string, args: readonly string[], options?: {
19
+ cwd?: string;
20
+ env?: NodeJS.ProcessEnv;
21
+ }): Promise<RunResult>;
22
+ export declare const ffmpeg: (args: readonly string[]) => Promise<RunResult>;
23
+ export declare const magick: (args: readonly string[]) => Promise<RunResult>;
24
+ export interface Probe {
25
+ width: number;
26
+ height: number;
27
+ duration: number;
28
+ hasAudio: boolean;
29
+ fps: number;
30
+ }
31
+ export declare function probe(file: string): Promise<Probe>;
32
+ export declare const frames: (seconds: number) => number;
33
+ /**
34
+ * Per-frame motion (YDIF: mean luma change from the frame before) and
35
+ * brightness (YAVG) of a video, measured on a small copy so it is fast.
36
+ */
37
+ export declare function signalStats(file: string, fps?: number): Promise<{
38
+ ydif: number[];
39
+ yavg: number[];
40
+ }>;
41
+ /** Means of a per-frame series over fixed windows (seconds) at the series' frame rate. */
42
+ export declare function windowMeans(series: readonly number[], fps: number, windowSeconds: number): number[];
43
+ /** Hard cuts (scene changes above the threshold), at least minGap seconds apart. */
44
+ export declare function sceneCuts(file: string, threshold?: number, minGap?: number): Promise<number[]>;
45
+ /** Integrated loudness (LUFS) and true peak (dBTP) of a file's audio. */
46
+ export declare function loudness(file: string): Promise<{
47
+ lufs: number;
48
+ truePeak: number;
49
+ } | null>;
50
+ export declare const round2: (value: number) => number;
51
+ /**
52
+ * Onsets (beats and hits) of a song, seconds: peaks of the rise in short-time
53
+ * energy, at least 0.12 s apart. Enough to land cuts on the music.
54
+ */
55
+ export declare function onsets(file: string, maxSeconds?: number): Promise<number[]>;
56
+ /** Onset times from mono samples (pure; see onsets). */
57
+ export declare function onsetTimes(samples: Float32Array, rate: number): number[];
@@ -0,0 +1,172 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { delimiter, join } from "node:path";
4
+ import { BuildToolError } from "../summer-bundle.js";
5
+ /**
6
+ * The media tools content.make runs on the person's computer: ffmpeg and
7
+ * ffprobe (cuts, motion, render, loudness), ImageMagick (captions) and,
8
+ * for a voice-over, a Whisper (word times). Nothing is installed here: a
9
+ * missing tool is a clear failure that names it.
10
+ */
11
+ export const FPS = 30;
12
+ export const W = 1080;
13
+ export const H = 1920;
14
+ /** Where a command is on PATH, or null. */
15
+ export function which(command, path = process.env.PATH ?? "") {
16
+ for (const dir of path.split(delimiter)) {
17
+ if (!dir)
18
+ continue;
19
+ const candidate = join(dir, command);
20
+ if (existsSync(candidate))
21
+ return candidate;
22
+ }
23
+ return null;
24
+ }
25
+ const INSTALL = {
26
+ ffmpeg: "Install ffmpeg (macOS: brew install ffmpeg), then run it again.",
27
+ ffprobe: "Install ffmpeg (macOS: brew install ffmpeg), then run it again.",
28
+ magick: "Install ImageMagick 7 (macOS: brew install imagemagick), then run it again.",
29
+ };
30
+ export function requireTools(names) {
31
+ const missing = names.filter((name) => !which(name));
32
+ if (missing.length) {
33
+ throw new BuildToolError("content_tool_missing", `content.make needs ${missing.join(" and ")} on this computer.`, INSTALL[missing[0]] ?? `Install ${missing[0]}, then run it again.`, undefined, { missing: `${missing.join(", ")} on this computer` });
34
+ }
35
+ }
36
+ /** Runs a command to the end; a non-zero exit is an error with the last lines of its output. */
37
+ export function run(command, args, options = {}) {
38
+ return new Promise((resolve, reject) => {
39
+ const child = spawn(command, args, { cwd: options.cwd, env: options.env ?? process.env, stdio: ["ignore", "pipe", "pipe"] });
40
+ const out = [];
41
+ const err = [];
42
+ child.stdout.on("data", (chunk) => out.push(chunk));
43
+ child.stderr.on("data", (chunk) => err.push(chunk));
44
+ child.on("error", reject);
45
+ child.on("close", (code) => {
46
+ const stdout = Buffer.concat(out).toString("utf8");
47
+ const stderr = Buffer.concat(err).toString("utf8");
48
+ if (code === 0)
49
+ resolve({ stdout, stderr });
50
+ else
51
+ reject(new Error(`${command} ${args.slice(0, 3).join(" ")}... exited ${code}: ${stderr.trim().split("\n").slice(-4).join(" | ")}`));
52
+ });
53
+ });
54
+ }
55
+ export const ffmpeg = (args) => run("ffmpeg", ["-v", "error", "-y", ...args]);
56
+ export const magick = (args) => run("magick", args);
57
+ export async function probe(file) {
58
+ const { stdout } = await run("ffprobe", ["-v", "error", "-show_entries", "stream=codec_type,width,height,avg_frame_rate:format=duration", "-of", "json", file]);
59
+ const parsed = JSON.parse(stdout);
60
+ const video = parsed.streams?.find((stream) => stream.codec_type === "video");
61
+ return {
62
+ width: video?.width ?? 0,
63
+ height: video?.height ?? 0,
64
+ duration: Number(parsed.format?.duration ?? 0),
65
+ hasAudio: Boolean(parsed.streams?.some((stream) => stream.codec_type === "audio")),
66
+ fps: rate(video?.avg_frame_rate),
67
+ };
68
+ }
69
+ function rate(value) {
70
+ const [num, den] = (value ?? "").split("/").map(Number);
71
+ return num > 0 && den > 0 ? num / den : FPS;
72
+ }
73
+ export const frames = (seconds) => Math.round(seconds * FPS);
74
+ /**
75
+ * Per-frame motion (YDIF: mean luma change from the frame before) and
76
+ * brightness (YAVG) of a video, measured on a small copy so it is fast.
77
+ */
78
+ export async function signalStats(file, fps) {
79
+ const vf = `${fps ? `fps=${fps},` : ""}scale=270:-2,signalstats,metadata=print:key=lavfi.signalstats.YDIF:file=-,metadata=print:key=lavfi.signalstats.YAVG:file=-`;
80
+ const { stdout } = await run("ffmpeg", ["-v", "error", "-i", file, "-vf", vf, "-an", "-f", "null", "-"]);
81
+ const ydif = [];
82
+ const yavg = [];
83
+ for (const line of stdout.split("\n")) {
84
+ const match = /lavfi\.signalstats\.(YDIF|YAVG)=([\d.]+)/.exec(line);
85
+ if (!match)
86
+ continue;
87
+ (match[1] === "YDIF" ? ydif : yavg).push(Number(match[2]));
88
+ }
89
+ return { ydif, yavg };
90
+ }
91
+ /** Means of a per-frame series over fixed windows (seconds) at the series' frame rate. */
92
+ export function windowMeans(series, fps, windowSeconds) {
93
+ const size = Math.max(1, Math.round(fps * windowSeconds));
94
+ const out = [];
95
+ for (let start = 0; start < series.length; start += size) {
96
+ const slice = series.slice(start, start + size);
97
+ out.push(round2(slice.reduce((sum, value) => sum + value, 0) / slice.length));
98
+ }
99
+ return out;
100
+ }
101
+ /** Hard cuts (scene changes above the threshold), at least minGap seconds apart. */
102
+ export async function sceneCuts(file, threshold = 0.3, minGap = 0.12) {
103
+ const { stderr } = await run("ffmpeg", ["-hide_banner", "-i", file, "-vf", `select='gt(scene,${threshold})',showinfo`, "-an", "-f", "null", "-"]);
104
+ const cuts = [];
105
+ for (const match of stderr.matchAll(/pts_time:([0-9.]+)/g)) {
106
+ const t = round2(Number(match[1]));
107
+ if (t > minGap && (!cuts.length || t - cuts[cuts.length - 1] >= minGap))
108
+ cuts.push(t);
109
+ }
110
+ return cuts;
111
+ }
112
+ /** Integrated loudness (LUFS) and true peak (dBTP) of a file's audio. */
113
+ export async function loudness(file) {
114
+ try {
115
+ const { stderr } = await run("ffmpeg", ["-hide_banner", "-nostats", "-i", file, "-filter_complex", "ebur128=peak=true", "-f", "null", "-"]);
116
+ const summary = stderr.slice(stderr.lastIndexOf("Summary:"));
117
+ const lufs = /I:\s+(-?[\d.]+) LUFS/.exec(summary);
118
+ const peak = /Peak:\s+(-?[\d.]+) dBFS/.exec(summary);
119
+ return lufs ? { lufs: Number(lufs[1]), truePeak: peak ? Number(peak[1]) : 0 } : null;
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ export const round2 = (value) => Math.round(value * 100) / 100;
126
+ /**
127
+ * Onsets (beats and hits) of a song, seconds: peaks of the rise in short-time
128
+ * energy, at least 0.12 s apart. Enough to land cuts on the music.
129
+ */
130
+ export async function onsets(file, maxSeconds = 240) {
131
+ const rate = 4000;
132
+ const stdout = await runBinary("ffmpeg", ["-v", "error", "-t", String(maxSeconds), "-i", file, "-ac", "1", "-ar", String(rate), "-f", "f32le", "-"]);
133
+ const aligned = new Float32Array(new Uint8Array(stdout).buffer, 0, Math.floor(stdout.byteLength / 4));
134
+ return onsetTimes(aligned, rate);
135
+ }
136
+ /** Onset times from mono samples (pure; see onsets). */
137
+ export function onsetTimes(samples, rate) {
138
+ const hop = Math.round(rate * 0.01);
139
+ const energy = [];
140
+ for (let start = 0; start + hop <= samples.length; start += hop) {
141
+ let sum = 0;
142
+ for (let i = start; i < start + hop; i++)
143
+ sum += samples[i] * samples[i];
144
+ energy.push(Math.log10(1e-9 + sum / hop));
145
+ }
146
+ const flux = energy.map((value, index) => Math.max(0, value - (energy[index - 1] ?? value)));
147
+ const peaks = [];
148
+ const window = 50;
149
+ for (let i = 1; i < flux.length - 1; i++) {
150
+ if (flux[i] <= flux[i - 1] || flux[i] < flux[i + 1])
151
+ continue;
152
+ const around = flux.slice(Math.max(0, i - window), i + window);
153
+ const mean = around.reduce((sum, value) => sum + value, 0) / around.length;
154
+ if (flux[i] > mean * 2.2 && flux[i] > 0.15 && (!peaks.length || i * 0.01 - peaks[peaks.length - 1].t >= 0.12))
155
+ peaks.push({ t: round2(i * 0.01), strength: flux[i] });
156
+ }
157
+ // a busy song has an onset every few frames; cuts belong on its accents, the stronger 40%
158
+ const sorted = peaks.map((peak) => peak.strength).sort((a, b) => a - b);
159
+ const floor = peaks.length > 20 ? sorted[Math.floor(sorted.length * 0.6)] : 0;
160
+ return peaks.filter((peak) => peak.strength >= floor).map((peak) => peak.t);
161
+ }
162
+ function runBinary(command, args) {
163
+ return new Promise((resolve, reject) => {
164
+ const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
165
+ const out = [];
166
+ let err = "";
167
+ child.stdout.on("data", (chunk) => out.push(chunk));
168
+ child.stderr.on("data", (chunk) => (err += chunk.toString()));
169
+ child.on("error", reject);
170
+ child.on("close", (code) => (code === 0 ? resolve(Buffer.concat(out)) : reject(new Error(`${command} exited ${code}: ${err.trim().slice(-300)}`))));
171
+ });
172
+ }
@@ -0,0 +1,130 @@
1
+ import type { CaptionItem, CaptionStyle, Check, FootageClip, Plan, RefBeat, Reference, ShotSpec, Study } from "./types.js";
2
+ /** Search words of a text: lowercase, letters and digits, three or more characters, no filler. */
3
+ export declare function wordsOf(text: string | undefined | null): string[];
4
+ /** True when the reference speaks (a voice-over or a talking host), not only captions and music. */
5
+ export declare function refHasVoice(ref: Pick<Reference, "beats" | "voPattern">): boolean;
6
+ export interface PickContext {
7
+ usedReferenceIds: ReadonlySet<string>;
8
+ usedFormats: ReadonlySet<string>;
9
+ niches: readonly string[];
10
+ /** The game's recent commits (what changed today); a brief from the caller comes first, without a sha. */
11
+ commits: ReadonlyArray<{
12
+ sha?: string;
13
+ title: string;
14
+ }>;
15
+ /** A voice-over in the team's voice is at hand (voiceFile). */
16
+ voiceReady: boolean;
17
+ today: string;
18
+ }
19
+ export interface RefPick {
20
+ ref: Reference;
21
+ score: number;
22
+ /** One line on why it fits today, for the person ("You shipped ... today (commit 3f2a1c)"). */
23
+ fitReason: string;
24
+ /** The change it fits, when one does. */
25
+ commit?: {
26
+ sha: string;
27
+ title: string;
28
+ };
29
+ }
30
+ /** Scores one reference for today; null when it cannot be used (locked, expired, no beats, already made). */
31
+ export declare function scoreReference(ref: Reference, ctx: PickContext): RefPick | null;
32
+ /** The best reference for today, ties broken by id so the same inputs give the same pick. */
33
+ export declare function pickReference(refs: readonly Reference[], ctx: PickContext): RefPick | null;
34
+ /**
35
+ * The reference's caption look, read from its plain-words description:
36
+ * where it sits, its case, colour, font kind, and whether it holds one
37
+ * line or follows the voice word by word.
38
+ */
39
+ export declare function parseCaptionStyle(captionStyle: string | undefined, hasVoice: boolean): CaptionStyle;
40
+ /** Piecewise-linear map of reference times onto our timeline between matching anchors. */
41
+ export declare function mapTimes(times: readonly number[], refAnchors: readonly number[], ourAnchors: readonly number[]): number[];
42
+ export interface Word {
43
+ w: string;
44
+ s: number;
45
+ e: number;
46
+ }
47
+ /** Caption chunks of up to max words, broken at punctuation or a pause. */
48
+ export declare function chunkWords(words: readonly Word[], max?: number, gap?: number): Word[][];
49
+ /**
50
+ * Our script words on the times Whisper heard: matching words take their
51
+ * time; a run Whisper heard differently shares the span it took.
52
+ */
53
+ export declare function alignScript(script: readonly string[], heard: readonly Word[]): Word[];
54
+ /** Start time of each non-empty line in the aligned words (null for an empty line). */
55
+ export declare function lineStarts(lines: readonly string[], aligned: readonly Word[]): Array<number | null>;
56
+ /** Our cut times under a voice-over: the reference's beat starts land on the starts of our matching lines. */
57
+ export declare function voiceAnchors(beats: readonly RefBeat[], starts: ReadonlyArray<number | null>, refDuration: number, voiceEnd: number): {
58
+ ref: number[];
59
+ ours: number[];
60
+ };
61
+ /** The window of a clip with the most motion, outside the ranges already used, not too dark. */
62
+ export declare function bestWindow(clip: FootageClip, seconds: number, used: ReadonlyArray<[number, number]>, after?: number): {
63
+ t: number;
64
+ motion: number;
65
+ clean: boolean;
66
+ };
67
+ export interface BeatMapInput {
68
+ study: Pick<Study, "cuts" | "duration" | "black">;
69
+ beats: readonly RefBeat[];
70
+ lines: readonly string[];
71
+ clips: readonly FootageClip[];
72
+ /** Our cut times (the reference's, or mapped under our voice). */
73
+ cuts: readonly number[];
74
+ /** What the piece is about (the make's brief): weighs every beat's clip choice. */
75
+ brief?: string;
76
+ }
77
+ /**
78
+ * Which clip window plays each reference shot. One player's story: the
79
+ * clip that fits the most beats leads, and its windows move forward in
80
+ * time; a beat about cards or pages takes stills when there are any; a
81
+ * fast run of cuts never shows the same clip twice in a row; a reference
82
+ * black beat stays black.
83
+ */
84
+ export declare function beatMap(input: BeatMapInput): ShotSpec[];
85
+ /** Held or per-beat captions from our lines: consecutive beats sharing the reference's caption share one line. */
86
+ export declare function beatCaptions(beats: readonly RefBeat[], lines: readonly string[], cuts: readonly number[], refBounds: readonly number[], mode: CaptionStyle["mode"]): CaptionItem[];
87
+ /** Lines still to write: the reference's own words stay out of our output. */
88
+ export declare function unwrittenLines(plan: Pick<Plan, "lines">): number[];
89
+ export interface CheckInput {
90
+ shotMotion: readonly number[];
91
+ /** Longest run of frozen frames in each shot, seconds. */
92
+ shotFreeze?: readonly number[];
93
+ shotKinds: ReadonlyArray<"video" | "still" | "black">;
94
+ shotSeconds: readonly number[];
95
+ /** Mean motion over the first second. */
96
+ firstSecondMotion: number;
97
+ /** Seconds to the first caption or voice (null: none). */
98
+ firstWords: number | null;
99
+ refFirstWords: number | null;
100
+ length: number;
101
+ refLength: number;
102
+ loudness: {
103
+ lufs: number;
104
+ truePeak: number;
105
+ } | null;
106
+ /** Caption boxes as shares of the frame: [left, top, right, bottom]. */
107
+ captionBoxes: ReadonlyArray<[number, number, number, number]>;
108
+ }
109
+ /** The safe area for text: clear of the top bar, the right-hand buttons and the caption block at the bottom. */
110
+ export declare const SAFE: {
111
+ left: number;
112
+ top: number;
113
+ right: number;
114
+ bottom: number;
115
+ };
116
+ /** A shot may hold still this long (a beat of a held pose); longer reads as a frozen picture. */
117
+ export declare const FREEZE_LIMIT = 0.8;
118
+ /** The measured checks every piece shows next to it. */
119
+ export declare function evaluateChecks(input: CheckInput): Check[];
120
+ /**
121
+ * Where to start the song so the most cuts land on its beats: tries every
122
+ * start (0.01 s steps) and counts cuts within 70 ms of an onset. Ties go to
123
+ * the start closest to the beats, then the earliest.
124
+ */
125
+ export declare function musicStart(onsets: readonly number[], cuts: readonly number[], songLength: number): {
126
+ start: number;
127
+ hits: number;
128
+ };
129
+ /** Moves each cut onto the song's nearest onset within reach (70 ms), keeping every shot at least 0.1 s. */
130
+ export declare function snapCuts(cuts: readonly number[], onsets: readonly number[], start: number, reach?: number): number[];