@cueframe/kernel 0.2.41 → 0.2.42

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 (66) hide show
  1. package/dist/_members/component-runtime/contracts.d.ts +19 -0
  2. package/dist/_members/component-runtime/contracts.d.ts.map +1 -1
  3. package/dist/_members/composition/floors/clips.d.ts +18 -0
  4. package/dist/_members/composition/floors/cohesionFloors.d.ts +31 -0
  5. package/dist/_members/composition/floors/craftFloors.d.ts +86 -0
  6. package/dist/_members/composition/floors/index.d.ts +24 -0
  7. package/dist/_members/composition/floors/occlusionFloor.d.ts +160 -0
  8. package/dist/_members/composition/floors/staticHold.d.ts +103 -0
  9. package/dist/_members/composition/floors/types.d.ts +52 -0
  10. package/dist/_members/composition/scene/introspection.d.ts +15 -0
  11. package/dist/_members/composition/scene/schema.d.ts +2 -8
  12. package/dist/_members/errors/registry.d.ts +6 -0
  13. package/dist/animate/preparation-facts.js +2 -2
  14. package/dist/animate.js +3 -3
  15. package/dist/authoring/scene.js +2 -2
  16. package/dist/authoring.js +2 -2
  17. package/dist/{chunk-JBWSK2YD.js → chunk-2PU2TSUG.js} +2 -2
  18. package/dist/{chunk-TE3PNQG6.js → chunk-4B4V3HYW.js} +6 -6
  19. package/dist/{chunk-WAUVKF2F.js → chunk-5ZQLMPDY.js} +1 -1
  20. package/dist/{chunk-C7TTI7FP.js → chunk-62VTAA6O.js} +1 -1
  21. package/dist/chunk-BUK4P6AW.js +571 -0
  22. package/dist/{chunk-XACXNDFL.js → chunk-CL5Y6V6K.js} +18 -16
  23. package/dist/{chunk-JRJQECCF.js → chunk-CZOVEX2D.js} +1 -1
  24. package/dist/{chunk-L5XECASW.js → chunk-D72CKEOC.js} +9 -9
  25. package/dist/{chunk-GA6NRDK3.js → chunk-DQRJJ2YN.js} +3 -1
  26. package/dist/{chunk-JNOM4FW3.js → chunk-J53SGAWP.js} +1 -1
  27. package/dist/{chunk-5NGYBI5Z.js → chunk-JX7NKSJP.js} +1 -1
  28. package/dist/{chunk-4MK6YGKV.js → chunk-N2OZDD44.js} +4 -1
  29. package/dist/{chunk-BLDL2EKL.js → chunk-P472GIBJ.js} +8 -563
  30. package/dist/{chunk-5BAWOULP.js → chunk-PCCVZYAF.js} +2 -2
  31. package/dist/{chunk-CJF3KBJ5.js → chunk-RO57QD5M.js} +4 -4
  32. package/dist/{chunk-QHLCJ3PE.js → chunk-UAOTQSS3.js} +4 -4
  33. package/dist/{chunk-ZVG446DJ.js → chunk-UGYYZBG4.js} +2 -0
  34. package/dist/{chunk-OBA6NW4S.js → chunk-XV2K3AQT.js} +1 -1
  35. package/dist/component-runtime/browserOptions.js +3 -3
  36. package/dist/component-runtime/contracts.js +3 -1
  37. package/dist/component-runtime/importPolicy.js +2 -2
  38. package/dist/component-runtime/render.js +5 -5
  39. package/dist/component-runtime/sourceAdmission.js +2 -2
  40. package/dist/component-runtime.js +9 -7
  41. package/dist/compose.js +15 -15
  42. package/dist/composition/floors.d.ts +1 -0
  43. package/dist/composition/floors.js +517 -0
  44. package/dist/composition/scene.js +1 -1
  45. package/dist/composition-ops.js +3 -3
  46. package/dist/core/render.js +3 -3
  47. package/dist/core.js +3 -3
  48. package/dist/mcp/adapter.js +2 -2
  49. package/dist/mcp/core.js +1 -1
  50. package/dist/mcp/scene-shot.d.ts +1 -0
  51. package/dist/mcp/scene-shot.js +18 -0
  52. package/dist/mcp/server.js +4 -3
  53. package/dist/mcp.js +4 -3
  54. package/dist/primitives/captions.js +3 -3
  55. package/dist/primitives/effects.js +4 -4
  56. package/dist/primitives/hero-text.js +2 -2
  57. package/dist/primitives/layouts.js +4 -4
  58. package/dist/primitives/lib/index.js +8 -8
  59. package/dist/primitives/lib.js +8 -8
  60. package/dist/primitives/scenes.js +5 -5
  61. package/dist/primitives/text.js +2 -2
  62. package/dist/primitives.js +13 -13
  63. package/dist/render-harness.js +10 -10
  64. package/package.json +17 -9
  65. package/dist/{chunk-RL5SO653.js → chunk-7SQOXIWR.js} +3 -3
  66. package/dist/{chunk-RBWS5XHE.js → chunk-X34KW2SL.js} +4 -4
@@ -0,0 +1,517 @@
1
+ import {
2
+ getPrimitiveCatalogMeta
3
+ } from "../chunk-7AZLFN26.js";
4
+ import "../chunk-7PA7FGLD.js";
5
+ import "../chunk-2ESYSVXG.js";
6
+
7
+ // ../composition/src/floors/types.ts
8
+ function floorAdvisoryLine(advisory) {
9
+ return `${advisory.rule} \u2014 ${advisory.detail}. Fix: ${advisory.fix}`;
10
+ }
11
+
12
+ // ../composition/src/floors/staticHold.ts
13
+ var DEFAULT_FPS = 30;
14
+ var CONTINUOUS_CARD_MOTION_VARS = [
15
+ "--cf-frame",
16
+ "--cf-t",
17
+ "--cf-progress",
18
+ "--cf-ease"
19
+ ];
20
+ var EDGE_CARD_MOTION_VARS = ["--cf-enter", "--cf-exit"];
21
+ var CARD_EDGE_RAMP_FRAMES = 18;
22
+ function compositionFps(composition) {
23
+ return composition.format.fps ?? DEFAULT_FPS;
24
+ }
25
+ function keyframeMotionSeconds(clip) {
26
+ const channels = clip.animate;
27
+ if (!channels) return 0;
28
+ let widest = 0;
29
+ for (const keyframes of Object.values(channels)) {
30
+ if (!Array.isArray(keyframes) || keyframes.length < 2) continue;
31
+ const times = keyframes.map((k) => k.time);
32
+ widest = Math.max(widest, Math.max(...times) - Math.min(...times));
33
+ }
34
+ return Math.min(widest, clip.duration);
35
+ }
36
+ function cardText(source) {
37
+ return [source.html, ...Object.values(source.tokens ?? {})].join("\n");
38
+ }
39
+ function cardMotionSeconds(source, duration, fps) {
40
+ const text = cardText(source);
41
+ if (CONTINUOUS_CARD_MOTION_VARS.some((name) => text.includes(name))) return duration;
42
+ const ramp = CARD_EDGE_RAMP_FRAMES / fps;
43
+ let seconds = 0;
44
+ for (const name of EDGE_CARD_MOTION_VARS) if (text.includes(name)) seconds += ramp;
45
+ return Math.min(seconds, duration);
46
+ }
47
+ function overlayDesignedSeconds(primitiveId, fallbackFps) {
48
+ const meta = getPrimitiveCatalogMeta(primitiveId);
49
+ if (meta === void 0 || typeof meta.durationInFrames !== "number") return null;
50
+ const fps = meta.fps ?? fallbackFps;
51
+ if (!(fps > 0)) return null;
52
+ return meta.durationInFrames / fps;
53
+ }
54
+ function analyzeHold(clip, fps, trackKind) {
55
+ const source = clip.source;
56
+ const keyframed = keyframeMotionSeconds(clip);
57
+ if (source.kind === "card") {
58
+ const moving = Math.max(keyframed, cardMotionSeconds(source, clip.duration, fps));
59
+ return { staticSeconds: clip.duration - moving, medium: "card" };
60
+ }
61
+ if (source.kind === "overlay") {
62
+ const designedSeconds = overlayDesignedSeconds(source.primitiveId, fps);
63
+ if (designedSeconds === null) return null;
64
+ const moving = Math.max(keyframed, designedSeconds);
65
+ return {
66
+ staticSeconds: clip.duration - moving,
67
+ medium: "overlay",
68
+ designedSeconds,
69
+ primitiveId: source.primitiveId
70
+ };
71
+ }
72
+ if (source.kind === "media" && (clip.kind ?? trackKind) === "image") {
73
+ const kenBurnsed = source.kenBurns !== void 0 && source.kenBurns !== "none";
74
+ if (kenBurnsed) return null;
75
+ return { staticSeconds: clip.duration - keyframed, medium: "still image" };
76
+ }
77
+ return null;
78
+ }
79
+
80
+ // ../composition/src/floors/clips.ts
81
+ function visualClips(composition) {
82
+ const out = [];
83
+ for (const track of composition.tracks) {
84
+ if (track.kind === "audio") continue;
85
+ if (track.enabled === false) continue;
86
+ for (const item of track.contents) {
87
+ if (!("source" in item)) continue;
88
+ out.push({ clip: item, trackKind: track.kind });
89
+ }
90
+ }
91
+ return out;
92
+ }
93
+ function audioClips(composition) {
94
+ const out = [];
95
+ for (const track of composition.tracks) {
96
+ if (track.kind !== "audio") continue;
97
+ if (track.enabled === false) continue;
98
+ for (const item of track.contents) {
99
+ if (!("source" in item)) continue;
100
+ out.push(item);
101
+ }
102
+ }
103
+ return out;
104
+ }
105
+ function isVideoClip(clip, trackKind) {
106
+ return clip.kind === "video" || trackKind === "video";
107
+ }
108
+ function isFullFrame(clip) {
109
+ const r = clip.region;
110
+ if (r === void 0) return true;
111
+ return r.x <= 0.02 && r.y <= 0.02 && r.w >= 0.98 && r.h >= 0.98;
112
+ }
113
+
114
+ // ../composition/src/floors/cohesionFloors.ts
115
+ var STATIC_HOLD_MAX_SEC = 4;
116
+ function detectStaticHolds(composition) {
117
+ const fps = compositionFps(composition);
118
+ const advisories = [];
119
+ for (const { clip, trackKind } of visualClips(composition)) {
120
+ const hold = analyzeHold(clip, fps, trackKind);
121
+ if (hold === null) continue;
122
+ if (hold.staticSeconds <= STATIC_HOLD_MAX_SEC) continue;
123
+ advisories.push({
124
+ criterion: "editorial",
125
+ rule: "static-hold",
126
+ clipId: clip.id,
127
+ detail: hold.medium === "overlay" ? `overlay clip "${clip.id}" mounts \`${hold.primitiveId}\`, whose animation is designed to run ${hold.designedSeconds?.toFixed(1)}s, and holds it for ${clip.duration.toFixed(1)}s \u2014 ${hold.staticSeconds.toFixed(1)}s of that is the finished animation sitting on its last frame (max ${STATIC_HOLD_MAX_SEC}s before it reads as a slide)` : `${hold.medium} clip "${clip.id}" holds a static frame for ${hold.staticSeconds.toFixed(1)}s of its ${clip.duration.toFixed(1)}s (max ${STATIC_HOLD_MAX_SEC}s before it reads as a slide)`,
128
+ fix: staticHoldFix(clip.id, hold.medium)
129
+ });
130
+ }
131
+ return advisories;
132
+ }
133
+ function staticHoldFix(clipId, medium) {
134
+ switch (medium) {
135
+ case "card":
136
+ return `give "${clipId}" motion the renderer can actually drive \u2014 the card motion runtime sets \`--cf-progress\`, \`--cf-ease\`, \`--cf-frame\` and \`--cf-t\` on every frame, so read one of them (\`transform: translateY(calc(var(--cf-ease) * -20px))\`) instead of a CSS \`@keyframes\`, which a frame-seeking render never advances \u2014 or split the beat so no static state outlives ${STATIC_HOLD_MAX_SEC}s`;
137
+ case "still image":
138
+ return `add motion (kenBurns) to "${clipId}" or shorten it below ${STATIC_HOLD_MAX_SEC}s`;
139
+ case "overlay":
140
+ return `shorten "${clipId}" to about its primitive's designed length, cut to a different beat when the animation lands, or choose a primitive built to run this long`;
141
+ }
142
+ }
143
+ function detectSilentNarration(composition) {
144
+ const voClip = audioClips(composition).find(
145
+ (c) => c.source.kind === "media" && c.source.audioRole === "vo"
146
+ );
147
+ if (voClip === void 0) return [];
148
+ const wordCount = (composition.captions?.segments ?? []).reduce(
149
+ (n, s) => n + s.words.length,
150
+ 0
151
+ );
152
+ if (wordCount > 0) return [];
153
+ return [
154
+ {
155
+ criterion: "editorial",
156
+ rule: "caption-spine",
157
+ clipId: voClip.id,
158
+ detail: `narration clip "${voClip.id}" (audioRole: vo, ${voClip.duration.toFixed(1)}s) has no word-timed caption layer \u2014 the spoken words appear nowhere on screen`,
159
+ fix: `author the caption spine with setCaptions (segments of word timings matching the VO), or captions.fromTranscript if the narration was uploaded \u2014 the persistent word-synced line is the film's strongest continuity device`
160
+ }
161
+ ];
162
+ }
163
+ function detectCohesionAdvisories(composition) {
164
+ return [...detectStaticHolds(composition), ...detectSilentNarration(composition)];
165
+ }
166
+
167
+ // ../composition/src/floors/occlusionFloor.ts
168
+ var OCCLUSION_MIN_COVERAGE = 0.9;
169
+ var OCCLUSION_MIN_SEC = 3;
170
+ var OCCLUSION_DECLARED_ALPHA_MIN_SEC = 10;
171
+ var ALPHA_POLICY_ALLOWS_OPAQUE = "allows-opaque";
172
+ var ALPHA_POLICY_REQUIRES_TRANSPARENCY = "requires-transparency";
173
+ function declaredAlphaPolicy(source) {
174
+ const bag = source.params ?? source.props;
175
+ if (bag === null || typeof bag !== "object") return null;
176
+ const manifest = bag.componentManifest;
177
+ if (manifest === null || typeof manifest !== "object") return null;
178
+ const policy = manifest.alphaPolicy;
179
+ if (policy === ALPHA_POLICY_ALLOWS_OPAQUE || policy === ALPHA_POLICY_REQUIRES_TRANSPARENCY) {
180
+ return policy;
181
+ }
182
+ return null;
183
+ }
184
+ function layeredVisualClips(composition) {
185
+ const trackCount = composition.tracks.length;
186
+ const out = [];
187
+ for (const [trackIndex, track] of composition.tracks.entries()) {
188
+ if (track.kind === "audio") continue;
189
+ if (track.enabled === false) continue;
190
+ const layerIndex = trackCount - trackIndex;
191
+ for (const item of track.contents) {
192
+ if (!("source" in item)) continue;
193
+ out.push({ clip: item, layerIndex });
194
+ }
195
+ }
196
+ return out;
197
+ }
198
+ var NOT_OPAQUE_TOKENS = /\b(?:transparent|none|currentColor)\b/i;
199
+ var ALPHA_LEGACY = /(?:rgba|hsla)\([^)]*,\s*(?:0|0?\.\d+)\s*\)/i;
200
+ var ALPHA_SLASH = /(?:rgba?|hsla?)\([^)]*\/\s*(?:0|0?\.\d+|\d{1,2}%)\s*\)/i;
201
+ var ALPHA_HEX = /#(?:[0-9a-f]{3}|[0-9a-f]{6})(?:[0-9a-f]|[0-9a-f]{2})(?![0-9a-f])/i;
202
+ function isOpaquePaint(value) {
203
+ const v = value.trim();
204
+ if (v === "") return false;
205
+ if (NOT_OPAQUE_TOKENS.test(v)) return false;
206
+ if (ALPHA_LEGACY.test(v) || ALPHA_SLASH.test(v) || ALPHA_HEX.test(v)) return false;
207
+ if (/^#(?:[0-9a-f]{3}|[0-9a-f]{6})\b/i.test(v)) return true;
208
+ if (/^(?:rgba?|hsla?)\(/i.test(v)) return true;
209
+ if (/gradient\(/i.test(v)) return true;
210
+ return /^[a-z]+$/i.test(v);
211
+ }
212
+ function resolveCardVar(value, tokens) {
213
+ return value.replace(/var\(\s*--cf-([a-z0-9_-]+)\s*(?:,[^)]*)?\)/gi, (whole, key) => {
214
+ const resolved = tokens?.[key];
215
+ return resolved === void 0 ? whole : resolved;
216
+ });
217
+ }
218
+ var FULL_EXTENT = /^(?:100%|100vw|100vh|100dvw|100dvh|auto|inherit|unset|initial)$/i;
219
+ var BACKGROUND_DECL = /(?<![\w-])background(?:-color|-image)?\s*:\s*([^;]+)/i;
220
+ var INLINE_STYLE_ATTR = /style\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
221
+ var ROOT_TAG = /<[a-z][^>]*>/i;
222
+ function maskStyleElements(html) {
223
+ return html.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, (block) => " ".repeat(block.length));
224
+ }
225
+ function inlineStyleBlocks(html) {
226
+ const root = ROOT_TAG.exec(html);
227
+ const rootStart = root === null ? -1 : root.index;
228
+ const rootEnd = root === null ? -1 : root.index + root[0].length;
229
+ const out = [];
230
+ for (const attr of html.matchAll(INLINE_STYLE_ATTR)) {
231
+ const at = attr.index ?? 0;
232
+ out.push({ block: attr[1] ?? attr[2] ?? "", isRoot: at >= rootStart && at < rootEnd });
233
+ }
234
+ return out;
235
+ }
236
+ function declaresPartialExtent(block) {
237
+ for (const m of block.matchAll(/\b(?:min-|max-)?(?:width|height)\s*:\s*([^;]+)/gi)) {
238
+ if (!FULL_EXTENT.test(m[1].trim())) return true;
239
+ }
240
+ return false;
241
+ }
242
+ function claimsWholeFrame(block) {
243
+ if (/\bwidth\s*:\s*100%/i.test(block) && /\bheight\s*:\s*100%/i.test(block)) return true;
244
+ if (!/\bposition\s*:\s*(?:absolute|fixed)/i.test(block)) return false;
245
+ if (/\binset\s*:\s*0(?:px)?\s*(?:;|$)/i.test(block)) return true;
246
+ return ["top", "right", "bottom", "left"].every(
247
+ (edge) => new RegExp(`\\b${edge}\\s*:\\s*0(?:px)?\\s*(?:;|$)`, "i").test(block)
248
+ );
249
+ }
250
+ function cardIsOpaque(source) {
251
+ for (const { block, isRoot } of inlineStyleBlocks(maskStyleElements(source.html))) {
252
+ const decl = BACKGROUND_DECL.exec(block);
253
+ if (decl === null) continue;
254
+ if (declaresPartialExtent(block)) return false;
255
+ if (!isRoot && !claimsWholeFrame(block)) return false;
256
+ return isOpaquePaint(resolveCardVar(decl[1], source.tokens));
257
+ }
258
+ return false;
259
+ }
260
+ function coversFrame(clip) {
261
+ if (!isFullFrame(clip)) return false;
262
+ if (clip.position !== void 0 && (clip.position.x !== 0 || clip.position.y !== 0)) return false;
263
+ if (clip.scale !== void 0 && clip.scale < 1) return false;
264
+ if (clip.rotation !== void 0 && clip.rotation !== 0) return false;
265
+ return true;
266
+ }
267
+ function isSolidThroughout(clip) {
268
+ if ((clip.opacity ?? 1) < 1) return false;
269
+ const channel = clip.animate?.opacity;
270
+ if (Array.isArray(channel) && channel.some((k) => k.value < 1)) return false;
271
+ return true;
272
+ }
273
+ function occluderEvidence(clip) {
274
+ if (!coversFrame(clip) || !isSolidThroughout(clip)) return null;
275
+ if (clip.source.kind === "card") return cardIsOpaque(clip.source) ? "proven" : null;
276
+ if (clip.source.kind === "effect") return null;
277
+ return declaredAlphaPolicy(clip.source) === ALPHA_POLICY_ALLOWS_OPAQUE ? "declared" : null;
278
+ }
279
+ function triggerSec(evidence) {
280
+ return evidence === "proven" ? OCCLUSION_MIN_SEC : OCCLUSION_DECLARED_ALPHA_MIN_SEC;
281
+ }
282
+ function isPaintedLane(clip) {
283
+ return clip.source.kind !== "effect";
284
+ }
285
+ function detectBackdropOcclusion(composition) {
286
+ const layered = layeredVisualClips(composition);
287
+ const advisories = [];
288
+ for (const { clip: occluder, layerIndex } of layered) {
289
+ const evidence = occluderEvidence(occluder);
290
+ if (evidence === null) continue;
291
+ const start = occluder.startTime;
292
+ const end = start + occluder.duration;
293
+ const buried = [];
294
+ for (const { clip: victim, layerIndex: victimLayer } of layered) {
295
+ if (victimLayer >= layerIndex) continue;
296
+ if (!isPaintedLane(victim)) continue;
297
+ const overlap = Math.min(end, victim.startTime + victim.duration) - Math.max(start, victim.startTime);
298
+ if (overlap <= 0) continue;
299
+ const coverage = overlap / victim.duration;
300
+ if (coverage < OCCLUSION_MIN_COVERAGE) continue;
301
+ buried.push({ id: victim.id, overlap, coverage });
302
+ }
303
+ const trigger = triggerSec(evidence);
304
+ if (!buried.some((b) => b.overlap >= trigger)) continue;
305
+ const worst = buried.reduce((a, b) => b.overlap > a.overlap ? b : a);
306
+ const names = buried.map((b) => `"${b.id}"`).join(", ");
307
+ const geometry = `sits on layer ${layerIndex} for ${occluder.duration.toFixed(1)}s`;
308
+ const victims = `${buried.length} clip(s) below it (${names}) \u2014 each covered for \u2265${Math.round(OCCLUSION_MIN_COVERAGE * 100)}% of its own life (worst: "${worst.id}", ${worst.overlap.toFixed(1)}s, ${Math.round(worst.coverage * 100)}%)`;
309
+ const convention = `In CueFrame track index 0 is the TOP layer, so a full-frame ${evidence === "proven" ? "background" : "component"} authored on the first track renders OVER the film, not behind it`;
310
+ const move = `move "${occluder.id}" to the LAST track \u2014 track index 0 renders ON TOP and the last track is the backdrop (layerIndex = trackCount - trackIndex). \`addTrack\` APPENDS, so add a track for the backdrop and put "${occluder.id}" on it; or, if it is meant to be a takeover rather than a world, shorten it so nothing below stays hidden past ${trigger}s`;
311
+ advisories.push({
312
+ criterion: "editorial",
313
+ rule: "backdrop-occlusion",
314
+ clipId: occluder.id,
315
+ detail: evidence === "proven" ? `opaque full-frame card "${occluder.id}" ${geometry} and hides ${victims}. ${convention}` : `full-frame component "${occluder.id}" ${geometry}, and its manifest declares alphaPolicy "${ALPHA_POLICY_ALLOWS_OPAQUE}" \u2014 its pixels are not on the wire, so that is PERMISSION to paint opaque, not proof that it does. If it does, it hides ${victims}. ${convention}`,
316
+ fix: evidence === "proven" ? move : `first check whether "${occluder.id}" actually paints a full-frame opaque plate. If it draws through, nothing is wrong here \u2014 declare "${ALPHA_POLICY_REQUIRES_TRANSPARENCY}" in its manifest and this stops firing. If it does paint one, ${move}`
317
+ });
318
+ }
319
+ return advisories;
320
+ }
321
+
322
+ // ../composition/src/floors/craftFloors.ts
323
+ var SFX_VARIETY_MIN_HITS = 6;
324
+ var SFX_VARIETY_MIN_RATIO = 0.4;
325
+ var DEAD_HOLD_MAX_SEC = 8;
326
+ var ROOM_CHANGE_MAX = 2;
327
+ var MONTAGE_FAST_CLIP_SEC = 1.2;
328
+ var MONTAGE_HARD_MIN_SEC = 0.4;
329
+ var MONTAGE_MIN_RUN = 3;
330
+ var MONTAGE_ADJACENCY_GAP_SEC = 0.5;
331
+ function detectSfxSameSoundReuse(composition) {
332
+ const sfx = audioClips(composition).filter(
333
+ (c) => c.source.kind === "media" && c.source.audioRole === "sfx"
334
+ );
335
+ const hits = sfx.length;
336
+ if (hits < SFX_VARIETY_MIN_HITS) return [];
337
+ const distinct = new Set(
338
+ sfx.map((c) => c.source.kind === "media" ? c.source.mediaId : void 0).filter(
339
+ (id) => id !== void 0
340
+ )
341
+ ).size;
342
+ if (distinct === 0) return [];
343
+ const ratio = distinct / hits;
344
+ if (ratio >= SFX_VARIETY_MIN_RATIO) return [];
345
+ return [
346
+ {
347
+ criterion: "editorial",
348
+ rule: "sfx-same-sound-reuse",
349
+ detail: `${hits} SFX hits draw from only ${distinct} distinct sound(s) (variety ${ratio.toFixed(2)}, min ${SFX_VARIETY_MIN_RATIO}) \u2014 the same transient repeated reads as canned`,
350
+ fix: `widen the SFX palette so distinct sounds \xF7 hits \u2265 ${SFX_VARIETY_MIN_RATIO} (swap repeated hits for different impacts, ticks and whooshes)`
351
+ }
352
+ ];
353
+ }
354
+ function detectDeadHolds(composition) {
355
+ const fps = compositionFps(composition);
356
+ const advisories = [];
357
+ for (const { clip, trackKind } of visualClips(composition)) {
358
+ const hold = analyzeHold(clip, fps, trackKind);
359
+ if (hold === null) continue;
360
+ if (hold.staticSeconds <= DEAD_HOLD_MAX_SEC) continue;
361
+ advisories.push({
362
+ criterion: "editorial",
363
+ rule: "dead-hold",
364
+ clipId: clip.id,
365
+ detail: `${hold.medium} clip "${clip.id}" parks one visual state for ${hold.staticSeconds.toFixed(1)}s of its ${clip.duration.toFixed(1)}s (hard max ${DEAD_HOLD_MAX_SEC}s \u2014 past this a single beat is a parked slide no matter how it is dressed)`,
366
+ fix: `split "${clip.id}" into multiple beats so no single visual state outlives ${DEAD_HOLD_MAX_SEC}s (and give it motion below the ${STATIC_HOLD_MAX_SEC}s slide line)`
367
+ });
368
+ }
369
+ return advisories;
370
+ }
371
+ function isRoom(clip) {
372
+ if (!isPaintedLane(clip)) return false;
373
+ if (!coversFrame(clip)) return false;
374
+ const source = clip.source;
375
+ const declared = declaredAlphaPolicy(source);
376
+ if (declared === "requires-transparency") return false;
377
+ if (declared === "allows-opaque") return true;
378
+ if (source.kind === "card") return cardIsOpaque(source);
379
+ if (source.kind !== "overlay") return false;
380
+ const meta = getPrimitiveCatalogMeta(source.primitiveId);
381
+ return meta?.category === "backgrounds" || meta?.kind === "scene";
382
+ }
383
+ function paintsBeneath(candidate, all) {
384
+ const start = candidate.clip.startTime;
385
+ const end = start + candidate.clip.duration;
386
+ return all.some(({ clip, layerIndex }) => {
387
+ if (layerIndex >= candidate.layerIndex) return false;
388
+ if (!isPaintedLane(clip)) return false;
389
+ return Math.min(end, clip.startTime + clip.duration) - Math.max(start, clip.startTime) > 0;
390
+ });
391
+ }
392
+ function detectRoomChanges(composition) {
393
+ if (visualClips(composition).some(({ clip, trackKind }) => isVideoClip(clip, trackKind))) {
394
+ return [];
395
+ }
396
+ const layered = layeredVisualClips(composition);
397
+ const backdrops = layered.filter((entry) => isRoom(entry.clip) && !paintsBeneath(entry, layered)).map((entry) => entry.clip).sort((a, b) => a.startTime - b.startTime);
398
+ let rooms = 0;
399
+ let prevEnd = -Infinity;
400
+ for (const clip of backdrops) {
401
+ if (clip.startTime >= prevEnd - 1e-6) {
402
+ rooms += 1;
403
+ prevEnd = clip.startTime + clip.duration;
404
+ }
405
+ }
406
+ if (rooms <= ROOM_CHANGE_MAX) return [];
407
+ return [
408
+ {
409
+ criterion: "editorial",
410
+ rule: "room-changes",
411
+ detail: `${rooms} sequential full-frame backdrops in a graphics-led film (max ${ROOM_CHANGE_MAX}) \u2014 cutting between that many "rooms" reads as a slide deck, not one continuous space`,
412
+ fix: `hold fewer distinct full-frame worlds (\u2264${ROOM_CHANGE_MAX}) and move within them, or carry a persistent element across the cuts so the space feels continuous`
413
+ }
414
+ ];
415
+ }
416
+ function detectMontagePacing(composition) {
417
+ const advisories = [];
418
+ const clips = visualClips(composition).filter(({ clip, trackKind }) => isVideoClip(clip, trackKind)).map(({ clip }) => clip).sort((a, b) => a.startTime - b.startTime);
419
+ if (clips.length === 0) return [];
420
+ for (const clip of clips) {
421
+ if (clip.duration < MONTAGE_HARD_MIN_SEC) {
422
+ advisories.push({
423
+ criterion: "editorial",
424
+ rule: "montage-pacing",
425
+ clipId: clip.id,
426
+ detail: `video clip "${clip.id}" is ${clip.duration.toFixed(2)}s \u2014 under ${MONTAGE_HARD_MIN_SEC}s the eye cannot register it (subliminal cut)`,
427
+ fix: `extend "${clip.id}" to at least ${MONTAGE_HARD_MIN_SEC}s or drop it`
428
+ });
429
+ }
430
+ }
431
+ const runs = [];
432
+ let run = [];
433
+ let prevEnd = -Infinity;
434
+ for (const clip of clips) {
435
+ const adjacent = clip.startTime <= prevEnd + MONTAGE_ADJACENCY_GAP_SEC;
436
+ if (adjacent && run.length > 0) {
437
+ run.push(clip);
438
+ } else {
439
+ if (run.length > 0) runs.push(run);
440
+ run = [clip];
441
+ }
442
+ prevEnd = clip.startTime + clip.duration;
443
+ }
444
+ if (run.length > 0) runs.push(run);
445
+ for (const r of runs) {
446
+ if (r.length < MONTAGE_MIN_RUN) continue;
447
+ let firedFast = false;
448
+ for (let i = 0; i + MONTAGE_MIN_RUN <= r.length && !firedFast; i++) {
449
+ const window = r.slice(i, i + MONTAGE_MIN_RUN);
450
+ if (window.every((c) => c.duration < MONTAGE_FAST_CLIP_SEC)) {
451
+ firedFast = true;
452
+ advisories.push({
453
+ criterion: "editorial",
454
+ rule: "montage-pacing",
455
+ clipId: window[0].id,
456
+ detail: `${MONTAGE_MIN_RUN}+ consecutive video clips from "${window[0].id}" each under ${MONTAGE_FAST_CLIP_SEC}s \u2014 the cuts strobe faster than the eye can follow`,
457
+ fix: `lengthen the run around "${window[0].id}" so cuts land \u2265${MONTAGE_FAST_CLIP_SEC}s apart, or cut fewer of them`
458
+ });
459
+ }
460
+ }
461
+ }
462
+ return advisories;
463
+ }
464
+ function detectCraftAdvisories(composition) {
465
+ return [
466
+ ...detectSfxSameSoundReuse(composition),
467
+ ...detectDeadHolds(composition),
468
+ ...detectRoomChanges(composition),
469
+ ...detectMontagePacing(composition)
470
+ ];
471
+ }
472
+
473
+ // ../composition/src/floors/index.ts
474
+ function detectFloorAdvisories(composition) {
475
+ return [
476
+ ...detectCohesionAdvisories(composition),
477
+ ...detectCraftAdvisories(composition),
478
+ ...detectBackdropOcclusion(composition)
479
+ ];
480
+ }
481
+ function floorAdvisoryLines(composition) {
482
+ return detectFloorAdvisories(composition).map(floorAdvisoryLine);
483
+ }
484
+ export {
485
+ CARD_EDGE_RAMP_FRAMES,
486
+ DEAD_HOLD_MAX_SEC,
487
+ MONTAGE_FAST_CLIP_SEC,
488
+ MONTAGE_HARD_MIN_SEC,
489
+ MONTAGE_MIN_RUN,
490
+ OCCLUSION_DECLARED_ALPHA_MIN_SEC,
491
+ OCCLUSION_MIN_COVERAGE,
492
+ OCCLUSION_MIN_SEC,
493
+ ROOM_CHANGE_MAX,
494
+ SFX_VARIETY_MIN_HITS,
495
+ SFX_VARIETY_MIN_RATIO,
496
+ STATIC_HOLD_MAX_SEC,
497
+ analyzeHold,
498
+ cardIsOpaque,
499
+ cardMotionSeconds,
500
+ compositionFps,
501
+ declaredAlphaPolicy,
502
+ detectBackdropOcclusion,
503
+ detectCohesionAdvisories,
504
+ detectCraftAdvisories,
505
+ detectDeadHolds,
506
+ detectFloorAdvisories,
507
+ detectMontagePacing,
508
+ detectRoomChanges,
509
+ detectSfxSameSoundReuse,
510
+ detectSilentNarration,
511
+ detectStaticHolds,
512
+ floorAdvisoryLine,
513
+ floorAdvisoryLines,
514
+ keyframeMotionSeconds,
515
+ layeredVisualClips,
516
+ overlayDesignedSeconds
517
+ };
@@ -130,7 +130,7 @@ import {
130
130
  selectSceneBakeScale,
131
131
  verifySceneVideoPresentationIndexV1,
132
132
  verifySealedSceneShotPlanV1
133
- } from "../chunk-XACXNDFL.js";
133
+ } from "../chunk-CL5Y6V6K.js";
134
134
  import {
135
135
  MIB,
136
136
  SCENE_CONTEXT_GL_BUDGET_BYTES,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  playableSpans
3
- } from "./chunk-L5XECASW.js";
3
+ } from "./chunk-D72CKEOC.js";
4
4
  import "./chunk-QH3YI3ND.js";
5
+ import "./chunk-IYZEK6WY.js";
5
6
  import "./chunk-QISFYLBB.js";
6
7
  import "./chunk-VZ7TYX6Z.js";
7
- import "./chunk-IYZEK6WY.js";
8
8
  import "./chunk-DENF4TPI.js";
9
9
  import {
10
10
  createHeadlessStore,
@@ -35,7 +35,7 @@ import "./chunk-7PA7FGLD.js";
35
35
  import {
36
36
  SCENE_SLOT_MEDIA_FORMATS,
37
37
  SceneShotSpecV2
38
- } from "./chunk-XACXNDFL.js";
38
+ } from "./chunk-CL5Y6V6K.js";
39
39
  import "./chunk-2L7PYYJK.js";
40
40
  import {
41
41
  introducedCaptionFaceIssues
@@ -66,21 +66,21 @@ import {
66
66
  sliceTranscriptWords,
67
67
  splitIntoSentences,
68
68
  validateComposition
69
- } from "../chunk-L5XECASW.js";
69
+ } from "../chunk-D72CKEOC.js";
70
70
  import {
71
71
  buildEasedZooms,
72
72
  buildPannedCenters,
73
73
  easeOutCubic
74
74
  } from "../chunk-QH3YI3ND.js";
75
+ import "../chunk-IYZEK6WY.js";
75
76
  import "../chunk-QISFYLBB.js";
76
77
  import "../chunk-VZ7TYX6Z.js";
77
- import "../chunk-IYZEK6WY.js";
78
78
  import "../chunk-DENF4TPI.js";
79
79
  import "../chunk-KYRDKK2D.js";
80
80
  import "../chunk-WREYUH2E.js";
81
81
  import "../chunk-QSWONRX2.js";
82
82
  import "../chunk-7PA7FGLD.js";
83
- import "../chunk-XACXNDFL.js";
83
+ import "../chunk-CL5Y6V6K.js";
84
84
  import "../chunk-2L7PYYJK.js";
85
85
  import "../chunk-AVTEPAWI.js";
86
86
  import "../chunk-2ESYSVXG.js";
package/dist/core.js CHANGED
@@ -10,17 +10,17 @@ import {
10
10
  ensureEven,
11
11
  extractCaptionWordsFromDoc,
12
12
  isEmphasized
13
- } from "./chunk-L5XECASW.js";
13
+ } from "./chunk-D72CKEOC.js";
14
14
  import "./chunk-QH3YI3ND.js";
15
+ import "./chunk-IYZEK6WY.js";
15
16
  import "./chunk-QISFYLBB.js";
16
17
  import "./chunk-VZ7TYX6Z.js";
17
- import "./chunk-IYZEK6WY.js";
18
18
  import "./chunk-DENF4TPI.js";
19
19
  import "./chunk-KYRDKK2D.js";
20
20
  import "./chunk-WREYUH2E.js";
21
21
  import "./chunk-QSWONRX2.js";
22
22
  import "./chunk-7PA7FGLD.js";
23
- import "./chunk-XACXNDFL.js";
23
+ import "./chunk-CL5Y6V6K.js";
24
24
  import "./chunk-2L7PYYJK.js";
25
25
  import "./chunk-AVTEPAWI.js";
26
26
  import "./chunk-2ESYSVXG.js";
@@ -2,7 +2,7 @@ import {
2
2
  querySchemaShape,
3
3
  serializeQuery,
4
4
  waitForJob
5
- } from "../chunk-JNOM4FW3.js";
5
+ } from "../chunk-J53SGAWP.js";
6
6
  import {
7
7
  CURATED_TOOLS
8
8
  } from "../chunk-5YO4LAB7.js";
@@ -16,7 +16,7 @@ import {
16
16
  opById,
17
17
  safeParse,
18
18
  sniffImageMediaType
19
- } from "../chunk-GA6NRDK3.js";
19
+ } from "../chunk-DQRJJ2YN.js";
20
20
  import "../chunk-2ESYSVXG.js";
21
21
 
22
22
  // ../mcp/src/adapter.ts
package/dist/mcp/core.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  text,
19
19
  textWithData,
20
20
  withMedia
21
- } from "../chunk-GA6NRDK3.js";
21
+ } from "../chunk-DQRJJ2YN.js";
22
22
  import "../chunk-2ESYSVXG.js";
23
23
  export {
24
24
  __resetPreviewCache,
@@ -0,0 +1 @@
1
+ export * from "../_members/mcp/sceneShot.js";
@@ -0,0 +1,18 @@
1
+ import {
2
+ SCENE_SHOT_LIMITS,
3
+ SCENE_SHOT_RESOURCE_DESCRIPTION,
4
+ SCENE_SHOT_RESOURCE_URI,
5
+ SCENE_SHOT_RULES,
6
+ sceneBrandTokens,
7
+ sceneShotContract
8
+ } from "../chunk-P472GIBJ.js";
9
+ import "../chunk-DQRJJ2YN.js";
10
+ import "../chunk-2ESYSVXG.js";
11
+ export {
12
+ SCENE_SHOT_LIMITS,
13
+ SCENE_SHOT_RESOURCE_DESCRIPTION,
14
+ SCENE_SHOT_RESOURCE_URI,
15
+ SCENE_SHOT_RULES,
16
+ sceneBrandTokens,
17
+ sceneShotContract
18
+ };
@@ -7,10 +7,11 @@ import {
7
7
  registerRawTools,
8
8
  registerSkillPrompts,
9
9
  titleCase
10
- } from "../chunk-BLDL2EKL.js";
11
- import "../chunk-JNOM4FW3.js";
10
+ } from "../chunk-BUK4P6AW.js";
11
+ import "../chunk-P472GIBJ.js";
12
+ import "../chunk-J53SGAWP.js";
12
13
  import "../chunk-5YO4LAB7.js";
13
- import "../chunk-GA6NRDK3.js";
14
+ import "../chunk-DQRJJ2YN.js";
14
15
  import "../chunk-2ESYSVXG.js";
15
16
  export {
16
17
  RAW_EXCLUDED_OPERATION_IDS,
package/dist/mcp.js CHANGED
@@ -7,10 +7,11 @@ import {
7
7
  buildServer,
8
8
  registerCuratedTools,
9
9
  registerRawTools
10
- } from "./chunk-BLDL2EKL.js";
11
- import "./chunk-JNOM4FW3.js";
10
+ } from "./chunk-BUK4P6AW.js";
11
+ import "./chunk-P472GIBJ.js";
12
+ import "./chunk-J53SGAWP.js";
12
13
  import "./chunk-5YO4LAB7.js";
13
- import "./chunk-GA6NRDK3.js";
14
+ import "./chunk-DQRJJ2YN.js";
14
15
  import "./chunk-2ESYSVXG.js";
15
16
  export {
16
17
  API_URLS,