@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.
- package/README.md +98 -2
- package/benchmarks/perf-thresholds.json +54 -0
- package/package.json +4 -7
- package/src/asset-pack.mjs +8 -1
- package/src/authored-project.mjs +1449 -0
- package/src/authored-runtime.mjs +2016 -0
- package/src/authoring/avatar-animation-graph.mjs +648 -0
- package/src/bin-integrity.mjs +272 -0
- package/src/build-contract/assets.mjs +130 -0
- package/src/build-contract/capabilities.mjs +116 -0
- package/src/build-contract/constants.mjs +6 -0
- package/src/build-contract/helpers.mjs +44 -0
- package/src/build-contract/web-templates.mjs +5993 -0
- package/src/build-contract.mjs +27 -2910
- package/src/bundler.mjs +188 -55
- package/src/cli.mjs +4840 -1512
- package/src/commands/project-authoring.mjs +454 -0
- package/src/config.mjs +44 -0
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
- package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
- package/src/conformance/cases/index.mjs +11 -0
- package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
- package/src/conformance/shared.mjs +27 -0
- package/src/conformance-runner.mjs +25 -13
- package/src/conformance.mjs +619 -4020
- package/src/cutscene.mjs +362 -5
- package/src/dev-cli-action.mjs +249 -0
- package/src/dev-cli-inspect.mjs +92 -0
- package/src/dev-cli-state.mjs +80 -0
- package/src/external-asset-cache.mjs +587 -0
- package/src/external-asset-policy.mjs +217 -0
- package/src/external-package-surface.mjs +206 -0
- package/src/game-action-runtime.mjs +869 -0
- package/src/game-state-runtime.mjs +206 -6
- package/src/headless-action.mjs +186 -0
- package/src/headless-test/runtime-animation.mjs +1173 -0
- package/src/headless-test/runtime-coordinator.mjs +1514 -0
- package/src/headless-test/runtime-primitives.mjs +320 -0
- package/src/headless-test/runtime-world.mjs +2253 -0
- package/src/headless-test.mjs +392 -4298
- package/src/host-binary.mjs +342 -14
- package/src/icon-discovery.mjs +64 -0
- package/src/make-catalog.mjs +109 -0
- package/src/make.mjs +197 -0
- package/src/package-integrity.mjs +586 -0
- package/src/perf-benchmark.mjs +353 -0
- package/src/postinstall.mjs +5 -5
- package/src/prefabs/index.mjs +34 -0
- package/src/prefabs/scene-serialization.mjs +184 -0
- package/src/project-importer.mjs +620 -0
- package/src/project-registry.mjs +24 -0
- package/src/publish-command.mjs +195 -0
- package/src/publish-env-example.mjs +83 -0
- package/src/publish-validation.mjs +708 -0
- package/src/retro/assets/compile.mjs +232 -0
- package/src/retro/backend-gba/authoring.mjs +1029 -0
- package/src/retro/backend-gba/rom.mjs +363 -0
- package/src/retro/backend-gbc/rom.mjs +85 -0
- package/src/retro/build.mjs +278 -0
- package/src/retro/cli/commands.mjs +292 -0
- package/src/retro/cli/templates.mjs +84 -0
- package/src/retro/diagnostics/catalog.mjs +110 -0
- package/src/retro/diagnostics/emit.mjs +72 -0
- package/src/retro/emulator/case-overlay.mjs +64 -0
- package/src/retro/emulator/discovery.mjs +158 -0
- package/src/retro/emulator/macos-case-overlay.swift +220 -0
- package/src/retro/emulator/profiles.mjs +146 -0
- package/src/retro/emulator/runner.mjs +289 -0
- package/src/retro/frontend/load-project.mjs +98 -0
- package/src/retro/index.mjs +30 -0
- package/src/retro/ir/build-ir.mjs +108 -0
- package/src/retro/runtime-gba/contract.mjs +151 -0
- package/src/retro/runtime-gbc/contract.mjs +117 -0
- package/src/retro/shared/span.mjs +26 -0
- package/src/retro/shared/targets.mjs +64 -0
- package/src/retro/validator/check-project.mjs +114 -0
- package/src/runtime-hotspot-audit.mjs +707 -0
- package/src/scaffold/config.mjs +1000 -0
- package/src/scaffold/fs.mjs +56 -0
- package/src/scaffold/layout.mjs +318 -0
- package/src/scaffold/project-docs.mjs +439 -0
- package/src/scaffold.mjs +93 -596
- package/src/scene-composition/index.mjs +326 -0
- package/src/scene-composition/runtime.mjs +751 -0
- package/src/self-hosted-assets.mjs +604 -0
- package/src/session-client.mjs +750 -0
- package/src/session-native-launcher.mjs +74 -0
- package/src/session-protocol.mjs +75 -0
- package/src/session-runtime.mjs +321 -0
- package/src/session-server.mjs +360 -0
- package/src/shader-kits/index.mjs +773 -0
- package/src/starter-content-registry.mjs +292 -0
- package/src/state-artifacts.mjs +662 -24
- package/src/state-dev-reload.mjs +99 -2
- package/src/terminal-ui.mjs +245 -0
- package/src/web-conformance.mjs +219 -0
- package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
- package/templates/create/2d/content/gameplay/waves.json +26 -0
- package/templates/create/2d/content/registries/.gitkeep +1 -0
- package/templates/create/2d/docs/design/.gitkeep +1 -0
- package/templates/create/2d/docs/design/loop.md +5 -0
- package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
- package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
- package/templates/create/2d/prefabs/player.prefab.js +36 -0
- package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
- package/templates/create/2d/scenes/boot.scene.js +12 -0
- package/templates/create/2d/scenes/gameplay.scene.js +230 -0
- package/templates/create/2d/scenes/menu.scene.js +9 -0
- package/templates/create/2d/src/main.js +6 -185
- package/templates/create/2d/src/runtime/app.js +49 -0
- package/templates/create/2d/src/runtime/capabilities.js +35 -0
- package/templates/create/2d/ui/hud.screen.js +40 -0
- package/templates/create/2d/ui/pause.screen.js +149 -0
- package/templates/create/2d/ui/settings.screen.js +347 -0
- package/templates/create/2d/ui/title.screen.js +13 -0
- package/templates/create/2d-adventure/aura.config.json +28 -0
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
- package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/2d-adventure/docs/design/loop.md +5 -0
- package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
- package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
- package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
- package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
- package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
- package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/loop.md +5 -0
- package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
- package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
- package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
- package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
- package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
- package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
- package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
- package/templates/create/2d-survivor/src/main.js +5 -332
- package/templates/create/2d-survivor/src/runtime/app.js +49 -0
- package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
- package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
- package/templates/create/2d-survivor/ui/title.screen.js +13 -0
- package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
- package/templates/create/3d/content/gameplay/course.js +40 -0
- package/templates/create/3d/content/registries/.gitkeep +1 -0
- package/templates/create/3d/docs/design/.gitkeep +1 -0
- package/templates/create/3d/docs/design/loop.md +5 -0
- package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
- package/templates/create/3d/prefabs/player.prefab.js +204 -0
- package/templates/create/3d/prefabs/world.prefab.js +112 -0
- package/templates/create/3d/scenes/boot.scene.js +12 -0
- package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d/scenes/gameplay.scene.js +292 -0
- package/templates/create/3d/src/main.js +6 -295
- package/templates/create/3d/src/runtime/app.js +49 -0
- package/templates/create/3d/src/runtime/capabilities.js +53 -0
- package/templates/create/3d/src/runtime/materials.js +34 -0
- package/templates/create/3d/src/runtime/state.js +39 -0
- package/templates/create/3d/ui/hud.screen.js +75 -0
- package/templates/create/3d/ui/pause.screen.js +166 -0
- package/templates/create/3d/ui/settings.screen.js +387 -0
- package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-adventure/aura.config.json +28 -0
- package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
- package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
- package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/3d-adventure/docs/design/loop.md +5 -0
- package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
- package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
- package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
- package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
- package/templates/create/3d-adventure/src/runtime/state.js +31 -0
- package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
- package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
- package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
- package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
- package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
- package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
- package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
- package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
- package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
- package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
- package/templates/create/3d-collectathon/src/main.js +5 -355
- package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
- package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
- package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
- package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
- package/templates/create/blank/config/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/registries/.gitkeep +1 -0
- package/templates/create/blank/docs/design/.gitkeep +1 -0
- package/templates/create/blank/docs/design/loop.md +5 -0
- package/templates/create/blank/prefabs/.gitkeep +1 -0
- package/templates/create/blank/scenes/.gitkeep +1 -0
- package/templates/create/blank/src/runtime/.gitkeep +1 -0
- package/templates/create/blank/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
- package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/aura.config.json +28 -0
- package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
- package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
- package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
- package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
- package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
- package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
- package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
- package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
- package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
- package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/main.js +17 -0
- package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
- package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
- package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
- package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
- package/templates/create/local-multiplayer/aura.config.json +41 -0
- package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
- package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
- package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
- package/templates/create/local-multiplayer/docs/design/loop.md +16 -0
- package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
- package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
- package/templates/create/local-multiplayer/scenes/gameplay.scene.js +472 -0
- package/templates/create/local-multiplayer/src/main.js +17 -0
- package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
- package/templates/create/local-multiplayer/ui/hud.screen.js +65 -0
- package/templates/create/shared/src/runtime/project-inspector.js +105 -0
- package/templates/create/shared/src/runtime/scene-flow.js +290 -0
- package/templates/create/shared/src/runtime/screen-shell.js +222 -0
- package/templates/create/shared/src/runtime/ui-forms.js +209 -0
- package/templates/create/shared/src/runtime/ui-settings.js +237 -0
- package/templates/create/shared/src/runtime/ui-theme.js +352 -0
- package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
- package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
- package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
- package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
- package/templates/create/shared/src/starter-utils/core.js +39 -3
- package/templates/create/shared/src/starter-utils/index.js +8 -2
- package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
- package/templates/create/shared/src/starter-utils/triggers.js +662 -0
- package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
- package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
- package/templates/create/video-cutscene/aura.config.json +28 -0
- package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
- package/templates/create/video-cutscene/docs/design/loop.md +22 -0
- package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
- package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
- package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
- package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
- package/templates/create/video-cutscene/src/main.js +17 -0
- package/templates/create/video-cutscene/src/runtime/app.js +52 -0
- package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
- package/templates/create/video-cutscene/src/runtime/state.js +13 -0
- package/templates/create/video-cutscene/ui/.gitkeep +0 -0
- package/templates/create-bin/play.js +1192 -0
- package/templates/make/README.md +46 -0
- package/templates/make/catalog.json +51 -0
- package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
- package/templates/make/component/manifest.json +9 -0
- package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
- package/templates/make/data/manifest.json +9 -0
- package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
- package/templates/make/material/manifest.json +9 -0
- package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
- package/templates/make/prefab/manifest.json +9 -0
- package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
- package/templates/make/scene/manifest.json +9 -0
- package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
- package/templates/make/shader/manifest.json +9 -0
- package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
- package/templates/make/system/manifest.json +9 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
- package/templates/make/ui-screen/manifest.json +10 -0
- package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
- package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
- package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
- package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
- package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
- package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
- package/templates/retro/platformer/README.md +10 -0
- package/templates/retro/platformer/assets/retro/assets.json +91 -0
- package/templates/retro/platformer/aura.config.json +7 -0
- package/templates/retro/platformer/package.json +5 -0
- package/templates/retro/platformer/src/main.js +40 -0
- package/templates/retro/puzzle-grid/README.md +10 -0
- package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
- package/templates/retro/puzzle-grid/aura.config.json +7 -0
- package/templates/retro/puzzle-grid/package.json +5 -0
- package/templates/retro/puzzle-grid/src/main.js +29 -0
- package/templates/retro/tactics-grid/README.md +10 -0
- package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
- package/templates/retro/tactics-grid/aura.config.json +7 -0
- package/templates/retro/tactics-grid/package.json +5 -0
- package/templates/retro/tactics-grid/src/main.js +35 -0
- package/templates/retro/topdown-adventure/README.md +10 -0
- package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
- package/templates/retro/topdown-adventure/aura.config.json +7 -0
- package/templates/retro/topdown-adventure/package.json +5 -0
- package/templates/retro/topdown-adventure/src/main.js +29 -0
- 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()
|