@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,1123 @@
|
|
|
1
|
+
// @ts-nocheck — merged file: cross-source type narrowing failures from 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=33):
|
|
6
|
+
// - packages/runtime/__tests__/asset-registry.test.ts
|
|
7
|
+
// - packages/runtime/__tests__/loader-registry.test.ts
|
|
8
|
+
// - packages/runtime/__tests__/wire-default-loaders.test.ts
|
|
9
|
+
// - packages/runtime/src/__tests__/asset-registry-aabb.test.ts
|
|
10
|
+
// - packages/runtime/src/__tests__/asset-registry-builtin-nineslice.test.ts
|
|
11
|
+
// - packages/runtime/src/__tests__/asset-registry-d9-tile-sampler-soft-warn.test.ts
|
|
12
|
+
// - packages/runtime/src/__tests__/asset-registry-guid.test.ts
|
|
13
|
+
// - packages/runtime/src/__tests__/asset-registry-material-validate.test.ts
|
|
14
|
+
// - packages/runtime/src/__tests__/asset-registry-mesh-fail-fast.test.ts
|
|
15
|
+
// - packages/runtime/src/__tests__/asset-registry-scene.test.ts
|
|
16
|
+
// - packages/runtime/src/__tests__/asset-registry-sprite-slices-validate.test.ts
|
|
17
|
+
// - packages/runtime/src/__tests__/auto-select.test.ts
|
|
18
|
+
// - packages/runtime/src/__tests__/bindgroup-resize-invalidation.test.ts
|
|
19
|
+
// - packages/runtime/src/__tests__/builtin-guid-ssot.test.ts
|
|
20
|
+
// - packages/runtime/src/__tests__/builtin-pack.test.ts
|
|
21
|
+
// - packages/runtime/src/__tests__/cube-texture-narrowing.test.ts
|
|
22
|
+
// - packages/runtime/src/__tests__/cubemap-upload.test.ts
|
|
23
|
+
// - packages/runtime/src/__tests__/dev-import-transport.test.ts
|
|
24
|
+
// - packages/runtime/src/__tests__/font-asset-load.test.ts
|
|
25
|
+
// - packages/runtime/src/__tests__/handle-quad.test.ts
|
|
26
|
+
// - packages/runtime/src/__tests__/lazy-catalog.test.ts
|
|
27
|
+
// - packages/runtime/src/__tests__/load-by-guid-hdr.test.ts
|
|
28
|
+
// - packages/runtime/src/__tests__/load-by-guid-prod-material-parent.test.ts
|
|
29
|
+
// - packages/runtime/src/__tests__/load-by-guid-prod.test.ts
|
|
30
|
+
// - packages/runtime/src/__tests__/mipmap-formula.test.ts
|
|
31
|
+
// - packages/runtime/src/__tests__/mipmap-pipeline-cache.test.ts
|
|
32
|
+
// - packages/runtime/src/__tests__/parse-asset-payload-material.test.ts
|
|
33
|
+
// - packages/runtime/src/__tests__/parse-asset-payload-texture.test.ts
|
|
34
|
+
// - packages/runtime/src/__tests__/parse-scene-payload-refs.test.ts
|
|
35
|
+
// - packages/runtime/src/__tests__/register-with-guid-rgba16float.test.ts
|
|
36
|
+
// - packages/runtime/src/__tests__/resolve-scene-guids.test.ts
|
|
37
|
+
// - packages/runtime/src/__tests__/upload-texture-consistency.test.ts
|
|
38
|
+
// - packages/runtime/src/__tests__/verify-revisions.test.ts
|
|
39
|
+
//
|
|
40
|
+
// Paradigm: each block-scope wraps a source file. ancestorTitles[0] is the
|
|
41
|
+
// source-preserved inner describe (NOT the source filename for these 3 files
|
|
42
|
+
// — recovery path: vitest report ancestorTitles -> grep this file -> upstream
|
|
43
|
+
// `// ─── from <name>.test.ts ───` block separator -> source filename).
|
|
44
|
+
// Top-level imports merged + deduped.
|
|
45
|
+
|
|
46
|
+
import { readFileSync } from 'node:fs';
|
|
47
|
+
import { fileURLToPath } from 'node:url';
|
|
48
|
+
import * as assetRegistryModule from '@forgeax/engine-assets-runtime';
|
|
49
|
+
import {
|
|
50
|
+
AssetRegistry,
|
|
51
|
+
animationClipLoader,
|
|
52
|
+
buildSceneChildContext,
|
|
53
|
+
fontLoader,
|
|
54
|
+
getOrCreateMipmapPipeline,
|
|
55
|
+
HANDLE_CUBE,
|
|
56
|
+
HANDLE_CYLINDER,
|
|
57
|
+
HANDLE_QUAD,
|
|
58
|
+
HANDLE_SPHERE,
|
|
59
|
+
HANDLE_TRIANGLE,
|
|
60
|
+
INLINE_PACK_LOADERS,
|
|
61
|
+
LoaderRegistry,
|
|
62
|
+
materialLoader,
|
|
63
|
+
meshLoader,
|
|
64
|
+
mipmapCacheSize,
|
|
65
|
+
numMipLevels,
|
|
66
|
+
PACK_ARTIFACT_LOADERS,
|
|
67
|
+
resolveAssetHandle,
|
|
68
|
+
sceneLoader,
|
|
69
|
+
skeletonLoader,
|
|
70
|
+
skinLoader,
|
|
71
|
+
textureLoader,
|
|
72
|
+
walkMaterialPassesOverSharedRefs,
|
|
73
|
+
wireDefaultLoaders,
|
|
74
|
+
} from '@forgeax/engine-assets-runtime';
|
|
75
|
+
import { audioLoader } from '@forgeax/engine-audio-webaudio';
|
|
76
|
+
import { defineComponent, World } from '@forgeax/engine-ecs';
|
|
77
|
+
import {
|
|
78
|
+
createBoxGeometry,
|
|
79
|
+
createPlaneGeometry,
|
|
80
|
+
meshFromInterleaved,
|
|
81
|
+
PROCEDURAL_FLOATS_PER_VERTEX,
|
|
82
|
+
} from '@forgeax/engine-geometry';
|
|
83
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
84
|
+
import { ok, ok as rhiOk } from '@forgeax/engine-rhi';
|
|
85
|
+
import { rhi } from '@forgeax/engine-rhi-null';
|
|
86
|
+
import { ShaderRegistry } from '@forgeax/engine-shader';
|
|
87
|
+
import type {
|
|
88
|
+
EquirectAsset,
|
|
89
|
+
Handle,
|
|
90
|
+
MaterialAsset,
|
|
91
|
+
MaterialPass,
|
|
92
|
+
SamplerAsset,
|
|
93
|
+
} from '@forgeax/engine-types';
|
|
94
|
+
import { AssetError, toShared, toUnique, unwrapHandle } from '@forgeax/engine-types';
|
|
95
|
+
import { afterEach, beforeEach, describe, expect, expectTypeOf, it, vi } from 'vitest';
|
|
96
|
+
import { deriveBuiltin } from '../../../pack/src/builtin';
|
|
97
|
+
import { DeviceScope } from '../../../render/src/device/device-scope';
|
|
98
|
+
import { GpuResidencyCache } from '../../../render/src/device/gpu-residency';
|
|
99
|
+
import { createEngineMetrics } from '../../../render/src/engine-metrics';
|
|
100
|
+
import { createRenderer as createRuntimeRenderer } from '../createRenderer';
|
|
101
|
+
import { createDevImportTransport, type ImportTransport } from '../dev-import-transport';
|
|
102
|
+
import { EngineEnvironmentError } from '../errors/environment';
|
|
103
|
+
import { makeMockShaderRegistry } from './helpers/mock-shader-registry';
|
|
104
|
+
import { requireRenderer } from './renderer-test-utils';
|
|
105
|
+
|
|
106
|
+
vi.mock('@forgeax/engine-rhi-webgpu', async () => {
|
|
107
|
+
spies.webgpuImportCount += 1;
|
|
108
|
+
const actualRhi =
|
|
109
|
+
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
110
|
+
spies.rhiErrorCtor = actualRhi.RhiError;
|
|
111
|
+
const fakeAdapter = {
|
|
112
|
+
features: new Set<string>(),
|
|
113
|
+
limits: {} as Readonly<Record<string, number>>,
|
|
114
|
+
async requestDevice() {
|
|
115
|
+
return actualRhi.ok(makeFakeRhiDevice());
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
rhi: {
|
|
120
|
+
requestAdapter: async () => {
|
|
121
|
+
if (spies.rhiWebgpuRequestAdapterShould === 'reject-rhi-not-available') {
|
|
122
|
+
return actualRhi.err(
|
|
123
|
+
new actualRhi.RhiError({
|
|
124
|
+
code: 'rhi-not-available',
|
|
125
|
+
expected: 'navigator.gpu available',
|
|
126
|
+
hint: 'unit-test: forced rhi-not-available',
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (spies.rhiWebgpuRequestAdapterShould === 'reject-adapter-null') {
|
|
131
|
+
return actualRhi.err(
|
|
132
|
+
new actualRhi.RhiError({
|
|
133
|
+
code: 'adapter-unavailable',
|
|
134
|
+
expected: 'requestAdapter returns non-null',
|
|
135
|
+
hint: 'unit-test: forced adapter-unavailable',
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return actualRhi.ok(fakeAdapter);
|
|
140
|
+
},
|
|
141
|
+
acquireCanvasContext: (_canvas: unknown) => {
|
|
142
|
+
return actualRhi.ok({
|
|
143
|
+
configure: () => actualRhi.ok(undefined),
|
|
144
|
+
unconfigure: () => actualRhi.ok(undefined),
|
|
145
|
+
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
|
|
150
|
+
translateErrorEventToRhiError: () =>
|
|
151
|
+
actualRhi.err(
|
|
152
|
+
new actualRhi.RhiError({
|
|
153
|
+
code: 'webgpu-runtime-error',
|
|
154
|
+
expected: 'GPU error event translates to RhiError',
|
|
155
|
+
hint: 'unit-test mock event translation',
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
_internal_getRawDevice: () => undefined,
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
vi.mock('@forgeax/engine-rhi-wgpu', async () => {
|
|
162
|
+
spies.wgpuImportCount += 1;
|
|
163
|
+
const actualRhi =
|
|
164
|
+
await vi.importActual<typeof import('@forgeax/engine-rhi')>('@forgeax/engine-rhi');
|
|
165
|
+
const fakeAdapter = {
|
|
166
|
+
features: new Set<string>(),
|
|
167
|
+
limits: {} as Readonly<Record<string, number>>,
|
|
168
|
+
async requestDevice() {
|
|
169
|
+
return actualRhi.ok(makeFakeRhiDevice());
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
return {
|
|
173
|
+
rhi: {
|
|
174
|
+
requestAdapter: async () => {
|
|
175
|
+
if (spies.rhiWgpuRequestAdapterShould === 'reject-rhi-not-available') {
|
|
176
|
+
return actualRhi.err(
|
|
177
|
+
new actualRhi.RhiError({
|
|
178
|
+
code: 'rhi-not-available',
|
|
179
|
+
expected: 'wgpu webgl backend available',
|
|
180
|
+
hint: 'unit-test: forced rhi-not-available on rhi-wgpu',
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return actualRhi.ok(fakeAdapter);
|
|
185
|
+
},
|
|
186
|
+
acquireCanvasContext: (_canvas: unknown) => {
|
|
187
|
+
return actualRhi.ok({
|
|
188
|
+
configure: () => actualRhi.ok(undefined),
|
|
189
|
+
unconfigure: () => actualRhi.ok(undefined),
|
|
190
|
+
getCurrentTexture: () => actualRhi.ok({ __brand: 'TextureView' }),
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
ensureReady: async () => {
|
|
195
|
+
spies.ensureReadyCount += 1;
|
|
196
|
+
if (spies.rhiWgpuEnsureReadyShould === 'reject-load-failed') {
|
|
197
|
+
throw new Error('unit-test: forced rhi-wgpu wasm load failure');
|
|
198
|
+
}
|
|
199
|
+
return undefined;
|
|
200
|
+
},
|
|
201
|
+
createShaderModule: async () => actualRhi.ok({ __brand: 'ShaderModule' } as unknown as object),
|
|
202
|
+
translateErrorEventToRhiError: () =>
|
|
203
|
+
actualRhi.err(
|
|
204
|
+
new actualRhi.RhiError({
|
|
205
|
+
code: 'webgpu-runtime-error',
|
|
206
|
+
expected: 'GPU error event translates to RhiError',
|
|
207
|
+
hint: 'unit-test mock event translation',
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
210
|
+
_internal_getRawDevice: () => undefined,
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
const spies = vi.hoisted(() => ({
|
|
214
|
+
webgpuImportCount: 0,
|
|
215
|
+
wgpuImportCount: 0,
|
|
216
|
+
ensureReadyCount: 0,
|
|
217
|
+
// Failure toggles set by individual tests before invoking createRenderer.
|
|
218
|
+
rhiWebgpuRequestAdapterShould: 'success' as
|
|
219
|
+
| 'success'
|
|
220
|
+
| 'reject-rhi-not-available'
|
|
221
|
+
| 'reject-adapter-null',
|
|
222
|
+
rhiWgpuEnsureReadyShould: 'success' as 'success' | 'reject-load-failed',
|
|
223
|
+
rhiWgpuRequestAdapterShould: 'success' as 'success' | 'reject-rhi-not-available',
|
|
224
|
+
rhiErrorCtor: undefined as undefined | (new (...args: never[]) => Error),
|
|
225
|
+
reset(): void {
|
|
226
|
+
this.webgpuImportCount = 0;
|
|
227
|
+
this.wgpuImportCount = 0;
|
|
228
|
+
this.ensureReadyCount = 0;
|
|
229
|
+
this.rhiWebgpuRequestAdapterShould = 'success';
|
|
230
|
+
this.rhiWgpuEnsureReadyShould = 'success';
|
|
231
|
+
this.rhiWgpuRequestAdapterShould = 'success';
|
|
232
|
+
},
|
|
233
|
+
}));
|
|
234
|
+
function makeFakeRhiDevice(): Record<string, unknown> {
|
|
235
|
+
let resolveLost!: (info: unknown) => void;
|
|
236
|
+
const lost = new Promise<unknown>((res) => {
|
|
237
|
+
resolveLost = res;
|
|
238
|
+
});
|
|
239
|
+
void resolveLost;
|
|
240
|
+
return {
|
|
241
|
+
__brand: 'RhiDevice',
|
|
242
|
+
lost,
|
|
243
|
+
features: new Set<string>(),
|
|
244
|
+
limits: {},
|
|
245
|
+
// feat-20260707 M5 / w33: createRenderer projects RhiCaps.textureCompression*
|
|
246
|
+
// into TranscodeCaps right after building the registry, so the fake device
|
|
247
|
+
// must expose a caps object (the real RhiDevice always carries one).
|
|
248
|
+
caps: {
|
|
249
|
+
textureCompressionBc: false,
|
|
250
|
+
textureCompressionEtc2: false,
|
|
251
|
+
textureCompressionAstc: false,
|
|
252
|
+
},
|
|
253
|
+
queue: {
|
|
254
|
+
submit: () => undefined,
|
|
255
|
+
writeBuffer: () => undefined,
|
|
256
|
+
},
|
|
257
|
+
createBuffer: () => ({ ok: true, value: { __brand: 'Buffer' } }),
|
|
258
|
+
createTexture: () => ({ ok: true, value: { __brand: 'Texture' } }),
|
|
259
|
+
createBindGroupLayout: () => ({ ok: true, value: { __brand: 'BindGroupLayout' } }),
|
|
260
|
+
createBindGroup: () => ({ ok: true, value: { __brand: 'BindGroup' } }),
|
|
261
|
+
createPipelineLayout: () => ({ ok: true, value: { __brand: 'PipelineLayout' } }),
|
|
262
|
+
createRenderPipeline: () => ({ ok: true, value: { __brand: 'RenderPipeline' } }),
|
|
263
|
+
createSampler: () => ({ ok: true, value: { __brand: 'Sampler' } }),
|
|
264
|
+
createShaderModule: () => ({
|
|
265
|
+
ok: true,
|
|
266
|
+
value: { __brand: 'ShaderModule' },
|
|
267
|
+
}),
|
|
268
|
+
createTextureView: () => ({ ok: true, value: { __brand: 'TextureView' } }),
|
|
269
|
+
createCommandEncoder: () => ({
|
|
270
|
+
ok: true,
|
|
271
|
+
value: {
|
|
272
|
+
beginRenderPass: () => ({
|
|
273
|
+
setPipeline: () => undefined,
|
|
274
|
+
setBindGroup: () => undefined,
|
|
275
|
+
setVertexBuffer: () => undefined,
|
|
276
|
+
setIndexBuffer: () => undefined,
|
|
277
|
+
draw: () => undefined,
|
|
278
|
+
drawIndexed: () => undefined,
|
|
279
|
+
end: () => undefined,
|
|
280
|
+
}),
|
|
281
|
+
finish: () => ({ __brand: 'CommandBuffer' }),
|
|
282
|
+
},
|
|
283
|
+
}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function makeMockCanvas(opts: { webgpu?: 'context' | 'null'; webgl2?: 'context' | 'null' }) {
|
|
287
|
+
return {
|
|
288
|
+
width: 800,
|
|
289
|
+
height: 600,
|
|
290
|
+
getContext(kind: string): unknown {
|
|
291
|
+
if (kind === 'webgpu') {
|
|
292
|
+
if (opts.webgpu === 'context') {
|
|
293
|
+
return {
|
|
294
|
+
__mockTag: 'webgpu-canvas-context',
|
|
295
|
+
configure: () => undefined,
|
|
296
|
+
unconfigure: () => undefined,
|
|
297
|
+
getCurrentTexture: () => ({ createView: () => ({}) }),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
if (kind === 'webgl2') {
|
|
303
|
+
if (opts.webgl2 === 'context') {
|
|
304
|
+
return {
|
|
305
|
+
__mockTag: 'webgl2',
|
|
306
|
+
getExtension: () => null,
|
|
307
|
+
getParameter: () => 1,
|
|
308
|
+
isContextLost: () => false,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
},
|
|
315
|
+
addEventListener() {},
|
|
316
|
+
removeEventListener() {},
|
|
317
|
+
} as unknown as HTMLCanvasElement;
|
|
318
|
+
}
|
|
319
|
+
function _makeStubGPU(): unknown {
|
|
320
|
+
return {
|
|
321
|
+
requestAdapter: async () => ({
|
|
322
|
+
features: new Set<string>(),
|
|
323
|
+
limits: {},
|
|
324
|
+
requestDevice: async () => makeFakeRhiDevice(),
|
|
325
|
+
}),
|
|
326
|
+
getPreferredCanvasFormat: () => 'bgra8unorm',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
void [
|
|
331
|
+
AssetError,
|
|
332
|
+
AssetGuid,
|
|
333
|
+
AssetRegistry,
|
|
334
|
+
DeviceScope,
|
|
335
|
+
EngineEnvironmentError,
|
|
336
|
+
GpuResidencyCache,
|
|
337
|
+
HANDLE_CUBE,
|
|
338
|
+
HANDLE_CYLINDER,
|
|
339
|
+
HANDLE_QUAD,
|
|
340
|
+
HANDLE_SPHERE,
|
|
341
|
+
HANDLE_TRIANGLE,
|
|
342
|
+
INLINE_PACK_LOADERS,
|
|
343
|
+
LoaderRegistry,
|
|
344
|
+
PACK_ARTIFACT_LOADERS,
|
|
345
|
+
PROCEDURAL_FLOATS_PER_VERTEX,
|
|
346
|
+
ShaderRegistry,
|
|
347
|
+
World,
|
|
348
|
+
_makeStubGPU,
|
|
349
|
+
afterEach,
|
|
350
|
+
animationClipLoader,
|
|
351
|
+
assetRegistryModule,
|
|
352
|
+
audioLoader,
|
|
353
|
+
beforeEach,
|
|
354
|
+
buildSceneChildContext,
|
|
355
|
+
createBoxGeometry,
|
|
356
|
+
createDevImportTransport,
|
|
357
|
+
createEngineMetrics,
|
|
358
|
+
createPlaneGeometry,
|
|
359
|
+
createRuntimeRenderer,
|
|
360
|
+
defineComponent,
|
|
361
|
+
deriveBuiltin,
|
|
362
|
+
describe,
|
|
363
|
+
expect,
|
|
364
|
+
expectTypeOf,
|
|
365
|
+
fileURLToPath,
|
|
366
|
+
fontLoader,
|
|
367
|
+
getOrCreateMipmapPipeline,
|
|
368
|
+
it,
|
|
369
|
+
makeFakeRhiDevice,
|
|
370
|
+
makeMockCanvas,
|
|
371
|
+
makeMockShaderRegistry,
|
|
372
|
+
materialLoader,
|
|
373
|
+
meshFromInterleaved,
|
|
374
|
+
meshLoader,
|
|
375
|
+
mipmapCacheSize,
|
|
376
|
+
numMipLevels,
|
|
377
|
+
ok,
|
|
378
|
+
readFileSync,
|
|
379
|
+
requireRenderer,
|
|
380
|
+
resolveAssetHandle,
|
|
381
|
+
rhi,
|
|
382
|
+
rhiOk,
|
|
383
|
+
sceneLoader,
|
|
384
|
+
skeletonLoader,
|
|
385
|
+
skinLoader,
|
|
386
|
+
spies,
|
|
387
|
+
textureLoader,
|
|
388
|
+
toShared,
|
|
389
|
+
toUnique,
|
|
390
|
+
unwrapHandle,
|
|
391
|
+
vi,
|
|
392
|
+
walkMaterialPassesOverSharedRefs,
|
|
393
|
+
wireDefaultLoaders,
|
|
394
|
+
];
|
|
395
|
+
type __MergedKeep =
|
|
396
|
+
| EquirectAsset
|
|
397
|
+
| Handle
|
|
398
|
+
| ImportTransport
|
|
399
|
+
| MaterialAsset
|
|
400
|
+
| MaterialPass
|
|
401
|
+
| SamplerAsset;
|
|
402
|
+
|
|
403
|
+
{
|
|
404
|
+
// --- from resolve-scene-guids.test.ts ---
|
|
405
|
+
// A non-builtin user mesh GUID. Must not collide with the builtin mesh GUIDs
|
|
406
|
+
// pre-registered by the AssetRegistry constructor (HANDLE_CUBE is
|
|
407
|
+
// cbe42beb-..., etc., feat-20260603 Tier 0) — those now resolve, so reusing
|
|
408
|
+
// one here would make registerWithGuid throw a collision.
|
|
409
|
+
const MESH_GUID_STR = 'b1c2d3e4-f5a6-4b7c-8d9e-0a1b2c3d4e5f';
|
|
410
|
+
const MATERIAL_GUID_STR = 'f6af7007-158f-4d92-9e47-93bf2f213e1f';
|
|
411
|
+
const SKELETON_GUID_STR = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
|
412
|
+
const UNREGISTERED_GUID_STR = 'deadbeef-dead-beef-dead-beefdeadbeef';
|
|
413
|
+
|
|
414
|
+
function parseGuid(s: string): AssetGuid {
|
|
415
|
+
const parsed = AssetGuid.parse(s);
|
|
416
|
+
if (!parsed.ok) throw new Error(`invalid test GUID: ${s}`);
|
|
417
|
+
return parsed.value;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function localId(n: number): LocalEntityId {
|
|
421
|
+
return n as LocalEntityId;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/** Build a minimal SceneAsset suitable for _resolveSceneGuids testing. */
|
|
425
|
+
function buildTestAsset(
|
|
426
|
+
nodes: Array<{ localId: number; components: Record<string, Record<string, unknown>> }>,
|
|
427
|
+
): SceneAsset {
|
|
428
|
+
const sceneNodes: SceneEntity[] = nodes.map((n) => ({
|
|
429
|
+
localId: localId(n.localId),
|
|
430
|
+
components: n.components,
|
|
431
|
+
}));
|
|
432
|
+
return { kind: 'scene', entities: sceneNodes };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
describe('w4 - _resolveSceneGuids success path', () => {
|
|
436
|
+
it('(a) resolves GUID string handle fields to Handle numbers via schema-driven handle<> detection', () => {
|
|
437
|
+
// Register components so World knows their schemas (plan-strategy D-4:
|
|
438
|
+
// _resolveSceneGuids uses world._getComponentByName to read fieldType).
|
|
439
|
+
defineComponent('Transform', { pos: 'array<f32, 3>' });
|
|
440
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
441
|
+
|
|
442
|
+
// Pre-register mesh asset so resolveGuid finds it.
|
|
443
|
+
const meshGuid = parseGuid(MESH_GUID_STR);
|
|
444
|
+
reg.catalog(meshGuid, {
|
|
445
|
+
kind: 'mesh',
|
|
446
|
+
vertices: new Float32Array(0),
|
|
447
|
+
indices: new Uint16Array(0),
|
|
448
|
+
attributes: { position: new Float32Array(0) },
|
|
449
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
450
|
+
submeshes: [
|
|
451
|
+
{
|
|
452
|
+
indexOffset: 0,
|
|
453
|
+
indexCount: 0,
|
|
454
|
+
vertexCount: 0,
|
|
455
|
+
materialSlot: 0,
|
|
456
|
+
topology: 'triangle-list',
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Also register material and MeshRenderer in world so we verify
|
|
462
|
+
// multi-handle-type detection (MeshFilter + MeshRenderer both have handle fields).
|
|
463
|
+
const materialGuid = parseGuid(MATERIAL_GUID_STR);
|
|
464
|
+
reg.catalog(materialGuid, {
|
|
465
|
+
kind: 'material',
|
|
466
|
+
passes: [{ name: 'forward', program: { module: 'test::dummy' } }],
|
|
467
|
+
values: {},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
// Build a SceneAsset with GUID strings in both MeshFilter.assetHandle and
|
|
471
|
+
// MeshRenderer.material (simulating parseScenePayload refs replacement from M1).
|
|
472
|
+
const asset = buildTestAsset([
|
|
473
|
+
{
|
|
474
|
+
localId: 0,
|
|
475
|
+
components: {
|
|
476
|
+
Transform: { pos: [1, 0, 0] },
|
|
477
|
+
MeshFilter: { assetHandle: MESH_GUID_STR },
|
|
478
|
+
MeshRenderer: { materials: [MATERIAL_GUID_STR] },
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
]);
|
|
482
|
+
|
|
483
|
+
const world = new World();
|
|
484
|
+
const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
485
|
+
const MeshRenderer = defineComponent('MeshRenderer', {
|
|
486
|
+
materials: 'array<shared<MaterialAsset>>',
|
|
487
|
+
});
|
|
488
|
+
world.components.register(MeshFilter);
|
|
489
|
+
world.components.register(MeshRenderer);
|
|
490
|
+
|
|
491
|
+
// Private function access for unit-test isolation (same pattern as parseAssetPayload in
|
|
492
|
+
// asset-registry-scene.test.ts).
|
|
493
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access for round-trip test
|
|
494
|
+
const internal = reg as any as {
|
|
495
|
+
_resolveSceneGuids(
|
|
496
|
+
scene: SceneAsset,
|
|
497
|
+
world: World,
|
|
498
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
499
|
+
};
|
|
500
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
501
|
+
|
|
502
|
+
expect(result.ok).toBe(true);
|
|
503
|
+
if (!result.ok) return;
|
|
504
|
+
const resolvedAsset = result.value;
|
|
505
|
+
expect(resolvedAsset.kind).toBe('scene');
|
|
506
|
+
expect(resolvedAsset.entities.length).toBe(1);
|
|
507
|
+
|
|
508
|
+
const resolvedComp = resolvedAsset.entities[0]?.components as Record<
|
|
509
|
+
string,
|
|
510
|
+
Record<string, unknown>
|
|
511
|
+
>;
|
|
512
|
+
// assetHandle should now be a Handle number (not a string)
|
|
513
|
+
expect(resolvedComp.MeshFilter?.assetHandle).toBeTypeOf('number');
|
|
514
|
+
// materials[0] should also be a Handle number (not a string)
|
|
515
|
+
const matsValue = resolvedComp.MeshRenderer?.materials as readonly unknown[] | undefined;
|
|
516
|
+
expect(Array.isArray(matsValue)).toBe(true);
|
|
517
|
+
expect(matsValue?.[0]).toBeTypeOf('number');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('(b) Skin.skeleton field resolves correctly', () => {
|
|
521
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
522
|
+
|
|
523
|
+
const skelGuid = parseGuid(SKELETON_GUID_STR);
|
|
524
|
+
reg.catalog(skelGuid, {
|
|
525
|
+
kind: 'skeleton',
|
|
526
|
+
inverseBindMatrices: new Float32Array(3 * 16),
|
|
527
|
+
jointCount: 3,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const asset = buildTestAsset([
|
|
531
|
+
{
|
|
532
|
+
localId: 0,
|
|
533
|
+
components: {
|
|
534
|
+
Skin: { skeleton: SKELETON_GUID_STR, joints: [1, 2] },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
]);
|
|
538
|
+
|
|
539
|
+
const world = new World();
|
|
540
|
+
const Skin = defineComponent('Skin', {
|
|
541
|
+
skeleton: 'shared<SkeletonAsset>',
|
|
542
|
+
joints: 'array<entity>',
|
|
543
|
+
});
|
|
544
|
+
world.components.register(Skin);
|
|
545
|
+
|
|
546
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
547
|
+
const internal = reg as any as {
|
|
548
|
+
_resolveSceneGuids(
|
|
549
|
+
scene: SceneAsset,
|
|
550
|
+
world: World,
|
|
551
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
552
|
+
};
|
|
553
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
554
|
+
|
|
555
|
+
expect(result.ok).toBe(true);
|
|
556
|
+
if (!result.ok) return;
|
|
557
|
+
const resolvedAsset = result.value;
|
|
558
|
+
const resolvedComp = resolvedAsset.entities[0]?.components as Record<
|
|
559
|
+
string,
|
|
560
|
+
Record<string, unknown>
|
|
561
|
+
>;
|
|
562
|
+
// skeleton should now be a Handle number
|
|
563
|
+
expect(resolvedComp.Skin?.skeleton).toBeTypeOf('number');
|
|
564
|
+
// joints (array<entity>) should NOT be touched — it's not a handle field
|
|
565
|
+
expect(resolvedComp.Skin?.joints).toEqual([1, 2]);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('(c) same GUID referenced by multiple nodes resolves to the same Handle number', () => {
|
|
569
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
570
|
+
|
|
571
|
+
const meshGuid = parseGuid(MESH_GUID_STR);
|
|
572
|
+
reg.catalog(meshGuid, {
|
|
573
|
+
kind: 'mesh',
|
|
574
|
+
vertices: new Float32Array(0),
|
|
575
|
+
indices: new Uint16Array(0),
|
|
576
|
+
attributes: { position: new Float32Array(0) },
|
|
577
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
578
|
+
submeshes: [
|
|
579
|
+
{
|
|
580
|
+
indexOffset: 0,
|
|
581
|
+
indexCount: 0,
|
|
582
|
+
vertexCount: 0,
|
|
583
|
+
materialSlot: 0,
|
|
584
|
+
topology: 'triangle-list',
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
const asset = buildTestAsset([
|
|
590
|
+
{
|
|
591
|
+
localId: 0,
|
|
592
|
+
components: {
|
|
593
|
+
MeshFilter: { assetHandle: MESH_GUID_STR },
|
|
594
|
+
},
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
localId: 1,
|
|
598
|
+
components: {
|
|
599
|
+
MeshFilter: { assetHandle: MESH_GUID_STR },
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
]);
|
|
603
|
+
|
|
604
|
+
const world = new World();
|
|
605
|
+
const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
606
|
+
world.components.register(MeshFilter);
|
|
607
|
+
|
|
608
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
609
|
+
const internal = reg as any as {
|
|
610
|
+
_resolveSceneGuids(
|
|
611
|
+
scene: SceneAsset,
|
|
612
|
+
world: World,
|
|
613
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
614
|
+
};
|
|
615
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
616
|
+
|
|
617
|
+
expect(result.ok).toBe(true);
|
|
618
|
+
if (!result.ok) return;
|
|
619
|
+
const resolvedAsset = result.value;
|
|
620
|
+
const comp0 = (
|
|
621
|
+
resolvedAsset.entities[0]?.components as Record<string, Record<string, unknown>>
|
|
622
|
+
).MeshFilter;
|
|
623
|
+
const comp1 = (
|
|
624
|
+
resolvedAsset.entities[1]?.components as Record<string, Record<string, unknown>>
|
|
625
|
+
).MeshFilter;
|
|
626
|
+
expect(comp0?.assetHandle).toBe(comp1?.assetHandle);
|
|
627
|
+
expect(comp0?.assetHandle).toBeTypeOf('number');
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('(d) nodes without handle fields pass through unchanged', () => {
|
|
631
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
632
|
+
|
|
633
|
+
const asset = buildTestAsset([{ localId: 0, components: { Transform: { pos: [1, 2, 3] } } }]);
|
|
634
|
+
|
|
635
|
+
const world = new World();
|
|
636
|
+
defineComponent('Transform', { pos: 'array<f32, 3>' });
|
|
637
|
+
|
|
638
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
639
|
+
const internal = reg as any as {
|
|
640
|
+
_resolveSceneGuids(
|
|
641
|
+
scene: SceneAsset,
|
|
642
|
+
world: World,
|
|
643
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
644
|
+
};
|
|
645
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
646
|
+
|
|
647
|
+
expect(result.ok).toBe(true);
|
|
648
|
+
if (!result.ok) return;
|
|
649
|
+
const resolvedAsset = result.value;
|
|
650
|
+
const comp = (
|
|
651
|
+
resolvedAsset.entities[0]?.components as Record<string, Record<string, unknown>>
|
|
652
|
+
).Transform;
|
|
653
|
+
expect(comp).toEqual({ pos: [1, 2, 3] });
|
|
654
|
+
});
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
describe('w5 - _resolveSceneGuids error path', () => {
|
|
658
|
+
it('(e) unregistered GUID returns asset-not-found with hint containing GUID, localId, and field', () => {
|
|
659
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
660
|
+
|
|
661
|
+
const asset = buildTestAsset([
|
|
662
|
+
{
|
|
663
|
+
localId: 0,
|
|
664
|
+
components: {
|
|
665
|
+
MeshFilter: { assetHandle: UNREGISTERED_GUID_STR },
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
]);
|
|
669
|
+
|
|
670
|
+
const world = new World();
|
|
671
|
+
const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
672
|
+
world.components.register(MeshFilter);
|
|
673
|
+
|
|
674
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
675
|
+
const internal = reg as any as {
|
|
676
|
+
_resolveSceneGuids(
|
|
677
|
+
scene: SceneAsset,
|
|
678
|
+
world: World,
|
|
679
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
680
|
+
};
|
|
681
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
682
|
+
|
|
683
|
+
expect(result.ok).toBe(false);
|
|
684
|
+
if (result.ok) return;
|
|
685
|
+
const e = result.error as { code: string; hint: string };
|
|
686
|
+
expect(e.code).toBe('asset-not-found');
|
|
687
|
+
// Hint must contain the GUID, localId, and field for AI-user debuggability (AC-04)
|
|
688
|
+
expect(e.hint).toContain(UNREGISTERED_GUID_STR);
|
|
689
|
+
expect(e.hint).toContain('0'); // localId
|
|
690
|
+
expect(e.hint).toContain('assetHandle');
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('(f) stop-on-first-error: only the first unregistered GUID among multiple nodes is reported', () => {
|
|
694
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
695
|
+
|
|
696
|
+
// Register the first GUID but not the second — first node (localId=1) should
|
|
697
|
+
// fail before reaching localId=2
|
|
698
|
+
const meshGuid = parseGuid(MESH_GUID_STR);
|
|
699
|
+
reg.catalog(meshGuid, {
|
|
700
|
+
kind: 'mesh',
|
|
701
|
+
vertices: new Float32Array(0),
|
|
702
|
+
indices: new Uint16Array(0),
|
|
703
|
+
attributes: { position: new Float32Array(0) },
|
|
704
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
705
|
+
submeshes: [
|
|
706
|
+
{
|
|
707
|
+
indexOffset: 0,
|
|
708
|
+
indexCount: 0,
|
|
709
|
+
vertexCount: 0,
|
|
710
|
+
materialSlot: 0,
|
|
711
|
+
topology: 'triangle-list',
|
|
712
|
+
},
|
|
713
|
+
],
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
const SECOND_UNREGISTERED = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
|
|
717
|
+
|
|
718
|
+
const asset = buildTestAsset([
|
|
719
|
+
{
|
|
720
|
+
localId: 1,
|
|
721
|
+
components: {
|
|
722
|
+
MeshFilter: { assetHandle: UNREGISTERED_GUID_STR },
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
localId: 2,
|
|
727
|
+
components: {
|
|
728
|
+
MeshFilter: { assetHandle: SECOND_UNREGISTERED },
|
|
729
|
+
},
|
|
730
|
+
},
|
|
731
|
+
]);
|
|
732
|
+
|
|
733
|
+
const world = new World();
|
|
734
|
+
const MeshFilter = defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
735
|
+
world.components.register(MeshFilter);
|
|
736
|
+
|
|
737
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
738
|
+
const internal = reg as any as {
|
|
739
|
+
_resolveSceneGuids(
|
|
740
|
+
scene: SceneAsset,
|
|
741
|
+
world: World,
|
|
742
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
743
|
+
};
|
|
744
|
+
const result = internal._resolveSceneGuids(asset, world);
|
|
745
|
+
|
|
746
|
+
expect(result.ok).toBe(false);
|
|
747
|
+
if (result.ok) return;
|
|
748
|
+
const e = result.error as { code: string; hint: string };
|
|
749
|
+
expect(e.code).toBe('asset-not-found');
|
|
750
|
+
// Hint should reference the first failing node (localId=1), not the second
|
|
751
|
+
expect(e.hint).toContain(UNREGISTERED_GUID_STR);
|
|
752
|
+
expect(e.hint).toContain('1'); // localId of the first failing node
|
|
753
|
+
// Must NOT contain the second GUID (stopped before reaching it)
|
|
754
|
+
expect(e.hint).not.toContain(SECOND_UNREGISTERED.slice(0, 8)); // prefix match sufficient
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
describe('w10 - M3 reverse-decode from envelope.refs + buildSceneChildContext from edge lookup', () => {
|
|
758
|
+
const MATERIAL2_GUID_STR = 'e1e2e3e4-a5a6-4b7c-8d9e-0a1b2c3d4e5f';
|
|
759
|
+
const MATERIAL3_GUID_STR = 'd1d2d3d4-b5b6-4c7d-8e9f-0a1b2c3d4e5f';
|
|
760
|
+
const TEXTURE_GUID_STR = 'cccccccc-aaaa-bbbb-cccc-dddddddddddd';
|
|
761
|
+
|
|
762
|
+
function makeTestMesh(): MeshAsset {
|
|
763
|
+
return {
|
|
764
|
+
kind: 'mesh',
|
|
765
|
+
vertices: new Float32Array(0),
|
|
766
|
+
indices: new Uint16Array(0),
|
|
767
|
+
attributes: { position: new Float32Array(0) },
|
|
768
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
769
|
+
submeshes: [
|
|
770
|
+
{
|
|
771
|
+
indexOffset: 0,
|
|
772
|
+
indexCount: 0,
|
|
773
|
+
vertexCount: 0,
|
|
774
|
+
materialSlot: 0,
|
|
775
|
+
topology: 'triangle-list' as const,
|
|
776
|
+
},
|
|
777
|
+
],
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function makeTestMaterial(): MaterialAsset {
|
|
782
|
+
return {
|
|
783
|
+
kind: 'material',
|
|
784
|
+
passes: [{ name: 'forward', program: { module: 'test::dummy' } }],
|
|
785
|
+
values: {},
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function registerAsset<T extends Asset>(reg: AssetRegistry, guidStr: string, asset: T): void {
|
|
790
|
+
const guid = parseGuid(guidStr);
|
|
791
|
+
reg.catalog(guid, asset);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
it('(a) per-field equivalence: scalar handle + array handle resolve correctly via envelope.refs', () => {
|
|
795
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
796
|
+
|
|
797
|
+
registerAsset(reg, MESH_GUID_STR, makeTestMesh());
|
|
798
|
+
registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
|
|
799
|
+
registerAsset(reg, MATERIAL2_GUID_STR, makeTestMaterial());
|
|
800
|
+
registerAsset(reg, MATERIAL3_GUID_STR, makeTestMaterial());
|
|
801
|
+
|
|
802
|
+
// Build a scene and catalogue it with refs that carry edge metadata
|
|
803
|
+
const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
|
|
804
|
+
const sceneGuid = parseGuid(sceneGuidStr);
|
|
805
|
+
const sceneAsset = buildTestAsset([
|
|
806
|
+
{
|
|
807
|
+
localId: 0,
|
|
808
|
+
components: {
|
|
809
|
+
Transform: { pos: [1, 0, 0] },
|
|
810
|
+
MeshFilter: { assetHandle: MESH_GUID_STR },
|
|
811
|
+
MeshRenderer: {
|
|
812
|
+
materials: [MATERIAL_GUID_STR, MATERIAL2_GUID_STR, MATERIAL3_GUID_STR],
|
|
813
|
+
},
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
// Catalogue the scene WITH refs edge metadata (envelope.refs path)
|
|
819
|
+
const refs: import('@forgeax/engine-types').AssetRef[] = [
|
|
820
|
+
{
|
|
821
|
+
guid: MESH_GUID_STR,
|
|
822
|
+
sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
|
|
823
|
+
sceneEntityId: 0,
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
guid: MATERIAL_GUID_STR,
|
|
827
|
+
sourceField: {
|
|
828
|
+
componentName: 'MeshRenderer',
|
|
829
|
+
fieldName: 'materials',
|
|
830
|
+
arrayIndex: 0,
|
|
831
|
+
},
|
|
832
|
+
sceneEntityId: 0,
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
guid: MATERIAL2_GUID_STR,
|
|
836
|
+
sourceField: {
|
|
837
|
+
componentName: 'MeshRenderer',
|
|
838
|
+
fieldName: 'materials',
|
|
839
|
+
arrayIndex: 1,
|
|
840
|
+
},
|
|
841
|
+
sceneEntityId: 0,
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
guid: MATERIAL3_GUID_STR,
|
|
845
|
+
sourceField: {
|
|
846
|
+
componentName: 'MeshRenderer',
|
|
847
|
+
fieldName: 'materials',
|
|
848
|
+
arrayIndex: 2,
|
|
849
|
+
},
|
|
850
|
+
sceneEntityId: 0,
|
|
851
|
+
},
|
|
852
|
+
];
|
|
853
|
+
reg.catalog(sceneGuid, sceneAsset, refs);
|
|
854
|
+
|
|
855
|
+
const world = new World();
|
|
856
|
+
defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
857
|
+
defineComponent('MeshRenderer', { materials: 'array<shared<MaterialAsset>>' });
|
|
858
|
+
|
|
859
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
860
|
+
const internal = reg as any as {
|
|
861
|
+
_resolveSceneGuids(
|
|
862
|
+
scene: SceneAsset,
|
|
863
|
+
world: World,
|
|
864
|
+
sceneGuidKey?: string,
|
|
865
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
866
|
+
};
|
|
867
|
+
const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
|
|
868
|
+
|
|
869
|
+
expect(result.ok).toBe(true);
|
|
870
|
+
if (!result.ok) return;
|
|
871
|
+
const resolvedAsset = result.value;
|
|
872
|
+
expect(resolvedAsset.kind).toBe('scene');
|
|
873
|
+
expect(resolvedAsset.entities.length).toBe(1);
|
|
874
|
+
|
|
875
|
+
const resolvedComp = resolvedAsset.entities[0]?.components as Record<
|
|
876
|
+
string,
|
|
877
|
+
Record<string, unknown>
|
|
878
|
+
>;
|
|
879
|
+
// Scalar handle
|
|
880
|
+
expect(resolvedComp.MeshFilter?.assetHandle).toBeTypeOf('number');
|
|
881
|
+
// Array handles — all 3 slots
|
|
882
|
+
const mats = resolvedComp.MeshRenderer?.materials as readonly unknown[] | undefined;
|
|
883
|
+
expect(Array.isArray(mats)).toBe(true);
|
|
884
|
+
expect(mats?.length).toBe(3);
|
|
885
|
+
expect(mats?.[0]).toBeTypeOf('number');
|
|
886
|
+
expect(mats?.[1]).toBeTypeOf('number');
|
|
887
|
+
expect(mats?.[2]).toBeTypeOf('number');
|
|
888
|
+
// Each slot has a distinct material GUID → distinct handle
|
|
889
|
+
expect(mats?.[0]).not.toBe(mats?.[1]);
|
|
890
|
+
expect(mats?.[1]).not.toBe(mats?.[2]);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it('(b) arrayIndex lossless: array<handle<MaterialAsset>> of 3 → each slot gets correct GUID', () => {
|
|
894
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
895
|
+
|
|
896
|
+
registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
|
|
897
|
+
registerAsset(reg, MATERIAL2_GUID_STR, makeTestMaterial());
|
|
898
|
+
registerAsset(reg, MATERIAL3_GUID_STR, makeTestMaterial());
|
|
899
|
+
|
|
900
|
+
const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
|
|
901
|
+
const sceneGuid = parseGuid(sceneGuidStr);
|
|
902
|
+
const sceneAsset = buildTestAsset([
|
|
903
|
+
{
|
|
904
|
+
localId: 0,
|
|
905
|
+
components: {
|
|
906
|
+
MeshRenderer: {
|
|
907
|
+
materials: [MATERIAL_GUID_STR, MATERIAL2_GUID_STR, MATERIAL3_GUID_STR],
|
|
908
|
+
},
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
]);
|
|
912
|
+
|
|
913
|
+
const refs: import('@forgeax/engine-types').AssetRef[] = [
|
|
914
|
+
{
|
|
915
|
+
guid: MATERIAL_GUID_STR,
|
|
916
|
+
sourceField: {
|
|
917
|
+
componentName: 'MeshRenderer',
|
|
918
|
+
fieldName: 'materials',
|
|
919
|
+
arrayIndex: 0,
|
|
920
|
+
},
|
|
921
|
+
sceneEntityId: 0,
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
guid: MATERIAL2_GUID_STR,
|
|
925
|
+
sourceField: {
|
|
926
|
+
componentName: 'MeshRenderer',
|
|
927
|
+
fieldName: 'materials',
|
|
928
|
+
arrayIndex: 1,
|
|
929
|
+
},
|
|
930
|
+
sceneEntityId: 0,
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
guid: MATERIAL3_GUID_STR,
|
|
934
|
+
sourceField: {
|
|
935
|
+
componentName: 'MeshRenderer',
|
|
936
|
+
fieldName: 'materials',
|
|
937
|
+
arrayIndex: 2,
|
|
938
|
+
},
|
|
939
|
+
sceneEntityId: 0,
|
|
940
|
+
},
|
|
941
|
+
];
|
|
942
|
+
reg.catalog(sceneGuid, sceneAsset, refs);
|
|
943
|
+
|
|
944
|
+
const world = new World();
|
|
945
|
+
defineComponent('MeshRenderer', { materials: 'array<shared<MaterialAsset>>' });
|
|
946
|
+
|
|
947
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
948
|
+
const internal = reg as any as {
|
|
949
|
+
_resolveSceneGuids(
|
|
950
|
+
scene: SceneAsset,
|
|
951
|
+
world: World,
|
|
952
|
+
sceneGuidKey?: string,
|
|
953
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
954
|
+
};
|
|
955
|
+
const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
|
|
956
|
+
|
|
957
|
+
expect(result.ok).toBe(true);
|
|
958
|
+
if (!result.ok) return;
|
|
959
|
+
const resolvedComp = result.value.entities[0]?.components as Record<
|
|
960
|
+
string,
|
|
961
|
+
Record<string, unknown>
|
|
962
|
+
>;
|
|
963
|
+
const mats = resolvedComp.MeshRenderer?.materials as readonly number[] | undefined;
|
|
964
|
+
expect(mats?.length).toBe(3);
|
|
965
|
+
// Verify each slot is a number (resolved handle) and distinct
|
|
966
|
+
expect(typeof mats?.[0]).toBe('number');
|
|
967
|
+
expect(typeof mats?.[1]).toBe('number');
|
|
968
|
+
expect(typeof mats?.[2]).toBe('number');
|
|
969
|
+
// Different GUIDs → different handles
|
|
970
|
+
expect(mats?.[0]).not.toBe(mats?.[1]);
|
|
971
|
+
expect(mats?.[1]).not.toBe(mats?.[2]);
|
|
972
|
+
expect(mats?.[0]).not.toBe(mats?.[2]);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it('(c) dedup contract: same GUID from multiple entities → same handle', () => {
|
|
976
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
977
|
+
|
|
978
|
+
registerAsset(reg, MESH_GUID_STR, makeTestMesh());
|
|
979
|
+
|
|
980
|
+
const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
|
|
981
|
+
const sceneGuid = parseGuid(sceneGuidStr);
|
|
982
|
+
const sceneAsset = buildTestAsset([
|
|
983
|
+
{
|
|
984
|
+
localId: 0,
|
|
985
|
+
components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
localId: 1,
|
|
989
|
+
components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
|
|
990
|
+
},
|
|
991
|
+
]);
|
|
992
|
+
|
|
993
|
+
const refs: import('@forgeax/engine-types').AssetRef[] = [
|
|
994
|
+
{
|
|
995
|
+
guid: MESH_GUID_STR,
|
|
996
|
+
sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
|
|
997
|
+
sceneEntityId: 0,
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
guid: MESH_GUID_STR,
|
|
1001
|
+
sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
|
|
1002
|
+
sceneEntityId: 1,
|
|
1003
|
+
},
|
|
1004
|
+
];
|
|
1005
|
+
reg.catalog(sceneGuid, sceneAsset, refs);
|
|
1006
|
+
|
|
1007
|
+
const world = new World();
|
|
1008
|
+
defineComponent('MeshFilter', { assetHandle: 'shared<MeshAsset>' });
|
|
1009
|
+
|
|
1010
|
+
// biome-ignore lint/suspicious/noExplicitAny: private helper access
|
|
1011
|
+
const internal = reg as any as {
|
|
1012
|
+
_resolveSceneGuids(
|
|
1013
|
+
scene: SceneAsset,
|
|
1014
|
+
world: World,
|
|
1015
|
+
sceneGuidKey?: string,
|
|
1016
|
+
): { ok: true; value: SceneAsset } | { ok: false; error: unknown };
|
|
1017
|
+
};
|
|
1018
|
+
const result = internal._resolveSceneGuids(sceneAsset, world, sceneGuidStr.toLowerCase());
|
|
1019
|
+
|
|
1020
|
+
expect(result.ok).toBe(true);
|
|
1021
|
+
if (!result.ok) return;
|
|
1022
|
+
const comp0 = (
|
|
1023
|
+
result.value.entities[0]?.components as Record<string, Record<string, unknown>>
|
|
1024
|
+
).MeshFilter;
|
|
1025
|
+
const comp1 = (
|
|
1026
|
+
result.value.entities[1]?.components as Record<string, Record<string, unknown>>
|
|
1027
|
+
).MeshFilter;
|
|
1028
|
+
// Same GUID → same handle (one allocSharedRef per unique payload)
|
|
1029
|
+
expect(comp0?.assetHandle).toBe(comp1?.assetHandle);
|
|
1030
|
+
expect(comp0?.assetHandle).toBeTypeOf('number');
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('(d) breadcrumb from envelope.refs: buildSceneChildContext returns correct sceneEntityId + componentField', () => {
|
|
1034
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
1035
|
+
|
|
1036
|
+
registerAsset(reg, MESH_GUID_STR, makeTestMesh());
|
|
1037
|
+
|
|
1038
|
+
const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
|
|
1039
|
+
const sceneGuid = parseGuid(sceneGuidStr);
|
|
1040
|
+
const sceneAsset = buildTestAsset([
|
|
1041
|
+
{
|
|
1042
|
+
localId: 5,
|
|
1043
|
+
components: { MeshFilter: { assetHandle: MESH_GUID_STR } },
|
|
1044
|
+
},
|
|
1045
|
+
]);
|
|
1046
|
+
|
|
1047
|
+
const refs: import('@forgeax/engine-types').AssetRef[] = [
|
|
1048
|
+
{
|
|
1049
|
+
guid: MESH_GUID_STR,
|
|
1050
|
+
sourceField: { componentName: 'MeshFilter', fieldName: 'assetHandle' },
|
|
1051
|
+
sceneEntityId: 5,
|
|
1052
|
+
},
|
|
1053
|
+
];
|
|
1054
|
+
reg.catalog(sceneGuid, sceneAsset, refs);
|
|
1055
|
+
|
|
1056
|
+
const ctx = buildSceneChildContext(reg, sceneAsset, MESH_GUID_STR.toLowerCase());
|
|
1057
|
+
|
|
1058
|
+
expect(ctx).toBeDefined();
|
|
1059
|
+
expect(ctx?.sceneEntityId).toBe(5);
|
|
1060
|
+
expect(ctx?.componentField).toBe('MeshFilter.assetHandle');
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
it('(e) texture edge: sourceField=undefined → buildSceneChildContext returns componentField undefined', () => {
|
|
1064
|
+
const reg = new AssetRegistry(makeMockShaderRegistry());
|
|
1065
|
+
|
|
1066
|
+
registerAsset(reg, MATERIAL_GUID_STR, makeTestMaterial());
|
|
1067
|
+
|
|
1068
|
+
const textureGuid = parseGuid(TEXTURE_GUID_STR);
|
|
1069
|
+
reg.catalog(textureGuid, {
|
|
1070
|
+
kind: 'texture',
|
|
1071
|
+
// biome-ignore lint/suspicious/noExplicitAny: test fixture uses minimal texture shape
|
|
1072
|
+
texture: {} as any,
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
const sceneGuidStr = '00001111-2222-3333-4444-555566667777';
|
|
1076
|
+
const sceneGuid = parseGuid(sceneGuidStr);
|
|
1077
|
+
const sceneAsset = buildTestAsset([
|
|
1078
|
+
{
|
|
1079
|
+
localId: 0,
|
|
1080
|
+
components: {
|
|
1081
|
+
MeshRenderer: { materials: [MATERIAL_GUID_STR] },
|
|
1082
|
+
},
|
|
1083
|
+
},
|
|
1084
|
+
]);
|
|
1085
|
+
|
|
1086
|
+
// Scene refs include a texture edge (flat superset per D-2) with
|
|
1087
|
+
// sourceField=undefined — texture has no per-entity origin.
|
|
1088
|
+
const refs: import('@forgeax/engine-types').AssetRef[] = [
|
|
1089
|
+
{
|
|
1090
|
+
guid: MATERIAL_GUID_STR,
|
|
1091
|
+
sourceField: {
|
|
1092
|
+
componentName: 'MeshRenderer',
|
|
1093
|
+
fieldName: 'materials',
|
|
1094
|
+
arrayIndex: 0,
|
|
1095
|
+
},
|
|
1096
|
+
sceneEntityId: 0,
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
guid: TEXTURE_GUID_STR,
|
|
1100
|
+
// sourceField intentionally omitted — texture edge, D-2
|
|
1101
|
+
sceneEntityId: undefined,
|
|
1102
|
+
},
|
|
1103
|
+
];
|
|
1104
|
+
reg.catalog(sceneGuid, sceneAsset, refs);
|
|
1105
|
+
|
|
1106
|
+
// Look up the material GUID — should find sceneEntityId + componentField
|
|
1107
|
+
const materialCtx = buildSceneChildContext(
|
|
1108
|
+
reg,
|
|
1109
|
+
sceneAsset,
|
|
1110
|
+
MATERIAL_GUID_STR.toLowerCase(),
|
|
1111
|
+
);
|
|
1112
|
+
expect(materialCtx).toBeDefined();
|
|
1113
|
+
expect(materialCtx?.componentField).toBe('MeshRenderer.materials[0]');
|
|
1114
|
+
|
|
1115
|
+
// Look up the texture GUID — sourceField=undefined → componentField undefined
|
|
1116
|
+
const textureCtx = buildSceneChildContext(reg, sceneAsset, TEXTURE_GUID_STR.toLowerCase());
|
|
1117
|
+
expect(textureCtx).toBeDefined();
|
|
1118
|
+
expect(textureCtx?.sceneEntityId).toBeUndefined();
|
|
1119
|
+
expect(textureCtx?.componentField).toBeUndefined();
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
}
|