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,4 +1,8 @@
1
- import { camera, createGameApp, effects, game, lights, scene, ui } from "@aura3d/engine";
1
+ import { camera, effects, game, gameFeel, lights, looks, scene, ui } from "@aura3d/engine";
2
+ // PRD-09: mounted via the shared runtime — createGame owns mount/lifecycle,
3
+ // the §7.7 fighting HUD theme, the §6.11 dpad-4btn touch preset,
4
+ // game-sfx-core cues, and the juice event map.
5
+ import { createGame, sfxUrl } from "@aura3d/engine/game";
2
6
  import { assets } from "./aura-assets";
3
7
  import {
4
8
  animationLayer,
@@ -26,6 +30,7 @@ import {
26
30
  import "./styles.css";
27
31
 
28
32
  type Aura3DGameWindow = Window & {
33
+ __AURA3D_GAME__?: { readonly state?: string };
29
34
  __AURA3D_GAME_DEBUG__?: unknown;
30
35
  __AURA3D_GAME_EVIDENCE__?: unknown;
31
36
  __AURA3D_GAME_RUNTIME__?: unknown;
@@ -61,7 +66,6 @@ ui.html(
61
66
  <section class="hud__panel hud__panel--meta" aria-label="Runtime evidence">
62
67
  <span id="hud-round">Round 1 - 99</span>
63
68
  <span id="hud-frame">Frame 0</span>
64
- <span id="hud-camera">Camera zoom 1.00</span>
65
69
  <span id="hud-assets">Typed assets ${typedFighterAssetCount}/2</span>
66
70
  <span id="hud-stage">Stage ${fightingStage.id}</span>
67
71
  <span id="hud-replay">Replay ready</span>
@@ -79,43 +83,59 @@ const hudRivalHealth = ui.text("#hud-rival-health");
79
83
  const hudPlayerMeter = ui.text("#hud-player-meter");
80
84
  const hudRound = ui.text("#hud-round");
81
85
  const hudFrame = ui.text("#hud-frame");
82
- const hudCamera = ui.text("#hud-camera");
83
86
  const hudAssets = ui.text("#hud-assets");
84
87
  const hudStage = ui.text("#hud-stage");
85
88
  const hudReplay = ui.text("#hud-replay");
86
89
  const replayButton = ui.button("#hud-replay-button");
87
90
  const pauseButton = ui.button("#hud-pause-button");
88
91
 
89
- const hudBindings = game.hud.bindings([
90
- game.hud.health({ actorId: "player", label: "Player health", a11yLabel: "player health" }),
91
- game.hud.health({ actorId: "rival", label: "Rival health", a11yLabel: "rival health" }),
92
- game.hud.meter({ actorId: "player", label: "Player meter", a11yLabel: "player special meter" }),
93
- game.hud.timer({ label: "Round timer", valuePath: "round.timeRemaining", a11yLabel: "round timer" }),
94
- game.hud.combo({ actorId: "player", label: "Player combo", valuePath: "combat.player.combo", a11yLabel: "player combo" }),
95
- game.hud.round({ label: "Round", valuePath: "round.index", a11yLabel: "round index" }),
96
- game.hud.debugToggle({ label: "Runtime evidence", action: "debug", statePath: "debug.visible" })
97
- ]);
92
+ // Descriptor literals replace the deprecated game.hud.* / game.accessibility.*
93
+ // helpers — same evidence shape, no factory call.
94
+ const hudBindings = [
95
+ { kind: "aura-game-hud-binding", owner: "app", binding: "health", id: "hud:player:health", label: "Player health", source: "combat", targetId: "player", valuePath: "combat.actors.player.health", maxPath: "rules.maxHealth", format: "percent", a11yLabel: "player health" },
96
+ { kind: "aura-game-hud-binding", owner: "app", binding: "health", id: "hud:rival:health", label: "Rival health", source: "combat", targetId: "rival", valuePath: "combat.actors.rival.health", maxPath: "rules.maxHealth", format: "percent", a11yLabel: "rival health" },
97
+ { kind: "aura-game-hud-binding", owner: "app", binding: "meter", id: "hud:player:meter", label: "Player meter", source: "combat", targetId: "player", valuePath: "combat.actors.player.meter", maxPath: "rules.maxMeter", format: "percent", a11yLabel: "player super meter value" },
98
+ { kind: "aura-game-hud-binding", owner: "app", binding: "timer", id: "hud:round:timer", label: "Round timer", source: "app-state", valuePath: "round.timeRemaining", format: "clock", a11yLabel: "round timer" },
99
+ { kind: "aura-game-hud-binding", owner: "app", binding: "combo", id: "hud:player:combo", label: "Player combo", source: "combat", targetId: "player", valuePath: "combat.player.combo", format: "number", a11yLabel: "player combo" },
100
+ { kind: "aura-game-hud-binding", owner: "app", binding: "round", id: "hud:round:index", label: "Round", source: "app-state", valuePath: "round.index", format: "number", a11yLabel: "round index" },
101
+ { kind: "aura-game-hud-binding", owner: "app", binding: "debug-toggle", id: "hud:debug:toggle", label: "Runtime evidence", source: "app-state", valuePath: "debug.visible", format: "boolean", a11yLabel: "runtime evidence toggle", debugOnly: true }
102
+ ] as const;
98
103
 
99
104
  const accessibilitySources = [
100
- game.accessibility.label({
101
- targetId: "hud",
105
+ {
106
+ kind: "aura-game-accessibility-source", feature: "label", id: "a11y:hud:label", owner: "app",
102
107
  label: "Live fighting game HUD with health, timer, stage, assets, and replay status.",
103
- live: true
104
- }),
105
- game.accessibility.focus({
106
- scopeId: "hud-controls",
107
- label: "HUD controls",
108
- targets: ["#hud-replay-button", "#hud-pause-button"]
109
- }),
110
- game.accessibility.reducedMotion({ enabled: reducedMotion }),
111
- game.accessibility.reducedFlash({ enabled: reducedFlash }),
112
- game.accessibility.highContrast({ enabled: highContrast }),
113
- game.accessibility.pauseControls({
114
- actions: ["pause", "Escape"],
115
- resumeActions: ["pause", "Enter"],
116
- menuId: "hud-controls"
117
- })
118
- ];
108
+ targetId: "hud", role: "status", actions: [], source: "dom",
109
+ evidence: "App owns an aria-live label for this gameplay target."
110
+ },
111
+ {
112
+ kind: "aura-game-accessibility-source", feature: "focus", id: "a11y:hud-controls:focus", owner: "app",
113
+ label: "HUD controls", targetId: "hud-controls",
114
+ actions: ["#hud-replay-button", "#hud-pause-button"], source: "dom",
115
+ evidence: "Focus is scoped to the replay and pause controls."
116
+ },
117
+ {
118
+ kind: "aura-game-accessibility-source", feature: "reduced-motion", id: "a11y:reduced-motion", owner: "app",
119
+ label: "reduced motion", actions: [], source: "media-query",
120
+ evidence: reducedMotion ? "prefers-reduced-motion is enabled." : "prefers-reduced-motion is not set."
121
+ },
122
+ {
123
+ kind: "aura-game-accessibility-source", feature: "reduced-flash", id: "a11y:reduced-flash", owner: "app",
124
+ label: "reduced flash", actions: [], source: "media-query",
125
+ evidence: reducedFlash ? "Reduced flash enabled via reduced-motion preference." : "Reduced flash not required."
126
+ },
127
+ {
128
+ kind: "aura-game-accessibility-source", feature: "high-contrast", id: "a11y:high-contrast", owner: "app",
129
+ label: "high contrast", actions: [], source: "media-query",
130
+ evidence: highContrast ? "prefers-contrast: more is enabled." : "prefers-contrast: more is not set."
131
+ },
132
+ {
133
+ kind: "aura-game-accessibility-source", feature: "pause-controls", id: "a11y:pause-controls", owner: "app",
134
+ label: "pause controls", targetId: "hud-controls",
135
+ actions: ["pause", "Escape"], source: "app-state",
136
+ evidence: "Pause/resume through the pause action or Escape; resume via pause or Enter."
137
+ }
138
+ ] as const;
119
139
 
120
140
  const inputOptions = {
121
141
  actions: {
@@ -129,29 +149,65 @@ const inputOptions = {
129
149
  gamepad: true
130
150
  } as const;
131
151
 
152
+ const LOOK_ID = "arena-fight" as const;
132
153
  const arena = scene()
133
- .background("#10071c")
154
+ // The arena-fight look supplies the interior-industrial backdrop, key light,
155
+ // haze and post grade; only the accent rim spot rides on top of it.
156
+ .add(looks.preset(LOOK_ID))
134
157
  .addMany(fightingStage.nodes)
135
- // The creature rival renders roughly twice the player height at scale 1, so
136
- // it mounts slightly under scale to share the frame with the humanoid player.
158
+ // Both library rigs are metre-scale humanoids (~1.8 m) matched to the 1.7 m
159
+ // fighter colliders — full scale for both, no per-model correction needed.
137
160
  .add(createFighterNode("player", PLAYER_FIGHTER_ASSET, "Player fighter", playerStart, 1, "#45f5bb", typedFighterAssets, 1))
138
- .add(createFighterNode("rival", RIVAL_FIGHTER_ASSET, "Rival fighter", rivalStart, -1, "#ffca5f", typedFighterAssets, 0.75))
161
+ .add(createFighterNode("rival", RIVAL_FIGHTER_ASSET, "Rival fighter", rivalStart, -1, "#ffca5f", typedFighterAssets, 1))
139
162
  .addMany([
140
163
  effects.bloom({ intensity: 0.32 }),
141
- lights.studio({ intensity: 1.15 }),
142
164
  lights.directional({ name: "rim light", color: "#80ffd4", intensity: 0.7 }).position(0, 4, 3)
143
165
  ])
144
166
  .camera(camera.perspective({ position: [0, 1.75, 5.8], target: [0, 0.85, 0], fov: 42 }));
145
167
 
146
- const gameApp = createGameApp("#app", {
147
- diagnostics: { overlay: true, performancePanel: true },
168
+ const fightingGame = createGame({
169
+ id: "fighting-game",
170
+ target: "#app",
171
+ autoStart: true,
172
+ diagnostics: { overlay: false, performancePanel: false },
148
173
  input: inputOptions,
149
174
  loop: { fixedDt: 1 / 60 },
150
- scene: arena
175
+ scene: () => arena,
176
+ hud: { theme: "fighting", widgets: [] },
177
+ touch: {
178
+ preset: "dpad-4btn",
179
+ bindings: { left: "left", right: "right", block: "guard", light: "light", heavy: "heavy", special: "special", jump: "jump" }
180
+ },
181
+ sound: {
182
+ cues: {
183
+ hit: { id: "hit", asset: { url: sfxUrl("impact.flesh.medium.00") }, volume: 0.7 },
184
+ blocked: { id: "blocked", asset: { url: sfxUrl("impact.metal.light.00") }, volume: 0.6 },
185
+ special: { id: "special", asset: { url: sfxUrl("impact.energy.heavy.00") }, volume: 0.8 },
186
+ dash: { id: "dash", asset: { url: sfxUrl("vehicle.boost") }, volume: 0.35 },
187
+ "replay-start": { id: "replay-start", asset: { url: sfxUrl("ui.confirm.00") }, volume: 0.5 }
188
+ }
189
+ },
190
+ juice: {
191
+ hit: { hitStop: 0.045, shake: 0.32, punch: { fovDeg: 1.8, ms: 160 }, rumble: { strong: 0.5, ms: 140 } },
192
+ blocked: { shake: 0.14, rumble: { weak: 0.4, ms: 100 } },
193
+ special: { hitStop: 0.06, punch: { fovDeg: 2.4, ms: 260 }, flash: { color: "#45f5bb", peak: 0.2, ms: 240 }, rumble: { strong: 0.7, ms: 260 } },
194
+ dash: { punch: { fovDeg: 1.1, ms: 120 } }
195
+ },
196
+ qualityRebuild: { flags: ["game"] },
197
+ evidence: {
198
+ schema: 1,
199
+ sections: {
200
+ fightingGame: () => ({
201
+ replay: gameWindow.__AURA3D_GAME_REPLAY__ ?? { status: "unbound" },
202
+ debug: gameWindow.__AURA3D_GAME_DEBUG__ ?? { status: "unbound" },
203
+ source: gameWindow.__AURA3D_GAME_SOURCE__ ?? { status: "unbound" }
204
+ })
205
+ }
206
+ }
151
207
  });
152
-
153
- const app = gameApp.app;
154
- const input = gameApp.input;
208
+ const app = fightingGame.app;
209
+ fightingGame.start();
210
+ const input = fightingGame.input;
155
211
  if (!input) throw new Error("create-aura3d fighting-game template failed to create runtime-owned input.");
156
212
  const replayInput = game.input({ ...inputOptions, autoListen: false, gamepad: false });
157
213
  const openingReplay = game.inputReplay(
@@ -188,6 +244,13 @@ const director = game.cameraDirector({
188
244
  });
189
245
  const playerNode = app.nodes.require("player");
190
246
  const rivalNode = app.nodes.require("rival");
247
+ // F-08-1: the fighting rig owns the presented camera — it frames both
248
+ // fighters from the runtime actor positions (flag-off mounts the no-op stub
249
+ // controller; the scene camera spec is the fallback).
250
+ // F-08-2/F-08-5: fighting feel preset + GameFeel attach through app.time.
251
+ app.camera?.use(camera.rigs.fighting({ fighters: ["player", "rival"] }));
252
+ const feel = gameFeel.create({ app, time: app.time });
253
+ app.feel?.preset("fighting");
191
254
  const playerAnimation = createFighterAnimationController("player", typedFighterAssets[PLAYER_FIGHTER_ASSET]);
192
255
  const rivalAnimation = createFighterAnimationController("rival", typedFighterAssets[RIVAL_FIGHTER_ASSET]);
193
256
 
@@ -204,10 +267,13 @@ gameWindow.__AURA3D_GAME_SOURCE__ = {
204
267
  template: "fighting-game",
205
268
  package: "create-aura3d",
206
269
  publicEngineApi: true,
270
+ look: { id: LOOK_ID, category: "studio", biome: "interior-industrial" },
207
271
  lifecycle: {
208
- kind: gameApp.kind,
209
- usesCreateGameApp: true,
210
- runtimeEvidenceGlobal: "__AURA3D_GAME_RUNTIME__"
272
+ kind: "createGame",
273
+ usesCreateGameApp: false,
274
+ usesCreateGame: true,
275
+ beaconGlobal: "__AURA3D_GAME__",
276
+ evidenceGlobal: "__AURA3D_GAME_EVIDENCE__"
211
277
  },
212
278
  typedAssetPattern: "src/aura-assets.ts",
213
279
  typedAssetKeys: REQUIRED_FIGHTER_ASSETS,
@@ -239,6 +305,7 @@ let playerClip: FighterClip = "idle";
239
305
  let rivalClip: FighterClip = "idle";
240
306
 
241
307
  ui.onClick(replayButton, () => {
308
+ void fightingGame.sound?.cue("replay-start");
242
309
  replayActive = true;
243
310
  replayHitCount = 0;
244
311
  replayDriver.reset();
@@ -248,13 +315,11 @@ ui.onClick(replayButton, () => {
248
315
 
249
316
  ui.onClick(pauseButton, () => setPaused(!paused));
250
317
 
251
- gameApp.onFrame(({ dt }) => {
318
+ app.onFrame(({ dt }) => {
252
319
  const activeInput = replayActive ? replayInput : input;
253
320
  if (replayActive) {
254
321
  replayDriver.step(dt);
255
322
  if (replayDriver.snapshot().complete) replayActive = false;
256
- } else {
257
- input.update(dt);
258
323
  }
259
324
 
260
325
  if (activeInput.pressed("pause")) setPaused(!paused);
@@ -272,6 +337,8 @@ gameApp.onFrame(({ dt }) => {
272
337
  if (activeInput.pressed("dash")) {
273
338
  playerBody.dash([playerBody.facing, 0, 0], 8);
274
339
  runtimeEffects.dashTrail(playerBody.position, { ownerId: "player", intensity: 0.55 });
340
+ fightingGame.juice.fire("dash");
341
+ void fightingGame.sound?.cue("dash");
275
342
  }
276
343
 
277
344
  aiCooldown -= dt;
@@ -294,7 +361,11 @@ gameApp.onFrame(({ dt }) => {
294
361
  : undefined;
295
362
  if (playerAttack) {
296
363
  combat.beginAttack("player", playerAttack);
297
- if (playerAttack.id.includes("special")) runtimeEffects.auraBurst(playerBody.position, { ownerId: "player", intensity: 0.7 });
364
+ if (playerAttack.id.includes("special")) {
365
+ runtimeEffects.auraBurst(playerBody.position, { ownerId: "player", intensity: 0.7 });
366
+ fightingGame.juice.fire("special");
367
+ void fightingGame.sound?.cue("special");
368
+ }
298
369
  }
299
370
 
300
371
  playerBody.update(dt);
@@ -313,13 +384,21 @@ gameApp.onFrame(({ dt }) => {
313
384
  totalHitCount += 1;
314
385
  if (replayActive || replayDriver.snapshot().frame > 0) replayHitCount += 1;
315
386
  }
387
+ fightingGame.juice.fire(event.type);
388
+ void fightingGame.sound?.cue(event.type);
389
+ // F-08-6: combat outcomes emit on the feel bus — the preset's hitStop
390
+ // spec freezes gameplay through app.time (auto hit-stop).
391
+ app.feel?.emit(event.type === "hit" ? "hit-light" : "block", { strength: event.type === "hit" ? 0.6 : 0.3 });
316
392
  if (event.targetId === "player") playerBody.applyKnockback([event.attackerId === "rival" ? -2.2 : 2.2, 1.5, 0]);
317
393
  if (event.targetId === "rival") rivalBody.applyKnockback([event.attackerId === "player" ? 2.2 : -2.2, 1.5, 0]);
318
394
  }
319
395
  }
320
396
 
321
397
  runtimeEffects.update(dt);
322
- const cameraFrame = director.update(dt, [
398
+ feel.update(dt * 1000);
399
+ // The fighting rig presents the frame; the director still decays impact
400
+ // impulses queued above so its shake bookkeeping stays bounded.
401
+ director.update(dt, [
323
402
  { id: "player", position: playerBody.position },
324
403
  { id: "rival", position: rivalBody.position }
325
404
  ]);
@@ -358,7 +437,6 @@ gameApp.onFrame(({ dt }) => {
358
437
  ui.setText(hudPlayerMeter, `${Math.round(player.meter)}%`);
359
438
  ui.setText(hudRound, `Round 1 - ${Math.ceil(roundTime)}`);
360
439
  ui.setText(hudFrame, `Frame ${app.runtime.frame}`);
361
- ui.setText(hudCamera, `Camera zoom ${cameraFrame.zoom.toFixed(2)}`);
362
440
  ui.setText(
363
441
  hudAssets,
364
442
  missingFighterAssets.length === 0
@@ -385,7 +463,11 @@ gameApp.onFrame(({ dt }) => {
385
463
  totalHitCount,
386
464
  liveInputEvents: input.recorded()
387
465
  };
388
- gameWindow.__AURA3D_GAME_RUNTIME__ = gameApp.evidence;
466
+ gameWindow.__AURA3D_GAME_RUNTIME__ = {
467
+ kind: "createGame",
468
+ beacon: gameWindow.__AURA3D_GAME__ ?? { state: "unbound" },
469
+ frame: app.runtime.frame
470
+ };
389
471
  gameWindow.__AURA3D_GAME_DEBUG__ = game.debug.overlay({
390
472
  runtime: app.runtime,
391
473
  input: activeInput,
@@ -423,8 +505,8 @@ function setPaused(next: boolean): void {
423
505
  paused = next;
424
506
  ui.setPressed(pauseButton, paused);
425
507
  ui.setText(pauseButton, paused ? "Resume" : "Pause");
426
- if (paused) gameApp.pause();
427
- else gameApp.resume();
508
+ if (paused) app.pause();
509
+ else app.resume();
428
510
  }
429
511
 
430
512
  function syncFighterAnimation(controller: ReturnType<typeof createFighterAnimationController>, clip: FighterClip, dt: number): void {
@@ -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(90_000);
6
+ test.setTimeout(240_000);
7
7
 
8
8
  /**
9
9
  * E1 certified-rig mount proof for the fighting-game template.
@@ -6,38 +6,30 @@ test("input replay produces runtime evidence and a hit declaration", async ({ pa
6
6
  await page.goto("/");
7
7
  await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness));
8
8
  await page.getByRole("button", { name: "Run replay" }).click();
9
- await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_EVIDENCE__?.systems?.inputPlan));
10
- await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_RUNTIME__?.kind === "aura-game-app-runtime-evidence"));
11
- await page.waitForFunction(() => ((window as any).__AURA3D_GAME_REPLAY__?.hitCount ?? 0) > 0);
12
- const evidence = await page.evaluate(() => (window as any).__AURA3D_GAME_EVIDENCE__);
13
- const runtime = await page.evaluate(() => (window as any).__AURA3D_GAME_RUNTIME__);
14
- const replay = await page.evaluate(() => (window as any).__AURA3D_GAME_REPLAY__);
15
- const source = await page.evaluate(() => (window as any).__AURA3D_GAME_SOURCE__);
16
-
17
- expect(runtime).toMatchObject({
18
- status: "running",
19
- running: true,
20
- started: true,
21
- startCount: 1,
22
- inputControllers: 1,
23
- activeInputControllers: 1
9
+ await page.waitForFunction(() => {
10
+ const evidence = (window as any).__AURA3D_GAME_EVIDENCE__;
11
+ const runtime = (window as any).__AURA3D_GAME_RUNTIME__;
12
+ const replay = (window as any).__AURA3D_GAME_REPLAY__;
13
+ const source = (window as any).__AURA3D_GAME_SOURCE__;
14
+ return Boolean(
15
+ runtime?.kind === "createGame" &&
16
+ runtime.frame > 0 &&
17
+ typeof runtime.beacon?.state === "string" &&
18
+ evidence?.systems?.mutableNodes &&
19
+ evidence.systems.inputPlan &&
20
+ evidence.systems.physicsPlan &&
21
+ evidence.systems.collisionPlan &&
22
+ evidence.systems.animationPlan &&
23
+ evidence.systems.effectsPlan &&
24
+ evidence.systems.cameraPlan &&
25
+ evidence.systems.stagePlan &&
26
+ replay?.hitCount > 0 &&
27
+ source?.readiness?.sourceOnly === false &&
28
+ source.readiness.placeholderMode === false &&
29
+ source.readiness.proofMode === "typed-assets" &&
30
+ Array.isArray(source.readiness.missingTypedAssets) &&
31
+ source.readiness.missingTypedAssets.length === 0 &&
32
+ source.readiness.publicEngineApis?.includes("games.fighting.stagePreset")
33
+ );
24
34
  });
25
- expect(runtime.frame).toBeGreaterThan(0);
26
- expect(runtime.loop.frame).toBeGreaterThan(0);
27
- expect(evidence.systems.mutableNodes).toBeTruthy();
28
- expect(evidence.systems.inputPlan).toBeTruthy();
29
- expect(evidence.systems.physicsPlan).toBeTruthy();
30
- expect(evidence.systems.collisionPlan).toBeTruthy();
31
- expect(evidence.systems.animationPlan).toBeTruthy();
32
- expect(evidence.systems.effectsPlan).toBeTruthy();
33
- expect(evidence.systems.cameraPlan).toBeTruthy();
34
- expect(evidence.systems.stagePlan).toBeTruthy();
35
- expect(replay.hitCount).toBeGreaterThan(0);
36
- // Certified heroes ship as typed fighter assets (humanoid-a + creature), so
37
- // the route runs in typed-assets proof mode instead of source placeholders.
38
- expect(source.readiness.sourceOnly).toBe(false);
39
- expect(source.readiness.placeholderMode).toBe(false);
40
- expect(source.readiness.proofMode).toBe("typed-assets");
41
- expect(source.readiness.missingTypedAssets ?? []).toEqual([]);
42
- expect(source.readiness.publicEngineApis).toContain("games.fighting.stagePreset");
43
35
  });
@@ -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
+ }