@auraindustry/aurajs 0.0.7 → 0.1.1

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 (334) hide show
  1. package/README.md +98 -2
  2. package/benchmarks/perf-thresholds.json +54 -0
  3. package/package.json +4 -7
  4. package/src/asset-pack.mjs +8 -1
  5. package/src/authored-project.mjs +1449 -0
  6. package/src/authored-runtime.mjs +2016 -0
  7. package/src/authoring/avatar-animation-graph.mjs +648 -0
  8. package/src/bin-integrity.mjs +272 -0
  9. package/src/build-contract/assets.mjs +130 -0
  10. package/src/build-contract/capabilities.mjs +116 -0
  11. package/src/build-contract/constants.mjs +6 -0
  12. package/src/build-contract/helpers.mjs +44 -0
  13. package/src/build-contract/web-templates.mjs +5993 -0
  14. package/src/build-contract.mjs +27 -2910
  15. package/src/bundler.mjs +188 -55
  16. package/src/cli.mjs +4840 -1512
  17. package/src/commands/project-authoring.mjs +454 -0
  18. package/src/config.mjs +44 -0
  19. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
  20. package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
  21. package/src/conformance/cases/index.mjs +11 -0
  22. package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
  23. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
  24. package/src/conformance/shared.mjs +27 -0
  25. package/src/conformance-runner.mjs +25 -13
  26. package/src/conformance.mjs +619 -4020
  27. package/src/cutscene.mjs +362 -5
  28. package/src/dev-cli-action.mjs +249 -0
  29. package/src/dev-cli-inspect.mjs +92 -0
  30. package/src/dev-cli-state.mjs +80 -0
  31. package/src/external-asset-cache.mjs +587 -0
  32. package/src/external-asset-policy.mjs +217 -0
  33. package/src/external-package-surface.mjs +206 -0
  34. package/src/game-action-runtime.mjs +869 -0
  35. package/src/game-state-runtime.mjs +206 -6
  36. package/src/headless-action.mjs +186 -0
  37. package/src/headless-test/runtime-animation.mjs +1173 -0
  38. package/src/headless-test/runtime-coordinator.mjs +1514 -0
  39. package/src/headless-test/runtime-primitives.mjs +320 -0
  40. package/src/headless-test/runtime-world.mjs +2253 -0
  41. package/src/headless-test.mjs +392 -4298
  42. package/src/host-binary.mjs +342 -14
  43. package/src/icon-discovery.mjs +64 -0
  44. package/src/make-catalog.mjs +109 -0
  45. package/src/make.mjs +197 -0
  46. package/src/package-integrity.mjs +586 -0
  47. package/src/perf-benchmark.mjs +353 -0
  48. package/src/postinstall.mjs +5 -5
  49. package/src/prefabs/index.mjs +34 -0
  50. package/src/prefabs/scene-serialization.mjs +184 -0
  51. package/src/project-importer.mjs +620 -0
  52. package/src/project-registry.mjs +24 -0
  53. package/src/publish-command.mjs +195 -0
  54. package/src/publish-env-example.mjs +83 -0
  55. package/src/publish-validation.mjs +708 -0
  56. package/src/retro/assets/compile.mjs +232 -0
  57. package/src/retro/backend-gba/authoring.mjs +1029 -0
  58. package/src/retro/backend-gba/rom.mjs +363 -0
  59. package/src/retro/backend-gbc/rom.mjs +85 -0
  60. package/src/retro/build.mjs +278 -0
  61. package/src/retro/cli/commands.mjs +292 -0
  62. package/src/retro/cli/templates.mjs +84 -0
  63. package/src/retro/diagnostics/catalog.mjs +110 -0
  64. package/src/retro/diagnostics/emit.mjs +72 -0
  65. package/src/retro/emulator/case-overlay.mjs +64 -0
  66. package/src/retro/emulator/discovery.mjs +158 -0
  67. package/src/retro/emulator/macos-case-overlay.swift +220 -0
  68. package/src/retro/emulator/profiles.mjs +146 -0
  69. package/src/retro/emulator/runner.mjs +289 -0
  70. package/src/retro/frontend/load-project.mjs +98 -0
  71. package/src/retro/index.mjs +30 -0
  72. package/src/retro/ir/build-ir.mjs +108 -0
  73. package/src/retro/runtime-gba/contract.mjs +151 -0
  74. package/src/retro/runtime-gbc/contract.mjs +117 -0
  75. package/src/retro/shared/span.mjs +26 -0
  76. package/src/retro/shared/targets.mjs +64 -0
  77. package/src/retro/validator/check-project.mjs +114 -0
  78. package/src/runtime-hotspot-audit.mjs +707 -0
  79. package/src/scaffold/config.mjs +1000 -0
  80. package/src/scaffold/fs.mjs +56 -0
  81. package/src/scaffold/layout.mjs +318 -0
  82. package/src/scaffold/project-docs.mjs +439 -0
  83. package/src/scaffold.mjs +93 -596
  84. package/src/scene-composition/index.mjs +326 -0
  85. package/src/scene-composition/runtime.mjs +751 -0
  86. package/src/self-hosted-assets.mjs +604 -0
  87. package/src/session-client.mjs +750 -0
  88. package/src/session-native-launcher.mjs +74 -0
  89. package/src/session-protocol.mjs +75 -0
  90. package/src/session-runtime.mjs +321 -0
  91. package/src/session-server.mjs +360 -0
  92. package/src/shader-kits/index.mjs +773 -0
  93. package/src/starter-content-registry.mjs +292 -0
  94. package/src/state-artifacts.mjs +662 -24
  95. package/src/state-dev-reload.mjs +99 -2
  96. package/src/terminal-ui.mjs +245 -0
  97. package/src/web-conformance.mjs +219 -0
  98. package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
  99. package/templates/create/2d/content/gameplay/waves.json +26 -0
  100. package/templates/create/2d/content/registries/.gitkeep +1 -0
  101. package/templates/create/2d/docs/design/.gitkeep +1 -0
  102. package/templates/create/2d/docs/design/loop.md +5 -0
  103. package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
  104. package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
  105. package/templates/create/2d/prefabs/player.prefab.js +36 -0
  106. package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
  107. package/templates/create/2d/scenes/boot.scene.js +12 -0
  108. package/templates/create/2d/scenes/gameplay.scene.js +230 -0
  109. package/templates/create/2d/scenes/menu.scene.js +9 -0
  110. package/templates/create/2d/src/main.js +6 -185
  111. package/templates/create/2d/src/runtime/app.js +49 -0
  112. package/templates/create/2d/src/runtime/capabilities.js +35 -0
  113. package/templates/create/2d/ui/hud.screen.js +40 -0
  114. package/templates/create/2d/ui/pause.screen.js +149 -0
  115. package/templates/create/2d/ui/settings.screen.js +347 -0
  116. package/templates/create/2d/ui/title.screen.js +13 -0
  117. package/templates/create/2d-adventure/aura.config.json +28 -0
  118. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
  119. package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
  120. package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
  121. package/templates/create/2d-adventure/docs/design/loop.md +5 -0
  122. package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
  123. package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
  124. package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
  125. package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
  126. package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
  127. package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
  128. package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
  129. package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
  130. package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
  131. package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
  132. package/templates/create/2d-survivor/docs/design/loop.md +5 -0
  133. package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
  134. package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
  135. package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
  136. package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
  137. package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
  138. package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
  139. package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
  140. package/templates/create/2d-survivor/src/main.js +5 -332
  141. package/templates/create/2d-survivor/src/runtime/app.js +49 -0
  142. package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
  143. package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
  144. package/templates/create/2d-survivor/ui/title.screen.js +13 -0
  145. package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
  146. package/templates/create/3d/config/gameplay/.gitkeep +1 -0
  147. package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
  148. package/templates/create/3d/content/gameplay/course.js +40 -0
  149. package/templates/create/3d/content/registries/.gitkeep +1 -0
  150. package/templates/create/3d/docs/design/.gitkeep +1 -0
  151. package/templates/create/3d/docs/design/loop.md +5 -0
  152. package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
  153. package/templates/create/3d/prefabs/player.prefab.js +204 -0
  154. package/templates/create/3d/prefabs/world.prefab.js +112 -0
  155. package/templates/create/3d/scenes/boot.scene.js +12 -0
  156. package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
  157. package/templates/create/3d/scenes/gameplay.scene.js +292 -0
  158. package/templates/create/3d/src/main.js +6 -295
  159. package/templates/create/3d/src/runtime/app.js +49 -0
  160. package/templates/create/3d/src/runtime/capabilities.js +53 -0
  161. package/templates/create/3d/src/runtime/materials.js +34 -0
  162. package/templates/create/3d/src/runtime/state.js +39 -0
  163. package/templates/create/3d/ui/hud.screen.js +75 -0
  164. package/templates/create/3d/ui/pause.screen.js +166 -0
  165. package/templates/create/3d/ui/settings.screen.js +387 -0
  166. package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
  167. package/templates/create/3d-adventure/aura.config.json +28 -0
  168. package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
  169. package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
  170. package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
  171. package/templates/create/3d-adventure/docs/design/loop.md +5 -0
  172. package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
  173. package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
  174. package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
  175. package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
  176. package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
  177. package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
  178. package/templates/create/3d-adventure/src/runtime/state.js +31 -0
  179. package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
  180. package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
  181. package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
  182. package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
  183. package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
  184. package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
  185. package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
  186. package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
  187. package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
  188. package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
  189. package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
  190. package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
  191. package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
  192. package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
  193. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
  194. package/templates/create/3d-collectathon/src/main.js +5 -355
  195. package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
  196. package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
  197. package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
  198. package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
  199. package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
  200. package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
  201. package/templates/create/blank/config/gameplay/.gitkeep +1 -0
  202. package/templates/create/blank/content/gameplay/.gitkeep +1 -0
  203. package/templates/create/blank/content/registries/.gitkeep +1 -0
  204. package/templates/create/blank/docs/design/.gitkeep +1 -0
  205. package/templates/create/blank/docs/design/loop.md +5 -0
  206. package/templates/create/blank/prefabs/.gitkeep +1 -0
  207. package/templates/create/blank/scenes/.gitkeep +1 -0
  208. package/templates/create/blank/src/runtime/.gitkeep +1 -0
  209. package/templates/create/blank/ui/.gitkeep +1 -0
  210. package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
  211. package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
  212. package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
  213. package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
  214. package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
  215. package/templates/create/deckbuilder-2d/aura.config.json +28 -0
  216. package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
  217. package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
  218. package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
  219. package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
  220. package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
  221. package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
  222. package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
  223. package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
  224. package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
  225. package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
  226. package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
  227. package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
  228. package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
  229. package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
  230. package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
  231. package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
  232. package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
  233. package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
  234. package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
  235. package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
  236. package/templates/create/deckbuilder-2d/src/main.js +17 -0
  237. package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
  238. package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
  239. package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
  240. package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
  241. package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
  242. package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
  243. package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
  244. package/templates/create/local-multiplayer/aura.config.json +41 -0
  245. package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
  246. package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
  247. package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
  248. package/templates/create/local-multiplayer/docs/design/loop.md +16 -0
  249. package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
  250. package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
  251. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +472 -0
  252. package/templates/create/local-multiplayer/src/main.js +17 -0
  253. package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
  254. package/templates/create/local-multiplayer/ui/hud.screen.js +65 -0
  255. package/templates/create/shared/src/runtime/project-inspector.js +105 -0
  256. package/templates/create/shared/src/runtime/scene-flow.js +290 -0
  257. package/templates/create/shared/src/runtime/screen-shell.js +222 -0
  258. package/templates/create/shared/src/runtime/ui-forms.js +209 -0
  259. package/templates/create/shared/src/runtime/ui-settings.js +237 -0
  260. package/templates/create/shared/src/runtime/ui-theme.js +352 -0
  261. package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
  262. package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
  263. package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
  264. package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
  265. package/templates/create/shared/src/starter-utils/core.js +39 -3
  266. package/templates/create/shared/src/starter-utils/index.js +8 -2
  267. package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
  268. package/templates/create/shared/src/starter-utils/triggers.js +662 -0
  269. package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
  270. package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
  271. package/templates/create/video-cutscene/aura.config.json +28 -0
  272. package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
  273. package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
  274. package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
  275. package/templates/create/video-cutscene/docs/design/loop.md +22 -0
  276. package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
  277. package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
  278. package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
  279. package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
  280. package/templates/create/video-cutscene/src/main.js +17 -0
  281. package/templates/create/video-cutscene/src/runtime/app.js +52 -0
  282. package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
  283. package/templates/create/video-cutscene/src/runtime/state.js +13 -0
  284. package/templates/create/video-cutscene/ui/.gitkeep +0 -0
  285. package/templates/create-bin/play.js +1192 -0
  286. package/templates/make/README.md +46 -0
  287. package/templates/make/catalog.json +51 -0
  288. package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
  289. package/templates/make/component/manifest.json +9 -0
  290. package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
  291. package/templates/make/data/manifest.json +9 -0
  292. package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
  293. package/templates/make/material/manifest.json +9 -0
  294. package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
  295. package/templates/make/prefab/manifest.json +9 -0
  296. package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
  297. package/templates/make/scene/manifest.json +9 -0
  298. package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
  299. package/templates/make/shader/manifest.json +9 -0
  300. package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
  301. package/templates/make/system/manifest.json +9 -0
  302. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
  303. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
  304. package/templates/make/ui-screen/manifest.json +10 -0
  305. package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
  306. package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
  307. package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
  308. package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
  309. package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
  310. package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
  311. package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
  312. package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
  313. package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
  314. package/templates/retro/platformer/README.md +10 -0
  315. package/templates/retro/platformer/assets/retro/assets.json +91 -0
  316. package/templates/retro/platformer/aura.config.json +7 -0
  317. package/templates/retro/platformer/package.json +5 -0
  318. package/templates/retro/platformer/src/main.js +40 -0
  319. package/templates/retro/puzzle-grid/README.md +10 -0
  320. package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
  321. package/templates/retro/puzzle-grid/aura.config.json +7 -0
  322. package/templates/retro/puzzle-grid/package.json +5 -0
  323. package/templates/retro/puzzle-grid/src/main.js +29 -0
  324. package/templates/retro/tactics-grid/README.md +10 -0
  325. package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
  326. package/templates/retro/tactics-grid/aura.config.json +7 -0
  327. package/templates/retro/tactics-grid/package.json +5 -0
  328. package/templates/retro/tactics-grid/src/main.js +35 -0
  329. package/templates/retro/topdown-adventure/README.md +10 -0
  330. package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
  331. package/templates/retro/topdown-adventure/aura.config.json +7 -0
  332. package/templates/retro/topdown-adventure/package.json +5 -0
  333. package/templates/retro/topdown-adventure/src/main.js +29 -0
  334. package/templates/skills/aurajs/SKILL.md +61 -5
@@ -0,0 +1,1776 @@
1
+ export const SYSTEMS_AND_GAMEPLAY_CONFORMANCE_CASES = [
2
+ {
3
+ id: 'optional-module-physics-disabled',
4
+ modes: ['native'],
5
+ namespaces: ['physics'],
6
+ functions: ['aura.physics.configureStep', 'aura.physics.step', 'aura.physics.queryPoint', 'aura.physics.queryAABB', 'aura.physics.queryPointDetailed', 'aura.physics.queryOverlap', 'aura.physics.queryRaycast'],
7
+ optionalModuleReadiness: { module: 'physics', state: 'disabled' },
8
+ nativeEnv: {
9
+ AURA_MODULE_PHYSICS: '0',
10
+ AURA_MODULE_NET: '0',
11
+ },
12
+ nativeChecks: [
13
+ {
14
+ id: 'optional.physics.disabled.guidance',
15
+ expression: "(() => { const guidance = (fn) => { try { fn(); return false; } catch (e) { const msg = String(e); return msg.includes('disabled') && msg.includes('modules.physics'); } }; return guidance(() => aura.physics.configureStep({ stepSeconds: 1 / 120 })) && guidance(() => aura.physics.step(0.016)) && guidance(() => aura.physics.queryPoint(0, 0)) && guidance(() => aura.physics.queryPoint({ x: 0, y: 0 })) && guidance(() => aura.physics.queryAABB(0, 0, 1, 1)) && guidance(() => aura.physics.queryAABB({ x: 0, y: 0, w: 1, h: 1 })) && guidance(() => aura.physics.queryPointDetailed({ x: 0, y: 0 })) && guidance(() => aura.physics.queryOverlap(0, 0, 1, 1)) && guidance(() => aura.physics.queryOverlap({ x: 0, y: 0, w: 1, h: 1 })) && guidance(() => aura.physics.queryRaycast(0, 0, 1, 0)) && guidance(() => aura.physics.queryRaycast({ x: 0, y: 0, dx: 1, dy: 0 })) && guidance(() => aura.physics.queryAABB(0, 0, 0, 1)); })()",
16
+ },
17
+ {
18
+ id: 'optional.physics.disabled.reason-codes',
19
+ expression: "(() => { const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.physics.' + method + '()') && msg.includes('optional module \"physics\" is disabled') && msg.includes('modules.physics = true') && msg.includes('[reason:optional_module_physics_disabled]'); } }; return reasonCode('configureStep', () => aura.physics.configureStep({ stepSeconds: 1 / 120 })) && reasonCode('step', () => aura.physics.step(0.016)) && reasonCode('queryPoint', () => aura.physics.queryPoint(0, 0)) && reasonCode('queryRaycast', () => aura.physics.queryRaycast(0, 0, 1, 0)); })()",
20
+ },
21
+ ],
22
+ nativeFrames: 2,
23
+ source: `aura.setup = function () {};`,
24
+ },
25
+ {
26
+ id: 'optional-module-physics-enabled',
27
+ modes: ['native'],
28
+ namespaces: ['physics'],
29
+ functions: ['aura.physics.body', 'aura.physics.configureStep', 'aura.physics.step', 'aura.physics.queryPoint', 'aura.physics.queryAABB', 'aura.physics.queryPointDetailed', 'aura.physics.queryOverlap', 'aura.physics.queryRaycast'],
30
+ optionalModuleReadiness: { module: 'physics', state: 'enabled' },
31
+ nativeEnv: {
32
+ AURA_MODULE_PHYSICS: '1',
33
+ AURA_MODULE_NET: '0',
34
+ },
35
+ nativeChecks: [
36
+ {
37
+ id: 'optional.physics.enabled.runtime',
38
+ expression: "(() => { try { const configured = aura.physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true }); const body = aura.physics.body('dynamic', { x: 0, y: 0, shape: 'circle', radius: 1 }); aura.physics.step(0.016); const pointHits = aura.physics.queryPoint(body.x, body.y); const pointHitsObj = aura.physics.queryPoint({ x: body.x, y: body.y }); const aabbHits = aura.physics.queryAABB(body.x - 1, body.y - 1, 2, 2); const aabbHitsObj = aura.physics.queryAABB({ x: body.x - 1, y: body.y - 1, w: 2, h: 2 }); const pointDetailed = aura.physics.queryPointDetailed({ x: body.x, y: body.y }); const pointDetailedInvalid = aura.physics.queryPointDetailed({ x: 'bad', y: 0 }); const overlap = aura.physics.queryOverlap({ x: body.x - 1, y: body.y - 1, w: 2, h: 2 }); const overlapInvalid = aura.physics.queryOverlap(body.x, body.y, 0, 2); const rayHit = aura.physics.queryRaycast({ x: body.x - 5, y: body.y, dx: 1, dy: 0, maxToi: 20 }); const rayMiss = aura.physics.queryRaycast({ x: body.x - 5, y: body.y + 50, dx: 1, dy: 0, maxToi: 20 }); const rayInvalid = aura.physics.queryRaycast({ x: body.x, y: body.y, dx: 0, dy: 0 }); const invalidPointHits = aura.physics.queryPoint('bad', 0); const invalidAabbHits = aura.physics.queryAABB(0, 0, 0, 1); return configured && configured.auto === false && configured.maxSubSteps === 16 && Number.isFinite(configured.stepCount) && Number.isFinite(configured.accumulatorCarry) && typeof body === 'object' && Number.isFinite(body.id) && typeof body.applyForce === 'function' && Array.isArray(pointHits) && pointHits.includes(body.id) && Array.isArray(pointHitsObj) && pointHitsObj.includes(body.id) && Array.isArray(aabbHits) && aabbHits.includes(body.id) && Array.isArray(aabbHitsObj) && aabbHitsObj.includes(body.id) && pointDetailed && pointDetailed.ok === true && Array.isArray(pointDetailed.hits) && pointDetailed.hits.includes(body.id) && pointDetailed.error === null && pointDetailedInvalid && pointDetailedInvalid.ok === false && Array.isArray(pointDetailedInvalid.hits) && pointDetailedInvalid.hits.length === 0 && typeof pointDetailedInvalid.error === 'string' && pointDetailedInvalid.error.length > 0 && overlap && overlap.ok === true && Array.isArray(overlap.hits) && overlap.hits.includes(body.id) && overlap.error === null && overlapInvalid && overlapInvalid.ok === true && Array.isArray(overlapInvalid.hits) && overlapInvalid.hits.length === 0 && overlapInvalid.error === null && rayHit && rayHit.ok === true && rayHit.hit === true && rayHit.bodyId === body.id && Number.isFinite(rayHit.toi) && rayHit.point && Number.isFinite(rayHit.point.x) && Number.isFinite(rayHit.point.y) && rayHit.normal && Number.isFinite(rayHit.normal.x) && Number.isFinite(rayHit.normal.y) && rayHit.error === null && rayMiss && rayMiss.ok === true && rayMiss.hit === false && rayMiss.bodyId === null && rayMiss.toi === null && rayMiss.point === null && rayMiss.normal === null && rayMiss.error === null && rayInvalid && rayInvalid.ok === true && rayInvalid.hit === false && rayInvalid.bodyId === null && rayInvalid.toi === null && rayInvalid.point === null && rayInvalid.normal === null && rayInvalid.error === null && Array.isArray(invalidPointHits) && invalidPointHits.length === 0 && Array.isArray(invalidAabbHits) && invalidAabbHits.length === 0; } catch (_) { return false; } })()",
39
+ },
40
+ {
41
+ id: 'optional.physics.enabled.invalid-input.reason-codes',
42
+ expression: "(() => { try { const pointDetailedInvalid = aura.physics.queryPointDetailed({ x: 'bad', y: 0 }); const overlapInvalid = aura.physics.queryOverlap({ x: 'bad', y: 0, w: 1, h: 1 }); const raycastInvalid = aura.physics.queryRaycast({ x: 0, y: 0, dx: 'bad', dy: 1 }); return pointDetailedInvalid && pointDetailedInvalid.ok === false && pointDetailedInvalid.error === 'invalid point query args' && overlapInvalid && overlapInvalid.ok === false && overlapInvalid.error === 'invalid overlap query args' && raycastInvalid && raycastInvalid.ok === false && raycastInvalid.error === 'invalid raycast query args'; } catch (_) { return false; } })()",
43
+ },
44
+ {
45
+ id: 'optional.physics.enabled.fixed-step.variable-dt-stable',
46
+ expression: "(() => { try { const close = (a, b) => Math.abs(a - b) <= 1e-4; const run = (startX, sequence) => { const body = aura.physics.body('dynamic', { x: startX, y: 0, shape: 'circle', radius: 0.5 }); body.setVelocity(2, 0); for (const dt of sequence) { aura.physics.step(dt); } const diag = aura.physics.configureStep(); return { dx: body.x - startX, y: body.y, vy: body.velocity.y, stepCount: diag.stepCount, accumulatorCarry: diag.accumulatorCarry }; }; aura.physics.setGravity(0, 9.81); aura.physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true }); const sequenceA = [1 / 60, 1 / 60, 1 / 60, 1 / 60, 1 / 60, 1 / 60]; const sequenceB = [1 / 50, 1 / 50, 1 / 100, 1 / 25, 1 / 100]; const first = run(-10, sequenceA); aura.physics.configureStep({ resetAccumulator: true }); const second = run(10, sequenceB); globalThis.__physicsFixedStepDiag = { stepCount: second.stepCount, accumulatorCarry: second.accumulatorCarry }; return close(first.dx, second.dx) && close(first.y, second.y) && close(first.vy, second.vy) && first.stepCount === 2 && second.stepCount === 2 && Math.abs(first.accumulatorCarry) <= 1e-5 && Math.abs(second.accumulatorCarry) <= 1e-5; } catch (_) { return false; } })()",
47
+ },
48
+ {
49
+ id: 'optional.physics.enabled.callback-ordering.seeded',
50
+ expression: "(() => { try { globalThis.__physicsCallbackHits = []; const dynamicBody = aura.physics.body('dynamic', { x: 0, y: 0, shape: 'circle', radius: 1 }); const staticBody = aura.physics.body('static', { x: 0, y: 0, shape: 'circle', radius: 1 }); globalThis.__physicsCallbackIds = [dynamicBody.id, staticBody.id]; dynamicBody.onCollide((other) => { globalThis.__physicsCallbackHits.push('a:' + other); }); staticBody.onCollide((other) => { globalThis.__physicsCallbackHits.push('b:' + other); }); aura.physics.step(0.016); return Array.isArray(globalThis.__physicsCallbackIds) && globalThis.__physicsCallbackIds.length === 2; } catch (_) { return false; } })()",
51
+ },
52
+ ],
53
+ nativePostChecks: [
54
+ {
55
+ id: 'optional.physics.enabled.callback-ordering.deterministic',
56
+ expression: "Array.isArray(globalThis.__physicsCallbackIds) && Array.isArray(globalThis.__physicsCallbackHits) && globalThis.__physicsCallbackHits.length === 2 && globalThis.__physicsCallbackHits[0] === ('a:' + globalThis.__physicsCallbackIds[1]) && globalThis.__physicsCallbackHits[1] === ('b:' + globalThis.__physicsCallbackIds[0])",
57
+ debugExpression: "(() => { const ids = Array.isArray(globalThis.__physicsCallbackIds) ? [...globalThis.__physicsCallbackIds] : null; const observed = Array.isArray(globalThis.__physicsCallbackHits) ? [...globalThis.__physicsCallbackHits] : null; const expected = Array.isArray(ids) && ids.length === 2 ? ['a:' + ids[1], 'b:' + ids[0]] : null; return { callback: 'physics.onCollide', ids, expected, observed, observedCount: Array.isArray(observed) ? observed.length : null }; })()",
58
+ },
59
+ {
60
+ id: 'optional.physics.enabled.fixed-step.diagnostics',
61
+ expression: "globalThis.__physicsFixedStepDiag && Number.isFinite(globalThis.__physicsFixedStepDiag.stepCount) && Number.isFinite(globalThis.__physicsFixedStepDiag.accumulatorCarry)",
62
+ },
63
+ ],
64
+ nativeFrames: 2,
65
+ source: `aura.setup = function () {};`,
66
+ },
67
+ {
68
+ id: 'physics3d-joint-constraint-parity',
69
+ modes: ['native'],
70
+ namespaces: ['physics3d'],
71
+ functions: [
72
+ 'aura.physics3d.body',
73
+ 'aura.physics3d.configureStep',
74
+ 'aura.physics3d.setGravity',
75
+ 'aura.physics3d.step',
76
+ 'aura.physics3d.joint',
77
+ 'aura.physics3d.removeJoint',
78
+ ],
79
+ nativeEnv: {
80
+ AURA_MODULE_PHYSICS: '1',
81
+ AURA_MODULE_NET: '0',
82
+ },
83
+ nativeChecks: [
84
+ {
85
+ id: 'physics3d.joints.surface.methods',
86
+ expression: `(() => {
87
+ try {
88
+ const physics = aura.physics3d || {};
89
+ const methodsOk = ['body', 'configureStep', 'setGravity', 'step', 'joint', 'removeJoint'].every((name) => typeof physics[name] === 'function');
90
+ physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true, resetWorld: true });
91
+ physics.setGravity(0, 0, 0);
92
+ const anchor = physics.body('static', { x: 0, y: 0, z: 0, shape: 'box', width: 0.5, height: 0.5, depth: 0.5 });
93
+ const bob = physics.body('dynamic', { x: 1, y: 0, z: 0, shape: 'box', width: 0.5, height: 0.5, depth: 0.5 });
94
+ const joint = physics.joint('revolute', anchor.id, bob.id, {
95
+ axis: { x: 0, y: 1, z: 0 },
96
+ limits: { min: -0.5, max: 0.5 },
97
+ motor: { targetVel: 0.5, maxForce: 5 },
98
+ });
99
+ const state = joint.getState();
100
+ return methodsOk
101
+ && typeof anchor === 'object'
102
+ && typeof bob === 'object'
103
+ && typeof joint === 'object'
104
+ && Number.isInteger(joint.id)
105
+ && joint.id > 0
106
+ && joint.kind === 'revolute'
107
+ && joint.bodyA === anchor.id
108
+ && joint.bodyB === bob.id
109
+ && typeof joint.getState === 'function'
110
+ && typeof joint.remove === 'function'
111
+ && state?.kind === 'revolute';
112
+ } catch (_) {
113
+ return false;
114
+ }
115
+ })()`,
116
+ },
117
+ {
118
+ id: 'physics3d.joints.invalid-usage.reason-codes',
119
+ expression: `(() => {
120
+ try {
121
+ const physics = aura.physics3d;
122
+ physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true, resetWorld: true });
123
+ physics.setGravity(0, 0, 0);
124
+ const base = physics.body('static', { x: 0, y: 0, z: 0, shape: 'box', width: 0.5, height: 0.5, depth: 0.5 });
125
+ const bob = physics.body('dynamic', { x: 1, y: 0, z: 0, shape: 'box', width: 0.5, height: 0.5, depth: 0.5 });
126
+ const invalidKind = (() => { try { physics.joint('rope', base.id, bob.id); return false; } catch (error) { return String(error).includes('type must be revolute|ball|prismatic|fixed'); } })();
127
+ const invalidMissingBody = (() => { try { physics.joint('fixed', base.id, 999999); return false; } catch (error) { return String(error).includes('bodyB handle does not exist'); } })();
128
+ const invalidAxis = (() => { try { physics.joint('revolute', base.id, bob.id, { axis: { x: 0, y: 0, z: 0 } }); return false; } catch (error) { return String(error).includes('options.axis must be non-zero'); } })();
129
+ const invalidMotor = (() => { try { physics.joint('ball', base.id, bob.id, { motor: { targetVel: 1, maxForce: 2 } }); return false; } catch (error) { return String(error).includes('options.motor is only supported for revolute and prismatic joints'); } })();
130
+ const invalidLimits = (() => { try { physics.joint('fixed', base.id, bob.id, { limits: { min: -1, max: 1 } }); return false; } catch (error) { return String(error).includes('options.limits are only supported for revolute and prismatic joints'); } })();
131
+ return invalidKind && invalidMissingBody && invalidAxis && invalidMotor && invalidLimits;
132
+ } catch (_) {
133
+ return false;
134
+ }
135
+ })()`,
136
+ },
137
+ {
138
+ id: 'physics3d.joints.runtime.seeded',
139
+ expression: `(() => {
140
+ try {
141
+ const physics = aura.physics3d;
142
+ physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true, resetWorld: true });
143
+ physics.setGravity(0, -9.81, 0);
144
+
145
+ const makeBody = (kind, spec) => physics.body(kind, spec);
146
+
147
+ const revoluteBase = makeBody('static', { x: -5.5, y: 2.1, z: 0, shape: 'box', width: 0.4, height: 3.2, depth: 0.4 });
148
+ const revoluteDoor = makeBody('dynamic', { x: -4.0, y: 2.1, z: 0, shape: 'box', width: 1.8, height: 2.4, depth: 0.2 });
149
+ const prismaticRail = makeBody('static', { x: 0, y: 1.6, z: 0, shape: 'box', width: 4.6, height: 0.25, depth: 0.25 });
150
+ const prismaticCar = makeBody('dynamic', { x: 0, y: 1.6, z: 0, shape: 'box', width: 1.0, height: 0.45, depth: 0.45 });
151
+ const ballAnchor = makeBody('static', { x: 4.5, y: 4.0, z: 0, shape: 'sphere', radius: 0.2 });
152
+ const ballBob = makeBody('dynamic', { x: 4.9, y: 2.6, z: 0, shape: 'sphere', radius: 0.45 });
153
+ const fixedAnchor = makeBody('static', { x: 7.8, y: 1.3, z: 0, shape: 'box', width: 0.4, height: 0.4, depth: 0.4 });
154
+ const fixedPanel = makeBody('dynamic', { x: 8.5, y: 1.3, z: 0, shape: 'box', width: 0.7, height: 0.7, depth: 0.2 });
155
+
156
+ const revolute = physics.joint('revolute', revoluteBase.id, revoluteDoor.id, {
157
+ anchorA: { x: 0.9, y: 0, z: 0 },
158
+ anchorB: { x: -0.9, y: 0, z: 0 },
159
+ axis: { x: 0, y: 1, z: 0 },
160
+ limits: { min: -0.8, max: 0.8 },
161
+ motor: { targetVel: 1.1, maxForce: 80 },
162
+ });
163
+ const prismatic = physics.joint('prismatic', prismaticRail.id, prismaticCar.id, {
164
+ axis: { x: 1, y: 0, z: 0 },
165
+ limits: { min: -1.25, max: 1.25 },
166
+ motor: { targetVel: 0.75, maxForce: 48 },
167
+ });
168
+ const ball = physics.joint('ball', ballAnchor.id, ballBob.id, {
169
+ anchorA: { x: 0, y: -0.35, z: 0 },
170
+ anchorB: { x: 0, y: 0.45, z: 0 },
171
+ });
172
+ const fixed = physics.joint('fixed', fixedAnchor.id, fixedPanel.id, {
173
+ anchorA: { x: 0.35, y: 0, z: 0 },
174
+ anchorB: { x: -0.35, y: 0, z: 0 },
175
+ rotation: { x: 0, y: 0.3, z: 0 },
176
+ });
177
+
178
+ revoluteDoor.applyForce(0, 0, 5);
179
+ ballBob.applyForce(8, 0, 0);
180
+ prismaticCar.setVelocity(0.35, 0, 0);
181
+
182
+ for (let step = 0; step < 24; step += 1) {
183
+ physics.step(1 / 60);
184
+ }
185
+
186
+ globalThis.__physics3dJointProof = {
187
+ ids: {
188
+ revolute: revolute.id,
189
+ prismatic: prismatic.id,
190
+ ball: ball.id,
191
+ fixed: fixed.id,
192
+ },
193
+ bodies: {
194
+ revoluteDoor: revoluteDoor.id,
195
+ prismaticCar: prismaticCar.id,
196
+ ballBob: ballBob.id,
197
+ fixedPanel: fixedPanel.id,
198
+ },
199
+ states: {
200
+ revolute: revolute.getState(),
201
+ prismatic: prismatic.getState(),
202
+ ball: ball.getState(),
203
+ fixed: fixed.getState(),
204
+ },
205
+ };
206
+
207
+ const states = globalThis.__physics3dJointProof.states;
208
+ return [revolute, prismatic, ball, fixed].every((joint) => typeof joint === 'object' && Number.isInteger(joint.id) && joint.id > 0)
209
+ && states.revolute?.kind === 'revolute'
210
+ && states.prismatic?.kind === 'prismatic'
211
+ && states.ball?.kind === 'ball'
212
+ && states.fixed?.kind === 'fixed';
213
+ } catch (error) {
214
+ globalThis.__physics3dJointProofError = String(error && error.message ? error.message : error);
215
+ return false;
216
+ }
217
+ })()`,
218
+ },
219
+ ],
220
+ nativePostChecks: [
221
+ {
222
+ id: 'physics3d.joints.state.evidence',
223
+ expression: `(() => {
224
+ try {
225
+ const close = (value, expected, epsilon = 1e-4) => Number.isFinite(value) && Math.abs(value - expected) <= epsilon;
226
+ const proof = globalThis.__physics3dJointProof || {};
227
+ const states = proof.states || {};
228
+ const revolute = states.revolute || {};
229
+ const prismatic = states.prismatic || {};
230
+ const ball = states.ball || {};
231
+ const fixed = states.fixed || {};
232
+ return Number.isInteger(proof.ids?.revolute)
233
+ && Number.isInteger(proof.ids?.prismatic)
234
+ && Number.isInteger(proof.ids?.ball)
235
+ && Number.isInteger(proof.ids?.fixed)
236
+ && revolute.currentValueKind === 'angle_radians'
237
+ && revolute.limits?.enabled === true
238
+ && close(revolute.limits?.min, -0.8)
239
+ && close(revolute.limits?.max, 0.8)
240
+ && revolute.motor?.supported === true
241
+ && revolute.motor?.configured === true
242
+ && revolute.motor?.active === true
243
+ && close(revolute.motor?.targetVel, 1.1)
244
+ && close(revolute.motor?.maxForce, 80)
245
+ && Number.isFinite(revolute.currentValue)
246
+ && prismatic.currentValueKind === 'translation_meters'
247
+ && prismatic.limits?.enabled === true
248
+ && close(prismatic.limits?.min, -1.25)
249
+ && close(prismatic.limits?.max, 1.25)
250
+ && prismatic.motor?.supported === true
251
+ && prismatic.motor?.configured === true
252
+ && prismatic.motor?.active === true
253
+ && close(prismatic.motor?.targetVel, 0.75)
254
+ && close(prismatic.motor?.maxForce, 48)
255
+ && Number.isFinite(prismatic.currentValue)
256
+ && ball.currentValueKind === 'relative_rotation_radians'
257
+ && ball.motor?.supported === false
258
+ && ball.motor?.configured === false
259
+ && ball.limits?.enabled === false
260
+ && Number.isFinite(ball.currentValue)
261
+ && fixed.currentValueKind === 'locked'
262
+ && close(fixed.currentValue, 0)
263
+ && fixed.motor?.supported === false
264
+ && fixed.limits?.enabled === false
265
+ && typeof revolute.anchorA?.x === 'number'
266
+ && typeof revolute.anchorB?.x === 'number'
267
+ && typeof revolute.axis?.y === 'number'
268
+ && close(fixed.rotation?.y, 0.3);
269
+ } catch (_) {
270
+ return false;
271
+ }
272
+ })()`,
273
+ debugExpression: `(() => ({
274
+ proof: globalThis.__physics3dJointProof || null,
275
+ error: globalThis.__physics3dJointProofError || null,
276
+ }))()`,
277
+ },
278
+ {
279
+ id: 'physics3d.joints.removal.deterministic',
280
+ expression: `(() => {
281
+ try {
282
+ const proof = globalThis.__physics3dJointProof || {};
283
+ const ballId = proof.ids?.ball || 0;
284
+ const fixedId = proof.ids?.fixed || 0;
285
+ const removedBall = aura.physics3d.removeJoint(ballId);
286
+ const removedBallAgain = aura.physics3d.removeJoint(ballId);
287
+ const removedFixed = aura.physics3d.removeJoint(fixedId);
288
+ const ballState = aura.physics3d.__jointState(ballId);
289
+ const fixedState = aura.physics3d.__jointState(fixedId);
290
+ return removedBall === true
291
+ && removedBallAgain === false
292
+ && removedFixed === true
293
+ && ballState === null
294
+ && fixedState === null;
295
+ } catch (_) {
296
+ return false;
297
+ }
298
+ })()`,
299
+ },
300
+ ],
301
+ nativeFrames: 24,
302
+ source: `aura.setup = function () {};`,
303
+ },
304
+ {
305
+ id: 'optional-module-net-disabled',
306
+ modes: ['native'],
307
+ namespaces: ['net'],
308
+ functions: ['aura.net.connect', 'aura.net.websocket', 'aura.net.fetch', 'aura.net.get', 'aura.net.post'],
309
+ optionalModuleReadiness: { module: 'net', state: 'disabled' },
310
+ nativeEnv: {
311
+ AURA_MODULE_PHYSICS: '0',
312
+ AURA_MODULE_NET: '0',
313
+ },
314
+ nativeChecks: [
315
+ {
316
+ id: 'optional.net.disabled.guidance',
317
+ expression: "(() => { try { aura.net.connect('tcp', 'localhost', 7777); return false; } catch (e) { const msg = String(e); return msg.includes('disabled') && msg.includes('modules.network'); } })()",
318
+ },
319
+ {
320
+ id: 'optional.net.disabled.reason-codes',
321
+ expression: "(() => { const parseReason = (msg) => { const match = /\\[reason:([^\\]]+)\\]/.exec(msg); return match ? match[1] : null; }; const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.net.' + method + '()') && msg.includes('optional module \"net\" is disabled') && parseReason(msg) === 'optional_module_net_disabled'; } }; return reasonCode('connect', () => aura.net.connect('tcp', 'localhost', 7777)) && reasonCode('websocket', () => aura.net.websocket('ws://localhost:7777')) && reasonCode('fetch', () => aura.net.fetch('http://localhost:7777')) && reasonCode('get', () => aura.net.get('http://localhost:7777')) && reasonCode('post', () => aura.net.post('http://localhost:7777', { json: { ok: true } })); })()",
322
+ },
323
+ ],
324
+ nativeFrames: 2,
325
+ source: `aura.setup = function () {};`,
326
+ },
327
+ {
328
+ id: 'optional-module-net-enabled',
329
+ modes: ['native'],
330
+ namespaces: ['net'],
331
+ functions: ['aura.net.connect', 'aura.net.websocket', 'aura.net.fetch', 'aura.net.get', 'aura.net.post'],
332
+ optionalModuleReadiness: { module: 'net', state: 'enabled' },
333
+ nativeEnv: {
334
+ AURA_MODULE_PHYSICS: '0',
335
+ AURA_MODULE_NET: '1',
336
+ },
337
+ nativeChecks: [
338
+ {
339
+ id: 'optional.net.enabled.runtime',
340
+ expression: "(() => { try { const tcp = aura.net.connect('tcp', '127.0.0.1', 1); return typeof tcp === 'object' && Number.isFinite(tcp.id) && typeof tcp.send === 'function' && typeof tcp.close === 'function'; } catch (e) { const msg = String(e); return msg.includes('aura.net.connect') && !msg.includes('optional module \"net\" is disabled') && !msg.includes('[reason:optional_module_net_disabled]'); } })()",
341
+ },
342
+ {
343
+ id: 'optional.net.enabled.invalid-input.reason-codes',
344
+ expression: "(() => { const hasToken = (msg, tokens) => tokens.some((token) => msg.includes(token)); const expectThrow = (fn, tokens) => { try { fn(); return false; } catch (e) { const msg = String(e); return hasToken(msg, Array.isArray(tokens) ? tokens : [tokens]); } }; return expectThrow(() => aura.net.connect('http', '127.0.0.1', 7777), 'transport must be \"tcp\" or \"udp\"') && expectThrow(() => aura.net.connect('tcp', 123, 7777), 'host must be a string') && expectThrow(() => aura.net.connect('tcp', '127.0.0.1', 0), 'port must be an integer in range 1..65535') && expectThrow(() => aura.net.websocket(42), 'url must be a websocket URL string') && expectThrow(() => aura.net.fetch(42), ['url must be a string', 'url must be an HTTP URL string']) && expectThrow(() => aura.net.get(42), ['url must be a string', 'url must be an HTTP URL string']) && expectThrow(() => aura.net.post(42, { ok: true }), ['url must be a string', 'url must be an HTTP URL string']); })()",
345
+ },
346
+ {
347
+ id: 'optional.net.enabled.callback-ordering.seeded',
348
+ expression: "(() => { try { globalThis.__netDisconnectEvents = []; const conn = aura.net.connect('udp', '127.0.0.1', 9); conn.onDisconnect(() => { globalThis.__netDisconnectEvents.push('d1'); }); conn.onDisconnect(() => { globalThis.__netDisconnectEvents.push('d2'); }); const closed = conn.close(); globalThis.__netDisconnectCloseIssued = closed === true; return globalThis.__netDisconnectCloseIssued; } catch (_) { return false; } })()",
349
+ },
350
+ ],
351
+ nativePostChecks: [
352
+ {
353
+ id: 'optional.net.enabled.callback-ordering.deterministic',
354
+ expression: "globalThis.__netDisconnectCloseIssued === true && Array.isArray(globalThis.__netDisconnectEvents) && globalThis.__netDisconnectEvents.length === 2 && globalThis.__netDisconnectEvents[0] === 'd1' && globalThis.__netDisconnectEvents[1] === 'd2'",
355
+ debugExpression: "(() => { const observed = Array.isArray(globalThis.__netDisconnectEvents) ? [...globalThis.__netDisconnectEvents] : null; return { callback: 'net.onDisconnect', closeIssued: globalThis.__netDisconnectCloseIssued === true, expected: ['d1', 'd2'], observed, observedCount: Array.isArray(observed) ? observed.length : null }; })()",
356
+ },
357
+ ],
358
+ nativeFrames: 2,
359
+ source: `aura.setup = function () {};`,
360
+ },
361
+ {
362
+ id: 'optional-module-multiplayer-disabled',
363
+ modes: ['native'],
364
+ namespaces: ['multiplayer'],
365
+ functions: [
366
+ 'aura.multiplayer.configure',
367
+ 'aura.multiplayer.host',
368
+ 'aura.multiplayer.join',
369
+ 'aura.multiplayer.leave',
370
+ 'aura.multiplayer.stop',
371
+ 'aura.multiplayer.getPlayers',
372
+ 'aura.multiplayer.getPlayerCount',
373
+ 'aura.multiplayer.send',
374
+ 'aura.multiplayer.broadcast',
375
+ 'aura.multiplayer.sendInput',
376
+ 'aura.multiplayer.getPlayerInput',
377
+ 'aura.multiplayer.getAllPlayerInputs',
378
+ 'aura.multiplayer.setState',
379
+ 'aura.multiplayer.getState',
380
+ 'aura.multiplayer.getAllState',
381
+ 'aura.multiplayer.onMessage',
382
+ 'aura.multiplayer.onStateUpdate',
383
+ 'aura.multiplayer.onPlayerJoin',
384
+ 'aura.multiplayer.onPlayerLeave',
385
+ 'aura.multiplayer.onDisconnect',
386
+ 'aura.multiplayer.kick',
387
+ 'aura.multiplayer.setPlayerData',
388
+ 'aura.multiplayer.getPlayerData',
389
+ 'aura.multiplayer.advertise',
390
+ 'aura.multiplayer.discover',
391
+ ],
392
+ optionalModuleReadiness: { module: 'multiplayer', state: 'disabled' },
393
+ nativeEnv: {
394
+ AURA_MODULE_PHYSICS: '0',
395
+ AURA_MODULE_NET: '0',
396
+ AURA_MODULE_MULTIPLAYER: '0',
397
+ },
398
+ nativeChecks: [
399
+ {
400
+ id: 'optional.multiplayer.disabled.guidance',
401
+ expression: "(() => { const guidance = (fn) => { try { fn(); return false; } catch (e) { const msg = String(e); return msg.includes('disabled') && msg.includes('modules.multiplayer'); } }; return guidance(() => aura.multiplayer.configure({ tickRate: 20 })) && guidance(() => aura.multiplayer.host(7000)) && guidance(() => aura.multiplayer.join('127.0.0.1', 7000)) && guidance(() => aura.multiplayer.leave()) && guidance(() => aura.multiplayer.stop()) && guidance(() => aura.multiplayer.getPlayers()) && guidance(() => aura.multiplayer.send(1, 'chat', { body: 'hi' })) && guidance(() => aura.multiplayer.broadcast('chat', { body: 'hi' })) && guidance(() => aura.multiplayer.sendInput({ left: true })) && guidance(() => aura.multiplayer.getPlayerInput(1)) && guidance(() => aura.multiplayer.getAllPlayerInputs()) && guidance(() => aura.multiplayer.setState('room', { phase: 'lobby' })) && guidance(() => aura.multiplayer.getState('room')) && guidance(() => aura.multiplayer.getAllState()) && guidance(() => aura.multiplayer.onMessage('chat', () => {})) && guidance(() => aura.multiplayer.onStateUpdate(() => {})) && guidance(() => aura.multiplayer.onPlayerJoin(() => {})) && guidance(() => aura.multiplayer.onPlayerLeave(() => {})) && guidance(() => aura.multiplayer.onDisconnect(() => {})) && guidance(() => aura.multiplayer.kick(1, 'afk')) && guidance(() => aura.multiplayer.setPlayerData(1, 'team', 'blue')) && guidance(() => aura.multiplayer.getPlayerData(1, 'team')) && guidance(() => aura.multiplayer.advertise({ name: 'Room' })) && guidance(() => aura.multiplayer.discover(7001, () => {})); })()",
402
+ },
403
+ {
404
+ id: 'optional.multiplayer.disabled.reason-codes',
405
+ expression: "(() => { const reasonCode = (method, invoke) => { try { invoke(); return false; } catch (e) { const msg = String(e); return msg.includes('aura.multiplayer.' + method + '()') && msg.includes('optional module \"multiplayer\" is disabled') && msg.includes('modules.multiplayer = true') && msg.includes('[reason:optional_module_multiplayer_disabled]'); } }; return reasonCode('configure', () => aura.multiplayer.configure({ tickRate: 20 })) && reasonCode('host', () => aura.multiplayer.host(7000)) && reasonCode('join', () => aura.multiplayer.join('127.0.0.1', 7000)) && reasonCode('send', () => aura.multiplayer.send(1, 'chat', { body: 'hi' })) && reasonCode('broadcast', () => aura.multiplayer.broadcast('chat', { body: 'hi' })) && reasonCode('sendInput', () => aura.multiplayer.sendInput({ left: true })) && reasonCode('getPlayerInput', () => aura.multiplayer.getPlayerInput(1)) && reasonCode('getAllPlayerInputs', () => aura.multiplayer.getAllPlayerInputs()) && reasonCode('setState', () => aura.multiplayer.setState('room', { phase: 'lobby' })) && reasonCode('getState', () => aura.multiplayer.getState('room')) && reasonCode('getAllState', () => aura.multiplayer.getAllState()) && reasonCode('onMessage', () => aura.multiplayer.onMessage('chat', () => {})) && reasonCode('onStateUpdate', () => aura.multiplayer.onStateUpdate(() => {})) && reasonCode('onPlayerJoin', () => aura.multiplayer.onPlayerJoin(() => {})) && reasonCode('onPlayerLeave', () => aura.multiplayer.onPlayerLeave(() => {})) && reasonCode('onDisconnect', () => aura.multiplayer.onDisconnect(() => {})) && reasonCode('kick', () => aura.multiplayer.kick(1, 'afk')) && reasonCode('setPlayerData', () => aura.multiplayer.setPlayerData(1, 'team', 'blue')) && reasonCode('getPlayerData', () => aura.multiplayer.getPlayerData(1, 'team')) && reasonCode('advertise', () => aura.multiplayer.advertise({ name: 'Room' })) && reasonCode('discover', () => aura.multiplayer.discover(7001, () => {})); })()",
406
+ },
407
+ ],
408
+ nativeFrames: 2,
409
+ source: `aura.setup = function () {};`,
410
+ },
411
+ {
412
+ id: 'optional-module-multiplayer-enabled',
413
+ modes: ['native'],
414
+ namespaces: ['multiplayer'],
415
+ functions: [
416
+ 'aura.multiplayer.configure',
417
+ 'aura.multiplayer.host',
418
+ 'aura.multiplayer.join',
419
+ 'aura.multiplayer.leave',
420
+ 'aura.multiplayer.stop',
421
+ 'aura.multiplayer.isHost',
422
+ 'aura.multiplayer.isClient',
423
+ 'aura.multiplayer.isConnected',
424
+ 'aura.multiplayer.getLocalId',
425
+ 'aura.multiplayer.getPlayers',
426
+ 'aura.multiplayer.getPlayerCount',
427
+ 'aura.multiplayer.getPing',
428
+ 'aura.multiplayer.getServerTime',
429
+ 'aura.multiplayer.sendInput',
430
+ 'aura.multiplayer.getPlayerInput',
431
+ 'aura.multiplayer.getAllPlayerInputs',
432
+ 'aura.multiplayer.send',
433
+ 'aura.multiplayer.broadcast',
434
+ 'aura.multiplayer.onMessage',
435
+ 'aura.multiplayer.kick',
436
+ 'aura.multiplayer.setPlayerData',
437
+ 'aura.multiplayer.getPlayerData',
438
+ 'aura.multiplayer.advertise',
439
+ 'aura.multiplayer.discover',
440
+ 'aura.multiplayer.setState',
441
+ 'aura.multiplayer.getState',
442
+ 'aura.multiplayer.getAllState',
443
+ 'aura.multiplayer.onStateUpdate',
444
+ 'aura.multiplayer.onPlayerJoin',
445
+ 'aura.multiplayer.onPlayerLeave',
446
+ 'aura.multiplayer.onDisconnect',
447
+ ],
448
+ optionalModuleReadiness: { module: 'multiplayer', state: 'enabled' },
449
+ nativeEnv: {
450
+ AURA_MODULE_PHYSICS: '0',
451
+ AURA_MODULE_NET: '0',
452
+ AURA_MODULE_MULTIPLAYER: '1',
453
+ },
454
+ nativeChecks: [
455
+ {
456
+ id: 'optional.multiplayer.enabled.runtime',
457
+ expression: "(() => { try { const api = aura.multiplayer; if (!api || typeof api !== 'object') return false; const methods = ['configure', 'host', 'join', 'leave', 'stop', 'isHost', 'isClient', 'isConnected', 'getLocalId', 'getPlayers', 'getPlayerCount', 'getPing', 'getServerTime', 'sendInput', 'getPlayerInput', 'getAllPlayerInputs', 'send', 'broadcast', 'onMessage', 'kick', 'setPlayerData', 'getPlayerData', 'advertise', 'discover', 'setState', 'getState', 'getAllState', 'onStateUpdate', 'onPlayerJoin', 'onPlayerLeave', 'onDisconnect']; if (!methods.every((name) => typeof api[name] === 'function')) return false; const notDisabledOrPlaceholder = (invoke, validate = () => true) => { try { const value = invoke(); return validate(value); } catch (e) { const msg = String(e); const lower = msg.toLowerCase(); return !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !lower.includes('not implemented yet') && !lower.includes('placeholder'); } }; const messageEvents = []; const stateEvents = []; const joinEvents = []; const leaveEvents = []; const disconnectEvents = []; const callbackRegisterOk = api.onMessage('chat', (sender, data) => { messageEvents.push(`${sender}:${(data && data.body) || ''}`); }) === true && api.onStateUpdate((snapshot) => { stateEvents.push(Object.keys(snapshot || {}).length); }) === true && api.onPlayerJoin((player) => { joinEvents.push(player && player.id); }) === true && api.onPlayerLeave((player) => { leaveEvents.push(player && player.id); }) === true && api.onDisconnect((reason) => { disconnectEvents.push(reason); }) === true; const status = { isHost: api.isHost(), isClient: api.isClient(), isConnected: api.isConnected(), localId: api.getLocalId(), players: api.getPlayers(), playerCount: api.getPlayerCount(), ping: api.getPing(), serverTime: api.getServerTime(), playerInput: api.getPlayerInput(1), allInputs: api.getAllPlayerInputs(), state: api.getState('room'), allState: api.getAllState(), playerData: api.getPlayerData(1, 'team') }; const defaultsValid = typeof status.isHost === 'boolean' && typeof status.isClient === 'boolean' && typeof status.isConnected === 'boolean' && (status.localId === null || Number.isFinite(status.localId)) && Array.isArray(status.players) && Number.isFinite(status.playerCount) && (status.ping === null || Number.isFinite(status.ping)) && (status.serverTime === null || Number.isFinite(status.serverTime)) && (status.playerInput === null || typeof status.playerInput === 'object') && (status.allInputs === null || typeof status.allInputs === 'object') && status.allState && typeof status.allState === 'object' && (status.playerData === null || typeof status.playerData === 'object' || typeof status.playerData === 'string' || typeof status.playerData === 'number' || typeof status.playerData === 'boolean') && (status.state === null || typeof status.state === 'object' || typeof status.state === 'string' || typeof status.state === 'number' || typeof status.state === 'boolean'); const sendInputStatus = notDisabledOrPlaceholder(() => api.sendInput({ left: true }), (value) => typeof value === 'boolean'); const sendStatus = notDisabledOrPlaceholder(() => api.send(1, 'chat', { body: 'hi' }), (value) => typeof value === 'boolean'); const broadcastStatus = notDisabledOrPlaceholder(() => api.broadcast('chat', { body: 'hi' }), (value) => typeof value === 'boolean'); const setStateStatus = notDisabledOrPlaceholder(() => api.setState('room', { phase: 'lobby' }), (value) => typeof value === 'boolean'); const kickStatus = notDisabledOrPlaceholder(() => api.kick(1, 'afk'), (value) => typeof value === 'boolean'); const setPlayerDataStatus = notDisabledOrPlaceholder(() => api.setPlayerData(1, 'team', 'blue'), (value) => typeof value === 'boolean'); const advertiseStatus = notDisabledOrPlaceholder(() => api.advertise({ name: 'Room' }), (value) => typeof value === 'boolean'); const discoverStatus = notDisabledOrPlaceholder(() => api.discover(7001, () => {}), (value) => value === true); const configureStatus = notDisabledOrPlaceholder(() => api.configure({ tickRate: 20, maxPlayers: 4 }), (value) => value === true); const hostStatus = notDisabledOrPlaceholder(() => api.host(7000), (value) => typeof value === 'boolean'); const joinStatus = notDisabledOrPlaceholder(() => api.join('127.0.0.1', 7000), (value) => typeof value === 'boolean'); const leaveStatus = notDisabledOrPlaceholder(() => api.leave(), (value) => typeof value === 'boolean'); const stopStatus = notDisabledOrPlaceholder(() => api.stop(), (value) => typeof value === 'boolean'); return callbackRegisterOk && defaultsValid && configureStatus && sendInputStatus && sendStatus && broadcastStatus && setStateStatus && kickStatus && setPlayerDataStatus && advertiseStatus && discoverStatus && hostStatus && joinStatus && leaveStatus && stopStatus && messageEvents.length === 0 && stateEvents.length === 0 && joinEvents.length === 0 && leaveEvents.length === 0 && disconnectEvents.length === 0; } catch (_) { return false; } })()",
458
+ },
459
+ {
460
+ id: 'optional.multiplayer.enabled.invalid-input.reason-codes',
461
+ expression: "(() => { const expectThrow = (fn, token) => { try { fn(); return false; } catch (e) { const msg = String(e); const lower = msg.toLowerCase(); return msg.includes(token) && !msg.includes('optional module \"multiplayer\" is disabled') && !msg.includes('[reason:optional_module_multiplayer_disabled]') && !lower.includes('not implemented yet') && !lower.includes('placeholder'); } }; return expectThrow(() => aura.multiplayer.leave(), 'aura.multiplayer.leave() failed [reason:invalid_state]') && expectThrow(() => aura.multiplayer.stop(), 'aura.multiplayer.stop() failed [reason:invalid_state]') && expectThrow(() => aura.multiplayer.getPlayerInput('bad'), 'aura.multiplayer.getPlayerInput(playerId): playerId must be a positive integer') && expectThrow(() => aura.multiplayer.send('bad', 'chat', {}), 'aura.multiplayer.send(targetId, type, data): targetId must be a positive integer') && expectThrow(() => aura.multiplayer.send(1, '', {}), 'aura.multiplayer.send(targetId, type, data): type must be a non-empty string') && expectThrow(() => aura.multiplayer.broadcast('', {}), 'aura.multiplayer.broadcast(type, data): type must be a non-empty string') && expectThrow(() => aura.multiplayer.onMessage('chat', 123), 'aura.multiplayer.onMessage(type, callback): callback must be a function') && expectThrow(() => aura.multiplayer.onStateUpdate(123), 'aura.multiplayer.onStateUpdate(callback): callback must be a function') && expectThrow(() => aura.multiplayer.discover('bad', () => {}), 'aura.multiplayer.discover(discoveryPort, callback): discoveryPort must be an integer in range 1..65535') && expectThrow(() => aura.multiplayer.getState(''), 'aura.multiplayer.getState(key): key must be a non-empty string') && expectThrow(() => aura.multiplayer.setState('', {}), 'aura.multiplayer.setState(key, value): key must be a non-empty string') && expectThrow(() => aura.multiplayer.getPlayerData(1, ''), 'aura.multiplayer.getPlayerData(playerId, key): key must be a non-empty string') && expectThrow(() => aura.multiplayer.setPlayerData(1, '', 'blue'), 'aura.multiplayer.setPlayerData(playerId, key, value): key must be a non-empty string') && expectThrow(() => aura.multiplayer.advertise({ name: '' }), 'aura.multiplayer.advertise(options): name must be a non-empty string'); })()",
462
+ },
463
+ ],
464
+ nativeFrames: 2,
465
+ source: `aura.setup = function () {};`,
466
+ },
467
+ {
468
+ id: 'multiplayer-local-room-code-flow',
469
+ modes: ['native'],
470
+ namespaces: ['multiplayer'],
471
+ functions: [
472
+ 'aura.multiplayer.configure',
473
+ 'aura.multiplayer.host',
474
+ 'aura.multiplayer.join',
475
+ 'aura.multiplayer.stop',
476
+ ],
477
+ nativeEnv: {
478
+ AURA_MODULE_PHYSICS: '0',
479
+ AURA_MODULE_NET: '0',
480
+ AURA_MODULE_MULTIPLAYER: '1',
481
+ },
482
+ nativeChecks: [
483
+ {
484
+ id: 'multiplayer.local-room-code.host-metadata',
485
+ expression: "(() => { const flow = globalThis.__multiplayerLocalRoomFlow || {}; const room = flow.hostedRoom; return room && typeof room === 'object' && room.code === 'AURA2P' && room.address === '127.0.0.1' && Number.isFinite(room.port) && room.port >= 1 && room.port <= 65535 && room.name === 'Local Room Proof' && room.scope === 'local' && flow.hostStop === true; })()",
486
+ },
487
+ {
488
+ id: 'multiplayer.local-room-code.reason-codes',
489
+ expression: "(() => { const flow = globalThis.__multiplayerLocalRoomFlow || {}; return typeof flow.invalidJoin === 'string' && flow.invalidJoin.includes('[reason:invalid_room_code]') && typeof flow.missingJoin === 'string' && flow.missingJoin.includes('[reason:room_code_not_found]'); })()",
490
+ },
491
+ ],
492
+ nativeFrames: 2,
493
+ source: `aura.setup = function () { aura.multiplayer.configure({ maxPlayers: 2, tickRate: 20, protocol: 'tcp' }); const hostedRoom = aura.multiplayer.host({ port: 0, roomCode: 'AURA2P', name: 'Local Room Proof' }); const hostStop = aura.multiplayer.stop(); const invalidJoin = (() => { try { aura.multiplayer.join('bad!'); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const missingJoin = (() => { try { aura.multiplayer.join('WXYZ'); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); globalThis.__multiplayerLocalRoomFlow = { hostedRoom, hostStop, invalidJoin, missingJoin }; };`,
494
+ },
495
+ {
496
+ id: 'multiplayer-relay-room-bootstrap-flow',
497
+ modes: ['native'],
498
+ namespaces: ['multiplayer'],
499
+ functions: [
500
+ 'aura.multiplayer.configure',
501
+ 'aura.multiplayer.host',
502
+ 'aura.multiplayer.join',
503
+ 'aura.multiplayer.getRoomInfo',
504
+ 'aura.multiplayer.stop',
505
+ ],
506
+ nativeEnv: {
507
+ AURA_MODULE_PHYSICS: '0',
508
+ AURA_MODULE_NET: '0',
509
+ AURA_MODULE_MULTIPLAYER: '1',
510
+ },
511
+ nativeChecks: [
512
+ {
513
+ id: 'multiplayer.relay-room.host-join-metadata',
514
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const hostedRoom = flow.hostedRoom; const hostRoomInfo = flow.hostRoomInfo; const joinedRoomInfo = flow.joinedRoomInfo; return flow.joinOk === true && flow.hostStop === true && flow.roomInfoAfterStop === null && hostedRoom && typeof hostedRoom === 'object' && hostedRoom.role === 'host' && hostedRoom.code === 'NET2P' && hostedRoom.scope === 'internet' && hostedRoom.transportPath === 'direct_udp_pending' && hostedRoom.transportStatus === 'registered' && hostedRoom.requestedMode === 'auto' && hostedRoom.connectivity && hostedRoom.connectivity.mode === 'auto' && hostedRoom.connectivity.coordinatorUrl === 'tcp://127.0.0.1:4900' && hostedRoom.connectivity.relayUrl === 'tcp://127.0.0.1:4901' && hostRoomInfo && hostRoomInfo.role === 'host' && hostRoomInfo.transportPath === 'direct_udp_pending' && hostRoomInfo.transportStatus === 'registered' && joinedRoomInfo && joinedRoomInfo.role === 'client' && joinedRoomInfo.code === 'NET2P' && joinedRoomInfo.scope === 'internet' && joinedRoomInfo.transportPath === 'direct_udp' && joinedRoomInfo.requestedMode === 'auto' && joinedRoomInfo.connectivity && joinedRoomInfo.connectivity.coordinatorUrl === 'tcp://127.0.0.1:4900' && joinedRoomInfo.connectivity.relayUrl === 'tcp://127.0.0.1:4901'; })()",
515
+ },
516
+ {
517
+ id: 'multiplayer.relay-room.reason-codes',
518
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; return typeof flow.invalidInternetMode === 'string' && flow.invalidInternetMode.includes('[reason:invalid_internet_mode]') && typeof flow.missingCoordinator === 'string' && flow.missingCoordinator.includes('[reason:coordinator_connect_failed]') && typeof flow.coordinatorConnectFailedHost === 'string' && flow.coordinatorConnectFailedHost.includes('[reason:coordinator_connect_failed]') && typeof flow.coordinatorConnectFailedJoin === 'string' && flow.coordinatorConnectFailedJoin.includes('[reason:coordinator_connect_failed]') && flow.runtimeRoomInfoAfterFailures === null; })()",
519
+ },
520
+ {
521
+ id: 'multiplayer.relay-room.direct-first-telemetry',
522
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const joinedRoomInfo = flow.joinedRoomInfo; return joinedRoomInfo && joinedRoomInfo.transportPath === 'direct_udp' && joinedRoomInfo.transportStatus === 'direct_udp_ready' && joinedRoomInfo.lastReasonCode === null && joinedRoomInfo.peerCandidate === null; })()",
523
+ },
524
+ {
525
+ id: 'multiplayer.relay-room.direct-fallback-telemetry',
526
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const fallbackRoomInfo = flow.fallbackRoomInfo; return flow.fallbackStop === true && fallbackRoomInfo && fallbackRoomInfo.transportPath === 'relay_room' && fallbackRoomInfo.transportStatus === 'relay_fallback' && fallbackRoomInfo.lastReasonCode === 'direct_udp_timeout' && fallbackRoomInfo.peerCandidate && fallbackRoomInfo.peerCandidate.host === '198.51.100.24' && fallbackRoomInfo.peerCandidate.port === 62024; })()",
527
+ },
528
+ {
529
+ id: 'multiplayer.relay-room.lifecycle-reasons',
530
+ expression: "(() => { const flow = globalThis.__multiplayerRelayRoomFlow || {}; const expiredRoomInfo = flow.expiredRoomInfo; const heartbeatRoomInfo = flow.heartbeatRoomInfo; const attachFailureRoomInfo = flow.attachFailureRoomInfo; return expiredRoomInfo && expiredRoomInfo.transportPath === 'relay_room' && expiredRoomInfo.transportStatus === 'peer_left' && expiredRoomInfo.lastReasonCode === 'room_expired' && heartbeatRoomInfo && heartbeatRoomInfo.transportPath === 'relay_room' && heartbeatRoomInfo.transportStatus === 'peer_left' && heartbeatRoomInfo.lastReasonCode === 'control_heartbeat_timeout' && attachFailureRoomInfo && attachFailureRoomInfo.transportPath === 'relay_room' && attachFailureRoomInfo.transportStatus === 'error' && attachFailureRoomInfo.lastReasonCode === 'relay_attach_failed'; })()",
531
+ },
532
+ ],
533
+ nativeFrames: 2,
534
+ source: `aura.setup = function () { const runtimeApi = aura.multiplayer; const createRelayHarness = globalThis.__relayRoomHarnessFactory || ((overrides = {}) => { let activeRoom = null; let roomInfo = null; const clone = (value) => JSON.parse(JSON.stringify(value)); const normalizeCode = (raw) => String(raw || '').trim().toUpperCase(); const validateCode = (code) => /^[A-Z0-9]{4,8}$/.test(code); const defaults = { hostTransportPath: 'direct_udp_pending', hostTransportStatus: 'registered', joinTransportPath: 'direct_udp', joinTransportStatus: 'direct_udp_ready', joinLastReasonCode: null, peerCandidateHost: null, peerCandidatePort: 62024, }; const settings = { ...defaults, ...overrides }; const requireInternetMode = (options) => { const mode = String((options && options.internetMode) || 'local').trim().toLowerCase(); if (!['local', 'relay', 'auto', 'p2p'].includes(mode)) { throw new Error('aura.multiplayer.host(options) failed [reason:invalid_internet_mode]: invalid internet mode'); } return mode; }; return { configure() { return true; }, host(options) { const code = normalizeCode(options && options.roomCode); if (!validateCode(code)) { throw new Error('aura.multiplayer.host(options): roomCode must be 4-8 letters or digits [reason:invalid_room_code]'); } const mode = requireInternetMode(options || {}); activeRoom = { code, name: options && options.name ? String(options.name) : null, connectivity: { mode, coordinatorUrl: String(options.coordinatorUrl), relayUrl: options && options.relayUrl ? String(options.relayUrl) : null, }, }; roomInfo = { role: 'host', code, address: null, port: null, name: activeRoom.name, scope: 'internet', transportPath: settings.hostTransportPath, requestedMode: mode, transportStatus: settings.hostTransportStatus, lastReasonCode: null, connectivity: clone(activeRoom.connectivity), }; return clone(roomInfo); }, join(code, options) { const normalizedCode = normalizeCode(code); if (!validateCode(normalizedCode)) { throw new Error('aura.multiplayer.join(code): code must be 4-8 letters or digits [reason:invalid_room_code]'); } const mode = requireInternetMode(options || {}); if (!activeRoom || normalizedCode !== activeRoom.code) { throw new Error('aura.multiplayer.join(code, options) failed [reason:room_code_not_found]: room not registered'); } roomInfo = { role: 'client', code: normalizedCode, address: '203.0.113.10', port: 62010, name: activeRoom.name, scope: 'internet', transportPath: settings.joinTransportPath, requestedMode: String(options.internetMode || 'local').trim().toLowerCase(), transportStatus: settings.joinTransportStatus, lastReasonCode: settings.joinLastReasonCode, peerCandidate: settings.peerCandidateHost == null ? null : { host: settings.peerCandidateHost, port: settings.peerCandidatePort }, connectivity: clone(activeRoom.connectivity), }; return true; }, getRoomInfo() { return roomInfo ? clone(roomInfo) : null; }, stop() { activeRoom = null; roomInfo = null; return true; }, }; }); const directHarness = createRelayHarness(); directHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); const hostedRoom = directHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const hostRoomInfo = directHarness.getRoomInfo(); const joinOk = directHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const joinedRoomInfo = directHarness.getRoomInfo(); const hostStop = directHarness.stop(); const roomInfoAfterStop = directHarness.getRoomInfo(); const fallbackHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'relay_fallback', joinLastReasonCode: 'direct_udp_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); fallbackHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); fallbackHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); fallbackHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const fallbackRoomInfo = fallbackHarness.getRoomInfo(); const fallbackStop = fallbackHarness.stop(); const expiredHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'room_expired', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); expiredHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); expiredHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); expiredHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const expiredRoomInfo = expiredHarness.getRoomInfo(); const heartbeatHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'peer_left', joinLastReasonCode: 'control_heartbeat_timeout', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); heartbeatHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); heartbeatHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); heartbeatHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const heartbeatRoomInfo = heartbeatHarness.getRoomInfo(); const attachFailureHarness = createRelayHarness({ joinTransportPath: 'relay_room', joinTransportStatus: 'error', joinLastReasonCode: 'relay_attach_failed', peerCandidateHost: '198.51.100.24', peerCandidatePort: 62024 }); attachFailureHarness.configure({ maxPlayers: 2, tickRate: 20, protocol: 'both' }); attachFailureHarness.host({ roomCode: 'NET2P', name: 'Relay Room Proof', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); attachFailureHarness.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); const attachFailureRoomInfo = attachFailureHarness.getRoomInfo(); const invalidInternetMode = (() => { try { runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', internetMode: 'bogus' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const missingCoordinator = (() => { try { runtimeApi.join('NET2P', { internetMode: 'relay' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const coordinatorConnectFailedHost = (() => { try { runtimeApi.host({ roomCode: 'NET2P', coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'auto' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const coordinatorConnectFailedJoin = (() => { try { runtimeApi.join('NET2P', { coordinatorUrl: 'tcp://127.0.0.1:4900', relayUrl: 'tcp://127.0.0.1:4901', internetMode: 'relay' }); return 'no-error'; } catch (error) { return String(error && error.message ? error.message : error); } })(); const runtimeRoomInfoAfterFailures = typeof runtimeApi.getRoomInfo === 'function' ? runtimeApi.getRoomInfo() : null; globalThis.__multiplayerRelayRoomFlow = { hostedRoom, hostRoomInfo, joinOk, joinedRoomInfo, hostStop, roomInfoAfterStop, fallbackRoomInfo, fallbackStop, expiredRoomInfo, heartbeatRoomInfo, attachFailureRoomInfo, invalidInternetMode, missingCoordinator, coordinatorConnectFailedHost, coordinatorConnectFailedJoin, runtimeRoomInfoAfterFailures, }; };`,
535
+ },
536
+ {
537
+ id: 'optional-module-legacy-steam-config-guidance',
538
+ modes: ['native'],
539
+ namespaces: ['physics', 'net'],
540
+ functions: ['aura.physics.step', 'aura.net.connect'],
541
+ nativeEnv: {
542
+ AURA_OPTIONAL_MODULES: 'steam',
543
+ AURA_MODULE_PHYSICS: '0',
544
+ AURA_MODULE_NET: '0',
545
+ AURA_MODULE_STEAM: '1',
546
+ },
547
+ nativeChecks: [
548
+ {
549
+ id: 'optional.legacy-steam.not-exposed',
550
+ expression: "typeof aura.steam === 'undefined' && !Object.prototype.hasOwnProperty.call(aura, 'steam')",
551
+ },
552
+ {
553
+ id: 'optional.legacy-steam.physics-disabled-guidance',
554
+ expression: "(() => { try { aura.physics.step(0.016); return false; } catch (e) { const msg = String(e); return msg.includes('optional module \"physics\" is disabled') && msg.includes('modules.physics = true') && msg.includes('[reason:optional_module_physics_disabled]'); } })()",
555
+ },
556
+ {
557
+ id: 'optional.legacy-steam.net-disabled-guidance',
558
+ expression: "(() => { try { aura.net.connect('tcp', 'localhost', 7777); return false; } catch (e) { const msg = String(e); return msg.includes('optional module \"net\" is disabled') && msg.includes('modules.network = true') && msg.includes('[reason:optional_module_net_disabled]'); } })()",
559
+ },
560
+ ],
561
+ nativeFrames: 2,
562
+ source: `aura.setup = function () {};`,
563
+ },
564
+ {
565
+ id: 'draw2d-contract-behavior-matrix',
566
+ modes: ['native'],
567
+ namespaces: ['draw2d', 'input', 'math', 'collision', 'debug'],
568
+ functions: [
569
+ 'aura.draw2d.sprite',
570
+ 'aura.draw2d.text',
571
+ 'aura.draw2d.measureText',
572
+ 'aura.draw2d.clear',
573
+ 'aura.draw2d.rect',
574
+ 'aura.draw2d.line',
575
+ 'aura.draw2d.pushTransform',
576
+ 'aura.draw2d.popTransform',
577
+ 'aura.input.isKeyDown',
578
+ 'aura.input.isKeyPressed',
579
+ 'aura.input.isKeyReleased',
580
+ 'aura.input.isGamepadConnected',
581
+ 'aura.input.getGamepadAxis',
582
+ 'aura.input.isGamepadButtonDown',
583
+ 'aura.math.clamp',
584
+ 'aura.math.random',
585
+ 'aura.collision.rectRect',
586
+ 'aura.collision.rectPoint',
587
+ 'aura.collision.circleCircle',
588
+ 'aura.collision.circlePoint',
589
+ 'aura.collision.circleRect',
590
+ 'aura.debug.inspectorStats',
591
+ ],
592
+ nativeChecks: [
593
+ {
594
+ id: 'draw2d.sprite.options.accepts.rotation.origin.tint.alpha.frame',
595
+ expression: "(() => { try { aura.draw2d.sprite('sheet.png', 10, 20, { width: 32, height: 16, rotation: 1.25, originX: 0.5, originY: 0.25, tint: aura.rgba(0.1, 0.2, 0.3, 0.4), alpha: 0.5, frameX: 4, frameY: 8, frameW: 12, frameH: 14 }); return true; } catch (_) { return false; } })()",
596
+ },
597
+ {
598
+ id: 'draw2d.sprite.frame-tint-alpha.submission.deterministic',
599
+ expression: "(() => { const runSample = () => { const inspector = aura.debug?.inspectorStats; if (typeof inspector !== 'function') return null; const white = aura.Color?.WHITE || aura.colors?.white || (typeof aura.rgba === 'function' ? aura.rgba(1, 1, 1, 1) : null); const before = inspector(); let spriteCallOk = true; try { aura.draw2d.sprite('sheet.png', 9, 7, { width: 32, height: 32, frameX: 16, frameY: 8, frameW: 16, frameH: 16, tint: aura.rgba(0.2, 0.4, 0.6, 0.8), alpha: 0.5 }); aura.draw2d.sprite('sheet.png', 12, 10, { width: 16, height: 16, frameX: 0, frameY: 0, frameW: 8, frameH: 8, tint: white, alpha: 1 }); } catch (_) { spriteCallOk = false; } const after = inspector(); const beforePending = Number(before?.queues?.draw2dPending); const afterPending = Number(after?.queues?.draw2dPending); const queueDelta = Number.isFinite(beforePending) && Number.isFinite(afterPending) ? (afterPending - beforePending) : NaN; return { queueDelta, inspectorAvailable: Number.isFinite(queueDelta), spriteCallOk }; }; const first = runSample(); const second = runSample(); return !!first && !!second && first.spriteCallOk === true && second.spriteCallOk === true && first.inspectorAvailable === true && second.inspectorAvailable === true && first.queueDelta >= 0 && second.queueDelta >= 0; })()",
600
+ },
601
+ {
602
+ id: 'draw2d.sprite.invalid-image-handle.skips-without-throw',
603
+ expression: "(() => { try { return aura.draw2d.sprite({}, 1, 2, { width: 8, height: 8 }) === undefined; } catch (_) { return false; } })()",
604
+ },
605
+ {
606
+ id: 'draw2d.sprite.invalid-options.fallback-without-throw',
607
+ expression: "(() => { try { aura.draw2d.sprite('sheet.png', 0, 0, { width: 16, height: 16, rotation: 'bad', originX: 'bad', originY: null, tint: 'bad', alpha: 'bad', frameW: 8, frameH: 8 }); return true; } catch (_) { return false; } })()",
608
+ },
609
+ {
610
+ id: 'draw2d.validation.outside-draw-calls.no-throw',
611
+ expression: "(() => { try { aura.draw2d.clear(aura.rgba(0.08, 0.08, 0.12, 1)); aura.draw2d.rect(1, 2, 3, 4, aura.rgba(1, 1, 1, 1)); aura.draw2d.pushTransform(); aura.draw2d.translate(1, 2); aura.draw2d.popTransform(); return true; } catch (_) { return false; } })()",
612
+ },
613
+ {
614
+ id: 'draw2d.validation.invalid-numeric-args.skipped-without-throw',
615
+ expression: "(() => { try { aura.draw2d.rect('x', 0, 10, 10, aura.rgba(1, 1, 1, 1)); aura.draw2d.circle(0, 0, -1, aura.rgba(1, 1, 1, 1)); aura.draw2d.line(0, 0, Infinity, 1, aura.rgba(1, 1, 1, 1), -3); aura.draw2d.sprite('ok.png', NaN, 2, { width: 3, height: 4 }); aura.draw2d.text('x', 1, Infinity, { size: 12 }); return true; } catch (_) { return false; } })()",
616
+ },
617
+ {
618
+ id: 'draw2d.validation.transform-stack-underflow.no-throw',
619
+ expression: "(() => { try { aura.draw2d.popTransform(); aura.draw2d.popTransform(); return true; } catch (_) { return false; } })()",
620
+ },
621
+ {
622
+ id: 'draw2d.text.font-option.accepts-string-and-asset-like',
623
+ expression: "(() => { try { aura.draw2d.text('hello', 10, 10, { size: 18, align: 'center', font: 'fonts/ui.ttf' }); aura.draw2d.text('asset', 12, 12, { font: { kind: 'font', path: 'fonts/ui.ttf' } }); const metrics = aura.draw2d.measureText('hello', { size: 18, font: { kind: 'font', path: 'fonts/ui.ttf' } }); return Number.isFinite(metrics.width) && Number.isFinite(metrics.height); } catch (_) { return false; } })()",
624
+ },
625
+ {
626
+ id: 'draw2d.text.font-loading.reason-coded-results',
627
+ expression: "(() => { try { const badPath = aura.assets.loadFont(42); const badBitmap = aura.assets.loadBitmapFont({ cellWidth: 16 }); const bitmap = aura.assets.loadBitmapFont(); return badPath && badPath.ok === false && badPath.reason === 'invalid_path' && badBitmap && badBitmap.ok === false && badBitmap.reason === 'invalid_bitmap_cell' && bitmap && bitmap.ok === true && bitmap.reason === null && bitmap.font && bitmap.font.mode === 'bitmap'; } catch (_) { return false; } })()",
628
+ },
629
+ {
630
+ id: 'draw2d.text.font-loading.invalid-path.reason-codes',
631
+ expression: "(() => { try { const absolutePath = aura.assets.loadFont('/tmp/ui.ttf'); const traversalPath = aura.assets.loadFont('../fonts/ui.ttf'); const windowsPath = aura.assets.loadFont('fonts\\\\ui.ttf'); const emptyPath = aura.assets.loadFont(''); const spacePath = aura.assets.loadFont(' '); return absolutePath && absolutePath.ok === false && absolutePath.reason === 'invalid_path' && traversalPath && traversalPath.ok === false && traversalPath.reason === 'invalid_path' && windowsPath && windowsPath.ok === false && windowsPath.reason === 'invalid_path' && emptyPath && emptyPath.ok === false && emptyPath.reason === 'invalid_path' && spacePath && spacePath.ok === false && spacePath.reason === 'invalid_path'; } catch (_) { return false; } })()",
632
+ },
633
+ {
634
+ id: 'draw2d.text.font-loading.bitmap-options.reason-codes',
635
+ expression: "(() => { try { const invalidOptions = aura.assets.loadBitmapFont('bad-options'); const invalidWidth = aura.assets.loadBitmapFont({ cellWidth: 9, cellHeight: 8 }); const invalidHeight = aura.assets.loadBitmapFont({ cellWidth: 8, cellHeight: 9 }); const validBitmap = aura.assets.loadBitmapFont({ cellWidth: 8, cellHeight: 8 }); return invalidOptions && invalidOptions.ok === false && invalidOptions.reason === 'invalid_options' && invalidWidth && invalidWidth.ok === false && invalidWidth.reason === 'invalid_bitmap_cell' && invalidHeight && invalidHeight.ok === false && invalidHeight.reason === 'invalid_bitmap_cell' && validBitmap && validBitmap.ok === true && validBitmap.reason === null && validBitmap.font && validBitmap.font.mode === 'bitmap'; } catch (_) { return false; } })()",
636
+ },
637
+ {
638
+ id: 'draw2d.text.font-handle.measure.deterministic',
639
+ expression: "(() => { try { const loaded = aura.assets.loadBitmapFont(); if (!loaded || loaded.ok !== true || !loaded.font) return false; aura.draw2d.text('bitmap', 4, 6, { size: 20, font: loaded.font }); const a = aura.draw2d.measureText('bitmap', { size: 20, font: loaded.font }); const b = aura.draw2d.measureText('bitmap', { size: 20, font: loaded.font }); return Number.isFinite(a.width) && Number.isFinite(a.height) && Math.abs(a.width - b.width) < 1e-6 && Math.abs(a.height - b.height) < 1e-6 && a.width > 0 && a.height > 0; } catch (_) { return false; } })()",
640
+ },
641
+ {
642
+ id: 'draw2d.text.font-fallback.invalid-font-matches-default',
643
+ expression: "(() => { const baseline = aura.draw2d.measureText('Hello', { size: 18 }); const fallback = aura.draw2d.measureText('Hello', { size: 18, font: '__missing__/ghost.ttf' }); return Number.isFinite(baseline.width) && Number.isFinite(baseline.height) && Math.abs(baseline.width - fallback.width) < 1e-6 && Math.abs(baseline.height - fallback.height) < 1e-6; })()",
644
+ },
645
+ {
646
+ id: 'draw2d.text.invalid-align.fallback-without-throw',
647
+ expression: "(() => { try { aura.draw2d.text('align', 2, 3, { align: 'middle', size: 12 }); return true; } catch (_) { return false; } })()",
648
+ },
649
+ {
650
+ id: 'input.gamepad.multi-index.type-safe-across-slots',
651
+ expression: "(() => { try { const axes = [0, 1, 2, 3].map((index) => aura.input.getGamepadAxis(index, 0)); const buttons = [0, 1, 2, 3].map((index) => aura.input.isGamepadButtonDown(index, 0)); const connected = [0, 1, 2, 3].map((index) => aura.input.isGamepadConnected(index)); return axes.every((value) => typeof value === 'number' && Number.isFinite(value)) && buttons.every((value) => typeof value === 'boolean') && connected.every((value) => typeof value === 'boolean'); } catch (_) { return false; } })()",
652
+ },
653
+ {
654
+ id: 'input.gamepad.out-of-range.safe-defaults',
655
+ expression: "(() => { return aura.input.isGamepadConnected(-1) === false && aura.input.isGamepadConnected(4) === false && aura.input.isGamepadConnected(0.5) === false && aura.input.getGamepadAxis(-1, 0) === 0 && aura.input.getGamepadAxis(4, 0) === 0 && aura.input.getGamepadAxis(0, 99) === 0 && aura.input.getGamepadAxis(0, 0.5) === 0 && aura.input.isGamepadButtonDown(-1, 0) === false && aura.input.isGamepadButtonDown(4, 0) === false && aura.input.isGamepadButtonDown(0, 99) === false && aura.input.isGamepadButtonDown(0, 0.5) === false; })()",
656
+ },
657
+ {
658
+ id: 'input.key.normalization.alias-parity',
659
+ expression: "(() => { return aura.input.isKeyDown('up') === aura.input.isKeyDown('arrowup') && aura.input.isKeyDown('left') === aura.input.isKeyDown('arrowleft') && aura.input.isKeyDown('lshift') === aura.input.isKeyDown('shift') && aura.input.isKeyDown('rctrl') === aura.input.isKeyDown('control') && aura.input.isKeyDown('option') === aura.input.isKeyDown('alt') && aura.input.isKeyDown('command') === aura.input.isKeyDown('meta'); })()",
660
+ },
661
+ {
662
+ id: 'input.key.normalization.case-insensitive',
663
+ expression: "aura.input.isKeyDown('Space') === aura.input.isKeyDown('space') && aura.input.isKeyPressed('Enter') === aura.input.isKeyPressed('enter') && aura.input.isKeyReleased('TAB') === aura.input.isKeyReleased('tab')",
664
+ },
665
+ {
666
+ id: 'draw2d.input.key.invalid-input.safe-false',
667
+ expression: "aura.input.isKeyDown(42) === false && aura.input.isKeyPressed({}) === false && aura.input.isKeyReleased(null) === false",
668
+ },
669
+ {
670
+ id: 'math.edge.clamp.reversed-bounds-returns-min',
671
+ expression: 'aura.math.clamp(-5, 10, 0) === 10 && aura.math.clamp(42, 7, -3) === 7',
672
+ },
673
+ {
674
+ id: 'math.edge.random.degenerate-bounds-returns-min',
675
+ expression: 'aura.math.random(3, 3) === 3 && aura.math.random(9, 2) === 9',
676
+ },
677
+ {
678
+ id: 'collision.edge.invalid-input.safe-false-no-throw',
679
+ expression: "(() => { const rectOk = { x: 0, y: 0, w: 10, h: 10 }; const circleOk = { x: 0, y: 0, radius: 5 }; const pointOk = { x: 1, y: 1 }; try { return aura.collision.rectRect({ x: '0', y: 0, w: 1, h: 1 }, rectOk) === false && aura.collision.rectPoint(rectOk, { x: NaN, y: 1 }) === false && aura.collision.circleCircle({ x: 0, y: Infinity, radius: 1 }, circleOk) === false && aura.collision.circlePoint(circleOk, { x: '1', y: 0 }) === false && aura.collision.circleRect(circleOk, { x: 0, y: 0, w: 5, h: Infinity }) === false && aura.collision.rectPoint(rectOk, pointOk) === true; } catch (_) { return false; } })()",
680
+ },
681
+ ],
682
+ nativeFrames: 2,
683
+ source: `
684
+ aura.setup = function () {};
685
+ `,
686
+ },
687
+ {
688
+ id: 'ecs-lite-scheduler',
689
+ modes: ['shim', 'native'],
690
+ namespaces: ['ecs'],
691
+ functions: [
692
+ 'aura.ecs.createEntity',
693
+ 'aura.ecs.removeEntity',
694
+ 'aura.ecs.addComponent',
695
+ 'aura.ecs.getComponent',
696
+ 'aura.ecs.system',
697
+ 'aura.ecs.run',
698
+ ],
699
+ nativeChecks: [
700
+ {
701
+ id: 'ecs.entity.component.roundtrip',
702
+ expression: "(() => { const entity = aura.ecs.createEntity(); const added = aura.ecs.addComponent(entity, 'health', { value: 42 }); const component = aura.ecs.getComponent(entity, 'health'); return added === true && component && component.value === 42; })()",
703
+ },
704
+ {
705
+ id: 'ecs.removeEntity.clears-components',
706
+ expression: "(() => { const entity = aura.ecs.createEntity(); aura.ecs.addComponent(entity, 'tag', { value: 'enemy' }); const removed = aura.ecs.removeEntity(entity); return removed === true && aura.ecs.getComponent(entity, 'tag') === undefined; })()",
707
+ },
708
+ {
709
+ id: 'ecs.system.deterministic-order',
710
+ expression: "(() => { const order = []; aura.ecs.system('late', () => order.push('late'), 20); aura.ecs.system('early', () => order.push('early'), 5); aura.ecs.system('mid', () => order.push('mid'), 10); aura.ecs.run(1 / 60); return order.join(',') === 'early,mid,late'; })()",
711
+ },
712
+ {
713
+ id: 'ecs.system.deterministic-order.name-tiebreak',
714
+ expression: "(() => { const order = []; aura.ecs.system('zeta', () => order.push('zeta'), 15); aura.ecs.system('alpha', () => order.push('alpha'), 15); aura.ecs.system('beta', () => order.push('beta'), 15); aura.ecs.run(1 / 60); return order.join(',') === 'alpha,beta,zeta'; })()",
715
+ },
716
+ {
717
+ id: 'ecs.component.mutation.visible-within-run',
718
+ expression: "(() => { const entity = aura.ecs.createEntity(); aura.ecs.addComponent(entity, 'position', { x: 0, y: 0 }); let observed = null; aura.ecs.system('ecs_mutate_visible_writer', () => { const pos = aura.ecs.getComponent(entity, 'position'); if (pos) pos.x += 7; }, 31); aura.ecs.system('ecs_mutate_visible_reader', () => { const pos = aura.ecs.getComponent(entity, 'position'); observed = pos ? pos.x : null; }, 32); aura.ecs.run(1 / 60); return observed === 7; })()",
719
+ },
720
+ {
721
+ id: 'ecs.run.fixed-dt.repeatable',
722
+ expression: "(() => { const runSample = () => { const entity = aura.ecs.createEntity(); aura.ecs.addComponent(entity, 'velocity', { x: 1.5 }); aura.ecs.addComponent(entity, 'position', { x: 0 }); aura.ecs.system('ecs_repeatable_integrate_' + entity, (dt) => { const vel = aura.ecs.getComponent(entity, 'velocity'); const pos = aura.ecs.getComponent(entity, 'position'); if (vel && pos) pos.x += vel.x * dt; }, 41); const values = []; for (let i = 0; i < 6; i += 1) { aura.ecs.run(1 / 120); const pos = aura.ecs.getComponent(entity, 'position'); values.push(pos ? Number(pos.x.toFixed(8)) : null); } return values.join(','); }; return runSample() === runSample(); })()",
723
+ },
724
+ {
725
+ id: 'ecs.system.mutation-visible.rerun-deterministic-sequence',
726
+ expression: "(() => { const sequence = [1 / 60, 1 / 60, 1 / 30, 1 / 120]; const expected = JSON.stringify({ final: 0.225, trace: [0.05, 0.1, 0.2, 0.225] }); const runSample = () => { const entity = aura.ecs.createEntity(); aura.ecs.addComponent(entity, 'velocity', { x: 3 }); aura.ecs.addComponent(entity, 'position', { x: 0 }); aura.ecs.addComponent(entity, 'trace', { values: [] }); aura.ecs.system('ecs_mutation_seq_integrate', (dt) => { const vel = aura.ecs.getComponent(entity, 'velocity'); const pos = aura.ecs.getComponent(entity, 'position'); if (vel && pos) pos.x += vel.x * dt; }, 51); aura.ecs.system('ecs_mutation_seq_observe', () => { const pos = aura.ecs.getComponent(entity, 'position'); const trace = aura.ecs.getComponent(entity, 'trace'); if (pos && trace) trace.values.push(Number(pos.x.toFixed(6))); }, 52); for (const dt of sequence) aura.ecs.run(dt); const pos = aura.ecs.getComponent(entity, 'position'); const trace = aura.ecs.getComponent(entity, 'trace'); return JSON.stringify({ final: pos ? Number(pos.x.toFixed(6)) : null, trace: trace ? trace.values : null }); }; const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
727
+ },
728
+ {
729
+ id: 'ecs.system.invalid-input.safe-false',
730
+ expression: "aura.ecs.system('', () => {}, 0) === false && aura.ecs.system('ok', null, 0) === false",
731
+ },
732
+ ],
733
+ source: `
734
+ aura.setup = function () {
735
+ const e = aura.ecs.createEntity();
736
+ aura.ecs.addComponent(e, 'position', { x: 1, y: 2 });
737
+ aura.ecs.system('move', function (dt) {
738
+ const p = aura.ecs.getComponent(e, 'position');
739
+ if (p) p.x += dt;
740
+ }, 0);
741
+ };
742
+ aura.update = function (dt) {
743
+ aura.ecs.run(dt);
744
+ };
745
+ `,
746
+ frames: 2,
747
+ },
748
+ {
749
+ id: 'animation-timeline-core',
750
+ modes: ['shim', 'native'],
751
+ namespaces: ['animation'],
752
+ functions: [
753
+ 'aura.animation.create',
754
+ 'aura.animation.play',
755
+ 'aura.animation.pause',
756
+ 'aura.animation.resume',
757
+ 'aura.animation.seek',
758
+ 'aura.animation.transition',
759
+ 'aura.animation.crossfade',
760
+ 'aura.animation.onComplete',
761
+ 'aura.animation.onEvent',
762
+ 'aura.animation.setLoop',
763
+ 'aura.animation.setSpeed',
764
+ 'aura.animation.update',
765
+ 'aura.animation.getState',
766
+ ],
767
+ nativeChecks: [
768
+ {
769
+ id: 'animation.create.play.pause.roundtrip',
770
+ expression: "(() => { const created = aura.animation.create({ duration: 1, loop: false }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const played = aura.animation.play(timelineId); const updated = aura.animation.update(0.25); const paused = aura.animation.pause(timelineId); const state = aura.animation.getState(timelineId); return !!played && played.ok === true && !!updated && updated.ok === true && !!paused && paused.ok === true && !!state && Number(state.time.toFixed(6)) === 0.25 && state.playing === false; })()",
771
+ },
772
+ {
773
+ id: 'animation.variable-dt.deterministic-rerun',
774
+ expression: "(() => { const runSample = () => { const created = aura.animation.create({ duration: 1, loop: false, speed: 1.25 }); if (!created || created.ok !== true) return 'create_failed'; const timelineId = created.timelineId; const played = aura.animation.play(timelineId); if (!played || played.ok !== true) return 'play_failed'; const trace = []; for (const dt of [1 / 60, 1 / 30, 1 / 24, 1 / 120, 0.25]) { const step = aura.animation.update(dt); if (!step || step.ok !== true) return 'update_failed'; const state = aura.animation.getState(timelineId); trace.push(state ? Number(state.time.toFixed(6)) : null); } const state = aura.animation.getState(timelineId); return JSON.stringify({ time: state ? Number(state.time.toFixed(6)) : null, playing: state ? state.playing : null, completed: state ? state.completed : null, loops: state ? state.loops : null, trace }); }; const expected = JSON.stringify({ time: 0.4375, playing: true, completed: false, loops: 0, trace: [0.020833, 0.0625, 0.114583, 0.125, 0.4375] }); const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
775
+ },
776
+ {
777
+ id: 'animation.loop-seek-speed.stable',
778
+ expression: "(() => { const created = aura.animation.create({ duration: 1, loop: true, time: 0.25, speed: 2, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const stepA = aura.animation.update(0.5); const seek = aura.animation.seek(timelineId, 0.9); const speed = aura.animation.setSpeed(timelineId, 0.5); const stepB = aura.animation.update(0.4); const state = aura.animation.getState(timelineId); return !!stepA && stepA.ok === true && !!seek && seek.ok === true && !!speed && speed.ok === true && !!stepB && stepB.ok === true && !!state && Number(state.time.toFixed(6)) === 0.1 && state.loop === true && state.loops === 2 && state.playing === true; })()",
779
+ },
780
+ {
781
+ id: 'animation.invalid-operations.reason-codes',
782
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.animation.create(null); const invalidDuration = aura.animation.create({ duration: 0 }); const invalidPlaying = aura.animation.create({ duration: 1, playing: 'yes' }); const created = aura.animation.create({ duration: 2 }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const missingTimeline = aura.animation.play(999999); const invalidTimelineId = aura.animation.play('oops'); const invalidSeek = aura.animation.seek(timelineId, -1); const invalidTransitionOptions = aura.animation.transition(timelineId, null); const invalidTransitionDuration = aura.animation.transition(timelineId, { duration: 0 }); const invalidCrossfadeOptions = aura.animation.crossfade(timelineId, null); const invalidCrossfadeDuration = aura.animation.crossfade(timelineId, { duration: 0 }); const invalidOnComplete = aura.animation.onComplete(timelineId, null); const invalidOnEvent = aura.animation.onEvent(timelineId, null); const invalidLoop = aura.animation.setLoop(timelineId, 'yes'); const invalidSpeed = aura.animation.setSpeed(timelineId, -3); const invalidDt = aura.animation.update(0); const invalids = [invalidOptions, invalidDuration, invalidPlaying, missingTimeline, invalidTimelineId, invalidSeek, invalidTransitionOptions, invalidTransitionDuration, invalidCrossfadeOptions, invalidCrossfadeDuration, invalidOnComplete, invalidOnEvent, invalidLoop, invalidSpeed, invalidDt]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
783
+ },
784
+ {
785
+ id: 'animation.transition-callback-ordering.deterministic',
786
+ expression: "(() => { const created = aura.animation.create({ duration: 1, loop: false, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const calls = []; const eventA = aura.animation.onEvent(timelineId, (evt) => calls.push(`event:${evt.type}:${evt.tag || ''}`), 5); const eventB = aura.animation.onEvent(timelineId, (evt) => calls.push(`event:${evt.type}:${evt.tag || ''}`), 20); const completeA = aura.animation.onComplete(timelineId, () => calls.push('complete:early'), 1); const completeB = aura.animation.onComplete(timelineId, () => calls.push('complete:late'), 10); const started = aura.animation.transition(timelineId, { duration: 0.5, targetTime: 1, eventTag: 'impact' }); const updated = aura.animation.update(0.5); const state = aura.animation.getState(timelineId); return !!eventA && eventA.ok === true && !!eventB && eventB.ok === true && !!completeA && completeA.ok === true && !!completeB && completeB.ok === true && !!started && started.ok === true && !!updated && updated.ok === true && !!state && state.completed === true && state.playing === false && calls.join('|') === 'event:transition_complete:impact|event:transition_complete:impact|event:marker:impact|event:marker:impact|complete:early|complete:late'; })()",
787
+ },
788
+ {
789
+ id: 'animation.pause-resume-seek.active-transition.stable',
790
+ expression: "(() => { const created = aura.animation.create({ duration: 2, loop: false, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const transition = aura.animation.transition(timelineId, { duration: 1, targetTime: 1.5, eventTag: 'dash' }); const stepA = aura.animation.update(0.25); const stateA = aura.animation.getState(timelineId); const paused = aura.animation.pause(timelineId); const stepPaused = aura.animation.update(0.5); const statePaused = aura.animation.getState(timelineId); const resumed = aura.animation.resume(timelineId); const stepB = aura.animation.update(0.25); const stateB = aura.animation.getState(timelineId); const seeked = aura.animation.seek(timelineId, 0.4); const stateSeek = aura.animation.getState(timelineId); return !!transition && transition.ok === true && !!stepA && stepA.ok === true && !!paused && paused.ok === true && !!stepPaused && stepPaused.ok === true && !!resumed && resumed.ok === true && !!stepB && stepB.ok === true && !!seeked && seeked.ok === true && !!stateA && Number(stateA.time.toFixed(6)) === 0.375 && !!statePaused && Number(statePaused.time.toFixed(6)) === 0.375 && statePaused.transition && statePaused.transition.paused === true && !!stateB && Number(stateB.time.toFixed(6)) === 0.75 && stateB.transition && stateB.transition.paused === false && !!stateSeek && Number(stateSeek.time.toFixed(6)) === 0.4 && stateSeek.transition === null; })()",
791
+ },
792
+ {
793
+ id: 'animation.transition-loop-edge.ordering.deterministic',
794
+ expression: "(() => { const created = aura.animation.create({ duration: 1, loop: true, time: 0.75, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const calls = []; const eventEarly = aura.animation.onEvent(timelineId, (evt) => calls.push(`early:${evt.type}:${evt.tag || ''}:${evt.loops || 0}`), 5); const eventLate = aura.animation.onEvent(timelineId, (evt) => calls.push(`late:${evt.type}:${evt.tag || ''}:${evt.loops || 0}`), 20); const paused = aura.animation.pause(timelineId); const pausedStep = aura.animation.update(0.5); const pausedState = aura.animation.getState(timelineId); const resumed = aura.animation.resume(timelineId); const transition = aura.animation.transition(timelineId, { duration: 0.25, targetTime: 0.95, eventTag: 'blend' }); const step = aura.animation.update(0.25); const steppedState = aura.animation.getState(timelineId); const seeked = aura.animation.seek(timelineId, 0.1); const seekState = aura.animation.getState(timelineId); return !!eventEarly && eventEarly.ok === true && !!eventLate && eventLate.ok === true && !!paused && paused.ok === true && !!pausedStep && pausedStep.ok === true && !!resumed && resumed.ok === true && !!transition && transition.ok === true && !!step && step.ok === true && !!seeked && seeked.ok === true && !!pausedState && Number(pausedState.time.toFixed(6)) === 0.75 && !!steppedState && steppedState.transition === null && Number(steppedState.time.toFixed(6)) === 0.2 && steppedState.loops === 1 && !!seekState && seekState.transition === null && Number(seekState.time.toFixed(6)) === 0.1 && calls.join('|') === 'early:transition_complete:blend:0|late:transition_complete:blend:0|early:marker:blend:0|late:marker:blend:0|early:loop::1|late:loop::1'; })()",
795
+ },
796
+ {
797
+ id: 'animation.crossfade-loop-edge.ordering.deterministic',
798
+ expression: "(() => { const created = aura.animation.create({ duration: 1, loop: true, time: 0.6, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const calls = []; const eventEarly = aura.animation.onEvent(timelineId, (evt) => calls.push(`early:${evt.type}:${evt.blendMode || ''}:${evt.tag || ''}:${evt.loops || 0}`), 5); const eventLate = aura.animation.onEvent(timelineId, (evt) => calls.push(`late:${evt.type}:${evt.blendMode || ''}:${evt.tag || ''}:${evt.loops || 0}`), 20); const crossfade = aura.animation.crossfade(timelineId, { duration: 0.25, fromTime: 0.75, toTime: 0.95, eventTag: 'mix' }); const step = aura.animation.update(0.25); const steppedState = aura.animation.getState(timelineId); const seeked = aura.animation.seek(timelineId, 0.1); const seekState = aura.animation.getState(timelineId); return !!eventEarly && eventEarly.ok === true && !!eventLate && eventLate.ok === true && !!crossfade && crossfade.ok === true && !!step && step.ok === true && !!seeked && seeked.ok === true && !!steppedState && steppedState.transition === null && Number(steppedState.time.toFixed(6)) === 0.2 && steppedState.loops === 1 && !!seekState && seekState.transition === null && Number(seekState.time.toFixed(6)) === 0.1 && calls.join('|') === 'early:crossfade_complete:crossfade:mix:0|late:crossfade_complete:crossfade:mix:0|early:marker:crossfade:mix:0|late:marker:crossfade:mix:0|early:loop:::1|late:loop:::1'; })()",
799
+ },
800
+ {
801
+ id: 'animation.crossfade.pause-resume-seek.stable',
802
+ expression: "(() => { const created = aura.animation.create({ duration: 2, loop: false, playing: true }); if (!created || created.ok !== true) return false; const timelineId = created.timelineId; const crossfade = aura.animation.crossfade(timelineId, { duration: 1, fromTime: 0.2, toTime: 1.4, eventTag: 'mix' }); const stepA = aura.animation.update(0.25); const stateA = aura.animation.getState(timelineId); const paused = aura.animation.pause(timelineId); const stepPaused = aura.animation.update(0.5); const statePaused = aura.animation.getState(timelineId); const resumed = aura.animation.resume(timelineId); const stepB = aura.animation.update(0.25); const stateB = aura.animation.getState(timelineId); const seeked = aura.animation.seek(timelineId, 0.4); const stateSeek = aura.animation.getState(timelineId); return !!crossfade && crossfade.ok === true && !!stepA && stepA.ok === true && !!paused && paused.ok === true && !!stepPaused && stepPaused.ok === true && !!resumed && resumed.ok === true && !!stepB && stepB.ok === true && !!seeked && seeked.ok === true && !!stateA && Number(stateA.time.toFixed(6)) === 0.5 && stateA.transition && stateA.transition.mode === 'crossfade' && stateA.transition.paused === false && !!statePaused && Number(statePaused.time.toFixed(6)) === 0.5 && statePaused.transition && statePaused.transition.mode === 'crossfade' && statePaused.transition.paused === true && !!stateB && Number(stateB.time.toFixed(6)) === 0.8 && stateB.transition && stateB.transition.mode === 'crossfade' && stateB.transition.paused === false && !!stateSeek && Number(stateSeek.time.toFixed(6)) === 0.4 && stateSeek.transition === null; })()",
803
+ },
804
+ ],
805
+ source: `
806
+ aura.setup = function () {
807
+ const timeline = aura.animation.create({ duration: 1, playing: true, loop: true });
808
+ aura.test.assert(timeline && timeline.ok === true, 'animation timeline should be created');
809
+ };
810
+ aura.update = function (dt) {
811
+ aura.animation.update(dt);
812
+ };
813
+ `,
814
+ frames: 2,
815
+ },
816
+ {
817
+ id: 'animation-atlas-spritesheet-workflow',
818
+ modes: ['shim', 'native'],
819
+ namespaces: ['animation', 'draw2d'],
820
+ functions: [
821
+ 'aura.animation.registerAtlas',
822
+ 'aura.animation.resolveAtlasFrame',
823
+ 'aura.animation.createAtlasClip',
824
+ 'aura.animation.stepAtlasClip',
825
+ 'aura.animation.getAtlasClipState',
826
+ 'aura.draw2d.sprite',
827
+ ],
828
+ nativeChecks: [
829
+ {
830
+ id: 'animation.atlas.frame-selection-and-clip-stepping.deterministic',
831
+ expression: "(() => { const fixture = { image: 'hero-sheet.png', frames: { walk_0: { x: 0, y: 0, w: 16, h: 16 }, walk_1: { x: 16, y: 0, w: 16, h: 16 }, walk_2: { x: 32, y: 0, w: 16, h: 16 } }, clips: { walk: { frames: ['walk_0', 'walk_1', 'walk_2'], frameDuration: 0.1, loop: true } } }; const registered = aura.animation.registerAtlas(fixture); if (!registered || registered.ok !== true) return false; const atlasId = registered.atlasId; const frame = aura.animation.resolveAtlasFrame(atlasId, 'walk_1'); if (!frame || frame.ok !== true) return false; aura.draw2d.sprite(frame.image, 10, 20, { frameX: frame.frameX, frameY: frame.frameY, frameW: frame.frameW, frameH: frame.frameH }); const runSample = () => { const created = aura.animation.createAtlasClip({ atlasId, clipKey: 'walk', playing: true }); if (!created || created.ok !== true) return 'create_failed'; const clipId = created.clipId; const trace = []; for (const dt of [0.05, 0.06, 0.11, 0.08]) { const stepped = aura.animation.stepAtlasClip(clipId, dt); if (!stepped || stepped.ok !== true) return 'step_failed'; const state = aura.animation.getAtlasClipState(clipId); trace.push(state ? `${state.frameKey}:${state.frameIndex}:${state.loops}` : null); } const finalState = aura.animation.getAtlasClipState(clipId); return JSON.stringify({ trace, final: finalState ? { frameKey: finalState.frameKey, frameIndex: finalState.frameIndex, loops: finalState.loops, completed: finalState.completed } : null }); }; const expected = JSON.stringify({ trace: ['walk_0:0:0', 'walk_1:1:0', 'walk_2:2:0', 'walk_0:0:1'], final: { frameKey: 'walk_0', frameIndex: 0, loops: 1, completed: false } }); const first = runSample(); const second = runSample(); return frame.frameX === 16 && frame.frameY === 0 && frame.frameW === 16 && frame.frameH === 16 && first === expected && second === expected; })()",
832
+ },
833
+ {
834
+ id: 'animation.atlas.spritesheet-frame-list.deterministic',
835
+ expression: "(() => { const fixture = { image: 'runner-sheet.png', frames: { s0: { x: 0, y: 0, w: 8, h: 8, duration: 0.05 }, s1: { x: 8, y: 0, w: 8, h: 8, duration: 0.1 }, s2: { x: 16, y: 0, w: 8, h: 8, duration: 0.2 } } }; const registered = aura.animation.registerAtlas(fixture); if (!registered || registered.ok !== true) return false; const atlasId = registered.atlasId; const runSample = () => { const created = aura.animation.createAtlasClip({ atlasId, frames: ['s0', 's1', 's2'], loop: false, playing: true }); if (!created || created.ok !== true) return 'create_failed'; const clipId = created.clipId; const checkpoints = []; for (const dt of [0.05, 0.1, 0.15]) { const stepped = aura.animation.stepAtlasClip(clipId, dt); if (!stepped || stepped.ok !== true) return 'step_failed'; const state = aura.animation.getAtlasClipState(clipId); checkpoints.push(state ? `${state.frameKey}:${state.frameIndex}:${state.completed}` : null); } const finalState = aura.animation.getAtlasClipState(clipId); return JSON.stringify({ checkpoints, final: finalState ? { frameKey: finalState.frameKey, frameIndex: finalState.frameIndex, completed: finalState.completed, playing: finalState.playing } : null }); }; const expected = JSON.stringify({ checkpoints: ['s1:1:false', 's2:2:false', 's2:2:false'], final: { frameKey: 's2', frameIndex: 2, completed: false, playing: true } }); const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
836
+ },
837
+ {
838
+ id: 'animation.atlas.invalid-payloads.reason-codes',
839
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidPayload = aura.animation.registerAtlas(null); const invalidFrames = aura.animation.registerAtlas({ image: 'sheet.png', frames: {} }); const unsupportedFlags = aura.animation.registerAtlas({ image: 'sheet.png', frames: { bad: { frame: { x: 0, y: 0, w: 16, h: 16 }, rotated: true } } }); const missingFrame = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } }, clips: { run: { frames: ['missing'] } } }); const valid = aura.animation.registerAtlas({ image: 'sheet.png', frames: { idle_0: { x: 0, y: 0, w: 16, h: 16 } } }); if (!valid || valid.ok !== true) return false; const atlasId = valid.atlasId; const invalidAtlasId = aura.animation.resolveAtlasFrame('oops', 'idle_0'); const missingAtlas = aura.animation.resolveAtlasFrame(999999, 'idle_0'); const invalidFrameKey = aura.animation.resolveAtlasFrame(atlasId, null); const missingFrameKey = aura.animation.resolveAtlasFrame(atlasId, 'idle_1'); const invalidClipOptions = aura.animation.createAtlasClip(null); const missingClip = aura.animation.createAtlasClip({ atlasId, clipKey: 'run' }); const invalidClipId = aura.animation.stepAtlasClip('bad', 0.1); const invalids = [invalidPayload, invalidFrames, unsupportedFlags, missingFrame, invalidAtlasId, missingAtlas, invalidFrameKey, missingFrameKey, invalidClipOptions, missingClip, invalidClipId]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
840
+ },
841
+ ],
842
+ source: `
843
+ aura.setup = function () {};
844
+ `,
845
+ frames: 2,
846
+ },
847
+ {
848
+ id: 'tween-easing-core',
849
+ modes: ['native'],
850
+ namespaces: ['tween'],
851
+ functions: [
852
+ 'aura.tween.create',
853
+ 'aura.tween.pause',
854
+ 'aura.tween.resume',
855
+ 'aura.tween.cancel',
856
+ 'aura.tween.onUpdate',
857
+ 'aura.tween.onComplete',
858
+ 'aura.tween.update',
859
+ 'aura.tween.getState',
860
+ ],
861
+ nativeChecks: [
862
+ {
863
+ id: 'tween.create.pause.resume.cancel.roundtrip',
864
+ expression: "(() => { const created = aura.tween.create({ from: 0, to: 1, duration: 1, easing: 'linear' }); if (!created || created.ok !== true) return false; const tweenId = created.tweenId; const paused = aura.tween.pause(tweenId); const resumed = aura.tween.resume(tweenId); const updated = aura.tween.update(0.25); const state = aura.tween.getState(tweenId); const cancelled = aura.tween.cancel(tweenId); const afterCancel = aura.tween.getState(tweenId); return !!paused && paused.ok === true && !!resumed && resumed.ok === true && !!updated && updated.ok === true && !!state && Number(state.value.toFixed(6)) === 0.25 && state.playing === true && !!cancelled && cancelled.ok === true && cancelled.cancelled === true && afterCancel === null; })()",
865
+ },
866
+ {
867
+ id: 'tween.variable-dt.easing.deterministic-rerun',
868
+ expression: "(() => { const runSample = () => { const created = aura.tween.create({ from: 0, to: 10, duration: 1, easing: 'easeInOutQuad' }); if (!created || created.ok !== true) return 'create_failed'; const tweenId = created.tweenId; const trace = []; for (const dt of [1 / 60, 1 / 30, 1 / 24, 1 / 120, 0.25]) { const step = aura.tween.update(dt); if (!step || step.ok !== true) return 'update_failed'; const state = aura.tween.getState(tweenId); trace.push(state ? Number(state.value.toFixed(6)) : null); } const state = aura.tween.getState(tweenId); return JSON.stringify({ value: state ? Number(state.value.toFixed(6)) : null, progress: state ? Number(state.progress.toFixed(6)) : null, playing: state ? state.playing : null, completed: state ? state.completed : null, trace }); }; const expected = JSON.stringify({ value: 2.45, progress: 0.35, playing: true, completed: false, trace: [0.005556, 0.05, 0.168056, 0.2, 2.45] }); const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
869
+ },
870
+ {
871
+ id: 'tween.invalid-operations.reason-codes',
872
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.tween.create(null); const invalidDuration = aura.tween.create({ from: 0, to: 1, duration: 0 }); const invalidFrom = aura.tween.create({ from: 'bad', to: 1, duration: 1 }); const invalidTo = aura.tween.create({ from: 0, to: 'bad', duration: 1 }); const invalidEasing = aura.tween.create({ from: 0, to: 1, duration: 1, easing: 'bounce' }); const invalidPlaying = aura.tween.create({ from: 0, to: 1, duration: 1, playing: 'yes' }); const created = aura.tween.create({ from: 0, to: 1, duration: 1 }); if (!created || created.ok !== true) return false; const tweenId = created.tweenId; const missingTween = aura.tween.pause(999999); const invalidTweenId = aura.tween.pause('oops'); const invalidOnUpdate = aura.tween.onUpdate(tweenId, null); const invalidOnComplete = aura.tween.onComplete(tweenId, null); const invalidDt = aura.tween.update(0); const invalids = [invalidOptions, invalidDuration, invalidFrom, invalidTo, invalidEasing, invalidPlaying, missingTween, invalidTweenId, invalidOnUpdate, invalidOnComplete, invalidDt]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0); })()",
873
+ },
874
+ {
875
+ id: 'tween.callback-ordering.deterministic',
876
+ expression: "(() => { const first = aura.tween.create({ from: 0, to: 1, duration: 0.5 }); const second = aura.tween.create({ from: 0, to: 2, duration: 0.5 }); if (!first || first.ok !== true || !second || second.ok !== true) return false; const idA = first.tweenId; const idB = second.tweenId; const calls = []; aura.tween.onUpdate(idA, (evt) => calls.push(`uA:late:${evt.tweenId}`), 20); aura.tween.onUpdate(idA, (evt) => calls.push(`uA:early:${evt.tweenId}`), 5); aura.tween.onComplete(idA, (evt) => calls.push(`cA:late:${evt.tweenId}`), 10); aura.tween.onComplete(idA, (evt) => calls.push(`cA:early:${evt.tweenId}`), 1); aura.tween.onUpdate(idB, (evt) => calls.push(`uB:late:${evt.tweenId}`), 20); aura.tween.onUpdate(idB, (evt) => calls.push(`uB:early:${evt.tweenId}`), 5); aura.tween.onComplete(idB, (evt) => calls.push(`cB:late:${evt.tweenId}`), 10); aura.tween.onComplete(idB, (evt) => calls.push(`cB:early:${evt.tweenId}`), 1); const updated = aura.tween.update(0.5); const stateA = aura.tween.getState(idA); const stateB = aura.tween.getState(idB); return !!updated && updated.ok === true && !!stateA && stateA.completed === true && stateA.playing === false && !!stateB && stateB.completed === true && stateB.playing === false && (() => { const expectedCalls = ['uA:early:' + idA, 'uA:late:' + idA, 'cA:early:' + idA, 'cA:late:' + idA, 'uB:early:' + idB, 'uB:late:' + idB, 'cB:early:' + idB, 'cB:late:' + idB].join('|'); return calls.join('|') === expectedCalls; })(); })()",
877
+ },
878
+ ],
879
+ source: `
880
+ aura.setup = function () {};
881
+ `,
882
+ nativeFrames: 2,
883
+ },
884
+ {
885
+ id: 'particles-lite-runtime',
886
+ modes: ['native'],
887
+ namespaces: ['particles', 'draw2d'],
888
+ functions: [
889
+ 'aura.particles.emit',
890
+ 'aura.particles.burst',
891
+ 'aura.particles.update',
892
+ 'aura.particles.draw',
893
+ 'aura.particles.stop',
894
+ 'aura.particles.kill',
895
+ 'aura.particles.setLayer',
896
+ 'aura.particles.setBlend',
897
+ 'aura.particles.getState',
898
+ 'aura.draw2d.circleFill',
899
+ ],
900
+ nativeChecks: [
901
+ {
902
+ id: 'particles.vfx.surface.methods',
903
+ expression: "(() => ['emit','burst','update','draw','stop','kill','setLayer','setBlend','getState'].every((name) => typeof aura.particles?.[name] === 'function'))()",
904
+ },
905
+ {
906
+ id: 'particles.emit-update-draw.deterministic',
907
+ expression: "(() => { const runSample = () => { const created = aura.particles.emit({ x: 12, y: 20, count: 4, life: 0.5, size: 2, speedMin: 20, speedMax: 20, direction: 0, spread: 0, loop: true, rate: 8 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const trace = []; for (const dt of [0.1, 0.15, 0.2]) { const step = aura.particles.update(dt); if (!step || step.ok !== true) return null; const state = aura.particles.getState(emitterId); trace.push(state ? state.particles : null); } const drawn = aura.particles.draw(emitterId); const beforeStop = aura.particles.getState(emitterId); const stopped = aura.particles.stop(emitterId); const afterStop = aura.particles.getState(emitterId); return { trace, drawn: drawn && drawn.ok ? drawn.drawnParticles : null, particles: beforeStop ? beforeStop.particles : null, stoppedOk: !!stopped && stopped.ok === true, stopMode: stopped && typeof stopped.mode === 'string' ? stopped.mode : null, afterStopIsNull: afterStop === null, afterStopInactive: !!afterStop && afterStop.active === false }; }; const first = runSample(); const second = runSample(); if (!first || !second) return false; return JSON.stringify(first) === JSON.stringify(second) && JSON.stringify(first.trace) === JSON.stringify([4, 6, 7]) && first.drawn === 7 && first.particles === 7 && first.stoppedOk === true && (first.afterStopIsNull === true || first.afterStopInactive === true); })()",
908
+ },
909
+ {
910
+ id: 'particles.vfx.blend-layer.fixed-step.deterministic',
911
+ expression: "(() => { const runSample = (tag) => { const created = aura.particles.emit({ x: 24, y: 24, count: 8, life: 0.5, size: 2, speedMin: 12, speedMax: 12, direction: 0, spread: 0, loop: true, rate: 24 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const layer = aura.particles.setLayer(emitterId, 3); const blend = aura.particles.setBlend(emitterId, 'add'); const burst = aura.particles.burst(emitterId, 5); const trace = []; for (const dt of [1 / 120, 1 / 30, 1 / 24, 1 / 15]) { const step = aura.particles.update(dt); const state = aura.particles.getState(emitterId); trace.push({ ok: !!step && step.ok === true, particles: state ? state.particles : null }); } const drawn = aura.particles.draw(emitterId); const killed = aura.particles.kill(emitterId); const afterKill = aura.particles.getState(emitterId); return { tag, layerOk: !!layer && layer.ok === true, blendOk: !!blend && blend.ok === true, burstOk: !!burst && burst.ok === true, trace, drawn: drawn && drawn.ok === true ? drawn.drawnParticles : null, killed: !!killed && killed.ok === true, afterKillNull: afterKill === null }; }; const first = runSample('a'); const second = runSample('b'); if (!first || !second) return false; const normalizedFirst = JSON.stringify({ ...first, tag: 'x' }); const normalizedSecond = JSON.stringify({ ...second, tag: 'x' }); return normalizedFirst === normalizedSecond && first.layerOk === true && first.blendOk === true && first.burstOk === true && first.trace.every((entry) => entry.ok === true && Number.isFinite(entry.particles) && entry.particles >= 0) && Number.isFinite(first.drawn) && first.drawn > 0 && first.killed === true && first.afterKillNull === true; })()",
912
+ },
913
+ {
914
+ id: 'particles.pool-cap-and-loop.stable',
915
+ expression: "(() => { const created = aura.particles.emit({ x: 0, y: 0, count: 16, life: 0.2, size: 1, speedMin: 4, speedMax: 4, direction: 0, spread: 0, loop: true, rate: 64 }); if (!created || created.ok !== true) return false; const emitterId = created.emitterId; let maxSeen = 0; for (let i = 0; i < 20; i += 1) { const step = aura.particles.update(0.1); if (!step || step.ok !== true) return false; const state = aura.particles.getState(emitterId); if (!state) return false; if (state.particles > maxSeen) maxSeen = state.particles; if (state.particles > 2048) return false; } const stopped = aura.particles.stop(emitterId); return !!stopped && stopped.ok === true && maxSeen > 0 && maxSeen <= 2048; })()",
916
+ },
917
+ {
918
+ id: 'particles.invalid-operations.reason-codes',
919
+ expression: "(() => { const reasonOf = (entry) => entry && (entry.reasonCode || entry.reason); const invalidOptions = aura.particles.emit(null); const invalidPosition = aura.particles.emit({ x: 'bad' }); const invalidCount = aura.particles.emit({ count: 0 }); const invalidSpeedRange = aura.particles.emit({ speedMin: 30, speedMax: 10 }); const invalidRate = aura.particles.emit({ loop: true, rate: 0 }); const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return false; const emitterId = created.emitterId; const invalidDt = aura.particles.update(0); const invalidEmitterId = aura.particles.stop('oops'); const missingEmitter = aura.particles.stop(999999); const invalidDrawEmitter = aura.particles.draw(0); const invalidMaxParticles = aura.particles.draw({ maxParticles: 0 }); const stopped = aura.particles.stop(emitterId); const invalids = [invalidOptions, invalidPosition, invalidCount, invalidSpeedRange, invalidRate, invalidDt, invalidEmitterId, missingEmitter, invalidDrawEmitter, invalidMaxParticles]; return invalids.every((entry) => entry && entry.ok === false && typeof reasonOf(entry) === 'string' && reasonOf(entry).length > 0) && stopped && stopped.ok === true; })()",
920
+ },
921
+ {
922
+ id: 'particles.vfx.reason-codes.stable',
923
+ expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const runSample = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidBurstCount = aura.particles.burst(emitterId, 0); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidBurstCount: reasonOf(invalidBurstCount), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = runSample(); const second = runSample(); const reasonsPresent = !!first && typeof first.invalidBurstCount === 'string' && typeof first.invalidLayer === 'string' && typeof first.invalidBlendMode === 'string' && typeof first.invalidKillId === 'string' && typeof first.missingKill === 'string'; return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && reasonsPresent && first.cleaned === true; })()",
924
+ },
925
+ ],
926
+ source: `
927
+ aura.setup = function () {};
928
+ `,
929
+ nativeFrames: 2,
930
+ },
931
+ {
932
+ id: 'phaser-particles-vfx-blend-parity',
933
+ modes: ['native'],
934
+ namespaces: ['particles', 'draw2d'],
935
+ functions: [
936
+ 'aura.particles.emit',
937
+ 'aura.particles.burst',
938
+ 'aura.particles.update',
939
+ 'aura.particles.draw',
940
+ 'aura.particles.stop',
941
+ 'aura.particles.kill',
942
+ 'aura.particles.setLayer',
943
+ 'aura.particles.setBlend',
944
+ 'aura.particles.getState',
945
+ 'aura.draw2d.circleFill',
946
+ ],
947
+ nativeChecks: [
948
+ {
949
+ id: 'particles.vfx.surface.methods',
950
+ expression: "(() => ['emit','burst','update','draw','stop','kill','setLayer','setBlend','getState'].every((name) => typeof aura.particles?.[name] === 'function'))()",
951
+ },
952
+ {
953
+ id: 'particles.vfx.blend-layer.fixed-step.deterministic',
954
+ expression: "(() => { const runSample = (tag) => { const createdA = aura.particles.emit({ x: 24, y: 24, count: 5, life: 0.5, size: 2, speedMin: 12, speedMax: 12, direction: 0, spread: 0, loop: true, rate: 24, layer: 3, blend: 'add' }); const createdB = aura.particles.emit({ x: 40, y: 24, count: 5, life: 0.5, size: 2, speedMin: 8, speedMax: 8, direction: 0, spread: 0, loop: true, rate: 12, layer: 1, blend: 'screen' }); if (!createdA || createdA.ok !== true || !createdB || createdB.ok !== true) return null; const emitterIdA = createdA.emitterId; const emitterIdB = createdB.emitterId; const layer = aura.particles.setLayer(emitterIdA, 3); const blend = aura.particles.setBlend(emitterIdA, 'add'); const burstA = aura.particles.burst(emitterIdA, { count: 4 }); const burstB = aura.particles.burst(emitterIdB); const trace = []; for (const dt of [1 / 120, 1 / 30, 1 / 24, 1 / 15]) { const step = aura.particles.update(dt); const stateA = aura.particles.getState(emitterIdA); const stateB = aura.particles.getState(emitterIdB); trace.push({ ok: !!step && step.ok === true, particlesA: stateA ? stateA.particles : null, particlesB: stateB ? stateB.particles : null }); } const drawn = aura.particles.draw({ maxParticles: 64 }); const order = Array.isArray(drawn?.drawOrder) ? drawn.drawOrder.map((entry) => `${entry.layer}:${entry.blend}:${entry.drawnParticles}`).join('|') : null; const killedA = aura.particles.kill(emitterIdA); const killedB = aura.particles.kill(emitterIdB); const afterKillA = aura.particles.getState(emitterIdA); const afterKillB = aura.particles.getState(emitterIdB); return { tag, layerOk: !!layer && layer.ok === true, blendOk: !!blend && blend.ok === true, burstASpawned: burstA?.spawned ?? null, burstBSpawned: burstB?.spawned ?? null, trace, drawn: drawn && drawn.ok === true ? drawn.drawnParticles : null, order, killedA: !!killedA && killedA.ok === true, killedB: !!killedB && killedB.ok === true, afterKillNull: afterKillA === null && afterKillB === null }; }; const first = runSample('a'); const second = runSample('b'); if (!first || !second) return false; const normalizedFirst = JSON.stringify({ ...first, tag: 'x' }); const normalizedSecond = JSON.stringify({ ...second, tag: 'x' }); return normalizedFirst === normalizedSecond && first.layerOk === true && first.blendOk === true && first.burstASpawned === 4 && first.burstBSpawned === 5 && first.trace.every((entry) => entry.ok === true && Number.isFinite(entry.particlesA) && Number.isFinite(entry.particlesB) && entry.particlesA >= 0 && entry.particlesB >= 0) && Number.isFinite(first.drawn) && first.drawn > 0 && (first.order === null || (typeof first.order === 'string' && first.order.length > 0)) && first.killedA === true && first.killedB === true && first.afterKillNull === true; })()",
955
+ },
956
+ {
957
+ id: 'particles.vfx.stop-vs-kill.fixed-step.deterministic',
958
+ expression: "(() => { const runSample = () => { const drainedEmitter = aura.particles.emit({ x: 0, y: 0, count: 3, life: 0.2, size: 1, speedMin: 6, speedMax: 6, direction: 0, spread: 0, loop: false }); const killedEmitter = aura.particles.emit({ x: 0, y: 0, count: 3, life: 0.2, size: 1, speedMin: 6, speedMax: 6, direction: 0, spread: 0, loop: true, rate: 30 }); if (!drainedEmitter?.ok || !killedEmitter?.ok) return null; const drainedId = drainedEmitter.emitterId; const killedId = killedEmitter.emitterId; const drained = aura.particles.stop(drainedId, { mode: 'drain' }); const drainedAfterStop = aura.particles.getState(drainedId); const killed = aura.particles.kill(killedId); const killedAfterKill = aura.particles.getState(killedId); for (const dt of [0.05, 0.05, 0.05, 0.05]) { aura.particles.update(dt); } const drainedAfterTick = aura.particles.getState(drainedId); const killedAfterTick = aura.particles.getState(killedId); return { drainedOk: !!drained && drained.ok === true, drainedRemovedAfterStop: drainedAfterStop === null, drainedPersistedAfterStop: drainedAfterStop !== null, killedOk: !!killed && killed.ok === true, killedRemovedAfterKill: killedAfterKill === null, drainedRemovedAfterTick: drainedAfterTick === null, killedRemovedAfterTick: killedAfterTick === null }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.drainedOk === true && (first.drainedRemovedAfterStop === true || first.drainedPersistedAfterStop === true) && first.killedOk === true && first.killedRemovedAfterKill === true && first.drainedRemovedAfterTick === true && first.killedRemovedAfterTick === true; })()",
959
+ },
960
+ {
961
+ id: 'particles.vfx.reason-codes.stable',
962
+ expression: "(() => { const reasonOf = (entry) => (entry && (entry.reasonCode || entry.reason)) || null; const collect = () => { const created = aura.particles.emit({ x: 0, y: 0, count: 1 }); if (!created || created.ok !== true) return null; const emitterId = created.emitterId; const invalidLayerId = aura.particles.setLayer('bad-id', 1); const invalidBlendId = aura.particles.setBlend('bad-id', 'add'); const invalidLayer = aura.particles.setLayer(emitterId, -1); const invalidBlendMode = aura.particles.setBlend(emitterId, 'unsupported'); const invalidBurstArgs = aura.particles.burst(emitterId, 'bad'); const invalidBurstCount = aura.particles.burst(emitterId, { count: 0 }); const invalidStopMode = aura.particles.stop(emitterId, { mode: 'pause' }); const invalidKillId = aura.particles.kill('bad-id'); const missingKill = aura.particles.kill(999999); const cleaned = aura.particles.kill(emitterId); return { invalidLayerId: reasonOf(invalidLayerId), invalidBlendId: reasonOf(invalidBlendId), invalidLayer: reasonOf(invalidLayer), invalidBlendMode: reasonOf(invalidBlendMode), invalidBurstArgs: reasonOf(invalidBurstArgs), invalidBurstCount: reasonOf(invalidBurstCount), invalidStopMode: reasonOf(invalidStopMode), invalidKillId: reasonOf(invalidKillId), missingKill: reasonOf(missingKill), cleaned: !!cleaned && cleaned.ok === true }; }; const first = collect(); const second = collect(); const requiredReasonKeys = ['invalidLayerId','invalidBlendId','invalidLayer','invalidBlendMode','invalidBurstArgs','invalidBurstCount','invalidKillId','missingKill']; const reasonsPresent = !!first && requiredReasonKeys.every((key) => typeof first[key] === 'string' && first[key].length > 0); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && reasonsPresent && (first.invalidStopMode === null || typeof first.invalidStopMode === 'string') && (first.cleaned === true || first.invalidStopMode === null); })()",
963
+ },
964
+ ],
965
+ source: `
966
+ aura.setup = function () {};
967
+ `,
968
+ nativeFrames: 2,
969
+ },
970
+ {
971
+ id: 'threejs-draw3d-postfx-composer-parity',
972
+ modes: ['native'],
973
+ namespaces: ['draw3d', 'scene3d', 'mesh', 'material', 'camera3d', 'debug'],
974
+ functions: [
975
+ 'aura.draw3d.setPostFXPass',
976
+ 'aura.draw3d.setPostFXEnabled',
977
+ 'aura.draw3d.removePostFXPass',
978
+ 'aura.draw3d.getPostFXState',
979
+ 'aura.draw3d.clear3d',
980
+ 'aura.scene3d.createNode',
981
+ 'aura.scene3d.bindRenderNode',
982
+ 'aura.scene3d.submitRenderBindings',
983
+ 'aura.scene3d.getRenderSubmissionState',
984
+ 'aura.mesh.createBox',
985
+ 'aura.mesh.unload',
986
+ 'aura.material.create',
987
+ 'aura.material.unload',
988
+ 'aura.camera3d.perspective',
989
+ 'aura.camera3d.setPosition',
990
+ 'aura.camera3d.lookAt',
991
+ 'aura.debug.enableInspector',
992
+ 'aura.debug.inspectorStats',
993
+ ],
994
+ nativeChecks: [
995
+ {
996
+ id: 'draw3d.postfx.surface.methods',
997
+ expression: "(() => ['setPostFXPass','setPostFXEnabled','removePostFXPass','getPostFXState'].every((name) => typeof aura.draw3d?.[name] === 'function'))()",
998
+ },
999
+ {
1000
+ id: 'draw3d.postfx.chain-mutation.deterministic',
1001
+ expression: "(() => { const runSample = () => { const baseline = aura.draw3d.getPostFXState(); aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('colorGrade'); aura.draw3d.removePostFXPass('vignette'); aura.draw3d.removePostFXPass('fxaa'); const setBloom = aura.draw3d.setPostFXPass('bloom', { strength: 1.25, radius: 0.6, threshold: 0.7 }); const setFxaa = aura.draw3d.setPostFXPass('fxaa', { enabled: true, strength: 1.0 }); const setColorGrade = aura.draw3d.setPostFXPass('colorGrade', { strength: 0.8 }); const disableBloom = aura.draw3d.setPostFXEnabled('bloom', false); const enableBloom = aura.draw3d.setPostFXEnabled('bloom', true); const removeFxaa = aura.draw3d.removePostFXPass('fxaa'); const setVignette = aura.draw3d.setPostFXPass('vignette', { strength: 0.4, radius: 0.9 }); const state = aura.draw3d.getPostFXState(); return { status: [setBloom?.reasonCode, setFxaa?.reasonCode, setColorGrade?.reasonCode, disableBloom?.reasonCode, enableBloom?.reasonCode, removeFxaa?.reasonCode, setVignette?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((pass) => pass.pass).join('|') : '', enabled: state?.enabledPasses, total: state?.totalPasses, fingerprint: state?.orderFingerprint, mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'colorGrade|bloom|vignette' && first.total === 3 && first.enabled === 3 && Number.isFinite(first.fingerprint) && first.fingerprint > 0 && first.mutationDelta === 11 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1002
+ },
1003
+ {
1004
+ id: 'draw3d.postfx.target-chain.custom-pass.deterministic',
1005
+ expression: "(() => { const runSample = () => { const baseline = aura.draw3d.getPostFXState(); aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('colorGrade'); aura.draw3d.removePostFXPass('vignette'); aura.draw3d.removePostFXPass('fxaa'); aura.draw3d.removePostFXPass('custom:filmgrain'); aura.draw3d.removePostFXPass('custom:glow'); const setBloom = aura.draw3d.setPostFXPass('bloom', { strength: 1.1, targetChain: { intermediateTargets: ['mainA', 'mainB', 'mainA'], pingPong: true, composeMode: 'additive' } }); const setCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.75, threshold: 0.33 } }); const updateCustom = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.5, threshold: 0.2 } }); const setFilmgrain = aura.draw3d.setPostFXPass('custom:filmgrain', { customParams: { amount: 0.2, grain: 0.1 } }); const state = aura.draw3d.getPostFXState(); const targetChain = state?.targetChain || {}; const customGlow = Array.isArray(state?.passes) ? state.passes.find((pass) => pass.pass === 'custom:glow') : null; const customParams = customGlow?.customParams || {}; const customParamKeys = Object.keys(customParams).sort().join('|'); return { status: [setBloom?.reasonCode, setCustom?.reasonCode, updateCustom?.reasonCode, setFilmgrain?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((pass) => pass.pass).join('|') : '', enabled: state?.enabledPasses, total: state?.totalPasses, customPassCount: state?.customPassCount, targetChainTargets: Array.isArray(targetChain?.intermediateTargets) ? targetChain.intermediateTargets.join('|') : '', targetChainCount: targetChain?.intermediateTargetCount, targetChainPingPong: targetChain?.pingPong === true, targetChainComposeMode: targetChain?.composeMode, targetChainFingerprint: state?.targetChainFingerprint, customParamFingerprint: state?.customParamFingerprint, customGlowIsCustom: customGlow?.isCustom === true, customGlowParamKeys: customParamKeys, customGlowIntensity: customParams?.intensity, customGlowThreshold: customParams?.threshold, mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'bloom|custom:filmgrain|custom:glow' && first.total === 3 && first.enabled === 3 && first.customPassCount === 2 && first.targetChainTargets === 'maina|mainb' && first.targetChainCount === 2 && first.targetChainPingPong === true && first.targetChainComposeMode === 'additive' && Number.isFinite(first.targetChainFingerprint) && first.targetChainFingerprint > 0 && Number.isFinite(first.customParamFingerprint) && first.customParamFingerprint > 0 && first.customGlowIsCustom === true && first.customGlowParamKeys === 'intensity|threshold' && Math.abs(Number(first.customGlowIntensity) - 0.5) < 1e-3 && Math.abs(Number(first.customGlowThreshold) - 0.2) < 1e-3 && first.mutationDelta === 10 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1006
+ },
1007
+ {
1008
+ id: 'draw3d.postfx.motion-blur.state.deterministic',
1009
+ expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.75, radius: 12, threshold: 1.0, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('motionBlur', false); const reenable = aura.draw3d.setPostFXEnabled('motionBlur', true); const update = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.35, radius: 6, threshold: 0.0 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'motionBlur') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'motionBlur' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.35) < 1e-3 && Math.abs(first.radius - 6.0) < 1e-3 && Math.abs(first.threshold - 0.0) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1010
+ },
1011
+ {
1012
+ id: 'draw3d.postfx.ssao.state.deterministic',
1013
+ expression: "(() => { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; const runSample = () => { resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const setInitial = aura.draw3d.setPostFXPass('ssao', { strength: 1.1, radius: 0.42, threshold: 0.18, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const disable = aura.draw3d.setPostFXEnabled('ssao', false); const reenable = aura.draw3d.setPostFXEnabled('ssao', true); const update = aura.draw3d.setPostFXPass('ssao', { strength: 0.92, radius: 0.36, threshold: 0.24 }); const state = aura.draw3d.getPostFXState() || {}; const pass = Array.isArray(state?.passes) ? state.passes.find((entry) => entry.pass === 'ssao') : null; return { status: [setInitial?.reasonCode, disable?.reasonCode, reenable?.reasonCode, update?.reasonCode].join('|'), order: Array.isArray(state?.passes) ? state.passes.map((entry) => entry.pass).join('|') : '', total: Number(state?.totalPasses || 0), enabled: Number(state?.enabledPasses || 0), strength: Number(pass?.strength || 0), radius: Number(pass?.radius || 0), threshold: Number(pass?.threshold || 0), mutationDelta: Number(state?.mutationCount || 0) - Number(baseline?.mutationCount || 0), lastReasonCode: state?.lastReasonCode, lastOk: state?.lastOk, targetChainCount: Number(state?.targetChain?.intermediateTargetCount || 0), targetChainPingPong: state?.targetChain?.pingPong === true, targetChainComposeMode: state?.targetChain?.composeMode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.status === 'postfx_ok|postfx_ok|postfx_ok|postfx_ok' && first.order === 'ssao' && first.total === 1 && first.enabled === 1 && Math.abs(first.strength - 0.92) < 1e-3 && Math.abs(first.radius - 0.36) < 1e-3 && Math.abs(first.threshold - 0.24) < 1e-3 && first.targetChainCount === 0 && first.targetChainPingPong === false && first.targetChainComposeMode === 'replace' && first.mutationDelta === 4 && first.lastReasonCode === 'postfx_ok' && first.lastOk === true; })()",
1014
+ },
1015
+ {
1016
+ id: 'draw3d.postfx.reason-codes.stable',
1017
+ expression: "(() => { const reasonOf = (entry) => (entry && entry.reasonCode) || null; const collect = () => { aura.draw3d.removePostFXPass('bloom'); aura.draw3d.removePostFXPass('custom:glow'); const invalidPassEmpty = aura.draw3d.setPostFXPass('', {}); const invalidPassType = aura.draw3d.setPostFXPass(42, {}); const configuredSsao = aura.draw3d.setPostFXPass('ssao', { radius: 0.6, intensity: 0.8 }); const removeSsao = aura.draw3d.removePostFXPass('ssao'); const invalidOptions = aura.draw3d.setPostFXPass('bloom', 'bad'); const invalidTargetChain = aura.draw3d.setPostFXPass('bloom', { targetChain: 'bad' }); const invalidIntermediateTargetType = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: 'bad' } }); const invalidIntermediateTargetEntry = aura.draw3d.setPostFXPass('bloom', { targetChain: { intermediateTargets: ['ok', 'bad!'] } }); const invalidComposeMode = aura.draw3d.setPostFXPass('bloom', { targetChain: { composeMode: 'overlay' } }); const invalidPingPong = aura.draw3d.setPostFXPass('bloom', { targetChain: { pingPong: 'yes' } }); const invalidCustomPassName = aura.draw3d.setPostFXPass('custom:bad name', {}); const invalidCustomParams = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 'high' } }); const unsupportedCustomParams = aura.draw3d.setPostFXPass('bloom', { customParams: { intensity: 1 } }); const configured = aura.draw3d.setPostFXPass('custom:glow', { customParams: { intensity: 0.25 } }); const invalidEnabled = aura.draw3d.setPostFXEnabled('custom:glow', 'yes'); const removeConfigured = aura.draw3d.removePostFXPass('custom:glow'); const missingToggle = aura.draw3d.setPostFXEnabled('custom:glow', true); const missingRemove = aura.draw3d.removePostFXPass('custom:glow'); return { invalidPassEmpty: reasonOf(invalidPassEmpty), invalidPassType: reasonOf(invalidPassType), configuredSsaoOk: !!configuredSsao && configuredSsao.ok === true, configuredSsaoReason: reasonOf(configuredSsao), removeSsaoOk: !!removeSsao && removeSsao.ok === true, removeSsaoReason: reasonOf(removeSsao), invalidOptions: reasonOf(invalidOptions), invalidTargetChain: reasonOf(invalidTargetChain), invalidIntermediateTargetType: reasonOf(invalidIntermediateTargetType), invalidIntermediateTargetEntry: reasonOf(invalidIntermediateTargetEntry), invalidComposeMode: reasonOf(invalidComposeMode), invalidPingPong: reasonOf(invalidPingPong), invalidCustomPassName: reasonOf(invalidCustomPassName), invalidCustomParams: reasonOf(invalidCustomParams), unsupportedCustomParams: reasonOf(unsupportedCustomParams), configuredOk: !!configured && configured.ok === true, configuredReason: reasonOf(configured), invalidEnabled: reasonOf(invalidEnabled), removeConfiguredOk: !!removeConfigured && removeConfigured.ok === true, removeConfiguredReason: reasonOf(removeConfigured), missingToggle: reasonOf(missingToggle), missingRemove: reasonOf(missingRemove) }; }; const first = collect(); const second = collect(); const ssaoConfiguredStable = (first.configuredSsaoOk === true && first.configuredSsaoReason === 'postfx_ok') || (first.configuredSsaoOk === false && first.configuredSsaoReason === 'postfx_pass_unsupported'); const ssaoRemovedStable = (first.removeSsaoOk === true && first.removeSsaoReason === 'postfx_ok') || (first.removeSsaoOk === false && first.removeSsaoReason === 'postfx_pass_unsupported'); return JSON.stringify(first) === JSON.stringify(second) && first.invalidPassEmpty === 'postfx_invalid_pass_name' && first.invalidPassType === 'postfx_invalid_pass_name' && ssaoConfiguredStable && ssaoRemovedStable && first.invalidOptions === 'postfx_invalid_options' && first.invalidTargetChain === 'postfx_invalid_target_chain' && first.invalidIntermediateTargetType === 'postfx_invalid_intermediate_target' && first.invalidIntermediateTargetEntry === 'postfx_invalid_intermediate_target' && first.invalidComposeMode === 'postfx_invalid_compose_mode' && first.invalidPingPong === 'postfx_invalid_ping_pong' && first.invalidCustomPassName === 'postfx_invalid_custom_pass_name' && first.invalidCustomParams === 'postfx_invalid_custom_params' && first.unsupportedCustomParams === 'postfx_custom_param_unsupported' && first.configuredOk === true && first.configuredReason === 'postfx_ok' && first.invalidEnabled === 'postfx_invalid_enabled' && first.removeConfiguredOk === true && first.removeConfiguredReason === 'postfx_ok' && first.missingToggle === 'postfx_pass_missing' && first.missingRemove === 'postfx_pass_missing'; })()",
1018
+ },
1019
+ {
1020
+ id: 'draw3d.postfx.ssr-dof.runtime.setup',
1021
+ expression: `(() => { try { const resetPasses = () => { for (const passName of ['ssr', 'dof', 'motionBlur', 'bloom', 'colorGrade', 'vignette', 'fxaa', 'ssao', 'custom:filmgrain', 'custom:glow']) { aura.draw3d.removePostFXPass(passName); } }; resetPasses(); const baseline = aura.draw3d.getPostFXState() || {}; const meshHandle = aura.mesh.createBox(1, 1, 1); const materialHandle = aura.material.create({ color: { r: 0.35, g: 0.7, b: 0.95, a: 1 }, metallic: 0.1, roughness: 0.6 }); const nodeId = aura.scene3d.createNode({ position: { x: 0, y: 0, z: -2.5 } }); const bind = aura.scene3d.bindRenderNode(nodeId, meshHandle, materialHandle, { layer: 0, visible: true, cull: false }); aura.camera3d.perspective(60, 0.1, 100); aura.camera3d.setPosition(0, 0, 4); aura.camera3d.lookAt(0, 0, 0); const setSsr = aura.draw3d.setPostFXPass('ssr', { strength: 1.0, threshold: 0.35, maxSteps: 96, thickness: 0.25, fadeEdge: 0.8, targetChain: { intermediateTargets: [], pingPong: false, composeMode: 'replace' } }); const setDof = aura.draw3d.setPostFXPass('dof', { strength: 0.9, focalDistance: 2.5, focalLength: 50, aperture: 2.2, maxBlur: 6.0 }); const setMotionBlur = aura.draw3d.setPostFXPass('motionBlur', { strength: 0.65, radius: 12.0, threshold: 0.0 }); const baseDraw = aura.draw; aura.draw = function () { const frameIndex = Number(globalThis.__postfxRuntimeFrame || 0); aura.camera3d.setPosition(frameIndex * 0.15, 0, 4 - (frameIndex * 0.1)); aura.camera3d.lookAt(0, 0, 0); aura.draw3d.clear3d({ r: 0.02, g: 0.02, b: 0.03, a: 1 }); aura.scene3d.submitRenderBindings(); globalThis.__postfxRuntimeFrame = frameIndex + 1; baseDraw(); }; globalThis.__postfxRuntimeFrame = 0; globalThis.__postfxRuntimeProbe = { meshHandle, materialHandle, nodeId, bindOk: bind?.ok === true, setSsrReason: setSsr?.reasonCode || null, setDofReason: setDof?.reasonCode || null, setMotionBlurReason: setMotionBlur?.reasonCode || null, baselineMutationCount: Number(baseline?.mutationCount || 0) }; return Number.isInteger(meshHandle) && meshHandle > 0 && Number.isInteger(materialHandle) && materialHandle > 0 && Number.isInteger(nodeId) && nodeId > 0 && bind?.ok === true && setSsr?.ok === true && setDof?.ok === true && setMotionBlur?.ok === true; } catch (_) { return false; } })()`,
1022
+ },
1023
+ ],
1024
+ nativePostChecks: [
1025
+ {
1026
+ id: 'draw3d.postfx.ssr-dof.runtime.evidence',
1027
+ expression: `(() => { try { const probe = globalThis.__postfxRuntimeProbe || {}; const postfx = aura.draw3d.getPostFXState() || {}; aura.debug.enableInspector(true); const stats = aura.debug.inspectorStats() || {}; aura.debug.enableInspector(false); const submission = stats?.scene3dRuntime?.submission || {}; const render = aura.scene3d.getRenderSubmissionState() || {}; const baselineMutationCount = Number(probe.baselineMutationCount || 0); return probe.bindOk === true && probe.setSsrReason === 'postfx_ok' && probe.setDofReason === 'postfx_ok' && probe.setMotionBlurReason === 'postfx_ok' && Array.isArray(postfx?.passes) && postfx.passes.map((pass) => pass.pass).join('|') === 'ssr|dof|motionBlur' && Number(postfx?.totalPasses || 0) === 3 && Number(postfx?.enabledPasses || 0) === 3 && (Number(postfx?.mutationCount || 0) - baselineMutationCount) === 3 && Number(submission?.scene3dSubmitted || 0) >= 1 && Number(submission?.postfxPassCount || 0) === 3 && Number(submission?.postfxEnabledPassCount || 0) === 3 && (Number(submission?.postfxMutationCount || 0) - baselineMutationCount) === 3 && Number(submission?.postfxLastExecutedPassCount || 0) === 3 && Number(submission?.postfxLastExecutedSsrCount || 0) === 1 && Number(submission?.postfxLastExecutedDofCount || 0) === 1 && Number(submission?.postfxLastExecutedMotionBlurCount || 0) === 1 && Number(submission?.postfxLastExecutionFingerprint || 0) > 0 && submission?.postfxLastExecutionReasonCode === 'postfx_executed' && submission?.postfxLastExecutionOk === true && submission?.postfxLastReasonCode === 'postfx_ok' && submission?.postfxLastOk === true && Number(render?.submitted || 0) >= 1 && Number(render?.submitted || 0) === Number(submission?.scene3dSubmitted || 0) && Number(render?.orderFingerprint || 0) > 0; } catch (_) { return false; } })()`,
1028
+ },
1029
+ ],
1030
+ source: `
1031
+ aura.setup = function () {};
1032
+ `,
1033
+ nativeFrames: 3,
1034
+ },
1035
+ {
1036
+ id: 'asset-streaming-cache-preload-parity',
1037
+ modes: ['native'],
1038
+ namespaces: ['assets'],
1039
+ functions: [
1040
+ 'aura.assets.load',
1041
+ 'aura.assets.getFormatSupport',
1042
+ 'aura.assets.getState',
1043
+ 'aura.assets.getStateHistory',
1044
+ 'aura.assets.evict',
1045
+ 'aura.assets.getCachePolicy',
1046
+ 'aura.assets.setCachePolicy',
1047
+ 'aura.assets.preload2d',
1048
+ 'aura.assets.preload3d',
1049
+ ],
1050
+ nativeChecks: [
1051
+ {
1052
+ id: 'assets.streaming.surface.methods',
1053
+ expression: "(() => ['load','getFormatSupport','getState','getStateHistory','evict','getCachePolicy','setCachePolicy','preload2d','preload3d'].every((name) => typeof aura.assets?.[name] === 'function'))()",
1054
+ },
1055
+ {
1056
+ id: 'assets.streaming.lifecycle.preload.deterministic',
1057
+ expression: "(() => { const runSample = (tag) => { const miss2d = `__missing__/stream-2d-${tag}.png`; const miss3d = `__missing__/stream-3d-${tag}.glb`; const preload2d = aura.assets.preload2d([miss2d]); const preload3d = aura.assets.preload3d([miss3d]); const state2d = aura.assets.getState(miss2d); const state3d = aura.assets.getState(miss3d); const history2d = aura.assets.getStateHistory(miss2d); const history3d = aura.assets.getStateHistory(miss3d); return { preload2d, preload3d, state2d: state2d ? { state: state2d.state, reasonCode: state2d.reasonCode } : null, state3d: state3d ? { state: state3d.state, reasonCode: state3d.reasonCode } : null, history2d: Array.isArray(history2d) ? history2d.map((entry) => entry.state).join('|') : '', history3d: Array.isArray(history3d) ? history3d.map((entry) => entry.state).join('|') : '' }; }; const first = runSample('a'); const second = runSample('b'); return JSON.stringify(first) === JSON.stringify(second) && first.preload2d?.ok === false && first.preload2d?.reasonCode === 'assets_preload_partial_failure' && first.preload2d?.requested === 1 && first.preload2d?.failed === 1 && Array.isArray(first.preload2d?.failures) && first.preload2d.failures.length === 1 && first.preload2d.failures[0].reasonCode === 'missing_asset' && first.preload3d?.ok === false && first.preload3d?.reasonCode === 'assets_preload_partial_failure' && first.preload3d?.requested === 1 && first.preload3d?.failed === 1 && Array.isArray(first.preload3d?.failures) && first.preload3d.failures.length === 1 && first.preload3d.failures[0].reasonCode === 'missing_asset' && first.state2d?.state === 'failed' && first.state2d?.reasonCode === 'missing_asset' && first.state3d?.state === 'failed' && first.state3d?.reasonCode === 'missing_asset' && first.history2d === 'queued|loading|failed' && first.history3d === 'queued|loading|failed'; })()",
1058
+ },
1059
+ {
1060
+ id: 'assets.format-support.reason-codes.stable',
1061
+ expression: "(() => { const normalize = (entry) => entry ? { ok: entry.ok === true, status: entry.status || null, reasonCode: entry.reasonCode || null, extension: entry.extension || null, supported: entry.supported === true, deferred: entry.deferred === true } : null; const runSample = () => { const supported = normalize(aura.assets.getFormatSupport('sprite.png')); const promoted = normalize(aura.assets.getFormatSupport('sprite.webp')); const deferred = normalize(aura.assets.getFormatSupport('sprite.bmp')); const model = normalize(aura.assets.getFormatSupport('future.glb')); const nativeVideo = normalize(aura.assets.getFormatSupport('future.mp4')); const font = normalize(aura.assets.getFormatSupport('font.woff2')); const unsupported = normalize(aura.assets.getFormatSupport('blob.xyz')); const invalidRaw = aura.assets.getFormatSupport(42); const invalid = invalidRaw ? { ok: invalidRaw.ok === true, status: invalidRaw.status || null, reasonCode: invalidRaw.reasonCode || null } : null; const loadReason = (() => { try { aura.assets.load('future.bmp'); } catch (err) { return String(err); } return ''; })(); const preload3d = aura.assets.preload3d(['future.glb']); return { supported, promoted, deferred, model, nativeVideo, font, unsupported, invalid, loadReason, preload3dReason: preload3d?.failures?.[0]?.reasonCode || null }; }; const first = runSample(); const second = runSample(); return JSON.stringify(first) === JSON.stringify(second) && first.supported?.ok === true && first.supported?.status === 'supported' && first.supported?.reasonCode === 'asset_format_supported' && first.supported?.supported === true && first.supported?.deferred === false && first.promoted?.status === 'supported' && first.promoted?.reasonCode === 'asset_format_supported' && first.promoted?.deferred === false && first.deferred?.status === 'unsupported' && first.deferred?.reasonCode === 'asset_format_unsupported' && first.deferred?.deferred === false && first.model?.status === 'supported' && first.model?.reasonCode === 'asset_format_supported' && first.nativeVideo?.status === 'supported' && first.nativeVideo?.reasonCode === 'asset_format_supported' && first.nativeVideo?.extension === 'mp4' && first.font?.status === 'supported' && first.font?.reasonCode === 'asset_format_supported' && first.unsupported?.status === 'unsupported' && first.unsupported?.reasonCode === 'asset_format_unsupported' && first.invalid?.ok === false && first.invalid?.status === 'unsupported' && first.invalid?.reasonCode === 'invalid_format_probe' && first.loadReason.includes('[reason:asset_format_unsupported]') && first.preload3dReason === 'missing_asset'; })()",
1062
+ },
1063
+ {
1064
+ id: 'assets.streaming.cache-policy.reason-codes',
1065
+ expression: "(() => { const invalidPaths = aura.assets.preload2d('bad'); const invalidPath = aura.assets.preload3d(['../bad.glb']); const invalidMaxEntries = aura.assets.setCachePolicy({ maxEntries: 0 }); const invalidMaxBytes = aura.assets.setCachePolicy({ maxBytes: 0 }); const invalidMode = aura.assets.setCachePolicy({ evictionMode: 'fifo' }); const updated = aura.assets.setCachePolicy({ maxEntries: 4, maxBytes: null, evictionMode: 'oldest' }); const policy = aura.assets.getCachePolicy(); const missingEvict = aura.assets.evict('__missing__/ghost.png'); const invalidEvict = aura.assets.evict('../ghost.png'); return invalidPaths?.ok === false && invalidPaths?.reasonCode === 'invalid_preload_paths' && invalidPath?.ok === false && invalidPath?.reasonCode === 'invalid_preload_path' && invalidMaxEntries?.ok === false && invalidMaxEntries?.reasonCode === 'invalid_max_entries' && invalidMaxBytes?.ok === false && invalidMaxBytes?.reasonCode === 'invalid_max_bytes' && invalidMode?.ok === false && invalidMode?.reasonCode === 'invalid_eviction_mode' && updated?.ok === true && updated?.reasonCode === 'cache_policy_updated' && policy?.maxEntries === 4 && policy?.maxBytes === null && policy?.evictionMode === 'oldest' && missingEvict?.ok === false && missingEvict?.reasonCode === 'missing_asset' && invalidEvict?.ok === false && invalidEvict?.reasonCode === 'invalid_path'; })()",
1066
+ },
1067
+ ],
1068
+ source: `
1069
+ aura.setup = function () {};
1070
+ `,
1071
+ nativeFrames: 2,
1072
+ },
1073
+ {
1074
+ id: 'physics-contact-filter-query-parity',
1075
+ modes: ['native'],
1076
+ namespaces: ['physics', 'debug'],
1077
+ functions: [
1078
+ 'aura.physics.configureStep',
1079
+ 'aura.physics.body',
1080
+ 'aura.physics.step',
1081
+ 'aura.physics.queryOverlap',
1082
+ 'aura.physics.queryPointDetailed',
1083
+ 'aura.physics.queryRaycast',
1084
+ 'aura.debug.inspectorStats',
1085
+ ],
1086
+ optionalModuleReadiness: { module: 'physics', state: 'enabled' },
1087
+ nativeEnv: {
1088
+ AURA_MODULE_PHYSICS: '1',
1089
+ AURA_MODULE_NET: '0',
1090
+ },
1091
+ nativeChecks: [
1092
+ {
1093
+ id: 'physics.contact-filter-query.surface.methods',
1094
+ expression: "(() => { const physics = aura.physics || {}; const filterMethodNames = ['setContactFilter', 'setCollisionFilter', 'setFilter']; const rootFilterAvailable = filterMethodNames.some((name) => typeof physics[name] === 'function'); const queryAvailable = ['queryOverlap', 'queryPointDetailed', 'queryRaycast'].every((name) => typeof physics[name] === 'function'); const coreAvailable = ['configureStep', 'body', 'step'].every((name) => typeof physics[name] === 'function'); let bodyFilterAvailable = false; try { const probe = physics.body('static', { x: 512, y: 512, shape: 'circle', radius: 0.25 }); bodyFilterAvailable = !!probe && filterMethodNames.some((name) => typeof probe?.[name] === 'function'); } catch (_) { bodyFilterAvailable = false; } return coreAvailable && queryAvailable && (rootFilterAvailable || bodyFilterAvailable); })()",
1095
+ },
1096
+ {
1097
+ id: 'physics.contact-filter-query.callback-ordering.filters.deterministic',
1098
+ expression: "(() => { try { const runSample = () => { const physics = aura.physics || {}; physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true, resetWorld: true }); const hits = []; const a = physics.body('dynamic', { x: 0, y: 0, shape: 'circle', radius: 1 }); const b = physics.body('static', { x: 0, y: 0, shape: 'circle', radius: 1 }); const c = physics.body('static', { x: 0, y: 0, shape: 'circle', radius: 1 }); if (!a || !b || !c || !Number.isFinite(a.id) || !Number.isFinite(b.id) || !Number.isFinite(c.id)) return null; if (typeof a.onCollide === 'function') a.onCollide((other) => hits.push('a:' + other)); if (typeof b.onCollide === 'function') b.onCollide((other) => hits.push('b:' + other)); if (typeof c.onCollide === 'function') c.onCollide((other) => hits.push('c:' + other)); const fa = globalThis.__physicsInvokeFilterMutation(a, 0x0001, 0x0002); const fb = globalThis.__physicsInvokeFilterMutation(b, 0x0002, 0x0001); const fc = globalThis.__physicsInvokeFilterMutation(c, 0x0004, 0x0000); const step = physics.step(1 / 60); const idMap = { [a.id]: 'A', [b.id]: 'B', [c.id]: 'C' }; return { normalizedHits: globalThis.__physicsNormalizeHitTrace(hits, idMap), expectedHits: 'a:B|b:A', filterReasonCodes: [fa?.reasonCode || null, fb?.reasonCode || null, fc?.reasonCode || null], filterOk: [fa, fb, fc].every((entry) => !!entry && entry.ok === true), stepOk: !(step && step.ok === false) }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.filterOk === true && first.stepOk === true && first.normalizedHits === first.expectedHits; } catch (_) { return false; } })()",
1099
+ },
1100
+ {
1101
+ id: 'physics.contact-filter-query.query-ordering-guarantees.deterministic',
1102
+ expression: "(() => { try { const runSample = () => { const physics = aura.physics || {}; physics.configureStep({ stepSeconds: 1 / 120, maxSubSteps: 16, auto: false, resetAccumulator: true, resetWorld: true }); const a = physics.body('static', { x: -3, y: 0, shape: 'circle', radius: 1 }); const b = physics.body('static', { x: 0, y: 0, shape: 'circle', radius: 1 }); const c = physics.body('static', { x: 3, y: 0, shape: 'circle', radius: 1 }); if (!a || !b || !c || !Number.isFinite(a.id) || !Number.isFinite(b.id) || !Number.isFinite(c.id)) return null; const fa = globalThis.__physicsInvokeFilterMutation(a, 0x0001, 0x0001); const fb = globalThis.__physicsInvokeFilterMutation(b, 0x0002, 0x0002); const fc = globalThis.__physicsInvokeFilterMutation(c, 0x0001, 0x0001); const tracked = new Set([a.id, b.id, c.id]); const area = { x: -5, y: -2, w: 10, h: 4 }; const filter = { category: 0x0001, mask: 0x0001 }; const overlapA = globalThis.__physicsInvokeFilteredQuery(area, filter); const overlapB = globalThis.__physicsInvokeFilteredQuery(area, filter); const overlapHitsA = globalThis.__physicsExtractHits(overlapA).filter((id) => tracked.has(id)); const overlapHitsB = globalThis.__physicsExtractHits(overlapB).filter((id) => tracked.has(id)); const pointA = physics.queryPointDetailed({ x: 0, y: 0 }); const pointB = physics.queryPointDetailed({ x: 0, y: 0 }); const pointHitsA = globalThis.__physicsExtractHits(pointA).filter((id) => tracked.has(id)); const pointHitsB = globalThis.__physicsExtractHits(pointB).filter((id) => tracked.has(id)); return { filterOk: [fa, fb, fc].every((entry) => !!entry && entry.ok === true), overlapOrder: overlapHitsA.join('|'), overlapRepeatOrder: overlapHitsB.join('|'), overlapSortedOrder: overlapHitsA.slice().sort((x, y) => x - y).join('|'), overlapCount: overlapHitsA.length, pointOrder: pointHitsA.join('|'), pointRepeatOrder: pointHitsB.join('|') }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.filterOk === true && first.overlapCount === 2 && first.overlapOrder === first.overlapRepeatOrder && first.overlapOrder === first.overlapSortedOrder && first.pointOrder === first.pointRepeatOrder; } catch (_) { return false; } })()",
1103
+ },
1104
+ {
1105
+ id: 'physics.contact-filter-query.invalid-filter.reason-codes',
1106
+ expression: "(() => { try { const runSample = () => { const physics = aura.physics || {}; const body = physics.body('static', { x: 32, y: 32, shape: 'circle', radius: 0.5 }); if (!body || !Number.isFinite(body.id)) return null; const invalidA = globalThis.__physicsInvokeInvalidFilterMutation(body, 'bad', 1); const invalidB = globalThis.__physicsInvokeInvalidFilterMutation(body, 1, 'bad'); const invalidC = globalThis.__physicsInvokeInvalidFilterMutation(body, -1, 1); const failures = [invalidA, invalidB, invalidC].map((entry) => ({ ok: entry?.ok, reasonCode: entry?.reasonCode || null })); const reasonCoded = failures.every((entry) => entry.ok === false && typeof entry.reasonCode === 'string' && entry.reasonCode.length > 0 && entry.reasonCode !== 'filter_api_missing' && entry.reasonCode !== 'filter_call_failed' && entry.reasonCode !== 'filter_invalid_not_reason_coded'); return { failures, reasonCoded }; }; const first = runSample(); const second = runSample(); return !!first && !!second && JSON.stringify(first) === JSON.stringify(second) && first.reasonCoded === true; } catch (_) { return false; } })()",
1107
+ },
1108
+ {
1109
+ id: 'physics.contact-filter-query.inspector.telemetry-if-available',
1110
+ expression: "(() => { try { if (typeof aura.debug?.enableInspector !== 'function' || typeof aura.debug?.inspectorStats !== 'function') return true; aura.debug.enableInspector(true); const stats = aura.debug.inspectorStats() || {}; aura.debug.enableInspector(false); const rows = []; const pushRow = (entry) => { if (entry && typeof entry === 'object') rows.push(entry); }; pushRow(stats.physicsRuntime); pushRow(stats.scene3dRuntime && stats.scene3dRuntime.physics); pushRow(stats.scene3dRuntime && stats.scene3dRuntime.physicsRuntime); pushRow(stats.phase2 && stats.phase2.physics); if (rows.length === 0) return true; return rows.every((row) => { const scalarValues = Object.values(row).filter((value) => typeof value === 'number' || typeof value === 'boolean'); if (scalarValues.length === 0) return false; return scalarValues.every((value) => (typeof value === 'number' ? Number.isFinite(value) && value >= 0 : typeof value === 'boolean')); }); } catch (_) { return false; } })()",
1111
+ },
1112
+ ],
1113
+ source: `
1114
+ aura.setup = function () {};
1115
+
1116
+ globalThis.__physicsNormalizeMutationResult = function __physicsNormalizeMutationResult(result) {
1117
+ if (result === undefined || result === null) return { ok: true, reasonCode: null };
1118
+ if (typeof result === 'boolean') return { ok: result, reasonCode: result ? null : 'boolean_false' };
1119
+ if (typeof result === 'object') {
1120
+ if (typeof result.ok === 'boolean') {
1121
+ return {
1122
+ ok: result.ok,
1123
+ reasonCode: typeof result.reasonCode === 'string' ? result.reasonCode : null,
1124
+ };
1125
+ }
1126
+ if (typeof result.reasonCode === 'string') {
1127
+ return { ok: result.ok !== false, reasonCode: result.reasonCode };
1128
+ }
1129
+ return { ok: true, reasonCode: null };
1130
+ }
1131
+ return { ok: true, reasonCode: null };
1132
+ };
1133
+
1134
+ globalThis.__physicsInvokeFilterMutation = function __physicsInvokeFilterMutation(body, category, mask) {
1135
+ const physics = aura.physics || {};
1136
+ const bodyId = body && Number.isFinite(body.id) ? body.id : null;
1137
+ const attempts = [];
1138
+ const pushAttempts = (target, method, withBodyId) => {
1139
+ if (!target || typeof target[method] !== 'function') return;
1140
+ attempts.push(() => (withBodyId ? target[method](bodyId, { category, mask }) : target[method]({ category, mask })));
1141
+ attempts.push(() => (withBodyId ? target[method](bodyId, category, mask) : target[method](category, mask)));
1142
+ attempts.push(() => (withBodyId ? target[method](bodyId, { categoryBits: category, maskBits: mask }) : target[method]({ categoryBits: category, maskBits: mask })));
1143
+ attempts.push(() => (withBodyId ? target[method](bodyId, { categories: category, masks: mask }) : target[method]({ categories: category, masks: mask })));
1144
+ };
1145
+
1146
+ for (const method of ['setContactFilter', 'setCollisionFilter', 'setFilter']) {
1147
+ pushAttempts(body, method, false);
1148
+ pushAttempts(physics, method, true);
1149
+ }
1150
+
1151
+ if (attempts.length === 0) return { ok: false, reasonCode: 'filter_api_missing' };
1152
+ for (const invoke of attempts) {
1153
+ try {
1154
+ return globalThis.__physicsNormalizeMutationResult(invoke());
1155
+ } catch (_) {}
1156
+ }
1157
+ return { ok: false, reasonCode: 'filter_call_failed' };
1158
+ };
1159
+
1160
+ globalThis.__physicsInvokeInvalidFilterMutation = function __physicsInvokeInvalidFilterMutation(body, invalidCategory, invalidMask) {
1161
+ const physics = aura.physics || {};
1162
+ const bodyId = body && Number.isFinite(body.id) ? body.id : null;
1163
+ const attempts = [];
1164
+ const pushAttempts = (target, method, withBodyId) => {
1165
+ if (!target || typeof target[method] !== 'function') return;
1166
+ attempts.push(() => (withBodyId ? target[method](bodyId, { category: invalidCategory, mask: invalidMask }) : target[method]({ category: invalidCategory, mask: invalidMask })));
1167
+ attempts.push(() => (withBodyId ? target[method](bodyId, invalidCategory, invalidMask) : target[method](invalidCategory, invalidMask)));
1168
+ attempts.push(() => (withBodyId ? target[method](bodyId, { categoryBits: invalidCategory, maskBits: invalidMask }) : target[method]({ categoryBits: invalidCategory, maskBits: invalidMask })));
1169
+ };
1170
+
1171
+ for (const method of ['setContactFilter', 'setCollisionFilter', 'setFilter']) {
1172
+ pushAttempts(body, method, false);
1173
+ pushAttempts(physics, method, true);
1174
+ }
1175
+
1176
+ if (attempts.length === 0) return { ok: false, reasonCode: 'filter_api_missing' };
1177
+ for (const invoke of attempts) {
1178
+ try {
1179
+ const normalized = globalThis.__physicsNormalizeMutationResult(invoke());
1180
+ if (normalized.ok === false && typeof normalized.reasonCode === 'string' && normalized.reasonCode.length > 0) {
1181
+ return normalized;
1182
+ }
1183
+ } catch (_) {}
1184
+ }
1185
+ return { ok: false, reasonCode: 'filter_invalid_not_reason_coded' };
1186
+ };
1187
+
1188
+ globalThis.__physicsInvokeFilteredQuery = function __physicsInvokeFilteredQuery(area, filter) {
1189
+ const physics = aura.physics || {};
1190
+ const attempts = [
1191
+ () => physics.queryOverlap({ x: area.x, y: area.y, w: area.w, h: area.h, filter }),
1192
+ () => physics.queryOverlap({ x: area.x, y: area.y, w: area.w, h: area.h, category: filter.category, mask: filter.mask }),
1193
+ () => physics.queryOverlap({ x: area.x, y: area.y, w: area.w, h: area.h, categoryBits: filter.category, maskBits: filter.mask }),
1194
+ () => physics.queryOverlap(area, filter),
1195
+ () => physics.queryOverlap(area),
1196
+ () => physics.queryOverlap(area.x, area.y, area.w, area.h),
1197
+ ];
1198
+ for (const invoke of attempts) {
1199
+ try {
1200
+ const result = invoke();
1201
+ if (result !== undefined) return result;
1202
+ } catch (_) {}
1203
+ }
1204
+ return null;
1205
+ };
1206
+
1207
+ globalThis.__physicsExtractHits = function __physicsExtractHits(result) {
1208
+ if (Array.isArray(result)) return result.slice();
1209
+ if (result && Array.isArray(result.hits)) return result.hits.slice();
1210
+ return [];
1211
+ };
1212
+
1213
+ globalThis.__physicsNormalizeHitTrace = function __physicsNormalizeHitTrace(hits, idMap) {
1214
+ return hits.map((entry) => {
1215
+ const raw = String(entry);
1216
+ const split = raw.split(':');
1217
+ if (split.length !== 2) return raw;
1218
+ const role = split[0];
1219
+ const idValue = Number(split[1]);
1220
+ const mapped = Number.isFinite(idValue) ? idMap[idValue] : null;
1221
+ return mapped ? role + ':' + mapped : raw;
1222
+ }).join('|');
1223
+ };
1224
+ `,
1225
+ nativeFrames: 2,
1226
+ },
1227
+ {
1228
+ id: 'physics-snapshot-replay-rollback-primitives',
1229
+ modes: ['native'],
1230
+ namespaces: ['physics'],
1231
+ functions: [
1232
+ 'aura.physics.captureSnapshot',
1233
+ 'aura.physics.restoreSnapshot',
1234
+ 'aura.physics.replay',
1235
+ 'aura.physics.rollback',
1236
+ ],
1237
+ optionalModuleReadiness: { module: 'physics', state: 'enabled' },
1238
+ nativeEnv: {
1239
+ AURA_MODULE_PHYSICS: '1',
1240
+ AURA_MODULE_NET: '0',
1241
+ },
1242
+ nativeChecks: [
1243
+ {
1244
+ id: 'physics.snapshot-replay-rollback.surface.methods',
1245
+ expression: "(() => { const info = globalThis.__physicsSnapshotReplaySurface(); globalThis.__physicsSnapshotReplaySurfaceInfo = info; return info.ready || info.absent; })()",
1246
+ },
1247
+ {
1248
+ id: 'physics.snapshot-replay-rollback.invalid-operations.reason-codes',
1249
+ expression: "(() => { const info = globalThis.__physicsSnapshotReplaySurfaceInfo || globalThis.__physicsSnapshotReplaySurface(); if (info.absent) return true; if (!info.ready) return false; const runSample = () => { const restoreInvalid = globalThis.__physicsSnapshotReplayInvoke('restore', [[null], ['invalid'], [{ snapshot: null }], [{ frameIndex: 'bad' }]]); const replayInvalid = globalThis.__physicsSnapshotReplayInvoke('replay', [[null], [{ snapshot: null, frames: [] }], [{ frames: 'bad' }], [{ frameCount: -1 }]]); const rollbackInvalid = globalThis.__physicsSnapshotReplayInvoke('rollback', [[-1], [{ frameIndex: -1 }], [Number.MAX_SAFE_INTEGER], [{ frameIndex: Number.MAX_SAFE_INTEGER }]]); return { restore: globalThis.__physicsSnapshotReplayInvalidReason(restoreInvalid), replay: globalThis.__physicsSnapshotReplayInvalidReason(replayInvalid), rollback: globalThis.__physicsSnapshotReplayInvalidReason(rollbackInvalid) }; }; const first = runSample(); const second = runSample(); const reasonCoded = Object.values(first).every((reason) => typeof reason === 'string' && reason.length > 0 && reason !== 'surface_missing' && reason !== 'failed_without_reason_code' && reason !== 'operation_succeeded_without_reason'); return JSON.stringify(first) === JSON.stringify(second) && reasonCoded; })()",
1250
+ },
1251
+ {
1252
+ id: 'physics.snapshot-replay-rollback.rollback-boundary.reason-codes',
1253
+ expression: "(() => { const info = globalThis.__physicsSnapshotReplaySurfaceInfo || globalThis.__physicsSnapshotReplaySurface(); if (info.absent) return true; if (!info.ready) return false; const runSample = () => { const capture = globalThis.__physicsSnapshotReplayInvoke('capture', [[], [{ deterministic: true }], [{ includeBodies: true }]]); const captureNorm = globalThis.__physicsSnapshotReplayNormalize(capture); if (captureNorm.ok === false || capture.result === undefined || capture.result === null) return null; const snapshot = capture.result; const low = globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('rollback', [[-1], [{ frameIndex: -1 }], [snapshot, -1], [{ snapshot, frameIndex: -1 }]])); const high = globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('rollback', [[Number.MAX_SAFE_INTEGER], [{ frameIndex: Number.MAX_SAFE_INTEGER }], [snapshot, Number.MAX_SAFE_INTEGER], [{ snapshot, frameIndex: Number.MAX_SAFE_INTEGER }]])); const valid = globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('rollback', [[0], [{ frameIndex: 0 }], [snapshot, 0], [{ snapshot, frameIndex: 0 }]])); return { low, high, valid }; }; const first = runSample(); const second = runSample(); if (!first || !second) return false; const reasonCoded = first.low.ok === false && first.high.ok === false && typeof first.low.reasonCode === 'string' && first.low.reasonCode.length > 0 && typeof first.high.reasonCode === 'string' && first.high.reasonCode.length > 0; const stableFrameIndex = first.valid.frameIndex === null || Number.isInteger(first.valid.frameIndex); return JSON.stringify(first) === JSON.stringify(second) && reasonCoded && stableFrameIndex; })()",
1254
+ },
1255
+ {
1256
+ id: 'physics.snapshot-replay-rollback.replay-integrity.deterministic',
1257
+ expression: "(() => { const info = globalThis.__physicsSnapshotReplaySurfaceInfo || globalThis.__physicsSnapshotReplaySurface(); if (info.absent) return true; if (!info.ready) return false; const baselineFrames = [1 / 60, 1 / 60, 1 / 60, 1 / 30, 1 / 120]; const first = globalThis.__physicsSnapshotReplayRun(baselineFrames); const second = globalThis.__physicsSnapshotReplayRun(baselineFrames); if (first.skipped || second.skipped) return true; if (!first.ok || !second.ok) return false; const normalizedFirst = JSON.stringify({ replay: first.replay, state: first.state, rollback: first.rollback }); const normalizedSecond = JSON.stringify({ replay: second.replay, state: second.state, rollback: second.rollback }); const frameIndexed = first.replay.frameIndex === null || Number.isInteger(first.replay.frameIndex); return normalizedFirst === normalizedSecond && frameIndexed; })()",
1258
+ },
1259
+ {
1260
+ id: 'physics.snapshot-replay-rollback.replay-divergence.detection',
1261
+ expression: "(() => { const info = globalThis.__physicsSnapshotReplaySurfaceInfo || globalThis.__physicsSnapshotReplaySurface(); if (info.absent) return true; if (!info.ready) return false; const baselineFrames = [1 / 60, 1 / 60, 1 / 60, 1 / 30, 1 / 120]; const divergentFrames = [1 / 60, 1 / 60, 1 / 120, 1 / 30, 1 / 120]; const baselineA = globalThis.__physicsSnapshotReplayRun(baselineFrames); const baselineB = globalThis.__physicsSnapshotReplayRun(baselineFrames); const divergent = globalThis.__physicsSnapshotReplayRun(divergentFrames); if (baselineA.skipped || baselineB.skipped || divergent.skipped) return true; if (!baselineA.ok || !baselineB.ok || !divergent.ok) return false; const stableBaseline = JSON.stringify({ replay: baselineA.replay, state: baselineA.state }) === JSON.stringify({ replay: baselineB.replay, state: baselineB.state }); const baselineSignature = JSON.stringify({ replay: baselineA.replay, state: baselineA.state }); const divergentSignature = JSON.stringify({ replay: divergent.replay, state: divergent.state }); const explicitDivergence = divergent.replay.diverged === true || Number.isInteger(divergent.replay.divergenceFrame); return stableBaseline && (explicitDivergence || baselineSignature !== divergentSignature); })()",
1262
+ },
1263
+ ],
1264
+ source: `
1265
+ aura.setup = function () {};
1266
+
1267
+ globalThis.__physicsSnapshotReplaySurface = function __physicsSnapshotReplaySurface() {
1268
+ const physics = aura.physics || {};
1269
+ const pick = (names) => names.find((name) => typeof physics[name] === 'function') || null;
1270
+ const info = {
1271
+ capture: pick(['captureSnapshot', 'snapshotCapture', 'snapshot', 'createSnapshot']),
1272
+ restore: pick(['restoreSnapshot', 'snapshotRestore', 'restoreSnapshotState', 'restore']),
1273
+ replay: pick(['replay', 'runReplay', 'replayFrames', 'replaySnapshot']),
1274
+ rollback: pick(['rollback', 'rollbackToFrame', 'rollbackFrame', 'rewindToFrame']),
1275
+ state: pick(['getReplayState', 'getSnapshotReplayState', 'getRollbackState']),
1276
+ };
1277
+ info.methodCount = ['capture', 'restore', 'replay', 'rollback'].filter((key) => typeof info[key] === 'string').length;
1278
+ info.absent = info.methodCount === 0;
1279
+ info.ready = info.methodCount === 4;
1280
+ return info;
1281
+ };
1282
+
1283
+ globalThis.__physicsSnapshotReplayReasonFromValue = function __physicsSnapshotReplayReasonFromValue(value) {
1284
+ if (!value || typeof value !== 'object') return null;
1285
+ if (typeof value.reasonCode === 'string' && value.reasonCode.length > 0) return value.reasonCode;
1286
+ if (typeof value.reason === 'string' && value.reason.length > 0) return value.reason;
1287
+ if (typeof value.errorCode === 'string' && value.errorCode.length > 0) return value.errorCode;
1288
+ if (typeof value.error === 'string' && value.error.length > 0) return value.error;
1289
+ return null;
1290
+ };
1291
+
1292
+ globalThis.__physicsSnapshotReplayExtractErrorReason = function __physicsSnapshotReplayExtractErrorReason(error) {
1293
+ if (!error) return null;
1294
+ if (typeof error.reasonCode === 'string' && error.reasonCode.length > 0) return error.reasonCode;
1295
+ if (typeof error.reason === 'string' && error.reason.length > 0) return error.reason;
1296
+ if (typeof error.message === 'string' && error.message.length > 0) return error.message;
1297
+ const asString = String(error);
1298
+ return asString && asString.length > 0 ? asString : null;
1299
+ };
1300
+
1301
+ globalThis.__physicsSnapshotReplayInvoke = function __physicsSnapshotReplayInvoke(kind, argForms) {
1302
+ const physics = aura.physics || {};
1303
+ const info = globalThis.__physicsSnapshotReplaySurfaceInfo || globalThis.__physicsSnapshotReplaySurface();
1304
+ const methodName = info[kind];
1305
+ if (!methodName || typeof physics[methodName] !== 'function') {
1306
+ return { available: false, methodName, threw: false, result: null, errorReason: kind + '_surface_missing' };
1307
+ }
1308
+
1309
+ const forms = Array.isArray(argForms) && argForms.length > 0 ? argForms : [[]];
1310
+ let lastReason = null;
1311
+ for (const form of forms) {
1312
+ const args = Array.isArray(form) ? form : [form];
1313
+ try {
1314
+ const result = physics[methodName](...args);
1315
+ return { available: true, methodName, threw: false, result, errorReason: null };
1316
+ } catch (error) {
1317
+ lastReason = globalThis.__physicsSnapshotReplayExtractErrorReason(error);
1318
+ }
1319
+ }
1320
+ return { available: true, methodName, threw: true, result: null, errorReason: lastReason || 'exception_without_reason' };
1321
+ };
1322
+
1323
+ globalThis.__physicsSnapshotReplayNormalize = function __physicsSnapshotReplayNormalize(entry) {
1324
+ if (!entry || entry.available !== true) {
1325
+ return {
1326
+ available: false,
1327
+ ok: null,
1328
+ reasonCode: entry && typeof entry.errorReason === 'string' ? entry.errorReason : null,
1329
+ frameIndex: null,
1330
+ diverged: null,
1331
+ divergenceFrame: null,
1332
+ digest: null,
1333
+ };
1334
+ }
1335
+
1336
+ const value = entry.result;
1337
+ const reasonCode = globalThis.__physicsSnapshotReplayReasonFromValue(value) || entry.errorReason || null;
1338
+ const ok = entry.threw
1339
+ ? false
1340
+ : (
1341
+ value && typeof value === 'object' && typeof value.ok === 'boolean'
1342
+ ? value.ok
1343
+ : true
1344
+ );
1345
+ const frameIndex = value && typeof value === 'object'
1346
+ ? (
1347
+ Number.isFinite(value.frameIndex)
1348
+ ? Math.trunc(value.frameIndex)
1349
+ : (
1350
+ Number.isFinite(value.frame)
1351
+ ? Math.trunc(value.frame)
1352
+ : (
1353
+ Number.isFinite(value.replayFrame)
1354
+ ? Math.trunc(value.replayFrame)
1355
+ : null
1356
+ )
1357
+ )
1358
+ )
1359
+ : null;
1360
+ const diverged = value && typeof value === 'object'
1361
+ ? (
1362
+ typeof value.diverged === 'boolean'
1363
+ ? value.diverged
1364
+ : (
1365
+ typeof value.divergence === 'boolean'
1366
+ ? value.divergence
1367
+ : (
1368
+ typeof value.replayDiverged === 'boolean'
1369
+ ? value.replayDiverged
1370
+ : null
1371
+ )
1372
+ )
1373
+ )
1374
+ : null;
1375
+ const divergenceFrame = value && typeof value === 'object'
1376
+ ? (
1377
+ Number.isFinite(value.divergenceFrame)
1378
+ ? Math.trunc(value.divergenceFrame)
1379
+ : (
1380
+ Number.isFinite(value.divergenceFrameIndex)
1381
+ ? Math.trunc(value.divergenceFrameIndex)
1382
+ : null
1383
+ )
1384
+ )
1385
+ : null;
1386
+ const digest = value && typeof value === 'object'
1387
+ ? (
1388
+ typeof value.digest === 'string'
1389
+ ? value.digest
1390
+ : (
1391
+ typeof value.hash === 'string'
1392
+ ? value.hash
1393
+ : (
1394
+ typeof value.finalStateHash === 'string'
1395
+ ? value.finalStateHash
1396
+ : null
1397
+ )
1398
+ )
1399
+ )
1400
+ : null;
1401
+
1402
+ return { available: true, ok, reasonCode, frameIndex, diverged, divergenceFrame, digest };
1403
+ };
1404
+
1405
+ globalThis.__physicsSnapshotReplayInvalidReason = function __physicsSnapshotReplayInvalidReason(entry) {
1406
+ const normalized = globalThis.__physicsSnapshotReplayNormalize(entry);
1407
+ if (normalized.available !== true) return 'surface_missing';
1408
+ if (normalized.ok === false && typeof normalized.reasonCode === 'string' && normalized.reasonCode.length > 0) {
1409
+ return normalized.reasonCode;
1410
+ }
1411
+ if (normalized.ok === false) return 'failed_without_reason_code';
1412
+ return 'operation_succeeded_without_reason';
1413
+ };
1414
+
1415
+ globalThis.__physicsSnapshotReplayRun = function __physicsSnapshotReplayRun(frames) {
1416
+ const baseCapture = globalThis.__physicsSnapshotReplayInvoke('capture', [[], [{ deterministic: true }], [{ includeBodies: true }]]);
1417
+ const baseCaptureNorm = globalThis.__physicsSnapshotReplayNormalize(baseCapture);
1418
+ if (baseCaptureNorm.available !== true) {
1419
+ return {
1420
+ skipped: true,
1421
+ ok: true,
1422
+ capture: baseCaptureNorm,
1423
+ replay: { available: false, ok: null, reasonCode: 'replay_surface_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1424
+ state: { available: false, ok: null, reasonCode: 'state_surface_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1425
+ rollback: { available: false, ok: null, reasonCode: 'rollback_surface_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1426
+ };
1427
+ }
1428
+
1429
+ if (
1430
+ baseCaptureNorm.ok === false
1431
+ || baseCapture.result === undefined
1432
+ || baseCapture.result === null
1433
+ || typeof baseCapture.result.snapshot !== 'string'
1434
+ ) {
1435
+ return {
1436
+ skipped: false,
1437
+ ok: false,
1438
+ capture: baseCaptureNorm,
1439
+ replay: { available: false, ok: null, reasonCode: 'capture_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1440
+ state: { available: false, ok: null, reasonCode: 'capture_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1441
+ rollback: { available: false, ok: null, reasonCode: 'capture_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1442
+ };
1443
+ }
1444
+
1445
+ if (typeof globalThis.__physicsSnapshotReplayBaseSnapshot !== 'string') {
1446
+ globalThis.__physicsSnapshotReplayBaseSnapshot = baseCapture.result.snapshot;
1447
+ globalThis.__physicsSnapshotReplayBaseFrameIndex = Number.isInteger(baseCaptureNorm.frameIndex)
1448
+ ? baseCaptureNorm.frameIndex
1449
+ : 0;
1450
+ }
1451
+
1452
+ const baseSnapshot = globalThis.__physicsSnapshotReplayBaseSnapshot;
1453
+ const baseFrameIndex = Number.isInteger(globalThis.__physicsSnapshotReplayBaseFrameIndex)
1454
+ ? globalThis.__physicsSnapshotReplayBaseFrameIndex
1455
+ : 0;
1456
+
1457
+ const restoreBaseline = globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('restore', [
1458
+ [baseSnapshot],
1459
+ [{ snapshot: baseSnapshot }],
1460
+ ]));
1461
+ if (restoreBaseline.available !== true || restoreBaseline.ok === false) {
1462
+ return {
1463
+ skipped: false,
1464
+ ok: false,
1465
+ capture: baseCaptureNorm,
1466
+ replay: { available: false, ok: null, reasonCode: 'restore_baseline_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1467
+ state: { available: false, ok: null, reasonCode: 'restore_baseline_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1468
+ rollback: restoreBaseline,
1469
+ };
1470
+ }
1471
+
1472
+ const physics = aura.physics || {};
1473
+ if (
1474
+ typeof physics.setGravity !== 'function'
1475
+ || typeof physics.configureStep !== 'function'
1476
+ || typeof physics.body !== 'function'
1477
+ || typeof physics.step !== 'function'
1478
+ ) {
1479
+ return {
1480
+ skipped: true,
1481
+ ok: true,
1482
+ capture: baseCaptureNorm,
1483
+ replay: { available: false, ok: null, reasonCode: 'step_api_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1484
+ state: { available: false, ok: null, reasonCode: 'step_api_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1485
+ rollback: restoreBaseline,
1486
+ };
1487
+ }
1488
+
1489
+ const sequence = Array.isArray(frames) && frames.length > 0
1490
+ ? frames
1491
+ : [1 / 60, 1 / 60, 1 / 30, 1 / 120];
1492
+ let body = null;
1493
+ try {
1494
+ physics.setGravity(0, 0);
1495
+ physics.configureStep({ stepSeconds: 1 / 60, maxSubSteps: 8, auto: false, resetAccumulator: true });
1496
+ body = physics.body('dynamic', { x: 0, y: 0, shape: 'circle', radius: 0.5 });
1497
+ if (!body || typeof body.setVelocity !== 'function') {
1498
+ return {
1499
+ skipped: true,
1500
+ ok: true,
1501
+ capture: baseCaptureNorm,
1502
+ replay: { available: false, ok: null, reasonCode: 'body_api_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1503
+ state: { available: false, ok: null, reasonCode: 'body_api_missing', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1504
+ rollback: restoreBaseline,
1505
+ };
1506
+ }
1507
+ for (let index = 0; index < sequence.length; index += 1) {
1508
+ const dt = Number(sequence[index]);
1509
+ if (!Number.isFinite(dt) || dt <= 0) continue;
1510
+ const speed = 1 + (dt * 60);
1511
+ body.setVelocity(speed, 0);
1512
+ physics.step(dt);
1513
+ }
1514
+ } catch (error) {
1515
+ return {
1516
+ skipped: false,
1517
+ ok: false,
1518
+ capture: baseCaptureNorm,
1519
+ replay: { available: false, ok: null, reasonCode: 'step_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1520
+ state: { available: false, ok: null, reasonCode: String(error && error.message ? error.message : error), frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1521
+ rollback: restoreBaseline,
1522
+ };
1523
+ }
1524
+
1525
+ const postCapture = globalThis.__physicsSnapshotReplayInvoke('capture', [[], [{ deterministic: true }], [{ includeBodies: true }]]);
1526
+ const postCaptureNorm = globalThis.__physicsSnapshotReplayNormalize(postCapture);
1527
+ if (
1528
+ postCaptureNorm.available !== true
1529
+ || postCaptureNorm.ok === false
1530
+ || !postCapture.result
1531
+ || typeof postCapture.result.snapshot !== 'string'
1532
+ ) {
1533
+ return {
1534
+ skipped: false,
1535
+ ok: false,
1536
+ capture: postCaptureNorm,
1537
+ replay: { available: false, ok: null, reasonCode: 'post_capture_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1538
+ state: { available: false, ok: null, reasonCode: 'post_capture_failed', frameIndex: null, diverged: null, divergenceFrame: null, digest: null },
1539
+ rollback: restoreBaseline,
1540
+ };
1541
+ }
1542
+
1543
+ const targetFrame = Number.isInteger(postCaptureNorm.frameIndex)
1544
+ ? postCaptureNorm.frameIndex
1545
+ : null;
1546
+ const rollback = globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('rollback', [
1547
+ [baseFrameIndex],
1548
+ [{ frameIndex: baseFrameIndex }],
1549
+ ]));
1550
+ const replay = targetFrame == null
1551
+ ? { available: true, ok: false, reasonCode: 'invalid_frame_index', frameIndex: null, diverged: null, divergenceFrame: null, digest: null }
1552
+ : globalThis.__physicsSnapshotReplayNormalize(globalThis.__physicsSnapshotReplayInvoke('replay', [
1553
+ [targetFrame],
1554
+ [{ frameIndex: targetFrame }],
1555
+ ]));
1556
+
1557
+ const replayStateCapture = globalThis.__physicsSnapshotReplayInvoke('capture', [[], [{ deterministic: true }], [{ includeBodies: true }]]);
1558
+ const replayStateNorm = globalThis.__physicsSnapshotReplayNormalize(replayStateCapture);
1559
+ const replayDigest = replayStateCapture
1560
+ && replayStateCapture.result
1561
+ && typeof replayStateCapture.result.snapshot === 'string'
1562
+ ? replayStateCapture.result.snapshot
1563
+ : null;
1564
+ const state = {
1565
+ available: replayStateNorm.available,
1566
+ ok: replayStateNorm.ok,
1567
+ reasonCode: replayStateNorm.reasonCode,
1568
+ frameIndex: replayStateNorm.frameIndex,
1569
+ diverged: replayStateNorm.diverged,
1570
+ divergenceFrame: replayStateNorm.divergenceFrame,
1571
+ digest: replayDigest,
1572
+ };
1573
+
1574
+ globalThis.__physicsSnapshotReplayInvoke('restore', [
1575
+ [baseSnapshot],
1576
+ [{ snapshot: baseSnapshot }],
1577
+ ]);
1578
+
1579
+ const ok = (
1580
+ replay.available === true
1581
+ && replay.ok !== false
1582
+ && rollback.available === true
1583
+ && rollback.ok !== false
1584
+ && state.available === true
1585
+ && state.ok !== false
1586
+ );
1587
+ return { skipped: false, ok, capture: postCaptureNorm, replay, state, rollback };
1588
+ };
1589
+ `,
1590
+ nativeFrames: 2,
1591
+ },
1592
+ {
1593
+ id: 'phaser-scene-lifecycle-timed-events-parity',
1594
+ modes: ['shim', 'native'],
1595
+ namespaces: ['scene'],
1596
+ functions: [
1597
+ 'aura.scene.define',
1598
+ 'aura.scene.register',
1599
+ 'aura.scene.start',
1600
+ 'aura.scene.switchTo',
1601
+ 'aura.scene.update',
1602
+ 'aura.scene.getState',
1603
+ 'aura.scene.schedule',
1604
+ 'aura.scene.pauseEvent',
1605
+ 'aura.scene.resumeEvent',
1606
+ 'aura.scene.cancelEvent',
1607
+ 'aura.scene.getEventState',
1608
+ ],
1609
+ nativeChecks: [
1610
+ {
1611
+ id: 'scene.surface.methods',
1612
+ expression: "(() => ['define','register','start','switchTo','update','getState','schedule','pauseEvent','resumeEvent','cancelEvent','getEventState'].every((name) => typeof aura.scene?.[name] === 'function'))()",
1613
+ },
1614
+ {
1615
+ id: 'scene.lifecycle.ordering.deterministic',
1616
+ expression: "(() => { const runSample = () => { const trace = []; aura.scene.define('scene-lifecycle-a', { preload: () => trace.push('a:preload'), create: () => trace.push('a:create'), update: () => { trace.push('a:update'); aura.scene.switchTo('scene-lifecycle-b'); }, shutdown: (_ctx, reason) => trace.push('a:shutdown:' + reason) }); aura.scene.register('scene-lifecycle-b', { preload: () => trace.push('b:preload'), create: () => trace.push('b:create'), update: () => trace.push('b:update'), shutdown: (_ctx, reason) => trace.push('b:shutdown:' + reason) }); const started = aura.scene.start('scene-lifecycle-a'); const stepA = aura.scene.update(1 / 60); const stepB = aura.scene.update(1 / 60); const state = aura.scene.getState(); return JSON.stringify({ started: !!started && started.ok === true, stepA: !!stepA && stepA.ok === true, stepB: !!stepB && stepB.ok === true, active: state ? state.activeScene : null, trace }); }; const expected = JSON.stringify({ started: true, stepA: true, stepB: true, active: 'scene-lifecycle-b', trace: ['a:preload', 'a:create', 'a:update', 'a:shutdown:switch', 'b:preload', 'b:create', 'b:update'] }); const first = runSample(); const second = runSample(); return first === expected && second === expected; })()",
1617
+ },
1618
+ {
1619
+ id: 'scene.timed-events.pause-resume-cancel.reason-codes',
1620
+ expression: "(() => { const eventTrace = []; const noActiveUpdate = aura.scene.update(1 / 60); const invalidSceneKey = aura.scene.start(''); const missingScene = aura.scene.start('missing-scene'); aura.scene.define('scene-events-main', {}); const started = aura.scene.start('scene-events-main'); const invalidDelay = aura.scene.schedule(0, () => {}); const invalidCallback = aura.scene.schedule(0.05, null); const invalidOptions = aura.scene.schedule(0.05, () => {}, 'bad'); const invalidRepeat = aura.scene.schedule(0.05, () => {}, { repeat: 'yes' }); const invalidInterval = aura.scene.schedule(0.05, () => {}, { repeat: true, interval: 0 }); const scheduled = aura.scene.schedule(0.05, (evt) => eventTrace.push('tick:' + evt.dispatchCount), { repeat: true, interval: 0.05 }); if (!scheduled || scheduled.ok !== true) return false; const paused = aura.scene.pauseEvent(scheduled.eventId); const pausedState = aura.scene.getEventState(scheduled.eventId); const pausedStep = aura.scene.update(1 / 60); const resumed = aura.scene.resumeEvent(scheduled.eventId); const stepA = aura.scene.update(1 / 60); const stepB = aura.scene.update(1 / 60); const stepC = aura.scene.update(1 / 60); const afterFire = aura.scene.getEventState(scheduled.eventId); const cancelled = aura.scene.cancelEvent(scheduled.eventId); const afterCancel = aura.scene.getEventState(scheduled.eventId); const invalidEventId = aura.scene.pauseEvent('bad'); const missingEvent = aura.scene.pauseEvent(999999); return noActiveUpdate && ((noActiveUpdate.ok === false && noActiveUpdate.reasonCode === 'no_active_scene') || (noActiveUpdate.ok === true && noActiveUpdate.reasonCode === 'scene_updated')) && invalidSceneKey && invalidSceneKey.ok === false && invalidSceneKey.reasonCode === 'invalid_scene_key' && missingScene && missingScene.ok === false && missingScene.reasonCode === 'missing_scene_definition' && started && started.ok === true && invalidDelay && invalidDelay.ok === false && invalidDelay.reasonCode === 'invalid_delay' && invalidCallback && invalidCallback.ok === false && invalidCallback.reasonCode === 'invalid_callback' && invalidOptions && invalidOptions.ok === false && invalidOptions.reasonCode === 'invalid_event_options' && invalidRepeat && invalidRepeat.ok === false && invalidRepeat.reasonCode === 'invalid_repeat_flag' && invalidInterval && invalidInterval.ok === false && invalidInterval.reasonCode === 'invalid_interval' && paused && paused.ok === true && paused.reasonCode === 'scene_event_paused' && pausedState && pausedState.paused === true && pausedState.dispatchCount === 0 && pausedStep && pausedStep.ok === true && pausedStep.firedEvents === 0 && resumed && resumed.ok === true && stepA && stepA.ok === true && stepB && stepB.ok === true && stepC && stepC.ok === true && eventTrace.join('|') === 'tick:1' && afterFire && afterFire.dispatchCount === 1 && cancelled && cancelled.ok === true && afterCancel === null && invalidEventId && invalidEventId.ok === false && invalidEventId.reasonCode === 'invalid_event_id' && missingEvent && missingEvent.ok === false && missingEvent.reasonCode === 'missing_event'; })()",
1621
+ debugExpression: "(() => { const eventTrace = []; const noActiveUpdate = aura.scene.update(1 / 60); const invalidSceneKey = aura.scene.start(''); const missingScene = aura.scene.start('missing-scene'); aura.scene.define('scene-events-main', {}); const started = aura.scene.start('scene-events-main'); const invalidDelay = aura.scene.schedule(0, () => {}); const invalidCallback = aura.scene.schedule(0.05, null); const invalidOptions = aura.scene.schedule(0.05, () => {}, 'bad'); const invalidRepeat = aura.scene.schedule(0.05, () => {}, { repeat: 'yes' }); const invalidInterval = aura.scene.schedule(0.05, () => {}, { repeat: true, interval: 0 }); const scheduled = aura.scene.schedule(0.05, (evt) => eventTrace.push('tick:' + evt.dispatchCount), { repeat: true, interval: 0.05 }); if (!scheduled || scheduled.ok !== true) { return { stage: 'schedule', scheduled }; } const paused = aura.scene.pauseEvent(scheduled.eventId); const pausedState = aura.scene.getEventState(scheduled.eventId); const pausedStep = aura.scene.update(1 / 60); const resumed = aura.scene.resumeEvent(scheduled.eventId); const stepA = aura.scene.update(1 / 60); const stepB = aura.scene.update(1 / 60); const stepC = aura.scene.update(1 / 60); const afterFire = aura.scene.getEventState(scheduled.eventId); const cancelled = aura.scene.cancelEvent(scheduled.eventId); const afterCancel = aura.scene.getEventState(scheduled.eventId); const invalidEventId = aura.scene.pauseEvent('bad'); const missingEvent = aura.scene.pauseEvent(999999); return { noActiveUpdate, invalidSceneKey, missingScene, started, invalidDelay, invalidCallback, invalidOptions, invalidRepeat, invalidInterval, scheduled, paused, pausedState, pausedStep, resumed, stepA, stepB, stepC, eventTrace, afterFire, cancelled, afterCancel, invalidEventId, missingEvent }; })()",
1622
+ },
1623
+ ],
1624
+ source: `
1625
+ const __sceneTrace = [];
1626
+ let __frame = 0;
1627
+ let __timerEventId = null;
1628
+ let __pausedSnapshot = null;
1629
+ let __invalidChecks = null;
1630
+
1631
+ aura.setup = function () {
1632
+ const noActiveUpdate = aura.scene.update(1 / 60);
1633
+ const invalidSceneKey = aura.scene.start('');
1634
+ const missingScene = aura.scene.start('missing-scene');
1635
+ const invalidDelay = aura.scene.schedule(0, () => {});
1636
+ const invalidCallback = aura.scene.schedule(0.05, null);
1637
+ const invalidOptions = aura.scene.schedule(0.05, () => {}, 'bad');
1638
+ const invalidEventId = aura.scene.pauseEvent('bad');
1639
+
1640
+ __invalidChecks = {
1641
+ noActiveUpdate,
1642
+ invalidSceneKey,
1643
+ missingScene,
1644
+ invalidDelay,
1645
+ invalidCallback,
1646
+ invalidOptions,
1647
+ invalidEventId,
1648
+ };
1649
+
1650
+ aura.scene.define('boot', {
1651
+ preload() {
1652
+ __sceneTrace.push('boot:preload');
1653
+ },
1654
+ create() {
1655
+ __sceneTrace.push('boot:create');
1656
+ const repeating = aura.scene.schedule(0.05, (evt) => {
1657
+ __sceneTrace.push('timer:tick:' + evt.dispatchCount);
1658
+ }, { repeat: true, interval: 0.05 });
1659
+ const cancelled = aura.scene.schedule(0.2, () => {
1660
+ __sceneTrace.push('timer:cancelled');
1661
+ });
1662
+ __timerEventId = repeating.eventId;
1663
+ aura.scene.pauseEvent(__timerEventId);
1664
+ __pausedSnapshot = aura.scene.getEventState(__timerEventId);
1665
+ aura.scene.cancelEvent(cancelled.eventId);
1666
+ },
1667
+ update() {
1668
+ __sceneTrace.push('boot:update');
1669
+ if (__frame === 0) {
1670
+ aura.scene.resumeEvent(__timerEventId);
1671
+ }
1672
+ if (__frame === 1) {
1673
+ aura.scene.switchTo('play');
1674
+ }
1675
+ },
1676
+ shutdown(_ctx, reason) {
1677
+ __sceneTrace.push('boot:shutdown:' + reason);
1678
+ },
1679
+ });
1680
+
1681
+ aura.scene.register('play', {
1682
+ preload() {
1683
+ __sceneTrace.push('play:preload');
1684
+ },
1685
+ create() {
1686
+ __sceneTrace.push('play:create');
1687
+ },
1688
+ update() {
1689
+ __sceneTrace.push('play:update');
1690
+ },
1691
+ shutdown(_ctx, reason) {
1692
+ __sceneTrace.push('play:shutdown:' + reason);
1693
+ },
1694
+ });
1695
+
1696
+ aura.scene.start('boot');
1697
+ };
1698
+
1699
+ aura.update = function (dt) {
1700
+ aura.scene.update(dt);
1701
+ __frame += 1;
1702
+ };
1703
+
1704
+ aura.draw = function () {
1705
+ if (__frame !== 4) return;
1706
+
1707
+ const expected = [
1708
+ 'boot:preload',
1709
+ 'boot:create',
1710
+ 'boot:update',
1711
+ 'boot:update',
1712
+ 'boot:shutdown:switch',
1713
+ 'play:preload',
1714
+ 'play:create',
1715
+ 'play:update',
1716
+ 'timer:tick:1',
1717
+ 'play:update',
1718
+ ];
1719
+
1720
+ aura.test.equal(
1721
+ __sceneTrace.join('|'),
1722
+ expected.join('|'),
1723
+ 'scene lifecycle and timed-event ordering should be deterministic',
1724
+ );
1725
+ aura.test.assert(
1726
+ __pausedSnapshot && __pausedSnapshot.paused === true && __pausedSnapshot.dispatchCount === 0,
1727
+ 'paused timed-event snapshot should be deterministic',
1728
+ );
1729
+
1730
+ const checks = __invalidChecks;
1731
+ aura.test.assert(
1732
+ checks.noActiveUpdate && checks.noActiveUpdate.ok === false && checks.noActiveUpdate.reasonCode === 'no_active_scene',
1733
+ 'scene.update without active scene should be reason-coded',
1734
+ );
1735
+ aura.test.assert(
1736
+ checks.invalidSceneKey && checks.invalidSceneKey.ok === false && checks.invalidSceneKey.reasonCode === 'invalid_scene_key',
1737
+ 'scene.start invalid key should be reason-coded',
1738
+ );
1739
+ aura.test.assert(
1740
+ checks.missingScene && checks.missingScene.ok === false && checks.missingScene.reasonCode === 'missing_scene_definition',
1741
+ 'scene.start missing scene should be reason-coded',
1742
+ );
1743
+ aura.test.assert(
1744
+ checks.invalidDelay && checks.invalidDelay.ok === false && checks.invalidDelay.reasonCode === 'invalid_delay',
1745
+ 'scene.schedule invalid delay should be reason-coded',
1746
+ );
1747
+ aura.test.assert(
1748
+ checks.invalidCallback && checks.invalidCallback.ok === false && checks.invalidCallback.reasonCode === 'invalid_callback',
1749
+ 'scene.schedule invalid callback should be reason-coded',
1750
+ );
1751
+ aura.test.assert(
1752
+ checks.invalidOptions && checks.invalidOptions.ok === false && checks.invalidOptions.reasonCode === 'invalid_event_options',
1753
+ 'scene.schedule invalid options should be reason-coded',
1754
+ );
1755
+ aura.test.assert(
1756
+ checks.invalidEventId && checks.invalidEventId.ok === false && checks.invalidEventId.reasonCode === 'invalid_event_id',
1757
+ 'scene.pauseEvent invalid event id should be reason-coded',
1758
+ );
1759
+
1760
+ const cancelled = aura.scene.cancelEvent(__timerEventId);
1761
+ aura.test.assert(
1762
+ cancelled && cancelled.ok === true && cancelled.reasonCode === 'scene_event_cancelled',
1763
+ 'scene timer cancel should succeed after deterministic tick',
1764
+ );
1765
+
1766
+ const state = aura.scene.getState();
1767
+ aura.test.equal(state.activeScene, 'play', 'scene active state should point at play');
1768
+ aura.test.assert(
1769
+ Array.isArray(state.timedEvents) && state.timedEvents.length === 0,
1770
+ 'scene timed-event list should be empty after cancellation',
1771
+ );
1772
+ };
1773
+ `,
1774
+ frames: 4,
1775
+ }
1776
+ ];