@auraindustry/aurajs 0.0.7 → 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 +98 -2
  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
@@ -0,0 +1,99 @@
1
+ import { clamp } from '../src/starter-utils/index.js';
2
+ import { LOCAL_MULTIPLAYER_CONFIG } from '../config/gameplay/local-multiplayer.config.js';
3
+ import { findSpawnSlot } from '../content/gameplay/room-layout.js';
4
+
5
+ export const LOCAL_PLAYER_SIZE = Object.freeze({
6
+ width: LOCAL_MULTIPLAYER_CONFIG.playerWidth,
7
+ height: LOCAL_MULTIPLAYER_CONFIG.playerHeight,
8
+ });
9
+
10
+ export const LOCAL_PLAYER_SPEED = LOCAL_MULTIPLAYER_CONFIG.playerSpeed;
11
+
12
+ export const LOCAL_PLAYER_COLORS = Object.freeze([
13
+ { r: 0.28, g: 0.78, b: 0.98 },
14
+ { r: 0.98, g: 0.56, b: 0.34 },
15
+ ]);
16
+
17
+ const localMultiplayerPlayerPrefab = {
18
+ id: 'player',
19
+ kind: 'prefab',
20
+ role: 'player',
21
+ view: {
22
+ kind: 'actor2d',
23
+ collider: {
24
+ shape: 'rect',
25
+ size: {
26
+ x: LOCAL_PLAYER_SIZE.width,
27
+ y: LOCAL_PLAYER_SIZE.height,
28
+ },
29
+ },
30
+ },
31
+ components: {
32
+ movementProfile: 'arcade',
33
+ multiplayerRole: 'local-room-avatar',
34
+ },
35
+ hooks: {},
36
+ notes: [
37
+ 'Keep starter-owned local-room avatar tuning here before promoting it into a broader multiplayer runtime seam.',
38
+ 'This prefab stays agnostic about transport; the starter defaults to local-first hosting and can promote the same room-code flow through aura.config.json -> multiplayer.',
39
+ ],
40
+ };
41
+
42
+ function defaultPlayerLabel(id) {
43
+ return id === 0 ? 'host' : `p${id}`;
44
+ }
45
+
46
+ export function createPlayerPrefab() {
47
+ return localMultiplayerPlayerPrefab;
48
+ }
49
+
50
+ export function createLocalPlayerState(id = 0, label = defaultPlayerLabel(id)) {
51
+ const spawnSlot = findSpawnSlot(id === 0 ? 'host' : 'guest');
52
+ return {
53
+ x: spawnSlot?.x ?? (id === 0 ? 180 : LOCAL_MULTIPLAYER_CONFIG.width - 216),
54
+ y: spawnSlot?.y ?? (id === 0 ? 210 : 300),
55
+ label,
56
+ color: LOCAL_PLAYER_COLORS[id % LOCAL_PLAYER_COLORS.length],
57
+ hidden: false,
58
+ };
59
+ }
60
+
61
+ export function applyLocalPlayerMovement(playerState, input, dt) {
62
+ const dx = Number(input && input.dx) || 0;
63
+ const dy = Number(input && input.dy) || 0;
64
+ return {
65
+ ...playerState,
66
+ x: clamp(
67
+ playerState.x + (dx * LOCAL_PLAYER_SPEED * dt),
68
+ 24,
69
+ LOCAL_MULTIPLAYER_CONFIG.width - LOCAL_PLAYER_SIZE.width - 24,
70
+ ),
71
+ y: clamp(
72
+ playerState.y + (dy * LOCAL_PLAYER_SPEED * dt),
73
+ 96,
74
+ LOCAL_MULTIPLAYER_CONFIG.height - LOCAL_PLAYER_SIZE.height - 28,
75
+ ),
76
+ hidden: false,
77
+ };
78
+ }
79
+
80
+ export function drawLocalPlayerState(playerState, key = 'player') {
81
+ if (!playerState || playerState.hidden) return;
82
+ const color = playerState.color || LOCAL_PLAYER_COLORS[0];
83
+ aura.draw2d.rectFill(
84
+ playerState.x,
85
+ playerState.y,
86
+ LOCAL_PLAYER_SIZE.width,
87
+ LOCAL_PLAYER_SIZE.height,
88
+ aura.rgb(color.r, color.g, color.b),
89
+ );
90
+ aura.draw2d.text(
91
+ playerState.label || key,
92
+ playerState.x - 2,
93
+ playerState.y - 14,
94
+ 11,
95
+ aura.rgb(0.92, 0.94, 0.98),
96
+ );
97
+ }
98
+
99
+ export default localMultiplayerPlayerPrefab;
@@ -0,0 +1,12 @@
1
+ export function createBootScene() {
2
+ return {
3
+ id: 'boot',
4
+ nextSceneId: 'gameplay',
5
+ notes: [
6
+ 'Keep long-lived boot orchestration here if the starter grows beyond the current local-first room-code loop.',
7
+ 'Do not move room/session ownership into src/main.js; keep it in the authored runtime and scene files.',
8
+ ],
9
+ };
10
+ }
11
+
12
+ export default createBootScene;
@@ -0,0 +1,443 @@
1
+ import { axisFromKeys } from '../src/starter-utils/index.js';
2
+ import { ensureSessionState, ensureUiState } from '../src/runtime/app-state.js';
3
+ import { LOCAL_MULTIPLAYER_CONFIG } from '../config/gameplay/local-multiplayer.config.js';
4
+ import {
5
+ applyLocalPlayerMovement,
6
+ createLocalPlayerState,
7
+ drawLocalPlayerState,
8
+ } from '../prefabs/player.prefab.js';
9
+ import { ROOM_LAYOUT } from '../content/gameplay/room-layout.js';
10
+
11
+ function hasMethod(obj, method) {
12
+ return Boolean(obj) && typeof obj[method] === 'function';
13
+ }
14
+
15
+ function failWithReason(reasonCode, message) {
16
+ throw new Error(`[local-multiplayer-template] ${message} [reason:${reasonCode}]`);
17
+ }
18
+
19
+ function normalizeConnectivityMode(value) {
20
+ const normalized = String(value || '').trim().toLowerCase();
21
+ if (normalized === 'auto' || normalized === 'internet' || normalized === 'internet-auto' || normalized === 'online') {
22
+ return 'auto';
23
+ }
24
+ if (normalized === 'relay' || normalized === 'internet-relay') {
25
+ return 'relay';
26
+ }
27
+ return 'local';
28
+ }
29
+
30
+ function readOptionalText(value) {
31
+ const normalized = typeof value === 'string' ? value.trim() : '';
32
+ return normalized || null;
33
+ }
34
+
35
+ function readAuraEnv(name) {
36
+ if (typeof aura === 'undefined' || typeof aura.env !== 'function') {
37
+ return null;
38
+ }
39
+ return readOptionalText(aura.env(name));
40
+ }
41
+
42
+ function readAuraEnvBoolean(name) {
43
+ const raw = readAuraEnv(name);
44
+ if (!raw) return null;
45
+ const normalized = raw.toLowerCase();
46
+ if (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on') {
47
+ return true;
48
+ }
49
+ if (normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off') {
50
+ return false;
51
+ }
52
+ return null;
53
+ }
54
+
55
+ function isKnownConnectivityMode(value) {
56
+ const normalized = String(value || '').trim().toLowerCase();
57
+ return normalized === 'local'
58
+ || normalized === 'auto'
59
+ || normalized === 'internet'
60
+ || normalized === 'internet-auto'
61
+ || normalized === 'online'
62
+ || normalized === 'relay'
63
+ || normalized === 'internet-relay';
64
+ }
65
+
66
+ function resolveRoomConfig() {
67
+ return {
68
+ roomCode: (readAuraEnv('AURA_MULTIPLAYER_ROOM_CODE') || ROOM_LAYOUT.roomCode).toUpperCase(),
69
+ roomName: readAuraEnv('AURA_MULTIPLAYER_ROOM_NAME') || ROOM_LAYOUT.roomName,
70
+ };
71
+ }
72
+
73
+ function resolveConnectivityConfig() {
74
+ const raw = LOCAL_MULTIPLAYER_CONFIG.connectivity;
75
+ const envMode = readAuraEnv('AURA_MULTIPLAYER_CONNECTIVITY_MODE');
76
+ const envRelayHost = readAuraEnv('AURA_MULTIPLAYER_RELAY_HOST') || readAuraEnv('AURA_MULTIPLAYER_RELAY');
77
+ const envCoordinatorUrl = readAuraEnv('AURA_COORDINATOR_URL');
78
+ const envRelayUrl = readAuraEnv('AURA_RELAY_URL');
79
+
80
+ if (typeof raw === 'string') {
81
+ const value = readOptionalText(raw);
82
+ if (!value) {
83
+ return {
84
+ mode: envRelayHost || envCoordinatorUrl || envRelayUrl ? 'auto' : 'local',
85
+ relayHost: envRelayHost,
86
+ coordinatorUrl: envCoordinatorUrl,
87
+ relayUrl: envRelayUrl,
88
+ };
89
+ }
90
+ if (isKnownConnectivityMode(value)) {
91
+ const mode = normalizeConnectivityMode(value);
92
+ return {
93
+ mode: (envRelayHost || envCoordinatorUrl || envRelayUrl) && mode !== 'relay' ? 'auto' : mode,
94
+ relayHost: envRelayHost,
95
+ coordinatorUrl: envCoordinatorUrl,
96
+ relayUrl: envRelayUrl,
97
+ };
98
+ }
99
+ return {
100
+ mode: 'auto',
101
+ relayHost: value,
102
+ coordinatorUrl: envCoordinatorUrl,
103
+ relayUrl: envRelayUrl,
104
+ };
105
+ }
106
+
107
+ if (!raw || typeof raw !== 'object') {
108
+ return {
109
+ mode: envRelayHost || envCoordinatorUrl || envRelayUrl ? 'auto' : 'local',
110
+ relayHost: envRelayHost,
111
+ coordinatorUrl: envCoordinatorUrl,
112
+ relayUrl: envRelayUrl,
113
+ };
114
+ }
115
+ const relayHost = readOptionalText(raw.relay || raw.relayHost) || envRelayHost;
116
+ const coordinatorUrl = readOptionalText(raw.coordinatorUrl) || envCoordinatorUrl;
117
+ const relayUrl = readOptionalText(raw.relayUrl) || envRelayUrl;
118
+ const requestedMode = normalizeConnectivityMode(envMode || raw.mode || raw.internetMode || raw.hostingMode);
119
+ const mode = relayHost || coordinatorUrl || relayUrl
120
+ ? (requestedMode === 'relay' ? 'relay' : 'auto')
121
+ : requestedMode;
122
+ return {
123
+ mode,
124
+ relayHost,
125
+ coordinatorUrl,
126
+ relayUrl,
127
+ };
128
+ }
129
+
130
+ function resolveDiagnosticsConfig() {
131
+ const enabled = readAuraEnvBoolean('AURA_MULTIPLAYER_SHOW_DIAGNOSTICS');
132
+ return {
133
+ enabled: enabled !== false,
134
+ };
135
+ }
136
+
137
+ const ROOM_CONFIG = resolveRoomConfig();
138
+ const CONNECTIVITY_CONFIG = resolveConnectivityConfig();
139
+ const DIAGNOSTICS_CONFIG = resolveDiagnosticsConfig();
140
+ const LAUNCH_JOIN_CODE = (() => {
141
+ const value = readAuraEnv('AURA_MULTIPLAYER_JOIN_CODE');
142
+ return value ? value.toUpperCase() : null;
143
+ })();
144
+
145
+ function usesInternetBackedHosting() {
146
+ return CONNECTIVITY_CONFIG.mode !== 'local';
147
+ }
148
+
149
+ function launchedViaExplicitJoin() {
150
+ return Boolean(LAUNCH_JOIN_CODE);
151
+ }
152
+
153
+ function resolveHostFallbackSeconds() {
154
+ const configured = Math.max(0, Number(LOCAL_MULTIPLAYER_CONFIG.hostFallbackSeconds) || 0);
155
+ if (!usesInternetBackedHosting()) {
156
+ return configured;
157
+ }
158
+ return Math.max(configured, 2.5);
159
+ }
160
+
161
+ function assertLocalMultiplayerCapabilities() {
162
+ const missing = [];
163
+ if (!hasMethod(aura.input, 'isKeyDown')) missing.push('aura.input.isKeyDown');
164
+ if (!hasMethod(aura.draw2d, 'clear')) missing.push('aura.draw2d.clear');
165
+ if (!hasMethod(aura.draw2d, 'rectFill')) missing.push('aura.draw2d.rectFill');
166
+ if (!hasMethod(aura.draw2d, 'text')) missing.push('aura.draw2d.text');
167
+ if (typeof aura.rgb !== 'function') missing.push('aura.rgb');
168
+ if (!hasMethod(aura.multiplayer, 'configure')) missing.push('aura.multiplayer.configure');
169
+ if (!hasMethod(aura.multiplayer, 'getAllPlayerInputs')) missing.push('aura.multiplayer.getAllPlayerInputs');
170
+ if (!hasMethod(aura.multiplayer, 'getAllState')) missing.push('aura.multiplayer.getAllState');
171
+ if (!hasMethod(aura.multiplayer, 'getPlayerCount')) missing.push('aura.multiplayer.getPlayerCount');
172
+ if (!hasMethod(aura.multiplayer, 'getRoomInfo')) missing.push('aura.multiplayer.getRoomInfo');
173
+ if (!hasMethod(aura.multiplayer, 'getState')) missing.push('aura.multiplayer.getState');
174
+ if (!hasMethod(aura.multiplayer, 'host')) missing.push('aura.multiplayer.host');
175
+ if (!hasMethod(aura.multiplayer, 'isConnected')) missing.push('aura.multiplayer.isConnected');
176
+ if (!hasMethod(aura.multiplayer, 'onPlayerJoin')) missing.push('aura.multiplayer.onPlayerJoin');
177
+ if (!hasMethod(aura.multiplayer, 'onPlayerLeave')) missing.push('aura.multiplayer.onPlayerLeave');
178
+ if (!hasMethod(aura.multiplayer, 'sendInput')) missing.push('aura.multiplayer.sendInput');
179
+ if (!hasMethod(aura.multiplayer, 'setState')) missing.push('aura.multiplayer.setState');
180
+
181
+ if (missing.length > 0) {
182
+ failWithReason('missing_runtime_api', `runtime missing required APIs: ${missing.join(', ')}`);
183
+ }
184
+ }
185
+
186
+ function createSessionSeed() {
187
+ return {
188
+ runsStarted: 0,
189
+ lastRole: 'pending',
190
+ lastRoomCode: ROOM_CONFIG.roomCode,
191
+ lastRoomName: ROOM_CONFIG.roomName,
192
+ lastPlayerCount: 0,
193
+ lastConnected: false,
194
+ lastSceneId: 'gameplay',
195
+ };
196
+ }
197
+
198
+ function createUiSeed() {
199
+ return {
200
+ showControlsHint: true,
201
+ };
202
+ }
203
+
204
+ function createSceneState() {
205
+ return {
206
+ role: 'pending',
207
+ hostedRoom: null,
208
+ pendingElapsed: 0,
209
+ registeredHostCallbacks: false,
210
+ };
211
+ }
212
+
213
+ function currentRoomInfo(sceneState) {
214
+ return aura.multiplayer.getRoomInfo() || sceneState.hostedRoom || null;
215
+ }
216
+
217
+ function currentRoomCode(sceneState) {
218
+ const roomInfo = currentRoomInfo(sceneState);
219
+ if (roomInfo && roomInfo.code) {
220
+ return roomInfo.code;
221
+ }
222
+ if (LAUNCH_JOIN_CODE) {
223
+ return LAUNCH_JOIN_CODE;
224
+ }
225
+ return ROOM_CONFIG.roomCode;
226
+ }
227
+
228
+ function readInput() {
229
+ return {
230
+ dx: axisFromKeys(aura.input, ['arrowleft', 'a'], ['arrowright', 'd']),
231
+ dy: axisFromKeys(aura.input, ['arrowup', 'w'], ['arrowdown', 's']),
232
+ };
233
+ }
234
+
235
+ function currentStatusLine(sceneState, roomCode) {
236
+ if (sceneState.role === 'pending') {
237
+ if (launchedViaExplicitJoin()) {
238
+ return `Joining room code ${roomCode}...`;
239
+ }
240
+ return 'Checking for a room-code match before hosting this room...';
241
+ }
242
+ if (sceneState.role === 'host') {
243
+ if (usesInternetBackedHosting()) {
244
+ return `Share this room code anywhere: npm run join -- ${roomCode}`;
245
+ }
246
+ return `Second terminal: npm run join -- ${roomCode}`;
247
+ }
248
+ if (usesInternetBackedHosting()) {
249
+ return 'Joined through room-code lookup. Direct UDP first, relay fallback ready.';
250
+ }
251
+ return 'Joined through the local-first room-code multiplayer flow.';
252
+ }
253
+
254
+ export function createGameplayScene(context = {}) {
255
+ const appState = context.appState && typeof context.appState === 'object'
256
+ ? context.appState
257
+ : (context.appState = {});
258
+ const multiplayerSession = ensureSessionState(appState, 'localMultiplayer', createSessionSeed());
259
+ const multiplayerUi = ensureUiState(appState, 'localMultiplayerHud', createUiSeed());
260
+ const sceneState = createSceneState();
261
+
262
+ function syncSessionState() {
263
+ const roomInfo = currentRoomInfo(sceneState);
264
+ multiplayerSession.lastRole = sceneState.role;
265
+ multiplayerSession.lastRoomCode = roomInfo?.code || ROOM_CONFIG.roomCode;
266
+ multiplayerSession.lastRoomName = roomInfo?.name || ROOM_CONFIG.roomName;
267
+ multiplayerSession.lastPlayerCount = Number(aura.multiplayer.getPlayerCount?.() || 0);
268
+ multiplayerSession.lastConnected = aura.multiplayer.isConnected() === true;
269
+ multiplayerSession.lastSceneId = 'gameplay';
270
+ }
271
+
272
+ function syncHud() {
273
+ const roomInfo = currentRoomInfo(sceneState);
274
+ const roomCode = currentRoomCode(sceneState);
275
+ const diagnostics = DIAGNOSTICS_CONFIG.enabled
276
+ ? {
277
+ mode: roomInfo?.requestedMode || CONNECTIVITY_CONFIG.mode,
278
+ scope: roomInfo?.scope || (usesInternetBackedHosting() ? 'internet' : 'local'),
279
+ transportPath: roomInfo?.transportPath || (usesInternetBackedHosting() ? 'internet_pending' : 'local_room'),
280
+ transportStatus: roomInfo?.transportStatus || (aura.multiplayer.isConnected() ? 'connected' : 'waiting'),
281
+ joinPath: roomInfo?.joinPath || (usesInternetBackedHosting() ? 'internet_fallback' : 'local'),
282
+ lastReasonCode: roomInfo?.lastReasonCode || null,
283
+ pingMs: Number(aura.multiplayer.getPing?.()),
284
+ }
285
+ : null;
286
+ context.setHudScreen?.('hud', {
287
+ role: sceneState.role === 'pending' ? 'WAITING' : sceneState.role.toUpperCase(),
288
+ roomCode,
289
+ roomName: roomInfo?.name || ROOM_CONFIG.roomName,
290
+ playerCount: Number(aura.multiplayer.getPlayerCount?.() || 0),
291
+ connected: aura.multiplayer.isConnected() === true,
292
+ showControlsHint: multiplayerUi.showControlsHint !== false,
293
+ statusLine: currentStatusLine(sceneState, roomCode),
294
+ diagnostics,
295
+ });
296
+ }
297
+
298
+ function ensureHostCallbacks() {
299
+ if (sceneState.registeredHostCallbacks) return;
300
+ sceneState.registeredHostCallbacks = true;
301
+
302
+ aura.multiplayer.onPlayerJoin((player) => {
303
+ if (!player || !Number.isFinite(player.id)) return;
304
+ aura.multiplayer.setState(`player_${player.id}`, createLocalPlayerState(player.id, `p${player.id}`));
305
+ });
306
+
307
+ aura.multiplayer.onPlayerLeave((player) => {
308
+ if (!player || !Number.isFinite(player.id)) return;
309
+ const key = `player_${player.id}`;
310
+ const current = aura.multiplayer.getState(key) || createLocalPlayerState(player.id, `p${player.id}`);
311
+ aura.multiplayer.setState(key, { ...current, hidden: true });
312
+ });
313
+ }
314
+
315
+ function activateHost() {
316
+ if (sceneState.role === 'host') return;
317
+ const hostOptions = {
318
+ port: LOCAL_MULTIPLAYER_CONFIG.port,
319
+ roomCode: ROOM_CONFIG.roomCode,
320
+ name: ROOM_CONFIG.roomName,
321
+ maxPlayers: LOCAL_MULTIPLAYER_CONFIG.maxPlayers,
322
+ internetMode: CONNECTIVITY_CONFIG.mode,
323
+ };
324
+ if (CONNECTIVITY_CONFIG.relayHost) {
325
+ hostOptions.relay = CONNECTIVITY_CONFIG.relayHost;
326
+ }
327
+ if (CONNECTIVITY_CONFIG.coordinatorUrl) {
328
+ hostOptions.coordinatorUrl = CONNECTIVITY_CONFIG.coordinatorUrl;
329
+ }
330
+ if (CONNECTIVITY_CONFIG.relayUrl) {
331
+ hostOptions.relayUrl = CONNECTIVITY_CONFIG.relayUrl;
332
+ }
333
+ sceneState.hostedRoom = aura.multiplayer.host(hostOptions);
334
+ sceneState.role = 'host';
335
+ ensureHostCallbacks();
336
+ aura.multiplayer.setState('player_0', createLocalPlayerState(0, 'host'));
337
+ }
338
+
339
+ function resetRun() {
340
+ sceneState.role = 'pending';
341
+ sceneState.hostedRoom = null;
342
+ sceneState.pendingElapsed = 0;
343
+ sceneState.registeredHostCallbacks = false;
344
+ multiplayerSession.runsStarted += 1;
345
+ multiplayerUi.showControlsHint = true;
346
+ syncSessionState();
347
+ syncHud();
348
+ }
349
+
350
+ return {
351
+ id: 'gameplay',
352
+ playerPrefabId: 'player',
353
+ hudScreenId: 'hud',
354
+ dataFiles: [
355
+ 'aura.config.json',
356
+ 'config/gameplay/local-multiplayer.config.js',
357
+ 'content/gameplay/room-layout.js',
358
+ ],
359
+ summary: 'Room-code multiplayer starter. `npm run dev` hosts, `npm run join -- CODE` joins, `aura.config.json -> multiplayer` owns room/connectivity defaults, and the HUD surfaces live transport diagnostics.',
360
+
361
+ setup() {
362
+ assertLocalMultiplayerCapabilities();
363
+ aura.multiplayer.configure({
364
+ maxPlayers: LOCAL_MULTIPLAYER_CONFIG.maxPlayers,
365
+ tickRate: LOCAL_MULTIPLAYER_CONFIG.tickRate,
366
+ protocol: LOCAL_MULTIPLAYER_CONFIG.protocol,
367
+ });
368
+ resetRun();
369
+ console.log('{{PROJECT_TITLE}} room-code multiplayer starter ready');
370
+ },
371
+
372
+ update(dt) {
373
+ const frameDt = Math.max(0, Number(dt) || 0);
374
+ const input = readInput();
375
+ if (input.dx !== 0 || input.dy !== 0) {
376
+ multiplayerUi.showControlsHint = false;
377
+ }
378
+
379
+ if (sceneState.role === 'pending') {
380
+ sceneState.pendingElapsed += frameDt;
381
+ if (aura.multiplayer.isConnected()) {
382
+ sceneState.role = 'client';
383
+ } else if (!launchedViaExplicitJoin() && sceneState.pendingElapsed >= resolveHostFallbackSeconds()) {
384
+ activateHost();
385
+ }
386
+ }
387
+
388
+ if (sceneState.role === 'host') {
389
+ const hostState = aura.multiplayer.getState('player_0') || createLocalPlayerState(0, 'host');
390
+ aura.multiplayer.setState('player_0', applyLocalPlayerMovement(hostState, input, frameDt));
391
+
392
+ const inputs = aura.multiplayer.getAllPlayerInputs() || {};
393
+ for (const [playerId, playerInput] of Object.entries(inputs)) {
394
+ const numericId = Number(playerId);
395
+ const key = `player_${playerId}`;
396
+ const current = aura.multiplayer.getState(key) || createLocalPlayerState(numericId, `p${playerId}`);
397
+ aura.multiplayer.setState(key, applyLocalPlayerMovement(current, playerInput, frameDt));
398
+ }
399
+ } else if (sceneState.role === 'client' && aura.multiplayer.isConnected()) {
400
+ aura.multiplayer.sendInput(input);
401
+ }
402
+
403
+ syncSessionState();
404
+ syncHud();
405
+ },
406
+
407
+ onExit() {
408
+ context.clearHudScreen?.();
409
+ },
410
+
411
+ draw() {
412
+ aura.draw2d.clear(aura.rgb(0.07, 0.09, 0.13));
413
+
414
+ const allState = aura.multiplayer.getAllState() || {};
415
+ for (const key of Object.keys(allState).sort()) {
416
+ drawLocalPlayerState(allState[key], key);
417
+ }
418
+ },
419
+
420
+ getState() {
421
+ return {
422
+ role: sceneState.role,
423
+ pendingElapsed: sceneState.pendingElapsed,
424
+ hostedRoom: sceneState.hostedRoom
425
+ ? {
426
+ code: sceneState.hostedRoom.code || null,
427
+ name: sceneState.hostedRoom.name || null,
428
+ port: sceneState.hostedRoom.port || null,
429
+ }
430
+ : null,
431
+ };
432
+ },
433
+
434
+ notes: [
435
+ 'Keep the room-code multiplayer dev loop starter-owned here instead of splitting it between src/main.js and layout-generated files.',
436
+ 'Use appState.session for durable room/session summary, appState.ui for HUD presentation state, and keep connection-side details scene-local.',
437
+ 'aura.config.json -> multiplayer is the project-level source of truth for room code, room name, diagnostics, and relay defaults.',
438
+ 'config/gameplay/local-multiplayer.config.js stays focused on local gameplay tuning like port, player speed, bounds, and fallback timing.',
439
+ ],
440
+ };
441
+ }
442
+
443
+ export default createGameplayScene;
@@ -0,0 +1,17 @@
1
+ // {{PROJECT_TITLE}} - AuraJS local multiplayer starter bootstrap
2
+
3
+ import { createApp } from './runtime/app.js';
4
+
5
+ const app = createApp();
6
+
7
+ aura.setup = function () {
8
+ app.setup();
9
+ };
10
+
11
+ aura.update = function (dt) {
12
+ app.update(dt);
13
+ };
14
+
15
+ aura.draw = function () {
16
+ app.draw();
17
+ };
@@ -0,0 +1,28 @@
1
+ function hasMethod(obj, method) {
2
+ return Boolean(obj) && typeof obj[method] === 'function';
3
+ }
4
+
5
+ export function assertRuntimeCapabilities() {
6
+ const missing = [];
7
+ if (!hasMethod(aura.input, 'isKeyDown')) missing.push('aura.input.isKeyDown');
8
+ if (!hasMethod(aura.draw2d, 'clear')) missing.push('aura.draw2d.clear');
9
+ if (!hasMethod(aura.draw2d, 'rectFill')) missing.push('aura.draw2d.rectFill');
10
+ if (!hasMethod(aura.draw2d, 'text')) missing.push('aura.draw2d.text');
11
+ if (typeof aura.rgb !== 'function') missing.push('aura.rgb');
12
+ if (!hasMethod(aura.multiplayer, 'configure')) missing.push('aura.multiplayer.configure');
13
+ if (!hasMethod(aura.multiplayer, 'getAllPlayerInputs')) missing.push('aura.multiplayer.getAllPlayerInputs');
14
+ if (!hasMethod(aura.multiplayer, 'getAllState')) missing.push('aura.multiplayer.getAllState');
15
+ if (!hasMethod(aura.multiplayer, 'getPlayerCount')) missing.push('aura.multiplayer.getPlayerCount');
16
+ if (!hasMethod(aura.multiplayer, 'getRoomInfo')) missing.push('aura.multiplayer.getRoomInfo');
17
+ if (!hasMethod(aura.multiplayer, 'getState')) missing.push('aura.multiplayer.getState');
18
+ if (!hasMethod(aura.multiplayer, 'host')) missing.push('aura.multiplayer.host');
19
+ if (!hasMethod(aura.multiplayer, 'isConnected')) missing.push('aura.multiplayer.isConnected');
20
+ if (!hasMethod(aura.multiplayer, 'onPlayerJoin')) missing.push('aura.multiplayer.onPlayerJoin');
21
+ if (!hasMethod(aura.multiplayer, 'onPlayerLeave')) missing.push('aura.multiplayer.onPlayerLeave');
22
+ if (!hasMethod(aura.multiplayer, 'sendInput')) missing.push('aura.multiplayer.sendInput');
23
+ if (!hasMethod(aura.multiplayer, 'setState')) missing.push('aura.multiplayer.setState');
24
+
25
+ if (missing.length > 0) {
26
+ throw new Error(`[local-multiplayer-template] runtime missing required APIs: ${missing.join(', ')} [reason:missing_runtime_api]`);
27
+ }
28
+ }
@@ -0,0 +1,60 @@
1
+ import { LOCAL_MULTIPLAYER_CONFIG } from '../config/gameplay/local-multiplayer.config.js';
2
+ import { ROOM_LAYOUT } from '../content/gameplay/room-layout.js';
3
+
4
+ const hudScreen = {
5
+ id: 'hud',
6
+ kind: 'ui-screen',
7
+ role: 'hud',
8
+ draw: drawLocalMultiplayerHud,
9
+ notes: [
10
+ 'Keep multiplayer room status and join guidance in the authored HUD instead of baking it into src/main.js.',
11
+ 'This starter keeps room-code guidance in one place and makes the local-first default plus the one-field relay upgrade path explicit in authored copy.',
12
+ ],
13
+ };
14
+
15
+ export function createHudScreen() {
16
+ return hudScreen;
17
+ }
18
+
19
+ export function drawLocalMultiplayerHud({
20
+ role = 'WAITING',
21
+ roomCode = ROOM_LAYOUT.roomCode,
22
+ roomName = ROOM_LAYOUT.roomName,
23
+ playerCount = 0,
24
+ connected = false,
25
+ showControlsHint = true,
26
+ statusLine = '',
27
+ diagnostics = null,
28
+ } = {}) {
29
+ aura.draw2d.text('{{PROJECT_TITLE}} Multiplayer', 18, 16, 18, aura.rgb(0.97, 0.94, 0.82));
30
+ aura.draw2d.text(`Role: ${role}`, 18, 42, 12, aura.rgb(0.84, 0.92, 0.98));
31
+ aura.draw2d.text(`Room code: ${roomCode}`, 18, 58, 12, aura.rgb(0.84, 0.92, 0.98));
32
+ aura.draw2d.text(`Players: ${playerCount}`, 18, 74, 12, aura.rgb(0.8, 0.92, 0.84));
33
+ aura.draw2d.text(statusLine, 18, 104, 11, aura.rgb(0.92, 0.88, 0.76));
34
+ aura.draw2d.text(`Room name: ${roomName}`, 18, 122, 11, aura.rgb(0.8, 0.86, 0.96));
35
+
36
+ if (showControlsHint) {
37
+ aura.draw2d.text('Move: Arrow keys / WASD', 18, LOCAL_MULTIPLAYER_CONFIG.height - 46, 11, aura.rgb(0.78, 0.9, 0.84));
38
+ }
39
+
40
+ if (diagnostics && typeof diagnostics === 'object') {
41
+ const pingLabel = Number.isFinite(diagnostics.pingMs) ? `${Math.round(diagnostics.pingMs)} ms` : '-';
42
+ aura.draw2d.text(`Mode: ${diagnostics.mode || '-'}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 42, 11, aura.rgb(0.82, 0.9, 0.86));
43
+ aura.draw2d.text(`Path: ${diagnostics.transportPath || '-'}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 58, 11, aura.rgb(0.82, 0.9, 0.86));
44
+ aura.draw2d.text(`Status: ${diagnostics.transportStatus || '-'}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 74, 11, aura.rgb(0.82, 0.9, 0.86));
45
+ aura.draw2d.text(`Join: ${diagnostics.joinPath || '-'}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 90, 11, aura.rgb(0.82, 0.9, 0.86));
46
+ aura.draw2d.text(`Ping: ${pingLabel}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 106, 11, aura.rgb(0.82, 0.9, 0.86));
47
+ aura.draw2d.text(`Reason: ${diagnostics.lastReasonCode || '-'}`, LOCAL_MULTIPLAYER_CONFIG.width - 248, 122, 11, aura.rgb(0.82, 0.9, 0.86));
48
+ }
49
+
50
+ aura.draw2d.text(
51
+ 'Room-code multiplayer. Put room + relay defaults in aura.config.json -> multiplayer.',
52
+ 18,
53
+ LOCAL_MULTIPLAYER_CONFIG.height - 28,
54
+ 10,
55
+ aura.rgb(0.76, 0.82, 0.92),
56
+ );
57
+ aura.draw2d.text(`Connected: ${connected}`, LOCAL_MULTIPLAYER_CONFIG.width - 166, 16, 11, aura.rgb(0.82, 0.9, 0.86));
58
+ }
59
+
60
+ export default hudScreen;