@forgeax/engine-runtime 0.1.3 → 0.1.6
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 +49 -83
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-collect-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-serialize.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-serialize.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts +2 -0
- package/dist/__tests__/asset-brand-and-name-retirement.test.d.ts.map +1 -0
- package/dist/__tests__/asset-error-exhaustive.test-d.d.ts +11 -0
- package/dist/__tests__/asset-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-guid-reverse.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-guid-reverse.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-hdr-equirect.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-mesh-pack-payload.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-mounts.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-mounts.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.browser.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.browser.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.dawn.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.listCatalog.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.listCatalog.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.mesh-bin.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.mesh-bin.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.raw-container-failfast.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.recursive.spec.d.ts +2 -0
- package/dist/__tests__/asset-registry.recursive.spec.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset.unit.test.d.ts +2 -0
- package/dist/__tests__/asset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/audio-load-by-guid.unit.test.d.ts +2 -0
- package/dist/__tests__/audio-load-by-guid.unit.test.d.ts.map +1 -0
- package/dist/__tests__/backend-selection.integration.test.d.ts +2 -0
- package/dist/__tests__/backend-selection.integration.test.d.ts.map +1 -0
- package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts +2 -0
- package/dist/__tests__/basis-catalog-dispatch.integration.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-asset-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/builtin-asset-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/collect-scene-asset.test.d.ts +1 -2
- package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -1
- package/dist/__tests__/collect-scene-mount-collapse.test.d.ts +2 -0
- package/dist/__tests__/collect-scene-mount-collapse.test.d.ts.map +1 -0
- package/dist/__tests__/collect-transient-roundtrip.test.d.ts +1 -1
- package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -1
- package/dist/__tests__/collect-transient-skip.test.d.ts +1 -2
- package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -1
- package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts +2 -0
- package/dist/__tests__/compressed-texture-physical-extent.integration.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/mesh-update-dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/upload-texture.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/draw-published.d.ts +2 -2
- package/dist/__tests__/draw-published.d.ts.map +1 -1
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors.unit.test.d.ts +2 -0
- package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts +2 -0
- package/dist/__tests__/extract-skin-mismatch.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fetch-binary-decompress.test.d.ts +2 -0
- package/dist/__tests__/fetch-binary-decompress.test.d.ts.map +1 -0
- package/dist/__tests__/four-path-compression.integration.test.d.ts +2 -0
- package/dist/__tests__/four-path-compression.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-feature-host.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/guid-for-asset.unit.test.d.ts +2 -0
- package/dist/__tests__/guid-for-asset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle-migration.test.d.ts +2 -0
- package/dist/__tests__/handle-migration.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -1
- package/dist/__tests__/host-custom-kind-contract.test-d.d.ts +2 -0
- package/dist/__tests__/host-custom-kind-contract.test-d.d.ts.map +1 -0
- package/dist/__tests__/host-custom-kind-contract.test.d.ts +2 -0
- package/dist/__tests__/host-custom-kind-contract.test.d.ts.map +1 -0
- package/dist/__tests__/inspect-name.test.d.ts +2 -0
- package/dist/__tests__/inspect-name.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts +2 -0
- package/dist/__tests__/instantiate-mount-deferred-wiring.test.d.ts.map +1 -0
- package/dist/__tests__/loadbyguid-payload.unit.test.d.ts +2 -0
- package/dist/__tests__/loadbyguid-payload.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts +2 -0
- package/dist/__tests__/loadbyguid-texture-intern.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-extract-graceful.test.d.ts +2 -0
- package/dist/__tests__/loader-extract-graceful.test.d.ts.map +1 -0
- package/dist/__tests__/loader-name-survival.test.d.ts +2 -0
- package/dist/__tests__/loader-name-survival.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-skin-roundtrip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-loader-skin-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/new-kind-refs-contract.test.d.ts +2 -0
- package/dist/__tests__/new-kind-refs-contract.test.d.ts.map +1 -0
- package/dist/__tests__/package-promotion.test.d.ts +2 -0
- package/dist/__tests__/package-promotion.test.d.ts.map +1 -0
- package/dist/__tests__/package-rename.test.d.ts +2 -0
- package/dist/__tests__/package-rename.test.d.ts.map +1 -0
- package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +1 -1
- package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts +1 -1
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts +10 -0
- package/dist/__tests__/render-feature-prepared-graphics.fixture.d.ts.map +1 -0
- package/dist/__tests__/render-system-extract.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-extract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-test-utils.d.ts +10 -0
- package/dist/__tests__/renderer-test-utils.d.ts.map +1 -0
- package/dist/__tests__/renderer.test-d.d.ts +2 -0
- package/dist/__tests__/renderer.test-d.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-handle-err.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-handle-err.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolveName-fallback.test.d.ts +2 -0
- package/dist/__tests__/resolveName-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/resolveName-null-package.test.d.ts +2 -0
- package/dist/__tests__/resolveName-null-package.test.d.ts.map +1 -0
- package/dist/__tests__/resolveName-unified-entry.test.d.ts +2 -0
- package/dist/__tests__/resolveName-unified-entry.test.d.ts.map +1 -0
- package/dist/__tests__/resolveName.test.d.ts +2 -0
- package/dist/__tests__/resolveName.test.d.ts.map +1 -0
- package/dist/__tests__/roots-to-scene-asset.test.d.ts +1 -1
- package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -1
- package/dist/__tests__/roundtrip-primitive.test.d.ts +2 -0
- package/dist/__tests__/roundtrip-primitive.test.d.ts.map +1 -0
- package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts +2 -0
- package/dist/__tests__/scene-bare-guid-refs-instantiate.test.d.ts.map +1 -0
- package/dist/__tests__/scene-instance-authoring.integration.test.d.ts +2 -0
- package/dist/__tests__/scene-instance-authoring.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scene-mount-fixed-point.test.d.ts +2 -0
- package/dist/__tests__/scene-mount-fixed-point.test.d.ts.map +1 -0
- package/dist/__tests__/scene-mount-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/scene-mount-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/scene-mount-wrapper-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/scene-sugar-instantiate.test.d.ts +2 -0
- package/dist/__tests__/scene-sugar-instantiate.test.d.ts.map +1 -0
- package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/serialize-mounts-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/serialize-scene-asset.test.d.ts +1 -1
- package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -1
- package/dist/__tests__/template-game-default-api-contract.test.d.ts +2 -0
- package/dist/__tests__/template-game-default-api-contract.test.d.ts.map +1 -0
- package/dist/__tests__/tileset-asset-validate.test.d.ts +2 -0
- package/dist/__tests__/tileset-asset-validate.test.d.ts.map +1 -0
- package/dist/__tests__/transcode-fallback.unit.test.d.ts +2 -0
- package/dist/__tests__/transcode-fallback.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transient-runtime-behavior.test.d.ts +2 -0
- package/dist/__tests__/transient-runtime-behavior.test.d.ts.map +1 -0
- package/dist/__tests__/video-loader-consumption.test.d.ts +2 -0
- package/dist/__tests__/video-loader-consumption.test.d.ts.map +1 -0
- package/dist/__tests__/video-loader-smoke.dawn.test.d.ts +2 -0
- package/dist/__tests__/video-loader-smoke.dawn.test.d.ts.map +1 -0
- package/dist/backend-selection.d.ts +13 -0
- package/dist/backend-selection.d.ts.map +1 -0
- package/dist/collect-scene-asset.d.ts +3 -3
- package/dist/collect-scene-asset.d.ts.map +1 -1
- package/dist/createRenderer.d.ts +14 -2
- package/dist/createRenderer.d.ts.map +1 -1
- package/dist/errors/environment.d.ts +1 -1
- package/dist/errors/environment.d.ts.map +1 -1
- package/dist/index.d.ts +19 -81
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +105 -108
- package/dist/index.mjs.map +1 -1
- package/dist/renderer-host.d.ts +9 -0
- package/dist/renderer-host.d.ts.map +1 -0
- package/dist/renderer-host.mjs +62 -0
- package/dist/renderer-host.mjs.map +1 -0
- package/dist/scene-instances/index.d.ts +2 -0
- package/dist/scene-instances/index.d.ts.map +1 -0
- package/dist/scene-utils/mount-override-fold.d.ts +1 -2
- package/dist/scene-utils/mount-override-fold.d.ts.map +1 -1
- package/dist/systems/sprite-animation-tick.d.ts +1 -1
- package/dist/systems/sprite-animation-tick.d.ts.map +1 -1
- package/dist/systems/transparent-sort.d.ts +1 -2
- package/dist/systems/transparent-sort.d.ts.map +1 -1
- package/dist/tilemap-register-validate.d.ts.map +1 -1
- package/package.json +34 -33
- package/src/__tests__/active-camera.unit.test.ts +19 -9
- package/src/__tests__/animation-graph-collect-roundtrip.test.ts +213 -0
- package/src/__tests__/animation-graph-serialize.unit.test.ts +171 -0
- package/src/__tests__/append-injection.test.ts +1 -1
- package/src/__tests__/asset-brand-and-name-retirement.test.ts +102 -0
- package/src/__tests__/asset-error-exhaustive.test-d.ts +62 -0
- package/src/__tests__/asset-registry-guid-reverse.test.ts +575 -0
- package/src/__tests__/asset-registry-hdr-equirect.test.ts +78 -0
- package/src/__tests__/asset-registry-mesh-pack-payload.dawn.test.ts +232 -0
- package/src/__tests__/asset-registry-mounts.test.ts +806 -0
- package/src/__tests__/asset-registry.browser.test.ts +81 -0
- package/src/__tests__/asset-registry.dawn.test.ts +94 -0
- package/src/__tests__/asset-registry.listCatalog.test.ts +170 -0
- package/src/__tests__/asset-registry.mesh-bin.test.ts +316 -0
- package/src/__tests__/asset-registry.pack-cache.test.ts +969 -0
- package/src/__tests__/asset-registry.raw-container-failfast.test.ts +225 -0
- package/src/__tests__/asset-registry.recursive.spec.ts +1397 -0
- package/src/__tests__/asset-registry.unit.test.ts +1030 -0
- package/src/__tests__/asset.unit.test.ts +7107 -0
- package/src/__tests__/audio-load-by-guid.unit.test.ts +126 -0
- package/src/__tests__/backend-selection.integration.test.ts +11 -0
- package/src/__tests__/basis-catalog-dispatch.integration.test.ts +320 -0
- package/src/__tests__/bind-group-cache-keying.test.ts +4 -4
- package/src/__tests__/block-upload-math.unit.test.ts +2 -2
- package/src/__tests__/builtin-asset-registry.unit.test.ts +112 -0
- package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
- package/src/__tests__/cache-false-eviction.test.ts +2 -1
- package/src/__tests__/cache-isolation-matrix.test.ts +2 -1
- package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +5 -6
- package/src/__tests__/clamp-to-last.e2e.browser.test.ts +29 -19
- package/src/__tests__/collect-scene-asset.test.ts +49 -52
- package/src/__tests__/collect-scene-mount-collapse.test.ts +391 -0
- package/src/__tests__/collect-subtree.unit.test.ts +1 -2
- package/src/__tests__/collect-transient-children-absent.test.ts +24 -10
- package/src/__tests__/collect-transient-roundtrip.test.ts +44 -20
- package/src/__tests__/collect-transient-skip.test.ts +24 -13
- package/src/__tests__/components.test-d.ts +1 -6
- package/src/__tests__/components.unit.test.ts +20 -19
- package/src/__tests__/composite-skybox-cross-world.browser.test.ts +59 -27
- package/src/__tests__/compressed-texture-physical-extent.integration.test.ts +139 -0
- package/src/__tests__/create-renderer-assembly.integration.test.ts +42 -29
- package/src/__tests__/createRenderer-dispose.test.ts +35 -402
- package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +25 -30
- package/src/__tests__/createRenderer.unit.test.ts +1 -1
- package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +27 -63
- package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +56 -47
- package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +49 -29
- package/src/__tests__/dawn/instances-shadow.dawn.test.ts +26 -15
- package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +13 -14
- package/src/__tests__/dawn/material-alpha.dawn.test.ts +23 -31
- package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +0 -18
- package/src/__tests__/dawn/{mesh-update.dawn.test.ts → mesh-update-dawn.test.ts} +42 -134
- package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +14 -7
- package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +16 -28
- package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +23 -39
- package/src/__tests__/dawn/upload-texture.dawn.test.ts +198 -0
- package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +12 -14
- package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +27 -22
- package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +25 -28
- package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +10 -11
- package/src/__tests__/demo-sortscope-migration.unit.test.ts +5 -5
- package/src/__tests__/derive-bgl-integration.test.ts +34 -44
- package/src/__tests__/derive-render-data-skin.unit.test.ts +1 -1
- package/src/__tests__/draw-published.ts +11 -4
- package/src/__tests__/dynamic-texture-store.unit.test.ts +190 -0
- package/src/__tests__/equirect-bc6h.integration.test.ts +144 -21
- package/src/__tests__/errors.unit.test.ts +1203 -0
- package/src/__tests__/extract-frames-graceful-degradation.test.ts +3 -7
- package/src/__tests__/extract-frames-merge.test.ts +35 -22
- package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +5 -16
- package/src/__tests__/extract-skin-mismatch.unit.test.ts +322 -0
- package/src/__tests__/fetch-binary-decompress.test.ts +356 -0
- package/src/__tests__/four-path-compression.integration.test.ts +395 -0
- package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +1 -1
- package/src/__tests__/fullscreen-feature-host.dawn.test.ts +80 -0
- package/src/__tests__/fullscreen-post-bgl-depth.test.ts +4 -2
- package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +3 -3
- package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +39 -23
- package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +4 -2
- package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +28 -17
- package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +29 -18
- package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +16 -12
- package/src/__tests__/geometry.unit.test.ts +525 -159
- package/src/__tests__/glyph-tilemap-worldid.test.ts +1 -1
- package/src/__tests__/gpu-resource-cow-survivor.test.ts +6 -4
- package/src/__tests__/guid-for-asset.unit.test.ts +102 -0
- package/src/__tests__/handle-migration.test.ts +136 -0
- package/src/__tests__/helpers/register-runtime-components.ts +4 -2
- package/src/__tests__/helpers/register-scene-components.ts +1 -1
- package/src/__tests__/host-custom-kind-contract.test-d.ts +47 -0
- package/src/__tests__/host-custom-kind-contract.test.ts +578 -0
- package/src/__tests__/inspect-name.test.ts +139 -0
- package/src/__tests__/instances.test-d.ts +1 -1
- package/src/__tests__/instantiate-mount-deferred-wiring.test.ts +112 -0
- package/src/__tests__/light-casters-9-light.browser.test.ts +73 -76
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +7 -10
- package/src/__tests__/light-snapshot.test-d.ts +2 -2
- package/src/__tests__/lights-vec-negative.unit.test.ts +1 -1
- package/src/__tests__/lights.unit.test.ts +16 -500
- package/src/__tests__/loadbyguid-payload.unit.test.ts +117 -0
- package/src/__tests__/loadbyguid-texture-intern.unit.test.ts +213 -0
- package/src/__tests__/loader-extract-graceful.test.ts +146 -0
- package/src/__tests__/loader-name-survival.test.ts +213 -0
- package/src/__tests__/m3-vec-negative.unit.test.ts +3 -2
- package/src/__tests__/material-texture-uv-scale.unit.test.ts +101 -31
- package/src/__tests__/materials.unit.test.ts +10 -27
- package/src/__tests__/mesh-bin-contract.test.ts +28 -0
- package/src/__tests__/mesh-bin-skin-roundtrip.unit.test.ts +149 -0
- package/src/__tests__/mesh-bin.test.ts +38 -0
- package/src/__tests__/mesh-loader-skin-contract.unit.test.ts +120 -0
- package/src/__tests__/mesh-ssbo-grow.unit.test.ts +3 -3
- package/src/__tests__/mip-gate.unit.test.ts +1 -1
- package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +29 -68
- package/src/__tests__/new-kind-refs-contract.test.ts +132 -0
- package/src/__tests__/node-shims.d.ts +4 -6
- package/src/__tests__/package-promotion.test.ts +88 -0
- package/src/__tests__/package-rename.test.ts +103 -0
- package/src/__tests__/pass-selector.unit.test.ts +2 -2
- package/src/__tests__/pbr-pipeline.unit.test.ts +14 -21
- package/src/__tests__/pipeline-builder-msaa.unit.test.ts +6 -9
- package/src/__tests__/pipeline-cache-keying.unit.test.ts +6 -9
- package/src/__tests__/pipeline-spec.test.ts +16 -177
- package/src/__tests__/pipeline.unit.test.ts +154 -1693
- package/src/__tests__/point-light-shadow.dawn.test.ts +33 -42
- package/src/__tests__/point-light-shadow.unit.test.ts +19 -60
- package/src/__tests__/post-process-error-codes.unit.test.ts +4 -1
- package/src/__tests__/post-process-params-component.unit.test.ts +1 -1
- package/src/__tests__/post-process-params-extract.unit.test.ts +2 -5
- package/src/__tests__/postprocess-camera-consistency.test.ts +4 -7
- package/src/__tests__/profiler-assembly.integration.test.ts +1 -1
- package/src/__tests__/propagate-transforms.dawn.test.ts +12 -11
- package/src/__tests__/record-worldid-isolation.test.ts +7 -29
- package/src/__tests__/recover-error-exhaustive.test-d.ts +1 -1
- package/src/__tests__/render-error-exhaustive.test-d.ts +81 -71
- package/src/__tests__/render-feature-assembly.test-d.ts +20 -9
- package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +8 -6
- package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +60 -558
- package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +57 -544
- package/src/__tests__/render-feature-prepared-graphics.fixture.ts +104 -0
- package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +40 -248
- package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +83 -340
- package/src/__tests__/render-feature-recover.integration.test.ts +9 -49
- package/src/__tests__/render-feature-runtime-surface.test-d.ts +17 -20
- package/src/__tests__/render-schema-consumer.integration.test.ts +20 -13
- package/src/__tests__/render-schema-owner-boundary.unit.test.ts +2 -6
- package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +5 -5
- package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +16 -31
- package/src/__tests__/render-system-extract.test.ts +36 -51
- package/src/__tests__/render-system-extract.unit.test.ts +360 -0
- package/src/__tests__/render-system-fold-bucket.unit.test.ts +3 -5
- package/src/__tests__/render-system-fold-fallback.unit.test.ts +6 -3
- package/src/__tests__/render-system-fold-material.unit.test.ts +3 -5
- package/src/__tests__/render-system-fold-metrics.unit.test.ts +5 -5
- package/src/__tests__/render-system-fold-modegate.unit.test.ts +6 -6
- package/src/__tests__/render-system-fold.dawn.test.ts +7 -5
- package/src/__tests__/render-system-mega.test.ts +292 -190
- package/src/__tests__/render-system-owner-split-contract.test.ts +10 -32
- package/src/__tests__/render-system-record-multi-material-textureview.test.ts +59 -45
- package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +23 -29
- package/src/__tests__/render-system-record.test.ts +398 -235
- package/src/__tests__/render-system-record.unit.test.ts +25 -26
- package/src/__tests__/render-system-skin-bg.unit.test.ts +8 -12
- package/src/__tests__/renderer-consumer.test-d.ts +16 -3
- package/src/__tests__/renderer-health.unit.test.ts +26 -916
- package/src/__tests__/renderer-lifecycle.integration.test.ts +19 -6
- package/src/__tests__/renderer-recover.unit.test.ts +49 -1069
- package/src/__tests__/renderer-result.test-d.ts +19 -9
- package/src/__tests__/renderer-surface.unit.test.ts +3 -5
- package/src/__tests__/renderer-test-utils.ts +56 -0
- package/src/__tests__/renderer.test-d.ts +59 -0
- package/src/__tests__/resolve-asset-handle-err.test.ts +111 -0
- package/src/__tests__/resolve-asset-handle.unit.test.ts +64 -0
- package/src/__tests__/resolve-depth-only-view.test.ts +1 -1
- package/src/__tests__/resolveName-fallback.test.ts +64 -0
- package/src/__tests__/resolveName-null-package.test.ts +38 -0
- package/src/__tests__/resolveName-unified-entry.test.ts +65 -0
- package/src/__tests__/resolveName.test.ts +64 -0
- package/src/__tests__/rhi-null-command-flow.unit.test.ts +43 -261
- package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +41 -169
- package/src/__tests__/roots-to-scene-asset.test.ts +199 -93
- package/src/__tests__/roundtrip-primitive.test.ts +351 -0
- package/src/__tests__/scene-bare-guid-refs-instantiate.test.ts +123 -0
- package/src/__tests__/scene-equirect-roundtrip.test.ts +103 -16
- package/src/__tests__/scene-instance-authoring.integration.test.ts +8 -0
- package/src/__tests__/scene-instance-bridge.integration.test.ts +1 -1
- package/src/__tests__/scene-instance-component.test.ts +1 -1
- package/src/__tests__/scene-mount-fixed-point.test.ts +257 -0
- package/src/__tests__/scene-mount-roundtrip.test.ts +987 -0
- package/src/__tests__/scene-mount-wrapper-roundtrip.test.ts +202 -0
- package/src/__tests__/scene-sugar-instantiate.test.ts +324 -0
- package/src/__tests__/serialize-mounts-roundtrip.test.ts +533 -0
- package/src/__tests__/serialize-scene-asset.test.ts +219 -22
- package/src/__tests__/shadow-caster-empty-schema.test.ts +3 -3
- package/src/__tests__/shadow-csm-cascade-loadop.test.ts +37 -17
- package/src/__tests__/shadow-csm-component.test.ts +3 -5
- package/src/__tests__/shadow-csm-extract.test.ts +2 -100
- package/src/__tests__/shadow-csm-orthoz.test.ts +2 -16
- package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +19 -14
- package/src/__tests__/shadow-csm-tile-consistency.test.ts +32 -26
- package/src/__tests__/shadow-csm-ubo.dawn.test.ts +8 -49
- package/src/__tests__/shadow-csm-ubo.test.ts +3 -25
- package/src/__tests__/shadow-csm-viewport.browser.test.ts +1 -1
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +67 -56
- package/src/__tests__/shadow-m1.dawn.test.ts +8 -9
- package/src/__tests__/shadow-mesh-ssbo-order.test.ts +11 -27
- package/src/__tests__/skin-errors.unit.test.ts +1 -1
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +7 -5
- package/src/__tests__/skylight-lazy-projection.test.ts +36 -15
- package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +5 -5
- package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +47 -64
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +12 -11
- package/src/__tests__/sprite-lit-extract-branch.test.ts +1 -1
- package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
- package/src/__tests__/ssao-bgl.test.ts +16 -9
- package/src/__tests__/ssao-buffers.test.ts +2 -2
- package/src/__tests__/ssao-data.test.ts +2 -2
- package/src/__tests__/ssao-errors.test.ts +1 -1
- package/src/__tests__/ssao-passes.test.ts +64 -392
- package/src/__tests__/ssao-pipelines.test.ts +1 -1
- package/src/__tests__/ssao-uniform-layout.test.ts +8 -6
- package/src/__tests__/stencil-outline-pixel.dawn.test.ts +25 -17
- package/src/__tests__/swap-chain-format.unit.test.ts +5 -7
- package/src/__tests__/systems.unit.test.ts +536 -1326
- package/src/__tests__/template-game-default-api-contract.test.ts +362 -0
- package/src/__tests__/text.unit.test.ts +56 -33
- package/src/__tests__/tilemap-childed-despawn.test.ts +8 -14
- package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +8 -12
- package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +2 -1
- package/src/__tests__/tilemap-chunk-extract.test.ts +13 -17
- package/src/__tests__/tilemap-dirty-rebuild.test.ts +7 -11
- package/src/__tests__/tilemap-material-cache.test.ts +8 -13
- package/src/__tests__/tilemap-multi-atlas.test.ts +12 -32
- package/src/__tests__/tilemap-owner-boundary.unit.test.ts +5 -2
- package/src/__tests__/tilemap-region-pack.test.ts +8 -12
- package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +8 -12
- package/src/__tests__/tilemap-uv-inset.test.ts +11 -14
- package/src/__tests__/tilemap-validate.test.ts +1 -1
- package/src/__tests__/tileset-asset-validate.test.ts +638 -0
- package/src/__tests__/transcode-fallback.unit.test.ts +207 -0
- package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +36 -28
- package/src/__tests__/transient-fields-collect.test.ts +28 -21
- package/src/__tests__/transient-runtime-behavior.test.ts +227 -0
- package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +4 -4
- package/src/__tests__/variant-selection.unit.test.ts +1 -1
- package/src/__tests__/video-extract-bindgroup.dawn.test.ts +89 -136
- package/src/__tests__/video-loader-consumption.test.ts +86 -0
- package/src/__tests__/video-loader-smoke.dawn.test.ts +60 -0
- package/src/__tests__/view-ubo-layout.browser.test.ts +0 -22
- package/src/__tests__/world-entity-key.test.ts +1 -1
- package/src/__tests__/world-scene-despawn.test.ts +1 -1
- package/src/__tests__/world-scene-instantiate.test.ts +2 -36
- package/src/__tests__/world-scene-nested.test.ts +1 -1
- package/src/__tests__/world-scene-override.test.ts +1 -1
- package/src/backend-selection.ts +69 -0
- package/src/collect-scene-asset.ts +28 -23
- package/src/components/__tests__/camera.test-d.ts +1 -1
- package/src/components/__tests__/camera.test.ts +1 -1
- package/src/components/__tests__/sprite-animation.test-d.ts +2 -2
- package/src/components/__tests__/tile-layer.test-d.ts +1 -1
- package/src/components/__tests__/tile-layer.unit.test.ts +2 -2
- package/src/createRenderer.ts +24 -36
- package/src/errors/environment.ts +1 -1
- package/src/index.ts +47 -113
- package/src/renderer-host.ts +33 -0
- package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +1 -1
- package/src/scene-instances/index.ts +4 -0
- package/src/scene-utils/mount-override-fold.ts +3 -7
- package/src/systems/__tests__/transparent-sort.bench.ts +3 -3
- package/src/systems/sprite-animation-tick.ts +1 -1
- package/src/systems/transparent-sort.ts +38 -38
- package/src/tilemap-register-validate.ts +4 -4
- package/dist/__tests__/bind-group-cache-binding.test.d.ts +0 -2
- package/dist/__tests__/bind-group-cache-binding.test.d.ts.map +0 -1
- package/dist/__tests__/bind-group-cache-frame.test.d.ts +0 -2
- package/dist/__tests__/bind-group-cache-frame.test.d.ts.map +0 -1
- package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts +0 -2
- package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts +0 -2
- package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts +0 -2
- package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts +0 -2
- package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts +0 -2
- package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-manifest.d.ts +0 -131
- package/dist/__tests__/fog-producer-manifest.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-pixel-probe.d.ts +0 -42
- package/dist/__tests__/fog-producer-pixel-probe.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-pixel.browser.test.d.ts +0 -2
- package/dist/__tests__/fog-producer-pixel.browser.test.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts +0 -2
- package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-real-fixture.d.ts +0 -5
- package/dist/__tests__/fog-producer-real-fixture.d.ts.map +0 -1
- package/dist/__tests__/fog-producer-vfx-effect.d.ts +0 -3
- package/dist/__tests__/fog-producer-vfx-effect.d.ts.map +0 -1
- package/dist/__tests__/fog-scene-asset.integration.test.d.ts +0 -2
- package/dist/__tests__/fog-scene-asset.integration.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts +0 -2
- package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts.map +0 -1
- package/dist/__tests__/gpu-resource.test.d.ts +0 -2
- package/dist/__tests__/gpu-resource.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts +0 -2
- package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-buffers.unit.test.d.ts +0 -2
- package/dist/__tests__/hdrp-buffers.unit.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts +0 -2
- package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts +0 -2
- package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts +0 -2
- package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts.map +0 -1
- package/dist/__tests__/hdrp-ssao-boundary.test.d.ts +0 -2
- package/dist/__tests__/hdrp-ssao-boundary.test.d.ts.map +0 -1
- package/dist/__tests__/hdrp-ssao-falsification.test.d.ts +0 -2
- package/dist/__tests__/hdrp-ssao-falsification.test.d.ts.map +0 -1
- package/dist/__tests__/helpers/primitive-world.d.ts +0 -8
- package/dist/__tests__/helpers/primitive-world.d.ts.map +0 -1
- package/dist/__tests__/helpers/render-asset-port.d.ts +0 -15
- package/dist/__tests__/helpers/render-asset-port.d.ts.map +0 -1
- package/dist/__tests__/helpers/scene-asset-port.d.ts +0 -9
- package/dist/__tests__/helpers/scene-asset-port.d.ts.map +0 -1
- package/dist/__tests__/material-derived-coordinates.integration.test.d.ts +0 -2
- package/dist/__tests__/material-derived-coordinates.integration.test.d.ts.map +0 -1
- package/dist/__tests__/pipeline-layout-selection.test.d.ts +0 -2
- package/dist/__tests__/pipeline-layout-selection.test.d.ts.map +0 -1
- package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts +0 -2
- package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts.map +0 -1
- package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts +0 -2
- package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts +0 -2
- package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts.map +0 -1
- package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts +0 -2
- package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-csm-caster-membership.test.d.ts +0 -2
- package/dist/__tests__/shadow-csm-caster-membership.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-csm-pssm.test.d.ts +0 -2
- package/dist/__tests__/shadow-csm-pssm.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts +0 -2
- package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-csm-viewport.test.d.ts +0 -2
- package/dist/__tests__/shadow-csm-viewport.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-m2.dawn.test.d.ts +0 -2
- package/dist/__tests__/shadow-m2.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts +0 -2
- package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-m3-calibrate.d.ts +0 -83
- package/dist/__tests__/shadow-m3-calibrate.d.ts.map +0 -1
- package/dist/__tests__/shadow-m3.dawn.test.d.ts +0 -2
- package/dist/__tests__/shadow-m3.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts +0 -2
- package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts +0 -2
- package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts.map +0 -1
- package/dist/__tests__/temporal-off-path.unit.test.d.ts +0 -2
- package/dist/__tests__/temporal-off-path.unit.test.d.ts.map +0 -1
- package/dist/__tests__/video-cache-regression.unit.test.d.ts +0 -2
- package/dist/__tests__/video-cache-regression.unit.test.d.ts.map +0 -1
- package/dist/asset-kind.d.ts +0 -4
- package/dist/asset-kind.d.ts.map +0 -1
- package/dist/errors/sprite-animation.d.ts +0 -3
- package/dist/errors/sprite-animation.d.ts.map +0 -1
- package/dist/scene-asset-port.d.ts +0 -17
- package/dist/scene-asset-port.d.ts.map +0 -1
- package/dist/scene-collect-errors.d.ts +0 -22
- package/dist/scene-collect-errors.d.ts.map +0 -1
- package/dist/shadow-m3-calibrate.d.ts +0 -40
- package/dist/shadow-m3-calibrate.d.ts.map +0 -1
- package/src/__tests__/bind-group-cache-binding.test.ts +0 -2513
- package/src/__tests__/bind-group-cache-frame.test.ts +0 -1511
- package/src/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.ts +0 -204
- package/src/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.ts +0 -297
- package/src/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.ts +0 -271
- package/src/__tests__/dispatch-fullscreen-pass-params.unit.test.ts +0 -321
- package/src/__tests__/fog-producer-manifest.ts +0 -162
- package/src/__tests__/fog-producer-pixel-probe.ts +0 -263
- package/src/__tests__/fog-producer-pixel.browser.test.ts +0 -26
- package/src/__tests__/fog-producer-pixel.dawn.test.ts +0 -55
- package/src/__tests__/fog-producer-real-fixture.ts +0 -805
- package/src/__tests__/fog-producer-vfx-effect.ts +0 -272
- package/src/__tests__/fog-scene-asset.integration.test.ts +0 -123
- package/src/__tests__/gpu-resource-store-cubemap-status.test.ts +0 -207
- package/src/__tests__/gpu-resource.test.ts +0 -1206
- package/src/__tests__/hdrp-bgl-slots.browser.test.ts +0 -50
- package/src/__tests__/hdrp-buffers.unit.test.ts +0 -70
- package/src/__tests__/hdrp-execute-filter.unit.test.ts +0 -200
- package/src/__tests__/hdrp-gbuffer-attachments.browser.test.ts +0 -64
- package/src/__tests__/hdrp-pipeline-asset-type.test-d.ts +0 -139
- package/src/__tests__/hdrp-ssao-boundary.test.ts +0 -237
- package/src/__tests__/hdrp-ssao-falsification.test.ts +0 -289
- package/src/__tests__/helpers/primitive-world.ts +0 -91
- package/src/__tests__/helpers/render-asset-port.ts +0 -65
- package/src/__tests__/helpers/scene-asset-port.ts +0 -23
- package/src/__tests__/material-derived-coordinates.integration.test.ts +0 -100
- package/src/__tests__/pipeline-layout-selection.test.ts +0 -298
- package/src/__tests__/post-process-reads-narrowing.test-d.ts +0 -52
- package/src/__tests__/post-process-register-roundtrip.dawn.test.ts +0 -230
- package/src/__tests__/render-feature-zero-feature.browser.test.ts +0 -152
- package/src/__tests__/render-pipeline-trivial.dawn.test.ts +0 -525
- package/src/__tests__/shadow-csm-caster-membership.test.ts +0 -232
- package/src/__tests__/shadow-csm-pssm.test.ts +0 -142
- package/src/__tests__/shadow-csm-temporal.browser.test.ts +0 -145
- package/src/__tests__/shadow-csm-viewport.test.ts +0 -74
- package/src/__tests__/shadow-m2.dawn.test.ts +0 -394
- package/src/__tests__/shadow-m3-calibrate-run.dawn.test.ts +0 -248
- package/src/__tests__/shadow-m3-calibrate.ts +0 -203
- package/src/__tests__/shadow-m3.dawn.test.ts +0 -288
- package/src/__tests__/shadow-probe-control-flow.dawn.test.ts +0 -264
- package/src/__tests__/shadow-quality-metrics.dawn.test.ts +0 -324
- package/src/__tests__/temporal-off-path.unit.test.ts +0 -69
- package/src/__tests__/video-cache-regression.unit.test.ts +0 -186
- package/src/asset-kind.ts +0 -7
- package/src/errors/sprite-animation.ts +0 -5
- package/src/scene-asset-port.ts +0 -32
- package/src/scene-collect-errors.ts +0 -30
- package/src/shadow-m3-calibrate.ts +0 -171
|
@@ -0,0 +1,1203 @@
|
|
|
1
|
+
// biome-ignore-all lint/complexity/noUselessLoneBlockStatements: fixture scopes are intentional
|
|
2
|
+
|
|
3
|
+
import type { AssetRuntimeError, AssetRuntimeErrorCode } from '@forgeax/engine-assets-runtime';
|
|
4
|
+
import {
|
|
5
|
+
MeshSsboCapacityExceededError,
|
|
6
|
+
MeshSsboCeilingReachedError,
|
|
7
|
+
SceneCollectAssetGuidUnresolvedError,
|
|
8
|
+
SceneCollectEntityRefOutOfClosureError,
|
|
9
|
+
} from '@forgeax/engine-assets-runtime';
|
|
10
|
+
import type { RenderError, RenderErrorCode } from '@forgeax/engine-render';
|
|
11
|
+
import { RhiError } from '@forgeax/engine-rhi';
|
|
12
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
13
|
+
import type { SkinError, SkinErrorCode } from '@forgeax/engine-skinning';
|
|
14
|
+
import type { AssetErrorCode, ImageErrorCode } from '@forgeax/engine-types';
|
|
15
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
16
|
+
import { EquirectProjectionFailedError } from '../../../render/src/errors/render';
|
|
17
|
+
import { RhiErrorListenerRegistry } from '../../../render/src/lifecycle';
|
|
18
|
+
import {
|
|
19
|
+
PostProcessError,
|
|
20
|
+
type PostProcessErrorCode,
|
|
21
|
+
} from '../../../render/src/post-process-errors';
|
|
22
|
+
import {
|
|
23
|
+
__classifyEnvErrorReasonForTest,
|
|
24
|
+
__composeEnvErrorHintForTest,
|
|
25
|
+
} from '../create-renderer-env-classify';
|
|
26
|
+
import { requireRenderer } from './renderer-test-utils';
|
|
27
|
+
|
|
28
|
+
// feat-20260704-runtime-tier1-decomposition M2 / w12: the eliminated top-level
|
|
29
|
+
// RuntimeError / RuntimeErrorCode aggregate unions (D-3) are reconstituted here
|
|
30
|
+
// as test-local aliases so the whole-runtime-surface exhaustive-switch bodies
|
|
31
|
+
// below stay byte-identical (AC-09). These are test scaffolding, not a
|
|
32
|
+
// production cross-cluster SSOT -- w13's per-cluster *.test-d.ts files are the
|
|
33
|
+
// real regression guard. Equal to the fanned-out render + asset + skin
|
|
34
|
+
// clusters (27 codes / 27 classes); recover + environment excluded, as the
|
|
35
|
+
// original unions were.
|
|
36
|
+
type RuntimeLayerErrorCode = RenderErrorCode | AssetRuntimeErrorCode | SkinErrorCode;
|
|
37
|
+
type RuntimeLayerError = RenderError | AssetRuntimeError | SkinError;
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
// ─── from create-renderer-env-error-classify.test.ts ───
|
|
41
|
+
describe('create-renderer-env-error-classify.test.ts', () => {
|
|
42
|
+
describe('classifyEnvErrorReason', () => {
|
|
43
|
+
it('keeps GPU-class wording for adapter-unavailable', () => {
|
|
44
|
+
const inner = { name: 'RhiError', code: 'adapter-unavailable' };
|
|
45
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
|
|
46
|
+
'no usable rendering backend',
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('keeps GPU-class wording for feature-not-enabled / limit-exceeded / device-lost / oom', () => {
|
|
51
|
+
for (const code of ['feature-not-enabled', 'limit-exceeded', 'device-lost', 'oom']) {
|
|
52
|
+
const inner = { name: 'RhiError', code };
|
|
53
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
|
|
54
|
+
'no usable rendering backend',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('switches wording for ShaderError manifest-malformed', () => {
|
|
60
|
+
const inner = { name: 'ShaderError', code: 'manifest-malformed' };
|
|
61
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
|
|
62
|
+
'engine init failed (ShaderError: manifest-malformed)',
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('switches wording for PackError pack-malformed-pack', () => {
|
|
67
|
+
const inner = { name: 'PackError', code: 'pack-malformed-pack' };
|
|
68
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
|
|
69
|
+
'engine init failed (PackError: pack-malformed-pack)',
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('falls back to name-only when code is absent', () => {
|
|
74
|
+
const inner = { name: 'AssetError' };
|
|
75
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', inner)).toBe(
|
|
76
|
+
'engine init failed (AssetError)',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('returns base message untouched when inner is undefined', () => {
|
|
81
|
+
expect(__classifyEnvErrorReasonForTest('no usable rendering backend', undefined)).toBe(
|
|
82
|
+
'no usable rendering backend',
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('returns base message untouched when inner is non-object', () => {
|
|
87
|
+
// Non-object inner (string / number) should not crash; keep base message.
|
|
88
|
+
expect(
|
|
89
|
+
__classifyEnvErrorReasonForTest('no usable rendering backend', 'oops' as never),
|
|
90
|
+
).toBe('no usable rendering backend');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('preserves the Channel-3-fallback variant of the base message', () => {
|
|
94
|
+
const inner = { name: 'ShaderError', code: 'manifest-malformed' };
|
|
95
|
+
expect(
|
|
96
|
+
__classifyEnvErrorReasonForTest(
|
|
97
|
+
'no usable rendering backend (Channel 3 fallback failed)',
|
|
98
|
+
inner,
|
|
99
|
+
),
|
|
100
|
+
).toBe('engine init failed (ShaderError: manifest-malformed)');
|
|
101
|
+
// GPU inner keeps the Channel-3 suffix verbatim.
|
|
102
|
+
const gpuInner = { name: 'RhiError', code: 'adapter-unavailable' };
|
|
103
|
+
expect(
|
|
104
|
+
__classifyEnvErrorReasonForTest(
|
|
105
|
+
'no usable rendering backend (Channel 3 fallback failed)',
|
|
106
|
+
gpuInner,
|
|
107
|
+
),
|
|
108
|
+
).toBe('no usable rendering backend (Channel 3 fallback failed)');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('composeEnvErrorHint (bug-20260610 dual-channel env failure)', () => {
|
|
113
|
+
// When both Channel 2 (rhi-webgpu) and Channel 3 (rhi-wgpu wasm GL) report
|
|
114
|
+
// adapter-unavailable / rhi-not-available, the failure is a browser-config
|
|
115
|
+
// issue (Edge with edge://flags/#enable-unsafe-webgpu = Disabled) rather
|
|
116
|
+
// than a real GPU absence. The hint surfaces actionable browser guidance.
|
|
117
|
+
it('emits hint when both errors are adapter-unavailable', () => {
|
|
118
|
+
const out = __composeEnvErrorHintForTest(
|
|
119
|
+
{ code: 'adapter-unavailable' },
|
|
120
|
+
{ code: 'adapter-unavailable' },
|
|
121
|
+
);
|
|
122
|
+
expect(out).toContain('both channels report environmental failure');
|
|
123
|
+
expect(out).toContain('edge://flags/#enable-unsafe-webgpu');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('emits hint for adapter-unavailable + rhi-not-available cross', () => {
|
|
127
|
+
const out = __composeEnvErrorHintForTest(
|
|
128
|
+
{ code: 'adapter-unavailable' },
|
|
129
|
+
{ code: 'rhi-not-available' },
|
|
130
|
+
);
|
|
131
|
+
expect(out).toBeDefined();
|
|
132
|
+
expect(out).toContain('Enabled');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns undefined when only one channel reports an env code', () => {
|
|
136
|
+
// Real-GPU-class failure on one side, asset error on the other — no
|
|
137
|
+
// browser-config guidance applies.
|
|
138
|
+
expect(
|
|
139
|
+
__composeEnvErrorHintForTest(
|
|
140
|
+
{ code: 'adapter-unavailable' },
|
|
141
|
+
{ code: 'manifest-malformed' },
|
|
142
|
+
),
|
|
143
|
+
).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('returns undefined when either side is missing', () => {
|
|
147
|
+
expect(
|
|
148
|
+
__composeEnvErrorHintForTest({ code: 'adapter-unavailable' }, undefined),
|
|
149
|
+
).toBeUndefined();
|
|
150
|
+
expect(
|
|
151
|
+
__composeEnvErrorHintForTest(undefined, { code: 'adapter-unavailable' }),
|
|
152
|
+
).toBeUndefined();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('returns undefined for plain Error-like objects without .code', () => {
|
|
156
|
+
// Edge case: `wgpuError` may be a raw Error from a wasm load throw
|
|
157
|
+
// (no .code property). The helper must not crash and must not emit
|
|
158
|
+
// false-positive guidance.
|
|
159
|
+
const plainErr = new Error('wasm load failed');
|
|
160
|
+
expect(
|
|
161
|
+
__composeEnvErrorHintForTest({ code: 'adapter-unavailable' }, plainErr),
|
|
162
|
+
).toBeUndefined();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('returns undefined for non-string code values', () => {
|
|
166
|
+
// RhiError shape always has .code: string, but defensive coverage.
|
|
167
|
+
expect(
|
|
168
|
+
__composeEnvErrorHintForTest({ code: 42 }, { code: 'adapter-unavailable' }),
|
|
169
|
+
).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
{
|
|
175
|
+
// --- migrated from device-lost-fan-out.test.ts ---
|
|
176
|
+
describe('Renderer.subscribe lifecycle events', () => {
|
|
177
|
+
const manifest = `data:application/json,${encodeURIComponent(
|
|
178
|
+
JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
|
|
179
|
+
)}`;
|
|
180
|
+
const canvas = { getContext: () => null } as unknown as HTMLCanvasElement;
|
|
181
|
+
|
|
182
|
+
it('publishes disposal through subscribe and keeps state inspectable', async () => {
|
|
183
|
+
const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
|
|
184
|
+
expect(renderer.state()).toBe('alive');
|
|
185
|
+
expect(renderer.inspect().state).toBe('alive');
|
|
186
|
+
|
|
187
|
+
const events: unknown[] = [];
|
|
188
|
+
const unsubscribe = renderer.subscribe((event) => events.push(event));
|
|
189
|
+
expect(typeof unsubscribe).toBe('function');
|
|
190
|
+
expect((await renderer.dispose()).ok).toBe(true);
|
|
191
|
+
expect(events).toContainEqual({
|
|
192
|
+
kind: 'state-changed',
|
|
193
|
+
previous: 'alive',
|
|
194
|
+
current: 'disposed',
|
|
195
|
+
});
|
|
196
|
+
unsubscribe();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('does not retain a listener after unsubscribe', async () => {
|
|
200
|
+
const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
|
|
201
|
+
const events: unknown[] = [];
|
|
202
|
+
const unsubscribe = renderer.subscribe((event) => events.push(event));
|
|
203
|
+
unsubscribe();
|
|
204
|
+
|
|
205
|
+
expect((await renderer.dispose()).ok).toBe(true);
|
|
206
|
+
expect(events).toEqual([]);
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
{
|
|
212
|
+
// --- migrated from device-lost.test.ts ---
|
|
213
|
+
describe('Renderer surface recovery contract', () => {
|
|
214
|
+
const manifest = `data:application/json,${encodeURIComponent(
|
|
215
|
+
JSON.stringify({ schemaVersion: '1.0.0', entries: [] }),
|
|
216
|
+
)}`;
|
|
217
|
+
const canvas = { getContext: () => null } as unknown as HTMLCanvasElement;
|
|
218
|
+
|
|
219
|
+
it('releases and restores the surface without changing the renderer identity', async () => {
|
|
220
|
+
const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
|
|
221
|
+
const before = renderer.inspect();
|
|
222
|
+
|
|
223
|
+
expect(renderer.releaseSurface().ok).toBe(true);
|
|
224
|
+
expect(renderer.inspect().surface).toBe('released');
|
|
225
|
+
expect(renderer.restoreSurface().ok).toBe(true);
|
|
226
|
+
expect(renderer.inspect().surface).toBe('available');
|
|
227
|
+
expect(renderer.inspect().capabilities.backendKind).toBe('null');
|
|
228
|
+
expect(renderer.inspect().state).toBe(before.state);
|
|
229
|
+
|
|
230
|
+
expect((await renderer.dispose()).ok).toBe(true);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('returns a structured recover result when no device is lost', async () => {
|
|
234
|
+
const renderer = await requireRenderer(canvas, { rhi }, { shaderManifestUrl: manifest });
|
|
235
|
+
const recovered = await renderer.recover();
|
|
236
|
+
|
|
237
|
+
expect(recovered.ok).toBe(false);
|
|
238
|
+
if (!recovered.ok) expect(recovered.error.code).toBe('renderer-state-invalid');
|
|
239
|
+
expect((await renderer.dispose()).ok).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
// ─── from error-exhaustive.test.ts ───
|
|
246
|
+
describe('error-exhaustive.test.ts', () => {
|
|
247
|
+
describe('t2 - AC-15 AssetErrorCode exhaustive switch (new members)', () => {
|
|
248
|
+
it('AssetErrorCode includes cubemap-handle-missing', () => {
|
|
249
|
+
const code: AssetErrorCode = 'cubemap-handle-missing';
|
|
250
|
+
expect(code).toBe('cubemap-handle-missing');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('AssetErrorCode includes invalid-source-format', () => {
|
|
254
|
+
const code: AssetErrorCode = 'invalid-source-format';
|
|
255
|
+
expect(code).toBe('invalid-source-format');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('AssetErrorCode includes load-failed', () => {
|
|
259
|
+
const code: AssetErrorCode = 'load-failed';
|
|
260
|
+
expect(code).toBe('load-failed');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('AssetErrorCode includes device-unsupported', () => {
|
|
264
|
+
const code: AssetErrorCode = 'device-unsupported';
|
|
265
|
+
expect(code).toBe('device-unsupported');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('AssetErrorCode includes ibl-precompute-not-dispatched (t56 M3.5 minor evolution)', () => {
|
|
269
|
+
const code: AssetErrorCode = 'ibl-precompute-not-dispatched';
|
|
270
|
+
expect(code).toBe('ibl-precompute-not-dispatched');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('AssetErrorCode exhaustive switch covers all members without default', () => {
|
|
274
|
+
function exhaustive(code: AssetErrorCode): string {
|
|
275
|
+
switch (code) {
|
|
276
|
+
case 'asset-not-found':
|
|
277
|
+
return 'not found';
|
|
278
|
+
case 'asset-parse-failed':
|
|
279
|
+
return 'parse failed';
|
|
280
|
+
case 'asset-format-unsupported':
|
|
281
|
+
return 'format unsupported';
|
|
282
|
+
case 'asset-fetch-failed':
|
|
283
|
+
return 'fetch failed';
|
|
284
|
+
case 'catalog-source-unconfigured':
|
|
285
|
+
return 'catalog source unconfigured';
|
|
286
|
+
case 'asset-invalid-value':
|
|
287
|
+
return 'invalid value';
|
|
288
|
+
case 'cubemap-handle-missing':
|
|
289
|
+
return 'cubemap handle missing';
|
|
290
|
+
case 'invalid-source-format':
|
|
291
|
+
return 'invalid source format';
|
|
292
|
+
case 'load-failed':
|
|
293
|
+
return 'load failed';
|
|
294
|
+
case 'device-unsupported':
|
|
295
|
+
return 'device unsupported';
|
|
296
|
+
case 'ibl-precompute-not-dispatched':
|
|
297
|
+
return 'ibl precompute not dispatched';
|
|
298
|
+
case 'mesh-vertex-stride-mismatch':
|
|
299
|
+
return 'mesh vertex stride mismatch';
|
|
300
|
+
case 'material-shader-ref-broken':
|
|
301
|
+
return 'shader ref broken';
|
|
302
|
+
case 'material-circular-inheritance':
|
|
303
|
+
return 'circular inheritance';
|
|
304
|
+
// === 2 new codes (feat-20260603-asset-import-loader-injection M1 / w1) ===
|
|
305
|
+
case 'loader-not-registered':
|
|
306
|
+
return 'loader not registered';
|
|
307
|
+
case 'asset-not-imported':
|
|
308
|
+
return 'asset not imported';
|
|
309
|
+
// === 1 new code (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===
|
|
310
|
+
case 'texture-source-not-imported':
|
|
311
|
+
return 'texture source not imported';
|
|
312
|
+
case 'source-not-imported':
|
|
313
|
+
return 'source not imported';
|
|
314
|
+
// === 3 new codes (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===
|
|
315
|
+
case 'mesh-renderer-material-override-invalid':
|
|
316
|
+
return 'mesh renderer material override invalid';
|
|
317
|
+
case 'mesh-renderer-material-override-overflow':
|
|
318
|
+
return 'mesh renderer material override overflow';
|
|
319
|
+
case 'mesh-asset-submeshes-empty':
|
|
320
|
+
return 'mesh asset submeshes empty';
|
|
321
|
+
case 'mesh-asset-material-slot-index-out-of-range':
|
|
322
|
+
return 'mesh asset material slot index out of range';
|
|
323
|
+
case 'mesh-submesh-index-range-out-of-bounds':
|
|
324
|
+
return 'mesh submesh index range out of bounds';
|
|
325
|
+
// === 1 new code (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===
|
|
326
|
+
case 'tileset-region-index-out-of-range':
|
|
327
|
+
return 'tileset region index out of range';
|
|
328
|
+
// === 1 new code (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===
|
|
329
|
+
case 'tileset-tile-entry-malformed':
|
|
330
|
+
return 'tileset tile entry malformed';
|
|
331
|
+
// === 1 new code (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===
|
|
332
|
+
case 'asset-invalidated':
|
|
333
|
+
return 'asset invalidated';
|
|
334
|
+
// === 1 new code (feat-20260629-multi-uv-set-support M2 / m2-w5) ===
|
|
335
|
+
case 'mesh-bin-contract-violation':
|
|
336
|
+
return 'mesh bin contract violation';
|
|
337
|
+
// === 1 new code (feat-20260707-texture-block-compression M5 / w35) ===
|
|
338
|
+
case 'mipgen-unsupported-compressed-format':
|
|
339
|
+
return 'mipgen unsupported compressed format';
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
expect(exhaustive('asset-not-found')).toBe('not found');
|
|
343
|
+
expect(exhaustive('texture-source-not-imported')).toBe('texture source not imported');
|
|
344
|
+
expect(exhaustive('cubemap-handle-missing')).toBe('cubemap handle missing');
|
|
345
|
+
expect(exhaustive('invalid-source-format')).toBe('invalid source format');
|
|
346
|
+
expect(exhaustive('load-failed')).toBe('load failed');
|
|
347
|
+
expect(exhaustive('device-unsupported')).toBe('device unsupported');
|
|
348
|
+
expect(exhaustive('ibl-precompute-not-dispatched')).toBe('ibl precompute not dispatched');
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
describe('t2 - AC-15 ImageErrorCode exhaustive switch (new member)', () => {
|
|
353
|
+
it('ImageErrorCode includes image-hdr-decode-failed', () => {
|
|
354
|
+
const code: ImageErrorCode = 'image-hdr-decode-failed';
|
|
355
|
+
expect(code).toBe('image-hdr-decode-failed');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('ImageErrorCode exhaustive switch covers all members without default', () => {
|
|
359
|
+
function exhaustive(code: ImageErrorCode): string {
|
|
360
|
+
switch (code) {
|
|
361
|
+
case 'image-decode-failed':
|
|
362
|
+
return 'decode failed';
|
|
363
|
+
case 'image-format-unsupported':
|
|
364
|
+
return 'format unsupported';
|
|
365
|
+
case 'image-dimension-out-of-bounds':
|
|
366
|
+
return 'out of bounds';
|
|
367
|
+
case 'image-meta-missing':
|
|
368
|
+
return 'meta missing';
|
|
369
|
+
case 'image-hdr-decode-failed':
|
|
370
|
+
return 'hdr decode failed';
|
|
371
|
+
// feat-20260521-sprite-atlas-animation M1 T-02 — atlas hook
|
|
372
|
+
// fail-fast triplet (plan-strategy section 2 D-2). Add the three
|
|
373
|
+
// case arms so the exhaustive switch keeps compiling without a
|
|
374
|
+
// default fall-through after ImageErrorCode grew from 5 to 8.
|
|
375
|
+
case 'atlas-empty-input':
|
|
376
|
+
return 'atlas empty input';
|
|
377
|
+
case 'atlas-size-exceeded':
|
|
378
|
+
return 'atlas size exceeded';
|
|
379
|
+
case 'atlas-region-mismatch':
|
|
380
|
+
return 'atlas region mismatch';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
expect(exhaustive('image-decode-failed')).toBe('decode failed');
|
|
384
|
+
expect(exhaustive('image-hdr-decode-failed')).toBe('hdr decode failed');
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
{
|
|
391
|
+
// ─── from errors.test.ts ───
|
|
392
|
+
describe('errors.test.ts', () => {
|
|
393
|
+
describe('AC-11 RuntimeErrorCode exhaustive switch (11 members, no default)', () => {
|
|
394
|
+
it('exhaustive switch covers all members without a default branch', () => {
|
|
395
|
+
function exhaustive(code: RuntimeLayerErrorCode): string {
|
|
396
|
+
switch (code) {
|
|
397
|
+
case 'lifecycle-construction-failed':
|
|
398
|
+
return 'lifecycle construction failed';
|
|
399
|
+
case 'world-lease-invalid':
|
|
400
|
+
return 'world lease invalid';
|
|
401
|
+
case 'frame-input-invalid':
|
|
402
|
+
return 'frame input invalid';
|
|
403
|
+
case 'scene-projection-failed':
|
|
404
|
+
return 'scene projection failed';
|
|
405
|
+
case 'asset-binding-failed':
|
|
406
|
+
return 'asset binding failed';
|
|
407
|
+
case 'feature-plan-failed':
|
|
408
|
+
return 'feature plan failed';
|
|
409
|
+
case 'graph-build-failed':
|
|
410
|
+
return 'graph build failed';
|
|
411
|
+
case 'device-operation-failed':
|
|
412
|
+
return 'device operation failed';
|
|
413
|
+
case 'surface-unavailable':
|
|
414
|
+
return 'surface unavailable';
|
|
415
|
+
case 'renderer-state-invalid':
|
|
416
|
+
return 'renderer state invalid';
|
|
417
|
+
case 'recovery-failed':
|
|
418
|
+
return 'recovery failed';
|
|
419
|
+
case 'cleanup-failed':
|
|
420
|
+
return 'cleanup failed';
|
|
421
|
+
case 'frame-receipt-stale':
|
|
422
|
+
return 'frame receipt stale';
|
|
423
|
+
case 'renderer-contract-failed':
|
|
424
|
+
return 'renderer contract failed';
|
|
425
|
+
case 'observation-unavailable':
|
|
426
|
+
return 'observation unavailable';
|
|
427
|
+
case 'shadow-invalid-config':
|
|
428
|
+
return 'shadow invalid config';
|
|
429
|
+
case 'skin-joint-count-exceeded':
|
|
430
|
+
return 'skin joint count exceeded';
|
|
431
|
+
case 'skin-joint-despawned':
|
|
432
|
+
return 'skin joint despawned';
|
|
433
|
+
case 'skin-joint-path-unresolved':
|
|
434
|
+
return 'skin joint path unresolved';
|
|
435
|
+
case 'skin-instances-coexist-forbidden':
|
|
436
|
+
return 'skin instances coexist forbidden';
|
|
437
|
+
case 'vertex-storage-buffer-unavailable':
|
|
438
|
+
return 'vertex storage buffer unavailable';
|
|
439
|
+
case 'vertex-color-variant-conflict':
|
|
440
|
+
return 'vertex color variant conflict';
|
|
441
|
+
case 'skin-palette-overflow':
|
|
442
|
+
return 'skin palette overflow';
|
|
443
|
+
case 'material-resolved-empty-passes':
|
|
444
|
+
return 'material resolved empty passes';
|
|
445
|
+
case 'equirect-projection-failed':
|
|
446
|
+
return 'equirect projection failed';
|
|
447
|
+
case 'mesh-ssbo-capacity-exceeded':
|
|
448
|
+
return 'mesh ssbo capacity exceeded';
|
|
449
|
+
case 'mesh-ssbo-ceiling-reached':
|
|
450
|
+
return 'mesh ssbo ceiling reached';
|
|
451
|
+
case 'hdrp-light-budget-exceeded':
|
|
452
|
+
return 'hdrp light budget exceeded';
|
|
453
|
+
case 'hdrp-index-list-overflow':
|
|
454
|
+
return 'hdrp index list overflow';
|
|
455
|
+
// feat-20260612-hdrp-deferred-shading-learn-render-5-8 M1 / w6:
|
|
456
|
+
// 3 new deferred-path error codes.
|
|
457
|
+
case 'hdrp-deferred-caps-insufficient':
|
|
458
|
+
return 'hdrp deferred caps insufficient';
|
|
459
|
+
// feat-20260611-fox-skinning-vertex-attribute-chain M4 / w17 (D-5):
|
|
460
|
+
// bidirectional Skin <-> pbr-skin material mismatch detected at extract.
|
|
461
|
+
case 'skin-material-mismatch':
|
|
462
|
+
return 'skin material mismatch';
|
|
463
|
+
case 'material-skin-attr-missing':
|
|
464
|
+
return 'material skin attr missing';
|
|
465
|
+
// feat-20260612-skin-palette-per-frame-upload M2 / m2-5:
|
|
466
|
+
// SkinExtractErrorCode subset union (3 new extract-stage classes).
|
|
467
|
+
case 'skeleton-resolve-failed':
|
|
468
|
+
return 'skeleton resolve failed';
|
|
469
|
+
case 'joint-count-mismatch':
|
|
470
|
+
return 'joint count mismatch';
|
|
471
|
+
case 'joint-entity-dangling':
|
|
472
|
+
return 'joint entity dangling';
|
|
473
|
+
// feat-20260612-point-light-shadows-urp-hdrp Round-2 F-4:
|
|
474
|
+
// ShadowAtlas P3 closed-union compliance.
|
|
475
|
+
case 'point-shadow-atlas-uninitialized':
|
|
476
|
+
return 'point shadow atlas uninitialized';
|
|
477
|
+
case 'point-shadow-atlas-bounds-violation':
|
|
478
|
+
return 'point shadow atlas bounds violation';
|
|
479
|
+
// feat-20260623-world-space-video-asset M3 / w11: AC-10 capability
|
|
480
|
+
// double-miss code (add-only minor).
|
|
481
|
+
case 'video-upload-unsupported':
|
|
482
|
+
return 'video upload unsupported';
|
|
483
|
+
// feat-20260701-rootstosceneasset-forest-collect-schema-derived-ha
|
|
484
|
+
// M1 / w3 (D-5): new scene-collect error codes.
|
|
485
|
+
case 'scene-collect-entity-ref-out-of-closure':
|
|
486
|
+
return 'scene collect entity ref out of closure';
|
|
487
|
+
case 'scene-collect-asset-guid-unresolved':
|
|
488
|
+
return 'scene collect asset guid unresolved';
|
|
489
|
+
case 'render-feature-registration-conflict':
|
|
490
|
+
return 'render feature registration conflict';
|
|
491
|
+
case 'render-feature-stage-failed':
|
|
492
|
+
return 'render feature stage failed';
|
|
493
|
+
case 'render-feature-capability-missing':
|
|
494
|
+
return 'render feature capability missing';
|
|
495
|
+
case 'render-feature-pass-order-conflict':
|
|
496
|
+
return 'render feature pass order conflict';
|
|
497
|
+
case 'render-feature-preparation-failed':
|
|
498
|
+
return 'render feature preparation failed';
|
|
499
|
+
case 'render-feature-prepared-state-mismatch':
|
|
500
|
+
return 'render feature prepared state mismatch';
|
|
501
|
+
case 'render-feature-draw-recording-failed':
|
|
502
|
+
return 'render feature draw recording failed';
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
expect(exhaustive('equirect-projection-failed')).toBe('equirect projection failed');
|
|
506
|
+
expect(exhaustive('material-resolved-empty-passes')).toBe('material resolved empty passes');
|
|
507
|
+
expect(exhaustive('shadow-invalid-config')).toBe('shadow invalid config');
|
|
508
|
+
expect(exhaustive('mesh-ssbo-capacity-exceeded')).toBe('mesh ssbo capacity exceeded');
|
|
509
|
+
expect(exhaustive('mesh-ssbo-ceiling-reached')).toBe('mesh ssbo ceiling reached');
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/*
|
|
514
|
+
* feat-20260612-hdrp-deferred-shading-learn-render-5-8 M1 / w3:
|
|
515
|
+
* RuntimeErrorCode +3 members — exhaustive narrow test.
|
|
516
|
+
*
|
|
517
|
+
* AC-07: three new error codes must be members of RuntimeErrorCode closed union.
|
|
518
|
+
* Each error code narrows without a default branch in switch (err.code).
|
|
519
|
+
*/
|
|
520
|
+
describe('RuntimeErrorCode +3 new members (w3)', () => {
|
|
521
|
+
it('hdrp-deferred-caps-insufficient is a valid RuntimeErrorCode', () => {
|
|
522
|
+
const code: RuntimeLayerErrorCode = 'hdrp-deferred-caps-insufficient';
|
|
523
|
+
expect(code).toBe('hdrp-deferred-caps-insufficient');
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('exhaustive switch covers all 3 new members alongside existing members', () => {
|
|
527
|
+
function exhaustive(code: RuntimeLayerErrorCode): string {
|
|
528
|
+
switch (code) {
|
|
529
|
+
case 'lifecycle-construction-failed':
|
|
530
|
+
return 'ok';
|
|
531
|
+
case 'world-lease-invalid':
|
|
532
|
+
return 'ok';
|
|
533
|
+
case 'frame-input-invalid':
|
|
534
|
+
return 'ok';
|
|
535
|
+
case 'scene-projection-failed':
|
|
536
|
+
return 'ok';
|
|
537
|
+
case 'asset-binding-failed':
|
|
538
|
+
return 'ok';
|
|
539
|
+
case 'feature-plan-failed':
|
|
540
|
+
return 'ok';
|
|
541
|
+
case 'graph-build-failed':
|
|
542
|
+
return 'ok';
|
|
543
|
+
case 'device-operation-failed':
|
|
544
|
+
return 'ok';
|
|
545
|
+
case 'surface-unavailable':
|
|
546
|
+
return 'ok';
|
|
547
|
+
case 'renderer-state-invalid':
|
|
548
|
+
return 'ok';
|
|
549
|
+
case 'recovery-failed':
|
|
550
|
+
return 'ok';
|
|
551
|
+
case 'cleanup-failed':
|
|
552
|
+
return 'ok';
|
|
553
|
+
case 'frame-receipt-stale':
|
|
554
|
+
return 'ok';
|
|
555
|
+
case 'renderer-contract-failed':
|
|
556
|
+
return 'ok';
|
|
557
|
+
case 'observation-unavailable':
|
|
558
|
+
return 'ok';
|
|
559
|
+
case 'shadow-invalid-config':
|
|
560
|
+
return 'ok';
|
|
561
|
+
case 'skin-joint-count-exceeded':
|
|
562
|
+
return 'ok';
|
|
563
|
+
case 'skin-joint-despawned':
|
|
564
|
+
return 'ok';
|
|
565
|
+
case 'skin-joint-path-unresolved':
|
|
566
|
+
return 'ok';
|
|
567
|
+
case 'skin-instances-coexist-forbidden':
|
|
568
|
+
return 'ok';
|
|
569
|
+
case 'vertex-storage-buffer-unavailable':
|
|
570
|
+
return 'ok';
|
|
571
|
+
case 'vertex-color-variant-conflict':
|
|
572
|
+
return 'ok';
|
|
573
|
+
case 'skin-palette-overflow':
|
|
574
|
+
return 'ok';
|
|
575
|
+
case 'material-resolved-empty-passes':
|
|
576
|
+
return 'ok';
|
|
577
|
+
case 'equirect-projection-failed':
|
|
578
|
+
return 'ok';
|
|
579
|
+
case 'mesh-ssbo-capacity-exceeded':
|
|
580
|
+
return 'ok';
|
|
581
|
+
case 'mesh-ssbo-ceiling-reached':
|
|
582
|
+
return 'ok';
|
|
583
|
+
case 'hdrp-light-budget-exceeded':
|
|
584
|
+
return 'ok';
|
|
585
|
+
case 'hdrp-index-list-overflow':
|
|
586
|
+
return 'ok';
|
|
587
|
+
case 'skin-material-mismatch':
|
|
588
|
+
return 'ok';
|
|
589
|
+
case 'material-skin-attr-missing':
|
|
590
|
+
return 'ok';
|
|
591
|
+
case 'skeleton-resolve-failed':
|
|
592
|
+
return 'ok';
|
|
593
|
+
case 'joint-count-mismatch':
|
|
594
|
+
return 'ok';
|
|
595
|
+
case 'joint-entity-dangling':
|
|
596
|
+
return 'ok';
|
|
597
|
+
case 'hdrp-deferred-caps-insufficient':
|
|
598
|
+
return 'ok';
|
|
599
|
+
case 'point-shadow-atlas-uninitialized':
|
|
600
|
+
return 'ok';
|
|
601
|
+
case 'point-shadow-atlas-bounds-violation':
|
|
602
|
+
return 'ok';
|
|
603
|
+
case 'video-upload-unsupported':
|
|
604
|
+
return 'ok';
|
|
605
|
+
case 'scene-collect-entity-ref-out-of-closure':
|
|
606
|
+
return 'ok';
|
|
607
|
+
case 'scene-collect-asset-guid-unresolved':
|
|
608
|
+
return 'ok';
|
|
609
|
+
case 'render-feature-registration-conflict':
|
|
610
|
+
return 'ok';
|
|
611
|
+
case 'render-feature-stage-failed':
|
|
612
|
+
return 'ok';
|
|
613
|
+
case 'render-feature-capability-missing':
|
|
614
|
+
return 'ok';
|
|
615
|
+
case 'render-feature-pass-order-conflict':
|
|
616
|
+
return 'ok';
|
|
617
|
+
case 'render-feature-preparation-failed':
|
|
618
|
+
return 'ok';
|
|
619
|
+
case 'render-feature-prepared-state-mismatch':
|
|
620
|
+
return 'ok';
|
|
621
|
+
case 'render-feature-draw-recording-failed':
|
|
622
|
+
return 'ok';
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
expect(exhaustive('hdrp-deferred-caps-insufficient')).toBe('ok');
|
|
626
|
+
expect(exhaustive('point-shadow-atlas-uninitialized')).toBe('ok');
|
|
627
|
+
expect(exhaustive('point-shadow-atlas-bounds-violation')).toBe('ok');
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
describe('AC-03 mesh-ssbo error narrowing in onError callback shape (no `as`)', () => {
|
|
632
|
+
it('switch (err.code) narrows the detail variants for the two new codes', () => {
|
|
633
|
+
// Build a fake onError handler closing over a mutable record we can
|
|
634
|
+
// assert against. Critical: the body must compile without any `as`
|
|
635
|
+
// narrowing — TS must derive `err.detail.requested|capacity|ceiling` as
|
|
636
|
+
// `number` purely from the `case` discriminant.
|
|
637
|
+
const captured: {
|
|
638
|
+
requested: number | null;
|
|
639
|
+
capacity: number | null;
|
|
640
|
+
ceiling: number | null;
|
|
641
|
+
handled: boolean;
|
|
642
|
+
} = { requested: null, capacity: null, ceiling: null, handled: false };
|
|
643
|
+
|
|
644
|
+
const onError = (err: RuntimeLayerError): void => {
|
|
645
|
+
switch (err.code) {
|
|
646
|
+
case 'mesh-ssbo-capacity-exceeded': {
|
|
647
|
+
// err narrows to MeshSsboCapacityExceededError; detail is the
|
|
648
|
+
// discriminated-union variant carrying { requested, capacity,
|
|
649
|
+
// ceiling: number }.
|
|
650
|
+
const r: number = err.detail.requested;
|
|
651
|
+
const c: number = err.detail.capacity;
|
|
652
|
+
const ce: number = err.detail.ceiling;
|
|
653
|
+
captured.requested = r;
|
|
654
|
+
captured.capacity = c;
|
|
655
|
+
captured.ceiling = ce;
|
|
656
|
+
captured.handled = true;
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
case 'mesh-ssbo-ceiling-reached': {
|
|
660
|
+
const r: number = err.detail.requested;
|
|
661
|
+
const c: number = err.detail.capacity;
|
|
662
|
+
const ce: number = err.detail.ceiling;
|
|
663
|
+
captured.requested = r;
|
|
664
|
+
captured.capacity = c;
|
|
665
|
+
captured.ceiling = ce;
|
|
666
|
+
captured.handled = true;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
default:
|
|
670
|
+
// Other arms not exercised in this test; leave captured untouched.
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
// Construct the two new error classes via the published surface and
|
|
676
|
+
// pump them through onError to confirm runtime + compile time agree.
|
|
677
|
+
onError(new MeshSsboCapacityExceededError(2048, 1024, 524288));
|
|
678
|
+
expect(captured.handled).toBe(true);
|
|
679
|
+
expect(captured.requested).toBe(2048);
|
|
680
|
+
expect(captured.capacity).toBe(1024);
|
|
681
|
+
expect(captured.ceiling).toBe(524288);
|
|
682
|
+
|
|
683
|
+
captured.handled = false;
|
|
684
|
+
onError(new MeshSsboCeilingReachedError(600000, 524288, 524288));
|
|
685
|
+
expect(captured.handled).toBe(true);
|
|
686
|
+
expect(captured.requested).toBe(600000);
|
|
687
|
+
expect(captured.capacity).toBe(524288);
|
|
688
|
+
expect(captured.ceiling).toBe(524288);
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
{
|
|
695
|
+
// ─── from on-error-fan-out.test.ts ───
|
|
696
|
+
describe('on-error-fan-out.test.ts', () => {
|
|
697
|
+
describe('RhiErrorListenerRegistry — onError fan-out', () => {
|
|
698
|
+
afterEach(() => {
|
|
699
|
+
vi.restoreAllMocks();
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('(a) listeners fire in FIFO insertion order', () => {
|
|
703
|
+
const reg = new RhiErrorListenerRegistry();
|
|
704
|
+
const callOrder: number[] = [];
|
|
705
|
+
reg.add(() => {
|
|
706
|
+
callOrder.push(1);
|
|
707
|
+
});
|
|
708
|
+
reg.add(() => {
|
|
709
|
+
callOrder.push(2);
|
|
710
|
+
});
|
|
711
|
+
reg.add(() => {
|
|
712
|
+
callOrder.push(3);
|
|
713
|
+
});
|
|
714
|
+
reg.fire(new RhiError({ code: 'webgpu-runtime-error', expected: 'e', hint: 'h' }));
|
|
715
|
+
expect(callOrder).toEqual([1, 2, 3]);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
it('(b) a throwing listener does not abort subsequent listeners', () => {
|
|
719
|
+
const reg = new RhiErrorListenerRegistry();
|
|
720
|
+
const after = vi.fn();
|
|
721
|
+
reg.add(() => {
|
|
722
|
+
throw new Error('listener boom');
|
|
723
|
+
});
|
|
724
|
+
reg.add(after);
|
|
725
|
+
expect(() =>
|
|
726
|
+
reg.fire(new RhiError({ code: 'webgpu-runtime-error', expected: 'e', hint: 'h' })),
|
|
727
|
+
).not.toThrow();
|
|
728
|
+
expect(after).toHaveBeenCalledTimes(1);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it('(c) zero-listener fire() falls back to console.error', () => {
|
|
732
|
+
const reg = new RhiErrorListenerRegistry();
|
|
733
|
+
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
734
|
+
reg.fire(
|
|
735
|
+
new RhiError({
|
|
736
|
+
code: 'device-lost',
|
|
737
|
+
expected: 'device intact',
|
|
738
|
+
hint: 'reload page or rebuild renderer',
|
|
739
|
+
}),
|
|
740
|
+
);
|
|
741
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it('(d) a RuntimeError (EquirectProjectionFailedError) fans out without an as-any cast (F-1)', () => {
|
|
745
|
+
const reg = new RhiErrorListenerRegistry();
|
|
746
|
+
const seen: Array<{ code: string; handle: number | undefined }> = [];
|
|
747
|
+
reg.add((e) => {
|
|
748
|
+
// AI-user view: switch (e.code) reaches the RuntimeError arm and narrows
|
|
749
|
+
// to EquirectProjectionFailedError (no cast at the fire() call site below).
|
|
750
|
+
if (e.code === 'equirect-projection-failed') {
|
|
751
|
+
seen.push({ code: e.code, handle: e.detail.handle });
|
|
752
|
+
} else {
|
|
753
|
+
seen.push({ code: e.code, handle: undefined });
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
reg.fire(new EquirectProjectionFailedError(42));
|
|
757
|
+
expect(seen).toEqual([{ code: 'equirect-projection-failed', handle: 42 }]);
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
// feat-20260608-mesh-ssbo-dynamic-grow-l1-lift-1024-entity-cap M4 / T-M4-01:
|
|
761
|
+
// outer catch in render-system.ts recordFrame passes `detail.error` as the
|
|
762
|
+
// caught exception object (preserving .code / .expected / .hint for RhiError).
|
|
763
|
+
// AI users switch on err.code === 'webgpu-runtime-error' then narrow detail
|
|
764
|
+
// via 'error' in e.detail to access e.detail.error.code without `as`.
|
|
765
|
+
it('(e) webgpu-runtime-error detail.error is a structured object (RhiError preserved, narrowing without as on inner code)', () => {
|
|
766
|
+
const reg = new RhiErrorListenerRegistry();
|
|
767
|
+
|
|
768
|
+
// Simulate the outer catch: an underlying RhiError (e.g. 'queue-write-buffer-out-of-bounds')
|
|
769
|
+
// is caught and wrapped into a new RhiError with code='webgpu-runtime-error',
|
|
770
|
+
// whose detail.error preserves the original RhiError object (with .code / .expected / .hint).
|
|
771
|
+
const innerErr = new RhiError({
|
|
772
|
+
code: 'queue-write-buffer-out-of-bounds',
|
|
773
|
+
expected: 'offset + size <= buffer size',
|
|
774
|
+
hint: 'check writeBuffer byte range against buffer size; does growMeshSsbo need to fire?',
|
|
775
|
+
});
|
|
776
|
+
const outerErr = new RhiError({
|
|
777
|
+
code: 'webgpu-runtime-error',
|
|
778
|
+
expected: 'RenderSystem to record one frame without an internal exception',
|
|
779
|
+
hint: 'inspect detail.error for the underlying cause; next frame will retry',
|
|
780
|
+
detail: { error: innerErr },
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
const seen: Array<{ outerCode: string; innerCode: string }> = [];
|
|
784
|
+
reg.add((e) => {
|
|
785
|
+
// AI-user view: switch on e.code narrows to RhiError (vs RuntimeError).
|
|
786
|
+
// Since RhiError is a single class, e.detail is still RhiErrorDetail;
|
|
787
|
+
// use 'error' in guard to narrow to RhiWebgpuRuntimeDetail.
|
|
788
|
+
if (e.code === 'webgpu-runtime-error' && e.detail && 'error' in e.detail) {
|
|
789
|
+
// e.detail.error is RhiError (preserved from outer catch), not string.
|
|
790
|
+
// .code is accessible without `as` — both union branches carry .code.
|
|
791
|
+
seen.push({ outerCode: e.code, innerCode: e.detail.error.code });
|
|
792
|
+
} else {
|
|
793
|
+
seen.push({ outerCode: 'unknown', innerCode: 'unknown' });
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
reg.fire(outerErr);
|
|
797
|
+
expect(seen).toEqual([
|
|
798
|
+
{ outerCode: 'webgpu-runtime-error', innerCode: 'queue-write-buffer-out-of-bounds' },
|
|
799
|
+
]);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('(f) webgpu-runtime-error detail.error fallback for non-RhiError throws (narrowing without as on inner code)', () => {
|
|
803
|
+
const reg = new RhiErrorListenerRegistry();
|
|
804
|
+
|
|
805
|
+
// Simulate a non-RhiError (e.g. plain TypeError) caught by outer catch.
|
|
806
|
+
// detail.error becomes { code: 'unknown', message: '...' } fallback.
|
|
807
|
+
const outerErr = new RhiError({
|
|
808
|
+
code: 'webgpu-runtime-error',
|
|
809
|
+
expected: 'RenderSystem to record one frame without an internal exception',
|
|
810
|
+
hint: 'inspect detail.error for the underlying cause; next frame will retry',
|
|
811
|
+
detail: { error: { code: 'unknown' as const, message: 'plain TypeError boomed' } },
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
const seen: Array<{ outerCode: string; innerCode: string }> = [];
|
|
815
|
+
reg.add((e) => {
|
|
816
|
+
if (e.code === 'webgpu-runtime-error' && e.detail && 'error' in e.detail) {
|
|
817
|
+
// e.detail.error.code is accessible without `as` — both union branches
|
|
818
|
+
// (RhiError | { code: string; message: string }) carry .code.
|
|
819
|
+
seen.push({ outerCode: 'unknown', innerCode: e.detail.error.code });
|
|
820
|
+
} else {
|
|
821
|
+
seen.push({ outerCode: 'unknown', innerCode: 'unknown' });
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
reg.fire(outerErr);
|
|
825
|
+
expect(seen).toEqual([{ outerCode: 'unknown', innerCode: 'unknown' }]);
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
{
|
|
832
|
+
// ─── from post-process-errors.test.ts ───
|
|
833
|
+
describe('post-process-errors.test.ts', () => {
|
|
834
|
+
// ─── PostProcessErrorCode variant scaffold (mirrors the shared registry error model) ────
|
|
835
|
+
//
|
|
836
|
+
// These are the TWO expected union members, matching D-4 / D-9:
|
|
837
|
+
// - 'post-process-already-registered' (programmer error → throw)
|
|
838
|
+
// - 'post-process-not-found' (runtime path → Result.err)
|
|
839
|
+
//
|
|
840
|
+
// Before w12 implements post-process-errors.ts, these inline definitions
|
|
841
|
+
// serve as the compile-time contract: the tests compile, pass, and validate
|
|
842
|
+
// the shape. Once w12 lands, the imports switch to the real module and the
|
|
843
|
+
// tests run against real throw/Result paths after w13.
|
|
844
|
+
|
|
845
|
+
type LegacyPostProcessErrorCode = 'post-process-already-registered' | 'post-process-not-found';
|
|
846
|
+
|
|
847
|
+
describe('feat-20260604 M2 w11: PostProcessErrorCode closed union', () => {
|
|
848
|
+
it('exhaustive switch on PostProcessErrorCode compiles without default', () => {
|
|
849
|
+
// AC-08 / charter P3: the union must be exhaustively switchable without
|
|
850
|
+
// a default branch. This test uses the scaffold type above; after w12,
|
|
851
|
+
// it switches to the real PostProcessErrorCode from post-process-errors.ts.
|
|
852
|
+
const code = 'post-process-already-registered' as LegacyPostProcessErrorCode;
|
|
853
|
+
let matched = false;
|
|
854
|
+
switch (code) {
|
|
855
|
+
case 'post-process-already-registered':
|
|
856
|
+
matched = true;
|
|
857
|
+
break;
|
|
858
|
+
case 'post-process-not-found':
|
|
859
|
+
matched = true;
|
|
860
|
+
break;
|
|
861
|
+
// No default case: TS proves completeness.
|
|
862
|
+
}
|
|
863
|
+
expect(matched).toBe(true);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it('post-process-already-registered has an id detail field', () => {
|
|
867
|
+
// Mirror the retired registry error model PipelinePreviouslyRegisteredDetail.
|
|
868
|
+
// The detail payload for the 'post-process-already-registered' code
|
|
869
|
+
// carries the duplicate id so AI users can self-diagnose.
|
|
870
|
+
const detail: { readonly id: string } = { id: 'fxaa' };
|
|
871
|
+
expect(detail.id).toBe('fxaa');
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it('post-process-not-found has an id detail field', () => {
|
|
875
|
+
// Mirror the retired registry error model PipelineNotFoundDetail but with an
|
|
876
|
+
// id string (not a handle number — post-process lookup is by string id,
|
|
877
|
+
// not by asset handle).
|
|
878
|
+
const detail: { readonly id: string } = { id: 'non-existent' };
|
|
879
|
+
expect(detail.id).toBe('non-existent');
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
it('PostProcessErrorCode member count is exactly 2', () => {
|
|
883
|
+
// AC-19: the union has exactly 2 members. This guarantees future
|
|
884
|
+
// additions stay additively evolvable.
|
|
885
|
+
const members: LegacyPostProcessErrorCode[] = [
|
|
886
|
+
'post-process-already-registered',
|
|
887
|
+
'post-process-not-found',
|
|
888
|
+
];
|
|
889
|
+
expect(members.length).toBe(2);
|
|
890
|
+
// Verify all members are distinct.
|
|
891
|
+
const unique = new Set(members);
|
|
892
|
+
expect(unique.size).toBe(2);
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
describe('dual-channel contract (throw vs Result.err)', () => {
|
|
896
|
+
it('same-id register is programmer error → throw', () => {
|
|
897
|
+
// Mirror the retired registry error model: Map.has -> throw fail-fast for
|
|
898
|
+
// the retired duplicate-registration case. Same semantics for postProcess.register.
|
|
899
|
+
//
|
|
900
|
+
// Test shape: when postProcess.register('fxaa', ...) is called a
|
|
901
|
+
// second time with the same id, a PostProcessError with code
|
|
902
|
+
// 'post-process-already-registered' is THROWN (not returned as Result).
|
|
903
|
+
const registered = new Set<string>();
|
|
904
|
+
const mockRegister = (id: string): void => {
|
|
905
|
+
if (registered.has(id)) {
|
|
906
|
+
throw Object.assign(new Error(`post-process id '${id}' already registered`), {
|
|
907
|
+
code: 'post-process-already-registered' as const,
|
|
908
|
+
detail: { id },
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
registered.add(id);
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
// First call succeeds.
|
|
915
|
+
mockRegister('fxaa');
|
|
916
|
+
expect(registered.has('fxaa')).toBe(true);
|
|
917
|
+
|
|
918
|
+
// Second call with same id throws.
|
|
919
|
+
expect(() => mockRegister('fxaa')).toThrow();
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
it('reference to unregistered id is runtime path → Result.err', () => {
|
|
923
|
+
// Mirror the retired registry error model: a missing reference returns Result.err.
|
|
924
|
+
// The post-process error still carries the structured recovery detail.
|
|
925
|
+
// an unregistered post-process id.
|
|
926
|
+
//
|
|
927
|
+
// The error must carry:
|
|
928
|
+
// - code: 'post-process-not-found'
|
|
929
|
+
// - detail: { id: string }
|
|
930
|
+
// - hint: a string directing the user to postProcess.register
|
|
931
|
+
const registered = new Set<string>();
|
|
932
|
+
const mockLookup = (
|
|
933
|
+
id: string,
|
|
934
|
+
):
|
|
935
|
+
| { ok: true }
|
|
936
|
+
| {
|
|
937
|
+
ok: false;
|
|
938
|
+
error: { code: LegacyPostProcessErrorCode; detail: { id: string }; hint: string };
|
|
939
|
+
} => {
|
|
940
|
+
if (!registered.has(id)) {
|
|
941
|
+
return {
|
|
942
|
+
ok: false,
|
|
943
|
+
error: {
|
|
944
|
+
code: 'post-process-not-found',
|
|
945
|
+
detail: { id },
|
|
946
|
+
hint: `call renderer.postProcess.register('${id}', ...) before referencing it`,
|
|
947
|
+
},
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
return { ok: true };
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
// Reference to unregistered id.
|
|
954
|
+
const result = mockLookup('non-existent');
|
|
955
|
+
expect(result.ok).toBe(false);
|
|
956
|
+
if (!result.ok) {
|
|
957
|
+
expect(result.error.code).toBe('post-process-not-found');
|
|
958
|
+
expect(result.error.detail.id).toBe('non-existent');
|
|
959
|
+
expect(result.error.hint).toContain('postProcess.register');
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// Reference to registered id succeeds.
|
|
963
|
+
registered.add('fxaa');
|
|
964
|
+
const result2 = mockLookup('fxaa');
|
|
965
|
+
expect(result2.ok).toBe(true);
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
describe('error detail narrowing discriminated union', () => {
|
|
970
|
+
it('narrowing on code narrows detail', () => {
|
|
971
|
+
// Charter P3 + P4: discriminated union pattern. After checking
|
|
972
|
+
// `err.code === 'post-process-already-registered'`, TS narrows
|
|
973
|
+
// `err.detail` to the per-code payload without `as` casts.
|
|
974
|
+
//
|
|
975
|
+
// This test validates the narrowing shape. The real discriminated
|
|
976
|
+
// union (PostProcessErrorVariant<C>) is implemented by w12 mirroring
|
|
977
|
+
// The discriminated variant keeps the selected detail correlated.
|
|
978
|
+
|
|
979
|
+
// Simulate a variant for 'post-process-already-registered'.
|
|
980
|
+
const err = {
|
|
981
|
+
code: 'post-process-already-registered' as const,
|
|
982
|
+
detail: { id: 'fxaa' },
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
if (err.code === 'post-process-already-registered') {
|
|
986
|
+
// detail.id is available after narrowing — no `as` cast needed.
|
|
987
|
+
expect(err.detail.id).toBe('fxaa');
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it('post-process-not-found variant narrows to id detail', () => {
|
|
992
|
+
const err = {
|
|
993
|
+
code: 'post-process-not-found' as const,
|
|
994
|
+
detail: { id: 'missing-id' },
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
if (err.code === 'post-process-not-found') {
|
|
998
|
+
expect(err.detail.id).toBe('missing-id');
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
// ─── feat-20260609-learn-render-4-5-framebuffers M1 / T-1 ──────────────────
|
|
1005
|
+
//
|
|
1006
|
+
// After T-2 lands, PostProcessErrorCode grows to a 3-member closed union with
|
|
1007
|
+
// 'fullscreen-input-not-found' joining the existing two members. The first two
|
|
1008
|
+
// tests below pin the closed-set contract against the REAL PostProcessErrorCode
|
|
1009
|
+
// import (not the inline scaffold above) so the union member count + exhaustive
|
|
1010
|
+
// switch + new variant detail shape are all locked. The third test exercises
|
|
1011
|
+
// the runtime throw site in dispatchFullscreenPass: when reads[0] points to a
|
|
1012
|
+
// graph color target that the resolve context cannot resolve (typo / unregistered
|
|
1013
|
+
// colorTarget key), the dispatcher MUST throw a structured PostProcessError with
|
|
1014
|
+
// code 'fullscreen-input-not-found' and detail = { readsKey, passName } (charter
|
|
1015
|
+
// P3 fail-fast + P4 property access).
|
|
1016
|
+
describe('feat-20260609 M1 T-1: PostProcessErrorCode 3-member closed union + dispatchFullscreenPass throw', () => {
|
|
1017
|
+
it('PostProcessErrorCode union includes fullscreen-input-not-found member (T-2 closed-set growth)', () => {
|
|
1018
|
+
type RealCode = typeof PostProcessError extends {
|
|
1019
|
+
new (args: { code: infer C; detail: never }): unknown;
|
|
1020
|
+
}
|
|
1021
|
+
? C
|
|
1022
|
+
: never;
|
|
1023
|
+
// Compile-time: 'fullscreen-input-not-found' must be a literal of the closed
|
|
1024
|
+
// union (T-2 growth). Runtime assertion mirrors the type to keep vitest happy.
|
|
1025
|
+
const member: RealCode = 'fullscreen-input-not-found' as RealCode;
|
|
1026
|
+
expect(member).toBe('fullscreen-input-not-found');
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
it('exhaustive switch over PostProcessErrorCode covers 3 members without default', () => {
|
|
1030
|
+
type Code = PostProcessErrorCode;
|
|
1031
|
+
const classify = (code: Code): string => {
|
|
1032
|
+
switch (code) {
|
|
1033
|
+
case 'post-process-already-registered':
|
|
1034
|
+
return 'register';
|
|
1035
|
+
case 'post-process-not-found':
|
|
1036
|
+
return 'lookup';
|
|
1037
|
+
case 'fullscreen-input-not-found':
|
|
1038
|
+
return 'reads';
|
|
1039
|
+
case 'ssao-radius-non-positive':
|
|
1040
|
+
return 'radius';
|
|
1041
|
+
case 'ssao-bias-negative':
|
|
1042
|
+
return 'bias';
|
|
1043
|
+
case 'params-size-mismatch':
|
|
1044
|
+
return 'params';
|
|
1045
|
+
case 'params-update-size-mismatch':
|
|
1046
|
+
return 'params-update';
|
|
1047
|
+
default: {
|
|
1048
|
+
const exhaustive: never = code;
|
|
1049
|
+
return exhaustive;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
expect(classify('post-process-already-registered')).toBe('register');
|
|
1054
|
+
expect(classify('post-process-not-found')).toBe('lookup');
|
|
1055
|
+
expect(classify('fullscreen-input-not-found')).toBe('reads');
|
|
1056
|
+
expect(typeof PostProcessError).toBe('function');
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it('fullscreen-input-not-found variant detail = { readsKey, passName } (T-2 detail shape)', () => {
|
|
1060
|
+
const err = new PostProcessError({
|
|
1061
|
+
code: 'fullscreen-input-not-found',
|
|
1062
|
+
detail: { readsKey: 'offscreenColor', passName: 'pp' },
|
|
1063
|
+
});
|
|
1064
|
+
expect(err.code).toBe('fullscreen-input-not-found');
|
|
1065
|
+
if (err.code === 'fullscreen-input-not-found') {
|
|
1066
|
+
// Property access narrows to FullscreenInputNotFoundDetail without cast.
|
|
1067
|
+
const detail: import('../../../render/src/post-process-errors').FullscreenInputNotFoundDetail =
|
|
1068
|
+
err.detail;
|
|
1069
|
+
expect(detail.readsKey).toBe('offscreenColor');
|
|
1070
|
+
expect(detail.passName).toBe('pp');
|
|
1071
|
+
}
|
|
1072
|
+
expect(typeof err.expected).toBe('string');
|
|
1073
|
+
expect(err.expected.length).toBeGreaterThan(0);
|
|
1074
|
+
expect(err.hint).toContain('offscreenColor');
|
|
1075
|
+
expect(err.hint).toContain('pp');
|
|
1076
|
+
});
|
|
1077
|
+
});
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
{
|
|
1082
|
+
// ─── from render-skylight-warn.test.ts ───
|
|
1083
|
+
describe('render-skylight-warn.test.ts', () => {
|
|
1084
|
+
describe('0-light three-condition conjunction (AC-10) -- feat-20260520-skylight-ibl-cubemap M4 / t23', () => {
|
|
1085
|
+
it('(a) three-condition conjunction: all true -> warn fires', () => {
|
|
1086
|
+
// Conditions: no Skylight (true) AND 0 direct light (true) AND
|
|
1087
|
+
// StandardMaterial (true) -> all three true -> warn MUST fire.
|
|
1088
|
+
//
|
|
1089
|
+
// When t27 wires the 3-condition check in render-system-record.ts,
|
|
1090
|
+
// this contract assertion becomes:
|
|
1091
|
+
// expect(console.warn).toHaveBeenCalled()
|
|
1092
|
+
const allThreeTrue = true;
|
|
1093
|
+
expect(allThreeTrue).toBe(true);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
it('(b) Skylight present + 0 direct light + StandardMaterial -> NO warn', () => {
|
|
1097
|
+
// Conditions: no Skylight (FALSE -- Skylight present) AND
|
|
1098
|
+
// 0 direct light (true) AND StandardMaterial (true).
|
|
1099
|
+
// Since condition 1 is false, the 3-condition conjunction is false.
|
|
1100
|
+
// Skylight is a legitimate light source; ambient IBL renders normally.
|
|
1101
|
+
//
|
|
1102
|
+
// When t27 wires the check, this becomes:
|
|
1103
|
+
// expect(console.warn).not.toHaveBeenCalled()
|
|
1104
|
+
const skylightSuppressesWarn = true;
|
|
1105
|
+
expect(skylightSuppressesWarn).toBe(true);
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
it('(c) multiple Skylight entities -> warn in dev AND prod', () => {
|
|
1109
|
+
// F-4 nit: >1 Skylight entity fires console.warn with message
|
|
1110
|
+
// "multiple skylight entities found, using first" in both dev
|
|
1111
|
+
// and prod environments. The first Skylight (by archetype order) wins.
|
|
1112
|
+
//
|
|
1113
|
+
// When t27 wires the check in render-system-record.ts, this becomes:
|
|
1114
|
+
// expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('multiple skylight'))
|
|
1115
|
+
const multiWarnBothEnvs = true;
|
|
1116
|
+
expect(multiWarnBothEnvs).toBe(true);
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
it('(d) Skylight intensity=0 -> ambient=0, NO warn', () => {
|
|
1120
|
+
// intensity=0 is mathematically valid: ambient term = 0.
|
|
1121
|
+
// Conditions: no Skylight (FALSE -- Skylight present with intensity=0)
|
|
1122
|
+
// AND 0 direct light (true) AND StandardMaterial (true).
|
|
1123
|
+
// 3-condition conjunction false (Skylight exists even if intensity=0);
|
|
1124
|
+
// no warn emitted. Ambient term computes to 0 via shader math.
|
|
1125
|
+
//
|
|
1126
|
+
// When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
|
|
1127
|
+
const zeroIntensityNoWarn = true;
|
|
1128
|
+
expect(zeroIntensityNoWarn).toBe(true);
|
|
1129
|
+
});
|
|
1130
|
+
|
|
1131
|
+
it('(e) Skylight + direct light + StandardMaterial -> NO zero-light warn', () => {
|
|
1132
|
+
// Both Skylight and direct light present: zero-light condition never
|
|
1133
|
+
// activates. 3-condition conjunction false because direct light present.
|
|
1134
|
+
//
|
|
1135
|
+
// When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
|
|
1136
|
+
const bothLightsNoWarn = true;
|
|
1137
|
+
expect(bothLightsNoWarn).toBe(true);
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
it('(f) no Skylight + 0 direct light + UnlitMaterial -> NO warn', () => {
|
|
1141
|
+
// 3-condition conjunction: StandardMaterial condition is FALSE (unlit).
|
|
1142
|
+
// Unlit materials don't consume PBR IBL ambient.
|
|
1143
|
+
//
|
|
1144
|
+
// When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
|
|
1145
|
+
const unlitNoWarn = true;
|
|
1146
|
+
expect(unlitNoWarn).toBe(true);
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
it('(g) no Skylight + direct light present + StandardMaterial -> NO warn', () => {
|
|
1150
|
+
// 3-condition conjunction: 0-direct-light condition is FALSE.
|
|
1151
|
+
// Direct light(s) render normally; no zero-light warn.
|
|
1152
|
+
//
|
|
1153
|
+
// When t27 wires this, becomes expect(console.warn).not.toHaveBeenCalled()
|
|
1154
|
+
const directLightNoWarn = true;
|
|
1155
|
+
expect(directLightNoWarn).toBe(true);
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
{
|
|
1162
|
+
// ─── from errors.unit.test.ts (M1 new error codes) ───
|
|
1163
|
+
describe('errors.unit.test.ts', () => {
|
|
1164
|
+
describe('SceneCollectEntityRefOutOfClosureError — AC-11', () => {
|
|
1165
|
+
it('has readonly code / expected / hint / detail fields with correct types', () => {
|
|
1166
|
+
const err = new SceneCollectEntityRefOutOfClosureError(42, 'parent', 99);
|
|
1167
|
+
expect(err.code).toBe('scene-collect-entity-ref-out-of-closure');
|
|
1168
|
+
expect(typeof err.expected).toBe('string');
|
|
1169
|
+
expect(err.expected).toContain('42');
|
|
1170
|
+
expect(typeof err.hint).toBe('string');
|
|
1171
|
+
expect(err.hint).toBe(
|
|
1172
|
+
'Expand roots to include the target entity, or remove the reference.',
|
|
1173
|
+
);
|
|
1174
|
+
expect(err.detail).toEqual({ entity: 42, field: 'parent', target: 99 });
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
it('code is a valid RuntimeErrorCode literal', () => {
|
|
1178
|
+
const code: RuntimeLayerErrorCode = 'scene-collect-entity-ref-out-of-closure';
|
|
1179
|
+
expect(code).toBe('scene-collect-entity-ref-out-of-closure');
|
|
1180
|
+
});
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
describe('SceneCollectAssetGuidUnresolvedError — AC-12', () => {
|
|
1184
|
+
it('has readonly code / expected / hint / detail fields with correct types', () => {
|
|
1185
|
+
const err = new SceneCollectAssetGuidUnresolvedError('skeleton', 7);
|
|
1186
|
+
expect(err.code).toBe('scene-collect-asset-guid-unresolved');
|
|
1187
|
+
expect(typeof err.expected).toBe('string');
|
|
1188
|
+
expect(err.expected).toContain('skeleton');
|
|
1189
|
+
expect(typeof err.hint).toBe('string');
|
|
1190
|
+
expect(err.hint).toBe(
|
|
1191
|
+
'source SceneAsset is not catalogued: call registry.catalog(guid, payload) first, ' +
|
|
1192
|
+
'or load through loadByGuid() which auto-catalogs GUID-scoped assets',
|
|
1193
|
+
);
|
|
1194
|
+
expect(err.detail).toEqual({ field: 'skeleton', handle: 7 });
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it('code is a valid RuntimeErrorCode literal', () => {
|
|
1198
|
+
const code: RuntimeLayerErrorCode = 'scene-collect-asset-guid-unresolved';
|
|
1199
|
+
expect(code).toBe('scene-collect-asset-guid-unresolved');
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
});
|
|
1203
|
+
}
|