create-aura3d 3.0.1 → 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 (396) 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 +3 -2
  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 +1 -1
  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 +4 -3
  71. package/templates/animation-studio/scripts/build-clip-library.ts +6 -5
  72. package/templates/animation-studio/scripts/resolve-asset.ts +7 -4
  73. package/templates/animation-studio/src/scene-player.ts +6 -6
  74. package/templates/animation-studio/src/set-templates.ts +13 -9
  75. package/templates/animation-studio/tests/look-floor.ts +270 -0
  76. package/templates/animation-studio/tests/route-health.spec.ts +2 -2
  77. package/templates/animation-studio/tests/screenshot.spec.ts +18 -10
  78. package/templates/animation-studio/vite.config.ts +2 -0
  79. package/templates/architecture-interior/.claude/CLAUDE.md +3 -0
  80. package/templates/architecture-interior/.github/workflows/aura3d-lookdev.yml +67 -0
  81. package/templates/architecture-interior/AGENTS.md +3 -0
  82. package/templates/architecture-interior/README.md +16 -0
  83. package/templates/architecture-interior/aura.assets.json +24 -0
  84. package/templates/architecture-interior/package.json +24 -0
  85. package/templates/architecture-interior/public/aura-assets/kenneyModularDungeonRoomSmall.a7e74a5f.glb +0 -0
  86. package/templates/architecture-interior/src/aura-assets.ts +16 -0
  87. package/templates/architecture-interior/src/main.ts +23 -0
  88. package/templates/architecture-interior/tests/look-floor.ts +270 -0
  89. package/templates/{three-compat-architecture-interior → architecture-interior}/tests/route-health.spec.ts +3 -3
  90. package/templates/architecture-interior/tests/screenshot.spec.ts +21 -0
  91. package/templates/architecture-interior/vite.config.ts +7 -0
  92. package/templates/arena-shooter/.claude/CLAUDE.md +3 -0
  93. package/templates/arena-shooter/.github/workflows/aura3d-lookdev.yml +67 -0
  94. package/templates/arena-shooter/AGENTS.md +3 -0
  95. package/templates/arena-shooter/README.md +45 -0
  96. package/templates/arena-shooter/aura.assets.json +62 -0
  97. package/templates/arena-shooter/index.html +32 -0
  98. package/templates/arena-shooter/package.json +23 -0
  99. package/templates/arena-shooter/playwright.config.ts +15 -0
  100. package/templates/arena-shooter/public/aura-assets/craft_miner.de323205.glb +0 -0
  101. package/templates/arena-shooter/public/aura-assets/craft_racer.bce8cb3d.glb +0 -0
  102. package/templates/arena-shooter/public/aura-assets/meteor_detailed.f30379ab.glb +0 -0
  103. package/templates/arena-shooter/src/aura-assets.ts +56 -0
  104. package/templates/arena-shooter/src/main.ts +616 -0
  105. package/templates/arena-shooter/tests/look-floor.ts +270 -0
  106. package/templates/arena-shooter/tests/playable.spec.ts +56 -0
  107. package/templates/arena-shooter/tests/route-health.spec.ts +87 -0
  108. package/templates/arena-shooter/tests/screenshot.spec.ts +22 -0
  109. package/templates/arena-shooter/tsconfig.json +15 -0
  110. package/templates/arena-shooter/vite.config.ts +7 -0
  111. package/templates/asset-inspector/.claude/CLAUDE.md +3 -0
  112. package/templates/asset-inspector/.github/workflows/aura3d-lookdev.yml +67 -0
  113. package/templates/asset-inspector/AGENTS.md +3 -0
  114. package/templates/asset-inspector/README.md +13 -0
  115. package/templates/asset-inspector/aura.assets.json +24 -0
  116. package/templates/{three-compat-custom-threejs-migration → asset-inspector}/package.json +5 -3
  117. package/templates/asset-inspector/public/aura-assets/kenneyCarKitSedanSports.2889c428.glb +0 -0
  118. package/templates/asset-inspector/src/aura-assets.ts +16 -0
  119. package/templates/asset-inspector/src/main.ts +23 -0
  120. package/templates/asset-inspector/tests/look-floor.ts +270 -0
  121. package/templates/{three-compat-character-viewer → asset-inspector}/tests/route-health.spec.ts +3 -3
  122. package/templates/asset-inspector/tests/screenshot.spec.ts +21 -0
  123. package/templates/asset-inspector/vite.config.ts +7 -0
  124. package/templates/character-controller/.claude/CLAUDE.md +3 -0
  125. package/templates/character-controller/.github/workflows/aura3d-lookdev.yml +67 -0
  126. package/templates/character-controller/AGENTS.md +3 -0
  127. package/templates/character-controller/README.md +7 -1
  128. package/templates/character-controller/aura.assets.json +29 -22
  129. package/templates/character-controller/index.html +4 -5
  130. package/templates/character-controller/package.json +5 -4
  131. package/templates/character-controller/public/aura-assets/auraclash-player-rig.glb +0 -0
  132. package/templates/character-controller/src/aura-assets.ts +25 -7
  133. package/templates/character-controller/src/main.ts +190 -47
  134. package/templates/character-controller/tests/certified-rig.spec.ts +2 -2
  135. package/templates/character-controller/tests/look-floor.ts +270 -0
  136. package/templates/character-controller/tests/route-health.spec.ts +31 -6
  137. package/templates/character-controller/tests/screenshot.spec.ts +17 -5
  138. package/templates/character-controller/vite.config.ts +7 -0
  139. package/templates/character-viewer/.claude/CLAUDE.md +3 -0
  140. package/templates/character-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  141. package/templates/character-viewer/AGENTS.md +3 -0
  142. package/templates/character-viewer/README.md +13 -0
  143. package/templates/character-viewer/aura.assets.json +24 -0
  144. package/templates/{three-compat-asset-inspector → character-viewer}/package.json +5 -3
  145. package/templates/character-viewer/public/aura-assets/kenneyMiniForestCharacterArcher.0fd0f92e.glb +0 -0
  146. package/templates/character-viewer/src/aura-assets.ts +16 -0
  147. package/templates/character-viewer/src/main.ts +21 -0
  148. package/templates/character-viewer/tests/look-floor.ts +270 -0
  149. package/templates/{three-compat-custom-threejs-migration → character-viewer}/tests/route-health.spec.ts +3 -3
  150. package/templates/character-viewer/tests/screenshot.spec.ts +21 -0
  151. package/templates/character-viewer/vite.config.ts +7 -0
  152. package/templates/cinematic-scene/.claude/CLAUDE.md +3 -0
  153. package/templates/cinematic-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  154. package/templates/cinematic-scene/AGENTS.md +3 -0
  155. package/templates/cinematic-scene/README.md +11 -2
  156. package/templates/cinematic-scene/package.json +3 -2
  157. package/templates/cinematic-scene/src/main.ts +30 -7
  158. package/templates/cinematic-scene/tests/look-floor.ts +270 -0
  159. package/templates/cinematic-scene/tests/route-health.spec.ts +31 -3
  160. package/templates/cinematic-scene/tests/screenshot.spec.ts +34 -45
  161. package/templates/cinematic-scene/vite.config.ts +7 -0
  162. package/templates/custom-scene/.claude/CLAUDE.md +3 -0
  163. package/templates/custom-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  164. package/templates/custom-scene/AGENTS.md +3 -0
  165. package/templates/custom-scene/README.md +13 -0
  166. package/templates/custom-scene/aura.assets.json +24 -0
  167. package/templates/{three-compat-character-viewer → custom-scene}/package.json +5 -3
  168. package/templates/custom-scene/public/aura-assets/kenneyPirateKitShipMedium.279ea019.glb +0 -0
  169. package/templates/custom-scene/src/aura-assets.ts +16 -0
  170. package/templates/custom-scene/src/main.ts +23 -0
  171. package/templates/custom-scene/tests/look-floor.ts +270 -0
  172. package/templates/{three-compat-asset-inspector → custom-scene}/tests/route-health.spec.ts +3 -3
  173. package/templates/custom-scene/tests/screenshot.spec.ts +21 -0
  174. package/templates/custom-scene/vite.config.ts +7 -0
  175. package/templates/episode-builder/.claude/CLAUDE.md +3 -0
  176. package/templates/episode-builder/.github/workflows/aura3d-lookdev.yml +67 -0
  177. package/templates/episode-builder/AGENTS.md +3 -0
  178. package/templates/episode-builder/package.json +3 -2
  179. package/templates/episode-builder/src/main.ts +5 -3
  180. package/templates/episode-builder/src/render-plan.ts +5 -3
  181. package/templates/episode-builder/tests/look-floor.ts +270 -0
  182. package/templates/episode-builder/tests/screenshot.spec.ts +10 -4
  183. package/templates/episode-builder/vite.config.ts +7 -0
  184. package/templates/falling-blocks-starter/.claude/CLAUDE.md +3 -0
  185. package/templates/falling-blocks-starter/.github/workflows/aura3d-lookdev.yml +67 -0
  186. package/templates/falling-blocks-starter/AGENTS.md +3 -0
  187. package/templates/falling-blocks-starter/README.md +10 -1
  188. package/templates/falling-blocks-starter/aura.assets.json +40 -23
  189. package/templates/falling-blocks-starter/package.json +3 -2
  190. package/templates/falling-blocks-starter/public/aura-assets/quaternius-cube-crate.glb +0 -0
  191. package/templates/falling-blocks-starter/public/aura-assets/quaternius-wall-modular.glb +0 -0
  192. package/templates/falling-blocks-starter/src/aura-assets.ts +33 -10
  193. package/templates/falling-blocks-starter/src/main.ts +316 -117
  194. package/templates/falling-blocks-starter/tests/look-floor.ts +270 -0
  195. package/templates/falling-blocks-starter/tests/playable.spec.ts +51 -18
  196. package/templates/falling-blocks-starter/tests/route-health.spec.ts +17 -7
  197. package/templates/falling-blocks-starter/tests/screenshot.spec.ts +21 -0
  198. package/templates/falling-blocks-starter/vite.config.ts +7 -0
  199. package/templates/fighting-game/.claude/CLAUDE.md +3 -0
  200. package/templates/fighting-game/.github/workflows/aura3d-lookdev.yml +67 -0
  201. package/templates/fighting-game/AGENTS.md +3 -0
  202. package/templates/fighting-game/README.md +2 -0
  203. package/templates/fighting-game/aura.assets.json +56 -46
  204. package/templates/fighting-game/package.json +4 -2
  205. package/templates/fighting-game/public/aura-assets/auraclash-player-rig.glb +0 -0
  206. package/templates/fighting-game/public/aura-assets/auraclash-rival-rig.glb +0 -0
  207. package/templates/fighting-game/src/aura-assets.ts +54 -20
  208. package/templates/fighting-game/src/game/fighters.ts +57 -12
  209. package/templates/fighting-game/src/main.ts +135 -53
  210. package/templates/fighting-game/tests/certified-rig.spec.ts +1 -1
  211. package/templates/fighting-game/tests/gameplay-smoke.spec.ts +2 -8
  212. package/templates/fighting-game/tests/look-floor.ts +270 -0
  213. package/templates/fighting-game/tests/route-health.spec.ts +12 -4
  214. package/templates/fighting-game/tests/screenshot.spec.ts +10 -4
  215. package/templates/fighting-game/vite.config.ts +7 -0
  216. package/templates/large-scene/.claude/CLAUDE.md +3 -0
  217. package/templates/large-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  218. package/templates/large-scene/AGENTS.md +3 -0
  219. package/templates/large-scene/README.md +13 -0
  220. package/templates/large-scene/aura.assets.json +40 -0
  221. package/templates/{three-compat-architecture-interior → large-scene}/package.json +5 -3
  222. package/templates/large-scene/public/aura-assets/kenneyMiniForestRocksHigh.226728a0.glb +0 -0
  223. package/templates/large-scene/public/aura-assets/kenneyMiniForestTree.0075d505.glb +0 -0
  224. package/templates/large-scene/src/aura-assets.ts +28 -0
  225. package/templates/large-scene/src/main.ts +43 -0
  226. package/templates/large-scene/tests/look-floor.ts +270 -0
  227. package/templates/large-scene/tests/route-health.spec.ts +16 -0
  228. package/templates/large-scene/tests/screenshot.spec.ts +21 -0
  229. package/templates/large-scene/vite.config.ts +7 -0
  230. package/templates/material-authoring/.claude/CLAUDE.md +3 -0
  231. package/templates/material-authoring/.github/workflows/aura3d-lookdev.yml +67 -0
  232. package/templates/material-authoring/AGENTS.md +3 -0
  233. package/templates/material-authoring/README.md +15 -0
  234. package/templates/material-authoring/aura.assets.json +24 -0
  235. package/templates/material-authoring/package.json +24 -0
  236. package/templates/material-authoring/public/aura-assets/kenneyCarKitSedanSports.2889c428.glb +0 -0
  237. package/templates/material-authoring/src/aura-assets.ts +16 -0
  238. package/templates/material-authoring/src/main.ts +78 -0
  239. package/templates/material-authoring/tests/look-floor.ts +270 -0
  240. package/templates/material-authoring/tests/route-health.spec.ts +16 -0
  241. package/templates/material-authoring/tests/screenshot.spec.ts +21 -0
  242. package/templates/material-authoring/vite.config.ts +7 -0
  243. package/templates/mini-game/.claude/CLAUDE.md +3 -0
  244. package/templates/mini-game/.github/workflows/aura3d-lookdev.yml +67 -0
  245. package/templates/mini-game/AGENTS.md +3 -0
  246. package/templates/mini-game/README.md +9 -6
  247. package/templates/mini-game/aura.assets.json +177 -36
  248. package/templates/mini-game/package.json +3 -1
  249. package/templates/mini-game/public/aura-assets/kaykit-barbarian.glb +0 -0
  250. package/templates/mini-game/public/aura-assets/kenneyPlatformerKitKey.2e930961.glb +0 -0
  251. package/templates/mini-game/public/aura-assets/kenneyPlatformerKitStar.a102a1b9.glb +0 -0
  252. package/templates/mini-game/public/aura-assets/quaternius-crate.glb +0 -0
  253. package/templates/mini-game/public/aura-assets/quaternius-cube-crate.glb +0 -0
  254. package/templates/mini-game/src/aura-assets.ts +140 -32
  255. package/templates/mini-game/src/main.ts +204 -71
  256. package/templates/mini-game/tests/certified-rig.spec.ts +2 -2
  257. package/templates/mini-game/tests/look-floor.ts +270 -0
  258. package/templates/mini-game/tests/playable.spec.ts +23 -2
  259. package/templates/mini-game/tests/route-health.spec.ts +2 -2
  260. package/templates/mini-game/tests/screenshot.spec.ts +44 -53
  261. package/templates/mini-game/vite.config.ts +7 -0
  262. package/templates/postprocess-scene/.claude/CLAUDE.md +3 -0
  263. package/templates/postprocess-scene/.github/workflows/aura3d-lookdev.yml +67 -0
  264. package/templates/postprocess-scene/AGENTS.md +3 -0
  265. package/templates/postprocess-scene/README.md +17 -0
  266. package/templates/postprocess-scene/aura.assets.json +38 -0
  267. package/templates/postprocess-scene/package.json +24 -0
  268. package/templates/postprocess-scene/public/aura-assets/showcaseLuma.ef389217.glb +0 -0
  269. package/templates/postprocess-scene/public/aura-assets/showcaseMiko.fcdc2b75.glb +0 -0
  270. package/templates/postprocess-scene/src/aura-assets.ts +28 -0
  271. package/templates/postprocess-scene/src/main.ts +29 -0
  272. package/templates/postprocess-scene/tests/look-floor.ts +270 -0
  273. package/templates/postprocess-scene/tests/route-health.spec.ts +16 -0
  274. package/templates/postprocess-scene/tests/screenshot.spec.ts +21 -0
  275. package/templates/postprocess-scene/vite.config.ts +7 -0
  276. package/templates/premium-product-viewer/.claude/CLAUDE.md +3 -0
  277. package/templates/premium-product-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  278. package/templates/premium-product-viewer/AGENTS.md +3 -0
  279. package/templates/premium-product-viewer/README.md +18 -0
  280. package/templates/premium-product-viewer/aura.assets.json +24 -0
  281. package/templates/premium-product-viewer/package.json +24 -0
  282. package/templates/premium-product-viewer/public/aura-assets/khronosToyCar.01a60862.glb +0 -0
  283. package/templates/premium-product-viewer/src/aura-assets.ts +16 -0
  284. package/templates/premium-product-viewer/src/main.ts +33 -0
  285. package/templates/premium-product-viewer/tests/look-floor.ts +270 -0
  286. package/templates/premium-product-viewer/tests/route-health.spec.ts +16 -0
  287. package/templates/premium-product-viewer/tests/screenshot.spec.ts +21 -0
  288. package/templates/premium-product-viewer/vite.config.ts +7 -0
  289. package/templates/product-viewer/.claude/CLAUDE.md +3 -0
  290. package/templates/product-viewer/.github/workflows/aura3d-lookdev.yml +67 -0
  291. package/templates/product-viewer/AGENTS.md +3 -0
  292. package/templates/product-viewer/README.md +10 -8
  293. package/templates/product-viewer/package.json +3 -1
  294. package/templates/product-viewer/src/aura-assets.ts +1 -1
  295. package/templates/product-viewer/src/main.ts +78 -19
  296. package/templates/product-viewer/tests/look-floor.ts +270 -0
  297. package/templates/product-viewer/tests/route-health.spec.ts +19 -1
  298. package/templates/product-viewer/tests/screenshot.spec.ts +35 -41
  299. package/templates/product-viewer/vite.config.ts +7 -0
  300. package/templates/prompt-animation-channel/.claude/CLAUDE.md +3 -0
  301. package/templates/prompt-animation-channel/.github/workflows/aura3d-lookdev.yml +67 -0
  302. package/templates/prompt-animation-channel/AGENTS.md +3 -0
  303. package/templates/prompt-animation-channel/package.json +3 -2
  304. package/templates/prompt-animation-channel/src/main.ts +5 -3
  305. package/templates/prompt-animation-channel/src/render-plan.ts +5 -3
  306. package/templates/prompt-animation-channel/tests/look-floor.ts +270 -0
  307. package/templates/prompt-animation-channel/tests/route-health.spec.ts +1 -1
  308. package/templates/prompt-animation-channel/tests/screenshot.spec.ts +10 -4
  309. package/templates/prompt-animation-channel/tests/storyboard-playback.spec.ts +1 -1
  310. package/templates/prompt-animation-channel/vite.config.ts +7 -0
  311. package/templates/racing-starter/.claude/CLAUDE.md +3 -0
  312. package/templates/racing-starter/.github/workflows/aura3d-lookdev.yml +67 -0
  313. package/templates/racing-starter/AGENTS.md +3 -0
  314. package/templates/racing-starter/README.md +8 -6
  315. package/templates/racing-starter/package.json +3 -2
  316. package/templates/racing-starter/src/main.ts +168 -146
  317. package/templates/racing-starter/tests/geometry-certification.json +34 -0
  318. package/templates/racing-starter/tests/look-floor.ts +270 -0
  319. package/templates/racing-starter/tests/playable.spec.ts +63 -80
  320. package/templates/racing-starter/tests/route-health.spec.ts +14 -12
  321. package/templates/racing-starter/tests/screenshot.spec.ts +21 -0
  322. package/templates/racing-starter/vite.config.ts +7 -0
  323. package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
  324. package/templates/character-controller/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +0 -1
  325. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.679d52fe.glb +0 -0
  326. package/templates/falling-blocks-starter/public/aura-assets/showcaseBlockfallCabinet.thumb.svg +0 -1
  327. package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.252b3a16.glb +0 -0
  328. package/templates/fighting-game/public/aura-assets/showcaseRunnerRobot.thumb.svg +0 -1
  329. package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.93872fc2.glb +0 -0
  330. package/templates/fighting-game/public/aura-assets/showcaseWalkAnimatedGirl.thumb.svg +0 -1
  331. package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.3f821141.glb +0 -0
  332. package/templates/mini-game/public/aura-assets/showcaseKenneyOobiPlatformerHero.thumb.svg +0 -1
  333. package/templates/three-compat-architecture-interior/aura.assets.json +0 -7
  334. package/templates/three-compat-architecture-interior/src/aura-assets.ts +0 -3
  335. package/templates/three-compat-architecture-interior/src/main.ts +0 -27
  336. package/templates/three-compat-architecture-interior/tests/screenshot.spec.ts +0 -15
  337. package/templates/three-compat-asset-inspector/aura.assets.json +0 -7
  338. package/templates/three-compat-asset-inspector/src/aura-assets.ts +0 -3
  339. package/templates/three-compat-asset-inspector/src/main.ts +0 -17
  340. package/templates/three-compat-asset-inspector/tests/screenshot.spec.ts +0 -15
  341. package/templates/three-compat-character-viewer/aura.assets.json +0 -7
  342. package/templates/three-compat-character-viewer/src/aura-assets.ts +0 -3
  343. package/templates/three-compat-character-viewer/src/main.ts +0 -17
  344. package/templates/three-compat-character-viewer/tests/screenshot.spec.ts +0 -15
  345. package/templates/three-compat-custom-threejs-migration/aura.assets.json +0 -7
  346. package/templates/three-compat-custom-threejs-migration/src/aura-assets.ts +0 -3
  347. package/templates/three-compat-custom-threejs-migration/src/main.ts +0 -16
  348. package/templates/three-compat-custom-threejs-migration/tests/screenshot.spec.ts +0 -15
  349. package/templates/three-compat-large-scene/aura.assets.json +0 -7
  350. package/templates/three-compat-large-scene/package.json +0 -22
  351. package/templates/three-compat-large-scene/src/aura-assets.ts +0 -3
  352. package/templates/three-compat-large-scene/src/main.ts +0 -31
  353. package/templates/three-compat-large-scene/tests/route-health.spec.ts +0 -16
  354. package/templates/three-compat-large-scene/tests/screenshot.spec.ts +0 -15
  355. package/templates/three-compat-material-authoring/aura.assets.json +0 -7
  356. package/templates/three-compat-material-authoring/package.json +0 -22
  357. package/templates/three-compat-material-authoring/src/aura-assets.ts +0 -3
  358. package/templates/three-compat-material-authoring/src/main.ts +0 -33
  359. package/templates/three-compat-material-authoring/tests/route-health.spec.ts +0 -16
  360. package/templates/three-compat-material-authoring/tests/screenshot.spec.ts +0 -15
  361. package/templates/three-compat-postprocess-scene/aura.assets.json +0 -7
  362. package/templates/three-compat-postprocess-scene/package.json +0 -22
  363. package/templates/three-compat-postprocess-scene/src/aura-assets.ts +0 -3
  364. package/templates/three-compat-postprocess-scene/src/main.ts +0 -17
  365. package/templates/three-compat-postprocess-scene/tests/route-health.spec.ts +0 -16
  366. package/templates/three-compat-postprocess-scene/tests/screenshot.spec.ts +0 -15
  367. package/templates/three-compat-premium-product-viewer/aura.assets.json +0 -7
  368. package/templates/three-compat-premium-product-viewer/package.json +0 -22
  369. package/templates/three-compat-premium-product-viewer/src/aura-assets.ts +0 -3
  370. package/templates/three-compat-premium-product-viewer/src/main.ts +0 -18
  371. package/templates/three-compat-premium-product-viewer/tests/route-health.spec.ts +0 -16
  372. package/templates/three-compat-premium-product-viewer/tests/screenshot.spec.ts +0 -15
  373. /package/templates/{three-compat-architecture-interior → architecture-interior}/index.html +0 -0
  374. /package/templates/{three-compat-architecture-interior → architecture-interior}/playwright.config.ts +0 -0
  375. /package/templates/{three-compat-architecture-interior → architecture-interior}/tsconfig.json +0 -0
  376. /package/templates/{three-compat-asset-inspector → asset-inspector}/index.html +0 -0
  377. /package/templates/{three-compat-asset-inspector → asset-inspector}/playwright.config.ts +0 -0
  378. /package/templates/{three-compat-asset-inspector → asset-inspector}/tsconfig.json +0 -0
  379. /package/templates/{three-compat-character-viewer → character-viewer}/index.html +0 -0
  380. /package/templates/{three-compat-character-viewer → character-viewer}/playwright.config.ts +0 -0
  381. /package/templates/{three-compat-character-viewer → character-viewer}/tsconfig.json +0 -0
  382. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/index.html +0 -0
  383. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/playwright.config.ts +0 -0
  384. /package/templates/{three-compat-custom-threejs-migration → custom-scene}/tsconfig.json +0 -0
  385. /package/templates/{three-compat-large-scene → large-scene}/index.html +0 -0
  386. /package/templates/{three-compat-large-scene → large-scene}/playwright.config.ts +0 -0
  387. /package/templates/{three-compat-large-scene → large-scene}/tsconfig.json +0 -0
  388. /package/templates/{three-compat-material-authoring → material-authoring}/index.html +0 -0
  389. /package/templates/{three-compat-material-authoring → material-authoring}/playwright.config.ts +0 -0
  390. /package/templates/{three-compat-material-authoring → material-authoring}/tsconfig.json +0 -0
  391. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/index.html +0 -0
  392. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/playwright.config.ts +0 -0
  393. /package/templates/{three-compat-postprocess-scene → postprocess-scene}/tsconfig.json +0 -0
  394. /package/templates/{three-compat-premium-product-viewer → premium-product-viewer}/index.html +0 -0
  395. /package/templates/{three-compat-premium-product-viewer → premium-product-viewer}/playwright.config.ts +0 -0
  396. /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,35 +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 evidenceMode = navigator.webdriver;
132
- const app = createAuraApp("#app", {
133
- autoStart: !evidenceMode,
166
+ const blockfallGame = createGame({
167
+ id: "falling-blocks-starter",
168
+ target: "#app",
134
169
  diagnostics: { overlay: true, performancePanel: true },
135
- 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
+ }
136
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.");
137
201
 
138
- const boardNodes = Array.from({ length: boardHeight - hiddenRows }, (_, rowIndex) =>
139
- Array.from({ length: boardWidth }, (_, x) => app.nodes.require(`board-cell-${x}-${rowIndex + hiddenRows}`))
140
- );
141
- const activeNodes = Array.from({ length: 4 }, (_, index) => app.nodes.require(`active-cell-${index}`));
142
- 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();
143
208
  const hudRoot = createHud();
144
209
  let objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
145
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;
146
216
 
147
- app.onFrame(({ dt }: { readonly dt: number }) => {
148
- 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);
149
232
  if (input.pressed("reset")) {
150
233
  setupPracticeBoard();
234
+ flashRows = { rows: [], until: 0 };
151
235
  routeEvents.push({ type: "reset", label: "reset" });
152
236
  objective = "Clear the prepared line. Move, rotate, hold, or hard drop.";
153
237
  }
@@ -166,36 +250,19 @@ app.onFrame(({ dt }: { readonly dt: number }) => {
166
250
  }
167
251
 
168
252
  const state = falling.snapshot();
253
+ rebuildSettledCells(state);
169
254
  renderBoard(state);
170
255
  renderHud(state);
171
256
  publishEvidence(state);
257
+ previousBoard = state.board;
172
258
  });
173
259
 
174
260
  renderBoard(falling.snapshot());
175
261
  renderHud(falling.snapshot());
176
262
  publishEvidence(falling.snapshot());
177
263
 
178
- // Keep browser evidence deterministic on software GPUs without replacing real
179
- // keyboard input. The input controller receives each DOM event first; this
180
- // handler then advances the same app.onFrame gameplay callback and presents one
181
- // completed frame. Generated apps outside WebDriver retain continuous playback.
182
- if (evidenceMode) {
183
- await app.ready();
184
- // Complete one real production frame for the exact-installed visual gate.
185
- // Software renderers may take seconds to drain this typed GLB scene, so keep
186
- // later input-contract reads free of synchronous GPU submissions.
187
- await app.stepAsync(0);
188
- const advanceFromKeyboard = () => {
189
- // Advance the real input and gameplay callback. The playable test reads
190
- // simulation-owned evidence; rendering again for every key transition only
191
- // blocks the browser main thread without adding gameplay coverage.
192
- app.advance(1 / 60);
193
- };
194
- window.addEventListener("keydown", advanceFromKeyboard);
195
- window.addEventListener("keyup", advanceFromKeyboard);
196
- }
197
264
 
198
- function recordKitEvents(state: ReturnType<typeof falling.snapshot>): void {
265
+ function recordKitEvents(state: KitState): void {
199
266
  for (const event of state.events) {
200
267
  routeEvents.push({
201
268
  type: event.type,
@@ -203,10 +270,21 @@ function recordKitEvents(state: ReturnType<typeof falling.snapshot>): void {
203
270
  severity: event.type === "line-clear" ? "success" : event.type === "game-over" ? "warning" : "info",
204
271
  frame: event.frame
205
272
  });
206
- 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
+ }
207
280
  if (event.type === "hold") objective = "Held piece. Press R or continue.";
208
281
  if (event.type === "rotate") objective = "Rotation accepted.";
209
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
+ }
210
288
  }
211
289
  }
212
290
 
@@ -224,85 +302,207 @@ function createPracticeBoard(): Cell[][] {
224
302
  return board;
225
303
  }
226
304
 
227
- function buildScene() {
305
+ function buildScene(board: Board) {
228
306
  const nodes: AuraNodeInput[] = [
229
- model(assets.cabinetModel, { name: "typed arcade cabinet", castShadow: true })
230
- .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)
231
311
  .scale(0.2),
232
- primitives.box({ name: "falling blocks board backplate", material: material.pbr({ color: "#172027", roughness: 0.82, metallic: 0.04 }) })
233
- .position(0, 0, -0.08)
234
- .scale([2.76, 5.25, 0.08]),
235
- primitives.box({ name: "falling blocks board frame", material: material.neon({ color: "#8ee8d5", emissive: "#8ee8d5", emissiveIntensity: 0.22 }) })
236
- .position(0, 0, -0.02)
237
- .scale([2.92, 5.42, 0.04]),
238
- ...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),
239
318
  ...activeCellNodes(),
240
- primitives.box({ name: "hold preview", material: material.neon({ color: "#b987d0", emissive: "#b987d0", emissiveIntensity: 0.38 }) })
241
- .position(-2.02, 1.92, 0.08)
242
- .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))
243
323
  .runtime(game.runtimeNode("hold-preview", { tags: ["hold", "runtime"] }))
244
324
  ];
245
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.
246
328
  return scene()
247
- .background("#06090d")
329
+ .add(looks.preset(LOOK_ID))
248
330
  .addMany(nodes)
249
- .add(lights.ambient({ name: "blockfall ambient", intensity: 0.36, color: "#e8fbff" }))
250
- .add(lights.directional({ name: "blockfall key", position: [3.8, 6.2, 5], intensity: 1.15, color: "#ffffff" }))
251
- .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 }));
252
332
  }
253
333
 
254
- function boardCellNodes(): AuraNodeInput[] {
255
- 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[] = [];
256
359
  for (let y = hiddenRows; y < boardHeight; y += 1) {
257
360
  for (let x = 0; x < boardWidth; x += 1) {
258
- nodes.push(
259
- primitives.box({ name: `board cell ${x} ${y}`, material: material.pbr({ color: "#dccf78", roughness: 0.62, metallic: 0.02 }) })
260
- .position(...cellPosition(x, y), 0)
261
- .scale([cellSize * 0.86, cellSize * 0.86, 0.12])
262
- .runtime(game.runtimeNode(`board-cell-${x}-${y}`, { tags: ["board-cell", "runtime"] }))
263
- );
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]);
264
366
  }
265
367
  }
266
- 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
+ ];
267
382
  }
268
383
 
269
384
  function activeCellNodes(): AuraNodeInput[] {
270
385
  return Array.from({ length: 4 }, (_, index) =>
271
- primitives.box({ name: `active piece cell ${index}`, material: material.neon({ color: "#5ed7df", emissive: "#5ed7df", emissiveIntensity: 0.4 }) })
272
- .position(-3, -3, 0.12)
273
- .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)
274
389
  .runtime(game.runtimeNode(`active-cell-${index}`, { tags: ["active-cell", "runtime"] }))
275
390
  );
276
391
  }
277
392
 
278
- 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[] = [];
279
429
  for (let y = hiddenRows; y < boardHeight; y += 1) {
280
430
  for (let x = 0; x < boardWidth; x += 1) {
281
- const node = boardNodes[y - hiddenRows][x];
282
- const cell = state.board[y][x];
283
- node.setVisible(Boolean(cell)).setPosition(...cellPosition(x, y), 0);
431
+ const cell = board[y]?.[x];
432
+ if (cell) cells.push(`${x}:${y}:${cell}`);
284
433
  }
285
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
+ }
286
445
 
287
- activeNodes.forEach((node) => node.setVisible(false));
446
+ function filledCellCount(board: Board): number {
447
+ let count = 0;
448
+ for (let y = hiddenRows; y < boardHeight; y += 1) {
449
+ for (let x = 0; x < boardWidth; x += 1) {
450
+ if (board[y]?.[x]) count += 1;
451
+ }
452
+ }
453
+ return count;
454
+ }
455
+
456
+ function renderBoard(state: KitState): void {
288
457
  if (state.active) {
289
- const cells = activeShapes[state.active.kind][state.active.rotation] ?? activeShapes[state.active.kind][0];
290
- 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
+ }
291
471
  const x = state.active!.x + cell.x;
292
472
  const y = state.active!.y + cell.y;
293
- activeNodes[index]
294
- .setVisible(y >= hiddenRows)
295
- .setPosition(...cellPosition(x, y), 0.14)
296
- .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);
297
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);
298
499
  }
299
- holdNode.setVisible(Boolean(state.hold));
300
500
  }
301
501
 
302
502
  function cellPosition(x: number, y: number): [number, number] {
303
503
  return [
304
504
  boardOrigin.x + (x + 0.5) * cellSize,
305
- boardOrigin.y + (boardHeight - hiddenRows - (y - hiddenRows) - 0.5) * cellSize
505
+ boardOrigin.y + (visibleRows - (y - hiddenRows) - 0.5) * cellSize
306
506
  ];
307
507
  }
308
508
 
@@ -317,8 +517,8 @@ function createHud(): HTMLElement {
317
517
  "min-width:310px",
318
518
  "font:600 13px/1.35 Inter, system-ui, sans-serif",
319
519
  "color:#f5fbff",
320
- "background:rgba(3,9,14,0.78)",
321
- "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)",
322
522
  "border-radius:8px",
323
523
  "padding:12px",
324
524
  "pointer-events:none"
@@ -327,7 +527,7 @@ function createHud(): HTMLElement {
327
527
  return root;
328
528
  }
329
529
 
330
- function renderHud(state: ReturnType<typeof falling.snapshot>): void {
530
+ function renderHud(state: KitState): void {
331
531
  hudRoot.innerHTML = [
332
532
  `<strong>Aura3D Falling Blocks Starter</strong>`,
333
533
  `<div>Score ${state.score} | Lines ${state.lines} | Level ${state.level}</div>`,
@@ -337,11 +537,11 @@ function renderHud(state: ReturnType<typeof falling.snapshot>): void {
337
537
  ].join("");
338
538
  }
339
539
 
340
- function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
540
+ function publishEvidence(state: KitState): void {
341
541
  const evidence = app.evidence({
342
542
  input,
343
543
  events: routeEvents,
344
- hud,
544
+ hud: hudBindings,
345
545
  appState: {
346
546
  score: state.score,
347
547
  objective,
@@ -353,6 +553,7 @@ function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
353
553
  },
354
554
  source: { expectsGame: true }
355
555
  });
556
+ const diagnostics = app.diagnostics();
356
557
  window.__AURA3D_FALLING_BLOCKS_STARTER__ = {
357
558
  frame: state.frame,
358
559
  score: state.score,
@@ -362,19 +563,17 @@ function publishEvidence(state: ReturnType<typeof falling.snapshot>): void {
362
563
  hold: state.hold,
363
564
  gameOver: state.gameOver,
364
565
  events: routeEvents.events().map((event: { readonly label: string }) => event.label),
365
- 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 : [] },
366
577
  evidence
367
578
  };
368
579
  }
369
-
370
- function createLineClearProof(): FallingBlocksStarterEvidence["lineClearProof"] {
371
- const proof = game.fallingBlocks({ width: boardWidth, height: boardHeight, hiddenRows, seed: 7, gravityFrames: 999 });
372
- proof.setBoard(createPracticeBoard());
373
- proof.setActive({ kind: "I", x: 3, y: boardHeight - 3, rotation: 0 });
374
- const cleared = proof.hardDrop();
375
- return {
376
- lines: cleared.lines,
377
- events: cleared.events.map((event) => event.piece ? `${event.type}:${event.piece}` : event.type),
378
- checksum: cleared.checksum
379
- };
380
- }