@forgeax/engine-import 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 +227 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/decode-image-owner.test-d.d.ts +2 -0
- package/dist/__tests__/decode-image-owner.test-d.d.ts.map +1 -0
- package/dist/__tests__/import-contract-migration.test.d.ts +2 -0
- package/dist/__tests__/import-contract-migration.test.d.ts.map +1 -0
- package/dist/__tests__/import-dependencies.test.d.ts +2 -0
- package/dist/__tests__/import-dependencies.test.d.ts.map +1 -0
- package/dist/__tests__/import-finalizer-digest-retry.test.d.ts +2 -0
- package/dist/__tests__/import-finalizer-digest-retry.test.d.ts.map +1 -0
- package/dist/__tests__/import-local-artifacts.test.d.ts +2 -0
- package/dist/__tests__/import-local-artifacts.test.d.ts.map +1 -0
- package/dist/__tests__/import.unit.test.d.ts +2 -0
- package/dist/__tests__/import.unit.test.d.ts.map +1 -0
- package/dist/__tests__/mesh-bin-contract.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin-contract.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.test.d.ts +2 -0
- package/dist/__tests__/mesh-bin.test.d.ts.map +1 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts +2 -0
- package/dist/__tests__/owner-chain.integration.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-host.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-output-producers.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-output-producers.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-production-producers.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-production-producers.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack-staged-snapshot.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-staged-snapshot.unit.test.d.ts.map +1 -0
- package/dist/__tests__/scriptable-pack.integration.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +1 -0
- package/dist/__tests__/source-overrides-import-context.unit.test.d.ts +2 -0
- package/dist/__tests__/source-overrides-import-context.unit.test.d.ts.map +1 -0
- package/dist/__tests__/source-package-publication.integration.test.d.ts +2 -0
- package/dist/__tests__/source-package-publication.integration.test.d.ts.map +1 -0
- package/dist/browser.d.ts +5 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.mjs +782 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/build-production.d.ts +48 -0
- package/dist/build-production.d.ts.map +1 -0
- package/dist/catalog-importer-policy.d.ts +13 -0
- package/dist/catalog-importer-policy.d.ts.map +1 -0
- package/dist/catalog-inventory.d.ts +4 -0
- package/dist/catalog-inventory.d.ts.map +1 -0
- package/dist/import-product.d.ts +27 -0
- package/dist/import-product.d.ts.map +1 -0
- package/dist/import-runner.d.ts +136 -0
- package/dist/import-runner.d.ts.map +1 -0
- package/dist/importer-registry.d.ts +48 -0
- package/dist/importer-registry.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +2849 -0
- package/dist/index.mjs.map +1 -0
- package/dist/mesh-bin.d.ts +19 -0
- package/dist/mesh-bin.d.ts.map +1 -0
- package/dist/mesh-bin.mjs +182 -0
- package/dist/mesh-bin.mjs.map +1 -0
- package/dist/pack-projection.d.ts +5 -0
- package/dist/pack-projection.d.ts.map +1 -0
- package/dist/scriptable-pack-host.d.ts +72 -0
- package/dist/scriptable-pack-host.d.ts.map +1 -0
- package/dist/scriptable-pack-output-producers.d.ts +7 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts +21 -0
- package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -0
- package/dist/scriptable-pack.d.ts +141 -0
- package/dist/scriptable-pack.d.ts.map +1 -0
- package/dist/scriptable-source-package.d.ts +14 -0
- package/dist/scriptable-source-package.d.ts.map +1 -0
- package/dist/source-package-errors.d.ts +52 -0
- package/dist/source-package-errors.d.ts.map +1 -0
- package/dist/source-package-publication.d.ts +63 -0
- package/dist/source-package-publication.d.ts.map +1 -0
- package/dist/source-package.d.ts +60 -0
- package/dist/source-package.d.ts.map +1 -0
- package/package.json +69 -0
- package/src/__tests__/decode-image-owner.test-d.ts +53 -0
- package/src/__tests__/import-contract-migration.test.ts +223 -0
- package/src/__tests__/import-dependencies.test.ts +65 -0
- package/src/__tests__/import-finalizer-digest-retry.test.ts +113 -0
- package/src/__tests__/import-local-artifacts.test.ts +102 -0
- package/src/__tests__/import.unit.test.ts +429 -0
- package/src/__tests__/mesh-bin-contract.test.ts +95 -0
- package/src/__tests__/mesh-bin-morph-v3.test.ts +44 -0
- package/src/__tests__/mesh-bin.test.ts +102 -0
- package/src/__tests__/owner-chain.integration.test.ts +37 -0
- package/src/__tests__/scriptable-pack-host.unit.test.ts +86 -0
- package/src/__tests__/scriptable-pack-output-producers.unit.test.ts +190 -0
- package/src/__tests__/scriptable-pack-product.contract.test.ts +47 -0
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +488 -0
- package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +290 -0
- package/src/__tests__/scriptable-pack.integration.test.ts +435 -0
- package/src/__tests__/source-overrides-import-context.unit.test.ts +92 -0
- package/src/__tests__/source-package-publication.integration.test.ts +75 -0
- package/src/browser.ts +14 -0
- package/src/build-production.ts +493 -0
- package/src/catalog-importer-policy.ts +58 -0
- package/src/catalog-inventory.ts +24 -0
- package/src/import-product.ts +163 -0
- package/src/import-runner.ts +749 -0
- package/src/importer-registry.ts +103 -0
- package/src/index.ts +175 -0
- package/src/mesh-bin.ts +229 -0
- package/src/pack-projection.ts +21 -0
- package/src/scriptable-pack-host.ts +544 -0
- package/src/scriptable-pack-output-producers.ts +456 -0
- package/src/scriptable-pack-staged-snapshot.ts +172 -0
- package/src/scriptable-pack.ts +626 -0
- package/src/scriptable-source-package.ts +88 -0
- package/src/source-package-errors.ts +201 -0
- package/src/source-package-publication.ts +326 -0
- package/src/source-package.ts +187 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { packMeshBinV4 } from '@forgeax/engine-import';
|
|
2
|
+
import { decodeMeshBinHeader } from '@forgeax/engine-pack';
|
|
3
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
function buildPayload(vertexCount: number, extraUvCount: number, hasSkin: boolean) {
|
|
7
|
+
const baseFpv = hasSkin ? 18 : 12;
|
|
8
|
+
const fpv = baseFpv + extraUvCount * 2;
|
|
9
|
+
const vertices = new Float32Array(vertexCount * fpv);
|
|
10
|
+
for (let i = 0; i < vertices.length; i++) vertices[i] = i * 0.1;
|
|
11
|
+
const attributes: Record<string, unknown> = {
|
|
12
|
+
position: new Float32Array(vertexCount * 3),
|
|
13
|
+
normal: new Float32Array(vertexCount * 3),
|
|
14
|
+
uv: new Float32Array(vertexCount * 2),
|
|
15
|
+
tangent: new Float32Array(vertexCount * 4),
|
|
16
|
+
};
|
|
17
|
+
for (let set = 1; set <= extraUvCount; set++) {
|
|
18
|
+
attributes[`uv${set}`] = new Float32Array(vertexCount * 2);
|
|
19
|
+
}
|
|
20
|
+
if (hasSkin) {
|
|
21
|
+
attributes.skinIndex = new Uint16Array(vertexCount * 4);
|
|
22
|
+
attributes.skinWeight = new Float32Array(vertexCount * 4);
|
|
23
|
+
}
|
|
24
|
+
return { vertices, indices: new Uint16Array([0, 1, 2]), attributes };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('mesh-bin v4 roundtrip contract', () => {
|
|
28
|
+
it.each([
|
|
29
|
+
[0, false, 1],
|
|
30
|
+
[1, false, 2],
|
|
31
|
+
[2, false, 3],
|
|
32
|
+
[7, false, 8],
|
|
33
|
+
[1, true, 2],
|
|
34
|
+
])('records canonical projection for %i extra UV sets and skin=%s', (extra, skin, uvSets) => {
|
|
35
|
+
const result = packMeshBinV4(buildPayload(4, extra, skin), `gltf://mesh/${extra}/${skin}`);
|
|
36
|
+
expect(result.ok).toBe(true);
|
|
37
|
+
if (!result.ok) return;
|
|
38
|
+
const header = decodeMeshBinHeader(result.value, `gltf://mesh/${extra}/${skin}`);
|
|
39
|
+
expect(header.ok).toBe(true);
|
|
40
|
+
if (!header.ok) return;
|
|
41
|
+
expect(header.value.version).toBe(4);
|
|
42
|
+
expect(header.value.projectionVersion).toBe(1);
|
|
43
|
+
expect(header.value.vertexCount).toBe(4);
|
|
44
|
+
expect(header.value.stride).toBe((skin ? 18 + extra * 2 : 12 + extra * 2) * 4);
|
|
45
|
+
expect(header.value.mask).toBeGreaterThan(0);
|
|
46
|
+
expect(uvSets).toBeGreaterThan(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('stores canonical interleaved bytes and material refs as indexes', () => {
|
|
50
|
+
const guid = '019d0000-0000-7000-8000-000000000005';
|
|
51
|
+
const parsed = AssetGuid.parse(guid);
|
|
52
|
+
if (!parsed.ok) throw new Error('fixture guid must parse');
|
|
53
|
+
const payload = {
|
|
54
|
+
vertices: new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]),
|
|
55
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
56
|
+
attributes: {
|
|
57
|
+
position: new Float32Array([1, 2, 3]),
|
|
58
|
+
normal: new Float32Array([4, 5, 6]),
|
|
59
|
+
uv: new Float32Array([7, 8]),
|
|
60
|
+
tangent: new Float32Array([9, 10, 11, 12]),
|
|
61
|
+
},
|
|
62
|
+
submeshes: [
|
|
63
|
+
{
|
|
64
|
+
indexOffset: 0,
|
|
65
|
+
indexCount: 3,
|
|
66
|
+
vertexCount: 1,
|
|
67
|
+
topology: 'triangle-list' as const,
|
|
68
|
+
materialSlot: 0,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
materialSlots: [
|
|
72
|
+
{ slotName: 'Body', sourceKey: 'gltf:material:0', defaultMaterial: parsed.value },
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
const result = packMeshBinV4(payload, 'gltf://color', [guid]);
|
|
76
|
+
expect(result.ok).toBe(true);
|
|
77
|
+
if (!result.ok) return;
|
|
78
|
+
const header = decodeMeshBinHeader(result.value, 'gltf://color');
|
|
79
|
+
expect(header.ok).toBe(true);
|
|
80
|
+
if (!header.ok) return;
|
|
81
|
+
const jsonOffset = 80 + header.value.vertexBytes + header.value.indexBytes;
|
|
82
|
+
const json = new TextDecoder().decode(
|
|
83
|
+
result.value.subarray(jsonOffset, jsonOffset + header.value.jsonBytes),
|
|
84
|
+
);
|
|
85
|
+
expect(JSON.parse(json).materialSlots).toEqual([
|
|
86
|
+
{ slotName: 'Body', sourceKey: 'gltf:material:0', defaultMaterialRef: 0 },
|
|
87
|
+
]);
|
|
88
|
+
expect(json).not.toContain(guid);
|
|
89
|
+
expect(Array.from(result.value.subarray(80, 80 + payload.vertices.byteLength))).toEqual(
|
|
90
|
+
Array.from(new Uint8Array(payload.vertices.buffer)),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('returns a closed error without bytes for invalid stride cardinality', () => {
|
|
95
|
+
const payload = buildPayload(2, 0, false);
|
|
96
|
+
const result = packMeshBinV4({ ...payload, vertexCount: 3 }, 'gltf://invalid');
|
|
97
|
+
expect(result.ok).toBe(false);
|
|
98
|
+
if (result.ok) return;
|
|
99
|
+
expect(result.error.sourceKey).toBe('gltf://invalid');
|
|
100
|
+
expect(result.error.recovery).toContain('re-cook');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createImportProduct } from '../import-product.js';
|
|
3
|
+
|
|
4
|
+
describe('Import owner chain', () => {
|
|
5
|
+
it('hands one complete terminal product to the next owner', () => {
|
|
6
|
+
const result = createImportProduct({
|
|
7
|
+
assets: [
|
|
8
|
+
{
|
|
9
|
+
guid: '019e3969-1d48-7c3b-ac24-6d68f457065f',
|
|
10
|
+
kind: 'texture',
|
|
11
|
+
payload: { kind: 'texture' },
|
|
12
|
+
refs: [],
|
|
13
|
+
artifacts: {},
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
sourceDependencies: ['hero.png'],
|
|
17
|
+
refs: [],
|
|
18
|
+
artifacts: {},
|
|
19
|
+
receipts: [],
|
|
20
|
+
diagnostics: [],
|
|
21
|
+
sourceRevision: 'sha256:hero',
|
|
22
|
+
sourceKey: 'hero/albedo',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(result.ok).toBe(true);
|
|
26
|
+
if (result.ok) {
|
|
27
|
+
expect(result.value).toMatchObject({
|
|
28
|
+
sourceRevision: 'sha256:hero',
|
|
29
|
+
sourceKey: 'hero/albedo',
|
|
30
|
+
refs: [],
|
|
31
|
+
artifacts: {},
|
|
32
|
+
receipts: [],
|
|
33
|
+
diagnostics: [],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { ScanSourceDeclaration } from '@forgeax/engine-pack/scanner';
|
|
3
|
+
import type {
|
|
4
|
+
Asset,
|
|
5
|
+
AssetGuid as AssetGuidType,
|
|
6
|
+
ImportContext,
|
|
7
|
+
Importer,
|
|
8
|
+
} from '@forgeax/engine-types';
|
|
9
|
+
import { describe, expect, it } from 'vitest';
|
|
10
|
+
import { ImporterRegistry } from '../importer-registry.js';
|
|
11
|
+
import { declaredPackExternalOutputs } from '../scriptable-pack-host.js';
|
|
12
|
+
|
|
13
|
+
const GUID = '019ffa97-0000-7000-8000-000000000001';
|
|
14
|
+
|
|
15
|
+
function parseGuid(value: string): AssetGuidType {
|
|
16
|
+
const parsed = AssetGuid.parse(value);
|
|
17
|
+
if (!parsed.ok) throw parsed.error;
|
|
18
|
+
return parsed.value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('ScriptablePack external Meta staging', () => {
|
|
22
|
+
it('imports ordinary Meta dependencies into the current staged generation', async () => {
|
|
23
|
+
const importer: Importer = {
|
|
24
|
+
key: 'fixture',
|
|
25
|
+
import: async (ctx: ImportContext) => ({
|
|
26
|
+
ok: true,
|
|
27
|
+
value: {
|
|
28
|
+
assets: [
|
|
29
|
+
{
|
|
30
|
+
guid: ctx.subAssets[0]?.guid ?? GUID,
|
|
31
|
+
kind: 'mesh',
|
|
32
|
+
payload: {
|
|
33
|
+
kind: 'mesh',
|
|
34
|
+
vertices: new Float32Array([0, 1, 2]),
|
|
35
|
+
attributes: {},
|
|
36
|
+
submeshes: [],
|
|
37
|
+
materialSlots: [],
|
|
38
|
+
} satisfies Asset,
|
|
39
|
+
refs: [],
|
|
40
|
+
artifacts: {},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
sourceDependencies: [],
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
const registry = new ImporterRegistry();
|
|
48
|
+
registry.register(importer);
|
|
49
|
+
const declaration = {
|
|
50
|
+
format: 'meta.json',
|
|
51
|
+
sourcePath: '/project/assets/model.bin.meta.json',
|
|
52
|
+
sourceRevision: 'sha256:meta',
|
|
53
|
+
value: {
|
|
54
|
+
schemaVersion: '1.0.0',
|
|
55
|
+
kind: 'external-asset-package',
|
|
56
|
+
importer: 'fixture',
|
|
57
|
+
source: 'model.bin',
|
|
58
|
+
importSettings: {},
|
|
59
|
+
subAssets: [{ guid: GUID, sourceIndex: 0, kind: 'mesh' }],
|
|
60
|
+
},
|
|
61
|
+
} satisfies Extract<ScanSourceDeclaration, { readonly format: 'meta.json' }>;
|
|
62
|
+
|
|
63
|
+
const outputs = await declaredPackExternalOutputs(
|
|
64
|
+
new Map([[declaration.sourcePath, declaration]]),
|
|
65
|
+
[],
|
|
66
|
+
[parseGuid(GUID)],
|
|
67
|
+
{
|
|
68
|
+
importerRegistry: registry,
|
|
69
|
+
fsForImport: {
|
|
70
|
+
readSource: async () => ({ ok: true as const, value: new Uint8Array([1, 2, 3]) }),
|
|
71
|
+
},
|
|
72
|
+
assetPaths: {},
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(outputs).toHaveLength(1);
|
|
77
|
+
const output = outputs[0];
|
|
78
|
+
expect(output).toBeDefined();
|
|
79
|
+
if (output === undefined) return;
|
|
80
|
+
expect(AssetGuid.format(output.guid)).toBe(GUID);
|
|
81
|
+
expect(output.asset).toMatchObject({ kind: 'mesh' });
|
|
82
|
+
expect(Array.from((output.asset as Asset & { vertices: Float32Array }).vertices)).toEqual([
|
|
83
|
+
0, 1, 2,
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { AssetGuid as AssetGuidType, MaterialAsset, MeshAsset } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
materialAssetOutputProducer,
|
|
6
|
+
meshAssetOutputProducer,
|
|
7
|
+
} from '../scriptable-pack-output-producers.js';
|
|
8
|
+
|
|
9
|
+
function guid(value: string): AssetGuidType {
|
|
10
|
+
const parsed = AssetGuid.parse(value);
|
|
11
|
+
if (!parsed.ok) throw parsed.error;
|
|
12
|
+
return parsed.value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MATERIAL_GUID = guid('019ffa97-3000-7000-8000-000000000001');
|
|
16
|
+
const PARENT_GUID = guid('019ffa97-3000-7000-8000-000000000002');
|
|
17
|
+
const TEXTURE_GUID = guid('019ffa97-3000-7000-8000-000000000003');
|
|
18
|
+
const SAMPLER_GUID = guid('019ffa97-3000-7000-8000-000000000004');
|
|
19
|
+
|
|
20
|
+
describe('ScriptablePack standard output producers', () => {
|
|
21
|
+
it('packs generated mesh bytes and derives default-material references', async () => {
|
|
22
|
+
const mesh: MeshAsset = {
|
|
23
|
+
kind: 'mesh',
|
|
24
|
+
vertices: new Float32Array([
|
|
25
|
+
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0,
|
|
26
|
+
1, 1, 0, 0, 1,
|
|
27
|
+
]),
|
|
28
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
29
|
+
attributes: {
|
|
30
|
+
position: new Float32Array(9),
|
|
31
|
+
normal: new Float32Array(9),
|
|
32
|
+
uv: new Float32Array(6),
|
|
33
|
+
tangent: new Float32Array(12),
|
|
34
|
+
},
|
|
35
|
+
aabb: new Float32Array([0, 0, 0, 1, 0, 1]),
|
|
36
|
+
submeshes: [
|
|
37
|
+
{
|
|
38
|
+
topology: 'triangle-list',
|
|
39
|
+
indexOffset: 0,
|
|
40
|
+
indexCount: 3,
|
|
41
|
+
vertexCount: 3,
|
|
42
|
+
materialSlot: 0,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
materialSlots: [{ slotName: 'Shell', sourceKey: 'shell', defaultMaterial: MATERIAL_GUID }],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = await meshAssetOutputProducer.produce({
|
|
49
|
+
guid: '019ffa97-3000-7000-8000-000000000010',
|
|
50
|
+
sourceKey: 'geometry/shell',
|
|
51
|
+
asset: mesh,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.ok).toBe(true);
|
|
55
|
+
if (!result.ok) return;
|
|
56
|
+
expect(result.value.payload).toBe(mesh);
|
|
57
|
+
expect(result.value.refs).toEqual([
|
|
58
|
+
{
|
|
59
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
60
|
+
sourceField: { fieldName: 'materialSlots', arrayIndex: 0 },
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
expect(result.value.artifacts.body).toMatchObject({
|
|
64
|
+
mediaType: 'application/x-forgeax-mesh',
|
|
65
|
+
assetCodec: { name: 'mesh-binary', version: '4' },
|
|
66
|
+
});
|
|
67
|
+
expect(result.value.artifacts.body?.bytes.byteLength).toBeGreaterThan(28);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('projects material GUID fields into one deterministic refs graph', async () => {
|
|
71
|
+
const material: MaterialAsset = {
|
|
72
|
+
kind: 'material',
|
|
73
|
+
parent: PARENT_GUID,
|
|
74
|
+
parameters: [{ name: 'surface', type: 'texture' }],
|
|
75
|
+
values: {
|
|
76
|
+
surface: {
|
|
77
|
+
texture: TEXTURE_GUID,
|
|
78
|
+
sampler: SAMPLER_GUID,
|
|
79
|
+
coordinates: { set: 1, transform: { scale: [2, 2] } },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const result = await materialAssetOutputProducer.produce({
|
|
85
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
86
|
+
sourceKey: 'material/shell',
|
|
87
|
+
asset: material,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result.ok).toBe(true);
|
|
91
|
+
if (!result.ok) return;
|
|
92
|
+
expect(result.value.refs).toEqual([
|
|
93
|
+
{ guid: AssetGuid.format(PARENT_GUID), sourceField: { fieldName: 'parent' } },
|
|
94
|
+
{
|
|
95
|
+
guid: AssetGuid.format(TEXTURE_GUID),
|
|
96
|
+
sourceField: { componentName: '<material>', fieldName: 'surface' },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
guid: AssetGuid.format(SAMPLER_GUID),
|
|
100
|
+
sourceField: { componentName: '<material>', fieldName: 'surface.sampler' },
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
expect(result.value.payload).toEqual({
|
|
104
|
+
...material,
|
|
105
|
+
parent: 0,
|
|
106
|
+
values: {
|
|
107
|
+
surface: {
|
|
108
|
+
texture: 1,
|
|
109
|
+
sampler: 2,
|
|
110
|
+
coordinates: { set: 1, transform: { scale: [2, 2] } },
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
expect(result.value.artifacts).toEqual({});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('projects refs while keeping string texture authoring compact', async () => {
|
|
118
|
+
const material: MaterialAsset = {
|
|
119
|
+
kind: 'material',
|
|
120
|
+
parameters: [{ name: 'surface', type: 'texture' }],
|
|
121
|
+
values: { surface: AssetGuid.format(TEXTURE_GUID) },
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const result = await materialAssetOutputProducer.produce({
|
|
125
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
126
|
+
sourceKey: 'material/compact-surface',
|
|
127
|
+
asset: material,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.ok).toBe(true);
|
|
131
|
+
if (!result.ok) return;
|
|
132
|
+
expect(result.value.refs).toEqual([
|
|
133
|
+
{
|
|
134
|
+
guid: AssetGuid.format(TEXTURE_GUID),
|
|
135
|
+
sourceField: { componentName: '<material>', fieldName: 'surface' },
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
const payload = result.value.payload as { values?: Record<string, unknown> };
|
|
139
|
+
expect(payload.values).toEqual({ surface: 0 });
|
|
140
|
+
expect(payload.values?.surface).not.toEqual({
|
|
141
|
+
texture: 0,
|
|
142
|
+
coordinates: { set: 0, transform: { offset: [0, 0], scale: [1, 1], rotation: 0 } },
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('projects a string texture shorthand as a ref when parameters are omitted', async () => {
|
|
147
|
+
const material: MaterialAsset = {
|
|
148
|
+
kind: 'material',
|
|
149
|
+
values: { baseColorTexture: AssetGuid.format(TEXTURE_GUID) },
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const result = await materialAssetOutputProducer.produce({
|
|
153
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
154
|
+
sourceKey: 'material/compact-standard',
|
|
155
|
+
asset: material,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(result.ok).toBe(true);
|
|
159
|
+
if (!result.ok) return;
|
|
160
|
+
expect(result.value.refs).toEqual([
|
|
161
|
+
{
|
|
162
|
+
guid: AssetGuid.format(TEXTURE_GUID),
|
|
163
|
+
sourceField: { componentName: '<material>', fieldName: 'baseColorTexture' },
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
expect((result.value.payload as { values?: Record<string, unknown> }).values).toEqual({
|
|
167
|
+
baseColorTexture: 0,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('does not misclassify an ordinary string as a texture when parameters are omitted', async () => {
|
|
172
|
+
const material: MaterialAsset = {
|
|
173
|
+
kind: 'material',
|
|
174
|
+
values: { label: 'authoring-note' },
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const result = await materialAssetOutputProducer.produce({
|
|
178
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
179
|
+
sourceKey: 'material/compact-standard-string',
|
|
180
|
+
asset: material,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(result.ok).toBe(true);
|
|
184
|
+
if (!result.ok) return;
|
|
185
|
+
expect(result.value.refs).toEqual([]);
|
|
186
|
+
expect((result.value.payload as { values?: Record<string, unknown> }).values).toEqual({
|
|
187
|
+
label: 'authoring-note',
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import { ok } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { buildScriptablePack } from '../scriptable-pack.js';
|
|
5
|
+
import { createStandardAssetOutputProducerRegistry } from '../scriptable-pack-output-producers.js';
|
|
6
|
+
|
|
7
|
+
const PACKAGE_ID = AssetGuid.parse('019e3969-1d48-7c3b-ac24-6d68f457065e');
|
|
8
|
+
const HERO_GUID = AssetGuid.parse('019e3969-1d48-7c3b-ac24-6d68f457065f');
|
|
9
|
+
|
|
10
|
+
describe('ScriptablePack terminal product contract', () => {
|
|
11
|
+
it('keeps one complete product shape for direct and custom producer outputs', async () => {
|
|
12
|
+
const result = await buildScriptablePack({
|
|
13
|
+
definition: {
|
|
14
|
+
schemaVersion: '1.0.0',
|
|
15
|
+
packageId: PACKAGE_ID.ok ? PACKAGE_ID.value : ({} as never),
|
|
16
|
+
assets: {
|
|
17
|
+
hero: { guid: HERO_GUID.ok ? HERO_GUID.value : ({} as never), kind: 'texture' },
|
|
18
|
+
},
|
|
19
|
+
externalAssets: {},
|
|
20
|
+
build: async () =>
|
|
21
|
+
ok({
|
|
22
|
+
hero: {
|
|
23
|
+
kind: 'texture',
|
|
24
|
+
width: 1,
|
|
25
|
+
height: 1,
|
|
26
|
+
format: 'rgba8unorm',
|
|
27
|
+
data: new Uint8Array(),
|
|
28
|
+
colorSpace: 'srgb',
|
|
29
|
+
mipmap: false,
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
sourcePath: 'hero.pack.ts',
|
|
34
|
+
outputs: createStandardAssetOutputProducerRegistry(),
|
|
35
|
+
sourceClosure: [{ path: 'hero.pack.ts', digest: 'sha256:hero' }],
|
|
36
|
+
authoringContractVersion: 'scriptable-pack-production/1',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result.ok).toBe(true);
|
|
40
|
+
if (!result.ok) return;
|
|
41
|
+
expect(result.value.product).toHaveProperty('refs');
|
|
42
|
+
expect(result.value.product).toHaveProperty('artifacts');
|
|
43
|
+
expect(result.value.product).toHaveProperty('receipts');
|
|
44
|
+
expect(result.value.product).toHaveProperty('diagnostics');
|
|
45
|
+
expect(result.value.product).toHaveProperty('sourceRevision');
|
|
46
|
+
});
|
|
47
|
+
});
|