@cueframe/kernel 0.1.0 → 0.1.1

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 (355) hide show
  1. package/LICENSE +661 -0
  2. package/dist/_members/composition/ops/compositionOps.d.ts +1 -0
  3. package/dist/_members/composition/projection/behind-readiness.d.ts +127 -0
  4. package/dist/_members/composition/projection/index.d.ts +2 -0
  5. package/dist/_members/composition/projection/reframe-readiness.d.ts +156 -0
  6. package/dist/_members/composition/validators/composition.d.ts +42 -25
  7. package/dist/_members/composition/validators/render.d.ts +32 -0
  8. package/dist/_members/core/render/compositionRender.d.ts +17 -1
  9. package/dist/_members/core/render/types.d.ts +31 -0
  10. package/dist/authoring/build-tools.js +6 -6
  11. package/dist/authoring/context.js +4 -4
  12. package/dist/authoring/tools.js +5 -5
  13. package/dist/authoring.js +11 -9
  14. package/dist/{chunk-ZB3C3HM7.js → chunk-7FO3ZJ43.js} +3 -0
  15. package/dist/{chunk-ODL7GQJG.js → chunk-AP6ZIVAV.js} +2 -2
  16. package/dist/{chunk-IBYQG46O.js → chunk-AQBREB5O.js} +1 -1
  17. package/dist/{chunk-YIOY2XEO.js → chunk-DK2ODBNT.js} +15 -5
  18. package/dist/{chunk-JRQVPFLF.js → chunk-HY3RIEZW.js} +1 -1
  19. package/dist/{chunk-GEA7E2FL.js → chunk-JJ6XD7NG.js} +151 -1
  20. package/dist/{chunk-COYMZ76S.js → chunk-MBCXCJOE.js} +1 -1
  21. package/dist/{chunk-37NJ27UU.js → chunk-OCFDUCF4.js} +3 -0
  22. package/dist/{chunk-VOO5FQSS.js → chunk-PDYO3DOJ.js} +5 -5
  23. package/dist/{chunk-MXFAADCQ.js → chunk-PODIYN2K.js} +9 -2
  24. package/dist/{chunk-NFEGPWGS.js → chunk-TOJEXFXL.js} +1 -1
  25. package/dist/{chunk-LM4WDUQJ.js → chunk-UHGSZZDI.js} +1 -1
  26. package/dist/{chunk-XWKZB5U5.js → chunk-ZWMV6DD2.js} +24 -1
  27. package/dist/compose/renderer.js +3 -3
  28. package/dist/compose.js +6 -6
  29. package/dist/composition/graphics-plan.js +1 -1
  30. package/dist/composition/ops.js +3 -3
  31. package/dist/composition/primitive-catalog.js +1 -1
  32. package/dist/composition/projection.js +19 -3
  33. package/dist/composition/validators.js +6 -4
  34. package/dist/composition-ops.js +8 -6
  35. package/dist/composition.js +24 -6
  36. package/dist/{context-OKX3HHFS.js → context-YWUZDG5X.js} +4 -4
  37. package/dist/core/render.js +3 -3
  38. package/dist/core/select.js +2 -2
  39. package/dist/core.js +3 -3
  40. package/dist/errors.js +1 -1
  41. package/dist/mcp/adapter.js +1 -0
  42. package/dist/media-model/testing.js +1071 -718
  43. package/dist/nle-export/server.js +328 -130
  44. package/dist/nle-export.js +7 -6
  45. package/package.json +13 -14
  46. package/dist/_members/account-model/index.js +0 -4
  47. package/dist/_members/account-model/testing/conversationStoreContract.js +0 -168
  48. package/dist/_members/account-model/testing/index.js +0 -7
  49. package/dist/_members/account-model/testing/restorePointStoreContract.js +0 -69
  50. package/dist/_members/account-model/types/index.js +0 -1
  51. package/dist/_members/account-model/validators/contextFile.js +0 -18
  52. package/dist/_members/account-model/validators/conversation.js +0 -95
  53. package/dist/_members/account-model/validators/dashboardInsight.js +0 -16
  54. package/dist/_members/account-model/validators/index.js +0 -7
  55. package/dist/_members/account-model/validators/sessionToken.js +0 -49
  56. package/dist/_members/account-model/validators/uploadToken.js +0 -16
  57. package/dist/_members/account-model/validators/userSettings.js +0 -12
  58. package/dist/_members/account-model/validators/workingComposition.js +0 -20
  59. package/dist/_members/brand-derivation/constants.js +0 -20
  60. package/dist/_members/brand-derivation/contrastSafe.js +0 -114
  61. package/dist/_members/brand-derivation/index.js +0 -1
  62. package/dist/_members/cloud-import/chunk-XKO6ZSWI.js +0 -162
  63. package/dist/_members/cloud-import/chunk-XKO6ZSWI.js.map +0 -1
  64. package/dist/_members/cloud-import/index.js +0 -241
  65. package/dist/_members/cloud-import/index.js.map +0 -1
  66. package/dist/_members/cloud-import/multipartUpload.js +0 -7
  67. package/dist/_members/cloud-import/multipartUpload.js.map +0 -1
  68. package/dist/_members/composition/audio/audioMix.js +0 -61
  69. package/dist/_members/composition/audio/index.js +0 -5
  70. package/dist/_members/composition/audio/mixStaging.js +0 -195
  71. package/dist/_members/composition/audio/musicPack.generated.js +0 -136
  72. package/dist/_members/composition/audio/musicPack.js +0 -36
  73. package/dist/_members/composition/audio/pack.generated.js +0 -603
  74. package/dist/_members/composition/audio/pack.js +0 -48
  75. package/dist/_members/composition/audio/palette.js +0 -134
  76. package/dist/_members/composition/autozoom/index.js +0 -83
  77. package/dist/_members/composition/brand-kit.js +0 -45
  78. package/dist/_members/composition/brief/contract.js +0 -209
  79. package/dist/_members/composition/color-grade.js +0 -80
  80. package/dist/_members/composition/depth.js +0 -8
  81. package/dist/_members/composition/fixtures/index.js +0 -143
  82. package/dist/_members/composition/fixtures/vaayaErrored.d.ts +0 -25
  83. package/dist/_members/composition/fixtures/vaayaErrored.js +0 -203
  84. package/dist/_members/composition/fonts.js +0 -162
  85. package/dist/_members/composition/graphics-plan/compile.js +0 -221
  86. package/dist/_members/composition/graphics-plan/index.js +0 -10
  87. package/dist/_members/composition/graphics-plan/instantiateMotif.js +0 -44
  88. package/dist/_members/composition/graphics-plan/motion.js +0 -66
  89. package/dist/_members/composition/graphics-plan/schemas.js +0 -261
  90. package/dist/_members/composition/graphics-plan/solve.js +0 -145
  91. package/dist/_members/composition/index.js +0 -5
  92. package/dist/_members/composition/invariants/clipPlacement.js +0 -69
  93. package/dist/_members/composition/invariants/composition.js +0 -28
  94. package/dist/_members/composition/invariants/errors.js +0 -10
  95. package/dist/_members/composition/invariants/etag.js +0 -38
  96. package/dist/_members/composition/invariants/ids.js +0 -33
  97. package/dist/_members/composition/invariants/index.js +0 -9
  98. package/dist/_members/composition/invariants/keyframes.js +0 -45
  99. package/dist/_members/composition/invariants/markers.js +0 -29
  100. package/dist/_members/composition/invariants/time.js +0 -20
  101. package/dist/_members/composition/invariants/tracks.js +0 -304
  102. package/dist/_members/composition/ops/compositionOps.js +0 -1056
  103. package/dist/_members/composition/ops/headlessStore.js +0 -94
  104. package/dist/_members/composition/ops/index.js +0 -4
  105. package/dist/_members/composition/ops/selectors.js +0 -118
  106. package/dist/_members/composition/ops/timeline-word.js +0 -1
  107. package/dist/_members/composition/ops/transcript.js +0 -77
  108. package/dist/_members/composition/ops/types.js +0 -16
  109. package/dist/_members/composition/primitive-catalog/catalogQuery.js +0 -150
  110. package/dist/_members/composition/primitive-catalog/disabled.js +0 -32
  111. package/dist/_members/composition/primitive-catalog/generated/meta.js +0 -15161
  112. package/dist/_members/composition/primitive-catalog/index.js +0 -46
  113. package/dist/_members/composition/primitive-catalog/paramValidation.js +0 -231
  114. package/dist/_members/composition/primitive-catalog/projection.js +0 -164
  115. package/dist/_members/composition/primitive-catalog/types.js +0 -14
  116. package/dist/_members/composition/projection/behind-placement.js +0 -62
  117. package/dist/_members/composition/projection/brandKit.js +0 -482
  118. package/dist/_members/composition/projection/cardTokens.js +0 -209
  119. package/dist/_members/composition/projection/composition-floor.js +0 -582
  120. package/dist/_members/composition/projection/cropProjection.js +0 -257
  121. package/dist/_members/composition/projection/index.js +0 -7
  122. package/dist/_members/composition/projection/occlusion-budget.js +0 -104
  123. package/dist/_members/composition/projection/safe-zones.js +0 -99
  124. package/dist/_members/composition/reframe/aspectLoss.js +0 -115
  125. package/dist/_members/composition/reframe/coverage.js +0 -62
  126. package/dist/_members/composition/reframe/derive.js +0 -73
  127. package/dist/_members/composition/reframe/index.js +0 -109
  128. package/dist/_members/composition/reframe/kenBurns.js +0 -64
  129. package/dist/_members/composition/reframe/shotScale.js +0 -176
  130. package/dist/_members/composition/reframe/trajectory.js +0 -124
  131. package/dist/_members/composition/timeline/compound.js +0 -134
  132. package/dist/_members/composition/timeline/index.js +0 -9
  133. package/dist/_members/composition/timeline/intent.js +0 -228
  134. package/dist/_members/composition/timeline/keyframes.js +0 -121
  135. package/dist/_members/composition/timeline/link.js +0 -59
  136. package/dist/_members/composition/timeline/occupancy.js +0 -36
  137. package/dist/_members/composition/timeline/placement.js +0 -74
  138. package/dist/_members/composition/timeline/plan.js +0 -31
  139. package/dist/_members/composition/timeline/resolve.js +0 -170
  140. package/dist/_members/composition/timeline/trims.js +0 -278
  141. package/dist/_members/composition/types/index.js +0 -1
  142. package/dist/_members/composition/validators/brandKit.js +0 -295
  143. package/dist/_members/composition/validators/composePlan.js +0 -113
  144. package/dist/_members/composition/validators/composition.js +0 -703
  145. package/dist/_members/composition/validators/font.js +0 -34
  146. package/dist/_members/composition/validators/graphicKinds.js +0 -30
  147. package/dist/_members/composition/validators/index.js +0 -9
  148. package/dist/_members/composition/validators/project.js +0 -29
  149. package/dist/_members/composition/validators/registry.js +0 -41
  150. package/dist/_members/composition/validators/render.js +0 -139
  151. package/dist/_members/composition/validators/session.js +0 -37
  152. package/dist/_members/composition/validators/webhook.js +0 -72
  153. package/dist/_members/composition-ops/alphaLayers.js +0 -141
  154. package/dist/_members/composition-ops/index.js +0 -637
  155. package/dist/_members/composition-ops/materializeGrid.js +0 -99
  156. package/dist/_members/composition-ops/opRecorder.js +0 -196
  157. package/dist/_members/composition-ops/recordingStore.js +0 -14
  158. package/dist/_members/composition-ops/replaceRegion.js +0 -102
  159. package/dist/_members/composition-ops/testFixtures.js +0 -19
  160. package/dist/_members/core/index.js +0 -5
  161. package/dist/_members/core/render/bookends.js +0 -53
  162. package/dist/_members/core/render/captions.js +0 -127
  163. package/dist/_members/core/render/compositionRender.js +0 -1249
  164. package/dist/_members/core/render/deriveTrajectory.js +0 -889
  165. package/dist/_members/core/render/emphasis.js +0 -16
  166. package/dist/_members/core/render/eval/cropBox.js +0 -139
  167. package/dist/_members/core/render/eval/inclusion.js +0 -96
  168. package/dist/_members/core/render/eval/index.js +0 -146
  169. package/dist/_members/core/render/eval/metrics.js +0 -94
  170. package/dist/_members/core/render/exportSettings.js +0 -65
  171. package/dist/_members/core/render/index.js +0 -20
  172. package/dist/_members/core/render/mapFaceToPerson.js +0 -73
  173. package/dist/_members/core/render/types.js +0 -8
  174. package/dist/_members/core/render/vcam/cameraPath.js +0 -248
  175. package/dist/_members/core/render/vcam/constants.js +0 -115
  176. package/dist/_members/core/render/vcam/errorCodes.js +0 -39
  177. package/dist/_members/core/render/vcam/framing.js +0 -136
  178. package/dist/_members/core/render/vcam/requiredRegion.js +0 -137
  179. package/dist/_members/core/render/vcam/shots.js +0 -53
  180. package/dist/_members/core/render/vcam/subjectSet.js +0 -388
  181. package/dist/_members/core/select/errorCodes.js +0 -38
  182. package/dist/_members/core/select/index.js +0 -26
  183. package/dist/_members/core/select/model.js +0 -18
  184. package/dist/_members/core/select/select.js +0 -244
  185. package/dist/_members/core/select/stabilize.js +0 -242
  186. package/dist/_members/core/types.js +0 -7
  187. package/dist/_members/errors/index.js +0 -1
  188. package/dist/_members/errors/registry.js +0 -560
  189. package/dist/_members/foundation/codes/index.js +0 -31
  190. package/dist/_members/foundation/index.js +0 -2
  191. package/dist/_members/foundation/primitives/index.js +0 -5
  192. package/dist/_members/mcp/adapter.js +0 -771
  193. package/dist/_members/mcp/burnedNames.js +0 -73
  194. package/dist/_members/mcp/core.js +0 -390
  195. package/dist/_members/mcp/curation.js +0 -792
  196. package/dist/_members/mcp/executor.js +0 -142
  197. package/dist/_members/mcp/format.js +0 -9
  198. package/dist/_members/mcp/generated/catalogPreviews.js +0 -95
  199. package/dist/_members/mcp/generated/harness.js +0 -81
  200. package/dist/_members/mcp/generated/operations.js +0 -131
  201. package/dist/_members/mcp/index.js +0 -50
  202. package/dist/_members/mcp/internalLoopTools.js +0 -109
  203. package/dist/_members/mcp/prompts.js +0 -45
  204. package/dist/_members/mcp/resources.js +0 -166
  205. package/dist/_members/mcp/server.js +0 -340
  206. package/dist/_members/mcp/wait.js +0 -378
  207. package/dist/_members/mcp/x402Meta.js +0 -66
  208. package/dist/_members/media-model/api.js +0 -175
  209. package/dist/_members/media-model/index.js +0 -1
  210. package/dist/_members/media-model/media-kind.js +0 -116
  211. package/dist/_members/media-model/testing/index.js +0 -1
  212. package/dist/_members/media-model/testing/mediaStoreContract.js +0 -82
  213. package/dist/_members/media-model/transcriptSearch.js +0 -70
  214. package/dist/_members/media-model/transcriptSearchCases.json +0 -79
  215. package/dist/_members/nle-export/compact-timeline.js +0 -73
  216. package/dist/_members/nle-export/compact-timeline.js.map +0 -1
  217. package/dist/_members/nle-export/composition-to-input.js +0 -147
  218. package/dist/_members/nle-export/composition-to-input.js.map +0 -1
  219. package/dist/_members/nle-export/fcpxml.js +0 -159
  220. package/dist/_members/nle-export/fcpxml.js.map +0 -1
  221. package/dist/_members/nle-export/index.js +0 -12
  222. package/dist/_members/nle-export/index.js.map +0 -1
  223. package/dist/_members/nle-export/premiere.js +0 -161
  224. package/dist/_members/nle-export/premiere.js.map +0 -1
  225. package/dist/_members/nle-export/server/index.js +0 -2
  226. package/dist/_members/nle-export/server/index.js.map +0 -1
  227. package/dist/_members/nle-export/server/pack-zip.js +0 -38
  228. package/dist/_members/nle-export/server/pack-zip.js.map +0 -1
  229. package/dist/_members/nle-export/srt.js +0 -50
  230. package/dist/_members/nle-export/srt.js.map +0 -1
  231. package/dist/_members/nle-export/types.js +0 -9
  232. package/dist/_members/nle-export/types.js.map +0 -1
  233. package/dist/_members/nle-export/xml/index.js +0 -11
  234. package/dist/_members/nle-export/xml/index.js.map +0 -1
  235. package/dist/_members/nle-export/xml-utils.js +0 -48
  236. package/dist/_members/nle-export/xml-utils.js.map +0 -1
  237. package/dist/_members/perception/activeSpeaker.js +0 -103
  238. package/dist/_members/perception/audioQuality.js +0 -76
  239. package/dist/_members/perception/energyBeat.js +0 -99
  240. package/dist/_members/perception/identityMatch.js +0 -91
  241. package/dist/_members/perception/index.js +0 -48
  242. package/dist/_members/perception/visualQuality.js +0 -95
  243. package/dist/_members/plane/conformance.js +0 -90
  244. package/dist/_members/plane/contract.js +0 -49
  245. package/dist/_members/plane/index.js +0 -16
  246. package/dist/_members/primitives/backgrounds/index.js +0 -32
  247. package/dist/_members/primitives/backgrounds/index.js.map +0 -1
  248. package/dist/_members/primitives/captions/index.js +0 -398
  249. package/dist/_members/primitives/captions/index.js.map +0 -1
  250. package/dist/_members/primitives/chunk-2LVE4UIT.js +0 -139
  251. package/dist/_members/primitives/chunk-2LVE4UIT.js.map +0 -1
  252. package/dist/_members/primitives/chunk-3IQUH6KK.js +0 -20
  253. package/dist/_members/primitives/chunk-3IQUH6KK.js.map +0 -1
  254. package/dist/_members/primitives/chunk-3OMURV6Q.js +0 -112
  255. package/dist/_members/primitives/chunk-3OMURV6Q.js.map +0 -1
  256. package/dist/_members/primitives/chunk-4IS6TMEG.js +0 -21
  257. package/dist/_members/primitives/chunk-4IS6TMEG.js.map +0 -1
  258. package/dist/_members/primitives/chunk-5MWGAEGQ.js +0 -1644
  259. package/dist/_members/primitives/chunk-5MWGAEGQ.js.map +0 -1
  260. package/dist/_members/primitives/chunk-7RRPFPQK.js +0 -15
  261. package/dist/_members/primitives/chunk-7RRPFPQK.js.map +0 -1
  262. package/dist/_members/primitives/chunk-DH5TRZK2.js +0 -125
  263. package/dist/_members/primitives/chunk-DH5TRZK2.js.map +0 -1
  264. package/dist/_members/primitives/chunk-E3KNUSFG.js +0 -2337
  265. package/dist/_members/primitives/chunk-E3KNUSFG.js.map +0 -1
  266. package/dist/_members/primitives/chunk-G2B3G4HK.js +0 -47
  267. package/dist/_members/primitives/chunk-G2B3G4HK.js.map +0 -1
  268. package/dist/_members/primitives/chunk-HFPFGZXT.js +0 -1494
  269. package/dist/_members/primitives/chunk-HFPFGZXT.js.map +0 -1
  270. package/dist/_members/primitives/chunk-HIWVFZVH.js +0 -19
  271. package/dist/_members/primitives/chunk-HIWVFZVH.js.map +0 -1
  272. package/dist/_members/primitives/chunk-IDAL6G3Y.js +0 -134
  273. package/dist/_members/primitives/chunk-IDAL6G3Y.js.map +0 -1
  274. package/dist/_members/primitives/chunk-KNC6JP5F.js +0 -34
  275. package/dist/_members/primitives/chunk-KNC6JP5F.js.map +0 -1
  276. package/dist/_members/primitives/chunk-KYRDVZST.js +0 -459
  277. package/dist/_members/primitives/chunk-KYRDVZST.js.map +0 -1
  278. package/dist/_members/primitives/chunk-LWLHS2NS.js +0 -3410
  279. package/dist/_members/primitives/chunk-LWLHS2NS.js.map +0 -1
  280. package/dist/_members/primitives/chunk-QAQ3P7DR.js +0 -8855
  281. package/dist/_members/primitives/chunk-QAQ3P7DR.js.map +0 -1
  282. package/dist/_members/primitives/chunk-ROV2ODWC.js +0 -81
  283. package/dist/_members/primitives/chunk-ROV2ODWC.js.map +0 -1
  284. package/dist/_members/primitives/chunk-TCRC2FH3.js +0 -183
  285. package/dist/_members/primitives/chunk-TCRC2FH3.js.map +0 -1
  286. package/dist/_members/primitives/chunk-US4YEDAA.js +0 -96
  287. package/dist/_members/primitives/chunk-US4YEDAA.js.map +0 -1
  288. package/dist/_members/primitives/chunk-UVKRO5ER.js +0 -13
  289. package/dist/_members/primitives/chunk-UVKRO5ER.js.map +0 -1
  290. package/dist/_members/primitives/chunk-W4EIIF2I.js +0 -10
  291. package/dist/_members/primitives/chunk-W4EIIF2I.js.map +0 -1
  292. package/dist/_members/primitives/chunk-WPGKIPCO.js +0 -3563
  293. package/dist/_members/primitives/chunk-WPGKIPCO.js.map +0 -1
  294. package/dist/_members/primitives/chunk-WZFLEGMD.js +0 -1435
  295. package/dist/_members/primitives/chunk-WZFLEGMD.js.map +0 -1
  296. package/dist/_members/primitives/chunk-XHU3WBEI.js +0 -33
  297. package/dist/_members/primitives/chunk-XHU3WBEI.js.map +0 -1
  298. package/dist/_members/primitives/chunk-YN7WTZZ7.js +0 -46
  299. package/dist/_members/primitives/chunk-YN7WTZZ7.js.map +0 -1
  300. package/dist/_members/primitives/effects/index.js +0 -30
  301. package/dist/_members/primitives/effects/index.js.map +0 -1
  302. package/dist/_members/primitives/hero-text/index.js +0 -14
  303. package/dist/_members/primitives/hero-text/index.js.map +0 -1
  304. package/dist/_members/primitives/index.js +0 -64
  305. package/dist/_members/primitives/index.js.map +0 -1
  306. package/dist/_members/primitives/layouts/index.js +0 -24
  307. package/dist/_members/primitives/layouts/index.js.map +0 -1
  308. package/dist/_members/primitives/lib/easings.js +0 -26
  309. package/dist/_members/primitives/lib/easings.js.map +0 -1
  310. package/dist/_members/primitives/lib/index.js +0 -74
  311. package/dist/_members/primitives/lib/index.js.map +0 -1
  312. package/dist/_members/primitives/lib/palette.js +0 -72
  313. package/dist/_members/primitives/lib/palette.js.map +0 -1
  314. package/dist/_members/primitives/lib/preview.js +0 -8
  315. package/dist/_members/primitives/lib/preview.js.map +0 -1
  316. package/dist/_members/primitives/lib/schema.js +0 -12
  317. package/dist/_members/primitives/lib/schema.js.map +0 -1
  318. package/dist/_members/primitives/lib/seeded-random.js +0 -14
  319. package/dist/_members/primitives/lib/seeded-random.js.map +0 -1
  320. package/dist/_members/primitives/lib/text/contrast.js +0 -12
  321. package/dist/_members/primitives/lib/text/contrast.js.map +0 -1
  322. package/dist/_members/primitives/lib/text/fill.js +0 -8
  323. package/dist/_members/primitives/lib/text/fill.js.map +0 -1
  324. package/dist/_members/primitives/lib/text/fitText.js +0 -10
  325. package/dist/_members/primitives/lib/text/fitText.js.map +0 -1
  326. package/dist/_members/primitives/lib/text/marker.js +0 -8
  327. package/dist/_members/primitives/lib/text/marker.js.map +0 -1
  328. package/dist/_members/primitives/lib/text/motion.js +0 -16
  329. package/dist/_members/primitives/lib/text/motion.js.map +0 -1
  330. package/dist/_members/primitives/lib/text/recolor.js +0 -8
  331. package/dist/_members/primitives/lib/text/recolor.js.map +0 -1
  332. package/dist/_members/primitives/lib/text/spec.js +0 -1
  333. package/dist/_members/primitives/lib/text/spec.js.map +0 -1
  334. package/dist/_members/primitives/lib/text/stage.js +0 -8
  335. package/dist/_members/primitives/lib/text/stage.js.map +0 -1
  336. package/dist/_members/primitives/lib/text/typeface.js +0 -8
  337. package/dist/_members/primitives/lib/text/typeface.js.map +0 -1
  338. package/dist/_members/primitives/lib/tokens.js +0 -24
  339. package/dist/_members/primitives/lib/tokens.js.map +0 -1
  340. package/dist/_members/primitives/lib/types.js +0 -1
  341. package/dist/_members/primitives/lib/types.js.map +0 -1
  342. package/dist/_members/primitives/params-required.json +0 -153
  343. package/dist/_members/primitives/scenes/index.js +0 -25
  344. package/dist/_members/primitives/scenes/index.js.map +0 -1
  345. package/dist/_members/primitives/subject-foreground/index.js +0 -30
  346. package/dist/_members/primitives/subject-foreground/index.js.map +0 -1
  347. package/dist/_members/primitives/text/index.js +0 -57
  348. package/dist/_members/primitives/text/index.js.map +0 -1
  349. package/dist/_members/primitives/transitions/index.js +0 -67
  350. package/dist/_members/primitives/transitions/index.js.map +0 -1
  351. package/dist/_members/primitives/ui-blocks/index.js +0 -76
  352. package/dist/_members/primitives/ui-blocks/index.js.map +0 -1
  353. package/dist/_members/primitives/version-map.json +0 -105
  354. package/dist/_members/primitives/workspace.js +0 -60
  355. package/dist/_members/primitives/workspace.js.map +0 -1
@@ -1,582 +0,0 @@
1
- /**
2
- * Composition FLOOR — the deterministic correctness layer of the
3
- * aspect-agnostic composition design system. The spatial sibling of brand `clampToFloor`:
4
- * `clampToFloor` guarantees a COLOR clears a contrast floor; `resolveComposition-
5
- * Floor` guarantees a LAYOUT clears the spatial/legibility floors.
6
- *
7
- * Pure, deterministic, ZERO I/O, ZERO proprietary imports (it is on the OSS
8
- * render path — spec §16). Home: `@cueframe/composition/projection`. APCA
9
- * math is consumed from `@cueframe/brand-derivation` (the package that owns it) —
10
- * never re-implemented here, never a proxy.
11
- *
12
- * Coordinate system: every Rect is FRACTIONS of the output rect, origin
13
- * TOP-LEFT, y-down, all in [0,1]. One math path for 9:16/1:1/4:5/16:9 because
14
- * the output rect already carries the aspect.
15
- *
16
- * The FLOOR invariants it guarantees (spec §2):
17
- * 1. Face never occluded — the face keep-out has zero overlap with caption
18
- * text / `inset` opaque graphics.
19
- * 2. Captions/graphic text legible — APCA worst-tile Lc ≥ CAPTION_LC_FLOOR
20
- * over the real backdrop beneath the element.
21
- * 3. A usable, composed area exists for every REQUIRED element — the reframe
22
- * yields space (reposition → zoom → designed mat, never a flat letterbox on
23
- * vertical); only NON-required elements may drop.
24
- * 4. Destination safe zones respected (platform UI + title/action-safe).
25
- *
26
- * The algorithm is LINEAR (yield-before-place, one stage per clip) — no feedback
27
- * loop, fully deterministic. Legibility is a pure DECISION over a caller-supplied
28
- * luminance summary; the actual pixel sampling lives in the render consumer.
29
- *
30
- * The contract is MULTI-SUBJECT NATIVE (`subjects[]`, `LayoutIntent`) so the
31
- * speaker-switch / split layout work (spec §8) extends it without a contract
32
- * rewrite. THIS module fully implements single-focus placement and a correct
33
- * N-subject union keep-out (captions never occlude ANY face); the DESIGNED
34
- * split/stacked/speaker-switch geometry is a later plan and is reported via
35
- * `LayoutIntent` + a diagnostic, never silently single-focused.
36
- */
37
- import { apcaLcFromLuminance, relativeLuminance } from "@cueframe/brand-derivation";
38
- import { lookupSafeZones } from "./safe-zones";
39
- // Re-export the hex-input APCA primitive so composition CONSUMERS that may import
40
- // composition but NOT brand-derivation directly (notably the @cueframe/evaluation
41
- // contrast critic — evaluation's package.json declares only composition+perception)
42
- // reach the SAME APCA math. Single source of truth: the math stays owned by
43
- // @cueframe/brand-derivation — never re-implemented here, never a proxy (see the
44
- // module header). `apcaLcFromLuminance` (luminance-input sibling) is already used
45
- // internally by decideLegibility below.
46
- export { apcaLc } from "@cueframe/brand-derivation";
47
- /** Bump on ANY change to the algorithm or constants below. The render consumer
48
- * embeds this into renderCacheKey (spec §15) so a floor change invalidates
49
- * cached renders transitively — never a remembered manual bump. */
50
- // v2: speaker-switch tracks the active speaker's keep-out (not the union) so
51
- // captions clear the in-frame speaker on 2-shots (spec §8, item G).
52
- // v3: co-view (captions-behind) reframe widens the crop so the silhouette clears
53
- // the centred caption laterally (coViewReframe) — fixes captions vanishing behind
54
- // the body; the reframe zoom-yield is now applied (no longer deferred).
55
- // v4: co-view silhouette target tightened 0.58→0.45 for a more readable body-bite.
56
- // floor-v5: §22.2 captions-behind eligibility gate is LIVE — buildFloorInput
57
- // injects the captionsBehind summary on depthMode clips, so a low-presence
58
- // (b-roll / no-speaker) depthMode clip now DEGRADES to a lower-third caption
59
- // (output differs from v4). The bake-time matte-coverage scalar is a follow-on
60
- // refinement; until then the coverage gate is skipped (presence + sizing gate).
61
- export const FLOOR_VERSION = "floor-v6-2026.06.09";
62
- // ── Constants (all versioned via FLOOR_VERSION) ──────────────────────────────
63
- /** Face keep-out padding, in fractions of the FACE box (scales with face size,
64
- * aspect-agnostic). Top grows most (hair/headroom), then bottom, then sides. */
65
- export const FACE_PADDING = { top: 0.6, bottom: 0.3, side: 0.25 };
66
- /** APCA Lc target for caption/graphic text over video (spec §2 invariant 2).
67
- * Numerically equals brand-derivation's `FLOORS.nonBody`, but it is a DISTINCT
68
- * concept (text-over-hostile-video, not muted-UI-text) — kept separate on
69
- * purpose so a change to one never silently moves the other. */
70
- export const CAPTION_LC_FLOOR = 60;
71
- /** Minimum effective zoom the yield may request — mirrors the bake's source-fit
72
- * floor (`CropFromTrajectory` effectiveZoom clamp). Below this the source no
73
- * longer fills the frame, so we fall to a designed mat instead. */
74
- export const MIN_ZOOM = 0.4;
75
- const clamp01 = (v) => Math.max(0, Math.min(1, v));
76
- // ── Face keep-out ────────────────────────────────────────────────────────────
77
- /** Pad a single face box by the face-relative padding vector and clamp to [0,1].
78
- * `null` (no-face frame) → `null`. In depthMode the lower edge is NOT grown
79
- * (the torso band is legible-behind, spec §12). */
80
- export function faceKeepOut(face, depthMode = false) {
81
- if (!face)
82
- return null;
83
- const padX = FACE_PADDING.side * face.w;
84
- const top = FACE_PADDING.top * face.h;
85
- // depthMode relaxes the LOWER edge only (spec §12): the torso band is legible-
86
- // behind via z-order, so no bottom growth — keeps top/sides hard.
87
- const bottom = depthMode ? 0 : FACE_PADDING.bottom * face.h;
88
- const x = clamp01(face.x - padX);
89
- const y = clamp01(face.y - top);
90
- const right = clamp01(face.x + face.w + padX);
91
- const bot = clamp01(face.y + face.h + bottom);
92
- return { x, y, w: right - x, h: bot - y };
93
- }
94
- /** Axis-aligned bounding box of a set of rects. */
95
- export function boundingBox(rects) {
96
- const x = Math.min(...rects.map((r) => r.x));
97
- const y = Math.min(...rects.map((r) => r.y));
98
- const right = Math.max(...rects.map((r) => r.x + r.w));
99
- const bot = Math.max(...rects.map((r) => r.y + r.h));
100
- return { x, y, w: right - x, h: bot - y };
101
- }
102
- /** Per-frame UNION of every subject's padded face keep-out. `null` for a frame
103
- * where no subject has a face. This is the floor's "face class" — captions never
104
- * occlude ANY subject's face, single- or multi-subject. */
105
- export function unionFaceKeepOutSeries(subjects, depthMode = false) {
106
- const frameCount = subjects.reduce((m, s) => Math.max(m, s.faceBoxSeries.length), 0);
107
- const series = [];
108
- for (let f = 0; f < frameCount; f++) {
109
- const present = [];
110
- for (const s of subjects) {
111
- const k = faceKeepOut(s.faceBoxSeries[f] ?? null, depthMode);
112
- if (k)
113
- present.push(k);
114
- }
115
- series.push(present.length ? boundingBox(present) : null);
116
- }
117
- return series;
118
- }
119
- /** Per-frame keep-out for SPEAKER-SWITCH (spec §8): the crop follows the active
120
- * speaker, so only THEY are prominently in frame — the keep-out tracks the
121
- * active subject (whose `activeWindows` cover the frame), NOT the union of all
122
- * subjects. This lets captions sit tight under the in-frame speaker instead of
123
- * clearing an off-frame second subject's union (the over-conservative single-
124
- * stage behavior). Gaps (before the first switch, or no window covers the frame)
125
- * hold the first subject that has a face there; truly faceless frames → null. */
126
- export function activeFaceKeepOutSeries(subjects, depthMode = false) {
127
- const frameCount = subjects.reduce((m, s) => Math.max(m, s.faceBoxSeries.length), 0);
128
- const series = [];
129
- for (let f = 0; f < frameCount; f++) {
130
- let active = subjects.find((s) => (s.activeWindows ?? []).some((w) => f >= w.startFrame && f < w.endFrame));
131
- if (!active || active.faceBoxSeries[f] == null) {
132
- active = subjects.find((s) => s.faceBoxSeries[f] != null);
133
- }
134
- series.push(active ? faceKeepOut(active.faceBoxSeries[f] ?? null, depthMode) : null);
135
- }
136
- return series;
137
- }
138
- // ── Layout intent ────────────────────────────────────────────────────────────
139
- /** Resolve the composed layout shape from `subjects[]`. One subject → single-
140
- * focus. Two+ subjects → speaker-switch when active-speaker windows exist,
141
- * else a static split. (The DESIGNED geometry for the multi cases is spec §8 /
142
- * a later plan; this module places them with a correct union keep-out and
143
- * reports the intent so nothing is silently flattened.) */
144
- export function resolveLayoutIntent(subjects) {
145
- if (subjects.length <= 1)
146
- return "single-focus";
147
- const anyActiveWindows = subjects.some((s) => (s.activeWindows?.length ?? 0) > 0);
148
- return anyActiveWindows ? "speaker-switch" : "split";
149
- }
150
- // ── Reframe-yield (yield BEFORE place; spec §6.4) ────────────────────────────
151
- /**
152
- * Compute the reframe yield needed to open `neededBand` of vertical space below
153
- * the (clip-max) face keep-out for a required element. Precedence:
154
- * 1. already enough band below → identity transform
155
- * 2. reposition: raise the source so the keep-out leaves `neededBand` below it
156
- * 3. zoom-out: shrink the subject (capped at MIN_ZOOM) to free vertical space
157
- * 4. designed mat: a reserved band at the bottom (the render fills it with a
158
- * designed treatment — NEVER a flat letterbox; forbidden as a frame-border
159
- * pillarbox on 9:16/4:5).
160
- */
161
- export function reframeYield(input) {
162
- const { maxFaceKeepOut: f, neededBand } = input;
163
- if (neededBand <= 0)
164
- return { kind: "transform", dy: 0, zoom: 1 };
165
- const bandBelow = 1 - (f.y + f.h);
166
- if (bandBelow >= neededBand)
167
- return { kind: "transform", dy: 0, zoom: 1 };
168
- // 1) reposition: slide the source up so the keep-out's bottom leaves the band.
169
- const dyNeeded = neededBand - bandBelow;
170
- const headroomAbove = f.y; // room to raise the face before it clips the top
171
- if (dyNeeded <= headroomAbove)
172
- return { kind: "transform", dy: -dyNeeded, zoom: 1 };
173
- // 2) zoom-out: after using all headroom, shrink the subject to free the rest,
174
- // capped at the source-fit floor.
175
- const zoom = (f.h - (dyNeeded - headroomAbove)) / f.h;
176
- if (zoom > MIN_ZOOM)
177
- return { kind: "transform", dy: -headroomAbove, zoom };
178
- // 3) designed mat — a reserved BAND at the bottom (never a flat letterbox).
179
- return { kind: "mat", rect: { x: 0, y: 1 - neededBand, w: 1, h: neededBand } };
180
- }
181
- /**
182
- * TOP-direction reframe-yield — open `neededBand` of vertical space ABOVE the
183
- * face keep-out for a HERO caption that sits ABOVE the subject (the "STEAL"
184
- * composition: subject low, big text in the headroom). Mirror of `reframeYield`
185
- * but UP-direction:
186
- * 1. already enough band above → identity
187
- * 2. reposition: LOWER the subject (dy>0) so the keep-out's top leaves the band
188
- * 3. zoom-out: shrink the subject (capped at MIN_ZOOM) to free the rest
189
- * 4. INFEASIBLE → `null` (NEVER a designed mat — "no fake stuff", per the hero
190
- * caption decision). The caller degrades to a readable FRONT caption when
191
- * real headroom can't fit the band (e.g. the head is already frame-high).
192
- * NOTE: operates in OUTPUT-frame keep-out coords like reframeYield; the SOURCE
193
- * headroom check (whether the source actually has room above the head) belongs
194
- * to the eligibility gate that feeds this — see captionsBehind eligibility.
195
- */
196
- export function reframeYieldTop(input) {
197
- const { maxFaceKeepOut: f, neededBand } = input;
198
- if (neededBand <= 0)
199
- return { kind: "transform", dy: 0, zoom: 1 };
200
- const bandAbove = f.y;
201
- if (bandAbove >= neededBand)
202
- return { kind: "transform", dy: 0, zoom: 1 };
203
- // 1) reposition: slide the source DOWN (dy>0) so the keep-out's TOP leaves the
204
- // band — bounded by the room below the face before it clips the bottom edge.
205
- const dyNeeded = neededBand - bandAbove;
206
- const roomBelow = 1 - (f.y + f.h);
207
- if (dyNeeded <= roomBelow)
208
- return { kind: "transform", dy: dyNeeded, zoom: 1 };
209
- // 2) zoom-out: after using the room below, shrink the subject to free the rest.
210
- const zoom = (f.h - (dyNeeded - roomBelow)) / f.h;
211
- if (zoom > MIN_ZOOM)
212
- return { kind: "transform", dy: roomBelow, zoom };
213
- // 3) no fake band — even max zoom can't fit it → ineligible (degrade to front).
214
- return null;
215
- }
216
- /** Speaker silhouette width ÷ face KEEP-OUT width. Shoulders span ~2.2× the bare
217
- * face; the keep-out already carries side padding (~1.5× the face), so the
218
- * silhouette is ~1.5× the keep-out. Used to size the co-view crop. */
219
- export const SILHOUETTE_TO_KEEPOUT_W = 1.5;
220
- /** Co-view target: the speaker silhouette must occupy at most this fraction of
221
- * the OUTPUT width, so a centred caption (~0.8 wide) extends past the body on
222
- * both sides and reads WITH a body-bite (spec §12) instead of vanishing. At 0.45
223
- * the body covers ~45% of the width → ~44% of the caption is visible (a clear
224
- * readable bite, not just the stray ends) while keeping a comfortable medium
225
- * shot. Lower = more caption shown but a smaller subject (pure aesthetic knob). */
226
- export const COVIEW_MAX_SILHOUETTE_W = 0.45;
227
- /**
228
- * Reframe for CAPTIONS-BEHIND (co-view, depthMode). Unlike reframeYield (which
229
- * opens a vertical band BELOW the face for a lower-third caption), the co-view
230
- * caption sits CENTRED behind the speaker — so the constraint is LATERAL: the
231
- * silhouette must be narrower than the caption or the body hides the whole line
232
- * (the over-occlusion bug). Widen the crop (shrink the subject, face-scale<1,
233
- * capped at MIN_ZOOM) until the silhouette fits COVIEW_MAX_SILHOUETTE_W. dy=0 —
234
- * no vertical reposition; the caption is behind, not below.
235
- */
236
- export function coViewReframe(maxFaceKeepOut) {
237
- const silhouetteW = SILHOUETTE_TO_KEEPOUT_W * maxFaceKeepOut.w;
238
- if (silhouetteW <= COVIEW_MAX_SILHOUETTE_W)
239
- return { kind: "transform", dy: 0, zoom: 1 };
240
- const zoom = Math.max(MIN_ZOOM, COVIEW_MAX_SILHOUETTE_W / silhouetteW);
241
- return { kind: "transform", dy: 0, zoom };
242
- }
243
- // ── Captions-behind-speaker eligibility (spec §22.2 — the moat gate) ──────────
244
- // Decides WHETHER the depth effect applies; sizing is coViewReframe above.
245
- // NOT single-speaker-only: eligible whenever an ACTIVE speaker's silhouette
246
- // occludes the caption band in the readable range — the floor's
247
- // activeFaceKeepOutSeries then follows the active speaker per-window (single OR
248
- // two-shot OR speaker-switch). When ineligible (or a window has no silhouette)
249
- // the caller DEGRADES to a flat, floor-placed caption — never drops it.
250
- // Thresholds are CALIBRATION DEFAULTS, tuned against the golden set (R1–R10),
251
- // not theory.
252
- export const MIN_ACTIVE_SPEAKER_PRESENCE = 0.6; // ≥ this fraction of clip frames have an active speaker (else ~b-roll/no-face)
253
- export const COVIEW_MIN_BAND_COVERAGE = 0.15; // < this: too little body-bite to read as depth → flat caption
254
- export const COVIEW_MAX_BAND_COVERAGE = 0.7; // > this: silhouette eats the words (the 100%-occluded bug) → flat caption
255
- /**
256
- * Pure eligibility gate for captions-behind-speaker. Evaluated AFTER the co-view
257
- * reframe sizing (spec §22.2), so `activeBandCoverage` is the post-sizing
258
- * occlusion of the caption band by the active speaker's silhouette.
259
- *
260
- * @param activeSpeakerPresenceFraction fraction of clip frames with ANY active
261
- * speaker present (single or two-shot; not "single speaker"). Injected.
262
- * @param activeBandCoverage mean fraction of the caption band the active
263
- * speaker's silhouette occludes over its windows; `null` ⇒ NOT YET MEASURED
264
- * (the bake-time coverage scalar is a follow-on) → the coverage gate is
265
- * SKIPPED and presence + coViewReframe sizing gate instead. Injected like
266
- * `LuminanceSummary` (bake/render-env), keeping this function pure.
267
- * @param legible APCA decision (Lc ≥ CAPTION_LC_FLOOR) on the visible portion.
268
- */
269
- export function captionsBehindEligible(input) {
270
- // Presence gates the "is there a speaker to put captions behind" case (single
271
- // OR two-shot). Low presence ⇒ b-roll / no-speaker ⇒ degrade.
272
- if (input.activeSpeakerPresenceFraction < MIN_ACTIVE_SPEAKER_PRESENCE) {
273
- return { eligible: false, reason: "no-active-speaker" };
274
- }
275
- // Coverage gate applies ONLY when the bake-time scalar is measured. `null` =
276
- // not yet computed ⇒ skip it and rely on presence + the coViewReframe sizing
277
- // (which already caps gross over-occlusion at COVIEW_MAX_SILHOUETTE_W). A
278
- // measured value of ~0 (silhouette doesn't reach the band) falls into
279
- // coverage-too-low; the no-person case is already handled by presence above.
280
- if (input.activeBandCoverage != null) {
281
- if (input.activeBandCoverage < COVIEW_MIN_BAND_COVERAGE) {
282
- return { eligible: false, reason: "coverage-too-low" };
283
- }
284
- if (input.activeBandCoverage > COVIEW_MAX_BAND_COVERAGE) {
285
- return { eligible: false, reason: "coverage-too-high" };
286
- }
287
- }
288
- if (!input.legible) {
289
- return { eligible: false, reason: "illegible" };
290
- }
291
- return { eligible: true, reason: "eligible" };
292
- }
293
- const CENTER = 0.5;
294
- const MIN_CAPTION_WIDTH = 0.5; // never squeeze a caption narrower than this to clear a side rail
295
- /** A blocker is a vertical-BAND blocker if it straddles the horizontal centre
296
- * (face keep-out, top/bottom UI strips, a centred caption already placed) — it
297
- * blocks the band's vertical POSITION. A blocker entirely on one side of centre
298
- * (a platform action rail) is a SIDE region — it caps the caption's horizontal
299
- * corridor instead of blocking the whole row. This is what lets a centred
300
- * caption sit at lower-third while a right rail only narrows/shifts it. */
301
- const straddlesCentre = (r) => r.x < CENTER && r.x + r.w > CENTER;
302
- /**
303
- * Place a full-width-ish horizontal band element (caption / lower-third) in the
304
- * legal area. Two-zone model (spec §6.2): center-crossing blockers carve the
305
- * legal vertical GAPS (1-D solve); side regions (rails) cap the horizontal
306
- * CORRIDOR of the chosen band. Returns `null` when no legal vertical gap of
307
- * `minH` fits, or when side rails squeeze the corridor below MIN_CAPTION_WIDTH.
308
- * (Full 2-D packing for split/stacked layouts is spec §8 / a later plan.)
309
- */
310
- export function placeInLegalArea(el, b) {
311
- const all = [...b.faceKeepOut, ...b.safeZones];
312
- const bands = all.filter(straddlesCentre);
313
- const sideRegions = all.filter((r) => !straddlesCentre(r));
314
- // Blocked vertical spans from the band blockers only.
315
- const spans = bands.map((r) => [r.y, r.y + r.h]).sort((a, c) => a[0] - c[0]);
316
- const merged = [];
317
- for (const s of spans) {
318
- const last = merged[merged.length - 1];
319
- if (last && s[0] <= last[1])
320
- last[1] = Math.max(last[1], s[1]);
321
- else
322
- merged.push([s[0], s[1]]);
323
- }
324
- // Open vertical gaps within [0,1] that fit minH.
325
- const gaps = [];
326
- let cursor = 0;
327
- for (const [a, c] of merged) {
328
- if (a - cursor >= el.minH)
329
- gaps.push([cursor, a]);
330
- cursor = Math.max(cursor, c);
331
- }
332
- if (1 - cursor >= el.minH)
333
- gaps.push([cursor, 1]);
334
- if (gaps.length === 0)
335
- return null;
336
- // Anchor preference: top → highest gap; lower-third/bottom → lowest; free →
337
- // tallest. (left/right collapse to the tallest gap in this band model.)
338
- const pick = el.anchor === "top"
339
- ? gaps[0]
340
- : el.anchor === "free" || el.anchor === "left" || el.anchor === "right"
341
- ? gaps.reduce((m, g) => (g[1] - g[0] > m[1] - m[0] ? g : m))
342
- : gaps[gaps.length - 1];
343
- // Bottom-anchored elements sit at the BOTTOM of their gap; others at the top.
344
- const isBottom = el.anchor === "bottom" || el.anchor === "lower-third";
345
- const y = isBottom ? pick[1] - el.minH : pick[0];
346
- // Cap the horizontal corridor by side regions overlapping the chosen band.
347
- let leftBound = 0;
348
- let rightBound = 1;
349
- for (const r of sideRegions) {
350
- const overlapsBand = r.y < y + el.minH && r.y + r.h > y;
351
- if (!overlapsBand)
352
- continue;
353
- if (r.x + r.w <= CENTER)
354
- leftBound = Math.max(leftBound, r.x + r.w); // left rail
355
- else if (r.x >= CENTER)
356
- rightBound = Math.min(rightBound, r.x); // right rail
357
- }
358
- const avail = rightBound - leftBound;
359
- const w = Math.min(avail, Math.min(0.92, Math.max(el.minW, 0.8)));
360
- if (w < MIN_CAPTION_WIDTH)
361
- return null; // corridor too narrow → no legal placement here
362
- const x = leftBound + (avail - w) / 2; // centre within the available corridor
363
- return { x, y, w, h: el.minH };
364
- }
365
- /**
366
- * The safe caption REGION for a destination — the floor's caption placement
367
- * WITHOUT per-clip face context. The reframe handles faces per clip; the caption
368
- * position itself is clip-independent (safe-zone-driven), so the GLOBAL caption
369
- * layer renders into THIS band and it matches the per-clip floor's caption region
370
- * (and its scrim) by construction — same placeInLegalArea, same safe zones.
371
- *
372
- * For a bottom/lower-third anchor the region's BOTTOM edge sits at the top of the
373
- * platform's bottom-UI strip, so a caption rendered into it clears the chrome on
374
- * any vertical surface (lookupSafeZones unions platforms when none is threaded).
375
- */
376
- export function resolveCaptionRegion(input) {
377
- const sz = lookupSafeZones(input.platform ?? "", input.aspect);
378
- const ext = input.captionExtent ?? { w: 0.8, h: 0.15 };
379
- const region = placeInLegalArea({ id: "captions", anchor: input.anchor ?? "lower-third", minH: ext.h, minW: ext.w }, { faceKeepOut: [], safeZones: sz.exclusions });
380
- // Captions are required → never dropped: forced band just above the safe strip.
381
- return region ?? { x: 0.1, y: clamp01(1 - ext.h), w: ext.w, h: ext.h };
382
- }
383
- /**
384
- * The region a code/graphic overlay (jsxSource) must render WITHIN — the largest
385
- * band clear of the caption + platform safe zones (item #10: floor assigns the
386
- * region, the jsx obeys). Keeps editorial graphics off the captions and the
387
- * platform UI; the agent's jsx reads `params.region` (output fractions) for its
388
- * bounds instead of hard-coding positions / full-frame useVideoConfig math.
389
- *
390
- * v1 is face-agnostic (clip-independent, like the caption band); per-clip
391
- * face-aware overlay placement is a follow-up — the brief tells the agent the
392
- * region may contain the speaker, so graphic elements should favour its edges.
393
- */
394
- export function resolveOverlayRegion(input) {
395
- const sz = lookupSafeZones(input.platform ?? "", input.aspect);
396
- const ext = input.extent ?? { w: 0.7, h: 0.35 };
397
- const blockers = input.captionRegion ? [input.captionRegion] : [];
398
- const region = placeInLegalArea({ id: "overlay", anchor: "free", minH: ext.h, minW: ext.w }, { faceKeepOut: [], safeZones: [...sz.exclusions, ...blockers] });
399
- // No legal band (caption + chrome fill the frame) → a conservative upper band.
400
- return region ?? { x: 0.1, y: clamp01((sz.exclusions.find((r) => r.y === 0)?.h ?? 0) + 0.02), w: 0.8, h: 0.3 };
401
- }
402
- // ── Legibility (worst-tile APCA over a supplied luminance summary; spec §6.3) ─
403
- const WHITE_POLE = "#FFFFFF";
404
- /**
405
- * Decide the legibility remedy for text of `textColor` over a backdrop luminance
406
- * summary. Real APCA (no proxy): the text's relative luminance vs each tile's
407
- * luminance, reduced to the WORST tile. ≥ CAPTION_LC_FLOOR → no remedy; else the
408
- * floor REQUESTS the cheapest always-legible remedy (`scrim`; the render makes it
409
- * a designed blurred-glass/gradient, never a hard stroke — spec §14). The
410
- * `lcTarget` is always the floor (the remedy must clear it).
411
- */
412
- export function decideLegibility(backdrop, textColor = WHITE_POLE) {
413
- const textY = relativeLuminance(textColor);
414
- const worst = backdrop.tiles.reduce((m, tileY) => Math.min(m, apcaLcFromLuminance(textY, tileY)), Number.POSITIVE_INFINITY);
415
- if (worst >= CAPTION_LC_FLOOR)
416
- return { lcTarget: CAPTION_LC_FLOOR, remedy: "none" };
417
- return { lcTarget: CAPTION_LC_FLOOR, remedy: "scrim" };
418
- }
419
- // ── Top-level resolve (linear: keep-out → safe → yield → place → legibility) ─
420
- function sampleFrames(win, k = 5) {
421
- const { startFrame: start, endFrame: end } = win;
422
- const span = Math.max(0, end - start);
423
- const n = Math.max(1, Math.min(k, span || 1));
424
- if (n === 1)
425
- return [start];
426
- return Array.from({ length: n }, (_, i) => Math.round(start + (span * i) / (n - 1)));
427
- }
428
- function resolveOne(el, region, input) {
429
- const legibility = el.hasText
430
- ? decideLegibility(input.backdrop(region, sampleFrames(el.timeWindow)), el.textColor)
431
- : { lcTarget: CAPTION_LC_FLOOR, remedy: "none" };
432
- return { id: el.id, region, legibility };
433
- }
434
- /**
435
- * Resolve the composition floor for one clip. Linear, single-stage-per-clip, no
436
- * feedback loop → deterministic. Never throws; required elements are never
437
- * dropped (they get a forced designed-mat / lowest-gap placement), only
438
- * non-required elements may drop (logged in diagnostics).
439
- */
440
- export function resolveCompositionFloor(input) {
441
- const layout = resolveLayoutIntent(input.subjects);
442
- const diagnostics = {
443
- fallbacksTaken: [],
444
- droppedElements: [],
445
- matApplied: false,
446
- layout,
447
- };
448
- // §22.2 — gate captions-behind-speaker. When the depth effect is requested AND
449
- // eligibility signals are injected, an ineligible clip DEGRADES to a normal
450
- // lower-third caption (effectiveDepthMode=false) — the caption is never dropped.
451
- // Presence + band coverage gate here; the floor's own legibility invariant
452
- // enforces visible-portion readability downstream, so legible=true at the gate.
453
- let effectiveDepthMode = input.depthMode ?? false;
454
- if (effectiveDepthMode && input.captionsBehind) {
455
- const elig = captionsBehindEligible({
456
- activeSpeakerPresenceFraction: input.captionsBehind.activeSpeakerPresenceFraction,
457
- activeBandCoverage: input.captionsBehind.activeBandCoverage,
458
- legible: true,
459
- });
460
- if (!elig.eligible) {
461
- effectiveDepthMode = false;
462
- diagnostics.fallbacksTaken.push(`captions-behind-ineligible:${elig.reason}`);
463
- }
464
- }
465
- // 1) per-frame face keep-out + the clip-level box used for placement/yield.
466
- // speaker-switch: track the ACTIVE speaker (the crop follows them, so only
467
- // they're in frame); single-focus / static split: union every subject's face.
468
- // No face anywhere → conservative centre band.
469
- const keepOutSeries = layout === "speaker-switch"
470
- ? activeFaceKeepOutSeries(input.subjects, effectiveDepthMode)
471
- : unionFaceKeepOutSeries(input.subjects, effectiveDepthMode);
472
- const present = keepOutSeries.filter((r) => r !== null);
473
- const clipKeepOut = present.length
474
- ? boundingBox(present)
475
- : { x: 0.3, y: 0.3, w: 0.4, h: 0.4 };
476
- if (!present.length)
477
- diagnostics.fallbacksTaken.push("no-face-center-band");
478
- // static split/stacked still use the union (designed two-up geometry is a later
479
- // plan); speaker-switch is now a designed path (active-speaker keep-out), not a
480
- // fallback, so it isn't logged as one.
481
- if (layout === "split" || layout === "stacked") {
482
- diagnostics.fallbacksTaken.push(`multi-subject-union-placement:${layout}`);
483
- }
484
- // 2) safe zones for the destination.
485
- const sz = lookupSafeZones(input.destination.platform, input.destination.aspect);
486
- // 3) reframe FIRST, against the clip-max face extent. (Kills any feedback loop;
487
- // one stable stage.) depthMode (captions-behind) is a LATERAL constraint —
488
- // widen so the silhouette clears the centred caption; otherwise it's the
489
- // vertical band-yield for the tallest required (lower-third) element.
490
- const requiredBand = Math.max(0, ...input.elements.filter((e) => e.required).map((e) => e.measuredExtent.h));
491
- // captions-behind at the TOP anchor = the HERO composition (a big phrase above
492
- // the subject — the "STEAL" look): open REAL top headroom (lower the subject +
493
- // zoom, reframeYieldTop). center/lower-third behind = the existing lateral
494
- // co-view body-bite. front = the vertical bottom-band yield. NO fake band: if
495
- // a top hero caption can't get real headroom (head already frame-high),
496
- // reframeYieldTop returns null → the treatment is wrong here, so DEGRADE to a
497
- // readable lower-third FRONT caption (a top caption with no headroom would
498
- // still occlude the face).
499
- const captionEl = input.elements.find((e) => e.kind === "caption");
500
- let effectiveElements = input.elements;
501
- let reframe;
502
- if (effectiveDepthMode && captionEl?.preferredRegion === "top") {
503
- const topYield = reframeYieldTop({
504
- maxFaceKeepOut: clipKeepOut,
505
- neededBand: requiredBand,
506
- aspect: input.destination.aspect,
507
- });
508
- if (topYield) {
509
- reframe = topYield;
510
- }
511
- else {
512
- effectiveDepthMode = false;
513
- effectiveElements = input.elements.map((e) => e.kind === "caption" ? { ...e, preferredRegion: "lower-third" } : e);
514
- diagnostics.fallbacksTaken.push("captions-behind-top-no-headroom");
515
- reframe = reframeYield({
516
- maxFaceKeepOut: clipKeepOut,
517
- neededBand: requiredBand,
518
- aspect: input.destination.aspect,
519
- });
520
- }
521
- }
522
- else if (effectiveDepthMode) {
523
- reframe = coViewReframe(clipKeepOut);
524
- }
525
- else {
526
- // Phase 4 (framing ownership): the plain non-depthMode CROP path no longer
527
- // reframes — the renderer draws the agent's AUTHORED crop verbatim (the
528
- // floor-vision fix; the agent owns framing, the floor doesn't doctor it).
529
- // reframeYield stays for the depthMode→front degrade above (Phase 6 removes
530
- // that branch with the rest of the captions-behind reframes).
531
- reframe = { kind: "transform", dy: 0, zoom: 1 };
532
- }
533
- if (reframe.kind === "mat") {
534
- diagnostics.matApplied = true;
535
- diagnostics.fallbacksTaken.push("designed-mat");
536
- }
537
- // Apply the yield to the keep-out used for placement (raise/shrink mirrors the
538
- // render transform). A mat reserves its band as an additional placement zone.
539
- const placedKeepOut = reframe.kind === "transform"
540
- ? [{ ...clipKeepOut, y: clamp01(clipKeepOut.y + reframe.dy), h: clipKeepOut.h * reframe.zoom }]
541
- : [clipKeepOut];
542
- const placementSafe = reframe.kind === "mat" ? [...sz.exclusions, reframe.rect] : sz.exclusions.slice();
543
- // 4) place by priority. takeover ignores the face + inter-element (intentional
544
- // occlusion). required never drops; non-required drops + logs on no-fit.
545
- const ordered = [...effectiveElements].sort((a, b) => b.priority - a.priority);
546
- const perElement = [];
547
- const taken = [];
548
- for (const el of ordered) {
549
- if (el.intent === "takeover") {
550
- // Full-frame editorial card: occupies the safe area, honours only safe zones.
551
- const region = { x: sz.titleSafe.x, y: sz.titleSafe.y, w: sz.titleSafe.w, h: sz.titleSafe.h };
552
- perElement.push(resolveOne(el, region, input));
553
- continue;
554
- }
555
- const region = placeInLegalArea({
556
- id: el.id,
557
- anchor: el.preferredRegion ?? "free",
558
- minH: el.measuredExtent.h,
559
- minW: el.measuredExtent.w,
560
- }, { faceKeepOut: placedKeepOut, safeZones: [...placementSafe, ...taken] });
561
- if (region) {
562
- perElement.push(resolveOne(el, region, input));
563
- taken.push(region);
564
- continue;
565
- }
566
- // No legal slot.
567
- if (el.required) {
568
- // Guaranteed placement: the reserved mat band (yield already opened it), or
569
- // a forced bottom band above the safe strip. Required is NEVER dropped.
570
- const fallback = reframe.kind === "mat"
571
- ? reframe.rect
572
- : { x: 0.1, y: clamp01(1 - el.measuredExtent.h), w: 0.8, h: el.measuredExtent.h };
573
- diagnostics.fallbacksTaken.push(`required-forced:${el.id}`);
574
- perElement.push(resolveOne(el, fallback, input));
575
- taken.push(fallback);
576
- }
577
- else {
578
- diagnostics.droppedElements.push(el.id);
579
- }
580
- }
581
- return { perElement, reframe, faceKeepOut: keepOutSeries, layout, diagnostics };
582
- }