@auraindustry/aurajs 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -7
- 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
package/src/cutscene.mjs
CHANGED
|
@@ -34,11 +34,49 @@ function normalizeCues(cues) {
|
|
|
34
34
|
function collectCueIdsUpTo(cues, time) {
|
|
35
35
|
const ids = new Set();
|
|
36
36
|
for (const cue of cues) {
|
|
37
|
-
if (cue.time
|
|
37
|
+
if (cue.time > time) break;
|
|
38
|
+
ids.add(cue.id);
|
|
38
39
|
}
|
|
39
40
|
return ids;
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function secondsToMilliseconds(value, fallback = 0) {
|
|
44
|
+
const numeric = Number(value);
|
|
45
|
+
return Number.isFinite(numeric) ? numeric * 1000 : fallback;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeProgress(currentTime, duration) {
|
|
49
|
+
const current = Number(currentTime);
|
|
50
|
+
const total = Number(duration);
|
|
51
|
+
if (!Number.isFinite(current) || !Number.isFinite(total) || total <= 0) return 0;
|
|
52
|
+
return Math.max(0, Math.min(1, current / total));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findLatestCueAtOrBefore(cues, time, type = null) {
|
|
56
|
+
let match = null;
|
|
57
|
+
for (const cue of cues) {
|
|
58
|
+
if (cue.time > time) break;
|
|
59
|
+
if (type === null || cue.type === type) {
|
|
60
|
+
match = cue;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return match;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function findNextCue(cues, time) {
|
|
67
|
+
for (const cue of cues) {
|
|
68
|
+
if (cue.time >= time) return cue;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function listCueIdsInOrder(cues, firedCueIds) {
|
|
74
|
+
if (!(firedCueIds instanceof Set) || firedCueIds.size === 0) return [];
|
|
75
|
+
return cues
|
|
76
|
+
.filter((cue) => firedCueIds.has(cue.id))
|
|
77
|
+
.map((cue) => cue.id);
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
export class VideoCutsceneController {
|
|
43
81
|
constructor(videoApi, handle, options = {}) {
|
|
44
82
|
if (!videoApi || typeof videoApi !== 'object') {
|
|
@@ -77,16 +115,32 @@ export class VideoCutsceneController {
|
|
|
77
115
|
|
|
78
116
|
getState() {
|
|
79
117
|
const info = this.getInfo();
|
|
80
|
-
const currentTime = info ? asFiniteNumber(info.currentTime || info.currentTimeSecs,
|
|
118
|
+
const currentTime = info ? asFiniteNumber(info.currentTime || info.currentTimeSecs, this.lastTime) : this.lastTime;
|
|
81
119
|
const duration = info ? asFiniteNumber(info.duration || info.durationSecs, 0) : 0;
|
|
82
|
-
const
|
|
120
|
+
const currentTimeMs = info ? asFiniteNumber(info.currentTimeMs, secondsToMilliseconds(currentTime)) : secondsToMilliseconds(currentTime);
|
|
121
|
+
const durationMs = info ? asFiniteNumber(info.durationMs, secondsToMilliseconds(duration)) : secondsToMilliseconds(duration);
|
|
122
|
+
const firedCueIds = listCueIdsInOrder(this.options.cues, this.firedCueIds);
|
|
123
|
+
const nextCue = findNextCue(this.options.cues, currentTime);
|
|
124
|
+
const activeSubtitle = findLatestCueAtOrBefore(this.options.cues, currentTime, 'subtitle');
|
|
125
|
+
const lastCheckpoint = findLatestCueAtOrBefore(this.options.cues, currentTime, 'checkpoint');
|
|
126
|
+
const playbackProgress = normalizeProgress(currentTime, duration);
|
|
83
127
|
return {
|
|
84
128
|
handle: this.handle,
|
|
85
129
|
info,
|
|
86
130
|
currentTime,
|
|
131
|
+
currentTimeMs,
|
|
87
132
|
duration,
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
durationMs,
|
|
134
|
+
playbackProgress,
|
|
135
|
+
cueCount: this.options.cues.length,
|
|
136
|
+
firedCueCount: firedCueIds.length,
|
|
137
|
+
remainingCueCount: Math.max(0, this.options.cues.length - firedCueIds.length),
|
|
138
|
+
firedCueIds,
|
|
139
|
+
nextCue,
|
|
140
|
+
activeSubtitle,
|
|
141
|
+
lastCheckpoint,
|
|
142
|
+
textureReady: info?.textureReady === true,
|
|
143
|
+
nativeOnly: info?.nativeOnly === true || info?.sourceKind === 'mp4',
|
|
90
144
|
};
|
|
91
145
|
}
|
|
92
146
|
|
|
@@ -202,4 +256,307 @@ export function createVideoCutsceneController(videoApi, handle, options = {}) {
|
|
|
202
256
|
return new VideoCutsceneController(videoApi, handle, options);
|
|
203
257
|
}
|
|
204
258
|
|
|
259
|
+
function requireRuntimeFactory(target, method, label) {
|
|
260
|
+
if (!target || typeof target !== 'object' || typeof target[method] !== 'function') {
|
|
261
|
+
throw new TypeError(`${label} requires ${method}() on the provided runtime API object.`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function createSpatialAudioEmitter(audioApi, path, options = {}) {
|
|
266
|
+
requireRuntimeFactory(audioApi, 'createSpatialEmitter', 'createSpatialAudioEmitter(audioApi, path, options)');
|
|
267
|
+
return audioApi.createSpatialEmitter(path, options);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function createVideoBillboardSurface(videoApi, source, options = {}) {
|
|
271
|
+
requireRuntimeFactory(videoApi, 'createBillboardSurface', 'createVideoBillboardSurface(videoApi, source, options)');
|
|
272
|
+
return videoApi.createBillboardSurface(source, options);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeAudioSeekMode(value) {
|
|
276
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
277
|
+
switch (normalized) {
|
|
278
|
+
case 'none':
|
|
279
|
+
case 'restart':
|
|
280
|
+
case 'presentation-default':
|
|
281
|
+
return normalized;
|
|
282
|
+
default:
|
|
283
|
+
return 'presentation-default';
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function safePresentationState(presentation, fallback = null) {
|
|
288
|
+
if (!presentation || typeof presentation.getState !== 'function') return fallback;
|
|
289
|
+
const state = presentation.getState();
|
|
290
|
+
return state && typeof state === 'object' ? state : fallback;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function summarizePresentationState(state) {
|
|
294
|
+
const presentationState = state && typeof state === 'object' ? state : null;
|
|
295
|
+
const surface = presentationState?.surface && typeof presentationState.surface === 'object'
|
|
296
|
+
? presentationState.surface
|
|
297
|
+
: null;
|
|
298
|
+
const emitter = presentationState?.emitter && typeof presentationState.emitter === 'object'
|
|
299
|
+
? presentationState.emitter
|
|
300
|
+
: null;
|
|
301
|
+
const info = surface?.info && typeof surface.info === 'object' ? surface.info : null;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
targetAttached: presentationState?.targetAttached === true,
|
|
305
|
+
hasAudio: !!emitter,
|
|
306
|
+
audioAttached: emitter?.attached === true,
|
|
307
|
+
handle: Number.isInteger(surface?.handle) && surface.handle > 0 ? surface.handle : 0,
|
|
308
|
+
textureHandle: Number.isInteger(surface?.textureHandle) && surface.textureHandle > 0 ? surface.textureHandle : 0,
|
|
309
|
+
playbackState: typeof info?.state === 'string' ? info.state : null,
|
|
310
|
+
currentTime: asFiniteNumber(info?.currentTime || info?.currentTimeSecs, 0),
|
|
311
|
+
currentTimeMs: asFiniteNumber(info?.currentTimeMs, secondsToMilliseconds(info?.currentTime || info?.currentTimeSecs, 0)),
|
|
312
|
+
duration: asFiniteNumber(info?.duration || info?.durationSecs, 0),
|
|
313
|
+
durationMs: asFiniteNumber(info?.durationMs, secondsToMilliseconds(info?.duration || info?.durationSecs, 0)),
|
|
314
|
+
playbackProgress: normalizeProgress(info?.currentTime || info?.currentTimeSecs, info?.duration || info?.durationSecs),
|
|
315
|
+
sourceKind: typeof info?.sourceKind === 'string' ? info.sourceKind : null,
|
|
316
|
+
textureReady: typeof info?.textureReady === 'boolean'
|
|
317
|
+
? info.textureReady
|
|
318
|
+
: (Number.isInteger(surface?.textureHandle) && surface.textureHandle > 0),
|
|
319
|
+
nativeOnly: typeof info?.nativeOnly === 'boolean'
|
|
320
|
+
? info.nativeOnly
|
|
321
|
+
: info?.sourceKind === 'mp4',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function createMediaPresentationController(runtime, source, options = {}) {
|
|
326
|
+
if (!runtime || typeof runtime !== 'object') {
|
|
327
|
+
throw new TypeError('createMediaPresentationController(runtime, source, options) requires a runtime object.');
|
|
328
|
+
}
|
|
329
|
+
requireRuntimeFactory(runtime.video, 'createPresentation', 'createMediaPresentationController(runtime, source, options)');
|
|
330
|
+
|
|
331
|
+
const presentation = runtime.video.createPresentation(source, options);
|
|
332
|
+
const audioSeekMode = normalizeAudioSeekMode(options?.audioSeekMode);
|
|
333
|
+
const usesCutsceneCues = !!(
|
|
334
|
+
options
|
|
335
|
+
&& typeof options === 'object'
|
|
336
|
+
&& (
|
|
337
|
+
Array.isArray(options.cues)
|
|
338
|
+
|| typeof options.onCue === 'function'
|
|
339
|
+
|| typeof options.onSubtitle === 'function'
|
|
340
|
+
|| typeof options.onCheckpoint === 'function'
|
|
341
|
+
|| typeof options.onStateChange === 'function'
|
|
342
|
+
)
|
|
343
|
+
);
|
|
344
|
+
let cutscene = null;
|
|
345
|
+
let unloaded = false;
|
|
346
|
+
let lastAction = 'idle';
|
|
347
|
+
let lastResult = null;
|
|
348
|
+
let lastAudioResync = null;
|
|
349
|
+
let lastPresentationState = safePresentationState(presentation);
|
|
350
|
+
|
|
351
|
+
function ensureCutsceneController() {
|
|
352
|
+
if (!usesCutsceneCues) return null;
|
|
353
|
+
if (cutscene) return cutscene;
|
|
354
|
+
if (!runtime.video || typeof runtime.video.getInfo !== 'function') return null;
|
|
355
|
+
const state = safePresentationState(presentation, lastPresentationState);
|
|
356
|
+
const handle = Number(state?.surface?.handle || 0);
|
|
357
|
+
if (!Number.isInteger(handle) || handle <= 0) return null;
|
|
358
|
+
cutscene = createVideoCutsceneController(runtime.video, handle, options);
|
|
359
|
+
return cutscene;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function syncCutsceneAfterExternalSeek(controller, targetTime) {
|
|
363
|
+
const normalizedTarget = Number(targetTime);
|
|
364
|
+
if (!Number.isFinite(normalizedTarget) || normalizedTarget < 0) {
|
|
365
|
+
return controller ? controller.sync() : null;
|
|
366
|
+
}
|
|
367
|
+
controller.firedCueIds = collectCueIdsUpTo(controller.options.cues, normalizedTarget);
|
|
368
|
+
controller.lastTime = normalizedTarget;
|
|
369
|
+
return controller.sync();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function capturePresentationState() {
|
|
373
|
+
lastPresentationState = safePresentationState(presentation, lastPresentationState);
|
|
374
|
+
return lastPresentationState;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function recordAction(action, result, audioResync = null) {
|
|
378
|
+
lastAction = action;
|
|
379
|
+
lastResult = result ?? null;
|
|
380
|
+
if (audioResync !== null) {
|
|
381
|
+
lastAudioResync = audioResync;
|
|
382
|
+
}
|
|
383
|
+
capturePresentationState();
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function requestAudioResync(reason, timeSecs) {
|
|
388
|
+
if (audioSeekMode === 'none') {
|
|
389
|
+
lastAudioResync = { ok: false, reasonCode: 'audio_seek_mode_disabled', reason, timeSecs };
|
|
390
|
+
return lastAudioResync;
|
|
391
|
+
}
|
|
392
|
+
if (audioSeekMode === 'restart' && typeof presentation.restartAudio === 'function') {
|
|
393
|
+
const result = presentation.restartAudio({ reason, timeSecs });
|
|
394
|
+
lastAudioResync = result ?? { ok: true, reasonCode: null, reason, timeSecs };
|
|
395
|
+
return lastAudioResync;
|
|
396
|
+
}
|
|
397
|
+
if (typeof presentation.resyncAudio === 'function') {
|
|
398
|
+
const result = presentation.resyncAudio({ reason, timeSecs, mode: audioSeekMode });
|
|
399
|
+
lastAudioResync = result ?? { ok: true, reasonCode: null, reason, timeSecs };
|
|
400
|
+
return lastAudioResync;
|
|
401
|
+
}
|
|
402
|
+
lastAudioResync = { ok: false, reasonCode: 'audio_resync_unavailable', reason, timeSecs };
|
|
403
|
+
return lastAudioResync;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function currentState() {
|
|
407
|
+
const presentationState = capturePresentationState();
|
|
408
|
+
const media = summarizePresentationState(presentationState);
|
|
409
|
+
const cutsceneState = cutscene ? cutscene.getState() : null;
|
|
410
|
+
const emitterState = presentationState?.emitter || null;
|
|
411
|
+
return {
|
|
412
|
+
unloaded,
|
|
413
|
+
source,
|
|
414
|
+
audioSeekMode,
|
|
415
|
+
lastAction,
|
|
416
|
+
lastResult,
|
|
417
|
+
lastAudioResync,
|
|
418
|
+
media,
|
|
419
|
+
surface: presentationState?.surface || null,
|
|
420
|
+
emitter: emitterState,
|
|
421
|
+
presentation: presentationState,
|
|
422
|
+
cutscene: cutsceneState,
|
|
423
|
+
observability: {
|
|
424
|
+
nativeOnly: media.nativeOnly,
|
|
425
|
+
textureReady: media.textureReady,
|
|
426
|
+
sourceKind: media.sourceKind,
|
|
427
|
+
currentTimeMs: media.currentTimeMs,
|
|
428
|
+
durationMs: media.durationMs,
|
|
429
|
+
playbackProgress: media.playbackProgress,
|
|
430
|
+
audioSeekMode,
|
|
431
|
+
spatialAudioAttached: emitterState?.attached === true,
|
|
432
|
+
spatialAudioReasonCode: typeof lastAudioResync?.reasonCode === 'string' && lastAudioResync.reasonCode.length > 0
|
|
433
|
+
? lastAudioResync.reasonCode
|
|
434
|
+
: (typeof emitterState?.lastReasonCode === 'string' ? emitterState.lastReasonCode : null),
|
|
435
|
+
cueCoverage: {
|
|
436
|
+
total: cutsceneState?.cueCount || 0,
|
|
437
|
+
fired: cutsceneState?.firedCueCount || 0,
|
|
438
|
+
remaining: cutsceneState?.remainingCueCount || 0,
|
|
439
|
+
},
|
|
440
|
+
activeSubtitle: cutsceneState?.activeSubtitle || null,
|
|
441
|
+
lastCheckpoint: cutsceneState?.lastCheckpoint || null,
|
|
442
|
+
portability: {
|
|
443
|
+
browserParity: 'out_of_scope',
|
|
444
|
+
muxedAudioGuarantees: 'out_of_scope',
|
|
445
|
+
cinematicEditor: 'out_of_scope',
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
play() {
|
|
453
|
+
unloaded = false;
|
|
454
|
+
const result = presentation.play();
|
|
455
|
+
const controller = ensureCutsceneController();
|
|
456
|
+
if (controller) controller.sync();
|
|
457
|
+
return recordAction('play', result);
|
|
458
|
+
},
|
|
459
|
+
pause() {
|
|
460
|
+
const result = presentation.pause();
|
|
461
|
+
if (cutscene) cutscene.sync();
|
|
462
|
+
return recordAction('pause', result);
|
|
463
|
+
},
|
|
464
|
+
resume() {
|
|
465
|
+
unloaded = false;
|
|
466
|
+
const result = presentation.resume();
|
|
467
|
+
const controller = ensureCutsceneController();
|
|
468
|
+
if (controller) controller.sync();
|
|
469
|
+
return recordAction('resume', result);
|
|
470
|
+
},
|
|
471
|
+
stop() {
|
|
472
|
+
const result = presentation.stop();
|
|
473
|
+
if (cutscene) {
|
|
474
|
+
cutscene.firedCueIds.clear();
|
|
475
|
+
cutscene.lastInfo = null;
|
|
476
|
+
cutscene.lastTime = 0;
|
|
477
|
+
cutscene.lastState = 'stopped';
|
|
478
|
+
cutscene.sync();
|
|
479
|
+
}
|
|
480
|
+
return recordAction('stop', result);
|
|
481
|
+
},
|
|
482
|
+
attach(target, attachOptions = {}) {
|
|
483
|
+
return recordAction('attach', presentation.attach(target, attachOptions));
|
|
484
|
+
},
|
|
485
|
+
update() {
|
|
486
|
+
const result = presentation.update();
|
|
487
|
+
const controller = ensureCutsceneController();
|
|
488
|
+
if (controller) controller.update();
|
|
489
|
+
return recordAction('update', result);
|
|
490
|
+
},
|
|
491
|
+
draw(drawOptions = {}) {
|
|
492
|
+
return recordAction('draw', presentation.draw(drawOptions));
|
|
493
|
+
},
|
|
494
|
+
seek(timeSecs) {
|
|
495
|
+
const result = presentation.seek(timeSecs);
|
|
496
|
+
const controller = ensureCutsceneController();
|
|
497
|
+
if (controller) syncCutsceneAfterExternalSeek(controller, timeSecs);
|
|
498
|
+
return recordAction('seek', result, requestAudioResync('seek', Number(timeSecs)));
|
|
499
|
+
},
|
|
500
|
+
skip(targetTime = null) {
|
|
501
|
+
const controller = ensureCutsceneController();
|
|
502
|
+
if (controller) {
|
|
503
|
+
const info = controller.getInfo();
|
|
504
|
+
const fallbackDuration = info ? asFiniteNumber(info.duration || info.durationSecs, 0) : 0;
|
|
505
|
+
const resolvedTarget = targetTime != null && Number.isFinite(Number(targetTime))
|
|
506
|
+
? Math.max(0, Number(targetTime))
|
|
507
|
+
: (controller.options.skipTarget != null ? controller.options.skipTarget : fallbackDuration);
|
|
508
|
+
if (presentation && typeof presentation.seek === 'function') {
|
|
509
|
+
const result = presentation.seek(resolvedTarget);
|
|
510
|
+
const synced = syncCutsceneAfterExternalSeek(controller, resolvedTarget);
|
|
511
|
+
recordAction('skip', result ?? synced, requestAudioResync('skip', resolvedTarget));
|
|
512
|
+
}
|
|
513
|
+
return currentState();
|
|
514
|
+
}
|
|
515
|
+
const resolvedTarget = targetTime == null ? 0 : targetTime;
|
|
516
|
+
return recordAction('skip', presentation.seek(resolvedTarget), requestAudioResync('skip', Number(resolvedTarget)));
|
|
517
|
+
},
|
|
518
|
+
exportCheckpointState() {
|
|
519
|
+
return {
|
|
520
|
+
source,
|
|
521
|
+
audioSeekMode,
|
|
522
|
+
presentation: currentState().media,
|
|
523
|
+
cutscene: cutscene ? cutscene.exportCheckpointState() : null,
|
|
524
|
+
};
|
|
525
|
+
},
|
|
526
|
+
restoreCheckpointState(snapshot) {
|
|
527
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
528
|
+
throw new TypeError('createMediaPresentationController(...).restoreCheckpointState(snapshot) requires an object snapshot.');
|
|
529
|
+
}
|
|
530
|
+
unloaded = false;
|
|
531
|
+
const cutsceneSnapshot = snapshot.cutscene && typeof snapshot.cutscene === 'object'
|
|
532
|
+
? snapshot.cutscene
|
|
533
|
+
: snapshot;
|
|
534
|
+
const targetTime = cutsceneSnapshot && Number.isFinite(Number(cutsceneSnapshot.currentTime))
|
|
535
|
+
? Math.max(0, Number(cutsceneSnapshot.currentTime))
|
|
536
|
+
: asFiniteNumber(snapshot.presentation?.currentTime, 0);
|
|
537
|
+
let result = null;
|
|
538
|
+
const controller = ensureCutsceneController();
|
|
539
|
+
if (controller && cutsceneSnapshot && Number.isFinite(Number(cutsceneSnapshot.currentTime))) {
|
|
540
|
+
result = controller.restoreCheckpointState(cutsceneSnapshot);
|
|
541
|
+
} else if (typeof presentation.seek === 'function') {
|
|
542
|
+
result = presentation.seek(targetTime);
|
|
543
|
+
}
|
|
544
|
+
return recordAction(
|
|
545
|
+
'restoreCheckpointState',
|
|
546
|
+
result,
|
|
547
|
+
requestAudioResync('restore-checkpoint', targetTime),
|
|
548
|
+
);
|
|
549
|
+
},
|
|
550
|
+
unload() {
|
|
551
|
+
const result = presentation.unload();
|
|
552
|
+
unloaded = true;
|
|
553
|
+
cutscene = null;
|
|
554
|
+
return recordAction('unload', result);
|
|
555
|
+
},
|
|
556
|
+
getState() {
|
|
557
|
+
return currentState();
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
205
562
|
export default createVideoCutsceneController;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GAME_ACTION_REQUEST_SCHEMA_VERSION,
|
|
3
|
+
GAME_ACTION_RESULT_SCHEMA_VERSION,
|
|
4
|
+
GAME_ACTION_SCHEMA_VERSION,
|
|
5
|
+
} from './game-action-runtime.mjs';
|
|
6
|
+
import { GAME_STATE_SCHEMA_VERSION } from './game-state-runtime.mjs';
|
|
7
|
+
|
|
8
|
+
export class DevCliActionError extends Error {
|
|
9
|
+
constructor(reasonCode, message, details = {}) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'DevCliActionError';
|
|
12
|
+
this.reasonCode = reasonCode;
|
|
13
|
+
this.details = details;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function runActionSchemaFlow({
|
|
18
|
+
parsed,
|
|
19
|
+
projectRoot,
|
|
20
|
+
loadConfig,
|
|
21
|
+
runHeadlessActionSchema,
|
|
22
|
+
}) {
|
|
23
|
+
const config = await loadConfig({ projectRoot, mode: 'build' });
|
|
24
|
+
const file = parsed.file || config.build.entry;
|
|
25
|
+
|
|
26
|
+
const schemaRun = await runHeadlessActionSchema({
|
|
27
|
+
projectRoot,
|
|
28
|
+
file,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!schemaRun || typeof schemaRun !== 'object') {
|
|
32
|
+
throw new DevCliActionError(
|
|
33
|
+
'action_runtime_failed',
|
|
34
|
+
'Action schema runtime returned an invalid result.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const schemaResult = schemaRun.schemaResult;
|
|
39
|
+
if (!schemaResult || typeof schemaResult !== 'object') {
|
|
40
|
+
throw new DevCliActionError(
|
|
41
|
+
'action_runtime_failed',
|
|
42
|
+
'Action schema runtime returned an invalid schema result.',
|
|
43
|
+
{ schemaRun },
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (schemaResult.ok !== true) {
|
|
47
|
+
throw new DevCliActionError(
|
|
48
|
+
typeof schemaResult.reasonCode === 'string' ? schemaResult.reasonCode : 'action_contract_unavailable',
|
|
49
|
+
schemaResult.detail
|
|
50
|
+
? `Action schema export failed: ${schemaResult.detail}`
|
|
51
|
+
: 'Action schema export failed.',
|
|
52
|
+
{ schemaRun, schemaResult },
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (!schemaResult.payload || typeof schemaResult.payload !== 'object') {
|
|
56
|
+
throw new DevCliActionError(
|
|
57
|
+
'action_schema_invalid_payload',
|
|
58
|
+
'Action schema runtime returned an invalid schema payload.',
|
|
59
|
+
{ schemaRun, schemaResult },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (schemaResult.payload.schemaVersion !== parsed.schemaVersion) {
|
|
63
|
+
throw new DevCliActionError(
|
|
64
|
+
'schema_version_mismatch',
|
|
65
|
+
`Action schema "${schemaResult.payload.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
|
|
66
|
+
{ exported: schemaResult.payload.schemaVersion, requested: parsed.schemaVersion },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
file,
|
|
72
|
+
payload: schemaResult.payload,
|
|
73
|
+
schemaRun,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function runActionRunFlow({
|
|
78
|
+
parsed,
|
|
79
|
+
projectRoot,
|
|
80
|
+
loadConfig,
|
|
81
|
+
readJsonInputFile,
|
|
82
|
+
runHeadlessActionRun,
|
|
83
|
+
}) {
|
|
84
|
+
const config = await loadConfig({ projectRoot, mode: 'build' });
|
|
85
|
+
const file = parsed.file || config.build.entry;
|
|
86
|
+
const request = readJsonInputFile(parsed.requestPath, 'action request payload', 'action_request_input');
|
|
87
|
+
const statePayload = parsed.statePath
|
|
88
|
+
? readJsonInputFile(parsed.statePath, 'action seed state payload', 'action_seed_state_input')
|
|
89
|
+
: null;
|
|
90
|
+
|
|
91
|
+
if (!request || typeof request !== 'object' || Array.isArray(request)) {
|
|
92
|
+
throw new DevCliActionError(
|
|
93
|
+
'invalid_action_request',
|
|
94
|
+
'Action request payload must be a JSON object.',
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (request.schemaVersion !== GAME_ACTION_REQUEST_SCHEMA_VERSION) {
|
|
98
|
+
throw new DevCliActionError(
|
|
99
|
+
'schema_version_mismatch',
|
|
100
|
+
`Unsupported action request schema "${request.schemaVersion}". Expected "${GAME_ACTION_REQUEST_SCHEMA_VERSION}".`,
|
|
101
|
+
{ exported: request.schemaVersion, requested: GAME_ACTION_REQUEST_SCHEMA_VERSION },
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (statePayload != null) {
|
|
105
|
+
validateSeedStatePayload(statePayload);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const actionRun = await runHeadlessActionRun({
|
|
109
|
+
projectRoot,
|
|
110
|
+
file,
|
|
111
|
+
request,
|
|
112
|
+
statePayload,
|
|
113
|
+
frames: parsed.frames,
|
|
114
|
+
mode: parsed.mode,
|
|
115
|
+
includeState: parsed.includeState,
|
|
116
|
+
frameIndex: parsed.frames,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (!actionRun || typeof actionRun !== 'object') {
|
|
120
|
+
throw new DevCliActionError(
|
|
121
|
+
'action_runtime_failed',
|
|
122
|
+
'Action run runtime returned an invalid result.',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (actionRun.seedStateApplyResult && actionRun.seedStateApplyResult.ok !== true) {
|
|
126
|
+
throw new DevCliActionError(
|
|
127
|
+
typeof actionRun.seedStateApplyResult.reasonCode === 'string'
|
|
128
|
+
? actionRun.seedStateApplyResult.reasonCode
|
|
129
|
+
: 'state_apply_failed',
|
|
130
|
+
actionRun.seedStateApplyResult.detail
|
|
131
|
+
? `Action seed state apply failed: ${actionRun.seedStateApplyResult.detail}`
|
|
132
|
+
: 'Action seed state apply failed.',
|
|
133
|
+
{
|
|
134
|
+
actionRun,
|
|
135
|
+
seedStateApplyResult: actionRun.seedStateApplyResult,
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const actionResult = actionRun.actionResult;
|
|
141
|
+
if (!actionResult || typeof actionResult !== 'object') {
|
|
142
|
+
throw new DevCliActionError(
|
|
143
|
+
'action_runtime_failed',
|
|
144
|
+
'Action run runtime returned an invalid action result.',
|
|
145
|
+
{ actionRun },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (actionResult.schemaVersion !== parsed.schemaVersion) {
|
|
149
|
+
throw new DevCliActionError(
|
|
150
|
+
'schema_version_mismatch',
|
|
151
|
+
`Action result schema "${actionResult.schemaVersion}" does not match requested schema "${parsed.schemaVersion}".`,
|
|
152
|
+
{ exported: actionResult.schemaVersion, requested: parsed.schemaVersion },
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (actionResult.ok !== true) {
|
|
156
|
+
throw new DevCliActionError(
|
|
157
|
+
typeof actionResult.reasonCode === 'string' ? actionResult.reasonCode : 'action_run_failed',
|
|
158
|
+
actionResult.detail
|
|
159
|
+
? `Action run failed: ${actionResult.detail}`
|
|
160
|
+
: 'Action run failed.',
|
|
161
|
+
{ actionRun, actionResult },
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const report = orderActionRunReport({
|
|
166
|
+
...actionResult,
|
|
167
|
+
...(actionRun.stateResult?.payload
|
|
168
|
+
? {
|
|
169
|
+
followUpFrames: parsed.frames,
|
|
170
|
+
state: validateExportedStatePayload(actionRun.stateResult.payload),
|
|
171
|
+
}
|
|
172
|
+
: {}),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
file,
|
|
177
|
+
report,
|
|
178
|
+
actionRun,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function serializeActionPayload(payload, compact = false) {
|
|
183
|
+
return compact
|
|
184
|
+
? `${JSON.stringify(payload)}\n`
|
|
185
|
+
: `${JSON.stringify(payload, null, 2)}\n`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function validateExportedStatePayload(payload) {
|
|
189
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
190
|
+
throw new DevCliActionError(
|
|
191
|
+
'action_runtime_failed',
|
|
192
|
+
'Action follow-up state export returned an invalid payload.',
|
|
193
|
+
{ payload },
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (payload.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
|
|
197
|
+
throw new DevCliActionError(
|
|
198
|
+
'schema_version_mismatch',
|
|
199
|
+
`Action follow-up state schema "${payload.schemaVersion}" does not match expected schema "${GAME_STATE_SCHEMA_VERSION}".`,
|
|
200
|
+
{ payload },
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return payload;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function validateSeedStatePayload(payload) {
|
|
207
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
208
|
+
throw new DevCliActionError(
|
|
209
|
+
'invalid_schema_payload',
|
|
210
|
+
'Action seed state payload must be a JSON object.',
|
|
211
|
+
{ payload },
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
if (payload.schemaVersion !== GAME_STATE_SCHEMA_VERSION) {
|
|
215
|
+
throw new DevCliActionError(
|
|
216
|
+
'schema_version_mismatch',
|
|
217
|
+
`Action seed state schema "${payload.schemaVersion}" does not match expected schema "${GAME_STATE_SCHEMA_VERSION}".`,
|
|
218
|
+
{ payload },
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return payload;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function orderActionRunReport(payload) {
|
|
225
|
+
const ordered = {};
|
|
226
|
+
const keyOrder = [
|
|
227
|
+
'schemaVersion',
|
|
228
|
+
'ok',
|
|
229
|
+
'reasonCode',
|
|
230
|
+
'actionId',
|
|
231
|
+
'detail',
|
|
232
|
+
'output',
|
|
233
|
+
'followUpFrames',
|
|
234
|
+
'state',
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (const key of keyOrder) {
|
|
238
|
+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
239
|
+
ordered[key] = payload[key];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return ordered;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export {
|
|
247
|
+
GAME_ACTION_SCHEMA_VERSION,
|
|
248
|
+
GAME_ACTION_RESULT_SCHEMA_VERSION,
|
|
249
|
+
};
|