@forgeax/engine-assets-runtime 0.1.3 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +442 -175
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/aabb.unit.test.d.ts +2 -0
- package/dist/__tests__/aabb.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-integrity.unit.test.d.ts.map +1 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts +2 -0
- package/dist/__tests__/artifact-io.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence-sdk.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-evidence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-kind.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.integration.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog-reconcile.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry-catalog.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.pack-cache.test.d.ts.map +1 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/asset-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts +2 -0
- package/dist/__tests__/asset-runtime-error-code-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-replica.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source-v2.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-source.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription-order.unit.test.d.ts.map +1 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts +2 -0
- package/dist/__tests__/catalog-subscription.unit.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes-pass-through.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.browser.test.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.test-d.d.ts.map +1 -0
- package/dist/__tests__/decode-image-bytes.test.d.ts +2 -0
- package/dist/__tests__/decode-image-bytes.test.d.ts.map +1 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts +2 -0
- package/dist/__tests__/dynamic-texture-store.unit.test.d.ts.map +1 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts +2 -0
- package/dist/__tests__/errors-asset.unit.test.d.ts.map +1 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts +2 -0
- package/dist/__tests__/inline-pack-loaders.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate-publication-fence.unit.test.d.ts.map +1 -0
- package/dist/__tests__/instantiate.unit.test.d.ts +2 -0
- package/dist/__tests__/instantiate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-input-contract.unit.test.d.ts.map +1 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts +2 -0
- package/dist/__tests__/loader-registry.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-dependency-invalidation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/material-generation-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts +2 -0
- package/dist/__tests__/material-load-consumer.test-d.d.ts.map +1 -0
- package/dist/__tests__/material-ready.integration.test.d.ts +2 -0
- package/dist/__tests__/material-ready.integration.test.d.ts.map +1 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts +2 -0
- package/dist/__tests__/material-stale-generation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-morph-v3.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts +2 -0
- package/dist/__tests__/mipmap-generator.unit.test.d.ts.map +1 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts +2 -0
- package/dist/__tests__/pack-artifact-transcode-model-owner.test.d.ts.map +1 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts +2 -0
- package/dist/__tests__/pack-basis-load.integration.test.d.ts.map +1 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts +2 -0
- package/dist/__tests__/payload-validate.unit.test.d.ts.map +1 -0
- package/dist/__tests__/plugin.test.d.ts +2 -0
- package/dist/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts +2 -0
- package/dist/__tests__/registry-concurrency.bench.test.d.ts.map +1 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-failure-retry.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts +2 -0
- package/dist/__tests__/registry-load-graph.integration.test.d.ts.map +1 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-load-state.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-package-cache.unit.test.d.ts.map +1 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts +2 -0
- package/dist/__tests__/registry-public-ready.unit.test.d.ts.map +1 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts +2 -0
- package/dist/__tests__/resolve-asset-handle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-handle-fields.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts +2 -0
- package/dist/__tests__/scene-payload.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts +2 -0
- package/dist/__tests__/scene-publication-fence.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-public-surface.test-d.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__/validate-material.unit.test.d.ts +2 -0
- package/dist/__tests__/validate-material.unit.test.d.ts.map +1 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts +2 -0
- package/dist/__tests__/video-pack-loader.integration.test.d.ts.map +1 -0
- package/dist/aabb.d.ts +3 -0
- package/dist/aabb.d.ts.map +1 -0
- package/dist/asset-registry.d.ts +552 -0
- package/dist/asset-registry.d.ts.map +1 -0
- package/dist/builtin-asset-registry.d.ts +22 -0
- package/dist/builtin-asset-registry.d.ts.map +1 -0
- package/dist/catalog-source.d.ts +2 -1
- package/dist/catalog-source.d.ts.map +1 -1
- package/dist/decode-image-bytes.d.ts +57 -0
- package/dist/decode-image-bytes.d.ts.map +1 -0
- package/dist/dynamic-texture-store.d.ts +89 -0
- package/dist/dynamic-texture-store.d.ts.map +1 -0
- package/dist/errors/asset.d.ts +201 -0
- package/dist/errors/asset.d.ts.map +1 -0
- package/dist/handles.d.ts +141 -0
- package/dist/handles.d.ts.map +1 -0
- package/dist/image-error.d.ts +6 -0
- package/dist/image-error.d.ts.map +1 -0
- package/dist/index.d.ts +32 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +7218 -114
- package/dist/index.mjs.map +1 -1
- package/dist/internal.d.ts +2 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.mjs +317 -3
- package/dist/internal.mjs.map +1 -1
- package/dist/loader-registry.d.ts +63 -0
- package/dist/loader-registry.d.ts.map +1 -0
- package/dist/loaders/inline-pack.d.ts +72 -0
- package/dist/loaders/inline-pack.d.ts.map +1 -0
- package/dist/loaders/mesh-bin.d.ts +21 -0
- package/dist/loaders/mesh-bin.d.ts.map +1 -0
- package/dist/loaders/pack-artifact.d.ts +20 -0
- package/dist/loaders/pack-artifact.d.ts.map +1 -0
- package/dist/material/dependency-graph.d.ts +9 -0
- package/dist/material/dependency-graph.d.ts.map +1 -0
- package/dist/material/generation-cache.d.ts +24 -0
- package/dist/material/generation-cache.d.ts.map +1 -0
- package/dist/material/inspection.d.ts +24 -0
- package/dist/material/inspection.d.ts.map +1 -0
- package/dist/material/loader.d.ts +59 -0
- package/dist/material/loader.d.ts.map +1 -0
- package/dist/material/runtime-shader.d.ts +14 -0
- package/dist/material/runtime-shader.d.ts.map +1 -0
- package/dist/mesh-bin.d.ts +3 -0
- package/dist/mesh-bin.d.ts.map +1 -0
- package/dist/mipmap-generator.d.ts +132 -0
- package/dist/mipmap-generator.d.ts.map +1 -0
- package/dist/payload-validate.d.ts +60 -0
- package/dist/payload-validate.d.ts.map +1 -0
- package/dist/plugin.d.ts +16 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/registry/artifact-io.d.ts +16 -0
- package/dist/registry/artifact-io.d.ts.map +1 -0
- package/dist/registry/asset-evidence.d.ts +13 -0
- package/dist/registry/asset-evidence.d.ts.map +1 -0
- package/dist/registry/catalog-state.d.ts +47 -0
- package/dist/registry/catalog-state.d.ts.map +1 -0
- package/dist/registry/catalog.d.ts +48 -0
- package/dist/registry/catalog.d.ts.map +1 -0
- package/dist/registry/instantiate.d.ts +153 -0
- package/dist/registry/instantiate.d.ts.map +1 -0
- package/dist/registry/load-by-guid.d.ts +247 -0
- package/dist/registry/load-by-guid.d.ts.map +1 -0
- package/dist/registry/load-state.d.ts +26 -0
- package/dist/registry/load-state.d.ts.map +1 -0
- package/dist/registry/runtime-projection.d.ts +16 -0
- package/dist/registry/runtime-projection.d.ts.map +1 -0
- package/dist/registry/scene-publication-fence.d.ts +38 -0
- package/dist/registry/scene-publication-fence.d.ts.map +1 -0
- package/dist/registry/validate-material.d.ts +77 -0
- package/dist/registry/validate-material.d.ts.map +1 -0
- package/dist/resolve-asset-handle.d.ts +43 -0
- package/dist/resolve-asset-handle.d.ts.map +1 -0
- package/dist/resolve-tileset-runtime.d.ts +23 -0
- package/dist/resolve-tileset-runtime.d.ts.map +1 -0
- package/dist/scene-handle-fields.d.ts +88 -0
- package/dist/scene-handle-fields.d.ts.map +1 -0
- package/dist/scene-payload.d.ts +15 -0
- package/dist/scene-payload.d.ts.map +1 -0
- package/dist/wire-default-loaders.d.ts +29 -0
- package/dist/wire-default-loaders.d.ts.map +1 -0
- package/package.json +22 -9
- package/src/__tests__/aabb.unit.test.ts +97 -0
- package/src/__tests__/artifact-integrity.unit.test.ts +99 -0
- package/src/__tests__/artifact-io.unit.test.ts +127 -0
- package/src/__tests__/asset-evidence-sdk.integration.test.ts +32 -0
- package/src/__tests__/asset-evidence.unit.test.ts +32 -0
- package/src/__tests__/{asset-kind.test.ts → asset-kind.unit.test.ts} +1 -1
- package/src/__tests__/asset-registry-catalog-reconcile.integration.test.ts +74 -0
- package/src/__tests__/asset-registry-catalog-reconcile.unit.test.ts +89 -0
- package/src/__tests__/asset-registry-catalog.unit.test.ts +170 -0
- package/src/__tests__/asset-registry.pack-cache.test.ts +96 -0
- package/src/__tests__/asset-registry.unit.test.ts +511 -0
- package/src/__tests__/asset-runtime-error-code-owner.test-d.ts +63 -0
- package/src/__tests__/catalog-replica.unit.test.ts +196 -0
- package/src/__tests__/catalog-source-v2.unit.test.ts +158 -0
- package/src/__tests__/catalog-source.unit.test.ts +126 -0
- package/src/__tests__/catalog-subscription-order.unit.test.ts +65 -0
- package/src/__tests__/catalog-subscription.unit.test.ts +28 -0
- package/src/__tests__/decode-image-bytes-pass-through.test.ts +34 -0
- package/src/__tests__/decode-image-bytes.browser.test.ts +102 -0
- package/src/__tests__/decode-image-bytes.test-d.ts +108 -0
- package/src/__tests__/decode-image-bytes.test.ts +90 -0
- package/src/__tests__/decode-image-mime-owner.test.ts +2 -5
- package/src/__tests__/dynamic-texture-store.unit.test.ts +115 -0
- package/src/__tests__/errors-asset.unit.test.ts +92 -0
- package/src/__tests__/inline-pack-loaders.unit.test.ts +548 -0
- package/src/__tests__/instantiate-context.unit.test.ts +65 -0
- package/src/__tests__/instantiate-publication-fence.unit.test.ts +158 -0
- package/src/__tests__/instantiate.unit.test.ts +684 -0
- package/src/__tests__/loader-input-contract.unit.test.ts +89 -0
- package/src/__tests__/loader-registry.unit.test.ts +101 -0
- package/src/__tests__/material-dependency-invalidation.unit.test.ts +14 -0
- package/src/__tests__/material-generation-cache.unit.test.ts +46 -0
- package/src/__tests__/material-load-consumer.test-d.ts +42 -0
- package/src/__tests__/material-ready.integration.test.ts +492 -0
- package/src/__tests__/material-stale-generation.unit.test.ts +152 -0
- package/src/__tests__/mesh-bin-morph-v3.test.ts +22 -0
- package/src/__tests__/mesh-bin.unit.test.ts +152 -0
- package/src/__tests__/mipmap-generator.unit.test.ts +202 -0
- package/src/__tests__/pack-artifact-transcode-model-owner.test.ts +32 -0
- package/src/__tests__/pack-basis-load.integration.test.ts +168 -0
- package/src/__tests__/payload-validate.unit.test.ts +285 -0
- package/src/__tests__/plugin.test.ts +55 -0
- package/src/__tests__/registry-concurrency.bench.test.ts +40 -0
- package/src/__tests__/registry-failure-retry.integration.test.ts +69 -0
- package/src/__tests__/registry-load-graph.integration.test.ts +246 -0
- package/src/__tests__/registry-load-state.unit.test.ts +35 -0
- package/src/__tests__/registry-package-cache.unit.test.ts +53 -0
- package/src/__tests__/registry-public-ready.unit.test.ts +38 -0
- package/src/__tests__/resolve-asset-handle.unit.test.ts +238 -0
- package/src/__tests__/scene-handle-fields.unit.test.ts +129 -0
- package/src/__tests__/scene-payload.unit.test.ts +151 -0
- package/src/__tests__/scene-publication-fence.test.ts +129 -0
- package/src/__tests__/scriptable-pack-public-surface.test-d.ts +16 -0
- package/src/__tests__/static-rig.integration.test.ts +8 -0
- package/src/__tests__/validate-material.unit.test.ts +359 -0
- package/src/__tests__/video-pack-loader.integration.test.ts +83 -0
- package/src/aabb.ts +107 -0
- package/src/asset-registry.ts +2040 -0
- package/src/builtin-asset-registry.ts +164 -0
- package/src/catalog-source.ts +14 -70
- package/src/decode-image-bytes.ts +126 -0
- package/src/dynamic-texture-store.ts +234 -0
- package/src/errors/asset.ts +347 -0
- package/src/handles.ts +180 -0
- package/src/image-error.ts +45 -0
- package/src/index.ts +177 -19
- package/src/internal.ts +2 -0
- package/src/loader-registry.ts +157 -0
- package/src/loaders/inline-pack.ts +837 -0
- package/src/loaders/mesh-bin.ts +304 -0
- package/src/loaders/pack-artifact.ts +665 -0
- package/src/material/dependency-graph.ts +46 -0
- package/src/material/generation-cache.ts +177 -0
- package/src/material/inspection.ts +46 -0
- package/src/material/loader.ts +326 -0
- package/src/material/runtime-shader.ts +133 -0
- package/src/mesh-bin.ts +5 -0
- package/src/mipmap-generator.ts +450 -0
- package/src/payload-validate.ts +573 -0
- package/src/plugin.ts +48 -0
- package/src/registry/artifact-io.ts +259 -0
- package/src/registry/asset-evidence.ts +51 -0
- package/src/registry/catalog-state.ts +409 -0
- package/src/registry/catalog.ts +369 -0
- package/src/registry/instantiate.ts +1056 -0
- package/src/registry/load-by-guid.ts +1657 -0
- package/src/registry/load-state.ts +115 -0
- package/src/registry/runtime-projection.ts +63 -0
- package/src/registry/scene-publication-fence.ts +256 -0
- package/src/registry/validate-material.ts +386 -0
- package/src/resolve-asset-handle.ts +147 -0
- package/src/resolve-tileset-runtime.ts +72 -0
- package/src/scene-handle-fields.ts +221 -0
- package/src/scene-payload.ts +207 -0
- package/src/wire-default-loaders.ts +79 -0
- package/dist/__tests__/asset-graph-red.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-graph-red.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-kind.test.d.ts +0 -2
- package/dist/__tests__/asset-kind.test.d.ts.map +0 -1
- package/dist/__tests__/asset-registry-core.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-registry-core.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts +0 -2
- package/dist/__tests__/asset-registry-public-api.test-d.d.ts.map +0 -1
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-core-lifecycle.integration.test.d.ts.map +0 -1
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts +0 -2
- package/dist/__tests__/asset-runtime-snapshot.unit.test.d.ts.map +0 -1
- package/dist/__tests__/catalog-session-red.unit.test.d.ts +0 -2
- package/dist/__tests__/catalog-session-red.unit.test.d.ts.map +0 -1
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts +0 -2
- package/dist/__tests__/registry-lifecycle-red.integration.test.d.ts.map +0 -1
- package/src/__tests__/asset-graph-red.integration.test.ts +0 -113
- package/src/__tests__/asset-registry-core.integration.test.ts +0 -161
- package/src/__tests__/asset-registry-public-api.test-d.ts +0 -31
- package/src/__tests__/asset-runtime-core-lifecycle.integration.test.ts +0 -79
- package/src/__tests__/asset-runtime-snapshot.unit.test.ts +0 -23
- package/src/__tests__/catalog-session-red.unit.test.ts +0 -276
- package/src/__tests__/registry-lifecycle-red.integration.test.ts +0 -80
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- decodeImageBytes browser e2e test
|
|
2
|
+
// (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M3 / m3-1).
|
|
3
|
+
//
|
|
4
|
+
// Runs under the vitest browser project (chromium + real createImageBitmap +
|
|
5
|
+
// OffscreenCanvas 2D). Anchors the end-to-end "bytes -> POD -> world column
|
|
6
|
+
// handle" happy path that the Node-side unit test cannot exercise (Node has
|
|
7
|
+
// no createImageBitmap; unit test only covers the structured-failure arms).
|
|
8
|
+
//
|
|
9
|
+
// Coverage:
|
|
10
|
+
// - AC-01: real PNG bytes -> ok(TextureAsset) with all POD fields populated
|
|
11
|
+
// (kind / width / height / format / data / colorSpace / mipmap /
|
|
12
|
+
// mipLevelCount); default (opts omitted) drives srgb + mipmap=true, and a
|
|
13
|
+
// second run with opts.colorSpace='linear' + opts.mipmap=false confirms
|
|
14
|
+
// the two POD fields the opts derive (format + mipLevelCount).
|
|
15
|
+
// - AC-02: the returned POD is accepted by `world.allocSharedRef(
|
|
16
|
+
// 'TextureAsset', pod)` -- the SSOT runtime column-handle path (charter
|
|
17
|
+
// P4 consistent abstraction: the same POD any static loader would
|
|
18
|
+
// produce). The handle is a live u32 whose resolve returns payload
|
|
19
|
+
// identity (structural equivalence, not deep-copy).
|
|
20
|
+
// - AC-03 (regression): running this file alongside the existing browser
|
|
21
|
+
// project confirms the pre-existing static loader path is unaffected
|
|
22
|
+
// (checked by the M3 boundary sweep `pnpm test:browser`, no new arms
|
|
23
|
+
// added here).
|
|
24
|
+
//
|
|
25
|
+
// Fixture: 1x1 red PNG inlined as base64 (67 bytes) -- keeps the whole
|
|
26
|
+
// contract self-contained in this one file so an AI user grep-discovering
|
|
27
|
+
// the test does not need to chase a binary fixture (charter F1 single-page).
|
|
28
|
+
|
|
29
|
+
import { World } from '@forgeax/engine-ecs';
|
|
30
|
+
import type { TextureAsset } from '@forgeax/engine-types';
|
|
31
|
+
import { describe, expect, it } from 'vitest';
|
|
32
|
+
import { decodeImageBytes } from '../decode-image-bytes';
|
|
33
|
+
import { numMipLevels } from '../mipmap-generator';
|
|
34
|
+
|
|
35
|
+
// 1x1 red PNG (RGBA 8-bit). Universally decodable by browser createImageBitmap.
|
|
36
|
+
const PNG_1X1_RED_BASE64 =
|
|
37
|
+
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==';
|
|
38
|
+
|
|
39
|
+
function pngBytes(): Uint8Array {
|
|
40
|
+
const bin = atob(PNG_1X1_RED_BASE64);
|
|
41
|
+
const bytes = new Uint8Array(bin.length);
|
|
42
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
43
|
+
return bytes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('decodeImageBytes browser real-PNG happy path (AC-01)', () => {
|
|
47
|
+
it('AC-01: real PNG bytes -> ok(TextureAsset) with all POD fields populated (default srgb + mipmap)', async () => {
|
|
48
|
+
const result = await decodeImageBytes(pngBytes(), 'image/png');
|
|
49
|
+
expect(result.ok).toBe(true);
|
|
50
|
+
if (!result.ok) return;
|
|
51
|
+
const pod = result.value;
|
|
52
|
+
expect(pod.kind).toBe('texture');
|
|
53
|
+
expect(pod.width).toBeGreaterThan(0);
|
|
54
|
+
expect(pod.height).toBeGreaterThan(0);
|
|
55
|
+
// RGBA8 tight-packed: width * height * 4 bytes.
|
|
56
|
+
expect(pod.data.length).toBe(pod.width * pod.height * 4);
|
|
57
|
+
expect(pod.format).toBe('rgba8unorm-srgb');
|
|
58
|
+
expect(pod.colorSpace).toBe('srgb');
|
|
59
|
+
expect(pod.mipmap).toBe(true);
|
|
60
|
+
expect(pod.mipLevelCount).toBe(numMipLevels({ width: pod.width, height: pod.height }));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('AC-01: opts.colorSpace="linear" + opts.mipmap=false narrows format + mipLevelCount', async () => {
|
|
64
|
+
const result = await decodeImageBytes(pngBytes(), 'image/png', {
|
|
65
|
+
colorSpace: 'linear',
|
|
66
|
+
mipmap: false,
|
|
67
|
+
});
|
|
68
|
+
expect(result.ok).toBe(true);
|
|
69
|
+
if (!result.ok) return;
|
|
70
|
+
const pod = result.value;
|
|
71
|
+
expect(pod.format).toBe('rgba8unorm');
|
|
72
|
+
expect(pod.colorSpace).toBe('linear');
|
|
73
|
+
expect(pod.mipmap).toBe(false);
|
|
74
|
+
expect(pod.mipLevelCount).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('decodeImageBytes browser structural equivalence with static loader path (AC-02)', () => {
|
|
79
|
+
it('AC-02: POD is accepted by world.allocSharedRef and resolves back to the same identity', async () => {
|
|
80
|
+
const result = await decodeImageBytes(pngBytes(), 'image/png');
|
|
81
|
+
expect(result.ok).toBe(true);
|
|
82
|
+
if (!result.ok) return;
|
|
83
|
+
const pod = result.value;
|
|
84
|
+
|
|
85
|
+
const world = new World();
|
|
86
|
+
const handle = world.allocSharedRef<'TextureAsset', TextureAsset>('TextureAsset', pod);
|
|
87
|
+
// Handle is a branded u32 -- must be a finite non-negative integer.
|
|
88
|
+
expect(typeof handle).toBe('number');
|
|
89
|
+
expect(Number.isFinite(handle as unknown as number)).toBe(true);
|
|
90
|
+
expect(handle as unknown as number).toBeGreaterThan(0);
|
|
91
|
+
|
|
92
|
+
// Structural equivalence: the shared-ref store round-trips the SAME POD
|
|
93
|
+
// reference (identity preserved, no deep-copy) -- what any static
|
|
94
|
+
// texture loader path produces after allocSharedRef.
|
|
95
|
+
const resolved = world.sharedRefs.resolve<'TextureAsset', TextureAsset>(handle);
|
|
96
|
+
expect(resolved.ok).toBe(true);
|
|
97
|
+
if (!resolved.ok) return;
|
|
98
|
+
expect(resolved.value).toBe(pod);
|
|
99
|
+
expect(resolved.value.kind).toBe('texture');
|
|
100
|
+
expect(resolved.value.format).toBe('rgba8unorm-srgb');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- decodeImageBytes type-only contract test
|
|
2
|
+
// (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M1 / m1-1).
|
|
3
|
+
//
|
|
4
|
+
// AC-05 + AC-06 compile-time narrowing anchor. The vitest typecheck project
|
|
5
|
+
// runs `.test-d.ts` files so any drift in the exported signature / Result
|
|
6
|
+
// narrowing / ImageErrorDetail per-code shape trips CI (charter P4 explicit
|
|
7
|
+
// failure -- typechecker acts as the reviewer).
|
|
8
|
+
//
|
|
9
|
+
// The SSOT `ImageError` type correlates the envelope `.code` with its
|
|
10
|
+
// per-code `.detail` shape, so consumers switch on one discriminant.
|
|
11
|
+
|
|
12
|
+
import type { ImageError, Result, TextureAsset } from '@forgeax/engine-types';
|
|
13
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
14
|
+
import { decodeImageBytes } from '../index';
|
|
15
|
+
|
|
16
|
+
describe('decodeImageBytes signature (AC-05 / AC-06 / opts compat)', () => {
|
|
17
|
+
it('returns Promise<Result<TextureAsset, ImageError>>', () => {
|
|
18
|
+
expectTypeOf(decodeImageBytes).returns.toEqualTypeOf<
|
|
19
|
+
Promise<Result<TextureAsset, ImageError>>
|
|
20
|
+
>();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('AC-05: `if (r.ok)` narrows r.value to TextureAsset, else r.error to ImageError', async () => {
|
|
24
|
+
const r = await decodeImageBytes(new Uint8Array(), 'image/png');
|
|
25
|
+
if (r.ok) {
|
|
26
|
+
expectTypeOf(r.value).toEqualTypeOf<TextureAsset>();
|
|
27
|
+
} else {
|
|
28
|
+
expectTypeOf(r.error).toEqualTypeOf<ImageError>();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('AC-06: `switch (err.code)` narrows err.detail per-code (exhaustive)', async () => {
|
|
33
|
+
const r = await decodeImageBytes(new Uint8Array(), 'image/png');
|
|
34
|
+
if (r.ok) return;
|
|
35
|
+
// Exhaustive over the correlated ImageError union so a future minor-add
|
|
36
|
+
// to ImageErrorCode fails compilation here until this file catches up.
|
|
37
|
+
switch (r.error.code) {
|
|
38
|
+
case 'image-decode-failed': {
|
|
39
|
+
expectTypeOf(r.error.detail.reason).toBeString();
|
|
40
|
+
expectTypeOf(r.error.detail.path).toEqualTypeOf<string | undefined>();
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'image-format-unsupported': {
|
|
44
|
+
expectTypeOf(r.error.detail.actualMime).toBeString();
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case 'image-dimension-out-of-bounds': {
|
|
48
|
+
expectTypeOf(r.error.detail.requested.width).toBeNumber();
|
|
49
|
+
expectTypeOf(r.error.detail.limit).toBeNumber();
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'image-meta-missing': {
|
|
53
|
+
expectTypeOf(r.error.detail.sourcePath).toBeString();
|
|
54
|
+
expectTypeOf(r.error.detail.expectedSidecarPath).toBeString();
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case 'image-hdr-decode-failed': {
|
|
58
|
+
expectTypeOf(r.error.detail.reason).toBeString();
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case 'atlas-empty-input': {
|
|
62
|
+
expectTypeOf(r.error.detail.receivedCount).toBeNumber();
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case 'atlas-size-exceeded': {
|
|
66
|
+
expectTypeOf(r.error.detail.maxAtlasSize).toBeNumber();
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case 'atlas-region-mismatch': {
|
|
70
|
+
expectTypeOf(r.error.detail.atlasPixels).toBeNumber();
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('opts compat: all optional, partial, and full forms compile', async () => {
|
|
77
|
+
await decodeImageBytes(new Uint8Array(), 'image/png');
|
|
78
|
+
await decodeImageBytes(new Uint8Array(), 'image/png', { colorSpace: 'linear' });
|
|
79
|
+
await decodeImageBytes(new Uint8Array(), 'image/png', { mipmap: false });
|
|
80
|
+
await decodeImageBytes(new Uint8Array(), 'image/png', {
|
|
81
|
+
colorSpace: 'srgb',
|
|
82
|
+
mipmap: true,
|
|
83
|
+
});
|
|
84
|
+
await decodeImageBytes(new ArrayBuffer(0), 'image/png');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('AC-05 negative: `if (!r.ok)` MUST NOT let r.value type-check (@ts-expect-error)', async () => {
|
|
88
|
+
const r = await decodeImageBytes(new Uint8Array(), 'image/png');
|
|
89
|
+
if (!r.ok) {
|
|
90
|
+
// @ts-expect-error -- on the err branch `r` is `ResultErr<ImageError>`
|
|
91
|
+
// and has no `.value` property; if this line ever compiles, the
|
|
92
|
+
// Result narrowing broke.
|
|
93
|
+
const _v: TextureAsset = r.value;
|
|
94
|
+
void _v;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('AC-06 negative: `reason` is not on the image-format-unsupported detail arm (@ts-expect-error)', async () => {
|
|
99
|
+
const r = await decodeImageBytes(new Uint8Array(), 'image/png');
|
|
100
|
+
if (r.ok) return;
|
|
101
|
+
if (r.error.detail.code === 'image-format-unsupported') {
|
|
102
|
+
// @ts-expect-error -- `reason` belongs to the image-decode-failed arm;
|
|
103
|
+
// it must NOT be reachable on the image-format-unsupported arm.
|
|
104
|
+
const _r: string = r.error.detail.reason;
|
|
105
|
+
void _r;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- decodeImageBytes unit test skeleton
|
|
2
|
+
// (tweak-20260714-runtime-image-bytes-decoder-add-decodeimagebytes M1 / m1-1).
|
|
3
|
+
//
|
|
4
|
+
// Red skeleton at M1: the M1 stub throws 'not-implemented', so any behavioural
|
|
5
|
+
// assertion here is red until M2 fills the function body. Two forever-tests
|
|
6
|
+
// still hold at M1:
|
|
7
|
+
// - AC-07 (a): the symbol is importable from the @forgeax/engine-assets-
|
|
8
|
+
// runtime main barrel (grep-discoverable single-hop entry point).
|
|
9
|
+
// - AC-07 (b): the source file leads with a `/**` TSDoc block on the
|
|
10
|
+
// exported function (charter F1: single-page contract).
|
|
11
|
+
//
|
|
12
|
+
// Behavioural AC-04 / AC-08 / AC-09 arms are declared here in the exhaustive
|
|
13
|
+
// switch skeleton so the M1 red matches the M2 green surface without churn
|
|
14
|
+
// (charter P3 explicit failure -- red arms declare the target shape).
|
|
15
|
+
|
|
16
|
+
/// <reference types="node" />
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from 'node:fs';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { describe, expect, it } from 'vitest';
|
|
22
|
+
import { decodeImageBytes } from '../index';
|
|
23
|
+
|
|
24
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
|
|
26
|
+
describe('decodeImageBytes barrel + TSDoc contract (AC-07)', () => {
|
|
27
|
+
it('is exported from the @forgeax/engine-assets-runtime main barrel', () => {
|
|
28
|
+
expect(typeof decodeImageBytes).toBe('function');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('carries a leading `/**` TSDoc block on the exported function', () => {
|
|
32
|
+
const src = readFileSync(join(here, '..', 'decode-image-bytes.ts'), 'utf8');
|
|
33
|
+
// The exported function must be immediately preceded by a `*/` closing
|
|
34
|
+
// its TSDoc block; the `/**` opener must appear before that. This is a
|
|
35
|
+
// structural (not textual) check on the AI user's IDE-hover surface.
|
|
36
|
+
const exportIdx = src.indexOf('export async function decodeImageBytes(');
|
|
37
|
+
expect(exportIdx).toBeGreaterThan(0);
|
|
38
|
+
const preface = src.slice(0, exportIdx);
|
|
39
|
+
const jsdocClose = preface.lastIndexOf('*/');
|
|
40
|
+
const jsdocOpen = preface.lastIndexOf('/**');
|
|
41
|
+
expect(jsdocOpen).toBeGreaterThan(0);
|
|
42
|
+
expect(jsdocClose).toBeGreaterThan(jsdocOpen);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('decodeImageBytes structured failure surface (AC-04 / AC-08 / AC-09)', () => {
|
|
47
|
+
it('AC-08: unsupported mime -> err with image-format-unsupported', async () => {
|
|
48
|
+
const result = await decodeImageBytes(new Uint8Array([1]), 'image/gif');
|
|
49
|
+
expect(result.ok).toBe(false);
|
|
50
|
+
if (result.ok) return;
|
|
51
|
+
const expected =
|
|
52
|
+
"mime is one of ['image/png', 'image/jpeg', 'image/x-tga']; texture format <-> colorSpace family agrees";
|
|
53
|
+
const hint =
|
|
54
|
+
'supports PNG / JPG / TGA true-color sources; convert unsupported formats with: magick convert <input> <output>.png; check importSettings.colorSpace consistency with format family if formatColorSpaceConflict present';
|
|
55
|
+
expect(result.error.code).toBe('image-format-unsupported');
|
|
56
|
+
expect(result.error.detail).toEqual({
|
|
57
|
+
code: 'image-format-unsupported',
|
|
58
|
+
actualMime: 'image/gif',
|
|
59
|
+
});
|
|
60
|
+
expect(result.error.expected).toBe(expected);
|
|
61
|
+
expect(result.error.hint).toBe(hint);
|
|
62
|
+
expect(result.error.name).toBe('ImageError');
|
|
63
|
+
expect(result.error.message).toBe(
|
|
64
|
+
`[ImageError image-format-unsupported] expected: ${expected}; hint: ${hint}`,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('AC-09: corrupt bytes with declared mime -> err with image-decode-failed', async () => {
|
|
69
|
+
// Node env has no createImageBitmap; the decoder returns the structured
|
|
70
|
+
// env-missing failure through the same `image-decode-failed` arm as a
|
|
71
|
+
// real decoder rejection in the browser (charter P3 explicit failure --
|
|
72
|
+
// AI users switch on one arm regardless of environment).
|
|
73
|
+
const result = await decodeImageBytes(new Uint8Array([0, 0, 0]), 'image/png');
|
|
74
|
+
expect(result.ok).toBe(false);
|
|
75
|
+
if (result.ok) return;
|
|
76
|
+
expect(result.error.code).toBe('image-decode-failed');
|
|
77
|
+
if (result.error.detail.code === 'image-decode-failed') {
|
|
78
|
+
expect(typeof result.error.detail.reason).toBe('string');
|
|
79
|
+
expect(result.error.detail.reason.length).toBeGreaterThan(0);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('runtime-only bytes remain orthogonal to static pack loading', () => {
|
|
85
|
+
it('keeps the bytes decoder available without accepting static catalog rows', async () => {
|
|
86
|
+
const result = await decodeImageBytes(new Uint8Array([0]), 'image/gif');
|
|
87
|
+
expect(result.ok).toBe(false);
|
|
88
|
+
if (!result.ok) expect(result.error.code).toBe('image-format-unsupported');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
|
-
import type { decodeImageBytes } from '@forgeax/engine-image';
|
|
5
4
|
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
5
|
+
import type { decodeImageBytes } from '../index';
|
|
6
6
|
|
|
7
7
|
type ExpectedSupportedMime = 'image/png' | 'image/jpeg';
|
|
8
8
|
type PublicMime = Parameters<typeof decodeImageBytes>[1];
|
|
9
9
|
|
|
10
|
-
const decoderSource = readFileSync(
|
|
11
|
-
new URL('../../../image/src/runtime/decode-image-bytes.ts', import.meta.url),
|
|
12
|
-
'utf8',
|
|
13
|
-
);
|
|
10
|
+
const decoderSource = readFileSync(new URL('../decode-image-bytes.ts', import.meta.url), 'utf8');
|
|
14
11
|
|
|
15
12
|
describe('decodeImageBytes supported MIME owner', () => {
|
|
16
13
|
it('keeps the supported MIME vocabulary and public boundary exact', () => {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- DynamicTextureStore coverage (fix issue #709).
|
|
2
|
+
// Transient per-frame video texture store: configureGpuDevice / uploadFrame
|
|
3
|
+
// (allocate-once + resize-realloc + copy) / getView / destroyAll, driven by a
|
|
4
|
+
// small in-memory device stub (no GPU).
|
|
5
|
+
|
|
6
|
+
import type { Result, RhiError, Texture, TextureView } from '@forgeax/engine-rhi';
|
|
7
|
+
import { ok } from '@forgeax/engine-rhi';
|
|
8
|
+
import type { Handle } from '@forgeax/engine-types';
|
|
9
|
+
import { toShared } from '@forgeax/engine-types';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
type CopyExternalImageSource,
|
|
13
|
+
type DynamicTextureDevice,
|
|
14
|
+
DynamicTextureStore,
|
|
15
|
+
} from '../dynamic-texture-store';
|
|
16
|
+
|
|
17
|
+
interface Counters {
|
|
18
|
+
created: number;
|
|
19
|
+
destroyed: number;
|
|
20
|
+
copies: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function makeDevice(counters: Counters): DynamicTextureDevice {
|
|
24
|
+
let nextTex = 0;
|
|
25
|
+
return {
|
|
26
|
+
createTexture: () => {
|
|
27
|
+
counters.created++;
|
|
28
|
+
return ok({ tag: `tex-${nextTex++}` } as unknown as Texture);
|
|
29
|
+
},
|
|
30
|
+
createTextureView: (tex: Texture) =>
|
|
31
|
+
ok({ tag: `view-of-${(tex as unknown as { tag: string }).tag}` } as unknown as TextureView),
|
|
32
|
+
destroyTexture: () => {
|
|
33
|
+
counters.destroyed++;
|
|
34
|
+
return ok(undefined) as Result<void, RhiError>;
|
|
35
|
+
},
|
|
36
|
+
queue: {
|
|
37
|
+
copyExternalImageToTexture: () => {
|
|
38
|
+
counters.copies++;
|
|
39
|
+
return ok(undefined) as Result<void, RhiError>;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const CLIP = toShared<'VideoAsset'>(2000) as Handle<'VideoAsset', 'shared'>;
|
|
46
|
+
const SOURCE = {} as CopyExternalImageSource;
|
|
47
|
+
|
|
48
|
+
describe('DynamicTextureStore', () => {
|
|
49
|
+
it('returns undefined before a device is wired', () => {
|
|
50
|
+
const store = new DynamicTextureStore();
|
|
51
|
+
expect(store.uploadFrame(CLIP, SOURCE, 16, 16)).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('returns undefined for a non-positive source size', () => {
|
|
55
|
+
const store = new DynamicTextureStore();
|
|
56
|
+
store.configureGpuDevice(makeDevice({ created: 0, destroyed: 0, copies: 0 }));
|
|
57
|
+
expect(store.uploadFrame(CLIP, SOURCE, 0, 16)).toBeUndefined();
|
|
58
|
+
expect(store.uploadFrame(CLIP, SOURCE, 16, -1)).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('allocates once, then re-uploads in place for a steady-size clip', () => {
|
|
62
|
+
const counters = { created: 0, destroyed: 0, copies: 0 };
|
|
63
|
+
const store = new DynamicTextureStore();
|
|
64
|
+
store.configureGpuDevice(makeDevice(counters));
|
|
65
|
+
|
|
66
|
+
const a = store.uploadFrame(CLIP, SOURCE, 32, 32);
|
|
67
|
+
const b = store.uploadFrame(CLIP, SOURCE, 32, 32);
|
|
68
|
+
expect(a?.ok).toBe(true);
|
|
69
|
+
expect(b?.ok).toBe(true);
|
|
70
|
+
expect(counters.created).toBe(1); // allocate-once
|
|
71
|
+
expect(counters.copies).toBe(2); // re-uploaded each frame
|
|
72
|
+
if (a?.ok && b?.ok) expect(a.value).toBe(b.value); // same view reused
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('reallocates (destroys old, creates new) when the source size changes', () => {
|
|
76
|
+
const counters = { created: 0, destroyed: 0, copies: 0 };
|
|
77
|
+
const store = new DynamicTextureStore();
|
|
78
|
+
store.configureGpuDevice(makeDevice(counters));
|
|
79
|
+
store.uploadFrame(CLIP, SOURCE, 32, 32);
|
|
80
|
+
store.uploadFrame(CLIP, SOURCE, 64, 64);
|
|
81
|
+
expect(counters.created).toBe(2);
|
|
82
|
+
expect(counters.destroyed).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('getView returns the current view after an upload, undefined before', () => {
|
|
86
|
+
const store = new DynamicTextureStore();
|
|
87
|
+
expect(store.getView(CLIP)).toBeUndefined();
|
|
88
|
+
store.configureGpuDevice(makeDevice({ created: 0, destroyed: 0, copies: 0 }));
|
|
89
|
+
store.uploadFrame(CLIP, SOURCE, 8, 8);
|
|
90
|
+
expect(store.getView(CLIP)).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('destroyAll destroys every transient texture and clears the map', () => {
|
|
94
|
+
const counters = { created: 0, destroyed: 0, copies: 0 };
|
|
95
|
+
const store = new DynamicTextureStore();
|
|
96
|
+
store.configureGpuDevice(makeDevice(counters));
|
|
97
|
+
store.uploadFrame(CLIP, SOURCE, 8, 8);
|
|
98
|
+
store.uploadFrame(toShared<'VideoAsset'>(2001) as Handle<'VideoAsset', 'shared'>, SOURCE, 8, 8);
|
|
99
|
+
store.destroyAll();
|
|
100
|
+
expect(counters.destroyed).toBe(2);
|
|
101
|
+
expect(store.getView(CLIP)).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('surfaces a structured error when texture allocation fails', () => {
|
|
105
|
+
const store = new DynamicTextureStore();
|
|
106
|
+
store.configureGpuDevice({
|
|
107
|
+
createTexture: () => ({ ok: false, error: { code: 'rhi-not-available' } }) as never,
|
|
108
|
+
createTextureView: () => ok({} as unknown as TextureView),
|
|
109
|
+
destroyTexture: () => ok(undefined) as Result<void, RhiError>,
|
|
110
|
+
queue: { copyExternalImageToTexture: () => ok(undefined) as Result<void, RhiError> },
|
|
111
|
+
});
|
|
112
|
+
const res = store.uploadFrame(CLIP, SOURCE, 8, 8);
|
|
113
|
+
expect(res?.ok).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- asset-cluster error class coverage
|
|
2
|
+
// (fix issue #709). Exercises every constructor branch + .code / .expected /
|
|
3
|
+
// .hint / .detail four-field surface of the closed AssetRuntimeError union.
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
MaterialResolvedEmptyPassesError,
|
|
8
|
+
MeshSsboCapacityExceededError,
|
|
9
|
+
MeshSsboCeilingReachedError,
|
|
10
|
+
SceneCollectAssetGuidUnresolvedError,
|
|
11
|
+
SceneCollectEntityRefOutOfClosureError,
|
|
12
|
+
} from '../errors/asset';
|
|
13
|
+
|
|
14
|
+
describe('MaterialResolvedEmptyPassesError', () => {
|
|
15
|
+
it('missing-parent reason carries the parent handle in expected/hint/detail', () => {
|
|
16
|
+
const e = new MaterialResolvedEmptyPassesError('mat-guid', 'missing-parent', 42);
|
|
17
|
+
expect(e.code).toBe('material-resolved-empty-passes');
|
|
18
|
+
expect(e.name).toBe('MaterialResolvedEmptyPassesError');
|
|
19
|
+
expect(e.detail).toEqual({
|
|
20
|
+
materialGuid: 'mat-guid',
|
|
21
|
+
reason: 'missing-parent',
|
|
22
|
+
missingParentHandle: 42,
|
|
23
|
+
});
|
|
24
|
+
expect(e.expected).toContain('42');
|
|
25
|
+
expect(e.hint).toContain('mat-guid');
|
|
26
|
+
expect(e.message).toContain('42');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('no-pass-in-chain reason omits missingParentHandle from detail', () => {
|
|
30
|
+
const e = new MaterialResolvedEmptyPassesError('mat-guid', 'no-pass-in-chain');
|
|
31
|
+
expect(e.detail).toEqual({ materialGuid: 'mat-guid', reason: 'no-pass-in-chain' });
|
|
32
|
+
expect('missingParentHandle' in e.detail).toBe(false);
|
|
33
|
+
expect(e.expected).toContain('at least one material');
|
|
34
|
+
expect(e.message).toContain('zero passes');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('missing-parent with undefined handle still omits it from detail', () => {
|
|
38
|
+
const e = new MaterialResolvedEmptyPassesError('g', 'missing-parent', undefined);
|
|
39
|
+
expect('missingParentHandle' in e.detail).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('MeshSsboCapacityExceededError', () => {
|
|
44
|
+
it('surfaces requested / capacity / ceiling across all four fields', () => {
|
|
45
|
+
const e = new MeshSsboCapacityExceededError(100, 64, 1024);
|
|
46
|
+
expect(e.code).toBe('mesh-ssbo-capacity-exceeded');
|
|
47
|
+
expect(e.name).toBe('MeshSsboCapacityExceededError');
|
|
48
|
+
expect(e.detail).toEqual({ requested: 100, capacity: 64, ceiling: 1024 });
|
|
49
|
+
expect(e.expected).toContain('100');
|
|
50
|
+
expect(e.hint).toContain('100');
|
|
51
|
+
expect(e.message).toContain('100');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('MeshSsboCeilingReachedError', () => {
|
|
56
|
+
it('surfaces requested / capacity / ceiling across all four fields', () => {
|
|
57
|
+
const e = new MeshSsboCeilingReachedError(200, 128, 2048);
|
|
58
|
+
expect(e.code).toBe('mesh-ssbo-ceiling-reached');
|
|
59
|
+
expect(e.name).toBe('MeshSsboCeilingReachedError');
|
|
60
|
+
expect(e.detail).toEqual({ requested: 200, capacity: 128, ceiling: 2048 });
|
|
61
|
+
expect(e.expected).toContain('2048');
|
|
62
|
+
expect(e.hint).toContain('2048');
|
|
63
|
+
expect(e.message).toContain('200');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('SceneCollectEntityRefOutOfClosureError', () => {
|
|
68
|
+
it('carries entity / field / target and composes an expected+hint message', () => {
|
|
69
|
+
const e = new SceneCollectEntityRefOutOfClosureError(3, 'target', 99);
|
|
70
|
+
expect(e.code).toBe('scene-collect-entity-ref-out-of-closure');
|
|
71
|
+
expect(e.name).toBe('SceneCollectEntityRefOutOfClosureError');
|
|
72
|
+
expect(e.detail).toEqual({ entity: 3, field: 'target', target: 99 });
|
|
73
|
+
expect(e.message).toBe(`${e.expected} — ${e.hint}`);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('SceneCollectAssetGuidUnresolvedError', () => {
|
|
78
|
+
it('numeric ref (collect path) populates detail.handle only', () => {
|
|
79
|
+
const e = new SceneCollectAssetGuidUnresolvedError('meshHandle', 7);
|
|
80
|
+
expect(e.code).toBe('scene-collect-asset-guid-unresolved');
|
|
81
|
+
expect(e.detail).toEqual({ field: 'meshHandle', handle: 7 });
|
|
82
|
+
expect('guid' in e.detail).toBe(false);
|
|
83
|
+
expect(e.expected).toContain('handle 7');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('string ref (serialize path) populates detail.guid only', () => {
|
|
87
|
+
const e = new SceneCollectAssetGuidUnresolvedError('meshHandle', 'abc-guid');
|
|
88
|
+
expect(e.detail).toEqual({ field: 'meshHandle', guid: 'abc-guid' });
|
|
89
|
+
expect('handle' in e.detail).toBe(false);
|
|
90
|
+
expect(e.expected).toContain("guid 'abc-guid'");
|
|
91
|
+
});
|
|
92
|
+
});
|