@forgeax/engine-runtime 0.1.23 → 0.1.24
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 +15 -6
- package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/advance-animation-player.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-aabb.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-bootstrap.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-builtin-nineslice.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-guid.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-material-validate.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-scene.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-renderer-assembly.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/asset.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/bloom-gating.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/bloom-gating.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/builtin-guid-ssot.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-pack.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/builtin-pack.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-antialias.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-antialias.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-bloom.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-bloom.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-clear-schema.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/camera-ortho.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-ortho.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/cluster-binner.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/cluster-binner.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-uniform-fallback.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/cubemap-upload.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/dev-import-transport.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/engine-metrics.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/engine-metrics.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/equirect-projection-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/font-asset-load.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/font-asset-load.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/fxaa-intermediate-texture.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/fxaa-pipeline.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-residency.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-residency.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-release.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/handle-quad.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/handle-quad.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-bgl-slots.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-grid-invalid.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-index-list-overflow-once.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-light-budget.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-pipeline-asset-config.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts +25 -0
- package/dist/__tests__/helpers/renderer-device-loss-dawn.d.ts.map +1 -0
- package/dist/__tests__/helpers/standard-material-manifest.d.ts +0 -1
- package/dist/__tests__/helpers/standard-material-manifest.d.ts.map +1 -1
- package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/ibl-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/lazy-catalog.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-hdr.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-prod-material-parent.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/load-by-guid-prod.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-registry.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-registry.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/m7-hdrp-demo-shape.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-formula.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-asset-payload-material.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-asset-payload-texture.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/parse-scene-payload-refs.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pass-selector.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pass-selector.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-builder.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-cache-key-topology.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-vertex-stride-branch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-register.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-register.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/propagate-transforms.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-all-topology.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-all-topology.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-draw-branch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/record-strip-index-format.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/register-with-guid-rgba16float.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-data.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-data.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -1
- package/dist/__tests__/render-query-regression.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-query-regression.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-submesh.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-warn-once.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-multi-world.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-provider-feasibility.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts +2 -0
- package/dist/__tests__/renderer-device-loss-recovery.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-draw-world.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-recovery-consumer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/renderstate-pipeline-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-scene-guids.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/shadow-skip-non-triangle.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-errors-kebab-case.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-instances-coexist.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-happy.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-joint-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-null-assets.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-m3-record-cache.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-palette-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-pipeline-routing.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skin.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skybox-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skybox-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skybox-shader-compile.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-bind-group.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-component.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-component.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-fallback-path.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-pipeline-layout.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/skylight-solid-color-extract.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-boundary.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-clamp.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-loop.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-override-probe.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/storage-buffer-caps.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/systems.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/systems.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap-hdr-target.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap-pipeline-split.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tonemap.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/tonemap.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort-config-get.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort-config-set.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/upload-texture-consistency.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/verify-revisions.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/verify-revisions.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/wire-default-loaders.cases.unit.test.d.ts.map +1 -0
- package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts +2 -0
- package/dist/__tests__/zero-camera-clear-fallback.cases.unit.test.d.ts.map +1 -0
- package/dist/dev-import-transport.d.ts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +30 -30
- package/src/__tests__/advance-animation-player.cases.unit.test.ts +1666 -0
- package/src/__tests__/asset-registry-aabb.cases.unit.test.ts +676 -0
- package/src/__tests__/asset-registry-bootstrap.cases.unit.test.ts +472 -0
- package/src/__tests__/asset-registry-builtin-nineslice.cases.unit.test.ts +481 -0
- package/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.cases.unit.test.ts +568 -0
- package/src/__tests__/asset-registry-guid.cases.unit.test.ts +500 -0
- package/src/__tests__/asset-registry-material-validate.cases.unit.test.ts +756 -0
- package/src/__tests__/asset-registry-mesh-fail-fast.cases.unit.test.ts +591 -0
- package/src/__tests__/asset-registry-scene.cases.unit.test.ts +501 -0
- package/src/__tests__/asset-registry-sprite-slices-validate.cases.unit.test.ts +536 -0
- package/src/__tests__/asset-renderer-assembly.cases.unit.test.ts +496 -0
- package/src/__tests__/asset.cases.unit.test.ts +439 -0
- package/src/__tests__/bind-group-cache-keying.test.ts +6 -2
- package/src/__tests__/bloom-gating.cases.unit.test.ts +428 -0
- package/src/__tests__/builtin-guid-ssot.cases.unit.test.ts +472 -0
- package/src/__tests__/builtin-pack.cases.unit.test.ts +639 -0
- package/src/__tests__/builtin-shader-register-e2e.test.ts +1 -1
- package/src/__tests__/camera-antialias.cases.unit.test.ts +372 -0
- package/src/__tests__/camera-bloom.cases.unit.test.ts +396 -0
- package/src/__tests__/camera-clear-schema.cases.unit.test.ts +391 -0
- package/src/__tests__/camera-ortho.cases.unit.test.ts +484 -0
- package/src/__tests__/cluster-binner.cases.unit.test.ts +817 -0
- package/src/__tests__/collect-scene-mount-collapse.test.ts +5 -1
- package/src/__tests__/create-renderer-fallback-shader-manifest.cases.unit.test.ts +345 -0
- package/src/__tests__/create-renderer-uniform-fallback.cases.unit.test.ts +445 -0
- package/src/__tests__/cubemap-upload.cases.unit.test.ts +593 -0
- package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +184 -17
- package/src/__tests__/dawn/material-alpha.dawn.test.ts +17 -4
- package/src/__tests__/derive-bgl-integration.test.ts +8 -8
- package/src/__tests__/dev-import-transport.cases.unit.test.ts +503 -0
- package/src/__tests__/engine-metrics.cases.unit.test.ts +371 -0
- package/src/__tests__/equirect-projection-error.cases.unit.test.ts +416 -0
- package/src/__tests__/font-asset-load.cases.unit.test.ts +510 -0
- package/src/__tests__/fxaa-intermediate-texture.cases.unit.test.ts +690 -0
- package/src/__tests__/fxaa-pipeline.cases.unit.test.ts +546 -0
- package/src/__tests__/gpu-residency.cases.unit.test.ts +479 -0
- package/src/__tests__/gpu-resource-release.cases.unit.test.ts +929 -0
- package/src/__tests__/handle-quad.cases.unit.test.ts +491 -0
- package/src/__tests__/hdrp-bgl-slots.cases.unit.test.ts +451 -0
- package/src/__tests__/hdrp-grid-invalid.cases.unit.test.ts +408 -0
- package/src/__tests__/hdrp-index-list-overflow-once.cases.unit.test.ts +405 -0
- package/src/__tests__/hdrp-light-budget.cases.unit.test.ts +483 -0
- package/src/__tests__/hdrp-pipeline-asset-config.cases.unit.test.ts +441 -0
- package/src/__tests__/helpers/renderer-device-loss-dawn.ts +69 -0
- package/src/__tests__/helpers/standard-material-manifest.ts +11 -15
- package/src/__tests__/ibl-pipeline-cache.cases.unit.test.ts +415 -0
- package/src/__tests__/lazy-catalog.cases.unit.test.ts +848 -0
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +6 -5
- package/src/__tests__/load-by-guid-hdr.cases.unit.test.ts +582 -0
- package/src/__tests__/load-by-guid-prod-material-parent.cases.unit.test.ts +1514 -0
- package/src/__tests__/load-by-guid-prod.cases.unit.test.ts +557 -0
- package/src/__tests__/loader-registry.cases.unit.test.ts +767 -0
- package/src/__tests__/m7-hdrp-demo-shape.cases.unit.test.ts +361 -0
- package/src/__tests__/material-texture-uv-scale.unit.test.ts +2 -2
- package/src/__tests__/materials.unit.test.ts +29 -10
- package/src/__tests__/mipmap-formula.cases.unit.test.ts +434 -0
- package/src/__tests__/mipmap-pipeline-cache.cases.unit.test.ts +490 -0
- package/src/__tests__/parse-asset-payload-material.cases.unit.test.ts +565 -0
- package/src/__tests__/parse-asset-payload-texture.cases.unit.test.ts +720 -0
- package/src/__tests__/parse-scene-payload-refs.cases.unit.test.ts +767 -0
- package/src/__tests__/pass-selector.cases.unit.test.ts +376 -0
- package/src/__tests__/pbr-pipeline.unit.test.ts +23 -5
- package/src/__tests__/pipeline-builder.cases.unit.test.ts +624 -0
- package/src/__tests__/pipeline-cache-key-topology.cases.unit.test.ts +807 -0
- package/src/__tests__/pipeline-vertex-stride-branch.cases.unit.test.ts +379 -0
- package/src/__tests__/pipeline.cases.unit.test.ts +370 -0
- package/src/__tests__/post-process-register.cases.unit.test.ts +405 -0
- package/src/__tests__/propagate-transforms.cases.unit.test.ts +502 -0
- package/src/__tests__/propagate-transforms.dawn.test.ts +19 -2
- package/src/__tests__/record-all-topology.cases.unit.test.ts +608 -0
- package/src/__tests__/record-draw-branch.cases.unit.test.ts +621 -0
- package/src/__tests__/record-strip-index-format.cases.unit.test.ts +419 -0
- package/src/__tests__/register-with-guid-rgba16float.cases.unit.test.ts +469 -0
- package/src/__tests__/render-data.cases.unit.test.ts +553 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +1 -0
- package/src/__tests__/render-query-regression.cases.unit.test.ts +531 -0
- package/src/__tests__/render-system-record-submesh.cases.unit.test.ts +740 -0
- package/src/__tests__/render-system-record-warn-once.cases.unit.test.ts +418 -0
- package/src/__tests__/render-system-record.test.ts +15 -33
- package/src/__tests__/renderer-device-loss-multi-world.integration.test.ts +103 -0
- package/src/__tests__/renderer-device-loss-provider-feasibility.dawn.test.ts +124 -0
- package/src/__tests__/renderer-device-loss-recovery.dawn.test.ts +50 -0
- package/src/__tests__/renderer-draw-world.cases.unit.test.ts +514 -0
- package/src/__tests__/renderer-recovery-consumer.integration.test.ts +96 -0
- package/src/__tests__/renderstate-pipeline-cache.cases.unit.test.ts +631 -0
- package/src/__tests__/resolve-scene-guids.cases.unit.test.ts +1123 -0
- package/src/__tests__/shadow-caster-empty-schema.test.ts +1 -1
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +2 -2
- package/src/__tests__/shadow-m1.dawn.test.ts +1 -1
- package/src/__tests__/shadow-skip-non-triangle.cases.unit.test.ts +681 -0
- package/src/__tests__/skin-errors-kebab-case.cases.unit.test.ts +475 -0
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +5 -4
- package/src/__tests__/skin-instances-coexist.cases.unit.test.ts +375 -0
- package/src/__tests__/skin-m2-extract-happy.cases.unit.test.ts +592 -0
- package/src/__tests__/skin-m2-extract-joint-error.cases.unit.test.ts +619 -0
- package/src/__tests__/skin-m2-extract-null-assets.cases.unit.test.ts +569 -0
- package/src/__tests__/skin-m2-extract-skeleton-error.cases.unit.test.ts +585 -0
- package/src/__tests__/skin-m3-record-cache.cases.unit.test.ts +457 -0
- package/src/__tests__/skin-palette-extract.cases.unit.test.ts +635 -0
- package/src/__tests__/skin-pipeline-routing.cases.unit.test.ts +411 -0
- package/src/__tests__/skin.cases.unit.test.ts +421 -0
- package/src/__tests__/skybox-extract.cases.unit.test.ts +511 -0
- package/src/__tests__/skybox-shader-compile.cases.unit.test.ts +437 -0
- package/src/__tests__/skylight-bind-group.cases.unit.test.ts +642 -0
- package/src/__tests__/skylight-component.cases.unit.test.ts +386 -0
- package/src/__tests__/skylight-fallback-path.cases.unit.test.ts +566 -0
- package/src/__tests__/skylight-lazy-projection.test.ts +35 -20
- package/src/__tests__/skylight-pipeline-layout.cases.unit.test.ts +506 -0
- package/src/__tests__/skylight-solid-color-extract.cases.unit.test.ts +399 -0
- package/src/__tests__/sprite-animation-tick-boundary.cases.unit.test.ts +521 -0
- package/src/__tests__/sprite-animation-tick-clamp.cases.unit.test.ts +453 -0
- package/src/__tests__/sprite-animation-tick-frame-duration-negative.cases.unit.test.ts +447 -0
- package/src/__tests__/sprite-animation-tick-frame-duration-zero.cases.unit.test.ts +395 -0
- package/src/__tests__/sprite-animation-tick-loop.cases.unit.test.ts +494 -0
- package/src/__tests__/sprite-animation-tick-override-probe.cases.unit.test.ts +439 -0
- package/src/__tests__/sprite-animation-tick-regions-mismatch.cases.unit.test.ts +466 -0
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +5 -5
- package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +1 -1
- package/src/__tests__/ssao-pipelines.test.ts +1 -1
- package/src/__tests__/storage-buffer-caps.cases.unit.test.ts +373 -0
- package/src/__tests__/systems.cases.unit.test.ts +425 -0
- package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +1 -1
- package/src/__tests__/tonemap-hdr-target.cases.unit.test.ts +670 -0
- package/src/__tests__/tonemap-pipeline-split.cases.unit.test.ts +867 -0
- package/src/__tests__/tonemap.cases.unit.test.ts +446 -0
- package/src/__tests__/transparent-sort-config-get.cases.unit.test.ts +395 -0
- package/src/__tests__/transparent-sort-config-set.cases.unit.test.ts +424 -0
- package/src/__tests__/transparent-sort.cases.unit.test.ts +684 -0
- package/src/__tests__/upload-texture-consistency.cases.unit.test.ts +478 -0
- package/src/__tests__/verify-revisions.cases.unit.test.ts +619 -0
- package/src/__tests__/wire-default-loaders.cases.unit.test.ts +461 -0
- package/src/__tests__/zero-camera-clear-fallback.cases.unit.test.ts +374 -0
- package/src/dev-import-transport.ts +2 -1
- package/dist/__tests__/asset.unit.test.d.ts +0 -2
- package/dist/__tests__/asset.unit.test.d.ts.map +0 -1
- package/dist/__tests__/pipeline.unit.test.d.ts +0 -2
- package/dist/__tests__/pipeline.unit.test.d.ts.map +0 -1
- package/dist/__tests__/systems.unit.test.d.ts +0 -2
- package/dist/__tests__/systems.unit.test.d.ts.map +0 -1
- package/src/__tests__/asset.unit.test.ts +0 -7113
- package/src/__tests__/pipeline.unit.test.ts +0 -5279
- package/src/__tests__/systems.unit.test.ts +0 -9061
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
// @ts-nocheck — merged file: indexed-access checks cascade across noUncheckedIndexedAccess for blocks originally outside src/ rootDir
|
|
2
|
+
// Consolidated by feat-20260609-test-pool-startup-reduction-merge-tiny-test-files
|
|
3
|
+
// biome-ignore-all lint/complexity/noUselessLoneBlockStatements: block-scope isolation between merged source files (consolidation paradigm)
|
|
4
|
+
//
|
|
5
|
+
// Source files (N=44):
|
|
6
|
+
// - packages/runtime/__tests__/antialias-from-f32.test.ts
|
|
7
|
+
// - packages/runtime/src/__tests__/bloom-gating.test.ts
|
|
8
|
+
// - packages/runtime/src/__tests__/camera-antialias.test.ts
|
|
9
|
+
// - packages/runtime/src/__tests__/camera-bloom.test.ts
|
|
10
|
+
// - packages/runtime/src/__tests__/camera-clear-schema.test.ts
|
|
11
|
+
// - packages/runtime/src/__tests__/camera-ortho.test.ts
|
|
12
|
+
// - packages/runtime/src/__tests__/fxaa-intermediate-texture.test.ts
|
|
13
|
+
// - packages/runtime/src/__tests__/fxaa-pipeline.test.ts
|
|
14
|
+
// - packages/runtime/src/__tests__/ibl-pipeline-cache.test.ts
|
|
15
|
+
// - packages/runtime/src/__tests__/ibl-runtime-probe.test.ts
|
|
16
|
+
// - packages/runtime/src/__tests__/render-system-record-warn-once.test.ts
|
|
17
|
+
// - packages/runtime/src/__tests__/shadow-skip-non-triangle.test.ts
|
|
18
|
+
// - packages/runtime/src/__tests__/skin-errors-kebab-case.test.ts
|
|
19
|
+
// - packages/runtime/src/__tests__/skybox-error.test.ts
|
|
20
|
+
// - packages/runtime/src/__tests__/skybox-shader-compile.test.ts
|
|
21
|
+
// - packages/runtime/src/__tests__/skylight-bind-group.test.ts
|
|
22
|
+
// - packages/runtime/src/__tests__/skylight-component.test.ts
|
|
23
|
+
// - packages/runtime/src/__tests__/skylight-fallback-path.test.ts
|
|
24
|
+
// - packages/runtime/src/__tests__/skylight-pipeline-layout.test.ts
|
|
25
|
+
// - packages/runtime/src/__tests__/tonemap-hdr-target.test.ts
|
|
26
|
+
// - packages/runtime/src/__tests__/tonemap-pipeline-split.test.ts
|
|
27
|
+
// - packages/runtime/src/__tests__/zero-camera-clear-fallback.test.ts
|
|
28
|
+
// - packages/runtime/src/components/__tests__/skin.test.ts
|
|
29
|
+
// - packages/runtime/src/systems/__tests__/advance-animation-player.test.ts
|
|
30
|
+
// - packages/runtime/src/systems/__tests__/graph-skybox.test.ts
|
|
31
|
+
// - packages/runtime/src/systems/__tests__/propagate-transforms.test.ts
|
|
32
|
+
// - packages/runtime/src/systems/__tests__/skin-cap-gate.test.ts
|
|
33
|
+
// - packages/runtime/src/systems/__tests__/skin-instances-coexist.test.ts
|
|
34
|
+
// - packages/runtime/src/systems/__tests__/skin-palette-extract.test.ts
|
|
35
|
+
// - packages/runtime/src/systems/__tests__/skin-pipeline-routing.test.ts
|
|
36
|
+
// - packages/runtime/src/systems/__tests__/skybox-extract.test.ts
|
|
37
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-boundary.test.ts
|
|
38
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-clamp.test.ts
|
|
39
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-frame-duration-negative.test.ts
|
|
40
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-frame-duration-zero.test.ts
|
|
41
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-loop.test.ts
|
|
42
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-override-probe.test.ts
|
|
43
|
+
// - packages/runtime/src/systems/__tests__/sprite-animation-tick-regions-mismatch.test.ts
|
|
44
|
+
// - packages/runtime/src/systems/__tests__/tonemap.test.ts
|
|
45
|
+
// - packages/runtime/src/systems/__tests__/transparent-sort-config-get.test.ts
|
|
46
|
+
// - packages/runtime/src/systems/__tests__/transparent-sort-config-set.test.ts
|
|
47
|
+
// - packages/runtime/src/systems/__tests__/transparent-sort.test.ts
|
|
48
|
+
// - packages/runtime/src/__tests__/render-system-multi-material.test.ts
|
|
49
|
+
// - packages/runtime/src/__tests__/render-system-record-submesh.test.ts
|
|
50
|
+
//
|
|
51
|
+
// Paradigm: each block-scope wraps a source file. ancestorTitles[0] is the
|
|
52
|
+
// source-preserved inner describe (NOT the source filename for these 3 files
|
|
53
|
+
// — recovery path: vitest report ancestorTitles -> grep this file -> upstream
|
|
54
|
+
// `// ─── from <name>.test.ts ───` block separator -> source filename).
|
|
55
|
+
// Top-level imports merged + deduped.
|
|
56
|
+
|
|
57
|
+
import { readFileSync } from 'node:fs';
|
|
58
|
+
import { resolve } from 'node:path';
|
|
59
|
+
import { fileURLToPath } from 'node:url';
|
|
60
|
+
import { AnimationPlayer } from '@forgeax/engine-animation';
|
|
61
|
+
import type { AssetRuntimeErrorCode } from '@forgeax/engine-assets-runtime';
|
|
62
|
+
import { AssetRegistry } from '@forgeax/engine-assets-runtime';
|
|
63
|
+
import type { EntityHandle, World as WorldType } from '@forgeax/engine-ecs';
|
|
64
|
+
import { ENTITY_NULL_RAW, World } from '@forgeax/engine-ecs';
|
|
65
|
+
import { SpriteAnimationInvalidError } from '@forgeax/engine-ecs/projection';
|
|
66
|
+
import { mat4, vec3 } from '@forgeax/engine-math';
|
|
67
|
+
import type { RenderErrorCode, Renderer as RendererType } from '@forgeax/engine-render';
|
|
68
|
+
import {
|
|
69
|
+
ANTIALIAS_FXAA,
|
|
70
|
+
ANTIALIAS_NONE,
|
|
71
|
+
BLOOM_DISABLED,
|
|
72
|
+
BLOOM_ENABLED,
|
|
73
|
+
CAMERA_PROJECTION_ORTHOGRAPHIC,
|
|
74
|
+
CAMERA_PROJECTION_PERSPECTIVE,
|
|
75
|
+
Camera,
|
|
76
|
+
MeshFilter,
|
|
77
|
+
MeshRenderer,
|
|
78
|
+
SkyboxBackground,
|
|
79
|
+
Skylight,
|
|
80
|
+
} from '@forgeax/engine-render';
|
|
81
|
+
import type { BindGroupEntry, Buffer, Sampler, Texture, TextureView } from '@forgeax/engine-rhi';
|
|
82
|
+
import { ok as rhiOk } from '@forgeax/engine-rhi';
|
|
83
|
+
import { ChildOf, Name, propagateTransforms, Transform } from '@forgeax/engine-scene';
|
|
84
|
+
import { TONEMAP_LUMINANCE_EPSILON } from '@forgeax/engine-shader';
|
|
85
|
+
import type { SkinErrorCode } from '@forgeax/engine-skinning';
|
|
86
|
+
import {
|
|
87
|
+
Skin,
|
|
88
|
+
SkinInstancesCoexistForbiddenError,
|
|
89
|
+
SkinJointCountExceededError,
|
|
90
|
+
SkinJointDespawnedError,
|
|
91
|
+
SkinJointPathUnresolvedError,
|
|
92
|
+
} from '@forgeax/engine-skinning';
|
|
93
|
+
import type {
|
|
94
|
+
Handle,
|
|
95
|
+
MaterialAsset,
|
|
96
|
+
MeshAsset,
|
|
97
|
+
SkeletonAsset,
|
|
98
|
+
TextureFormat,
|
|
99
|
+
} from '@forgeax/engine-types';
|
|
100
|
+
import { toShared } from '@forgeax/engine-types';
|
|
101
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
102
|
+
import { worldInternal } from '../../../ecs/src/world-internal';
|
|
103
|
+
import {
|
|
104
|
+
SPRITE_PLAYBACK_MODE_CLAMP,
|
|
105
|
+
SPRITE_PLAYBACK_MODE_LOOP,
|
|
106
|
+
SpriteAnimation,
|
|
107
|
+
SpriteRegionOverride,
|
|
108
|
+
} from '../../../render/src/components';
|
|
109
|
+
import {
|
|
110
|
+
type CameraProjection,
|
|
111
|
+
cameraProjectionFromF32,
|
|
112
|
+
} from '../../../render/src/components/camera';
|
|
113
|
+
import {
|
|
114
|
+
assembleMaterialWithSkylightEntries,
|
|
115
|
+
createSkylightFallback,
|
|
116
|
+
mergeSkylightIntoMaterialBgl,
|
|
117
|
+
} from '../../../render/src/ibl/skylight-bind-group';
|
|
118
|
+
import { buildPbrPipelineLayouts, buildUnlitMaterialBgl } from '../../../render/src/pbr-pipeline';
|
|
119
|
+
import { INSTANCE_STORAGE_STRIDE_FLOATS } from '../../../render/src/record/mesh-ssbo';
|
|
120
|
+
import { selectSwapChainFormat } from '../../../render/src/render-system';
|
|
121
|
+
import { createSkinPaletteAllocator } from '../../../render/src/systems/skin-palette-allocator';
|
|
122
|
+
import type { TransparentEntry } from '../../../render/src/systems/transparent-sort-config';
|
|
123
|
+
import { standardMaterialShaderVariants } from './helpers/standard-material-manifest';
|
|
124
|
+
import { drawWithOwners } from './renderer-test-utils';
|
|
125
|
+
|
|
126
|
+
function componentFieldType(component: { fields: Record<string, { type: string }> }, name: string) {
|
|
127
|
+
return component.fields[name]?.type;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function componentFieldDefault(
|
|
131
|
+
component: { fields: Record<string, { default?: unknown }> },
|
|
132
|
+
name: string,
|
|
133
|
+
) {
|
|
134
|
+
return component.fields[name]?.default;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function componentSchemaTypes(component: { fields: Record<string, { type: string }> }) {
|
|
138
|
+
return Object.fromEntries(
|
|
139
|
+
Object.entries(component.fields).map(([name, field]) => [name, field.type]),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
afterEach(() => vi.restoreAllMocks());
|
|
144
|
+
|
|
145
|
+
type RendererErrorObservation = {
|
|
146
|
+
readonly code: string;
|
|
147
|
+
readonly detail?: unknown;
|
|
148
|
+
readonly hint?: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
function unwrapRendererError(value: unknown): RendererErrorObservation {
|
|
152
|
+
let current = value as RendererErrorObservation;
|
|
153
|
+
while (
|
|
154
|
+
current.detail !== undefined &&
|
|
155
|
+
typeof current.detail === 'object' &&
|
|
156
|
+
current.detail !== null
|
|
157
|
+
) {
|
|
158
|
+
const cause = (current.detail as { cause?: unknown }).cause;
|
|
159
|
+
if (
|
|
160
|
+
cause === undefined ||
|
|
161
|
+
typeof cause !== 'object' ||
|
|
162
|
+
cause === null ||
|
|
163
|
+
typeof (cause as { code?: unknown }).code !== 'string'
|
|
164
|
+
) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
current = cause as RendererErrorObservation;
|
|
168
|
+
}
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function subscribeRendererErrors(
|
|
173
|
+
renderer: RendererType,
|
|
174
|
+
listener: (error: RendererErrorObservation) => void,
|
|
175
|
+
): () => void {
|
|
176
|
+
return renderer.subscribe((event) => {
|
|
177
|
+
if (event.kind === 'error') listener(unwrapRendererError(event.error));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function drawPublished(renderer: RendererType, world: WorldType) {
|
|
182
|
+
const attached = renderer.attach(world);
|
|
183
|
+
if (!attached.ok) throw attached.error;
|
|
184
|
+
world.update().unwrap();
|
|
185
|
+
return drawWithOwners(renderer, world);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// feat-20260704-runtime-tier1-decomposition M2 / w12: reconstitute the
|
|
189
|
+
// eliminated top-level RuntimeErrorCode aggregate union (D-3) as a test-local
|
|
190
|
+
// alias so the exhaustive-switch bodies below stay byte-identical (AC-09).
|
|
191
|
+
type RuntimeLayerErrorCode = RenderErrorCode | AssetRuntimeErrorCode | SkinErrorCode;
|
|
192
|
+
|
|
193
|
+
import {
|
|
194
|
+
AnimationTargetId,
|
|
195
|
+
bindAnimationTargets,
|
|
196
|
+
advanceAnimationPlayer as canonicalAdvanceAnimationPlayer,
|
|
197
|
+
} from '@forgeax/engine-animation';
|
|
198
|
+
import { deriveAnimationTargetId } from '@forgeax/engine-animation/target-id';
|
|
199
|
+
import { DeviceScope } from '../../../render/src/device/device-scope';
|
|
200
|
+
import { GpuBuffer } from '../../../render/src/gpu-resource';
|
|
201
|
+
import { getOrCreateIblCache, hasIblCache } from '../../../render/src/ibl/IblPipelineCache';
|
|
202
|
+
import {
|
|
203
|
+
disposeInstanceBuffers,
|
|
204
|
+
type InstanceBufferCacheEntry,
|
|
205
|
+
} from '../../../render/src/instance-buffer-cache';
|
|
206
|
+
import { standardPipeline as urpPipeline } from '../../../render/src/pipeline/standard-pipeline';
|
|
207
|
+
import { ZERO_CAMERA_CLEAR_FALLBACK } from '../../../render/src/record/frame-snapshot';
|
|
208
|
+
import {
|
|
209
|
+
warnMultiLightDirectional,
|
|
210
|
+
warnMultiLightPoint,
|
|
211
|
+
warnMultiLightSpot,
|
|
212
|
+
} from '../../../render/src/record/helpers';
|
|
213
|
+
import type { CameraSnapshot } from '../../../render/src/render-contract';
|
|
214
|
+
import {
|
|
215
|
+
type ExtractedLights,
|
|
216
|
+
extractFrame,
|
|
217
|
+
extractFrames,
|
|
218
|
+
prepareExtractContext,
|
|
219
|
+
} from '../../../render/src/render-system-extract';
|
|
220
|
+
import {
|
|
221
|
+
getTransparentSortConfig,
|
|
222
|
+
setTransparentSortConfig,
|
|
223
|
+
TRANSPARENT_SORT_CONFIG_KEY,
|
|
224
|
+
TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
225
|
+
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
226
|
+
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
227
|
+
} from '../../../render/src/systems/transparent-sort-config';
|
|
228
|
+
import { spriteAnimationTickSystem } from '../systems/sprite-animation-tick';
|
|
229
|
+
import { REC709_LUMA_WEIGHTS, tonemapReinhardLuminance } from '../systems/tonemap';
|
|
230
|
+
import { transparentSortEntries } from '../systems/transparent-sort';
|
|
231
|
+
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
232
|
+
|
|
233
|
+
void [
|
|
234
|
+
ANTIALIAS_FXAA,
|
|
235
|
+
ANTIALIAS_NONE,
|
|
236
|
+
AnimationPlayer,
|
|
237
|
+
AnimationTargetId,
|
|
238
|
+
AssetRegistry,
|
|
239
|
+
BLOOM_DISABLED,
|
|
240
|
+
BLOOM_ENABLED,
|
|
241
|
+
CAMERA_PROJECTION_ORTHOGRAPHIC,
|
|
242
|
+
CAMERA_PROJECTION_PERSPECTIVE,
|
|
243
|
+
Camera,
|
|
244
|
+
ChildOf,
|
|
245
|
+
DeviceScope,
|
|
246
|
+
ENTITY_NULL_RAW,
|
|
247
|
+
GpuBuffer,
|
|
248
|
+
INSTANCE_STORAGE_STRIDE_FLOATS,
|
|
249
|
+
MeshFilter,
|
|
250
|
+
MeshRenderer,
|
|
251
|
+
Name,
|
|
252
|
+
REC709_LUMA_WEIGHTS,
|
|
253
|
+
SPRITE_PLAYBACK_MODE_CLAMP,
|
|
254
|
+
SPRITE_PLAYBACK_MODE_LOOP,
|
|
255
|
+
Skin,
|
|
256
|
+
SkinInstancesCoexistForbiddenError,
|
|
257
|
+
SkinJointCountExceededError,
|
|
258
|
+
SkinJointDespawnedError,
|
|
259
|
+
SkinJointPathUnresolvedError,
|
|
260
|
+
SkyboxBackground,
|
|
261
|
+
Skylight,
|
|
262
|
+
SpriteAnimation,
|
|
263
|
+
SpriteAnimationInvalidError,
|
|
264
|
+
SpriteRegionOverride,
|
|
265
|
+
TONEMAP_LUMINANCE_EPSILON,
|
|
266
|
+
TRANSPARENT_SORT_CONFIG_KEY,
|
|
267
|
+
TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
268
|
+
TRANSPARENT_SORT_MODE_LAYER_YZ,
|
|
269
|
+
TRANSPARENT_SORT_MODE_LAYER_Z,
|
|
270
|
+
Transform,
|
|
271
|
+
World,
|
|
272
|
+
ZERO_CAMERA_CLEAR_FALLBACK,
|
|
273
|
+
afterEach,
|
|
274
|
+
assembleMaterialWithSkylightEntries,
|
|
275
|
+
beforeAll,
|
|
276
|
+
beforeEach,
|
|
277
|
+
bindAnimationTargets,
|
|
278
|
+
buildPbrPipelineLayouts,
|
|
279
|
+
buildUnlitMaterialBgl,
|
|
280
|
+
cameraProjectionFromF32,
|
|
281
|
+
canonicalAdvanceAnimationPlayer,
|
|
282
|
+
componentFieldDefault,
|
|
283
|
+
componentFieldType,
|
|
284
|
+
componentSchemaTypes,
|
|
285
|
+
createSkinPaletteAllocator,
|
|
286
|
+
createSkylightFallback,
|
|
287
|
+
deriveAnimationTargetId,
|
|
288
|
+
describe,
|
|
289
|
+
disposeInstanceBuffers,
|
|
290
|
+
drawPublished,
|
|
291
|
+
drawWithOwners,
|
|
292
|
+
expect,
|
|
293
|
+
extractFrame,
|
|
294
|
+
extractFrames,
|
|
295
|
+
fileURLToPath,
|
|
296
|
+
getOrCreateIblCache,
|
|
297
|
+
getTransparentSortConfig,
|
|
298
|
+
hasIblCache,
|
|
299
|
+
it,
|
|
300
|
+
makeMockShaderRegistry,
|
|
301
|
+
mat4,
|
|
302
|
+
mergeSkylightIntoMaterialBgl,
|
|
303
|
+
prepareExtractContext,
|
|
304
|
+
propagateTransforms,
|
|
305
|
+
readFileSync,
|
|
306
|
+
resolve,
|
|
307
|
+
rhiOk,
|
|
308
|
+
selectSwapChainFormat,
|
|
309
|
+
setTransparentSortConfig,
|
|
310
|
+
spriteAnimationTickSystem,
|
|
311
|
+
standardMaterialShaderVariants,
|
|
312
|
+
subscribeRendererErrors,
|
|
313
|
+
toShared,
|
|
314
|
+
tonemapReinhardLuminance,
|
|
315
|
+
transparentSortEntries,
|
|
316
|
+
unwrapRendererError,
|
|
317
|
+
urpPipeline,
|
|
318
|
+
vec3,
|
|
319
|
+
vi,
|
|
320
|
+
warnMultiLightDirectional,
|
|
321
|
+
warnMultiLightPoint,
|
|
322
|
+
warnMultiLightSpot,
|
|
323
|
+
worldInternal,
|
|
324
|
+
];
|
|
325
|
+
type __MergedKeep =
|
|
326
|
+
| AssetRuntimeErrorCode
|
|
327
|
+
| BindGroupEntry
|
|
328
|
+
| Buffer
|
|
329
|
+
| CameraProjection
|
|
330
|
+
| CameraSnapshot
|
|
331
|
+
| EntityHandle
|
|
332
|
+
| ExtractedLights
|
|
333
|
+
| Handle
|
|
334
|
+
| InstanceBufferCacheEntry
|
|
335
|
+
| MaterialAsset
|
|
336
|
+
| MeshAsset
|
|
337
|
+
| RenderErrorCode
|
|
338
|
+
| RendererErrorObservation
|
|
339
|
+
| RendererType
|
|
340
|
+
| RuntimeLayerErrorCode
|
|
341
|
+
| Sampler
|
|
342
|
+
| SkeletonAsset
|
|
343
|
+
| SkinErrorCode
|
|
344
|
+
| Texture
|
|
345
|
+
| TextureFormat
|
|
346
|
+
| TextureView
|
|
347
|
+
| TransparentEntry
|
|
348
|
+
| WorldType;
|
|
349
|
+
|
|
350
|
+
// Split source block: GPU resource symmetric release contract.
|
|
351
|
+
// ── feat-20260612-skin-palette-per-frame-upload M2: extractFrame hasSkin ──
|
|
352
|
+
// Helpers shared by m2-1..m2-4 tests covering the T-21 placeholder retirement
|
|
353
|
+
// (real allocator wiring + 3 new SkinExtractErrorCode routings + assets===null
|
|
354
|
+
// bind-pose equivalence).
|
|
355
|
+
|
|
356
|
+
{
|
|
357
|
+
// ── Helpers ──
|
|
358
|
+
|
|
359
|
+
async function _loadM4Libs() {
|
|
360
|
+
const { err: m4err, ok: m4ok, RhiError } = await import('@forgeax/engine-rhi');
|
|
361
|
+
return { m4err, m4ok, RhiError, GpuBuffer };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic-import return types are opaque
|
|
365
|
+
function _mkBufDevice(m4err: any, m4ok: any, RhiError: any) {
|
|
366
|
+
const destroyedSet = new WeakSet<object>();
|
|
367
|
+
const dev = {
|
|
368
|
+
destroyBuffer(buf: object) {
|
|
369
|
+
if (destroyedSet.has(buf))
|
|
370
|
+
return m4err(new RhiError({ code: 'destroy-after-destroy', expected: '', hint: '' }));
|
|
371
|
+
destroyedSet.add(buf);
|
|
372
|
+
return m4ok(undefined);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
return dev as never;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic-import return type is opaque
|
|
379
|
+
function _mkBufEntry(GpuBuffer: any, dev: never, bl: number, av: number) {
|
|
380
|
+
return {
|
|
381
|
+
buffer: new GpuBuffer(dev, {} as never),
|
|
382
|
+
uploadedByteLength: bl,
|
|
383
|
+
uploadedArchVersion: av,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ── createRenderer integration harness (drives the real record passes) ──
|
|
388
|
+
//
|
|
389
|
+
// A WebGPU-shaped mock device whose createBuffer hands back raw handles
|
|
390
|
+
// that record their own .destroy() invocations. The runtime wraps each in a
|
|
391
|
+
// GpuBuffer; the F12 set-before-destroy path calls GpuBuffer.destroy() ->
|
|
392
|
+
// rhi-webgpu destroyBuffer -> rawBuf.destroy(), landing in `destroyed`.
|
|
393
|
+
|
|
394
|
+
interface IntegrationBufLog {
|
|
395
|
+
created: Array<{ handle: object; size: number; usage: number }>;
|
|
396
|
+
destroyed: object[];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// The per-entity instance-transform buffer is the only buffer created with
|
|
400
|
+
// usage STORAGE|COPY_DST (128|8 = 136) at the packed storage instance byte
|
|
401
|
+
// size. Storage carries current and previous mat4 values, so each instance
|
|
402
|
+
// occupies `INSTANCE_STORAGE_STRIDE_FLOATS` f32 values. Matching BOTH usage
|
|
403
|
+
// and size pins the F12 destroy to the instance buffer, not an incidental
|
|
404
|
+
// same-sized transient / uniform buffer elsewhere in the frame.
|
|
405
|
+
const INSTANCE_USAGE = 128 | 8;
|
|
406
|
+
const INSTANCE_BYTES = (instanceCount: number): number =>
|
|
407
|
+
instanceCount * INSTANCE_STORAGE_STRIDE_FLOATS * 4;
|
|
408
|
+
|
|
409
|
+
function latestInstanceBuffer(log: IntegrationBufLog, instanceCount: number): object {
|
|
410
|
+
const created = log.created.filter(
|
|
411
|
+
(c) => c.usage === INSTANCE_USAGE && c.size === INSTANCE_BYTES(instanceCount),
|
|
412
|
+
);
|
|
413
|
+
const latest = created[created.length - 1];
|
|
414
|
+
if (latest === undefined) throw new Error('instance buffer was not created');
|
|
415
|
+
return latest.handle;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function makeIntegrationCanvas(): HTMLCanvasElement {
|
|
419
|
+
const canvas = {
|
|
420
|
+
width: 800,
|
|
421
|
+
height: 600,
|
|
422
|
+
getContext(kind: string): unknown {
|
|
423
|
+
if (kind === 'webgl2') {
|
|
424
|
+
return {
|
|
425
|
+
__mockTag: 'webgl2',
|
|
426
|
+
getExtension: () => null,
|
|
427
|
+
getParameter: () => 1,
|
|
428
|
+
isContextLost: () => false,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
if (kind === 'webgpu') {
|
|
432
|
+
return {
|
|
433
|
+
__mockTag: 'webgpu-canvas-context',
|
|
434
|
+
configure: () => undefined,
|
|
435
|
+
unconfigure: () => undefined,
|
|
436
|
+
getCurrentTexture: () => ({ createView: () => ({}) }),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
},
|
|
441
|
+
addEventListener: () => undefined,
|
|
442
|
+
removeEventListener: () => undefined,
|
|
443
|
+
};
|
|
444
|
+
return canvas as Partial<HTMLCanvasElement> as HTMLCanvasElement;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function makeIntegrationDevice(
|
|
448
|
+
log: IntegrationBufLog,
|
|
449
|
+
opts?: { destroyThrows?: boolean },
|
|
450
|
+
): unknown {
|
|
451
|
+
const lost = new Promise<unknown>(() => undefined);
|
|
452
|
+
const destroyedSet = new WeakSet<object>();
|
|
453
|
+
return {
|
|
454
|
+
__mockTag: 'gpu-device',
|
|
455
|
+
lost,
|
|
456
|
+
features: new Set(),
|
|
457
|
+
// maxStorageBuffersPerShaderStage > 0 => rhi-webgpu caps.storageBuffer
|
|
458
|
+
// true (device.ts:406) => the instance buffer takes the STORAGE path
|
|
459
|
+
// (usage 136), distinguishing it from uniform buffers in the frame.
|
|
460
|
+
limits: {
|
|
461
|
+
maxStorageBufferBindingSize: 1024 * 1024 * 1024,
|
|
462
|
+
maxStorageBuffersPerShaderStage: 8,
|
|
463
|
+
},
|
|
464
|
+
queue: {
|
|
465
|
+
submit: () => undefined,
|
|
466
|
+
onSubmittedWorkDone: () => Promise.resolve(undefined),
|
|
467
|
+
writeBuffer: () => undefined,
|
|
468
|
+
writeTexture: () => undefined,
|
|
469
|
+
},
|
|
470
|
+
createShaderModule: () => ({ getCompilationInfo: async () => ({ messages: [] }) }),
|
|
471
|
+
createBindGroupLayout: () => ({}),
|
|
472
|
+
createPipelineLayout: () => ({}),
|
|
473
|
+
createRenderPipeline: () => ({}),
|
|
474
|
+
createBindGroup: () => ({}),
|
|
475
|
+
createBuffer: (desc: { size?: number; usage?: number }) => {
|
|
476
|
+
const raw = {
|
|
477
|
+
__role: `buffer-${log.created.length}`,
|
|
478
|
+
destroy: () => {
|
|
479
|
+
if (opts?.destroyThrows) throw new Error('mock destroy failure');
|
|
480
|
+
if (destroyedSet.has(raw)) return;
|
|
481
|
+
destroyedSet.add(raw);
|
|
482
|
+
log.destroyed.push(raw);
|
|
483
|
+
},
|
|
484
|
+
getMappedRange: () => new ArrayBuffer(desc?.size ?? 64),
|
|
485
|
+
unmap: () => undefined,
|
|
486
|
+
};
|
|
487
|
+
log.created.push({ handle: raw, size: desc?.size ?? 0, usage: desc?.usage ?? 0 });
|
|
488
|
+
return raw;
|
|
489
|
+
},
|
|
490
|
+
createCommandEncoder: () => ({
|
|
491
|
+
beginRenderPass: () => ({
|
|
492
|
+
setPipeline: () => undefined,
|
|
493
|
+
setVertexBuffer: () => undefined,
|
|
494
|
+
setIndexBuffer: () => undefined,
|
|
495
|
+
setBindGroup: () => undefined,
|
|
496
|
+
setViewport: () => undefined,
|
|
497
|
+
setStencilReference: () => undefined,
|
|
498
|
+
draw: () => undefined,
|
|
499
|
+
drawIndexed: () => undefined,
|
|
500
|
+
end: () => undefined,
|
|
501
|
+
}),
|
|
502
|
+
finish: () => ({}),
|
|
503
|
+
}),
|
|
504
|
+
createTexture: () => ({ createView: () => ({}) }),
|
|
505
|
+
createSampler: () => ({}),
|
|
506
|
+
destroy: () => undefined,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function makeIntegrationGPU(device: unknown): unknown {
|
|
511
|
+
return {
|
|
512
|
+
requestAdapter: async () => ({ requestDevice: async () => device }),
|
|
513
|
+
getPreferredCanvasFormat: () => 'bgra8unorm',
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const integrationNavigator = {
|
|
518
|
+
userAgent: 'mock-engine-test',
|
|
519
|
+
} as Partial<Navigator> as Navigator;
|
|
520
|
+
|
|
521
|
+
// The shadow-caster shader registration gates recordShadowPass: with it,
|
|
522
|
+
// the shadow pass records instances BEFORE the main pass each frame and so
|
|
523
|
+
// owns the F12 set-before-destroy (render-system-record.ts:3323); without
|
|
524
|
+
// it recordShadowPass early-exits (shadow PSO null) and the main pass owns
|
|
525
|
+
// the F12 destroy (:4521). Each w13 sub-test selects the manifest that
|
|
526
|
+
// isolates the production site it falsifies, so disabling that exact line
|
|
527
|
+
// turns exactly that sub-test red.
|
|
528
|
+
function buildIntegrationManifestUrl(withShadowCaster: boolean): string {
|
|
529
|
+
const materialShaderStub = (identifier: string) => ({
|
|
530
|
+
identifier,
|
|
531
|
+
sourcePath: `${identifier}.wgsl`,
|
|
532
|
+
composedWgsl: '/* stub */',
|
|
533
|
+
paramSchema: '[]',
|
|
534
|
+
variants:
|
|
535
|
+
identifier === 'forgeax::default-standard-pbr' ? standardMaterialShaderVariants() : [],
|
|
536
|
+
});
|
|
537
|
+
const entries: Array<{ hash: string; wgsl: string; glsl: string; bindings: string }> = [
|
|
538
|
+
{ hash: 'pbr00000', wgsl: '/* pbr stub - calls f_schlick( */', glsl: '', bindings: '' },
|
|
539
|
+
{ hash: 'unlit000', wgsl: '/* unlit stub */', glsl: '', bindings: '' },
|
|
540
|
+
{
|
|
541
|
+
hash: 'tonemap0',
|
|
542
|
+
wgsl: '/* tonemap stub - struct TonemapParams { exposure: f32 }; */',
|
|
543
|
+
glsl: '',
|
|
544
|
+
bindings: '',
|
|
545
|
+
},
|
|
546
|
+
];
|
|
547
|
+
const materialShaders = [
|
|
548
|
+
materialShaderStub('forgeax::default-standard-pbr'),
|
|
549
|
+
materialShaderStub('forgeax::default-unlit'),
|
|
550
|
+
];
|
|
551
|
+
if (withShadowCaster) {
|
|
552
|
+
materialShaders.push(
|
|
553
|
+
materialShaderStub(
|
|
554
|
+
'forgeax::default-shadow-caster',
|
|
555
|
+
'/* reserved shadow caster - @location(0) position vertex-only */',
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
const manifest = {
|
|
560
|
+
schemaVersion: '1.0.0',
|
|
561
|
+
entries,
|
|
562
|
+
materialShaders,
|
|
563
|
+
};
|
|
564
|
+
return `data:application/json,${encodeURIComponent(JSON.stringify(manifest))}`;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
type IntegrationRenderer = RendererType;
|
|
568
|
+
|
|
569
|
+
interface IntegrationWorld {
|
|
570
|
+
spawn: (...a: unknown[]) => { unwrap: () => unknown };
|
|
571
|
+
set: (...a: unknown[]) => unknown;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
interface IntegrationComponents {
|
|
575
|
+
Transform: unknown;
|
|
576
|
+
MeshFilter: unknown;
|
|
577
|
+
MeshRenderer: unknown;
|
|
578
|
+
Camera: unknown;
|
|
579
|
+
Instances: unknown;
|
|
580
|
+
DirectionalLight: unknown;
|
|
581
|
+
HANDLE_CUBE: unknown;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function bootIntegrationRenderer(
|
|
585
|
+
device: unknown,
|
|
586
|
+
withShadowCaster = false,
|
|
587
|
+
): Promise<{
|
|
588
|
+
renderer: IntegrationRenderer;
|
|
589
|
+
world: IntegrationWorld;
|
|
590
|
+
C: IntegrationComponents;
|
|
591
|
+
errors: { code: string }[];
|
|
592
|
+
}> {
|
|
593
|
+
vi.stubGlobal('navigator', { ...integrationNavigator, gpu: makeIntegrationGPU(device) });
|
|
594
|
+
const { createRenderer } = (await import('../createRenderer')) as {
|
|
595
|
+
createRenderer: (...args: unknown[]) => Promise<{ unwrap(): IntegrationRenderer }>;
|
|
596
|
+
};
|
|
597
|
+
const renderer = (
|
|
598
|
+
await createRenderer(
|
|
599
|
+
makeIntegrationCanvas(),
|
|
600
|
+
{},
|
|
601
|
+
{ shaderManifestUrl: buildIntegrationManifestUrl(withShadowCaster) },
|
|
602
|
+
)
|
|
603
|
+
).unwrap();
|
|
604
|
+
const { World: WorldCtor } = (await import('@forgeax/engine-ecs')) as unknown as {
|
|
605
|
+
World: new () => IntegrationWorld;
|
|
606
|
+
};
|
|
607
|
+
const C = {
|
|
608
|
+
...(await import('@forgeax/engine-render')),
|
|
609
|
+
...(await import('@forgeax/engine-scene')),
|
|
610
|
+
...(await import('@forgeax/engine-assets-runtime')),
|
|
611
|
+
} as unknown as IntegrationComponents;
|
|
612
|
+
const errors: { code: string }[] = [];
|
|
613
|
+
subscribeRendererErrors(renderer, (e) => errors.push(e));
|
|
614
|
+
return { renderer, world: new WorldCtor(), C, errors };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function spawnCamera(world: IntegrationWorld, C: IntegrationComponents): void {
|
|
618
|
+
world.spawn(
|
|
619
|
+
{
|
|
620
|
+
component: C.Camera,
|
|
621
|
+
data: {
|
|
622
|
+
fov: Math.PI / 4,
|
|
623
|
+
aspect: 16 / 9,
|
|
624
|
+
near: 0.1,
|
|
625
|
+
far: 100,
|
|
626
|
+
projection: 0,
|
|
627
|
+
left: -1,
|
|
628
|
+
right: 1,
|
|
629
|
+
bottom: -1,
|
|
630
|
+
top: 1,
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
component: C.Transform,
|
|
635
|
+
data: {
|
|
636
|
+
pos: [0, 0, 5],
|
|
637
|
+
quat: [0, 0, 0, 1],
|
|
638
|
+
scale: [1, 1, 1],
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function spawnInstancedCube(
|
|
645
|
+
world: IntegrationWorld,
|
|
646
|
+
C: IntegrationComponents,
|
|
647
|
+
instanceCount: number,
|
|
648
|
+
): unknown {
|
|
649
|
+
return world
|
|
650
|
+
.spawn(
|
|
651
|
+
{ component: C.MeshFilter, data: { assetHandle: C.HANDLE_CUBE } },
|
|
652
|
+
{ component: C.MeshRenderer, data: {} },
|
|
653
|
+
{
|
|
654
|
+
component: C.Transform,
|
|
655
|
+
data: {
|
|
656
|
+
pos: [0, 0, 0],
|
|
657
|
+
quat: [0, 0, 0, 1],
|
|
658
|
+
scale: [1, 1, 1],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{ component: C.Instances, data: { transforms: new Float32Array(instanceCount * 16) } },
|
|
662
|
+
)
|
|
663
|
+
.unwrap();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// ── w14: AC-07 B-family error strategy ──
|
|
667
|
+
//
|
|
668
|
+
// dispose-path sub-cases drive the real disposeInstanceBuffers (already
|
|
669
|
+
// correct round 1); the F12 sub-case drives the real record pass with a
|
|
670
|
+
// destroy that fails (rawBuf.destroy throws -> rhi-webgpu surfaces
|
|
671
|
+
// webgpu-runtime-error -> the F12 production fires errorRegistry + sweeps on).
|
|
672
|
+
|
|
673
|
+
describe('instance buffer error strategy (AC-07) [w14]', () => {
|
|
674
|
+
afterEach(() => {
|
|
675
|
+
vi.unstubAllGlobals();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('disposeInstanceBuffers: destroy clears map, isDestroyed gate skips pre-destroyed entries', async () => {
|
|
679
|
+
const { m4err, m4ok, RhiError, GpuBuffer } = await _loadM4Libs();
|
|
680
|
+
const dev = _mkBufDevice(m4err, m4ok, RhiError);
|
|
681
|
+
|
|
682
|
+
const e1 = _mkBufEntry(GpuBuffer, dev, 256, 1);
|
|
683
|
+
const e2 = _mkBufEntry(GpuBuffer, dev, 512, 2);
|
|
684
|
+
e2.buffer.destroy(); // pre-destroy
|
|
685
|
+
const map = new Map<number, ReturnType<typeof _mkBufEntry>>();
|
|
686
|
+
map.set(1, e1);
|
|
687
|
+
map.set(2, e2);
|
|
688
|
+
|
|
689
|
+
const fires: unknown[] = [];
|
|
690
|
+
disposeInstanceBuffers(map, {
|
|
691
|
+
fire: (e: unknown) => {
|
|
692
|
+
fires.push(e);
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
expect(e1.buffer.isDestroyed).toBe(true);
|
|
697
|
+
expect(e2.buffer.isDestroyed).toBe(true);
|
|
698
|
+
expect(map.size).toBe(0);
|
|
699
|
+
expect(fires).toHaveLength(0);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('disposeInstanceBuffers: sweep continues, all non-pre-destroyed entries destroyed', async () => {
|
|
703
|
+
const { m4err, m4ok, RhiError, GpuBuffer } = await _loadM4Libs();
|
|
704
|
+
const dev = _mkBufDevice(m4err, m4ok, RhiError);
|
|
705
|
+
|
|
706
|
+
const e1 = _mkBufEntry(GpuBuffer, dev, 256, 1);
|
|
707
|
+
const e2 = _mkBufEntry(GpuBuffer, dev, 512, 2);
|
|
708
|
+
const e3 = _mkBufEntry(GpuBuffer, dev, 768, 3);
|
|
709
|
+
e2.buffer.destroy(); // pre-destroy
|
|
710
|
+
const map = new Map<number, ReturnType<typeof _mkBufEntry>>();
|
|
711
|
+
map.set(1, e1);
|
|
712
|
+
map.set(2, e2);
|
|
713
|
+
map.set(3, e3);
|
|
714
|
+
|
|
715
|
+
const fires: unknown[] = [];
|
|
716
|
+
disposeInstanceBuffers(map, {
|
|
717
|
+
fire: (e: unknown) => {
|
|
718
|
+
fires.push(e);
|
|
719
|
+
},
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
expect(e1.buffer.isDestroyed).toBe(true);
|
|
723
|
+
expect(e3.buffer.isDestroyed).toBe(true);
|
|
724
|
+
expect(map.size).toBe(0);
|
|
725
|
+
expect(fires).toHaveLength(0);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('disposeInstanceBuffers: without errorRegistry parameter, no fire (no crash)', async () => {
|
|
729
|
+
const { m4err, m4ok, RhiError, GpuBuffer } = await _loadM4Libs();
|
|
730
|
+
const dev = _mkBufDevice(m4err, m4ok, RhiError);
|
|
731
|
+
|
|
732
|
+
const e1 = _mkBufEntry(GpuBuffer, dev, 256, 1);
|
|
733
|
+
const map = new Map<number, ReturnType<typeof _mkBufEntry>>();
|
|
734
|
+
map.set(1, e1);
|
|
735
|
+
|
|
736
|
+
// Call without errorRegistry — should not throw.
|
|
737
|
+
disposeInstanceBuffers(map);
|
|
738
|
+
|
|
739
|
+
expect(e1.buffer.isDestroyed).toBe(true);
|
|
740
|
+
expect(map.size).toBe(0);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('F12 set-before-destroy failure fires errorRegistry + sweep continues (real record pass)', async () => {
|
|
744
|
+
const log: IntegrationBufLog = { created: [], destroyed: [] };
|
|
745
|
+
const device = makeIntegrationDevice(log, { destroyThrows: true });
|
|
746
|
+
const { renderer, world, C, errors } = await bootIntegrationRenderer(device);
|
|
747
|
+
|
|
748
|
+
spawnCamera(world, C);
|
|
749
|
+
const cube = spawnInstancedCube(world, C, 2);
|
|
750
|
+
|
|
751
|
+
// Frame 1: allocate the instance buffer (fingerprint = 2 instances).
|
|
752
|
+
drawPublished(renderer, world as WorldType);
|
|
753
|
+
|
|
754
|
+
// Frame 2: fingerprint mismatch -> F12 destroys the old buffer, whose
|
|
755
|
+
// raw .destroy() throws -> rhi-webgpu webgpu-runtime-error -> the F12
|
|
756
|
+
// production fires errorRegistry and continues to set the new buffer.
|
|
757
|
+
world.set(cube, C.Instances, { transforms: new Float32Array(3 * 16) });
|
|
758
|
+
drawPublished(renderer, world as WorldType);
|
|
759
|
+
|
|
760
|
+
// Sweep continued: a fresh (larger) instance buffer was still allocated
|
|
761
|
+
// after the failed destroy. The failure surfaced as a fired error.
|
|
762
|
+
expect(errors.some((e) => e.code === 'webgpu-runtime-error')).toBe(true);
|
|
763
|
+
expect(log.created.length).toBeGreaterThan(0);
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// ── w13: AC-06 B-family F12 set-before-destroy (real recordMainPass + recordShadowPass) ──
|
|
768
|
+
|
|
769
|
+
describe('instance buffer F12 set-before-destroy (AC-06) [w13]', () => {
|
|
770
|
+
afterEach(() => {
|
|
771
|
+
vi.unstubAllGlobals();
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('main pass: fingerprint change destroys the old cached buffer before set', async () => {
|
|
775
|
+
const log: IntegrationBufLog = { created: [], destroyed: [] };
|
|
776
|
+
const device = makeIntegrationDevice(log);
|
|
777
|
+
const { renderer, world, C, errors } = await bootIntegrationRenderer(device);
|
|
778
|
+
|
|
779
|
+
spawnCamera(world, C);
|
|
780
|
+
const cube = spawnInstancedCube(world, C, 2);
|
|
781
|
+
|
|
782
|
+
// Frame 1: cold allocate the 2-instance buffer (256 B).
|
|
783
|
+
drawPublished(renderer, world as WorldType);
|
|
784
|
+
expect(errors).toHaveLength(0);
|
|
785
|
+
const createdAfterF1 = log.created.length;
|
|
786
|
+
expect(createdAfterF1).toBeGreaterThan(0);
|
|
787
|
+
expect(log.destroyed).toHaveLength(0);
|
|
788
|
+
const oldInstanceBuffer = latestInstanceBuffer(log, 2);
|
|
789
|
+
|
|
790
|
+
// Frame 2: 2 -> 3 instances => byteLength fingerprint mismatch => the
|
|
791
|
+
// main-pass F12 path (render-system-record.ts:4521) destroys the old
|
|
792
|
+
// 256 B buffer, then sets a fresh 384 B one. No shadow-caster shader is
|
|
793
|
+
// registered, so recordShadowPass early-exits and the main pass is the
|
|
794
|
+
// sole F12 owner this frame.
|
|
795
|
+
world.set(cube, C.Instances, { transforms: new Float32Array(3 * 16) });
|
|
796
|
+
drawPublished(renderer, world as WorldType);
|
|
797
|
+
|
|
798
|
+
// The destroyed buffer is specifically the old instance buffer (STORAGE
|
|
799
|
+
// usage, 256 B), not an incidental destroy elsewhere. Disabling :4521
|
|
800
|
+
// drops it.
|
|
801
|
+
expect(log.destroyed.includes(oldInstanceBuffer)).toBe(true);
|
|
802
|
+
// A new (larger) 384 B instance buffer replaced the destroyed one.
|
|
803
|
+
expect(
|
|
804
|
+
log.created.some((c) => c.usage === INSTANCE_USAGE && c.size === INSTANCE_BYTES(3)),
|
|
805
|
+
).toBe(true);
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it('shadow pass: fingerprint change destroys the old cached buffer before set', async () => {
|
|
809
|
+
const log: IntegrationBufLog = { created: [], destroyed: [] };
|
|
810
|
+
const device = makeIntegrationDevice(log);
|
|
811
|
+
// withShadowCaster=true registers forgeax::default-shadow-caster so
|
|
812
|
+
// recordShadowPass runs.
|
|
813
|
+
const { renderer, world, C } = await bootIntegrationRenderer(device, true);
|
|
814
|
+
|
|
815
|
+
spawnCamera(world, C);
|
|
816
|
+
// DirectionalLight with castShadow => recordShadowPass runs and
|
|
817
|
+
// records the instance entity BEFORE the main pass, so the shadow F12
|
|
818
|
+
// path (render-system-record.ts:3323) owns the destroy this frame.
|
|
819
|
+
world.spawn({
|
|
820
|
+
component: C.DirectionalLight,
|
|
821
|
+
data: {
|
|
822
|
+
direction: [-0.5, -1, -0.3],
|
|
823
|
+
color: [1, 1, 1],
|
|
824
|
+
intensity: 1,
|
|
825
|
+
cascadeCount: 1,
|
|
826
|
+
mapSize: 1024,
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
const cube = spawnInstancedCube(world, C, 2);
|
|
830
|
+
|
|
831
|
+
drawPublished(renderer, world as WorldType);
|
|
832
|
+
expect(log.destroyed).toHaveLength(0);
|
|
833
|
+
const oldInstanceBuffer = latestInstanceBuffer(log, 2);
|
|
834
|
+
|
|
835
|
+
world.set(cube, C.Instances, { transforms: new Float32Array(3 * 16) });
|
|
836
|
+
drawPublished(renderer, world as WorldType);
|
|
837
|
+
|
|
838
|
+
// The shadow pass records the instance entity before the main pass, so
|
|
839
|
+
// it owns the F12 destroy of the old (STORAGE, 256 B) instance buffer
|
|
840
|
+
// this frame. Disabling :3323 drops this assertion (main reuses the
|
|
841
|
+
// already-updated entry and never re-destroys).
|
|
842
|
+
expect(log.destroyed.includes(oldInstanceBuffer)).toBe(true);
|
|
843
|
+
});
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// ── w15: AC-10 WeakMap chain behavior invariants ──
|
|
847
|
+
//
|
|
848
|
+
// feat-20260622-handle-to-id-allocator-elimination: the old
|
|
849
|
+
// getOrAssignHandleId is gone; the old numeric counter is removed. WeakMap chain
|
|
850
|
+
// determinism replaces it — same handle object identity → same
|
|
851
|
+
// leaf BG, different handles → different leaf.
|
|
852
|
+
|
|
853
|
+
describe('WeakMap chain behavior invariants [w15]', () => {
|
|
854
|
+
it('same handle object in chain → same leaf BindGroup (deterministic)', () => {
|
|
855
|
+
const root = new WeakMap<object, unknown>();
|
|
856
|
+
const h1 = {};
|
|
857
|
+
const h2 = {};
|
|
858
|
+
|
|
859
|
+
// We simulate the chain by building two levels manually for the test
|
|
860
|
+
const inner = new WeakMap<object, unknown>();
|
|
861
|
+
root.set(h1, inner);
|
|
862
|
+
const leaf = new Map<string, unknown>();
|
|
863
|
+
inner.set(h2, leaf);
|
|
864
|
+
const bg1 = { __label: 'bg-1' };
|
|
865
|
+
leaf.set('variant-a', bg1);
|
|
866
|
+
|
|
867
|
+
// Same handle path → same leaf entry
|
|
868
|
+
const innerCheck = root.get(h1) as WeakMap<object, unknown>;
|
|
869
|
+
expect(innerCheck).toBeDefined();
|
|
870
|
+
const leafCheck = innerCheck.get(h2) as Map<string, unknown>;
|
|
871
|
+
expect(leafCheck).toBeDefined();
|
|
872
|
+
expect(leafCheck.get('variant-a')).toBe(bg1);
|
|
873
|
+
expect(leafCheck.get('variant-a')).toBe(bg1);
|
|
874
|
+
|
|
875
|
+
// Different variant on same chain → different leaf entry
|
|
876
|
+
const bg2 = { __label: 'bg-2' };
|
|
877
|
+
leaf.set('variant-b', bg2);
|
|
878
|
+
expect(leaf.get('variant-a')).toBe(bg1);
|
|
879
|
+
expect(leaf.get('variant-b')).toBe(bg2);
|
|
880
|
+
expect(bg1).not.toBe(bg2);
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it('different handle objects → different chain position → different leaf', () => {
|
|
884
|
+
const root = new WeakMap<object, unknown>();
|
|
885
|
+
const hA = {};
|
|
886
|
+
const hB = {};
|
|
887
|
+
const innerA = new WeakMap<object, unknown>();
|
|
888
|
+
const innerB = new WeakMap<object, unknown>();
|
|
889
|
+
const leafA = new Map<string, unknown>();
|
|
890
|
+
const leafB = new Map<string, unknown>();
|
|
891
|
+
innerA.set({}, leafA);
|
|
892
|
+
innerB.set({}, leafB);
|
|
893
|
+
root.set(hA, innerA);
|
|
894
|
+
root.set(hB, innerB);
|
|
895
|
+
|
|
896
|
+
// Different root-level key → completely independent chains
|
|
897
|
+
const chainA = root.get(hA) as WeakMap<object, unknown>;
|
|
898
|
+
const chainB = root.get(hB) as WeakMap<object, unknown>;
|
|
899
|
+
expect(chainA).not.toBe(chainB);
|
|
900
|
+
|
|
901
|
+
// No shared leaf — hA's chain entries don't hit hB's chain
|
|
902
|
+
leafA.set('v', 'from-A');
|
|
903
|
+
leafB.set('v', 'from-B');
|
|
904
|
+
expect(
|
|
905
|
+
(root.get(hA) as WeakMap<object, unknown>).get({}) as Map<string, unknown>,
|
|
906
|
+
).toBeUndefined();
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it('grow miss: new inner buffer is new WeakMap key → cache miss (AC-07)', () => {
|
|
910
|
+
// AC-07: when mesh SSBO grows, the inner buffer object is replaced.
|
|
911
|
+
// The old inner buffer was a WeakMap key in the chain; the new one is
|
|
912
|
+
// a different object, so chain lookup naturally misses.
|
|
913
|
+
const root = new WeakMap<object, unknown>();
|
|
914
|
+
const oldBuf = {};
|
|
915
|
+
const inner = new WeakMap<object, unknown>();
|
|
916
|
+
const leaf = new Map<string, unknown>();
|
|
917
|
+
inner.set({}, leaf);
|
|
918
|
+
root.set(oldBuf, inner);
|
|
919
|
+
|
|
920
|
+
// Old buffer hits
|
|
921
|
+
expect(root.has(oldBuf)).toBe(true);
|
|
922
|
+
|
|
923
|
+
// New buffer (grow replacement) misses
|
|
924
|
+
const newBuf = {};
|
|
925
|
+
expect(root.has(newBuf)).toBe(false);
|
|
926
|
+
expect(oldBuf).not.toBe(newBuf);
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
}
|