@forgeax/engine-assets-runtime 0.0.0-dev.8d955ade1c79
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 +491 -0
- package/dist/.tsbuildinfo +1 -0
- 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__/decode-image-mime-owner.test.d.ts +2 -0
- package/dist/__tests__/decode-image-mime-owner.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-kind.d.ts +4 -0
- package/dist/asset-kind.d.ts.map +1 -0
- package/dist/asset-registry.d.ts +554 -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 +24 -0
- package/dist/catalog-source.d.ts.map +1 -0
- 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 +143 -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 +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +8359 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/artifact-cache.d.ts +17 -0
- package/dist/internal/artifact-cache.d.ts.map +1 -0
- package/dist/internal/asset-graph.d.ts +70 -0
- package/dist/internal/asset-graph.d.ts.map +1 -0
- package/dist/internal/catalog-session.d.ts +60 -0
- package/dist/internal/catalog-session.d.ts.map +1 -0
- package/dist/internal/decoder-registry.d.ts +19 -0
- package/dist/internal/decoder-registry.d.ts.map +1 -0
- package/dist/internal/immutable-payload.d.ts +9 -0
- package/dist/internal/immutable-payload.d.ts.map +1 -0
- package/dist/internal/load-asset.d.ts +31 -0
- package/dist/internal/load-asset.d.ts.map +1 -0
- package/dist/internal/pack-reader.d.ts +18 -0
- package/dist/internal/pack-reader.d.ts.map +1 -0
- package/dist/internal/validate-runtime-row.d.ts +7 -0
- package/dist/internal/validate-runtime-row.d.ts.map +1 -0
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.mjs +329 -0
- package/dist/internal.mjs.map +1 -0
- 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 +76 -0
- 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.unit.test.ts +14 -0
- 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 +38 -0
- 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 +508 -0
- package/src/__tests__/material-stale-generation.unit.test.ts +147 -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 +27 -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-kind.ts +6 -0
- package/src/asset-registry.ts +2045 -0
- package/src/builtin-asset-registry.ts +164 -0
- package/src/catalog-source.ts +89 -0
- 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 +186 -0
- package/src/image-error.ts +45 -0
- package/src/index.ts +189 -0
- package/src/internal/artifact-cache.ts +65 -0
- package/src/internal/asset-graph.ts +420 -0
- package/src/internal/catalog-session.ts +345 -0
- package/src/internal/decoder-registry.ts +175 -0
- package/src/internal/immutable-payload.ts +20 -0
- package/src/internal/load-asset.ts +254 -0
- package/src/internal/pack-reader.ts +183 -0
- package/src/internal/validate-runtime-row.ts +51 -0
- package/src/internal.ts +6 -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 +385 -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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { AssetRegistry } from '../asset-registry';
|
|
3
|
+
|
|
4
|
+
const GUID = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc';
|
|
5
|
+
|
|
6
|
+
describe('asset registry cache cleanup', () => {
|
|
7
|
+
it('clears failed package and in-flight state so a retry can succeed', async () => {
|
|
8
|
+
const registry = new AssetRegistry({} as never);
|
|
9
|
+
registry.loaders.register({
|
|
10
|
+
kind: 'host-blob',
|
|
11
|
+
load: (payload) => ({ kind: 'host-blob', ...payload }),
|
|
12
|
+
});
|
|
13
|
+
registry.configurePackIndex('/pack-index.json');
|
|
14
|
+
let attempts = 0;
|
|
15
|
+
vi.stubGlobal(
|
|
16
|
+
'fetch',
|
|
17
|
+
vi.fn(async (url: string) => {
|
|
18
|
+
if (url.endsWith('pack-index.json')) {
|
|
19
|
+
return new Response(
|
|
20
|
+
JSON.stringify([
|
|
21
|
+
{
|
|
22
|
+
guid: GUID,
|
|
23
|
+
packageUrl: '/asset.pack.json',
|
|
24
|
+
kind: 'host-blob',
|
|
25
|
+
sourcePath: 'asset',
|
|
26
|
+
},
|
|
27
|
+
]),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
attempts += 1;
|
|
31
|
+
if (attempts === 1) return new Response('missing', { status: 404 });
|
|
32
|
+
return new Response(
|
|
33
|
+
JSON.stringify({
|
|
34
|
+
schemaVersion: '2.0.0',
|
|
35
|
+
kind: 'internal-text-package',
|
|
36
|
+
assets: [
|
|
37
|
+
{ guid: GUID, kind: 'host-blob', payload: { value: 1 }, refs: [], artifacts: {} },
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const first = await registry.loadByGuid(registry.parseGuid(GUID));
|
|
45
|
+
expect(first.ok).toBe(false);
|
|
46
|
+
const second = await registry.loadByGuid(registry.parseGuid(GUID));
|
|
47
|
+
expect(second.ok).toBe(true);
|
|
48
|
+
expect(attempts).toBe(2);
|
|
49
|
+
expect(registry.lookup(GUID)).toBeDefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('does not expose an unresolved package as a public fast-path hit', async () => {
|
|
53
|
+
const registry = new AssetRegistry({} as never);
|
|
54
|
+
registry.loaders.register({
|
|
55
|
+
kind: 'host-blob',
|
|
56
|
+
load: (payload) => ({ kind: 'host-blob', ...payload }),
|
|
57
|
+
});
|
|
58
|
+
registry.configurePackIndex('/pack-index.json');
|
|
59
|
+
let release: (() => void) | undefined;
|
|
60
|
+
const pending = new Promise<void>((resolve) => {
|
|
61
|
+
release = resolve;
|
|
62
|
+
});
|
|
63
|
+
vi.stubGlobal(
|
|
64
|
+
'fetch',
|
|
65
|
+
vi.fn(async (url: string) => {
|
|
66
|
+
if (url.endsWith('pack-index.json')) {
|
|
67
|
+
return new Response(
|
|
68
|
+
JSON.stringify([
|
|
69
|
+
{
|
|
70
|
+
guid: GUID,
|
|
71
|
+
packageUrl: '/asset.pack.json',
|
|
72
|
+
kind: 'host-blob',
|
|
73
|
+
sourcePath: 'asset',
|
|
74
|
+
},
|
|
75
|
+
]),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
await pending;
|
|
79
|
+
return new Response(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
schemaVersion: '2.0.0',
|
|
82
|
+
kind: 'internal-text-package',
|
|
83
|
+
assets: [
|
|
84
|
+
{ guid: GUID, kind: 'host-blob', payload: { value: 1 }, refs: [], artifacts: {} },
|
|
85
|
+
],
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const loading = registry.loadByGuid(registry.parseGuid(GUID));
|
|
92
|
+
expect(registry.lookup(GUID)).toBeUndefined();
|
|
93
|
+
release?.();
|
|
94
|
+
await loading;
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
// @forgeax/engine-assets-runtime -- AssetRegistry coverage (fix issue #709).
|
|
2
|
+
// Drives the GUID -> payload catalogue through its public surface: parseGuid,
|
|
3
|
+
// catalog + register-time validation, lookup, loadByGuid (catalogued fast-path
|
|
4
|
+
// + not-imported miss), resolveName / packageOf / rename, invalidate /
|
|
5
|
+
// invalidateAll, inspect, listCatalog. Uses a mock ShaderRegistry (no GPU).
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
authoringCapabilityForAssetKind,
|
|
9
|
+
createStandaloneRuntimeAssetBinding,
|
|
10
|
+
type MaterialAsset,
|
|
11
|
+
type MeshAsset,
|
|
12
|
+
type TilesetAsset,
|
|
13
|
+
} from '@forgeax/engine-types';
|
|
14
|
+
import { describe, expect, expectTypeOf, it, vi } from 'vitest';
|
|
15
|
+
import { AssetRegistry } from '../asset-registry';
|
|
16
|
+
import { type CatalogRecord, fetchPackIndex } from '../registry/catalog';
|
|
17
|
+
import { registerPackagesFromIndex } from '../registry/load-by-guid';
|
|
18
|
+
|
|
19
|
+
const GUID_A = '11111111-1111-4111-8111-111111111111';
|
|
20
|
+
const GUID_B = '22222222-2222-4222-8222-222222222222';
|
|
21
|
+
|
|
22
|
+
function makeMockShaderRegistry() {
|
|
23
|
+
return {
|
|
24
|
+
getMaterialShaderManifest: vi.fn().mockReturnValue(undefined),
|
|
25
|
+
findMaterialArtifact: vi.fn().mockReturnValue({ ok: false, error: new Error('mock') }),
|
|
26
|
+
getPipeline: vi.fn().mockReturnValue(undefined),
|
|
27
|
+
installMaterialArtifact: vi.fn(),
|
|
28
|
+
inspect: vi.fn().mockReturnValue({ materialShaders: [] }),
|
|
29
|
+
} as unknown as import('@forgeax/engine-shader').ShaderRegistry;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeRegistry(): AssetRegistry {
|
|
33
|
+
return new AssetRegistry(makeMockShaderRegistry());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function meshPayload(): MeshAsset {
|
|
37
|
+
return {
|
|
38
|
+
kind: 'mesh',
|
|
39
|
+
vertices: new Float32Array(36), // 3 verts * 12
|
|
40
|
+
indices: Uint16Array.of(0, 1, 2),
|
|
41
|
+
attributes: { position: Float32Array.of(0, 0, 0, 1, 0, 0, 0, 1, 0) },
|
|
42
|
+
submeshes: [
|
|
43
|
+
{ indexOffset: 0, indexCount: 3, vertexCount: 3, topology: 'triangle-list', materialSlot: 0 },
|
|
44
|
+
],
|
|
45
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
46
|
+
} as MeshAsset;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('parseGuid', () => {
|
|
50
|
+
it('parses a valid dash-form GUID', () => {
|
|
51
|
+
const reg = makeRegistry();
|
|
52
|
+
expect(() => reg.parseGuid(GUID_A)).not.toThrow();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('throws an AssetError on a malformed GUID', () => {
|
|
56
|
+
const reg = makeRegistry();
|
|
57
|
+
expect(() => reg.parseGuid('not-a-guid')).toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('catalog + lookup', () => {
|
|
62
|
+
it('catalogs a mesh payload (computes aabb) and looks it up by GUID', () => {
|
|
63
|
+
const reg = makeRegistry();
|
|
64
|
+
const res = reg.catalog(GUID_A, meshPayload());
|
|
65
|
+
expect(res.ok).toBe(true);
|
|
66
|
+
const got = reg.lookup<MeshAsset>(GUID_A);
|
|
67
|
+
expect(got?.kind).toBe('mesh');
|
|
68
|
+
expect(got?.aabb).toBeInstanceOf(Float32Array);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('lookup returns undefined for an uncatalogued GUID', () => {
|
|
72
|
+
expect(makeRegistry().lookup(GUID_B)).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('rejects an invalid mesh payload at register time (stride mismatch)', () => {
|
|
76
|
+
const reg = makeRegistry();
|
|
77
|
+
const bad = { ...meshPayload(), vertices: new Float32Array(13) };
|
|
78
|
+
const res = reg.catalog(GUID_A, bad);
|
|
79
|
+
expect(res.ok).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('rejects an invalid tileset payload at register time', () => {
|
|
83
|
+
const reg = makeRegistry();
|
|
84
|
+
const bad = {
|
|
85
|
+
kind: 'tileset',
|
|
86
|
+
atlases: [],
|
|
87
|
+
tileWidth: 16,
|
|
88
|
+
tileHeight: 16,
|
|
89
|
+
columns: 1,
|
|
90
|
+
rows: 1,
|
|
91
|
+
regions: [],
|
|
92
|
+
tiles: [],
|
|
93
|
+
} as unknown as TilesetAsset;
|
|
94
|
+
expect(reg.catalog(GUID_A, bad).ok).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('rejects a material with an explicit empty passes[]', () => {
|
|
98
|
+
const reg = makeRegistry();
|
|
99
|
+
const bad = { kind: 'material', passes: [] } as unknown as MaterialAsset;
|
|
100
|
+
expect(reg.catalog(GUID_A, bad).ok).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('loadByGuid', () => {
|
|
105
|
+
it('accepts a static pack-index after a runtime binding keeps dev scope state', async () => {
|
|
106
|
+
const reg = makeRegistry();
|
|
107
|
+
reg.configureRuntimeBinding(createStandaloneRuntimeAssetBinding('game-a'));
|
|
108
|
+
reg.configurePackIndex('/pack-index.json');
|
|
109
|
+
const originalFetch = globalThis.fetch;
|
|
110
|
+
const fetcher = vi.fn(
|
|
111
|
+
async (_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
112
|
+
new Response(
|
|
113
|
+
JSON.stringify([
|
|
114
|
+
{
|
|
115
|
+
guid: GUID_A,
|
|
116
|
+
packageUrl: '/assets/mesh.pack.json',
|
|
117
|
+
kind: 'mesh',
|
|
118
|
+
},
|
|
119
|
+
]),
|
|
120
|
+
),
|
|
121
|
+
);
|
|
122
|
+
vi.stubGlobal('fetch', fetcher);
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const result = await fetchPackIndex(reg);
|
|
126
|
+
|
|
127
|
+
expect(result.ok).toBe(true);
|
|
128
|
+
expect(fetcher).toHaveBeenCalledWith('/pack-index.json', { cache: 'no-store' });
|
|
129
|
+
} finally {
|
|
130
|
+
globalThis.fetch = originalFetch;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns the payload for an already-catalogued GUID (fast path)', async () => {
|
|
135
|
+
const reg = makeRegistry();
|
|
136
|
+
reg.catalog(GUID_A, meshPayload());
|
|
137
|
+
const parsed = reg.parseGuid(GUID_A);
|
|
138
|
+
const res = await reg.loadByGuid<MeshAsset>(parsed);
|
|
139
|
+
expect(res.ok).toBe(true);
|
|
140
|
+
if (!res.ok) return;
|
|
141
|
+
expect(res.value.kind).toBe('mesh');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('fails for an uncatalogued GUID with no pack index configured', async () => {
|
|
145
|
+
const reg = makeRegistry();
|
|
146
|
+
const res = await reg.loadByGuid(reg.parseGuid(GUID_B));
|
|
147
|
+
expect(res.ok).toBe(false);
|
|
148
|
+
if (res.ok) return;
|
|
149
|
+
// No packIndexUrl + no transport: the load misses the catalogue fast-path
|
|
150
|
+
// and surfaces the closed-union asset-not-found (the DDC asset-not-imported
|
|
151
|
+
// arm only fires once a pack index is configured).
|
|
152
|
+
expect((res.error as { code: string }).code).toBe('asset-not-found');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('does not route a static load failure through decodeImageBytes', async () => {
|
|
156
|
+
const reg = makeRegistry();
|
|
157
|
+
const res = await reg.loadByGuid(reg.parseGuid(GUID_B));
|
|
158
|
+
expect(res.ok).toBe(false);
|
|
159
|
+
if (!res.ok) expect((res.error as { code: string }).code).toBe('asset-not-found');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('does not return a payload for a failed cooked projection', async () => {
|
|
163
|
+
const reg = makeRegistry();
|
|
164
|
+
reg.configurePackIndex('/pack-index.json');
|
|
165
|
+
const originalFetch = globalThis.fetch;
|
|
166
|
+
vi.stubGlobal(
|
|
167
|
+
'fetch',
|
|
168
|
+
vi.fn(async (input: RequestInfo | URL) => {
|
|
169
|
+
const url = String(input);
|
|
170
|
+
if (url.endsWith('/pack-index.json')) {
|
|
171
|
+
return new Response(
|
|
172
|
+
JSON.stringify([
|
|
173
|
+
{
|
|
174
|
+
guid: GUID_A,
|
|
175
|
+
packageUrl: '/runtime/failed.pack.json',
|
|
176
|
+
kind: 'mesh',
|
|
177
|
+
sourcePath: 'authoring/mesh.pack.json',
|
|
178
|
+
subject: 'internal-asset',
|
|
179
|
+
execution: 'cooked',
|
|
180
|
+
lifecycle: 'failed',
|
|
181
|
+
projection: {
|
|
182
|
+
subject: 'internal-asset',
|
|
183
|
+
execution: 'cooked',
|
|
184
|
+
lifecycle: 'failed',
|
|
185
|
+
operations: {
|
|
186
|
+
preview: { operation: 'preview', enabled: false },
|
|
187
|
+
save: { operation: 'save', enabled: false },
|
|
188
|
+
rebuild: { operation: 'rebuild', enabled: true },
|
|
189
|
+
sourceOverride: { operation: 'sourceOverride', enabled: false },
|
|
190
|
+
instanceOverride: { operation: 'instanceOverride', enabled: false },
|
|
191
|
+
promote: { operation: 'promote', enabled: false },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
]),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return new Response(
|
|
199
|
+
JSON.stringify({
|
|
200
|
+
schemaVersion: '2.0.0',
|
|
201
|
+
kind: 'internal-text-package',
|
|
202
|
+
assets: [
|
|
203
|
+
{
|
|
204
|
+
guid: GUID_A,
|
|
205
|
+
kind: 'mesh',
|
|
206
|
+
payload: { vertices: new Array(36).fill(0), indices: [0, 1, 2] },
|
|
207
|
+
refs: [],
|
|
208
|
+
artifacts: {},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = await reg.loadByGuid(reg.parseGuid(GUID_A));
|
|
218
|
+
|
|
219
|
+
expect(result.ok).toBe(false);
|
|
220
|
+
if (!result.ok) expect(result.error.code).toBe('asset-not-imported');
|
|
221
|
+
} finally {
|
|
222
|
+
globalThis.fetch = originalFetch;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('refreshCatalog bypasses the browser cache', async () => {
|
|
227
|
+
const reg = makeRegistry();
|
|
228
|
+
reg.configurePackIndex('/pack-index.json');
|
|
229
|
+
const originalFetch = globalThis.fetch;
|
|
230
|
+
const fetcher = vi.fn(
|
|
231
|
+
async (_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
232
|
+
new Response(
|
|
233
|
+
JSON.stringify([
|
|
234
|
+
{
|
|
235
|
+
guid: GUID_A,
|
|
236
|
+
packageUrl: '/runtime/mesh.pack.json',
|
|
237
|
+
kind: 'mesh',
|
|
238
|
+
sourcePath: 'authoring/mesh.glb',
|
|
239
|
+
},
|
|
240
|
+
]),
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
vi.stubGlobal('fetch', fetcher);
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
await expect(reg.refreshCatalog()).resolves.toBe(true);
|
|
247
|
+
expect(fetcher).toHaveBeenCalledWith('/pack-index.json', { cache: 'no-store' });
|
|
248
|
+
} finally {
|
|
249
|
+
globalThis.fetch = originalFetch;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('resolveName / packageOf / rename', () => {
|
|
255
|
+
it('a freshly catalogued inline asset has no package and an empty derived name', () => {
|
|
256
|
+
const reg = makeRegistry();
|
|
257
|
+
reg.catalog(GUID_A, meshPayload());
|
|
258
|
+
expect(reg.packageOf(GUID_A)).toBeNull();
|
|
259
|
+
expect(reg.resolveName(GUID_A)).toBe('');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('packageOf returns undefined for an unregistered GUID', () => {
|
|
263
|
+
expect(makeRegistry().packageOf(GUID_B)).toBeUndefined();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('rename sets a stored name on a no-package asset', () => {
|
|
267
|
+
const reg = makeRegistry();
|
|
268
|
+
reg.catalog(GUID_A, meshPayload());
|
|
269
|
+
const res = reg.rename(GUID_A, 'hero-mesh');
|
|
270
|
+
expect(res.ok).toBe(true);
|
|
271
|
+
expect(reg.resolveName(GUID_A)).toBe('hero-mesh');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('rename errors with asset-not-found for an unregistered GUID', () => {
|
|
275
|
+
const res = makeRegistry().rename(GUID_B, 'x');
|
|
276
|
+
expect(res.ok).toBe(false);
|
|
277
|
+
if (res.ok) return;
|
|
278
|
+
expect(res.error.code).toBe('asset-not-found');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('invalidate / invalidateAll', () => {
|
|
283
|
+
it('invalidate drops a single catalogued asset', () => {
|
|
284
|
+
const reg = makeRegistry();
|
|
285
|
+
reg.catalog(GUID_A, meshPayload());
|
|
286
|
+
reg.invalidate(GUID_A);
|
|
287
|
+
expect(reg.lookup(GUID_A)).toBeUndefined();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('invalidate preserves a derived package name when a reload changes the DDC path', () => {
|
|
291
|
+
const reg = makeRegistry();
|
|
292
|
+
reg._registerPackage('/assets/sky.hdr', [GUID_A]);
|
|
293
|
+
reg.catalog(GUID_A, meshPayload());
|
|
294
|
+
expect(reg.resolveName(GUID_A)).toBe('sky.hdr');
|
|
295
|
+
|
|
296
|
+
reg.invalidate(GUID_A);
|
|
297
|
+
registerPackagesFromIndex(
|
|
298
|
+
reg,
|
|
299
|
+
new Map([
|
|
300
|
+
[
|
|
301
|
+
GUID_A,
|
|
302
|
+
{
|
|
303
|
+
packageUrl: '/node_modules/.cache/forgeax-ddc/sky.bin',
|
|
304
|
+
sourcePath: '/assets/sky.hdr',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
]),
|
|
308
|
+
);
|
|
309
|
+
reg.catalog(GUID_A, meshPayload());
|
|
310
|
+
|
|
311
|
+
expect(reg.resolveName(GUID_A)).toBe('sky.bin');
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('invalidateAll clears every catalogued asset and reports the count', () => {
|
|
315
|
+
const reg = makeRegistry();
|
|
316
|
+
reg.catalog(GUID_A, meshPayload());
|
|
317
|
+
reg.catalog(GUID_B, meshPayload());
|
|
318
|
+
const { clearedCount } = reg.invalidateAll();
|
|
319
|
+
expect(clearedCount).toBeGreaterThanOrEqual(2);
|
|
320
|
+
expect(reg.lookup(GUID_A)).toBeUndefined();
|
|
321
|
+
// Idempotent: a second call reports 0.
|
|
322
|
+
expect(reg.invalidateAll().clearedCount).toBe(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('runtime binding transitions preserve engine-owned builtin meshes', async () => {
|
|
326
|
+
const reg = makeRegistry();
|
|
327
|
+
const builtinGuid = 'cbe42beb-8975-5096-b3a1-3dda4cb4c077';
|
|
328
|
+
const before = reg.lookup<MeshAsset>(builtinGuid);
|
|
329
|
+
expect(before?.kind).toBe('mesh');
|
|
330
|
+
|
|
331
|
+
reg.configureRuntimeBinding(createStandaloneRuntimeAssetBinding('game-a'));
|
|
332
|
+
|
|
333
|
+
const after = reg.lookup<MeshAsset>(builtinGuid);
|
|
334
|
+
expect(after).toBe(before);
|
|
335
|
+
const loaded = await reg.loadByGuid<MeshAsset>(reg.parseGuid(builtinGuid));
|
|
336
|
+
expect(loaded.ok).toBe(true);
|
|
337
|
+
if (loaded.ok) expect(loaded.value).toBe(before);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe('imported-output package identity', () => {
|
|
342
|
+
it('uses the authored source path for a single-asset DDC output', () => {
|
|
343
|
+
const reg = makeRegistry();
|
|
344
|
+
registerPackagesFromIndex(
|
|
345
|
+
reg,
|
|
346
|
+
new Map([
|
|
347
|
+
[
|
|
348
|
+
GUID_A,
|
|
349
|
+
{
|
|
350
|
+
packageUrl: `/__forgeax-ddc/${GUID_A}.pack.json`,
|
|
351
|
+
sourcePath: 'demo-assets/template-game-default/sky.hdr',
|
|
352
|
+
subject: 'imported-output',
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
]),
|
|
356
|
+
);
|
|
357
|
+
reg.catalog(GUID_A, meshPayload());
|
|
358
|
+
|
|
359
|
+
expect(reg.resolveName(GUID_A)).toBe('sky.hdr');
|
|
360
|
+
expect(reg.packageOf(GUID_A)?.path).toBe('demo-assets/template-game-default/sky.hdr');
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('inspect / listCatalog', () => {
|
|
365
|
+
it('returns an empty readonly catalog before a registry is bound', () => {
|
|
366
|
+
const reg = makeRegistry();
|
|
367
|
+
reg.assetCatalog.clear();
|
|
368
|
+
const rows = reg.listCatalog();
|
|
369
|
+
expect(rows).toEqual([]);
|
|
370
|
+
expectTypeOf(rows).toMatchTypeOf<readonly unknown[]>();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('inspect lists catalogued assets with guid/kind/name', () => {
|
|
374
|
+
const reg = makeRegistry();
|
|
375
|
+
reg.catalog(GUID_A, meshPayload());
|
|
376
|
+
const snap = reg.inspect();
|
|
377
|
+
const entry = snap.assets.find((a) => a.guid === GUID_A.toLowerCase());
|
|
378
|
+
expect(entry?.kind).toBe('mesh');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('listCatalog returns a fresh snapshot including the inline asset', () => {
|
|
382
|
+
const reg = makeRegistry();
|
|
383
|
+
reg.catalog(GUID_A, meshPayload());
|
|
384
|
+
const rows = reg.listCatalog();
|
|
385
|
+
expect(rows.some((r) => r.guid === GUID_A.toLowerCase() && r.kind === 'mesh')).toBe(true);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('listCatalog deeply preserves every canonical producer fact from the full catalog', () => {
|
|
389
|
+
const reg = makeRegistry();
|
|
390
|
+
reg.assetCatalog.clear();
|
|
391
|
+
const provenance = { provider: 'host-importer', version: '7.2.0' } as const;
|
|
392
|
+
const revision = { digest: 'sha256:catalog', observedAt: 42, rootId: 'game-root' } as const;
|
|
393
|
+
const relations = [
|
|
394
|
+
{
|
|
395
|
+
from: { type: 'asset', id: GUID_A },
|
|
396
|
+
to: { type: 'resource', id: 'source/blob' },
|
|
397
|
+
type: 'produces',
|
|
398
|
+
provenance,
|
|
399
|
+
},
|
|
400
|
+
] as const;
|
|
401
|
+
const diagnostics = [
|
|
402
|
+
{
|
|
403
|
+
code: 'producer-warning',
|
|
404
|
+
severity: 'warning',
|
|
405
|
+
expected: 'stable source key',
|
|
406
|
+
actual: 'legacy index fallback',
|
|
407
|
+
hint: 'repair the producer declaration',
|
|
408
|
+
authority: 'producer',
|
|
409
|
+
},
|
|
410
|
+
] as const;
|
|
411
|
+
const canonicalRow: CatalogRecord = {
|
|
412
|
+
packageUrl: '/assets/blob.pack.json',
|
|
413
|
+
sourcePath: 'models/blob.host',
|
|
414
|
+
kind: 'host/blob',
|
|
415
|
+
packageId: 'host-package',
|
|
416
|
+
provenance,
|
|
417
|
+
revision,
|
|
418
|
+
sourceKey: 'blob/main',
|
|
419
|
+
sourceIndex: 3,
|
|
420
|
+
relations,
|
|
421
|
+
diagnostics,
|
|
422
|
+
} as const;
|
|
423
|
+
const legacyRow: CatalogRecord = {
|
|
424
|
+
packageUrl: '/assets/legacy.pack.json',
|
|
425
|
+
sourcePath: 'models/legacy.host',
|
|
426
|
+
kind: 'host/legacy',
|
|
427
|
+
} as const;
|
|
428
|
+
reg.packIndexCache = new Map([
|
|
429
|
+
[GUID_A.toLowerCase(), canonicalRow],
|
|
430
|
+
[GUID_B.toLowerCase(), legacyRow],
|
|
431
|
+
]);
|
|
432
|
+
|
|
433
|
+
expect(reg.listCatalog()).toEqual([
|
|
434
|
+
{
|
|
435
|
+
guid: GUID_A.toLowerCase(),
|
|
436
|
+
...canonicalRow,
|
|
437
|
+
authoring: authoringCapabilityForAssetKind(canonicalRow.kind),
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
guid: GUID_B.toLowerCase(),
|
|
441
|
+
...legacyRow,
|
|
442
|
+
authoring: authoringCapabilityForAssetKind(legacyRow.kind),
|
|
443
|
+
},
|
|
444
|
+
]);
|
|
445
|
+
expect(reg.listCatalog()[1]).toEqual({
|
|
446
|
+
guid: GUID_B.toLowerCase(),
|
|
447
|
+
...legacyRow,
|
|
448
|
+
authoring: authoringCapabilityForAssetKind(legacyRow.kind),
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('listCatalog includes a catalogued AnimationGraph with kind=animation-graph', () => {
|
|
453
|
+
const reg = makeRegistry();
|
|
454
|
+
const graph = {
|
|
455
|
+
kind: 'animation-graph' as const,
|
|
456
|
+
nodes: [{ type: 'clip' as const, clip: 0 as never, weight: 1 }],
|
|
457
|
+
root: 0,
|
|
458
|
+
};
|
|
459
|
+
reg.catalog(GUID_B, graph);
|
|
460
|
+
const rows = reg.listCatalog();
|
|
461
|
+
expect(rows.some((r) => r.guid === GUID_B.toLowerCase() && r.kind === 'animation-graph')).toBe(
|
|
462
|
+
true,
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
describe('configuration setters + payload parse delegation', () => {
|
|
468
|
+
it('setMetrics / setTranscodeCaps / configurePackIndex mutate config without throwing', () => {
|
|
469
|
+
const reg = makeRegistry();
|
|
470
|
+
reg.setMetrics({ increment: () => {} } as never);
|
|
471
|
+
reg.setTranscodeCaps({ bc: false, etc2: false, astc: false } as never);
|
|
472
|
+
reg.configurePackIndex('/pack-index.json');
|
|
473
|
+
// configurePackIndex is idempotent-safe on a re-set (resets the cache).
|
|
474
|
+
reg.configurePackIndex('/pack-index.json');
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('materialShaderTextureFieldNames returns undefined for an unregistered shader', () => {
|
|
478
|
+
expect(makeRegistry().materialShaderTextureFieldNames('forgeax::nope')).toBeUndefined();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('parseAssetPayload dispatches a mesh payload through the loader registry', () => {
|
|
482
|
+
const reg = makeRegistry();
|
|
483
|
+
const out = reg.parseAssetPayload('mesh', {
|
|
484
|
+
vertices: new Array(12).fill(0),
|
|
485
|
+
indices: [0, 0, 0],
|
|
486
|
+
});
|
|
487
|
+
expect((out as { kind?: string }).kind).toBe('mesh');
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('parseAssetPayload passes an unregistered kind through as a raw payload', () => {
|
|
491
|
+
// Unknown kinds have no engine loader; the payload is returned verbatim
|
|
492
|
+
// (stamped with kind) so a host-registered loader can own the parse (D-1).
|
|
493
|
+
const out = makeRegistry().parseAssetPayload('sampler', { addressModeU: 'repeat' });
|
|
494
|
+
expect((out as { kind?: string }).kind).toBe('sampler');
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('parseAndReturnAsset returns a Result carrying the parsed asset + refs', () => {
|
|
498
|
+
const reg = makeRegistry();
|
|
499
|
+
const res = reg.parseAndReturnAsset({
|
|
500
|
+
kind: 'mesh',
|
|
501
|
+
payload: { vertices: new Array(12).fill(0), indices: [0, 0, 0] },
|
|
502
|
+
});
|
|
503
|
+
expect(res.ok).toBe(true);
|
|
504
|
+
if (!res.ok) return;
|
|
505
|
+
expect(res.value.asset.kind).toBe('mesh');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('refreshCatalog is a no-op (returns false) when no pack index is configured', async () => {
|
|
509
|
+
expect(await makeRegistry().refreshCatalog()).toBe(false);
|
|
510
|
+
});
|
|
511
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
2
|
+
import type { AssetRuntimeError, AssetRuntimeErrorCode } from '../errors/asset';
|
|
3
|
+
|
|
4
|
+
describe('AssetRuntimeErrorCode owner proof', () => {
|
|
5
|
+
it('derives the exact closed code surface and narrows the correlated detail', () => {
|
|
6
|
+
expectTypeOf<AssetRuntimeError>().toMatchTypeOf<{
|
|
7
|
+
code: string;
|
|
8
|
+
expected: string;
|
|
9
|
+
hint: string;
|
|
10
|
+
}>();
|
|
11
|
+
expectTypeOf<AssetRuntimeErrorCode>().toEqualTypeOf<AssetRuntimeError['code']>();
|
|
12
|
+
expectTypeOf<AssetRuntimeErrorCode>().toEqualTypeOf<
|
|
13
|
+
| 'material-resolved-empty-passes'
|
|
14
|
+
| 'mesh-ssbo-capacity-exceeded'
|
|
15
|
+
| 'mesh-ssbo-ceiling-reached'
|
|
16
|
+
| 'scene-collect-entity-ref-out-of-closure'
|
|
17
|
+
| 'scene-collect-asset-guid-unresolved'
|
|
18
|
+
>();
|
|
19
|
+
|
|
20
|
+
const acceptAssetRuntimeErrorCode = (code: AssetRuntimeErrorCode): void => {
|
|
21
|
+
void code;
|
|
22
|
+
};
|
|
23
|
+
acceptAssetRuntimeErrorCode('material-resolved-empty-passes');
|
|
24
|
+
acceptAssetRuntimeErrorCode('mesh-ssbo-capacity-exceeded');
|
|
25
|
+
acceptAssetRuntimeErrorCode('mesh-ssbo-ceiling-reached');
|
|
26
|
+
acceptAssetRuntimeErrorCode('scene-collect-entity-ref-out-of-closure');
|
|
27
|
+
acceptAssetRuntimeErrorCode('scene-collect-asset-guid-unresolved');
|
|
28
|
+
// @ts-expect-error invalid codes must not be accepted.
|
|
29
|
+
acceptAssetRuntimeErrorCode('not-an-asset-runtime-error');
|
|
30
|
+
|
|
31
|
+
const exhaustiveAssetRuntimeErrorCode = (code: AssetRuntimeErrorCode): string => {
|
|
32
|
+
switch (code) {
|
|
33
|
+
case 'material-resolved-empty-passes':
|
|
34
|
+
return code;
|
|
35
|
+
case 'mesh-ssbo-capacity-exceeded':
|
|
36
|
+
return code;
|
|
37
|
+
case 'mesh-ssbo-ceiling-reached':
|
|
38
|
+
return code;
|
|
39
|
+
case 'scene-collect-entity-ref-out-of-closure':
|
|
40
|
+
return code;
|
|
41
|
+
case 'scene-collect-asset-guid-unresolved':
|
|
42
|
+
return code;
|
|
43
|
+
default: {
|
|
44
|
+
const exhaustive: never = code;
|
|
45
|
+
return exhaustive;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
void exhaustiveAssetRuntimeErrorCode;
|
|
50
|
+
|
|
51
|
+
const narrowAssetRuntimeError = (error: AssetRuntimeError): void => {
|
|
52
|
+
if (error.code === 'material-resolved-empty-passes') {
|
|
53
|
+
const materialGuid: string = error.detail.materialGuid;
|
|
54
|
+
const reason: 'missing-parent' | 'no-pass-in-chain' = error.detail.reason;
|
|
55
|
+
void materialGuid;
|
|
56
|
+
void reason;
|
|
57
|
+
// @ts-expect-error code-driven narrowing excludes unrelated detail fields.
|
|
58
|
+
void error.detail.requested;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
void narrowAssetRuntimeError;
|
|
62
|
+
});
|
|
63
|
+
});
|