@forgeax/engine-runtime 0.1.4 → 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
|
@@ -1,1075 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// w19 — RecoverErrorCode exhaustiveness grep gate + switch (A-AC-09)
|
|
10
|
-
//
|
|
11
|
-
// recover() reuses tryCreateWebGPURenderer / gpuStore.destroyAll /
|
|
12
|
-
// ensureContextConfigured (plan-strategy D-1). A2/T1 precedent: the device
|
|
13
|
-
// factory is injectable via the mocked RHI backend pack so the rebuild path
|
|
14
|
-
// runs without a real GPU (A-OOS-7 honest downgrade; real wgpu device-lost
|
|
15
|
-
// end-to-end stays a local-only gate).
|
|
16
|
-
|
|
17
|
-
import { readFileSync } from 'node:fs';
|
|
18
|
-
import { fileURLToPath } from 'node:url';
|
|
19
|
-
import type { RenderError, RenderFeature } from '@forgeax/engine-render';
|
|
20
|
-
import type { HealthSnapshot } from '@forgeax/engine-render/internal';
|
|
21
|
-
import { RecoverError, type RecoverErrorCode } from '@forgeax/engine-render/internal';
|
|
22
|
-
import type { Result, RhiInstance } from '@forgeax/engine-rhi';
|
|
23
|
-
import { ok, RenderQueue } from '@forgeax/engine-types';
|
|
24
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
25
|
-
import type { SubmissionPlan } from '../../../render/src/gpu-driven/batch-topology';
|
|
26
|
-
import { executeCpuMultiViewVisibility } from '../../../render/src/gpu-driven/view-gpu';
|
|
27
|
-
import { classifyGpuDrivenView } from '../../../render/src/gpu-driven/view-reference';
|
|
28
|
-
import { GpuScene } from '../../../render/src/gpu-scene';
|
|
29
|
-
import {
|
|
30
|
-
RenderSceneProjection,
|
|
31
|
-
type RenderSceneProjectionSlot,
|
|
32
|
-
} from '../../../render/src/render-scene-projection';
|
|
33
|
-
|
|
34
|
-
// ── Controllable mock state (module-level so vi.mock factories can read) ──────
|
|
35
|
-
//
|
|
36
|
-
// recover() re-invokes pack.rhi.requestAdapter -> adapter.requestDevice. These
|
|
37
|
-
// flags let a single test flip the next acquisition to fail on the adapter or
|
|
38
|
-
// device step, exercising the two add-only RecoverErrorCode members.
|
|
39
|
-
|
|
40
|
-
const recoverMock = {
|
|
41
|
-
adapterReturnsNull: false,
|
|
42
|
-
deviceThrows: false,
|
|
43
|
-
deviceRefusalRemaining: 0,
|
|
44
|
-
requestAdapterCalls: 0,
|
|
45
|
-
requestDeviceCalls: 0,
|
|
46
|
-
requestDeviceOptions: [] as unknown[],
|
|
47
|
-
shaderModuleLabels: [] as string[],
|
|
48
|
-
deviceRequestGate: undefined as Promise<void> | undefined,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
function resetRecoverMock(): void {
|
|
52
|
-
recoverMock.adapterReturnsNull = false;
|
|
53
|
-
recoverMock.deviceThrows = false;
|
|
54
|
-
recoverMock.deviceRefusalRemaining = 0;
|
|
55
|
-
recoverMock.requestAdapterCalls = 0;
|
|
56
|
-
recoverMock.requestDeviceCalls = 0;
|
|
57
|
-
recoverMock.requestDeviceOptions = [];
|
|
58
|
-
recoverMock.shaderModuleLabels = [];
|
|
59
|
-
recoverMock.deviceRequestGate = undefined;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let testDeviceLostResolve: ((info: unknown) => void) | null = null;
|
|
63
|
-
const testDeviceLostResolvers: Array<(info: unknown) => void> = [];
|
|
64
|
-
|
|
65
|
-
function makeFakeRhiDevice(): Record<string, unknown> {
|
|
66
|
-
let resolveLost!: (info: unknown) => void;
|
|
67
|
-
const lost = new Promise<unknown>((res) => {
|
|
68
|
-
resolveLost = res;
|
|
69
|
-
});
|
|
70
|
-
testDeviceLostResolvers.push(resolveLost);
|
|
71
|
-
testDeviceLostResolve = resolveLost;
|
|
72
|
-
return {
|
|
73
|
-
__brand: 'RhiDevice',
|
|
74
|
-
lost,
|
|
75
|
-
features: new Set<string>(),
|
|
76
|
-
limits: {} as Record<string, number>,
|
|
77
|
-
caps: {},
|
|
78
|
-
queue: {
|
|
79
|
-
submit: () => ({ ok: true, value: undefined }),
|
|
80
|
-
writeBuffer: () => ({ ok: true, value: undefined }),
|
|
81
|
-
writeTexture: () => ({ ok: true, value: undefined }),
|
|
82
|
-
onSubmittedWorkDone: async () => undefined,
|
|
83
|
-
},
|
|
84
|
-
createBuffer: () => ({ ok: true, value: { __brand: 'Buffer' } }),
|
|
85
|
-
createTexture: () => ({ ok: true, value: { __brand: 'Texture' } }),
|
|
86
|
-
destroyTexture: () => ({ ok: true, value: undefined }),
|
|
87
|
-
destroyBuffer: () => ({ ok: true, value: undefined }),
|
|
88
|
-
createBindGroupLayout: () => ({ ok: true, value: { __brand: 'BindGroupLayout' } }),
|
|
89
|
-
createBindGroup: () => ({ ok: true, value: { __brand: 'BindGroup' } }),
|
|
90
|
-
createPipelineLayout: () => ({ ok: true, value: { __brand: 'PipelineLayout' } }),
|
|
91
|
-
createRenderPipeline: () => ({
|
|
92
|
-
ok: true,
|
|
93
|
-
value: {
|
|
94
|
-
__brand: 'RenderPipeline',
|
|
95
|
-
getBindGroupLayout: () => ({ __brand: 'BindGroupLayout' }),
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
createComputePipeline: () => ({
|
|
99
|
-
ok: true,
|
|
100
|
-
value: {
|
|
101
|
-
__brand: 'ComputePipeline',
|
|
102
|
-
getBindGroupLayout: () => ({ __brand: 'BindGroupLayout' }),
|
|
103
|
-
},
|
|
104
|
-
}),
|
|
105
|
-
createSampler: () => ({ ok: true, value: { __brand: 'Sampler' } }),
|
|
106
|
-
createShaderModule: () => ({
|
|
107
|
-
ok: true,
|
|
108
|
-
value: { __brand: 'ShaderModule', getCompilationInfo: async () => ({ messages: [] }) },
|
|
109
|
-
}),
|
|
110
|
-
createTextureView: () => ({ ok: true, value: { __brand: 'TextureView' } }),
|
|
111
|
-
createCommandEncoder: () => ({
|
|
112
|
-
ok: true,
|
|
113
|
-
value: {
|
|
114
|
-
beginRenderPass: () => ({
|
|
115
|
-
setPipeline: () => undefined,
|
|
116
|
-
setBindGroup: () => undefined,
|
|
117
|
-
setVertexBuffer: () => undefined,
|
|
118
|
-
setIndexBuffer: () => undefined,
|
|
119
|
-
setViewport: () => undefined,
|
|
120
|
-
setScissorRect: () => undefined,
|
|
121
|
-
draw: () => undefined,
|
|
122
|
-
drawIndexed: () => undefined,
|
|
123
|
-
end: () => undefined,
|
|
124
|
-
}),
|
|
125
|
-
beginComputePass: () => ({
|
|
126
|
-
setPipeline: () => undefined,
|
|
127
|
-
setBindGroup: () => undefined,
|
|
128
|
-
dispatchWorkgroups: () => undefined,
|
|
129
|
-
end: () => undefined,
|
|
130
|
-
}),
|
|
131
|
-
copyTextureToTexture: () => undefined,
|
|
132
|
-
copyBufferToBuffer: () => undefined,
|
|
133
|
-
finish: () => ({ ok: true, value: { __brand: 'CommandBuffer' } }),
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function makeExplicitRhi(): RhiInstance {
|
|
140
|
-
const adapter = {
|
|
141
|
-
features: new Set<string>(['texture-compression-bc']),
|
|
142
|
-
limits: {},
|
|
143
|
-
requestDevice: async (options?: unknown) => {
|
|
144
|
-
recoverMock.requestDeviceCalls += 1;
|
|
145
|
-
recoverMock.requestDeviceOptions.push(options);
|
|
146
|
-
await recoverMock.deviceRequestGate;
|
|
147
|
-
if (recoverMock.deviceRefusalRemaining > 0) {
|
|
148
|
-
recoverMock.deviceRefusalRemaining -= 1;
|
|
149
|
-
throw new Error('mock transient requestDevice refusal (M40 red reproducer)');
|
|
150
|
-
}
|
|
151
|
-
if (recoverMock.deviceThrows) {
|
|
152
|
-
throw new Error('mock requestDevice failure (recover-device-unavailable path)');
|
|
153
|
-
}
|
|
154
|
-
return { ok: true, value: makeFakeRhiDevice() };
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
return {
|
|
158
|
-
requestAdapter: async () => {
|
|
159
|
-
recoverMock.requestAdapterCalls += 1;
|
|
160
|
-
if (recoverMock.adapterReturnsNull) {
|
|
161
|
-
return {
|
|
162
|
-
ok: false,
|
|
163
|
-
error: { code: 'adapter-unavailable', message: 'mock adapter null' },
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return { ok: true, value: adapter };
|
|
167
|
-
},
|
|
168
|
-
acquireCanvasContext: () => ({
|
|
169
|
-
ok: true,
|
|
170
|
-
value: {
|
|
171
|
-
configure: () => ({ ok: true, value: undefined }),
|
|
172
|
-
unconfigure: () => ({ ok: true, value: undefined }),
|
|
173
|
-
getCurrentTexture: () => ({ ok: true, value: { __brand: 'TextureView' } }),
|
|
174
|
-
},
|
|
175
|
-
}),
|
|
176
|
-
createShaderModule: async (_device: unknown, descriptor?: { readonly label?: string }) => {
|
|
177
|
-
if (descriptor?.label !== undefined) recoverMock.shaderModuleLabels.push(descriptor.label);
|
|
178
|
-
return { ok: true, value: { __brand: 'ShaderModule' } };
|
|
179
|
-
},
|
|
180
|
-
} as unknown as RhiInstance;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function makeMockCanvas(): HTMLCanvasElement {
|
|
184
|
-
return {
|
|
185
|
-
width: 800,
|
|
186
|
-
height: 600,
|
|
187
|
-
getContext(kind: string): unknown {
|
|
188
|
-
if (kind === 'webgpu') {
|
|
189
|
-
return {
|
|
190
|
-
__mockTag: 'webgpu-canvas-context',
|
|
191
|
-
configure: () => undefined,
|
|
192
|
-
unconfigure: () => undefined,
|
|
193
|
-
getCurrentTexture: () => ({ createView: () => ({}) }),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
if (kind === 'webgl2') {
|
|
197
|
-
return {
|
|
198
|
-
__mockTag: 'webgl2',
|
|
199
|
-
getExtension: () => null,
|
|
200
|
-
getParameter: () => 1,
|
|
201
|
-
isContextLost: () => false,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
return null;
|
|
205
|
-
},
|
|
206
|
-
addEventListener: () => undefined,
|
|
207
|
-
removeEventListener: () => undefined,
|
|
208
|
-
} as Partial<HTMLCanvasElement> as HTMLCanvasElement;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
vi.mock('@forgeax/engine-rhi-webgpu', async () => {
|
|
212
|
-
const actualRhi =
|
|
213
|
-
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
214
|
-
const fakeAdapter = {
|
|
215
|
-
features: new Set<string>(),
|
|
216
|
-
limits: {} as Readonly<Record<string, number>>,
|
|
217
|
-
async requestDevice() {
|
|
218
|
-
recoverMock.requestDeviceCalls += 1;
|
|
219
|
-
if (recoverMock.deviceThrows) {
|
|
220
|
-
throw new Error('mock requestDevice failure (recover-device-unavailable path)');
|
|
221
|
-
}
|
|
222
|
-
return actualRhi.ok(makeFakeRhiDevice());
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
return {
|
|
226
|
-
rhi: {
|
|
227
|
-
requestAdapter: async () => {
|
|
228
|
-
recoverMock.requestAdapterCalls += 1;
|
|
229
|
-
if (recoverMock.adapterReturnsNull) {
|
|
230
|
-
return actualRhi.err({
|
|
231
|
-
code: 'adapter-unavailable',
|
|
232
|
-
message: 'mock adapter null (recover-adapter-unavailable path)',
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return actualRhi.ok(fakeAdapter);
|
|
236
|
-
},
|
|
237
|
-
acquireCanvasContext: (_canvas: unknown) =>
|
|
238
|
-
actualRhi.ok({
|
|
239
|
-
configure: () => actualRhi.ok(undefined),
|
|
240
|
-
unconfigure: () => actualRhi.ok(undefined),
|
|
241
|
-
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
242
|
-
}),
|
|
243
|
-
},
|
|
244
|
-
createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
vi.mock('@forgeax/engine-rhi-wgpu', async () => {
|
|
249
|
-
const actualRhi =
|
|
250
|
-
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
251
|
-
const fakeAdapter = {
|
|
252
|
-
features: new Set<string>(),
|
|
253
|
-
limits: {} as Readonly<Record<string, number>>,
|
|
254
|
-
async requestDevice() {
|
|
255
|
-
recoverMock.requestDeviceCalls += 1;
|
|
256
|
-
if (recoverMock.deviceThrows) {
|
|
257
|
-
throw new Error('mock requestDevice failure (recover-device-unavailable path)');
|
|
258
|
-
}
|
|
259
|
-
return actualRhi.ok(makeFakeRhiDevice());
|
|
260
|
-
},
|
|
261
|
-
};
|
|
262
|
-
return {
|
|
263
|
-
rhi: {
|
|
264
|
-
requestAdapter: async () => {
|
|
265
|
-
recoverMock.requestAdapterCalls += 1;
|
|
266
|
-
if (recoverMock.adapterReturnsNull) {
|
|
267
|
-
return actualRhi.err({
|
|
268
|
-
code: 'adapter-unavailable',
|
|
269
|
-
message: 'mock adapter null (recover-adapter-unavailable path)',
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
return actualRhi.ok(fakeAdapter);
|
|
273
|
-
},
|
|
274
|
-
acquireCanvasContext: (_canvas: unknown) =>
|
|
275
|
-
actualRhi.ok({
|
|
276
|
-
configure: () => actualRhi.ok(undefined),
|
|
277
|
-
unconfigure: () => actualRhi.ok(undefined),
|
|
278
|
-
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
279
|
-
}),
|
|
280
|
-
},
|
|
281
|
-
ensureReady: async () => undefined,
|
|
282
|
-
};
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
function makeStubGPU() {
|
|
286
|
-
return {
|
|
287
|
-
requestAdapter: async () => ({
|
|
288
|
-
features: new Set<string>(),
|
|
289
|
-
limits: {},
|
|
290
|
-
requestDevice: async () => makeFakeRhiDevice(),
|
|
291
|
-
}),
|
|
292
|
-
getPreferredCanvasFormat: () => 'bgra8unorm',
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Shader manifest as a data URL (no HTTP). Carries the engine SSOT triple
|
|
297
|
-
// (pbr/unlit/tonemap content markers) buildReadyWebGPU requires, mirroring the
|
|
298
|
-
// production manifest shape — bind-group-cache-binding.test.ts:163. The recover()
|
|
299
|
-
// rebuild re-runs buildReadyWebGPU against an already-populated ShaderRegistry,
|
|
300
|
-
// so the manifest must satisfy the triple check on the rebuild path (a single-
|
|
301
|
-
// entry manifest only passes the boot path because the registry is still empty
|
|
302
|
-
// when boot's buildReadyWebGPU walks it).
|
|
303
|
-
const RECOVER_TEST_MANIFEST_URL = (() => {
|
|
304
|
-
const materialShaderStub = (identifier: string) => ({
|
|
305
|
-
identifier,
|
|
306
|
-
sourcePath: `${identifier}.wgsl`,
|
|
307
|
-
composedWgsl: '/* stub */',
|
|
308
|
-
paramSchema: '[]',
|
|
309
|
-
variants: [],
|
|
310
|
-
});
|
|
311
|
-
const manifest = {
|
|
312
|
-
schemaVersion: '1.0.0',
|
|
313
|
-
entries: [
|
|
314
|
-
{ hash: 'pbr00000', wgsl: '/* pbr stub - calls f_schlick( */', glsl: '', bindings: '' },
|
|
315
|
-
{ hash: 'unlit000', wgsl: '/* unlit stub */', glsl: '', bindings: '' },
|
|
316
|
-
{
|
|
317
|
-
hash: 'tonemap0',
|
|
318
|
-
wgsl: '/* tonemap stub - struct TonemapParams { exposure: f32 }; */',
|
|
319
|
-
glsl: '',
|
|
320
|
-
bindings: '',
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
materialShaders: [
|
|
324
|
-
materialShaderStub('forgeax::default-standard-pbr'),
|
|
325
|
-
materialShaderStub('forgeax::default-unlit'),
|
|
326
|
-
materialShaderStub('forgeax::late-recovery-test'),
|
|
327
|
-
],
|
|
328
|
-
};
|
|
329
|
-
return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
|
|
330
|
-
})();
|
|
331
|
-
|
|
332
|
-
type TestRenderer = {
|
|
333
|
-
device: object;
|
|
334
|
-
recover: () => Promise<Result<void, RecoverError>>;
|
|
335
|
-
health: () => HealthSnapshot;
|
|
336
|
-
store: { destroyAll: () => void };
|
|
337
|
-
assets: { inspect?: () => unknown };
|
|
338
|
-
onHealthChange: (listener: (snapshot: HealthSnapshot) => void) => () => void;
|
|
339
|
-
ready: Promise<unknown>;
|
|
340
|
-
installRenderFeature: (feature: RenderFeature<unknown>) => Promise<Result<void, RenderError>>;
|
|
341
|
-
postProcess: {
|
|
342
|
-
register(id: string, entry: { source: string }): () => void;
|
|
343
|
-
};
|
|
344
|
-
dispose: () => void;
|
|
345
|
-
};
|
|
346
|
-
|
|
347
|
-
async function makeRenderer(): Promise<TestRenderer> {
|
|
348
|
-
const canvas = makeMockCanvas();
|
|
349
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
350
|
-
const renderer = await createRenderer(
|
|
351
|
-
canvas,
|
|
352
|
-
{ rhi: makeExplicitRhi() },
|
|
353
|
-
{ shaderManifestUrl: RECOVER_TEST_MANIFEST_URL },
|
|
354
|
-
);
|
|
355
|
-
return renderer as unknown as TestRenderer;
|
|
1
|
+
import { World } from '@forgeax/engine-ecs';
|
|
2
|
+
import type { FrameReceipt } from '@forgeax/engine-render';
|
|
3
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import { requireRenderer } from './renderer-test-utils';
|
|
6
|
+
|
|
7
|
+
function canvas(): HTMLCanvasElement {
|
|
8
|
+
return { width: 64, height: 64, getContext: () => null } as unknown as HTMLCanvasElement;
|
|
356
9
|
}
|
|
357
10
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Each rebuild test boots a full renderer (createRenderer + ready) and then
|
|
372
|
-
// runs recover()'s second full assembly. Two heavy createRenderer boots in one
|
|
373
|
-
// test occasionally exceed vitest's 5 s default under max-parallel suite load
|
|
374
|
-
// (same cold-boot contention the dawn project mitigates with a raised budget);
|
|
375
|
-
// a per-test 30 s budget removes the flake without affecting the stable path.
|
|
376
|
-
const RECOVER_BOOT_TIMEOUT_MS = 30000;
|
|
377
|
-
|
|
378
|
-
describe('recover() single idempotent rebuild (M3)', () => {
|
|
379
|
-
beforeEach(() => {
|
|
380
|
-
resetRecoverMock();
|
|
381
|
-
testDeviceLostResolve = null;
|
|
382
|
-
testDeviceLostResolvers.length = 0;
|
|
383
|
-
vi.stubGlobal('navigator', { ...navigator, gpu: makeStubGPU() });
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
afterEach(() => {
|
|
387
|
-
vi.unstubAllGlobals();
|
|
388
|
-
testDeviceLostResolve = null;
|
|
389
|
-
testDeviceLostResolvers.length = 0;
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// ── w10: injectable factory success rebuild (A-AC-06) ──────────────────────
|
|
393
|
-
|
|
394
|
-
it(
|
|
395
|
-
'w10: device-lost -> recover() rebuilds via injected factory -> ok + alive',
|
|
396
|
-
async () => {
|
|
397
|
-
const renderer = await makeRenderer();
|
|
398
|
-
await renderer.ready;
|
|
399
|
-
const destroyAllSpy = vi.spyOn(renderer.store, 'destroyAll');
|
|
400
|
-
|
|
401
|
-
await driveDeviceLost(renderer);
|
|
402
|
-
expect(renderer.health().reason).toBe('device-lost');
|
|
403
|
-
|
|
404
|
-
const adapterCallsBefore = recoverMock.requestAdapterCalls;
|
|
405
|
-
const deviceCallsBefore = recoverMock.requestDeviceCalls;
|
|
406
|
-
|
|
407
|
-
const result = await renderer.recover();
|
|
408
|
-
|
|
409
|
-
expect(result.ok).toBe(true);
|
|
410
|
-
expect(renderer.health().reason).toBe('alive');
|
|
411
|
-
// destroyAll cleared GPU resources before rebuild.
|
|
412
|
-
expect(destroyAllSpy).toHaveBeenCalled();
|
|
413
|
-
// tryCreateWebGPURenderer re-invoked requestAdapter + requestDevice.
|
|
414
|
-
expect(recoverMock.requestAdapterCalls).toBeGreaterThan(adapterCallsBefore);
|
|
415
|
-
expect(recoverMock.requestDeviceCalls).toBeGreaterThan(deviceCallsBefore);
|
|
416
|
-
// pipeline rebuild completes; subsequent ready resolves ok.
|
|
417
|
-
await renderer.ready;
|
|
418
|
-
},
|
|
419
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
it(
|
|
423
|
-
'fences a late loss notification from the replaced device after recovery',
|
|
424
|
-
async () => {
|
|
425
|
-
const renderer = await makeRenderer();
|
|
426
|
-
await renderer.ready;
|
|
427
|
-
const staleDeviceLostResolve = testDeviceLostResolvers[0];
|
|
428
|
-
if (staleDeviceLostResolve === undefined) throw new Error('initial device resolver missing');
|
|
429
|
-
|
|
430
|
-
await driveDeviceLost(renderer);
|
|
431
|
-
expect((await renderer.recover()).ok).toBe(true);
|
|
432
|
-
expect(renderer.health().reason).toBe('alive');
|
|
433
|
-
|
|
434
|
-
staleDeviceLostResolve({ reason: 'unknown', message: 'late loss from replaced device' });
|
|
435
|
-
await Promise.resolve();
|
|
436
|
-
await Promise.resolve();
|
|
437
|
-
|
|
438
|
-
expect(renderer.health().reason).toBe('alive');
|
|
439
|
-
renderer.dispose();
|
|
440
|
-
},
|
|
441
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
it(
|
|
445
|
-
'keeps unregister authoritative while recovery is acquiring the replacement device',
|
|
446
|
-
async () => {
|
|
447
|
-
const renderer = await makeRenderer();
|
|
448
|
-
await renderer.ready;
|
|
449
|
-
const unregister = renderer.postProcess.register('test::recover-race', {
|
|
450
|
-
source: 'fn main() {}',
|
|
451
|
-
});
|
|
452
|
-
await driveDeviceLost(renderer);
|
|
453
|
-
|
|
454
|
-
let releaseDevice!: () => void;
|
|
455
|
-
recoverMock.deviceRequestGate = new Promise<void>((resolve) => {
|
|
456
|
-
releaseDevice = resolve;
|
|
457
|
-
});
|
|
458
|
-
const recovering = renderer.recover();
|
|
459
|
-
await vi.waitFor(() => expect(recoverMock.requestDeviceCalls).toBeGreaterThan(1));
|
|
460
|
-
unregister();
|
|
461
|
-
releaseDevice();
|
|
462
|
-
|
|
463
|
-
expect((await recovering).ok).toBe(true);
|
|
464
|
-
const unregisterReplacement = renderer.postProcess.register('test::recover-race', {
|
|
465
|
-
source: 'fn main() {}',
|
|
466
|
-
});
|
|
467
|
-
unregisterReplacement();
|
|
468
|
-
renderer.dispose();
|
|
469
|
-
},
|
|
470
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
it(
|
|
474
|
-
'recovery preserves adapter compression features when requesting the replacement device',
|
|
475
|
-
async () => {
|
|
476
|
-
const renderer = await makeRenderer();
|
|
477
|
-
await renderer.ready;
|
|
478
|
-
await driveDeviceLost(renderer);
|
|
479
|
-
|
|
480
|
-
const result = await renderer.recover();
|
|
481
|
-
expect(result.ok).toBe(true);
|
|
482
|
-
expect(recoverMock.requestDeviceOptions.length).toBeGreaterThanOrEqual(2);
|
|
483
|
-
for (const options of recoverMock.requestDeviceOptions) {
|
|
484
|
-
expect(options).toEqual({ requiredFeatures: ['texture-compression-bc'] });
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
488
|
-
);
|
|
489
|
-
|
|
490
|
-
it(
|
|
491
|
-
'recovery prewarms shader modules for a feature installed after boot',
|
|
492
|
-
async () => {
|
|
493
|
-
const renderer = await makeRenderer();
|
|
494
|
-
await renderer.ready;
|
|
495
|
-
const feature: RenderFeature<unknown> = {
|
|
496
|
-
identity: 'synthetic.late-recovery-shader',
|
|
497
|
-
requiredMaterialShaders: ['forgeax::late-recovery-test'],
|
|
498
|
-
extract: () => ok({}),
|
|
499
|
-
prepare: () => ok(undefined),
|
|
500
|
-
contribute: () => ok(undefined),
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
const installed = await renderer.installRenderFeature(feature);
|
|
504
|
-
expect(installed.ok).toBe(true);
|
|
505
|
-
const installCount = recoverMock.shaderModuleLabels.filter(
|
|
506
|
-
(label) => label === 'module-forgeax::late-recovery-test',
|
|
507
|
-
).length;
|
|
508
|
-
expect(installCount).toBe(1);
|
|
509
|
-
|
|
510
|
-
await driveDeviceLost(renderer);
|
|
511
|
-
expect((await renderer.recover()).ok).toBe(true);
|
|
512
|
-
const recoveryCount = recoverMock.shaderModuleLabels.filter(
|
|
513
|
-
(label) => label === 'module-forgeax::late-recovery-test',
|
|
514
|
-
).length;
|
|
515
|
-
expect(recoveryCount).toBeGreaterThan(installCount);
|
|
516
|
-
renderer.dispose();
|
|
517
|
-
},
|
|
518
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// ── w10b: reason 'destroyed' is intentional teardown, NOT device-lost ───────
|
|
522
|
-
// Regression (PR #495 vitest-browser red): F4 made the device.lost Promise
|
|
523
|
-
// actually resolve. On CI chromium the GPUDevice is destroyed between test
|
|
524
|
-
// files (reason 'destroyed'); without this gate the health channel flipped to
|
|
525
|
-
// 'device-lost', the M2 draw() guard refused the frame, and unrelated browser
|
|
526
|
-
// tests failed with "renderer.draw frame N failed". Per the requirements A
|
|
527
|
-
// constraint, reason 'destroyed' (host/driver device.destroy()) must leave
|
|
528
|
-
// health alive. dawn/unit could not see this -- only the browser path does.
|
|
529
|
-
it(
|
|
530
|
-
'w10b: device.lost reason=destroyed does NOT flip health to device-lost (intentional teardown)',
|
|
531
|
-
async () => {
|
|
532
|
-
const renderer = await makeRenderer();
|
|
533
|
-
await renderer.ready;
|
|
534
|
-
expect(renderer.health().reason).toBe('alive');
|
|
535
|
-
|
|
536
|
-
if (!testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
537
|
-
testDeviceLostResolve({ reason: 'destroyed', message: 'device was destroyed (teardown)' });
|
|
538
|
-
|
|
539
|
-
// Give the lost Promise's microtask chain a tick to settle, then assert
|
|
540
|
-
// health stayed alive (the fanout gated the destroyed reason). Because
|
|
541
|
-
// health stays alive, the M2 draw() guard does NOT refuse frames -- which
|
|
542
|
-
// is exactly what unblocks the browser tests that regressed in PR #495.
|
|
543
|
-
await Promise.resolve();
|
|
544
|
-
await Promise.resolve();
|
|
545
|
-
expect(renderer.health().reason).toBe('alive');
|
|
546
|
-
},
|
|
547
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
// ── w11: adapter-unavailable failure code (A-AC-07) ────────────────────────
|
|
551
|
-
|
|
552
|
-
it(
|
|
553
|
-
'w11: recover() with null adapter -> recover-adapter-unavailable, health stays device-lost',
|
|
554
|
-
async () => {
|
|
555
|
-
const renderer = await makeRenderer();
|
|
556
|
-
await renderer.ready;
|
|
557
|
-
await driveDeviceLost(renderer);
|
|
558
|
-
|
|
559
|
-
recoverMock.adapterReturnsNull = true;
|
|
560
|
-
const result = await renderer.recover();
|
|
561
|
-
|
|
562
|
-
expect(result.ok).toBe(false);
|
|
563
|
-
if (!result.ok) {
|
|
564
|
-
expect(result.error.code).toBe('recover-adapter-unavailable');
|
|
565
|
-
expect(typeof result.error.expected).toBe('string');
|
|
566
|
-
expect(result.error.expected.length).toBeGreaterThan(0);
|
|
567
|
-
expect(typeof result.error.hint).toBe('string');
|
|
568
|
-
expect(result.error.hint.length).toBeGreaterThan(0);
|
|
569
|
-
}
|
|
570
|
-
// Failure does not fake the renderer back to alive.
|
|
571
|
-
expect(renderer.health().reason).toBe('device-lost');
|
|
572
|
-
},
|
|
573
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
it(
|
|
577
|
-
'm39: one adapter refusal leaves the same renderer retryable and publishes a fresh device on retry',
|
|
578
|
-
async () => {
|
|
579
|
-
const renderer = await makeRenderer();
|
|
580
|
-
await renderer.ready;
|
|
581
|
-
const originalDevice = renderer.device;
|
|
582
|
-
const cpuBefore = renderer.assets.inspect?.();
|
|
583
|
-
await driveDeviceLost(renderer);
|
|
584
|
-
|
|
585
|
-
const adapterCallsBeforeRefusal = recoverMock.requestAdapterCalls;
|
|
586
|
-
const deviceCallsBeforeRefusal = recoverMock.requestDeviceCalls;
|
|
587
|
-
recoverMock.adapterReturnsNull = true;
|
|
588
|
-
const refusal = await renderer.recover();
|
|
589
|
-
|
|
590
|
-
expect(refusal.ok).toBe(false);
|
|
591
|
-
if (!refusal.ok) {
|
|
592
|
-
expect(refusal.error.code).toBe('recover-adapter-unavailable');
|
|
593
|
-
expect(refusal.error.expected).toBe(
|
|
594
|
-
'requestAdapter returned null; driver/GPU may have been reset',
|
|
595
|
-
);
|
|
596
|
-
expect(refusal.error.hint).toBe(
|
|
597
|
-
'retry recover() after a host-chosen delay; adapter availability is transient',
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
expect(renderer.health().reason).toBe('device-lost');
|
|
601
|
-
expect(renderer.device).toBe(originalDevice);
|
|
602
|
-
expect(recoverMock.requestAdapterCalls).toBe(adapterCallsBeforeRefusal + 1);
|
|
603
|
-
expect(recoverMock.requestDeviceCalls).toBe(deviceCallsBeforeRefusal);
|
|
604
|
-
if (cpuBefore !== undefined && renderer.assets.inspect !== undefined) {
|
|
605
|
-
expect(renderer.assets.inspect()).toEqual(cpuBefore);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
recoverMock.adapterReturnsNull = false;
|
|
609
|
-
const retry = await renderer.recover();
|
|
610
|
-
expect(retry.ok).toBe(true);
|
|
611
|
-
expect(renderer.health().reason).toBe('alive');
|
|
612
|
-
expect(renderer.device).not.toBe(originalDevice);
|
|
613
|
-
if (cpuBefore !== undefined && renderer.assets.inspect !== undefined) {
|
|
614
|
-
expect(renderer.assets.inspect()).toEqual(cpuBefore);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const alive = await renderer.recover();
|
|
618
|
-
expect(alive.ok).toBe(false);
|
|
619
|
-
if (!alive.ok) expect(alive.error.code).toBe('recover-not-needed');
|
|
620
|
-
renderer.dispose();
|
|
621
|
-
renderer.dispose();
|
|
622
|
-
},
|
|
623
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
624
|
-
);
|
|
625
|
-
|
|
626
|
-
// ── w12: device-unavailable failure code (A-AC-07) ─────────────────────────
|
|
627
|
-
|
|
628
|
-
it(
|
|
629
|
-
'w12: recover() with requestDevice throw -> recover-device-unavailable, health stays device-lost',
|
|
630
|
-
async () => {
|
|
631
|
-
const renderer = await makeRenderer();
|
|
632
|
-
await renderer.ready;
|
|
633
|
-
await driveDeviceLost(renderer);
|
|
634
|
-
|
|
635
|
-
recoverMock.deviceThrows = true;
|
|
636
|
-
const result = await renderer.recover();
|
|
637
|
-
|
|
638
|
-
expect(result.ok).toBe(false);
|
|
639
|
-
if (!result.ok) {
|
|
640
|
-
expect(result.error.code).toBe('recover-device-unavailable');
|
|
641
|
-
expect(typeof result.error.expected).toBe('string');
|
|
642
|
-
expect(result.error.expected.length).toBeGreaterThan(0);
|
|
643
|
-
expect(typeof result.error.hint).toBe('string');
|
|
644
|
-
expect(result.error.hint.length).toBeGreaterThan(0);
|
|
645
|
-
}
|
|
646
|
-
expect(renderer.health().reason).toBe('device-lost');
|
|
647
|
-
},
|
|
648
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
649
|
-
);
|
|
650
|
-
|
|
651
|
-
it(
|
|
652
|
-
'm40: one requestDevice refusal preserves the same renderer for the next recover',
|
|
653
|
-
async () => {
|
|
654
|
-
const renderer = await makeRenderer();
|
|
655
|
-
await renderer.ready;
|
|
656
|
-
const originalDevice = renderer.device;
|
|
657
|
-
const originalAssets = renderer.assets;
|
|
658
|
-
const cpuBefore = renderer.assets.inspect?.();
|
|
659
|
-
const healthTransitions: HealthSnapshot[] = [];
|
|
660
|
-
const removeHealthListener = renderer.onHealthChange((snapshot) => {
|
|
661
|
-
healthTransitions.push(snapshot);
|
|
662
|
-
});
|
|
663
|
-
await driveDeviceLost(renderer);
|
|
664
|
-
|
|
665
|
-
const adapterCallsBeforeRefusal = recoverMock.requestAdapterCalls;
|
|
666
|
-
const deviceCallsBeforeRefusal = recoverMock.requestDeviceCalls;
|
|
667
|
-
recoverMock.deviceRefusalRemaining = 1;
|
|
668
|
-
const refused = await renderer.recover();
|
|
669
|
-
|
|
670
|
-
expect(refused.ok).toBe(false);
|
|
671
|
-
if (!refused.ok) {
|
|
672
|
-
expect(refused.error.code).toBe('recover-device-unavailable');
|
|
673
|
-
expect(refused.error.expected).toBe('requestDevice failed or threw');
|
|
674
|
-
expect(refused.error.hint).toBe(
|
|
675
|
-
'retry recover() after a host-chosen delay; device creation is driver-dependent',
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
expect(renderer.health().reason).toBe('device-lost');
|
|
679
|
-
expect(renderer.device).toBe(originalDevice);
|
|
680
|
-
expect(renderer.assets).toBe(originalAssets);
|
|
681
|
-
if (cpuBefore !== undefined && renderer.assets.inspect !== undefined) {
|
|
682
|
-
expect(renderer.assets.inspect()).toEqual(cpuBefore);
|
|
683
|
-
}
|
|
684
|
-
expect(recoverMock.requestAdapterCalls).toBe(adapterCallsBeforeRefusal + 1);
|
|
685
|
-
expect(recoverMock.requestDeviceCalls).toBe(deviceCallsBeforeRefusal + 1);
|
|
686
|
-
const transitionCountAfterRefusal = healthTransitions.length;
|
|
687
|
-
await Promise.resolve();
|
|
688
|
-
await Promise.resolve();
|
|
689
|
-
expect(healthTransitions).toHaveLength(transitionCountAfterRefusal);
|
|
690
|
-
|
|
691
|
-
const recovered = await renderer.recover();
|
|
692
|
-
expect(recovered.ok).toBe(true);
|
|
693
|
-
expect(renderer.health().reason).toBe('alive');
|
|
694
|
-
expect(renderer.device).not.toBe(originalDevice);
|
|
695
|
-
expect(renderer.assets).toBe(originalAssets);
|
|
696
|
-
expect(recoverMock.requestAdapterCalls).toBe(adapterCallsBeforeRefusal + 2);
|
|
697
|
-
expect(recoverMock.requestDeviceCalls).toBe(deviceCallsBeforeRefusal + 2);
|
|
698
|
-
const third = await renderer.recover();
|
|
699
|
-
expect(third.ok).toBe(false);
|
|
700
|
-
if (!third.ok) expect(third.error.code).toBe('recover-not-needed');
|
|
701
|
-
removeHealthListener();
|
|
702
|
-
renderer.dispose();
|
|
703
|
-
renderer.dispose();
|
|
704
|
-
},
|
|
705
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
706
|
-
);
|
|
707
|
-
|
|
708
|
-
// ── w13: idempotent + alive sentinel + no background state (A-AC-08) ────────
|
|
709
|
-
|
|
710
|
-
it(
|
|
711
|
-
'w13(a): recover() on alive renderer returns recover-not-needed',
|
|
712
|
-
async () => {
|
|
713
|
-
const renderer = await makeRenderer();
|
|
714
|
-
await renderer.ready;
|
|
715
|
-
expect(renderer.health().reason).toBe('alive');
|
|
716
|
-
|
|
717
|
-
const result = await renderer.recover();
|
|
718
|
-
expect(result.ok).toBe(false);
|
|
719
|
-
if (!result.ok) {
|
|
720
|
-
expect(result.error.code).toBe('recover-not-needed');
|
|
721
|
-
}
|
|
722
|
-
},
|
|
723
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
724
|
-
);
|
|
725
|
-
|
|
726
|
-
it(
|
|
727
|
-
'w13(b): after a successful recover, second recover returns recover-not-needed',
|
|
728
|
-
async () => {
|
|
729
|
-
const renderer = await makeRenderer();
|
|
730
|
-
await renderer.ready;
|
|
731
|
-
await driveDeviceLost(renderer);
|
|
732
|
-
|
|
733
|
-
const first = await renderer.recover();
|
|
734
|
-
expect(first.ok).toBe(true);
|
|
735
|
-
expect(renderer.health().reason).toBe('alive');
|
|
736
|
-
|
|
737
|
-
// Idempotent: a second recover() in the now-alive state is a no-op signal.
|
|
738
|
-
const second = await renderer.recover();
|
|
739
|
-
expect(second.ok).toBe(false);
|
|
740
|
-
if (!second.ok) {
|
|
741
|
-
expect(second.error.code).toBe('recover-not-needed');
|
|
742
|
-
}
|
|
743
|
-
},
|
|
744
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
745
|
-
);
|
|
746
|
-
|
|
747
|
-
it(
|
|
748
|
-
'w13(c): concurrent recover calls share one device rebuild',
|
|
749
|
-
async () => {
|
|
750
|
-
const renderer = await makeRenderer();
|
|
751
|
-
await renderer.ready;
|
|
752
|
-
await driveDeviceLost(renderer);
|
|
753
|
-
const adapterCallsBefore = recoverMock.requestAdapterCalls;
|
|
754
|
-
|
|
755
|
-
const first = renderer.recover();
|
|
756
|
-
const second = renderer.recover();
|
|
757
|
-
expect(second).toBe(first);
|
|
758
|
-
expect((await first).ok).toBe(true);
|
|
759
|
-
expect(recoverMock.requestAdapterCalls).toBe(adapterCallsBefore + 1);
|
|
760
|
-
},
|
|
761
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
it(
|
|
765
|
-
'w13(d): dispose wins while recovery is acquiring a replacement device',
|
|
766
|
-
async () => {
|
|
767
|
-
const renderer = await makeRenderer();
|
|
768
|
-
await renderer.ready;
|
|
769
|
-
await driveDeviceLost(renderer);
|
|
770
|
-
|
|
771
|
-
let releaseDevice!: () => void;
|
|
772
|
-
recoverMock.deviceRequestGate = new Promise<void>((resolve) => {
|
|
773
|
-
releaseDevice = resolve;
|
|
774
|
-
});
|
|
775
|
-
const recovering = renderer.recover();
|
|
776
|
-
await vi.waitFor(() => expect(recoverMock.requestDeviceCalls).toBeGreaterThan(1));
|
|
777
|
-
renderer.dispose();
|
|
778
|
-
releaseDevice();
|
|
779
|
-
|
|
780
|
-
const result = await recovering;
|
|
781
|
-
expect(result.ok).toBe(false);
|
|
782
|
-
if (!result.ok) expect(result.error.code).toBe('recover-not-needed');
|
|
783
|
-
expect(renderer.health().reason).not.toBe('alive');
|
|
784
|
-
},
|
|
785
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
it('w13(e): recover() function body has no timer or retry loop', () => {
|
|
789
|
-
// Hard acceptanceCheck (PD5): the compressed-discipline mandate forbids any
|
|
790
|
-
// background state machine, retry counter, or timer in the recover path.
|
|
791
|
-
// Source-text assertion over createRenderer.ts isolates the recover()
|
|
792
|
-
// method body and greps for the forbidden constructs.
|
|
793
|
-
const src = readFileSync(
|
|
794
|
-
fileURLToPath(new URL('../../../render/src/renderer/renderer-factory.ts', import.meta.url)),
|
|
795
|
-
'utf8',
|
|
796
|
-
);
|
|
797
|
-
const startIdx = src.indexOf('recover(): Promise<Result<void, RecoverError>>');
|
|
798
|
-
expect(startIdx).toBeGreaterThan(-1);
|
|
799
|
-
// The onHealthChange method declaration immediately follows recover() in
|
|
800
|
-
// the renderer facade object; bound the slice there.
|
|
801
|
-
const endIdx = src.indexOf('onHealthChange(', startIdx);
|
|
802
|
-
expect(endIdx).toBeGreaterThan(startIdx);
|
|
803
|
-
const body = src.slice(startIdx, endIdx);
|
|
804
|
-
expect(body).not.toMatch(/setTimeout/);
|
|
805
|
-
expect(body).not.toMatch(/setInterval/);
|
|
806
|
-
expect(body).not.toMatch(/retryCount/);
|
|
807
|
-
expect(body).not.toMatch(/maxRetries/);
|
|
808
|
-
expect(body).not.toMatch(/maxLetries/);
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
// ── w14: preserves CPU POD cache (A-AC-12) ─────────────────────────────────
|
|
812
|
-
|
|
813
|
-
it(
|
|
814
|
-
'w14: recover() preserves AssetRegistry CPU POD entries; only GPU resources cleared',
|
|
815
|
-
async () => {
|
|
816
|
-
const renderer = await makeRenderer();
|
|
817
|
-
await renderer.ready;
|
|
818
|
-
|
|
819
|
-
// Inspect the AssetRegistry catalog entry count before recover. The
|
|
820
|
-
// AssetRegistry survives recover (CPU POD cache); only the GpuResourceStore
|
|
821
|
-
// is destroyed + the device rebuilt.
|
|
822
|
-
const inspectBefore =
|
|
823
|
-
typeof renderer.assets.inspect === 'function' ? renderer.assets.inspect() : null;
|
|
824
|
-
|
|
825
|
-
const destroyAllSpy = vi.spyOn(renderer.store, 'destroyAll');
|
|
826
|
-
await driveDeviceLost(renderer);
|
|
827
|
-
const result = await renderer.recover();
|
|
828
|
-
expect(result.ok).toBe(true);
|
|
829
|
-
|
|
830
|
-
// GPU resources cleared via destroyAll.
|
|
831
|
-
expect(destroyAllSpy).toHaveBeenCalled();
|
|
832
|
-
|
|
833
|
-
// AssetRegistry instance identity is preserved (same CPU POD registry).
|
|
834
|
-
const inspectAfter =
|
|
835
|
-
typeof renderer.assets.inspect === 'function' ? renderer.assets.inspect() : null;
|
|
836
|
-
// The catalog entry count is unchanged across recover (CPU POD preserved).
|
|
837
|
-
if (inspectBefore !== null && inspectAfter !== null) {
|
|
838
|
-
expect(JSON.stringify(inspectAfter)).toBe(JSON.stringify(inspectBefore));
|
|
839
|
-
}
|
|
840
|
-
},
|
|
841
|
-
RECOVER_BOOT_TIMEOUT_MS,
|
|
842
|
-
);
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
// ── w19: RecoverErrorCode exhaustiveness grep gate + switch (A-AC-09) ─────────
|
|
846
|
-
|
|
847
|
-
describe('RecoverErrorCode closed union (A-AC-09)', () => {
|
|
848
|
-
it('errors.ts RecoverErrorCode definition line declares exactly 4 literal members', () => {
|
|
849
|
-
const src = readFileSync(
|
|
850
|
-
fileURLToPath(new URL('../../../render/src/errors/recover.ts', import.meta.url)),
|
|
851
|
-
'utf8',
|
|
852
|
-
);
|
|
853
|
-
const defLine = src.split('\n').find((l) => l.includes('export type RecoverErrorCode'));
|
|
854
|
-
expect(defLine).toBeDefined();
|
|
855
|
-
const literals = (defLine as string).match(/recover-[a-z-]+/g) ?? [];
|
|
856
|
-
expect(literals).toEqual([
|
|
857
|
-
'recover-not-needed',
|
|
858
|
-
'recover-not-implemented',
|
|
859
|
-
'recover-adapter-unavailable',
|
|
860
|
-
'recover-device-unavailable',
|
|
861
|
-
]);
|
|
862
|
-
});
|
|
863
|
-
|
|
864
|
-
it('exhaustive switch over RecoverErrorCode compiles without default / as cast', () => {
|
|
865
|
-
function handle(code: RecoverErrorCode): string {
|
|
866
|
-
switch (code) {
|
|
867
|
-
case 'recover-not-needed':
|
|
868
|
-
return 'not-needed';
|
|
869
|
-
case 'recover-not-implemented':
|
|
870
|
-
return 'not-implemented';
|
|
871
|
-
case 'recover-adapter-unavailable':
|
|
872
|
-
return 'adapter-unavailable';
|
|
873
|
-
case 'recover-device-unavailable':
|
|
874
|
-
return 'device-unavailable';
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
expect(handle('recover-adapter-unavailable')).toBe('adapter-unavailable');
|
|
878
|
-
expect(handle('recover-device-unavailable')).toBe('device-unavailable');
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
it('both new codes carry valid .expected + .hint', () => {
|
|
882
|
-
for (const code of ['recover-adapter-unavailable', 'recover-device-unavailable'] as const) {
|
|
883
|
-
const e = new RecoverError(code);
|
|
884
|
-
expect(e.code).toBe(code);
|
|
885
|
-
expect(e.expected.length).toBeGreaterThan(0);
|
|
886
|
-
expect(e.hint.length).toBeGreaterThan(0);
|
|
11
|
+
const manifest = `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
|
|
12
|
+
|
|
13
|
+
describe('renderer recovery and receipt contract', () => {
|
|
14
|
+
it('returns a structured not-needed result while inspection is healthy', async () => {
|
|
15
|
+
const renderer = await requireRenderer(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
16
|
+
expect(renderer.inspect().state).toBe('alive');
|
|
17
|
+
const result = await renderer.recover();
|
|
18
|
+
expect(result.ok).toBe(false);
|
|
19
|
+
if (!result.ok) {
|
|
20
|
+
expect(result.error.code).toBe('renderer-state-invalid');
|
|
21
|
+
expect(result.error.hint.length).toBeGreaterThan(0);
|
|
887
22
|
}
|
|
888
|
-
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
queue: RenderQueue.Geometry,
|
|
903
|
-
topology: 'triangle-list',
|
|
904
|
-
pipelineClass: 'forgeax::default-unlit',
|
|
905
|
-
materialResourceClass: 'empty',
|
|
906
|
-
materialClass: 'forgeax::default-unlit',
|
|
907
|
-
resourceRevision: 4,
|
|
908
|
-
materialRevision: 2,
|
|
909
|
-
alphaTest: false,
|
|
910
|
-
doubleSided: false,
|
|
911
|
-
pbr: false,
|
|
912
|
-
shadowCaster: true,
|
|
913
|
-
} as unknown as SubmissionPlan['batches'][number]['key'];
|
|
914
|
-
|
|
915
|
-
function slot(generation: number): RenderSceneProjectionSlot {
|
|
916
|
-
return {
|
|
917
|
-
slot: 3,
|
|
918
|
-
generation,
|
|
919
|
-
snapshot: {
|
|
920
|
-
localAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
|
|
921
|
-
transform: { world: identity },
|
|
922
|
-
instances: { transforms: identity, instanceCount: 1 },
|
|
923
|
-
},
|
|
924
|
-
} as unknown as RenderSceneProjectionSlot;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
function plan(): SubmissionPlan {
|
|
928
|
-
return {
|
|
929
|
-
revision: 9,
|
|
930
|
-
batches: [
|
|
931
|
-
{
|
|
932
|
-
batchId: 0,
|
|
933
|
-
generation: 1,
|
|
934
|
-
key,
|
|
935
|
-
candidates: [
|
|
936
|
-
{ primitiveIndex: 3, generation: 7, drawItemIndex: 0, instanceOrdinal: 0 },
|
|
937
|
-
{ primitiveIndex: 3, generation: 7, drawItemIndex: 0, instanceOrdinal: 0 },
|
|
938
|
-
{ primitiveIndex: 3, generation: 6, drawItemIndex: 0, instanceOrdinal: 0 },
|
|
939
|
-
],
|
|
940
|
-
visibleBase: 0,
|
|
941
|
-
visibleCapacity: 3,
|
|
942
|
-
indirectOffset: 0,
|
|
943
|
-
},
|
|
944
|
-
],
|
|
945
|
-
candidateCount: 3,
|
|
946
|
-
visibleCapacity: 3,
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
it('surfaces capacity overflow and rejects stale generations in the indirect CPU oracle', () => {
|
|
951
|
-
const result = classifyGpuDrivenView(plan(), [slot(7)], new Float32Array(), new Map([[0, 1]]));
|
|
952
|
-
|
|
953
|
-
expect(result.candidateCount).toBe(3);
|
|
954
|
-
expect(result.rejectedCount).toBe(1);
|
|
955
|
-
expect(result.visibleCount).toBe(1);
|
|
956
|
-
expect(result.overflowedBatches).toEqual([0]);
|
|
957
|
-
expect(new DataView(result.indirectArgs).getUint32(4, true)).toBe(1);
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
it('uses the same candidates for CPU cascade fallback and reports caster overflow', () => {
|
|
961
|
-
const result = executeCpuMultiViewVisibility({
|
|
962
|
-
views: [
|
|
963
|
-
{
|
|
964
|
-
viewId: 'cascade-0',
|
|
965
|
-
kind: 'cascade',
|
|
966
|
-
cascadeIndex: 0,
|
|
967
|
-
planes: new Float32Array(),
|
|
968
|
-
casterCandidateIndices: new Set([10, 11]),
|
|
969
|
-
volumeRevision: 6,
|
|
970
|
-
spatialIndexRevision: 12,
|
|
971
|
-
},
|
|
972
|
-
],
|
|
973
|
-
candidates: [
|
|
974
|
-
{
|
|
975
|
-
stableId: 100,
|
|
976
|
-
generation: 7,
|
|
977
|
-
candidateIndex: 10,
|
|
978
|
-
worldAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
|
|
979
|
-
casterEligible: true,
|
|
980
|
-
batchId: 0,
|
|
981
|
-
instanceOrdinal: 0,
|
|
982
|
-
},
|
|
983
|
-
{
|
|
984
|
-
stableId: 101,
|
|
985
|
-
generation: 7,
|
|
986
|
-
candidateIndex: 11,
|
|
987
|
-
worldAabb: new Float32Array([-1, -1, -1, 1, 1, 1]),
|
|
988
|
-
casterEligible: true,
|
|
989
|
-
batchId: 0,
|
|
990
|
-
instanceOrdinal: 1,
|
|
991
|
-
},
|
|
992
|
-
],
|
|
993
|
-
capacityByView: new Map([['cascade-0', 1]]),
|
|
994
|
-
capacityByBatch: new Map([[0, 1]]),
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
expect(result.views[0]).toMatchObject({
|
|
998
|
-
stableIds: [100],
|
|
999
|
-
generations: [7],
|
|
1000
|
-
overflow: true,
|
|
1001
|
-
volumeRevision: 6,
|
|
1002
|
-
spatialIndexRevision: 12,
|
|
23
|
+
await renderer.dispose();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('observes only a receipt from the current frame generation', async () => {
|
|
27
|
+
const renderer = await requireRenderer(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
28
|
+
const world = new World();
|
|
29
|
+
const attached = renderer.attach(world);
|
|
30
|
+
expect(attached.ok).toBe(true);
|
|
31
|
+
if (!attached.ok) return;
|
|
32
|
+
expect(world.update().ok).toBe(true);
|
|
33
|
+
const frame = renderer.draw({
|
|
34
|
+
leases: [attached.value],
|
|
35
|
+
camera: { lease: attached.value },
|
|
36
|
+
environment: { lease: attached.value },
|
|
1003
37
|
});
|
|
1004
|
-
expect(
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
]);
|
|
38
|
+
expect(frame.ok).toBe(true);
|
|
39
|
+
if (!frame.ok) return;
|
|
40
|
+
const receipt: FrameReceipt = frame.value;
|
|
41
|
+
const observed = await renderer.observe(receipt, { include: ['draws', 'bindings'] });
|
|
42
|
+
expect(observed.ok).toBe(true);
|
|
43
|
+
if (observed.ok) expect(observed.value.frameId).toBe(receipt.frameId);
|
|
44
|
+
await renderer.dispose();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('does not recover a disposed renderer or reopen a second frame path', async () => {
|
|
48
|
+
const renderer = await requireRenderer(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
49
|
+
renderer.dispose();
|
|
50
|
+
expect(renderer.inspect().state).toBe('disposed');
|
|
51
|
+
const result = await renderer.recover();
|
|
52
|
+
expect(result.ok).toBe(false);
|
|
53
|
+
if (!result.ok) expect(result.error.code).toBe('renderer-state-invalid');
|
|
1008
54
|
});
|
|
1009
|
-
|
|
1010
|
-
it('grows all GPU Scene tables together so recovery can rebuild the same resources', () => {
|
|
1011
|
-
const device = {
|
|
1012
|
-
caps: { storageBuffer: true },
|
|
1013
|
-
createBuffer: ({ size }: { size: number }) => ok({ size }),
|
|
1014
|
-
destroyBuffer: () => undefined,
|
|
1015
|
-
queue: { writeBuffer: () => ok(undefined) },
|
|
1016
|
-
} as unknown as Parameters<typeof GpuScene.create>[0];
|
|
1017
|
-
const available = GpuScene.create(device, 1).unwrap();
|
|
1018
|
-
expect(available.status).toBe('available');
|
|
1019
|
-
if (available.status !== 'available') return;
|
|
1020
|
-
|
|
1021
|
-
const projection = new RenderSceneProjection();
|
|
1022
|
-
projection.reset([gpuSceneSlot(0).snapshot]);
|
|
1023
|
-
const first = available.scene.rebuild(projection).unwrap();
|
|
1024
|
-
expect(first.grew).toBe(true);
|
|
1025
|
-
expect(available.scene.inspect()).toMatchObject({ capacity: 2, capacityGrows: 1 });
|
|
1026
|
-
|
|
1027
|
-
projection.reset([gpuSceneSlot(0).snapshot, gpuSceneSlot(1).snapshot]);
|
|
1028
|
-
const second = available.scene.rebuild(projection).unwrap();
|
|
1029
|
-
expect(second.grew).toBe(true);
|
|
1030
|
-
const inspection = available.scene.inspect();
|
|
1031
|
-
expect(inspection.capacity).toBeGreaterThanOrEqual(4);
|
|
1032
|
-
expect(inspection.capacityGrows).toBe(2);
|
|
1033
|
-
for (const table of Object.values(inspection.tables)) {
|
|
1034
|
-
expect(table.capacity).toBe(inspection.capacity);
|
|
1035
|
-
}
|
|
1036
|
-
available.scene.dispose();
|
|
1037
|
-
});
|
|
1038
|
-
|
|
1039
|
-
function gpuSceneSlot(slotIndex: number): RenderSceneProjectionSlot {
|
|
1040
|
-
const bounds = new Float32Array([-1, -1, -1, 1, 1, 1]);
|
|
1041
|
-
const material = { baseColor: [1, 1, 1] };
|
|
1042
|
-
return {
|
|
1043
|
-
slot: slotIndex,
|
|
1044
|
-
generation: 1,
|
|
1045
|
-
snapshot: {
|
|
1046
|
-
assetHandle: slotIndex + 1,
|
|
1047
|
-
transform: { world: identity },
|
|
1048
|
-
localAabb: bounds,
|
|
1049
|
-
material,
|
|
1050
|
-
materials: [material],
|
|
1051
|
-
materialBindingSources: [],
|
|
1052
|
-
worldId: 0,
|
|
1053
|
-
entityKey: slotIndex + 1,
|
|
1054
|
-
},
|
|
1055
|
-
fact: {
|
|
1056
|
-
slot: slotIndex,
|
|
1057
|
-
generation: 1,
|
|
1058
|
-
worldId: 0,
|
|
1059
|
-
entityKey: slotIndex + 1,
|
|
1060
|
-
bounds: { status: 'present', localAabb: bounds, worldAabb: bounds },
|
|
1061
|
-
mobility: 'dynamic',
|
|
1062
|
-
casterEligibility: 'unknown',
|
|
1063
|
-
revisions: {
|
|
1064
|
-
transform: 1,
|
|
1065
|
-
geometry: 1,
|
|
1066
|
-
material: 1,
|
|
1067
|
-
eligibility: 1,
|
|
1068
|
-
space: 1,
|
|
1069
|
-
batch: 1,
|
|
1070
|
-
resource: 1,
|
|
1071
|
-
},
|
|
1072
|
-
},
|
|
1073
|
-
} as unknown as RenderSceneProjectionSlot;
|
|
1074
|
-
}
|
|
1075
55
|
});
|