@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
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import {
2
3
  existsSync,
3
4
  mkdirSync,
@@ -8,13 +9,28 @@ import {
8
9
  } from 'node:fs';
9
10
  import { dirname, join, resolve } from 'node:path';
10
11
 
12
+ import { GAME_STATE_SCHEMA_VERSION } from './game-state-runtime.mjs';
13
+
11
14
  export const STATE_ARTIFACT_SCHEMA_VERSION = 'aurajs.state-artifact.v1';
12
15
  export const STATE_ARTIFACT_KINDS = Object.freeze(['slot', 'checkpoint']);
16
+ export const STATE_ARTIFACT_VERSION = 2;
13
17
 
14
18
  const STATE_ARTIFACT_DIRECTORY_NAMES = Object.freeze({
15
19
  slot: 'slots',
16
20
  checkpoint: 'checkpoints',
17
21
  });
22
+ const LEGACY_STATE_ARTIFACT_VERSION = 1;
23
+ const LEGACY_GAME_STATE_SCHEMA_VERSION = 'aurajs.game-state.v0';
24
+ const STATE_SECTION_NAMES = Object.freeze([
25
+ 'globals',
26
+ 'camera',
27
+ 'project',
28
+ 'scene3d',
29
+ 'physics',
30
+ 'ecs',
31
+ 'tilemap',
32
+ ]);
33
+ const DEFAULT_STATE_ARTIFACT_MIGRATION_REASON_CODE = 'state_artifact_migrated';
18
34
 
19
35
  export class StateArtifactError extends Error {
20
36
  constructor(reasonCode, message, details = {}) {
@@ -25,6 +41,525 @@ export class StateArtifactError extends Error {
25
41
  }
26
42
  }
27
43
 
44
+ function isPlainObject(value) {
45
+ return !!value && typeof value === 'object' && !Array.isArray(value);
46
+ }
47
+
48
+ function parsePositiveInteger(value) {
49
+ if (Number.isInteger(value) && value > 0) {
50
+ return value;
51
+ }
52
+ if (typeof value === 'string' && /^[0-9]+$/.test(value.trim())) {
53
+ const parsed = Number.parseInt(value.trim(), 10);
54
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function sanitizeJsonValue(value) {
60
+ if (value === null) return null;
61
+ if (typeof value === 'string' || typeof value === 'boolean') return value;
62
+ if (typeof value === 'number') {
63
+ return Number.isFinite(value) ? Number(value) : null;
64
+ }
65
+ if (Array.isArray(value)) {
66
+ return value.map((entry) => sanitizeJsonValue(entry));
67
+ }
68
+ if (isPlainObject(value)) {
69
+ const out = {};
70
+ for (const key of Object.keys(value).sort()) {
71
+ const next = sanitizeJsonValue(value[key]);
72
+ if (next === undefined) continue;
73
+ out[key] = next;
74
+ }
75
+ return out;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ function sanitizeJsonObject(value) {
81
+ return isPlainObject(value) ? sanitizeJsonValue(value) : {};
82
+ }
83
+
84
+ function stableStringify(value) {
85
+ if (value === null || typeof value !== 'object') {
86
+ return JSON.stringify(value);
87
+ }
88
+ if (Array.isArray(value)) {
89
+ return `[${value.map((entry) => stableStringify(entry)).join(',')}]`;
90
+ }
91
+ const keys = Object.keys(value).sort();
92
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
93
+ }
94
+
95
+ function sha256(value) {
96
+ return createHash('sha256').update(String(value)).digest('hex');
97
+ }
98
+
99
+ function resolveEnvelopeStateSchemaVersion(envelope) {
100
+ if (typeof envelope?.stateSchemaVersion === 'string' && envelope.stateSchemaVersion.trim().length > 0) {
101
+ return envelope.stateSchemaVersion.trim();
102
+ }
103
+ if (typeof envelope?.payload?.schemaVersion === 'string' && envelope.payload.schemaVersion.trim().length > 0) {
104
+ return envelope.payload.schemaVersion.trim();
105
+ }
106
+ return null;
107
+ }
108
+
109
+ function normalizeMigrationSteps(steps) {
110
+ if (!Array.isArray(steps)) return [];
111
+ const seen = new Set();
112
+ const output = [];
113
+ for (const step of steps) {
114
+ if (typeof step !== 'string') continue;
115
+ const normalized = step.trim();
116
+ if (normalized.length === 0 || seen.has(normalized)) continue;
117
+ seen.add(normalized);
118
+ output.push(normalized);
119
+ }
120
+ return output;
121
+ }
122
+
123
+ function normalizeStateArtifactMigrationMetadata(
124
+ migration,
125
+ {
126
+ artifactVersion = STATE_ARTIFACT_VERSION,
127
+ stateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
128
+ } = {},
129
+ ) {
130
+ const normalizedArtifactVersion = parsePositiveInteger(artifactVersion) || STATE_ARTIFACT_VERSION;
131
+ const normalizedStateSchemaVersion = typeof stateSchemaVersion === 'string' && stateSchemaVersion.trim().length > 0
132
+ ? stateSchemaVersion.trim()
133
+ : GAME_STATE_SCHEMA_VERSION;
134
+ const input = isPlainObject(migration) ? migration : {};
135
+ const sourceArtifactVersion = parsePositiveInteger(input.sourceArtifactVersion) || normalizedArtifactVersion;
136
+ const sourceStateSchemaVersion = typeof input.sourceStateSchemaVersion === 'string'
137
+ && input.sourceStateSchemaVersion.trim().length > 0
138
+ ? input.sourceStateSchemaVersion.trim()
139
+ : normalizedStateSchemaVersion;
140
+ const steps = normalizeMigrationSteps(input.steps);
141
+ const applied = input.applied === true
142
+ || steps.length > 0
143
+ || sourceArtifactVersion !== normalizedArtifactVersion
144
+ || sourceStateSchemaVersion !== normalizedStateSchemaVersion;
145
+ return {
146
+ applied,
147
+ sourceArtifactVersion,
148
+ targetArtifactVersion: normalizedArtifactVersion,
149
+ sourceStateSchemaVersion,
150
+ targetStateSchemaVersion: normalizedStateSchemaVersion,
151
+ steps,
152
+ reasonCode: typeof input.reasonCode === 'string' && input.reasonCode.trim().length > 0
153
+ ? input.reasonCode.trim()
154
+ : (applied ? DEFAULT_STATE_ARTIFACT_MIGRATION_REASON_CODE : null),
155
+ };
156
+ }
157
+
158
+ function mergeStateArtifactMigrationMetadata(
159
+ base,
160
+ delta,
161
+ {
162
+ artifactVersion = STATE_ARTIFACT_VERSION,
163
+ stateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
164
+ } = {},
165
+ ) {
166
+ const targetArtifactVersion = parsePositiveInteger(artifactVersion) || STATE_ARTIFACT_VERSION;
167
+ const targetStateSchemaVersion = typeof stateSchemaVersion === 'string' && stateSchemaVersion.trim().length > 0
168
+ ? stateSchemaVersion.trim()
169
+ : GAME_STATE_SCHEMA_VERSION;
170
+ const normalizedBase = normalizeStateArtifactMigrationMetadata(base, {
171
+ artifactVersion: targetArtifactVersion,
172
+ stateSchemaVersion: targetStateSchemaVersion,
173
+ });
174
+ const normalizedDelta = normalizeStateArtifactMigrationMetadata(delta, {
175
+ artifactVersion: targetArtifactVersion,
176
+ stateSchemaVersion: targetStateSchemaVersion,
177
+ });
178
+ const sourceArtifactVersion = Math.min(
179
+ normalizedBase.sourceArtifactVersion,
180
+ normalizedDelta.sourceArtifactVersion,
181
+ );
182
+ const sourceStateSchemaVersion = normalizedBase.applied
183
+ ? normalizedBase.sourceStateSchemaVersion
184
+ : normalizedDelta.sourceStateSchemaVersion;
185
+ const steps = normalizeMigrationSteps([
186
+ ...normalizedBase.steps,
187
+ ...normalizedDelta.steps,
188
+ ]);
189
+ const applied = normalizedBase.applied || normalizedDelta.applied || steps.length > 0;
190
+ return {
191
+ applied,
192
+ sourceArtifactVersion,
193
+ targetArtifactVersion,
194
+ sourceStateSchemaVersion,
195
+ targetStateSchemaVersion,
196
+ steps,
197
+ reasonCode: normalizedDelta.reasonCode
198
+ || normalizedBase.reasonCode
199
+ || (applied ? DEFAULT_STATE_ARTIFACT_MIGRATION_REASON_CODE : null),
200
+ };
201
+ }
202
+
203
+ function normalizeMigrationHookMap(input, defaults = new Map()) {
204
+ const map = new Map(defaults);
205
+ if (input instanceof Map) {
206
+ for (const [key, handler] of input.entries()) {
207
+ if (typeof handler === 'function') {
208
+ map.set(key, handler);
209
+ }
210
+ }
211
+ return map;
212
+ }
213
+ if (isPlainObject(input)) {
214
+ for (const [key, handler] of Object.entries(input)) {
215
+ if (typeof handler === 'function') {
216
+ map.set(key, handler);
217
+ }
218
+ }
219
+ }
220
+ return map;
221
+ }
222
+
223
+ function normalizeMigrationHooks(migrationHooks = null) {
224
+ const input = isPlainObject(migrationHooks) ? migrationHooks : {};
225
+ return {
226
+ artifactVersions: normalizeMigrationHookMap(
227
+ input.artifactVersions,
228
+ new Map([
229
+ [LEGACY_STATE_ARTIFACT_VERSION, migrateArtifactEnvelopeV1ToV2],
230
+ ]),
231
+ ),
232
+ stateSchemas: normalizeMigrationHookMap(
233
+ input.stateSchemas,
234
+ new Map([
235
+ [LEGACY_GAME_STATE_SCHEMA_VERSION, migrateLegacyGameStatePayloadV0ToV1],
236
+ ]),
237
+ ),
238
+ };
239
+ }
240
+
241
+ function normalizeStateArtifactReadOptions({
242
+ targetArtifactVersion = STATE_ARTIFACT_VERSION,
243
+ targetStateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
244
+ migrationHooks = null,
245
+ exportDefaults = null,
246
+ } = {}) {
247
+ return {
248
+ targetArtifactVersion: parsePositiveInteger(targetArtifactVersion) || STATE_ARTIFACT_VERSION,
249
+ targetStateSchemaVersion: typeof targetStateSchemaVersion === 'string' && targetStateSchemaVersion.trim().length > 0
250
+ ? targetStateSchemaVersion.trim()
251
+ : GAME_STATE_SCHEMA_VERSION,
252
+ migrationHooks: normalizeMigrationHooks(migrationHooks),
253
+ exportDefaults: normalizeStateArtifactExportDefaults(exportDefaults),
254
+ };
255
+ }
256
+
257
+ function normalizeStateArtifactExportDefaults(exportDefaults = null) {
258
+ const input = isPlainObject(exportDefaults) ? exportDefaults : {};
259
+ return {
260
+ mode: typeof input.mode === 'string' && input.mode.trim().length > 0
261
+ ? input.mode.trim().toLowerCase()
262
+ : null,
263
+ seed: Number.isInteger(input.seed) && input.seed >= 0 ? input.seed : 0,
264
+ frameIndex: Number.isInteger(input.frameIndex) && input.frameIndex >= 0 ? input.frameIndex : 0,
265
+ elapsedSeconds: Number.isFinite(input.elapsedSeconds) && input.elapsedSeconds >= 0
266
+ ? Number(input.elapsedSeconds)
267
+ : null,
268
+ capturedAt: typeof input.capturedAt === 'string' && input.capturedAt.trim().length > 0
269
+ ? input.capturedAt.trim()
270
+ : null,
271
+ };
272
+ }
273
+
274
+ function migrateArtifactEnvelopeV1ToV2({ envelope }) {
275
+ const sourceStateSchemaVersion = resolveEnvelopeStateSchemaVersion(envelope) || GAME_STATE_SCHEMA_VERSION;
276
+ return {
277
+ envelope: {
278
+ ...envelope,
279
+ artifactVersion: STATE_ARTIFACT_VERSION,
280
+ },
281
+ migration: {
282
+ applied: true,
283
+ sourceArtifactVersion: LEGACY_STATE_ARTIFACT_VERSION,
284
+ targetArtifactVersion: STATE_ARTIFACT_VERSION,
285
+ sourceStateSchemaVersion,
286
+ targetStateSchemaVersion: sourceStateSchemaVersion,
287
+ steps: ['state_artifact_version_backfill'],
288
+ reasonCode: DEFAULT_STATE_ARTIFACT_MIGRATION_REASON_CODE,
289
+ },
290
+ };
291
+ }
292
+
293
+ function migrateLegacyGameStatePayloadV0ToV1({ envelope, exportDefaults = null }) {
294
+ const rawState = isPlainObject(envelope?.payload?.state)
295
+ ? envelope.payload.state
296
+ : (isPlainObject(envelope?.payload) ? envelope.payload : {});
297
+ const normalizedExportDefaults = normalizeStateArtifactExportDefaults(exportDefaults);
298
+ const migratedState = {
299
+ globals: sanitizeJsonObject(rawState.globals),
300
+ };
301
+ for (const sectionName of STATE_SECTION_NAMES) {
302
+ if (sectionName === 'globals') continue;
303
+ if (!Object.prototype.hasOwnProperty.call(rawState, sectionName)) continue;
304
+ const normalizedValue = sanitizeJsonValue(rawState[sectionName]);
305
+ if (normalizedValue == null) continue;
306
+ migratedState[sectionName] = normalizedValue;
307
+ }
308
+ if (isPlainObject(migratedState.camera)) {
309
+ if (typeof migratedState.camera.following !== 'boolean') {
310
+ migratedState.camera.following = false;
311
+ }
312
+ if (!Number.isInteger(migratedState.camera.activeEffects) || migratedState.camera.activeEffects < 0) {
313
+ migratedState.camera.activeEffects = 0;
314
+ }
315
+ }
316
+
317
+ const exportMode = normalizedExportDefaults.mode
318
+ || (typeof envelope?.source?.mode === 'string' && envelope.source.mode.trim().length > 0
319
+ ? envelope.source.mode.trim().toLowerCase()
320
+ : 'headless');
321
+ const exportElapsedSeconds = normalizedExportDefaults.elapsedSeconds != null
322
+ ? normalizedExportDefaults.elapsedSeconds
323
+ : (exportMode === 'headless' ? 0 : null);
324
+
325
+ const migratedPayload = {
326
+ schemaVersion: GAME_STATE_SCHEMA_VERSION,
327
+ export: {
328
+ mode: exportMode,
329
+ seed: normalizedExportDefaults.seed,
330
+ frameIndex: normalizedExportDefaults.frameIndex,
331
+ elapsedSeconds: exportElapsedSeconds,
332
+ fingerprint: sha256(stableStringify(migratedState)),
333
+ capturedAt: normalizedExportDefaults.capturedAt,
334
+ },
335
+ state: migratedState,
336
+ };
337
+
338
+ return {
339
+ envelope: {
340
+ ...envelope,
341
+ stateSchemaVersion: GAME_STATE_SCHEMA_VERSION,
342
+ payload: migratedPayload,
343
+ },
344
+ migration: {
345
+ applied: true,
346
+ sourceArtifactVersion: parsePositiveInteger(envelope?.artifactVersion) || LEGACY_STATE_ARTIFACT_VERSION,
347
+ targetArtifactVersion: parsePositiveInteger(envelope?.artifactVersion) || STATE_ARTIFACT_VERSION,
348
+ sourceStateSchemaVersion: LEGACY_GAME_STATE_SCHEMA_VERSION,
349
+ targetStateSchemaVersion: GAME_STATE_SCHEMA_VERSION,
350
+ steps: ['game_state_payload_v0_to_v1'],
351
+ reasonCode: DEFAULT_STATE_ARTIFACT_MIGRATION_REASON_CODE,
352
+ },
353
+ };
354
+ }
355
+
356
+ function applyArtifactVersionMigrations(
357
+ envelope,
358
+ {
359
+ filePath = null,
360
+ targetArtifactVersion = STATE_ARTIFACT_VERSION,
361
+ migrationHooks,
362
+ migration,
363
+ } = {},
364
+ ) {
365
+ let working = envelope;
366
+ let currentVersion = parsePositiveInteger(working?.artifactVersion) || LEGACY_STATE_ARTIFACT_VERSION;
367
+ let currentMigration = normalizeStateArtifactMigrationMetadata(migration, {
368
+ artifactVersion: currentVersion,
369
+ stateSchemaVersion: resolveEnvelopeStateSchemaVersion(working) || GAME_STATE_SCHEMA_VERSION,
370
+ });
371
+
372
+ if (currentVersion > targetArtifactVersion) {
373
+ throw new StateArtifactError(
374
+ 'state_artifact_version_unsupported',
375
+ `State artifact${filePath ? ` "${filePath}"` : ''} uses artifactVersion "${currentVersion}", expected <= "${targetArtifactVersion}".`,
376
+ {
377
+ filePath,
378
+ sourceArtifactVersion: currentVersion,
379
+ targetArtifactVersion,
380
+ },
381
+ );
382
+ }
383
+
384
+ while (currentVersion < targetArtifactVersion) {
385
+ const migrate = migrationHooks.artifactVersions.get(currentVersion);
386
+ if (typeof migrate !== 'function') {
387
+ throw new StateArtifactError(
388
+ 'state_artifact_migration_unavailable',
389
+ `State artifact${filePath ? ` "${filePath}"` : ''} cannot migrate artifactVersion "${currentVersion}" to "${targetArtifactVersion}".`,
390
+ {
391
+ filePath,
392
+ sourceArtifactVersion: currentVersion,
393
+ targetArtifactVersion,
394
+ },
395
+ );
396
+ }
397
+
398
+ let result;
399
+ try {
400
+ result = migrate({ envelope: working, filePath });
401
+ } catch (error) {
402
+ throw new StateArtifactError(
403
+ 'state_artifact_migration_failed',
404
+ `State artifact${filePath ? ` "${filePath}"` : ''} migration from artifactVersion "${currentVersion}" failed: ${error.message}`,
405
+ {
406
+ filePath,
407
+ sourceArtifactVersion: currentVersion,
408
+ targetArtifactVersion,
409
+ },
410
+ );
411
+ }
412
+
413
+ if (!isPlainObject(result?.envelope)) {
414
+ throw new StateArtifactError(
415
+ 'state_artifact_migration_failed',
416
+ `State artifact${filePath ? ` "${filePath}"` : ''} migration from artifactVersion "${currentVersion}" returned an invalid envelope.`,
417
+ {
418
+ filePath,
419
+ sourceArtifactVersion: currentVersion,
420
+ targetArtifactVersion,
421
+ },
422
+ );
423
+ }
424
+
425
+ working = result.envelope;
426
+ const nextVersion = parsePositiveInteger(working.artifactVersion);
427
+ if (!nextVersion || nextVersion <= currentVersion) {
428
+ throw new StateArtifactError(
429
+ 'state_artifact_migration_failed',
430
+ `State artifact${filePath ? ` "${filePath}"` : ''} migration from artifactVersion "${currentVersion}" did not advance the artifactVersion.`,
431
+ {
432
+ filePath,
433
+ sourceArtifactVersion: currentVersion,
434
+ targetArtifactVersion,
435
+ nextArtifactVersion: nextVersion,
436
+ },
437
+ );
438
+ }
439
+
440
+ currentVersion = nextVersion;
441
+ currentMigration = mergeStateArtifactMigrationMetadata(currentMigration, result.migration, {
442
+ artifactVersion: currentVersion,
443
+ stateSchemaVersion: resolveEnvelopeStateSchemaVersion(working) || GAME_STATE_SCHEMA_VERSION,
444
+ });
445
+ }
446
+
447
+ return {
448
+ envelope: working,
449
+ migration: currentMigration,
450
+ };
451
+ }
452
+
453
+ function applyStateSchemaMigrations(
454
+ envelope,
455
+ {
456
+ filePath = null,
457
+ targetStateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
458
+ migrationHooks,
459
+ migration,
460
+ exportDefaults = null,
461
+ } = {},
462
+ ) {
463
+ let working = envelope;
464
+ let currentStateSchemaVersion = resolveEnvelopeStateSchemaVersion(working);
465
+ if (!currentStateSchemaVersion) {
466
+ throw new StateArtifactError(
467
+ 'invalid_state_artifact_payload',
468
+ `State artifact${filePath ? ` "${filePath}"` : ''} stateSchemaVersion must be a non-empty string.`,
469
+ { filePath },
470
+ );
471
+ }
472
+
473
+ const currentArtifactVersion = parsePositiveInteger(working?.artifactVersion) || STATE_ARTIFACT_VERSION;
474
+ let currentMigration = normalizeStateArtifactMigrationMetadata(migration, {
475
+ artifactVersion: currentArtifactVersion,
476
+ stateSchemaVersion: currentStateSchemaVersion,
477
+ });
478
+
479
+ const seenSchemas = new Set([currentStateSchemaVersion]);
480
+ while (currentStateSchemaVersion !== targetStateSchemaVersion) {
481
+ const migrate = migrationHooks.stateSchemas.get(currentStateSchemaVersion);
482
+ if (typeof migrate !== 'function') {
483
+ throw new StateArtifactError(
484
+ 'state_artifact_state_schema_migration_unavailable',
485
+ `State artifact${filePath ? ` "${filePath}"` : ''} cannot migrate state schema "${currentStateSchemaVersion}" to "${targetStateSchemaVersion}".`,
486
+ {
487
+ filePath,
488
+ sourceArtifactVersion: currentArtifactVersion,
489
+ targetArtifactVersion: currentArtifactVersion,
490
+ sourceStateSchemaVersion: currentStateSchemaVersion,
491
+ targetStateSchemaVersion,
492
+ },
493
+ );
494
+ }
495
+
496
+ let result;
497
+ try {
498
+ result = migrate({
499
+ envelope: working,
500
+ filePath,
501
+ targetStateSchemaVersion,
502
+ exportDefaults,
503
+ });
504
+ } catch (error) {
505
+ throw new StateArtifactError(
506
+ 'state_artifact_state_schema_migration_failed',
507
+ `State artifact${filePath ? ` "${filePath}"` : ''} state schema migration from "${currentStateSchemaVersion}" failed: ${error.message}`,
508
+ {
509
+ filePath,
510
+ sourceArtifactVersion: currentArtifactVersion,
511
+ targetArtifactVersion: currentArtifactVersion,
512
+ sourceStateSchemaVersion: currentStateSchemaVersion,
513
+ targetStateSchemaVersion,
514
+ },
515
+ );
516
+ }
517
+
518
+ if (!isPlainObject(result?.envelope)) {
519
+ throw new StateArtifactError(
520
+ 'state_artifact_state_schema_migration_failed',
521
+ `State artifact${filePath ? ` "${filePath}"` : ''} state schema migration from "${currentStateSchemaVersion}" returned an invalid envelope.`,
522
+ {
523
+ filePath,
524
+ sourceArtifactVersion: currentArtifactVersion,
525
+ targetArtifactVersion: currentArtifactVersion,
526
+ sourceStateSchemaVersion: currentStateSchemaVersion,
527
+ targetStateSchemaVersion,
528
+ },
529
+ );
530
+ }
531
+
532
+ working = result.envelope;
533
+ const nextStateSchemaVersion = resolveEnvelopeStateSchemaVersion(working);
534
+ if (!nextStateSchemaVersion || seenSchemas.has(nextStateSchemaVersion)) {
535
+ throw new StateArtifactError(
536
+ 'state_artifact_state_schema_migration_failed',
537
+ `State artifact${filePath ? ` "${filePath}"` : ''} state schema migration from "${currentStateSchemaVersion}" did not advance toward "${targetStateSchemaVersion}".`,
538
+ {
539
+ filePath,
540
+ sourceArtifactVersion: currentArtifactVersion,
541
+ targetArtifactVersion: currentArtifactVersion,
542
+ sourceStateSchemaVersion: currentStateSchemaVersion,
543
+ targetStateSchemaVersion,
544
+ nextStateSchemaVersion,
545
+ },
546
+ );
547
+ }
548
+
549
+ currentStateSchemaVersion = nextStateSchemaVersion;
550
+ seenSchemas.add(currentStateSchemaVersion);
551
+ currentMigration = mergeStateArtifactMigrationMetadata(currentMigration, result.migration, {
552
+ artifactVersion: parsePositiveInteger(working?.artifactVersion) || currentArtifactVersion,
553
+ stateSchemaVersion: currentStateSchemaVersion,
554
+ });
555
+ }
556
+
557
+ return {
558
+ envelope: working,
559
+ migration: currentMigration,
560
+ };
561
+ }
562
+
28
563
  export function normalizeStateArtifactKind(value) {
29
564
  const normalized = String(value || '').trim().toLowerCase();
30
565
  if (!STATE_ARTIFACT_KINDS.includes(normalized)) {
@@ -98,6 +633,7 @@ export function createStateArtifactEnvelope({
98
633
  const output = {
99
634
  schemaVersion: STATE_ARTIFACT_SCHEMA_VERSION,
100
635
  artifactType: normalizedKind,
636
+ artifactVersion: STATE_ARTIFACT_VERSION,
101
637
  name: normalizedName,
102
638
  createdAt: typeof createdAt === 'string' && createdAt.trim().length > 0
103
639
  ? createdAt
@@ -108,6 +644,10 @@ export function createStateArtifactEnvelope({
108
644
  : null,
109
645
  source: normalizeArtifactSource(source),
110
646
  payload,
647
+ migration: normalizeStateArtifactMigrationMetadata(null, {
648
+ artifactVersion: STATE_ARTIFACT_VERSION,
649
+ stateSchemaVersion: payload.schemaVersion,
650
+ }),
111
651
  };
112
652
 
113
653
  if (typeof note === 'string' && note.trim().length > 0) {
@@ -124,7 +664,7 @@ export function writeStateArtifactEnvelope(filePath, envelope) {
124
664
  return validated;
125
665
  }
126
666
 
127
- export function readStateArtifactEnvelope(filePath) {
667
+ export function readStateArtifactEnvelope(filePath, options = {}) {
128
668
  let text = '';
129
669
  try {
130
670
  text = readFileSync(filePath, 'utf8');
@@ -147,10 +687,19 @@ export function readStateArtifactEnvelope(filePath) {
147
687
  );
148
688
  }
149
689
 
150
- return normalizeStateArtifactEnvelope(parsed, { filePath });
690
+ return normalizeStateArtifactEnvelope(parsed, { filePath, ...options });
151
691
  }
152
692
 
153
- export function loadStateArtifact({ projectRoot, kind, name, artifactPath = null } = {}) {
693
+ export function loadStateArtifact({
694
+ projectRoot,
695
+ kind,
696
+ name,
697
+ artifactPath = null,
698
+ targetArtifactVersion = STATE_ARTIFACT_VERSION,
699
+ targetStateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
700
+ migrationHooks = null,
701
+ exportDefaults = null,
702
+ } = {}) {
154
703
  const resolvedPath = artifactPath
155
704
  ? resolve(projectRoot, artifactPath)
156
705
  : resolveStateArtifactPath(projectRoot, kind, name);
@@ -161,7 +710,15 @@ export function loadStateArtifact({ projectRoot, kind, name, artifactPath = null
161
710
  { filePath: resolvedPath },
162
711
  );
163
712
  }
164
- const envelope = readStateArtifactEnvelope(resolvedPath);
713
+ const envelope = readStateArtifactEnvelope(
714
+ resolvedPath,
715
+ normalizeStateArtifactReadOptions({
716
+ targetArtifactVersion,
717
+ targetStateSchemaVersion,
718
+ migrationHooks,
719
+ exportDefaults,
720
+ }),
721
+ );
165
722
  return {
166
723
  filePath: resolvedPath,
167
724
  envelope,
@@ -169,9 +726,24 @@ export function loadStateArtifact({ projectRoot, kind, name, artifactPath = null
169
726
  };
170
727
  }
171
728
 
172
- export function listStateArtifacts(projectRoot, kind) {
729
+ export function listStateArtifacts(
730
+ projectRoot,
731
+ kind,
732
+ {
733
+ targetArtifactVersion = STATE_ARTIFACT_VERSION,
734
+ targetStateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
735
+ migrationHooks = null,
736
+ exportDefaults = null,
737
+ } = {},
738
+ ) {
173
739
  const normalizedKind = normalizeStateArtifactKind(kind);
174
740
  const directory = resolveStateArtifactDirectory(projectRoot, normalizedKind);
741
+ const readOptions = normalizeStateArtifactReadOptions({
742
+ targetArtifactVersion,
743
+ targetStateSchemaVersion,
744
+ migrationHooks,
745
+ exportDefaults,
746
+ });
175
747
  if (!existsSync(directory)) {
176
748
  return {
177
749
  kind: normalizedKind,
@@ -187,7 +759,7 @@ export function listStateArtifacts(projectRoot, kind) {
187
759
  if (!entry.endsWith('.json')) continue;
188
760
  const filePath = join(directory, entry);
189
761
  try {
190
- const envelope = readStateArtifactEnvelope(filePath);
762
+ const envelope = readStateArtifactEnvelope(filePath, readOptions);
191
763
  if (envelope.artifactType !== normalizedKind) {
192
764
  throw new StateArtifactError(
193
765
  'state_artifact_kind_mismatch',
@@ -203,6 +775,14 @@ export function listStateArtifacts(projectRoot, kind) {
203
775
  ? error.reasonCode
204
776
  : 'state_artifact_invalid',
205
777
  detail: error instanceof Error ? error.message : String(error),
778
+ migration: error instanceof StateArtifactError && isPlainObject(error.details)
779
+ ? {
780
+ sourceArtifactVersion: error.details.sourceArtifactVersion ?? null,
781
+ targetArtifactVersion: error.details.targetArtifactVersion ?? null,
782
+ sourceStateSchemaVersion: error.details.sourceStateSchemaVersion ?? null,
783
+ targetStateSchemaVersion: error.details.targetStateSchemaVersion ?? null,
784
+ }
785
+ : null,
206
786
  });
207
787
  }
208
788
  }
@@ -215,7 +795,16 @@ export function listStateArtifacts(projectRoot, kind) {
215
795
  };
216
796
  }
217
797
 
218
- function normalizeStateArtifactEnvelope(envelope, { filePath = null } = {}) {
798
+ function normalizeStateArtifactEnvelope(
799
+ envelope,
800
+ {
801
+ filePath = null,
802
+ targetArtifactVersion = STATE_ARTIFACT_VERSION,
803
+ targetStateSchemaVersion = GAME_STATE_SCHEMA_VERSION,
804
+ migrationHooks = normalizeMigrationHooks(),
805
+ exportDefaults = normalizeStateArtifactExportDefaults(),
806
+ } = {},
807
+ ) {
219
808
  if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)) {
220
809
  throw new StateArtifactError(
221
810
  'invalid_state_artifact_payload',
@@ -231,26 +820,58 @@ function normalizeStateArtifactEnvelope(envelope, { filePath = null } = {}) {
231
820
  );
232
821
  }
233
822
 
234
- const normalizedKind = normalizeStateArtifactKind(envelope.artifactType);
235
- const normalizedName = normalizeStateArtifactName(envelope.name, { kind: normalizedKind });
236
- if (!envelope.payload || typeof envelope.payload !== 'object' || Array.isArray(envelope.payload)) {
823
+ let working = envelope;
824
+ let migration = normalizeStateArtifactMigrationMetadata(working.migration, {
825
+ artifactVersion: parsePositiveInteger(working.artifactVersion) || LEGACY_STATE_ARTIFACT_VERSION,
826
+ stateSchemaVersion: resolveEnvelopeStateSchemaVersion(working) || GAME_STATE_SCHEMA_VERSION,
827
+ });
828
+ const artifactMigration = applyArtifactVersionMigrations(working, {
829
+ filePath,
830
+ targetArtifactVersion,
831
+ migrationHooks,
832
+ migration,
833
+ });
834
+ working = artifactMigration.envelope;
835
+ migration = artifactMigration.migration;
836
+ const stateSchemaMigration = applyStateSchemaMigrations(working, {
837
+ filePath,
838
+ targetStateSchemaVersion,
839
+ migrationHooks,
840
+ migration,
841
+ exportDefaults,
842
+ });
843
+ working = stateSchemaMigration.envelope;
844
+ migration = stateSchemaMigration.migration;
845
+
846
+ const normalizedKind = normalizeStateArtifactKind(working.artifactType);
847
+ const normalizedName = normalizeStateArtifactName(working.name, { kind: normalizedKind });
848
+ if (!working.payload || typeof working.payload !== 'object' || Array.isArray(working.payload)) {
237
849
  throw new StateArtifactError(
238
850
  'invalid_state_artifact_payload',
239
851
  `State artifact${filePath ? ` "${filePath}"` : ''} payload must be an object.`,
240
852
  { filePath },
241
853
  );
242
854
  }
243
- if (typeof envelope.stateSchemaVersion !== 'string' || envelope.stateSchemaVersion.trim().length === 0) {
855
+ const artifactVersion = parsePositiveInteger(working.artifactVersion);
856
+ if (!artifactVersion) {
857
+ throw new StateArtifactError(
858
+ 'invalid_state_artifact_payload',
859
+ `State artifact${filePath ? ` "${filePath}"` : ''} artifactVersion must be a positive integer.`,
860
+ { filePath },
861
+ );
862
+ }
863
+ const stateSchemaVersion = resolveEnvelopeStateSchemaVersion(working);
864
+ if (typeof stateSchemaVersion !== 'string' || stateSchemaVersion.trim().length === 0) {
244
865
  throw new StateArtifactError(
245
866
  'invalid_state_artifact_payload',
246
867
  `State artifact${filePath ? ` "${filePath}"` : ''} stateSchemaVersion must be a non-empty string.`,
247
868
  { filePath },
248
869
  );
249
870
  }
250
- if (envelope.payload.schemaVersion !== envelope.stateSchemaVersion) {
871
+ if (working.payload.schemaVersion !== stateSchemaVersion) {
251
872
  throw new StateArtifactError(
252
873
  'state_artifact_schema_mismatch',
253
- `State artifact${filePath ? ` "${filePath}"` : ''} payload schema "${envelope.payload.schemaVersion}" does not match declared stateSchemaVersion "${envelope.stateSchemaVersion}".`,
874
+ `State artifact${filePath ? ` "${filePath}"` : ''} payload schema "${working.payload.schemaVersion}" does not match declared stateSchemaVersion "${stateSchemaVersion}".`,
254
875
  { filePath },
255
876
  );
256
877
  }
@@ -258,22 +879,34 @@ function normalizeStateArtifactEnvelope(envelope, { filePath = null } = {}) {
258
879
  const output = {
259
880
  schemaVersion: STATE_ARTIFACT_SCHEMA_VERSION,
260
881
  artifactType: normalizedKind,
882
+ artifactVersion,
261
883
  name: normalizedName,
262
- createdAt: typeof envelope.createdAt === 'string' && envelope.createdAt.trim().length > 0
263
- ? envelope.createdAt
884
+ createdAt: typeof working.createdAt === 'string' && working.createdAt.trim().length > 0
885
+ ? working.createdAt
264
886
  : null,
265
- stateSchemaVersion: envelope.stateSchemaVersion,
266
- payloadFingerprint: typeof envelope.payloadFingerprint === 'string'
267
- ? envelope.payloadFingerprint
268
- : (typeof envelope?.payload?.export?.fingerprint === 'string'
269
- ? envelope.payload.export.fingerprint
887
+ stateSchemaVersion,
888
+ payloadFingerprint: typeof working?.payload?.export?.fingerprint === 'string'
889
+ ? working.payload.export.fingerprint
890
+ : (typeof working.payloadFingerprint === 'string'
891
+ ? working.payloadFingerprint
270
892
  : null),
271
- source: normalizeArtifactSource(envelope.source),
272
- payload: envelope.payload,
893
+ source: normalizeArtifactSource(working.source),
894
+ payload: working.payload,
895
+ migration: mergeStateArtifactMigrationMetadata(
896
+ normalizeStateArtifactMigrationMetadata(working.migration, {
897
+ artifactVersion,
898
+ stateSchemaVersion,
899
+ }),
900
+ migration,
901
+ {
902
+ artifactVersion,
903
+ stateSchemaVersion,
904
+ },
905
+ ),
273
906
  };
274
907
 
275
- if (typeof envelope.note === 'string' && envelope.note.trim().length > 0) {
276
- output.note = envelope.note.trim();
908
+ if (typeof working.note === 'string' && working.note.trim().length > 0) {
909
+ output.note = working.note.trim();
277
910
  }
278
911
 
279
912
  return output;
@@ -283,12 +916,17 @@ function createArtifactMetadata(filePath, envelope) {
283
916
  const stat = statSync(filePath);
284
917
  return {
285
918
  kind: envelope.artifactType,
919
+ artifactVersion: envelope.artifactVersion,
286
920
  name: envelope.name,
287
921
  path: filePath,
288
922
  createdAt: envelope.createdAt,
289
923
  note: envelope.note || null,
290
924
  stateSchemaVersion: envelope.stateSchemaVersion,
291
925
  payloadFingerprint: envelope.payloadFingerprint,
926
+ migration: normalizeStateArtifactMigrationMetadata(envelope.migration, {
927
+ artifactVersion: envelope.artifactVersion,
928
+ stateSchemaVersion: envelope.stateSchemaVersion,
929
+ }),
292
930
  mode: typeof envelope?.payload?.export?.mode === 'string' ? envelope.payload.export.mode : null,
293
931
  frameIndex: Number.isInteger(envelope?.payload?.export?.frameIndex) ? envelope.payload.export.frameIndex : null,
294
932
  elapsedSeconds: Number.isFinite(envelope?.payload?.export?.elapsedSeconds)