create-aura3d 3.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-skills.d.ts +38 -0
  3. package/dist/agent-skills.d.ts.map +1 -0
  4. package/dist/agent-skills.js +103 -0
  5. package/dist/agent-skills.js.map +1 -0
  6. package/dist/cli.js +32 -5
  7. package/dist/cli.js.map +1 -1
  8. package/dist/index.d.ts +32 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +52 -10
  11. package/dist/index.js.map +1 -1
  12. package/dist/showcase-spec-artifacts.d.ts +1 -1
  13. package/dist/showcase-spec-game-geometry-extractor.js +8 -11
  14. package/dist/showcase-spec-game-geometry-extractor.js.map +1 -1
  15. package/dist/template-hdri.d.ts +12 -0
  16. package/dist/template-hdri.d.ts.map +1 -0
  17. package/dist/template-hdri.js +49 -0
  18. package/dist/template-hdri.js.map +1 -0
  19. package/hdri/ATTRIBUTION.md +8 -0
  20. package/hdri/autumn_field_puresky_1k.hdr +0 -0
  21. package/hdri/kloppenheim_06_puresky_1k.hdr +0 -0
  22. package/hdri/night-2k.hdr +0 -0
  23. package/hdri/studio_small_08_1k.hdr +0 -0
  24. package/package.json +14 -10
  25. package/skills/agent-files/AGENTS.md +3 -0
  26. package/skills/aura3d-animation-studio/SKILL.md +153 -0
  27. package/skills/aura3d-animation-studio/references/shot-looks.md +26 -0
  28. package/skills/aura3d-art-direction/SKILL.md +84 -0
  29. package/skills/aura3d-art-direction/references/failure-gallery.md +64 -0
  30. package/skills/aura3d-art-direction/references/framing-and-shots.md +45 -0
  31. package/skills/aura3d-art-direction/references/look-recipes.md +32 -0
  32. package/skills/aura3d-art-direction/references/quality-bar.md +103 -0
  33. package/skills/aura3d-art-direction/references/reference-frames.md +82 -0
  34. package/skills/aura3d-assets/SKILL.md +132 -0
  35. package/skills/aura3d-assets/references/preview-under-look.md +26 -0
  36. package/skills/aura3d-browser-game/SKILL.md +127 -0
  37. package/skills/aura3d-browser-game/references/genre-looks.md +52 -0
  38. package/skills/aura3d-character-animation/SKILL.md +129 -0
  39. package/skills/aura3d-character-animation/references/stage-and-lighting.md +31 -0
  40. package/skills/aura3d-core/SKILL.md +121 -0
  41. package/skills/aura3d-core/references/boundaries.md +91 -0
  42. package/skills/aura3d-evidence-review/SKILL.md +106 -0
  43. package/skills/aura3d-game-art/SKILL.md +118 -0
  44. package/skills/aura3d-game-art/references/sheet-check.mjs +58 -0
  45. package/skills/aura3d-game-art/references/sprite-palettes.md +25 -0
  46. package/skills/aura3d-materials-environments/SKILL.md +139 -0
  47. package/skills/aura3d-performance/SKILL.md +97 -0
  48. package/skills/aura3d-retexture/SKILL.md +115 -0
  49. package/skills/aura3d-retexture/references/geometry-hash.mjs +89 -0
  50. package/skills/aura3d-scene-authoring/SKILL.md +132 -0
  51. package/skills/aura3d-scene-authoring/references/look-by-family.md +28 -0
  52. package/skills/aura3d-threejs-migration/SKILL.md +125 -0
  53. package/skills/aura3d-threejs-migration/references/look-mapping.md +27 -0
  54. package/skills/llms.txt +117 -0
  55. package/skills/manifest.json +163 -0
  56. package/skills/meshy-cli/SKILL.md +133 -0
  57. package/templates/animation-channel/.claude/CLAUDE.md +3 -0
  58. package/templates/animation-channel/.github/workflows/aura3d-lookdev.yml +67 -0
  59. package/templates/animation-channel/AGENTS.md +3 -0
  60. package/templates/animation-channel/package.json +4 -3
  61. package/templates/animation-channel/src/main.ts +5 -6
  62. package/templates/animation-channel/src/render-plan.ts +5 -3
  63. package/templates/animation-channel/tests/look-floor.ts +270 -0
  64. package/templates/animation-channel/tests/route-health.spec.ts +13 -0
  65. package/templates/animation-channel/tests/screenshot.spec.ts +10 -5
  66. package/templates/animation-channel/vite.config.ts +7 -0
  67. package/templates/animation-studio/.claude/CLAUDE.md +3 -0
  68. package/templates/animation-studio/.github/workflows/aura3d-lookdev.yml +67 -0
  69. package/templates/animation-studio/AGENTS.md +2 -70
  70. package/templates/animation-studio/package.json +5 -4
  71. package/templates/animation-studio/public/aura-assets/luma.thumb.svg +1 -1
  72. package/templates/animation-studio/public/aura-assets/miko.thumb.svg +1 -1
  73. package/templates/{falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg → animation-studio/public/aura-assets/moonGarden.thumb.svg} +1 -1
  74. package/templates/animation-studio/scripts/build-clip-library.ts +6 -5
  75. package/templates/animation-studio/scripts/resolve-asset.ts +7 -4
  76. package/templates/animation-studio/src/asset-contract.ts +19 -0
  77. package/templates/animation-studio/src/aura-assets.ts +651 -54
  78. package/templates/animation-studio/src/scene-player.ts +6 -6
  79. package/templates/animation-studio/src/set-templates.ts +13 -9
  80. package/templates/animation-studio/tests/look-floor.ts +270 -0
  81. package/templates/animation-studio/tests/route-health.spec.ts +2 -2
  82. package/templates/animation-studio/tests/screenshot.spec.ts +18 -10
  83. package/templates/animation-studio/vite.config.ts +2 -0
  84. package/templates/architecture-interior/.claude/CLAUDE.md +3 -0
  85. package/templates/architecture-interior/.github/workflows/aura3d-lookdev.yml +67 -0
  86. package/templates/architecture-interior/AGENTS.md +3 -0
  87. package/templates/architecture-interior/README.md +16 -0
  88. package/templates/architecture-interior/aura.assets.json +24 -0
  89. package/templates/architecture-interior/package.json +24 -0
  90. package/templates/architecture-interior/public/aura-assets/kenneyModularDungeonRoomSmall.a7e74a5f.glb +0 -0
  91. package/templates/architecture-interior/src/aura-assets.ts +16 -0
  92. package/templates/architecture-interior/src/main.ts +23 -0
  93. package/templates/architecture-interior/tests/look-floor.ts +270 -0
  94. package/templates/{three-compat-architecture-interior → architecture-interior}/tests/route-health.spec.ts +3 -3
  95. package/templates/architecture-interior/tests/screenshot.spec.ts +21 -0
  96. package/templates/architecture-interior/vite.config.ts +7 -0
  97. package/templates/arena-shooter/.claude/CLAUDE.md +3 -0
  98. package/templates/arena-shooter/.github/workflows/aura3d-lookdev.yml +67 -0
  99. package/templates/arena-shooter/AGENTS.md +3 -0
  100. package/templates/arena-shooter/README.md +45 -0
  101. package/templates/arena-shooter/aura.assets.json +62 -0
  102. package/templates/arena-shooter/index.html +32 -0
  103. package/templates/arena-shooter/package.json +23 -0
  104. package/templates/arena-shooter/playwright.config.ts +15 -0
  105. package/templates/arena-shooter/public/aura-assets/craft_miner.de323205.glb +0 -0
  106. package/templates/arena-shooter/public/aura-assets/craft_racer.bce8cb3d.glb +0 -0
  107. package/templates/arena-shooter/public/aura-assets/meteor_detailed.f30379ab.glb +0 -0
  108. package/templates/arena-shooter/src/aura-assets.ts +56 -0
  109. package/templates/arena-shooter/src/main.ts +616 -0
  110. package/templates/arena-shooter/tests/look-floor.ts +270 -0
  111. package/templates/arena-shooter/tests/playable.spec.ts +56 -0
  112. package/templates/arena-shooter/tests/route-health.spec.ts +87 -0
  113. package/templates/arena-shooter/tests/screenshot.spec.ts +22 -0
  114. package/templates/arena-shooter/tsconfig.json +15 -0
  115. package/templates/arena-shooter/vite.config.ts +7 -0
  116. package/templates/asset-inspector/.claude/CLAUDE.md +3 -0
  117. package/templates/asset-inspector/.github/workflows/aura3d-lookdev.yml +67 -0
  118. package/templates/asset-inspector/AGENTS.md +3 -0
  119. package/templates/asset-inspector/README.md +13 -0
  120. package/templates/asset-inspector/aura.assets.json +24 -0
  121. package/templates/{three-compat-custom-threejs-migration → asset-inspector}/package.json +6 -4
  122. package/templates/asset-inspector/public/aura-assets/kenneyCarKitSedanSports.2889c428.glb +0 -0
  123. package/templates/asset-inspector/src/aura-assets.ts +16 -0
  124. package/templates/asset-inspector/src/main.ts +23 -0
  125. package/templates/asset-inspector/tests/look-floor.ts +270 -0
  126. package/templates/{three-compat-character-viewer → asset-inspector}/tests/route-health.spec.ts +3 -3
  127. package/templates/asset-inspector/tests/screenshot.spec.ts +21 -0
  128. package/templates/asset-inspector/vite.config.ts +7 -0
  129. package/templates/character-controller/.claude/CLAUDE.md +3 -0
  130. package/templates/character-controller/.github/workflows/aura3d-lookdev.yml +67 -0
  131. package/templates/character-controller/AGENTS.md +3 -0
  132. package/templates/character-controller/README.md +7 -1
  133. package/templates/character-controller/aura.assets.json +29 -22
  134. package/templates/character-controller/index.html +4 -5
  135. package/templates/character-controller/package.json +6 -5
  136. package/templates/character-controller/playwright.config.ts +1 -0
  137. package/templates/character-controller/public/aura-assets/auraclash-player-rig.glb +0 -0
  138. package/templates/character-controller/src/aura-assets.ts +25 -7
  139. package/templates/character-controller/src/main.ts +190 -47
  140. package/templates/character-controller/tests/certified-rig.spec.ts +2 -2
  141. package/templates/character-controller/tests/look-floor.ts +270 -0
  142. package/templates/character-controller/tests/route-health.spec.ts +49 -4
  143. package/templates/character-controller/tests/screenshot.spec.ts +17 -5
  144. package/templates/character-controller/vite.config.ts +7 -0
  145. package/templates/character-viewer/.claude/CLAUDE.md +3 -0
  146. package/templates/character-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  147. package/templates/character-viewer/AGENTS.md +3 -0
  148. package/templates/character-viewer/README.md +13 -0
  149. package/templates/character-viewer/aura.assets.json +24 -0
  150. package/templates/{three-compat-asset-inspector → character-viewer}/package.json +6 -4
  151. package/templates/character-viewer/public/aura-assets/kenneyMiniForestCharacterArcher.0fd0f92e.glb +0 -0
  152. package/templates/character-viewer/src/aura-assets.ts +16 -0
  153. package/templates/character-viewer/src/main.ts +21 -0
  154. package/templates/character-viewer/tests/look-floor.ts +270 -0
  155. package/templates/{three-compat-custom-threejs-migration → character-viewer}/tests/route-health.spec.ts +3 -3
  156. package/templates/character-viewer/tests/screenshot.spec.ts +21 -0
  157. package/templates/character-viewer/vite.config.ts +7 -0
  158. package/templates/cinematic-scene/.claude/CLAUDE.md +3 -0
  159. package/templates/cinematic-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  160. package/templates/cinematic-scene/AGENTS.md +3 -0
  161. package/templates/cinematic-scene/README.md +11 -2
  162. package/templates/cinematic-scene/package.json +4 -3
  163. package/templates/cinematic-scene/src/main.ts +46 -11
  164. package/templates/cinematic-scene/tests/look-floor.ts +270 -0
  165. package/templates/cinematic-scene/tests/route-health.spec.ts +31 -3
  166. package/templates/cinematic-scene/tests/screenshot.spec.ts +34 -45
  167. package/templates/cinematic-scene/vite.config.ts +7 -0
  168. package/templates/custom-scene/.claude/CLAUDE.md +3 -0
  169. package/templates/custom-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  170. package/templates/custom-scene/AGENTS.md +3 -0
  171. package/templates/custom-scene/README.md +13 -0
  172. package/templates/custom-scene/aura.assets.json +24 -0
  173. package/templates/{three-compat-character-viewer → custom-scene}/package.json +6 -4
  174. package/templates/custom-scene/public/aura-assets/kenneyPirateKitShipMedium.279ea019.glb +0 -0
  175. package/templates/custom-scene/src/aura-assets.ts +16 -0
  176. package/templates/custom-scene/src/main.ts +23 -0
  177. package/templates/custom-scene/tests/look-floor.ts +270 -0
  178. package/templates/{three-compat-asset-inspector → custom-scene}/tests/route-health.spec.ts +3 -3
  179. package/templates/custom-scene/tests/screenshot.spec.ts +21 -0
  180. package/templates/custom-scene/vite.config.ts +7 -0
  181. package/templates/episode-builder/.claude/CLAUDE.md +3 -0
  182. package/templates/episode-builder/.github/workflows/aura3d-lookdev.yml +67 -0
  183. package/templates/episode-builder/AGENTS.md +3 -0
  184. package/templates/episode-builder/package.json +4 -3
  185. package/templates/episode-builder/src/main.ts +5 -3
  186. package/templates/episode-builder/src/render-plan.ts +5 -3
  187. package/templates/episode-builder/tests/look-floor.ts +270 -0
  188. package/templates/episode-builder/tests/route-health.spec.ts +11 -0
  189. package/templates/episode-builder/tests/screenshot.spec.ts +10 -4
  190. package/templates/episode-builder/vite.config.ts +7 -0
  191. package/templates/falling-blocks-starter/.claude/CLAUDE.md +3 -0
  192. package/templates/falling-blocks-starter/.github/workflows/aura3d-lookdev.yml +67 -0
  193. package/templates/falling-blocks-starter/AGENTS.md +3 -0
  194. package/templates/falling-blocks-starter/README.md +10 -1
  195. package/templates/falling-blocks-starter/aura.assets.json +40 -23
  196. package/templates/falling-blocks-starter/package.json +4 -3
  197. package/templates/falling-blocks-starter/public/aura-assets/quaternius-cube-crate.glb +0 -0
  198. package/templates/falling-blocks-starter/public/aura-assets/quaternius-wall-modular.glb +0 -0
  199. package/templates/falling-blocks-starter/src/aura-assets.ts +33 -10
  200. package/templates/falling-blocks-starter/src/main.ts +317 -96
  201. package/templates/falling-blocks-starter/tests/look-floor.ts +270 -0
  202. package/templates/falling-blocks-starter/tests/playable.spec.ts +51 -18
  203. package/templates/falling-blocks-starter/tests/route-health.spec.ts +17 -7
  204. package/templates/falling-blocks-starter/tests/screenshot.spec.ts +21 -0
  205. package/templates/falling-blocks-starter/vite.config.ts +7 -0
  206. package/templates/fighting-game/.claude/CLAUDE.md +3 -0
  207. package/templates/fighting-game/.github/workflows/aura3d-lookdev.yml +67 -0
  208. package/templates/fighting-game/AGENTS.md +3 -0
  209. package/templates/fighting-game/README.md +2 -0
  210. package/templates/fighting-game/aura.assets.json +56 -46
  211. package/templates/fighting-game/package.json +5 -3
  212. package/templates/fighting-game/public/aura-assets/auraclash-player-rig.glb +0 -0
  213. package/templates/fighting-game/public/aura-assets/auraclash-rival-rig.glb +0 -0
  214. package/templates/fighting-game/src/aura-assets.ts +54 -20
  215. package/templates/fighting-game/src/game/fighters.ts +57 -12
  216. package/templates/fighting-game/src/main.ts +135 -53
  217. package/templates/fighting-game/tests/certified-rig.spec.ts +1 -1
  218. package/templates/fighting-game/tests/gameplay-smoke.spec.ts +25 -33
  219. package/templates/fighting-game/tests/look-floor.ts +270 -0
  220. package/templates/fighting-game/tests/route-health.spec.ts +23 -4
  221. package/templates/fighting-game/tests/screenshot.spec.ts +10 -4
  222. package/templates/fighting-game/vite.config.ts +7 -0
  223. package/templates/large-scene/.claude/CLAUDE.md +3 -0
  224. package/templates/large-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  225. package/templates/large-scene/AGENTS.md +3 -0
  226. package/templates/large-scene/README.md +13 -0
  227. package/templates/large-scene/aura.assets.json +40 -0
  228. package/templates/{three-compat-architecture-interior → large-scene}/package.json +6 -4
  229. package/templates/large-scene/public/aura-assets/kenneyMiniForestRocksHigh.226728a0.glb +0 -0
  230. package/templates/large-scene/public/aura-assets/kenneyMiniForestTree.0075d505.glb +0 -0
  231. package/templates/large-scene/src/aura-assets.ts +28 -0
  232. package/templates/large-scene/src/main.ts +43 -0
  233. package/templates/large-scene/tests/look-floor.ts +270 -0
  234. package/templates/large-scene/tests/route-health.spec.ts +16 -0
  235. package/templates/large-scene/tests/screenshot.spec.ts +21 -0
  236. package/templates/large-scene/vite.config.ts +7 -0
  237. package/templates/material-authoring/.claude/CLAUDE.md +3 -0
  238. package/templates/material-authoring/.github/workflows/aura3d-lookdev.yml +67 -0
  239. package/templates/material-authoring/AGENTS.md +3 -0
  240. package/templates/material-authoring/README.md +15 -0
  241. package/templates/material-authoring/aura.assets.json +24 -0
  242. package/templates/material-authoring/package.json +24 -0
  243. package/templates/material-authoring/public/aura-assets/kenneyCarKitSedanSports.2889c428.glb +0 -0
  244. package/templates/material-authoring/src/aura-assets.ts +16 -0
  245. package/templates/material-authoring/src/main.ts +78 -0
  246. package/templates/material-authoring/tests/look-floor.ts +270 -0
  247. package/templates/material-authoring/tests/route-health.spec.ts +16 -0
  248. package/templates/material-authoring/tests/screenshot.spec.ts +21 -0
  249. package/templates/material-authoring/vite.config.ts +7 -0
  250. package/templates/mini-game/.claude/CLAUDE.md +3 -0
  251. package/templates/mini-game/.github/workflows/aura3d-lookdev.yml +67 -0
  252. package/templates/mini-game/AGENTS.md +3 -0
  253. package/templates/mini-game/README.md +9 -6
  254. package/templates/mini-game/aura.assets.json +177 -36
  255. package/templates/mini-game/package.json +4 -2
  256. package/templates/mini-game/public/aura-assets/kaykit-barbarian.glb +0 -0
  257. package/templates/mini-game/public/aura-assets/kenneyPlatformerKitKey.2e930961.glb +0 -0
  258. package/templates/mini-game/public/aura-assets/kenneyPlatformerKitStar.a102a1b9.glb +0 -0
  259. package/templates/mini-game/public/aura-assets/quaternius-crate.glb +0 -0
  260. package/templates/mini-game/public/aura-assets/quaternius-cube-crate.glb +0 -0
  261. package/templates/mini-game/src/aura-assets.ts +140 -32
  262. package/templates/mini-game/src/main.ts +204 -71
  263. package/templates/mini-game/tests/certified-rig.spec.ts +2 -2
  264. package/templates/mini-game/tests/look-floor.ts +270 -0
  265. package/templates/mini-game/tests/playable.spec.ts +23 -2
  266. package/templates/mini-game/tests/route-health.spec.ts +2 -2
  267. package/templates/mini-game/tests/screenshot.spec.ts +44 -53
  268. package/templates/mini-game/vite.config.ts +7 -0
  269. package/templates/postprocess-scene/.claude/CLAUDE.md +3 -0
  270. package/templates/postprocess-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  271. package/templates/postprocess-scene/AGENTS.md +3 -0
  272. package/templates/postprocess-scene/README.md +17 -0
  273. package/templates/postprocess-scene/aura.assets.json +38 -0
  274. package/templates/postprocess-scene/package.json +24 -0
  275. package/templates/postprocess-scene/public/aura-assets/showcaseLuma.ef389217.glb +0 -0
  276. package/templates/postprocess-scene/public/aura-assets/showcaseMiko.fcdc2b75.glb +0 -0
  277. package/templates/postprocess-scene/src/aura-assets.ts +28 -0
  278. package/templates/postprocess-scene/src/main.ts +29 -0
  279. package/templates/postprocess-scene/tests/look-floor.ts +270 -0
  280. package/templates/postprocess-scene/tests/route-health.spec.ts +16 -0
  281. package/templates/postprocess-scene/tests/screenshot.spec.ts +21 -0
  282. package/templates/postprocess-scene/vite.config.ts +7 -0
  283. package/templates/premium-product-viewer/.claude/CLAUDE.md +3 -0
  284. package/templates/premium-product-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  285. package/templates/premium-product-viewer/AGENTS.md +3 -0
  286. package/templates/premium-product-viewer/README.md +18 -0
  287. package/templates/premium-product-viewer/aura.assets.json +24 -0
  288. package/templates/premium-product-viewer/package.json +24 -0
  289. package/templates/premium-product-viewer/public/aura-assets/khronosToyCar.01a60862.glb +0 -0
  290. package/templates/premium-product-viewer/src/aura-assets.ts +16 -0
  291. package/templates/premium-product-viewer/src/main.ts +33 -0
  292. package/templates/premium-product-viewer/tests/look-floor.ts +270 -0
  293. package/templates/premium-product-viewer/tests/route-health.spec.ts +16 -0
  294. package/templates/premium-product-viewer/tests/screenshot.spec.ts +21 -0
  295. package/templates/premium-product-viewer/vite.config.ts +7 -0
  296. package/templates/product-viewer/.claude/CLAUDE.md +3 -0
  297. package/templates/product-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  298. package/templates/product-viewer/AGENTS.md +3 -0
  299. package/templates/product-viewer/README.md +10 -8
  300. package/templates/product-viewer/package.json +4 -2
  301. package/templates/product-viewer/src/aura-assets.ts +1 -1
  302. package/templates/product-viewer/src/main.ts +78 -19
  303. package/templates/product-viewer/tests/look-floor.ts +270 -0
  304. package/templates/product-viewer/tests/route-health.spec.ts +19 -1
  305. package/templates/product-viewer/tests/screenshot.spec.ts +35 -41
  306. package/templates/product-viewer/vite.config.ts +7 -0
  307. package/templates/prompt-animation-channel/.claude/CLAUDE.md +3 -0
  308. package/templates/prompt-animation-channel/.github/workflows/aura3d-lookdev.yml +67 -0
  309. package/templates/prompt-animation-channel/AGENTS.md +3 -0
  310. package/templates/prompt-animation-channel/package.json +4 -3
  311. package/templates/prompt-animation-channel/src/main.ts +5 -3
  312. package/templates/prompt-animation-channel/src/render-plan.ts +5 -3
  313. package/templates/prompt-animation-channel/tests/look-floor.ts +270 -0
  314. package/templates/prompt-animation-channel/tests/route-health.spec.ts +12 -1
  315. package/templates/prompt-animation-channel/tests/screenshot.spec.ts +10 -4
  316. package/templates/prompt-animation-channel/tests/storyboard-playback.spec.ts +1 -1
  317. package/templates/prompt-animation-channel/vite.config.ts +7 -0
  318. package/templates/racing-starter/.claude/CLAUDE.md +3 -0
  319. package/templates/racing-starter/.github/workflows/aura3d-lookdev.yml +67 -0
  320. package/templates/racing-starter/AGENTS.md +3 -0
  321. package/templates/racing-starter/README.md +8 -6
  322. package/templates/racing-starter/package.json +4 -3
  323. package/templates/racing-starter/src/main.ts +169 -123
  324. package/templates/racing-starter/tests/geometry-certification.json +34 -0
  325. package/templates/racing-starter/tests/look-floor.ts +270 -0
  326. package/templates/racing-starter/tests/playable.spec.ts +63 -80
  327. package/templates/racing-starter/tests/route-health.spec.ts +14 -12
  328. package/templates/racing-starter/tests/screenshot.spec.ts +21 -0
  329. package/templates/racing-starter/vite.config.ts +7 -0
  330. package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
  331. package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +0 -1
  332. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
  333. package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.252b3a16.glb +0 -0
  334. package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.thumb.svg +0 -1
  335. package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
  336. package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +0 -1
  337. package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb +0 -0
  338. package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.thumb.svg +0 -1
  339. package/templates/three-compat-architecture-interior/aura.assets.json +0 -7
  340. package/templates/three-compat-architecture-interior/src/aura-assets.ts +0 -3
  341. package/templates/three-compat-architecture-interior/src/main.ts +0 -27
  342. package/templates/three-compat-architecture-interior/tests/screenshot.spec.ts +0 -15
  343. package/templates/three-compat-asset-inspector/aura.assets.json +0 -7
  344. package/templates/three-compat-asset-inspector/src/aura-assets.ts +0 -3
  345. package/templates/three-compat-asset-inspector/src/main.ts +0 -17
  346. package/templates/three-compat-asset-inspector/tests/screenshot.spec.ts +0 -15
  347. package/templates/three-compat-character-viewer/aura.assets.json +0 -7
  348. package/templates/three-compat-character-viewer/src/aura-assets.ts +0 -3
  349. package/templates/three-compat-character-viewer/src/main.ts +0 -17
  350. package/templates/three-compat-character-viewer/tests/screenshot.spec.ts +0 -15
  351. package/templates/three-compat-custom-threejs-migration/aura.assets.json +0 -7
  352. package/templates/three-compat-custom-threejs-migration/src/aura-assets.ts +0 -3
  353. package/templates/three-compat-custom-threejs-migration/src/main.ts +0 -16
  354. package/templates/three-compat-custom-threejs-migration/tests/screenshot.spec.ts +0 -15
  355. package/templates/three-compat-large-scene/aura.assets.json +0 -7
  356. package/templates/three-compat-large-scene/package.json +0 -22
  357. package/templates/three-compat-large-scene/src/aura-assets.ts +0 -3
  358. package/templates/three-compat-large-scene/src/main.ts +0 -31
  359. package/templates/three-compat-large-scene/tests/route-health.spec.ts +0 -16
  360. package/templates/three-compat-large-scene/tests/screenshot.spec.ts +0 -15
  361. package/templates/three-compat-material-authoring/aura.assets.json +0 -7
  362. package/templates/three-compat-material-authoring/package.json +0 -22
  363. package/templates/three-compat-material-authoring/src/aura-assets.ts +0 -3
  364. package/templates/three-compat-material-authoring/src/main.ts +0 -33
  365. package/templates/three-compat-material-authoring/tests/route-health.spec.ts +0 -16
  366. package/templates/three-compat-material-authoring/tests/screenshot.spec.ts +0 -15
  367. package/templates/three-compat-postprocess-scene/aura.assets.json +0 -7
  368. package/templates/three-compat-postprocess-scene/package.json +0 -22
  369. package/templates/three-compat-postprocess-scene/src/aura-assets.ts +0 -3
  370. package/templates/three-compat-postprocess-scene/src/main.ts +0 -17
  371. package/templates/three-compat-postprocess-scene/tests/route-health.spec.ts +0 -16
  372. package/templates/three-compat-postprocess-scene/tests/screenshot.spec.ts +0 -15
  373. package/templates/three-compat-premium-product-viewer/aura.assets.json +0 -7
  374. package/templates/three-compat-premium-product-viewer/package.json +0 -22
  375. package/templates/three-compat-premium-product-viewer/src/aura-assets.ts +0 -3
  376. package/templates/three-compat-premium-product-viewer/src/main.ts +0 -18
  377. package/templates/three-compat-premium-product-viewer/tests/route-health.spec.ts +0 -16
  378. package/templates/three-compat-premium-product-viewer/tests/screenshot.spec.ts +0 -15
  379. /package/templates/{three-compat-architecture-interior → architecture-interior}/index.html +0 -0
  380. /package/templates/{three-compat-architecture-interior → architecture-interior}/playwright.config.ts +0 -0
  381. /package/templates/{three-compat-architecture-interior → architecture-interior}/tsconfig.json +0 -0
  382. /package/templates/{three-compat-asset-inspector → asset-inspector}/index.html +0 -0
  383. /package/templates/{three-compat-asset-inspector → asset-inspector}/playwright.config.ts +0 -0
  384. /package/templates/{three-compat-asset-inspector → asset-inspector}/tsconfig.json +0 -0
  385. /package/templates/{three-compat-character-viewer → character-viewer}/index.html +0 -0
  386. /package/templates/{three-compat-character-viewer → character-viewer}/playwright.config.ts +0 -0
  387. /package/templates/{three-compat-character-viewer → character-viewer}/tsconfig.json +0 -0
  388. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/index.html +0 -0
  389. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/playwright.config.ts +0 -0
  390. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/tsconfig.json +0 -0
  391. /package/templates/{three-compat-large-scene → large-scene}/index.html +0 -0
  392. /package/templates/{three-compat-large-scene → large-scene}/playwright.config.ts +0 -0
  393. /package/templates/{three-compat-large-scene → large-scene}/tsconfig.json +0 -0
  394. /package/templates/{three-compat-material-authoring → material-authoring}/index.html +0 -0
  395. /package/templates/{three-compat-material-authoring → material-authoring}/playwright.config.ts +0 -0
  396. /package/templates/{three-compat-material-authoring → material-authoring}/tsconfig.json +0 -0
  397. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/index.html +0 -0
  398. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/playwright.config.ts +0 -0
  399. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/tsconfig.json +0 -0
  400. /package/templates/{three-compat-premium-product-viewer → premium-product-viewer}/index.html +0 -0
  401. /package/templates/{three-compat-premium-product-viewer → premium-product-viewer}/playwright.config.ts +0 -0
  402. /package/templates/{three-compat-premium-product-viewer → premium-product-viewer}/tsconfig.json +0 -0
@@ -1,7 +1,21 @@
1
- // Character controller preview: keyboard input -> kinematic speed -> @aura3d/animation locomotion
2
- // kit (idle/walk/run blended by speed) -> live proof object. Hold a direction key (W/A/S/D or
3
- // arrows) to walk; hold Shift to run. A minimal Aura3D stage renders beneath the HUD.
4
- import { camera, createAuraApp, lights, material, model, primitives, scene } from "@aura3d/engine";
1
+ // Character controller: keyboard input -> kinematic speed -> locomotion state
2
+ // -> the E1 humanoid rig driven through its certified clip (playback speed
3
+ // tracks locomotion) -> shoulder camera + DOM HUD + live proof object.
4
+ // PRD-09: mounted via the shared runtime — createGame owns mount/lifecycle,
5
+ // the §7.7 sci-fi-telemetry HUD theme, the §6.11 dpad-2btn touch preset,
6
+ // game-sfx-core cues, and the juice event map.
7
+ import {
8
+ camera,
9
+ game,
10
+ gameFeel,
11
+ looks,
12
+ material,
13
+ model,
14
+ primitives,
15
+ scene,
16
+ type AuraRuntimeNodeHandle
17
+ } from "@aura3d/engine";
18
+ import { createGame, sfxUrl } from "@aura3d/engine/game";
5
19
  import { createPerformanceGovernor, createTopDownGameRenderPreset } from "@aura3d/engine/production-runtime";
6
20
  import { createLocomotionKit } from "@aura3d/animation";
7
21
  import { assets } from "./aura-assets.js";
@@ -13,7 +27,11 @@ interface CharacterControllerProof {
13
27
  moving: boolean;
14
28
  running: boolean;
15
29
  clipWeights: ReadonlyArray<{ clip: string; weight: number }>;
16
- hero: { readonly assetId: string; readonly url: string; readonly clip: string };
30
+ hero: { readonly assetId: string; readonly url: string; readonly clip: string; readonly metres: readonly [number, number, number] };
31
+ position: readonly [number, number, number];
32
+ yawDegrees: number;
33
+ look: { readonly id: string; readonly category: string };
34
+ camera: { readonly rig: string; readonly presented: boolean };
17
35
  debugDraw: boolean;
18
36
  governor: { readonly resolutionScale: number; readonly particleScale: number; readonly lodBias: number; readonly shadowSize: number };
19
37
  }
@@ -24,80 +42,205 @@ declare global {
24
42
  }
25
43
  }
26
44
 
45
+ const LOOK_ID = "outdoor-day" as const;
27
46
  const tuning = defaultCharacterControllerTuning;
28
47
  const kit = createLocomotionKit({ idleClip: "Idle", walkClip: "Walk", runClip: "Run", walkSpeed: tuning.walkSpeed, runSpeed: tuning.runSpeed });
29
48
 
49
+ // auraclash-player-rig is authored in metres (1.79 m tall); the derived
50
+ // scale keeps the hero at the 1.62 m height the level was tuned for.
51
+ const HERO_SCALE = 1.62 / assets.auraclashPlayerRig.bounds[1];
52
+ // Locomotion is played by pacing the rig's walk loop to the movement speed
53
+ // (0 = posed idle frame, >1 = run-paced walk).
54
+ const CERTIFIED_CLIP = "Walk_Loop";
55
+ const CLIP_SPEED: Record<"idle" | "walk" | "run", number> = {
56
+ idle: 0,
57
+ walk: 1,
58
+ run: Number((tuning.runSpeed / tuning.walkSpeed).toFixed(2))
59
+ };
60
+
30
61
  const moveKeys = new Set(["KeyW", "KeyA", "KeyS", "KeyD", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"]);
31
62
  const held = new Set<string>();
32
63
  window.addEventListener("keydown", (event) => held.add(event.code));
33
64
  window.addEventListener("keyup", (event) => held.delete(event.code));
34
65
 
35
- // J3 scaffold surface: the engine perf governor watches frame cost against the
36
- // top-down preset budget, and the debug key gates overlay detail in the HUD.
37
66
  const governorBudget = createTopDownGameRenderPreset().performanceBudget;
38
67
  let governor = createPerformanceGovernor("conservative");
39
68
  let debugDraw = false;
40
69
 
41
- const root = document.getElementById("app") ?? document.body;
42
- const hud = document.createElement("pre");
43
- hud.id = "character-controller-hud";
44
- hud.style.cssText = "font:14px monospace;padding:16px;color:#6cf;background:#0c0f16;min-height:140px";
45
- root.appendChild(hud);
46
-
47
- // Certified hero stage: the E1 humanoid-a rig (showcaseWalkAnimatedGirl, "Take
48
- // 001") plays its certified clip on a floor so the route renders a real Aura3D
49
- // scene alongside the locomotion HUD proof.
50
- const stage = document.createElement("div");
51
- stage.id = "character-controller-stage";
52
- stage.style.cssText = "height:320px";
53
- root.appendChild(stage);
54
- createAuraApp(stage, {
55
- scene: scene()
56
- .background("#0c0f16")
57
- .camera(camera.orbit({ target: [0, 0.8, 0], distance: 3.2 }))
58
- .add(primitives.box({ name: "floor", size: [6, 0.1, 6], position: [0, -0.05, 0], material: material.pbr({ color: "#151b27", roughness: 0.9 }), receiveShadow: true }))
59
- .add(model(assets.showcaseWalkAnimatedGirl, { name: "certified hero humanoid-a" }).position(0, 0, 0).animate({ clip: "Take 001", loop: true }))
60
- .add(lights.ambient({ intensity: 0.35 }))
61
- .add(lights.directional({ name: "key", position: [3, 5, 2], intensity: 1.4 }))
70
+ // Full-bleed canvas: the app mounts straight into #app, which flexes to fill
71
+ // the viewport (see index.html).
72
+ const controllerGame = createGame({
73
+ id: "character-controller",
74
+ target: "#app",
75
+ autoStart: true,
76
+ input: {
77
+ actions: {
78
+ left: ["KeyA", "ArrowLeft"],
79
+ right: ["KeyD", "ArrowRight"],
80
+ run: ["ShiftLeft", "ShiftRight"],
81
+ debug: ["KeyT"]
82
+ },
83
+ bufferMs: 100
84
+ },
85
+ hud: { theme: "sci-fi-telemetry", widgets: [] },
86
+ touch: {
87
+ preset: "dpad-2btn",
88
+ bindings: { left: "left", right: "right", dash: "run", jump: "debug", attack: "debug" }
89
+ },
90
+ sound: {
91
+ cues: {
92
+ step: { id: "step", asset: { url: sfxUrl("footsteps.grass.00") }, volume: 0.4 },
93
+ "run-start": { id: "run-start", asset: { url: sfxUrl("vehicle.boost") }, volume: 0.4 },
94
+ "debug-toggle": { id: "debug-toggle", asset: { url: sfxUrl("ui.toggle.00") }, volume: 0.5 }
95
+ }
96
+ },
97
+ juice: {
98
+ "run-start": { punch: { fovDeg: 1.4, ms: 260 } },
99
+ "debug-toggle": { flash: { color: "#9fc7ff", peak: 0.12, ms: 120 } }
100
+ },
101
+ qualityRebuild: { flags: ["game"] },
102
+ evidence: {
103
+ schema: 1,
104
+ sections: { characterController: () => window.__AURA3D_CHARACTER_CONTROLLER_PROOF__ ?? { status: "unbound" } }
105
+ },
106
+ scene: () => scene()
107
+ .add(looks.preset(LOOK_ID))
108
+ .add(primitives.box({ name: "walkable ground", size: [30, 0.1, 30], position: [0, -0.05, 0], material: material.pbr({ color: "#4a6b3a", roughness: 0.92 }), receiveShadow: true }))
109
+ .add(primitives.box({ name: "distance marker north", size: [0.4, 1.4, 0.4], position: [0, 0.7, -6], material: material.pbr({ color: "#d97a3a", roughness: 0.6 }), castShadow: true }))
110
+ .add(primitives.box({ name: "distance marker east", size: [0.4, 1.4, 0.4], position: [5, 0.7, 0], material: material.pbr({ color: "#7fa3c2", roughness: 0.6 }), castShadow: true }))
111
+ .add(primitives.box({ name: "distance marker south-east", size: [0.4, 1.4, 0.4], position: [3.5, 0.7, 4.5], material: material.pbr({ color: "#b987d0", roughness: 0.6 }), castShadow: true }))
112
+ // C-10: a world wall for the shoulder rig's collision damper to pull in on.
113
+ .add(primitives.box({ name: "collision wall", size: [0.4, 2.4, 4], position: [2.4, 1.2, 4.2], material: material.pbr({ color: "#8a94a6", roughness: 0.85 }), castShadow: true }))
114
+ .add(
115
+ model(assets.auraclashPlayerRig, { name: "certified hero humanoid-a", castShadow: true })
116
+ .position(0, 0, 0)
117
+ .scale(HERO_SCALE)
118
+ .animate({ clip: CERTIFIED_CLIP, loop: true, speed: 0 })
119
+ .runtime(game.runtimeNode("hero", { tags: ["player"] }))
120
+ )
121
+ .camera(camera.perspective({ position: [0.55, 1.75, -2.8], target: [0, 1.35, 1.1], fov: 55 }))
62
122
  });
123
+ const app = controllerGame.app;
124
+ controllerGame.start();
125
+
126
+ const heroNode: AuraRuntimeNodeHandle = app.nodes.require("hero");
127
+ // F-08-1/C-10: the shoulder rig owns the presented camera and damps through
128
+ // world collision (flag-off mounts the no-op stub controller; the authored
129
+ // perspective spec stays the fallback). Right-half drag binds an orbit rig's
130
+ // pointer handler for look input (no-op while rigs resolve to stubs).
131
+ app.camera?.use(camera.rigs.shoulder({ target: "hero", collision: true }));
132
+ const orbit = camera.rigs.orbit({ target: "hero" });
133
+ const lookPad = document.createElement("div");
134
+ lookPad.id = "look-pad";
135
+ lookPad.setAttribute("style", "position:fixed;right:0;top:0;width:50%;height:100%;touch-action:none;");
136
+ document.body.appendChild(lookPad);
137
+ const orbitPointer = (orbit as { bindPointer?: (el: HTMLElement) => void }).bindPointer;
138
+ if (orbitPointer) orbitPointer.call(orbit, lookPad);
139
+ const feel = gameFeel.create({ app, time: app.time });
140
+ app.feel?.preset("platformer");
63
141
 
142
+ const hudRoot = createHud();
64
143
  let state: CharacterControllerState = { speed: 0 };
65
- let last = 0;
144
+ let position = { x: 0, y: 0, z: 0 };
145
+ let yaw = 0;
146
+ let locomotionState: "idle" | "walk" | "run" = "idle";
66
147
 
67
- function frame(time: number): void {
68
- const dt = last === 0 ? 1 / 60 : Math.min(0.05, (time - last) / 1000);
69
- last = time;
148
+ app.onFrame(({ dt }: { readonly dt: number }) => {
149
+ const seconds = Math.min(0.05, dt);
70
150
  if (held.has("KeyT")) {
71
151
  debugDraw = !debugDraw;
72
152
  held.delete("KeyT");
153
+ controllerGame.juice.fire("debug-toggle");
154
+ void controllerGame.sound?.cue("debug-toggle");
155
+ }
156
+
157
+ const dx = (held.has("KeyD") || held.has("ArrowRight") ? 1 : 0) - (held.has("KeyA") || held.has("ArrowLeft") ? 1 : 0);
158
+ const dz = (held.has("KeyS") || held.has("ArrowDown") ? 1 : 0) - (held.has("KeyW") || held.has("ArrowUp") ? 1 : 0);
159
+ const moving = [...held].some((code) => moveKeys.has(code));
160
+ const running = held.has("ShiftLeft") || held.has("ShiftRight");
161
+ const wasRunning = state.speed >= tuning.runSpeed - 0.01;
162
+ state = stepCharacterSpeed(state, { move: moving, run: running }, seconds, tuning);
163
+ const isRunning = state.speed >= tuning.runSpeed - 0.01;
164
+ if (isRunning && !wasRunning) {
165
+ controllerGame.juice.fire("run-start");
166
+ void controllerGame.sound?.cue("run-start");
167
+ }
168
+
169
+ if (dx !== 0 || dz !== 0) {
170
+ const length = Math.hypot(dx, dz);
171
+ const dirX = dx / length;
172
+ const dirZ = dz / length;
173
+ position = { x: position.x + dirX * state.speed * seconds, y: 0, z: position.z + dirZ * state.speed * seconds };
174
+ yaw = Math.atan2(dirX, dirZ);
175
+ }
176
+
177
+ const sample = kit.sample(state.speed, seconds);
178
+ const nextLocomotion = sample.state === "run" ? "run" : sample.state === "walk" ? "walk" : "idle";
179
+ heroNode.setPosition(position.x, position.y, position.z).setRotation(0, yaw, 0);
180
+ if (nextLocomotion !== locomotionState) {
181
+ locomotionState = nextLocomotion;
182
+ heroNode.setAnimation({ clip: CERTIFIED_CLIP, loop: true, speed: CLIP_SPEED[locomotionState] });
73
183
  }
74
- const move = [...held].some((code) => moveKeys.has(code));
75
- const run = held.has("ShiftLeft") || held.has("ShiftRight");
76
- state = stepCharacterSpeed(state, { move, run }, dt, tuning);
77
- const sample = kit.sample(state.speed, dt);
184
+
185
+ // The shoulder rig presents the camera; dt arrives scaled by app.time.
186
+ feel.update(dt * 1000);
187
+
78
188
  governor = governor.step(
79
- { fps: 1 / Math.max(1 / 240, dt), frameTimeMs: dt * 1000, draws: 0, tris: 0, particles: 0, shadowBytes: 0 },
189
+ { fps: 1 / Math.max(1 / 240, seconds), frameTimeMs: seconds * 1000, draws: 0, tris: 0, particles: 0, shadowBytes: 0 },
80
190
  governorBudget
81
191
  );
192
+
193
+ const presented = Boolean(app.camera?.presented());
82
194
  const proof: CharacterControllerProof = {
83
195
  speed: Number(state.speed.toFixed(3)),
84
196
  state: sample.state,
85
197
  moving: sample.moving,
86
198
  running: sample.running,
87
199
  clipWeights: sample.clipWeights.map((w) => ({ clip: w.clip, weight: Number(w.weight.toFixed(3)) })),
88
- hero: { assetId: assets.showcaseWalkAnimatedGirl.id, url: assets.showcaseWalkAnimatedGirl.url, clip: "Take 001" },
200
+ hero: {
201
+ assetId: assets.auraclashPlayerRig.id,
202
+ url: assets.auraclashPlayerRig.url,
203
+ clip: CERTIFIED_CLIP,
204
+ metres: assets.auraclashPlayerRig.bounds
205
+ },
206
+ position: [Number(position.x.toFixed(3)), position.y, Number(position.z.toFixed(3))],
207
+ yawDegrees: Number((yaw * 180 / Math.PI).toFixed(1)),
208
+ look: { id: LOOK_ID, category: "environment" },
209
+ camera: { rig: heroNode ? "shoulder:hero" : "", presented },
89
210
  debugDraw,
90
211
  governor: { ...governor.settings }
91
212
  };
92
213
  window.__AURA3D_CHARACTER_CONTROLLER_PROOF__ = proof;
93
- hud.textContent = [
94
- "Aura3D Character Controller — hold W/A/S/D (Shift = run, T = debug)",
95
- `speed: ${proof.speed}`,
96
- `state: ${proof.state}`,
97
- `clips: ${proof.clipWeights.map((w) => `${w.clip} ${w.weight}`).join(" ")}`,
98
- ...(debugDraw ? [`governor: res ${proof.governor.resolutionScale} lod ${proof.governor.lodBias} shadow ${proof.governor.shadowSize}`] : [])
99
- ].join("\n");
100
- requestAnimationFrame(frame);
214
+ renderHud(proof);
215
+ });
216
+
217
+ function createHud(): HTMLElement {
218
+ const root = document.createElement("aside");
219
+ root.id = "character-controller-hud";
220
+ root.style.cssText = [
221
+ "position:absolute",
222
+ "left:16px",
223
+ "top:16px",
224
+ "z-index:5",
225
+ "min-width:300px",
226
+ "font:600 13px/1.4 Inter, system-ui, sans-serif",
227
+ "color:#f2f8ff",
228
+ "background:rgba(16,26,34,0.72)",
229
+ "border:1px solid rgba(160,200,255,0.3)",
230
+ "border-radius:8px",
231
+ "padding:12px",
232
+ "pointer-events:none"
233
+ ].join(";");
234
+ document.body.append(root);
235
+ return root;
101
236
  }
102
237
 
103
- requestAnimationFrame(frame);
238
+ function renderHud(proof: CharacterControllerProof): void {
239
+ hudRoot.innerHTML = [
240
+ `<strong>Aura3D Character Controller</strong> — hold W/A/S/D (Shift = run, T = debug)`,
241
+ `<div>speed: ${proof.speed} | state: ${proof.state} | pos: ${proof.position[0]}, ${proof.position[2]}</div>`,
242
+ `<div>clips: ${proof.clipWeights.map((w) => `${w.clip} ${w.weight}`).join(" ")}</div>`,
243
+ `<div>look: ${proof.look.id} | camera: ${proof.camera.rig} presented=${proof.camera.presented}</div>`,
244
+ ...(proof.debugDraw ? [`<div>governor: res ${proof.governor.resolutionScale} lod ${proof.governor.lodBias} shadow ${proof.governor.shadowSize}</div>`] : [])
245
+ ].join("");
246
+ }
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { expect, test } from "@playwright/test";
5
5
 
6
- test.setTimeout(60_000);
6
+ test.setTimeout(240_000);
7
7
 
8
8
  /**
9
9
  * E1 certified-rig mount proof for the character-controller template.
@@ -38,7 +38,7 @@ test("character-controller hero mounts the certified humanoid-a rig", async ({ p
38
38
  await page.waitForFunction(
39
39
  () => Boolean((window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: unknown }).__AURA3D_CHARACTER_CONTROLLER_PROOF__),
40
40
  undefined,
41
- { timeout: 45_000 }
41
+ { timeout: 150_000 }
42
42
  );
43
43
  const hero = await page.evaluate(
44
44
  () =>
@@ -0,0 +1,270 @@
1
+ /**
2
+ * PRD-13 T3.12/T3.13 — the shared template look floor.
3
+ *
4
+ * Every create-aura3d template's `tests/screenshot.spec.ts` calls
5
+ * `assertTemplateLookFloor(page, ...)` after readiness: the same five checks
6
+ * everywhere (PRD §6.5/T3.12):
7
+ *
8
+ * 1. non-blank — sampled WebGL pixels are lit and varied (not a uniform
9
+ * clear/void frame);
10
+ * 2. `diagnostics().look.lint` carries no `severity === "error"` finding
11
+ * (empty when A3D_QR_LOOKS is off — the check never silently skips);
12
+ * 3. `appliedLook.environment.specularIntensity > 0` — an environment map
13
+ * actually reached the frame;
14
+ * 4. `appliedLook.shadows.strength >= 0.8` when the look declares a shadow
15
+ * caster (a null ShadowReport means the renderer produced no shadow
16
+ * evidence — recorded, not failed);
17
+ * 5. `appliedLook.pixelRatio >= min(devicePixelRatio, tierCap)` — the
18
+ * renderer used the display density up to the tier cap (2).
19
+ *
20
+ * …plus the optional §T3.12 subject check: the lit-pixel bounding box of the
21
+ * canvas' central region must sit within `tolerance` of the expected subject
22
+ * bounds (default ±10% of frame).
23
+ *
24
+ * Templates that mount through `createAuraApp` surface `appliedLook`/`look`
25
+ * via the global `__AURA3D_LIVE_APPS__` registry — no per-template wiring.
26
+ * `skipAppDiagnostics` covers the bespoke-renderer template (animation-studio),
27
+ * which reports its stage environment through its own readiness evidence.
28
+ */
29
+
30
+ import { expect, type Page } from "@playwright/test";
31
+
32
+ /** Normalized canvas-space bounds the lit subject mass is expected to occupy. */
33
+ export interface LookFloorSubjectBounds {
34
+ readonly x: number;
35
+ readonly y: number;
36
+ readonly width: number;
37
+ readonly height: number;
38
+ }
39
+
40
+ export interface LookFloorOptions {
41
+ /** Expected subject bounds in normalized canvas units. Omit to skip. */
42
+ readonly subject?: LookFloorSubjectBounds;
43
+ /** Fractional tolerance on each bound axis. Default 0.1 (±10%). */
44
+ readonly tolerance?: number;
45
+ /** Template has no `__AURA3D_LIVE_APPS__` app (bespoke renderer). */
46
+ readonly skipAppDiagnostics?: boolean;
47
+ }
48
+
49
+ export interface LookFloorReport {
50
+ readonly sampledPixels: number;
51
+ readonly brightPixels: number;
52
+ readonly uniqueBuckets: number;
53
+ readonly subjectBounds: LookFloorSubjectBounds | null;
54
+ readonly appliedLook: {
55
+ readonly specularIntensity: number;
56
+ readonly shadowStrength: number | null;
57
+ readonly pixelRatio: number;
58
+ readonly renderPath: string;
59
+ readonly background: string | null;
60
+ } | null;
61
+ readonly lookLintErrors: readonly string[];
62
+ readonly devicePixelRatio: number;
63
+ }
64
+
65
+ const MIN_BRIGHT_FRACTION = 0.02;
66
+ const MIN_UNIQUE_BUCKETS = 8;
67
+
68
+ export async function assertTemplateLookFloor(page: Page, options: LookFloorOptions = {}): Promise<LookFloorReport> {
69
+ const canvas = page.locator("canvas").first();
70
+ await expect(canvas).toBeVisible();
71
+
72
+ /*
73
+ * Deterministic frame before profiling: pause every live app, rewind its
74
+ * runtime clock and step a fixed 45 frames at 1/60 — through `stepAsync`,
75
+ * not the synchronous registry `settle()`. The production renderer mounts
76
+ * asynchronously, so a sync step loop renders nothing while the mount is in
77
+ * flight and never yields for it to finish (the failure mode on software-GL
78
+ * runners: blank frames, `environment.intensity` still NaN in diagnostics).
79
+ * Awaiting each step also lets pending environment/IBL/asset promises resolve
80
+ * between frames, and one final dt=0 step re-renders the same simulated
81
+ * instant once that work has applied.
82
+ */
83
+ const report = await page.evaluate(async (): Promise<LookFloorReport> => {
84
+ interface LiveApp {
85
+ pause?: () => void;
86
+ resetRuntimeClock?: () => void;
87
+ step?: (dt?: number) => void;
88
+ stepAsync?: (dt?: number) => Promise<unknown>;
89
+ diagnostics?: () => Record<string, unknown>;
90
+ }
91
+ const empty = (code: string): LookFloorReport => ({
92
+ sampledPixels: 0,
93
+ brightPixels: 0,
94
+ uniqueBuckets: 0,
95
+ subjectBounds: null,
96
+ appliedLook: null,
97
+ lookLintErrors: [code],
98
+ devicePixelRatio: globalThis.devicePixelRatio ?? 1
99
+ });
100
+ const registry = (globalThis as { __AURA3D_LIVE_APPS__?: { all?: () => readonly LiveApp[] } }).__AURA3D_LIVE_APPS__;
101
+ const apps = [...(registry?.all?.() ?? [])];
102
+ const stepApp = async (app: LiveApp, dt: number): Promise<void> => {
103
+ if (app.stepAsync) {
104
+ try {
105
+ await Promise.race([
106
+ app.stepAsync(dt),
107
+ new Promise((resolvePromise) => setTimeout(resolvePromise, 30_000))
108
+ ]);
109
+ return;
110
+ } catch {
111
+ // synchronous step fallback below
112
+ }
113
+ }
114
+ app.step?.(dt);
115
+ };
116
+ /*
117
+ * Wall-clock bound on the settle loop: on software-GL CI runners a single
118
+ * stepped render of a heavy scene costs several seconds, so an unbounded
119
+ * 45-step loop outlives the test budget and fails with a bare timeout and
120
+ * no report. Cap the stepping and always take the dt=0 settle frame — a
121
+ * partially settled scene still yields a readable floor report.
122
+ */
123
+ const STEP_WALLCLOCK_BUDGET_MS = 420_000;
124
+ const settleStartedAt = Date.now();
125
+ for (const app of apps) {
126
+ app.pause?.();
127
+ app.resetRuntimeClock?.();
128
+ for (let index = 0; index < 45; index += 1) {
129
+ if (Date.now() - settleStartedAt > STEP_WALLCLOCK_BUDGET_MS) break;
130
+ await stepApp(app, 1 / 60);
131
+ }
132
+ await stepApp(app, 0);
133
+ }
134
+ /*
135
+ * Renderers outside the liveApps registry (bespoke renderers, e.g.
136
+ * animation-studio's A3DRenderer) cannot be stepped. Two animation
137
+ * frames guarantee at least one full app render finished inside this
138
+ * evaluate task, so the readPixels below sees a real presented frame
139
+ * instead of a compositor-cleared buffer.
140
+ */
141
+ if (apps.length === 0) {
142
+ await new Promise<void>((resolve) => {
143
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
144
+ });
145
+ }
146
+
147
+ const diagnostics = apps[0]?.diagnostics?.() as
148
+ | { appliedLook?: { environment?: { specularIntensity?: number; background?: string }; shadows?: { strength?: number | null } | null; pixelRatio?: number; renderPath?: string }; look?: { lint?: readonly { severity?: string; code?: string }[] } }
149
+ | undefined;
150
+ const appliedLook = diagnostics?.appliedLook
151
+ ? {
152
+ specularIntensity: diagnostics.appliedLook.environment?.specularIntensity ?? 0,
153
+ shadowStrength: diagnostics.appliedLook.shadows?.strength ?? null,
154
+ pixelRatio: diagnostics.appliedLook.pixelRatio ?? 0,
155
+ renderPath: diagnostics.appliedLook.renderPath ?? "unknown",
156
+ background: diagnostics.appliedLook.environment?.background ?? null
157
+ }
158
+ : null;
159
+ const lookLintErrors = (diagnostics?.look?.lint ?? [])
160
+ .filter((finding) => finding.severity === "error")
161
+ .map((finding) => finding.code ?? "unknown");
162
+ const devicePixelRatio = globalThis.devicePixelRatio ?? 1;
163
+
164
+ /*
165
+ * The app's WebGL context is not preserveDrawingBuffer, so readPixels must
166
+ * run in the same task as the stepped render, before compositing clears the
167
+ * drawing buffer. That also removes the composited element screenshot that
168
+ * software-GL runners could not produce inside a test timeout.
169
+ */
170
+ const target = [...document.querySelectorAll("canvas")]
171
+ .map((element) => ({
172
+ element,
173
+ gl: (element.getContext("webgl2") ?? element.getContext("webgl")) as WebGL2RenderingContext | WebGLRenderingContext | null
174
+ }))
175
+ .find((entry) => entry.gl !== null);
176
+ if (!target?.gl) return { ...empty("no-webgl-context"), appliedLook, lookLintErrors };
177
+ const gl = target.gl;
178
+ const width = gl.drawingBufferWidth;
179
+ const height = gl.drawingBufferHeight;
180
+ if (width === 0 || height === 0) return { ...empty("empty-drawing-buffer"), appliedLook, lookLintErrors };
181
+ // readPixels reads the currently bound framebuffer: renderers that end a
182
+ // frame with an offscreen target bound (shadow/post FBOs — e.g.
183
+ // animation-studio's bespoke renderer) would return that target's pixels
184
+ // instead of the canvas backbuffer. Rebind the default framebuffer first;
185
+ // every renderer rebinds its own targets at the start of its next frame.
186
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
187
+ const pixels = new Uint8Array(width * height * 4);
188
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
189
+
190
+ const BRIGHT_LUMA = 48;
191
+ const stride = 4; // pixels — 16×16 sample grid
192
+ let sampled = 0;
193
+ let bright = 0;
194
+ const buckets = new Set<number>();
195
+ // Central-region lit-pixel bbox: edges carry floor/backdrop, the subject
196
+ // mass is what every §6.4 template frames mid-frame.
197
+ const x0 = Math.floor(width * 0.1);
198
+ const x1 = Math.floor(width * 0.9);
199
+ const y0 = Math.floor(height * 0.1);
200
+ const y1 = Math.floor(height * 0.9);
201
+ let minX = Number.POSITIVE_INFINITY;
202
+ let maxX = Number.NEGATIVE_INFINITY;
203
+ let minY = Number.POSITIVE_INFINITY;
204
+ let maxY = Number.NEGATIVE_INFINITY;
205
+ for (let y = 0; y < height; y += stride) {
206
+ for (let x = 0; x < width; x += stride) {
207
+ const offset = (y * width + x) * 4;
208
+ const r = pixels[offset] ?? 0;
209
+ const g = pixels[offset + 1] ?? 0;
210
+ const b = pixels[offset + 2] ?? 0;
211
+ sampled += 1;
212
+ buckets.add(((r >> 4) << 8) | ((g >> 4) << 4) | (b >> 4));
213
+ const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
214
+ if (luma > BRIGHT_LUMA) {
215
+ bright += 1;
216
+ if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
217
+ minX = Math.min(minX, x);
218
+ maxX = Math.max(maxX, x);
219
+ minY = Math.min(minY, y);
220
+ maxY = Math.max(maxY, y);
221
+ }
222
+ }
223
+ }
224
+ }
225
+ // readPixels rows run bottom-up; report `y` in the same top-down
226
+ // convention the composited-screenshot path produced so stored
227
+ // expectations stay comparable across the switch.
228
+ const subjectBounds = Number.isFinite(minX)
229
+ ? { x: minX / width, y: 1 - maxY / height, width: (maxX - minX) / width, height: (maxY - minY) / height }
230
+ : null;
231
+
232
+ return {
233
+ sampledPixels: sampled,
234
+ brightPixels: bright,
235
+ uniqueBuckets: buckets.size,
236
+ subjectBounds,
237
+ appliedLook,
238
+ lookLintErrors,
239
+ devicePixelRatio
240
+ };
241
+ });
242
+
243
+ // 1. Non-blank.
244
+ expect(report.brightPixels / Math.max(1, report.sampledPixels)).toBeGreaterThan(MIN_BRIGHT_FRACTION);
245
+ expect(report.uniqueBuckets).toBeGreaterThan(MIN_UNIQUE_BUCKETS);
246
+
247
+ if (!options.skipAppDiagnostics) {
248
+ // 2–5. Renderer/look evidence via __AURA3D_LIVE_APPS__.
249
+ expect(report.appliedLook, "appliedLook diagnostics missing — is the app mounted via createAuraApp?").not.toBeNull();
250
+ expect(report.lookLintErrors).toEqual([]);
251
+ expect(report.appliedLook!.specularIntensity).toBeGreaterThan(0);
252
+ if (report.appliedLook!.shadowStrength !== null) {
253
+ expect(report.appliedLook!.shadowStrength!).toBeGreaterThanOrEqual(0.8);
254
+ }
255
+ expect(report.appliedLook!.pixelRatio).toBeGreaterThanOrEqual(Math.min(report.devicePixelRatio, 2));
256
+ }
257
+
258
+ // 6. Subject bounds ±10%.
259
+ if (options.subject) {
260
+ const tolerance = options.tolerance ?? 0.1;
261
+ const measured = `measured ${JSON.stringify(report.subjectBounds)}`;
262
+ expect(report.subjectBounds, "no lit subject mass in the central frame").not.toBeNull();
263
+ expect(Math.abs(report.subjectBounds!.x - options.subject.x), measured).toBeLessThanOrEqual(tolerance);
264
+ expect(Math.abs(report.subjectBounds!.y - options.subject.y), measured).toBeLessThanOrEqual(tolerance);
265
+ expect(Math.abs(report.subjectBounds!.width - options.subject.width), measured).toBeLessThanOrEqual(tolerance);
266
+ expect(Math.abs(report.subjectBounds!.height - options.subject.height), measured).toBeLessThanOrEqual(tolerance);
267
+ }
268
+
269
+ return report;
270
+ }
@@ -1,5 +1,12 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
1
3
  import { expect, test } from "@playwright/test";
2
4
 
5
+ // The locomotion proof's threshold hold alone can need most of a slow
6
+ // software-GL minute; give the whole spec the same 300s headroom the
7
+ // screenshot spec gets.
8
+ test.setTimeout(300_000);
9
+
3
10
  test("character controller route exposes a live locomotion proof", async ({ page }) => {
4
11
  const errors: string[] = [];
5
12
  page.on("pageerror", (error) => errors.push(error.message));
@@ -8,19 +15,57 @@ test("character controller route exposes a live locomotion proof", async ({ page
8
15
  await page.waitForFunction(() => Boolean((window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: unknown }).__AURA3D_CHARACTER_CONTROLLER_PROOF__));
9
16
 
10
17
  // idle at rest
11
- const idle = await page.evaluate(() => (window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { state: string } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__);
18
+ const idle = await page.evaluate(() => (window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { state: string; look?: { id: string }; camera?: { rig: string; presented: boolean } } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__);
12
19
  expect(idle!.state).toBe("idle");
20
+ expect(idle!.look?.id).toBe("outdoor-day");
21
+ expect(idle!.camera?.rig).toBe("shoulder:hero");
22
+ expect(idle!.camera?.presented).toBe(true);
13
23
 
14
- // holding a movement key accelerates into walk/run
24
+ // holding a movement key accelerates into walk/run and moves the hero
25
+ const before = await page.evaluate(() => (window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { position: readonly number[] } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__?.position ?? [0, 0, 0]);
15
26
  await page.keyboard.down("KeyD");
16
- await page.waitForTimeout(600);
17
- const moving = await page.evaluate(() => (window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { state: string; speed: number; clipWeights: { weight: number }[] } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__);
27
+ // Hold KeyD until the hero actually crosses the threshold rather than a
28
+ // wall-clock 600ms: sim time advances at most 0.25s per presented frame
29
+ // (dt clamp), so a fixed hold is meaningless on slow software-GL runners.
30
+ await page.waitForFunction(
31
+ (startX) => ((window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { position?: readonly number[] } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__?.position?.[0] ?? 0) > startX + 0.05,
32
+ before[0] ?? 0,
33
+ { timeout: 180_000 }
34
+ );
35
+ const moving = await page.evaluate(() => (window as unknown as { __AURA3D_CHARACTER_CONTROLLER_PROOF__?: { state: string; speed: number; position: readonly number[]; clipWeights: { weight: number }[] } }).__AURA3D_CHARACTER_CONTROLLER_PROOF__);
18
36
  await page.keyboard.up("KeyD");
19
37
 
20
38
  expect(moving!.speed).toBeGreaterThan(0.1);
21
39
  expect(["walk", "run"]).toContain(moving!.state);
40
+ expect(moving!.position[0]).toBeGreaterThan((before[0] ?? 0) + 0.05);
22
41
  const sum = moving!.clipWeights.reduce((acc, w) => acc + w.weight, 0);
23
42
  expect(sum).toBeGreaterThan(0.9);
24
43
  expect(sum).toBeLessThan(1.1);
25
44
  expect(errors).toEqual([]);
45
+ // Playwright's visibility engine and bare evaluate() can starve behind the
46
+ // continuously rendering main thread just like boundingBox() below.
47
+ // waitForFunction polls inside the page's own animation frame, so it always
48
+ // gets a slot — assert the same semantics there.
49
+ await page.waitForFunction(() => {
50
+ const element = document.querySelector("canvas");
51
+ if (!(element instanceof HTMLCanvasElement)) return false;
52
+ const style = getComputedStyle(element);
53
+ return style.display !== "none" && style.visibility !== "hidden";
54
+ }, undefined, { timeout: 60_000 });
55
+ // Locator boundingBox() can block behind the continuously rendering main
56
+ // thread even after Playwright has resolved the canvas as visible. Read the
57
+ // same layout rectangle synchronously in the page, as the proof reads its
58
+ // runtime state above.
59
+ const canvasBounds = await page.evaluate(() => {
60
+ const element = document.querySelector("canvas");
61
+ if (!(element instanceof HTMLCanvasElement)) return null;
62
+ const bounds = element.getBoundingClientRect();
63
+ return { x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height };
64
+ });
65
+ expect(canvasBounds?.width ?? 0).toBeGreaterThan(0);
66
+ expect(canvasBounds?.height ?? 0).toBeGreaterThan(0);
67
+ mkdirSync(resolve("tests/reports"), { recursive: true });
68
+ writeFileSync(resolve("tests/reports/route-health.json"), `${JSON.stringify({
69
+ template: "character-controller", url: page.url(), canvasBounds, idle, moving, clipWeightSum: sum, errors
70
+ }, null, 2)}\n`);
26
71
  });