@aura3d/engine 1.0.2 → 1.0.5

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 (296) hide show
  1. package/README.md +331 -16
  2. package/dist/animation/AnimationClipEvents.d.ts +57 -0
  3. package/dist/animation/AnimationClipEvents.d.ts.map +1 -0
  4. package/dist/animation/AnimationClipEvents.js +171 -0
  5. package/dist/animation/AnimationClipEvents.js.map +1 -0
  6. package/dist/animation/AnimationClipRegistry.d.ts +76 -0
  7. package/dist/animation/AnimationClipRegistry.d.ts.map +1 -0
  8. package/dist/animation/AnimationClipRegistry.js +130 -0
  9. package/dist/animation/AnimationClipRegistry.js.map +1 -0
  10. package/dist/animation/AnimationController.d.ts +168 -0
  11. package/dist/animation/AnimationController.d.ts.map +1 -0
  12. package/dist/animation/AnimationController.js +619 -0
  13. package/dist/animation/AnimationController.js.map +1 -0
  14. package/dist/animation/HumanoidRetargeting.d.ts +76 -0
  15. package/dist/animation/HumanoidRetargeting.d.ts.map +1 -0
  16. package/dist/animation/HumanoidRetargeting.js +331 -0
  17. package/dist/animation/HumanoidRetargeting.js.map +1 -0
  18. package/dist/animation/browser-index.d.ts +18 -0
  19. package/dist/animation/browser-index.d.ts.map +1 -1
  20. package/dist/animation/browser-index.js +13 -0
  21. package/dist/animation/browser-index.js.map +1 -1
  22. package/dist/animation/index.d.ts +16 -1
  23. package/dist/animation/index.d.ts.map +1 -1
  24. package/dist/animation/index.js +11 -1
  25. package/dist/animation/index.js.map +1 -1
  26. package/dist/animation/threejs-compatibility/AnimationDiagnostics.d.ts.map +1 -1
  27. package/dist/animation/threejs-compatibility/AnimationDiagnostics.js +3 -5
  28. package/dist/animation/threejs-compatibility/AnimationDiagnostics.js.map +1 -1
  29. package/dist/assets/GLTFAnimationRuntime.js +1 -1
  30. package/dist/assets/GLTFLoader.js +1 -1
  31. package/dist/aura3d-cli/cli.js +194 -8
  32. package/dist/aura3d-cli/cli.js.map +1 -1
  33. package/dist/aura3d-cli/index.d.ts +280 -3
  34. package/dist/aura3d-cli/index.d.ts.map +1 -1
  35. package/dist/aura3d-cli/index.js +886 -4
  36. package/dist/aura3d-cli/index.js.map +1 -1
  37. package/dist/aura3d-cli/pull-bridge.d.ts +95 -0
  38. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -0
  39. package/dist/aura3d-cli/pull-bridge.js +247 -0
  40. package/dist/aura3d-cli/pull-bridge.js.map +1 -0
  41. package/dist/create-aura3d/index.d.ts +1 -1
  42. package/dist/create-aura3d/index.d.ts.map +1 -1
  43. package/dist/create-aura3d/index.js +9 -2
  44. package/dist/create-aura3d/index.js.map +1 -1
  45. package/dist/editor-runtime/ProjectSerializer.d.ts +74 -1
  46. package/dist/editor-runtime/ProjectSerializer.d.ts.map +1 -1
  47. package/dist/editor-runtime/ProjectSerializer.js +123 -6
  48. package/dist/editor-runtime/ProjectSerializer.js.map +1 -1
  49. package/dist/editor-runtime/TimelineModel.d.ts +18 -0
  50. package/dist/editor-runtime/TimelineModel.d.ts.map +1 -1
  51. package/dist/editor-runtime/TimelineModel.js +67 -3
  52. package/dist/editor-runtime/TimelineModel.js.map +1 -1
  53. package/dist/editor-runtime/TimelineRuntimeBridge.d.ts +98 -0
  54. package/dist/editor-runtime/TimelineRuntimeBridge.d.ts.map +1 -0
  55. package/dist/editor-runtime/TimelineRuntimeBridge.js +186 -0
  56. package/dist/editor-runtime/TimelineRuntimeBridge.js.map +1 -0
  57. package/dist/editor-runtime/index.d.ts +3 -1
  58. package/dist/editor-runtime/index.d.ts.map +1 -1
  59. package/dist/editor-runtime/index.js +1 -0
  60. package/dist/editor-runtime/index.js.map +1 -1
  61. package/dist/engine/agent-api/AnimationController.d.ts +607 -0
  62. package/dist/engine/agent-api/AnimationController.d.ts.map +1 -0
  63. package/dist/engine/agent-api/AnimationController.js +2192 -0
  64. package/dist/engine/agent-api/AnimationController.js.map +1 -0
  65. package/dist/engine/agent-api/AssetEvidence.d.ts +88 -0
  66. package/dist/engine/agent-api/AssetEvidence.d.ts.map +1 -0
  67. package/dist/engine/agent-api/AssetEvidence.js +157 -0
  68. package/dist/engine/agent-api/AssetEvidence.js.map +1 -0
  69. package/dist/engine/agent-api/AuraAppHandle.d.ts +55 -0
  70. package/dist/engine/agent-api/AuraAppHandle.d.ts.map +1 -0
  71. package/dist/engine/agent-api/AuraAppHandle.js +15 -0
  72. package/dist/engine/agent-api/AuraAppHandle.js.map +1 -0
  73. package/dist/engine/agent-api/AuraVoiceBridge.d.ts +96 -0
  74. package/dist/engine/agent-api/AuraVoiceBridge.d.ts.map +1 -0
  75. package/dist/engine/agent-api/AuraVoiceBridge.js +370 -0
  76. package/dist/engine/agent-api/AuraVoiceBridge.js.map +1 -0
  77. package/dist/engine/agent-api/CartoonDirector.d.ts +95 -0
  78. package/dist/engine/agent-api/CartoonDirector.d.ts.map +1 -0
  79. package/dist/engine/agent-api/CartoonDirector.js +342 -0
  80. package/dist/engine/agent-api/CartoonDirector.js.map +1 -0
  81. package/dist/engine/agent-api/CartoonPerformance.d.ts +149 -0
  82. package/dist/engine/agent-api/CartoonPerformance.d.ts.map +1 -0
  83. package/dist/engine/agent-api/CartoonPerformance.js +317 -0
  84. package/dist/engine/agent-api/CartoonPerformance.js.map +1 -0
  85. package/dist/engine/agent-api/CartoonRenderQueue.d.ts +132 -0
  86. package/dist/engine/agent-api/CartoonRenderQueue.d.ts.map +1 -0
  87. package/dist/engine/agent-api/CartoonRenderQueue.js +385 -0
  88. package/dist/engine/agent-api/CartoonRenderQueue.js.map +1 -0
  89. package/dist/engine/agent-api/CharacterAssembly.d.ts +126 -0
  90. package/dist/engine/agent-api/CharacterAssembly.d.ts.map +1 -0
  91. package/dist/engine/agent-api/CharacterAssembly.js +280 -0
  92. package/dist/engine/agent-api/CharacterAssembly.js.map +1 -0
  93. package/dist/engine/agent-api/DialoguePerformance.d.ts +150 -0
  94. package/dist/engine/agent-api/DialoguePerformance.d.ts.map +1 -0
  95. package/dist/engine/agent-api/DialoguePerformance.js +335 -0
  96. package/dist/engine/agent-api/DialoguePerformance.js.map +1 -0
  97. package/dist/engine/agent-api/FrameLoop.d.ts +70 -0
  98. package/dist/engine/agent-api/FrameLoop.d.ts.map +1 -0
  99. package/dist/engine/agent-api/FrameLoop.js +165 -0
  100. package/dist/engine/agent-api/FrameLoop.js.map +1 -0
  101. package/dist/engine/agent-api/GameAssetValidation.d.ts +279 -0
  102. package/dist/engine/agent-api/GameAssetValidation.d.ts.map +1 -0
  103. package/dist/engine/agent-api/GameAssetValidation.js +719 -0
  104. package/dist/engine/agent-api/GameAssetValidation.js.map +1 -0
  105. package/dist/engine/agent-api/GameEvidence.d.ts +148 -0
  106. package/dist/engine/agent-api/GameEvidence.d.ts.map +1 -0
  107. package/dist/engine/agent-api/GameEvidence.js +269 -0
  108. package/dist/engine/agent-api/GameEvidence.js.map +1 -0
  109. package/dist/engine/agent-api/GameRuntime.d.ts +931 -0
  110. package/dist/engine/agent-api/GameRuntime.d.ts.map +1 -0
  111. package/dist/engine/agent-api/GameRuntime.js +2229 -0
  112. package/dist/engine/agent-api/GameRuntime.js.map +1 -0
  113. package/dist/engine/agent-api/GameSceneBridge.d.ts +54 -0
  114. package/dist/engine/agent-api/GameSceneBridge.d.ts.map +1 -0
  115. package/dist/engine/agent-api/GameSceneBridge.js +110 -0
  116. package/dist/engine/agent-api/GameSceneBridge.js.map +1 -0
  117. package/dist/engine/agent-api/PromptAnimationContract.d.ts +278 -0
  118. package/dist/engine/agent-api/PromptAnimationContract.d.ts.map +1 -0
  119. package/dist/engine/agent-api/PromptAnimationContract.js +238 -0
  120. package/dist/engine/agent-api/PromptAnimationContract.js.map +1 -0
  121. package/dist/engine/agent-api/PromptAnimationEvidence.d.ts +183 -0
  122. package/dist/engine/agent-api/PromptAnimationEvidence.d.ts.map +1 -0
  123. package/dist/engine/agent-api/PromptAnimationEvidence.js +454 -0
  124. package/dist/engine/agent-api/PromptAnimationEvidence.js.map +1 -0
  125. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +100 -0
  126. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -0
  127. package/dist/engine/agent-api/RuntimeNodeHandle.js +36 -0
  128. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -0
  129. package/dist/engine/agent-api/ShotTimeline.d.ts +179 -0
  130. package/dist/engine/agent-api/ShotTimeline.d.ts.map +1 -0
  131. package/dist/engine/agent-api/ShotTimeline.js +264 -0
  132. package/dist/engine/agent-api/ShotTimeline.js.map +1 -0
  133. package/dist/engine/agent-api/VisemeController.d.ts +89 -0
  134. package/dist/engine/agent-api/VisemeController.d.ts.map +1 -0
  135. package/dist/engine/agent-api/VisemeController.js +207 -0
  136. package/dist/engine/agent-api/VisemeController.js.map +1 -0
  137. package/dist/engine/agent-api/game-kits/fighting.d.ts +123 -0
  138. package/dist/engine/agent-api/game-kits/fighting.d.ts.map +1 -0
  139. package/dist/engine/agent-api/game-kits/fighting.js +483 -0
  140. package/dist/engine/agent-api/game-kits/fighting.js.map +1 -0
  141. package/dist/engine/agent-api/game-kits/index.d.ts +15 -0
  142. package/dist/engine/agent-api/game-kits/index.d.ts.map +1 -0
  143. package/dist/engine/agent-api/game-kits/index.js +6 -0
  144. package/dist/engine/agent-api/game-kits/index.js.map +1 -0
  145. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts +18 -81
  146. package/dist/engine/agent-api/humanoid-walk-runtime.d.ts.map +1 -1
  147. package/dist/engine/agent-api/humanoid-walk-runtime.js +4 -279
  148. package/dist/engine/agent-api/humanoid-walk-runtime.js.map +1 -1
  149. package/dist/engine/agent-api/index.d.ts +490 -4
  150. package/dist/engine/agent-api/index.d.ts.map +1 -1
  151. package/dist/engine/agent-api/index.js +759 -1802
  152. package/dist/engine/agent-api/index.js.map +1 -1
  153. package/dist/engine/agent-api/particle-fountain-runtime.d.ts +5 -80
  154. package/dist/engine/agent-api/particle-fountain-runtime.d.ts.map +1 -1
  155. package/dist/engine/agent-api/particle-fountain-runtime.js +7 -291
  156. package/dist/engine/agent-api/particle-fountain-runtime.js.map +1 -1
  157. package/dist/engine/agent-api/product-viewer-runtime.d.ts +17 -107
  158. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  159. package/dist/engine/agent-api/product-viewer-runtime.js +4 -330
  160. package/dist/engine/agent-api/product-viewer-runtime.js.map +1 -1
  161. package/dist/index.d.ts +1 -0
  162. package/dist/index.js +1 -0
  163. package/dist/physics/CollisionVolumes.d.ts +57 -0
  164. package/dist/physics/CollisionVolumes.d.ts.map +1 -0
  165. package/dist/physics/CollisionVolumes.js +159 -0
  166. package/dist/physics/CollisionVolumes.js.map +1 -0
  167. package/dist/physics/HitboxWorld.d.ts +229 -0
  168. package/dist/physics/HitboxWorld.d.ts.map +1 -0
  169. package/dist/physics/HitboxWorld.js +640 -0
  170. package/dist/physics/HitboxWorld.js.map +1 -0
  171. package/dist/physics/KinematicBody.d.ts +157 -0
  172. package/dist/physics/KinematicBody.d.ts.map +1 -0
  173. package/dist/physics/KinematicBody.js +405 -0
  174. package/dist/physics/KinematicBody.js.map +1 -0
  175. package/dist/physics/KinematicWorld.d.ts +58 -0
  176. package/dist/physics/KinematicWorld.d.ts.map +1 -0
  177. package/dist/physics/KinematicWorld.js +246 -0
  178. package/dist/physics/KinematicWorld.js.map +1 -0
  179. package/dist/physics/index.d.ts +4 -0
  180. package/dist/physics/index.d.ts.map +1 -1
  181. package/dist/physics/index.js +4 -0
  182. package/dist/physics/index.js.map +1 -1
  183. package/dist/rendering/ForwardPass.js +2 -2
  184. package/dist/rendering/ShaderLibrary.js +2 -2
  185. package/dist/rendering/SkinnedLitMaterial.js +3 -3
  186. package/dist/rendering/SkinnedUnlitMaterial.js +3 -3
  187. package/dist/scene/Renderable.js +2 -2
  188. package/dist/scripting/VisualGraph.d.ts +2 -1
  189. package/dist/scripting/VisualGraph.d.ts.map +1 -1
  190. package/dist/scripting/VisualGraph.js +118 -1
  191. package/dist/scripting/VisualGraph.js.map +1 -1
  192. package/dist/scripting/VisualGraphContext.d.ts +123 -0
  193. package/dist/scripting/VisualGraphContext.d.ts.map +1 -0
  194. package/dist/scripting/VisualGraphContext.js +2 -0
  195. package/dist/scripting/VisualGraphContext.js.map +1 -0
  196. package/dist/scripting/VisualGraphExecutor.d.ts +6 -1
  197. package/dist/scripting/VisualGraphExecutor.d.ts.map +1 -1
  198. package/dist/scripting/VisualGraphExecutor.js +364 -7
  199. package/dist/scripting/VisualGraphExecutor.js.map +1 -1
  200. package/dist/scripting/VisualNodeCatalog.d.ts +1 -1
  201. package/dist/scripting/VisualNodeCatalog.d.ts.map +1 -1
  202. package/dist/scripting/VisualNodeCatalog.js +61 -1
  203. package/dist/scripting/VisualNodeCatalog.js.map +1 -1
  204. package/dist/scripting/index.d.ts +1 -0
  205. package/dist/scripting/index.d.ts.map +1 -1
  206. package/dist/scripting/index.js.map +1 -1
  207. package/package.json +193 -121
  208. package/templates/product-viewer/src/main.ts +1 -0
  209. package/dist/three-compat/ThreeApiInventory.d.ts +0 -18
  210. package/dist/three-compat/ThreeApiInventory.d.ts.map +0 -1
  211. package/dist/three-compat/ThreeApiInventory.js +0 -99
  212. package/dist/three-compat/ThreeApiInventory.js.map +0 -1
  213. package/dist/three-compat/ThreeCompatibilityMatrix.d.ts +0 -30
  214. package/dist/three-compat/ThreeCompatibilityMatrix.d.ts.map +0 -1
  215. package/dist/three-compat/ThreeCompatibilityMatrix.js +0 -69
  216. package/dist/three-compat/ThreeCompatibilityMatrix.js.map +0 -1
  217. package/dist/three-compat/animation/index.d.ts +0 -3
  218. package/dist/three-compat/animation/index.d.ts.map +0 -1
  219. package/dist/three-compat/animation/index.js +0 -2
  220. package/dist/three-compat/animation/index.js.map +0 -1
  221. package/dist/three-compat/cameras/index.d.ts +0 -24
  222. package/dist/three-compat/cameras/index.d.ts.map +0 -1
  223. package/dist/three-compat/cameras/index.js +0 -44
  224. package/dist/three-compat/cameras/index.js.map +0 -1
  225. package/dist/three-compat/controls/index.d.ts +0 -3
  226. package/dist/three-compat/controls/index.d.ts.map +0 -1
  227. package/dist/three-compat/controls/index.js +0 -2
  228. package/dist/three-compat/controls/index.js.map +0 -1
  229. package/dist/three-compat/core/Object3DCompat.d.ts +0 -62
  230. package/dist/three-compat/core/Object3DCompat.d.ts.map +0 -1
  231. package/dist/three-compat/core/Object3DCompat.js +0 -112
  232. package/dist/three-compat/core/Object3DCompat.js.map +0 -1
  233. package/dist/three-compat/core/RaycasterCompat.d.ts +0 -18
  234. package/dist/three-compat/core/RaycasterCompat.d.ts.map +0 -1
  235. package/dist/three-compat/core/RaycasterCompat.js +0 -28
  236. package/dist/three-compat/core/RaycasterCompat.js.map +0 -1
  237. package/dist/three-compat/core/SceneCompat.d.ts +0 -8
  238. package/dist/three-compat/core/SceneCompat.d.ts.map +0 -1
  239. package/dist/three-compat/core/SceneCompat.js +0 -8
  240. package/dist/three-compat/core/SceneCompat.js.map +0 -1
  241. package/dist/three-compat/geometries/index.d.ts +0 -67
  242. package/dist/three-compat/geometries/index.d.ts.map +0 -1
  243. package/dist/three-compat/geometries/index.js +0 -114
  244. package/dist/three-compat/geometries/index.js.map +0 -1
  245. package/dist/three-compat/helpers/index.d.ts +0 -40
  246. package/dist/three-compat/helpers/index.d.ts.map +0 -1
  247. package/dist/three-compat/helpers/index.js +0 -129
  248. package/dist/three-compat/helpers/index.js.map +0 -1
  249. package/dist/three-compat/index.d.ts +0 -32
  250. package/dist/three-compat/index.d.ts.map +0 -1
  251. package/dist/three-compat/index.js +0 -22
  252. package/dist/three-compat/index.js.map +0 -1
  253. package/dist/three-compat/lights/index.d.ts +0 -35
  254. package/dist/three-compat/lights/index.d.ts.map +0 -1
  255. package/dist/three-compat/lights/index.js +0 -39
  256. package/dist/three-compat/lights/index.js.map +0 -1
  257. package/dist/three-compat/loaders/index.d.ts +0 -54
  258. package/dist/three-compat/loaders/index.d.ts.map +0 -1
  259. package/dist/three-compat/loaders/index.js +0 -79
  260. package/dist/three-compat/loaders/index.js.map +0 -1
  261. package/dist/three-compat/materials/index.d.ts +0 -70
  262. package/dist/three-compat/materials/index.d.ts.map +0 -1
  263. package/dist/three-compat/materials/index.js +0 -85
  264. package/dist/three-compat/materials/index.js.map +0 -1
  265. package/dist/three-compat/math/index.d.ts +0 -34
  266. package/dist/three-compat/math/index.d.ts.map +0 -1
  267. package/dist/three-compat/math/index.js +0 -90
  268. package/dist/three-compat/math/index.js.map +0 -1
  269. package/dist/three-compat/migration/CompatibilityWarnings.d.ts +0 -6
  270. package/dist/three-compat/migration/CompatibilityWarnings.d.ts.map +0 -1
  271. package/dist/three-compat/migration/CompatibilityWarnings.js +0 -8
  272. package/dist/three-compat/migration/CompatibilityWarnings.js.map +0 -1
  273. package/dist/three-compat/migration/ImportMap.d.ts +0 -2
  274. package/dist/three-compat/migration/ImportMap.d.ts.map +0 -1
  275. package/dist/three-compat/migration/ImportMap.js +0 -10
  276. package/dist/three-compat/migration/ImportMap.js.map +0 -1
  277. package/dist/three-compat/migration/ThreeToA3DAdapter.d.ts +0 -8
  278. package/dist/three-compat/migration/ThreeToA3DAdapter.d.ts.map +0 -1
  279. package/dist/three-compat/migration/ThreeToA3DAdapter.js +0 -20
  280. package/dist/three-compat/migration/ThreeToA3DAdapter.js.map +0 -1
  281. package/dist/three-compat/postprocessing/index.d.ts +0 -2
  282. package/dist/three-compat/postprocessing/index.d.ts.map +0 -1
  283. package/dist/three-compat/postprocessing/index.js +0 -2
  284. package/dist/three-compat/postprocessing/index.js.map +0 -1
  285. package/dist/three-compat/render-targets/index.d.ts +0 -17
  286. package/dist/three-compat/render-targets/index.d.ts.map +0 -1
  287. package/dist/three-compat/render-targets/index.js +0 -29
  288. package/dist/three-compat/render-targets/index.js.map +0 -1
  289. package/dist/three-compat/shaders/index.d.ts +0 -3
  290. package/dist/three-compat/shaders/index.d.ts.map +0 -1
  291. package/dist/three-compat/shaders/index.js +0 -3
  292. package/dist/three-compat/shaders/index.js.map +0 -1
  293. package/dist/three-compat/textures/index.d.ts +0 -19
  294. package/dist/three-compat/textures/index.d.ts.map +0 -1
  295. package/dist/three-compat/textures/index.js +0 -24
  296. package/dist/three-compat/textures/index.js.map +0 -1
@@ -38,10 +38,19 @@ export function addAsset(options) {
38
38
  hash: `sha256-${hash}`,
39
39
  sizeBytes: statSync(sourcePath).size,
40
40
  bounds: inspection.bounds,
41
+ boundsMetadata: inspection.boundsMetadata,
41
42
  materials: inspection.materials,
43
+ materialMetadata: inspection.materialMetadata,
42
44
  animations: inspection.animations,
45
+ animationMetadata: inspection.animation,
46
+ humanoid: inspection.humanoid,
47
+ skeleton: inspection.skeleton,
48
+ morphTargets: inspection.morphTargets,
49
+ provenance: createAssetProvenance(projectDir, sourcePath, options, inspection.provenance),
43
50
  textures: inspection.textures,
44
51
  dependencies: inspection.dependencies,
52
+ orientation: inspection.orientation,
53
+ nodeNames: inspection.nodeNames,
45
54
  thumbnailUrl,
46
55
  warnings: createAssetWarnings(sourcePath, inspection)
47
56
  };
@@ -91,14 +100,55 @@ export function scanAssets(options) {
91
100
  messages: ["No supported assets found."]
92
101
  };
93
102
  }
103
+ export function inspectAsset(options) {
104
+ const projectDir = resolve(options.projectDir ?? process.cwd());
105
+ const sourcePath = resolve(projectDir, options.file);
106
+ if (!existsSync(sourcePath)) {
107
+ throw new Error(`Aura3D assets inspect failed: "${options.file}" does not exist.`);
108
+ }
109
+ const format = extname(sourcePath).slice(1).toLowerCase();
110
+ const inspection = inspectAssetFile(sourcePath, format);
111
+ const warnings = createAssetWarnings(sourcePath, inspection);
112
+ return {
113
+ ok: warnings.length === 0,
114
+ schema: "aura3d.asset-inspection/1.0",
115
+ file: normalizeRelativePath(relative(projectDir, sourcePath)),
116
+ format,
117
+ sizeBytes: statSync(sourcePath).size,
118
+ bounds: inspection.bounds,
119
+ boundsMetadata: inspection.boundsMetadata,
120
+ materials: inspection.materials,
121
+ materialMetadata: inspection.materialMetadata,
122
+ animations: inspection.animations,
123
+ ...(options.animation ? { animation: inspection.animation } : {}),
124
+ ...(options.humanoid ? { humanoid: inspection.humanoid } : {}),
125
+ ...(options.skeleton ? { skeleton: inspection.skeleton } : {}),
126
+ ...(options.morphs ? { morphTargets: inspection.morphTargets } : {}),
127
+ ...(options.license ? { provenance: createAssetProvenance(projectDir, sourcePath, {}, inspection.provenance) } : {}),
128
+ textures: inspection.textures,
129
+ orientation: inspection.orientation,
130
+ nodeNames: inspection.nodeNames,
131
+ dependencies: inspection.dependencies,
132
+ warnings,
133
+ messages: warnings.length === 0 ? ["Asset inspection completed."] : warnings
134
+ };
135
+ }
94
136
  export function validateAssets(options = {}) {
95
137
  const projectDir = resolve(options.projectDir ?? process.cwd());
96
138
  const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
97
139
  const manifestMissing = !existsSync(manifestPath);
98
- const manifest = readAssetManifest(projectDir);
140
+ const sourceManifest = readAssetManifest(projectDir);
141
+ const manifest = filterAssetManifest(sourceManifest, options.assetIds);
142
+ const externalProvenance = readExternalProvenance(projectDir, options.provenanceFile);
99
143
  const failures = manifestMissing
100
144
  ? [`Missing ${DEFAULT_AURA_ASSET_MANIFEST}. Suggested fix: run aura3d assets add ./asset.glb --name product or aura3d assets scan ./assets.`]
101
145
  : [];
146
+ const missingAssetIds = findMissingAssetIds(sourceManifest, options.assetIds);
147
+ for (const id of missingAssetIds)
148
+ failures.push(`Requested asset "${id}" was not found in ${DEFAULT_AURA_ASSET_MANIFEST}.`);
149
+ if (options.provenanceFile && !existsSync(resolve(projectDir, options.provenanceFile))) {
150
+ failures.push(`Missing asset provenance evidence file: ${options.provenanceFile}`);
151
+ }
102
152
  const warnings = [];
103
153
  for (const asset of manifest.assets) {
104
154
  const outputPath = resolve(projectDir, asset.outputPath);
@@ -109,6 +159,13 @@ export function validateAssets(options = {}) {
109
159
  const actualHash = `sha256-${hashFile(outputPath)}`;
110
160
  if (actualHash !== asset.hash)
111
161
  failures.push(`Hash mismatch for "${asset.id}": expected ${asset.hash}, found ${actualHash}`);
162
+ const provenance = resolveAssetProvenance(asset, externalProvenance);
163
+ if (options.noPlaceholders && isPlaceholderAsset(asset, provenance)) {
164
+ failures.push(`Placeholder asset is not allowed in strict release validation: "${asset.id}". Replace it with a real typed asset and provenance.`);
165
+ }
166
+ if (options.requireLicense && !hasUsableLicenseEvidence(provenance)) {
167
+ failures.push(`Missing license/provenance evidence for "${asset.id}". Add it with assets add --license ... --source-url ... or pass --provenance <evidence.json>.`);
168
+ }
112
169
  warnings.push(...asset.warnings.map((warning) => `${asset.id}: ${warning}`));
113
170
  if (asset.format === "gltf") {
114
171
  for (const dependency of asset.dependencies ?? asset.textures) {
@@ -145,6 +202,22 @@ export function writeTypedAssets(projectDir, manifest = readAssetManifest(projec
145
202
  const metadata = {
146
203
  materials: asset.materials,
147
204
  animations: asset.animations,
205
+ animationClips: asset.animations,
206
+ animationMetadata: asset.animationMetadata ?? createReadinessAnimationMetadata(asset.animations),
207
+ humanoid: asset.humanoid?.humanoid ?? false,
208
+ humanoidStatus: asset.humanoid?.status ?? "unknown",
209
+ humanoidConfidence: asset.humanoid?.confidence ?? "low",
210
+ skeleton: asset.skeleton,
211
+ morphTargets: asset.morphTargets,
212
+ provenance: asset.provenance,
213
+ sourcePath: asset.source,
214
+ outputPath: asset.outputPath,
215
+ license: asset.provenance?.license,
216
+ author: asset.provenance?.author,
217
+ boundsMetadata: asset.boundsMetadata,
218
+ materialMetadata: asset.materialMetadata,
219
+ orientation: asset.orientation,
220
+ nodeNames: asset.nodeNames ?? [],
148
221
  textures: asset.textures,
149
222
  dependencies: asset.dependencies ?? [],
150
223
  thumbnailUrl: asset.thumbnailUrl
@@ -228,6 +301,97 @@ export function checkDeploy(options = {}) {
228
301
  messages: failures.length === 0 ? ["Deploy check passed."] : failures
229
302
  };
230
303
  }
304
+ export function validateGameAssets(options = {}) {
305
+ return validateAssetReadiness("game", options);
306
+ }
307
+ export function validateCartoonAssets(options = {}) {
308
+ return validateAssetReadiness("cartoon", options);
309
+ }
310
+ export function createCharacterAssemblyPlan(options) {
311
+ const projectDir = resolve(options.projectDir ?? process.cwd());
312
+ const manifest = readAssetManifest(projectDir);
313
+ const failures = [];
314
+ const warnings = [];
315
+ const output = normalizeRelativePath(options.output ?? `src/aura-character-${sanitizeAssetId(options.name)}.assembly.json`);
316
+ const bodyAsset = manifest.assets.find((asset) => asset.id === options.body);
317
+ if (!bodyAsset) {
318
+ failures.push(`Missing body asset "${options.body}". Add it first with aura3d assets add ./body.glb --name ${options.body}.`);
319
+ }
320
+ else if (bodyAsset.type !== "model") {
321
+ failures.push(`Body asset "${options.body}" must be a model asset, found ${bodyAsset.type}.`);
322
+ }
323
+ else if (bodyAsset.humanoid && !bodyAsset.humanoid.humanoid) {
324
+ warnings.push(`Body asset "${options.body}" has humanoid status "${bodyAsset.humanoid.status}"; character assembly can still compose parts, but acting and retargeting may be limited.`);
325
+ }
326
+ const resolvePart = (part) => {
327
+ const asset = manifest.assets.find((entry) => entry.id === part.asset);
328
+ if (!asset) {
329
+ failures.push(`Missing ${part.slot} part asset "${part.asset}".`);
330
+ return undefined;
331
+ }
332
+ if (asset.type !== "model")
333
+ warnings.push(`${part.slot}: "${part.asset}" is ${asset.type}; character assembly expects model parts for rig/attachment safety.`);
334
+ return {
335
+ slot: part.slot,
336
+ asset: part.asset,
337
+ url: asset.url,
338
+ type: asset.type,
339
+ format: asset.format,
340
+ animations: asset.animations,
341
+ humanoid: asset.humanoid,
342
+ attachTo: part.attachTo ?? defaultAttachPoint(part.slot)
343
+ };
344
+ };
345
+ const parts = (options.parts ?? []).map(resolvePart).filter((part) => Boolean(part));
346
+ const body = bodyAsset
347
+ ? {
348
+ slot: "body",
349
+ asset: bodyAsset.id,
350
+ url: bodyAsset.url,
351
+ type: bodyAsset.type,
352
+ format: bodyAsset.format,
353
+ animations: bodyAsset.animations,
354
+ humanoid: bodyAsset.humanoid,
355
+ attachTo: "root"
356
+ }
357
+ : {
358
+ slot: "body",
359
+ asset: options.body,
360
+ url: "",
361
+ type: "model",
362
+ format: "missing",
363
+ animations: [],
364
+ attachTo: "root"
365
+ };
366
+ const plan = {
367
+ schema: "aura3d.character-assembly/1.0",
368
+ name: options.name,
369
+ output,
370
+ scale: options.scale ?? 1,
371
+ body,
372
+ parts,
373
+ rules: {
374
+ normalizeScale: true,
375
+ facePositiveZ: true,
376
+ preserveTypedAssetReferences: true,
377
+ requireNamedAttachments: true
378
+ }
379
+ };
380
+ mkdirSync(dirname(resolve(projectDir, output)), { recursive: true });
381
+ writeFileSync(resolve(projectDir, output), `${JSON.stringify(plan, null, 2)}\n`);
382
+ return {
383
+ ok: failures.length === 0,
384
+ schema: "aura3d.character-assembly/1.0",
385
+ name: options.name,
386
+ output,
387
+ body,
388
+ parts,
389
+ validation: { failures, warnings },
390
+ messages: failures.length === 0
391
+ ? [`Wrote ${output}. Import typed assets from src/aura-assets.ts and compose with model(assets.${options.body}).`]
392
+ : failures
393
+ };
394
+ }
231
395
  export function initAgentFiles(options) {
232
396
  const projectDir = resolve(options.projectDir ?? process.cwd());
233
397
  const targets = options.agent === "all" ? ["generic", "claude", "cursor", "copilot"] : [options.agent];
@@ -244,6 +408,339 @@ export function initAgentFiles(options) {
244
408
  }
245
409
  return written;
246
410
  }
411
+ function validateAssetReadiness(profile, options) {
412
+ const projectDir = resolve(options.projectDir ?? process.cwd());
413
+ const sourceManifest = readAssetManifest(projectDir);
414
+ const manifest = filterAssetManifest(sourceManifest, options.assetIds);
415
+ const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
416
+ const evidencePath = options.output ? resolve(projectDir, options.output) : undefined;
417
+ const validation = validateAssets({
418
+ projectDir,
419
+ noPlaceholders: options.noPlaceholders,
420
+ requireLicense: options.requireLicense,
421
+ provenanceFile: options.provenanceFile,
422
+ assetIds: options.assetIds
423
+ });
424
+ const externalProvenance = readExternalProvenance(projectDir, options.provenanceFile);
425
+ const failures = [...validation.failures];
426
+ const warnings = [...validation.warnings];
427
+ const modelAssets = manifest.assets.filter((asset) => asset.type === "model");
428
+ const animatedModels = modelAssets.filter((asset) => asset.animations.length > 0);
429
+ const animationClips = manifest.assets.reduce((total, asset) => total + asset.animations.length, 0);
430
+ const humanoidModels = modelAssets.filter((asset) => asset.humanoid?.humanoid).length;
431
+ const artifacts = createReadinessArtifacts(projectDir, manifest, evidencePath);
432
+ const assets = manifest.assets.map((asset) => {
433
+ const provenance = resolveAssetProvenance(asset, externalProvenance);
434
+ const placeholderFree = !isPlaceholderAsset(asset, provenance);
435
+ const licenseVerified = hasUsableLicenseEvidence(provenance);
436
+ const assetWarnings = [...asset.warnings];
437
+ const readinessIssues = createAssetReadinessIssues(profile, asset);
438
+ if (asset.type === "model" && !asset.bounds)
439
+ assetWarnings.push("Missing bounds; camera framing, collision proxies, and thumbnail composition will be weaker.");
440
+ if (asset.type === "model" && asset.materials.length === 0)
441
+ assetWarnings.push("No material names detected; authored visual diagnostics will be limited.");
442
+ if (asset.type === "model" && asset.sizeBytes > 50 * 1024 * 1024)
443
+ assetWarnings.push("Large model over 50MB; consider mesh/texture optimization before browser deployment.");
444
+ if (asset.type === "model" && asset.animations.length > 0 && asset.humanoid?.status === "unknown")
445
+ assetWarnings.push("Animated model has unknown humanoid status; inspect with --humanoid before using it as an acted character.");
446
+ assetWarnings.push(...readinessIssues.warnings);
447
+ failures.push(...readinessIssues.failures);
448
+ warnings.push(...readinessIssues.warnings);
449
+ const gameReady = asset.type === "model" && Boolean(asset.bounds) && asset.materials.length > 0 && asset.sizeBytes <= 50 * 1024 * 1024 && readinessIssues.failures.length === 0;
450
+ const cartoonReady = asset.type === "model"
451
+ ? Boolean(asset.bounds) && (asset.animations.length > 0 || /prop|set|stage|background|environment/i.test(asset.id))
452
+ : asset.type === "audio" || asset.type === "texture";
453
+ const artifactPaths = artifacts.assetFiles.find((entry) => entry.id === asset.id) ?? createReadinessAssetArtifacts(projectDir, manifest, asset);
454
+ return {
455
+ id: asset.id,
456
+ type: asset.type,
457
+ format: asset.format,
458
+ source: asset.source,
459
+ outputPath: asset.outputPath,
460
+ url: asset.url,
461
+ sizeBytes: asset.sizeBytes,
462
+ bounds: asset.bounds,
463
+ boundsMetadata: asset.boundsMetadata,
464
+ animations: asset.animations,
465
+ animation: createReadinessAnimationMetadata(asset.animations),
466
+ animationMetadata: asset.animationMetadata,
467
+ humanoid: asset.humanoid,
468
+ skeleton: asset.skeleton,
469
+ morphTargets: asset.morphTargets,
470
+ provenance,
471
+ placeholderFree,
472
+ licenseVerified,
473
+ materials: asset.materials,
474
+ materialMetadata: asset.materialMetadata,
475
+ textures: asset.textures,
476
+ orientation: asset.orientation,
477
+ nodeNames: asset.nodeNames,
478
+ artifactPaths,
479
+ gameReady,
480
+ cartoonReady,
481
+ warnings: assetWarnings
482
+ };
483
+ });
484
+ if (profile === "game") {
485
+ if (modelAssets.length === 0)
486
+ failures.push("Game readiness requires at least one typed model asset. Add a GLB/GLTF with aura3d assets add ./fighter.glb --name fighter.");
487
+ if (animatedModels.length === 0)
488
+ warnings.push("No animated model clips detected. Static scenes can ship, but playable character showcases should include idle/walk/attack/hurt clips.");
489
+ if (animatedModels.length > 0 && humanoidModels === 0)
490
+ warnings.push("No humanoid model metadata detected. Character-heavy game routes should confirm humanoid status with assets inspect --humanoid and typed asset metadata.");
491
+ for (const asset of assets) {
492
+ if (asset.type !== "model")
493
+ continue;
494
+ if (!asset.gameReady)
495
+ warnings.push(`${asset.id}: not game-ready yet; expected bounds, named materials, and browser-sized payload.`);
496
+ }
497
+ }
498
+ else {
499
+ if (modelAssets.length === 0)
500
+ failures.push("Cartoon readiness requires at least one typed model/set/prop GLB or GLTF.");
501
+ if (animatedModels.length === 0)
502
+ warnings.push("No animated character clips detected. Prompt-to-episode output can use transform animation, but character acting needs skeletal or pose clips.");
503
+ if (animatedModels.length > 0 && humanoidModels === 0)
504
+ warnings.push("No humanoid model metadata detected. Acting-heavy cartoon routes should confirm character rigs with assets inspect --humanoid.");
505
+ const audioAssets = manifest.assets.filter((asset) => asset.type === "audio");
506
+ if (audioAssets.length === 0)
507
+ warnings.push("No audio assets detected. AuraVoice bridge can still reference external narration manifests, but local episode proof is stronger with audio registered.");
508
+ }
509
+ const ok = failures.length === 0;
510
+ const baseMessage = ok
511
+ ? `${profile === "game" ? "Game" : "Cartoon"} asset readiness report completed.`
512
+ : failures;
513
+ const messages = [
514
+ ...(Array.isArray(baseMessage) ? baseMessage : [baseMessage]),
515
+ ...(evidencePath ? [`Wrote asset readiness evidence: ${normalizeRelativePath(relative(projectDir, evidencePath))}`] : [])
516
+ ];
517
+ const report = {
518
+ schema: "aura3d.asset-readiness/1.0",
519
+ profile,
520
+ ok,
521
+ status: ok ? "passed" : "failed",
522
+ validator: createReadinessValidatorEvidence(profile),
523
+ checkedAt: new Date().toISOString(),
524
+ manifestPath,
525
+ artifacts,
526
+ contracts: createReadinessValidationContracts(profile),
527
+ summary: {
528
+ totalAssets: manifest.assets.length,
529
+ modelAssets: modelAssets.length,
530
+ animatedModels: animatedModels.length,
531
+ textureAssets: manifest.assets.filter((asset) => asset.type === "texture").length,
532
+ audioAssets: manifest.assets.filter((asset) => asset.type === "audio").length,
533
+ environmentAssets: manifest.assets.filter((asset) => asset.type === "environment").length,
534
+ animationClips,
535
+ humanoidModels
536
+ },
537
+ assets,
538
+ failures,
539
+ warnings,
540
+ messages
541
+ };
542
+ if (evidencePath) {
543
+ mkdirSync(dirname(evidencePath), { recursive: true });
544
+ writeFileSync(evidencePath, `${JSON.stringify(report, null, 2)}\n`);
545
+ }
546
+ return report;
547
+ }
548
+ function filterAssetManifest(manifest, assetIds) {
549
+ const normalized = normalizeAssetIdFilter(assetIds);
550
+ if (normalized.length === 0)
551
+ return manifest;
552
+ const allowed = new Set(normalized);
553
+ return {
554
+ ...manifest,
555
+ assets: manifest.assets.filter((asset) => allowed.has(asset.id))
556
+ };
557
+ }
558
+ function findMissingAssetIds(manifest, assetIds) {
559
+ const normalized = normalizeAssetIdFilter(assetIds);
560
+ if (normalized.length === 0)
561
+ return [];
562
+ const existing = new Set(manifest.assets.map((asset) => asset.id));
563
+ return normalized.filter((id) => !existing.has(id));
564
+ }
565
+ function normalizeAssetIdFilter(assetIds) {
566
+ return [...new Set((assetIds ?? []).map((id) => id.trim()).filter(Boolean))];
567
+ }
568
+ function createReadinessValidatorEvidence(profile) {
569
+ return profile === "game"
570
+ ? {
571
+ id: "aura-clash-game-assets",
572
+ command: "assets validate-game",
573
+ label: "AuraClash game asset validator"
574
+ }
575
+ : {
576
+ id: "aura-voice-cartoon-assets",
577
+ command: "assets validate-cartoon",
578
+ label: "AuraVoice cartoon asset validator"
579
+ };
580
+ }
581
+ function createReadinessValidationContracts(profile) {
582
+ if (profile === "game") {
583
+ return [
584
+ {
585
+ id: "quaternius-game-ready-fighter-validation-contract",
586
+ label: "Quaternius-derived game-ready fighter validation contract",
587
+ profile: "game",
588
+ sourceFamily: "Quaternius",
589
+ intendedUse: "fighter",
590
+ sourceOnly: true,
591
+ requiredChecks: [
592
+ "typed Aura model asset entry generated by assets add",
593
+ "Quaternius provenance or source-family metadata",
594
+ "GLB/GLTF model with browser-sized payload",
595
+ "bounds with grounded pivot and fighter-scale dimensions",
596
+ "forward-facing +z or z orientation before runtime mirroring",
597
+ "humanoid skeleton metadata suitable for retarget diagnostics",
598
+ "readable visible materials and texture budget",
599
+ "thumbnail or first-frame artifact path",
600
+ "non-empty named fighting animation clips",
601
+ "no floating hair-only assembly without a body/head anchor"
602
+ ],
603
+ requiredAnimationClips: ["idle", "walk", "lightPunch"],
604
+ evidenceBoundary: "This CLI contract is source-only. It does not prove a Quaternius fighter passed validation until assets validate-game output and retained runtime/browser evidence are archived."
605
+ }
606
+ ];
607
+ }
608
+ return [
609
+ {
610
+ id: "auravoice-cartoon-character-asset-validation-contract",
611
+ label: "AuraVoice cartoon asset validation contract",
612
+ profile: "cartoon",
613
+ sourceFamily: "AuraVoice",
614
+ intendedUse: "cartoon-character",
615
+ sourceOnly: true,
616
+ requiredChecks: [
617
+ "typed Aura model, texture, audio, or environment asset entry",
618
+ "bounds for model/set composition",
619
+ "animation or transform-ready character metadata",
620
+ "audio or external AuraVoice manifest references for stronger episode proof"
621
+ ],
622
+ evidenceBoundary: "This CLI contract is source-only. It does not prove cartoon route readiness until validate-cartoon output, rendered frames, timing proof, and AuraVoice evidence are archived."
623
+ }
624
+ ];
625
+ }
626
+ function createReadinessArtifacts(projectDir, manifest, evidencePath) {
627
+ const artifacts = {
628
+ manifestPath: resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST),
629
+ typedAssetsPath: resolve(projectDir, manifest.typegen),
630
+ outputDir: resolve(projectDir, manifest.outputDir),
631
+ assetBasePath: manifest.assetBasePath,
632
+ assetFiles: manifest.assets.map((asset) => createReadinessAssetArtifacts(projectDir, manifest, asset))
633
+ };
634
+ if (evidencePath)
635
+ artifacts.evidencePath = evidencePath;
636
+ return artifacts;
637
+ }
638
+ function createReadinessAssetArtifacts(projectDir, manifest, asset) {
639
+ const artifact = {
640
+ id: asset.id,
641
+ sourcePath: resolve(projectDir, asset.source),
642
+ outputPath: resolve(projectDir, asset.outputPath),
643
+ publicUrl: asset.url,
644
+ dependencyPaths: (asset.dependencies ?? []).map((dependency) => resolve(dirname(resolve(projectDir, asset.outputPath)), dependency))
645
+ };
646
+ if (asset.thumbnailUrl) {
647
+ artifact.thumbnailUrl = asset.thumbnailUrl;
648
+ const thumbnailPath = resolvePublicArtifactPath(projectDir, manifest, asset.thumbnailUrl);
649
+ if (thumbnailPath)
650
+ artifact.thumbnailPath = thumbnailPath;
651
+ }
652
+ return artifact;
653
+ }
654
+ function createReadinessAnimationMetadata(animations) {
655
+ return {
656
+ clipCount: animations.length,
657
+ clips: animations.map((name, index) => ({ index, name }))
658
+ };
659
+ }
660
+ function createAssetReadinessIssues(profile, asset) {
661
+ if (asset.type !== "model")
662
+ return { failures: [], warnings: [] };
663
+ const failures = [];
664
+ const warnings = [];
665
+ const prefix = `${asset.id}:`;
666
+ const characterLike = isCharacterLikeAsset(asset);
667
+ if (profile === "game" && characterLike) {
668
+ const missing = missingRequiredGameClips(asset.animations);
669
+ if (missing.length > 0) {
670
+ failures.push(`${prefix} missing required game animation clip${missing.length === 1 ? "" : "s"}: ${missing.join(", ")}.`);
671
+ }
672
+ }
673
+ const emptyClips = (asset.animationMetadata?.clips ?? []).filter((clip) => clip.channelCount === 0 || clip.samplerCount === 0);
674
+ for (const clip of emptyClips) {
675
+ failures.push(`${prefix} animation clip "${clip.name}" is empty; expected at least one channel and sampler.`);
676
+ }
677
+ const bounds = asset.boundsMetadata;
678
+ if (bounds && bounds.maxDimension > 50) {
679
+ failures.push(`${prefix} oversized bounds detected; largest dimension is ${bounds.maxDimension}m, expected at most 50m for browser game assets.`);
680
+ }
681
+ else if (bounds && characterLike && bounds.maxDimension > 4) {
682
+ warnings.push(`${prefix} character-sized model is unusually large (${bounds.maxDimension}m); confirm scale before using it in gameplay.`);
683
+ }
684
+ if (bounds && characterLike && !bounds.grounded) {
685
+ warnings.push(`${prefix} bounds are not grounded at the pivot; min.y is ${bounds.min[1]}m.`);
686
+ }
687
+ const orientation = asset.orientation;
688
+ if (profile === "game" && characterLike && orientation?.forwardAxis && !["+z", "z"].includes(orientation.forwardAxis.toLowerCase())) {
689
+ failures.push(`${prefix} wrong facing direction "${orientation.forwardAxis}"; fighting-game characters are expected to face +z before runtime mirroring.`);
690
+ }
691
+ const invisibleMaterials = (asset.materialMetadata ?? []).filter((material) => !material.visible || !material.readable);
692
+ for (const material of invisibleMaterials) {
693
+ failures.push(`${prefix} invisible or unreadable material "${material.name}" detected${material.reasons.length ? ` (${material.reasons.join("; ")})` : ""}.`);
694
+ }
695
+ if (profile === "game" && hasFloatingHairRisk(asset)) {
696
+ failures.push(`${prefix} floating hair risk detected; hair-only geometry must be assembled onto a body/head with assets assemble-character before game validation.`);
697
+ }
698
+ return { failures, warnings };
699
+ }
700
+ function isCharacterLikeAsset(asset) {
701
+ if (asset.humanoid?.humanoid)
702
+ return true;
703
+ return /fighter|player|opponent|enemy|hero|character|avatar|humanoid|npc|body|mara/i.test(asset.id);
704
+ }
705
+ function missingRequiredGameClips(animations) {
706
+ const normalized = animations.map((name) => name.toLowerCase().replace(/[^a-z0-9]/g, ""));
707
+ const hasNamed = (patterns) => normalized.some((name) => patterns.some((pattern) => pattern.test(name)));
708
+ const missing = [];
709
+ if (!hasNamed([/idle/, /stand/]))
710
+ missing.push("idle");
711
+ if (!hasNamed([/walk/, /locomotion/, /move/]))
712
+ missing.push("walk");
713
+ if (!hasNamed([/lightpunch/, /lightattack/, /light/, /jab/, /punch/, /attack/]))
714
+ missing.push("lightPunch");
715
+ return missing;
716
+ }
717
+ function hasFloatingHairRisk(asset) {
718
+ const names = [asset.id, ...(asset.nodeNames ?? [])].join(" ").toLowerCase();
719
+ if (!names.includes("hair"))
720
+ return false;
721
+ const hasBodyAnchor = /body|torso|spine|chest|head|neck|skull|face|hips|pelvis/.test(names);
722
+ return !hasBodyAnchor;
723
+ }
724
+ function resolvePublicArtifactPath(projectDir, manifest, url) {
725
+ if (/^https?:\/\//i.test(url))
726
+ return undefined;
727
+ if (url.startsWith(manifest.assetBasePath)) {
728
+ return resolve(projectDir, manifest.outputDir, url.slice(manifest.assetBasePath.length));
729
+ }
730
+ return resolve(projectDir, url.replace(/^\//, "public/"));
731
+ }
732
+ function defaultAttachPoint(slot) {
733
+ const normalized = slot.toLowerCase();
734
+ if (normalized.includes("hair") || normalized.includes("hat") || normalized.includes("face"))
735
+ return "head";
736
+ if (normalized.includes("hand") || normalized.includes("weapon") || normalized.includes("prop"))
737
+ return "rightHand";
738
+ if (normalized.includes("shoe") || normalized.includes("boot"))
739
+ return "feet";
740
+ if (normalized.includes("cape") || normalized.includes("back"))
741
+ return "spine";
742
+ return "root";
743
+ }
247
744
  export function readAssetManifest(projectDir) {
248
745
  const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
249
746
  if (!existsSync(manifestPath)) {
@@ -270,8 +767,15 @@ function inspectAssetFile(path, format) {
270
767
  return inspectGlb(readFileSync(path), dirname(path));
271
768
  return {
272
769
  materials: [],
770
+ materialMetadata: [],
273
771
  animations: [],
772
+ animation: emptyAnimationInspection(),
773
+ humanoid: unknownHumanoidInspection("Humanoid detection is only available for GLB/glTF model assets."),
774
+ skeleton: emptySkeletonInspection("Skeleton detection is only available for GLB/glTF model assets."),
775
+ morphTargets: emptyMorphTargetInspection("Morph target detection is only available for GLB/glTF model assets."),
274
776
  textures: [],
777
+ orientation: unknownOrientationInspection(),
778
+ nodeNames: [],
275
779
  dependencies: [],
276
780
  bounds: undefined
277
781
  };
@@ -300,18 +804,226 @@ function inspectGltf(json, baseDir) {
300
804
  throw new Error(`Aura3D assets add failed: referenced asset file missing: ${missing.join(", ")}. Suggested fix: keep external .bin and texture files beside the .gltf or export as .glb.`);
301
805
  }
302
806
  }
807
+ const boundsMetadata = extractBoundsDetails(json);
303
808
  return {
304
- bounds: extractBounds(json),
809
+ bounds: boundsMetadata?.size,
810
+ boundsMetadata,
305
811
  materials: (json.materials ?? []).map((material, index) => material.name ?? `material-${index}`),
812
+ materialMetadata: inspectGltfMaterials(json),
306
813
  animations: (json.animations ?? []).map((animation, index) => animation.name ?? `clip-${index}`),
814
+ animation: inspectGltfAnimations(json),
815
+ humanoid: inspectGltfHumanoid(json),
816
+ skeleton: inspectGltfSkeleton(json),
817
+ morphTargets: inspectGltfMorphTargets(json),
818
+ provenance: inspectGltfProvenance(json),
307
819
  textures: (json.images ?? []).map((image, index) => image.uri ?? image.name ?? `image-${index}`),
820
+ orientation: inspectGltfOrientation(json),
821
+ nodeNames: (json.nodes ?? []).map((node, index) => node.name ?? `node-${index}`),
308
822
  dependencies
309
823
  };
310
824
  }
825
+ function emptyAnimationInspection() {
826
+ return {
827
+ clipCount: 0,
828
+ clips: [],
829
+ messages: ["No embedded animation clips detected."]
830
+ };
831
+ }
832
+ function inspectGltfAnimations(json) {
833
+ const clips = (json.animations ?? []).map((animation, index) => {
834
+ const channels = animation.channels ?? [];
835
+ const targetPaths = uniqueStrings(channels.map((channel) => channel.target?.path).filter(isString));
836
+ const targetNodes = uniqueStrings(channels.map((channel) => {
837
+ const nodeIndex = channel.target?.node;
838
+ return typeof nodeIndex === "number" ? json.nodes?.[nodeIndex]?.name ?? `node-${nodeIndex}` : undefined;
839
+ }).filter(isString));
840
+ return {
841
+ index,
842
+ name: animation.name ?? `clip-${index}`,
843
+ channelCount: channels.length,
844
+ samplerCount: animation.samplers?.length ?? 0,
845
+ targetPaths,
846
+ targetNodes
847
+ };
848
+ });
849
+ return {
850
+ clipCount: clips.length,
851
+ clips,
852
+ messages: clips.length === 0
853
+ ? ["No embedded animation clips detected."]
854
+ : [`Detected ${clips.length} embedded animation clip${clips.length === 1 ? "" : "s"}.`]
855
+ };
856
+ }
857
+ function inspectGltfHumanoid(json) {
858
+ const skinCount = json.skins?.length ?? 0;
859
+ const jointIndexes = uniqueNumbers((json.skins ?? []).flatMap((skin) => skin.joints ?? []));
860
+ const jointNames = uniqueStrings(jointIndexes.map((index) => json.nodes?.[index]?.name ?? `joint-${index}`));
861
+ const nodeNames = uniqueStrings((json.nodes ?? []).map((node, index) => node.name ?? `node-${index}`));
862
+ const candidates = jointNames.length > 0 ? jointNames : nodeNames;
863
+ const requiredBones = ["hips", "spine", "head", "leftArm", "rightArm", "leftLeg", "rightLeg"];
864
+ const matchedBones = requiredBones.filter((bone) => candidates.some((name) => matchesHumanoidBone(name, bone)));
865
+ const missingBones = requiredBones.filter((bone) => !matchedBones.includes(bone));
866
+ const hasSkin = jointIndexes.length > 0;
867
+ const hasTorso = matchedBones.includes("hips") && matchedBones.includes("spine") && matchedBones.includes("head");
868
+ const hasArms = matchedBones.includes("leftArm") && matchedBones.includes("rightArm");
869
+ const hasLegs = matchedBones.includes("leftLeg") && matchedBones.includes("rightLeg");
870
+ const humanoid = (hasSkin && hasTorso && hasArms && hasLegs) || (!hasSkin && hasTorso && matchedBones.length >= 5);
871
+ const status = humanoid
872
+ ? "humanoid"
873
+ : hasSkin || matchedBones.length > 0
874
+ ? "unknown"
875
+ : "non-humanoid";
876
+ const confidence = humanoid && hasSkin
877
+ ? "high"
878
+ : humanoid || (hasSkin && matchedBones.length >= 5)
879
+ ? "medium"
880
+ : "low";
881
+ return {
882
+ humanoid,
883
+ status,
884
+ confidence,
885
+ skinCount,
886
+ jointCount: jointIndexes.length,
887
+ matchedBones,
888
+ missingBones,
889
+ messages: humanoid
890
+ ? [`Humanoid signals detected from ${hasSkin ? "skinned joints" : "node names"}.`]
891
+ : status === "unknown"
892
+ ? [`Humanoid status is unknown; missing bone groups: ${missingBones.join(", ")}.`]
893
+ : ["No humanoid skeleton signals detected."]
894
+ };
895
+ }
896
+ function inspectGltfSkeleton(json) {
897
+ const skins = (json.skins ?? []).map((skin, index) => {
898
+ const joints = (skin.joints ?? []).map((jointIndex) => json.nodes?.[jointIndex]?.name ?? `joint-${jointIndex}`);
899
+ const skeletonIndex = skin.skeleton;
900
+ return {
901
+ index,
902
+ name: skin.name ?? `skin-${index}`,
903
+ jointCount: joints.length,
904
+ joints,
905
+ ...(typeof skeletonIndex === "number" ? { skeleton: json.nodes?.[skeletonIndex]?.name ?? `node-${skeletonIndex}` } : {})
906
+ };
907
+ });
908
+ const jointCount = uniqueStrings(skins.flatMap((skin) => skin.joints)).length;
909
+ return {
910
+ skinCount: skins.length,
911
+ jointCount,
912
+ skins,
913
+ messages: skins.length === 0
914
+ ? ["No skin/skeleton metadata detected."]
915
+ : [`Detected ${skins.length} skin${skins.length === 1 ? "" : "s"} with ${jointCount} unique joint${jointCount === 1 ? "" : "s"}.`]
916
+ };
917
+ }
918
+ function emptySkeletonInspection(message) {
919
+ return {
920
+ skinCount: 0,
921
+ jointCount: 0,
922
+ skins: [],
923
+ messages: [message]
924
+ };
925
+ }
926
+ function inspectGltfMorphTargets(json) {
927
+ const meshes = (json.meshes ?? []).map((mesh, index) => {
928
+ const meshExtras = objectValue(mesh.extras);
929
+ const namedTargets = stringArrayValue(meshExtras?.targetNames ?? meshExtras?.morphTargetNames);
930
+ const targetCount = Math.max(namedTargets.length, mesh.weights?.length ?? 0, ...(mesh.primitives ?? []).map((primitive) => primitive.targets?.length ?? 0));
931
+ if (targetCount === 0)
932
+ return undefined;
933
+ const targetNames = targetCount > 0
934
+ ? Array.from({ length: targetCount }, (_, targetIndex) => namedTargets[targetIndex] ?? `target-${targetIndex}`)
935
+ : [];
936
+ return {
937
+ index,
938
+ name: mesh.name ?? `mesh-${index}`,
939
+ targetNames
940
+ };
941
+ }).filter((mesh) => Boolean(mesh));
942
+ const targetNames = uniqueStrings(meshes.flatMap((mesh) => mesh.targetNames));
943
+ return {
944
+ targetCount: targetNames.length,
945
+ targetNames,
946
+ meshes,
947
+ messages: targetNames.length === 0
948
+ ? ["No morph target metadata detected."]
949
+ : [`Detected ${targetNames.length} morph target${targetNames.length === 1 ? "" : "s"}.`]
950
+ };
951
+ }
952
+ function emptyMorphTargetInspection(message) {
953
+ return {
954
+ targetCount: 0,
955
+ targetNames: [],
956
+ meshes: [],
957
+ messages: [message]
958
+ };
959
+ }
960
+ function inspectGltfProvenance(json) {
961
+ const assetExtras = objectValue(json.asset?.extras);
962
+ const auraExtras = objectValue(assetExtras?.aura3d) ?? assetExtras;
963
+ const provenance = objectValue(auraExtras?.provenance ?? auraExtras?.license ?? auraExtras?.source);
964
+ const sourceUrl = stringValue(provenance?.sourceUrl ?? provenance?.url ?? auraExtras?.sourceUrl);
965
+ const license = stringValue(provenance?.license ?? provenance?.spdx ?? auraExtras?.license);
966
+ const author = stringValue(provenance?.author ?? provenance?.creator ?? auraExtras?.author);
967
+ const sourceFamily = stringValue(provenance?.sourceFamily ?? provenance?.source ?? auraExtras?.sourceFamily);
968
+ const attribution = stringValue(provenance?.attribution ?? auraExtras?.attribution);
969
+ const evidence = stringArrayValue(provenance?.evidence ?? auraExtras?.evidence);
970
+ if (!sourceUrl && !license && !author && !sourceFamily && !attribution && evidence.length === 0)
971
+ return undefined;
972
+ return {
973
+ ...(sourceUrl ? { sourceUrl } : {}),
974
+ ...(license ? { license } : {}),
975
+ ...(author ? { author } : {}),
976
+ ...(sourceFamily ? { sourceFamily } : {}),
977
+ ...(attribution ? { attribution } : {}),
978
+ ...(evidence.length > 0 ? { evidence } : {})
979
+ };
980
+ }
981
+ function unknownHumanoidInspection(message) {
982
+ return {
983
+ humanoid: false,
984
+ status: "unknown",
985
+ confidence: "low",
986
+ skinCount: 0,
987
+ jointCount: 0,
988
+ matchedBones: [],
989
+ missingBones: ["hips", "spine", "head", "leftArm", "rightArm", "leftLeg", "rightLeg"],
990
+ messages: [message]
991
+ };
992
+ }
993
+ function matchesHumanoidBone(name, bone) {
994
+ const raw = name.toLowerCase();
995
+ const compact = raw.replace(/[^a-z0-9]/g, "");
996
+ const left = compact.includes("left") || /(^|[^a-z0-9])l([^a-z0-9]|$)/.test(raw) || /[^a-z0-9]l$/.test(raw);
997
+ const right = compact.includes("right") || /(^|[^a-z0-9])r([^a-z0-9]|$)/.test(raw) || /[^a-z0-9]r$/.test(raw);
998
+ const arm = compact.includes("arm") || compact.includes("forearm") || compact.includes("shoulder") || compact.includes("hand");
999
+ const leg = compact.includes("leg") || compact.includes("thigh") || compact.includes("foot") || compact.includes("toe");
1000
+ if (bone === "hips")
1001
+ return compact.includes("hip") || compact.includes("pelvis");
1002
+ if (bone === "spine")
1003
+ return compact.includes("spine") || compact.includes("chest") || compact.includes("torso");
1004
+ if (bone === "head")
1005
+ return compact.includes("head") || compact.includes("neck");
1006
+ if (bone === "leftArm")
1007
+ return left && arm;
1008
+ if (bone === "rightArm")
1009
+ return right && arm;
1010
+ if (bone === "leftLeg")
1011
+ return left && leg;
1012
+ return right && leg;
1013
+ }
311
1014
  function isExternalUri(uri) {
312
1015
  return typeof uri === "string" && uri.length > 0 && !uri.startsWith("data:");
313
1016
  }
314
- function extractBounds(json) {
1017
+ function isString(value) {
1018
+ return typeof value === "string";
1019
+ }
1020
+ function uniqueStrings(values) {
1021
+ return [...new Set(values)];
1022
+ }
1023
+ function uniqueNumbers(values) {
1024
+ return [...new Set(values)];
1025
+ }
1026
+ function extractBoundsDetails(json) {
315
1027
  let min;
316
1028
  let max;
317
1029
  for (const accessor of json.accessors ?? []) {
@@ -320,7 +1032,89 @@ function extractBounds(json) {
320
1032
  min = min ? [Math.min(min[0], accessor.min[0]), Math.min(min[1], accessor.min[1]), Math.min(min[2], accessor.min[2])] : [accessor.min[0], accessor.min[1], accessor.min[2]];
321
1033
  max = max ? [Math.max(max[0], accessor.max[0]), Math.max(max[1], accessor.max[1]), Math.max(max[2], accessor.max[2])] : [accessor.max[0], accessor.max[1], accessor.max[2]];
322
1034
  }
323
- return min && max ? [round(max[0] - min[0]), round(max[1] - min[1]), round(max[2] - min[2])] : undefined;
1035
+ if (!min || !max)
1036
+ return undefined;
1037
+ const size = [round(max[0] - min[0]), round(max[1] - min[1]), round(max[2] - min[2])];
1038
+ const center = [round((min[0] + max[0]) / 2), round((min[1] + max[1]) / 2), round((min[2] + max[2]) / 2)];
1039
+ const roundedMin = [round(min[0]), round(min[1]), round(min[2])];
1040
+ const roundedMax = [round(max[0]), round(max[1]), round(max[2])];
1041
+ return {
1042
+ min: roundedMin,
1043
+ max: roundedMax,
1044
+ size,
1045
+ center,
1046
+ maxDimension: Math.max(...size),
1047
+ grounded: Math.abs(roundedMin[1]) <= 0.08
1048
+ };
1049
+ }
1050
+ function inspectGltfMaterials(json) {
1051
+ return (json.materials ?? []).map((material, index) => {
1052
+ const name = material.name ?? `material-${index}`;
1053
+ const extras = objectValue(material.extras);
1054
+ const explicitVisible = booleanValue(extras?.visible);
1055
+ const explicitReadable = booleanValue(extras?.readable);
1056
+ const opacity = round(numberValue(material.pbrMetallicRoughness?.baseColorFactor?.[3]) ?? 1);
1057
+ const alphaMode = material.alphaMode;
1058
+ const alphaCutoff = numberValue(material.alphaCutoff);
1059
+ const reasons = [];
1060
+ if (opacity <= 0)
1061
+ reasons.push("baseColorFactor alpha is 0");
1062
+ if (alphaMode === "MASK" && (alphaCutoff ?? 0.5) >= 1)
1063
+ reasons.push("alpha mask cutoff hides fully transparent surfaces");
1064
+ if (explicitVisible === false)
1065
+ reasons.push("material extras mark visible=false");
1066
+ if (explicitReadable === false)
1067
+ reasons.push("material extras mark readable=false");
1068
+ const visible = explicitVisible ?? (opacity > 0 && !(alphaMode === "MASK" && (alphaCutoff ?? 0.5) >= 1));
1069
+ const readable = explicitReadable ?? visible;
1070
+ return {
1071
+ name,
1072
+ visible,
1073
+ readable,
1074
+ opacity,
1075
+ ...(alphaMode ? { alphaMode } : {}),
1076
+ reasons
1077
+ };
1078
+ });
1079
+ }
1080
+ function inspectGltfOrientation(json) {
1081
+ const assetExtras = objectValue(json.asset?.extras);
1082
+ const auraExtras = objectValue(assetExtras?.aura3d) ?? assetExtras;
1083
+ const orientation = objectValue(auraExtras?.orientation) ?? auraExtras;
1084
+ const forwardAxis = stringValue(orientation?.forwardAxis ?? orientation?.forward ?? orientation?.facing);
1085
+ const upAxis = stringValue(orientation?.upAxis ?? orientation?.up);
1086
+ if (!forwardAxis && !upAxis)
1087
+ return unknownOrientationInspection();
1088
+ return {
1089
+ source: "gltf-extras",
1090
+ ...(forwardAxis ? { forwardAxis } : {}),
1091
+ ...(upAxis ? { upAxis } : {}),
1092
+ messages: [`Orientation metadata detected${forwardAxis ? ` with forwardAxis=${forwardAxis}` : ""}${upAxis ? ` and upAxis=${upAxis}` : ""}.`]
1093
+ };
1094
+ }
1095
+ function unknownOrientationInspection() {
1096
+ return {
1097
+ source: "unknown",
1098
+ messages: ["No orientation metadata detected; facing direction cannot be proven."]
1099
+ };
1100
+ }
1101
+ function objectValue(value) {
1102
+ return typeof value === "object" && value !== null ? value : undefined;
1103
+ }
1104
+ function stringValue(value) {
1105
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
1106
+ }
1107
+ function stringArrayValue(value) {
1108
+ if (!Array.isArray(value))
1109
+ return [];
1110
+ return value.map((entry) => stringValue(entry)).filter((entry) => Boolean(entry));
1111
+ }
1112
+ function booleanValue(value) {
1113
+ return typeof value === "boolean" ? value : undefined;
1114
+ }
1115
+ function numberValue(value) {
1116
+ const number = Number(value);
1117
+ return Number.isFinite(number) ? number : undefined;
324
1118
  }
325
1119
  function createAssetWarnings(path, inspection) {
326
1120
  const warnings = [];
@@ -331,8 +1125,96 @@ function createAssetWarnings(path, inspection) {
331
1125
  warnings.push("bounds could not be extracted");
332
1126
  if (inspection.textures.length === 0 && ["glb", "gltf"].includes(extname(path).slice(1).toLowerCase()))
333
1127
  warnings.push("no texture references detected");
1128
+ if (inspection.orientation.source === "unknown" && ["glb", "gltf"].includes(extname(path).slice(1).toLowerCase()))
1129
+ warnings.push("orientation metadata missing; facing direction cannot be validated until GLTF extras declare aura3d.orientation.forwardAxis");
1130
+ if (inspection.materialMetadata.some((material) => !material.visible || !material.readable))
1131
+ warnings.push("one or more materials are invisible or unreadable");
334
1132
  return warnings;
335
1133
  }
1134
+ function createAssetProvenance(projectDir, sourcePath, options, detected) {
1135
+ return {
1136
+ sourcePath: normalizeRelativePath(relative(projectDir, sourcePath)),
1137
+ ...(options.sourceUrl ?? detected?.sourceUrl ? { sourceUrl: options.sourceUrl ?? detected?.sourceUrl } : {}),
1138
+ ...(options.license ?? detected?.license ? { license: options.license ?? detected?.license } : {}),
1139
+ ...(options.author ?? detected?.author ? { author: options.author ?? detected?.author } : {}),
1140
+ ...(options.sourceFamily ?? detected?.sourceFamily ? { sourceFamily: options.sourceFamily ?? detected?.sourceFamily } : {}),
1141
+ ...(options.attribution ?? detected?.attribution ? { attribution: options.attribution ?? detected?.attribution } : {}),
1142
+ ...(detected?.evidence && detected.evidence.length > 0 ? { evidence: detected.evidence } : {}),
1143
+ checkedAt: new Date().toISOString()
1144
+ };
1145
+ }
1146
+ function readExternalProvenance(projectDir, provenanceFile) {
1147
+ if (!provenanceFile)
1148
+ return new Map();
1149
+ const path = resolve(projectDir, provenanceFile);
1150
+ if (!existsSync(path))
1151
+ return new Map();
1152
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
1153
+ const root = objectValue(parsed);
1154
+ if (!root)
1155
+ return new Map();
1156
+ const checkedAt = stringValue(root.updatedAt ?? root.verifiedAt ?? root.checkedAt) ?? new Date().toISOString();
1157
+ const records = [
1158
+ ...arrayObjectValue(root.launchGlbs),
1159
+ ...arrayObjectValue(root.assets),
1160
+ ...arrayObjectValue(root.assetEvidence)
1161
+ ];
1162
+ const byId = new Map();
1163
+ for (const record of records) {
1164
+ const typedAsset = stringValue(record.typedAsset);
1165
+ const id = stringValue(record.assetKey ?? record.id) ?? typedAsset?.replace(/^assets\./, "");
1166
+ if (!id)
1167
+ continue;
1168
+ const nestedProvenance = objectValue(record.provenance);
1169
+ const sourcePath = stringValue(record.sourcePath ?? record.source ?? nestedProvenance?.builderOutput) ?? id;
1170
+ const license = stringValue(record.license ?? record.licenseNote ?? nestedProvenance?.license);
1171
+ const sourceUrl = stringValue(record.sourceUrl ?? record.publicUrl ?? record.officialPage ?? nestedProvenance?.sourceUrl);
1172
+ const sourceFamily = stringValue(record.sourceFamily ?? nestedProvenance?.sourceFamily ?? nestedProvenance?.sourcePack);
1173
+ const author = stringValue(record.author ?? nestedProvenance?.author);
1174
+ const attribution = stringValue(record.attribution ?? record.credit ?? nestedProvenance?.attribution);
1175
+ const evidence = [
1176
+ ...stringArrayValue(record.evidence),
1177
+ ...stringArrayValue(record.intendedRouteUsage),
1178
+ ...stringArrayValue(nestedProvenance?.evidence)
1179
+ ];
1180
+ byId.set(id, {
1181
+ sourcePath,
1182
+ ...(sourceUrl ? { sourceUrl } : {}),
1183
+ ...(license ? { license } : {}),
1184
+ ...(author ? { author } : {}),
1185
+ ...(sourceFamily ? { sourceFamily } : {}),
1186
+ ...(attribution ? { attribution } : {}),
1187
+ ...(evidence.length > 0 ? { evidence } : {}),
1188
+ checkedAt
1189
+ });
1190
+ }
1191
+ return byId;
1192
+ }
1193
+ function arrayObjectValue(value) {
1194
+ if (!Array.isArray(value))
1195
+ return [];
1196
+ return value.map((entry) => objectValue(entry)).filter((entry) => Boolean(entry));
1197
+ }
1198
+ function resolveAssetProvenance(asset, externalProvenance) {
1199
+ return asset.provenance ?? externalProvenance.get(asset.id);
1200
+ }
1201
+ function hasUsableLicenseEvidence(provenance) {
1202
+ const license = provenance?.license?.trim();
1203
+ if (!license)
1204
+ return false;
1205
+ return !/(unverified|unknown|candidate|needs[-\s]?confirmation|todo|placeholder)/i.test(license);
1206
+ }
1207
+ function isPlaceholderAsset(asset, provenance) {
1208
+ const value = [
1209
+ asset.id,
1210
+ asset.source,
1211
+ asset.outputPath,
1212
+ asset.url,
1213
+ provenance?.sourcePath,
1214
+ provenance?.sourceUrl
1215
+ ].filter(Boolean).join(" ");
1216
+ return /(^|[-_./\s])(placeholder|dummy|mock|todo|replace-me|sample-placeholder)([-_./\s]|$)/i.test(value);
1217
+ }
336
1218
  function copyAssetDependencies(projectDir, sourcePath, outputDir, dependencies) {
337
1219
  const sourceDir = dirname(sourcePath);
338
1220
  for (const dependency of dependencies) {