@forgeax/engine-runtime 0.1.4 → 0.1.7
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 +65 -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 +7 -4
- 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 +20 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +107 -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 +608 -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 +24 -9
- 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 +1227 -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 +31 -511
- 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 +21 -25
- 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 +117 -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 +65 -559
- 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 +61 -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 +56 -260
- 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 +15 -14
- 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 +68 -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 +35 -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 +51 -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,57 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// w2 — RecoverError union exhaustiveness test (AC-06)
|
|
6
|
-
// w3 — HealthListenerRegistry replay + unsubscribe + isolation test (AC-07/AC-08)
|
|
7
|
-
// w4 — HealthSnapshot shape + recoverable derivation + immutability test (AC-02)
|
|
8
|
-
// w8 — health() healthy-state baseline integration test
|
|
9
|
-
// w9 — recover() healthy-state returns recover-not-needed test (AC-04)
|
|
10
|
-
// w10 — recover() degraded-state placeholder + health unchanged test (AC-04b)
|
|
11
|
-
//
|
|
12
|
-
// w1-w4: unit tests (types + registry, GREEN after M1 w5-w7)
|
|
13
|
-
// w8-w10: integration tests (createRenderer + mock RHI, RED until w11-w13)
|
|
1
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import type { HealthReason, HealthSnapshot } from '../../../render/src/lifecycle';
|
|
4
|
+
import { requireRenderer } from './renderer-test-utils';
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
HealthSnapshot,
|
|
19
|
-
} from '@forgeax/engine-render/internal';
|
|
20
|
-
import {
|
|
21
|
-
deriveRecoverable,
|
|
22
|
-
HealthListenerRegistry,
|
|
23
|
-
RecoverError,
|
|
24
|
-
type RecoverErrorCode,
|
|
25
|
-
} from '@forgeax/engine-render/internal';
|
|
26
|
-
import type { Result, RhiInstance } from '@forgeax/engine-rhi';
|
|
27
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
28
|
-
|
|
29
|
-
// ── w1: HealthReason narrowing + detail discriminated union (AC-05) ─────────
|
|
6
|
+
function canvas(): HTMLCanvasElement {
|
|
7
|
+
return { width: 64, height: 64, getContext: () => null } as unknown as HTMLCanvasElement;
|
|
8
|
+
}
|
|
30
9
|
|
|
31
|
-
|
|
32
|
-
it('switch(snap.reason) narrows detail per reason without as casts', () => {
|
|
33
|
-
// Consumer-side switch exercising all 3 HealthReason members.
|
|
34
|
-
// TS must narrow snap.detail in each case branch — zero `as` casts.
|
|
35
|
-
function consumeHealth(snap: HealthSnapshot): string {
|
|
36
|
-
switch (snap.reason) {
|
|
37
|
-
case 'alive':
|
|
38
|
-
// alive variant has no detail field — zero as casts
|
|
39
|
-
return 'alive';
|
|
40
|
-
case 'device-lost':
|
|
41
|
-
// detail narrows to HealthDetailDeviceLost; lostReason scoped to this variant
|
|
42
|
-
return `device-lost(lostReason=${snap.detail.lostReason}): ${snap.detail.message}`;
|
|
43
|
-
case 'internal-fault':
|
|
44
|
-
// detail narrows to HealthDetailInternalFault; message is the only field
|
|
45
|
-
return `internal-fault: ${snap.detail.message}`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Compile-time assertion: the function compiles with zero `as` casts
|
|
49
|
-
expect(typeof consumeHealth).toBe('function');
|
|
50
|
-
});
|
|
10
|
+
const manifest = `data:application/json,${encodeURIComponent(JSON.stringify({ schemaVersion: '1.0.0', entries: [] }))}`;
|
|
51
11
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function
|
|
12
|
+
describe('renderer inspection and recovery contract', () => {
|
|
13
|
+
it('keeps health vocabulary closed for inspection consumers', () => {
|
|
14
|
+
function describeReason(reason: HealthReason): string {
|
|
55
15
|
switch (reason) {
|
|
56
16
|
case 'alive':
|
|
57
17
|
return 'alive';
|
|
@@ -61,875 +21,25 @@ describe('HealthReason narrowing (AC-05)', () => {
|
|
|
61
21
|
return 'internal-fault';
|
|
62
22
|
}
|
|
63
23
|
}
|
|
64
|
-
expect(typeof exhaustive).toBe('function');
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// ── w2: RecoverError union exhaustiveness (AC-06) ──────────────────────────
|
|
69
|
-
|
|
70
|
-
describe('RecoverError closed union (AC-06)', () => {
|
|
71
|
-
it('switch(err.code) exhaustively matches all members without default', () => {
|
|
72
|
-
// Consumer-side switch matching every RecoverErrorCode member.
|
|
73
|
-
// TS must not emit non-exhaustive errors; no `default` branch.
|
|
74
|
-
// feat-20260622-s5 M3 / w19: union grew add-only to 4 members (adapter /
|
|
75
|
-
// device unavailable join the S3 not-needed / not-implemented(reserved)).
|
|
76
|
-
function handleRecover(err: RecoverError): string {
|
|
77
|
-
switch (err.code) {
|
|
78
|
-
case 'recover-not-needed':
|
|
79
|
-
return `not-needed: ${err.hint}`;
|
|
80
|
-
case 'recover-not-implemented':
|
|
81
|
-
return `not-implemented: ${err.hint}`;
|
|
82
|
-
case 'recover-adapter-unavailable':
|
|
83
|
-
return `adapter-unavailable: ${err.hint}`;
|
|
84
|
-
case 'recover-device-unavailable':
|
|
85
|
-
return `device-unavailable: ${err.hint}`;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
expect(typeof handleRecover).toBe('function');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('RecoverErrorCode has exactly 4 members', () => {
|
|
92
|
-
function exhaustive(code: RecoverErrorCode): string {
|
|
93
|
-
switch (code) {
|
|
94
|
-
case 'recover-not-needed':
|
|
95
|
-
return 'not-needed';
|
|
96
|
-
case 'recover-not-implemented':
|
|
97
|
-
return 'not-implemented';
|
|
98
|
-
case 'recover-adapter-unavailable':
|
|
99
|
-
return 'adapter-unavailable';
|
|
100
|
-
case 'recover-device-unavailable':
|
|
101
|
-
return 'device-unavailable';
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
expect(typeof exhaustive).toBe('function');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('RecoverError carries .code, .expected, .hint fields', () => {
|
|
108
|
-
const err = new RecoverError('recover-not-needed');
|
|
109
|
-
expect(err.code).toBe('recover-not-needed');
|
|
110
|
-
expect(typeof err.expected).toBe('string');
|
|
111
|
-
expect(err.expected.length).toBeGreaterThan(0);
|
|
112
|
-
expect(typeof err.hint).toBe('string');
|
|
113
|
-
expect(err.hint.length).toBeGreaterThan(0);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('recover-not-implemented code also carries valid .expected + .hint', () => {
|
|
117
|
-
const err = new RecoverError('recover-not-implemented');
|
|
118
|
-
expect(err.code).toBe('recover-not-implemented');
|
|
119
|
-
expect(typeof err.expected).toBe('string');
|
|
120
|
-
expect(err.expected.length).toBeGreaterThan(0);
|
|
121
|
-
expect(typeof err.hint).toBe('string');
|
|
122
|
-
expect(err.hint.length).toBeGreaterThan(0);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// ── w3: HealthListenerRegistry replay + unsubscribe + isolation (AC-07/AC-08)
|
|
127
|
-
|
|
128
|
-
describe('HealthListenerRegistry (AC-07/AC-08)', () => {
|
|
129
|
-
it('late-attach replay: fire then add calls listener immediately', () => {
|
|
130
|
-
const registry = new HealthListenerRegistry();
|
|
131
|
-
const snapshot: HealthSnapshot = {
|
|
132
|
-
reason: 'device-lost',
|
|
133
|
-
detail: { lostReason: 'destroyed', message: 'test device lost' },
|
|
134
|
-
recoverable: true,
|
|
135
|
-
};
|
|
136
|
-
registry.fire(snapshot);
|
|
137
|
-
|
|
138
|
-
const cb = vi.fn<HealthChangeListener>();
|
|
139
|
-
registry.add(cb);
|
|
140
|
-
|
|
141
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
142
|
-
expect(cb).toHaveBeenCalledWith(snapshot);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('unsubscribe: add returns unsubscribe, listener not called after', () => {
|
|
146
|
-
const registry = new HealthListenerRegistry();
|
|
147
|
-
const cb = vi.fn<HealthChangeListener>();
|
|
148
|
-
const unsubscribe = registry.add(cb);
|
|
149
|
-
|
|
150
|
-
// Unsubscribe
|
|
151
|
-
unsubscribe();
|
|
152
|
-
|
|
153
|
-
// Fire after unsubscribe — cb must not be called
|
|
154
|
-
const snapshot: HealthSnapshot = {
|
|
155
|
-
reason: 'alive',
|
|
156
|
-
recoverable: false,
|
|
157
|
-
};
|
|
158
|
-
registry.fire(snapshot);
|
|
159
|
-
|
|
160
|
-
expect(cb).not.toHaveBeenCalled();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('listener-throw isolation: first throws, second still fires', () => {
|
|
164
|
-
const registry = new HealthListenerRegistry();
|
|
165
|
-
|
|
166
|
-
const cb1 = vi.fn<HealthChangeListener>(() => {
|
|
167
|
-
throw new Error('cb1 intentional throw');
|
|
168
|
-
});
|
|
169
|
-
const cb2 = vi.fn<HealthChangeListener>();
|
|
170
|
-
|
|
171
|
-
registry.add(cb1);
|
|
172
|
-
registry.add(cb2);
|
|
173
|
-
|
|
174
|
-
// Suppress console.error from the try/catch isolation
|
|
175
|
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
176
|
-
|
|
177
|
-
const snapshot: HealthSnapshot = {
|
|
178
|
-
reason: 'internal-fault',
|
|
179
|
-
detail: { message: 'simulated fault' },
|
|
180
|
-
recoverable: false,
|
|
181
|
-
};
|
|
182
|
-
registry.fire(snapshot);
|
|
183
|
-
|
|
184
|
-
expect(cb1).toHaveBeenCalledTimes(1);
|
|
185
|
-
expect(cb2).toHaveBeenCalledTimes(1);
|
|
186
|
-
expect(cb2).toHaveBeenCalledWith(snapshot);
|
|
187
|
-
// cb1's throw should be caught and surfaced via console.error
|
|
188
|
-
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
189
|
-
|
|
190
|
-
consoleErrorSpy.mockRestore();
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('clear detaches all listeners but preserves fired/lastSnapshot for replay', () => {
|
|
194
|
-
const registry = new HealthListenerRegistry();
|
|
195
|
-
|
|
196
|
-
const snapshot: HealthSnapshot = {
|
|
197
|
-
reason: 'device-lost',
|
|
198
|
-
detail: { lostReason: 'unknown', message: 'test' },
|
|
199
|
-
recoverable: true,
|
|
200
|
-
};
|
|
201
|
-
registry.fire(snapshot);
|
|
202
|
-
|
|
203
|
-
registry.clear();
|
|
204
|
-
|
|
205
|
-
// Late-attach after clear: fired/lastSnapshot preserved, so replay still works
|
|
206
|
-
const cb = vi.fn<HealthChangeListener>();
|
|
207
|
-
registry.add(cb);
|
|
208
|
-
|
|
209
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
210
|
-
expect(cb).toHaveBeenCalledWith(snapshot);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it('getLastSnapshot returns alive baseline when never fired', () => {
|
|
214
|
-
const registry = new HealthListenerRegistry();
|
|
215
|
-
const snapshot = registry.getLastSnapshot();
|
|
216
|
-
|
|
217
|
-
expect(snapshot.reason).toBe('alive');
|
|
218
|
-
expect(snapshot.recoverable).toBe(false);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('getLastSnapshot returns last fired snapshot after fire', () => {
|
|
222
|
-
const registry = new HealthListenerRegistry();
|
|
223
|
-
const fired: HealthSnapshot = {
|
|
224
|
-
reason: 'device-lost',
|
|
225
|
-
detail: { lostReason: 'destroyed', message: 'lost' },
|
|
226
|
-
recoverable: true,
|
|
227
|
-
};
|
|
228
|
-
registry.fire(fired);
|
|
229
|
-
|
|
230
|
-
const snapshot = registry.getLastSnapshot();
|
|
231
|
-
expect(snapshot.reason).toBe('device-lost');
|
|
232
|
-
expect(snapshot.recoverable).toBe(true);
|
|
233
|
-
if (snapshot.reason === 'device-lost') {
|
|
234
|
-
expect(snapshot.detail).toEqual({ lostReason: 'destroyed', message: 'lost' });
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// ── w4: HealthSnapshot shape + recoverable derivation + immutability (AC-02) ─
|
|
240
|
-
|
|
241
|
-
describe('HealthSnapshot shape + recoverable derivation (AC-02)', () => {
|
|
242
|
-
it('deriveRecoverable: alive -> false', () => {
|
|
243
|
-
expect(deriveRecoverable('alive')).toBe(false);
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('deriveRecoverable: device-lost -> true', () => {
|
|
247
|
-
expect(deriveRecoverable('device-lost')).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it('deriveRecoverable: internal-fault -> false', () => {
|
|
251
|
-
expect(deriveRecoverable('internal-fault')).toBe(false);
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('HealthSnapshot has exactly { reason, detail?, recoverable } fields', () => {
|
|
255
|
-
// Shape assertion: discriminated union — alive has no detail, degraded
|
|
256
|
-
// variants have required detail. TS would error on mismatched shapes.
|
|
257
|
-
const snapAlive: HealthSnapshot = { reason: 'alive', recoverable: false };
|
|
258
|
-
expect(snapAlive.reason).toBe('alive');
|
|
259
|
-
expect(snapAlive.recoverable).toBe(false);
|
|
260
|
-
|
|
261
|
-
const snapLost: HealthSnapshot = {
|
|
262
|
-
reason: 'device-lost',
|
|
263
|
-
detail: { lostReason: 'unknown', message: 'test' },
|
|
264
|
-
recoverable: true,
|
|
265
|
-
};
|
|
266
|
-
expect(snapLost.reason).toBe('device-lost');
|
|
267
|
-
expect(snapLost.recoverable).toBe(true);
|
|
268
|
-
expect(snapLost.detail).toEqual({ lostReason: 'unknown', message: 'test' });
|
|
269
|
-
|
|
270
|
-
const snapFault: HealthSnapshot = {
|
|
271
|
-
reason: 'internal-fault',
|
|
272
|
-
detail: { message: 'simulated' },
|
|
273
|
-
recoverable: false,
|
|
274
|
-
};
|
|
275
|
-
expect(snapFault.reason).toBe('internal-fault');
|
|
276
|
-
expect(snapFault.recoverable).toBe(false);
|
|
277
|
-
expect(snapFault.detail).toEqual({ message: 'simulated' });
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
282
|
-
// M2 Integration tests (w8 / w9 / w10 — TDD RED)
|
|
283
|
-
//
|
|
284
|
-
// These tests require a Renderer constructed via createRenderer with a mock RHI
|
|
285
|
-
// backend. They are RED until w11 (+ interface) + w12 (+ registry wire) + w13
|
|
286
|
-
// (+ implementation) are complete.
|
|
287
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
288
|
-
|
|
289
|
-
// ── Mock RHI setup (mirrors asset.unit.test.ts pattern) ──────────────────────
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Module-level resolver hook so M3 integration tests (w14/w15) can
|
|
293
|
-
* control when device.lost resolves. Set by makeFakeRhiDevice on each call.
|
|
294
|
-
* @internal test-only hook, not part of any package surface.
|
|
295
|
-
*/
|
|
296
|
-
let _testDeviceLostResolve: ((info: unknown) => void) | null = null;
|
|
297
|
-
|
|
298
|
-
function makeFakeRhiDevice(): Record<string, unknown> {
|
|
299
|
-
let resolveLost!: (info: unknown) => void;
|
|
300
|
-
const lost = new Promise<unknown>((res) => {
|
|
301
|
-
resolveLost = res;
|
|
302
|
-
});
|
|
303
|
-
_testDeviceLostResolve = resolveLost;
|
|
304
|
-
return {
|
|
305
|
-
__brand: 'RhiDevice',
|
|
306
|
-
lost,
|
|
307
|
-
features: new Set<string>(),
|
|
308
|
-
limits: {} as Record<string, number>,
|
|
309
|
-
// feat-20260707 M5 / w33: createRenderer projects RhiCaps.textureCompression*
|
|
310
|
-
// into TranscodeCaps at registry-build time; the fake device needs a caps
|
|
311
|
-
// object (the real RhiDevice always carries one).
|
|
312
|
-
caps: {
|
|
313
|
-
textureCompressionBc: false,
|
|
314
|
-
textureCompressionEtc2: false,
|
|
315
|
-
textureCompressionAstc: false,
|
|
316
|
-
},
|
|
317
|
-
queue: {
|
|
318
|
-
submit: () => undefined,
|
|
319
|
-
writeBuffer: () => undefined,
|
|
320
|
-
},
|
|
321
|
-
createBuffer: () => ({ ok: true, value: { __brand: 'Buffer' } }),
|
|
322
|
-
createTexture: () => ({ ok: true, value: { __brand: 'Texture' } }),
|
|
323
|
-
createBindGroupLayout: () => ({ ok: true, value: { __brand: 'BindGroupLayout' } }),
|
|
324
|
-
createBindGroup: () => ({ ok: true, value: { __brand: 'BindGroup' } }),
|
|
325
|
-
createPipelineLayout: () => ({ ok: true, value: { __brand: 'PipelineLayout' } }),
|
|
326
|
-
createRenderPipeline: () => ({ ok: true, value: { __brand: 'RenderPipeline' } }),
|
|
327
|
-
createSampler: () => ({ ok: true, value: { __brand: 'Sampler' } }),
|
|
328
|
-
createShaderModule: () => ({ ok: true, value: { __brand: 'ShaderModule' } }),
|
|
329
|
-
createTextureView: () => ({ ok: true, value: { __brand: 'TextureView' } }),
|
|
330
|
-
createCommandEncoder: () => ({
|
|
331
|
-
ok: true,
|
|
332
|
-
value: {
|
|
333
|
-
beginRenderPass: () => ({
|
|
334
|
-
setPipeline: () => undefined,
|
|
335
|
-
setBindGroup: () => undefined,
|
|
336
|
-
setVertexBuffer: () => undefined,
|
|
337
|
-
setIndexBuffer: () => undefined,
|
|
338
|
-
draw: () => undefined,
|
|
339
|
-
drawIndexed: () => undefined,
|
|
340
|
-
end: () => undefined,
|
|
341
|
-
}),
|
|
342
|
-
finish: () => ({ __brand: 'CommandBuffer' }),
|
|
343
|
-
},
|
|
344
|
-
}),
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function makeExplicitRhi(): RhiInstance {
|
|
349
|
-
const adapter = {
|
|
350
|
-
features: new Set<string>(),
|
|
351
|
-
limits: {},
|
|
352
|
-
requestDevice: async () => ({ ok: true, value: makeFakeRhiDevice() }),
|
|
353
|
-
};
|
|
354
|
-
return {
|
|
355
|
-
requestAdapter: async () => ({ ok: true, value: adapter }),
|
|
356
|
-
acquireCanvasContext: () => ({
|
|
357
|
-
ok: true,
|
|
358
|
-
value: {
|
|
359
|
-
configure: () => ({ ok: true, value: undefined }),
|
|
360
|
-
unconfigure: () => ({ ok: true, value: undefined }),
|
|
361
|
-
getCurrentTexture: () => ({ ok: true, value: { __brand: 'TextureView' } }),
|
|
362
|
-
},
|
|
363
|
-
}),
|
|
364
|
-
createShaderModule: async () => ({ ok: true, value: { __brand: 'ShaderModule' } }),
|
|
365
|
-
} as unknown as RhiInstance;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function makeMockCanvas(): HTMLCanvasElement {
|
|
369
|
-
return {
|
|
370
|
-
width: 800,
|
|
371
|
-
height: 600,
|
|
372
|
-
getContext(kind: string): unknown {
|
|
373
|
-
if (kind === 'webgpu') {
|
|
374
|
-
return {
|
|
375
|
-
__mockTag: 'webgpu-canvas-context',
|
|
376
|
-
configure: () => undefined,
|
|
377
|
-
unconfigure: () => undefined,
|
|
378
|
-
getCurrentTexture: () => ({ createView: () => ({}) }),
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
if (kind === 'webgl2') {
|
|
382
|
-
return {
|
|
383
|
-
__mockTag: 'webgl2',
|
|
384
|
-
getExtension: () => null,
|
|
385
|
-
getParameter: () => 1,
|
|
386
|
-
isContextLost: () => false,
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
return null;
|
|
390
|
-
},
|
|
391
|
-
addEventListener: () => undefined,
|
|
392
|
-
removeEventListener: () => undefined,
|
|
393
|
-
} as Partial<HTMLCanvasElement> as HTMLCanvasElement;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
vi.mock('@forgeax/engine-rhi-webgpu', async () => {
|
|
397
|
-
const actualRhi =
|
|
398
|
-
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
399
|
-
const fakeAdapter = {
|
|
400
|
-
features: new Set<string>(),
|
|
401
|
-
limits: {} as Readonly<Record<string, number>>,
|
|
402
|
-
async requestDevice() {
|
|
403
|
-
return actualRhi.ok(makeFakeRhiDevice());
|
|
404
|
-
},
|
|
405
|
-
};
|
|
406
|
-
return {
|
|
407
|
-
rhi: {
|
|
408
|
-
requestAdapter: async () => actualRhi.ok(fakeAdapter),
|
|
409
|
-
acquireCanvasContext: (_canvas: unknown) =>
|
|
410
|
-
actualRhi.ok({
|
|
411
|
-
configure: () => actualRhi.ok(undefined),
|
|
412
|
-
unconfigure: () => actualRhi.ok(undefined),
|
|
413
|
-
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
414
|
-
}),
|
|
415
|
-
},
|
|
416
|
-
createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
|
|
417
|
-
};
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
vi.mock('@forgeax/engine-rhi-wgpu', async () => {
|
|
421
|
-
const actualRhi =
|
|
422
|
-
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
423
|
-
const fakeAdapter = {
|
|
424
|
-
features: new Set<string>(),
|
|
425
|
-
limits: {} as Readonly<Record<string, number>>,
|
|
426
|
-
async requestDevice() {
|
|
427
|
-
return actualRhi.ok(makeFakeRhiDevice());
|
|
428
|
-
},
|
|
429
|
-
};
|
|
430
|
-
return {
|
|
431
|
-
rhi: {
|
|
432
|
-
requestAdapter: async () => actualRhi.ok(fakeAdapter),
|
|
433
|
-
acquireCanvasContext: (_canvas: unknown) =>
|
|
434
|
-
actualRhi.ok({
|
|
435
|
-
configure: () => actualRhi.ok(undefined),
|
|
436
|
-
unconfigure: () => actualRhi.ok(undefined),
|
|
437
|
-
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
438
|
-
}),
|
|
439
|
-
},
|
|
440
|
-
ensureReady: async () => undefined,
|
|
441
|
-
};
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
let makeGPUSpy: ReturnType<typeof makeStubGPU>;
|
|
445
|
-
function makeStubGPU() {
|
|
446
|
-
return {
|
|
447
|
-
requestAdapter: async () => ({
|
|
448
|
-
features: new Set<string>(),
|
|
449
|
-
limits: {},
|
|
450
|
-
requestDevice: async () => makeFakeRhiDevice(),
|
|
451
|
-
}),
|
|
452
|
-
getPreferredCanvasFormat: () => 'bgra8unorm',
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Minimal shader manifest as data URL — provides a manifest that the
|
|
457
|
-
// ShaderRegistry can parse without HTTP requests, following the
|
|
458
|
-
// bind-group-cache-frame.test.ts pattern. Includes a pbr stub with the
|
|
459
|
-
// `f_schlick` content marker so `buildReadyWebGPU` finds the PBR entry.
|
|
460
|
-
const HEALTH_TEST_MANIFEST_URL = (() => {
|
|
461
|
-
const manifest = {
|
|
462
|
-
schemaVersion: '1.0.0',
|
|
463
|
-
entries: [
|
|
464
|
-
{
|
|
465
|
-
hash: 'pbr00000',
|
|
466
|
-
wgsl: '/* pbr stub - calls f_schlick( */',
|
|
467
|
-
glsl: '',
|
|
468
|
-
bindings: '',
|
|
469
|
-
},
|
|
470
|
-
],
|
|
471
|
-
materialShaders: [],
|
|
472
|
-
};
|
|
473
|
-
return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
|
|
474
|
-
})();
|
|
475
|
-
|
|
476
|
-
describe('Renderer health/recover integration (M2 RED)', () => {
|
|
477
|
-
beforeEach(() => {
|
|
478
|
-
makeGPUSpy = makeStubGPU();
|
|
479
|
-
vi.stubGlobal('navigator', { ...navigator, gpu: makeGPUSpy });
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
afterEach(() => {
|
|
483
|
-
vi.unstubAllGlobals();
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// ── w8: health() healthy-state baseline ──────────────────────────────────
|
|
487
|
-
|
|
488
|
-
it('w8: health() returns alive baseline when registry has not been fired', async () => {
|
|
489
|
-
const canvas = makeMockCanvas();
|
|
490
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
491
|
-
const renderer = await createRenderer(
|
|
492
|
-
canvas,
|
|
493
|
-
{ rhi: makeExplicitRhi() },
|
|
494
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
495
|
-
);
|
|
496
|
-
|
|
497
|
-
// TDD RED: renderer.health() does not exist on Renderer until w11.
|
|
498
|
-
// After w13, this should return { reason: 'alive', recoverable: false }.
|
|
499
|
-
const snapshot = (
|
|
500
|
-
(renderer as unknown as Record<string, unknown>).health as () => HealthSnapshot
|
|
501
|
-
)?.();
|
|
502
|
-
if (!snapshot) {
|
|
503
|
-
// RED guard: health() not yet implemented — test fails here until w13.
|
|
504
|
-
throw new Error('TDD RED: renderer.health() not implemented yet (expected after w11+w13)');
|
|
505
|
-
}
|
|
506
|
-
expect(snapshot.reason).toBe('alive');
|
|
507
|
-
expect(snapshot.recoverable).toBe(false);
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// ── w9: recover() healthy-state returns recover-not-needed (AC-04) ────────
|
|
511
|
-
//
|
|
512
|
-
// feat-20260622-s5 M3 / w19 migration: recover() is now async (device rebuild
|
|
513
|
-
// is async). The alive-state guard returns recover-not-needed synchronously
|
|
514
|
-
// inside the Promise. The full rebuild success/failure-code matrix lives in
|
|
515
|
-
// renderer-recover.unit.test.ts (w10-w14); this stays a health-surface smoke.
|
|
516
|
-
|
|
517
|
-
it('w9: recover() on healthy renderer returns recover-not-needed error', async () => {
|
|
518
|
-
const canvas = makeMockCanvas();
|
|
519
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
520
|
-
const renderer = await createRenderer(
|
|
521
|
-
canvas,
|
|
522
|
-
{ rhi: makeExplicitRhi() },
|
|
523
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
const recoverFn = (renderer as unknown as Record<string, unknown>).recover as
|
|
527
|
-
| (() => Promise<Result<void, RecoverError>>)
|
|
528
|
-
| undefined;
|
|
529
|
-
if (!recoverFn) {
|
|
530
|
-
throw new Error('renderer.recover() missing');
|
|
531
|
-
}
|
|
532
|
-
const result = await recoverFn();
|
|
533
|
-
expect(result.ok).toBe(false);
|
|
534
|
-
if (!result.ok) {
|
|
535
|
-
expect(result.error.code).toBe('recover-not-needed');
|
|
536
|
-
expect(typeof result.error.expected).toBe('string');
|
|
537
|
-
expect(result.error.expected.length).toBeGreaterThan(0);
|
|
538
|
-
expect(typeof result.error.hint).toBe('string');
|
|
539
|
-
expect(result.error.hint.length).toBeGreaterThan(0);
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
// ── w10: recover() on degraded state attempts a rebuild, never fakes alive ─
|
|
544
|
-
//
|
|
545
|
-
// feat-20260622-s5 M3 / w19 migration: the S3 placeholder behaviour
|
|
546
|
-
// (recover-not-implemented, health unchanged) is gone — recover() now runs a
|
|
547
|
-
// single idempotent rebuild. On this minimal single-entry HEALTH manifest the
|
|
548
|
-
// rebuild's engine-SSOT-triple check fails, so recover() resolves
|
|
549
|
-
// recover-device-unavailable and LEAVES health at device-lost (A-AC-07: never
|
|
550
|
-
// fakes the renderer back to alive on failure). The success path (full triple
|
|
551
|
-
// manifest -> ok + alive) is covered in renderer-recover.unit.test.ts (w10).
|
|
552
|
-
|
|
553
|
-
it('w10: recover() on degraded renderer attempts rebuild; failure keeps health device-lost', async () => {
|
|
554
|
-
const canvas = makeMockCanvas();
|
|
555
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
556
|
-
const renderer = await createRenderer(
|
|
557
|
-
canvas,
|
|
558
|
-
{ rhi: makeExplicitRhi() },
|
|
559
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
const healthFn = (renderer as unknown as Record<string, unknown>).health as
|
|
563
|
-
| (() => HealthSnapshot)
|
|
564
|
-
| undefined;
|
|
565
|
-
if (!healthFn) {
|
|
566
|
-
throw new Error('renderer.health() missing');
|
|
567
|
-
}
|
|
568
|
-
const recoverFn = (renderer as unknown as Record<string, unknown>).recover as
|
|
569
|
-
| (() => Promise<Result<void, RecoverError>>)
|
|
570
|
-
| undefined;
|
|
571
|
-
if (!recoverFn) {
|
|
572
|
-
throw new Error('renderer.recover() missing');
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Drive into degraded state via device.lost (same mock path as w14).
|
|
576
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
577
|
-
_testDeviceLostResolve({
|
|
578
|
-
reason: 'unknown',
|
|
579
|
-
message: 'test device lost for w10 degraded-state',
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
await vi.waitFor(() => {
|
|
583
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
584
|
-
});
|
|
585
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
586
|
-
|
|
587
|
-
// recover() runs a single rebuild attempt. The reserved recover-not-implemented
|
|
588
|
-
// is never produced anymore; on this minimal manifest the rebuild fails with a
|
|
589
|
-
// device-unavailability code.
|
|
590
|
-
const result = await recoverFn();
|
|
591
|
-
expect(result.ok).toBe(false);
|
|
592
|
-
if (!result.ok) {
|
|
593
|
-
expect(result.error.code).not.toBe('recover-not-implemented');
|
|
594
|
-
expect(result.error.code).toBe('recover-device-unavailable');
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// A-AC-07: failure never fakes the renderer back to alive.
|
|
598
|
-
const snapAfter = healthFn();
|
|
599
|
-
expect(snapAfter.reason).toBe('device-lost');
|
|
600
|
-
expect(snapAfter.recoverable).toBe(true);
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
605
|
-
// M2 draw guard tests (w7 — TDD RED)
|
|
606
|
-
//
|
|
607
|
-
// These tests verify device-lost draw() silent return + disposed latch. They are
|
|
608
|
-
// RED until w9 adds the health-state guard in draw().
|
|
609
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
610
24
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
makeGPUSpy = makeStubGPU();
|
|
614
|
-
vi.stubGlobal('navigator', { ...navigator, gpu: makeGPUSpy });
|
|
25
|
+
const snapshot: HealthSnapshot = { reason: 'alive', recoverable: false };
|
|
26
|
+
expect(describeReason(snapshot.reason)).toBe('alive');
|
|
615
27
|
});
|
|
616
28
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
29
|
+
it('uses inspect for the public ready state and recover for an explicit retry', async () => {
|
|
30
|
+
const renderer = await requireRenderer(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
31
|
+
expect(renderer.inspect().state).toBe('alive');
|
|
32
|
+
const recovery = await renderer.recover();
|
|
33
|
+
expect(recovery.ok).toBe(false);
|
|
34
|
+
if (!recovery.ok) expect(recovery.error.code).toBe('renderer-state-invalid');
|
|
35
|
+
await renderer.dispose();
|
|
620
36
|
});
|
|
621
37
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
canvas,
|
|
629
|
-
{ rhi: makeExplicitRhi() },
|
|
630
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
631
|
-
);
|
|
632
|
-
|
|
633
|
-
const healthFn = (renderer as unknown as Record<string, unknown>).health as
|
|
634
|
-
| (() => HealthSnapshot)
|
|
635
|
-
| undefined;
|
|
636
|
-
if (!healthFn) {
|
|
637
|
-
throw new Error('TDD RED: renderer.health() not found');
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// Subscribe to onError to verify no per-frame fire
|
|
641
|
-
const onErrorFn = (renderer as unknown as Record<string, unknown>).onError as
|
|
642
|
-
| ((cb: (err: { code: string }) => void) => () => void)
|
|
643
|
-
| undefined;
|
|
644
|
-
if (!onErrorFn) {
|
|
645
|
-
throw new Error('TDD RED: renderer.onError() not found');
|
|
646
|
-
}
|
|
647
|
-
const errorCb = vi.fn<(err: { code: string }) => void>();
|
|
648
|
-
onErrorFn(errorCb);
|
|
649
|
-
|
|
650
|
-
// Need a World with Camera to reach draw internals
|
|
651
|
-
const { World } = await import('@forgeax/engine-ecs');
|
|
652
|
-
const { Transform } = await import('@forgeax/engine-scene');
|
|
653
|
-
const { Camera } = await import('@forgeax/engine-render/internal');
|
|
654
|
-
const world = new World();
|
|
655
|
-
world.spawn(
|
|
656
|
-
{
|
|
657
|
-
component: Transform,
|
|
658
|
-
data: {
|
|
659
|
-
pos: [0, 0, 10],
|
|
660
|
-
quat: [0, 0, 0, 1],
|
|
661
|
-
scale: [1, 1, 1],
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
component: Camera,
|
|
666
|
-
data: {
|
|
667
|
-
fov: Math.PI / 4,
|
|
668
|
-
aspect: 1,
|
|
669
|
-
near: 0.1,
|
|
670
|
-
far: 100,
|
|
671
|
-
clearColor: [0, 0, 0, 1],
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
);
|
|
675
|
-
|
|
676
|
-
await renderer.ready;
|
|
677
|
-
|
|
678
|
-
// Baseline: health is alive
|
|
679
|
-
expect(healthFn().reason).toBe('alive');
|
|
680
|
-
|
|
681
|
-
// Trigger device-lost
|
|
682
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
683
|
-
_testDeviceLostResolve({
|
|
684
|
-
reason: 'unknown',
|
|
685
|
-
message: 'device lost for draw guard test',
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
await vi.waitFor(() => {
|
|
689
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
// Reset error spy counter after lost event (the lost channel itself fires onError once)
|
|
693
|
-
errorCb.mockClear();
|
|
694
|
-
|
|
695
|
-
// Draw N=5 times in device-lost state
|
|
696
|
-
for (let i = 0; i < 5; i++) {
|
|
697
|
-
const result = renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
|
|
698
|
-
// TDD RED / AC-10: draw returns err silently (not ok for device-lost)
|
|
699
|
-
expect(result.ok).toBe(false);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// TDD RED / AC-10: onError was NOT fired during those 5 draws (silent return)
|
|
703
|
-
// Current behavior (w7 RED): draw fires onError each frame through the normal
|
|
704
|
-
// device-lost flow (dual-channel), or the disposed/rhi-not-available path fires.
|
|
705
|
-
// After w9 fix: zero onError fires for draw() during device-lost.
|
|
706
|
-
expect(errorCb).not.toHaveBeenCalled();
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
// ── w7(b): disposed latch — post-dispose lost callback is no-op ────────────
|
|
710
|
-
|
|
711
|
-
it('w7(b): disposed latch prevents reconstruction on device-lost event', async () => {
|
|
712
|
-
const canvas = makeMockCanvas();
|
|
713
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
714
|
-
const renderer = await createRenderer(
|
|
715
|
-
canvas,
|
|
716
|
-
{ rhi: makeExplicitRhi() },
|
|
717
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
// TDD RED: dispose then trigger lost — should not try to reconstruct
|
|
721
|
-
const disposeFn = (renderer as unknown as Record<string, unknown>).dispose as
|
|
722
|
-
| (() => void)
|
|
723
|
-
| undefined;
|
|
724
|
-
if (!disposeFn) {
|
|
725
|
-
throw new Error('TDD RED: renderer.dispose() not found');
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
disposeFn();
|
|
729
|
-
|
|
730
|
-
// After dispose, trigger device.lost (should be no-op, no throw)
|
|
731
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
732
|
-
_testDeviceLostResolve({
|
|
733
|
-
reason: 'destroyed',
|
|
734
|
-
message: 'lost after dispose',
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
// Allow microtask flush — no assertion needed beyond "doesn't throw/crash"
|
|
738
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
739
|
-
|
|
740
|
-
// TDD RED: disposed renderer.draw returns err
|
|
741
|
-
const { World } = await import('@forgeax/engine-ecs');
|
|
742
|
-
const world = new World();
|
|
743
|
-
const result = renderer.draw([world], { cameraOwner: 0, resourceOwner: 0 });
|
|
744
|
-
expect(result.ok).toBe(false);
|
|
745
|
-
});
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
749
|
-
// M3 Integration tests (w14 / w15 — TDD RED)
|
|
750
|
-
//
|
|
751
|
-
// These tests verify the device.lost health channel wire. They are RED until
|
|
752
|
-
// w16 adds the healthRegistry.fire() call in device.lost.then().
|
|
753
|
-
// ──────────────────────────────────────────────────────────────────────────────
|
|
754
|
-
|
|
755
|
-
describe('Renderer health device.lost integration (M3 RED)', () => {
|
|
756
|
-
beforeEach(() => {
|
|
757
|
-
makeGPUSpy = makeStubGPU();
|
|
758
|
-
vi.stubGlobal('navigator', { ...navigator, gpu: makeGPUSpy });
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
afterEach(() => {
|
|
762
|
-
vi.unstubAllGlobals();
|
|
763
|
-
_testDeviceLostResolve = null;
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// ── w14: device.lost health update (AC-10) ─────────────────────────────────
|
|
767
|
-
|
|
768
|
-
it('w14: device.lost reason=unknown sets health().reason to device-lost with correct detail + recoverable', async () => {
|
|
769
|
-
const canvas = makeMockCanvas();
|
|
770
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
771
|
-
const renderer = await createRenderer(
|
|
772
|
-
canvas,
|
|
773
|
-
{ rhi: makeExplicitRhi() },
|
|
774
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
775
|
-
);
|
|
776
|
-
|
|
777
|
-
// TDD RED: health() does not exist on Renderer until w11.
|
|
778
|
-
const healthFn = (renderer as unknown as Record<string, unknown>).health as
|
|
779
|
-
| (() => HealthSnapshot)
|
|
780
|
-
| undefined;
|
|
781
|
-
if (!healthFn) {
|
|
782
|
-
throw new Error('TDD RED: renderer.health() not implemented yet');
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// Baseline: alive before device loss
|
|
786
|
-
expect(healthFn().reason).toBe('alive');
|
|
787
|
-
|
|
788
|
-
// Trigger device.lost via the module-level resolver hook. reason 'unknown'
|
|
789
|
-
// = genuine unrecoverable loss (driver crash / TDR) which DOES flip health
|
|
790
|
-
// to device-lost. (reason 'destroyed' = intentional teardown, gated -- see
|
|
791
|
-
// w14b below.)
|
|
792
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
793
|
-
_testDeviceLostResolve({
|
|
794
|
-
reason: 'unknown',
|
|
795
|
-
message: 'device lost (driver crash)',
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
// Allow the microtask queue to flush so device.lost.then() fires
|
|
799
|
-
await vi.waitFor(() => {
|
|
800
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
const snap = healthFn();
|
|
804
|
-
expect(snap.reason).toBe('device-lost');
|
|
805
|
-
expect(snap.recoverable).toBe(true);
|
|
806
|
-
|
|
807
|
-
// TS discriminated union narrows snap: in 'device-lost' branch detail is
|
|
808
|
-
// HealthDetailDeviceLost with required lostReason (AC-05, no toHaveProperty bypass).
|
|
809
|
-
if (snap.reason === 'device-lost') {
|
|
810
|
-
expect(snap.detail.lostReason).toBe('unknown');
|
|
811
|
-
expect(snap.detail.message).toBe('device lost (driver crash)');
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
it('w14: device.lost with unknown reason sets lostReason to unknown', async () => {
|
|
816
|
-
const canvas = makeMockCanvas();
|
|
817
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
818
|
-
const renderer = await createRenderer(
|
|
819
|
-
canvas,
|
|
820
|
-
{ rhi: makeExplicitRhi() },
|
|
821
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
822
|
-
);
|
|
823
|
-
|
|
824
|
-
const healthFn = (renderer as unknown as Record<string, unknown>).health as
|
|
825
|
-
| (() => HealthSnapshot)
|
|
826
|
-
| undefined;
|
|
827
|
-
if (!healthFn) {
|
|
828
|
-
throw new Error('TDD RED: renderer.health() not implemented yet');
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Device lost with reason: 'unknown'
|
|
832
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
833
|
-
_testDeviceLostResolve({
|
|
834
|
-
reason: 'unknown',
|
|
835
|
-
message: '',
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
await vi.waitFor(() => {
|
|
839
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
const snap = healthFn();
|
|
843
|
-
if (snap.reason === 'device-lost') {
|
|
844
|
-
expect(snap.detail.lostReason).toBe('unknown');
|
|
845
|
-
}
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
// ── w15: onHealthChange callback triggered on device.lost (AC-10) ──────────
|
|
849
|
-
|
|
850
|
-
it('w15: onHealthChange callback called exactly once on device.lost', async () => {
|
|
851
|
-
const canvas = makeMockCanvas();
|
|
852
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
853
|
-
const renderer = await createRenderer(
|
|
854
|
-
canvas,
|
|
855
|
-
{ rhi: makeExplicitRhi() },
|
|
856
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
857
|
-
);
|
|
858
|
-
|
|
859
|
-
const onHealthChangeFn = (renderer as unknown as Record<string, unknown>).onHealthChange as
|
|
860
|
-
| ((cb: HealthChangeListener) => () => void)
|
|
861
|
-
| undefined;
|
|
862
|
-
if (!onHealthChangeFn) {
|
|
863
|
-
throw new Error('TDD RED: renderer.onHealthChange() not implemented yet');
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const cb = vi.fn<HealthChangeListener>();
|
|
867
|
-
onHealthChangeFn(cb);
|
|
868
|
-
|
|
869
|
-
// Before device loss, cb should not have been called
|
|
870
|
-
expect(cb).not.toHaveBeenCalled();
|
|
871
|
-
|
|
872
|
-
// Trigger device.lost
|
|
873
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
874
|
-
_testDeviceLostResolve({ reason: 'unknown', message: 'lost' });
|
|
875
|
-
|
|
876
|
-
// Wait for the health channel to fire
|
|
877
|
-
await vi.waitFor(() => {
|
|
878
|
-
expect(cb).toHaveBeenCalled();
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
// Called exactly once
|
|
882
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
883
|
-
|
|
884
|
-
// Callback received the correct snapshot
|
|
885
|
-
const snap = cb.mock.calls[0]?.[0];
|
|
886
|
-
expect(snap).toBeDefined();
|
|
887
|
-
if (snap) {
|
|
888
|
-
expect(snap.reason).toBe('device-lost');
|
|
889
|
-
expect(snap.recoverable).toBe(true);
|
|
890
|
-
}
|
|
891
|
-
});
|
|
892
|
-
|
|
893
|
-
it('w15: late-attach replay after device.lost fires immediately', async () => {
|
|
894
|
-
const canvas = makeMockCanvas();
|
|
895
|
-
const { createRenderer } = await import('../../../render/src/renderer/renderer-factory');
|
|
896
|
-
const renderer = await createRenderer(
|
|
897
|
-
canvas,
|
|
898
|
-
{ rhi: makeExplicitRhi() },
|
|
899
|
-
{ shaderManifestUrl: HEALTH_TEST_MANIFEST_URL },
|
|
900
|
-
);
|
|
901
|
-
|
|
902
|
-
// Trigger device.lost BEFORE registering the callback
|
|
903
|
-
if (!_testDeviceLostResolve) throw new Error('device.lost resolver not set up');
|
|
904
|
-
_testDeviceLostResolve({ reason: 'unknown', message: 'late-attach test' });
|
|
905
|
-
|
|
906
|
-
// Flush the microtask so healthRegistry.fire() completes
|
|
907
|
-
const healthFn = (renderer as unknown as Record<string, unknown>).health as
|
|
908
|
-
| (() => HealthSnapshot)
|
|
909
|
-
| undefined;
|
|
910
|
-
if (!healthFn) {
|
|
911
|
-
throw new Error('TDD RED: renderer.health() not implemented yet');
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
await vi.waitFor(() => {
|
|
915
|
-
expect(healthFn().reason).toBe('device-lost');
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
// Now register callback — late-attach replay should fire immediately
|
|
919
|
-
const onHealthChangeFn = (renderer as unknown as Record<string, unknown>).onHealthChange as
|
|
920
|
-
| ((cb: HealthChangeListener) => () => void)
|
|
921
|
-
| undefined;
|
|
922
|
-
if (!onHealthChangeFn) {
|
|
923
|
-
throw new Error('TDD RED: renderer.onHealthChange() not implemented yet');
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
const cb = vi.fn<HealthChangeListener>();
|
|
927
|
-
onHealthChangeFn(cb);
|
|
928
|
-
|
|
929
|
-
// Called immediately via late-attach replay
|
|
930
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
931
|
-
const snap = cb.mock.calls[0]?.[0];
|
|
932
|
-
expect(snap?.reason).toBe('device-lost');
|
|
933
|
-
expect(snap?.recoverable).toBe(true);
|
|
38
|
+
it('does not expose the removed health or device getters', async () => {
|
|
39
|
+
const renderer = await requireRenderer(canvas(), { rhi }, { shaderManifestUrl: manifest });
|
|
40
|
+
expect(renderer).not.toHaveProperty('health');
|
|
41
|
+
expect(renderer).not.toHaveProperty('device');
|
|
42
|
+
expect(renderer).not.toHaveProperty('ready');
|
|
43
|
+
renderer.dispose();
|
|
934
44
|
});
|
|
935
45
|
});
|