create-aura3d 1.1.2 → 1.3.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 (215) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +5 -3
  4. package/dist/index.js.map +1 -1
  5. package/package.json +11 -9
  6. package/templates/{cartoon-channel → animation-channel}/README.md +11 -11
  7. package/templates/{cartoon-studio → animation-channel}/index.html +1 -1
  8. package/templates/{cartoon-channel → animation-channel}/package.json +2 -2
  9. package/templates/{cartoon-channel → animation-channel}/src/episode.ts +22 -22
  10. package/templates/{cartoon-channel → animation-channel}/src/experimental/README.md +5 -5
  11. package/templates/{cartoon-channel → animation-channel}/src/experimental/concept-episode-2-5d.css +3 -3
  12. package/templates/{cartoon-channel → animation-channel}/src/experimental/concept-episode-2-5d.ts +7 -7
  13. package/templates/{cartoon-channel → animation-channel}/src/experimental/image-puppet-episode.css +4 -4
  14. package/templates/{cartoon-channel → animation-channel}/src/experimental/image-puppet-episode.ts +6 -6
  15. package/templates/{cartoon-channel → animation-channel}/src/experimental/puppet-episode-2d.css +3 -3
  16. package/templates/{cartoon-channel → animation-channel}/src/experimental/puppet-episode-2d.ts +7 -7
  17. package/templates/{cartoon-channel → animation-channel}/src/main.ts +32 -32
  18. package/templates/{cartoon-channel → animation-channel}/src/render-plan.ts +13 -13
  19. package/templates/{cartoon-channel → animation-channel}/src/sample-episode-visual.css +34 -34
  20. package/templates/{cartoon-channel → animation-channel}/src/sample-episode-visual.ts +20 -20
  21. package/templates/{cartoon-channel → animation-channel}/tests/concept-2-5d-animation.spec.ts +3 -3
  22. package/templates/{cartoon-channel → animation-channel}/tests/concept-2-5d.spec.ts +6 -6
  23. package/templates/{cartoon-channel → animation-channel}/tests/image-puppet-animation.spec.ts +3 -3
  24. package/templates/{cartoon-channel → animation-channel}/tests/image-puppet.spec.ts +5 -5
  25. package/templates/{cartoon-channel → animation-channel}/tests/puppet-2d-animation.spec.ts +3 -3
  26. package/templates/{cartoon-channel → animation-channel}/tests/puppet-2d.spec.ts +4 -4
  27. package/templates/{prompt-cartoon-channel → animation-channel}/tests/route-health.spec.ts +1 -1
  28. package/templates/{cartoon-channel → animation-channel}/tests/sample-episode-visual.spec.ts +5 -5
  29. package/templates/{cartoon-channel → animation-channel}/tests/storyboard-playback.spec.ts +7 -7
  30. package/templates/animation-studio/AGENTS.md +71 -0
  31. package/templates/{cartoon-studio → animation-studio}/ASSET-LICENSES.md +7 -7
  32. package/templates/{cartoon-studio → animation-studio}/README.md +6 -6
  33. package/templates/animation-studio/aura.assets.json +669 -0
  34. package/templates/animation-studio/dist/episodes/scene/episode-3d.webm +0 -0
  35. package/templates/animation-studio/dist/episodes/scene/frames/action.png +0 -0
  36. package/templates/animation-studio/dist/episodes/scene/frames/dialogue.png +0 -0
  37. package/templates/animation-studio/dist/episodes/scene/frames/final.png +0 -0
  38. package/templates/animation-studio/dist/episodes/scene/frames/first.png +0 -0
  39. package/templates/animation-studio/dist/episodes/scene/frames/mouth-closed.png +0 -0
  40. package/templates/animation-studio/dist/episodes/scene/frames/mouth-open.png +0 -0
  41. package/templates/animation-studio/dist/episodes/scene/render-live-summary.json +10284 -0
  42. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-1.png +0 -0
  43. package/templates/animation-studio/dist/episodes/scene/skeleton-overlays/worker-2.png +0 -0
  44. package/templates/animation-studio/dist/generated/second-scene.document.json +1210 -0
  45. package/templates/animation-studio/dist/generated/studio-edited.document.json +1196 -0
  46. package/templates/animation-studio/dist/scene/inshader-accept.document.json +1 -0
  47. package/templates/animation-studio/dist/scene/inshader-test.document.json +1 -0
  48. package/templates/animation-studio/dist/scene/integ.document.json +1 -0
  49. package/templates/animation-studio/dist/scene/working.document.json +723 -0
  50. package/templates/animation-studio/dist/scene/working.history.json +1 -0
  51. package/templates/{cartoon-studio → animation-studio}/live-route.html +1 -1
  52. package/templates/{cartoon-studio → animation-studio}/package-lock.json +2 -2
  53. package/templates/animation-studio/package.json +32 -0
  54. package/templates/animation-studio/public/aura-assets/cast-a.catalog.glb +0 -0
  55. package/templates/animation-studio/public/aura-assets/cast-b.catalog.glb +0 -0
  56. package/templates/animation-studio/public/aura-assets/luma.catalog.glb +0 -0
  57. package/templates/animation-studio/public/aura-assets/luma2.catalog.glb +0 -0
  58. package/templates/animation-studio/public/aura-assets/miko.catalog.glb +0 -0
  59. package/templates/{cartoon-studio → animation-studio}/public/aura-assets/moonGarden.gltf +1 -1
  60. package/templates/animation-studio/public/aura-assets/mushroom.catalog.glb +0 -0
  61. package/templates/animation-studio/public/aura-assets/rusty.catalog.glb +0 -0
  62. package/templates/animation-studio/public/cast-library/cast-library.json +71 -0
  63. package/templates/animation-studio/public/clip-library/gesture.json +173903 -0
  64. package/templates/animation-studio/public/clip-library/idle.json +23035 -0
  65. package/templates/animation-studio/public/clip-library/manifest.json +154 -0
  66. package/templates/animation-studio/public/clip-library/nod.json +173903 -0
  67. package/templates/animation-studio/public/clip-library/point.json +69879 -0
  68. package/templates/animation-studio/public/clip-library/react.json +66084 -0
  69. package/templates/animation-studio/public/clip-library/run.json +9648 -0
  70. package/templates/animation-studio/public/clip-library/talk.json +69879 -0
  71. package/templates/animation-studio/public/clip-library/walk.json +8283 -0
  72. package/templates/animation-studio/public/hifi-cast/BrainStem.glb +0 -0
  73. package/templates/animation-studio/public/hifi-cast/CesiumMan.glb +0 -0
  74. package/templates/animation-studio/public/hifi-cast/RiggedFigure.glb +0 -0
  75. package/templates/animation-studio/public/hifi-cast/kaykit/Barbarian.glb +0 -0
  76. package/templates/animation-studio/public/hifi-cast/kaykit/Knight.glb +0 -0
  77. package/templates/animation-studio/public/hifi-cast/kaykit/Mage.glb +0 -0
  78. package/templates/animation-studio/public/hifi-cast/kaykit/Rogue.glb +0 -0
  79. package/templates/animation-studio/public/hifi-cast/kaykit/Rogue_Hooded.glb +0 -0
  80. package/templates/animation-studio/public/hifi-cast/khronos-report.json +295 -0
  81. package/templates/animation-studio/public/hifi-cast/quaternius-report.json +449 -0
  82. package/templates/animation-studio/public/hifi-cast/rpm/cesiumman.glb +0 -0
  83. package/templates/animation-studio/public/hifi-cast/rpm/fox.glb +0 -0
  84. package/templates/animation-studio/public/hifi-cast/rpm/riggedfigure.glb +0 -0
  85. package/templates/animation-studio/public/hifi-cast/rpm-report.json +301 -0
  86. package/templates/animation-studio/scripts/anim.ts +103 -0
  87. package/templates/animation-studio/scripts/animation-scene.ts +457 -0
  88. package/templates/animation-studio/scripts/asset-motion-probe.ts +450 -0
  89. package/templates/animation-studio/scripts/asset-render-probe.ts +152 -0
  90. package/templates/animation-studio/scripts/author-office-scene.ts +65 -0
  91. package/templates/animation-studio/scripts/build-cast-library.ts +97 -0
  92. package/templates/animation-studio/scripts/build-characters.ts +1543 -0
  93. package/templates/animation-studio/scripts/build-clip-library.ts +972 -0
  94. package/templates/animation-studio/scripts/clip-decision-cli.ts +51 -0
  95. package/templates/animation-studio/scripts/determinism-check.ts +48 -0
  96. package/templates/animation-studio/scripts/generate-scene.ts +95 -0
  97. package/templates/animation-studio/scripts/hifi-cast-validate.ts +572 -0
  98. package/templates/animation-studio/scripts/motion-evidence-cli.ts +150 -0
  99. package/templates/animation-studio/scripts/motion-evidence.ts +362 -0
  100. package/templates/animation-studio/scripts/preview-server.ts +52 -0
  101. package/templates/animation-studio/scripts/regen-manifest.ts +156 -0
  102. package/templates/animation-studio/scripts/render-core.ts +360 -0
  103. package/templates/animation-studio/scripts/render-live.ts +813 -0
  104. package/templates/animation-studio/scripts/render-parallel.ts +70 -0
  105. package/templates/animation-studio/scripts/render-server.ts +119 -0
  106. package/templates/animation-studio/scripts/resolve-asset.ts +669 -0
  107. package/templates/animation-studio/scripts/skeleton-overlay-cli.ts +82 -0
  108. package/templates/animation-studio/scripts/skeleton-overlay.ts +293 -0
  109. package/templates/animation-studio/scripts/studio-tools-demo.ts +45 -0
  110. package/templates/animation-studio/scripts/texture-bake.ts +297 -0
  111. package/templates/{cartoon-studio → animation-studio}/scripts/validate-characters.ts +0 -0
  112. package/templates/animation-studio/scripts/verify-extracted-overlay-cli.ts +264 -0
  113. package/templates/animation-studio/src/animation-episode-validator.ts +97 -0
  114. package/templates/animation-studio/src/animation-performance.ts +556 -0
  115. package/templates/animation-studio/src/aura-assets.ts +71 -0
  116. package/templates/animation-studio/src/character.ts +40 -0
  117. package/templates/animation-studio/src/director/compile-episode-document.ts +45 -0
  118. package/templates/animation-studio/src/director/director-heuristics.ts +697 -0
  119. package/templates/animation-studio/src/director/prompt-to-scene.ts +369 -0
  120. package/templates/animation-studio/src/empty-document.ts +77 -0
  121. package/templates/animation-studio/src/episode-document.ts +442 -0
  122. package/templates/animation-studio/src/examples/moon-garden.example.ts +183 -0
  123. package/templates/animation-studio/src/fidelity.ts +129 -0
  124. package/templates/animation-studio/src/graph.ts +14 -0
  125. package/templates/animation-studio/src/procedural-hdri.ts +243 -0
  126. package/templates/animation-studio/src/profile.ts +97 -0
  127. package/templates/animation-studio/src/render-live-route.ts +42 -0
  128. package/templates/animation-studio/src/render-modes.ts +137 -0
  129. package/templates/animation-studio/src/scene-player.ts +867 -0
  130. package/templates/animation-studio/src/set-templates.ts +361 -0
  131. package/templates/animation-studio/src/studio/episode-document-store.ts +72 -0
  132. package/templates/animation-studio/src/studio/scene-tool-registry.ts +86 -0
  133. package/templates/animation-studio/src/studio/scene-tools.ts +169 -0
  134. package/templates/animation-studio/tests/reports/animation-studio/hifi-cast-validation.json +938 -0
  135. package/templates/animation-studio/tsconfig.json +26 -0
  136. package/templates/{cartoon-studio → animation-studio}/vite.config.ts +6 -1
  137. package/templates/character-controller/README.md +33 -0
  138. package/templates/character-controller/index.html +19 -0
  139. package/templates/character-controller/package.json +23 -0
  140. package/templates/character-controller/playwright.config.ts +14 -0
  141. package/templates/character-controller/src/controller.ts +44 -0
  142. package/templates/character-controller/src/main.ts +62 -0
  143. package/templates/character-controller/tests/route-health.spec.ts +26 -0
  144. package/templates/character-controller/tests/screenshot.spec.ts +9 -0
  145. package/templates/character-controller/tsconfig.json +15 -0
  146. package/templates/cinematic-scene/package.json +1 -1
  147. package/templates/episode-builder/README.md +4 -4
  148. package/templates/episode-builder/package.json +1 -1
  149. package/templates/episode-builder/src/aura-assets.ts +1 -1
  150. package/templates/episode-builder/src/builder.ts +4 -4
  151. package/templates/episode-builder/src/episode.ts +21 -21
  152. package/templates/episode-builder/src/main.ts +20 -20
  153. package/templates/episode-builder/src/render-plan.ts +13 -13
  154. package/templates/episode-builder/tests/storyboard-playback.spec.ts +7 -7
  155. package/templates/fighting-game/package.json +1 -1
  156. package/templates/mini-game/package.json +1 -1
  157. package/templates/product-viewer/package.json +1 -1
  158. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/README.md +5 -5
  159. package/templates/{cartoon-channel → prompt-animation-channel}/index.html +1 -1
  160. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/package.json +2 -2
  161. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/aura-assets.ts +1 -1
  162. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/episode.ts +22 -22
  163. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/main.ts +28 -28
  164. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/render-plan.ts +13 -13
  165. package/templates/{cartoon-channel → prompt-animation-channel}/tests/route-health.spec.ts +1 -1
  166. package/templates/{prompt-cartoon-channel → prompt-animation-channel}/tests/storyboard-playback.spec.ts +8 -8
  167. package/templates/cartoon-studio/aura.assets.json +0 -400
  168. package/templates/cartoon-studio/package.json +0 -33
  169. package/templates/cartoon-studio/public/aura-assets/luma.authored.glb +0 -0
  170. package/templates/cartoon-studio/public/aura-assets/miko.authored.glb +0 -0
  171. package/templates/cartoon-studio/scripts/build-characters.ts +0 -738
  172. package/templates/cartoon-studio/scripts/build-dialogue-audio.ts +0 -262
  173. package/templates/cartoon-studio/scripts/episode.ts +0 -323
  174. package/templates/cartoon-studio/scripts/render-live.ts +0 -873
  175. package/templates/cartoon-studio/src/aura-assets.ts +0 -64
  176. package/templates/cartoon-studio/src/characters.ts +0 -65
  177. package/templates/cartoon-studio/src/episode-renderer.ts +0 -605
  178. package/templates/cartoon-studio/src/episode.ts +0 -306
  179. package/templates/cartoon-studio/src/main.ts +0 -400
  180. package/templates/cartoon-studio/src/render-live-route.ts +0 -913
  181. package/templates/cartoon-studio/src/render-plan.ts +0 -567
  182. package/templates/cartoon-studio/src/review.ts +0 -99
  183. package/templates/cartoon-studio/src/studio.ts +0 -101
  184. package/templates/cartoon-studio/test-results/.last-run.json +0 -4
  185. package/templates/cartoon-studio/tests/episode-render.spec.ts +0 -44
  186. package/templates/cartoon-studio/tests/motion-quality.spec.ts +0 -45
  187. package/templates/cartoon-studio/tests/route-health.spec.ts +0 -7
  188. package/templates/cartoon-studio/tests/storyboard-playback.spec.ts +0 -99
  189. package/templates/cartoon-studio/tests/visual-review.spec.ts +0 -21
  190. package/templates/cartoon-studio/tsconfig.json +0 -14
  191. package/templates/prompt-cartoon-channel/index.html +0 -13
  192. package/templates/prompt-cartoon-channel/src/sets.ts +0 -14
  193. /package/templates/{cartoon-channel → animation-channel}/aura.assets.json +0 -0
  194. /package/templates/{cartoon-channel → animation-channel}/playwright.config.ts +0 -0
  195. /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/luma.047f5e5f.glb +0 -0
  196. /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/luma.thumb.svg +0 -0
  197. /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/miko.047f5e5f.glb +0 -0
  198. /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/miko.thumb.svg +0 -0
  199. /package/templates/{cartoon-channel → animation-channel}/public/aura-assets/moon-garden-feature-frame.png +0 -0
  200. /package/templates/{cartoon-channel → animation-channel}/src/aura-assets.ts +0 -0
  201. /package/templates/{cartoon-channel → animation-channel}/src/characters.ts +0 -0
  202. /package/templates/{cartoon-channel → animation-channel}/src/contract.ts +0 -0
  203. /package/templates/{cartoon-channel → animation-channel}/src/sets.ts +0 -0
  204. /package/templates/{cartoon-channel → animation-channel}/test-results/.last-run.json +0 -0
  205. /package/templates/{cartoon-channel → animation-channel}/tests/reports/prompt-animation/cartoon-sample-episode.png +0 -0
  206. /package/templates/{cartoon-channel → animation-channel}/tsconfig.json +0 -0
  207. /package/templates/{cartoon-studio → animation-studio}/playwright.config.ts +0 -0
  208. /package/templates/{cartoon-studio → animation-studio}/public/aura-assets/luma.thumb.svg +0 -0
  209. /package/templates/{cartoon-studio → animation-studio}/public/aura-assets/miko.thumb.svg +0 -0
  210. /package/templates/{cartoon-studio → animation-studio}/src/contract.ts +0 -0
  211. /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/playwright.config.ts +0 -0
  212. /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/characters.ts +0 -0
  213. /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/src/contract.ts +0 -0
  214. /package/templates/{cartoon-studio → prompt-animation-channel}/src/sets.ts +0 -0
  215. /package/templates/{prompt-cartoon-channel → prompt-animation-channel}/tsconfig.json +0 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * scene-tools.ts — pure EpisodeDocument transforms.
3
+ *
4
+ * Each function returns a NEW document; the store validates + commits (or rejects). The
5
+ * agent never edits raw code — only these typed, validated edits.
6
+ */
7
+
8
+ import type { CameraPresetId } from "@aura3d/engine";
9
+ import type { BlockingWaypoint, CharacterBlocking, DialogueLine, EpisodeDocument, PropPlacement, ShotBlocking, ShotSpec, Vec3 } from "../episode-document";
10
+ import { estimateSpeechDuration } from "../episode-document";
11
+
12
+ /** Small breath between consecutive lines so speech doesn't run together. */
13
+ const INTER_LINE_GAP = 0.15;
14
+
15
+ /** Re-lay shots contiguously from t=0 (preserving each shot's duration); update the
16
+ * document duration + world-state span to match. Shots must cover the timeline with no
17
+ * gaps, so every shot edit re-contiguizes. */
18
+ function withShots(doc: EpisodeDocument, shots: readonly ShotSpec[]): EpisodeDocument {
19
+ let t = 0;
20
+ const rec = shots.map((s) => {
21
+ const dur = Math.max(0.1, s.endTime - s.startTime);
22
+ const out = { ...s, startTime: t, endTime: t + dur };
23
+ t += dur;
24
+ return out;
25
+ });
26
+ const duration = rec.length ? rec[rec.length - 1]!.endTime : doc.duration;
27
+ return { ...doc, shots: rec, duration, worldState: { glowSpanSeconds: duration } };
28
+ }
29
+
30
+ /** Append a shot (given a duration); the timeline re-contiguizes. */
31
+ export function addShot(doc: EpisodeDocument, shotId: string, presetId: CameraPresetId, duration: number, cameraSubject: Vec3): EpisodeDocument {
32
+ return withShots(doc, [...doc.shots, { shotId, presetId, startTime: 0, endTime: duration, cameraSubject }]);
33
+ }
34
+
35
+ /** Remove a shot; the timeline closes up. */
36
+ export function removeShot(doc: EpisodeDocument, shotId: string): EpisodeDocument {
37
+ return withShots(doc, doc.shots.filter((s) => s.shotId !== shotId));
38
+ }
39
+
40
+ /** Change a shot's duration; following shots shift to stay contiguous. */
41
+ export function retimeShot(doc: EpisodeDocument, shotId: string, duration: number): EpisodeDocument {
42
+ return withShots(doc, doc.shots.map((s) => (s.shotId === shotId ? { ...s, endTime: s.startTime + Math.max(0.1, duration) } : s)));
43
+ }
44
+
45
+ function upsertCharacterShot(
46
+ blocking: readonly CharacterBlocking[],
47
+ characterId: string,
48
+ shotId: string,
49
+ update: (beat: ShotBlocking) => ShotBlocking
50
+ ): CharacterBlocking[] {
51
+ const defaultBeat: ShotBlocking = { shotId, clip: "idle", waypoints: [] };
52
+ const existing = blocking.find((b) => b.characterId === characterId);
53
+ if (!existing) {
54
+ return [...blocking, { characterId, shots: [update(defaultBeat)] }];
55
+ }
56
+ return blocking.map((b) =>
57
+ b.characterId !== characterId
58
+ ? b
59
+ : {
60
+ ...b,
61
+ shots: b.shots.some((s) => s.shotId === shotId)
62
+ ? b.shots.map((s) => (s.shotId !== shotId ? s : update(s)))
63
+ : [...b.shots, update(defaultBeat)]
64
+ }
65
+ );
66
+ }
67
+
68
+ /** Set a character's blocking waypoints (and optionally clip) for a shot. */
69
+ export function setBlocking(
70
+ doc: EpisodeDocument,
71
+ characterId: string,
72
+ shotId: string,
73
+ waypoints: readonly BlockingWaypoint[],
74
+ clip?: string
75
+ ): EpisodeDocument {
76
+ const blocking = upsertCharacterShot(doc.blocking, characterId, shotId, (beat) => ({
77
+ ...beat,
78
+ clip: clip ?? beat.clip,
79
+ waypoints: [...waypoints]
80
+ }));
81
+ return { ...doc, blocking };
82
+ }
83
+
84
+ /** Schedule a clip/gesture for a character in a shot (keeps the existing waypoints). */
85
+ export function setGesture(doc: EpisodeDocument, characterId: string, shotId: string, clip: string): EpisodeDocument {
86
+ const blocking = upsertCharacterShot(doc.blocking, characterId, shotId, (beat) => ({ ...beat, clip }));
87
+ return { ...doc, blocking };
88
+ }
89
+
90
+ /** Set a shot's camera framing (preset and/or subject). */
91
+ export function setCamera(
92
+ doc: EpisodeDocument,
93
+ shotId: string,
94
+ opts: { preset?: CameraPresetId; subject?: Vec3 }
95
+ ): EpisodeDocument {
96
+ return {
97
+ ...doc,
98
+ shots: doc.shots.map((s) =>
99
+ s.shotId !== shotId ? s : { ...s, presetId: opts.preset ?? s.presetId, cameraSubject: opts.subject ?? s.cameraSubject }
100
+ )
101
+ };
102
+ }
103
+
104
+ /** Add a prop instance to the set dressing. */
105
+ export function placeProp(doc: EpisodeDocument, placement: PropPlacement): EpisodeDocument {
106
+ return { ...doc, setDressing: [...doc.setDressing, placement] };
107
+ }
108
+
109
+ /** Clear set dressing (all, or just one prop type). */
110
+ export function clearProps(doc: EpisodeDocument, propId?: string): EpisodeDocument {
111
+ return { ...doc, setDressing: propId ? doc.setDressing.filter((d) => d.propId !== propId) : [] };
112
+ }
113
+
114
+ /** Set a character's uniform scale (sizing a resolved GLB to read at the right height). */
115
+ export function setCharacterScale(doc: EpisodeDocument, characterId: string, scale: number): EpisodeDocument {
116
+ return { ...doc, assets: { ...doc.assets, characters: doc.assets.characters.map((c) => (c.id === characterId ? { ...c, scale } : c)) } };
117
+ }
118
+
119
+ /**
120
+ * Add or update a dialogue line (the scene's caption + lip-sync source / AuraVoice
121
+ * contract). When `endTime` is not explicitly provided (omitted, or <= startTime — the
122
+ * CLI's "unset" sentinel of 0), it is auto-computed as
123
+ * `startTime + estimateSpeechDuration(text)` so captions show only while the line would
124
+ * be spoken instead of lingering on a fixed window.
125
+ */
126
+ export function setDialogueLine(
127
+ doc: EpisodeDocument,
128
+ line: Omit<DialogueLine, "endTime" | "startTime"> & { startTime?: number; endTime?: number }
129
+ ): EpisodeDocument {
130
+ const existing = doc.dialogue ?? { language: "en", lines: [] };
131
+ const startTime = typeof line.startTime === "number" ? line.startTime : 0;
132
+ const endProvided = typeof line.endTime === "number" && line.endTime > startTime;
133
+ const resolved: DialogueLine = {
134
+ lineId: line.lineId,
135
+ speakerId: line.speakerId,
136
+ text: line.text,
137
+ startTime,
138
+ endTime: endProvided ? (line.endTime as number) : startTime + estimateSpeechDuration(line.text)
139
+ };
140
+ const lines = existing.lines.some((l) => l.lineId === resolved.lineId)
141
+ ? existing.lines.map((l) => (l.lineId === resolved.lineId ? resolved : l))
142
+ : [...existing.lines, resolved];
143
+ return { ...doc, dialogue: { language: existing.language, lines: [...lines].sort((a, b) => a.startTime - b.startTime) } };
144
+ }
145
+
146
+ /**
147
+ * Re-sequence ALL dialogue lines back-to-back from t=0 using estimateSpeechDuration, so
148
+ * captions are paced by speech length with a small inter-line gap and never overlap.
149
+ * Lines keep their existing document order (already startTime-sorted by setDialogueLine);
150
+ * each line's start = previous line's end + gap, end = start + estimated speech duration.
151
+ * Returns a NEW document; the computed start/end are stored in the dialogue track.
152
+ */
153
+ export function retimeDialogue(doc: EpisodeDocument): EpisodeDocument {
154
+ if (!doc.dialogue || doc.dialogue.lines.length === 0) return doc;
155
+ let t = 0;
156
+ const lines: DialogueLine[] = doc.dialogue.lines.map((l, i) => {
157
+ const start = i === 0 ? 0 : t + INTER_LINE_GAP;
158
+ const end = start + estimateSpeechDuration(l.text);
159
+ t = end;
160
+ return { ...l, startTime: start, endTime: end };
161
+ });
162
+ return { ...doc, dialogue: { ...doc.dialogue, lines } };
163
+ }
164
+
165
+ /** Remove a dialogue line. */
166
+ export function removeDialogueLine(doc: EpisodeDocument, lineId: string): EpisodeDocument {
167
+ if (!doc.dialogue) return doc;
168
+ return { ...doc, dialogue: { ...doc.dialogue, lines: doc.dialogue.lines.filter((l) => l.lineId !== lineId) } };
169
+ }