@clipwright/core 0.35.0 → 0.37.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 (210) hide show
  1. package/README.md +3 -2
  2. package/dist/account-actors.d.ts +5 -6
  3. package/dist/account-actors.js +5 -6
  4. package/dist/account-actors.js.map +1 -1
  5. package/dist/actors.d.ts +8 -0
  6. package/dist/actors.js +2 -0
  7. package/dist/actors.js.map +1 -1
  8. package/dist/api-failure.d.ts +1 -1
  9. package/dist/api-failure.js +2 -2
  10. package/dist/api-failure.js.map +1 -1
  11. package/dist/aspect.d.ts +1 -1
  12. package/dist/aspect.js +5 -5
  13. package/dist/aspect.js.map +1 -1
  14. package/dist/assets.d.ts +13 -5
  15. package/dist/assets.js +7 -6
  16. package/dist/assets.js.map +1 -1
  17. package/dist/billing-terms.d.ts +1 -1
  18. package/dist/billing-terms.js +1 -1
  19. package/dist/billing-terms.js.map +1 -1
  20. package/dist/canvas-fit.js +1 -1
  21. package/dist/canvas-fit.js.map +1 -1
  22. package/dist/caption-geometry.d.ts +43 -0
  23. package/dist/caption-geometry.js +41 -0
  24. package/dist/caption-geometry.js.map +1 -0
  25. package/dist/contract-dispositions.d.ts +1005 -43
  26. package/dist/contract-dispositions.js +36 -16
  27. package/dist/contract-dispositions.js.map +1 -1
  28. package/dist/contracts/v1/workflow-brief.d.ts +2045 -38
  29. package/dist/contracts/v1/workflow-brief.js +2 -2
  30. package/dist/contracts/v1/workflow-brief.js.map +1 -1
  31. package/dist/contracts/v1/workflow-catalog.d.ts +2196 -72
  32. package/dist/contracts/v1/workflow-client.d.ts +1249 -70
  33. package/dist/contracts/v1/workflow-execution.d.ts +3 -3
  34. package/dist/contracts/v1/workflow-plan.d.ts +3294 -108
  35. package/dist/contracts/v1/workflow-run-read.d.ts +2 -2
  36. package/dist/edit-billing.d.ts +1 -0
  37. package/dist/edit-billing.js +3 -2
  38. package/dist/edit-billing.js.map +1 -1
  39. package/dist/edit-candidate.d.ts +2 -2
  40. package/dist/edit-candidate.js +2 -2
  41. package/dist/edit-candidate.js.map +1 -1
  42. package/dist/edit-document.d.ts +22 -0
  43. package/dist/edit-document.js +3 -0
  44. package/dist/edit-document.js.map +1 -1
  45. package/dist/edit-long-form.d.ts +2 -2
  46. package/dist/edit-long-form.js +3 -3
  47. package/dist/edit-long-form.js.map +1 -1
  48. package/dist/edit-revision.d.ts +10 -1
  49. package/dist/edit-revision.js +1 -1
  50. package/dist/edit-revision.js.map +1 -1
  51. package/dist/edit-rules.d.ts +1 -0
  52. package/dist/edit-rules.js +3 -0
  53. package/dist/edit-rules.js.map +1 -1
  54. package/dist/edit-shots.d.ts +2 -0
  55. package/dist/edit-validate.d.ts +4 -2
  56. package/dist/edit-validate.js +18 -2
  57. package/dist/edit-validate.js.map +1 -1
  58. package/dist/endpoint-notes.js +12 -8
  59. package/dist/endpoint-notes.js.map +1 -1
  60. package/dist/endpoints.d.ts +1 -1
  61. package/dist/endpoints.js +4 -1
  62. package/dist/endpoints.js.map +1 -1
  63. package/dist/errors.d.ts +3 -2
  64. package/dist/errors.js +5 -2
  65. package/dist/errors.js.map +1 -1
  66. package/dist/face-billing.d.ts +1 -1
  67. package/dist/face-billing.js +3 -3
  68. package/dist/face-billing.js.map +1 -1
  69. package/dist/face-delivery.d.ts +1 -1
  70. package/dist/face-delivery.js +1 -1
  71. package/dist/face-delivery.js.map +1 -1
  72. package/dist/faceless-media-record.d.ts +28 -1
  73. package/dist/faceless-media-record.js +16 -2
  74. package/dist/faceless-media-record.js.map +1 -1
  75. package/dist/faceless-price.d.ts +25 -6
  76. package/dist/faceless-price.js +13 -6
  77. package/dist/faceless-price.js.map +1 -1
  78. package/dist/faceless-public-plan.d.ts +6 -2
  79. package/dist/faceless-public-plan.js +11 -7
  80. package/dist/faceless-public-plan.js.map +1 -1
  81. package/dist/faceless-public.d.ts +108 -1
  82. package/dist/faceless-public.js +38 -5
  83. package/dist/faceless-public.js.map +1 -1
  84. package/dist/faceless-voices.d.ts +6 -6
  85. package/dist/faceless-voices.js +5 -5
  86. package/dist/faceless-voices.js.map +1 -1
  87. package/dist/feedback.d.ts +29 -0
  88. package/dist/feedback.js +29 -0
  89. package/dist/feedback.js.map +1 -0
  90. package/dist/generations.d.ts +2 -19
  91. package/dist/generations.js +28 -48
  92. package/dist/generations.js.map +1 -1
  93. package/dist/index.d.ts +5 -0
  94. package/dist/index.js +6 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/long-form-revision.d.ts +8 -0
  97. package/dist/media-timebase.js +2 -2
  98. package/dist/media-timebase.js.map +1 -1
  99. package/dist/openapi.d.ts +4 -3
  100. package/dist/openapi.js +8 -3
  101. package/dist/openapi.js.map +1 -1
  102. package/dist/parts-pricing.d.ts +3 -3
  103. package/dist/parts-pricing.js +6 -6
  104. package/dist/parts-pricing.js.map +1 -1
  105. package/dist/presenter-beats.d.ts +2 -2
  106. package/dist/presenter-beats.js +2 -2
  107. package/dist/presenter-beats.js.map +1 -1
  108. package/dist/prices.js +5 -1
  109. package/dist/prices.js.map +1 -1
  110. package/dist/public-responses.d.ts +1 -1
  111. package/dist/public-responses.js +1 -1
  112. package/dist/public-responses.js.map +1 -1
  113. package/dist/rate-limit.d.ts +2 -2
  114. package/dist/rate-limit.js +2 -2
  115. package/dist/rate-limit.js.map +1 -1
  116. package/dist/reference-analysis.d.ts +636 -43
  117. package/dist/reference-analysis.js +262 -22
  118. package/dist/reference-analysis.js.map +1 -1
  119. package/dist/reference-display.d.ts +36 -0
  120. package/dist/reference-display.js +78 -0
  121. package/dist/reference-display.js.map +1 -0
  122. package/dist/reference-kinds.d.ts +3 -1
  123. package/dist/reference-kinds.js +3 -1
  124. package/dist/reference-kinds.js.map +1 -1
  125. package/dist/reference-profile.d.ts +2394 -40
  126. package/dist/reference-profile.js +192 -17
  127. package/dist/reference-profile.js.map +1 -1
  128. package/dist/reference-staging.d.ts +71 -57
  129. package/dist/reference-staging.js +9 -6
  130. package/dist/reference-staging.js.map +1 -1
  131. package/dist/reference-voice.d.ts +1 -1
  132. package/dist/reference-voice.js +3 -3
  133. package/dist/reference-voice.js.map +1 -1
  134. package/dist/render-backend.d.ts +1 -1
  135. package/dist/resolved-timeline.d.ts +201 -4
  136. package/dist/resolved-timeline.js +20 -8
  137. package/dist/resolved-timeline.js.map +1 -1
  138. package/dist/run-static-warnings.js +1 -1
  139. package/dist/run-static-warnings.js.map +1 -1
  140. package/dist/runs.d.ts +3 -3
  141. package/dist/runs.js +3 -3
  142. package/dist/runs.js.map +1 -1
  143. package/dist/scene-place.d.ts +225 -0
  144. package/dist/scene-place.js +120 -0
  145. package/dist/scene-place.js.map +1 -0
  146. package/dist/semantic-inserts.d.ts +1 -1
  147. package/dist/semantic-inserts.js +2 -2
  148. package/dist/semantic-inserts.js.map +1 -1
  149. package/dist/skills.d.ts +4685 -30
  150. package/dist/skills.js +153 -37
  151. package/dist/skills.js.map +1 -1
  152. package/dist/templates/index.d.ts +15 -5
  153. package/dist/templates/index.js +50 -6
  154. package/dist/templates/index.js.map +1 -1
  155. package/dist/templates/size.d.ts +1 -1
  156. package/dist/templates/size.js +1 -1
  157. package/dist/templates/size.js.map +1 -1
  158. package/dist/templates/sounds.js +1 -1
  159. package/dist/templates/sounds.js.map +1 -1
  160. package/dist/ugc-call.d.ts +1 -1
  161. package/dist/ugc-call.js +2 -2
  162. package/dist/ugc-call.js.map +1 -1
  163. package/dist/ugc-delivery.d.ts +1 -1
  164. package/dist/ugc-delivery.js +1 -1
  165. package/dist/ugc-delivery.js.map +1 -1
  166. package/dist/ugc-interview.d.ts +122 -0
  167. package/dist/ugc-interview.js +260 -0
  168. package/dist/ugc-interview.js.map +1 -0
  169. package/dist/ugc-layers.d.ts +88 -6
  170. package/dist/ugc-layers.js +193 -5
  171. package/dist/ugc-layers.js.map +1 -1
  172. package/dist/ugc-video-media.d.ts +1 -1
  173. package/dist/ugc-video-media.js +1 -1
  174. package/dist/ugc-video-media.js.map +1 -1
  175. package/dist/uploads.d.ts +2 -2
  176. package/dist/uploads.js +2 -2
  177. package/dist/uploads.js.map +1 -1
  178. package/dist/vendor-response.d.ts +1 -1
  179. package/dist/vendor-response.js +1 -1
  180. package/dist/vendor-response.js.map +1 -1
  181. package/dist/voice-admission.d.ts +1450 -498
  182. package/dist/voice-admission.js +22 -5
  183. package/dist/voice-admission.js.map +1 -1
  184. package/dist/voice-listing.d.ts +4 -4
  185. package/dist/voice-listing.js +4 -5
  186. package/dist/voice-listing.js.map +1 -1
  187. package/dist/voice-pace.js +1 -1
  188. package/dist/voice-pace.js.map +1 -1
  189. package/dist/voices.d.ts +12 -12
  190. package/dist/voices.js +41 -20
  191. package/dist/voices.js.map +1 -1
  192. package/dist/workflows/methods/faceless-explainer.d.ts +612 -15
  193. package/dist/workflows/methods/kinetic-launch.d.ts +612 -15
  194. package/dist/workflows/methods/kinetic-launch.js +1 -1
  195. package/dist/workflows/methods/product-image-story.d.ts +612 -15
  196. package/dist/workflows/methods/reference-remake.d.ts +612 -15
  197. package/dist/workflows/methods/shared.d.ts +612 -15
  198. package/dist/workflows/methods/shared.js +2 -2
  199. package/dist/workflows/methods/shared.js.map +1 -1
  200. package/dist/workflows/methods/ugc-faq-answer.d.ts +612 -15
  201. package/dist/workflows/methods/ugc-product-intro.d.ts +612 -15
  202. package/dist/workflows/methods/website-proof-demo.d.ts +612 -15
  203. package/dist/workflows/methods/website-proof-demo.js +1 -1
  204. package/dist/workflows/reference-remake.d.ts +152 -35
  205. package/dist/workflows/reference-remake.js +188 -23
  206. package/dist/workflows/reference-remake.js.map +1 -1
  207. package/dist/workflows/validate.js +4 -4
  208. package/dist/workflows/validate.js.map +1 -1
  209. package/package.json +2 -3
  210. package/CHANGELOG.md +0 -1500
@@ -1,6 +1,6 @@
1
1
  import { compositionReasons, defineWorkflowMethod, nativeQa, verticalOutput } from './shared.js';
2
2
  export const websiteProofDemo = defineWorkflowMethod({
3
- id: 'website-proof-demo', version: 1, content_digest: '33db167354625c4588aedfe3af99894d3e2597744dead2e5f6095fff2e172ddf', authoring_status: 'ready', title: 'Demonstrate a verified website state',
3
+ id: 'website-proof-demo', version: 1, content_digest: '1305004523261f45a279ac91d73b8aafcced3c9e665f9ed68e2d280ce8f5b3f7', authoring_status: 'ready', title: 'Demonstrate a verified website state',
4
4
  purpose: 'Show a real action and result using approved captures and claims tied to a known product state.',
5
5
  required_inputs: ['audience', 'message', 'script', 'assets'], required_features: ['exact_script', 'verified_captures', 'authored_scenes'],
6
6
  script_rules: { structure: ['task', 'action', 'verified_result', 'next_action'], instructions: [
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { type ReferenceProfile } from '../reference-profile.js';
2
+ import { type ReferenceProfileInput as ReferenceProfile } from '../reference-profile.js';
3
3
  import type { CapabilityProfile } from '../contracts/v1/workflow-catalog.js';
4
4
  import { type WorkflowPlanDocument } from '../contracts/v1/workflow-plan.js';
5
5
  import { type WorkflowIssue } from '../contracts/v1/workflow-primitives.js';
@@ -60,19 +60,24 @@ export declare const planReferenceRemake: z.ZodObject<{
60
60
  unmapped: "unmapped";
61
61
  }>;
62
62
  source_kind: z.ZodEnum<{
63
+ banner: "banner";
63
64
  before_after_split: "before_after_split";
64
65
  callout: "callout";
65
66
  checklist: "checklist";
67
+ comment_box: "comment_box";
66
68
  compare: "compare";
69
+ concept_card: "concept_card";
67
70
  end_card: "end_card";
68
71
  insert_media: "insert_media";
69
72
  layout_split: "layout_split";
73
+ logo_card: "logo_card";
70
74
  lower_third: "lower_third";
71
75
  notification: "notification";
72
76
  other: "other";
73
77
  overlay_media: "overlay_media";
74
78
  particles: "particles";
75
79
  phone_call: "phone_call";
80
+ price_card: "price_card";
76
81
  product_ui: "product_ui";
77
82
  progress: "progress";
78
83
  quote: "quote";
@@ -154,6 +159,43 @@ export declare const planReferenceRemake: z.ZodObject<{
154
159
  reacting: "reacting";
155
160
  speaking: "speaking";
156
161
  }>;
162
+ key_frame_s: z.ZodOptional<z.ZodNumber>;
163
+ frames_s: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
164
+ speaker: z.ZodOptional<z.ZodNullable<z.ZodString>>;
165
+ speakers: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ id: z.ZodString;
167
+ seconds: z.ZodNumber;
168
+ }, z.core.$strict>>>;
169
+ gaze: z.ZodOptional<z.ZodObject<{
170
+ direction: z.ZodEnum<{
171
+ camera: "camera";
172
+ closed: "closed";
173
+ down: "down";
174
+ mixed: "mixed";
175
+ not_visible: "not_visible";
176
+ off_left: "off_left";
177
+ off_right: "off_right";
178
+ up: "up";
179
+ }>;
180
+ frames_s: z.ZodArray<z.ZodNumber>;
181
+ }, z.core.$strict>>;
182
+ gestures: z.ZodOptional<z.ZodArray<z.ZodObject<{
183
+ kind: z.ZodEnum<{
184
+ both_hands_to_viewer: "both_hands_to_viewer";
185
+ count_on_fingers: "count_on_fingers";
186
+ hold_object: "hold_object";
187
+ open_palms: "open_palms";
188
+ other: "other";
189
+ point_at_element: "point_at_element";
190
+ point_at_viewer: "point_at_viewer";
191
+ shrug: "shrug";
192
+ thumbs_up: "thumbs_up";
193
+ wave: "wave";
194
+ }>;
195
+ at_s: z.ZodNumber;
196
+ label: z.ZodString;
197
+ frames_s: z.ZodArray<z.ZodNumber>;
198
+ }, z.core.$strict>>>;
157
199
  }, z.core.$strict>>;
158
200
  captions: z.ZodObject<{
159
201
  present: z.ZodBoolean;
@@ -171,6 +213,12 @@ export declare const planReferenceRemake: z.ZodObject<{
171
213
  punch: "punch";
172
214
  }>;
173
215
  hidden_under_graphics: z.ZodBoolean;
216
+ box: z.ZodOptional<z.ZodObject<{
217
+ left: z.ZodNumber;
218
+ top: z.ZodNumber;
219
+ width: z.ZodNumber;
220
+ height: z.ZodNumber;
221
+ }, z.core.$strict>>;
174
222
  }, z.core.$strict>;
175
223
  pacing: z.ZodObject<{
176
224
  layer_change_every_s: z.ZodNumber;
@@ -183,12 +231,6 @@ export declare const planReferenceRemake: z.ZodObject<{
183
231
  beat_index: z.ZodNumber;
184
232
  suggested_substitute: z.ZodString;
185
233
  }, z.core.$strict>>;
186
- analysis: z.ZodObject<{
187
- models: z.ZodArray<z.ZodString>;
188
- cost_usd: z.ZodOptional<z.ZodNumber>;
189
- latency_ms: z.ZodNumber;
190
- warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
191
- }, z.core.$strict>;
192
234
  voice: z.ZodOptional<z.ZodObject<{
193
235
  version: z.ZodLiteral<"voice-profile-v1">;
194
236
  method: z.ZodObject<{
@@ -342,19 +384,24 @@ export declare const planReferenceRemake: z.ZodObject<{
342
384
  beat_index: z.ZodNumber;
343
385
  graphic_index: z.ZodNullable<z.ZodNumber>;
344
386
  kind: z.ZodEnum<{
387
+ banner: "banner";
345
388
  before_after_split: "before_after_split";
346
389
  callout: "callout";
347
390
  checklist: "checklist";
391
+ comment_box: "comment_box";
348
392
  compare: "compare";
393
+ concept_card: "concept_card";
349
394
  end_card: "end_card";
350
395
  insert_media: "insert_media";
351
396
  layout_split: "layout_split";
397
+ logo_card: "logo_card";
352
398
  lower_third: "lower_third";
353
399
  notification: "notification";
354
400
  other: "other";
355
401
  overlay_media: "overlay_media";
356
402
  particles: "particles";
357
403
  phone_call: "phone_call";
404
+ price_card: "price_card";
358
405
  product_ui: "product_ui";
359
406
  progress: "progress";
360
407
  quote: "quote";
@@ -380,13 +427,13 @@ export declare const planReferenceRemake: z.ZodObject<{
380
427
  height: z.ZodNumber;
381
428
  }, z.core.$strict>>;
382
429
  rect_method: z.ZodOptional<z.ZodEnum<{
383
- "cv-before-after/v1": "cv-before-after/v1";
384
- "cv-cells/v1": "cv-cells/v1";
385
- "cv-parallax/v1": "cv-parallax/v1";
386
- "cv-template/v1": "cv-template/v1";
387
- "vision-still": "vision-still";
388
- "vision-video": "vision-video";
389
- "yunet-native/v1": "yunet-native/v1";
430
+ "estimated-still": "estimated-still";
431
+ "estimated-video": "estimated-video";
432
+ "frames-box/v1": "frames-box/v1";
433
+ "frames-face/v1": "frames-face/v1";
434
+ "frames-layer/v1": "frames-layer/v1";
435
+ "frames-motion/v1": "frames-motion/v1";
436
+ "frames-split/v1": "frames-split/v1";
390
437
  }>>;
391
438
  rects: z.ZodOptional<z.ZodArray<z.ZodObject<{
392
439
  t_s: z.ZodNumber;
@@ -431,13 +478,13 @@ export declare const planReferenceRemake: z.ZodObject<{
431
478
  displacement: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
432
479
  scale_from: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
433
480
  method: z.ZodOptional<z.ZodEnum<{
434
- "cv-before-after/v1": "cv-before-after/v1";
435
- "cv-cells/v1": "cv-cells/v1";
436
- "cv-parallax/v1": "cv-parallax/v1";
437
- "cv-template/v1": "cv-template/v1";
438
- "vision-still": "vision-still";
439
- "vision-video": "vision-video";
440
- "yunet-native/v1": "yunet-native/v1";
481
+ "estimated-still": "estimated-still";
482
+ "estimated-video": "estimated-video";
483
+ "frames-box/v1": "frames-box/v1";
484
+ "frames-face/v1": "frames-face/v1";
485
+ "frames-layer/v1": "frames-layer/v1";
486
+ "frames-motion/v1": "frames-motion/v1";
487
+ "frames-split/v1": "frames-split/v1";
441
488
  }>>;
442
489
  }, z.core.$strict>>;
443
490
  exit: z.ZodOptional<z.ZodObject<{
@@ -473,13 +520,13 @@ export declare const planReferenceRemake: z.ZodObject<{
473
520
  duration_s: z.ZodOptional<z.ZodNumber>;
474
521
  displacement: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
475
522
  method: z.ZodOptional<z.ZodEnum<{
476
- "cv-before-after/v1": "cv-before-after/v1";
477
- "cv-cells/v1": "cv-cells/v1";
478
- "cv-parallax/v1": "cv-parallax/v1";
479
- "cv-template/v1": "cv-template/v1";
480
- "vision-still": "vision-still";
481
- "vision-video": "vision-video";
482
- "yunet-native/v1": "yunet-native/v1";
523
+ "estimated-still": "estimated-still";
524
+ "estimated-video": "estimated-video";
525
+ "frames-box/v1": "frames-box/v1";
526
+ "frames-face/v1": "frames-face/v1";
527
+ "frames-layer/v1": "frames-layer/v1";
528
+ "frames-motion/v1": "frames-motion/v1";
529
+ "frames-split/v1": "frames-split/v1";
483
530
  }>>;
484
531
  }, z.core.$strict>>;
485
532
  layer: z.ZodOptional<z.ZodObject<{
@@ -491,13 +538,13 @@ export declare const planReferenceRemake: z.ZodObject<{
491
538
  unknown: "unknown";
492
539
  }>;
493
540
  method: z.ZodEnum<{
494
- "cv-before-after/v1": "cv-before-after/v1";
495
- "cv-cells/v1": "cv-cells/v1";
496
- "cv-parallax/v1": "cv-parallax/v1";
497
- "cv-template/v1": "cv-template/v1";
498
- "vision-still": "vision-still";
499
- "vision-video": "vision-video";
500
- "yunet-native/v1": "yunet-native/v1";
541
+ "estimated-still": "estimated-still";
542
+ "estimated-video": "estimated-video";
543
+ "frames-box/v1": "frames-box/v1";
544
+ "frames-face/v1": "frames-face/v1";
545
+ "frames-layer/v1": "frames-layer/v1";
546
+ "frames-motion/v1": "frames-motion/v1";
547
+ "frames-split/v1": "frames-split/v1";
501
548
  }>;
502
549
  covered_by: z.ZodArray<z.ZodEnum<{
503
550
  body: "body";
@@ -594,6 +641,76 @@ export declare const planReferenceRemake: z.ZodObject<{
594
641
  }>;
595
642
  }, z.core.$strict>>;
596
643
  }, z.core.$strict>>;
644
+ transcript: z.ZodOptional<z.ZodArray<z.ZodObject<{
645
+ text: z.ZodString;
646
+ start_s: z.ZodNumber;
647
+ end_s: z.ZodNumber;
648
+ speaker: z.ZodOptional<z.ZodString>;
649
+ }, z.core.$strict>>>;
650
+ transcript_language: z.ZodOptional<z.ZodNullable<z.ZodString>>;
651
+ hook: z.ZodOptional<z.ZodObject<{
652
+ text: z.ZodString;
653
+ end_s: z.ZodNumber;
654
+ caption: z.ZodOptional<z.ZodString>;
655
+ }, z.core.$strict>>;
656
+ speakers: z.ZodOptional<z.ZodArray<z.ZodObject<{
657
+ id: z.ZodString;
658
+ on_screen: z.ZodBoolean;
659
+ role: z.ZodEnum<{
660
+ guest: "guest";
661
+ interviewer: "interviewer";
662
+ narrator: "narrator";
663
+ other: "other";
664
+ presenter: "presenter";
665
+ }>;
666
+ word_count: z.ZodNumber;
667
+ caption: z.ZodNullable<z.ZodObject<{
668
+ colour: z.ZodNullable<z.ZodString>;
669
+ style: z.ZodString;
670
+ position: z.ZodNullable<z.ZodEnum<{
671
+ bottom: "bottom";
672
+ lower: "lower";
673
+ middle: "middle";
674
+ top: "top";
675
+ }>>;
676
+ box: z.ZodOptional<z.ZodObject<{
677
+ left: z.ZodNumber;
678
+ top: z.ZodNumber;
679
+ width: z.ZodNumber;
680
+ height: z.ZodNumber;
681
+ }, z.core.$strict>>;
682
+ }, z.core.$strict>>;
683
+ frames_s: z.ZodArray<z.ZodNumber>;
684
+ }, z.core.$strict>>>;
685
+ segments: z.ZodOptional<z.ZodArray<z.ZodObject<{
686
+ start_s: z.ZodNumber;
687
+ end_s: z.ZodNumber;
688
+ text: z.ZodString;
689
+ speaker: z.ZodString;
690
+ mouth: z.ZodOptional<z.ZodObject<{
691
+ vision: z.ZodNullable<z.ZodEnum<{
692
+ closed: "closed";
693
+ moving: "moving";
694
+ not_visible: "not_visible";
695
+ }>>;
696
+ measured: z.ZodEnum<{
697
+ moving: "moving";
698
+ not_measured: "not_measured";
699
+ still: "still";
700
+ }>;
701
+ score: z.ZodNullable<z.ZodNumber>;
702
+ }, z.core.$strict>>;
703
+ frames_s: z.ZodArray<z.ZodNumber>;
704
+ }, z.core.$strict>>>;
705
+ spellings: z.ZodOptional<z.ZodArray<z.ZodObject<{
706
+ heard: z.ZodString;
707
+ shown: z.ZodString;
708
+ start_s: z.ZodNumber;
709
+ end_s: z.ZodNumber;
710
+ }, z.core.$strict>>>;
711
+ analysis: z.ZodObject<{
712
+ warnings: z.ZodOptional<z.ZodArray<z.ZodString>>;
713
+ }, z.core.$strip>;
597
714
  }, z.core.$strict>;
598
715
  script: z.ZodString;
599
716
  actor_id: z.ZodString;
@@ -5,13 +5,15 @@ import { assetId } from '../asset-identity.js';
5
5
  import { captionLanguageOf } from '../caption-language.js';
6
6
  import { LONG_FORM_QUALIFICATION_MAX_DURATION_SEC } from '../long-form-profile.js';
7
7
  import { estimateDurationSec } from '../pacing.js';
8
- import { referenceProfile } from '../reference-profile.js';
8
+ import { referenceProfileInput } from '../reference-profile.js';
9
9
  import { narrationPaceFromVoice, PAUSE_TRIM_CEILING_S, pauseTrimFromVoice } from '../reference-voice.js';
10
10
  import { countActorShots, coverWordOf, lexicalTokens, planSemanticInserts, quoteOccurrences } from '../semantic-inserts.js';
11
11
  import { actorId, callStage, isNarratedUgcInput, makeUgcInput, makeUgcInputShape, MAX_NARRATED_ACTOR_SHOTS, ugcCall } from '../skills.js';
12
12
  import { CALL_PHONE_DEFAULTS, CALL_STAGE_SIDES } from '../ugc-call.js';
13
- import { templateFaceWarnings, templateMediaOf, templateTextRefusal } from '../templates/index.js';
14
- import { faceZone, frameCanvasOf, OVERLAY_POSITIONS, overlayRect, rectsOverlap } from '../ugc-layers.js';
13
+ import { TEMPLATE_KINDS, templateFaceWarnings, templateMediaOf, templateTextRefusal } from '../templates/index.js';
14
+ import { CAPTION_POSITION_VERTICAL, captionEdgeAt } from '../caption-geometry.js';
15
+ import { fitPlacedRect, place, plannedFace, presenterPlace, resolvePlace, sharedArea } from '../scene-place.js';
16
+ import { FACE_COVER_SHARE, faceZone, frameCanvasOf, LAYER_ENTERS, MAX_ENTER_OFFSET_S, MAX_OVERLAYS, OVERLAY_POSITIONS, overlayRect, ownPixels, rectsOverlap } from '../ugc-layers.js';
15
17
  import { isEnglishScript, measuredPaceOf, PACE_MATCH_SHARE, VOICE_PACE_RANGE, voicesNearestPace } from '../voice-pace.js';
16
18
  import { BREAK_TAG_SOURCE, FIXED_PACE_MODELS, resolveTtsModel } from '../voices.js';
17
19
  import { workflowPlanDocument } from '../contracts/v1/workflow-plan.js';
@@ -19,7 +21,7 @@ import { workflowText } from '../contracts/v1/workflow-primitives.js';
19
21
  import { queryWorkflowMethod } from './catalog.js';
20
22
  // The reference-remake compiler (#857 S2a): a profile and an accepted script become a complete plan whose native input is a make_ugc input.
21
23
  export const planReferenceRemake = z.strictObject({
22
- reference_profile: referenceProfile.describe('The reference_profile analyze_reference returned, or one you wrote to the same schema.'),
24
+ reference_profile: referenceProfileInput.describe('The reference_profile analyze_reference returned, or one you wrote to the same schema.'),
23
25
  script: workflowText(10_000).describe('The accepted narration, copied into the plan unchanged.'),
24
26
  actor_id: actorId, voice: makeUgcInputShape.voice, voice_id: makeUgcInputShape.voice_id,
25
27
  assets: z.array(z.strictObject({ asset_id: assetId, media_type: z.enum(['image', 'video']) })).max(12).default([])
@@ -35,6 +37,36 @@ const TEXT_KINDS = new Set(['title_card', 'tag']);
35
37
  const MEDIA_KINDS = { overlay_media: 'overlays', insert_media: 'inserts', layout_split: 'layout_windows' };
36
38
  const MARKER = 'ref-long-form';
37
39
  const GENERATE = 'pass a ready ast_* asset, or make one separately with generate_image or generate_video (its own quote), then compile again';
40
+ /** The saved plan keeps the reference's techniques, not its words: the remake copies no line of it, and the analysis keeps them already. */
41
+ const withoutWords = ({ transcript: _transcript, hook: _hook, ...profile }) => profile;
42
+ /** A layer sits under the captions when its top is at most this share of the height below them and its centre this share of the width off theirs. */
43
+ const UNDER_CAPTIONS = 0.03;
44
+ /** With no caption box measured, a centred layer whose top lies this far below where top-anchored captions start: a line to a short block. */
45
+ const UNMEASURED_CAPTIONS_BELOW = [0.03, 0.15];
46
+ const ENTER_OF = { pop: 'pop', fade: 'fade', slide: 'slide', scale_up: 'scale_up', tilt_in: 'tilt_in' };
47
+ const EXIT_OF = { cut: 'cut', fade: 'fade', slide: 'slide', pop: 'pop_out' };
48
+ const FRAME = { left: 0, top: 0, width: 1, height: 1 };
49
+ const round = (value, places = 4) => Math.round(value * 10 ** places) / 10 ** places;
50
+ const median = (values) => {
51
+ const sorted = [...values].sort((a, b) => a - b), mid = sorted.length >> 1;
52
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
53
+ };
54
+ /** The reference presenter's face while its graphics show (#973 S5): each element's box over its face-unit twin gives the face; freezes stand in. */
55
+ function referenceFaceOf(profile) {
56
+ const staging = profile.staging, { width, height } = profile.source;
57
+ if (!staging)
58
+ return undefined;
59
+ const twins = staging.elements.flatMap(({ rect_hold: rect, rect_hold_face: twin }) => {
60
+ if (!rect || !twin)
61
+ return [];
62
+ const unit = rect.height / twin.height;
63
+ return [{ height: unit, x: rect.left - (twin.left * unit * height) / width, y: rect.top - twin.top * unit }];
64
+ });
65
+ const faces = twins.length ? twins : staging.freezes.map(({ face_box_start: box }) => ({ height: box.height, x: box.left + box.width / 2, y: box.top + box.height / 2 }));
66
+ if (!faces.length)
67
+ return undefined;
68
+ return { face_height: round(median(faces.map(face => face.height))), face_centre: [round(median(faces.map(face => face.x))), round(median(faces.map(face => face.y)))] };
69
+ }
38
70
  export function referenceScriptWords(script) {
39
71
  const tags = [...script.matchAll(new RegExp(BREAK_TAG_SOURCE, 'g'))].map(match => [match.index, match.index + match[0].length]);
40
72
  const words = [];
@@ -163,7 +195,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
163
195
  }
164
196
  }
165
197
  const templatesOff = executor?.field_dispositions.templates === 'rejected';
166
- // The reference's caption height, where the deployment draws caption_position (#884); otherwise the style's own place.
198
+ // The reference's caption height, where the run draws caption_position (#884); otherwise the style's own place.
167
199
  const captionsPlaced = executor?.field_dispositions.caption_position === 'implemented';
168
200
  // A wide reference asks for the wide view where it can render: honoured field, a library actor (personal ones have no wide view); graphics follow that face (#884 S5d).
169
201
  const framing = profile.presenter_framing === 'wide' && executor?.field_dispositions.presenter_framing === 'implemented'
@@ -213,6 +245,81 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
213
245
  return ranges.filter(range => words.slice(range.start, range.end).some(word => !word.tag));
214
246
  };
215
247
  const wordAt = (span, share) => quote(Math.min(span.end - 1, span.start + Math.max(1, Math.floor(share * (span.end - span.start)))), 2, span.end);
248
+ // Measured staging drawn as the reference drew it (#973 S5): a box under the captions, on the face or on the frame, its entry from our list.
249
+ const canvas = frameCanvasOf(DEFAULT_ASPECT_RATIO), measuredFace = referenceFaceOf(profile);
250
+ const presenterOk = longForm && executor?.field_dispositions.presenter_place === 'implemented';
251
+ const presenter = measuredFace && presenterOk ? presenterPlace.safeParse(measuredFace).data : undefined;
252
+ if (measuredFace && !presenter) {
253
+ note(0, `the reference presenter's face, ${measuredFace.face_height} of the frame's height`, !longForm
254
+ ? 'the actor\'s own framing: framing the presenter needs long-form qualification, not available right now'
255
+ : presenterOk ? 'the actor\'s own framing: the face size is outside what the presenter can be framed to' : 'the actor\'s own framing');
256
+ }
257
+ const face = plannedFace(DEFAULT_ASPECT_RATIO, canvas, framing, presenter);
258
+ /** Elements whose staging the plan draws or names on its own; the rest get the one note below. */
259
+ const drawn = new Set();
260
+ const gaps = { entry: [], exit: [], styles: new Map(), templates: [], inferred: [] };
261
+ // A box never measured on the frames is an estimate, and a note says so (#930 S2).
262
+ const sourceOf = (element) => element.rect_method?.startsWith('estimated') ? 'vision-pass' : 'measured';
263
+ const elementOf = (beat, graphic) => profile.staging?.elements.find(entry => entry.beat_index === beat && entry.graphic_index === graphic && entry.rect_hold);
264
+ const captionsSeen = profile.captions.present && !profile.captions.hidden_under_graphics;
265
+ const underCaptions = (rect) => {
266
+ const centre = rect.left + rect.width / 2, box = profile.captions.box;
267
+ if (!captionsSeen)
268
+ return undefined;
269
+ if (box) {
270
+ const gap = rect.top - box.top - box.height;
271
+ return Math.abs(centre - box.left - box.width / 2) <= UNDER_CAPTIONS && gap >= -0.005 && gap <= UNDER_CAPTIONS ? { gap: round(Math.max(0, gap)) } : undefined;
272
+ }
273
+ // No box: caption lines are centred, and a top-anchored block starts at its position's height while a bottom-anchored one ends there.
274
+ const at = CAPTION_POSITION_VERTICAL[profile.captions.position] / 100, topEdge = captionEdgeAt(at * 100) === 'top';
275
+ const [low, high] = topEdge ? [at + UNMEASURED_CAPTIONS_BELOW[0], at + UNMEASURED_CAPTIONS_BELOW[1]] : [at - 0.005, at + UNDER_CAPTIONS];
276
+ return Math.abs(centre - 0.5) <= UNDER_CAPTIONS && rect.top >= low && rect.top <= high ? {} : undefined;
277
+ };
278
+ /** The element's place on our presenter, or 'face' when it would cover our face; `own` is a template's own rect, which never stands by captions. */
279
+ const stagedPlace = (element, own) => {
280
+ const rect = element.rect_hold, under = own ? undefined : underCaptions(rect);
281
+ const parsed = place.safeParse(under ? { relative_to: 'captions', side: 'below', ...(under.gap === undefined ? {} : { gap: under.gap }), width: round(rect.width) }
282
+ : element.rect_hold_face ? { relative_to: 'face', rect: element.rect_hold_face } : { relative_to: 'frame', rect });
283
+ if (!parsed.success)
284
+ return undefined;
285
+ if (under)
286
+ return { place: parsed.data, inferred: under.gap === undefined };
287
+ const box = resolvePlace(parsed.data, { canvas, face }), shown = own ? fitPlacedRect(box, ownPixels(own, canvas), canvas) : box;
288
+ if (sharedArea(shown, FRAME) < (shown.width * shown.height) / 2)
289
+ return undefined;
290
+ return sharedArea(shown, face) > FACE_COVER_SHARE * face.width * face.height ? 'face' : { place: parsed.data, inferred: false };
291
+ };
292
+ /** The word the reference showed it on, at the same share of our beat's words, and its measured lead or lag on that word. */
293
+ const enterOf = (element, beat, span) => {
294
+ const shown = element.entry?.first_visible_s, source = profile.beats[beat];
295
+ if (shown === undefined)
296
+ return {};
297
+ const said = (profile.transcript ?? []).filter(word => word.start_s >= source.start_s && word.start_s < source.end_s);
298
+ let at = 0;
299
+ said.forEach((word, index) => { if (word.start_s <= shown)
300
+ at = index; });
301
+ const share = said.length ? (at + 0.5) / said.length : Math.min(1, Math.max(0, (shown - source.start_s) / (source.end_s - source.start_s)));
302
+ const found = quote(span.start + Math.min(span.end - span.start - 1, Math.floor(share * (span.end - span.start))), 2, span.end);
303
+ if (!found)
304
+ return {};
305
+ const offset = said.length ? round(Math.min(MAX_ENTER_OFFSET_S, Math.max(-MAX_ENTER_OFFSET_S, shown - said[at].start_s)), 2) : 0;
306
+ return { anchor: found.anchor, cover_words: span.end - found.start, ...(offset ? { enter_offset_s: offset } : {}) };
307
+ };
308
+ const motionOf = (element, beat) => {
309
+ const style = element.entry?.style, leave = element.exit?.style, enter = style && ENTER_OF[style], exit = leave && EXIT_OF[leave];
310
+ const unmapped = (what) => gaps.styles.set(what, [...gaps.styles.get(what) ?? [], beat]);
311
+ if (!style || style === 'unknown')
312
+ gaps.entry.push(beat);
313
+ else if (!enter)
314
+ unmapped(`${style} entries`);
315
+ if (!leave || leave === 'unknown')
316
+ gaps.exit.push(beat);
317
+ else if (!exit)
318
+ unmapped(`${leave} exits`);
319
+ const duration = element.entry?.duration_s;
320
+ return { ...(enter ? { enter, ...(duration ? { enter_duration_s: Math.min(1.5, Math.max(0.05, round(duration, 2))) } : {}) } : {}),
321
+ ...(exit && exit !== 'cut' ? { exit } : {}) };
322
+ };
216
323
  const spans = planSpans({ profile, words });
217
324
  const layers = [];
218
325
  const pool = [...request.assets], images = () => pool.filter(asset => asset.media_type === 'image').length;
@@ -240,29 +347,52 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
240
347
  const kind = graphic.kind;
241
348
  if (kind === 'particles' || kind === 'unmapped')
242
349
  continue;
243
- if (kind in MEDIA_KINDS) {
350
+ // A logo card is a brand picture: it shows as an overlay while an image is left in assets, else as the text card it maps to (#973 S5).
351
+ const room = layers.filter(layer => layer.field === 'overlays').length < MAX_OVERLAYS;
352
+ const wantsLogo = graphic.source_kind === 'logo_card' && longForm && !layered, logo = wantsLogo && images() > 0 && room;
353
+ if (wantsLogo && !logo) {
354
+ note(index, `logo_card (${room ? 'no image left in assets' : `a run shows at most ${MAX_OVERLAYS} overlays`})`, 'a text card with its words');
355
+ }
356
+ if (kind in MEDIA_KINDS || logo) {
357
+ const media = logo ? 'logo_card' : kind;
244
358
  if (layered) {
245
359
  note(index, `${kind} (one media layer per beat)`, 'the first one shows here');
246
360
  continue;
247
361
  }
362
+ if (MEDIA_KINDS[kind] === 'overlays' && !room) {
363
+ note(index, `${kind} (a run shows at most ${MAX_OVERLAYS} overlays)`, 'the presenter stays on screen on these words');
364
+ continue;
365
+ }
248
366
  if (pool.length === 0) {
249
367
  note(index, `${kind} (no image or video left in assets)`, GENERATE);
250
368
  continue;
251
369
  }
252
370
  if (!longForm) {
253
- note(index, `${kind} (media layers need long-form qualification on this deployment)`, 'the presenter stays on screen on these words');
371
+ note(index, `${kind} (media layers need long-form qualification, not available right now)`, 'the presenter stays on screen on these words');
372
+ continue;
373
+ }
374
+ const asset = take(logo), field = logo ? 'overlays' : MEDIA_KINDS[kind];
375
+ const { kind: _kind, ...item } = { ...envelope(media), asset_id: asset.asset_id, media_type: asset.media_type };
376
+ const element = field === 'overlays' ? elementOf(index, beat.graphics.indexOf(graphic)) : undefined, staged = element && stagedPlace(element);
377
+ if (element && staged)
378
+ drawn.add(element.id);
379
+ layered = true;
380
+ if (element && staged && staged !== 'face') {
381
+ if (staged.inferred)
382
+ gaps.inferred.push(index);
383
+ layers.push({ field, beat: index, kind: media, item: { ...item, ...enterOf(element, index, span), ...motionOf(element, index), place: staged.place } });
254
384
  continue;
255
385
  }
256
- const asset = take(), field = MEDIA_KINDS[kind], base = { ...envelope(kind), asset_id: asset.asset_id, media_type: asset.media_type };
257
- const { kind: _kind, ...item } = base;
258
386
  const position = graphic.position.replace(/^split_/, ''), wanted = OVERLAY_POSITIONS.includes(position) ? position : 'top_right';
259
387
  // Off the face: the reference place, else a corner on its side (right by default), a band last.
260
388
  const side = wanted.endsWith('left') ? 'left' : 'right', rank = (place) => place.endsWith(side) ? 0 : place.includes('_') ? 1 : 2;
261
389
  const clear = (place) => !rectsOverlap(overlayRect(place, frameCanvasOf(DEFAULT_ASPECT_RATIO)), faceZone(DEFAULT_ASPECT_RATIO, framing));
262
390
  const placed = clear(wanted) ? wanted : [...OVERLAY_POSITIONS].sort((left, right) => rank(left) - rank(right)).find(clear) ?? wanted;
263
- if (field === 'overlays' && placed !== position)
264
- note(index, `${kind} at ${graphic.position}`, `${placed}, to keep the face clear`);
265
- layers.push({ field, beat: index, kind, item: field === 'overlays' ? { ...item, position: placed }
391
+ if (staged === 'face')
392
+ note(index, `${media}: its ${sourceOf(element)} place, over the presenter's face`, `${placed}, to keep the face clear`);
393
+ else if (field === 'overlays' && placed !== position)
394
+ note(index, `${media} at ${graphic.position}`, `${placed}, to keep the face clear`);
395
+ layers.push({ field, beat: index, kind: media, item: field === 'overlays' ? { ...item, position: placed }
266
396
  : field === 'layout_windows' ? { ...item, layout: 'split' } : item });
267
397
  layered = true;
268
398
  fullFrame ||= field === 'inserts';
@@ -270,7 +400,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
270
400
  continue;
271
401
  }
272
402
  if (templatesOff) {
273
- note(index, `${kind} (templates are disabled on this deployment)`, 'the presenter speaks these words without it');
403
+ note(index, `${kind} (templates are not available right now)`, 'the presenter speaks these words without it');
274
404
  continue;
275
405
  }
276
406
  if (placed) {
@@ -287,9 +417,25 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
287
417
  if ((built.kind === 'phone_call' || built.kind === 'notification') && /[^\p{Script=Latin}\p{N}\p{P}\p{S}\s]/u.test(script)) {
288
418
  note(index, `${built.kind} card text stays English ("${built.kind === 'phone_call' ? 'Incoming call' : 'Messages'}")`, 'edit the plan to change it');
289
419
  }
290
- const item = { ...envelope(built.kind), ...(built.placement ? { placement: built.placement } : {}), data: built.data };
420
+ const item = { ...envelope(built.kind),
421
+ ...(built.placement ? { placement: built.placement } : {}), data: built.data };
422
+ // A measured box over the presenter takes the template whole, drawn as its kind draws with no place of its own (#973 S4, S5).
423
+ const element = elementOf(index, beat.graphics.indexOf(graphic)), definition = TEMPLATE_KINDS.find(entry => entry.kind === built.kind);
424
+ const { position: _place, ...unplaced } = built.data, own = definition?.data.safeParse(unplaced);
425
+ const staged = element && own?.success && (built.placement ?? definition.placements[0]) === 'over'
426
+ ? stagedPlace(element, definition.rect(own.data, DEFAULT_ASPECT_RATIO, undefined, framing)) : undefined;
427
+ if (element && staged)
428
+ drawn.add(element.id);
429
+ if (staged)
430
+ item.data = unplaced;
431
+ if (staged === 'face')
432
+ note(index, `${built.kind}: its ${sourceOf(element)} place, over the presenter's face`, `the ${built.kind}'s default place, to keep the face clear`);
433
+ else if (staged) {
434
+ item.place = staged.place;
435
+ gaps.templates.push(index);
436
+ }
291
437
  // A reference place over the face gives way to the kind's default, which every kind keeps off it.
292
- if ('position' in built.data && templateFaceWarnings({ templates: [item], presenter_framing: framing }).length) {
438
+ if (!item.place && 'position' in built.data && templateFaceWarnings({ templates: [item], presenter_framing: framing }).length) {
293
439
  const { position: _position, ...data } = built.data;
294
440
  item.data = data;
295
441
  note(index, `${built.kind} at ${graphic.position}`, `the ${built.kind}'s default place, to keep the face clear`);
@@ -407,7 +553,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
407
553
  if (listening >= 0 && !request.call)
408
554
  note(listening, 'the reference\'s call needs your lines', 'the presenter keeps talking');
409
555
  else if (listening >= 0 && !longForm)
410
- note(listening, 'the reference\'s call (a call needs long-form qualification on this deployment)', 'the presenter keeps talking');
556
+ note(listening, 'the reference\'s call (a call needs long-form qualification, not available right now)', 'the presenter keeps talking');
411
557
  else if (listening >= 0 && !before)
412
558
  note(listening, 'the reference\'s call (no sentence end with words after it to open it on)', 'the presenter keeps talking');
413
559
  else if (listening >= 0 && before && request.call) {
@@ -416,18 +562,35 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
416
562
  phone: { ...CALL_PHONE_DEFAULTS, ...request.call.phone }, ...(stage ? { stage } : {}) };
417
563
  for (const layer of layers.filter(layer => layer.beat === listening && layer.kind === 'phone_call'))
418
564
  layers.splice(layers.indexOf(layer), 1);
565
+ // The call's own phone draws the staging, so the dropped card's word on it goes too.
566
+ for (const entry of notes.filter(entry => entry.beat_index === listening && entry.what.startsWith('phone_call: its ')))
567
+ notes.splice(notes.indexOf(entry), 1);
419
568
  }
420
- // Measured staging the composition draws only for the call's phone: any other measured graphic the plan places keeps its default place, and says
421
- // so once per beat and kind (rule 3).
569
+ // Measured staging is drawn for overlays, templates over the presenter and the call's phone: any other measured graphic the plan places keeps its
570
+ // default place, and says so once per beat and kind (rule 3).
422
571
  const placed = new Set(layers.filter(layer => layer.field !== 'framing' && layer.field !== 'caption_emphasis').map(layer => layer.beat));
423
572
  const named = new Set();
424
573
  for (const element of profile.staging?.elements ?? []) {
425
574
  const graphic = element.graphic_index === null ? undefined : profile.beats[element.beat_index]?.graphics[element.graphic_index];
426
575
  const key = `${element.beat_index}:${graphic?.kind}`;
427
- if (!graphic || !placed.has(element.beat_index) || named.has(key) || (call && element.beat_index === listening && element.kind === 'phone_call'))
576
+ if (!graphic || !placed.has(element.beat_index) || named.has(key) || drawn.has(element.id)
577
+ || (call && element.beat_index === listening && element.kind === 'phone_call'))
428
578
  continue;
429
579
  named.add(key);
430
- note(element.beat_index, `${graphic.kind}: its measured place and entry (reference staging)`, 'its default place; measured staging is drawn for the call\'s phone only');
580
+ note(element.beat_index, `${graphic.kind}: its ${sourceOf(element)} place and entry (reference staging)`, 'its default place; measured staging is drawn for overlays, templates over the presenter and the call\'s phone');
581
+ }
582
+ const beatsOf = (list) => [...new Set(list)].map(beat => beat + 1).join(', ');
583
+ if (gaps.entry.length)
584
+ note(gaps.entry[0], `how the overlays on beats ${beatsOf(gaps.entry)} enter (reference staging did not measure it)`, 'a cut');
585
+ if (gaps.exit.length)
586
+ note(gaps.exit[0], `how the overlays on beats ${beatsOf(gaps.exit)} leave (reference staging did not measure it)`, 'a cut');
587
+ for (const [what, list] of gaps.styles) {
588
+ note(list[0], `${what} of the overlays on beats ${beatsOf(list)}`, what.endsWith('exits') ? 'a cut' : `a cut: overlays enter by ${LAYER_ENTERS.join(', ')}`);
589
+ }
590
+ if (gaps.templates.length)
591
+ note(gaps.templates[0], `the measured entry of the templates on beats ${beatsOf(gaps.templates)}`, 'each template\'s own entry');
592
+ if (gaps.inferred.length) {
593
+ note(gaps.inferred[0], `the reference's caption box, for the graphics on beats ${beatsOf(gaps.inferred)} (not measured)`, `placed under the captions: each sits centred just below where the reference's ${profile.captions.position} captions are`);
431
594
  }
432
595
  const aggregate = (values, keep, what, substitute) => {
433
596
  for (const value of new Set(values.filter((entry) => entry !== undefined && keep(entry)))) {
@@ -461,7 +624,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
461
624
  const music = profile.beats.findIndex(beat => beat.sounds.some(sound => sound.kind === 'music'));
462
625
  if (music >= 0)
463
626
  note(music, 'the music bed', 'no music; Clipwright adds none yet');
464
- // The narration chases the presenter's measured pace through the voice's speed (owner, 25.10); a model without one leaves the voice as the lever (#890).
627
+ // The narration chases the presenter's measured pace through the voice's speed; a model without one leaves the voice as the lever.
465
628
  const speechModel = resolveTtsModel({ voice: request.voice, voice_id: request.voice_id, script });
466
629
  const pace = narrationPaceFromVoice(profile.voice), fixedPace = FIXED_PACE_MODELS.has(speechModel);
467
630
  if (pace.kind === 'unmeasured')
@@ -482,7 +645,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
482
645
  else if (pace.clamped) {
483
646
  note(0, `narration pace ${pace.target_wps} words/s of the reference presenter (speed ${pace.wanted} wanted)`, `voice_pace ${pace.voice_pace}, the end of the speed range: the narration may run ${pace.wanted > pace.voice_pace ? 'slower' : 'faster'} than the reference`);
484
647
  }
485
- // Speed first, then the residual: pauses longer than the presenter's word-gap medians are cut in silence only (owner, 25.10).
648
+ // Speed first, then the residual: pauses longer than the presenter's word-gap medians are cut in silence only.
486
649
  const pauseTrim = pauseTrimFromVoice(profile.voice), pauseTargets = pauseTrim?.targets;
487
650
  if (!pauseTargets)
488
651
  note(0, 'narration pauses (the reference presenter\'s pauses were not measured)', 'pauses stay as the voice speaks them');
@@ -500,6 +663,8 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
500
663
  ...(captionsPlaced ? { caption_position: profile.captions.position } : {}) } : {}),
501
664
  ...field('caption_emphasis'), ...field('templates'), ...field('overlays'), ...field('inserts'), ...field('layout_windows'), ...field('framing'),
502
665
  ...(call ? { call } : {}) };
666
+ if (presenter && isNarratedUgcInput(native))
667
+ native.presenter_place = presenter;
503
668
  // The quote refuses customer media without this policy; the note below discloses it (rule 3).
504
669
  if (['overlays', 'inserts', 'layout_windows'].some(name => native[name]) || templateMediaOf(native.templates).length) {
505
670
  native.broll_policy = 'anyone';
@@ -589,7 +754,7 @@ export function compileReferenceRemake(input, capabilities, planSpans = proporti
589
754
  brief: { audience: request.audience ?? 'Viewers of short ads in the style of the reference',
590
755
  message: request.message ?? script.slice(words[firstSentence.start].from, words[firstSentence.end - 1].to),
591
756
  script, native: { skill: 'make_ugc', input: native },
592
- reference_profile: { ...profile, music: '', unmapped,
757
+ reference_profile: { ...withoutWords(profile), music: '', unmapped,
593
758
  beats: profile.beats.map(beat => ({ ...beat, spoken_summary: '', graphics: beat.graphics.map(graphic => ({ ...graphic, label_hint: '' })) })) } },
594
759
  beats, claims: [], assets: [] });
595
760
  return document.success ? { kind: 'compiled', document: document.data }