@auraindustry/aurajs 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +103 -7
  2. package/benchmarks/perf-thresholds.json +54 -0
  3. package/package.json +4 -7
  4. package/src/asset-pack.mjs +5 -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 +4825 -1512
  17. package/src/commands/project-authoring.mjs +434 -0
  18. package/src/config.mjs +27 -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 +438 -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 +40 -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 +14 -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 +443 -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 +60 -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 +1187 -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
package/src/cutscene.mjs CHANGED
@@ -34,11 +34,49 @@ function normalizeCues(cues) {
34
34
  function collectCueIdsUpTo(cues, time) {
35
35
  const ids = new Set();
36
36
  for (const cue of cues) {
37
- if (cue.time <= time) ids.add(cue.id);
37
+ if (cue.time > time) break;
38
+ ids.add(cue.id);
38
39
  }
39
40
  return ids;
40
41
  }
41
42
 
43
+ function secondsToMilliseconds(value, fallback = 0) {
44
+ const numeric = Number(value);
45
+ return Number.isFinite(numeric) ? numeric * 1000 : fallback;
46
+ }
47
+
48
+ function normalizeProgress(currentTime, duration) {
49
+ const current = Number(currentTime);
50
+ const total = Number(duration);
51
+ if (!Number.isFinite(current) || !Number.isFinite(total) || total <= 0) return 0;
52
+ return Math.max(0, Math.min(1, current / total));
53
+ }
54
+
55
+ function findLatestCueAtOrBefore(cues, time, type = null) {
56
+ let match = null;
57
+ for (const cue of cues) {
58
+ if (cue.time > time) break;
59
+ if (type === null || cue.type === type) {
60
+ match = cue;
61
+ }
62
+ }
63
+ return match;
64
+ }
65
+
66
+ function findNextCue(cues, time) {
67
+ for (const cue of cues) {
68
+ if (cue.time >= time) return cue;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function listCueIdsInOrder(cues, firedCueIds) {
74
+ if (!(firedCueIds instanceof Set) || firedCueIds.size === 0) return [];
75
+ return cues
76
+ .filter((cue) => firedCueIds.has(cue.id))
77
+ .map((cue) => cue.id);
78
+ }
79
+
42
80
  export class VideoCutsceneController {
43
81
  constructor(videoApi, handle, options = {}) {
44
82
  if (!videoApi || typeof videoApi !== 'object') {
@@ -77,16 +115,32 @@ export class VideoCutsceneController {
77
115
 
78
116
  getState() {
79
117
  const info = this.getInfo();
80
- const currentTime = info ? asFiniteNumber(info.currentTime || info.currentTimeSecs, 0) : 0;
118
+ const currentTime = info ? asFiniteNumber(info.currentTime || info.currentTimeSecs, this.lastTime) : this.lastTime;
81
119
  const duration = info ? asFiniteNumber(info.duration || info.durationSecs, 0) : 0;
82
- const currentCue = this.options.cues.find((cue) => cue.time >= currentTime) || null;
120
+ const currentTimeMs = info ? asFiniteNumber(info.currentTimeMs, secondsToMilliseconds(currentTime)) : secondsToMilliseconds(currentTime);
121
+ const durationMs = info ? asFiniteNumber(info.durationMs, secondsToMilliseconds(duration)) : secondsToMilliseconds(duration);
122
+ const firedCueIds = listCueIdsInOrder(this.options.cues, this.firedCueIds);
123
+ const nextCue = findNextCue(this.options.cues, currentTime);
124
+ const activeSubtitle = findLatestCueAtOrBefore(this.options.cues, currentTime, 'subtitle');
125
+ const lastCheckpoint = findLatestCueAtOrBefore(this.options.cues, currentTime, 'checkpoint');
126
+ const playbackProgress = normalizeProgress(currentTime, duration);
83
127
  return {
84
128
  handle: this.handle,
85
129
  info,
86
130
  currentTime,
131
+ currentTimeMs,
87
132
  duration,
88
- firedCueCount: this.firedCueIds.size,
89
- nextCue: currentCue,
133
+ durationMs,
134
+ playbackProgress,
135
+ cueCount: this.options.cues.length,
136
+ firedCueCount: firedCueIds.length,
137
+ remainingCueCount: Math.max(0, this.options.cues.length - firedCueIds.length),
138
+ firedCueIds,
139
+ nextCue,
140
+ activeSubtitle,
141
+ lastCheckpoint,
142
+ textureReady: info?.textureReady === true,
143
+ nativeOnly: info?.nativeOnly === true || info?.sourceKind === 'mp4',
90
144
  };
91
145
  }
92
146
 
@@ -202,4 +256,307 @@ export function createVideoCutsceneController(videoApi, handle, options = {}) {
202
256
  return new VideoCutsceneController(videoApi, handle, options);
203
257
  }
204
258
 
259
+ function requireRuntimeFactory(target, method, label) {
260
+ if (!target || typeof target !== 'object' || typeof target[method] !== 'function') {
261
+ throw new TypeError(`${label} requires ${method}() on the provided runtime API object.`);
262
+ }
263
+ }
264
+
265
+ export function createSpatialAudioEmitter(audioApi, path, options = {}) {
266
+ requireRuntimeFactory(audioApi, 'createSpatialEmitter', 'createSpatialAudioEmitter(audioApi, path, options)');
267
+ return audioApi.createSpatialEmitter(path, options);
268
+ }
269
+
270
+ export function createVideoBillboardSurface(videoApi, source, options = {}) {
271
+ requireRuntimeFactory(videoApi, 'createBillboardSurface', 'createVideoBillboardSurface(videoApi, source, options)');
272
+ return videoApi.createBillboardSurface(source, options);
273
+ }
274
+
275
+ function normalizeAudioSeekMode(value) {
276
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
277
+ switch (normalized) {
278
+ case 'none':
279
+ case 'restart':
280
+ case 'presentation-default':
281
+ return normalized;
282
+ default:
283
+ return 'presentation-default';
284
+ }
285
+ }
286
+
287
+ function safePresentationState(presentation, fallback = null) {
288
+ if (!presentation || typeof presentation.getState !== 'function') return fallback;
289
+ const state = presentation.getState();
290
+ return state && typeof state === 'object' ? state : fallback;
291
+ }
292
+
293
+ function summarizePresentationState(state) {
294
+ const presentationState = state && typeof state === 'object' ? state : null;
295
+ const surface = presentationState?.surface && typeof presentationState.surface === 'object'
296
+ ? presentationState.surface
297
+ : null;
298
+ const emitter = presentationState?.emitter && typeof presentationState.emitter === 'object'
299
+ ? presentationState.emitter
300
+ : null;
301
+ const info = surface?.info && typeof surface.info === 'object' ? surface.info : null;
302
+
303
+ return {
304
+ targetAttached: presentationState?.targetAttached === true,
305
+ hasAudio: !!emitter,
306
+ audioAttached: emitter?.attached === true,
307
+ handle: Number.isInteger(surface?.handle) && surface.handle > 0 ? surface.handle : 0,
308
+ textureHandle: Number.isInteger(surface?.textureHandle) && surface.textureHandle > 0 ? surface.textureHandle : 0,
309
+ playbackState: typeof info?.state === 'string' ? info.state : null,
310
+ currentTime: asFiniteNumber(info?.currentTime || info?.currentTimeSecs, 0),
311
+ currentTimeMs: asFiniteNumber(info?.currentTimeMs, secondsToMilliseconds(info?.currentTime || info?.currentTimeSecs, 0)),
312
+ duration: asFiniteNumber(info?.duration || info?.durationSecs, 0),
313
+ durationMs: asFiniteNumber(info?.durationMs, secondsToMilliseconds(info?.duration || info?.durationSecs, 0)),
314
+ playbackProgress: normalizeProgress(info?.currentTime || info?.currentTimeSecs, info?.duration || info?.durationSecs),
315
+ sourceKind: typeof info?.sourceKind === 'string' ? info.sourceKind : null,
316
+ textureReady: typeof info?.textureReady === 'boolean'
317
+ ? info.textureReady
318
+ : (Number.isInteger(surface?.textureHandle) && surface.textureHandle > 0),
319
+ nativeOnly: typeof info?.nativeOnly === 'boolean'
320
+ ? info.nativeOnly
321
+ : info?.sourceKind === 'mp4',
322
+ };
323
+ }
324
+
325
+ export function createMediaPresentationController(runtime, source, options = {}) {
326
+ if (!runtime || typeof runtime !== 'object') {
327
+ throw new TypeError('createMediaPresentationController(runtime, source, options) requires a runtime object.');
328
+ }
329
+ requireRuntimeFactory(runtime.video, 'createPresentation', 'createMediaPresentationController(runtime, source, options)');
330
+
331
+ const presentation = runtime.video.createPresentation(source, options);
332
+ const audioSeekMode = normalizeAudioSeekMode(options?.audioSeekMode);
333
+ const usesCutsceneCues = !!(
334
+ options
335
+ && typeof options === 'object'
336
+ && (
337
+ Array.isArray(options.cues)
338
+ || typeof options.onCue === 'function'
339
+ || typeof options.onSubtitle === 'function'
340
+ || typeof options.onCheckpoint === 'function'
341
+ || typeof options.onStateChange === 'function'
342
+ )
343
+ );
344
+ let cutscene = null;
345
+ let unloaded = false;
346
+ let lastAction = 'idle';
347
+ let lastResult = null;
348
+ let lastAudioResync = null;
349
+ let lastPresentationState = safePresentationState(presentation);
350
+
351
+ function ensureCutsceneController() {
352
+ if (!usesCutsceneCues) return null;
353
+ if (cutscene) return cutscene;
354
+ if (!runtime.video || typeof runtime.video.getInfo !== 'function') return null;
355
+ const state = safePresentationState(presentation, lastPresentationState);
356
+ const handle = Number(state?.surface?.handle || 0);
357
+ if (!Number.isInteger(handle) || handle <= 0) return null;
358
+ cutscene = createVideoCutsceneController(runtime.video, handle, options);
359
+ return cutscene;
360
+ }
361
+
362
+ function syncCutsceneAfterExternalSeek(controller, targetTime) {
363
+ const normalizedTarget = Number(targetTime);
364
+ if (!Number.isFinite(normalizedTarget) || normalizedTarget < 0) {
365
+ return controller ? controller.sync() : null;
366
+ }
367
+ controller.firedCueIds = collectCueIdsUpTo(controller.options.cues, normalizedTarget);
368
+ controller.lastTime = normalizedTarget;
369
+ return controller.sync();
370
+ }
371
+
372
+ function capturePresentationState() {
373
+ lastPresentationState = safePresentationState(presentation, lastPresentationState);
374
+ return lastPresentationState;
375
+ }
376
+
377
+ function recordAction(action, result, audioResync = null) {
378
+ lastAction = action;
379
+ lastResult = result ?? null;
380
+ if (audioResync !== null) {
381
+ lastAudioResync = audioResync;
382
+ }
383
+ capturePresentationState();
384
+ return result;
385
+ }
386
+
387
+ function requestAudioResync(reason, timeSecs) {
388
+ if (audioSeekMode === 'none') {
389
+ lastAudioResync = { ok: false, reasonCode: 'audio_seek_mode_disabled', reason, timeSecs };
390
+ return lastAudioResync;
391
+ }
392
+ if (audioSeekMode === 'restart' && typeof presentation.restartAudio === 'function') {
393
+ const result = presentation.restartAudio({ reason, timeSecs });
394
+ lastAudioResync = result ?? { ok: true, reasonCode: null, reason, timeSecs };
395
+ return lastAudioResync;
396
+ }
397
+ if (typeof presentation.resyncAudio === 'function') {
398
+ const result = presentation.resyncAudio({ reason, timeSecs, mode: audioSeekMode });
399
+ lastAudioResync = result ?? { ok: true, reasonCode: null, reason, timeSecs };
400
+ return lastAudioResync;
401
+ }
402
+ lastAudioResync = { ok: false, reasonCode: 'audio_resync_unavailable', reason, timeSecs };
403
+ return lastAudioResync;
404
+ }
405
+
406
+ function currentState() {
407
+ const presentationState = capturePresentationState();
408
+ const media = summarizePresentationState(presentationState);
409
+ const cutsceneState = cutscene ? cutscene.getState() : null;
410
+ const emitterState = presentationState?.emitter || null;
411
+ return {
412
+ unloaded,
413
+ source,
414
+ audioSeekMode,
415
+ lastAction,
416
+ lastResult,
417
+ lastAudioResync,
418
+ media,
419
+ surface: presentationState?.surface || null,
420
+ emitter: emitterState,
421
+ presentation: presentationState,
422
+ cutscene: cutsceneState,
423
+ observability: {
424
+ nativeOnly: media.nativeOnly,
425
+ textureReady: media.textureReady,
426
+ sourceKind: media.sourceKind,
427
+ currentTimeMs: media.currentTimeMs,
428
+ durationMs: media.durationMs,
429
+ playbackProgress: media.playbackProgress,
430
+ audioSeekMode,
431
+ spatialAudioAttached: emitterState?.attached === true,
432
+ spatialAudioReasonCode: typeof lastAudioResync?.reasonCode === 'string' && lastAudioResync.reasonCode.length > 0
433
+ ? lastAudioResync.reasonCode
434
+ : (typeof emitterState?.lastReasonCode === 'string' ? emitterState.lastReasonCode : null),
435
+ cueCoverage: {
436
+ total: cutsceneState?.cueCount || 0,
437
+ fired: cutsceneState?.firedCueCount || 0,
438
+ remaining: cutsceneState?.remainingCueCount || 0,
439
+ },
440
+ activeSubtitle: cutsceneState?.activeSubtitle || null,
441
+ lastCheckpoint: cutsceneState?.lastCheckpoint || null,
442
+ portability: {
443
+ browserParity: 'out_of_scope',
444
+ muxedAudioGuarantees: 'out_of_scope',
445
+ cinematicEditor: 'out_of_scope',
446
+ },
447
+ },
448
+ };
449
+ }
450
+
451
+ return {
452
+ play() {
453
+ unloaded = false;
454
+ const result = presentation.play();
455
+ const controller = ensureCutsceneController();
456
+ if (controller) controller.sync();
457
+ return recordAction('play', result);
458
+ },
459
+ pause() {
460
+ const result = presentation.pause();
461
+ if (cutscene) cutscene.sync();
462
+ return recordAction('pause', result);
463
+ },
464
+ resume() {
465
+ unloaded = false;
466
+ const result = presentation.resume();
467
+ const controller = ensureCutsceneController();
468
+ if (controller) controller.sync();
469
+ return recordAction('resume', result);
470
+ },
471
+ stop() {
472
+ const result = presentation.stop();
473
+ if (cutscene) {
474
+ cutscene.firedCueIds.clear();
475
+ cutscene.lastInfo = null;
476
+ cutscene.lastTime = 0;
477
+ cutscene.lastState = 'stopped';
478
+ cutscene.sync();
479
+ }
480
+ return recordAction('stop', result);
481
+ },
482
+ attach(target, attachOptions = {}) {
483
+ return recordAction('attach', presentation.attach(target, attachOptions));
484
+ },
485
+ update() {
486
+ const result = presentation.update();
487
+ const controller = ensureCutsceneController();
488
+ if (controller) controller.update();
489
+ return recordAction('update', result);
490
+ },
491
+ draw(drawOptions = {}) {
492
+ return recordAction('draw', presentation.draw(drawOptions));
493
+ },
494
+ seek(timeSecs) {
495
+ const result = presentation.seek(timeSecs);
496
+ const controller = ensureCutsceneController();
497
+ if (controller) syncCutsceneAfterExternalSeek(controller, timeSecs);
498
+ return recordAction('seek', result, requestAudioResync('seek', Number(timeSecs)));
499
+ },
500
+ skip(targetTime = null) {
501
+ const controller = ensureCutsceneController();
502
+ if (controller) {
503
+ const info = controller.getInfo();
504
+ const fallbackDuration = info ? asFiniteNumber(info.duration || info.durationSecs, 0) : 0;
505
+ const resolvedTarget = targetTime != null && Number.isFinite(Number(targetTime))
506
+ ? Math.max(0, Number(targetTime))
507
+ : (controller.options.skipTarget != null ? controller.options.skipTarget : fallbackDuration);
508
+ if (presentation && typeof presentation.seek === 'function') {
509
+ const result = presentation.seek(resolvedTarget);
510
+ const synced = syncCutsceneAfterExternalSeek(controller, resolvedTarget);
511
+ recordAction('skip', result ?? synced, requestAudioResync('skip', resolvedTarget));
512
+ }
513
+ return currentState();
514
+ }
515
+ const resolvedTarget = targetTime == null ? 0 : targetTime;
516
+ return recordAction('skip', presentation.seek(resolvedTarget), requestAudioResync('skip', Number(resolvedTarget)));
517
+ },
518
+ exportCheckpointState() {
519
+ return {
520
+ source,
521
+ audioSeekMode,
522
+ presentation: currentState().media,
523
+ cutscene: cutscene ? cutscene.exportCheckpointState() : null,
524
+ };
525
+ },
526
+ restoreCheckpointState(snapshot) {
527
+ if (!snapshot || typeof snapshot !== 'object') {
528
+ throw new TypeError('createMediaPresentationController(...).restoreCheckpointState(snapshot) requires an object snapshot.');
529
+ }
530
+ unloaded = false;
531
+ const cutsceneSnapshot = snapshot.cutscene && typeof snapshot.cutscene === 'object'
532
+ ? snapshot.cutscene
533
+ : snapshot;
534
+ const targetTime = cutsceneSnapshot && Number.isFinite(Number(cutsceneSnapshot.currentTime))
535
+ ? Math.max(0, Number(cutsceneSnapshot.currentTime))
536
+ : asFiniteNumber(snapshot.presentation?.currentTime, 0);
537
+ let result = null;
538
+ const controller = ensureCutsceneController();
539
+ if (controller && cutsceneSnapshot && Number.isFinite(Number(cutsceneSnapshot.currentTime))) {
540
+ result = controller.restoreCheckpointState(cutsceneSnapshot);
541
+ } else if (typeof presentation.seek === 'function') {
542
+ result = presentation.seek(targetTime);
543
+ }
544
+ return recordAction(
545
+ 'restoreCheckpointState',
546
+ result,
547
+ requestAudioResync('restore-checkpoint', targetTime),
548
+ );
549
+ },
550
+ unload() {
551
+ const result = presentation.unload();
552
+ unloaded = true;
553
+ cutscene = null;
554
+ return recordAction('unload', result);
555
+ },
556
+ getState() {
557
+ return currentState();
558
+ },
559
+ };
560
+ }
561
+
205
562
  export default createVideoCutsceneController;
@@ -0,0 +1,249 @@
1
+ import {
2
+ GAME_ACTION_REQUEST_SCHEMA_VERSION,
3
+ GAME_ACTION_RESULT_SCHEMA_VERSION,
4
+ GAME_ACTION_SCHEMA_VERSION,
5
+ } from './game-action-runtime.mjs';
6
+ import { GAME_STATE_SCHEMA_VERSION } from './game-state-runtime.mjs';
7
+
8
+ export class DevCliActionError extends Error {
9
+ constructor(reasonCode, message, details = {}) {
10
+ super(message);
11
+ this.name = 'DevCliActionError';
12
+ this.reasonCode = reasonCode;
13
+ this.details = details;
14
+ }
15
+ }
16
+
17
+ export async function runActionSchemaFlow({
18
+ parsed,
19
+ projectRoot,
20
+ loadConfig,
21
+ runHeadlessActionSchema,
22
+ }) {
23
+ const config = await loadConfig({ projectRoot, mode: 'build' });
24
+ const file = parsed.file || config.build.entry;
25
+
26
+ const schemaRun = await runHeadlessActionSchema({
27
+ projectRoot,
28
+ file,
29
+ });
30
+
31
+ if (!schemaRun || typeof schemaRun !== 'object') {
32
+ throw new DevCliActionError(
33
+ 'action_runtime_failed',
34
+ 'Action schema runtime returned an invalid result.',
35
+ );
36
+ }
37
+
38
+ const schemaResult = schemaRun.schemaResult;
39
+ if (!schemaResult || typeof schemaResult !== 'object') {
40
+ throw new DevCliActionError(
41
+ 'action_runtime_failed',
42
+ 'Action schema runtime returned an invalid schema result.',
43
+ { schemaRun },
44
+ );
45
+ }
46
+ if (schemaResult.ok !== true) {
47
+ throw new DevCliActionError(
48
+ typeof schemaResult.reasonCode === 'string' ? schemaResult.reasonCode : 'action_contract_unavailable',
49
+ schemaResult.detail
50
+ ? `Action schema export failed: ${schemaResult.detail}`
51
+ : 'Action schema export failed.',
52
+ { schemaRun, schemaResult },
53
+ );
54
+ }
55
+ if (!schemaResult.payload || typeof schemaResult.payload !== 'object') {
56
+ throw new DevCliActionError(
57
+ 'action_schema_invalid_payload',
58
+ 'Action schema runtime returned an invalid schema payload.',
59
+ { schemaRun, schemaResult },
60
+ );
61
+ }
62
+ if (schemaResult.payload.schemaVersion !== parsed.schemaVersion) {
63
+ throw new DevCliActionError(
64
+ 'schema_version_mismatch',
65
+ `Action schema "${schemaResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
66
+ { exported: schemaResult.payload.schemaVersion, requested: parsed.schemaVersion },
67
+ );
68
+ }
69
+
70
+ return {
71
+ file,
72
+ payload: schemaResult.payload,
73
+ schemaRun,
74
+ };
75
+ }
76
+
77
+ export async function runActionRunFlow({
78
+ parsed,
79
+ projectRoot,
80
+ loadConfig,
81
+ readJsonInputFile,
82
+ runHeadlessActionRun,
83
+ }) {
84
+ const config = await loadConfig({ projectRoot, mode: 'build' });
85
+ const file = parsed.file || config.build.entry;
86
+ const request = readJsonInputFile(parsed.requestPath, 'action request payload', 'action_request_input');
87
+ const statePayload = parsed.statePath
88
+ ? readJsonInputFile(parsed.statePath, 'action seed state payload', 'action_seed_state_input')
89
+ : null;
90
+
91
+ if (!request || typeof request !== 'object' || Array.isArray(request)) {
92
+ throw new DevCliActionError(
93
+ 'invalid_action_request',
94
+ 'Action request payload must be a JSON object.',
95
+ );
96
+ }
97
+ if (request.schemaVersion !== GAME_ACTION_REQUEST_SCHEMA_VERSION) {
98
+ throw new DevCliActionError(
99
+ 'schema_version_mismatch',
100
+ `Unsupported action request schema "${request.schemaVersion}". Expected "${GAME_ACTION_REQUEST_SCHEMA_VERSION}".`,
101
+ { exported: request.schemaVersion, requested: GAME_ACTION_REQUEST_SCHEMA_VERSION },
102
+ );
103
+ }
104
+ if (statePayload != null) {
105
+ validateSeedStatePayload(statePayload);
106
+ }
107
+
108
+ const actionRun = await runHeadlessActionRun({
109
+ projectRoot,
110
+ file,
111
+ request,
112
+ statePayload,
113
+ frames: parsed.frames,
114
+ mode: parsed.mode,
115
+ includeState: parsed.includeState,
116
+ frameIndex: parsed.frames,
117
+ });
118
+
119
+ if (!actionRun || typeof actionRun !== 'object') {
120
+ throw new DevCliActionError(
121
+ 'action_runtime_failed',
122
+ 'Action run runtime returned an invalid result.',
123
+ );
124
+ }
125
+ if (actionRun.seedStateApplyResult && actionRun.seedStateApplyResult.ok !== true) {
126
+ throw new DevCliActionError(
127
+ typeof actionRun.seedStateApplyResult.reasonCode === 'string'
128
+ ? actionRun.seedStateApplyResult.reasonCode
129
+ : 'state_apply_failed',
130
+ actionRun.seedStateApplyResult.detail
131
+ ? `Action seed state apply failed: ${actionRun.seedStateApplyResult.detail}`
132
+ : 'Action seed state apply failed.',
133
+ {
134
+ actionRun,
135
+ seedStateApplyResult: actionRun.seedStateApplyResult,
136
+ },
137
+ );
138
+ }
139
+
140
+ const actionResult = actionRun.actionResult;
141
+ if (!actionResult || typeof actionResult !== 'object') {
142
+ throw new DevCliActionError(
143
+ 'action_runtime_failed',
144
+ 'Action run runtime returned an invalid action result.',
145
+ { actionRun },
146
+ );
147
+ }
148
+ if (actionResult.schemaVersion !== parsed.schemaVersion) {
149
+ throw new DevCliActionError(
150
+ 'schema_version_mismatch',
151
+ `Action result schema "${actionResult.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
152
+ { exported: actionResult.schemaVersion, requested: parsed.schemaVersion },
153
+ );
154
+ }
155
+ if (actionResult.ok !== true) {
156
+ throw new DevCliActionError(
157
+ typeof actionResult.reasonCode === 'string' ? actionResult.reasonCode : 'action_run_failed',
158
+ actionResult.detail
159
+ ? `Action run failed: ${actionResult.detail}`
160
+ : 'Action run failed.',
161
+ { actionRun, actionResult },
162
+ );
163
+ }
164
+
165
+ const report = orderActionRunReport({
166
+ ...actionResult,
167
+ ...(actionRun.stateResult?.payload
168
+ ? {
169
+ followUpFrames: parsed.frames,
170
+ state: validateExportedStatePayload(actionRun.stateResult.payload),
171
+ }
172
+ : {}),
173
+ });
174
+
175
+ return {
176
+ file,
177
+ report,
178
+ actionRun,
179
+ };
180
+ }
181
+
182
+ export function serializeActionPayload(payload, compact = false) {
183
+ return compact
184
+ ? `${JSON.stringify(payload)}\n`
185
+ : `${JSON.stringify(payload, null, 2)}\n`;
186
+ }
187
+
188
+ function validateExportedStatePayload(payload) {
189
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
190
+ throw new DevCliActionError(
191
+ 'action_runtime_failed',
192
+ 'Action follow-up state export returned an invalid payload.',
193
+ { payload },
194
+ );
195
+ }
196
+ if (payload.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
197
+ throw new DevCliActionError(
198
+ 'schema_version_mismatch',
199
+ `Action follow-up state schema "${payload.schemaVersion}" does not match expected schema "${GAME_STATE_SCHEMA_VERSION}".`,
200
+ { payload },
201
+ );
202
+ }
203
+ return payload;
204
+ }
205
+
206
+ function validateSeedStatePayload(payload) {
207
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
208
+ throw new DevCliActionError(
209
+ 'invalid_schema_payload',
210
+ 'Action seed state payload must be a JSON object.',
211
+ { payload },
212
+ );
213
+ }
214
+ if (payload.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
215
+ throw new DevCliActionError(
216
+ 'schema_version_mismatch',
217
+ `Action seed state schema "${payload.schemaVersion}" does not match expected schema "${GAME_STATE_SCHEMA_VERSION}".`,
218
+ { payload },
219
+ );
220
+ }
221
+ return payload;
222
+ }
223
+
224
+ function orderActionRunReport(payload) {
225
+ const ordered = {};
226
+ const keyOrder = [
227
+ 'schemaVersion',
228
+ 'ok',
229
+ 'reasonCode',
230
+ 'actionId',
231
+ 'detail',
232
+ 'output',
233
+ 'followUpFrames',
234
+ 'state',
235
+ ];
236
+
237
+ for (const key of keyOrder) {
238
+ if (Object.prototype.hasOwnProperty.call(payload, key)) {
239
+ ordered[key] = payload[key];
240
+ }
241
+ }
242
+
243
+ return ordered;
244
+ }
245
+
246
+ export {
247
+ GAME_ACTION_SCHEMA_VERSION,
248
+ GAME_ACTION_RESULT_SCHEMA_VERSION,
249
+ };