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,14 +1,21 @@
1
1
  import {
2
2
  camera,
3
- createAuraApp,
4
3
  game,
5
- lights,
4
+ gameFeel,
5
+ instances,
6
+ looks,
6
7
  material,
7
8
  model,
8
- primitives,
9
9
  scene,
10
- type AuraNodeInput
10
+ type AuraColor,
11
+ type AuraNodeInput,
12
+ type AuraRuntimeNodeHandle,
13
+ type AuraTransformSpec
11
14
  } from "@aura3d/engine";
15
+ // PRD-09: mounted via the shared runtime — createGame owns mount/lifecycle,
16
+ // the §7.7 arcade-neon HUD theme, the §6.11 dpad-2btn touch preset,
17
+ // game-sfx-core cues, and the juice event map (no route-local hit-stop).
18
+ import { createGame, sfxUrl } from "@aura3d/engine/game";
12
19
  import { assets } from "./aura-assets";
13
20
 
14
21
  declare global {
@@ -19,6 +26,8 @@ declare global {
19
26
 
20
27
  type Piece = "I" | "J" | "L" | "O" | "S" | "T" | "Z";
21
28
  type Cell = Piece | null;
29
+ type Board = readonly (readonly Cell[])[];
30
+ type KitState = ReturnType<typeof falling.snapshot>;
22
31
 
23
32
  interface FallingBlocksStarterEvidence {
24
33
  readonly frame: number;
@@ -29,28 +38,53 @@ interface FallingBlocksStarterEvidence {
29
38
  readonly hold: Piece | null;
30
39
  readonly gameOver: boolean;
31
40
  readonly events: readonly string[];
32
- readonly lineClearProof: {
33
- readonly lines: number;
34
- readonly events: readonly string[];
35
- readonly checksum: string;
41
+ readonly look: { readonly id: string; readonly category: string };
42
+ readonly board: {
43
+ readonly width: number;
44
+ readonly height: number;
45
+ readonly visibleRows: number;
46
+ readonly filledCells: number;
47
+ readonly rendering: "instanced-model";
48
+ readonly cellAsset: { readonly id: string; readonly url: string; readonly metres: readonly [number, number, number] };
49
+ readonly drawCalls: number;
36
50
  };
51
+ readonly flash: { readonly modelBased: boolean; readonly activeRows: readonly number[] };
37
52
  readonly evidence: unknown;
38
53
  }
39
54
 
55
+ const LOOK_ID = "neon-arcade" as const;
40
56
  const boardWidth = 10;
41
57
  const boardHeight = 22;
42
58
  const hiddenRows = 2;
59
+ const visibleRows = boardHeight - hiddenRows;
43
60
  const cellSize = 0.24;
44
61
  const boardOrigin = { x: -1.2, y: -2.18 };
45
- const pieceColors: Record<Piece, string> = {
46
- I: "#5ed7df",
47
- J: "#7fa5df",
48
- L: "#dfbd65",
49
- O: "#dccf78",
50
- S: "#84d09a",
62
+ const boardTop = boardOrigin.y + visibleRows * cellSize;
63
+ const boardCenterY = (boardOrigin.y + boardTop) / 2;
64
+
65
+ // quaternius-cube-crate is a 2.021 m cube vs the retired 1.082 m block; the
66
+ // scale literals below were authored against the old cube, so RENORM keeps
67
+ // every rendered size identical. CELL_SCALE brings the crate to one
68
+ // 0.24 m board cell; the slight fill reduction keeps a visible bevel gap.
69
+ const RENORM = 1.082 / assets.quaterniusCubeCrate.bounds[1];
70
+ const bs = (n: number) => n * RENORM;
71
+ const CELL_SCALE = (cellSize / 2) * RENORM;
72
+ const CELL_FILL_SCALE = 0.112 * RENORM;
73
+ const FLASH_MS = 280;
74
+
75
+ // Per-piece colours stay inside the neon-arcade palette family: the cyan
76
+ // accent plus its complementary saturated neon hues over the dark base.
77
+ const pieceColors: Record<Piece, AuraColor> = {
78
+ I: "#4de8ff",
79
+ J: "#7d9dd4",
80
+ L: "#f4a259",
81
+ O: "#ffd166",
82
+ S: "#4ce8b0",
51
83
  T: "#b987d0",
52
- Z: "#d88791"
84
+ Z: "#ff5d7d"
53
85
  };
86
+ const FRAME_COLOR: AuraColor = "#232c4d";
87
+ const FRAME_ACCENT: AuraColor = "#4de8ff";
54
88
  const activeShapes: Record<Piece, readonly (readonly { readonly x: number; readonly y: number }[])[]> = {
55
89
  I: [
56
90
  [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 3, y: 1 }],
@@ -67,7 +101,7 @@ const activeShapes: Record<Piece, readonly (readonly { readonly x: number; reado
67
101
  L: [
68
102
  [{ x: 2, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
69
103
  [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
70
- [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 0, y: 2 }],
104
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
71
105
  [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
72
106
  ],
73
107
  O: [
@@ -89,14 +123,14 @@ const activeShapes: Record<Piece, readonly (readonly { readonly x: number; reado
89
123
  [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
90
124
  ],
91
125
  Z: [
92
- [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
126
+ [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }],
93
127
  [{ x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
94
128
  [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
95
129
  [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 0, y: 2 }]
96
130
  ]
97
131
  };
98
132
 
99
- const input = game.input({
133
+ const inputOptions = {
100
134
  actions: {
101
135
  left: ["KeyA", "ArrowLeft"],
102
136
  right: ["KeyD", "ArrowRight"],
@@ -108,7 +142,7 @@ const input = game.input({
108
142
  reset: ["KeyR"]
109
143
  },
110
144
  bufferMs: 100
111
- });
145
+ } as const;
112
146
 
113
147
  const falling = game.fallingBlocks({
114
148
  width: boardWidth,
@@ -119,33 +153,85 @@ const falling = game.fallingBlocks({
119
153
  lockDelayFrames: 18
120
154
  });
121
155
  const routeEvents = game.eventLog({ label: "falling blocks starter events", maxEvents: 18 });
122
- const hud = game.hud.bindings([
123
- game.hud.score({ valuePath: "appState.score" }),
124
- game.hud.objective({ valuePath: "appState.objective" }),
125
- game.hud.eventLog({ valuePath: "appState.events" })
126
- ]);
127
- const lineClearProof = createLineClearProof();
156
+ // HUD bindings as descriptor literals (the deprecated game.hud.* helpers are
157
+ // replaced by the createGame `hud` option + evidence channel sections).
158
+ const hudBindings = [
159
+ { kind: "aura-game-hud-binding", owner: "app", binding: "score", id: "hud:score", label: "score", source: "app-state", valuePath: "appState.score", format: "number", a11yLabel: "score" },
160
+ { kind: "aura-game-hud-binding", owner: "app", binding: "objective", id: "hud:objective", label: "objective", source: "app-state", valuePath: "appState.objective", format: "text", a11yLabel: "current objective" },
161
+ { kind: "aura-game-hud-binding", owner: "app", binding: "event-log", id: "hud:event-log", label: "event log", source: "app-state", valuePath: "appState.events", format: "text", a11yLabel: "game event log", debugOnly: true }
162
+ ] as const;
128
163
 
129
164
  setupPracticeBoard();
130
165
 
131
- const app = createAuraApp("#app", {
166
+ const blockfallGame = createGame({
167
+ id: "falling-blocks-starter",
168
+ target: "#app",
132
169
  diagnostics: { overlay: true, performancePanel: true },
133
- scene: buildScene()
170
+ scene: () => buildScene(falling.snapshot().board),
171
+ input: inputOptions,
172
+ hud: { theme: "arcade-neon", widgets: [] },
173
+ touch: {
174
+ preset: "dpad-2btn",
175
+ bindings: { left: "left", right: "right", dash: "softDrop", jump: "rotateRight", attack: "hardDrop" }
176
+ },
177
+ sound: {
178
+ cues: {
179
+ move: { id: "move", asset: { url: sfxUrl("ui.toggle.00") }, volume: 0.3 },
180
+ rotate: { id: "rotate", asset: { url: sfxUrl("ui.toggle.01") }, volume: 0.35 },
181
+ "hard-drop": { id: "hard-drop", asset: { url: sfxUrl("impact.wood.medium.00") }, volume: 0.6 },
182
+ "line-clear": { id: "line-clear", asset: { url: sfxUrl("pickup.coin.00") }, volume: 0.8 },
183
+ "game-over": { id: "game-over", asset: { url: sfxUrl("stinger.lose.00") }, volume: 0.8 }
184
+ }
185
+ },
186
+ juice: {
187
+ "hard-drop": { hitStop: 0.03, shake: 0.25, rumble: { strong: 0.4, ms: 120 } },
188
+ "line-clear": { flash: { color: "#5ed7df", peak: 0.22, ms: 240 }, punch: { fovDeg: 1.8, ms: 220 }, rumble: { weak: 0.5, ms: 200 } },
189
+ "game-over": { flash: { color: "#d88791", peak: 0.3, ms: 400 }, vignette: { amount: 0.6, ms: 900 } }
190
+ },
191
+ qualityRebuild: { flags: ["game"] },
192
+ evidence: {
193
+ schema: 1,
194
+ sections: { fallingBlocksStarter: () => window.__AURA3D_FALLING_BLOCKS_STARTER__ ?? { status: "unbound" } }
195
+ }
134
196
  });
197
+ const app = blockfallGame.app;
198
+ blockfallGame.start();
199
+ const input = blockfallGame.input;
200
+ if (!input) throw new Error("create-aura3d falling-blocks-starter failed to create runtime-owned input.");
135
201
 
136
- const boardNodes = Array.from({ length: boardHeight - hiddenRows }, (_, rowIndex) =>
137
- Array.from({ length: boardWidth }, (_, x) => app.nodes.require(`board-cell-${x}-${rowIndex + hiddenRows}`))
138
- );
139
- const activeNodes = Array.from({ length: 4 }, (_, index) => app.nodes.require(`active-cell-${index}`));
140
- const holdNode = app.nodes.require("hold-preview");
202
+ // Runtime handles are re-required after every setScene rebuild because the
203
+ // instanced settled board is mount-time data, not a mutable runtime spec.
204
+ let activeNodes: AuraRuntimeNodeHandle[] = [];
205
+ let flashNodes: AuraRuntimeNodeHandle[] = [];
206
+ let holdNode: AuraRuntimeNodeHandle | undefined;
207
+ mountRuntimeHandles();
141
208
  const hudRoot = createHud();
142
209
  let objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
143
210
  let tickAccumulator = 0;
211
+ let settledSignature = settledCellsSignature(falling.snapshot().board);
212
+ let previousBoard: Board = falling.snapshot().board;
213
+ let flashRows: { readonly rows: readonly number[]; readonly until: number } = { rows: [], until: 0 };
214
+ let activeKind: Piece | null = null;
215
+ let holdKind: Piece | null = null;
144
216
 
145
- app.onFrame(({ dt }: { readonly dt: number }) => {
146
- input.update(dt);
217
+ // F-08-1: a static rig presents the board framing, pitched ~10° about X off
218
+ // the authored spec (flag-off mounts the no-op stub controller; that spec
219
+ // stays the fallback). F-08-5: feel bus + GameFeel attach through app.time.
220
+ app.camera?.use(
221
+ camera.rigs.static({
222
+ position: [0.35, boardCenterY + 0.81, 4.6],
223
+ target: [0, boardCenterY, 0],
224
+ orthographicSize: 2.9
225
+ })
226
+ );
227
+ const feel = gameFeel.create({ app, time: app.time });
228
+ app.feel?.preset("puzzle");
229
+
230
+ app.onFrame(({ dt }) => {
231
+ feel.update(dt * 1000);
147
232
  if (input.pressed("reset")) {
148
233
  setupPracticeBoard();
234
+ flashRows = { rows: [], until: 0 };
149
235
  routeEvents.push({ type: "reset", label: "reset" });
150
236
  objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
151
237
  }
@@ -164,16 +250,19 @@ app.onFrame(({ dt }: { readonly dt: number }) => {
164
250
  }
165
251
 
166
252
  const state = falling.snapshot();
253
+ rebuildSettledCells(state);
167
254
  renderBoard(state);
168
255
  renderHud(state);
169
256
  publishEvidence(state);
257
+ previousBoard = state.board;
170
258
  });
171
259
 
172
260
  renderBoard(falling.snapshot());
173
261
  renderHud(falling.snapshot());
174
262
  publishEvidence(falling.snapshot());
175
263
 
176
- function recordKitEvents(state: ReturnType<typeof falling.snapshot>): void {
264
+
265
+ function recordKitEvents(state: KitState): void {
177
266
  for (const event of state.events) {
178
267
  routeEvents.push({
179
268
  type: event.type,
@@ -181,10 +270,21 @@ function recordKitEvents(state: ReturnType<typeof falling.snapshot>): void {
181
270
  severity: event.type === "line-clear" ? "success" : event.type === "game-over" ? "warning" : "info",
182
271
  frame: event.frame
183
272
  });
184
- if (event.type === "line-clear") objective = `Line clear. ${state.lines} line cleared. Press R to replay.`;
273
+ if (event.type === "line-clear") {
274
+ objective = `Line clear. ${state.lines} line cleared. Press R to replay.`;
275
+ // The kit reports the count, not the rows: the cleared rows are exactly
276
+ // the rows that were full in the previous snapshot's board.
277
+ const cleared = fullRows(previousBoard).filter((row) => row >= hiddenRows);
278
+ flashRows = { rows: cleared, until: performance.now() + FLASH_MS };
279
+ }
185
280
  if (event.type === "hold") objective = "Held piece. Press R or continue.";
186
281
  if (event.type === "rotate") objective = "Rotation accepted.";
187
282
  if (event.type === "move") objective = "Move accepted.";
283
+ if (event.type === "move" || event.type === "rotate") void blockfallGame.sound?.cue(event.type);
284
+ if (event.type === "hard-drop" || event.type === "line-clear" || event.type === "game-over") {
285
+ blockfallGame.juice.fire(event.type);
286
+ void blockfallGame.sound?.cue(event.type);
287
+ }
188
288
  }
189
289
  }
190
290
 
@@ -202,85 +302,207 @@ function createPracticeBoard(): Cell[][] {
202
302
  return board;
203
303
  }
204
304
 
205
- function buildScene() {
305
+ function buildScene(board: Board) {
206
306
  const nodes: AuraNodeInput[] = [
207
- model(assets.cabinetModel, { name: "typed arcade cabinet", castShadow: true })
208
- .position(1.75, 0.28, -0.55)
307
+ // Modular wall panel stands in for the arcade cabinet silhouette
308
+ // (library approximation, #481 mapping); scale keeps the old height.
309
+ model(assets.quaterniusWallModular, { name: "typed arcade cabinet", castShadow: true })
310
+ .position(1.9, 0.2, -0.7)
209
311
  .scale(0.2),
210
- primitives.box({ name: "falling blocks board backplate", material: material.pbr({ color: "#172027", roughness: 0.82, metallic: 0.04 }) })
211
- .position(0, 0, -0.08)
212
- .scale([2.76, 5.25, 0.08]),
213
- primitives.box({ name: "falling blocks board frame", material: material.neon({ color: "#8ee8d5", emissive: "#8ee8d5", emissiveIntensity: 0.22 }) })
214
- .position(0, 0, -0.02)
215
- .scale([2.92, 5.42, 0.04]),
216
- ...boardCellNodes(),
312
+ // Dark glossy board backing sourced from the same typed block GLB.
313
+ model(assets.quaterniusCubeCrate, { name: "board glossy backplate", material: material.pbr({ color: "#0b0e18", roughness: 0.18, metalness: 0.55 }) })
314
+ .position(0, boardCenterY, -0.16)
315
+ .scale([bs(1.42), bs(2.5), bs(0.03)]),
316
+ ...boardFrameNodes(),
317
+ ...settledCellsNode(board),
217
318
  ...activeCellNodes(),
218
- primitives.box({ name: "hold preview", material: material.neon({ color: "#b987d0", emissive: "#b987d0", emissiveIntensity: 0.38 }) })
219
- .position(-2.02, 1.92, 0.08)
220
- .scale([0.42, 0.42, 0.16])
319
+ ...flashRowNodes(),
320
+ model(assets.quaterniusCubeCrate, { name: "hold preview", material: material.neon({ color: "#b987d0", emissive: "#b987d0", emissiveIntensity: 0.5 }) })
321
+ .position(-2.05, 1.92, 0.1)
322
+ .scale(bs(0.09))
221
323
  .runtime(game.runtimeNode("hold-preview", { tags: ["hold", "runtime"] }))
222
324
  ];
223
325
 
326
+ // The neon-arcade look supplies the night-city sky gradient, key light,
327
+ // fog and grade — no ambient fill, background override or debug overlay.
224
328
  return scene()
225
- .background("#06090d")
329
+ .add(looks.preset(LOOK_ID))
226
330
  .addMany(nodes)
227
- .add(lights.ambient({ name: "blockfall ambient", intensity: 0.36, color: "#e8fbff" }))
228
- .add(lights.directional({ name: "blockfall key", position: [3.8, 6.2, 5], intensity: 1.15, color: "#ffffff" }))
229
- .camera(camera.perspective({ position: [0.25, 1.2, 7.2], target: [0.1, 0.1, 0], fov: 38 }));
331
+ .camera(camera.orthographic({ position: [0.35, 0.75, 4.6], target: [0, boardCenterY, 0], orthographicSize: 2.9 }));
230
332
  }
231
333
 
232
- function boardCellNodes(): AuraNodeInput[] {
233
- const nodes: AuraNodeInput[] = [];
334
+ function boardFrameNodes(): AuraNodeInput[] {
335
+ const rail = material.pbr({ color: FRAME_COLOR, roughness: 0.3, metalness: 0.5, emissive: FRAME_ACCENT, emissiveIntensity: 0.22 });
336
+ const railThickness = 0.045; // 0.09 m rails around the 2.4 × 4.8 m well
337
+ const railHeight = (visibleRows * cellSize) / 2 + railThickness;
338
+ return [
339
+ model(assets.quaterniusCubeCrate, { name: "board frame left rail", material: rail })
340
+ .position(boardOrigin.x - railThickness, boardCenterY, 0)
341
+ .scale([bs(railThickness), bs(railHeight), bs(0.09)]),
342
+ model(assets.quaterniusCubeCrate, { name: "board frame right rail", material: rail })
343
+ .position(-boardOrigin.x + railThickness, boardCenterY, 0)
344
+ .scale([bs(railThickness), bs(railHeight), bs(0.09)]),
345
+ model(assets.quaterniusCubeCrate, { name: "board frame bottom rail", material: rail })
346
+ .position(0, boardOrigin.y - railThickness, 0)
347
+ .scale([bs(0.69), bs(railThickness), bs(0.09)]),
348
+ model(assets.quaterniusCubeCrate, { name: "board frame top rail", material: rail })
349
+ .position(0, boardTop + railThickness, 0)
350
+ .scale([bs(0.69), bs(railThickness), bs(0.09)])
351
+ ];
352
+ }
353
+
354
+ // The settled board renders as ONE instanced bevelled-cube mesh: every filled
355
+ // cell is an instance of the typed block GLB tinted to its piece colour.
356
+ function settledCellsNode(board: Board): AuraNodeInput[] {
357
+ const transforms: AuraTransformSpec[] = [];
358
+ const colors: AuraColor[] = [];
234
359
  for (let y = hiddenRows; y < boardHeight; y += 1) {
235
360
  for (let x = 0; x < boardWidth; x += 1) {
236
- nodes.push(
237
- primitives.box({ name: `board cell ${x} ${y}`, material: material.pbr({ color: "#dccf78", roughness: 0.62, metallic: 0.02 }) })
238
- .position(...cellPosition(x, y), 0)
239
- .scale([cellSize * 0.86, cellSize * 0.86, 0.12])
240
- .runtime(game.runtimeNode(`board-cell-${x}-${y}`, { tags: ["board-cell", "runtime"] }))
241
- );
361
+ const cell = board[y]?.[x];
362
+ if (!cell) continue;
363
+ const [px, py] = cellPosition(x, y);
364
+ transforms.push({ position: [px, py, 0], scale: CELL_FILL_SCALE });
365
+ colors.push(pieceColors[cell]);
242
366
  }
243
367
  }
244
- return nodes;
368
+ if (transforms.length === 0) {
369
+ // instances.* requires at least one transform; park a scale-0 dummy.
370
+ transforms.push({ position: [0, -50, 0], scale: 0 });
371
+ colors.push("#141828");
372
+ }
373
+ return [
374
+ instances.model(assets.quaterniusCubeCrate, {
375
+ name: "settled board cells",
376
+ transforms,
377
+ colors,
378
+ material: material.pbr({ color: "#ffffff", roughness: 0.22, metalness: 0.3 }),
379
+ instancingAware: true
380
+ })
381
+ ];
245
382
  }
246
383
 
247
384
  function activeCellNodes(): AuraNodeInput[] {
248
385
  return Array.from({ length: 4 }, (_, index) =>
249
- primitives.box({ name: `active piece cell ${index}`, material: material.neon({ color: "#5ed7df", emissive: "#5ed7df", emissiveIntensity: 0.4 }) })
250
- .position(-3, -3, 0.12)
251
- .scale([cellSize * 0.92, cellSize * 0.92, 0.16])
386
+ model(assets.quaterniusCubeCrate, { name: `active piece cell ${index}`, material: material.neon({ color: "#4de8ff", emissive: "#4de8ff", emissiveIntensity: 0.45 }) })
387
+ .position(0, -50, 0.14)
388
+ .scale(CELL_SCALE)
252
389
  .runtime(game.runtimeNode(`active-cell-${index}`, { tags: ["active-cell", "runtime"] }))
253
390
  );
254
391
  }
255
392
 
256
- function renderBoard(state: ReturnType<typeof falling.snapshot>): void {
393
+ // Line-clear flash is a model surface, not a particle pool: four instanced-GLB
394
+ // bars stretch across the board and pulse emissive while a clear animates.
395
+ function flashRowNodes(): AuraNodeInput[] {
396
+ return Array.from({ length: 4 }, (_, index) =>
397
+ model(assets.quaterniusCubeCrate, { name: `line clear flash ${index}`, material: material.neon({ color: FRAME_ACCENT, emissive: FRAME_ACCENT, emissiveIntensity: 1.6 }) })
398
+ .position(0, -50, 0.16)
399
+ .scale([bs(0.66), CELL_FILL_SCALE, bs(0.05)])
400
+ .runtime(game.runtimeNode(`flash-row-${index}`, { tags: ["flash", "runtime"] }))
401
+ );
402
+ }
403
+
404
+ function mountRuntimeHandles(): void {
405
+ activeNodes = Array.from({ length: 4 }, (_, index) => app.nodes.require(`active-cell-${index}`));
406
+ flashNodes = Array.from({ length: 4 }, (_, index) => app.nodes.require(`flash-row-${index}`));
407
+ holdNode = app.nodes.require("hold-preview");
408
+ }
409
+
410
+ // The instanced settled mesh is mount-time data: rebuild the scene only when
411
+ // the settled board signature changes (lock, line clear, reset) — never on
412
+ // per-frame motion of the active piece.
413
+ function rebuildSettledCells(state: KitState): void {
414
+ const signature = settledCellsSignature(state.board);
415
+ if (signature === settledSignature) return;
416
+ try {
417
+ app.setScene(buildScene(state.board));
418
+ } catch {
419
+ // Frame submission can be pending during a mid-frame rebuild; retry on the
420
+ // next frame instead of tearing down the gameplay callback.
421
+ return;
422
+ }
423
+ settledSignature = signature;
424
+ mountRuntimeHandles();
425
+ }
426
+
427
+ function settledCellsSignature(board: Board): string {
428
+ const cells: string[] = [];
429
+ for (let y = hiddenRows; y < boardHeight; y += 1) {
430
+ for (let x = 0; x < boardWidth; x += 1) {
431
+ const cell = board[y]?.[x];
432
+ if (cell) cells.push(`${x}:${y}:${cell}`);
433
+ }
434
+ }
435
+ return cells.join("|");
436
+ }
437
+
438
+ function fullRows(board: Board): number[] {
439
+ const rows: number[] = [];
440
+ for (let y = hiddenRows; y < boardHeight; y += 1) {
441
+ if (board[y]?.every((cell) => cell !== null)) rows.push(y);
442
+ }
443
+ return rows;
444
+ }
445
+
446
+ function filledCellCount(board: Board): number {
447
+ let count = 0;
257
448
  for (let y = hiddenRows; y < boardHeight; y += 1) {
258
449
  for (let x = 0; x < boardWidth; x += 1) {
259
- const node = boardNodes[y - hiddenRows][x];
260
- const cell = state.board[y][x];
261
- node.setVisible(Boolean(cell)).setPosition(...cellPosition(x, y), 0);
450
+ if (board[y]?.[x]) count += 1;
262
451
  }
263
452
  }
453
+ return count;
454
+ }
264
455
 
265
- activeNodes.forEach((node) => node.setVisible(false));
456
+ function renderBoard(state: KitState): void {
266
457
  if (state.active) {
267
- const cells = activeShapes[state.active.kind][state.active.rotation] ?? activeShapes[state.active.kind][0];
268
- cells.forEach((cell, index) => {
458
+ const kind = state.active.kind;
459
+ if (kind !== activeKind) {
460
+ const activeMaterial = material.neon({ color: pieceColors[kind], emissive: pieceColors[kind], emissiveIntensity: 0.45 });
461
+ activeNodes.forEach((node) => node.setMaterial(activeMaterial));
462
+ activeKind = kind;
463
+ }
464
+ const cells = activeShapes[kind][state.active.rotation] ?? activeShapes[kind][0];
465
+ activeNodes.forEach((node, index) => {
466
+ const cell = cells[index];
467
+ if (!cell) {
468
+ node.setVisible(false);
469
+ return;
470
+ }
269
471
  const x = state.active!.x + cell.x;
270
472
  const y = state.active!.y + cell.y;
271
- activeNodes[index]
272
- .setVisible(y >= hiddenRows)
273
- .setPosition(...cellPosition(x, y), 0.14)
274
- .setScale([cellSize * 0.94, cellSize * 0.94, 0.16]);
473
+ const [px, py] = cellPosition(x, y);
474
+ node.setVisible(y >= hiddenRows).setPosition(px, py, 0.14).setScale(CELL_SCALE);
275
475
  });
476
+ } else {
477
+ activeNodes.forEach((node) => node.setVisible(false));
478
+ }
479
+
480
+ const flashing = performance.now() < flashRows.until;
481
+ flashNodes.forEach((node, index) => {
482
+ const row = flashRows.rows[index];
483
+ if (!flashing || row === undefined) {
484
+ node.setVisible(false);
485
+ return;
486
+ }
487
+ const [, py] = cellPosition(0, row);
488
+ node.setVisible(true).setPosition(0, py, 0.16);
489
+ });
490
+
491
+ if (state.hold) {
492
+ if (state.hold !== holdKind) {
493
+ holdKind = state.hold;
494
+ holdNode?.setMaterial(material.neon({ color: pieceColors[holdKind], emissive: pieceColors[holdKind], emissiveIntensity: 0.5 }));
495
+ }
496
+ holdNode?.setVisible(true);
497
+ } else {
498
+ holdNode?.setVisible(false);
276
499
  }
277
- holdNode.setVisible(Boolean(state.hold));
278
500
  }
279
501
 
280
502
  function cellPosition(x: number, y: number): [number, number] {
281
503
  return [
282
504
  boardOrigin.x + (x + 0.5) * cellSize,
283
- boardOrigin.y + (boardHeight - hiddenRows - (y - hiddenRows) - 0.5) * cellSize
505
+ boardOrigin.y + (visibleRows - (y - hiddenRows) - 0.5) * cellSize
284
506
  ];
285
507
  }
286
508
 
@@ -295,8 +517,8 @@ function createHud(): HTMLElement {
295
517
  "min-width:310px",
296
518
  "font:600 13px/1.35 Inter, system-ui, sans-serif",
297
519
  "color:#f5fbff",
298
- "background:rgba(3,9,14,0.78)",
299
- "border:1px solid rgba(125,220,235,0.34)",
520
+ "background:rgba(8,10,22,0.72)",
521
+ "border:1px solid rgba(77,232,255,0.34)",
300
522
  "border-radius:8px",
301
523
  "padding:12px",
302
524
  "pointer-events:none"
@@ -305,7 +527,7 @@ function createHud(): HTMLElement {
305
527
  return root;
306
528
  }
307
529
 
308
- function renderHud(state: ReturnType<typeof falling.snapshot>): void {
530
+ function renderHud(state: KitState): void {
309
531
  hudRoot.innerHTML = [
310
532
  `<strong>Aura3D Falling Blocks Starter</strong>`,
311
533
  `<div>Score ${state.score} | Lines ${state.lines} | Level ${state.level}</div>`,
@@ -315,11 +537,11 @@ function renderHud(state: ReturnType<typeof falling.snapshot>): void {
315
537
  ].join("");
316
538
  }
317
539
 
318
- function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
540
+ function publishEvidence(state: KitState): void {
319
541
  const evidence = app.evidence({
320
542
  input,
321
543
  events: routeEvents,
322
- hud,
544
+ hud: hudBindings,
323
545
  appState: {
324
546
  score: state.score,
325
547
  objective,
@@ -331,6 +553,7 @@ function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
331
553
  },
332
554
  source: { expectsGame: true }
333
555
  });
556
+ const diagnostics = app.diagnostics();
334
557
  window.__AURA3D_FALLING_BLOCKS_STARTER__ = {
335
558
  frame: state.frame,
336
559
  score: state.score,
@@ -340,19 +563,17 @@ function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
340
563
  hold: state.hold,
341
564
  gameOver: state.gameOver,
342
565
  events: routeEvents.events().map((event: { readonly label: string }) => event.label),
343
- lineClearProof,
566
+ look: { id: LOOK_ID, category: "studio" },
567
+ board: {
568
+ width: boardWidth,
569
+ height: boardHeight,
570
+ visibleRows,
571
+ filledCells: filledCellCount(state.board),
572
+ rendering: "instanced-model",
573
+ cellAsset: { id: "quaterniusCubeCrate", url: assets.quaterniusCubeCrate.url, metres: assets.quaterniusCubeCrate.bounds },
574
+ drawCalls: diagnostics.drawCalls
575
+ },
576
+ flash: { modelBased: true, activeRows: performance.now() < flashRows.until ? flashRows.rows : [] },
344
577
  evidence
345
578
  };
346
579
  }
347
-
348
- function createLineClearProof(): FallingBlocksStarterEvidence["lineClearProof"] {
349
- const proof = game.fallingBlocks({ width: boardWidth, height: boardHeight, hiddenRows, seed: 7, gravityFrames: 999 });
350
- proof.setBoard(createPracticeBoard());
351
- proof.setActive({ kind: "I", x: 3, y: boardHeight - 3, rotation: 0 });
352
- const cleared = proof.hardDrop();
353
- return {
354
- lines: cleared.lines,
355
- events: cleared.events.map((event) => event.piece ? `${event.type}:${event.piece}` : event.type),
356
- checksum: cleared.checksum
357
- };
358
- }