@forgeax/engine-runtime 0.1.2
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/LICENSE +202 -0
- package/README.md +183 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/active-camera.unit.test.d.ts +2 -0
- package/dist/__tests__/active-camera.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-add.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-add.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-asset-union.test-d.d.ts +2 -0
- package/dist/__tests__/animation-graph-asset-union.test-d.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-blend.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-blend.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-build.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-build.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-clip-missing.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-clip.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-clip.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-default-path.integration.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-default-path.integration.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-describe.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-describe.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-nesting.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-nesting.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-orthogonal-weight.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-graph-slot-fill.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-nslot.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-nslot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts +2 -0
- package/dist/__tests__/animation-slot-length-guard.unit.test.d.ts.map +1 -0
- package/dist/__tests__/append-injection.test.d.ts +2 -0
- package/dist/__tests__/append-injection.test.d.ts.map +1 -0
- package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts +2 -0
- package/dist/__tests__/asi-world-baseline-export.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asi-world-ci-config.unit.test.d.ts +2 -0
- package/dist/__tests__/asi-world-ci-config.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asi-world-smoke.unit.test.d.ts +2 -0
- package/dist/__tests__/asi-world-smoke.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-scene.test-d.d.ts +2 -0
- package/dist/__tests__/asset-registry-scene.test-d.d.ts.map +1 -0
- package/dist/__tests__/bind-group-cache-binding.test.d.ts +2 -0
- package/dist/__tests__/bind-group-cache-binding.test.d.ts.map +1 -0
- package/dist/__tests__/bind-group-cache-frame.test.d.ts +2 -0
- package/dist/__tests__/bind-group-cache-frame.test.d.ts.map +1 -0
- package/dist/__tests__/bind-group-cache-keying.test.d.ts +2 -0
- package/dist/__tests__/bind-group-cache-keying.test.d.ts.map +1 -0
- package/dist/__tests__/block-upload-math.unit.test.d.ts +2 -0
- package/dist/__tests__/block-upload-math.unit.test.d.ts.map +1 -0
- package/dist/__tests__/builtin-shader-register-e2e.test.d.ts +2 -0
- package/dist/__tests__/builtin-shader-register-e2e.test.d.ts.map +1 -0
- package/dist/__tests__/cache-false-eviction.test.d.ts +2 -0
- package/dist/__tests__/cache-false-eviction.test.d.ts.map +1 -0
- package/dist/__tests__/cache-isolation-matrix.test.d.ts +2 -0
- package/dist/__tests__/cache-isolation-matrix.test.d.ts.map +1 -0
- package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts +2 -0
- package/dist/__tests__/camera-clear-snapshot-equivalence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts +2 -0
- package/dist/__tests__/clamp-to-last.e2e.browser.test.d.ts.map +1 -0
- package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts +2 -0
- package/dist/__tests__/clamp-to-last.e2e.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/collect-scene-asset.test.d.ts +3 -0
- package/dist/__tests__/collect-scene-asset.test.d.ts.map +1 -0
- package/dist/__tests__/collect-subtree.unit.test.d.ts +2 -0
- package/dist/__tests__/collect-subtree.unit.test.d.ts.map +1 -0
- package/dist/__tests__/collect-transient-children-absent.test.d.ts +2 -0
- package/dist/__tests__/collect-transient-children-absent.test.d.ts.map +1 -0
- package/dist/__tests__/collect-transient-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/collect-transient-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/collect-transient-skip.test.d.ts +3 -0
- package/dist/__tests__/collect-transient-skip.test.d.ts.map +1 -0
- package/dist/__tests__/components.test-d.d.ts +2 -0
- package/dist/__tests__/components.test-d.d.ts.map +1 -0
- package/dist/__tests__/components.unit.test.d.ts +2 -0
- package/dist/__tests__/components.unit.test.d.ts.map +1 -0
- package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts +2 -0
- package/dist/__tests__/composite-skybox-cross-world.browser.test.d.ts.map +1 -0
- package/dist/__tests__/create-renderer-assembly.integration.test.d.ts +2 -0
- package/dist/__tests__/create-renderer-assembly.integration.test.d.ts.map +1 -0
- package/dist/__tests__/createRenderer-dispose.test.d.ts +2 -0
- package/dist/__tests__/createRenderer-dispose.test.d.ts.map +1 -0
- package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts +2 -0
- package/dist/__tests__/createRenderer-requiredFeatures.unit.test.d.ts.map +1 -0
- package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts +2 -0
- package/dist/__tests__/createRenderer.browser-no-webgpu.test.d.ts.map +1 -0
- package/dist/__tests__/createRenderer.unit.test.d.ts +2 -0
- package/dist/__tests__/createRenderer.unit.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/ibl-compose.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/ibl-modules-compose.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/ibl-precompute-readback.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/instances-per-instance-pbr.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/instances-shadow.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/instances-uniform-fallback.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/material-alpha.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/material-cooked-fixture.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/mesh-update.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/mipmap-srgb-linear.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/tonemap.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/tonemap.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/user-handle-mesh-render.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/user-mesh-upload.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn/zero-manifest-ready.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts +2 -0
- package/dist/__tests__/demo-sortscope-migration.unit.test.d.ts.map +1 -0
- package/dist/__tests__/derive-bgl-integration.test.d.ts +2 -0
- package/dist/__tests__/derive-bgl-integration.test.d.ts.map +1 -0
- package/dist/__tests__/derive-render-data-skin.unit.test.d.ts +2 -0
- package/dist/__tests__/derive-render-data-skin.unit.test.d.ts.map +1 -0
- package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts +2 -0
- package/dist/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts +2 -0
- package/dist/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts +2 -0
- package/dist/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts +2 -0
- package/dist/__tests__/dispatch-fullscreen-pass-params.unit.test.d.ts.map +1 -0
- package/dist/__tests__/draw-published.d.ts +5 -0
- package/dist/__tests__/draw-published.d.ts.map +1 -0
- package/dist/__tests__/environment-error-exhaustive.test-d.d.ts +8 -0
- package/dist/__tests__/environment-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/equirect-bc6h.integration.test.d.ts +2 -0
- package/dist/__tests__/equirect-bc6h.integration.test.d.ts.map +1 -0
- package/dist/__tests__/error-exhaustive.test.d.ts +2 -0
- package/dist/__tests__/error-exhaustive.test.d.ts.map +1 -0
- package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts +2 -0
- package/dist/__tests__/extract-frames-graceful-degradation.test.d.ts.map +1 -0
- package/dist/__tests__/extract-frames-merge.test.d.ts +2 -0
- package/dist/__tests__/extract-frames-merge.test.d.ts.map +1 -0
- package/dist/__tests__/extract-frames-reset.test.d.ts +2 -0
- package/dist/__tests__/extract-frames-reset.test.d.ts.map +1 -0
- package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts +2 -0
- package/dist/__tests__/extract-record-no-hardcoded-texture-fields.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-manifest.d.ts +131 -0
- package/dist/__tests__/fog-producer-manifest.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-pixel-probe.d.ts +42 -0
- package/dist/__tests__/fog-producer-pixel-probe.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-pixel.browser.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-pixel.browser.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts +2 -0
- package/dist/__tests__/fog-producer-pixel.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-real-fixture.d.ts +5 -0
- package/dist/__tests__/fog-producer-real-fixture.d.ts.map +1 -0
- package/dist/__tests__/fog-producer-vfx-effect.d.ts +3 -0
- package/dist/__tests__/fog-producer-vfx-effect.d.ts.map +1 -0
- package/dist/__tests__/fog-scene-asset.integration.test.d.ts +2 -0
- package/dist/__tests__/fog-scene-asset.integration.test.d.ts.map +1 -0
- package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts +2 -0
- package/dist/__tests__/frame-targets-shadow-regression.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-post-bgl-depth.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-post-depth-params-arity.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-post-process-pass.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts +2 -0
- package/dist/__tests__/fullscreen-post-process-pass.unit.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts +2 -0
- package/dist/__tests__/fxaa-pixel-diff.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts +2 -0
- package/dist/__tests__/fxaa-validation-and-resize.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts +2 -0
- package/dist/__tests__/fxaa-zero-overhead.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/geometry.unit.test.d.ts +2 -0
- package/dist/__tests__/geometry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/glyph-tilemap-worldid.test.d.ts +2 -0
- package/dist/__tests__/glyph-tilemap-worldid.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-cow-survivor.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource-store-cubemap-status.test.d.ts.map +1 -0
- package/dist/__tests__/gpu-resource.test.d.ts +2 -0
- package/dist/__tests__/gpu-resource.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts +2 -0
- package/dist/__tests__/hdrp-bgl-slots.browser.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-buffers.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-buffers.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts +2 -0
- package/dist/__tests__/hdrp-execute-filter.unit.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts +2 -0
- package/dist/__tests__/hdrp-gbuffer-attachments.browser.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts +2 -0
- package/dist/__tests__/hdrp-pipeline-asset-type.test-d.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-boundary.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-boundary.test.d.ts.map +1 -0
- package/dist/__tests__/hdrp-ssao-falsification.test.d.ts +2 -0
- package/dist/__tests__/hdrp-ssao-falsification.test.d.ts.map +1 -0
- package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts +2 -0
- package/dist/__tests__/hello-tilemap-smoke-upgrade.unit.test.d.ts.map +1 -0
- package/dist/__tests__/helpers/mock-shader-registry.d.ts +11 -0
- package/dist/__tests__/helpers/mock-shader-registry.d.ts.map +1 -0
- package/dist/__tests__/helpers/primitive-world.d.ts +8 -0
- package/dist/__tests__/helpers/primitive-world.d.ts.map +1 -0
- package/dist/__tests__/helpers/register-runtime-components.d.ts +4 -0
- package/dist/__tests__/helpers/register-runtime-components.d.ts.map +1 -0
- package/dist/__tests__/helpers/register-scene-components.d.ts +4 -0
- package/dist/__tests__/helpers/register-scene-components.d.ts.map +1 -0
- package/dist/__tests__/helpers/render-asset-port.d.ts +15 -0
- package/dist/__tests__/helpers/render-asset-port.d.ts.map +1 -0
- package/dist/__tests__/helpers/scene-asset-port.d.ts +9 -0
- package/dist/__tests__/helpers/scene-asset-port.d.ts.map +1 -0
- package/dist/__tests__/helpers/tilemap-assets.d.ts +6 -0
- package/dist/__tests__/helpers/tilemap-assets.d.ts.map +1 -0
- package/dist/__tests__/instances.test-d.d.ts +2 -0
- package/dist/__tests__/instances.test-d.d.ts.map +1 -0
- package/dist/__tests__/light-attenuation-formula.test.d.ts +2 -0
- package/dist/__tests__/light-attenuation-formula.test.d.ts.map +1 -0
- package/dist/__tests__/light-casters-9-light.browser.test.d.ts +2 -0
- package/dist/__tests__/light-casters-9-light.browser.test.d.ts.map +1 -0
- package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts +2 -0
- package/dist/__tests__/light-gpu-byte-neutral.unit.test.d.ts.map +1 -0
- package/dist/__tests__/light-snapshot.test-d.d.ts +2 -0
- package/dist/__tests__/light-snapshot.test-d.d.ts.map +1 -0
- package/dist/__tests__/lights-vec-negative.unit.test.d.ts +2 -0
- package/dist/__tests__/lights-vec-negative.unit.test.d.ts.map +1 -0
- package/dist/__tests__/lights.unit.test.d.ts +2 -0
- package/dist/__tests__/lights.unit.test.d.ts.map +1 -0
- package/dist/__tests__/m3-vec-negative.unit.test.d.ts +2 -0
- package/dist/__tests__/m3-vec-negative.unit.test.d.ts.map +1 -0
- package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts +2 -0
- package/dist/__tests__/m5-browser-dawn-sweep.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-derived-coordinates.integration.test.d.ts +2 -0
- package/dist/__tests__/material-derived-coordinates.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts +2 -0
- package/dist/__tests__/material-texture-uv-scale.unit.test.d.ts.map +1 -0
- package/dist/__tests__/materials.unit.test.d.ts +2 -0
- package/dist/__tests__/materials.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-ssbo-grow.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mip-gate.unit.test.d.ts +2 -0
- package/dist/__tests__/mip-gate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts +2 -0
- package/dist/__tests__/msaa-sprite-pixel-diff.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts +2 -0
- package/dist/__tests__/multi-uv-8set.e2e.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/name.unit.test.d.ts +2 -0
- package/dist/__tests__/name.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pass-selector.unit.test.d.ts +2 -0
- package/dist/__tests__/pass-selector.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pbr-pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/pbr-pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-builder-msaa.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline-cache-keying.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-layout-selection.test.d.ts +2 -0
- package/dist/__tests__/pipeline-layout-selection.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline-spec.test.d.ts +2 -0
- package/dist/__tests__/pipeline-spec.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.unit.test.d.ts +2 -0
- package/dist/__tests__/pipeline.unit.test.d.ts.map +1 -0
- package/dist/__tests__/point-light-shadow.browser.test.d.ts +2 -0
- package/dist/__tests__/point-light-shadow.browser.test.d.ts.map +1 -0
- package/dist/__tests__/point-light-shadow.dawn.test.d.ts +2 -0
- package/dist/__tests__/point-light-shadow.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/point-light-shadow.unit.test.d.ts +2 -0
- package/dist/__tests__/point-light-shadow.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-error-codes.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-error-codes.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-params-component.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-params-component.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-params-extract.unit.test.d.ts +2 -0
- package/dist/__tests__/post-process-params-extract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts +2 -0
- package/dist/__tests__/post-process-reads-narrowing.test-d.d.ts.map +1 -0
- package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts +2 -0
- package/dist/__tests__/post-process-register-roundtrip.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/postprocess-camera-consistency.test.d.ts +2 -0
- package/dist/__tests__/postprocess-camera-consistency.test.d.ts.map +1 -0
- package/dist/__tests__/profiler-assembly.integration.test.d.ts +2 -0
- package/dist/__tests__/profiler-assembly.integration.test.d.ts.map +1 -0
- package/dist/__tests__/propagate-transforms.dawn.test.d.ts +2 -0
- package/dist/__tests__/propagate-transforms.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/record-worldid-isolation.test.d.ts +2 -0
- package/dist/__tests__/record-worldid-isolation.test.d.ts.map +1 -0
- package/dist/__tests__/recover-error-exhaustive.test-d.d.ts +11 -0
- package/dist/__tests__/recover-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts +11 -0
- package/dist/__tests__/render-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-assembly.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics-docs.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-prepared-graphics.regression.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-recover.integration.test.d.ts +2 -0
- package/dist/__tests__/render-feature-recover.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts +2 -0
- package/dist/__tests__/render-feature-runtime-surface.test-d.d.ts.map +1 -0
- package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts +2 -0
- package/dist/__tests__/render-feature-zero-feature.browser.test.d.ts.map +1 -0
- package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-pipeline-trivial.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-schema-consumer.integration.test.d.ts +2 -0
- package/dist/__tests__/render-schema-consumer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/render-schema-owner-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts +2 -0
- package/dist/__tests__/render-system-cross-world-overlay-queue.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts +2 -0
- package/dist/__tests__/render-system-extract-dispatch-ordering.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-extract.test.d.ts +2 -0
- package/dist/__tests__/render-system-extract.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-bucket.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-fallback.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-material.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-material.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-metrics.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-modegate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold-scene-instances.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-fold.dawn.test.d.ts +2 -0
- package/dist/__tests__/render-system-fold.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-mega.test.d.ts +2 -0
- package/dist/__tests__/render-system-mega.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-owner-split-contract.test.d.ts +2 -0
- package/dist/__tests__/render-system-owner-split-contract.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-multi-material-textureview.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts +2 -0
- package/dist/__tests__/render-system-record-per-submesh-transparency.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record.test.d.ts +2 -0
- package/dist/__tests__/render-system-record.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-record.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-record.unit.test.d.ts.map +1 -0
- package/dist/__tests__/render-system-skin-bg.unit.test.d.ts +2 -0
- package/dist/__tests__/render-system-skin-bg.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/renderer-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/renderer-health.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-health.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts +2 -0
- package/dist/__tests__/renderer-lifecycle.integration.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-recover.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-recover.unit.test.d.ts.map +1 -0
- package/dist/__tests__/renderer-result.test-d.d.ts +2 -0
- package/dist/__tests__/renderer-result.test-d.d.ts.map +1 -0
- package/dist/__tests__/renderer-surface.unit.test.d.ts +2 -0
- package/dist/__tests__/renderer-surface.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-depth-only-view.test.d.ts +2 -0
- package/dist/__tests__/resolve-depth-only-view.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-null-command-flow.unit.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-null-noop-behavior.unit.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-null-renderer-lifecycle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/roots-to-scene-asset.test.d.ts +2 -0
- package/dist/__tests__/roots-to-scene-asset.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-authority-scan.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-authority-scan.unit.test.d.ts.map +1 -0
- package/dist/__tests__/runtime-domain-residue.unit.test.d.ts +2 -0
- package/dist/__tests__/runtime-domain-residue.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-equirect-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/scene-equirect-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/scene-instance-bridge.integration.test.d.ts +2 -0
- package/dist/__tests__/scene-instance-bridge.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scene-instance-component.test.d.ts +2 -0
- package/dist/__tests__/scene-instance-component.test.d.ts.map +1 -0
- package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts +2 -0
- package/dist/__tests__/scene-instantiate-cycle-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/serialize-scene-asset.test.d.ts +2 -0
- package/dist/__tests__/serialize-scene-asset.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-caster-empty-schema.test.d.ts +2 -0
- package/dist/__tests__/shadow-caster-empty-schema.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-atlas.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-atlas.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-blend.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-cascade-loadop.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-caster-membership.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-caster-membership.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-component.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-component.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-extract.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-extract.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-orthoz.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-orthoz.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-pssm.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-pssm.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-runtime-vary.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-shader.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-temporal.browser.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-tile-consistency.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-ubo.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-ubo.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-ubo.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-viewport.browser.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-csm-viewport.test.d.ts +2 -0
- package/dist/__tests__/shadow-csm-viewport.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-fields-observable.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-m1.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-m1.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-m2.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-m2.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-m3-calibrate-run.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-m3-calibrate.d.ts +83 -0
- package/dist/__tests__/shadow-m3-calibrate.d.ts.map +1 -0
- package/dist/__tests__/shadow-m3.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-m3.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts +2 -0
- package/dist/__tests__/shadow-mesh-ssbo-order.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-probe-control-flow.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts +2 -0
- package/dist/__tests__/shadow-quality-metrics.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/skin-error-exhaustive.test-d.d.ts +11 -0
- package/dist/__tests__/skin-error-exhaustive.test-d.d.ts.map +1 -0
- package/dist/__tests__/skin-errors.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-errors.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts +2 -0
- package/dist/__tests__/skin-extract-getarrayview-count.unit.test.d.ts.map +1 -0
- package/dist/__tests__/skin-parented-double-transform.test.d.ts +14 -0
- package/dist/__tests__/skin-parented-double-transform.test.d.ts.map +1 -0
- package/dist/__tests__/skylight-lazy-projection.test.d.ts +2 -0
- package/dist/__tests__/skylight-lazy-projection.test.d.ts.map +1 -0
- package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts +2 -0
- package/dist/__tests__/sparse-tag-scene-roundtrip.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts +2 -0
- package/dist/__tests__/sprite-cullmode-flip.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-bgl-byte-identical.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-extract-branch.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-extract-branch.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts +2 -0
- package/dist/__tests__/sprite-lit-pipeline-cache-key.test.d.ts.map +1 -0
- package/dist/__tests__/sprite-param-values.test-d.d.ts +2 -0
- package/dist/__tests__/sprite-param-values.test-d.d.ts.map +1 -0
- package/dist/__tests__/ssao-bgl.test.d.ts +2 -0
- package/dist/__tests__/ssao-bgl.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-buffers.test.d.ts +2 -0
- package/dist/__tests__/ssao-buffers.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-data.test.d.ts +2 -0
- package/dist/__tests__/ssao-data.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-errors.test.d.ts +2 -0
- package/dist/__tests__/ssao-errors.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-manifest.test.d.ts +2 -0
- package/dist/__tests__/ssao-manifest.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-passes.test.d.ts +2 -0
- package/dist/__tests__/ssao-passes.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-pipelines.test.d.ts +2 -0
- package/dist/__tests__/ssao-pipelines.test.d.ts.map +1 -0
- package/dist/__tests__/ssao-uniform-layout.test.d.ts +2 -0
- package/dist/__tests__/ssao-uniform-layout.test.d.ts.map +1 -0
- package/dist/__tests__/static-rig.integration.test.d.ts +2 -0
- package/dist/__tests__/static-rig.integration.test.d.ts.map +1 -0
- package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts +2 -0
- package/dist/__tests__/stencil-outline-pixel.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/swap-chain-format.unit.test.d.ts +2 -0
- package/dist/__tests__/swap-chain-format.unit.test.d.ts.map +1 -0
- package/dist/__tests__/systems.unit.test.d.ts +2 -0
- package/dist/__tests__/systems.unit.test.d.ts.map +1 -0
- package/dist/__tests__/temporal-off-path.unit.test.d.ts +2 -0
- package/dist/__tests__/temporal-off-path.unit.test.d.ts.map +1 -0
- package/dist/__tests__/text.unit.test.d.ts +2 -0
- package/dist/__tests__/text.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-childed-despawn.test.d.ts +2 -0
- package/dist/__tests__/tilemap-childed-despawn.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-extract.overhang-bounds.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-extract.propagate-order.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-extract.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-extract.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-extract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts +2 -0
- package/dist/__tests__/tilemap-chunk-y-sort-bench.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts +2 -0
- package/dist/__tests__/tilemap-dirty-rebuild.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-material-cache.test.d.ts +2 -0
- package/dist/__tests__/tilemap-material-cache.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-multi-atlas.test.d.ts +2 -0
- package/dist/__tests__/tilemap-multi-atlas.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts +2 -0
- package/dist/__tests__/tilemap-owner-boundary.unit.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-region-pack.test.d.ts +2 -0
- package/dist/__tests__/tilemap-region-pack.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts +2 -0
- package/dist/__tests__/tilemap-spawn-flip-pivot.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-uv-inset.test.d.ts +2 -0
- package/dist/__tests__/tilemap-uv-inset.test.d.ts.map +1 -0
- package/dist/__tests__/tilemap-validate.test.d.ts +2 -0
- package/dist/__tests__/tilemap-validate.test.d.ts.map +1 -0
- package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts +2 -0
- package/dist/__tests__/transform-hierarchy-pixel-diff.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/transient-fields-collect.test.d.ts +2 -0
- package/dist/__tests__/transient-fields-collect.test.d.ts.map +1 -0
- package/dist/__tests__/transitional-authoring.integration.test.d.ts +2 -0
- package/dist/__tests__/transitional-authoring.integration.test.d.ts.map +1 -0
- package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts +2 -0
- package/dist/__tests__/transparent-sort-tilemap-sprite-interleave.test.d.ts.map +1 -0
- package/dist/__tests__/variant-selection.unit.test.d.ts +2 -0
- package/dist/__tests__/variant-selection.unit.test.d.ts.map +1 -0
- package/dist/__tests__/vertex-attribute-layout.test.d.ts +2 -0
- package/dist/__tests__/vertex-attribute-layout.test.d.ts.map +1 -0
- package/dist/__tests__/video-cache-regression.unit.test.d.ts +2 -0
- package/dist/__tests__/video-cache-regression.unit.test.d.ts.map +1 -0
- package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts +2 -0
- package/dist/__tests__/video-extract-bindgroup.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/view-ubo-layout.browser.test.d.ts +2 -0
- package/dist/__tests__/view-ubo-layout.browser.test.d.ts.map +1 -0
- package/dist/__tests__/world-entity-key.test.d.ts +2 -0
- package/dist/__tests__/world-entity-key.test.d.ts.map +1 -0
- package/dist/__tests__/world-scene-despawn.test.d.ts +2 -0
- package/dist/__tests__/world-scene-despawn.test.d.ts.map +1 -0
- package/dist/__tests__/world-scene-instantiate.test.d.ts +2 -0
- package/dist/__tests__/world-scene-instantiate.test.d.ts.map +1 -0
- package/dist/__tests__/world-scene-nested.test.d.ts +2 -0
- package/dist/__tests__/world-scene-nested.test.d.ts.map +1 -0
- package/dist/__tests__/world-scene-override.test.d.ts +2 -0
- package/dist/__tests__/world-scene-override.test.d.ts.map +1 -0
- package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts +2 -0
- package/dist/__tests__/zero-compression-zero-load.integration.test.d.ts.map +1 -0
- package/dist/asset-contributions.d.ts +3 -0
- package/dist/asset-contributions.d.ts.map +1 -0
- package/dist/asset-kind.d.ts +4 -0
- package/dist/asset-kind.d.ts.map +1 -0
- package/dist/collect-scene-asset.d.ts +16 -0
- package/dist/collect-scene-asset.d.ts.map +1 -0
- package/dist/components/__tests__/camera.test-d.d.ts +2 -0
- package/dist/components/__tests__/camera.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/camera.test.d.ts +2 -0
- package/dist/components/__tests__/camera.test.d.ts.map +1 -0
- package/dist/components/__tests__/child-of.test.d.ts +2 -0
- package/dist/components/__tests__/child-of.test.d.ts.map +1 -0
- package/dist/components/__tests__/sprite-animation.test-d.d.ts +2 -0
- package/dist/components/__tests__/sprite-animation.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/sprite-instances.unit.test.d.ts +2 -0
- package/dist/components/__tests__/sprite-instances.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/sprite-region-override.test-d.d.ts +2 -0
- package/dist/components/__tests__/sprite-region-override.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/tile-layer.test-d.d.ts +2 -0
- package/dist/components/__tests__/tile-layer.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/tile-layer.unit.test.d.ts +2 -0
- package/dist/components/__tests__/tile-layer.unit.test.d.ts.map +1 -0
- package/dist/components/__tests__/tilemap.test-d.d.ts +2 -0
- package/dist/components/__tests__/tilemap.test-d.d.ts.map +1 -0
- package/dist/components/__tests__/transform-vec-schema.test.d.ts +2 -0
- package/dist/components/__tests__/transform-vec-schema.test.d.ts.map +1 -0
- package/dist/create-renderer-env-classify.d.ts +21 -0
- package/dist/create-renderer-env-classify.d.ts.map +1 -0
- package/dist/createRenderer.d.ts +3 -0
- package/dist/createRenderer.d.ts.map +1 -0
- package/dist/dev-import-transport.d.ts +24 -0
- package/dist/dev-import-transport.d.ts.map +1 -0
- package/dist/errors/environment.d.ts +2 -0
- package/dist/errors/environment.d.ts.map +1 -0
- package/dist/errors/sprite-animation.d.ts +3 -0
- package/dist/errors/sprite-animation.d.ts.map +1 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +814 -0
- package/dist/index.mjs.map +1 -0
- package/dist/scene-asset-port.d.ts +17 -0
- package/dist/scene-asset-port.d.ts.map +1 -0
- package/dist/scene-collect-errors.d.ts +22 -0
- package/dist/scene-collect-errors.d.ts.map +1 -0
- package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts +2 -0
- package/dist/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.d.ts.map +1 -0
- package/dist/scene-utils/mount-override-fold.d.ts +16 -0
- package/dist/scene-utils/mount-override-fold.d.ts.map +1 -0
- package/dist/shadow-m3-calibrate.d.ts +40 -0
- package/dist/shadow-m3-calibrate.d.ts.map +1 -0
- package/dist/sprite-param-values.d.ts +62 -0
- package/dist/sprite-param-values.d.ts.map +1 -0
- package/dist/systems/__tests__/_xorshift.d.ts +28 -0
- package/dist/systems/__tests__/_xorshift.d.ts.map +1 -0
- package/dist/systems/__tests__/argsort-in-place.test.d.ts +2 -0
- package/dist/systems/__tests__/argsort-in-place.test.d.ts.map +1 -0
- package/dist/systems/__tests__/transparent-sort.bench.d.ts +2 -0
- package/dist/systems/__tests__/transparent-sort.bench.d.ts.map +1 -0
- package/dist/systems/sprite-animation-tick.d.ts +50 -0
- package/dist/systems/sprite-animation-tick.d.ts.map +1 -0
- package/dist/systems/tonemap.d.ts +27 -0
- package/dist/systems/tonemap.d.ts.map +1 -0
- package/dist/systems/transparent-sort.d.ts +51 -0
- package/dist/systems/transparent-sort.d.ts.map +1 -0
- package/dist/tilemap-register-validate.d.ts +21 -0
- package/dist/tilemap-register-validate.d.ts.map +1 -0
- package/package.json +100 -0
- package/src/__tests__/active-camera.unit.test.ts +193 -0
- package/src/__tests__/animation-graph-add.unit.test.ts +67 -0
- package/src/__tests__/animation-graph-asset-union.test-d.ts +83 -0
- package/src/__tests__/animation-graph-blend.unit.test.ts +66 -0
- package/src/__tests__/animation-graph-build.unit.test.ts +100 -0
- package/src/__tests__/animation-graph-clip-missing.unit.test.ts +62 -0
- package/src/__tests__/animation-graph-clip.unit.test.ts +77 -0
- package/src/__tests__/animation-graph-default-path.integration.test.ts +65 -0
- package/src/__tests__/animation-graph-describe.unit.test.ts +68 -0
- package/src/__tests__/animation-graph-errors.unit.test.ts +75 -0
- package/src/__tests__/animation-graph-nesting.unit.test.ts +84 -0
- package/src/__tests__/animation-graph-orthogonal-weight.unit.test.ts +98 -0
- package/src/__tests__/animation-graph-slot-fill.unit.test.ts +95 -0
- package/src/__tests__/animation-nslot.unit.test.ts +63 -0
- package/src/__tests__/animation-slot-length-guard.unit.test.ts +81 -0
- package/src/__tests__/append-injection.test.ts +105 -0
- package/src/__tests__/asi-world-baseline-export.unit.test.ts +173 -0
- package/src/__tests__/asi-world-ci-config.unit.test.ts +186 -0
- package/src/__tests__/asi-world-smoke.unit.test.ts +192 -0
- package/src/__tests__/asset-registry-scene.test-d.ts +49 -0
- package/src/__tests__/bind-group-cache-binding.test.ts +2513 -0
- package/src/__tests__/bind-group-cache-frame.test.ts +1511 -0
- package/src/__tests__/bind-group-cache-keying.test.ts +295 -0
- package/src/__tests__/block-upload-math.unit.test.ts +192 -0
- package/src/__tests__/builtin-shader-register-e2e.test.ts +151 -0
- package/src/__tests__/cache-false-eviction.test.ts +126 -0
- package/src/__tests__/cache-isolation-matrix.test.ts +182 -0
- package/src/__tests__/camera-clear-snapshot-equivalence.unit.test.ts +76 -0
- package/src/__tests__/clamp-to-last.e2e.browser.test.ts +183 -0
- package/src/__tests__/clamp-to-last.e2e.dawn.test.ts +325 -0
- package/src/__tests__/collect-scene-asset.test.ts +876 -0
- package/src/__tests__/collect-subtree.unit.test.ts +76 -0
- package/src/__tests__/collect-transient-children-absent.test.ts +154 -0
- package/src/__tests__/collect-transient-roundtrip.test.ts +245 -0
- package/src/__tests__/collect-transient-skip.test.ts +165 -0
- package/src/__tests__/components.test-d.ts +236 -0
- package/src/__tests__/components.unit.test.ts +2665 -0
- package/src/__tests__/composite-skybox-cross-world.browser.test.ts +378 -0
- package/src/__tests__/create-renderer-assembly.integration.test.ts +42 -0
- package/src/__tests__/createRenderer-dispose.test.ts +416 -0
- package/src/__tests__/createRenderer-requiredFeatures.unit.test.ts +134 -0
- package/src/__tests__/createRenderer.browser-no-webgpu.test.ts +59 -0
- package/src/__tests__/createRenderer.unit.test.ts +231 -0
- package/src/__tests__/dawn/hello-sprite-nineslice-section.dawn.test.ts +393 -0
- package/src/__tests__/dawn/ibl-compose.dawn.test.ts +151 -0
- package/src/__tests__/dawn/ibl-modules-compose.dawn.test.ts +223 -0
- package/src/__tests__/dawn/ibl-precompute-readback.dawn.test.ts +241 -0
- package/src/__tests__/dawn/instances-per-instance-pbr.dawn.test.ts +611 -0
- package/src/__tests__/dawn/instances-shadow.dawn.test.ts +443 -0
- package/src/__tests__/dawn/instances-uniform-fallback.dawn.test.ts +313 -0
- package/src/__tests__/dawn/material-alpha.dawn.test.ts +304 -0
- package/src/__tests__/dawn/material-cooked-fixture.dawn.test.ts +64 -0
- package/src/__tests__/dawn/mesh-ssbo-normal-matrix.dawn.test.ts +106 -0
- package/src/__tests__/dawn/mesh-update.dawn.test.ts +342 -0
- package/src/__tests__/dawn/mipmap-srgb-linear.dawn.test.ts +117 -0
- package/src/__tests__/dawn/per-entity-material-texture-isolation.dawn.test.ts +567 -0
- package/src/__tests__/dawn/sprite-nineslice-mesh-bind.dawn.test.ts +251 -0
- package/src/__tests__/dawn/tonemap.dawn.test.ts +107 -0
- package/src/__tests__/dawn/user-handle-mesh-render.dawn.test.ts +291 -0
- package/src/__tests__/dawn/user-mesh-upload.dawn.test.ts +204 -0
- package/src/__tests__/dawn/zero-manifest-mesh-fail.dawn.test.ts +122 -0
- package/src/__tests__/dawn/zero-manifest-ready.dawn.test.ts +99 -0
- package/src/__tests__/demo-sortscope-migration.unit.test.ts +115 -0
- package/src/__tests__/derive-bgl-integration.test.ts +398 -0
- package/src/__tests__/derive-render-data-skin.unit.test.ts +90 -0
- package/src/__tests__/dispatch-fullscreen-pass-composite-swapchain.dawn.test.ts +204 -0
- package/src/__tests__/dispatch-fullscreen-pass-custom-reads.dawn.test.ts +297 -0
- package/src/__tests__/dispatch-fullscreen-pass-depth-composite.dawn.test.ts +271 -0
- package/src/__tests__/dispatch-fullscreen-pass-params.unit.test.ts +321 -0
- package/src/__tests__/draw-published.ts +10 -0
- package/src/__tests__/environment-error-exhaustive.test-d.ts +28 -0
- package/src/__tests__/equirect-bc6h.integration.test.ts +35 -0
- package/src/__tests__/error-exhaustive.test.ts +220 -0
- package/src/__tests__/extract-frames-graceful-degradation.test.ts +298 -0
- package/src/__tests__/extract-frames-merge.test.ts +643 -0
- package/src/__tests__/extract-frames-reset.test.ts +158 -0
- package/src/__tests__/extract-record-no-hardcoded-texture-fields.test.ts +92 -0
- package/src/__tests__/fog-producer-manifest.ts +162 -0
- package/src/__tests__/fog-producer-pixel-probe.ts +263 -0
- package/src/__tests__/fog-producer-pixel.browser.test.ts +26 -0
- package/src/__tests__/fog-producer-pixel.dawn.test.ts +55 -0
- package/src/__tests__/fog-producer-real-fixture.ts +805 -0
- package/src/__tests__/fog-producer-vfx-effect.ts +272 -0
- package/src/__tests__/fog-scene-asset.integration.test.ts +123 -0
- package/src/__tests__/frame-targets-shadow-regression.unit.test.ts +28 -0
- package/src/__tests__/fullscreen-post-bgl-depth.test.ts +131 -0
- package/src/__tests__/fullscreen-post-depth-params-arity.test.ts +84 -0
- package/src/__tests__/fullscreen-post-process-pass.dawn.test.ts +320 -0
- package/src/__tests__/fullscreen-post-process-pass.unit.test.ts +107 -0
- package/src/__tests__/fxaa-pixel-diff.dawn.test.ts +292 -0
- package/src/__tests__/fxaa-validation-and-resize.dawn.test.ts +358 -0
- package/src/__tests__/fxaa-zero-overhead.dawn.test.ts +254 -0
- package/src/__tests__/geometry.unit.test.ts +2265 -0
- package/src/__tests__/glyph-tilemap-worldid.test.ts +146 -0
- package/src/__tests__/gpu-resource-cow-survivor.test.ts +388 -0
- package/src/__tests__/gpu-resource-store-cubemap-status.test.ts +207 -0
- package/src/__tests__/gpu-resource.test.ts +1206 -0
- package/src/__tests__/hdrp-bgl-slots.browser.test.ts +50 -0
- package/src/__tests__/hdrp-buffers.unit.test.ts +70 -0
- package/src/__tests__/hdrp-execute-filter.unit.test.ts +200 -0
- package/src/__tests__/hdrp-gbuffer-attachments.browser.test.ts +64 -0
- package/src/__tests__/hdrp-pipeline-asset-type.test-d.ts +139 -0
- package/src/__tests__/hdrp-ssao-boundary.test.ts +237 -0
- package/src/__tests__/hdrp-ssao-falsification.test.ts +289 -0
- package/src/__tests__/hello-tilemap-smoke-upgrade.unit.test.ts +147 -0
- package/src/__tests__/helpers/mock-shader-registry.ts +139 -0
- package/src/__tests__/helpers/primitive-world.ts +91 -0
- package/src/__tests__/helpers/register-runtime-components.ts +69 -0
- package/src/__tests__/helpers/register-scene-components.ts +10 -0
- package/src/__tests__/helpers/render-asset-port.ts +65 -0
- package/src/__tests__/helpers/scene-asset-port.ts +23 -0
- package/src/__tests__/helpers/tilemap-assets.ts +29 -0
- package/src/__tests__/instances.test-d.ts +61 -0
- package/src/__tests__/light-attenuation-formula.test.ts +171 -0
- package/src/__tests__/light-casters-9-light.browser.test.ts +880 -0
- package/src/__tests__/light-gpu-byte-neutral.unit.test.ts +163 -0
- package/src/__tests__/light-snapshot.test-d.ts +84 -0
- package/src/__tests__/lights-vec-negative.unit.test.ts +52 -0
- package/src/__tests__/lights.unit.test.ts +2936 -0
- package/src/__tests__/m3-vec-negative.unit.test.ts +45 -0
- package/src/__tests__/m5-browser-dawn-sweep.integration.test.ts +8 -0
- package/src/__tests__/material-derived-coordinates.integration.test.ts +100 -0
- package/src/__tests__/material-texture-uv-scale.unit.test.ts +49 -0
- package/src/__tests__/materials.unit.test.ts +271 -0
- package/src/__tests__/mesh-ssbo-grow.unit.test.ts +244 -0
- package/src/__tests__/mip-gate.unit.test.ts +79 -0
- package/src/__tests__/msaa-sprite-pixel-diff.dawn.test.ts +406 -0
- package/src/__tests__/multi-uv-8set.e2e.dawn.test.ts +296 -0
- package/src/__tests__/name.unit.test.ts +123 -0
- package/src/__tests__/node-shims.d.ts +33 -0
- package/src/__tests__/pass-selector.unit.test.ts +102 -0
- package/src/__tests__/pbr-pipeline.unit.test.ts +204 -0
- package/src/__tests__/pipeline-builder-msaa.unit.test.ts +224 -0
- package/src/__tests__/pipeline-cache-keying.unit.test.ts +316 -0
- package/src/__tests__/pipeline-layout-selection.test.ts +298 -0
- package/src/__tests__/pipeline-spec.test.ts +1061 -0
- package/src/__tests__/pipeline.unit.test.ts +6974 -0
- package/src/__tests__/point-light-shadow.browser.test.ts +278 -0
- package/src/__tests__/point-light-shadow.dawn.test.ts +822 -0
- package/src/__tests__/point-light-shadow.unit.test.ts +716 -0
- package/src/__tests__/post-process-error-codes.unit.test.ts +158 -0
- package/src/__tests__/post-process-params-component.unit.test.ts +153 -0
- package/src/__tests__/post-process-params-extract.unit.test.ts +100 -0
- package/src/__tests__/post-process-reads-narrowing.test-d.ts +52 -0
- package/src/__tests__/post-process-register-roundtrip.dawn.test.ts +230 -0
- package/src/__tests__/postprocess-camera-consistency.test.ts +378 -0
- package/src/__tests__/profiler-assembly.integration.test.ts +74 -0
- package/src/__tests__/propagate-transforms.dawn.test.ts +253 -0
- package/src/__tests__/record-worldid-isolation.test.ts +247 -0
- package/src/__tests__/recover-error-exhaustive.test-d.ts +35 -0
- package/src/__tests__/render-error-exhaustive.test-d.ts +245 -0
- package/src/__tests__/render-feature-assembly.test-d.ts +28 -0
- package/src/__tests__/render-feature-prepared-graphics-docs.unit.test.ts +124 -0
- package/src/__tests__/render-feature-prepared-graphics.browser.test.ts +582 -0
- package/src/__tests__/render-feature-prepared-graphics.dawn.test.ts +602 -0
- package/src/__tests__/render-feature-prepared-graphics.integration.test.ts +264 -0
- package/src/__tests__/render-feature-prepared-graphics.regression.integration.test.ts +361 -0
- package/src/__tests__/render-feature-recover.integration.test.ts +142 -0
- package/src/__tests__/render-feature-runtime-surface.test-d.ts +45 -0
- package/src/__tests__/render-feature-zero-feature.browser.test.ts +152 -0
- package/src/__tests__/render-pipeline-trivial.dawn.test.ts +525 -0
- package/src/__tests__/render-schema-consumer.integration.test.ts +25 -0
- package/src/__tests__/render-schema-owner-boundary.unit.test.ts +26 -0
- package/src/__tests__/render-system-cross-world-overlay-queue.test.ts +172 -0
- package/src/__tests__/render-system-extract-dispatch-ordering.test.ts +322 -0
- package/src/__tests__/render-system-extract.test.ts +1399 -0
- package/src/__tests__/render-system-fold-bucket.unit.test.ts +288 -0
- package/src/__tests__/render-system-fold-fallback.unit.test.ts +148 -0
- package/src/__tests__/render-system-fold-material.unit.test.ts +145 -0
- package/src/__tests__/render-system-fold-metrics.unit.test.ts +170 -0
- package/src/__tests__/render-system-fold-modegate.unit.test.ts +166 -0
- package/src/__tests__/render-system-fold-scene-instances.unit.test.ts +51 -0
- package/src/__tests__/render-system-fold.dawn.test.ts +306 -0
- package/src/__tests__/render-system-mega.test.ts +1578 -0
- package/src/__tests__/render-system-owner-split-contract.test.ts +200 -0
- package/src/__tests__/render-system-record-multi-material-textureview.test.ts +684 -0
- package/src/__tests__/render-system-record-per-submesh-transparency.test.ts +464 -0
- package/src/__tests__/render-system-record.test.ts +1505 -0
- package/src/__tests__/render-system-record.unit.test.ts +270 -0
- package/src/__tests__/render-system-skin-bg.unit.test.ts +147 -0
- package/src/__tests__/renderer-consumer.test-d.ts +5 -0
- package/src/__tests__/renderer-health.unit.test.ts +935 -0
- package/src/__tests__/renderer-lifecycle.integration.test.ts +29 -0
- package/src/__tests__/renderer-recover.unit.test.ts +1075 -0
- package/src/__tests__/renderer-result.test-d.ts +26 -0
- package/src/__tests__/renderer-surface.unit.test.ts +416 -0
- package/src/__tests__/resolve-depth-only-view.test.ts +173 -0
- package/src/__tests__/rhi-null-command-flow.unit.test.ts +273 -0
- package/src/__tests__/rhi-null-noop-behavior.unit.test.ts +196 -0
- package/src/__tests__/rhi-null-renderer-lifecycle.unit.test.ts +182 -0
- package/src/__tests__/roots-to-scene-asset.test.ts +605 -0
- package/src/__tests__/runtime-authority-scan.unit.test.ts +19 -0
- package/src/__tests__/runtime-domain-residue.unit.test.ts +13 -0
- package/src/__tests__/scene-equirect-roundtrip.test.ts +33 -0
- package/src/__tests__/scene-instance-bridge.integration.test.ts +8 -0
- package/src/__tests__/scene-instance-component.test.ts +45 -0
- package/src/__tests__/scene-instantiate-cycle-runtime.test.ts +66 -0
- package/src/__tests__/serialize-scene-asset.test.ts +336 -0
- package/src/__tests__/shadow-caster-empty-schema.test.ts +83 -0
- package/src/__tests__/shadow-csm-atlas.test.ts +178 -0
- package/src/__tests__/shadow-csm-blend.dawn.test.ts +230 -0
- package/src/__tests__/shadow-csm-cascade-loadop.test.ts +334 -0
- package/src/__tests__/shadow-csm-caster-membership.test.ts +232 -0
- package/src/__tests__/shadow-csm-component.test.ts +286 -0
- package/src/__tests__/shadow-csm-extract.test.ts +334 -0
- package/src/__tests__/shadow-csm-orthoz.test.ts +151 -0
- package/src/__tests__/shadow-csm-pssm.test.ts +142 -0
- package/src/__tests__/shadow-csm-runtime-vary.dawn.test.ts +198 -0
- package/src/__tests__/shadow-csm-shader.dawn.test.ts +358 -0
- package/src/__tests__/shadow-csm-temporal.browser.test.ts +145 -0
- package/src/__tests__/shadow-csm-tile-consistency.test.ts +419 -0
- package/src/__tests__/shadow-csm-ubo.dawn.test.ts +293 -0
- package/src/__tests__/shadow-csm-ubo.test.ts +300 -0
- package/src/__tests__/shadow-csm-viewport.browser.test.ts +85 -0
- package/src/__tests__/shadow-csm-viewport.test.ts +74 -0
- package/src/__tests__/shadow-fields-observable.dawn.test.ts +615 -0
- package/src/__tests__/shadow-m1.dawn.test.ts +105 -0
- package/src/__tests__/shadow-m2.dawn.test.ts +394 -0
- package/src/__tests__/shadow-m3-calibrate-run.dawn.test.ts +248 -0
- package/src/__tests__/shadow-m3-calibrate.ts +203 -0
- package/src/__tests__/shadow-m3.dawn.test.ts +288 -0
- package/src/__tests__/shadow-mesh-ssbo-order.test.ts +64 -0
- package/src/__tests__/shadow-probe-control-flow.dawn.test.ts +264 -0
- package/src/__tests__/shadow-quality-metrics.dawn.test.ts +324 -0
- package/src/__tests__/skin-error-exhaustive.test-d.ts +74 -0
- package/src/__tests__/skin-errors.unit.test.ts +10 -0
- package/src/__tests__/skin-extract-getarrayview-count.unit.test.ts +23 -0
- package/src/__tests__/skin-parented-double-transform.test.ts +268 -0
- package/src/__tests__/skylight-lazy-projection.test.ts +359 -0
- package/src/__tests__/sparse-tag-scene-roundtrip.test.ts +43 -0
- package/src/__tests__/sprite-cullmode-flip.dawn.test.ts +470 -0
- package/src/__tests__/sprite-lit-bgl-byte-identical.test.ts +141 -0
- package/src/__tests__/sprite-lit-extract-branch.test.ts +117 -0
- package/src/__tests__/sprite-lit-pipeline-cache-key.test.ts +123 -0
- package/src/__tests__/sprite-param-values.test-d.ts +104 -0
- package/src/__tests__/ssao-bgl.test.ts +347 -0
- package/src/__tests__/ssao-buffers.test.ts +211 -0
- package/src/__tests__/ssao-data.test.ts +121 -0
- package/src/__tests__/ssao-errors.test.ts +162 -0
- package/src/__tests__/ssao-manifest.test.ts +109 -0
- package/src/__tests__/ssao-passes.test.ts +1229 -0
- package/src/__tests__/ssao-pipelines.test.ts +275 -0
- package/src/__tests__/ssao-uniform-layout.test.ts +178 -0
- package/src/__tests__/static-rig.integration.test.ts +9 -0
- package/src/__tests__/stencil-outline-pixel.dawn.test.ts +295 -0
- package/src/__tests__/swap-chain-format.unit.test.ts +117 -0
- package/src/__tests__/systems.unit.test.ts +9957 -0
- package/src/__tests__/temporal-off-path.unit.test.ts +69 -0
- package/src/__tests__/text.unit.test.ts +466 -0
- package/src/__tests__/tilemap-childed-despawn.test.ts +468 -0
- package/src/__tests__/tilemap-chunk-extract.overhang-bounds.test.ts +314 -0
- package/src/__tests__/tilemap-chunk-extract.propagate-order.test.ts +80 -0
- package/src/__tests__/tilemap-chunk-extract.terrain-rewrite.unit.test.ts +424 -0
- package/src/__tests__/tilemap-chunk-extract.test.ts +236 -0
- package/src/__tests__/tilemap-chunk-extract.unit.test.ts +383 -0
- package/src/__tests__/tilemap-chunk-y-sort-bench.unit.test.ts +109 -0
- package/src/__tests__/tilemap-dirty-rebuild.test.ts +300 -0
- package/src/__tests__/tilemap-material-cache.test.ts +137 -0
- package/src/__tests__/tilemap-multi-atlas.test.ts +237 -0
- package/src/__tests__/tilemap-owner-boundary.unit.test.ts +63 -0
- package/src/__tests__/tilemap-region-pack.test.ts +153 -0
- package/src/__tests__/tilemap-spawn-flip-pivot.test.ts +274 -0
- package/src/__tests__/tilemap-uv-inset.test.ts +204 -0
- package/src/__tests__/tilemap-validate.test.ts +201 -0
- package/src/__tests__/transform-hierarchy-pixel-diff.dawn.test.ts +274 -0
- package/src/__tests__/transient-fields-collect.test.ts +203 -0
- package/src/__tests__/transitional-authoring.integration.test.ts +7 -0
- package/src/__tests__/transparent-sort-tilemap-sprite-interleave.test.ts +227 -0
- package/src/__tests__/variant-selection.unit.test.ts +21 -0
- package/src/__tests__/vertex-attribute-layout.test.ts +324 -0
- package/src/__tests__/video-cache-regression.unit.test.ts +186 -0
- package/src/__tests__/video-extract-bindgroup.dawn.test.ts +450 -0
- package/src/__tests__/view-ubo-layout.browser.test.ts +183 -0
- package/src/__tests__/world-entity-key.test.ts +92 -0
- package/src/__tests__/world-scene-despawn.test.ts +132 -0
- package/src/__tests__/world-scene-instantiate.test.ts +228 -0
- package/src/__tests__/world-scene-nested.test.ts +230 -0
- package/src/__tests__/world-scene-override.test.ts +199 -0
- package/src/__tests__/zero-compression-zero-load.integration.test.ts +107 -0
- package/src/asset-contributions.ts +48 -0
- package/src/asset-kind.ts +7 -0
- package/src/collect-scene-asset.ts +689 -0
- package/src/components/__tests__/camera.test-d.ts +57 -0
- package/src/components/__tests__/camera.test.ts +95 -0
- package/src/components/__tests__/child-of.test.ts +35 -0
- package/src/components/__tests__/sprite-animation.test-d.ts +160 -0
- package/src/components/__tests__/sprite-instances.unit.test.ts +108 -0
- package/src/components/__tests__/sprite-region-override.test-d.ts +101 -0
- package/src/components/__tests__/tile-layer.test-d.ts +33 -0
- package/src/components/__tests__/tile-layer.unit.test.ts +125 -0
- package/src/components/__tests__/tilemap.test-d.ts +35 -0
- package/src/components/__tests__/transform-vec-schema.test.ts +121 -0
- package/src/create-renderer-env-classify.ts +110 -0
- package/src/createRenderer.ts +61 -0
- package/src/dev-import-transport.ts +97 -0
- package/src/errors/environment.ts +6 -0
- package/src/errors/sprite-animation.ts +5 -0
- package/src/index.ts +442 -0
- package/src/scene-asset-port.ts +32 -0
- package/src/scene-collect-errors.ts +30 -0
- package/src/scene-instances/__tests__/post-spawn-resolve-joints.unit.test.ts +183 -0
- package/src/scene-utils/mount-override-fold.ts +217 -0
- package/src/shadow-m3-calibrate.ts +171 -0
- package/src/sprite-param-values.ts +61 -0
- package/src/systems/__tests__/_xorshift.ts +83 -0
- package/src/systems/__tests__/argsort-in-place.test.ts +155 -0
- package/src/systems/__tests__/transparent-sort.bench.ts +218 -0
- package/src/systems/sprite-animation-tick.ts +302 -0
- package/src/systems/tonemap.ts +68 -0
- package/src/systems/transparent-sort.ts +434 -0
- package/src/tilemap-register-validate.ts +100 -0
|
@@ -0,0 +1,1505 @@
|
|
|
1
|
+
// Consolidated by feat-20260608-ci-time-cut M5 w13/w14: merges
|
|
2
|
+
// - render-system-record-pbr-ubo-stable.test.ts
|
|
3
|
+
// - render-system-record-scale-too-small.test.ts
|
|
4
|
+
// - render-system-record-sprite-region-override.test.ts
|
|
5
|
+
// - render-system-record-sprite-ubo-bytes.test.ts
|
|
6
|
+
// Each original file body sits in its own block-scope so helper names
|
|
7
|
+
// cannot collide; describe() inside still registers globally with vitest.
|
|
8
|
+
|
|
9
|
+
import { World } from '@forgeax/engine-ecs';
|
|
10
|
+
import {
|
|
11
|
+
SPRITE_PREMULTIPLIED_ALPHA_BLEND,
|
|
12
|
+
SpriteRegionOverride,
|
|
13
|
+
} from '@forgeax/engine-render/authoring';
|
|
14
|
+
import type { DispatchEntry } from '@forgeax/engine-render/internal';
|
|
15
|
+
import * as recordModule from '@forgeax/engine-render/internal';
|
|
16
|
+
import {
|
|
17
|
+
Camera,
|
|
18
|
+
createEngineMetrics,
|
|
19
|
+
detectNineSliceScaleTooSmall,
|
|
20
|
+
extractFrame,
|
|
21
|
+
isLitMaterialSnapshot,
|
|
22
|
+
type MaterialSnapshot,
|
|
23
|
+
MeshFilter,
|
|
24
|
+
MeshRenderer,
|
|
25
|
+
prepareExtractContext,
|
|
26
|
+
writeMaterialSnapshotPayload,
|
|
27
|
+
} from '@forgeax/engine-render/internal';
|
|
28
|
+
import { propagateTransforms, Transform } from '@forgeax/engine-scene';
|
|
29
|
+
import { ShaderRegistry, type ShaderRegistryDevice } from '@forgeax/engine-shader';
|
|
30
|
+
import type {
|
|
31
|
+
Handle,
|
|
32
|
+
MaterialAsset,
|
|
33
|
+
MaterialPass,
|
|
34
|
+
MeshAsset,
|
|
35
|
+
ParamSchemaEntry,
|
|
36
|
+
PassSelector,
|
|
37
|
+
} from '@forgeax/engine-types';
|
|
38
|
+
import { derive } from '@forgeax/engine-types';
|
|
39
|
+
import { describe, expect, it } from 'vitest';
|
|
40
|
+
import { createTestRenderAssetOwner } from './helpers/render-asset-port';
|
|
41
|
+
|
|
42
|
+
describe('derived material record stop-draw (w28)', () => {
|
|
43
|
+
it('returns a structured bounds error before a slot can be uploaded', () => {
|
|
44
|
+
const result = writeMaterialSnapshotPayload(
|
|
45
|
+
{
|
|
46
|
+
baseColor: new Float32Array([1, 1, 1]) as unknown as MaterialSnapshot['baseColor'],
|
|
47
|
+
metallic: 0,
|
|
48
|
+
roughness: 1,
|
|
49
|
+
materialParamSchema: [{ name: 'albedo', type: 'texture2d' }],
|
|
50
|
+
textureCoordinates: new Map([
|
|
51
|
+
[
|
|
52
|
+
'albedo',
|
|
53
|
+
{
|
|
54
|
+
set: 0,
|
|
55
|
+
transform: { offset: [0, 0], scale: [1, 1], rotation: 0 },
|
|
56
|
+
physicalUvScale: [1, 1],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
]),
|
|
60
|
+
},
|
|
61
|
+
16,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
expect(result).toMatchObject({
|
|
65
|
+
ok: false,
|
|
66
|
+
error: { code: 'material-payload-bounds', detail: { action: 'stop-draw' } },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ─── from render-system-record-scale-too-small.test.ts ───
|
|
72
|
+
{
|
|
73
|
+
// render-system-record-scale-too-small — feat-20260527-sprite-nineslice M4 / w17 (a).
|
|
74
|
+
//
|
|
75
|
+
// AC-16 end-to-end: when a 9-slice sprite entity carries a Transform.scale
|
|
76
|
+
// below the four corner anchors, the detection path bumps
|
|
77
|
+
// `renderer.metrics.snapshot()['nineslice.scale-too-small']` once per offending
|
|
78
|
+
// renderableIndex per RenderSystem lifetime. AI users observe the breach
|
|
79
|
+
// through the EngineMetrics counter rather than parsing console.warn text
|
|
80
|
+
// (charter P3 machine-readable signals).
|
|
81
|
+
//
|
|
82
|
+
// The test exercises `detectNineSliceScaleTooSmall` (the pure helper extracted
|
|
83
|
+
// from recordFrame in w17 prep) so the assertion runs without a GPU device.
|
|
84
|
+
// recordFrame's inline call site forwards the same arguments to this helper,
|
|
85
|
+
// so the unit test reflects production behaviour byte-for-byte.
|
|
86
|
+
//
|
|
87
|
+
// Anchors: requirements §AC-16; plan-strategy §5.3 key tests #9; plan-strategy
|
|
88
|
+
// §D-5 EngineMetrics machine-readable counter.
|
|
89
|
+
|
|
90
|
+
function diagonalWorld(scaleX: number, scaleY: number): Float32Array {
|
|
91
|
+
// Column-major mat4 with diagonal scale, no rotation/translation.
|
|
92
|
+
// biome-ignore format: matrix layout
|
|
93
|
+
return new Float32Array([
|
|
94
|
+
scaleX, 0, 0, 0,
|
|
95
|
+
0, scaleY, 0, 0,
|
|
96
|
+
0, 0, 1, 0,
|
|
97
|
+
0, 0, 0, 1,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
describe('detectNineSliceScaleTooSmall (M4 / w17, AC-16 E2E)', () => {
|
|
102
|
+
it('single offending entity bumps the counter to 1', () => {
|
|
103
|
+
const metrics = createEngineMetrics();
|
|
104
|
+
const seen = new Set<number>();
|
|
105
|
+
// anchor budget: |0.3| + |0.3| = 0.6 horizontal / 0.6 vertical.
|
|
106
|
+
// scale 0.4 < 0.6 -> breach on both axes.
|
|
107
|
+
detectNineSliceScaleTooSmall(diagonalWorld(0.4, 0.4), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
|
|
108
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('repeat call on the SAME renderableIndex stays at 1 (warn-once anchor)', () => {
|
|
112
|
+
const metrics = createEngineMetrics();
|
|
113
|
+
const seen = new Set<number>();
|
|
114
|
+
for (let i = 0; i < 10; i++) {
|
|
115
|
+
detectNineSliceScaleTooSmall(
|
|
116
|
+
diagonalWorld(0.4, 0.4),
|
|
117
|
+
[0.3, 0.3, 0.3, 0.3],
|
|
118
|
+
0,
|
|
119
|
+
seen,
|
|
120
|
+
metrics,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('N distinct offending entities yield counter === N', () => {
|
|
127
|
+
const metrics = createEngineMetrics();
|
|
128
|
+
const seen = new Set<number>();
|
|
129
|
+
for (let r = 0; r < 5; r++) {
|
|
130
|
+
detectNineSliceScaleTooSmall(
|
|
131
|
+
diagonalWorld(0.4, 0.4),
|
|
132
|
+
[0.3, 0.3, 0.3, 0.3],
|
|
133
|
+
r,
|
|
134
|
+
seen,
|
|
135
|
+
metrics,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(5);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('compliant scale (>= anchor) does NOT bump the counter', () => {
|
|
142
|
+
const metrics = createEngineMetrics();
|
|
143
|
+
const seen = new Set<number>();
|
|
144
|
+
detectNineSliceScaleTooSmall(diagonalWorld(1.0, 1.0), [0.3, 0.3, 0.3, 0.3], 0, seen, metrics);
|
|
145
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('all-zero slices is a no-op (legacy quad path, no detection)', () => {
|
|
149
|
+
const metrics = createEngineMetrics();
|
|
150
|
+
const seen = new Set<number>();
|
|
151
|
+
// Even with a tiny scale the all-zero slices means "this is not a 9-slice"
|
|
152
|
+
detectNineSliceScaleTooSmall(diagonalWorld(0.01, 0.01), [0, 0, 0, 0], 0, seen, metrics);
|
|
153
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBeUndefined();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('tile-mode sentinel (slices.w < 0) consumed via abs() — no false negative', () => {
|
|
157
|
+
const metrics = createEngineMetrics();
|
|
158
|
+
const seen = new Set<number>();
|
|
159
|
+
// sliceMode=1 negates slices.w; the helper takes abs() so the anchor budget
|
|
160
|
+
// still totals 0.6 on the vertical axis (|0.3| + |-0.3|).
|
|
161
|
+
detectNineSliceScaleTooSmall(
|
|
162
|
+
diagonalWorld(0.4, 0.4),
|
|
163
|
+
[0.3, 0.3, 0.3, -0.3],
|
|
164
|
+
0,
|
|
165
|
+
seen,
|
|
166
|
+
metrics,
|
|
167
|
+
);
|
|
168
|
+
expect(metrics.snapshot()['nineslice.scale-too-small']).toBe(1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ─── from render-system-record-sprite-region-override.test.ts ───
|
|
174
|
+
{
|
|
175
|
+
// render-system-record-sprite-region-override — feat-20260527-sprite-nineslice M4 / w17 (b).
|
|
176
|
+
//
|
|
177
|
+
// AC-14: SpriteRegionOverride per-entity UV sub-rectangle composes with 9-slice.
|
|
178
|
+
// When the entity carries the override component, the extract stage replaces
|
|
179
|
+
// the asset-side `values.region` with the override's 4-float region
|
|
180
|
+
// before downstream snapshot construction; downstream 9-slice paths therefore
|
|
181
|
+
// measure slices against the override's region.zw, not the asset's. Two
|
|
182
|
+
// renderables sharing the same MaterialAsset can render with different
|
|
183
|
+
// effective regions when only one carries the override.
|
|
184
|
+
//
|
|
185
|
+
// Coverage:
|
|
186
|
+
// (1) entity WITHOUT SpriteRegionOverride uses the asset-side region as the
|
|
187
|
+
// effective region in the snapshot.
|
|
188
|
+
// (2) entity WITH SpriteRegionOverride uses the override's region in the
|
|
189
|
+
// snapshot — a half-region [0, 0, 0.5, 1] flows through verbatim,
|
|
190
|
+
// displacing the asset's [0, 0, 1, 1].
|
|
191
|
+
// (3) Two renderables sharing the same material asset diverge on effective
|
|
192
|
+
// region — the override-bearing entity sees the override; the other
|
|
193
|
+
// sees the asset's region. Slices=[0.25,0.25,0.25,0.25] flow through
|
|
194
|
+
// both unchanged (slices remain in the [0..1] local-UV unit; the
|
|
195
|
+
// effective region only governs UV sampling, not the slices unit).
|
|
196
|
+
//
|
|
197
|
+
// Anchors: requirements §AC-14; plan-strategy §5.3 key tests #8.
|
|
198
|
+
|
|
199
|
+
function makeShaderRegistryWithSprite(): ShaderRegistry {
|
|
200
|
+
const mockDevice: ShaderRegistryDevice = {
|
|
201
|
+
createShaderModule() {
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
value: undefined,
|
|
205
|
+
unwrap: () => undefined,
|
|
206
|
+
unwrapOr: (d: unknown) => d,
|
|
207
|
+
} as unknown as ReturnType<ShaderRegistryDevice['createShaderModule']>;
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
const sr = new ShaderRegistry({ device: mockDevice, manifestUrl: undefined });
|
|
211
|
+
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w11+w12: the
|
|
212
|
+
// sprite paramSchema mirrors the WGSL UBO struct field set (4 vec4 +
|
|
213
|
+
// baseColorTexture). Legacy user inputs (slices/sliceMode/flipX/flipY/
|
|
214
|
+
// pivot) are still accepted by extract and folded into the UBO-aligned
|
|
215
|
+
// paramSnapshot vec4 entries (D-8).
|
|
216
|
+
sr.installMaterialArtifact('forgeax::sprite', {
|
|
217
|
+
source: 'fn main() {}',
|
|
218
|
+
paramSchema: [
|
|
219
|
+
{ name: 'colorTint', type: 'vec4', default: [1.0, 1.0, 1.0, 1.0] },
|
|
220
|
+
{ name: 'region', type: 'vec4', default: [0.0, 0.0, 1.0, 1.0] },
|
|
221
|
+
{ name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1.0, 1.0] },
|
|
222
|
+
{ name: 'slicesAndMode', type: 'vec4', default: [0.0, 0.0, 0.0, 0.0] },
|
|
223
|
+
{ name: 'baseColorTexture', type: 'texture2d' },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
return sr;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const SPRITE_PASS: MaterialPass = {
|
|
230
|
+
name: 'Sprite',
|
|
231
|
+
program: { module: 'forgeax::sprite' },
|
|
232
|
+
renderState: { queue: 3000 },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
function identity() {
|
|
236
|
+
return {
|
|
237
|
+
pos: [0, 0, 0],
|
|
238
|
+
quat: [0, 0, 0, 1],
|
|
239
|
+
scale: [1, 1, 1],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function registerSpriteMesh(world: World): Handle<'MeshAsset', 'shared'> {
|
|
244
|
+
const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
245
|
+
return world.allocSharedRef<'MeshAsset', MeshAsset>('MeshAsset', {
|
|
246
|
+
kind: 'mesh',
|
|
247
|
+
vertices: new Float32Array([
|
|
248
|
+
0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0,
|
|
249
|
+
1, 0, 0, 0, 0,
|
|
250
|
+
]),
|
|
251
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
252
|
+
attributes: { position: positions },
|
|
253
|
+
aabb: new Float32Array([0, 0, 0, 1, 1, 1]),
|
|
254
|
+
submeshes: [
|
|
255
|
+
{
|
|
256
|
+
indexOffset: 0,
|
|
257
|
+
indexCount: 3,
|
|
258
|
+
vertexCount: 36,
|
|
259
|
+
topology: 'triangle-list',
|
|
260
|
+
materialSlot: 0,
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
|
|
264
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function spawnCamera(world: World): void {
|
|
269
|
+
world
|
|
270
|
+
.spawn(
|
|
271
|
+
{ component: Transform, data: { ...identity(), pos: [0, 0, 5] } },
|
|
272
|
+
{
|
|
273
|
+
component: Camera,
|
|
274
|
+
data: {
|
|
275
|
+
fov: Math.PI / 4,
|
|
276
|
+
aspect: 1,
|
|
277
|
+
near: 0.1,
|
|
278
|
+
far: 100,
|
|
279
|
+
projection: 0,
|
|
280
|
+
left: -1,
|
|
281
|
+
right: 1,
|
|
282
|
+
bottom: -1,
|
|
283
|
+
top: 1,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
.unwrap();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
describe('SpriteRegionOverride compose 9-slice (M4 / w17b, AC-14)', () => {
|
|
291
|
+
it('(1) entity without override uses asset-side region', () => {
|
|
292
|
+
const world = new World();
|
|
293
|
+
const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
|
|
294
|
+
const mesh = registerSpriteMesh(world);
|
|
295
|
+
const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
|
|
296
|
+
kind: 'material',
|
|
297
|
+
passes: [SPRITE_PASS],
|
|
298
|
+
values: { region: [0.1, 0.1, 0.8, 0.8] },
|
|
299
|
+
});
|
|
300
|
+
spawnCamera(world);
|
|
301
|
+
world
|
|
302
|
+
.spawn(
|
|
303
|
+
{ component: Transform, data: identity() },
|
|
304
|
+
{ component: MeshFilter, data: { assetHandle: mesh } },
|
|
305
|
+
{ component: MeshRenderer, data: { materials: [matHandle] } },
|
|
306
|
+
)
|
|
307
|
+
.unwrap();
|
|
308
|
+
propagateTransforms(world);
|
|
309
|
+
const frame = extractFrame(world, prepareExtractContext(world, { assets }));
|
|
310
|
+
// feat-20260625 M3 / w12: sprite filter is now materialShaderId, not
|
|
311
|
+
// shadingModel; region lands on paramSnapshot.region (D-8 / AC-07).
|
|
312
|
+
const sprite = frame.renderables.find(
|
|
313
|
+
(r) => r.material.materialShaderId === 'forgeax::sprite',
|
|
314
|
+
);
|
|
315
|
+
expect(sprite).toBeDefined();
|
|
316
|
+
expect(sprite?.material.paramSnapshot?.region).toEqual([0.1, 0.1, 0.8, 0.8]);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('(2) entity with override uses the override region in the snapshot', () => {
|
|
320
|
+
const world = new World();
|
|
321
|
+
const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
|
|
322
|
+
const mesh = registerSpriteMesh(world);
|
|
323
|
+
const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
|
|
324
|
+
kind: 'material',
|
|
325
|
+
passes: [SPRITE_PASS],
|
|
326
|
+
values: { region: [0.0, 0.0, 1.0, 1.0] },
|
|
327
|
+
});
|
|
328
|
+
spawnCamera(world);
|
|
329
|
+
world
|
|
330
|
+
.spawn(
|
|
331
|
+
{ component: Transform, data: identity() },
|
|
332
|
+
{ component: MeshFilter, data: { assetHandle: mesh } },
|
|
333
|
+
{ component: MeshRenderer, data: { materials: [matHandle] } },
|
|
334
|
+
{
|
|
335
|
+
component: SpriteRegionOverride,
|
|
336
|
+
data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
.unwrap();
|
|
340
|
+
propagateTransforms(world);
|
|
341
|
+
const frame = extractFrame(world, prepareExtractContext(world, { assets }));
|
|
342
|
+
const sprite = frame.renderables.find(
|
|
343
|
+
(r) => r.material.materialShaderId === 'forgeax::sprite',
|
|
344
|
+
);
|
|
345
|
+
expect(sprite).toBeDefined();
|
|
346
|
+
// The override displaces the asset-side [0, 0, 1, 1] region with the
|
|
347
|
+
// half-width [0, 0, 0.5, 1] override, so downstream consumers (record
|
|
348
|
+
// stage UBO write, 9-slice anchor budget) see the override values via
|
|
349
|
+
// paramSnapshot.region (post-w12).
|
|
350
|
+
expect(sprite?.material.paramSnapshot?.region).toEqual([0, 0, 0.5, 1]);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('(3) two entities sharing one material diverge on effective region (compose with 9-slice)', () => {
|
|
354
|
+
const world = new World();
|
|
355
|
+
const assets = createTestRenderAssetOwner(makeShaderRegistryWithSprite());
|
|
356
|
+
const mesh = registerSpriteMesh(world);
|
|
357
|
+
const matHandle = world.allocSharedRef<'MaterialAsset', MaterialAsset>('MaterialAsset', {
|
|
358
|
+
kind: 'material',
|
|
359
|
+
passes: [SPRITE_PASS],
|
|
360
|
+
values: {
|
|
361
|
+
region: [0.0, 0.0, 1.0, 1.0],
|
|
362
|
+
slicesAndMode: [0.25, 0.25, 0.25, 0.25],
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
spawnCamera(world);
|
|
366
|
+
// entity A: no override, sees the asset-side region.
|
|
367
|
+
world
|
|
368
|
+
.spawn(
|
|
369
|
+
{ component: Transform, data: { ...identity(), pos: [-1, 0, 0] } },
|
|
370
|
+
{ component: MeshFilter, data: { assetHandle: mesh } },
|
|
371
|
+
{ component: MeshRenderer, data: { materials: [matHandle] } },
|
|
372
|
+
)
|
|
373
|
+
.unwrap();
|
|
374
|
+
// entity B: half-width override, sees the override region.
|
|
375
|
+
world
|
|
376
|
+
.spawn(
|
|
377
|
+
{ component: Transform, data: { ...identity(), pos: [1, 0, 0] } },
|
|
378
|
+
{ component: MeshFilter, data: { assetHandle: mesh } },
|
|
379
|
+
{ component: MeshRenderer, data: { materials: [matHandle] } },
|
|
380
|
+
{
|
|
381
|
+
component: SpriteRegionOverride,
|
|
382
|
+
data: { region: new Float32Array([0.0, 0.0, 0.5, 1.0]) },
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
.unwrap();
|
|
386
|
+
propagateTransforms(world);
|
|
387
|
+
const frame = extractFrame(world, prepareExtractContext(world, { assets }));
|
|
388
|
+
const sprites = frame.renderables.filter(
|
|
389
|
+
(r) => r.material.materialShaderId === 'forgeax::sprite',
|
|
390
|
+
);
|
|
391
|
+
expect(sprites.length).toBe(2);
|
|
392
|
+
const regions = sprites.map((s) => s.material.paramSnapshot?.region);
|
|
393
|
+
// One renderable lands on the asset-side region; the other lands on the
|
|
394
|
+
// override. Order is archetype-walk order (we check the set, not which is
|
|
395
|
+
// which, so the test stays robust to archetype iteration order).
|
|
396
|
+
expect(regions).toContainEqual([0, 0, 1, 1]);
|
|
397
|
+
expect(regions).toContainEqual([0, 0, 0.5, 1]);
|
|
398
|
+
// Slices ride through unchanged on both renderables -- slicesAndMode
|
|
399
|
+
// stretch encoding (positive .w, plan-strategy D-3); effective region
|
|
400
|
+
// governs UV sampling, not the slices unit.
|
|
401
|
+
for (const s of sprites) {
|
|
402
|
+
expect(s.material.paramSnapshot?.slicesAndMode).toEqual([0.25, 0.25, 0.25, 0.25]);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// --- from render-system-record-sprite-ubo-bytes.test.ts ---
|
|
409
|
+
{
|
|
410
|
+
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w8 (TDD red).
|
|
411
|
+
//
|
|
412
|
+
// Sprite UBO bytes via the GENERIC paramSnapshot writer (M1 / w3
|
|
413
|
+
// applyParamSnapshotToUbo). The sprite path consumes the same
|
|
414
|
+
// derive(uboLayout).entries-driven overlay every other paramSchema-bound
|
|
415
|
+
// material uses, so the 4 vec4 sprite layout (colorTint / region /
|
|
416
|
+
// pivotAndSize / slicesAndMode) is produced by `applyParamSnapshotToUbo`
|
|
417
|
+
// walking a sprite-shaped paramSchema -- not by the legacy
|
|
418
|
+
// `buildSpriteMaterialUboPayload` POD helper.
|
|
419
|
+
//
|
|
420
|
+
// Plan anchors:
|
|
421
|
+
// - plan-strategy section 2 D-2 / D-6: generic std140 writer is the
|
|
422
|
+
// single UBO write path; sprite-specific builder is ablated in w13.
|
|
423
|
+
// - plan-strategy section 5.3 #1: 4-vec4 std140 layout byte correctness.
|
|
424
|
+
// - requirements AC-03: buildSpriteMaterialUboPayload export does not
|
|
425
|
+
// survive M3 (closed-loop reverse-grep gate).
|
|
426
|
+
//
|
|
427
|
+
// Red phase: the legacy helper still ships; we assert here that the new
|
|
428
|
+
// production path (generic writer + sprite-shaped paramSnapshot) produces
|
|
429
|
+
// the expected bytes AND that `buildSpriteMaterialUboPayload` is no longer
|
|
430
|
+
// exported. Both flip green when w13 lands.
|
|
431
|
+
|
|
432
|
+
const mod = recordModule as unknown as {
|
|
433
|
+
applyParamSnapshotToUbo?: (
|
|
434
|
+
payload: ArrayBuffer | Uint8Array,
|
|
435
|
+
paramSchema: readonly ParamSchemaEntry[] | undefined,
|
|
436
|
+
paramSnapshot:
|
|
437
|
+
| Readonly<Record<string, number | readonly number[] | string | undefined>>
|
|
438
|
+
| undefined,
|
|
439
|
+
) => void;
|
|
440
|
+
buildSpriteMaterialUboPayload?: unknown;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Sprite UBO layout (matches sprite.wgsl post-w11 unit-quad struct: four
|
|
444
|
+
// vec4 fields, total 64 B; the writer fills the leading 64 B of the
|
|
445
|
+
// STANDARD_PBR_UBO_SIZE-sized payload buffer the runtime reuses).
|
|
446
|
+
const SPRITE_UBO_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
447
|
+
{ name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
|
|
448
|
+
{ name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
|
|
449
|
+
{ name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
|
|
450
|
+
{ name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
function makeSpriteSnapshot(opts: {
|
|
454
|
+
colorTintAlpha?: number;
|
|
455
|
+
region?: readonly [number, number, number, number];
|
|
456
|
+
pivot?: readonly [number, number];
|
|
457
|
+
slices?: readonly [number, number, number, number];
|
|
458
|
+
}): {
|
|
459
|
+
colorTint: readonly [number, number, number, number];
|
|
460
|
+
region: readonly [number, number, number, number];
|
|
461
|
+
pivotAndSize: readonly [number, number, number, number];
|
|
462
|
+
slicesAndMode: readonly [number, number, number, number];
|
|
463
|
+
} {
|
|
464
|
+
const pivot = opts.pivot ?? [0.5, 0.5];
|
|
465
|
+
return {
|
|
466
|
+
colorTint: [1, 1, 1, opts.colorTintAlpha ?? 1] as const,
|
|
467
|
+
region: opts.region ?? ([0, 0, 1, 1] as const),
|
|
468
|
+
// Unit quad: pivotAndSize.zw=(1,1) is the placeholder slot the shader
|
|
469
|
+
// no longer reads -- world scale flows entirely through worldFromLocal
|
|
470
|
+
// (plan-strategy section 2 D-6).
|
|
471
|
+
pivotAndSize: [pivot[0], pivot[1], 1, 1] as const,
|
|
472
|
+
slicesAndMode: (opts.slices ?? ([0, 0, 0, 0] as const)) as readonly [
|
|
473
|
+
number,
|
|
474
|
+
number,
|
|
475
|
+
number,
|
|
476
|
+
number,
|
|
477
|
+
],
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
describe('sprite Material UBO bytes via generic writer (M3 / w8)', () => {
|
|
482
|
+
it('grep: buildSpriteMaterialUboPayload is no longer exported from render-system-record (AC-03)', () => {
|
|
483
|
+
expect(mod.buildSpriteMaterialUboPayload).toBeUndefined();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('(1) no slices -> 64 B leading bytes; slot 3 = [0,0,0,0]; slots 0..2 = identity defaults', () => {
|
|
487
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
488
|
+
throw new Error('applyParamSnapshotToUbo not exported');
|
|
489
|
+
}
|
|
490
|
+
// 80 B buffer mirrors the per-entity slot stride the runtime allocates
|
|
491
|
+
// (STANDARD_PBR_UBO_SIZE). The sprite writer only touches the leading
|
|
492
|
+
// 64 B; trailing bytes stay zero on a fresh ArrayBuffer.
|
|
493
|
+
const buf = new ArrayBuffer(80);
|
|
494
|
+
const snap = makeSpriteSnapshot({});
|
|
495
|
+
mod.applyParamSnapshotToUbo(buf, SPRITE_UBO_SCHEMA, snap);
|
|
496
|
+
const f32 = new Float32Array(buf);
|
|
497
|
+
// colorTint slot 0 - default identity (1,1,1,1).
|
|
498
|
+
expect(Array.from(f32.slice(0, 4))).toEqual([1, 1, 1, 1]);
|
|
499
|
+
// region slot 1 - identity (no flip applied at this layer).
|
|
500
|
+
expect(Array.from(f32.slice(4, 8))).toEqual([0, 0, 1, 1]);
|
|
501
|
+
// pivotAndSize slot 2 - pivot=(0.5,0.5); .zw=(1,1) dead-slot placeholder.
|
|
502
|
+
expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 1, 1]);
|
|
503
|
+
// slicesAndMode slot 3 - no slices, all zero.
|
|
504
|
+
expect(Array.from(f32.slice(12, 16))).toEqual([0, 0, 0, 0]);
|
|
505
|
+
// Trailing 16 B left untouched (overlay semantics, charter P4).
|
|
506
|
+
expect(Array.from(f32.slice(16, 20))).toEqual([0, 0, 0, 0]);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('(2) stretch slices [.25,.25,.25,.25] -> slot 3 verbatim; slots 0..2 byte-stable vs baseline', () => {
|
|
510
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
511
|
+
throw new Error('applyParamSnapshotToUbo not exported');
|
|
512
|
+
}
|
|
513
|
+
const baseBuf = new ArrayBuffer(80);
|
|
514
|
+
mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
|
|
515
|
+
const stretchBuf = new ArrayBuffer(80);
|
|
516
|
+
mod.applyParamSnapshotToUbo(
|
|
517
|
+
stretchBuf,
|
|
518
|
+
SPRITE_UBO_SCHEMA,
|
|
519
|
+
makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, 0.25] }),
|
|
520
|
+
);
|
|
521
|
+
const baseF32 = new Float32Array(baseBuf);
|
|
522
|
+
const stretchF32 = new Float32Array(stretchBuf);
|
|
523
|
+
// First 48 B (slots 0..2, 12 floats) byte-stable across slices presence
|
|
524
|
+
// (plan-strategy section D-7 isolation: 9-slice expressions live only in
|
|
525
|
+
// slot 3).
|
|
526
|
+
expect(Array.from(stretchF32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
|
|
527
|
+
// Slot 3 verbatim copy of slicesAndMode (sliceMode=0 stretch).
|
|
528
|
+
expect(Array.from(stretchF32.slice(12, 16))).toEqual([0.25, 0.25, 0.25, 0.25]);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('(3) tile slicesAndMode [.25,.25,.25,-.25] -> slot 3 carries sentinel (w negative)', () => {
|
|
532
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
533
|
+
throw new Error('applyParamSnapshotToUbo not exported');
|
|
534
|
+
}
|
|
535
|
+
const buf = new ArrayBuffer(80);
|
|
536
|
+
// Extract folds sliceMode=1 into a negative slicesAndMode.w (plan-
|
|
537
|
+
// strategy D-3 sentinel); record writes the snapshot verbatim.
|
|
538
|
+
mod.applyParamSnapshotToUbo(
|
|
539
|
+
buf,
|
|
540
|
+
SPRITE_UBO_SCHEMA,
|
|
541
|
+
makeSpriteSnapshot({ slices: [0.25, 0.25, 0.25, -0.25] }),
|
|
542
|
+
);
|
|
543
|
+
const baseBuf = new ArrayBuffer(80);
|
|
544
|
+
mod.applyParamSnapshotToUbo(baseBuf, SPRITE_UBO_SCHEMA, makeSpriteSnapshot({}));
|
|
545
|
+
const f32 = new Float32Array(buf);
|
|
546
|
+
const baseF32 = new Float32Array(baseBuf);
|
|
547
|
+
// First 48 B still byte-identical to baseline across slot 3 changes.
|
|
548
|
+
expect(Array.from(f32.slice(0, 12))).toEqual(Array.from(baseF32.slice(0, 12)));
|
|
549
|
+
// Slot 3 carries the tile sentinel.
|
|
550
|
+
expect(f32[12]).toBe(0.25);
|
|
551
|
+
expect(f32[13]).toBe(0.25);
|
|
552
|
+
expect(f32[14]).toBe(0.25);
|
|
553
|
+
expect(f32[15]).toBe(-0.25);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
it('flipped region [0.5,0,-0.5,1] -> slot 1 carries flip-folded coords verbatim (D-8)', () => {
|
|
557
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
558
|
+
throw new Error('applyParamSnapshotToUbo not exported');
|
|
559
|
+
}
|
|
560
|
+
const buf = new ArrayBuffer(80);
|
|
561
|
+
// Extract folds flipX into region (region.x += region.z; region.z = -region.z).
|
|
562
|
+
// Starting from identity region [0,0,1,1], flipX yields [1,0,-1,1]; the
|
|
563
|
+
// snapshot reaches the writer with the fold already applied.
|
|
564
|
+
mod.applyParamSnapshotToUbo(
|
|
565
|
+
buf,
|
|
566
|
+
SPRITE_UBO_SCHEMA,
|
|
567
|
+
makeSpriteSnapshot({ region: [1, 0, -1, 1] }),
|
|
568
|
+
);
|
|
569
|
+
const f32 = new Float32Array(buf);
|
|
570
|
+
expect(Array.from(f32.slice(4, 8))).toEqual([1, 0, -1, 1]);
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// ─── from feat-20260609 M2: dispatch selector filtering ───
|
|
576
|
+
{
|
|
577
|
+
// feat-20260609-pipeline-driven-pass-selector-shadowcaster-via-mat M2
|
|
578
|
+
// T-005: dispatch entry selector filtering unit tests.
|
|
579
|
+
//
|
|
580
|
+
// AC-03: selector { LightMode: ['Forward'] } filters to only Forward entries.
|
|
581
|
+
// AC-04: selector { LightMode: ['ShadowCaster'] } filters to only matching entity.
|
|
582
|
+
// AC-18: multiple ShadowCaster passes per entity -> filtered count = N.
|
|
583
|
+
//
|
|
584
|
+
// Anchors: plan-strategy section 5.3 key tests; requirements AC-03/AC-04/AC-18.
|
|
585
|
+
|
|
586
|
+
const filterDispatchBySelector = (
|
|
587
|
+
recordModule as unknown as {
|
|
588
|
+
filterDispatchBySelector?: (
|
|
589
|
+
dispatch: readonly DispatchEntry[],
|
|
590
|
+
selector: PassSelector,
|
|
591
|
+
) => readonly DispatchEntry[];
|
|
592
|
+
}
|
|
593
|
+
).filterDispatchBySelector;
|
|
594
|
+
|
|
595
|
+
function makeDispatchEntry(
|
|
596
|
+
renderableIndex: number,
|
|
597
|
+
passIndex: number,
|
|
598
|
+
tags: Record<string, string>,
|
|
599
|
+
): DispatchEntry {
|
|
600
|
+
return {
|
|
601
|
+
entityIndex: renderableIndex,
|
|
602
|
+
materialHandle: 1,
|
|
603
|
+
renderableIndex,
|
|
604
|
+
passIndex,
|
|
605
|
+
queue: 2000,
|
|
606
|
+
layer: 0,
|
|
607
|
+
tags,
|
|
608
|
+
renderState: undefined,
|
|
609
|
+
defines: undefined,
|
|
610
|
+
vertexEntry: undefined,
|
|
611
|
+
fragmentEntry: undefined,
|
|
612
|
+
materialShaderId: 'forgeax::default-unlit',
|
|
613
|
+
paramSnapshot: undefined,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
describe('dispatch entry selector filtering (M2, T-005)', () => {
|
|
618
|
+
it('AC-03: empty selector returns all dispatch entries unchanged', () => {
|
|
619
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
620
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
621
|
+
}
|
|
622
|
+
const dispatch: DispatchEntry[] = [
|
|
623
|
+
makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
|
|
624
|
+
makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
|
|
625
|
+
];
|
|
626
|
+
const result = filterDispatchBySelector(dispatch, {});
|
|
627
|
+
expect(result.length).toBe(2);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('AC-03: selector { LightMode: [Forward] } returns only Forward entries', () => {
|
|
631
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
632
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
633
|
+
}
|
|
634
|
+
const dispatch: DispatchEntry[] = [
|
|
635
|
+
makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
|
|
636
|
+
makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
|
|
637
|
+
makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
|
|
638
|
+
];
|
|
639
|
+
const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
|
|
640
|
+
expect(result.length).toBe(2);
|
|
641
|
+
for (const e of result) {
|
|
642
|
+
expect(e.tags.LightMode).toBe('Forward');
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('AC-04: selector { LightMode: [ShadowCaster] } returns only ShadowCaster entries', () => {
|
|
647
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
648
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
649
|
+
}
|
|
650
|
+
const dispatch: DispatchEntry[] = [
|
|
651
|
+
makeDispatchEntry(0, 0, { LightMode: 'Forward' }),
|
|
652
|
+
makeDispatchEntry(1, 0, { LightMode: 'ShadowCaster' }),
|
|
653
|
+
makeDispatchEntry(2, 0, { LightMode: 'Forward' }),
|
|
654
|
+
];
|
|
655
|
+
const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
|
|
656
|
+
expect(result.length).toBe(1);
|
|
657
|
+
expect(result[0]?.tags.LightMode).toBe('ShadowCaster');
|
|
658
|
+
expect(result[0]?.renderableIndex).toBe(1);
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('AC-18: multiple ShadowCaster passes per entity -> filtered count equals N', () => {
|
|
662
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
663
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
664
|
+
}
|
|
665
|
+
// Entity at renderableIndex=0 has 3 ShadowCaster passes.
|
|
666
|
+
const dispatch: DispatchEntry[] = [
|
|
667
|
+
makeDispatchEntry(0, 0, { LightMode: 'ShadowCaster' }),
|
|
668
|
+
makeDispatchEntry(0, 1, { LightMode: 'ShadowCaster' }),
|
|
669
|
+
makeDispatchEntry(0, 2, { LightMode: 'ShadowCaster' }),
|
|
670
|
+
makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
|
|
671
|
+
];
|
|
672
|
+
const result = filterDispatchBySelector(dispatch, { LightMode: ['ShadowCaster'] });
|
|
673
|
+
expect(result.length).toBe(3);
|
|
674
|
+
for (const e of result) {
|
|
675
|
+
expect(e.tags.LightMode).toBe('ShadowCaster');
|
|
676
|
+
expect(e.renderableIndex).toBe(0);
|
|
677
|
+
}
|
|
678
|
+
// Each pass has a distinct passIndex.
|
|
679
|
+
expect(result[0]?.passIndex).toBe(0);
|
|
680
|
+
expect(result[1]?.passIndex).toBe(1);
|
|
681
|
+
expect(result[2]?.passIndex).toBe(2);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it('selector with non-matching key returns empty array', () => {
|
|
685
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
686
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
687
|
+
}
|
|
688
|
+
const dispatch: DispatchEntry[] = [makeDispatchEntry(0, 0, { LightMode: 'Forward' })];
|
|
689
|
+
const result = filterDispatchBySelector(dispatch, { RenderType: ['Opaque'] });
|
|
690
|
+
expect(result.length).toBe(0);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('selector matching on multiple keys requires both to match', () => {
|
|
694
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
695
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
696
|
+
}
|
|
697
|
+
const dispatch: DispatchEntry[] = [
|
|
698
|
+
makeDispatchEntry(0, 0, { LightMode: 'Forward', RenderQueue: 'Geometry' }),
|
|
699
|
+
makeDispatchEntry(1, 0, { LightMode: 'Forward', RenderQueue: 'Transparent' }),
|
|
700
|
+
];
|
|
701
|
+
const result = filterDispatchBySelector(dispatch, {
|
|
702
|
+
LightMode: ['Forward'],
|
|
703
|
+
RenderQueue: ['Geometry'],
|
|
704
|
+
});
|
|
705
|
+
expect(result.length).toBe(1);
|
|
706
|
+
expect(result[0]?.renderableIndex).toBe(0);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('entry with empty tags is filtered out by a non-empty selector', () => {
|
|
710
|
+
if (typeof filterDispatchBySelector !== 'function') {
|
|
711
|
+
throw new Error('filterDispatchBySelector not exported yet');
|
|
712
|
+
}
|
|
713
|
+
const dispatch: DispatchEntry[] = [
|
|
714
|
+
makeDispatchEntry(0, 0, {}),
|
|
715
|
+
makeDispatchEntry(1, 0, { LightMode: 'Forward' }),
|
|
716
|
+
];
|
|
717
|
+
const result = filterDispatchBySelector(dispatch, { LightMode: ['Forward'] });
|
|
718
|
+
expect(result.length).toBe(1);
|
|
719
|
+
expect(result[0]?.renderableIndex).toBe(1);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// --- from feat-20260625-refactor-sprite-as-transparent-mesh M1 / w1 ---
|
|
725
|
+
{
|
|
726
|
+
// Generic std140 UBO writer driven by derive(paramSchema).uboLayout.entries.
|
|
727
|
+
//
|
|
728
|
+
// Plan anchors:
|
|
729
|
+
// - plan-strategy section 2 D-2: the inline overlay at render-system-
|
|
730
|
+
// record.ts:4334-4374 is generalised to read every uboLayout.entries
|
|
731
|
+
// std140 offset and write paramSnapshot[name] (numeric / numeric[])
|
|
732
|
+
// into the payload buffer. standard-pbr stays byte-identical to the
|
|
733
|
+
// pre-feat buildPbrMaterialUboPayload output (covered by the w2 suite
|
|
734
|
+
// appended below); sprite-shaped layouts (4 x vec4 stride 16) are
|
|
735
|
+
// honoured per offset/size declared by derive.
|
|
736
|
+
// - requirements AC-03 sets buildSpriteMaterialUboPayload ablation as
|
|
737
|
+
// the M3 deliverable; M1 only establishes that the generic writer can
|
|
738
|
+
// produce sprite-shaped bytes from paramSnapshot alone (no asset get,
|
|
739
|
+
// gate R-H from plan-strategy section 5.6 prevents reading internals).
|
|
740
|
+
//
|
|
741
|
+
// The suite reads render-system-record dynamically so it sits RED until
|
|
742
|
+
// w3 lands. The expected helper is a pure function with the signature:
|
|
743
|
+
//
|
|
744
|
+
// applyParamSnapshotToUbo(
|
|
745
|
+
// payload: ArrayBuffer,
|
|
746
|
+
// paramSchema: readonly ParamSchemaEntry[] | undefined,
|
|
747
|
+
// paramSnapshot: { readonly [name: string]: number | readonly number[] }
|
|
748
|
+
// | undefined,
|
|
749
|
+
// ): void
|
|
750
|
+
//
|
|
751
|
+
// The helper is internal (export-for-test) and walks
|
|
752
|
+
// derive(paramSchema).uboLayout.entries to dispatch each field by its
|
|
753
|
+
// std140 offset. Vec / color entries pull numeric arrays from the
|
|
754
|
+
// snapshot; f32 / i32 / u32 entries pull scalars.
|
|
755
|
+
|
|
756
|
+
const mod = recordModule as unknown as {
|
|
757
|
+
applyParamSnapshotToUbo?: (
|
|
758
|
+
payload: ArrayBuffer | Uint8Array,
|
|
759
|
+
paramSchema: readonly ParamSchemaEntry[] | undefined,
|
|
760
|
+
paramSnapshot:
|
|
761
|
+
| Readonly<Record<string, number | readonly number[] | string | undefined>>
|
|
762
|
+
| undefined,
|
|
763
|
+
) => void;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// Sprite-shaped paramSchema: four vec4 entries at std140 offsets
|
|
767
|
+
// 0 / 16 / 32 / 48; total = 64 bytes rounded to 16 (still 64).
|
|
768
|
+
// This mirrors the post-ablation sprite.wgsl UBO layout (M3 / w11) but
|
|
769
|
+
// the writer must already accept it in M1 so M3 only needs to wire it in.
|
|
770
|
+
const SPRITE_SHAPED_SCHEMA: readonly ParamSchemaEntry[] = [
|
|
771
|
+
{ name: 'colorTint', type: 'vec4', default: [1, 1, 1, 1] },
|
|
772
|
+
{ name: 'region', type: 'vec4', default: [0, 0, 1, 1] },
|
|
773
|
+
{ name: 'pivotAndSize', type: 'vec4', default: [0.5, 0.5, 1, 1] },
|
|
774
|
+
{ name: 'slicesAndMode', type: 'vec4', default: [0, 0, 0, 0] },
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
describe('applyParamSnapshotToUbo: sprite-shaped 4x vec4 std140 (M1 / w1)', () => {
|
|
778
|
+
it('export: applyParamSnapshotToUbo helper is exported from render-system-record', () => {
|
|
779
|
+
expect(typeof mod.applyParamSnapshotToUbo).toBe('function');
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
it('derive(uboLayout).entries produces four vec4 entries at offsets 0/16/32/48', () => {
|
|
783
|
+
const { uboLayout } = derive(SPRITE_SHAPED_SCHEMA);
|
|
784
|
+
expect(uboLayout.entries.length).toBe(4);
|
|
785
|
+
expect(uboLayout.entries[0]).toMatchObject({
|
|
786
|
+
name: 'colorTint',
|
|
787
|
+
offset: 0,
|
|
788
|
+
size: 16,
|
|
789
|
+
type: 'vec4',
|
|
790
|
+
});
|
|
791
|
+
expect(uboLayout.entries[1]).toMatchObject({
|
|
792
|
+
name: 'region',
|
|
793
|
+
offset: 16,
|
|
794
|
+
size: 16,
|
|
795
|
+
type: 'vec4',
|
|
796
|
+
});
|
|
797
|
+
expect(uboLayout.entries[2]).toMatchObject({
|
|
798
|
+
name: 'pivotAndSize',
|
|
799
|
+
offset: 32,
|
|
800
|
+
size: 16,
|
|
801
|
+
type: 'vec4',
|
|
802
|
+
});
|
|
803
|
+
expect(uboLayout.entries[3]).toMatchObject({
|
|
804
|
+
name: 'slicesAndMode',
|
|
805
|
+
offset: 48,
|
|
806
|
+
size: 16,
|
|
807
|
+
type: 'vec4',
|
|
808
|
+
});
|
|
809
|
+
// std140 struct alignment rounds up to 16 -- 64 already aligns.
|
|
810
|
+
expect(uboLayout.totalBytes).toBe(64);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it('writes each vec4 at its std140 offset (byte-exact, no f32-slot heuristic)', () => {
|
|
814
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
815
|
+
throw new Error('helper not exported yet (red phase)');
|
|
816
|
+
}
|
|
817
|
+
const buf = new ArrayBuffer(64);
|
|
818
|
+
// Values chosen to round-trip exactly in IEEE 754 f32 (powers of 2 /
|
|
819
|
+
// sums thereof) so we can assert with toEqual; the non-exact-encoding
|
|
820
|
+
// values (0.1 / 0.2 / 0.3) would lose the trailing bit on f32 storage
|
|
821
|
+
// and force a toBeCloseTo, weakening the byte-level signal.
|
|
822
|
+
const snapshot = {
|
|
823
|
+
colorTint: [0.25, 0.5, 0.75, 1] as readonly number[],
|
|
824
|
+
region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
|
|
825
|
+
pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
|
|
826
|
+
slicesAndMode: [0.125, 0.125, 0.125, -0.125] as readonly number[],
|
|
827
|
+
};
|
|
828
|
+
mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
|
|
829
|
+
const f32 = new Float32Array(buf);
|
|
830
|
+
// colorTint at offset 0
|
|
831
|
+
expect(Array.from(f32.slice(0, 4))).toEqual([0.25, 0.5, 0.75, 1]);
|
|
832
|
+
// region at offset 16 (f32 index 4)
|
|
833
|
+
expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
|
|
834
|
+
// pivotAndSize at offset 32 (f32 index 8)
|
|
835
|
+
expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
|
|
836
|
+
// slicesAndMode at offset 48 (f32 index 12) -- carries 9-slice tile
|
|
837
|
+
// sentinel via negative w (verbatim from snapshot, charter P3 explicit
|
|
838
|
+
// signal vs the legacy spriteFields.sliceMode side channel).
|
|
839
|
+
expect(Array.from(f32.slice(12, 16))).toEqual([0.125, 0.125, 0.125, -0.125]);
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it('missing snapshot fields leave existing bytes untouched (overlay semantics)', () => {
|
|
843
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
844
|
+
throw new Error('helper not exported yet (red phase)');
|
|
845
|
+
}
|
|
846
|
+
const buf = new ArrayBuffer(64);
|
|
847
|
+
const pre = new Float32Array(buf);
|
|
848
|
+
// Seed the buffer with sentinel pre-values; the writer must only
|
|
849
|
+
// touch offsets it has snapshot values for (charter P4 -- the writer
|
|
850
|
+
// is an overlay, not a clear-and-fill stamp).
|
|
851
|
+
pre[0] = 9;
|
|
852
|
+
pre[1] = 9;
|
|
853
|
+
pre[2] = 9;
|
|
854
|
+
pre[3] = 9;
|
|
855
|
+
pre[12] = 7;
|
|
856
|
+
pre[13] = 7;
|
|
857
|
+
pre[14] = 7;
|
|
858
|
+
pre[15] = 7;
|
|
859
|
+
const snapshot = {
|
|
860
|
+
region: [0.125, 0.25, 0.5, 0.75] as readonly number[],
|
|
861
|
+
pivotAndSize: [0.5, 0.5, 2, 4] as readonly number[],
|
|
862
|
+
};
|
|
863
|
+
mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, snapshot);
|
|
864
|
+
const f32 = new Float32Array(buf);
|
|
865
|
+
// colorTint (no snapshot value) preserved
|
|
866
|
+
expect(Array.from(f32.slice(0, 4))).toEqual([9, 9, 9, 9]);
|
|
867
|
+
// region written
|
|
868
|
+
expect(Array.from(f32.slice(4, 8))).toEqual([0.125, 0.25, 0.5, 0.75]);
|
|
869
|
+
// pivotAndSize written
|
|
870
|
+
expect(Array.from(f32.slice(8, 12))).toEqual([0.5, 0.5, 2, 4]);
|
|
871
|
+
// slicesAndMode (no snapshot value) preserved
|
|
872
|
+
expect(Array.from(f32.slice(12, 16))).toEqual([7, 7, 7, 7]);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
it('no-op when paramSchema or paramSnapshot is undefined', () => {
|
|
876
|
+
if (typeof mod.applyParamSnapshotToUbo !== 'function') {
|
|
877
|
+
throw new Error('helper not exported yet (red phase)');
|
|
878
|
+
}
|
|
879
|
+
const buf = new ArrayBuffer(64);
|
|
880
|
+
const pre = new Float32Array(buf);
|
|
881
|
+
for (let i = 0; i < 16; i++) pre[i] = i + 1;
|
|
882
|
+
const snapshot = { colorTint: [0.5, 0.5, 0.5, 0.5] as readonly number[] };
|
|
883
|
+
mod.applyParamSnapshotToUbo(buf, undefined, snapshot);
|
|
884
|
+
// Schema undefined -> no writes.
|
|
885
|
+
for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
|
|
886
|
+
mod.applyParamSnapshotToUbo(buf, SPRITE_SHAPED_SCHEMA, undefined);
|
|
887
|
+
// Snapshot undefined -> no writes.
|
|
888
|
+
for (let i = 0; i < 16; i++) expect(pre[i]).toBe(i + 1);
|
|
889
|
+
});
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// --- from feat-20260625-refactor-sprite-as-transparent-mesh M2 / w4 ---
|
|
894
|
+
{
|
|
895
|
+
// AC-05: a MaterialSnapshot carrying transparent:true on its source pass
|
|
896
|
+
// (recorded as MaterialSnapshot.transparent === true) must drive the LDR
|
|
897
|
+
// split-pass decision and the blend-state lookup INDEPENDENTLY of the
|
|
898
|
+
// shader id. The shader here is a generic 'forgeax::unlit-test' stub -- if
|
|
899
|
+
// splitLdrSprite or the blend-state helper still keys on
|
|
900
|
+
// 'forgeax::sprite', this suite stays red.
|
|
901
|
+
//
|
|
902
|
+
// Plan anchors:
|
|
903
|
+
// - requirements AC-05 (transparent decoupled from sprite shader)
|
|
904
|
+
// - plan-strategy section 2 D-3 (MaterialSnapshot.transparent
|
|
905
|
+
// transparent passthrough; split reads snapshot, not pass)
|
|
906
|
+
// - plan-strategy section 5.4 falsification check (the inline comment
|
|
907
|
+
// below records the variant: flipping blend src=one/dst=zero turns
|
|
908
|
+
// premultiplied alpha into hard-edge composition and fails the dawn
|
|
909
|
+
// msaa-sprite-pixel-diff baseline -- documenting that the pixel
|
|
910
|
+
// diff smoke is sensitive to blend factor correctness).
|
|
911
|
+
//
|
|
912
|
+
// The `computeSplitLdrSprite` helper is added by w7. The dynamic-import
|
|
913
|
+
// sentinel keeps this suite red until it lands.
|
|
914
|
+
//
|
|
915
|
+
// feat-20260626-sprite-transparent-collapse M2 / M4: the sibling
|
|
916
|
+
// `resolveTransparentBlendState` helper has been removed — premultiplied-
|
|
917
|
+
// alpha blend factors are now pinned to the exported
|
|
918
|
+
// `SPRITE_PREMULTIPLIED_ALPHA_BLEND` constant (the SSOT for asset-side
|
|
919
|
+
// `renderState.blend`); the tests that exercised the resolver are deleted
|
|
920
|
+
// and the falsification check now lives on the dawn pixel-diff
|
|
921
|
+
// baseline + the asset-side constant export check.
|
|
922
|
+
|
|
923
|
+
type DispatchEntryLike = {
|
|
924
|
+
readonly source: {
|
|
925
|
+
readonly material: MaterialSnapshot;
|
|
926
|
+
readonly materials: readonly MaterialSnapshot[];
|
|
927
|
+
};
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
const mod = recordModule as unknown as {
|
|
931
|
+
computeSplitLdrSprite?: (
|
|
932
|
+
validatedOrdered: readonly (DispatchEntryLike | undefined)[],
|
|
933
|
+
tonemapActive: boolean,
|
|
934
|
+
) => boolean;
|
|
935
|
+
isEntityFullyTransparent?: (source: {
|
|
936
|
+
material: MaterialSnapshot;
|
|
937
|
+
materials?: readonly MaterialSnapshot[];
|
|
938
|
+
}) => boolean;
|
|
939
|
+
entityHasTransparentSubmesh?: (source: {
|
|
940
|
+
material: MaterialSnapshot;
|
|
941
|
+
materials?: readonly MaterialSnapshot[];
|
|
942
|
+
}) => boolean;
|
|
943
|
+
};
|
|
944
|
+
|
|
945
|
+
function makeTransparentSnap(opts: {
|
|
946
|
+
transparent?: boolean;
|
|
947
|
+
materialShaderId?: string;
|
|
948
|
+
}): MaterialSnapshot {
|
|
949
|
+
return {
|
|
950
|
+
baseColor: [1, 1, 1, 1] as const,
|
|
951
|
+
metallic: 0,
|
|
952
|
+
roughness: 1,
|
|
953
|
+
materialShaderId: opts.materialShaderId ?? 'forgeax::unlit-test',
|
|
954
|
+
paramSnapshot: {},
|
|
955
|
+
...(opts.transparent === true && { transparent: true }),
|
|
956
|
+
} as unknown as MaterialSnapshot;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function makeValidatedEntry(material: MaterialSnapshot): DispatchEntryLike {
|
|
960
|
+
return { source: { material, materials: [material] } };
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
describe('transparent decouples from sprite shader (M2 / w4, AC-05)', () => {
|
|
964
|
+
it('export: computeSplitLdrSprite helper is exported from render-system-record', () => {
|
|
965
|
+
expect(typeof mod.computeSplitLdrSprite).toBe('function');
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
it('non-sprite shader with transparent:true triggers LDR split', () => {
|
|
969
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
970
|
+
throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
|
|
971
|
+
}
|
|
972
|
+
const transparentSnap = makeTransparentSnap({
|
|
973
|
+
transparent: true,
|
|
974
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
975
|
+
});
|
|
976
|
+
const opaqueSnap = makeTransparentSnap({
|
|
977
|
+
transparent: false,
|
|
978
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
979
|
+
});
|
|
980
|
+
// tonemapActive=false (LDR path) + a transparent entry in the
|
|
981
|
+
// validated list -- the split decision must trip even though the
|
|
982
|
+
// shader is not forgeax::sprite (AC-05 proves the decoupling).
|
|
983
|
+
const split = mod.computeSplitLdrSprite(
|
|
984
|
+
[makeValidatedEntry(opaqueSnap), makeValidatedEntry(transparentSnap)],
|
|
985
|
+
false,
|
|
986
|
+
);
|
|
987
|
+
expect(split).toBe(true);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
it('LDR split stays false on pure-opaque non-sprite list', () => {
|
|
991
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
992
|
+
throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
|
|
993
|
+
}
|
|
994
|
+
const opaqueA = makeTransparentSnap({
|
|
995
|
+
transparent: false,
|
|
996
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
997
|
+
});
|
|
998
|
+
const opaqueB = makeTransparentSnap({
|
|
999
|
+
transparent: false,
|
|
1000
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1001
|
+
});
|
|
1002
|
+
const split = mod.computeSplitLdrSprite(
|
|
1003
|
+
[makeValidatedEntry(opaqueA), makeValidatedEntry(opaqueB)],
|
|
1004
|
+
false,
|
|
1005
|
+
);
|
|
1006
|
+
expect(split).toBe(false);
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it('HDR path (tonemapActive=true) suppresses split even with transparent entries', () => {
|
|
1010
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
1011
|
+
throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
|
|
1012
|
+
}
|
|
1013
|
+
const transparentSnap = makeTransparentSnap({
|
|
1014
|
+
transparent: true,
|
|
1015
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
1016
|
+
});
|
|
1017
|
+
const split = mod.computeSplitLdrSprite([makeValidatedEntry(transparentSnap)], true);
|
|
1018
|
+
expect(split).toBe(false);
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
it('M3 / w13: legacy sprite material without transparent:true does NOT trigger split (union arm deleted)', () => {
|
|
1022
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
1023
|
+
throw new Error('computeSplitLdrSprite helper not exported yet (red phase)');
|
|
1024
|
+
}
|
|
1025
|
+
// feat-20260625-refactor-sprite-as-transparent-mesh M3 / w13 (D-3):
|
|
1026
|
+
// the M2 union (`transparent || shadingModel === 'sprite'`) is gone
|
|
1027
|
+
// — transparent is the single SSOT. A legacy snapshot carrying
|
|
1028
|
+
// `materialShaderId: 'forgeax::sprite'` but no `transparent: true`
|
|
1029
|
+
// declaration MUST NOT trigger the LDR split (forces explicit
|
|
1030
|
+
// transparent attribution on the asset side; AC-04 / AC-05).
|
|
1031
|
+
const legacySpriteNoTransparent = {
|
|
1032
|
+
baseColor: [1, 1, 1, 1] as const,
|
|
1033
|
+
metallic: 0,
|
|
1034
|
+
roughness: 1,
|
|
1035
|
+
materialShaderId: 'forgeax::sprite',
|
|
1036
|
+
paramSnapshot: {},
|
|
1037
|
+
} as unknown as MaterialSnapshot;
|
|
1038
|
+
const splitWithoutTransparent = mod.computeSplitLdrSprite(
|
|
1039
|
+
[makeValidatedEntry(legacySpriteNoTransparent)],
|
|
1040
|
+
false,
|
|
1041
|
+
);
|
|
1042
|
+
expect(splitWithoutTransparent).toBe(false);
|
|
1043
|
+
|
|
1044
|
+
// Same snapshot with `transparent: true` declared on the pass DOES
|
|
1045
|
+
// trigger the split (the only path that should).
|
|
1046
|
+
const spriteTransparent = {
|
|
1047
|
+
...legacySpriteNoTransparent,
|
|
1048
|
+
transparent: true,
|
|
1049
|
+
} as unknown as MaterialSnapshot;
|
|
1050
|
+
const splitWithTransparent = mod.computeSplitLdrSprite(
|
|
1051
|
+
[makeValidatedEntry(spriteTransparent)],
|
|
1052
|
+
false,
|
|
1053
|
+
);
|
|
1054
|
+
expect(splitWithTransparent).toBe(true);
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
it('transparent:true resolves premultiplied-alpha blend state (src=one/dst=one-minus-src-alpha)', () => {
|
|
1058
|
+
// feat-20260626-sprite-transparent-collapse M2 / M4: the
|
|
1059
|
+
// `resolveTransparentBlendState` resolver is gone — premultiplied-alpha
|
|
1060
|
+
// factors are pinned to the exported `SPRITE_PREMULTIPLIED_ALPHA_BLEND`
|
|
1061
|
+
// constant (the asset-side SSOT for `renderState.blend`). This test
|
|
1062
|
+
// pins the constant's shape so a future maintainer flipping the color
|
|
1063
|
+
// blend to {src:'one', dst:'zero'} (opaque copy) still trips a unit
|
|
1064
|
+
// gate before the dawn msaa-sprite-pixel-diff smoke catches the
|
|
1065
|
+
// regression. AC-10 reaches the human arbiter through both paths.
|
|
1066
|
+
expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.srcFactor).toBe('one');
|
|
1067
|
+
expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.color.dstFactor).toBe('one-minus-src-alpha');
|
|
1068
|
+
expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.srcFactor).toBe('one');
|
|
1069
|
+
expect(SPRITE_PREMULTIPLIED_ALPHA_BLEND.alpha.dstFactor).toBe('one-minus-src-alpha');
|
|
1070
|
+
});
|
|
1071
|
+
|
|
1072
|
+
// feat-city-glb Bug 5 (per-submesh transparency): the split must trip when
|
|
1073
|
+
// a NON-zero submesh material is transparent even though the entity-level
|
|
1074
|
+
// material[0] is opaque (the crosswalk case: opaque road submesh[0] +
|
|
1075
|
+
// BLEND decal submesh[1] on one multi-material mesh).
|
|
1076
|
+
it('per-submesh: split trips when only a non-zero submesh is transparent', () => {
|
|
1077
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
1078
|
+
throw new Error('computeSplitLdrSprite helper not exported yet');
|
|
1079
|
+
}
|
|
1080
|
+
const opaque0 = makeTransparentSnap({
|
|
1081
|
+
transparent: false,
|
|
1082
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1083
|
+
});
|
|
1084
|
+
const transparent1 = makeTransparentSnap({
|
|
1085
|
+
transparent: true,
|
|
1086
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1087
|
+
});
|
|
1088
|
+
// Entity-level material (=materials[0]) is opaque; materials[1] transparent.
|
|
1089
|
+
const mixedEntry: DispatchEntryLike = {
|
|
1090
|
+
source: { material: opaque0, materials: [opaque0, transparent1] },
|
|
1091
|
+
};
|
|
1092
|
+
expect(mod.computeSplitLdrSprite([mixedEntry], false)).toBe(true);
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
it('per-submesh: split stays false when every submesh material is opaque', () => {
|
|
1096
|
+
if (typeof mod.computeSplitLdrSprite !== 'function') {
|
|
1097
|
+
throw new Error('computeSplitLdrSprite helper not exported yet');
|
|
1098
|
+
}
|
|
1099
|
+
const opaque0 = makeTransparentSnap({
|
|
1100
|
+
transparent: false,
|
|
1101
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1102
|
+
});
|
|
1103
|
+
const opaque1 = makeTransparentSnap({
|
|
1104
|
+
transparent: false,
|
|
1105
|
+
materialShaderId: 'forgeax::default-standard-pbr',
|
|
1106
|
+
});
|
|
1107
|
+
const opaqueMixed: DispatchEntryLike = {
|
|
1108
|
+
source: { material: opaque0, materials: [opaque0, opaque1] },
|
|
1109
|
+
};
|
|
1110
|
+
expect(mod.computeSplitLdrSprite([opaqueMixed], false)).toBe(false);
|
|
1111
|
+
});
|
|
1112
|
+
|
|
1113
|
+
it('isEntityFullyTransparent / entityHasTransparentSubmesh classify mixed meshes', () => {
|
|
1114
|
+
if (
|
|
1115
|
+
typeof mod.isEntityFullyTransparent !== 'function' ||
|
|
1116
|
+
typeof mod.entityHasTransparentSubmesh !== 'function'
|
|
1117
|
+
) {
|
|
1118
|
+
throw new Error('per-submesh transparency helpers not exported yet');
|
|
1119
|
+
}
|
|
1120
|
+
const opaque = makeTransparentSnap({ transparent: false });
|
|
1121
|
+
const transp = makeTransparentSnap({ transparent: true });
|
|
1122
|
+
// Mixed: opaque[0] + transparent[1] -> has a transparent submesh but not
|
|
1123
|
+
// fully transparent (must draw in BOTH geometry pass and blend sub-pass).
|
|
1124
|
+
const mixed = { material: opaque, materials: [opaque, transp] };
|
|
1125
|
+
expect(mod.entityHasTransparentSubmesh(mixed)).toBe(true);
|
|
1126
|
+
expect(mod.isEntityFullyTransparent(mixed)).toBe(false);
|
|
1127
|
+
// Fully transparent (e.g. a sprite / 4.3 window quad).
|
|
1128
|
+
const full = { material: transp, materials: [transp] };
|
|
1129
|
+
expect(mod.entityHasTransparentSubmesh(full)).toBe(true);
|
|
1130
|
+
expect(mod.isEntityFullyTransparent(full)).toBe(true);
|
|
1131
|
+
// Fully opaque.
|
|
1132
|
+
const none = { material: opaque, materials: [opaque, opaque] };
|
|
1133
|
+
expect(mod.entityHasTransparentSubmesh(none)).toBe(false);
|
|
1134
|
+
expect(mod.isEntityFullyTransparent(none)).toBe(false);
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// --- from feat-20260625-refactor-sprite-as-transparent-mesh M2 / w5 ---
|
|
1140
|
+
{
|
|
1141
|
+
// AC-14: a transparent material whose target shader pipeline is not yet
|
|
1142
|
+
// cached must surface a structured RhiError (.code / .expected / .hint),
|
|
1143
|
+
// NOT silently fall back to the debug-pink placeholder. The generic
|
|
1144
|
+
// materialShaderId path is the only resolver -- post feat-20260625 M3 / w14
|
|
1145
|
+
// the dedicated sprite PSO fields are gone; sprite uses the same generic
|
|
1146
|
+
// getMaterialShaderPipeline path every transparent material consumes.
|
|
1147
|
+
//
|
|
1148
|
+
// The helper signature added by w7:
|
|
1149
|
+
//
|
|
1150
|
+
// selectMaterialPipelineForRender({
|
|
1151
|
+
// materialShaderId, isHdr, renderState, topology, indexFormat,
|
|
1152
|
+
// variantSet, sampleCount,
|
|
1153
|
+
// getMaterialShaderPipeline, // injected at call site (runtime.getMaterialShaderPipeline)
|
|
1154
|
+
// }): Result<RenderPipeline, RhiError>
|
|
1155
|
+
//
|
|
1156
|
+
// Plan anchors:
|
|
1157
|
+
// - requirements AC-14 (structured RhiError, no debug-pink fall-through)
|
|
1158
|
+
// - plan-strategy section 8.3 (error info AI users consume via property
|
|
1159
|
+
// access: .code / .expected / .hint; charter P3 explicit failure)
|
|
1160
|
+
// - plan-strategy section 2 D-3 (transparent flows through the generic
|
|
1161
|
+
// materialShaderId path; the resolver therefore never branches on
|
|
1162
|
+
// 'forgeax::sprite' to recover)
|
|
1163
|
+
|
|
1164
|
+
type SelectArgs = {
|
|
1165
|
+
readonly materialShaderId: string;
|
|
1166
|
+
readonly isHdr: boolean;
|
|
1167
|
+
readonly renderState: {
|
|
1168
|
+
readonly blend?: {
|
|
1169
|
+
readonly color: { readonly srcFactor: string; readonly dstFactor: string };
|
|
1170
|
+
readonly alpha: { readonly srcFactor: string; readonly dstFactor: string };
|
|
1171
|
+
};
|
|
1172
|
+
};
|
|
1173
|
+
readonly topology: 'triangle-list' | 'triangle-strip';
|
|
1174
|
+
readonly indexFormat: 'uint16' | 'uint32';
|
|
1175
|
+
readonly variantSet: string;
|
|
1176
|
+
readonly sampleCount: number;
|
|
1177
|
+
readonly getMaterialShaderPipeline: (
|
|
1178
|
+
materialShaderId: string,
|
|
1179
|
+
isHdr: boolean,
|
|
1180
|
+
) => unknown | null;
|
|
1181
|
+
};
|
|
1182
|
+
|
|
1183
|
+
const mod = recordModule as unknown as {
|
|
1184
|
+
selectMaterialPipelineForRender?: (
|
|
1185
|
+
args: SelectArgs,
|
|
1186
|
+
) =>
|
|
1187
|
+
| { readonly ok: true; readonly value: unknown }
|
|
1188
|
+
| { readonly ok: false; readonly error: { code: string; expected: string; hint: string } };
|
|
1189
|
+
};
|
|
1190
|
+
|
|
1191
|
+
function makeSelectArgs(overrides: {
|
|
1192
|
+
materialShaderId?: string;
|
|
1193
|
+
getter?: SelectArgs['getMaterialShaderPipeline'];
|
|
1194
|
+
}): SelectArgs {
|
|
1195
|
+
const pipelineStub = { __pipeline: true };
|
|
1196
|
+
return {
|
|
1197
|
+
materialShaderId: overrides.materialShaderId ?? 'forgeax::unlit-test',
|
|
1198
|
+
isHdr: false,
|
|
1199
|
+
renderState: {
|
|
1200
|
+
blend: {
|
|
1201
|
+
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
|
|
1202
|
+
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },
|
|
1203
|
+
},
|
|
1204
|
+
},
|
|
1205
|
+
topology: 'triangle-list',
|
|
1206
|
+
indexFormat: 'uint16',
|
|
1207
|
+
variantSet: '',
|
|
1208
|
+
sampleCount: 1,
|
|
1209
|
+
getMaterialShaderPipeline: overrides.getter ?? (() => pipelineStub),
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
describe('selectMaterialPipelineForRender pipeline-miss surfaces RhiError (M2 / w5, AC-14)', () => {
|
|
1214
|
+
it('export: selectMaterialPipelineForRender helper is exported from render-system-record', () => {
|
|
1215
|
+
expect(typeof mod.selectMaterialPipelineForRender).toBe('function');
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
it('cache miss returns ok=false with structured RhiError (.code/.expected/.hint)', () => {
|
|
1219
|
+
if (typeof mod.selectMaterialPipelineForRender !== 'function') {
|
|
1220
|
+
throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
|
|
1221
|
+
}
|
|
1222
|
+
const result = mod.selectMaterialPipelineForRender(
|
|
1223
|
+
makeSelectArgs({
|
|
1224
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
1225
|
+
getter: () => null, // simulate cache miss / async build pending
|
|
1226
|
+
}),
|
|
1227
|
+
);
|
|
1228
|
+
expect(result.ok).toBe(false);
|
|
1229
|
+
if (result.ok) throw new Error('unreachable -- result.ok asserted false above');
|
|
1230
|
+
const err = result.error;
|
|
1231
|
+
// charter P3 -- AI users branch on .code (string union member from
|
|
1232
|
+
// RhiErrorCode) and consume .expected / .hint as property reads;
|
|
1233
|
+
// they do NOT parse the human-facing .message string.
|
|
1234
|
+
expect(typeof err.code).toBe('string');
|
|
1235
|
+
expect(err.code.length).toBeGreaterThan(0);
|
|
1236
|
+
expect(typeof err.expected).toBe('string');
|
|
1237
|
+
expect(err.expected.length).toBeGreaterThan(0);
|
|
1238
|
+
expect(typeof err.hint).toBe('string');
|
|
1239
|
+
expect(err.hint.length).toBeGreaterThan(0);
|
|
1240
|
+
});
|
|
1241
|
+
|
|
1242
|
+
it('cache miss .hint references the missing materialShaderId (AI users locate the cache key)', () => {
|
|
1243
|
+
if (typeof mod.selectMaterialPipelineForRender !== 'function') {
|
|
1244
|
+
throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
|
|
1245
|
+
}
|
|
1246
|
+
const result = mod.selectMaterialPipelineForRender(
|
|
1247
|
+
makeSelectArgs({
|
|
1248
|
+
materialShaderId: 'forgeax::missing-shader-id',
|
|
1249
|
+
getter: () => null,
|
|
1250
|
+
}),
|
|
1251
|
+
);
|
|
1252
|
+
expect(result.ok).toBe(false);
|
|
1253
|
+
if (result.ok) throw new Error('unreachable -- result.ok asserted false above');
|
|
1254
|
+
// The shader id must appear in .hint (or .expected) so AI users can
|
|
1255
|
+
// grep the registry / pipeline cache straight from the structured
|
|
1256
|
+
// error -- not from a human-prose substring of .message.
|
|
1257
|
+
const exposed = `${result.error.hint} ${result.error.expected}`;
|
|
1258
|
+
expect(exposed).toContain('forgeax::missing-shader-id');
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
it('cache hit returns ok=true with the pipeline (no error path on success)', () => {
|
|
1262
|
+
if (typeof mod.selectMaterialPipelineForRender !== 'function') {
|
|
1263
|
+
throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
|
|
1264
|
+
}
|
|
1265
|
+
const pipeline = { __pipeline: 'forgeax::unlit-test-pso' };
|
|
1266
|
+
const result = mod.selectMaterialPipelineForRender(
|
|
1267
|
+
makeSelectArgs({
|
|
1268
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
1269
|
+
getter: () => pipeline,
|
|
1270
|
+
}),
|
|
1271
|
+
);
|
|
1272
|
+
expect(result.ok).toBe(true);
|
|
1273
|
+
if (!result.ok) throw new Error('unreachable -- result.ok asserted true above');
|
|
1274
|
+
expect(result.value).toBe(pipeline);
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
it('cache miss does NOT silently substitute a debug-pink fallback pipeline', () => {
|
|
1278
|
+
if (typeof mod.selectMaterialPipelineForRender !== 'function') {
|
|
1279
|
+
throw new Error('selectMaterialPipelineForRender helper not exported yet (red phase)');
|
|
1280
|
+
}
|
|
1281
|
+
// Probe: the only branch the getter ever returns is the one passed in.
|
|
1282
|
+
// A cache miss must surface ok=false, NOT a synthesized pipeline value
|
|
1283
|
+
// that hides the miss (charter P3 -- the pre-M6 silent fallback to
|
|
1284
|
+
// pipelineState.standardPipeline* was exactly this anti-pattern, see
|
|
1285
|
+
// render-system-record.ts:5188-5198 commentary).
|
|
1286
|
+
let getterCallCount = 0;
|
|
1287
|
+
const result = mod.selectMaterialPipelineForRender(
|
|
1288
|
+
makeSelectArgs({
|
|
1289
|
+
materialShaderId: 'forgeax::unlit-test',
|
|
1290
|
+
getter: () => {
|
|
1291
|
+
getterCallCount += 1;
|
|
1292
|
+
return null;
|
|
1293
|
+
},
|
|
1294
|
+
}),
|
|
1295
|
+
);
|
|
1296
|
+
expect(getterCallCount).toBeGreaterThanOrEqual(1);
|
|
1297
|
+
expect(result.ok).toBe(false);
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// ─── bug-20260622-tilemap-ysort-transparent-sort-modes-followup M2 m2-1 ───
|
|
1303
|
+
//
|
|
1304
|
+
// AC-04 (error signal SSOT) + AC-05 (LAYER_Y footY ordering) + R-2
|
|
1305
|
+
// (mode=DISTANCE without a cameraPos falls back to the original list).
|
|
1306
|
+
// `sortTransparentDispatch` itself is render-system.ts-private (closure-
|
|
1307
|
+
// scoped helper invoked once per draw); the public-surface contract sits on
|
|
1308
|
+
// `transparentSortEntries` (same primary `layer ASC` + secondary mode-formula
|
|
1309
|
+
// + identical fallback semantics). Asserting against the exported helper
|
|
1310
|
+
// gives byte-exact coverage of the 4-mode dispatch SSOT without instantiating
|
|
1311
|
+
// the full RenderSystem (which would need a real GPU device + canvas, far
|
|
1312
|
+
// outside unit-test scope; plan-strategy R-2 mitigation).
|
|
1313
|
+
// biome-ignore lint/complexity/noUselessLoneBlockStatements: mirrors the per-test-file block-scope idiom this consolidated test file already uses (lines 31/159/261/501/633 -- each ported slab from a pre-consolidation file lives in its own block so helper names cannot collide).
|
|
1314
|
+
{
|
|
1315
|
+
// --- AC-04: setTransparentSortConfig mode=99 -> Result.err with 4 SSOT fields ---
|
|
1316
|
+
describe('AC-04 setTransparentSortConfig mode=99 returns Result.err (KV untouched)', () => {
|
|
1317
|
+
it('Result.err carries code/expected/hint/detail + KV resource is NOT inserted', async () => {
|
|
1318
|
+
const { setTransparentSortConfig, TRANSPARENT_SORT_CONFIG_KEY } = await import(
|
|
1319
|
+
'@forgeax/engine-render/internal'
|
|
1320
|
+
);
|
|
1321
|
+
const world = new World();
|
|
1322
|
+
// Pre-check: resource MUST be absent before the rejected call.
|
|
1323
|
+
expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
|
|
1324
|
+
|
|
1325
|
+
const r = setTransparentSortConfig(world, { mode: 99, yzAlpha: 1.0 });
|
|
1326
|
+
expect(r.ok).toBe(false);
|
|
1327
|
+
if (r.ok) return;
|
|
1328
|
+
// The 4 SSOT fields locked by plan-strategy D-4. The math symbol
|
|
1329
|
+
// U+2208 ("is element of") goes through the ASCII `∈` escape
|
|
1330
|
+
// so this source file stays English-only per AGENTS.md §Conventions
|
|
1331
|
+
// (mirrors EXPECTED_EXPECTED in systems.unit.test.ts).
|
|
1332
|
+
expect(r.error.code).toBe('resource-invalid-value');
|
|
1333
|
+
expect(r.error.expected).toBe('mode ∈ {0, 1, 2, 3}');
|
|
1334
|
+
expect(r.error.hint).toBe('0=layer-z, 1=layer-y, 2=layer-yz, 3=distance');
|
|
1335
|
+
expect((r.error.detail as { receivedMode: number }).receivedMode).toBe(99);
|
|
1336
|
+
// The KV resource MUST stay un-inserted after a rejected write
|
|
1337
|
+
// (charter P3 -- structured failure, never silently coerce).
|
|
1338
|
+
expect(world.hasResource(TRANSPARENT_SORT_CONFIG_KEY)).toBe(false);
|
|
1339
|
+
});
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
// --- AC-05: mode=LAYER_Y, 3 entities footY=10/20/30 same layer -> 30/20/10 ---
|
|
1343
|
+
describe('AC-05 LAYER_Y footY ordering (same layer, deeper foot draws later)', () => {
|
|
1344
|
+
it('footY=10/20/30 same layer -> output order footY=30/20/10 (back-to-front)', async () => {
|
|
1345
|
+
const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_LAYER_Y } = await import(
|
|
1346
|
+
'@forgeax/engine-render/internal'
|
|
1347
|
+
);
|
|
1348
|
+
const { transparentSortEntries } = await import('../systems/transparent-sort');
|
|
1349
|
+
const world = new World();
|
|
1350
|
+
setTransparentSortConfig(world, {
|
|
1351
|
+
mode: TRANSPARENT_SORT_MODE_LAYER_Y,
|
|
1352
|
+
yzAlpha: 1.0,
|
|
1353
|
+
}).unwrap();
|
|
1354
|
+
|
|
1355
|
+
// footY = posY - pivotY * sizeY. Pin pivotY=0 + sizeY=1 so footY === posY
|
|
1356
|
+
// -- the 10/20/30 numbers land verbatim, no algebra to second-guess.
|
|
1357
|
+
// mode=1 sortValue = -footY; ASC over [-10, -20, -30] yields entries
|
|
1358
|
+
// ordered footY=30, 20, 10 (deepest foot draws last = back-to-front).
|
|
1359
|
+
const entries = [
|
|
1360
|
+
{
|
|
1361
|
+
entityIndex: 0,
|
|
1362
|
+
materialHandle: 0,
|
|
1363
|
+
layer: 0,
|
|
1364
|
+
posX: 0,
|
|
1365
|
+
posY: 10,
|
|
1366
|
+
posZ: 0,
|
|
1367
|
+
pivotY: 0,
|
|
1368
|
+
sizeY: 1,
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
entityIndex: 1,
|
|
1372
|
+
materialHandle: 0,
|
|
1373
|
+
layer: 0,
|
|
1374
|
+
posX: 0,
|
|
1375
|
+
posY: 20,
|
|
1376
|
+
posZ: 0,
|
|
1377
|
+
pivotY: 0,
|
|
1378
|
+
sizeY: 1,
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
entityIndex: 2,
|
|
1382
|
+
materialHandle: 0,
|
|
1383
|
+
layer: 0,
|
|
1384
|
+
posX: 0,
|
|
1385
|
+
posY: 30,
|
|
1386
|
+
posZ: 0,
|
|
1387
|
+
pivotY: 0,
|
|
1388
|
+
sizeY: 1,
|
|
1389
|
+
},
|
|
1390
|
+
];
|
|
1391
|
+
const sorted = transparentSortEntries(entries, world);
|
|
1392
|
+
expect(sorted.map((e) => e.posY)).toEqual([30, 20, 10]);
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
// --- R-2: mode=DISTANCE without a cameraPos returns entries unchanged ---
|
|
1397
|
+
// plan-strategy R-2 risk mitigation: sortTransparentDispatch mode=3 +
|
|
1398
|
+
// cameras[0] missing must keep the original dispatch list (PR #401
|
|
1399
|
+
// baseline). transparentSortEntries surfaces the same fallback through
|
|
1400
|
+
// the public helper -- the 2-arg call (no cameraPos) falls through to
|
|
1401
|
+
// the mode=0 posZ formula; with posZ pinned constant the sort becomes
|
|
1402
|
+
// a no-op preserving insertion order (charter P3 deterministic output).
|
|
1403
|
+
describe('R-2 mode=DISTANCE + cameraPos absent preserves insertion order', () => {
|
|
1404
|
+
it('transparentSortEntries(entries, world) with mode=3 + no cameraPos = insertion order', async () => {
|
|
1405
|
+
const { setTransparentSortConfig, TRANSPARENT_SORT_MODE_DISTANCE } = await import(
|
|
1406
|
+
'@forgeax/engine-render/internal'
|
|
1407
|
+
);
|
|
1408
|
+
const { transparentSortEntries } = await import('../systems/transparent-sort');
|
|
1409
|
+
const world = new World();
|
|
1410
|
+
setTransparentSortConfig(world, {
|
|
1411
|
+
mode: TRANSPARENT_SORT_MODE_DISTANCE,
|
|
1412
|
+
yzAlpha: 1.0,
|
|
1413
|
+
}).unwrap();
|
|
1414
|
+
|
|
1415
|
+
// posZ pinned identical so the fallback (posZ ASC) is a no-op; the
|
|
1416
|
+
// assertion guards both R-2 (no crash, no reorder) and "fallback is
|
|
1417
|
+
// deterministic" together.
|
|
1418
|
+
const entries = [
|
|
1419
|
+
{
|
|
1420
|
+
entityIndex: 7,
|
|
1421
|
+
materialHandle: 0,
|
|
1422
|
+
layer: 0,
|
|
1423
|
+
posX: 1,
|
|
1424
|
+
posY: 0,
|
|
1425
|
+
posZ: 0,
|
|
1426
|
+
pivotY: 0.5,
|
|
1427
|
+
sizeY: 1,
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
entityIndex: 8,
|
|
1431
|
+
materialHandle: 0,
|
|
1432
|
+
layer: 0,
|
|
1433
|
+
posX: 2,
|
|
1434
|
+
posY: 0,
|
|
1435
|
+
posZ: 0,
|
|
1436
|
+
pivotY: 0.5,
|
|
1437
|
+
sizeY: 1,
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
entityIndex: 9,
|
|
1441
|
+
materialHandle: 0,
|
|
1442
|
+
layer: 0,
|
|
1443
|
+
posX: 3,
|
|
1444
|
+
posY: 0,
|
|
1445
|
+
posZ: 0,
|
|
1446
|
+
pivotY: 0.5,
|
|
1447
|
+
sizeY: 1,
|
|
1448
|
+
},
|
|
1449
|
+
];
|
|
1450
|
+
const sorted = transparentSortEntries(entries, world);
|
|
1451
|
+
expect(sorted.map((e) => e.entityIndex)).toEqual([7, 8, 9]);
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// --- tweak-20260701 M2 AC-02: zero-light warning polarity ---
|
|
1457
|
+
{
|
|
1458
|
+
// AC-02: the zero-light black-screen warning must still fire for
|
|
1459
|
+
// builtin PBR/standard materials and remain silent for custom, unlit /
|
|
1460
|
+
// sprite / default materials. Custom shader lighting contracts are authored
|
|
1461
|
+
// by the shader and cannot be inferred by this generic diagnostic.
|
|
1462
|
+
//
|
|
1463
|
+
// The test imports `isLitMaterialSnapshot` from the production
|
|
1464
|
+
// render-system-record module — the same function that recordFrame's
|
|
1465
|
+
// zero-light warning gate (line ~2122) calls to compute
|
|
1466
|
+
// hasStandardMaterial. A test-local copy would not anchor to the
|
|
1467
|
+
// production path.
|
|
1468
|
+
|
|
1469
|
+
function makeSnap(materialShaderId: string | undefined): MaterialSnapshot {
|
|
1470
|
+
return {
|
|
1471
|
+
baseColor: [1, 1, 1, 1] as const,
|
|
1472
|
+
metallic: 0,
|
|
1473
|
+
roughness: 1,
|
|
1474
|
+
materialShaderId,
|
|
1475
|
+
paramSnapshot: undefined,
|
|
1476
|
+
} as unknown as MaterialSnapshot;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
describe('AC-02: zero-light warning polarity', () => {
|
|
1480
|
+
it('standard / PBR material (materialShaderId !== undefined, !== default-unlit) -> lit', () => {
|
|
1481
|
+
const snap = makeSnap('forgeax::default-standard-pbr');
|
|
1482
|
+
expect(isLitMaterialSnapshot(snap)).toBe(true);
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
it('custom shader material -> not lit (custom lighting contract)', () => {
|
|
1486
|
+
const snap = makeSnap('my-custom-shader');
|
|
1487
|
+
expect(isLitMaterialSnapshot(snap)).toBe(false);
|
|
1488
|
+
});
|
|
1489
|
+
|
|
1490
|
+
it('unlit material (materialShaderId === forgeax::default-unlit) -> not lit (no warn)', () => {
|
|
1491
|
+
const snap = makeSnap('forgeax::default-unlit');
|
|
1492
|
+
expect(isLitMaterialSnapshot(snap)).toBe(false);
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
it('sprite material (materialShaderId === forgeax::sprite) -> not lit (no warn)', () => {
|
|
1496
|
+
const snap = makeSnap('forgeax::sprite');
|
|
1497
|
+
expect(isLitMaterialSnapshot(snap)).toBe(false);
|
|
1498
|
+
});
|
|
1499
|
+
|
|
1500
|
+
it('default material (materialShaderId === undefined) -> not lit (no warn)', () => {
|
|
1501
|
+
const snap = makeSnap(undefined);
|
|
1502
|
+
expect(isLitMaterialSnapshot(snap)).toBe(false);
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
}
|