@auraindustry/aurajs 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -2
- package/benchmarks/perf-thresholds.json +54 -0
- package/package.json +4 -7
- package/src/asset-pack.mjs +5 -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 +4825 -1512
- package/src/commands/project-authoring.mjs +434 -0
- package/src/config.mjs +27 -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 +438 -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 +40 -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 +14 -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 +443 -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 +60 -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 +1187 -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,1029 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
function createCompileError(reason, detail) {
|
|
4
|
+
const error = new Error(`Aura Retro GBA authoring compile failed: ${reason}${detail ? ` (${detail})` : ''}`);
|
|
5
|
+
error.code = reason;
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function walkBraces(source, startIndex, openChar, closeChar) {
|
|
10
|
+
let depth = 0;
|
|
11
|
+
let inString = null;
|
|
12
|
+
let escaped = false;
|
|
13
|
+
let inLineComment = false;
|
|
14
|
+
let inBlockComment = false;
|
|
15
|
+
|
|
16
|
+
for (let index = startIndex; index < source.length; index += 1) {
|
|
17
|
+
const char = source[index];
|
|
18
|
+
const next = source[index + 1] || '';
|
|
19
|
+
|
|
20
|
+
if (inLineComment) {
|
|
21
|
+
if (char === '\n') inLineComment = false;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (inBlockComment) {
|
|
25
|
+
if (char === '*' && next === '/') {
|
|
26
|
+
inBlockComment = false;
|
|
27
|
+
index += 1;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (inString) {
|
|
32
|
+
if (escaped) {
|
|
33
|
+
escaped = false;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === '\\') {
|
|
37
|
+
escaped = true;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (char === inString) {
|
|
41
|
+
inString = null;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (char === '/' && next === '/') {
|
|
47
|
+
inLineComment = true;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === '/' && next === '*') {
|
|
52
|
+
inBlockComment = true;
|
|
53
|
+
index += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (char === '\'' || char === '"' || char === '`') {
|
|
57
|
+
inString = char;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (char === openChar) {
|
|
62
|
+
depth += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (char === closeChar) {
|
|
66
|
+
depth -= 1;
|
|
67
|
+
if (depth === 0) {
|
|
68
|
+
return index;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw createCompileError('retro_gba_authoring_parse_failed', `unmatched ${openChar}${closeChar} pair`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractExportedStateSource(source) {
|
|
77
|
+
const match = /export\s+const\s+state\s*=\s*/m.exec(source);
|
|
78
|
+
if (!match) {
|
|
79
|
+
throw createCompileError('retro_gba_state_export_missing', 'export const state');
|
|
80
|
+
}
|
|
81
|
+
const braceIndex = source.indexOf('{', match.index);
|
|
82
|
+
if (braceIndex < 0) {
|
|
83
|
+
throw createCompileError('retro_gba_state_export_invalid', 'missing state object literal');
|
|
84
|
+
}
|
|
85
|
+
const endIndex = walkBraces(source, braceIndex, '{', '}');
|
|
86
|
+
return source.slice(braceIndex, endIndex + 1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function extractFunctionBody(source, exportName) {
|
|
90
|
+
const matcher = new RegExp(`export\\s+function\\s+${exportName}\\s*\\([^)]*\\)\\s*\\{`, 'm');
|
|
91
|
+
const match = matcher.exec(source);
|
|
92
|
+
if (!match) return '';
|
|
93
|
+
const braceIndex = source.indexOf('{', match.index);
|
|
94
|
+
const endIndex = walkBraces(source, braceIndex, '{', '}');
|
|
95
|
+
return source.slice(braceIndex + 1, endIndex);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function loadAuthoringSource(entryFile) {
|
|
99
|
+
const source = readFileSync(entryFile, 'utf8');
|
|
100
|
+
const stateSource = extractExportedStateSource(source);
|
|
101
|
+
let stateValue = null;
|
|
102
|
+
try {
|
|
103
|
+
stateValue = Function(`"use strict"; return (${stateSource});`)();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw createCompileError('retro_gba_state_literal_unsupported', error.message);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
source,
|
|
109
|
+
stateValue,
|
|
110
|
+
setupBody: extractFunctionBody(source, 'setup'),
|
|
111
|
+
updateBody: extractFunctionBody(source, 'update'),
|
|
112
|
+
drawBody: extractFunctionBody(source, 'draw'),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function flattenState(value, prefix = ['state'], entries = []) {
|
|
117
|
+
if (value == null || Array.isArray(value)) {
|
|
118
|
+
throw createCompileError('retro_gba_state_shape_unsupported', 'arrays and null are not supported');
|
|
119
|
+
}
|
|
120
|
+
for (const [key, child] of Object.entries(value)) {
|
|
121
|
+
const path = [...prefix, key];
|
|
122
|
+
if (typeof child === 'number') {
|
|
123
|
+
entries.push({ path, type: 'number', initialValue: child });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (typeof child === 'boolean') {
|
|
127
|
+
entries.push({ path, type: 'boolean', initialValue: child });
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (typeof child === 'string') {
|
|
131
|
+
entries.push({ path, type: 'string', initialValue: child });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (typeof child === 'object' && child) {
|
|
135
|
+
flattenState(child, path, entries);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
throw createCompileError('retro_gba_state_type_unsupported', `${path.join('.')} uses ${typeof child}`);
|
|
139
|
+
}
|
|
140
|
+
return entries;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function statePathToVariable(path) {
|
|
144
|
+
return path.join('__');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createStateContext(stateEntries) {
|
|
148
|
+
const byPath = new Map();
|
|
149
|
+
for (const entry of stateEntries) {
|
|
150
|
+
byPath.set(entry.path.join('.'), {
|
|
151
|
+
...entry,
|
|
152
|
+
variableName: statePathToVariable(entry.path),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
byPath,
|
|
157
|
+
stringVariables: new Set(
|
|
158
|
+
stateEntries
|
|
159
|
+
.filter((entry) => entry.type === 'string')
|
|
160
|
+
.map((entry) => statePathToVariable(entry.path)),
|
|
161
|
+
),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizeJsStringLiterals(expr) {
|
|
166
|
+
return expr.replace(/'([^'\\]*)'/g, (_, value) => JSON.stringify(value));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function mapInputButtonLiteral(rawValue) {
|
|
170
|
+
const normalized = String(rawValue).replace(/^['"`]|['"`]$/g, '').toLowerCase();
|
|
171
|
+
const lookup = {
|
|
172
|
+
a: 'AURA_KEY_A',
|
|
173
|
+
b: 'AURA_KEY_B',
|
|
174
|
+
select: 'AURA_KEY_SELECT',
|
|
175
|
+
start: 'AURA_KEY_START',
|
|
176
|
+
right: 'AURA_KEY_RIGHT',
|
|
177
|
+
left: 'AURA_KEY_LEFT',
|
|
178
|
+
up: 'AURA_KEY_UP',
|
|
179
|
+
down: 'AURA_KEY_DOWN',
|
|
180
|
+
r: 'AURA_KEY_R',
|
|
181
|
+
l: 'AURA_KEY_L',
|
|
182
|
+
};
|
|
183
|
+
if (!lookup[normalized]) {
|
|
184
|
+
throw createCompileError('retro_gba_input_button_unsupported', normalized);
|
|
185
|
+
}
|
|
186
|
+
return lookup[normalized];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function replaceStateReferences(expr, stateContext) {
|
|
190
|
+
return expr.replace(/\bstate(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+\b/g, (match) => {
|
|
191
|
+
const key = match.replace(/\./g, '.');
|
|
192
|
+
const entry = stateContext.byPath.get(key);
|
|
193
|
+
if (!entry) {
|
|
194
|
+
throw createCompileError('retro_gba_state_reference_unknown', match);
|
|
195
|
+
}
|
|
196
|
+
return entry.variableName;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function replaceStringComparisons(expr, stateContext) {
|
|
201
|
+
let rendered = expr;
|
|
202
|
+
for (const variableName of stateContext.stringVariables) {
|
|
203
|
+
const escapedVar = variableName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
204
|
+
rendered = rendered.replace(
|
|
205
|
+
new RegExp(`\\b${escapedVar}\\b\\s*===\\s*(\"[^\"]*\")`, 'g'),
|
|
206
|
+
(_, literal) => `aura_str_eq(${variableName}, ${literal})`,
|
|
207
|
+
);
|
|
208
|
+
rendered = rendered.replace(
|
|
209
|
+
new RegExp(`(\"[^\"]*\")\\s*===\\s*\\b${escapedVar}\\b`, 'g'),
|
|
210
|
+
(_, literal) => `aura_str_eq(${literal}, ${variableName})`,
|
|
211
|
+
);
|
|
212
|
+
rendered = rendered.replace(
|
|
213
|
+
new RegExp(`\\b${escapedVar}\\b\\s*!==\\s*(\"[^\"]*\")`, 'g'),
|
|
214
|
+
(_, literal) => `(!aura_str_eq(${variableName}, ${literal}))`,
|
|
215
|
+
);
|
|
216
|
+
rendered = rendered.replace(
|
|
217
|
+
new RegExp(`(\"[^\"]*\")\\s*!==\\s*\\b${escapedVar}\\b`, 'g'),
|
|
218
|
+
(_, literal) => `(!aura_str_eq(${literal}, ${variableName}))`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return rendered;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function emitCExpr(rawExpr, stateContext) {
|
|
225
|
+
let expr = String(rawExpr || '').trim();
|
|
226
|
+
if (!expr) {
|
|
227
|
+
throw createCompileError('retro_gba_expression_empty');
|
|
228
|
+
}
|
|
229
|
+
expr = normalizeJsStringLiterals(expr);
|
|
230
|
+
expr = expr.replace(/\baura\.input\.pressed\s*\(\s*("[^"]+")\s*\)/g, (_, value) => `aura_pressed(${mapInputButtonLiteral(value)})`);
|
|
231
|
+
expr = expr.replace(/\baura\.input\.justPressed\s*\(\s*("[^"]+")\s*\)/g, (_, value) => `aura_just_pressed(${mapInputButtonLiteral(value)})`);
|
|
232
|
+
expr = expr.replace(/\baura\.input\.repeatPressed\s*\(\s*("[^"]+")\s*\)/g, (_, value) => `aura_repeat_pressed(${mapInputButtonLiteral(value)})`);
|
|
233
|
+
expr = replaceStateReferences(expr, stateContext);
|
|
234
|
+
expr = replaceStringComparisons(expr, stateContext);
|
|
235
|
+
expr = expr.replace(/\btrue\b/g, '1');
|
|
236
|
+
expr = expr.replace(/\bfalse\b/g, '0');
|
|
237
|
+
expr = expr.replace(/!==/g, '!=');
|
|
238
|
+
expr = expr.replace(/===/g, '==');
|
|
239
|
+
return expr;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function parseTemplateLiteral(rawTemplate) {
|
|
243
|
+
const text = String(rawTemplate || '').trim();
|
|
244
|
+
if (!text.startsWith('`') || !text.endsWith('`')) {
|
|
245
|
+
throw createCompileError('retro_gba_template_literal_invalid', text);
|
|
246
|
+
}
|
|
247
|
+
const body = text.slice(1, -1);
|
|
248
|
+
const parts = [];
|
|
249
|
+
let literal = '';
|
|
250
|
+
for (let index = 0; index < body.length; index += 1) {
|
|
251
|
+
const char = body[index];
|
|
252
|
+
const next = body[index + 1] || '';
|
|
253
|
+
if (char === '$' && next === '{') {
|
|
254
|
+
if (literal) {
|
|
255
|
+
parts.push({ kind: 'literal', value: literal });
|
|
256
|
+
literal = '';
|
|
257
|
+
}
|
|
258
|
+
index += 2;
|
|
259
|
+
let depth = 1;
|
|
260
|
+
let expr = '';
|
|
261
|
+
for (; index < body.length; index += 1) {
|
|
262
|
+
const inner = body[index];
|
|
263
|
+
if (inner === '{') depth += 1;
|
|
264
|
+
if (inner === '}') {
|
|
265
|
+
depth -= 1;
|
|
266
|
+
if (depth === 0) break;
|
|
267
|
+
}
|
|
268
|
+
expr += inner;
|
|
269
|
+
}
|
|
270
|
+
parts.push({ kind: 'expression', value: expr.trim() });
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
literal += char;
|
|
274
|
+
}
|
|
275
|
+
if (literal) {
|
|
276
|
+
parts.push({ kind: 'literal', value: literal });
|
|
277
|
+
}
|
|
278
|
+
return parts;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function splitTopLevelArgs(source) {
|
|
282
|
+
const args = [];
|
|
283
|
+
let current = '';
|
|
284
|
+
let depthParen = 0;
|
|
285
|
+
let depthBrace = 0;
|
|
286
|
+
let depthBracket = 0;
|
|
287
|
+
let inString = null;
|
|
288
|
+
let escaped = false;
|
|
289
|
+
|
|
290
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
291
|
+
const char = source[index];
|
|
292
|
+
if (inString) {
|
|
293
|
+
current += char;
|
|
294
|
+
if (escaped) {
|
|
295
|
+
escaped = false;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (char === '\\') {
|
|
299
|
+
escaped = true;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (char === inString) {
|
|
303
|
+
inString = null;
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (char === '\'' || char === '"' || char === '`') {
|
|
308
|
+
inString = char;
|
|
309
|
+
current += char;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (char === '(') depthParen += 1;
|
|
313
|
+
if (char === ')') depthParen -= 1;
|
|
314
|
+
if (char === '{') depthBrace += 1;
|
|
315
|
+
if (char === '}') depthBrace -= 1;
|
|
316
|
+
if (char === '[') depthBracket += 1;
|
|
317
|
+
if (char === ']') depthBracket -= 1;
|
|
318
|
+
if (char === ',' && depthParen === 0 && depthBrace === 0 && depthBracket === 0) {
|
|
319
|
+
args.push(current.trim());
|
|
320
|
+
current = '';
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
current += char;
|
|
324
|
+
}
|
|
325
|
+
if (current.trim()) {
|
|
326
|
+
args.push(current.trim());
|
|
327
|
+
}
|
|
328
|
+
return args;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function parseCallStatement(source) {
|
|
332
|
+
const trimmed = String(source || '').trim().replace(/;$/, '');
|
|
333
|
+
const openIndex = trimmed.indexOf('(');
|
|
334
|
+
if (openIndex < 0 || !trimmed.endsWith(')')) {
|
|
335
|
+
throw createCompileError('retro_gba_call_statement_invalid', trimmed);
|
|
336
|
+
}
|
|
337
|
+
const callee = trimmed.slice(0, openIndex).trim();
|
|
338
|
+
const args = splitTopLevelArgs(trimmed.slice(openIndex + 1, -1));
|
|
339
|
+
return { callee, args };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function parseOptionsLiteral(rawOptions) {
|
|
343
|
+
if (!rawOptions) return {};
|
|
344
|
+
const text = String(rawOptions).trim();
|
|
345
|
+
if (!text.startsWith('{') || !text.endsWith('}')) return {};
|
|
346
|
+
const entries = splitTopLevelArgs(text.slice(1, -1));
|
|
347
|
+
const out = {};
|
|
348
|
+
for (const entry of entries) {
|
|
349
|
+
const colonIndex = entry.indexOf(':');
|
|
350
|
+
if (colonIndex < 0) continue;
|
|
351
|
+
const key = entry.slice(0, colonIndex).trim().replace(/^['"]|['"]$/g, '');
|
|
352
|
+
const value = entry.slice(colonIndex + 1).trim();
|
|
353
|
+
out[key] = value;
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function stripLineComments(line) {
|
|
359
|
+
let inString = null;
|
|
360
|
+
let escaped = false;
|
|
361
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
362
|
+
const char = line[index];
|
|
363
|
+
const next = line[index + 1] || '';
|
|
364
|
+
if (inString) {
|
|
365
|
+
if (escaped) {
|
|
366
|
+
escaped = false;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (char === '\\') {
|
|
370
|
+
escaped = true;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (char === inString) {
|
|
374
|
+
inString = null;
|
|
375
|
+
}
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (char === '\'' || char === '"' || char === '`') {
|
|
379
|
+
inString = char;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (char === '/' && next === '/') {
|
|
383
|
+
return line.slice(0, index);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return line;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function parseControlFlowBlock(bodySource) {
|
|
390
|
+
const lines = bodySource.split(/\r?\n/).map((line) => stripLineComments(line).trim()).filter(Boolean);
|
|
391
|
+
|
|
392
|
+
function readBlock(startIndex, insideBlock = false) {
|
|
393
|
+
const nodes = [];
|
|
394
|
+
let index = startIndex;
|
|
395
|
+
|
|
396
|
+
while (index < lines.length) {
|
|
397
|
+
const line = lines[index];
|
|
398
|
+
if (insideBlock && line === '}') {
|
|
399
|
+
return { nodes, nextIndex: index + 1 };
|
|
400
|
+
}
|
|
401
|
+
if (line.startsWith('} else')) {
|
|
402
|
+
throw createCompileError('retro_gba_else_unsupported', line);
|
|
403
|
+
}
|
|
404
|
+
if (line.startsWith('if')) {
|
|
405
|
+
const openParen = line.indexOf('(');
|
|
406
|
+
if (openParen < 0) {
|
|
407
|
+
throw createCompileError('retro_gba_if_invalid', line);
|
|
408
|
+
}
|
|
409
|
+
let depth = 0;
|
|
410
|
+
let closeParen = -1;
|
|
411
|
+
for (let scan = openParen; scan < line.length; scan += 1) {
|
|
412
|
+
if (line[scan] === '(') depth += 1;
|
|
413
|
+
if (line[scan] === ')') {
|
|
414
|
+
depth -= 1;
|
|
415
|
+
if (depth === 0) {
|
|
416
|
+
closeParen = scan;
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (closeParen < 0) {
|
|
422
|
+
throw createCompileError('retro_gba_if_invalid', line);
|
|
423
|
+
}
|
|
424
|
+
const condition = line.slice(openParen + 1, closeParen).trim();
|
|
425
|
+
const remainder = line.slice(closeParen + 1).trim();
|
|
426
|
+
if (remainder === '{') {
|
|
427
|
+
const nested = readBlock(index + 1, true);
|
|
428
|
+
nodes.push({ kind: 'if', condition, consequent: nested.nodes });
|
|
429
|
+
index = nested.nextIndex;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
if (remainder) {
|
|
433
|
+
nodes.push({ kind: 'if', condition, consequent: [{ kind: 'statement', source: remainder }] });
|
|
434
|
+
index += 1;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
throw createCompileError('retro_gba_if_invalid', line);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
nodes.push({ kind: 'statement', source: line });
|
|
441
|
+
index += 1;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (insideBlock) {
|
|
445
|
+
throw createCompileError('retro_gba_block_invalid', 'missing closing brace');
|
|
446
|
+
}
|
|
447
|
+
return { nodes, nextIndex: index };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return readBlock(0, false).nodes;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function emitIndented(lines, indentLevel, source) {
|
|
454
|
+
const indent = ' '.repeat(indentLevel);
|
|
455
|
+
for (const line of source) {
|
|
456
|
+
lines.push(line ? `${indent}${line}` : '');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function compileSetupOrUpdateStatement(statementSource, stateContext) {
|
|
461
|
+
const trimmed = statementSource.trim().replace(/;$/, '');
|
|
462
|
+
|
|
463
|
+
if (/^state(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+\s*(?:=|\+=|-=)/.test(trimmed)) {
|
|
464
|
+
const match = /^(state(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+)\s*(=|\+=|-=)\s*(.+)$/.exec(trimmed);
|
|
465
|
+
if (!match) {
|
|
466
|
+
throw createCompileError('retro_gba_assignment_invalid', trimmed);
|
|
467
|
+
}
|
|
468
|
+
const entry = stateContext.byPath.get(match[1].replace(/\./g, '.'));
|
|
469
|
+
if (!entry) {
|
|
470
|
+
throw createCompileError('retro_gba_state_reference_unknown', match[1]);
|
|
471
|
+
}
|
|
472
|
+
const rhs = emitCExpr(match[3], stateContext);
|
|
473
|
+
if (entry.type === 'string' && match[2] !== '=') {
|
|
474
|
+
throw createCompileError('retro_gba_string_arithmetic_unsupported', trimmed);
|
|
475
|
+
}
|
|
476
|
+
const operator = match[2] === '=' ? '=' : match[2];
|
|
477
|
+
return [
|
|
478
|
+
`${entry.variableName} ${operator} ${rhs};`,
|
|
479
|
+
'aura_needs_redraw = 1;',
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const call = parseCallStatement(trimmed);
|
|
484
|
+
if (call.callee === 'aura.audio.playMusic') {
|
|
485
|
+
return [`aura_play_music(${emitCExpr(call.args[0], stateContext)});`];
|
|
486
|
+
}
|
|
487
|
+
if (call.callee === 'aura.audio.playSfx') {
|
|
488
|
+
return [`aura_play_sfx(${emitCExpr(call.args[0], stateContext)});`];
|
|
489
|
+
}
|
|
490
|
+
if (call.callee === 'aura.save.write') {
|
|
491
|
+
return [`aura_save_write(${emitCExpr(call.args[0], stateContext)});`];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
throw createCompileError('retro_gba_setup_update_statement_unsupported', trimmed);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function emitTextBuilderLines(rawTextExpr, bufferName, stateContext) {
|
|
498
|
+
const expr = String(rawTextExpr).trim();
|
|
499
|
+
if (expr.startsWith('`')) {
|
|
500
|
+
const template = parseTemplateLiteral(expr);
|
|
501
|
+
const lines = [];
|
|
502
|
+
for (const part of template) {
|
|
503
|
+
if (part.kind === 'literal') {
|
|
504
|
+
lines.push(`aura_text_builder_append_literal(&${bufferName}, ${JSON.stringify(part.value)});`);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const emittedExpr = emitCExpr(part.value, stateContext);
|
|
508
|
+
const stateEntry = stateContext.byPath.get(part.value.replace(/\./g, '.'));
|
|
509
|
+
if (stateEntry?.type === 'string') {
|
|
510
|
+
lines.push(`aura_text_builder_append_literal(&${bufferName}, ${emittedExpr});`);
|
|
511
|
+
} else if (stateEntry?.type === 'boolean') {
|
|
512
|
+
lines.push(`aura_text_builder_append_literal(&${bufferName}, (${emittedExpr}) ? "TRUE" : "FALSE");`);
|
|
513
|
+
} else {
|
|
514
|
+
lines.push(`aura_text_builder_append_int(&${bufferName}, ${emittedExpr});`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return lines;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const normalized = emitCExpr(expr, stateContext);
|
|
521
|
+
if (/^"/.test(normalized) || normalized.includes('?')) {
|
|
522
|
+
return [`aura_text_builder_append_literal(&${bufferName}, ${normalized});`];
|
|
523
|
+
}
|
|
524
|
+
for (const variableName of stateContext.stringVariables) {
|
|
525
|
+
if (normalized === variableName) {
|
|
526
|
+
return [`aura_text_builder_append_literal(&${bufferName}, ${normalized});`];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (normalized === '1' || normalized === '0' || /\b(aura_pressed|aura_just_pressed|==|!=|>=|<=|>|<|&&|\|\|)\b/.test(normalized)) {
|
|
530
|
+
return [`aura_text_builder_append_literal(&${bufferName}, (${normalized}) ? "TRUE" : "FALSE");`];
|
|
531
|
+
}
|
|
532
|
+
return [`aura_text_builder_append_int(&${bufferName}, ${normalized});`];
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function compileDrawStatement(statementSource, stateContext, counterRef) {
|
|
536
|
+
const trimmed = statementSource.trim().replace(/;$/, '');
|
|
537
|
+
const call = parseCallStatement(trimmed);
|
|
538
|
+
|
|
539
|
+
if (call.callee === 'aura.tilemap.setTile') {
|
|
540
|
+
const options = parseOptionsLiteral(call.args[4]);
|
|
541
|
+
const paletteValue = options.palette ? emitCExpr(options.palette, stateContext) : '0';
|
|
542
|
+
return [
|
|
543
|
+
`aura_draw_tile_by_id(${emitCExpr(call.args[3], stateContext)}, ${emitCExpr(call.args[1], stateContext)}, ${emitCExpr(call.args[2], stateContext)}, ${paletteValue});`,
|
|
544
|
+
];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (call.callee === 'aura.sprite.draw') {
|
|
548
|
+
const options = parseOptionsLiteral(call.args[3]);
|
|
549
|
+
const paletteValue = options.palette ? emitCExpr(options.palette, stateContext) : '0';
|
|
550
|
+
return [
|
|
551
|
+
`aura_draw_sprite_by_id(${emitCExpr(call.args[0], stateContext)}, ${emitCExpr(call.args[1], stateContext)}, ${emitCExpr(call.args[2], stateContext)}, ${paletteValue});`,
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (call.callee === 'aura.text.write') {
|
|
556
|
+
const options = parseOptionsLiteral(call.args[3]);
|
|
557
|
+
const fontValue = options.font ? emitCExpr(options.font, stateContext) : '0';
|
|
558
|
+
const textIndex = counterRef.current;
|
|
559
|
+
counterRef.current += 1;
|
|
560
|
+
const bufferVar = `text_builder_${textIndex}`;
|
|
561
|
+
const charBuffer = `text_buffer_${textIndex}`;
|
|
562
|
+
return [
|
|
563
|
+
`char ${charBuffer}[64];`,
|
|
564
|
+
`AuraTextBuilder ${bufferVar};`,
|
|
565
|
+
`aura_text_builder_init(&${bufferVar}, ${charBuffer}, sizeof(${charBuffer}));`,
|
|
566
|
+
...emitTextBuilderLines(call.args[0], bufferVar, stateContext),
|
|
567
|
+
`aura_draw_text_by_font(${fontValue}, ${charBuffer}, ${emitCExpr(call.args[1], stateContext)}, ${emitCExpr(call.args[2], stateContext)});`,
|
|
568
|
+
];
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
throw createCompileError('retro_gba_draw_statement_unsupported', trimmed);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function compileNodes(nodes, stateContext, compileLeaf, indentLevel = 1, lines = [], counterRef = { current: 0 }) {
|
|
575
|
+
for (const node of nodes) {
|
|
576
|
+
if (node.kind === 'if') {
|
|
577
|
+
emitIndented(lines, indentLevel, [`if (${emitCExpr(node.condition, stateContext)}) {`]);
|
|
578
|
+
compileNodes(node.consequent, stateContext, compileLeaf, indentLevel + 1, lines, counterRef);
|
|
579
|
+
emitIndented(lines, indentLevel, ['}']);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
emitIndented(lines, indentLevel, compileLeaf(node.source, stateContext, counterRef));
|
|
583
|
+
}
|
|
584
|
+
return lines;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function escapeCString(value) {
|
|
588
|
+
return JSON.stringify(String(value));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function hexColorToRgb15(hex) {
|
|
592
|
+
const text = String(hex || '').trim().replace(/^#/, '');
|
|
593
|
+
if (!/^[0-9a-fA-F]{6}$/.test(text)) {
|
|
594
|
+
throw createCompileError('retro_gba_palette_color_invalid', hex);
|
|
595
|
+
}
|
|
596
|
+
const red = Math.round(parseInt(text.slice(0, 2), 16) / 255 * 31);
|
|
597
|
+
const green = Math.round(parseInt(text.slice(2, 4), 16) / 255 * 31);
|
|
598
|
+
const blue = Math.round(parseInt(text.slice(4, 6), 16) / 255 * 31);
|
|
599
|
+
return `(u16)(${red} | (${green} << 5) | (${blue} << 10))`;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function emitAssetTables(assets) {
|
|
603
|
+
const lines = [];
|
|
604
|
+
let paletteBaseIndex = 0;
|
|
605
|
+
|
|
606
|
+
for (const palette of assets.palettes) {
|
|
607
|
+
lines.push(`static const u16 palette__${palette.id}[] = { ${palette.colors.map((entry) => hexColorToRgb15(entry)).join(', ')} };`);
|
|
608
|
+
}
|
|
609
|
+
lines.push('');
|
|
610
|
+
lines.push('typedef struct AuraPalette { const char* id; int colorCount; int baseIndex; const u16* colors; } AuraPalette;');
|
|
611
|
+
lines.push('static const AuraPalette AURA_PALETTES[] = {');
|
|
612
|
+
for (const palette of assets.palettes) {
|
|
613
|
+
lines.push(` { ${escapeCString(palette.id)}, ${palette.colors.length}, ${paletteBaseIndex}, palette__${palette.id} },`);
|
|
614
|
+
paletteBaseIndex += palette.colors.length;
|
|
615
|
+
}
|
|
616
|
+
lines.push('};');
|
|
617
|
+
lines.push(`static const int AURA_PALETTE_COUNT = ${assets.palettes.length};`);
|
|
618
|
+
lines.push('');
|
|
619
|
+
|
|
620
|
+
const bitmaps = [
|
|
621
|
+
...assets.tiles.map((entry) => ({ ...entry, category: 'tile' })),
|
|
622
|
+
...assets.sprites.map((entry) => ({ ...entry, category: 'sprite' })),
|
|
623
|
+
];
|
|
624
|
+
for (const bitmap of bitmaps) {
|
|
625
|
+
lines.push(`static const char* const ${bitmap.category}__${bitmap.id}__rows[] = {`);
|
|
626
|
+
for (const row of bitmap.pixels) {
|
|
627
|
+
lines.push(` ${escapeCString(row)},`);
|
|
628
|
+
}
|
|
629
|
+
lines.push('};');
|
|
630
|
+
}
|
|
631
|
+
lines.push('');
|
|
632
|
+
lines.push('typedef struct AuraBitmap { const char* id; const char* paletteId; int width; int height; const char* const* rows; } AuraBitmap;');
|
|
633
|
+
lines.push('static const AuraBitmap AURA_TILES[] = {');
|
|
634
|
+
for (const tile of assets.tiles) {
|
|
635
|
+
lines.push(` { ${escapeCString(tile.id)}, ${escapeCString(tile.palette || assets.palettes[0]?.id || '')}, ${tile.width}, ${tile.height}, tile__${tile.id}__rows },`);
|
|
636
|
+
}
|
|
637
|
+
lines.push('};');
|
|
638
|
+
lines.push(`static const int AURA_TILE_COUNT = ${assets.tiles.length};`);
|
|
639
|
+
lines.push('static const AuraBitmap AURA_SPRITES[] = {');
|
|
640
|
+
for (const sprite of assets.sprites) {
|
|
641
|
+
lines.push(` { ${escapeCString(sprite.id)}, ${escapeCString(sprite.palette || assets.palettes[0]?.id || '')}, ${sprite.width}, ${sprite.height}, sprite__${sprite.id}__rows },`);
|
|
642
|
+
}
|
|
643
|
+
lines.push('};');
|
|
644
|
+
lines.push(`static const int AURA_SPRITE_COUNT = ${assets.sprites.length};`);
|
|
645
|
+
lines.push('');
|
|
646
|
+
|
|
647
|
+
lines.push('typedef struct AuraGlyph { char symbol; int width; int height; const char* const* rows; } AuraGlyph;');
|
|
648
|
+
lines.push('typedef struct AuraFont { const char* id; int width; int height; int glyphCount; const AuraGlyph* glyphs; } AuraFont;');
|
|
649
|
+
for (const font of assets.fonts) {
|
|
650
|
+
for (const [glyph, rows] of Object.entries(font.glyphs)) {
|
|
651
|
+
lines.push(`static const char* const font__${font.id}__${glyph.charCodeAt(0)}__rows[] = {`);
|
|
652
|
+
for (const row of rows) {
|
|
653
|
+
lines.push(` ${escapeCString(row)},`);
|
|
654
|
+
}
|
|
655
|
+
lines.push('};');
|
|
656
|
+
}
|
|
657
|
+
lines.push(`static const AuraGlyph font__${font.id}__glyphs[] = {`);
|
|
658
|
+
for (const [glyph, rows] of Object.entries(font.glyphs)) {
|
|
659
|
+
lines.push(` { '${glyph.replace(/'/g, "\\'")}', ${font.width}, ${font.height}, font__${font.id}__${glyph.charCodeAt(0)}__rows },`);
|
|
660
|
+
}
|
|
661
|
+
lines.push('};');
|
|
662
|
+
}
|
|
663
|
+
lines.push('static const AuraFont AURA_FONTS[] = {');
|
|
664
|
+
for (const font of assets.fonts) {
|
|
665
|
+
lines.push(` { ${escapeCString(font.id)}, ${font.width}, ${font.height}, ${Object.keys(font.glyphs).length}, font__${font.id}__glyphs },`);
|
|
666
|
+
}
|
|
667
|
+
lines.push('};');
|
|
668
|
+
lines.push(`static const int AURA_FONT_COUNT = ${assets.fonts.length};`);
|
|
669
|
+
|
|
670
|
+
return lines;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function emitStateDeclarations(stateEntries) {
|
|
674
|
+
const lines = [];
|
|
675
|
+
for (const entry of stateEntries) {
|
|
676
|
+
const variableName = statePathToVariable(entry.path);
|
|
677
|
+
if (entry.type === 'string') {
|
|
678
|
+
lines.push(`static const char* ${variableName};`);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
lines.push(`static int ${variableName};`);
|
|
682
|
+
}
|
|
683
|
+
return lines;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function emitStateInitialization(stateEntries) {
|
|
687
|
+
const lines = ['static void aura_init_state(void) {'];
|
|
688
|
+
for (const entry of stateEntries) {
|
|
689
|
+
const variableName = statePathToVariable(entry.path);
|
|
690
|
+
if (entry.type === 'string') {
|
|
691
|
+
lines.push(` ${variableName} = ${escapeCString(entry.initialValue)};`);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (entry.type === 'boolean') {
|
|
695
|
+
lines.push(` ${variableName} = ${entry.initialValue ? 1 : 0};`);
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
lines.push(` ${variableName} = ${Math.trunc(entry.initialValue)};`);
|
|
699
|
+
}
|
|
700
|
+
lines.push('}');
|
|
701
|
+
lines.push('');
|
|
702
|
+
return lines;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function emitRuntimeSupport(assets) {
|
|
706
|
+
const fallbackPalette = assets.palettes[0]?.id || '';
|
|
707
|
+
const fallbackFont = assets.fonts[0]?.id || '';
|
|
708
|
+
return [
|
|
709
|
+
'typedef unsigned int u32;',
|
|
710
|
+
'typedef unsigned short u16;',
|
|
711
|
+
'typedef unsigned char u8;',
|
|
712
|
+
'#define REG_DISPCNT (*(volatile u16*)0x04000000)',
|
|
713
|
+
'#define REG_VCOUNT (*(volatile u16*)0x04000006)',
|
|
714
|
+
'#define REG_KEYINPUT (*(volatile u16*)0x04000130)',
|
|
715
|
+
'#define PAL_BG ((volatile u16*)0x05000000)',
|
|
716
|
+
'#define VRAM_FRONT ((volatile u16*)0x06000000)',
|
|
717
|
+
'#define VRAM_BACK ((volatile u16*)0x0600A000)',
|
|
718
|
+
'#define MODE4 0x0004',
|
|
719
|
+
'#define BG2_ENABLE 0x0400',
|
|
720
|
+
'#define BACKBUFFER 0x0010',
|
|
721
|
+
'#define AURA_KEY_A 0x0001',
|
|
722
|
+
'#define AURA_KEY_B 0x0002',
|
|
723
|
+
'#define AURA_KEY_SELECT 0x0004',
|
|
724
|
+
'#define AURA_KEY_START 0x0008',
|
|
725
|
+
'#define AURA_KEY_RIGHT 0x0010',
|
|
726
|
+
'#define AURA_KEY_LEFT 0x0020',
|
|
727
|
+
'#define AURA_KEY_UP 0x0040',
|
|
728
|
+
'#define AURA_KEY_DOWN 0x0080',
|
|
729
|
+
'#define AURA_KEY_R 0x0100',
|
|
730
|
+
'#define AURA_KEY_L 0x0200',
|
|
731
|
+
'',
|
|
732
|
+
'static u16 aura_keys_current = 0;',
|
|
733
|
+
'static u16 aura_keys_previous = 0;',
|
|
734
|
+
'static int aura_left_frames = 0;',
|
|
735
|
+
'static int aura_right_frames = 0;',
|
|
736
|
+
'static int aura_up_frames = 0;',
|
|
737
|
+
'static int aura_down_frames = 0;',
|
|
738
|
+
'static int aura_needs_redraw;',
|
|
739
|
+
'static int aura_displaying_back_buffer;',
|
|
740
|
+
'static volatile u16* aura_draw_buffer;',
|
|
741
|
+
'',
|
|
742
|
+
'typedef struct AuraTextBuilder { char* data; int capacity; int length; } AuraTextBuilder;',
|
|
743
|
+
'',
|
|
744
|
+
'static inline void wait_vblank(void) {',
|
|
745
|
+
' while (REG_VCOUNT >= 160) {}',
|
|
746
|
+
' while (REG_VCOUNT < 160) {}',
|
|
747
|
+
'}',
|
|
748
|
+
'',
|
|
749
|
+
'static inline int clamp_i(int value, int lo, int hi) {',
|
|
750
|
+
' if (value < lo) return lo;',
|
|
751
|
+
' if (value > hi) return hi;',
|
|
752
|
+
' return value;',
|
|
753
|
+
'}',
|
|
754
|
+
'',
|
|
755
|
+
'static int aura_str_eq(const char* left, const char* right) {',
|
|
756
|
+
' int index = 0;',
|
|
757
|
+
' if (!left || !right) return left == right;',
|
|
758
|
+
' while (left[index] && right[index]) {',
|
|
759
|
+
' if (left[index] != right[index]) return 0;',
|
|
760
|
+
' index += 1;',
|
|
761
|
+
' }',
|
|
762
|
+
' return left[index] == right[index];',
|
|
763
|
+
'}',
|
|
764
|
+
'',
|
|
765
|
+
'static int aura_pressed(int mask) { return (aura_keys_current & mask) != 0; }',
|
|
766
|
+
'static void aura_update_repeat_frames(void) {',
|
|
767
|
+
' aura_left_frames = (aura_keys_current & AURA_KEY_LEFT) ? (aura_left_frames + 1) : 0;',
|
|
768
|
+
' aura_right_frames = (aura_keys_current & AURA_KEY_RIGHT) ? (aura_right_frames + 1) : 0;',
|
|
769
|
+
' aura_up_frames = (aura_keys_current & AURA_KEY_UP) ? (aura_up_frames + 1) : 0;',
|
|
770
|
+
' aura_down_frames = (aura_keys_current & AURA_KEY_DOWN) ? (aura_down_frames + 1) : 0;',
|
|
771
|
+
'}',
|
|
772
|
+
'static int aura_repeat_triggered(int frames) {',
|
|
773
|
+
' return frames == 1 || frames >= 3;',
|
|
774
|
+
'}',
|
|
775
|
+
'static int aura_just_pressed(int mask) {',
|
|
776
|
+
' return (aura_keys_current & mask) != 0 && (aura_keys_previous & mask) == 0;',
|
|
777
|
+
'}',
|
|
778
|
+
'static int aura_repeat_pressed(int mask) {',
|
|
779
|
+
' if (mask == AURA_KEY_LEFT) return aura_repeat_triggered(aura_left_frames);',
|
|
780
|
+
' if (mask == AURA_KEY_RIGHT) return aura_repeat_triggered(aura_right_frames);',
|
|
781
|
+
' if (mask == AURA_KEY_UP) return aura_repeat_triggered(aura_up_frames);',
|
|
782
|
+
' if (mask == AURA_KEY_DOWN) return aura_repeat_triggered(aura_down_frames);',
|
|
783
|
+
' return aura_just_pressed(mask);',
|
|
784
|
+
'}',
|
|
785
|
+
'',
|
|
786
|
+
'static void fill_screen(u8 colorIndex) {',
|
|
787
|
+
' u16 packed = (u16)(colorIndex | ((u16)colorIndex << 8));',
|
|
788
|
+
' for (int index = 0; index < (240 * 160) / 2; index += 1) {',
|
|
789
|
+
' aura_draw_buffer[index] = packed;',
|
|
790
|
+
' }',
|
|
791
|
+
'}',
|
|
792
|
+
'',
|
|
793
|
+
'static void put_pixel(int x, int y, u8 colorIndex) {',
|
|
794
|
+
' if (x < 0 || x >= 240 || y < 0 || y >= 160) return;',
|
|
795
|
+
' int offset = (y * 240) + x;',
|
|
796
|
+
' int wordIndex = offset >> 1;',
|
|
797
|
+
' u16 word = aura_draw_buffer[wordIndex];',
|
|
798
|
+
' if ((offset & 1) == 0) {',
|
|
799
|
+
' word = (u16)((word & 0xFF00) | colorIndex);',
|
|
800
|
+
' } else {',
|
|
801
|
+
' word = (u16)((word & 0x00FF) | ((u16)colorIndex << 8));',
|
|
802
|
+
' }',
|
|
803
|
+
' aura_draw_buffer[wordIndex] = word;',
|
|
804
|
+
'}',
|
|
805
|
+
'',
|
|
806
|
+
'static void aura_present(void) {',
|
|
807
|
+
' if (aura_displaying_back_buffer) {',
|
|
808
|
+
' REG_DISPCNT = MODE4 | BG2_ENABLE;',
|
|
809
|
+
' aura_draw_buffer = VRAM_BACK;',
|
|
810
|
+
' aura_displaying_back_buffer = 0;',
|
|
811
|
+
' return;',
|
|
812
|
+
' }',
|
|
813
|
+
' REG_DISPCNT = MODE4 | BG2_ENABLE | BACKBUFFER;',
|
|
814
|
+
' aura_draw_buffer = VRAM_FRONT;',
|
|
815
|
+
' aura_displaying_back_buffer = 1;',
|
|
816
|
+
'}',
|
|
817
|
+
'',
|
|
818
|
+
...emitAssetTables(assets),
|
|
819
|
+
'',
|
|
820
|
+
'static void aura_load_palette(void) {',
|
|
821
|
+
' for (int index = 0; index < 256; index += 1) {',
|
|
822
|
+
' PAL_BG[index] = 0;',
|
|
823
|
+
' }',
|
|
824
|
+
' for (int paletteIndex = 0; paletteIndex < AURA_PALETTE_COUNT; paletteIndex += 1) {',
|
|
825
|
+
' const AuraPalette* palette = &AURA_PALETTES[paletteIndex];',
|
|
826
|
+
' for (int colorIndex = 0; colorIndex < palette->colorCount; colorIndex += 1) {',
|
|
827
|
+
' int slot = palette->baseIndex + colorIndex;',
|
|
828
|
+
' if (slot >= 0 && slot < 256) {',
|
|
829
|
+
' PAL_BG[slot] = palette->colors[colorIndex];',
|
|
830
|
+
' }',
|
|
831
|
+
' }',
|
|
832
|
+
' }',
|
|
833
|
+
'}',
|
|
834
|
+
'',
|
|
835
|
+
'static const AuraPalette* aura_find_palette(const char* id) {',
|
|
836
|
+
' for (int index = 0; index < AURA_PALETTE_COUNT; index += 1) {',
|
|
837
|
+
' if (aura_str_eq(AURA_PALETTES[index].id, id)) return &AURA_PALETTES[index];',
|
|
838
|
+
' }',
|
|
839
|
+
' return AURA_PALETTE_COUNT > 0 ? &AURA_PALETTES[0] : 0;',
|
|
840
|
+
'}',
|
|
841
|
+
'',
|
|
842
|
+
'static const AuraBitmap* aura_find_tile(const char* id) {',
|
|
843
|
+
' for (int index = 0; index < AURA_TILE_COUNT; index += 1) {',
|
|
844
|
+
' if (aura_str_eq(AURA_TILES[index].id, id)) return &AURA_TILES[index];',
|
|
845
|
+
' }',
|
|
846
|
+
' return 0;',
|
|
847
|
+
'}',
|
|
848
|
+
'',
|
|
849
|
+
'static const AuraBitmap* aura_find_sprite(const char* id) {',
|
|
850
|
+
' for (int index = 0; index < AURA_SPRITE_COUNT; index += 1) {',
|
|
851
|
+
' if (aura_str_eq(AURA_SPRITES[index].id, id)) return &AURA_SPRITES[index];',
|
|
852
|
+
' }',
|
|
853
|
+
' return 0;',
|
|
854
|
+
'}',
|
|
855
|
+
'',
|
|
856
|
+
'static const AuraFont* aura_find_font(const char* id) {',
|
|
857
|
+
` const char* fallback = ${escapeCString(fallbackFont)};`,
|
|
858
|
+
' const char* needle = id ? id : fallback;',
|
|
859
|
+
' for (int index = 0; index < AURA_FONT_COUNT; index += 1) {',
|
|
860
|
+
' if (aura_str_eq(AURA_FONTS[index].id, needle)) return &AURA_FONTS[index];',
|
|
861
|
+
' }',
|
|
862
|
+
' return AURA_FONT_COUNT > 0 ? &AURA_FONTS[0] : 0;',
|
|
863
|
+
'}',
|
|
864
|
+
'',
|
|
865
|
+
'static const AuraGlyph* aura_find_glyph(const AuraFont* font, char symbol) {',
|
|
866
|
+
' if (!font) return 0;',
|
|
867
|
+
' for (int index = 0; index < font->glyphCount; index += 1) {',
|
|
868
|
+
' if (font->glyphs[index].symbol == symbol) return &font->glyphs[index];',
|
|
869
|
+
' }',
|
|
870
|
+
' return 0;',
|
|
871
|
+
'}',
|
|
872
|
+
'',
|
|
873
|
+
'static void aura_draw_bitmap_rows(const char* const* rows, int width, int height, const AuraPalette* palette, int x, int y, int transparentDots) {',
|
|
874
|
+
' for (int row = 0; row < height; row += 1) {',
|
|
875
|
+
' const char* pixels = rows[row];',
|
|
876
|
+
' for (int col = 0; col < width; col += 1) {',
|
|
877
|
+
' char pixel = pixels[col];',
|
|
878
|
+
' if (pixel == \'.\' && transparentDots) continue;',
|
|
879
|
+
' int paletteIndex = pixel == \'.\' ? 0 : (pixel - \'0\');',
|
|
880
|
+
' if (paletteIndex < 0 || !palette || paletteIndex >= palette->colorCount) continue;',
|
|
881
|
+
' int colorIndex = palette->baseIndex + paletteIndex;',
|
|
882
|
+
' if (colorIndex < 0 || colorIndex >= 256) continue;',
|
|
883
|
+
' put_pixel(x + col, y + row, (u8)colorIndex);',
|
|
884
|
+
' }',
|
|
885
|
+
' }',
|
|
886
|
+
'}',
|
|
887
|
+
'',
|
|
888
|
+
'static void aura_draw_tile_by_id(const char* tileId, int tileX, int tileY, const char* paletteOverride) {',
|
|
889
|
+
' const AuraBitmap* tile = aura_find_tile(tileId);',
|
|
890
|
+
' const AuraPalette* palette = aura_find_palette(paletteOverride ? paletteOverride : (tile ? tile->paletteId : 0));',
|
|
891
|
+
' if (!tile || !palette) return;',
|
|
892
|
+
' aura_draw_bitmap_rows(tile->rows, tile->width, tile->height, palette, tileX * 8, tileY * 8, 0);',
|
|
893
|
+
'}',
|
|
894
|
+
'',
|
|
895
|
+
'static void aura_draw_sprite_by_id(const char* spriteId, int x, int y, const char* paletteOverride) {',
|
|
896
|
+
' const AuraBitmap* sprite = aura_find_sprite(spriteId);',
|
|
897
|
+
' const AuraPalette* palette = aura_find_palette(paletteOverride ? paletteOverride : (sprite ? sprite->paletteId : 0));',
|
|
898
|
+
' if (!sprite || !palette) return;',
|
|
899
|
+
' aura_draw_bitmap_rows(sprite->rows, sprite->width, sprite->height, palette, x, y, 1);',
|
|
900
|
+
'}',
|
|
901
|
+
'',
|
|
902
|
+
'static void aura_text_builder_init(AuraTextBuilder* builder, char* data, int capacity) {',
|
|
903
|
+
' builder->data = data;',
|
|
904
|
+
' builder->capacity = capacity;',
|
|
905
|
+
' builder->length = 0;',
|
|
906
|
+
' if (capacity > 0) builder->data[0] = 0;',
|
|
907
|
+
'}',
|
|
908
|
+
'',
|
|
909
|
+
'static void aura_text_builder_append_char(AuraTextBuilder* builder, char value) {',
|
|
910
|
+
' if (builder->length + 1 >= builder->capacity) return;',
|
|
911
|
+
' builder->data[builder->length] = value;',
|
|
912
|
+
' builder->length += 1;',
|
|
913
|
+
' builder->data[builder->length] = 0;',
|
|
914
|
+
'}',
|
|
915
|
+
'',
|
|
916
|
+
'static void aura_text_builder_append_literal(AuraTextBuilder* builder, const char* text) {',
|
|
917
|
+
' if (!text) return;',
|
|
918
|
+
' for (int index = 0; text[index]; index += 1) {',
|
|
919
|
+
' aura_text_builder_append_char(builder, text[index]);',
|
|
920
|
+
' }',
|
|
921
|
+
'}',
|
|
922
|
+
'',
|
|
923
|
+
'static void aura_text_builder_append_int(AuraTextBuilder* builder, int value) {',
|
|
924
|
+
' char digits[16];',
|
|
925
|
+
' int index = 0;',
|
|
926
|
+
' int negative = value < 0;',
|
|
927
|
+
' unsigned int cursor = negative ? (unsigned int)(-value) : (unsigned int)value;',
|
|
928
|
+
' if (value == 0) { aura_text_builder_append_char(builder, \'0\'); return; }',
|
|
929
|
+
' while (cursor > 0 && index < 15) {',
|
|
930
|
+
' digits[index++] = (char)(\'0\' + (cursor % 10));',
|
|
931
|
+
' cursor /= 10;',
|
|
932
|
+
' }',
|
|
933
|
+
' if (negative) aura_text_builder_append_char(builder, \'-\');',
|
|
934
|
+
' while (index > 0) {',
|
|
935
|
+
' aura_text_builder_append_char(builder, digits[--index]);',
|
|
936
|
+
' }',
|
|
937
|
+
'}',
|
|
938
|
+
'',
|
|
939
|
+
'static void aura_draw_text_by_font(const char* fontId, const char* text, int x, int y) {',
|
|
940
|
+
' const AuraFont* font = aura_find_font(fontId);',
|
|
941
|
+
` const AuraPalette* palette = aura_find_palette(${escapeCString(fallbackPalette)});`,
|
|
942
|
+
' int cursorX = x;',
|
|
943
|
+
' if (!font || !palette || !text) return;',
|
|
944
|
+
' for (int index = 0; text[index]; index += 1) {',
|
|
945
|
+
' char symbol = text[index];',
|
|
946
|
+
' const AuraGlyph* glyph = aura_find_glyph(font, symbol);',
|
|
947
|
+
' if (!glyph) glyph = aura_find_glyph(font, (char)(symbol >= \'a\' && symbol <= \'z\' ? (symbol - 32) : symbol));',
|
|
948
|
+
' if (glyph) {',
|
|
949
|
+
' aura_draw_bitmap_rows(glyph->rows, glyph->width, glyph->height, palette, cursorX, y, 1);',
|
|
950
|
+
' }',
|
|
951
|
+
' cursorX += font->width + 1;',
|
|
952
|
+
' }',
|
|
953
|
+
'}',
|
|
954
|
+
'',
|
|
955
|
+
'static void aura_play_music(const char* trackId) { (void)trackId; }',
|
|
956
|
+
'static void aura_play_sfx(const char* effectId) { (void)effectId; }',
|
|
957
|
+
'static void aura_save_write(const char* key) { (void)key; }',
|
|
958
|
+
'',
|
|
959
|
+
];
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function emitFunction(name, nodes, stateContext, compileLeaf) {
|
|
963
|
+
const lines = [`static void ${name}(void) {`];
|
|
964
|
+
compileNodes(nodes, stateContext, compileLeaf, 1, lines, { current: 0 });
|
|
965
|
+
lines.push('}');
|
|
966
|
+
lines.push('');
|
|
967
|
+
return lines;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
export function buildAuthoringBackedGbaRuntimeSource({ entryFile, identity, ir, assets }) {
|
|
971
|
+
if (ir.moduleCount !== 1) {
|
|
972
|
+
throw createCompileError('retro_gba_multi_module_unsupported', `moduleCount=${ir.moduleCount}`);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const authoring = loadAuthoringSource(entryFile);
|
|
976
|
+
const stateEntries = flattenState(authoring.stateValue, ['state']);
|
|
977
|
+
const stateContext = createStateContext(stateEntries);
|
|
978
|
+
const setupNodes = parseControlFlowBlock(authoring.setupBody);
|
|
979
|
+
const updateNodes = parseControlFlowBlock(authoring.updateBody);
|
|
980
|
+
const drawNodes = parseControlFlowBlock(authoring.drawBody);
|
|
981
|
+
|
|
982
|
+
const lines = [
|
|
983
|
+
'/*',
|
|
984
|
+
` * Aura Retro GBA authored runtime for ${String(identity?.name || 'Aura Retro').replace(/\*\//g, '')}.`,
|
|
985
|
+
' * Generated from the current retro-safe entry module.',
|
|
986
|
+
' */',
|
|
987
|
+
'',
|
|
988
|
+
...emitRuntimeSupport(assets),
|
|
989
|
+
...emitStateDeclarations(stateEntries),
|
|
990
|
+
'',
|
|
991
|
+
...emitStateInitialization(stateEntries),
|
|
992
|
+
...emitFunction('aura_setup', setupNodes, stateContext, compileSetupOrUpdateStatement),
|
|
993
|
+
...emitFunction('aura_update', updateNodes, stateContext, compileSetupOrUpdateStatement),
|
|
994
|
+
...emitFunction('aura_draw', drawNodes, stateContext, compileDrawStatement),
|
|
995
|
+
'__attribute__((naked, section(".start"))) void _start(void) {',
|
|
996
|
+
' __asm__ volatile(',
|
|
997
|
+
' "ldr sp, =0x03007f00\\n"',
|
|
998
|
+
' "bl aura_main\\n"',
|
|
999
|
+
' "b .\\n"',
|
|
1000
|
+
' );',
|
|
1001
|
+
'}',
|
|
1002
|
+
'',
|
|
1003
|
+
'void aura_main(void) {',
|
|
1004
|
+
' REG_DISPCNT = MODE4 | BG2_ENABLE;',
|
|
1005
|
+
' aura_draw_buffer = VRAM_BACK;',
|
|
1006
|
+
' aura_displaying_back_buffer = 0;',
|
|
1007
|
+
' aura_needs_redraw = 1;',
|
|
1008
|
+
' aura_load_palette();',
|
|
1009
|
+
' aura_init_state();',
|
|
1010
|
+
' aura_setup();',
|
|
1011
|
+
' for (;;) {',
|
|
1012
|
+
' wait_vblank();',
|
|
1013
|
+
' aura_keys_previous = aura_keys_current;',
|
|
1014
|
+
' aura_keys_current = (u16)(~REG_KEYINPUT) & 0x03FF;',
|
|
1015
|
+
' aura_update_repeat_frames();',
|
|
1016
|
+
' if (aura_keys_current != aura_keys_previous) aura_needs_redraw = 1;',
|
|
1017
|
+
' aura_update();',
|
|
1018
|
+
' if (!aura_needs_redraw) continue;',
|
|
1019
|
+
' fill_screen(0);',
|
|
1020
|
+
' aura_draw();',
|
|
1021
|
+
' aura_present();',
|
|
1022
|
+
' aura_needs_redraw = 0;',
|
|
1023
|
+
' }',
|
|
1024
|
+
'}',
|
|
1025
|
+
'',
|
|
1026
|
+
];
|
|
1027
|
+
|
|
1028
|
+
return lines.join('\n');
|
|
1029
|
+
}
|