@forgeax/engine-app 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +424 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/animation-asset-lookup.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-asset-lookup.unit.test.d.ts.map +1 -0
- package/dist/__tests__/callback-deletion.unit.test.d.ts +2 -0
- package/dist/__tests__/callback-deletion.unit.test.d.ts.map +1 -0
- package/dist/__tests__/callback-inference.test-d.d.ts +2 -0
- package/dist/__tests__/callback-inference.test-d.d.ts.map +1 -0
- package/dist/__tests__/component-introspection.unit.test.d.ts +2 -0
- package/dist/__tests__/component-introspection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/consumer-migration.unit.test.d.ts +2 -0
- package/dist/__tests__/consumer-migration.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-error-retention.integration.test.d.ts +2 -0
- package/dist/__tests__/create-app-error-retention.integration.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-features.test.d.ts +2 -0
- package/dist/__tests__/create-app-features.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-listener-sync.test.d.ts +2 -0
- package/dist/__tests__/create-app-listener-sync.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-lock-provider.test.d.ts +2 -0
- package/dist/__tests__/create-app-lock-provider.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts +2 -0
- package/dist/__tests__/create-app-physics-plugin-failure.integration.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-plugin.test.d.ts +2 -0
- package/dist/__tests__/create-app-plugin.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-stop.test.d.ts +2 -0
- package/dist/__tests__/create-app-stop.test.d.ts.map +1 -0
- package/dist/__tests__/create-app-time.unit.test.d.ts +2 -0
- package/dist/__tests__/create-app-time.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-app.test-d.d.ts +2 -0
- package/dist/__tests__/create-app.test-d.d.ts.map +1 -0
- package/dist/__tests__/create-app.test.d.ts +2 -0
- package/dist/__tests__/create-app.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-error.integration.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-error.integration.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/docs-gate-green.unit.test.d.ts +2 -0
- package/dist/__tests__/docs-gate-green.unit.test.d.ts.map +1 -0
- package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts +2 -0
- package/dist/__tests__/draw-source-injected-world-update-visibility.test.d.ts.map +1 -0
- package/dist/__tests__/draw-source-single-world-regression.test.d.ts +2 -0
- package/dist/__tests__/draw-source-single-world-regression.test.d.ts.map +1 -0
- package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts +2 -0
- package/dist/__tests__/engine-worker-bootstrap-channel.unit.test.d.ts.map +1 -0
- package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts +2 -0
- package/dist/__tests__/engine-worker-world-swap.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors-pointer-lock-failed.test.d.ts +2 -0
- package/dist/__tests__/errors-pointer-lock-failed.test.d.ts.map +1 -0
- package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-bootstrap-isolation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-error-detail.test-d.d.ts +2 -0
- package/dist/__tests__/execution-error-detail.test-d.d.ts.map +1 -0
- package/dist/__tests__/execution-error-detail.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-error-detail.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-frame-order.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-frame-order.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-frame-protocol.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-frame-protocol.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-input-sample.contract.test.d.ts +2 -0
- package/dist/__tests__/execution-input-sample.contract.test.d.ts.map +1 -0
- package/dist/__tests__/execution-measurement.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-measurement.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-public-api.test-d.d.ts +2 -0
- package/dist/__tests__/execution-public-api.test-d.d.ts.map +1 -0
- package/dist/__tests__/execution-public-api.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-public-api.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-rebuild.contract.test.d.ts +2 -0
- package/dist/__tests__/execution-rebuild.contract.test.d.ts.map +1 -0
- package/dist/__tests__/execution-report.contract.test.d.ts +2 -0
- package/dist/__tests__/execution-report.contract.test.d.ts.map +1 -0
- package/dist/__tests__/execution-report.types.test-d.d.ts +2 -0
- package/dist/__tests__/execution-report.types.test-d.d.ts.map +1 -0
- package/dist/__tests__/execution-schema-freshness.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-schema-freshness.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-selector.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-selector.unit.test.d.ts.map +1 -0
- package/dist/__tests__/execution-shared-kernel.unit.test.d.ts +2 -0
- package/dist/__tests__/execution-shared-kernel.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fallback-camera.unit.test.d.ts +2 -0
- package/dist/__tests__/fallback-camera.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-loop-time-elapsed.test.d.ts +2 -0
- package/dist/__tests__/frame-loop-time-elapsed.test.d.ts.map +1 -0
- package/dist/__tests__/frame-loop-time.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-loop-time.unit.test.d.ts.map +1 -0
- package/dist/__tests__/frame-loop-world-array.test.d.ts +2 -0
- package/dist/__tests__/frame-loop-world-array.test.d.ts.map +1 -0
- package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts +2 -0
- package/dist/__tests__/kernel-worker-runtime.unit.test.d.ts.map +1 -0
- package/dist/__tests__/migration-surface-inventory.unit.test.d.ts +2 -0
- package/dist/__tests__/migration-surface-inventory.unit.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-policy.integration.test.d.ts +2 -0
- package/dist/__tests__/plugin-policy.integration.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-profiles.unit.test.d.ts +2 -0
- package/dist/__tests__/plugin-profiles.unit.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-default-off.test.d.ts +2 -0
- package/dist/__tests__/profiler-default-off.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-frame-token.test.d.ts +2 -0
- package/dist/__tests__/profiler-frame-token.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-catalog.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts +2 -0
- package/dist/__tests__/profiler-phase-integration.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-remote-bridge.test.d.ts +2 -0
- package/dist/__tests__/profiler-remote-bridge.test.d.ts.map +1 -0
- package/dist/__tests__/remote-integration.test.d.ts +2 -0
- package/dist/__tests__/remote-integration.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-capture-capability.test-d.d.ts +2 -0
- package/dist/__tests__/rhi-capture-capability.test-d.d.ts.map +1 -0
- package/dist/__tests__/rhi-capture-host.integration.test.d.ts +2 -0
- package/dist/__tests__/rhi-capture-host.integration.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-capture.browser.test.d.ts +2 -0
- package/dist/__tests__/rhi-capture.browser.test.d.ts.map +1 -0
- package/dist/__tests__/surface-handoff.test.d.ts +2 -0
- package/dist/__tests__/surface-handoff.test.d.ts.map +1 -0
- package/dist/__tests__/types-m2.test-d.d.ts +2 -0
- package/dist/__tests__/types-m2.test-d.d.ts.map +1 -0
- package/dist/__tests__/visibility-remote.integration.test.d.ts +2 -0
- package/dist/__tests__/visibility-remote.integration.test.d.ts.map +1 -0
- package/dist/__tests__/worker-execution-stop.test.d.ts +2 -0
- package/dist/__tests__/worker-execution-stop.test.d.ts.map +1 -0
- package/dist/animation-asset-lookup.d.ts +7 -0
- package/dist/animation-asset-lookup.d.ts.map +1 -0
- package/dist/browser-frame-signal.d.ts +17 -0
- package/dist/browser-frame-signal.d.ts.map +1 -0
- package/dist/canvas-policy.d.ts +4 -0
- package/dist/canvas-policy.d.ts.map +1 -0
- package/dist/create-app.d.ts +78 -0
- package/dist/create-app.d.ts.map +1 -0
- package/dist/engine-worker-runtime.mjs +827 -0
- package/dist/engine-worker-runtime.mjs.map +1 -0
- package/dist/errors.d.ts +254 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/execution/attached-world-swap.d.ts +10 -0
- package/dist/execution/attached-world-swap.d.ts.map +1 -0
- package/dist/execution/bootstrap-entry.d.ts +30 -0
- package/dist/execution/bootstrap-entry.d.ts.map +1 -0
- package/dist/execution/bootstrap-url.d.ts +5 -0
- package/dist/execution/bootstrap-url.d.ts.map +1 -0
- package/dist/execution/capabilities.d.ts +5 -0
- package/dist/execution/capabilities.d.ts.map +1 -0
- package/dist/execution/control.d.ts +12 -0
- package/dist/execution/control.d.ts.map +1 -0
- package/dist/execution/engine-worker-runtime.d.ts +2 -0
- package/dist/execution/engine-worker-runtime.d.ts.map +1 -0
- package/dist/execution/engine-worker.d.ts +24 -0
- package/dist/execution/engine-worker.d.ts.map +1 -0
- package/dist/execution/host-controller.d.ts +16 -0
- package/dist/execution/host-controller.d.ts.map +1 -0
- package/dist/execution/index.d.ts +10 -0
- package/dist/execution/index.d.ts.map +1 -0
- package/dist/execution/kernel-pool.d.ts +14 -0
- package/dist/execution/kernel-pool.d.ts.map +1 -0
- package/dist/execution/kernel-worker-runtime.d.ts +2 -0
- package/dist/execution/kernel-worker-runtime.d.ts.map +1 -0
- package/dist/execution/measurement.d.ts +7 -0
- package/dist/execution/measurement.d.ts.map +1 -0
- package/dist/execution/protocol.d.ts +87 -0
- package/dist/execution/protocol.d.ts.map +1 -0
- package/dist/execution/report.d.ts +5 -0
- package/dist/execution/report.d.ts.map +1 -0
- package/dist/execution/schema.d.ts +4 -0
- package/dist/execution/schema.d.ts.map +1 -0
- package/dist/execution/selector.d.ts +10 -0
- package/dist/execution/selector.d.ts.map +1 -0
- package/dist/execution/types.d.ts +123 -0
- package/dist/execution/types.d.ts.map +1 -0
- package/dist/fallback-camera.d.ts +8 -0
- package/dist/fallback-camera.d.ts.map +1 -0
- package/dist/game-context.d.ts +98 -0
- package/dist/game-context.d.ts.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +3980 -0
- package/dist/index.mjs.map +1 -0
- package/dist/input-map-plugin.d.ts +5 -0
- package/dist/input-map-plugin.d.ts.map +1 -0
- package/dist/input-plugin.d.ts +4 -0
- package/dist/input-plugin.d.ts.map +1 -0
- package/dist/internal/assembled-engine-profile.d.ts +5 -0
- package/dist/internal/assembled-engine-profile.d.ts.map +1 -0
- package/dist/internal/assets-world-plugin.d.ts +7 -0
- package/dist/internal/assets-world-plugin.d.ts.map +1 -0
- package/dist/internal/browser-remote-bridge.d.ts +24 -0
- package/dist/internal/browser-remote-bridge.d.ts.map +1 -0
- package/dist/internal/browser-rhi-debug-runtime.d.ts +18 -0
- package/dist/internal/browser-rhi-debug-runtime.d.ts.map +1 -0
- package/dist/internal/component-introspection.d.ts +24 -0
- package/dist/internal/component-introspection.d.ts.map +1 -0
- package/dist/internal/debug-draw.d.ts +9 -0
- package/dist/internal/debug-draw.d.ts.map +1 -0
- package/dist/internal/ecs-import.d.ts +6 -0
- package/dist/internal/ecs-import.d.ts.map +1 -0
- package/dist/internal/engine-profile-common.d.ts +9 -0
- package/dist/internal/engine-profile-common.d.ts.map +1 -0
- package/dist/internal/error-fanout.d.ts +66 -0
- package/dist/internal/error-fanout.d.ts.map +1 -0
- package/dist/internal/frame-loop.d.ts +38 -0
- package/dist/internal/frame-loop.d.ts.map +1 -0
- package/dist/internal/input-attach.d.ts +44 -0
- package/dist/internal/input-attach.d.ts.map +1 -0
- package/dist/internal/main-engine-profile.d.ts +19 -0
- package/dist/internal/main-engine-profile.d.ts.map +1 -0
- package/dist/internal/remote-serve-flag.d.ts +19 -0
- package/dist/internal/remote-serve-flag.d.ts.map +1 -0
- package/dist/internal/remote-server-plugin.d.ts +8 -0
- package/dist/internal/remote-server-plugin.d.ts.map +1 -0
- package/dist/internal/rhi-capture.d.ts +19 -0
- package/dist/internal/rhi-capture.d.ts.map +1 -0
- package/dist/internal/rhi-debug-flag.d.ts +18 -0
- package/dist/internal/rhi-debug-flag.d.ts.map +1 -0
- package/dist/internal/worker-engine-profile.d.ts +15 -0
- package/dist/internal/worker-engine-profile.d.ts.map +1 -0
- package/dist/kernel-worker-runtime.mjs +69 -0
- package/dist/kernel-worker-runtime.mjs.map +1 -0
- package/dist/load-game-errors.d.ts +131 -0
- package/dist/load-game-errors.d.ts.map +1 -0
- package/dist/load-game.d.ts +34 -0
- package/dist/load-game.d.ts.map +1 -0
- package/dist/plugin-factories.d.ts +3 -0
- package/dist/plugin-factories.d.ts.map +1 -0
- package/dist/renderer-plugin.d.ts +25 -0
- package/dist/renderer-plugin.d.ts.map +1 -0
- package/dist/tool-preview/bootstrap.d.ts +113 -0
- package/dist/tool-preview/bootstrap.d.ts.map +1 -0
- package/dist/tool-preview/evidence.d.ts +20 -0
- package/dist/tool-preview/evidence.d.ts.map +1 -0
- package/dist/tool-preview/framing.d.ts +13 -0
- package/dist/tool-preview/framing.d.ts.map +1 -0
- package/dist/tool-preview/recipe.d.ts +54 -0
- package/dist/tool-preview/recipe.d.ts.map +1 -0
- package/dist/types.d.ts +404 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +85 -0
- package/schema/execution-report.schema.json +168 -0
- package/src/__tests__/animation-asset-lookup.unit.test.ts +20 -0
- package/src/__tests__/callback-deletion.unit.test.ts +14 -0
- package/src/__tests__/callback-inference.test-d.ts +36 -0
- package/src/__tests__/component-introspection.unit.test.ts +56 -0
- package/src/__tests__/consumer-migration.unit.test.ts +34 -0
- package/src/__tests__/create-app-error-retention.integration.test.ts +125 -0
- package/src/__tests__/create-app-features.test.ts +51 -0
- package/src/__tests__/create-app-listener-sync.test.ts +381 -0
- package/src/__tests__/create-app-lock-provider.test.ts +212 -0
- package/src/__tests__/create-app-physics-plugin-failure.integration.test.ts +142 -0
- package/src/__tests__/create-app-plugin.test.ts +217 -0
- package/src/__tests__/create-app-stop.test.ts +609 -0
- package/src/__tests__/create-app-time.unit.test.ts +26 -0
- package/src/__tests__/create-app.test-d.ts +83 -0
- package/src/__tests__/create-app.test.ts +287 -0
- package/src/__tests__/create-renderer-error.integration.test.ts +25 -0
- package/src/__tests__/create-renderer-lifecycle.integration.test.ts +24 -0
- package/src/__tests__/docs-gate-green.unit.test.ts +61 -0
- package/src/__tests__/draw-source-injected-world-update-visibility.test.ts +176 -0
- package/src/__tests__/draw-source-single-world-regression.test.ts +184 -0
- package/src/__tests__/engine-worker-bootstrap-channel.unit.test.ts +82 -0
- package/src/__tests__/engine-worker-world-swap.unit.test.ts +69 -0
- package/src/__tests__/errors-pointer-lock-failed.test.ts +265 -0
- package/src/__tests__/execution-bootstrap-isolation.unit.test.ts +79 -0
- package/src/__tests__/execution-error-detail.test-d.ts +13 -0
- package/src/__tests__/execution-error-detail.unit.test.ts +35 -0
- package/src/__tests__/execution-frame-order.unit.test.ts +38 -0
- package/src/__tests__/execution-frame-protocol.unit.test.ts +60 -0
- package/src/__tests__/execution-input-sample.contract.test.ts +23 -0
- package/src/__tests__/execution-measurement.unit.test.ts +20 -0
- package/src/__tests__/execution-public-api.test-d.ts +14 -0
- package/src/__tests__/execution-public-api.unit.test.ts +9 -0
- package/src/__tests__/execution-rebuild.contract.test.ts +36 -0
- package/src/__tests__/execution-report.contract.test.ts +50 -0
- package/src/__tests__/execution-report.types.test-d.ts +16 -0
- package/src/__tests__/execution-schema-freshness.unit.test.ts +20 -0
- package/src/__tests__/execution-selector.unit.test.ts +72 -0
- package/src/__tests__/execution-shared-kernel.unit.test.ts +180 -0
- package/src/__tests__/fallback-camera.unit.test.ts +32 -0
- package/src/__tests__/frame-loop-time-elapsed.test.ts +115 -0
- package/src/__tests__/frame-loop-time.unit.test.ts +61 -0
- package/src/__tests__/frame-loop-world-array.test.ts +450 -0
- package/src/__tests__/kernel-worker-runtime.unit.test.ts +50 -0
- package/src/__tests__/migration-surface-inventory.unit.test.ts +13 -0
- package/src/__tests__/plugin-policy.integration.test.ts +15 -0
- package/src/__tests__/plugin-profiles.unit.test.ts +190 -0
- package/src/__tests__/profiler-default-off.test.ts +53 -0
- package/src/__tests__/profiler-frame-token.test.ts +235 -0
- package/src/__tests__/profiler-phase-catalog.test.ts +30 -0
- package/src/__tests__/profiler-phase-integration.test.ts +117 -0
- package/src/__tests__/profiler-remote-bridge.test.ts +31 -0
- package/src/__tests__/remote-integration.test.ts +224 -0
- package/src/__tests__/rhi-capture-capability.test-d.ts +22 -0
- package/src/__tests__/rhi-capture-host.integration.test.ts +157 -0
- package/src/__tests__/rhi-capture.browser.test.ts +51 -0
- package/src/__tests__/surface-handoff.test.ts +89 -0
- package/src/__tests__/types-m2.test-d.ts +81 -0
- package/src/__tests__/visibility-remote.integration.test.ts +116 -0
- package/src/__tests__/worker-execution-stop.test.ts +173 -0
- package/src/animation-asset-lookup.ts +13 -0
- package/src/browser-frame-signal.ts +44 -0
- package/src/canvas-policy.ts +16 -0
- package/src/create-app.ts +1264 -0
- package/src/errors.ts +442 -0
- package/src/execution/attached-world-swap.ts +33 -0
- package/src/execution/bootstrap-entry.ts +122 -0
- package/src/execution/bootstrap-url.ts +25 -0
- package/src/execution/capabilities.ts +102 -0
- package/src/execution/control.ts +48 -0
- package/src/execution/engine-worker-runtime.ts +383 -0
- package/src/execution/engine-worker.ts +163 -0
- package/src/execution/host-controller.ts +460 -0
- package/src/execution/index.ts +44 -0
- package/src/execution/kernel-pool.ts +236 -0
- package/src/execution/kernel-worker-runtime.ts +103 -0
- package/src/execution/measurement.ts +39 -0
- package/src/execution/protocol.ts +134 -0
- package/src/execution/report.ts +66 -0
- package/src/execution/schema.ts +168 -0
- package/src/execution/selector.ts +98 -0
- package/src/execution/types.ts +163 -0
- package/src/fallback-camera.ts +20 -0
- package/src/game-context.ts +126 -0
- package/src/index.ts +190 -0
- package/src/input-map-plugin.ts +21 -0
- package/src/input-plugin.ts +26 -0
- package/src/internal/assembled-engine-profile.ts +14 -0
- package/src/internal/assets-world-plugin.ts +33 -0
- package/src/internal/browser-remote-bridge.ts +269 -0
- package/src/internal/browser-rhi-debug-runtime.ts +67 -0
- package/src/internal/component-introspection.ts +116 -0
- package/src/internal/debug-draw.ts +38 -0
- package/src/internal/ecs-import.ts +41 -0
- package/src/internal/engine-profile-common.ts +9 -0
- package/src/internal/error-fanout.ts +129 -0
- package/src/internal/frame-loop.ts +539 -0
- package/src/internal/input-attach.ts +104 -0
- package/src/internal/main-engine-profile.ts +84 -0
- package/src/internal/remote-serve-flag.ts +41 -0
- package/src/internal/remote-server-plugin.ts +16 -0
- package/src/internal/rhi-capture.ts +211 -0
- package/src/internal/rhi-debug-flag.ts +40 -0
- package/src/internal/worker-engine-profile.ts +47 -0
- package/src/load-game-errors.ts +211 -0
- package/src/load-game.ts +115 -0
- package/src/plugin-factories.ts +2 -0
- package/src/renderer-plugin.ts +96 -0
- package/src/tool-preview/bootstrap.ts +835 -0
- package/src/tool-preview/evidence.ts +59 -0
- package/src/tool-preview/framing.ts +52 -0
- package/src/tool-preview/recipe.ts +137 -0
- package/src/types.ts +450 -0
- package/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// create-app.test-d.ts -- compile-time assertions for w2 acceptanceCheck.
|
|
2
|
+
//
|
|
3
|
+
// Anchors:
|
|
4
|
+
// - createApp(canvas, opts?) returns
|
|
5
|
+
// Promise<Result<App, AppError | RhiError | EngineEnvironmentError>>
|
|
6
|
+
// per AC-01 (canvas thin wrapper widens the error union with
|
|
7
|
+
// EngineEnvironmentError; M2 plugin-system-unify D-7 widens it with
|
|
8
|
+
// AppError for Cordis activation failures).
|
|
9
|
+
// - createApp({ renderer, world }) returns
|
|
10
|
+
// Promise<Result<App, AppError | RhiError>>
|
|
11
|
+
// per AC-02 (assemble form excludes EngineEnvironmentError -- renderer
|
|
12
|
+
// host-managed).
|
|
13
|
+
// - 'tagName' in arg dispatch lands the right overload at the call site
|
|
14
|
+
// (HTMLCanvasElement -> canvas form; AppAssembleArgs -> assemble form).
|
|
15
|
+
//
|
|
16
|
+
// charter awareness:
|
|
17
|
+
// - P3 explicit failure: Result envelope shape is type-level (no message strings).
|
|
18
|
+
// - P4 consistent abstraction: both routes return the same App handle.
|
|
19
|
+
|
|
20
|
+
import type { World } from '@forgeax/engine-ecs';
|
|
21
|
+
import type { RenderError, Renderer, RendererOptions } from '@forgeax/engine-render';
|
|
22
|
+
import type { RhiError } from '@forgeax/engine-rhi/errors';
|
|
23
|
+
import { createRenderer, type EngineEnvironmentError } from '@forgeax/engine-runtime';
|
|
24
|
+
import type { Result } from '@forgeax/engine-types';
|
|
25
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
26
|
+
import { type App, type AppError, createApp } from '../index';
|
|
27
|
+
|
|
28
|
+
describe('createApp double-SSOT entry signatures (w2 acceptanceCheck)', () => {
|
|
29
|
+
it('canvas form returns the App boundary error union', () => {
|
|
30
|
+
const canvas = null as unknown as HTMLCanvasElement;
|
|
31
|
+
const ret = createApp(canvas);
|
|
32
|
+
expectTypeOf(ret).toEqualTypeOf<
|
|
33
|
+
Promise<Result<App, AppError | RhiError | EngineEnvironmentError>>
|
|
34
|
+
>();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('assemble form returns the App boundary error union', () => {
|
|
38
|
+
const renderer = null as unknown as Renderer;
|
|
39
|
+
const world = null as unknown as World;
|
|
40
|
+
const ret = createApp({ renderer, world });
|
|
41
|
+
expectTypeOf(ret).toEqualTypeOf<Promise<Result<App, AppError | RhiError>>>();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('app.renderer / app.world are reference-equal types at the call site (AC-09)', async () => {
|
|
45
|
+
const renderer = null as unknown as Renderer;
|
|
46
|
+
const world = null as unknown as World;
|
|
47
|
+
const result = await createApp({ renderer, world });
|
|
48
|
+
if (result.ok) {
|
|
49
|
+
expectTypeOf(result.value.renderer).toEqualTypeOf<Renderer>();
|
|
50
|
+
expectTypeOf(result.value.world).toEqualTypeOf<World>();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Runtime createRenderer Result boundary (AC-11)', () => {
|
|
56
|
+
it('requires callers to narrow the construction Result', () => {
|
|
57
|
+
const canvas = null as unknown as HTMLCanvasElement;
|
|
58
|
+
const expected = null as unknown as Promise<
|
|
59
|
+
Result<Renderer, EngineEnvironmentError | RenderError>
|
|
60
|
+
>;
|
|
61
|
+
expectTypeOf(createRenderer(canvas)).toEqualTypeOf(expected);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// feat-20260608-create-app-param-surface-trim / M1 / AC-02 + TASK-006:
|
|
66
|
+
// `RendererOptions.clearColor` was deleted in this feat. AI users that
|
|
67
|
+
// pass `{ clearColor: ... }` to `createRenderer` / `createApp` get a
|
|
68
|
+
// TS2353 excess-property error at compile time (no shim, no
|
|
69
|
+
// deprecation window per AGENTS.md §Change stance).
|
|
70
|
+
describe('RendererOptions.clearColor removal (AC-02 + TASK-006)', () => {
|
|
71
|
+
it('RendererOptions does not carry a `clearColor` field', () => {
|
|
72
|
+
// The keyof of RendererOptions must not include 'clearColor'.
|
|
73
|
+
type Keys = keyof RendererOptions;
|
|
74
|
+
type HasClearColor = 'clearColor' extends Keys ? true : false;
|
|
75
|
+
expectTypeOf<HasClearColor>().toEqualTypeOf<false>();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('passing `clearColor` as an object literal is a compile-time error (TS2353)', () => {
|
|
79
|
+
// @ts-expect-error -- 'clearColor' is not assignable to RendererOptions
|
|
80
|
+
const opts: RendererOptions = { clearColor: [0, 0, 0, 1] };
|
|
81
|
+
void opts;
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// M3 / w15: FORGEAX_ENGINE_RHI_DEBUG guard three-segment dual-source evaluation.
|
|
2
|
+
//
|
|
3
|
+
// The canvas-form guard in create-app.ts reads the flag from two sources with a
|
|
4
|
+
// precise precedence + short-circuit shape (plan-strategy D-4):
|
|
5
|
+
//
|
|
6
|
+
// (typeof import.meta !== 'undefined' && import.meta.env?.FORGEAX_ENGINE_RHI_DEBUG)
|
|
7
|
+
// ?? globalThis.process?.env?.FORGEAX_ENGINE_RHI_DEBUG
|
|
8
|
+
//
|
|
9
|
+
// That expression is extracted into the pure `resolveRhiDebugFlag` helper so the
|
|
10
|
+
// three deployment scenarios can be exercised without a real bundler / runtime:
|
|
11
|
+
// (a) browser: import.meta.env replaced by the vite define -> '1' wins
|
|
12
|
+
// (b) dawn-node: import.meta absent (modelled as undefined env) -> falls
|
|
13
|
+
// through to globalThis.process.env
|
|
14
|
+
// (c) unset: neither source carries '1' -> undefined -> guard does not fire
|
|
15
|
+
//
|
|
16
|
+
// C5 (plan-strategy): the `typeof import.meta !== 'undefined'` prefix must
|
|
17
|
+
// short-circuit before any `.env` access, because under dawn-node `import.meta`
|
|
18
|
+
// itself can be undefined; passing `undefined` for the import.meta.env arg is
|
|
19
|
+
// the unit-level model of that prefix evaluating to false.
|
|
20
|
+
//
|
|
21
|
+
// OOS-1/2: only the DevTools-trigger path is in scope; this test does not
|
|
22
|
+
// exercise external HTTP triggers or in-page buttons.
|
|
23
|
+
|
|
24
|
+
import { World } from '@forgeax/engine-ecs';
|
|
25
|
+
import { Camera, orthographic, perspective } from '@forgeax/engine-render';
|
|
26
|
+
import { describe, expect, it } from 'vitest';
|
|
27
|
+
|
|
28
|
+
import { syncCameraAspect, syncCanvasDrawingBuffer } from '../create-app';
|
|
29
|
+
import { resolveRhiDebugFlag } from '../internal/rhi-debug-flag';
|
|
30
|
+
|
|
31
|
+
describe('create-app.test.ts', () => {
|
|
32
|
+
describe('resolveRhiDebugFlag three-segment dual-source (w15 / D-4)', () => {
|
|
33
|
+
it('(a) browser: import.meta.env.FORGEAX_ENGINE_RHI_DEBUG === "1" wins', () => {
|
|
34
|
+
const flag = resolveRhiDebugFlag(
|
|
35
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: '1' },
|
|
36
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: undefined },
|
|
37
|
+
);
|
|
38
|
+
expect(flag).toBe('1');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('(b) dawn-node: import.meta absent -> falls through to process.env', () => {
|
|
42
|
+
// `undefined` models `typeof import.meta === 'undefined'` short-circuiting
|
|
43
|
+
// the first operand to a falsy value, so the `??` chain consults process.env.
|
|
44
|
+
const flag = resolveRhiDebugFlag(undefined, { FORGEAX_ENGINE_RHI_DEBUG: '1' });
|
|
45
|
+
expect(flag).toBe('1');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('(b2) import.meta.env present but flag unset -> still falls through to process.env', () => {
|
|
49
|
+
// `import.meta.env` exists (vite always injects it) but the key is absent;
|
|
50
|
+
// the first operand is `undefined`, so `??` consults process.env.
|
|
51
|
+
const flag = resolveRhiDebugFlag({}, { FORGEAX_ENGINE_RHI_DEBUG: '1' });
|
|
52
|
+
expect(flag).toBe('1');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('(c) unset: neither source carries the flag -> undefined (guard does not fire)', () => {
|
|
56
|
+
expect(resolveRhiDebugFlag(undefined, undefined)).toBeUndefined();
|
|
57
|
+
expect(resolveRhiDebugFlag({}, {})).toBeUndefined();
|
|
58
|
+
expect(
|
|
59
|
+
resolveRhiDebugFlag(
|
|
60
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: undefined },
|
|
61
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: undefined },
|
|
62
|
+
),
|
|
63
|
+
).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('import.meta.env wins over process.env when both set (precedence)', () => {
|
|
67
|
+
// Browser path takes precedence: the `??` only consults process.env when
|
|
68
|
+
// the first operand is null/undefined, and '1' is neither.
|
|
69
|
+
const flag = resolveRhiDebugFlag(
|
|
70
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: '1' },
|
|
71
|
+
{ FORGEAX_ENGINE_RHI_DEBUG: '0' },
|
|
72
|
+
);
|
|
73
|
+
expect(flag).toBe('1');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('a non-"1" value is returned verbatim (guard compares === "1" at call site)', () => {
|
|
77
|
+
// resolveRhiDebugFlag returns the raw flag; the create-app guard does the
|
|
78
|
+
// `=== '1'` comparison. A '0' value must therefore not be coerced to fire.
|
|
79
|
+
expect(resolveRhiDebugFlag({ FORGEAX_ENGINE_RHI_DEBUG: '0' }, undefined)).toBe('0');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('zero-injection when flag unset (c) (w15 / F-3)', () => {
|
|
84
|
+
it('globalThis.__forgeax is undefined before any flagged createApp runs', () => {
|
|
85
|
+
// With the flag unset (the default unit-test environment: no vite define,
|
|
86
|
+
// no process.env override), the canvas form never touches globalThis, so
|
|
87
|
+
// the capture entry point does not exist. A DevTools caller invoking it
|
|
88
|
+
// would hit a TypeError -- the explicit-failure contract (charter P3).
|
|
89
|
+
const g = globalThis as { __forgeax?: { captureFrame?: unknown } };
|
|
90
|
+
expect(g.__forgeax).toBeUndefined();
|
|
91
|
+
expect(() => {
|
|
92
|
+
// Reading .captureFrame off an undefined __forgeax throws synchronously.
|
|
93
|
+
(g.__forgeax as { captureFrame: (n: number) => unknown }).captureFrame(1);
|
|
94
|
+
}).toThrow(TypeError);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('syncCanvasDrawingBuffer host boundary', () => {
|
|
99
|
+
it('matches the physical drawing buffer to CSS size at the device pixel ratio', () => {
|
|
100
|
+
const canvas = {
|
|
101
|
+
clientWidth: 800,
|
|
102
|
+
clientHeight: 400,
|
|
103
|
+
width: 300,
|
|
104
|
+
height: 150,
|
|
105
|
+
style: { width: '800px', height: '400px' },
|
|
106
|
+
};
|
|
107
|
+
const before = globalThis.devicePixelRatio;
|
|
108
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', { configurable: true, value: 2 });
|
|
109
|
+
try {
|
|
110
|
+
syncCanvasDrawingBuffer(canvas);
|
|
111
|
+
expect(canvas.width).toBe(1600);
|
|
112
|
+
expect(canvas.height).toBe(800);
|
|
113
|
+
} finally {
|
|
114
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', {
|
|
115
|
+
configurable: true,
|
|
116
|
+
value: before,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('leaves a zero-sized host untouched', () => {
|
|
122
|
+
const canvas = { clientWidth: 0, clientHeight: 400, width: 300, height: 150 };
|
|
123
|
+
syncCanvasDrawingBuffer(canvas);
|
|
124
|
+
expect(canvas.width).toBe(300);
|
|
125
|
+
expect(canvas.height).toBe(150);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('leaves a headless canvas without CSS dimensions untouched', () => {
|
|
129
|
+
const canvas = { width: 320, height: 180 } as HTMLCanvasElement;
|
|
130
|
+
syncCanvasDrawingBuffer(canvas);
|
|
131
|
+
expect(canvas.width).toBe(320);
|
|
132
|
+
expect(canvas.height).toBe(180);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('preserves an intrinsic canvas drawing buffer without CSS dimensions', () => {
|
|
136
|
+
let width = 256;
|
|
137
|
+
let height = 256;
|
|
138
|
+
const canvas = {
|
|
139
|
+
get clientWidth() {
|
|
140
|
+
return width;
|
|
141
|
+
},
|
|
142
|
+
get clientHeight() {
|
|
143
|
+
return height;
|
|
144
|
+
},
|
|
145
|
+
get width() {
|
|
146
|
+
return width;
|
|
147
|
+
},
|
|
148
|
+
set width(value: number) {
|
|
149
|
+
width = value;
|
|
150
|
+
},
|
|
151
|
+
get height() {
|
|
152
|
+
return height;
|
|
153
|
+
},
|
|
154
|
+
set height(value: number) {
|
|
155
|
+
height = value;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const before = globalThis.devicePixelRatio;
|
|
159
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', { configurable: true, value: 2 });
|
|
160
|
+
try {
|
|
161
|
+
syncCanvasDrawingBuffer(canvas);
|
|
162
|
+
syncCanvasDrawingBuffer(canvas);
|
|
163
|
+
expect(width).toBe(256);
|
|
164
|
+
expect(height).toBe(256);
|
|
165
|
+
} finally {
|
|
166
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', {
|
|
167
|
+
configurable: true,
|
|
168
|
+
value: before,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('does not repeatedly scale a CSS-sized canvas when layout follows width writes', () => {
|
|
174
|
+
let width = 256;
|
|
175
|
+
let height = 256;
|
|
176
|
+
const canvas = {
|
|
177
|
+
clientWidth: 256,
|
|
178
|
+
clientHeight: 256,
|
|
179
|
+
style: { width: '256px', height: '256px' },
|
|
180
|
+
get width() {
|
|
181
|
+
return width;
|
|
182
|
+
},
|
|
183
|
+
set width(value: number) {
|
|
184
|
+
width = value;
|
|
185
|
+
canvas.clientWidth = value;
|
|
186
|
+
},
|
|
187
|
+
get height() {
|
|
188
|
+
return height;
|
|
189
|
+
},
|
|
190
|
+
set height(value: number) {
|
|
191
|
+
height = value;
|
|
192
|
+
canvas.clientHeight = value;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const before = globalThis.devicePixelRatio;
|
|
196
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', { configurable: true, value: 2 });
|
|
197
|
+
try {
|
|
198
|
+
syncCanvasDrawingBuffer(canvas);
|
|
199
|
+
syncCanvasDrawingBuffer(canvas);
|
|
200
|
+
expect(width).toBe(512);
|
|
201
|
+
expect(height).toBe(512);
|
|
202
|
+
} finally {
|
|
203
|
+
Object.defineProperty(globalThis, 'devicePixelRatio', {
|
|
204
|
+
configurable: true,
|
|
205
|
+
value: before,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// feat-20260617 M3 / w10: aspect-sync sidecar (createApp path only).
|
|
212
|
+
// syncCameraAspect is the canvas-form per-frame Update system body
|
|
213
|
+
// closure calls with the live canvas width/height. It walks Camera entities via
|
|
214
|
+
// world.get (NOT the query bundle: a bool column read off the bundle returns a
|
|
215
|
+
// raw 0/1 number, so `!== 0` is always true -- the
|
|
216
|
+
// bool-field-compared-with-not-equal-zero-always-true trap; D-5/Finding 2). It
|
|
217
|
+
// writes canvas.width/height into Camera.aspect for perspective + autoAspect=true
|
|
218
|
+
// cameras only.
|
|
219
|
+
describe('syncCameraAspect sidecar (w10 / AC-07)', () => {
|
|
220
|
+
it('canvas resize -> perspective autoAspect=true camera aspect becomes w/h', () => {
|
|
221
|
+
const world = new World();
|
|
222
|
+
const cam = world
|
|
223
|
+
.spawn({ component: Camera, data: perspective({ fov: 1, aspect: 1 }) })
|
|
224
|
+
.unwrap();
|
|
225
|
+
|
|
226
|
+
syncCameraAspect(world, 800, 400);
|
|
227
|
+
|
|
228
|
+
const r = world.get(cam, Camera);
|
|
229
|
+
expect(r.ok).toBe(true);
|
|
230
|
+
if (!r.ok) return;
|
|
231
|
+
expect(r.value.aspect).toBeCloseTo(2, 5);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('autoAspect=false camera is left untouched', () => {
|
|
235
|
+
const world = new World();
|
|
236
|
+
const cam = world
|
|
237
|
+
.spawn({
|
|
238
|
+
component: Camera,
|
|
239
|
+
data: perspective({ fov: 1, aspect: 1.234, autoAspect: false }),
|
|
240
|
+
})
|
|
241
|
+
.unwrap();
|
|
242
|
+
|
|
243
|
+
syncCameraAspect(world, 800, 400);
|
|
244
|
+
|
|
245
|
+
const r = world.get(cam, Camera);
|
|
246
|
+
expect(r.ok).toBe(true);
|
|
247
|
+
if (!r.ok) return;
|
|
248
|
+
expect(r.value.aspect).toBeCloseTo(1.234, 5);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('canvas size 0 is skipped (no NaN / 0 written into aspect)', () => {
|
|
252
|
+
const world = new World();
|
|
253
|
+
const cam = world
|
|
254
|
+
.spawn({ component: Camera, data: perspective({ fov: 1, aspect: 1.5 }) })
|
|
255
|
+
.unwrap();
|
|
256
|
+
|
|
257
|
+
syncCameraAspect(world, 0, 0);
|
|
258
|
+
|
|
259
|
+
const r = world.get(cam, Camera);
|
|
260
|
+
expect(r.ok).toBe(true);
|
|
261
|
+
if (!r.ok) return;
|
|
262
|
+
expect(r.value.aspect).toBeCloseTo(1.5, 5);
|
|
263
|
+
expect(Number.isNaN(r.value.aspect)).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('orthographic camera is left untouched', () => {
|
|
267
|
+
const world = new World();
|
|
268
|
+
const cam = world
|
|
269
|
+
.spawn({
|
|
270
|
+
component: Camera,
|
|
271
|
+
data: orthographic({ left: -1, right: 1, bottom: -1, top: 1 }),
|
|
272
|
+
})
|
|
273
|
+
.unwrap();
|
|
274
|
+
const before = world.get(cam, Camera);
|
|
275
|
+
expect(before.ok).toBe(true);
|
|
276
|
+
if (!before.ok) return;
|
|
277
|
+
const aspectBefore = before.value.aspect;
|
|
278
|
+
|
|
279
|
+
syncCameraAspect(world, 800, 400);
|
|
280
|
+
|
|
281
|
+
const r = world.get(cam, Camera);
|
|
282
|
+
expect(r.ok).toBe(true);
|
|
283
|
+
if (!r.ok) return;
|
|
284
|
+
expect(r.value.aspect).toBeCloseTo(aspectBefore, 5);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createRenderer, EngineEnvironmentError } from '@forgeax/engine-runtime';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { createApp } from '../create-app';
|
|
4
|
+
|
|
5
|
+
describe('app renderer construction boundary', () => {
|
|
6
|
+
it('returns construction failures through the Runtime Result boundary', async () => {
|
|
7
|
+
const result = await createRenderer(null as never);
|
|
8
|
+
expect(result.ok).toBe(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('returns EngineEnvironmentError through the canvas Result path', async () => {
|
|
12
|
+
const canvas = {
|
|
13
|
+
tagName: 'CANVAS',
|
|
14
|
+
isConnected: true,
|
|
15
|
+
width: 16,
|
|
16
|
+
height: 16,
|
|
17
|
+
getContext: () => null,
|
|
18
|
+
addEventListener: () => undefined,
|
|
19
|
+
removeEventListener: () => undefined,
|
|
20
|
+
} as unknown as HTMLCanvasElement;
|
|
21
|
+
const result = await createApp(canvas, { rhi: undefined });
|
|
22
|
+
expect(result.ok).toBe(false);
|
|
23
|
+
if (!result.ok) expect(result.error).toBeInstanceOf(EngineEnvironmentError);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Renderer } from '@forgeax/engine-render';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
describe('app-facing renderer lifecycle', () => {
|
|
5
|
+
it('consumes the three lifecycle Result boundaries without casts', async () => {
|
|
6
|
+
const events: string[] = [];
|
|
7
|
+
const renderer = {
|
|
8
|
+
attach: () => ({ ok: false as const, error: {} }),
|
|
9
|
+
draw: () => ({ ok: false as const, error: {} }),
|
|
10
|
+
observe: async () => ({ ok: false as const, error: {} }),
|
|
11
|
+
releaseSurface: () => ({ ok: true as const, value: undefined }),
|
|
12
|
+
restoreSurface: () => ({ ok: true as const, value: undefined }),
|
|
13
|
+
recover: async () => ({ ok: true as const, value: undefined }),
|
|
14
|
+
dispose: () => events.push('dispose'),
|
|
15
|
+
onError: () => () => undefined,
|
|
16
|
+
onLost: () => () => undefined,
|
|
17
|
+
} as unknown as Renderer;
|
|
18
|
+
expect(renderer.attach({} as never).ok).toBe(false);
|
|
19
|
+
expect(renderer.draw({} as never).ok).toBe(false);
|
|
20
|
+
renderer.dispose();
|
|
21
|
+
renderer.dispose();
|
|
22
|
+
expect(events).toEqual(['dispose', 'dispose']);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @perf-budget-skip: intentional documentation subprocess gate.
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
|
|
9
|
+
const repoRoot = resolve(fileURLToPath(new URL('../../../..', import.meta.url)));
|
|
10
|
+
const docsGate = resolve(repoRoot, 'scripts/forgeax/check-token-first-docs.mjs');
|
|
11
|
+
|
|
12
|
+
function runDocsGate(): { readonly status: number; readonly output: string } {
|
|
13
|
+
try {
|
|
14
|
+
const output = execFileSync(process.execPath, [docsGate], {
|
|
15
|
+
cwd: repoRoot,
|
|
16
|
+
encoding: 'utf8',
|
|
17
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
18
|
+
});
|
|
19
|
+
return { status: 0, output };
|
|
20
|
+
} catch (error: unknown) {
|
|
21
|
+
const result = error as {
|
|
22
|
+
readonly status?: number;
|
|
23
|
+
readonly stdout?: string;
|
|
24
|
+
readonly stderr?: string;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
status: result.status ?? 1,
|
|
28
|
+
output: `${result.stdout ?? ''}${result.stderr ?? ''}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('M5 active-documentation token-first gate', () => {
|
|
34
|
+
it('keeps the engine-owned active documentation surface present', () => {
|
|
35
|
+
for (const path of [
|
|
36
|
+
'packages/ecs/README.md',
|
|
37
|
+
'packages/app/README.md',
|
|
38
|
+
'packages/audio-webaudio/README.md',
|
|
39
|
+
'packages/input/README.md',
|
|
40
|
+
'packages/runtime/README.md',
|
|
41
|
+
'packages/state/README.md',
|
|
42
|
+
'skills/forgeax-engine-ecs/SKILL.md',
|
|
43
|
+
'skills/forgeax-engine-app/SKILL.md',
|
|
44
|
+
'skills/forgeax-engine-audio/SKILL.md',
|
|
45
|
+
'skills/forgeax-engine-state/SKILL.md',
|
|
46
|
+
'apps/hello/sprite-atlas/README.md',
|
|
47
|
+
'apps/learn-render/1.getting-started/5.transformations/README.md',
|
|
48
|
+
'apps/learn-render/1.getting-started/7.camera/README.md',
|
|
49
|
+
'apps/learn-render/2.lighting/3.materials/README.md',
|
|
50
|
+
'packages/ecs/CHANGELOG.md',
|
|
51
|
+
]) {
|
|
52
|
+
expect(existsSync(resolve(repoRoot, path)), path).toBe(true);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('accepts only token-first registration and explicit-delta update examples', () => {
|
|
57
|
+
const result = runDocsGate();
|
|
58
|
+
expect(result.status, result.output).toBe(0);
|
|
59
|
+
expect(result.output).toContain('Active-documentation token-first gate passed');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// feat-20260709-editor-world-partition-editorworld-super-composite / M2 / w9
|
|
2
|
+
// (RED — impl lands in w12). Contract test: a world injected through drawSource
|
|
3
|
+
// MUST be world.update(1 / 60).unwrap()'d by the frame-loop in the SAME frame it is drawn, so
|
|
4
|
+
// the renderer reads a freshly-propagated Transform.world mat4 — never a stale
|
|
5
|
+
// one.
|
|
6
|
+
//
|
|
7
|
+
// Why this matters (Strategist D-3, not covered by research): the renderer's
|
|
8
|
+
// extract stage reads the DERIVED `Transform.world` column, whose sole writer is
|
|
9
|
+
// the propagateTransforms system (plugin-factories.ts / systems/propagate-
|
|
10
|
+
// transforms.ts). That writer only runs inside `world.update(1 / 60).unwrap()`. If drawSource
|
|
11
|
+
// merely fed injected worlds into `renderer.draw(worlds, ...)` WITHOUT the
|
|
12
|
+
// frame-loop running `world.update(1 / 60).unwrap()` on them first, the renderer would read the
|
|
13
|
+
// injected world's Transform.world at its previous (or default identity) value —
|
|
14
|
+
// a stale-matrix bug that renders the injected world one frame behind (or at the
|
|
15
|
+
// origin on the first frame).
|
|
16
|
+
//
|
|
17
|
+
// This test spawns an entity in the injected world with a DISTINGUISHING
|
|
18
|
+
// non-identity translation and never calls world.update(1 / 60).unwrap() on that world itself.
|
|
19
|
+
// The spy renderer reads `Transform.world` at draw time. A frame-loop that
|
|
20
|
+
// updates injected worlds surfaces the translation in the mat4's column-major
|
|
21
|
+
// [12]/[13]/[14] slots; a draw-only frame-loop surfaces the identity default
|
|
22
|
+
// (0/0/0), which fails the assertion. So the test has genuine discriminating
|
|
23
|
+
// power over the stale-matrix regression.
|
|
24
|
+
//
|
|
25
|
+
// Driven through createFrameLoop directly with injected now / raf / caf seams,
|
|
26
|
+
// mirroring frame-loop-world-array.test.ts. The drawSource option is reached
|
|
27
|
+
// through a typed alias before w12 widens the real FrameLoopOptions type.
|
|
28
|
+
//
|
|
29
|
+
// Anchors:
|
|
30
|
+
// plan-strategy §2 D-3 (frame-loop MUST update injected worlds — transform
|
|
31
|
+
// propagation — before draw; not draw-only)
|
|
32
|
+
// research F1 (frame-loop is the world.update -> renderer.draw driver)
|
|
33
|
+
// AGENTS.md invariant 6 (Transform.world is the round-trip fidelity seam)
|
|
34
|
+
|
|
35
|
+
import type { EntityHandle } from '@forgeax/engine-ecs';
|
|
36
|
+
import { World } from '@forgeax/engine-ecs';
|
|
37
|
+
import { createRenderReadLease } from '@forgeax/engine-ecs/projection';
|
|
38
|
+
import type { Renderer } from '@forgeax/engine-render';
|
|
39
|
+
import { registerPropagateTransforms, Transform } from '@forgeax/engine-scene';
|
|
40
|
+
import { describe, expect, it } from 'vitest';
|
|
41
|
+
import { createFrameLoop, type FrameLoopOptions } from '../internal/frame-loop';
|
|
42
|
+
|
|
43
|
+
// Distinguishing translation for the injected world's entity. Column-major mat4
|
|
44
|
+
// carries translation at indices [12] (x), [13] (y), [14] (z).
|
|
45
|
+
const INJECTED_TX = 5;
|
|
46
|
+
const INJECTED_TY = -3;
|
|
47
|
+
const INJECTED_TZ = 7;
|
|
48
|
+
|
|
49
|
+
function makeSyncScheduler() {
|
|
50
|
+
let pending: ((t: number) => void) | null = null;
|
|
51
|
+
let clock = 0;
|
|
52
|
+
const raf = (cb: (t: number) => void): number => {
|
|
53
|
+
pending = cb;
|
|
54
|
+
return 1;
|
|
55
|
+
};
|
|
56
|
+
const caf = (): void => {
|
|
57
|
+
pending = null;
|
|
58
|
+
};
|
|
59
|
+
const now = (): number => {
|
|
60
|
+
clock += 16;
|
|
61
|
+
return clock;
|
|
62
|
+
};
|
|
63
|
+
const pump = (frames: number): void => {
|
|
64
|
+
for (let i = 0; i < frames; i++) {
|
|
65
|
+
const cb = pending;
|
|
66
|
+
pending = null;
|
|
67
|
+
if (cb === null) break;
|
|
68
|
+
cb(clock);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
return { raf, caf, now, pump };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
type DrawSourceCallback = () =>
|
|
75
|
+
| { worlds: readonly World[]; cameraOwner: number; resourceOwner: number }
|
|
76
|
+
| undefined;
|
|
77
|
+
|
|
78
|
+
type FrameLoopOptionsWithDrawSource = FrameLoopOptions & {
|
|
79
|
+
drawSource?: DrawSourceCallback;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const createFrameLoopWithDrawSource = createFrameLoop as unknown as (
|
|
83
|
+
opts: FrameLoopOptionsWithDrawSource,
|
|
84
|
+
) => ReturnType<typeof createFrameLoop>;
|
|
85
|
+
|
|
86
|
+
describe('drawSource injected worlds are updated same-frame (w9, stale-matrix regression)', () => {
|
|
87
|
+
it('renderer reads a freshly-propagated Transform.world for the injected world at draw time', () => {
|
|
88
|
+
// The loop's OWN world — the single-world identity path. Never carries the
|
|
89
|
+
// injected entity, so it cannot mask a stale read on the injected world.
|
|
90
|
+
const loopWorld = new World();
|
|
91
|
+
|
|
92
|
+
// The injected world: register propagateTransforms (the sole Transform.world
|
|
93
|
+
// writer) and spawn an entity with a distinguishing non-identity translation.
|
|
94
|
+
// We deliberately do NOT call injectedWorld.update() here — the frame-loop
|
|
95
|
+
// must run it for the translation to reach Transform.world.
|
|
96
|
+
const injectedWorld = new World();
|
|
97
|
+
registerPropagateTransforms(injectedWorld);
|
|
98
|
+
const spawned = injectedWorld
|
|
99
|
+
.spawn({
|
|
100
|
+
component: Transform,
|
|
101
|
+
data: {
|
|
102
|
+
pos: [INJECTED_TX, INJECTED_TY, INJECTED_TZ],
|
|
103
|
+
quat: [0, 0, 0, 1],
|
|
104
|
+
scale: [1, 1, 1],
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
.unwrap();
|
|
108
|
+
const entity = spawned as unknown as EntityHandle;
|
|
109
|
+
|
|
110
|
+
// Sanity: BEFORE any frame, Transform.world is still the identity default
|
|
111
|
+
// (translation columns are 0). This proves the assertion below measures the
|
|
112
|
+
// frame-loop's update, not a pre-propagated value.
|
|
113
|
+
const before = injectedWorld.get(entity, Transform);
|
|
114
|
+
expect(before.ok).toBe(true);
|
|
115
|
+
if (before.ok) {
|
|
116
|
+
const w = before.value.world as unknown as ArrayLike<number>;
|
|
117
|
+
expect(w[12]).toBe(0);
|
|
118
|
+
expect(w[13]).toBe(0);
|
|
119
|
+
expect(w[14]).toBe(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Spy renderer: at draw time, read the injected world's Transform.world and
|
|
123
|
+
// record the translation columns. This is the exact read the real extract
|
|
124
|
+
// stage performs (Transform.world is the extract SSOT).
|
|
125
|
+
const observed: Array<[number, number, number]> = [];
|
|
126
|
+
const renderer = {
|
|
127
|
+
attach: (world: World) => ({ ok: true, value: createRenderReadLease(world) }),
|
|
128
|
+
draw(): { ok: true; value: undefined } {
|
|
129
|
+
const r = injectedWorld.get(entity, Transform);
|
|
130
|
+
if (r.ok) {
|
|
131
|
+
const m = r.value.world as unknown as ArrayLike<number>;
|
|
132
|
+
observed.push([m[12] as number, m[13] as number, m[14] as number]);
|
|
133
|
+
}
|
|
134
|
+
return { ok: true, value: undefined };
|
|
135
|
+
},
|
|
136
|
+
onError(): () => void {
|
|
137
|
+
return () => {
|
|
138
|
+
// no-op
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
dispose(): void {
|
|
142
|
+
// no-op
|
|
143
|
+
},
|
|
144
|
+
} as unknown as Renderer;
|
|
145
|
+
|
|
146
|
+
const drawSource: DrawSourceCallback = () => ({
|
|
147
|
+
worlds: [injectedWorld],
|
|
148
|
+
cameraOwner: 0,
|
|
149
|
+
resourceOwner: 0,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const { raf, caf, now, pump } = makeSyncScheduler();
|
|
153
|
+
const loop = createFrameLoopWithDrawSource({
|
|
154
|
+
world: loopWorld,
|
|
155
|
+
renderer,
|
|
156
|
+
now,
|
|
157
|
+
raf,
|
|
158
|
+
caf,
|
|
159
|
+
drawSource,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(loop.start().ok).toBe(true);
|
|
163
|
+
pump(2);
|
|
164
|
+
loop.stop();
|
|
165
|
+
|
|
166
|
+
// At least one frame observed the injected world, and every observation saw
|
|
167
|
+
// the FRESH translation — the frame-loop ran injectedWorld.update() before
|
|
168
|
+
// draw. A draw-only frame-loop would observe [0, 0, 0] (identity) and fail.
|
|
169
|
+
expect(observed.length).toBeGreaterThanOrEqual(1);
|
|
170
|
+
for (const [x, y, z] of observed) {
|
|
171
|
+
expect(x).toBeCloseTo(INJECTED_TX);
|
|
172
|
+
expect(y).toBeCloseTo(INJECTED_TY);
|
|
173
|
+
expect(z).toBeCloseTo(INJECTED_TZ);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|