create-aura3d 1.1.2 → 1.3.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 (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 +6 -4
  4. package/dist/index.js.map +1 -1
  5. package/package.json +10 -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,442 @@
1
+ /**
2
+ * episode-document.ts — the Episode Document schema + deterministic sampling.
3
+ *
4
+ * This is the data-driven contract that makes the animation renderer GENERIC: every
5
+ * scene-specific decision that used to be a constant in `render-live-route.ts`
6
+ * (blocking marks, camera framing, the clip per shot, prop layout, the dim→sparkle
7
+ * world-state, even the primitive set + lights) is now DATA in an EpisodeDocument.
8
+ * The generic player (`scene-player.ts`) reads this document and renders it; it
9
+ * contains no scene-specific constants. A Director GENERATES documents
10
+ * from a prompt; the same player plays them.
11
+ *
12
+ * Placement note: this lives in the template for fast iteration (no engine dist
13
+ * rebuild). The design doc promotes these contracts into
14
+ * `packages/engine/src/animation-studio/*` once stable.
15
+ */
16
+
17
+ import type { CameraPresetId } from "@aura3d/engine";
18
+
19
+ export type Vec3 = readonly [number, number, number];
20
+ export type Vec4 = readonly [number, number, number, number];
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Assets
24
+ // ---------------------------------------------------------------------------
25
+ /** Provenance for a catalog-resolved asset (CC-BY attribution + source + hash). */
26
+ export interface AssetProvenance {
27
+ readonly attribution?: string;
28
+ readonly license?: string;
29
+ readonly sourceUrl?: string;
30
+ readonly hash?: string;
31
+ }
32
+
33
+ export interface CharacterAsset extends AssetProvenance {
34
+ readonly id: string;
35
+ readonly url: string;
36
+ /** Uniform scale applied to the loaded GLB so it reads at the intended height. */
37
+ readonly scale: number;
38
+ /** Fallback clip name (resolved against the GLB's real clip list by the player). */
39
+ readonly defaultClip: string;
40
+ /** Full clip list resolved from the GLB, so scheduled clips can be checked to exist. */
41
+ readonly availableClips?: readonly string[];
42
+ /** Morph-target index that drives mouth-open, or -1 when the GLB has no face morph. */
43
+ readonly mouthMorphIndex?: number;
44
+ /** How this cast member was sourced — drives honest provenance labelling in the UI. */
45
+ readonly source?: "catalog-resolved" | "user-uploaded" | "authored-default";
46
+ /** Combined fidelity grade (rig + mesh): A/B/C/D — surfaced so C output can be flagged previz. */
47
+ readonly fidelityGrade?: "A" | "B" | "C" | "D";
48
+ }
49
+
50
+ export interface PropAsset extends AssetProvenance {
51
+ readonly id: string;
52
+ /**
53
+ * GLB url for a prop with a real mesh. OMITTED for a prompt-derived object that has no resolved
54
+ * asset (e.g. "car" with no catalog match): it is still recorded so the prompt influences the
55
+ * document, but the renderer SKIPS it (procedural set dressing covers the space) rather than
56
+ * 404-ing on a fictional placeholder GLB.
57
+ */
58
+ readonly url?: string;
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Set (primitive pieces + lights + environment) — also data, so the player is generic.
63
+ // ---------------------------------------------------------------------------
64
+ export type GeometryKind = "cube" | "sphere" | "cylinder";
65
+
66
+ /** Dim→full emissive strengths a glow piece ramps between as the world-state advances. */
67
+ export interface GlowSpec {
68
+ readonly dim: number;
69
+ readonly full: number;
70
+ }
71
+
72
+ export interface SetPiece {
73
+ readonly id: string;
74
+ readonly geometry: GeometryKind;
75
+ readonly position: Vec3;
76
+ readonly scale: Vec3;
77
+ readonly baseColor: Vec4;
78
+ readonly metallic?: number;
79
+ readonly roughness?: number;
80
+ readonly emissiveColor?: Vec3;
81
+ readonly emissiveStrength?: number;
82
+ /** When present, the world-state drives emissiveStrength from dim→full over the act. */
83
+ readonly glow?: GlowSpec;
84
+ /** Optional z-roll (radians) for tilted props like the broom handle. */
85
+ readonly roll?: number;
86
+ readonly includeInAutoFrame?: boolean;
87
+ }
88
+
89
+ export interface LightSpec {
90
+ readonly id: string;
91
+ readonly kind: "point";
92
+ readonly color: Vec3;
93
+ readonly position: Vec3;
94
+ readonly intensity: number;
95
+ readonly range: number;
96
+ }
97
+
98
+ export interface EnvironmentSpec {
99
+ readonly color: Vec3;
100
+ readonly intensity: number;
101
+ readonly proceduralMap: {
102
+ readonly skyColor: Vec3;
103
+ readonly horizonColor: Vec3;
104
+ readonly groundColor: Vec3;
105
+ readonly specularColor: Vec3;
106
+ readonly intensity: number;
107
+ readonly specularIntensity: number;
108
+ };
109
+ /**
110
+ * Optional real IBL (M3). When present, the player BAKES a 1024×512 equirectangular HDRI
111
+ * from this gradient (sky→horizon→ground + an optional sun disc) and wires it as the
112
+ * engine's `environmentMapTexture` (true image-based lighting for PBR mode), on TOP of the
113
+ * procedural ambient. `intensity`/`specularIntensity` scale the diffuse irradiance and the
114
+ * specular reflection sampled from the baked map. Absent → procedural-map IBL only.
115
+ */
116
+ readonly hdri?: EnvironmentHdriSpec;
117
+ }
118
+
119
+ /** A per-set equirectangular HDRI description the player bakes into a real IBL texture (M3). */
120
+ export interface EnvironmentHdriSpec {
121
+ /** Zenith (top of the equirect) colour. */
122
+ readonly skyColor: Vec3;
123
+ /** Colour at the horizon line (equator). */
124
+ readonly horizonColor: Vec3;
125
+ /** Nadir (bottom / ground bounce) colour. */
126
+ readonly groundColor: Vec3;
127
+ /** Optional sun/key disc baked into the sky hemisphere for a directional specular highlight. */
128
+ readonly sun?: {
129
+ readonly color: Vec3;
130
+ /** Brightness multiplier of the sun disc (HDR-ish; >1 is allowed, the bake clamps to 8-bit). */
131
+ readonly intensity: number;
132
+ /** Azimuth around the horizon, radians (0 = +Z behind camera). */
133
+ readonly azimuth: number;
134
+ /** Elevation above the horizon, radians (0 = horizon, π/2 = zenith). */
135
+ readonly elevation: number;
136
+ /** Angular radius of the disc, radians. */
137
+ readonly angularRadius: number;
138
+ };
139
+ /** Diffuse-irradiance scale for the sampled map (engine `environmentMapIntensity`). Default 1. */
140
+ readonly intensity?: number;
141
+ /** Specular-reflection scale for the sampled map (engine `environmentMapSpecularIntensity`). Default 0.4. */
142
+ readonly specularIntensity?: number;
143
+ }
144
+
145
+ export interface SetSpec {
146
+ readonly clearColor: Vec4;
147
+ /** Scale on the studio softbox rig (the directional key/fill/rim come from `lights`). */
148
+ readonly studioLightingScale: number;
149
+ readonly environment: EnvironmentSpec;
150
+ readonly pieces: readonly SetPiece[];
151
+ readonly lights: readonly LightSpec[];
152
+ /**
153
+ * High-fidelity rendering toggles (#12). Default OFF so the standard render
154
+ * path stays stable; the Director or scene tools can opt a document in.
155
+ * - inShaderCel: non-glow set pieces use the engine's REAL AnimationToonMaterial
156
+ * (GPU-side banded N·L + Fresnel rim), in addition to the CPU toon post-pass.
157
+ * - realShadows: enable the studio rig's shadow maps (cast/receive) instead of only
158
+ * the cheap contact-shadow blobs.
159
+ */
160
+ readonly inShaderCel?: boolean;
161
+ readonly realShadows?: boolean;
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Shots + camera
166
+ // ---------------------------------------------------------------------------
167
+ export interface ShotSpec {
168
+ readonly shotId: string;
169
+ readonly presetId: CameraPresetId;
170
+ /** Episode-time window (seconds) this shot is on screen. */
171
+ readonly startTime: number;
172
+ readonly endTime: number;
173
+ /** World point the camera frames for this shot (auto-framed or directed). */
174
+ readonly cameraSubject: Vec3;
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Blocking (per character, per shot waypoint paths) + performance (clip per beat)
179
+ // ---------------------------------------------------------------------------
180
+ export interface BlockingWaypoint {
181
+ /** Absolute episode time (seconds). */
182
+ readonly time: number;
183
+ readonly position: Vec3;
184
+ readonly yaw: number;
185
+ }
186
+
187
+ export interface ShotBlocking {
188
+ readonly shotId: string;
189
+ /** Clip to play during this beat (resolved against the GLB's real clips by the player). */
190
+ readonly clip: string;
191
+ readonly sweeping?: boolean;
192
+ /** 1+ waypoints; the player lerps position/yaw across them within the shot window. */
193
+ readonly waypoints: readonly BlockingWaypoint[];
194
+ }
195
+
196
+ export interface CharacterBlocking {
197
+ readonly characterId: string;
198
+ readonly shots: readonly ShotBlocking[];
199
+ }
200
+
201
+ // ---------------------------------------------------------------------------
202
+ // Set dressing (prop instances) + world-state
203
+ // ---------------------------------------------------------------------------
204
+ export interface PropPlacement {
205
+ readonly propId: string;
206
+ readonly position: Vec3;
207
+ readonly scale: number;
208
+ /** |minY| of the prop geometry (pre-scale) so it sits on the ground (y=0). */
209
+ readonly feetOffset: number;
210
+ }
211
+
212
+ export interface WorldStateTrack {
213
+ /** Seconds over which glow pieces ramp dim→full (the dim→sparkle world-state). */
214
+ readonly glowSpanSeconds: number;
215
+ }
216
+
217
+ // ---------------------------------------------------------------------------
218
+ // Dialogue (the script — also the AuraVoice contract). Lives IN the document so a
219
+ // generated/edited scene carries its own captions/lip-sync, not the Moon Garden's.
220
+ // ---------------------------------------------------------------------------
221
+ export interface DialogueLine {
222
+ readonly lineId: string;
223
+ readonly speakerId: string;
224
+ readonly text: string;
225
+ readonly startTime: number;
226
+ readonly endTime: number;
227
+ }
228
+
229
+ export interface DialogueTrack {
230
+ readonly language: string;
231
+ readonly lines: readonly DialogueLine[];
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Speech timing model (Phase C1). Captions must show only while a line would be
236
+ // SPOKEN — not on a fixed window — so the duration is estimated from the text.
237
+ // ---------------------------------------------------------------------------
238
+ /** Conversational speaking rate (words per minute). 165 wpm is a natural mid-pace. */
239
+ export const SPEECH_WPM = 165;
240
+ /** Extra dwell for a soft pause (`,` `;` `:`). */
241
+ const PAUSE_SOFT = 0.25;
242
+ /** Extra dwell for a sentence end (`.` `!` `?`). */
243
+ const PAUSE_HARD = 0.4;
244
+ /** Floor so short interjections ("Hi!") still read; cap so one line can't run away. */
245
+ const DURATION_FLOOR = 0.9;
246
+ const DURATION_CAP = 22;
247
+
248
+ /**
249
+ * Estimate how long `text` would take to speak, in seconds. Model:
250
+ * words / SPEECH_WPM (→ seconds) + punctuation pauses (soft/hard) clamped to a
251
+ * short-interjection floor and a sane upper cap. Deterministic + monotonic in word
252
+ * count (more words never shortens the estimate), so it drives caption windows,
253
+ * document retiming and the viseme cadence consistently.
254
+ */
255
+ export function estimateSpeechDuration(text: string): number {
256
+ const trimmed = (text ?? "").trim();
257
+ if (trimmed.length === 0) return DURATION_FLOOR;
258
+ const words = trimmed.split(/\s+/).filter(Boolean).length;
259
+ const speaking = (words / SPEECH_WPM) * 60;
260
+ const soft = (trimmed.match(/[,;:]/g) ?? []).length * PAUSE_SOFT;
261
+ const hard = (trimmed.match(/[.!?]/g) ?? []).length * PAUSE_HARD;
262
+ const raw = speaking + soft + hard;
263
+ return Math.min(DURATION_CAP, Math.max(DURATION_FLOOR, raw));
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // The document
268
+ // ---------------------------------------------------------------------------
269
+ export interface EpisodeDocument {
270
+ readonly id: string;
271
+ readonly duration: number;
272
+ readonly assets: {
273
+ readonly characters: readonly CharacterAsset[];
274
+ readonly props: readonly PropAsset[];
275
+ };
276
+ readonly set: SetSpec;
277
+ /** Area characters + props must stay within (used by the Director + coherence gate). */
278
+ readonly walkableBounds?: { readonly min: Vec3; readonly max: Vec3 };
279
+ readonly shots: readonly ShotSpec[];
280
+ readonly blocking: readonly CharacterBlocking[];
281
+ readonly setDressing: readonly PropPlacement[];
282
+ readonly worldState: WorldStateTrack;
283
+ /** The script + captions + lip-sync source — the AuraVoice contract for THIS scene. */
284
+ readonly dialogue?: DialogueTrack;
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // Deterministic sampling — the generic player calls only these.
289
+ // ---------------------------------------------------------------------------
290
+ function clamp01(x: number): number {
291
+ return x < 0 ? 0 : x > 1 ? 1 : x;
292
+ }
293
+
294
+ /** Smoothstep eased 0..1 (matches the original setGardenGlow curve). */
295
+ function smoothstep(x: number): number {
296
+ const t = clamp01(x);
297
+ return t * t * (3 - 2 * t);
298
+ }
299
+
300
+ /** The shot on screen at episode time `t`. */
301
+ export function shotAtTime(doc: EpisodeDocument, t: number): ShotSpec {
302
+ let chosen = doc.shots[0]!;
303
+ for (const shot of doc.shots) {
304
+ if (t >= shot.startTime) chosen = shot;
305
+ }
306
+ return chosen;
307
+ }
308
+
309
+ export interface SampledBlocking {
310
+ readonly position: Vec3;
311
+ readonly yaw: number;
312
+ readonly clip: string;
313
+ readonly sweeping: boolean;
314
+ /** True when the character is translating between distinct waypoints (→ walk clip). */
315
+ readonly moving: boolean;
316
+ }
317
+
318
+ /**
319
+ * Sample a character's blocking at episode time `t`: find the active shot's blocking,
320
+ * then lerp position/yaw across its waypoints by time. Single-waypoint beats hold a
321
+ * static mark (the original staging behaviour); multi-waypoint beats traverse.
322
+ */
323
+ export function sampleBlocking(
324
+ doc: EpisodeDocument,
325
+ characterId: string,
326
+ t: number,
327
+ fallback: { readonly position: Vec3; readonly yaw: number; readonly clip: string }
328
+ ): SampledBlocking {
329
+ const shot = shotAtTime(doc, t);
330
+ const charBlocking = doc.blocking.find((b) => b.characterId === characterId);
331
+ const beat = charBlocking?.shots.find((s) => s.shotId === shot.shotId);
332
+ if (!beat || beat.waypoints.length === 0) {
333
+ return { position: fallback.position, yaw: fallback.yaw, clip: fallback.clip, sweeping: false, moving: false };
334
+ }
335
+ const wps = beat.waypoints;
336
+ if (wps.length === 1) {
337
+ return { position: wps[0]!.position, yaw: wps[0]!.yaw, clip: beat.clip, sweeping: Boolean(beat.sweeping), moving: false };
338
+ }
339
+ // Find the waypoint segment containing t.
340
+ let a = wps[0]!;
341
+ let b = wps[wps.length - 1]!;
342
+ for (let i = 0; i < wps.length - 1; i += 1) {
343
+ if (t >= wps[i]!.time && t <= wps[i + 1]!.time) {
344
+ a = wps[i]!;
345
+ b = wps[i + 1]!;
346
+ break;
347
+ }
348
+ }
349
+ const span = Math.max(1e-3, b.time - a.time);
350
+ const f = clamp01((t - a.time) / span);
351
+ const lerp = (x: number, y: number): number => x + (y - x) * f;
352
+ // VELOCITY-gated, not displacement-gated: a character that drifts a tiny distance over a long
353
+ // time (e.g. 0.7m across an 8s shot = 0.09 m/s) is effectively STANDING — playing a full walk
354
+ // leg-cycle there reads as "walking in place while standing still". Only a real translation
355
+ // SPEED counts as moving. (≈0.35 m/s ≈ a slow amble; below it the character performs/idles.)
356
+ const WALK_MIN_SPEED = 0.35; // metres/second
357
+ const segmentDistance = Math.hypot(b.position[0] - a.position[0], b.position[2] - a.position[2]);
358
+ const speed = segmentDistance / span;
359
+ const moved = speed >= WALK_MIN_SPEED && f > 0 && f < 1;
360
+ return {
361
+ position: [lerp(a.position[0], b.position[0]), lerp(a.position[1], b.position[1]), lerp(a.position[2], b.position[2])],
362
+ yaw: lerp(a.yaw, b.yaw),
363
+ clip: beat.clip,
364
+ sweeping: Boolean(beat.sweeping),
365
+ moving: moved
366
+ };
367
+ }
368
+
369
+ /** Eased dim→sparkle world-state at episode time `t` (0 = dim, 1 = full twinkle). */
370
+ export function sampleWorldStateGlow(doc: EpisodeDocument, t: number): number {
371
+ return smoothstep(t / Math.max(1e-3, doc.worldState.glowSpanSeconds));
372
+ }
373
+
374
+ /** The active caption at episode time `t`, from the document's dialogue. */
375
+ export function sampleCaption(doc: EpisodeDocument, t: number): { text: string; speakerId: string; lineId: string } {
376
+ const line = doc.dialogue?.lines.find((l) => t >= l.startTime && t < l.endTime);
377
+ return line ? { text: line.text, speakerId: line.speakerId, lineId: line.lineId } : { text: "", speakerId: "", lineId: "" };
378
+ }
379
+
380
+ /**
381
+ * Lip-sync mouth-openness for `speakerId` at `t`, derived deterministically from the
382
+ * document's dialogue (a speaking mouth pulse). Drives the GLB mouth morph for rigs that
383
+ * have one; 0 when the speaker isn't talking. (The full per-phoneme viseme track is
384
+ * AuraVoice's; this is the engine-side fallback driver.)
385
+ */
386
+ export function sampleVisemeOpenness(doc: EpisodeDocument, t: number, speakerId: string): { mouthOpenness: number; visemeId: string } {
387
+ const line = doc.dialogue?.lines.find((l) => l.speakerId === speakerId && t >= l.startTime && t < l.endTime);
388
+ if (!line) return { mouthOpenness: 0, visemeId: "sil" };
389
+ const open = Math.max(0, 0.5 + 0.32 * Math.sin((t - line.startTime) * 9));
390
+ return { mouthOpenness: open, visemeId: open > 0.4 ? "ah" : "m" };
391
+ }
392
+
393
+ // ---------------------------------------------------------------------------
394
+ // Validation (minimal; extended by the animation-episode-validator).
395
+ // ---------------------------------------------------------------------------
396
+ export interface DocumentValidationResult {
397
+ readonly ok: boolean;
398
+ readonly errors: readonly string[];
399
+ }
400
+
401
+ export function validateEpisodeDocumentShape(doc: EpisodeDocument): DocumentValidationResult {
402
+ const errors: string[] = [];
403
+ if (doc.duration <= 0) errors.push("duration must be > 0");
404
+ if (doc.shots.length === 0) errors.push("at least one shot is required");
405
+ // NOTE: an empty cast is a VALID skeleton (a fresh prompt-built scene before `cast add`
406
+ // resolves real characters). The coherence validator surfaces it as a warning, not a hard
407
+ // error, so the skeleton persists + renders a set while the directing agent adds the cast.
408
+ // Shots cover the timeline contiguously from 0.
409
+ const sorted = [...doc.shots].sort((x, y) => x.startTime - y.startTime);
410
+ if (sorted[0] && sorted[0].startTime !== 0) errors.push("shots must start at t=0");
411
+ for (let i = 0; i < sorted.length - 1; i += 1) {
412
+ if (Math.abs(sorted[i]!.endTime - sorted[i + 1]!.startTime) > 1e-6) {
413
+ errors.push(`shot gap/overlap between ${sorted[i]!.shotId} and ${sorted[i + 1]!.shotId}`);
414
+ }
415
+ }
416
+ // Blocking references real characters/shots.
417
+ const charIds = new Set(doc.assets.characters.map((c) => c.id));
418
+ const shotIds = new Set(doc.shots.map((s) => s.shotId));
419
+ for (const b of doc.blocking) {
420
+ if (!charIds.has(b.characterId)) errors.push(`blocking references unknown character ${b.characterId}`);
421
+ for (const s of b.shots) if (!shotIds.has(s.shotId)) errors.push(`blocking references unknown shot ${s.shotId}`);
422
+ }
423
+ // Set dressing references real props.
424
+ const propIds = new Set(doc.assets.props.map((p) => p.id));
425
+ for (const d of doc.setDressing) {
426
+ if (!propIds.has(d.propId)) errors.push(`set dressing references unknown prop ${d.propId}`);
427
+ }
428
+ return { ok: errors.length === 0, errors };
429
+ }
430
+
431
+ /** One-line-per-fact summary of a document (for CLI output / agent grounding). */
432
+ export function summarizeDocument(doc: EpisodeDocument): string {
433
+ return [
434
+ `id=${doc.id} duration=${doc.duration}s`,
435
+ `cast: ${doc.assets.characters.map((c) => c.id).join(", ")}`,
436
+ `shots: ${doc.shots.map((s) => `${s.shotId}[${s.startTime}-${s.endTime}] ${s.presetId}`).join(", ")}`,
437
+ `props placed: ${doc.setDressing.length}`,
438
+ doc.walkableBounds
439
+ ? `walkable: x[${doc.walkableBounds.min[0]}..${doc.walkableBounds.max[0]}] z[${doc.walkableBounds.min[2]}..${doc.walkableBounds.max[2]}]`
440
+ : "walkable: (unbounded)"
441
+ ].join("\n");
442
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * moon-garden.example.ts — the Moon Garden scene as an EpisodeDocument.
3
+ *
4
+ * THIS IS AN EXAMPLE FIXTURE, NOT THE DEFAULT. Nothing in the product loads it
5
+ * automatically. It exists only so a user can EXPLICITLY render the reference scene
6
+ * (e.g. point `AURA_DOCUMENT` at a serialized copy) and as test/demo material. The
7
+ * generic `scene-player.ts` renders ANY EpisodeDocument; this one happens to be the
8
+ * hand-authored Moon Garden used to validate that the schema captures a full scene
9
+ * (cast, staging, camera, set, lights, props, dialogue). Real scenes come from
10
+ * `animation-scene new --prompt`, never from this file.
11
+ */
12
+
13
+ import type { EpisodeDocument, SetPiece, Vec3, Vec4 } from "../episode-document";
14
+
15
+ const DURATION = 60;
16
+ const YAW = (turns: number): number => Math.PI * turns;
17
+
18
+ // --- glow/material helpers (keep the data terse + faithful to makeGlowMaterial) ---
19
+ function glowPiece(
20
+ id: string,
21
+ geometry: SetPiece["geometry"],
22
+ baseColor: Vec4,
23
+ emissiveColor: Vec3,
24
+ dim: number,
25
+ full: number,
26
+ position: Vec3,
27
+ scale: Vec3
28
+ ): SetPiece {
29
+ return { id, geometry, position, scale, baseColor, metallic: 0, roughness: 0.85, emissiveColor, glow: { dim, full } };
30
+ }
31
+
32
+ const STONE_GOLD: Vec4 = [1, 0.882, 0.557, 1];
33
+ const STONE_CYAN: Vec4 = [0.49, 0.886, 1, 1];
34
+ const STAR_BASE: Vec4 = [0.9, 0.95, 1, 1];
35
+
36
+ function star(index: number, position: Vec3): SetPiece {
37
+ const size = 0.12 + (index % 3) * 0.05;
38
+ return glowPiece(`star-${index}`, "sphere", STAR_BASE, [0.78, 0.88, 1], 0.5, 1.2, position, [size, size, size]);
39
+ }
40
+
41
+ const STAR_Z = -9;
42
+ const STAR_POSITIONS: Vec3[] = [
43
+ [-9.5, 7.5, STAR_Z], [-7.0, 5.2, STAR_Z], [-8.2, 3.0, STAR_Z], [-5.5, 8.0, STAR_Z],
44
+ [-3.8, 6.0, STAR_Z], [-6.2, 1.6, STAR_Z], [5.4, 7.6, STAR_Z], [7.2, 5.4, STAR_Z],
45
+ [8.6, 8.2, STAR_Z], [8.0, 2.8, STAR_Z], [4.2, 8.6, STAR_Z], [-10.5, 5.0, STAR_Z],
46
+ [9.8, 4.0, STAR_Z], [-4.6, 2.2, STAR_Z], [6.0, 1.8, STAR_Z], [0.5, 8.8, STAR_Z]
47
+ ];
48
+
49
+ export const moonGardenDocument: EpisodeDocument = {
50
+ id: "moon-garden-001",
51
+ duration: DURATION,
52
+ assets: {
53
+ characters: [
54
+ { id: "miko", url: "/aura-assets/miko.catalog.glb", scale: 1.6, defaultClip: "Idle", mouthMorphIndex: -1 },
55
+ { id: "luma", url: "/aura-assets/luma2.catalog.glb", scale: 1.6, defaultClip: "Idle", mouthMorphIndex: -1 }
56
+ ],
57
+ props: [{ id: "mushroom", url: "/aura-assets/mushroom.catalog.glb" }]
58
+ },
59
+ set: {
60
+ clearColor: [0.043, 0.058, 0.101, 1],
61
+ studioLightingScale: 0.4,
62
+ environment: {
63
+ color: [0.34, 0.4, 0.52],
64
+ intensity: 0.32,
65
+ proceduralMap: {
66
+ skyColor: [0.16, 0.22, 0.34],
67
+ horizonColor: [0.22, 0.26, 0.32],
68
+ groundColor: [0.05, 0.06, 0.08],
69
+ specularColor: [0.85, 0.9, 1],
70
+ intensity: 0.4,
71
+ specularIntensity: 0.7
72
+ }
73
+ },
74
+ pieces: [
75
+ // Crater/garden floor (auto-framed).
76
+ {
77
+ id: "garden-floor",
78
+ geometry: "cube",
79
+ position: [0, -0.06, 0],
80
+ scale: [60, 0.12, 60],
81
+ baseColor: [0.09, 0.29, 0.245, 1],
82
+ metallic: 0.02,
83
+ roughness: 0.78,
84
+ emissiveColor: [0.03, 0.09, 0.08],
85
+ emissiveStrength: 0.18,
86
+ includeInAutoFrame: true
87
+ },
88
+ // Far night-sky backdrop.
89
+ {
90
+ id: "garden-sky",
91
+ geometry: "cube",
92
+ position: [0, 8.0, -12],
93
+ scale: [60, 36, 0.4],
94
+ baseColor: [0.04, 0.16, 0.255, 1],
95
+ metallic: 0,
96
+ roughness: 1,
97
+ emissiveColor: [0.06, 0.28, 0.4],
98
+ emissiveStrength: 0.55
99
+ },
100
+ // Moon halo + orb.
101
+ glowPiece("moon-halo", "sphere", [0.4, 0.72, 0.95, 1], [0.32, 0.62, 0.9], 0.35, 0.62, [2.5, 2.55, -6.2], [3.9, 3.9, 3.9]),
102
+ glowPiece("moon-orb", "sphere", [0.62, 0.84, 1, 1], [0.36, 0.62, 0.88], 0.4, 0.62, [2.5, 2.55, -6.0], [2.1, 2.1, 2.1]),
103
+ // Stars.
104
+ ...STAR_POSITIONS.map((p, i) => star(i, p)),
105
+ // Glow stones.
106
+ glowPiece("glow-stone-l", "sphere", STONE_GOLD, [1, 0.882, 0.557], 0.12, 1.85, [-1.7, 0.12, 0.45], [0.26, 0.13, 0.2]),
107
+ glowPiece("glow-stone-c", "sphere", STONE_CYAN, [0.49, 0.886, 1], 0.1, 1.7, [-0.15, 0.1, 0.35], [0.2, 0.1, 0.16]),
108
+ glowPiece("glow-stone-r", "sphere", STONE_GOLD, [1, 0.882, 0.557], 0.12, 1.85, [1.75, 0.12, 0.4], [0.26, 0.13, 0.2]),
109
+ glowPiece("glow-stone-back", "sphere", STONE_CYAN, [0.49, 0.886, 1], 0.1, 1.7, [0.55, 0.1, -0.7], [0.2, 0.1, 0.16]),
110
+ // Broom prop (handle + glowing bristles).
111
+ {
112
+ id: "broom-handle",
113
+ geometry: "cylinder",
114
+ position: [-0.55, 0.5, 0.25],
115
+ scale: [0.045, 1.0, 0.045],
116
+ roll: -0.55,
117
+ baseColor: [0.55, 0.42, 0.26, 1],
118
+ metallic: 0.1,
119
+ roughness: 0.7,
120
+ emissiveColor: [0.12, 0.1, 0.06],
121
+ emissiveStrength: 0.1
122
+ },
123
+ {
124
+ ...glowPiece("broom-bristles", "cube", STONE_GOLD, [1, 0.882, 0.557], 0.18, 1.0, [-0.82, 0.18, 0.27], [0.32, 0.18, 0.12]),
125
+ roll: -0.2
126
+ }
127
+ ],
128
+ lights: [
129
+ { id: "garden-key", kind: "point", color: [0.86, 0.93, 1], position: [-2.6, 3.6, 2.6], intensity: 4.4, range: 18 },
130
+ { id: "garden-cyan-fill", kind: "point", color: [0.49, 0.886, 1], position: [2.4, 2.0, 1.4], intensity: 2.0, range: 13 },
131
+ { id: "garden-warm-rim", kind: "point", color: [1, 0.78, 0.45], position: [0.4, 1.8, -2.0], intensity: 2.8, range: 13 }
132
+ ]
133
+ },
134
+ walkableBounds: { min: [-3.6, 0, -2], max: [3.6, 0, 2] },
135
+ shots: [
136
+ { shotId: "shot-moon-garden-open", presetId: "establishing", startTime: 0, endTime: 20, cameraSubject: [0.025, 0.75, 0] },
137
+ { shotId: "shot-glow-stone-teamwork", presetId: "two-shot", startTime: 20, endTime: 42, cameraSubject: [-0.06, 0.75, 0.215] },
138
+ { shotId: "shot-moon-garden-finish", presetId: "close-up", startTime: 42, endTime: 60, cameraSubject: [-0.95, -0.2, 0] }
139
+ ],
140
+ blocking: [
141
+ {
142
+ characterId: "miko",
143
+ shots: [
144
+ { shotId: "shot-moon-garden-open", clip: "Loops", waypoints: [{ time: 0, position: [-0.95, 0, 0], yaw: YAW(0.12) }] },
145
+ { shotId: "shot-glow-stone-teamwork", clip: "Loops", sweeping: true, waypoints: [{ time: 20, position: [-0.37, 0, 0.23], yaw: YAW(-0.5) }] },
146
+ { shotId: "shot-moon-garden-finish", clip: "Loops", waypoints: [{ time: 42, position: [-0.65, 0, 0], yaw: YAW(0.12) }] }
147
+ ]
148
+ },
149
+ {
150
+ characterId: "luma",
151
+ shots: [
152
+ { shotId: "shot-moon-garden-open", clip: "Run", waypoints: [{ time: 0, position: [1.0, 0, 0], yaw: YAW(-0.12) }] },
153
+ { shotId: "shot-glow-stone-teamwork", clip: "Run", waypoints: [{ time: 20, position: [0.25, 0, 0.2], yaw: YAW(-0.62) }] },
154
+ { shotId: "shot-moon-garden-finish", clip: "Idle", waypoints: [{ time: 42, position: [0.68, 0, 0], yaw: YAW(-0.12) }] }
155
+ ]
156
+ }
157
+ ],
158
+ setDressing: [
159
+ { propId: "mushroom", position: [-2.4, 0, 0.4], scale: 0.16, feetOffset: 2.8 },
160
+ { propId: "mushroom", position: [2.5, 0, 0.2], scale: 0.14, feetOffset: 2.8 },
161
+ { propId: "mushroom", position: [-1.6, 0, 1.0], scale: 0.1, feetOffset: 2.8 },
162
+ { propId: "mushroom", position: [1.8, 0, 0.9], scale: 0.11, feetOffset: 2.8 },
163
+ { propId: "mushroom", position: [0.2, 0, -1.5], scale: 0.13, feetOffset: 2.8 },
164
+ { propId: "mushroom", position: [-3.4, 0, -0.6], scale: 0.08, feetOffset: 2.8 },
165
+ { propId: "mushroom", position: [3.3, 0, -0.4], scale: 0.085, feetOffset: 2.8 },
166
+ { propId: "mushroom", position: [-2.7, 0, -1.6], scale: 0.07, feetOffset: 2.8 },
167
+ { propId: "mushroom", position: [2.6, 0, -1.7], scale: 0.075, feetOffset: 2.8 },
168
+ { propId: "mushroom", position: [-0.9, 0, 1.4], scale: 0.06, feetOffset: 2.8 },
169
+ { propId: "mushroom", position: [1.1, 0, 1.5], scale: 0.065, feetOffset: 2.8 }
170
+ ],
171
+ worldState: { glowSpanSeconds: DURATION },
172
+ dialogue: {
173
+ language: "en",
174
+ lines: [
175
+ { lineId: "shot-moon-garden-open:line-1", speakerId: "miko", startTime: 0, endTime: 10, text: "Luma, the moon lilies are losing their sparkle." },
176
+ { lineId: "shot-moon-garden-open:line-2", speakerId: "luma", startTime: 10, endTime: 20, text: "Then we sweep softly and wake the glow stones." },
177
+ { lineId: "shot-glow-stone-teamwork:line-1", speakerId: "miko", startTime: 20, endTime: 31, text: "I will polish the blue stones one tiny circle at a time." },
178
+ { lineId: "shot-glow-stone-teamwork:line-2", speakerId: "luma", startTime: 31, endTime: 42, text: "And I will hum the garden's sleepy cleanup song." },
179
+ { lineId: "shot-moon-garden-finish:line-1", speakerId: "luma", startTime: 42, endTime: 51, text: "The moon lilies are twinkling again." },
180
+ { lineId: "shot-moon-garden-finish:line-2", speakerId: "miko", startTime: 51, endTime: 60, text: "Goodnight, little garden. Keep glowing." }
181
+ ]
182
+ }
183
+ };