@cueframe/kernel 0.2.44 → 0.2.46

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 (70) hide show
  1. package/dist/_members/composition/ops/compositionOps.shared.d.ts +1 -1
  2. package/dist/_members/composition/projection/cropProjection.d.ts +2 -1
  3. package/dist/_members/composition-ops/index.d.ts +4 -0
  4. package/dist/_members/composition-ops/materializeCaptions.d.ts +19 -0
  5. package/dist/_members/core/render/compositionRender.d.ts +1 -1
  6. package/dist/_members/core/render/index.d.ts +1 -1
  7. package/dist/_members/core/render/trackProjection.d.ts +24 -0
  8. package/dist/_members/core/render/types.d.ts +1 -0
  9. package/dist/_members/errors/registry.d.ts +32 -0
  10. package/dist/_members/mcp/executor.d.ts +1 -1
  11. package/dist/_members/mcp/generated/harness.d.ts +1 -1
  12. package/dist/_members/mcp/server.d.ts +2 -0
  13. package/dist/_members/plane/contract.d.ts +12 -5
  14. package/dist/authoring/build-tools.js +3 -3
  15. package/dist/authoring/context.js +2 -2
  16. package/dist/authoring/tools.js +2 -2
  17. package/dist/authoring.js +6 -6
  18. package/dist/{chunk-S3HHEJMZ.js → chunk-4FKNPTLX.js} +1 -1
  19. package/dist/{chunk-4GLMARCJ.js → chunk-ACURZCPN.js} +1 -1
  20. package/dist/{chunk-JENEKKAM.js → chunk-AJ3YRZ7A.js} +44 -5
  21. package/dist/{chunk-46PFSJZE.js → chunk-AOIY7NGF.js} +3059 -3120
  22. package/dist/{chunk-MJWRC7G7.js → chunk-BBNF4QLD.js} +2 -2
  23. package/dist/{chunk-5YO4LAB7.js → chunk-CFIQY4OE.js} +1 -1
  24. package/dist/{chunk-GOJKL5FM.js → chunk-DV7ZFVL4.js} +1 -1
  25. package/dist/{chunk-XWOV6KI6.js → chunk-E63UEO5C.js} +8 -2
  26. package/dist/{chunk-ZXWSZUSU.js → chunk-FEG264CF.js} +55 -32
  27. package/dist/{chunk-OASSDEWY.js → chunk-FGJ7GKWZ.js} +6 -6
  28. package/dist/{chunk-SPF3AXTB.js → chunk-FYS2XUKJ.js} +1 -1
  29. package/dist/{chunk-QISFYLBB.js → chunk-IMIUHG2E.js} +1 -1
  30. package/dist/{chunk-V7LQ7ZHV.js → chunk-JPMVNMF3.js} +1 -1
  31. package/dist/chunk-LELDNLLA.js +1060 -0
  32. package/dist/{chunk-KKHI5NLK.js → chunk-N54EGUVM.js} +1 -1
  33. package/dist/{chunk-WYRKF2OX.js → chunk-Q5P5UFOP.js} +1 -1
  34. package/dist/{chunk-ZUJCWFS7.js → chunk-SLUTOUWI.js} +1 -1
  35. package/dist/{chunk-DENF4TPI.js → chunk-T56BK7NS.js} +6 -4
  36. package/dist/{chunk-B4VMU2IJ.js → chunk-TBHSOEYK.js} +1 -1
  37. package/dist/{chunk-POTCIQTA.js → chunk-VEQTURKN.js} +90 -11
  38. package/dist/{chunk-YIJZBLQE.js → chunk-W5AFGFVZ.js} +24 -3
  39. package/dist/{chunk-WKT7SEVP.js → chunk-WWXTJZ67.js} +4 -2
  40. package/dist/{chunk-OZNULDQE.js → chunk-XSBVOXSB.js} +1 -1
  41. package/dist/{chunk-FRNBTODO.js → chunk-Z4G3VM3H.js} +1 -1
  42. package/dist/compose.js +1 -1
  43. package/dist/composition/invariants.js +2 -2
  44. package/dist/composition/ops.js +1 -1
  45. package/dist/composition/projection.js +2 -2
  46. package/dist/composition/timeline.js +2 -2
  47. package/dist/composition-ops.js +36 -894
  48. package/dist/composition.js +4 -4
  49. package/dist/{context-KNFL62QR.js → context-YNR73FNI.js} +2 -2
  50. package/dist/core/render.js +7 -5
  51. package/dist/core/select.js +2 -2
  52. package/dist/core.js +5 -5
  53. package/dist/director/ai-sdk.js +3 -3
  54. package/dist/director/domain.js +2 -2
  55. package/dist/director.js +3 -3
  56. package/dist/errors.js +3 -1
  57. package/dist/mcp/adapter.js +3 -3
  58. package/dist/mcp/core.js +1 -1
  59. package/dist/mcp/curation.js +1 -1
  60. package/dist/mcp/executor.js +2 -2
  61. package/dist/mcp/scene-shot.js +2 -2
  62. package/dist/mcp/server.js +26 -5
  63. package/dist/mcp.js +26 -7
  64. package/dist/plane/conformance.js +1 -1
  65. package/dist/plane/contract.js +5 -1
  66. package/dist/plane.js +1 -1
  67. package/dist/render-harness/cardAssetAdmission.js +2 -2
  68. package/dist/render-harness/cardFonts.js +2 -2
  69. package/dist/render-harness.js +7 -7
  70. package/package.json +11 -11
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-7AZLFN26.js";
8
8
  import {
9
9
  selectAllOverlays
10
- } from "./chunk-POTCIQTA.js";
10
+ } from "./chunk-VEQTURKN.js";
11
11
  import {
12
12
  ComposePlan
13
13
  } from "./chunk-AG7HKF2U.js";
@@ -63,7 +63,7 @@ var COMPOSE_ROLE_TOOLS = Object.freeze({
63
63
  schema: z.object({}),
64
64
  metadata: { label: "Get Context" },
65
65
  handler: async (_args, capabilities) => {
66
- const { buildDirectorContext } = await import("./context-KNFL62QR.js");
66
+ const { buildDirectorContext } = await import("./context-YNR73FNI.js");
67
67
  return buildDirectorContext(capabilities);
68
68
  }
69
69
  },
@@ -312,7 +312,7 @@ Call wait_job(kind="compose", id="${j.composeJobId}", projectId="${a.projectId}"
312
312
  name: "apply_composition",
313
313
  operationId: "applyComposition",
314
314
  pathArgs: { projectId: "id" },
315
- description: 'Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Pass `if_match` for optimistic-concurrency control (409 stale_etag on mismatch) and `dry_run: true` to validate the batch without persisting. Ops land on the project\'s ACTIVE composition unless you pass `composition_id` (mutually exclusive with `session_id`), which targets a specific one \u2014 that is how you author a second composition minted with new_composition and run an A/B. Use this to seed a composition (add media clips) or make targeted edits (set a crop intent, reorder clips, set the format). CAPTIONS: attach content with {type:"captions.fromTranscript", mediaId, window?} \u2014 the server slices the cached word-timed transcript, rebases times, and sentence-splits into segments (no pasting words; 422 media_no_transcript if the media has no speech); style it with {type:"setCaptionStyle", style}; or set explicit words with {type:"setCaptions", segments, style?} (e.g. translations or your own VO timings). Brand is NOT set here \u2014 it lives on the project (create_project / update_project with brandKitId \u2014 or PATCH /projects/:id over REST). CARDS: a {kind:"card", html, tokens} clip projects each `tokens` KEY `x` to the CSS custom property `--cf-x`, so the html must read it as `var(--cf-x)` \u2014 write `tokens:{"plate":"#0af"}` + `background:var(--cf-plate)`. Do NOT put dashes in the key: `"--plate"` becomes `--cf---plate` and your `var(--plate)` then resolves to NOTHING, which CSS silently degrades to a transparent background and black text. Responses carry `warnings[]` when a card reads a var nothing defines. SCENE SHOTS: for a 3D product shot \u2014 a tilted page screenshot, a lit laptop/phone showing a real screenshot, two-colour type, a colour-field backdrop, focus/grain/vignette and a camera move \u2014 use {type:"scene.add", clipId, newTrack|trackId, startTime, duration, scene, assets} and revise it with {type:"scene.update", clipId, scene?, assets?, startTime?, duration?}. You author a scene SPEC, never wrapper TSX; the server registers the component for you. There is NO built-in scene primitive in list_catalog \u2014 READ cueframe://scene-shot first for the JSON schema, the canonical V2 world-objects recipe with nested groups, slabs, pages, phones, laptops, world/screen text, admitted surfaces, camera motion, lighting, and the current resource limits.',
315
+ description: 'Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Call describe_composition_ops without type for the compact operation list, then with a type for its exact JSON Schema. Pass `if_match` for optimistic-concurrency control (409 stale_etag on mismatch) and `dry_run: true` to validate the batch without persisting. Ops land on the project\'s ACTIVE composition unless you pass `composition_id` (mutually exclusive with `session_id`), which targets a specific one \u2014 that is how you author a second composition minted with new_composition and run an A/B. Use this to seed a composition (add media clips) or make targeted edits (set a crop intent, reorder clips, set the format). CAPTIONS FOR EDITED TIMELINES: after saving all clip edits, call again with {type:"captions.fromComposition", trackId} to build captions from every audible clip on that track, honoring trims, exclusions and placement. Clips must have duration matching playable source duration divided by playbackRate; unsupported timing fails explicitly. Re-run it after later timeline edits; captions do not update automatically. {type:"captions.fromTranscript", mediaId, window?} instead replaces all captions from one media transcript, rebased to zero; it does not follow a montage. Style with {type:"setCaptionStyle", style}; or set explicit words with {type:"setCaptions", segments, style?} (e.g. translations or your own VO timings). Brand is NOT set here \u2014 it lives on the project (create_project / update_project with brandKitId \u2014 or PATCH /projects/:id over REST). CARDS: a {kind:"card", html, tokens} clip projects each `tokens` KEY `x` to the CSS custom property `--cf-x`, so the html must read it as `var(--cf-x)` \u2014 write `tokens:{"plate":"#0af"}` + `background:var(--cf-plate)`. Do NOT put dashes in the key: `"--plate"` becomes `--cf---plate` and your `var(--plate)` then resolves to NOTHING, which CSS silently degrades to a transparent background and black text. Responses carry `warnings[]` when a card reads a var nothing defines. SCENE SHOTS: for a 3D product shot \u2014 a tilted page screenshot, a lit laptop/phone showing a real screenshot, two-colour type, a colour-field backdrop, focus/grain/vignette and a camera move \u2014 use {type:"scene.add", clipId, newTrack|trackId, startTime, duration, scene, assets} and revise it with {type:"scene.update", clipId, scene?, assets?, startTime?, duration?}. You author a scene SPEC, never wrapper TSX; the server registers the component for you. There is NO built-in scene primitive in list_catalog \u2014 READ cueframe://scene-shot first for the JSON schema, the canonical V2 world-objects recipe with nested groups, slabs, pages, phones, laptops, world/screen text, admitted surfaces, camera motion, lighting, and the current resource limits.',
316
316
  okFormat: (j, a) => {
317
317
  const warns = formatWarnings(j);
318
318
  if (j && typeof j.valid === "boolean") {
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-OSMXQV5V.js";
5
5
  import {
6
6
  CueframeError
7
- } from "./chunk-XWOV6KI6.js";
7
+ } from "./chunk-E63UEO5C.js";
8
8
 
9
9
  // ../execution/render-harness/src/cardAssetAdmission.ts
10
10
  import parseCss from "css-tree/parser";
@@ -229,8 +229,10 @@ var ERROR_CODES = {
229
229
  // for. None of that is the author's doing and none of it is visible to them,
230
230
  // so it stays a platform fault we see in Sentry.
231
231
  component_output_unverifiable: { stage: "render", category: "internal", retryable: false, httpStatus: 500 },
232
- // Two ways a bake dies without a diagnostic of its own. The sandbox ran out of
233
- // memory: the same bake on a larger box can succeed, so it is worth one retry.
232
+ // Two ways a bake dies without a diagnostic of its own. Both are raised as
233
+ // workflow failures by the host, never as a ComponentRuntimeError, so they are
234
+ // deliberately absent from COMPONENT_RUNTIME_ERROR_CODES. The sandbox ran out
235
+ // of memory: the same bake on a larger box can succeed, so it is worth one retry.
234
236
  sandbox_oom: { stage: "render", category: "internal", retryable: true, httpStatus: 503 },
235
237
  // The bake process died leaving nothing to read. Retrying reproduces it, so it
236
238
  // is ours to diagnose from the run, not the caller's to wait out.
@@ -239,6 +241,9 @@ var ERROR_CODES = {
239
241
  component_scene_invalid: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
240
242
  motion_preset_unknown: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
241
243
  motion_preset_target_invalid: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
244
+ caption_track_invalid: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
245
+ caption_clip_unsupported: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
246
+ caption_clips_overlap: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
242
247
  grid_target_invalid: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
243
248
  grid_unavailable: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
244
249
  shot_grid_unavailable: { stage: "validate", category: "authoring", retryable: false, httpStatus: 422 },
@@ -512,6 +517,7 @@ export {
512
517
  encodeError,
513
518
  decodeError,
514
519
  scrubSensitive,
520
+ DEFAULT_RETRY_AFTER_SECONDS,
515
521
  retryAfterSecondsFor,
516
522
  toWireEnvelope
517
523
  };
@@ -20,11 +20,11 @@ import {
20
20
  scoreAndRank,
21
21
  segmentShots,
22
22
  stabilizeSelectionHysteresis
23
- } from "./chunk-V7LQ7ZHV.js";
23
+ } from "./chunk-JPMVNMF3.js";
24
24
  import {
25
25
  flattenCompoundClips,
26
26
  hasCompoundClips
27
- } from "./chunk-QISFYLBB.js";
27
+ } from "./chunk-IMIUHG2E.js";
28
28
  import {
29
29
  compositionHasVo,
30
30
  roleDefaultGainDb
@@ -36,7 +36,7 @@ import {
36
36
  import {
37
37
  CueframeError,
38
38
  encodeError
39
- } from "./chunk-XWOV6KI6.js";
39
+ } from "./chunk-E63UEO5C.js";
40
40
  import {
41
41
  SCENE_CAPTURE_IDENTITY_FIELDS_V1,
42
42
  SCENE_PRESET_TRIANGLES,
@@ -1342,6 +1342,38 @@ function playableSpans(trimStart, trimEnd, excluded) {
1342
1342
  if (cursor < trimEnd) out.push({ start: cursor, end: trimEnd });
1343
1343
  return out;
1344
1344
  }
1345
+ function projectMediaPlaybackSpans({
1346
+ startTime,
1347
+ duration,
1348
+ trim,
1349
+ excludedRanges,
1350
+ playbackRate = 1,
1351
+ fps
1352
+ }) {
1353
+ const trimStart = trim?.start ?? 0;
1354
+ const trimEnd = trim?.end ?? trimStart + duration * playbackRate;
1355
+ let playedSourceSeconds = 0;
1356
+ return playableSpans(trimStart, trimEnd, excludedRanges).map((span) => {
1357
+ const timelineStart = startTime + playedSourceSeconds / playbackRate;
1358
+ playedSourceSeconds += span.end - span.start;
1359
+ const timelineEnd = startTime + playedSourceSeconds / playbackRate;
1360
+ const { startFrame: timelineStartFrames, durationInFrames } = timelineSpanFrames(
1361
+ timelineStart,
1362
+ timelineEnd,
1363
+ fps
1364
+ );
1365
+ return {
1366
+ sourceStart: span.start,
1367
+ sourceEnd: span.end,
1368
+ timelineStart,
1369
+ timelineEnd,
1370
+ sourceInFrames: frames(span.start, fps),
1371
+ sourceOutFrames: frames(span.end, fps),
1372
+ timelineStartFrames,
1373
+ durationInFrames
1374
+ };
1375
+ });
1376
+ }
1345
1377
  function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajectoryUrls, coViewUrls, trajectories) {
1346
1378
  const clips = [];
1347
1379
  const audio = [];
@@ -1365,7 +1397,6 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1365
1397
  const packedUrl = coViewUrls?.[c.id];
1366
1398
  const packedEnvelopeFields = packedUrl === void 0 ? [] : [
1367
1399
  c.region !== void 0 && !(c.region.x === 0 && c.region.y === 0 && c.region.w === 1 && c.region.h === 1) ? "region" : null,
1368
- c.fit !== void 0 && c.fit !== "cover" ? "fit" : null,
1369
1400
  c.opacity !== void 0 && c.opacity !== 1 ? "opacity" : null,
1370
1401
  c.scale !== void 0 && c.scale !== 1 ? "scale" : null,
1371
1402
  c.rotation !== void 0 && c.rotation !== 0 ? "rotation" : null,
@@ -1384,33 +1415,31 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1384
1415
  }
1385
1416
  );
1386
1417
  }
1418
+ const spans = projectMediaPlaybackSpans({
1419
+ startTime: c.startTime,
1420
+ duration: c.duration,
1421
+ trim: c.source.trim,
1422
+ excludedRanges: c.source.excludedRanges,
1423
+ playbackRate: c.source.playbackRate,
1424
+ fps
1425
+ });
1387
1426
  if (track.kind === "audio") {
1388
- const trimStart2 = c.source.trim?.start ?? 0;
1389
- const trimEnd2 = c.source.trim?.end ?? trimStart2 + c.duration;
1390
- const spans2 = playableSpans(trimStart2, trimEnd2, c.source.excludedRanges);
1391
1427
  const resolvedVolume = (c.source.volumeDb !== void 0 ? Math.pow(10, c.source.volumeDb / 20) : c.source.volume ?? 1) * (track.volume ?? 1);
1392
1428
  const fadeInFrames = c.source.fadeInMs !== void 0 ? Math.max(0, Math.round(c.source.fadeInMs / 1e3 * fps)) : void 0;
1393
1429
  const fadeOutFrames = c.source.fadeOutMs !== void 0 ? Math.max(0, Math.round(c.source.fadeOutMs / 1e3 * fps)) : void 0;
1394
- let playedSec2 = 0;
1395
- for (let s = 0; s < spans2.length; s++) {
1396
- const span = spans2[s];
1397
- const spanStartSec = c.startTime + playedSec2;
1398
- playedSec2 += span.end - span.start;
1399
- const { startFrame, durationInFrames: subDurationFrames } = timelineSpanFrames(
1400
- spanStartSec,
1401
- c.startTime + playedSec2,
1402
- fps
1403
- );
1430
+ for (let s = 0; s < spans.length; s++) {
1431
+ const span = spans[s];
1432
+ const { timelineStartFrames: startFrame, durationInFrames: subDurationFrames } = span;
1404
1433
  if (subDurationFrames === 0) continue;
1405
- const sourceInFrames = frames(span.start, fps);
1406
1434
  audio.push({
1407
- id: spans2.length === 1 ? c.id : `${c.id}#${s}`,
1435
+ id: spans.length === 1 ? c.id : `${c.id}#${s}`,
1408
1436
  src,
1409
- sourceInFrames,
1410
- sourceOutFrames: sourceInFrames + subDurationFrames,
1437
+ sourceInFrames: span.sourceInFrames,
1438
+ sourceOutFrames: span.sourceOutFrames,
1411
1439
  startFrame,
1412
1440
  durationInFrames: subDurationFrames,
1413
1441
  volume: resolvedVolume,
1442
+ ...c.source.playbackRate !== void 0 ? { playbackRate: c.source.playbackRate } : {},
1414
1443
  ...fadeInFrames !== void 0 ? { fadeInFrames } : {},
1415
1444
  ...fadeOutFrames !== void 0 ? { fadeOutFrames } : {},
1416
1445
  ...c.source.loop !== void 0 ? { loop: c.source.loop } : {}
@@ -1418,18 +1447,11 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1418
1447
  }
1419
1448
  continue;
1420
1449
  }
1421
- const trimStart = c.source.trim?.start ?? 0;
1422
- const trimEnd = c.source.trim?.end ?? trimStart + c.duration;
1423
- const spans = playableSpans(trimStart, trimEnd, c.source.excludedRanges);
1424
1450
  let firstEmittedSubId;
1425
- let playedSec = 0;
1426
1451
  for (let s = 0; s < spans.length; s++) {
1427
1452
  const span = spans[s];
1428
- const spanStartSec = c.startTime + playedSec;
1429
- playedSec += span.end - span.start;
1430
- const { startFrame: timelineCursorFrames, durationInFrames: subDurationFrames } = timelineSpanFrames(spanStartSec, c.startTime + playedSec, fps);
1453
+ const { timelineStartFrames: timelineCursorFrames, durationInFrames: subDurationFrames } = span;
1431
1454
  if (subDurationFrames === 0) continue;
1432
- const sourceInFrames = frames(span.start, fps);
1433
1455
  const subId = spans.length === 1 ? c.id : `${c.id}#${s}`;
1434
1456
  if (firstEmittedSubId === void 0) firstEmittedSubId = subId;
1435
1457
  clips.push({
@@ -1437,11 +1459,11 @@ function projectMediaTrack(track, trackIndex, trackCount, fps, srcUrls, trajecto
1437
1459
  id: subId,
1438
1460
  ...spans.length > 1 ? { parentId: c.id } : {},
1439
1461
  src,
1440
- sourceInFrames,
1441
- sourceOutFrames: sourceInFrames + subDurationFrames,
1462
+ sourceInFrames: span.sourceInFrames,
1463
+ sourceOutFrames: span.sourceOutFrames,
1442
1464
  timelineStartFrames: timelineCursorFrames,
1443
1465
  durationInFrames: subDurationFrames,
1444
- trimStartFrames: frames(trimStart, fps),
1466
+ trimStartFrames: frames(c.source.trim?.start ?? 0, fps),
1445
1467
  ...c.source.playbackRate !== void 0 ? { playbackRate: c.source.playbackRate } : {},
1446
1468
  ...c.source.volume !== void 0 ? { volume: c.source.volume } : {},
1447
1469
  ...c.source.muted !== void 0 ? { muted: c.source.muted } : {},
@@ -2952,6 +2974,7 @@ export {
2952
2974
  validateComposition,
2953
2975
  assertBehindSubjectCaptionCoverage,
2954
2976
  playableSpans,
2977
+ projectMediaPlaybackSpans,
2955
2978
  compositionFormatDimensions,
2956
2979
  referencedMediaIds,
2957
2980
  compositionToRenderInput,
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  CueframeError,
8
8
  encodeError
9
- } from "./chunk-XWOV6KI6.js";
9
+ } from "./chunk-E63UEO5C.js";
10
10
 
11
11
  // ../composition/src/projection/cardTokens.ts
12
12
  var CARD_MOTION_VAR_NAMES = [
@@ -454,17 +454,17 @@ function resolveSegmentAt(traj, srcFrame) {
454
454
  }
455
455
  return { seg: best, inSegIdx: Math.max(0, Math.min(bestInIdx, best.centers.length - 1)) };
456
456
  }
457
- function outputToTrajectoryFrame(sections, trimStartFrames, outFrame, srcFps, fps) {
457
+ function outputToTrajectoryFrame(sections, trimStartFrames, outFrame, srcFps, fps, playbackRate = 1) {
458
458
  let cum = 0;
459
459
  for (const s of sections) {
460
460
  if (outFrame < cum + s.durationInFrames) {
461
- const clipFramesAtFps2 = s.sourceInFrames - trimStartFrames + (outFrame - cum);
461
+ const clipFramesAtFps2 = s.sourceInFrames - trimStartFrames + (outFrame - cum) * playbackRate;
462
462
  return Math.round(clipFramesAtFps2 * srcFps / fps);
463
463
  }
464
464
  cum += s.durationInFrames;
465
465
  }
466
466
  const last = sections[sections.length - 1];
467
- const clipFramesAtFps = last.sourceInFrames - trimStartFrames + (last.durationInFrames - 1);
467
+ const clipFramesAtFps = last.sourceInFrames - trimStartFrames + (last.durationInFrames - 1) * playbackRate;
468
468
  return Math.round(clipFramesAtFps * srcFps / fps);
469
469
  }
470
470
  function computeCropTransform(input) {
@@ -519,13 +519,13 @@ function sourceRectToOutput(src, t, outW, outH) {
519
519
  };
520
520
  }
521
521
  function buildSubjectsOutput(args) {
522
- const { traj, sections, trimStartFrames, durationInFrames, outW, outH, fps } = args;
522
+ const { traj, sections, trimStartFrames, durationInFrames, outW, outH, fps, playbackRate = 1 } = args;
523
523
  const series = /* @__PURE__ */ new Map();
524
524
  const activeBySubject = /* @__PURE__ */ new Map();
525
525
  const order = [];
526
526
  const emptySeries = () => Array(durationInFrames).fill(null);
527
527
  for (let f = 0; f < durationInFrames; f++) {
528
- const srcFrame = outputToTrajectoryFrame(sections, trimStartFrames, f, traj.src_fps, fps);
528
+ const srcFrame = outputToTrajectoryFrame(sections, trimStartFrames, f, traj.src_fps, fps, playbackRate);
529
529
  const { seg, inSegIdx } = resolveSegmentAt(traj, srcFrame);
530
530
  const [cx, cy] = seg.centers[inSegIdx];
531
531
  const transform = computeCropTransform({
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  bodySchemaFor,
3
3
  opById
4
- } from "./chunk-46PFSJZE.js";
4
+ } from "./chunk-AOIY7NGF.js";
5
5
 
6
6
  // ../mcp/src/generated/sceneContract.ts
7
7
  function deepFreeze(value) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  InvariantError,
3
3
  isSourceKindCompatible
4
- } from "./chunk-DENF4TPI.js";
4
+ } from "./chunk-T56BK7NS.js";
5
5
  import {
6
6
  expandClipIdsToLinkGroups,
7
7
  linkPartners
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CueframeError,
3
3
  encodeError
4
- } from "./chunk-XWOV6KI6.js";
4
+ } from "./chunk-E63UEO5C.js";
5
5
 
6
6
  // ../execution/core/src/render/vcam/constants.ts
7
7
  var P_MIN = 0.5;