@auraindustry/aurajs 0.0.7 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/README.md +98 -2
  2. package/benchmarks/perf-thresholds.json +54 -0
  3. package/package.json +4 -7
  4. package/src/asset-pack.mjs +8 -1
  5. package/src/authored-project.mjs +1449 -0
  6. package/src/authored-runtime.mjs +2016 -0
  7. package/src/authoring/avatar-animation-graph.mjs +648 -0
  8. package/src/bin-integrity.mjs +272 -0
  9. package/src/build-contract/assets.mjs +130 -0
  10. package/src/build-contract/capabilities.mjs +116 -0
  11. package/src/build-contract/constants.mjs +6 -0
  12. package/src/build-contract/helpers.mjs +44 -0
  13. package/src/build-contract/web-templates.mjs +5993 -0
  14. package/src/build-contract.mjs +27 -2910
  15. package/src/bundler.mjs +188 -55
  16. package/src/cli.mjs +4840 -1512
  17. package/src/commands/project-authoring.mjs +454 -0
  18. package/src/config.mjs +44 -0
  19. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
  20. package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
  21. package/src/conformance/cases/index.mjs +11 -0
  22. package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
  23. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
  24. package/src/conformance/shared.mjs +27 -0
  25. package/src/conformance-runner.mjs +25 -13
  26. package/src/conformance.mjs +619 -4020
  27. package/src/cutscene.mjs +362 -5
  28. package/src/dev-cli-action.mjs +249 -0
  29. package/src/dev-cli-inspect.mjs +92 -0
  30. package/src/dev-cli-state.mjs +80 -0
  31. package/src/external-asset-cache.mjs +587 -0
  32. package/src/external-asset-policy.mjs +217 -0
  33. package/src/external-package-surface.mjs +206 -0
  34. package/src/game-action-runtime.mjs +869 -0
  35. package/src/game-state-runtime.mjs +206 -6
  36. package/src/headless-action.mjs +186 -0
  37. package/src/headless-test/runtime-animation.mjs +1173 -0
  38. package/src/headless-test/runtime-coordinator.mjs +1514 -0
  39. package/src/headless-test/runtime-primitives.mjs +320 -0
  40. package/src/headless-test/runtime-world.mjs +2253 -0
  41. package/src/headless-test.mjs +392 -4298
  42. package/src/host-binary.mjs +342 -14
  43. package/src/icon-discovery.mjs +64 -0
  44. package/src/make-catalog.mjs +109 -0
  45. package/src/make.mjs +197 -0
  46. package/src/package-integrity.mjs +586 -0
  47. package/src/perf-benchmark.mjs +353 -0
  48. package/src/postinstall.mjs +5 -5
  49. package/src/prefabs/index.mjs +34 -0
  50. package/src/prefabs/scene-serialization.mjs +184 -0
  51. package/src/project-importer.mjs +620 -0
  52. package/src/project-registry.mjs +24 -0
  53. package/src/publish-command.mjs +195 -0
  54. package/src/publish-env-example.mjs +83 -0
  55. package/src/publish-validation.mjs +708 -0
  56. package/src/retro/assets/compile.mjs +232 -0
  57. package/src/retro/backend-gba/authoring.mjs +1029 -0
  58. package/src/retro/backend-gba/rom.mjs +363 -0
  59. package/src/retro/backend-gbc/rom.mjs +85 -0
  60. package/src/retro/build.mjs +278 -0
  61. package/src/retro/cli/commands.mjs +292 -0
  62. package/src/retro/cli/templates.mjs +84 -0
  63. package/src/retro/diagnostics/catalog.mjs +110 -0
  64. package/src/retro/diagnostics/emit.mjs +72 -0
  65. package/src/retro/emulator/case-overlay.mjs +64 -0
  66. package/src/retro/emulator/discovery.mjs +158 -0
  67. package/src/retro/emulator/macos-case-overlay.swift +220 -0
  68. package/src/retro/emulator/profiles.mjs +146 -0
  69. package/src/retro/emulator/runner.mjs +289 -0
  70. package/src/retro/frontend/load-project.mjs +98 -0
  71. package/src/retro/index.mjs +30 -0
  72. package/src/retro/ir/build-ir.mjs +108 -0
  73. package/src/retro/runtime-gba/contract.mjs +151 -0
  74. package/src/retro/runtime-gbc/contract.mjs +117 -0
  75. package/src/retro/shared/span.mjs +26 -0
  76. package/src/retro/shared/targets.mjs +64 -0
  77. package/src/retro/validator/check-project.mjs +114 -0
  78. package/src/runtime-hotspot-audit.mjs +707 -0
  79. package/src/scaffold/config.mjs +1000 -0
  80. package/src/scaffold/fs.mjs +56 -0
  81. package/src/scaffold/layout.mjs +318 -0
  82. package/src/scaffold/project-docs.mjs +439 -0
  83. package/src/scaffold.mjs +93 -596
  84. package/src/scene-composition/index.mjs +326 -0
  85. package/src/scene-composition/runtime.mjs +751 -0
  86. package/src/self-hosted-assets.mjs +604 -0
  87. package/src/session-client.mjs +750 -0
  88. package/src/session-native-launcher.mjs +74 -0
  89. package/src/session-protocol.mjs +75 -0
  90. package/src/session-runtime.mjs +321 -0
  91. package/src/session-server.mjs +360 -0
  92. package/src/shader-kits/index.mjs +773 -0
  93. package/src/starter-content-registry.mjs +292 -0
  94. package/src/state-artifacts.mjs +662 -24
  95. package/src/state-dev-reload.mjs +99 -2
  96. package/src/terminal-ui.mjs +245 -0
  97. package/src/web-conformance.mjs +219 -0
  98. package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
  99. package/templates/create/2d/content/gameplay/waves.json +26 -0
  100. package/templates/create/2d/content/registries/.gitkeep +1 -0
  101. package/templates/create/2d/docs/design/.gitkeep +1 -0
  102. package/templates/create/2d/docs/design/loop.md +5 -0
  103. package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
  104. package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
  105. package/templates/create/2d/prefabs/player.prefab.js +36 -0
  106. package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
  107. package/templates/create/2d/scenes/boot.scene.js +12 -0
  108. package/templates/create/2d/scenes/gameplay.scene.js +230 -0
  109. package/templates/create/2d/scenes/menu.scene.js +9 -0
  110. package/templates/create/2d/src/main.js +6 -185
  111. package/templates/create/2d/src/runtime/app.js +49 -0
  112. package/templates/create/2d/src/runtime/capabilities.js +35 -0
  113. package/templates/create/2d/ui/hud.screen.js +40 -0
  114. package/templates/create/2d/ui/pause.screen.js +149 -0
  115. package/templates/create/2d/ui/settings.screen.js +347 -0
  116. package/templates/create/2d/ui/title.screen.js +13 -0
  117. package/templates/create/2d-adventure/aura.config.json +28 -0
  118. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
  119. package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
  120. package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
  121. package/templates/create/2d-adventure/docs/design/loop.md +5 -0
  122. package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
  123. package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
  124. package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
  125. package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
  126. package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
  127. package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
  128. package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
  129. package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
  130. package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
  131. package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
  132. package/templates/create/2d-survivor/docs/design/loop.md +5 -0
  133. package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
  134. package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
  135. package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
  136. package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
  137. package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
  138. package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
  139. package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
  140. package/templates/create/2d-survivor/src/main.js +5 -332
  141. package/templates/create/2d-survivor/src/runtime/app.js +49 -0
  142. package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
  143. package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
  144. package/templates/create/2d-survivor/ui/title.screen.js +13 -0
  145. package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
  146. package/templates/create/3d/config/gameplay/.gitkeep +1 -0
  147. package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
  148. package/templates/create/3d/content/gameplay/course.js +40 -0
  149. package/templates/create/3d/content/registries/.gitkeep +1 -0
  150. package/templates/create/3d/docs/design/.gitkeep +1 -0
  151. package/templates/create/3d/docs/design/loop.md +5 -0
  152. package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
  153. package/templates/create/3d/prefabs/player.prefab.js +204 -0
  154. package/templates/create/3d/prefabs/world.prefab.js +112 -0
  155. package/templates/create/3d/scenes/boot.scene.js +12 -0
  156. package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
  157. package/templates/create/3d/scenes/gameplay.scene.js +292 -0
  158. package/templates/create/3d/src/main.js +6 -295
  159. package/templates/create/3d/src/runtime/app.js +49 -0
  160. package/templates/create/3d/src/runtime/capabilities.js +53 -0
  161. package/templates/create/3d/src/runtime/materials.js +34 -0
  162. package/templates/create/3d/src/runtime/state.js +39 -0
  163. package/templates/create/3d/ui/hud.screen.js +75 -0
  164. package/templates/create/3d/ui/pause.screen.js +166 -0
  165. package/templates/create/3d/ui/settings.screen.js +387 -0
  166. package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
  167. package/templates/create/3d-adventure/aura.config.json +28 -0
  168. package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
  169. package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
  170. package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
  171. package/templates/create/3d-adventure/docs/design/loop.md +5 -0
  172. package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
  173. package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
  174. package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
  175. package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
  176. package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
  177. package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
  178. package/templates/create/3d-adventure/src/runtime/state.js +31 -0
  179. package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
  180. package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
  181. package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
  182. package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
  183. package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
  184. package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
  185. package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
  186. package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
  187. package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
  188. package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
  189. package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
  190. package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
  191. package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
  192. package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
  193. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
  194. package/templates/create/3d-collectathon/src/main.js +5 -355
  195. package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
  196. package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
  197. package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
  198. package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
  199. package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
  200. package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
  201. package/templates/create/blank/config/gameplay/.gitkeep +1 -0
  202. package/templates/create/blank/content/gameplay/.gitkeep +1 -0
  203. package/templates/create/blank/content/registries/.gitkeep +1 -0
  204. package/templates/create/blank/docs/design/.gitkeep +1 -0
  205. package/templates/create/blank/docs/design/loop.md +5 -0
  206. package/templates/create/blank/prefabs/.gitkeep +1 -0
  207. package/templates/create/blank/scenes/.gitkeep +1 -0
  208. package/templates/create/blank/src/runtime/.gitkeep +1 -0
  209. package/templates/create/blank/ui/.gitkeep +1 -0
  210. package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
  211. package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
  212. package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
  213. package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
  214. package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
  215. package/templates/create/deckbuilder-2d/aura.config.json +28 -0
  216. package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
  217. package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
  218. package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
  219. package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
  220. package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
  221. package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
  222. package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
  223. package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
  224. package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
  225. package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
  226. package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
  227. package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
  228. package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
  229. package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
  230. package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
  231. package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
  232. package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
  233. package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
  234. package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
  235. package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
  236. package/templates/create/deckbuilder-2d/src/main.js +17 -0
  237. package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
  238. package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
  239. package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
  240. package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
  241. package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
  242. package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
  243. package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
  244. package/templates/create/local-multiplayer/aura.config.json +41 -0
  245. package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
  246. package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
  247. package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
  248. package/templates/create/local-multiplayer/docs/design/loop.md +16 -0
  249. package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
  250. package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
  251. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +472 -0
  252. package/templates/create/local-multiplayer/src/main.js +17 -0
  253. package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
  254. package/templates/create/local-multiplayer/ui/hud.screen.js +65 -0
  255. package/templates/create/shared/src/runtime/project-inspector.js +105 -0
  256. package/templates/create/shared/src/runtime/scene-flow.js +290 -0
  257. package/templates/create/shared/src/runtime/screen-shell.js +222 -0
  258. package/templates/create/shared/src/runtime/ui-forms.js +209 -0
  259. package/templates/create/shared/src/runtime/ui-settings.js +237 -0
  260. package/templates/create/shared/src/runtime/ui-theme.js +352 -0
  261. package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
  262. package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
  263. package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
  264. package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
  265. package/templates/create/shared/src/starter-utils/core.js +39 -3
  266. package/templates/create/shared/src/starter-utils/index.js +8 -2
  267. package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
  268. package/templates/create/shared/src/starter-utils/triggers.js +662 -0
  269. package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
  270. package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
  271. package/templates/create/video-cutscene/aura.config.json +28 -0
  272. package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
  273. package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
  274. package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
  275. package/templates/create/video-cutscene/docs/design/loop.md +22 -0
  276. package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
  277. package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
  278. package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
  279. package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
  280. package/templates/create/video-cutscene/src/main.js +17 -0
  281. package/templates/create/video-cutscene/src/runtime/app.js +52 -0
  282. package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
  283. package/templates/create/video-cutscene/src/runtime/state.js +13 -0
  284. package/templates/create/video-cutscene/ui/.gitkeep +0 -0
  285. package/templates/create-bin/play.js +1192 -0
  286. package/templates/make/README.md +46 -0
  287. package/templates/make/catalog.json +51 -0
  288. package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
  289. package/templates/make/component/manifest.json +9 -0
  290. package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
  291. package/templates/make/data/manifest.json +9 -0
  292. package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
  293. package/templates/make/material/manifest.json +9 -0
  294. package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
  295. package/templates/make/prefab/manifest.json +9 -0
  296. package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
  297. package/templates/make/scene/manifest.json +9 -0
  298. package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
  299. package/templates/make/shader/manifest.json +9 -0
  300. package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
  301. package/templates/make/system/manifest.json +9 -0
  302. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
  303. package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
  304. package/templates/make/ui-screen/manifest.json +10 -0
  305. package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
  306. package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
  307. package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
  308. package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
  309. package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
  310. package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
  311. package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
  312. package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
  313. package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
  314. package/templates/retro/platformer/README.md +10 -0
  315. package/templates/retro/platformer/assets/retro/assets.json +91 -0
  316. package/templates/retro/platformer/aura.config.json +7 -0
  317. package/templates/retro/platformer/package.json +5 -0
  318. package/templates/retro/platformer/src/main.js +40 -0
  319. package/templates/retro/puzzle-grid/README.md +10 -0
  320. package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
  321. package/templates/retro/puzzle-grid/aura.config.json +7 -0
  322. package/templates/retro/puzzle-grid/package.json +5 -0
  323. package/templates/retro/puzzle-grid/src/main.js +29 -0
  324. package/templates/retro/tactics-grid/README.md +10 -0
  325. package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
  326. package/templates/retro/tactics-grid/aura.config.json +7 -0
  327. package/templates/retro/tactics-grid/package.json +5 -0
  328. package/templates/retro/tactics-grid/src/main.js +35 -0
  329. package/templates/retro/topdown-adventure/README.md +10 -0
  330. package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
  331. package/templates/retro/topdown-adventure/aura.config.json +7 -0
  332. package/templates/retro/topdown-adventure/package.json +5 -0
  333. package/templates/retro/topdown-adventure/src/main.js +29 -0
  334. package/templates/skills/aurajs/SKILL.md +61 -5
@@ -0,0 +1,110 @@
1
+ export const RETRO_DIAGNOSTIC_DEFINITIONS = Object.freeze({
2
+ retro_module_syntax_error: Object.freeze({
3
+ message: 'Module syntax is not valid for the Aura Retro compiler lane.',
4
+ hint: 'Fix the syntax error before retrying the retro target.',
5
+ doc: 'docs/retro/diagnostics.md#syntax-and-module-loading',
6
+ }),
7
+ retro_module_not_found: Object.freeze({
8
+ message: 'A referenced module could not be resolved inside the retro source boundary.',
9
+ hint: 'Use relative imports that stay inside the authored source boundary.',
10
+ doc: 'docs/retro/diagnostics.md#syntax-and-module-loading',
11
+ }),
12
+ retro_import_specifier_unsupported: Object.freeze({
13
+ message: 'Only static relative imports are supported in Aura Retro.',
14
+ hint: 'Replace package or bare specifiers with relative source modules.',
15
+ doc: 'docs/retro/language-subset.md#module-rules',
16
+ }),
17
+ retro_import_path_escape: Object.freeze({
18
+ message: 'Retro source files must stay inside the authored source boundary.',
19
+ hint: 'Move the imported module into the project source boundary or remove the import.',
20
+ doc: 'docs/retro/language-subset.md#module-rules',
21
+ }),
22
+ retro_dynamic_import_unsupported: Object.freeze({
23
+ message: 'Dynamic import() is not supported in Aura Retro.',
24
+ hint: 'Use static imports so the compiler can build a deterministic cartridge graph.',
25
+ doc: 'docs/retro/language-subset.md#forbidden-constructs',
26
+ }),
27
+ retro_circular_dependency_unsupported: Object.freeze({
28
+ message: 'Circular module dependencies are not supported in Aura Retro.',
29
+ hint: 'Break the cycle so setup, update, and draw dependencies stay explicit.',
30
+ doc: 'docs/retro/diagnostics.md#syntax-and-module-loading',
31
+ }),
32
+ retro_async_unsupported: Object.freeze({
33
+ message: 'Async control flow is not supported in Aura Retro.',
34
+ hint: 'Rewrite the logic to use synchronous state updates.',
35
+ doc: 'docs/retro/language-subset.md#forbidden-constructs',
36
+ }),
37
+ retro_eval_unsupported: Object.freeze({
38
+ message: 'Runtime code evaluation is not supported in Aura Retro.',
39
+ hint: 'Use plain functions and explicit state instead of eval.',
40
+ doc: 'docs/retro/language-subset.md#forbidden-constructs',
41
+ }),
42
+ retro_dynamic_property_access_unsupported: Object.freeze({
43
+ message: 'Dynamic computed property access is not supported in Aura Retro.',
44
+ hint: 'Use explicit object fields or fixed lookup tables with static keys.',
45
+ doc: 'docs/retro/language-subset.md#memory-shape-and-mutation',
46
+ }),
47
+ retro_runtime_asset_load_unsupported: Object.freeze({
48
+ message: 'Runtime asset loading is not supported in Aura Retro.',
49
+ hint: 'Move assets into compile-time project content instead of loading them at runtime.',
50
+ doc: 'docs/retro/api-subset.md#excluded-namespaces-and-behaviors',
51
+ }),
52
+ retro_3d_api_unsupported: Object.freeze({
53
+ message: 'Current AuraScript 3D APIs are outside the Aura Retro subset.',
54
+ hint: 'Stay inside the 2D tile, sprite, text, and audio subset for retro targets.',
55
+ doc: 'docs/retro/api-subset.md#excluded-namespaces-and-behaviors',
56
+ }),
57
+ retro_desktop_api_unsupported: Object.freeze({
58
+ message: 'Desktop-only Aura APIs are outside the Aura Retro subset.',
59
+ hint: 'Remove filesystem, networking, multiplayer, and platform-specific calls from retro builds.',
60
+ doc: 'docs/retro/api-subset.md#excluded-namespaces-and-behaviors',
61
+ }),
62
+ retro_gb_color_feature_unsupported: Object.freeze({
63
+ message: 'The original GB target does not support GBC-only color feature usage.',
64
+ hint: 'Build with `--target gbc` or remove palette/color-specific authored data.',
65
+ doc: 'docs/retro/target-profiles.md#gb-vs-gbc',
66
+ }),
67
+ retro_asset_format_unsupported: Object.freeze({
68
+ message: 'Retro asset content does not match the Aura Retro asset manifest contract.',
69
+ hint: 'Fix the asset manifest shape or pixel rows so the selected retro target can compile deterministic assets.',
70
+ doc: 'docs/retro/diagnostics.md#target-and-assets',
71
+ }),
72
+ retro_asset_budget_exceeded: Object.freeze({
73
+ message: 'Retro asset content exceeds the selected target profile budget.',
74
+ hint: 'Reduce palettes, tiles, sprites, or font glyph counts until the project fits the target profile.',
75
+ doc: 'docs/retro/diagnostics.md#target-and-assets',
76
+ }),
77
+ retro_build_blocked_by_diagnostics: Object.freeze({
78
+ message: 'Aura Retro build is blocked by compiler diagnostics.',
79
+ hint: 'Run `aura retro check` first and fix the reported unsupported code.',
80
+ doc: 'docs/retro/diagnostics.md#payload-shape',
81
+ }),
82
+ retro_emulator_not_found: Object.freeze({
83
+ message: 'No supported Aura Retro emulator was found.',
84
+ hint: 'Install mGBA or pass an explicit emulator path with `aura retro run --emulator <path>`.',
85
+ doc: 'docs/retro/emulator.md#discovery-order',
86
+ }),
87
+ retro_emulator_target_unsupported: Object.freeze({
88
+ message: 'The selected emulator does not support the requested Aura Retro target.',
89
+ hint: 'Switch to an emulator that supports the target or change the build target.',
90
+ doc: 'docs/retro/emulator.md#supported-emulators',
91
+ }),
92
+ retro_emulator_rom_not_found: Object.freeze({
93
+ message: 'Aura Retro could not find a ROM artifact to launch.',
94
+ hint: 'Run `aura build --target <gb|gbc|gba>` first or pass `--rom <path>`.',
95
+ doc: 'docs/retro/emulator.md#run-and-smoke-commands',
96
+ }),
97
+ retro_emulator_launch_failed: Object.freeze({
98
+ message: 'Aura Retro failed to launch the selected emulator.',
99
+ hint: 'Check the emulator path, ROM path, and CLI flags, then retry with `aura retro smoke --json` for details.',
100
+ doc: 'docs/retro/emulator.md#run-and-smoke-commands',
101
+ }),
102
+ });
103
+
104
+ export function getRetroDiagnosticDefinition(reasonCode) {
105
+ return RETRO_DIAGNOSTIC_DEFINITIONS[reasonCode] || {
106
+ message: 'Aura Retro reported an unknown compiler diagnostic.',
107
+ hint: 'Inspect the diagnostic payload and update the compiler contract if needed.',
108
+ doc: 'docs/retro/diagnostics.md',
109
+ };
110
+ }
@@ -0,0 +1,72 @@
1
+ import { getRetroDiagnosticDefinition } from './catalog.mjs';
2
+ import { relFile } from '../shared/span.mjs';
3
+
4
+ export class RetroCompileError extends Error {
5
+ constructor(reasonCode, message, details = {}) {
6
+ super(message);
7
+ this.name = 'RetroCompileError';
8
+ this.reasonCode = reasonCode;
9
+ this.details = details;
10
+ }
11
+ }
12
+
13
+ export function createRetroDiagnostic({
14
+ reasonCode,
15
+ filePath,
16
+ span,
17
+ message = null,
18
+ hint = null,
19
+ doc = null,
20
+ source = null,
21
+ }) {
22
+ const definition = getRetroDiagnosticDefinition(reasonCode);
23
+ return {
24
+ severity: 'error',
25
+ reasonCode,
26
+ filePath,
27
+ span: span || null,
28
+ message: message || definition.message,
29
+ hint: hint || definition.hint,
30
+ doc: doc || definition.doc,
31
+ source: source || 'retro-validator',
32
+ };
33
+ }
34
+
35
+ export function formatRetroDiagnostic(diagnostic, projectRoot = process.cwd()) {
36
+ const file = diagnostic.filePath ? relFile(projectRoot, diagnostic.filePath) : '<unknown>';
37
+ const span = diagnostic.span && diagnostic.span.line
38
+ ? `${file}:${diagnostic.span.line}:${diagnostic.span.column || 1}`
39
+ : file;
40
+ const detail = [`${diagnostic.reasonCode}: ${diagnostic.message}`, `at ${span}`];
41
+ if (diagnostic.hint) {
42
+ detail.push(`hint: ${diagnostic.hint}`);
43
+ }
44
+ if (diagnostic.doc) {
45
+ detail.push(`docs: ${diagnostic.doc}`);
46
+ }
47
+ return detail.join('\n');
48
+ }
49
+
50
+ export function formatRetroDiagnosticReport(report, projectRoot = process.cwd()) {
51
+ const lines = [];
52
+ const diagnostics = Array.isArray(report?.diagnostics) ? report.diagnostics : [];
53
+ lines.push(
54
+ report?.ok
55
+ ? `Aura Retro check passed (${report.target}, modules=${report.moduleCount}).`
56
+ : `Aura Retro check failed (${report?.target || 'unknown'}, diagnostics=${diagnostics.length}).`,
57
+ );
58
+ for (const diagnostic of diagnostics) {
59
+ lines.push('');
60
+ lines.push(formatRetroDiagnostic(diagnostic, projectRoot));
61
+ }
62
+ return lines.join('\n');
63
+ }
64
+
65
+ export function formatRetroCompileError(error, projectRoot = process.cwd()) {
66
+ const diagnostics = Array.isArray(error?.details?.diagnostics) ? error.details.diagnostics : [];
67
+ const header = `${error.reasonCode || 'retro_compile_failed'}: ${error.message}`;
68
+ if (diagnostics.length === 0) {
69
+ return header;
70
+ }
71
+ return [header, ...diagnostics.map((diagnostic) => formatRetroDiagnostic(diagnostic, projectRoot))].join('\n\n');
72
+ }
@@ -0,0 +1,64 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { spawn } from 'node:child_process';
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ const DEFAULT_SWIFT_HELPER_PATH = resolve(here, 'macos-case-overlay.swift');
8
+ const SUPPORTED_PROFILE_IDS = new Set(['mgba', 'mgba-qt']);
9
+
10
+ function isSupportedProfile(profileId) {
11
+ return SUPPORTED_PROFILE_IDS.has(String(profileId || '').trim());
12
+ }
13
+
14
+ function resolveCaseHelperPath() {
15
+ const override = String(process.env.AURA_RETRO_CASE_HELPER || '').trim();
16
+ if (override) {
17
+ return resolve(process.cwd(), override);
18
+ }
19
+ return DEFAULT_SWIFT_HELPER_PATH;
20
+ }
21
+
22
+ function createHelperArgs({ emulatorPid, caseStyle = 'gba' }) {
23
+ return [
24
+ '--pid',
25
+ String(emulatorPid),
26
+ '--style',
27
+ String(caseStyle || 'gba'),
28
+ ];
29
+ }
30
+
31
+ export function shouldUseRetroCaseOverlay({ caseEnabled = false, profileId = null, headless = false } = {}) {
32
+ return Boolean(caseEnabled)
33
+ && process.platform === 'darwin'
34
+ && !headless
35
+ && isSupportedProfile(profileId);
36
+ }
37
+
38
+ export function launchRetroCaseOverlay({
39
+ emulatorPid,
40
+ profileId,
41
+ caseStyle = 'gba',
42
+ stdio = 'ignore',
43
+ } = {}) {
44
+ if (!shouldUseRetroCaseOverlay({ caseEnabled: true, profileId, headless: false })) {
45
+ return null;
46
+ }
47
+ const helperPath = resolveCaseHelperPath();
48
+ if (!existsSync(helperPath)) {
49
+ return null;
50
+ }
51
+ const command = helperPath.endsWith('.swift')
52
+ ? 'swift'
53
+ : helperPath;
54
+ const args = helperPath.endsWith('.swift')
55
+ ? [helperPath, ...createHelperArgs({ emulatorPid, caseStyle })]
56
+ : createHelperArgs({ emulatorPid, caseStyle });
57
+ const child = spawn(command, args, {
58
+ cwd: process.cwd(),
59
+ stdio,
60
+ env: process.env,
61
+ });
62
+ child.once('error', () => {});
63
+ return child;
64
+ }
@@ -0,0 +1,158 @@
1
+ import { accessSync, constants, existsSync } from 'node:fs';
2
+ import { delimiter, extname, isAbsolute, join, resolve } from 'node:path';
3
+ import {
4
+ buildRetroEmulatorArgs,
5
+ detectRetroEmulatorProfile,
6
+ getRetroEmulatorCandidateNames,
7
+ supportsRetroEmulatorTarget,
8
+ } from './profiles.mjs';
9
+
10
+ function isExecutableFile(filePath) {
11
+ try {
12
+ accessSync(filePath, constants.X_OK);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ function isExplicitCommand(value) {
20
+ const normalized = String(value || '').trim();
21
+ return normalized.includes('/') || normalized.includes('\\') || normalized.startsWith('.');
22
+ }
23
+
24
+ function getWindowsExecutableVariants(commandName) {
25
+ if (process.platform !== 'win32') {
26
+ return [commandName];
27
+ }
28
+ const pathExt = String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
29
+ .split(';')
30
+ .map((entry) => entry.trim())
31
+ .filter(Boolean);
32
+ if (extname(commandName)) {
33
+ return [commandName];
34
+ }
35
+ return pathExt.map((entry) => `${commandName}${entry.toLowerCase()}`);
36
+ }
37
+
38
+ function resolveCommandPath(commandName) {
39
+ const normalized = String(commandName || '').trim();
40
+ if (!normalized) {
41
+ return null;
42
+ }
43
+ if (isExplicitCommand(normalized)) {
44
+ const absolute = isAbsolute(normalized) ? normalized : resolve(process.cwd(), normalized);
45
+ return existsSync(absolute) && isExecutableFile(absolute) ? absolute : null;
46
+ }
47
+ const pathEntries = String(process.env.PATH || '')
48
+ .split(delimiter)
49
+ .map((entry) => entry.trim())
50
+ .filter(Boolean);
51
+ for (const directory of pathEntries) {
52
+ for (const candidateName of getWindowsExecutableVariants(normalized)) {
53
+ const candidatePath = join(directory, candidateName);
54
+ if (existsSync(candidatePath) && isExecutableFile(candidatePath)) {
55
+ return candidatePath;
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ function buildDiscoveryRecord({ source, command, executablePath, profile }) {
63
+ return {
64
+ source,
65
+ command,
66
+ executablePath,
67
+ profileId: profile?.id || 'custom',
68
+ };
69
+ }
70
+
71
+ function resolveExplicitEmulator(command, target) {
72
+ const executablePath = resolveCommandPath(command);
73
+ if (!executablePath) {
74
+ return {
75
+ emulator: null,
76
+ checked: [buildDiscoveryRecord({ source: 'explicit', command, executablePath: null, profile: null })],
77
+ };
78
+ }
79
+ const profile = detectRetroEmulatorProfile(executablePath);
80
+ if (profile && !supportsRetroEmulatorTarget(profile, target)) {
81
+ return {
82
+ emulator: null,
83
+ checked: [buildDiscoveryRecord({ source: 'explicit', command, executablePath, profile })],
84
+ unsupportedProfile: profile,
85
+ };
86
+ }
87
+ return {
88
+ emulator: {
89
+ command,
90
+ executablePath,
91
+ profile,
92
+ argsFor(options) {
93
+ return buildRetroEmulatorArgs(profile, options);
94
+ },
95
+ },
96
+ checked: [buildDiscoveryRecord({ source: 'explicit', command, executablePath, profile })],
97
+ };
98
+ }
99
+
100
+ export function discoverRetroEmulator({
101
+ target,
102
+ explicitEmulator = null,
103
+ smoke = false,
104
+ env = process.env,
105
+ } = {}) {
106
+ const checked = [];
107
+ const requested = String(explicitEmulator || '').trim();
108
+ if (requested) {
109
+ const result = resolveExplicitEmulator(requested, target);
110
+ return {
111
+ emulator: result.emulator,
112
+ checked: checked.concat(result.checked),
113
+ unsupportedProfile: result.unsupportedProfile || null,
114
+ };
115
+ }
116
+
117
+ const envCommand = String(env.AURA_RETRO_EMULATOR || '').trim();
118
+ if (envCommand) {
119
+ const result = resolveExplicitEmulator(envCommand, target);
120
+ return {
121
+ emulator: result.emulator,
122
+ checked: checked.concat(result.checked.map((entry) => ({ ...entry, source: 'env' }))),
123
+ unsupportedProfile: result.unsupportedProfile || null,
124
+ };
125
+ }
126
+
127
+ for (const commandName of getRetroEmulatorCandidateNames({ target, smoke })) {
128
+ const executablePath = resolveCommandPath(commandName);
129
+ const profile = executablePath ? detectRetroEmulatorProfile(executablePath) : null;
130
+ checked.push(buildDiscoveryRecord({
131
+ source: 'candidate',
132
+ command: commandName,
133
+ executablePath,
134
+ profile,
135
+ }));
136
+ if (!executablePath) {
137
+ continue;
138
+ }
139
+ return {
140
+ emulator: {
141
+ command: commandName,
142
+ executablePath,
143
+ profile,
144
+ argsFor(options) {
145
+ return buildRetroEmulatorArgs(profile, options);
146
+ },
147
+ },
148
+ checked,
149
+ unsupportedProfile: null,
150
+ };
151
+ }
152
+
153
+ return {
154
+ emulator: null,
155
+ checked,
156
+ unsupportedProfile: null,
157
+ };
158
+ }
@@ -0,0 +1,220 @@
1
+ import AppKit
2
+ import CoreGraphics
3
+ import Foundation
4
+
5
+ struct Options {
6
+ let pid: pid_t
7
+ let style: String
8
+ }
9
+
10
+ func parseOptions() -> Options? {
11
+ var pid: pid_t?
12
+ var style = "gba"
13
+ var index = 1
14
+ while index < CommandLine.arguments.count {
15
+ let token = CommandLine.arguments[index]
16
+ switch token {
17
+ case "--pid":
18
+ guard index + 1 < CommandLine.arguments.count,
19
+ let value = Int32(CommandLine.arguments[index + 1]) else { return nil }
20
+ pid = value
21
+ index += 2
22
+ case "--style":
23
+ guard index + 1 < CommandLine.arguments.count else { return nil }
24
+ style = CommandLine.arguments[index + 1]
25
+ index += 2
26
+ default:
27
+ index += 1
28
+ }
29
+ }
30
+ guard let resolvedPid = pid else { return nil }
31
+ return Options(pid: resolvedPid, style: style)
32
+ }
33
+
34
+ func findWindowBounds(for pid: pid_t) -> CGRect? {
35
+ guard let info = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: Any]] else {
36
+ return nil
37
+ }
38
+ for entry in info {
39
+ guard let ownerPid = entry[kCGWindowOwnerPID as String] as? pid_t,
40
+ ownerPid == pid,
41
+ let layer = entry[kCGWindowLayer as String] as? Int,
42
+ layer == 0,
43
+ let boundsDictionary = entry[kCGWindowBounds as String] as? NSDictionary,
44
+ let bounds = CGRect(dictionaryRepresentation: boundsDictionary) else {
45
+ continue
46
+ }
47
+ if bounds.width > 100 && bounds.height > 80 {
48
+ return bounds
49
+ }
50
+ }
51
+ return nil
52
+ }
53
+
54
+ final class CaseView: NSView {
55
+ private let style: String
56
+ private var screenRect = CGRect.zero
57
+
58
+ init(frame frameRect: NSRect, style: String) {
59
+ self.style = style
60
+ super.init(frame: frameRect)
61
+ wantsLayer = true
62
+ layer?.backgroundColor = NSColor.clear.cgColor
63
+ }
64
+
65
+ required init?(coder: NSCoder) {
66
+ fatalError("init(coder:) has not been implemented")
67
+ }
68
+
69
+ func updateScreenRect(_ rect: CGRect) {
70
+ screenRect = rect
71
+ needsDisplay = true
72
+ }
73
+
74
+ override func draw(_ dirtyRect: NSRect) {
75
+ NSColor.clear.setFill()
76
+ dirtyRect.fill()
77
+
78
+ let bounds = self.bounds
79
+ let shellPath = NSBezierPath(roundedRect: bounds.insetBy(dx: 6, dy: 6), xRadius: 48, yRadius: 48)
80
+ let cutout = NSBezierPath(roundedRect: screenRect, xRadius: 18, yRadius: 18)
81
+ shellPath.append(cutout)
82
+ shellPath.windingRule = .evenOdd
83
+
84
+ NSColor(calibratedRed: 0.92, green: 0.92, blue: 0.86, alpha: 0.96).setFill()
85
+ shellPath.fill()
86
+
87
+ let stroke = NSBezierPath(roundedRect: bounds.insetBy(dx: 6, dy: 6), xRadius: 48, yRadius: 48)
88
+ NSColor(calibratedRed: 0.70, green: 0.70, blue: 0.64, alpha: 1.0).setStroke()
89
+ stroke.lineWidth = 6
90
+ stroke.stroke()
91
+
92
+ drawControls(in: bounds)
93
+ }
94
+
95
+ private func drawControls(in bounds: CGRect) {
96
+ let dpadCenter = CGPoint(x: bounds.minX + bounds.width * 0.18, y: bounds.minY + bounds.height * 0.28)
97
+ drawDPad(center: dpadCenter, size: min(bounds.width, bounds.height) * 0.14)
98
+
99
+ let aCenter = CGPoint(x: bounds.maxX - bounds.width * 0.18, y: bounds.minY + bounds.height * 0.31)
100
+ drawButton(center: CGPoint(x: aCenter.x + 28, y: aCenter.y), radius: 26)
101
+ drawButton(center: CGPoint(x: aCenter.x - 6, y: aCenter.y + 26), radius: 26)
102
+
103
+ drawPill(CGRect(x: bounds.midX - 58, y: bounds.minY + bounds.height * 0.15, width: 42, height: 14))
104
+ drawPill(CGRect(x: bounds.midX + 16, y: bounds.minY + bounds.height * 0.15, width: 42, height: 14))
105
+
106
+ if style == "gba" {
107
+ let speakerStartX = bounds.maxX - bounds.width * 0.14
108
+ let speakerStartY = bounds.minY + bounds.height * 0.16
109
+ for row in 0..<3 {
110
+ for col in 0..<5 {
111
+ let circle = NSBezierPath(ovalIn: CGRect(x: speakerStartX + CGFloat(col * 10), y: speakerStartY + CGFloat(row * 10), width: 4, height: 4))
112
+ NSColor(calibratedRed: 0.62, green: 0.62, blue: 0.57, alpha: 1).setFill()
113
+ circle.fill()
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ private func drawDPad(center: CGPoint, size: CGFloat) {
120
+ let thickness = size * 0.34
121
+ let vertical = NSBezierPath(roundedRect: CGRect(x: center.x - thickness / 2, y: center.y - size / 2, width: thickness, height: size), xRadius: 10, yRadius: 10)
122
+ let horizontal = NSBezierPath(roundedRect: CGRect(x: center.x - size / 2, y: center.y - thickness / 2, width: size, height: thickness), xRadius: 10, yRadius: 10)
123
+ NSColor(calibratedRed: 0.47, green: 0.47, blue: 0.44, alpha: 1).setFill()
124
+ vertical.fill()
125
+ horizontal.fill()
126
+ }
127
+
128
+ private func drawButton(center: CGPoint, radius: CGFloat) {
129
+ let circle = NSBezierPath(ovalIn: CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2))
130
+ NSColor(calibratedRed: 0.72, green: 0.46, blue: 0.55, alpha: 1).setFill()
131
+ circle.fill()
132
+ NSColor(calibratedRed: 0.55, green: 0.32, blue: 0.40, alpha: 1).setStroke()
133
+ circle.lineWidth = 3
134
+ circle.stroke()
135
+ }
136
+
137
+ private func drawPill(_ rect: CGRect) {
138
+ let pill = NSBezierPath(roundedRect: rect, xRadius: rect.height / 2, yRadius: rect.height / 2)
139
+ NSColor(calibratedRed: 0.63, green: 0.63, blue: 0.59, alpha: 1).setFill()
140
+ pill.fill()
141
+ }
142
+ }
143
+
144
+ final class OverlayController: NSObject, NSApplicationDelegate {
145
+ private let options: Options
146
+ private var window: NSWindow?
147
+ private var view: CaseView?
148
+ private var timer: Timer?
149
+ private let padding = NSEdgeInsets(top: 48, left: 120, bottom: 120, right: 120)
150
+
151
+ init(options: Options) {
152
+ self.options = options
153
+ super.init()
154
+ }
155
+
156
+ func applicationDidFinishLaunching(_ notification: Notification) {
157
+ guard let bounds = findWindowBounds(for: options.pid) else {
158
+ NSApp.terminate(nil)
159
+ return
160
+ }
161
+ let frame = expandedFrame(for: bounds)
162
+ let window = NSWindow(contentRect: frame, styleMask: [.borderless], backing: .buffered, defer: false)
163
+ window.isOpaque = false
164
+ window.backgroundColor = .clear
165
+ window.level = .statusBar
166
+ window.ignoresMouseEvents = true
167
+ window.hasShadow = false
168
+ window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
169
+
170
+ let view = CaseView(frame: NSRect(origin: .zero, size: frame.size), style: options.style)
171
+ window.contentView = view
172
+ window.orderFrontRegardless()
173
+ self.window = window
174
+ self.view = view
175
+ updateOverlayFrame(windowBounds: bounds)
176
+
177
+ timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
178
+ guard let self else { return }
179
+ if NSRunningApplication(processIdentifier: self.options.pid) == nil {
180
+ NSApp.terminate(nil)
181
+ return
182
+ }
183
+ guard let nextBounds = findWindowBounds(for: self.options.pid) else { return }
184
+ self.updateOverlayFrame(windowBounds: nextBounds)
185
+ }
186
+ }
187
+
188
+ private func expandedFrame(for bounds: CGRect) -> NSRect {
189
+ NSRect(
190
+ x: bounds.origin.x - padding.left,
191
+ y: bounds.origin.y - padding.bottom,
192
+ width: bounds.width + padding.left + padding.right,
193
+ height: bounds.height + padding.top + padding.bottom
194
+ )
195
+ }
196
+
197
+ private func updateOverlayFrame(windowBounds: CGRect) {
198
+ guard let window, let view else { return }
199
+ let frame = expandedFrame(for: windowBounds)
200
+ window.setFrame(frame, display: true)
201
+ view.frame = NSRect(origin: .zero, size: frame.size)
202
+ view.updateScreenRect(CGRect(
203
+ x: padding.left,
204
+ y: padding.bottom,
205
+ width: windowBounds.width,
206
+ height: windowBounds.height
207
+ ))
208
+ }
209
+ }
210
+
211
+ guard let options = parseOptions() else {
212
+ fputs("Usage: macos-case-overlay.swift --pid <pid> [--style gba]\n", stderr)
213
+ exit(2)
214
+ }
215
+
216
+ let app = NSApplication.shared
217
+ let delegate = OverlayController(options: options)
218
+ app.setActivationPolicy(.accessory)
219
+ app.delegate = delegate
220
+ app.run()