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,150 @@
1
+ /**
2
+ * motion-evidence-cli.ts — write the Phase B4/B5 motion-evidence artifacts to disk.
3
+ *
4
+ * Produces:
5
+ * B4 tests/reports/standard-clips/<intent>.png (8 strips, one per standard intent)
6
+ * tests/reports/standard-clips/summary.json (per-intent bones/maxRot/firstFinalDiff)
7
+ * B5 tests/reports/rig-overlays/<rigName>.png (5 retargeting-overlay strips)
8
+ * tests/reports/rig-overlays/summary.json (per-rig grade + coverage + refusal flag)
9
+ *
10
+ * Standalone (no Playwright/GPU): reuses `buildSkeletonStrip`'s FK projection and `sharp` for PNG
11
+ * encoding only. Deterministic. Emits a final machine-readable JSON line the test parses.
12
+ *
13
+ * Usage:
14
+ * tsx scripts/motion-evidence-cli.ts --out tests/reports [--clip gesture]
15
+ */
16
+ import { writeFileSync, mkdirSync, existsSync } from "node:fs";
17
+ import { dirname, resolve, join } from "node:path";
18
+ import { createRequire } from "node:module";
19
+ import { fileURLToPath } from "node:url";
20
+ import {
21
+ buildAllIntentEvidence,
22
+ buildAllRigEvidence,
23
+ type IntentEvidence,
24
+ type RigEvidence
25
+ } from "./motion-evidence.js";
26
+
27
+ interface SharpModule {
28
+ (input: Buffer | Uint8Array, options?: { raw: { width: number; height: number; channels: number } }): {
29
+ png(): { toBuffer(): Promise<Buffer> };
30
+ };
31
+ }
32
+ function loadSharp(): SharpModule {
33
+ try {
34
+ return createRequire(import.meta.url)("sharp") as SharpModule;
35
+ } catch {
36
+ const roots = [process.cwd(), dirname(fileURLToPath(import.meta.url))];
37
+ for (const start of roots) {
38
+ let dir = start;
39
+ for (let up = 0; up < 8; up += 1) {
40
+ const storePkg = resolve(dir, "node_modules/.pnpm/sharp@0.33.5/node_modules/sharp/package.json");
41
+ if (existsSync(storePkg)) return createRequire(storePkg)("sharp") as SharpModule;
42
+ const parent = dirname(dir);
43
+ if (parent === dir) break;
44
+ dir = parent;
45
+ }
46
+ }
47
+ throw new Error("Could not load `sharp`. Install it: pnpm add -D sharp");
48
+ }
49
+ }
50
+ async function rawRgbaToPng(pixels: Uint8Array, width: number, height: number): Promise<Uint8Array> {
51
+ const sharp = loadSharp();
52
+ const buffer = await sharp(Buffer.from(pixels.buffer, pixels.byteOffset, pixels.byteLength), {
53
+ raw: { width, height, channels: 4 }
54
+ })
55
+ .png()
56
+ .toBuffer();
57
+ return new Uint8Array(buffer);
58
+ }
59
+
60
+ function arg(name: string, fallback: string): string {
61
+ const i = process.argv.indexOf(`--${name}`);
62
+ return i >= 0 && process.argv[i + 1] ? process.argv[i + 1]! : fallback;
63
+ }
64
+
65
+ function round(n: number): number {
66
+ return Math.round(n * 1000) / 1000;
67
+ }
68
+
69
+ async function main(): Promise<void> {
70
+ const outRoot = resolve(arg("out", "tests/reports"));
71
+ const clipIntent = arg("clip", "gesture");
72
+
73
+ // --- B4: per-intent strips + summary ----------------------------------------------------------
74
+ const intentDir = join(outRoot, "standard-clips");
75
+ mkdirSync(intentDir, { recursive: true });
76
+ const intents: IntentEvidence[] = buildAllIntentEvidence();
77
+ const intentSummary: Record<string, unknown> = {};
78
+ for (const ev of intents) {
79
+ const png = await rawRgbaToPng(ev.strip.rgba, ev.strip.width, ev.strip.height);
80
+ const file = join(intentDir, `${ev.intent}.png`);
81
+ writeFileSync(file, png);
82
+ intentSummary[ev.intent] = {
83
+ png: file,
84
+ bonesTouched: ev.bonesTouched,
85
+ bonesTouchedCount: ev.bonesTouched.length,
86
+ maxRotAmplitudeRad: round(ev.maxRotAmplitudeRad),
87
+ firstFinalDiff: ev.firstFinalDiff,
88
+ panelJointCounts: ev.panelJointCounts,
89
+ pngBytes: png.byteLength
90
+ };
91
+ }
92
+ writeFileSync(
93
+ join(intentDir, "summary.json"),
94
+ JSON.stringify({ generated: "B4 standard-clip visual regression", intents: intentSummary }, null, 2)
95
+ );
96
+
97
+ // --- B5: per-rig retargeting overlays + summary -----------------------------------------------
98
+ const rigDir = join(outRoot, "rig-overlays");
99
+ mkdirSync(rigDir, { recursive: true });
100
+ const rigs: RigEvidence[] = buildAllRigEvidence(clipIntent);
101
+ const rigSummary: Record<string, unknown> = {};
102
+ for (const ev of rigs) {
103
+ const png = await rawRgbaToPng(ev.strip.rgba, ev.strip.width, ev.strip.height);
104
+ const file = join(rigDir, `${ev.name}.png`);
105
+ writeFileSync(file, png);
106
+ rigSummary[ev.name] = {
107
+ png: file,
108
+ convention: ev.convention,
109
+ grade: ev.grade,
110
+ mappedBoneCount: ev.mappedBoneCount,
111
+ retargetCoverage: round(ev.retargetCoverage),
112
+ retargetedBoneCount: ev.retargetedBones.length,
113
+ refusesBodyActing: ev.refusesBodyActing,
114
+ clip: ev.clip,
115
+ firstFinalDiff: ev.firstFinalDiff,
116
+ gradeReasons: ev.gradeReasons,
117
+ pngBytes: png.byteLength
118
+ };
119
+ }
120
+ writeFileSync(
121
+ join(rigDir, "summary.json"),
122
+ JSON.stringify({ generated: "B5 5-rig retargeting overlays", clip: clipIntent, rigs: rigSummary }, null, 2)
123
+ );
124
+
125
+ console.log(
126
+ JSON.stringify({
127
+ ok: true,
128
+ outRoot,
129
+ clip: clipIntent,
130
+ intents: intents.map((ev) => ({
131
+ intent: ev.intent,
132
+ bonesTouched: ev.bonesTouched.length,
133
+ maxRotAmplitudeRad: round(ev.maxRotAmplitudeRad),
134
+ firstFinalDiff: ev.firstFinalDiff
135
+ })),
136
+ rigs: rigs.map((ev) => ({
137
+ name: ev.name,
138
+ grade: ev.grade,
139
+ mappedBoneCount: ev.mappedBoneCount,
140
+ refusesBodyActing: ev.refusesBodyActing,
141
+ firstFinalDiff: ev.firstFinalDiff
142
+ }))
143
+ })
144
+ );
145
+ }
146
+
147
+ main().catch((err) => {
148
+ console.error(JSON.stringify({ ok: false, error: (err as Error).message, stack: (err as Error).stack }));
149
+ process.exitCode = 1;
150
+ });
@@ -0,0 +1,362 @@
1
+ /**
2
+ * motion-evidence.ts — Phase B4/B5 motion-evidence artifact generator (GPU-free, deterministic).
3
+ *
4
+ * This is the visual-regression + retargeting-overlay producer the PRD's B4/B5 acceptance asks for.
5
+ * It is intentionally browser-FREE and GPU-FREE: it reuses the FK bone-projection in
6
+ * `skeleton-overlay.ts` (which drives the canonical rest skeleton with the SHARED standard-library
7
+ * clip and rasterizes a first/mid/final PNG strip) and adds:
8
+ *
9
+ * B4 — one skeleton-overlay strip PER standard intent (idle/talk/gesture/point/nod/walk/run/react)
10
+ * onto the STANDARD reference rig, plus a per-intent motion summary (bones touched, max
11
+ * rotation amplitude in rad, first-vs-final pixel diff).
12
+ *
13
+ * B5 — five materially-different synthetic rigs (Mixamo / Blender / UE / sparse 8-bone mascot /
14
+ * rich humanoid) inferred from node-name lists, GRADED with `gradeRig`, with the SAME clip
15
+ * retargeted onto each. A per-rig skeleton-overlay strip is produced that draws ONLY the bones
16
+ * the rig actually maps (so a sparse rig visibly acts with fewer bones), and each rig's grade
17
+ * is recorded. A D-grade rig is reported as refusing body-acting.
18
+ *
19
+ * Everything here is pure data + raw RGBA; PNG encoding is delegated to the caller (the CLI uses
20
+ * `sharp`). Deterministic — same inputs, same bytes — so a vitest test can assert the artifacts exist
21
+ * and that every strip shows real motion.
22
+ */
23
+
24
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
25
+ // @ts-ignore — resolved at runtime to the freshly-built monorepo dist (has co-located .d.ts).
26
+ import {
27
+ createStandardHumanoidClipRegistry,
28
+ STANDARD_LIBRARY_RIG,
29
+ inferHumanoidRig,
30
+ gradeRig,
31
+ createHumanoidRetargetingMap,
32
+ type AnimationClipRegistry,
33
+ type HumanoidRigDefinition,
34
+ type RigGrade
35
+ } from "@aura3d/animation";
36
+ import { buildSkeletonStrip, type SkeletonStripResult } from "./skeleton-overlay.js";
37
+
38
+ /** The 8 shared standard intents (mirrors STANDARD_CLIP_IDS in packages/animation). */
39
+ export const STANDARD_INTENTS = [
40
+ "idle",
41
+ "talk",
42
+ "gesture",
43
+ "point",
44
+ "nod",
45
+ "walk",
46
+ "run",
47
+ "react"
48
+ ] as const;
49
+ export type StandardIntent = (typeof STANDARD_INTENTS)[number];
50
+
51
+ type Quat = readonly [number, number, number, number];
52
+
53
+ interface SampledTrack {
54
+ readonly target: string;
55
+ readonly valueType: string;
56
+ sample(t: number): readonly number[];
57
+ }
58
+ interface SampledClip {
59
+ readonly duration: number;
60
+ readonly loop: boolean;
61
+ readonly tracks: readonly SampledTrack[];
62
+ }
63
+
64
+ /** Angular distance (rad) between two unit quaternions. */
65
+ function quatAngle(a: Quat, b: Quat): number {
66
+ let dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
67
+ dot = Math.min(1, Math.max(-1, Math.abs(dot)));
68
+ return 2 * Math.acos(dot);
69
+ }
70
+
71
+ /**
72
+ * Measure a clip's motion semantics directly off its tracks (independent of the projection):
73
+ * - bonesTouched: distinct bone names with a rotation OR translation track.
74
+ * - maxRotAmplitudeRad: the largest angular deviation any rotation track travels from its t=0 pose,
75
+ * sampled densely across the clip duration. A static (offset-only) clip reports ~0 here.
76
+ */
77
+ export function measureClipMotion(clip: SampledClip): {
78
+ bonesTouched: string[];
79
+ maxRotAmplitudeRad: number;
80
+ rotationTracks: number;
81
+ } {
82
+ const duration = clip.duration > 0 ? clip.duration : 1;
83
+ const SAMPLES = 48;
84
+ const bones = new Set<string>();
85
+ let maxRot = 0;
86
+ let rotationTracks = 0;
87
+ for (const track of clip.tracks) {
88
+ const dot = track.target.lastIndexOf(".");
89
+ if (dot < 0) continue;
90
+ const bone = track.target.slice(0, dot);
91
+ const path = track.target.slice(dot + 1);
92
+ bones.add(bone);
93
+ if (path !== "rotation") continue;
94
+ rotationTracks += 1;
95
+ const first = track.sample(0);
96
+ if (first.length < 4) continue;
97
+ const q0: Quat = [first[0]!, first[1]!, first[2]!, first[3]!];
98
+ for (let i = 1; i <= SAMPLES; i += 1) {
99
+ const t = (duration * i) / SAMPLES;
100
+ const v = track.sample(clip.loop ? t % duration : Math.min(t, duration));
101
+ if (v.length < 4) continue;
102
+ const q: Quat = [v[0]!, v[1]!, v[2]!, v[3]!];
103
+ const angle = quatAngle(q0, q);
104
+ if (angle > maxRot) maxRot = angle;
105
+ }
106
+ }
107
+ return { bonesTouched: [...bones].sort(), maxRotAmplitudeRad: maxRot, rotationTracks };
108
+ }
109
+
110
+ export interface IntentEvidence {
111
+ readonly intent: StandardIntent;
112
+ readonly bonesTouched: string[];
113
+ readonly maxRotAmplitudeRad: number;
114
+ /** first-vs-final pixel diff from the rendered strip (>0 ⇒ the figure visibly moved). */
115
+ readonly firstFinalDiff: number;
116
+ readonly panelJointCounts: readonly number[];
117
+ readonly strip: SkeletonStripResult;
118
+ }
119
+
120
+ /** B4 — build the per-intent strip + measured motion for one standard intent. */
121
+ export function buildIntentEvidence(
122
+ intent: StandardIntent,
123
+ registry: AnimationClipRegistry
124
+ ): IntentEvidence {
125
+ const clip = (registry.get?.(intent) ?? registry.require?.(intent)) as SampledClip | undefined;
126
+ if (!clip) throw new Error(`motion-evidence: no standard clip for intent "${intent}".`);
127
+ const motion = measureClipMotion(clip);
128
+ const strip = buildSkeletonStrip({ intent, registry });
129
+ return {
130
+ intent,
131
+ bonesTouched: motion.bonesTouched,
132
+ maxRotAmplitudeRad: motion.maxRotAmplitudeRad,
133
+ firstFinalDiff: strip.firstFinalMaxDiff,
134
+ panelJointCounts: strip.panelJointCounts,
135
+ strip
136
+ };
137
+ }
138
+
139
+ /** Build evidence for every standard intent (B4). Shares one registry for determinism + speed. */
140
+ export function buildAllIntentEvidence(): IntentEvidence[] {
141
+ const registry = createStandardHumanoidClipRegistry();
142
+ return STANDARD_INTENTS.map((intent) => buildIntentEvidence(intent, registry));
143
+ }
144
+
145
+ // --------------------------------------------------------------------------------------------------
146
+ // B5 — five materially-different synthetic rigs.
147
+ // --------------------------------------------------------------------------------------------------
148
+
149
+ export interface SyntheticRigSpec {
150
+ readonly name: string;
151
+ /** Convention label for the report. */
152
+ readonly convention: string;
153
+ /** Node names a GLB of this convention would expose (drives `inferHumanoidRig`). */
154
+ readonly nodeNames: readonly string[];
155
+ }
156
+
157
+ /** Mixamo joints (`mixamorig:` namespace, full humanoid → expect grade A). */
158
+ const MIXAMO_NODES: readonly string[] = [
159
+ "mixamorig:Hips",
160
+ "mixamorig:Spine",
161
+ "mixamorig:Spine1",
162
+ "mixamorig:Spine2",
163
+ "mixamorig:Neck",
164
+ "mixamorig:Head",
165
+ "mixamorig:LeftShoulder",
166
+ "mixamorig:LeftArm",
167
+ "mixamorig:LeftForeArm",
168
+ "mixamorig:LeftHand",
169
+ "mixamorig:RightShoulder",
170
+ "mixamorig:RightArm",
171
+ "mixamorig:RightForeArm",
172
+ "mixamorig:RightHand",
173
+ "mixamorig:LeftUpLeg",
174
+ "mixamorig:LeftLeg",
175
+ "mixamorig:LeftFoot",
176
+ "mixamorig:LeftToeBase",
177
+ "mixamorig:RightUpLeg",
178
+ "mixamorig:RightLeg",
179
+ "mixamorig:RightFoot",
180
+ "mixamorig:RightToeBase"
181
+ ];
182
+
183
+ /** Blender / Rigify-ish names (full humanoid → expect grade A). */
184
+ const BLENDER_NODES: readonly string[] = [
185
+ "pelvis",
186
+ "spine",
187
+ "chest",
188
+ "upper_chest",
189
+ "neck",
190
+ "head",
191
+ "shoulder.L",
192
+ "upper_arm.L",
193
+ "forearm.L",
194
+ "hand.L",
195
+ "shoulder.R",
196
+ "upper_arm.R",
197
+ "forearm.R",
198
+ "hand.R",
199
+ "thigh.L",
200
+ "shin.L",
201
+ "foot.L",
202
+ "toe.L",
203
+ "thigh.R",
204
+ "shin.R",
205
+ "foot.R",
206
+ "toe.R"
207
+ ];
208
+
209
+ /** Unreal Engine mannequin names (full humanoid → expect grade A). */
210
+ const UE_NODES: readonly string[] = [
211
+ "pelvis",
212
+ "spine_01",
213
+ "spine_02",
214
+ "spine_03",
215
+ "neck_01",
216
+ "head",
217
+ "clavicle_l",
218
+ "upperarm_l",
219
+ "lowerarm_l",
220
+ "hand_l",
221
+ "clavicle_r",
222
+ "upperarm_r",
223
+ "lowerarm_r",
224
+ "hand_r",
225
+ "thigh_l",
226
+ "calf_l",
227
+ "foot_l",
228
+ "ball_l",
229
+ "thigh_r",
230
+ "calf_r",
231
+ "foot_r",
232
+ "ball_r"
233
+ ];
234
+
235
+ /** Sparse 8-node mascot: torso + head + stub arms, NO forearms/hands, NO legs → expect grade C. */
236
+ const MASCOT_NODES: readonly string[] = [
237
+ "root",
238
+ "spine",
239
+ "neck",
240
+ "head",
241
+ "arm_L",
242
+ "arm_R",
243
+ "eye_L",
244
+ "eye_R"
245
+ ];
246
+
247
+ /** Rich humanoid (descriptive anatomical names, full chains → expect grade A). */
248
+ const RICH_NODES: readonly string[] = [
249
+ "Hips",
250
+ "LowerSpine",
251
+ "Ribcage",
252
+ "UpperChest",
253
+ "Neck",
254
+ "Head",
255
+ "Left Clavicle",
256
+ "Left Humerus",
257
+ "Left Ulna",
258
+ "Left Wrist",
259
+ "Right Clavicle",
260
+ "Right Humerus",
261
+ "Right Ulna",
262
+ "Right Wrist",
263
+ "Left Femur",
264
+ "Left Shin",
265
+ "Left Ankle",
266
+ "Left Ball",
267
+ "Right Femur",
268
+ "Right Shin",
269
+ "Right Ankle",
270
+ "Right Ball"
271
+ ];
272
+
273
+ /**
274
+ * Props-only / no usable skeleton (root + decorative nodes, no spine/head/limbs) → expect grade D.
275
+ * This is the honest "this asset cannot body-act" case; it is reported as refusing body acting.
276
+ */
277
+ const PROPS_ONLY_NODES: readonly string[] = [
278
+ "root",
279
+ "Armature",
280
+ "Camera",
281
+ "Light",
282
+ "GroundPlane",
283
+ "Cube.001"
284
+ ];
285
+
286
+ export const SYNTHETIC_RIGS: readonly SyntheticRigSpec[] = [
287
+ { name: "mixamo", convention: "Mixamo (mixamorig:)", nodeNames: MIXAMO_NODES },
288
+ { name: "blender", convention: "Blender / Rigify (.L/.R)", nodeNames: BLENDER_NODES },
289
+ { name: "ue", convention: "Unreal Engine mannequin (_l/_r)", nodeNames: UE_NODES },
290
+ { name: "mascot8", convention: "Sparse 8-node mascot", nodeNames: MASCOT_NODES },
291
+ { name: "rich", convention: "Rich anatomical humanoid", nodeNames: RICH_NODES },
292
+ { name: "props-only", convention: "Props-only / no usable skeleton", nodeNames: PROPS_ONLY_NODES }
293
+ ];
294
+
295
+ export interface RigEvidence {
296
+ readonly name: string;
297
+ readonly convention: string;
298
+ readonly grade: RigGrade;
299
+ readonly gradeReasons: readonly string[];
300
+ readonly mappedBoneCount: number;
301
+ /** Canonical bones the standard clip's pose actually retargeted onto this rig. */
302
+ readonly retargetedBones: string[];
303
+ readonly retargetCoverage: number;
304
+ /** Honest: true ⇒ the rig is too sparse to drive body acting (grade D). */
305
+ readonly refusesBodyActing: boolean;
306
+ readonly clip: string;
307
+ readonly firstFinalDiff: number;
308
+ readonly strip: SkeletonStripResult;
309
+ }
310
+
311
+ /**
312
+ * Retarget one standard clip onto a synthetic rig and produce a skeleton-overlay strip restricted to
313
+ * the bones that rig actually maps. The strip therefore visualizes *this rig's* coverage of the clip:
314
+ * a full humanoid draws the whole figure, a sparse mascot draws only its torso/head.
315
+ */
316
+ export function buildRigEvidence(spec: SyntheticRigSpec, clipIntent: string): RigEvidence {
317
+ const rig: HumanoidRigDefinition = inferHumanoidRig(spec.nodeNames, {
318
+ id: `synthetic-${spec.name}`,
319
+ name: spec.convention
320
+ });
321
+ const report = gradeRig(rig);
322
+
323
+ // Which canonical bones map on this rig — used to mask the projection (and to retarget).
324
+ const mappedBones = Object.keys(rig.bones).filter((b) => {
325
+ const binding = (rig.bones as Record<string, { name?: string } | undefined>)[b];
326
+ return Boolean(binding && typeof binding.name === "string" && binding.name.length > 0);
327
+ });
328
+
329
+ // Build the retargeting map standard-library-rig → this rig and retarget the clip's first pose so
330
+ // we can report the bones that actually carried motion (coverage), even for D rigs.
331
+ const map = createHumanoidRetargetingMap(STANDARD_LIBRARY_RIG, rig, { minRequiredCoverage: 0 });
332
+ const retargetedBones = Object.keys(map.bindings).sort();
333
+
334
+ const refusesBodyActing = report.grade === "D";
335
+
336
+ // Mask the strip to the rig's mapped bones (a sparse rig draws a sparser figure). For D rigs we
337
+ // still render whatever (near-empty) skeleton it has — the strip + the refusal message together
338
+ // are the honest evidence.
339
+ const strip = buildSkeletonStrip({
340
+ intent: clipIntent,
341
+ bonesAllow: mappedBones
342
+ });
343
+
344
+ return {
345
+ name: spec.name,
346
+ convention: spec.convention,
347
+ grade: report.grade,
348
+ gradeReasons: report.reasons,
349
+ mappedBoneCount: report.mappedBoneCount,
350
+ retargetedBones,
351
+ retargetCoverage: map.requiredCoverage,
352
+ refusesBodyActing,
353
+ clip: clipIntent,
354
+ firstFinalDiff: strip.firstFinalMaxDiff,
355
+ strip
356
+ };
357
+ }
358
+
359
+ /** Build evidence for all five synthetic rigs (B5) against one shared clip. */
360
+ export function buildAllRigEvidence(clipIntent = "gesture"): RigEvidence[] {
361
+ return SYNTHETIC_RIGS.map((spec) => buildRigEvidence(spec, clipIntent));
362
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * preview-server.ts — a watch-and-preview server for the studio loop: it watches a
3
+ * document JSON and renders a short PREVIEW RANGE to PNGs on every edit, so the agent/creator
4
+ * sees changes quickly instead of re-rendering the whole 60s.
5
+ *
6
+ * HONEST: this renders the range per change (windowed, fast-ish via the `#1` readback). The
7
+ * further optimization — keeping the browser/page WARM across edits (no per-render startup) +
8
+ * parallel pages — is the productionization noted in the design doc.
9
+ *
10
+ * Run: AURA_DOCUMENT=<doc.json> AURA_PREVIEW_RANGE=0-6 \
11
+ * pnpm exec tsx --tsconfig tsconfig.base.json packages/create-aura3d/templates/animation-studio/scripts/preview-server.ts
12
+ */
13
+
14
+ import { spawnSync } from "node:child_process";
15
+ import { existsSync, watch } from "node:fs";
16
+ import { dirname, resolve } from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const REPO = resolve(__dirname, "..", "..", "..", "..", "..");
21
+ const DOC = process.env.AURA_DOCUMENT
22
+ ? resolve(process.env.AURA_DOCUMENT)
23
+ : resolve(__dirname, "..", "dist", "generated", "studio-edited.document.json");
24
+ const RANGE = process.env.AURA_PREVIEW_RANGE ?? "0-6";
25
+ const OUT = process.env.AURA_OUTPUT_DIR ?? "dist/episodes/studio-preview";
26
+
27
+ function renderPreview(): void {
28
+ const t = Date.now();
29
+ const r = spawnSync(
30
+ "pnpm",
31
+ ["exec", "tsx", "--tsconfig", "tsconfig.base.json", "packages/create-aura3d/templates/animation-studio/scripts/render-live.ts"],
32
+ { cwd: REPO, stdio: "ignore", env: { ...process.env, AURA_DOCUMENT: DOC, AURA_OUTPUT_DIR: OUT, AURA_PREVIEW_RANGE: RANGE } }
33
+ );
34
+ console.log(`preview ${r.status === 0 ? "ok" : "FAILED"} (range ${RANGE}) in ${((Date.now() - t) / 1000).toFixed(1)}s → ${OUT}`);
35
+ }
36
+
37
+ function main(): void {
38
+ if (!existsSync(DOC)) {
39
+ console.error(`document not found: ${DOC}\nGenerate one first (studio.ts / generate-scene.ts).`);
40
+ process.exitCode = 1;
41
+ return;
42
+ }
43
+ console.log(`preview-server watching ${DOC} (range ${RANGE}) ...`);
44
+ renderPreview();
45
+ let pending: NodeJS.Timeout | null = null;
46
+ watch(DOC, () => {
47
+ if (pending) clearTimeout(pending);
48
+ pending = setTimeout(renderPreview, 250); // debounce rapid writes
49
+ });
50
+ }
51
+
52
+ main();