@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,488 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { ScriptablePackSceneComponent } from '@forgeax/engine-pack/source';
|
|
3
|
+
import type {
|
|
4
|
+
AnimationClip,
|
|
5
|
+
AnimationGraph,
|
|
6
|
+
Asset,
|
|
7
|
+
AssetGuid as AssetGuidType,
|
|
8
|
+
AudioClipAsset,
|
|
9
|
+
EquirectAsset,
|
|
10
|
+
FontAsset,
|
|
11
|
+
MaterialAsset,
|
|
12
|
+
MeshAsset,
|
|
13
|
+
ParticleEffectAsset,
|
|
14
|
+
RenderPipelineAsset,
|
|
15
|
+
SamplerAsset,
|
|
16
|
+
SceneAsset,
|
|
17
|
+
SkeletonAsset,
|
|
18
|
+
SkinAsset,
|
|
19
|
+
TextureAsset,
|
|
20
|
+
TilesetAsset,
|
|
21
|
+
VideoAsset,
|
|
22
|
+
} from '@forgeax/engine-types';
|
|
23
|
+
import { describe, expect, it } from 'vitest';
|
|
24
|
+
import { createStandardAssetOutputProducerRegistry } from '../scriptable-pack-output-producers.js';
|
|
25
|
+
|
|
26
|
+
function guid(value: string): AssetGuidType {
|
|
27
|
+
const parsed = AssetGuid.parse(value);
|
|
28
|
+
if (!parsed.ok) throw parsed.error;
|
|
29
|
+
return parsed.value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MATERIAL_GUID = guid('019ffa97-3000-7000-8000-000000000001');
|
|
33
|
+
const MESH_GUID = guid('019ffa97-3000-7000-8000-000000000002');
|
|
34
|
+
const EQUIRECT_GUID = guid('019ffa97-3000-7000-8000-000000000005');
|
|
35
|
+
const sceneComponents = [
|
|
36
|
+
{
|
|
37
|
+
name: 'ScriptablePackTestMeshFilter',
|
|
38
|
+
fields: { assetHandle: 'shared<MeshAsset>' },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'ScriptablePackTestMeshRenderer',
|
|
42
|
+
fields: { materials: 'array<shared<MaterialAsset>>' },
|
|
43
|
+
},
|
|
44
|
+
] satisfies readonly ScriptablePackSceneComponent[];
|
|
45
|
+
|
|
46
|
+
const OUTPUT_GUID = guid('019ffa97-3000-7000-8000-000000000010');
|
|
47
|
+
const ATLAS_GUID = guid('019ffa97-3000-7000-8000-000000000011');
|
|
48
|
+
const SKELETON_GUID = guid('019ffa97-3000-7000-8000-000000000012');
|
|
49
|
+
|
|
50
|
+
function canonicalAttributes(vertexCount: number): MeshAsset['attributes'] {
|
|
51
|
+
return {
|
|
52
|
+
position: new Float32Array(vertexCount * 3),
|
|
53
|
+
normal: new Float32Array(vertexCount * 3),
|
|
54
|
+
uv: new Float32Array(vertexCount * 2),
|
|
55
|
+
tangent: new Float32Array(vertexCount * 4),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const particleProgram = {
|
|
60
|
+
format: 'forgeax-vfx-program-2',
|
|
61
|
+
fingerprint: 'sha256:scriptable-pack-matrix',
|
|
62
|
+
emitters: [
|
|
63
|
+
{
|
|
64
|
+
id: 'default',
|
|
65
|
+
module: 'matrix/default',
|
|
66
|
+
capacity: 16,
|
|
67
|
+
backend: { required: 'gpu' },
|
|
68
|
+
space: 'local',
|
|
69
|
+
schedule: {},
|
|
70
|
+
bounds: {},
|
|
71
|
+
renderers: [],
|
|
72
|
+
simulationWhenCulled: 'pause',
|
|
73
|
+
wgsl: 'fn main() {}',
|
|
74
|
+
reflection: {},
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
} as const;
|
|
78
|
+
|
|
79
|
+
const matrixAssets = [
|
|
80
|
+
{
|
|
81
|
+
kind: 'mesh',
|
|
82
|
+
vertices: new Float32Array(48),
|
|
83
|
+
indices: new Uint16Array([0]),
|
|
84
|
+
attributes: canonicalAttributes(4),
|
|
85
|
+
submeshes: [],
|
|
86
|
+
materialSlots: [],
|
|
87
|
+
} satisfies MeshAsset,
|
|
88
|
+
{ kind: 'material', values: { roughness: 0.5 } } satisfies MaterialAsset,
|
|
89
|
+
{ kind: 'scene', entities: [] } satisfies SceneAsset,
|
|
90
|
+
{
|
|
91
|
+
kind: 'texture',
|
|
92
|
+
width: 1,
|
|
93
|
+
height: 1,
|
|
94
|
+
format: 'rgba8unorm',
|
|
95
|
+
data: new Uint8Array([255, 255, 255, 255]),
|
|
96
|
+
colorSpace: 'srgb',
|
|
97
|
+
mipmap: false,
|
|
98
|
+
} satisfies TextureAsset,
|
|
99
|
+
{
|
|
100
|
+
kind: 'equirect',
|
|
101
|
+
width: 1,
|
|
102
|
+
height: 1,
|
|
103
|
+
format: 'rgba16float',
|
|
104
|
+
data: new Uint8Array(8),
|
|
105
|
+
colorSpace: 'linear',
|
|
106
|
+
} satisfies EquirectAsset,
|
|
107
|
+
{ kind: 'sampler', magFilter: 'linear' } satisfies SamplerAsset,
|
|
108
|
+
{
|
|
109
|
+
kind: 'font',
|
|
110
|
+
atlas: ATLAS_GUID,
|
|
111
|
+
sampler: OUTPUT_GUID,
|
|
112
|
+
glyphs: {},
|
|
113
|
+
common: {
|
|
114
|
+
lineHeight: 1,
|
|
115
|
+
base: 1,
|
|
116
|
+
distanceRange: 4,
|
|
117
|
+
pxRange: 4,
|
|
118
|
+
atlasWidth: 1,
|
|
119
|
+
atlasHeight: 1,
|
|
120
|
+
},
|
|
121
|
+
} satisfies FontAsset,
|
|
122
|
+
{ kind: 'render-pipeline', pipelineId: 'forgeax::urp' } satisfies RenderPipelineAsset,
|
|
123
|
+
{
|
|
124
|
+
kind: 'tileset',
|
|
125
|
+
atlases: [AssetGuid.format(ATLAS_GUID)],
|
|
126
|
+
tileWidth: 1,
|
|
127
|
+
tileHeight: 1,
|
|
128
|
+
columns: 1,
|
|
129
|
+
rows: 1,
|
|
130
|
+
regions: [{ x: 0, y: 0, width: 1, height: 1 }],
|
|
131
|
+
tiles: [{ regionIndex: 0 }],
|
|
132
|
+
} satisfies TilesetAsset,
|
|
133
|
+
{ kind: 'video', url: 'https://example.test/scene.webm' } satisfies VideoAsset,
|
|
134
|
+
{
|
|
135
|
+
kind: 'skeleton',
|
|
136
|
+
inverseBindMatrices: new Float32Array(16),
|
|
137
|
+
jointCount: 1,
|
|
138
|
+
} satisfies SkeletonAsset,
|
|
139
|
+
{
|
|
140
|
+
kind: 'skin',
|
|
141
|
+
skeletonGuid: AssetGuid.format(SKELETON_GUID),
|
|
142
|
+
jointPaths: ['root'],
|
|
143
|
+
} satisfies SkinAsset,
|
|
144
|
+
{ kind: 'animation-clip', duration: 1, channels: [] } satisfies AnimationClip,
|
|
145
|
+
{
|
|
146
|
+
kind: 'animation-graph',
|
|
147
|
+
nodes: [{ type: 'clip', clip: AssetGuid.format(OUTPUT_GUID), weight: 1 }],
|
|
148
|
+
root: 0,
|
|
149
|
+
} satisfies AnimationGraph,
|
|
150
|
+
{
|
|
151
|
+
kind: 'audio',
|
|
152
|
+
sourceKey: 'audio/matrix',
|
|
153
|
+
mediaType: 'audio/wav',
|
|
154
|
+
bytes: new Uint8Array([82, 73, 70, 70]),
|
|
155
|
+
} satisfies AudioClipAsset,
|
|
156
|
+
{
|
|
157
|
+
kind: 'particle-effect',
|
|
158
|
+
schemaVersion: 2,
|
|
159
|
+
programFingerprint: particleProgram.fingerprint,
|
|
160
|
+
emitters: [{ id: 'default', capacity: 16 }],
|
|
161
|
+
program: particleProgram,
|
|
162
|
+
} satisfies ParticleEffectAsset,
|
|
163
|
+
] as const satisfies readonly Asset[];
|
|
164
|
+
|
|
165
|
+
describe('standard ScriptablePack output producers', () => {
|
|
166
|
+
it('registers the production producer versions for all ordinary Asset kinds', () => {
|
|
167
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
168
|
+
expect(registry.versions()).toEqual({
|
|
169
|
+
'animation-clip': 'ordinary-pod/1',
|
|
170
|
+
'animation-graph': 'ordinary-pod/1',
|
|
171
|
+
audio: 'ordinary-pod/1',
|
|
172
|
+
equirect: 'ordinary-pod/1',
|
|
173
|
+
font: 'ordinary-pod/1',
|
|
174
|
+
material: 'material-pack/1',
|
|
175
|
+
mesh: 'mesh-binary/4',
|
|
176
|
+
'particle-effect': 'ordinary-pod/1',
|
|
177
|
+
'render-pipeline': 'ordinary-pod/1',
|
|
178
|
+
sampler: 'ordinary-pod/1',
|
|
179
|
+
scene: 'scene-pack/3',
|
|
180
|
+
skeleton: 'ordinary-pod/1',
|
|
181
|
+
skin: 'ordinary-pod/1',
|
|
182
|
+
texture: 'ordinary-pod/1',
|
|
183
|
+
tileset: 'ordinary-pod/1',
|
|
184
|
+
video: 'ordinary-pod/1',
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('provides a real producer contract for every ordinary Asset kind', async () => {
|
|
189
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
190
|
+
const expectedKinds = [
|
|
191
|
+
'mesh',
|
|
192
|
+
'material',
|
|
193
|
+
'scene',
|
|
194
|
+
'texture',
|
|
195
|
+
'equirect',
|
|
196
|
+
'sampler',
|
|
197
|
+
'font',
|
|
198
|
+
'render-pipeline',
|
|
199
|
+
'tileset',
|
|
200
|
+
'video',
|
|
201
|
+
'skeleton',
|
|
202
|
+
'skin',
|
|
203
|
+
'animation-clip',
|
|
204
|
+
'animation-graph',
|
|
205
|
+
'audio',
|
|
206
|
+
'particle-effect',
|
|
207
|
+
] as const;
|
|
208
|
+
|
|
209
|
+
expect(Object.keys(registry.versions()).sort()).toEqual([...expectedKinds].sort());
|
|
210
|
+
for (const [index, asset] of matrixAssets.entries()) {
|
|
211
|
+
const producer = registry.get(asset.kind);
|
|
212
|
+
expect(producer, `${asset.kind} producer`).toBeDefined();
|
|
213
|
+
if (producer === undefined) continue;
|
|
214
|
+
const result = await producer.produce({
|
|
215
|
+
guid: AssetGuid.format(OUTPUT_GUID),
|
|
216
|
+
sourceKey: `matrix/${index}/${asset.kind}`,
|
|
217
|
+
asset,
|
|
218
|
+
});
|
|
219
|
+
expect(result.ok, `${asset.kind} producer result`).toBe(true);
|
|
220
|
+
if (!result.ok) continue;
|
|
221
|
+
expect(result.value.payload.kind ?? asset.kind).toBe(asset.kind);
|
|
222
|
+
const hasPayload = Object.keys(result.value.payload).length > 0;
|
|
223
|
+
const hasDescriptor =
|
|
224
|
+
asset.kind === 'video' &&
|
|
225
|
+
'url' in asset &&
|
|
226
|
+
typeof asset.url === 'string' &&
|
|
227
|
+
asset.url.length > 0;
|
|
228
|
+
expect(
|
|
229
|
+
hasPayload ||
|
|
230
|
+
result.value.refs.length + Object.keys(result.value.artifacts).length > 0 ||
|
|
231
|
+
hasDescriptor,
|
|
232
|
+
`${asset.kind} producer facts`,
|
|
233
|
+
).toBe(true);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('publishes media and VFX producer facts without host execution', async () => {
|
|
238
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
239
|
+
for (const kind of ['audio', 'video', 'particle-effect'] as const) {
|
|
240
|
+
const asset = matrixAssets.find((candidate) => candidate.kind === kind);
|
|
241
|
+
const producer = registry.get(kind);
|
|
242
|
+
expect(producer, `${kind} producer`).toBeDefined();
|
|
243
|
+
expect(asset, `${kind} fixture`).toBeDefined();
|
|
244
|
+
if (producer === undefined || asset === undefined) continue;
|
|
245
|
+
const result = await producer.produce({
|
|
246
|
+
guid: AssetGuid.format(OUTPUT_GUID),
|
|
247
|
+
sourceKey: `media/${kind}`,
|
|
248
|
+
asset,
|
|
249
|
+
});
|
|
250
|
+
expect(result.ok, `${kind} result`).toBe(true);
|
|
251
|
+
if (!result.ok) continue;
|
|
252
|
+
if (kind === 'video') {
|
|
253
|
+
expect(result.value.payload).toMatchObject({ kind, url: (asset as VideoAsset).url });
|
|
254
|
+
} else {
|
|
255
|
+
expect(Object.keys(result.value.artifacts)).not.toHaveLength(0);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('serializes a scene through component schemas and emits recursive GUID refs', async () => {
|
|
261
|
+
const registry = createStandardAssetOutputProducerRegistry(sceneComponents);
|
|
262
|
+
const producer = registry.get('scene');
|
|
263
|
+
expect(producer).toBeDefined();
|
|
264
|
+
if (producer === undefined) return;
|
|
265
|
+
|
|
266
|
+
const scene: SceneAsset = {
|
|
267
|
+
kind: 'scene',
|
|
268
|
+
entities: [
|
|
269
|
+
{
|
|
270
|
+
localId: 0 as never,
|
|
271
|
+
components: {
|
|
272
|
+
ScriptablePackTestMeshFilter: { assetHandle: AssetGuid.format(MESH_GUID) },
|
|
273
|
+
ScriptablePackTestMeshRenderer: {
|
|
274
|
+
materials: [AssetGuid.format(MATERIAL_GUID)],
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
const result = await producer.produce({
|
|
281
|
+
guid: '019ffa97-3000-7000-8000-000000000003',
|
|
282
|
+
sourceKey: 'scene/showcase',
|
|
283
|
+
asset: scene,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
expect(result.ok).toBe(true);
|
|
287
|
+
if (!result.ok) return;
|
|
288
|
+
expect(result.value.payload).toMatchObject({
|
|
289
|
+
entities: [
|
|
290
|
+
{
|
|
291
|
+
localId: 0,
|
|
292
|
+
components: {
|
|
293
|
+
ScriptablePackTestMeshFilter: { assetHandle: 0 },
|
|
294
|
+
ScriptablePackTestMeshRenderer: { materials: [1] },
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
});
|
|
299
|
+
expect(result.value.refs).toEqual([
|
|
300
|
+
{
|
|
301
|
+
guid: AssetGuid.format(MESH_GUID),
|
|
302
|
+
sourceField: {
|
|
303
|
+
componentName: 'ScriptablePackTestMeshFilter',
|
|
304
|
+
fieldName: 'assetHandle',
|
|
305
|
+
},
|
|
306
|
+
sceneEntityId: 0,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
310
|
+
sourceField: {
|
|
311
|
+
componentName: 'ScriptablePackTestMeshRenderer',
|
|
312
|
+
fieldName: 'materials',
|
|
313
|
+
arrayIndex: 0,
|
|
314
|
+
},
|
|
315
|
+
sceneEntityId: 0,
|
|
316
|
+
},
|
|
317
|
+
]);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('fails closed when a scene component schema is not declared by the Pack', async () => {
|
|
321
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
322
|
+
const producer = registry.get('scene');
|
|
323
|
+
expect(producer).toBeDefined();
|
|
324
|
+
if (producer === undefined) return;
|
|
325
|
+
|
|
326
|
+
const result = await producer.produce({
|
|
327
|
+
guid: '019ffa97-3000-7000-8000-000000000004',
|
|
328
|
+
sourceKey: 'scene/unknown-component',
|
|
329
|
+
asset: {
|
|
330
|
+
kind: 'scene',
|
|
331
|
+
entities: [{ localId: 0 as never, components: { Mystery: { asset: 'not-a-ref' } } }],
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
expect(result).toMatchObject({
|
|
336
|
+
ok: false,
|
|
337
|
+
error: {
|
|
338
|
+
code: 'import-internal-error',
|
|
339
|
+
detail: { reason: expect.stringContaining('missing from sceneComponents') },
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('deduplicates one shared scene GUID across multiple component fields', async () => {
|
|
345
|
+
const registry = createStandardAssetOutputProducerRegistry([
|
|
346
|
+
...sceneComponents,
|
|
347
|
+
{ name: 'Skylight', fields: { equirect: 'shared<EquirectAsset>' } },
|
|
348
|
+
{ name: 'SkyboxBackground', fields: { equirect: 'shared<EquirectAsset>' } },
|
|
349
|
+
]);
|
|
350
|
+
const producer = registry.get('scene');
|
|
351
|
+
expect(producer).toBeDefined();
|
|
352
|
+
if (producer === undefined) return;
|
|
353
|
+
|
|
354
|
+
const result = await producer.produce({
|
|
355
|
+
guid: '019ffa97-3000-7000-8000-000000000006',
|
|
356
|
+
sourceKey: 'scene/environment',
|
|
357
|
+
asset: {
|
|
358
|
+
kind: 'scene',
|
|
359
|
+
entities: [
|
|
360
|
+
{
|
|
361
|
+
localId: 0 as never,
|
|
362
|
+
components: { Skylight: { equirect: AssetGuid.format(EQUIRECT_GUID) } },
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
localId: 1 as never,
|
|
366
|
+
components: { SkyboxBackground: { equirect: AssetGuid.format(EQUIRECT_GUID) } },
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(result.ok).toBe(true);
|
|
373
|
+
if (!result.ok) return;
|
|
374
|
+
expect(result.value.refs).toHaveLength(1);
|
|
375
|
+
expect(result.value.refs[0]?.guid).toBe(AssetGuid.format(EQUIRECT_GUID));
|
|
376
|
+
expect(result.value.payload).toMatchObject({
|
|
377
|
+
entities: [
|
|
378
|
+
{ components: { Skylight: { equirect: 0 } } },
|
|
379
|
+
{ components: { SkyboxBackground: { equirect: 0 } } },
|
|
380
|
+
],
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('packs mesh bytes and projects material parent references', async () => {
|
|
385
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
386
|
+
const mesh = registry.get('mesh');
|
|
387
|
+
const material = registry.get('material');
|
|
388
|
+
expect(mesh).toBeDefined();
|
|
389
|
+
expect(material).toBeDefined();
|
|
390
|
+
if (mesh === undefined || material === undefined) return;
|
|
391
|
+
|
|
392
|
+
const meshAsset: MeshAsset = {
|
|
393
|
+
kind: 'mesh',
|
|
394
|
+
vertices: new Float32Array(48),
|
|
395
|
+
indices: new Uint16Array([0]),
|
|
396
|
+
attributes: canonicalAttributes(4),
|
|
397
|
+
submeshes: [],
|
|
398
|
+
materialSlots: [{ slotName: 'Default', defaultMaterial: MATERIAL_GUID }],
|
|
399
|
+
};
|
|
400
|
+
const meshResult = await mesh.produce({
|
|
401
|
+
guid: AssetGuid.format(MESH_GUID),
|
|
402
|
+
sourceKey: 'mesh/generated',
|
|
403
|
+
asset: meshAsset,
|
|
404
|
+
});
|
|
405
|
+
expect(meshResult.ok).toBe(true);
|
|
406
|
+
if (meshResult.ok)
|
|
407
|
+
expect(meshResult.value.artifacts.body?.bytes.byteLength).toBeGreaterThan(28);
|
|
408
|
+
|
|
409
|
+
const materialAsset: MaterialAsset = { kind: 'material', parent: MATERIAL_GUID };
|
|
410
|
+
const materialResult = await material.produce({
|
|
411
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
412
|
+
sourceKey: 'material/generated',
|
|
413
|
+
asset: materialAsset,
|
|
414
|
+
});
|
|
415
|
+
expect(materialResult.ok).toBe(true);
|
|
416
|
+
if (materialResult.ok) {
|
|
417
|
+
expect(materialResult.value.payload).toMatchObject({ parent: 0 });
|
|
418
|
+
expect(materialResult.value.refs).toEqual([
|
|
419
|
+
{ guid: AssetGuid.format(MATERIAL_GUID), sourceField: { fieldName: 'parent' } },
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('keeps ordinary output identities and serialized bytes stable across builds', async () => {
|
|
425
|
+
const registry = createStandardAssetOutputProducerRegistry();
|
|
426
|
+
const mesh = registry.get('mesh');
|
|
427
|
+
const material = registry.get('material');
|
|
428
|
+
const scene = registry.get('scene');
|
|
429
|
+
expect(mesh).toBeDefined();
|
|
430
|
+
expect(material).toBeDefined();
|
|
431
|
+
expect(scene).toBeDefined();
|
|
432
|
+
if (mesh === undefined || material === undefined || scene === undefined) return;
|
|
433
|
+
|
|
434
|
+
const inputs = [
|
|
435
|
+
{
|
|
436
|
+
guid: AssetGuid.format(MESH_GUID),
|
|
437
|
+
sourceKey: 'mesh/generated',
|
|
438
|
+
asset: {
|
|
439
|
+
kind: 'mesh' as const,
|
|
440
|
+
vertices: new Float32Array([
|
|
441
|
+
0, 0, 0, 0, 0, 1, 0.5, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0.5, 1, 0, 0, 0, 1, 1, 0, 0, 0,
|
|
442
|
+
0, 1, 1, 1, 0, 0, 0, 1,
|
|
443
|
+
]),
|
|
444
|
+
indices: new Uint16Array([0, 1, 2]),
|
|
445
|
+
attributes: canonicalAttributes(3),
|
|
446
|
+
aabb: new Float32Array([0, 0, 0, 1, 1, 0]),
|
|
447
|
+
submeshes: [
|
|
448
|
+
{
|
|
449
|
+
topology: 'triangle-list' as const,
|
|
450
|
+
indexOffset: 0,
|
|
451
|
+
indexCount: 3,
|
|
452
|
+
vertexCount: 3,
|
|
453
|
+
materialSlot: 0,
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
materialSlots: [{ slotName: 'Default' }],
|
|
457
|
+
},
|
|
458
|
+
producer: mesh,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
guid: AssetGuid.format(MATERIAL_GUID),
|
|
462
|
+
sourceKey: 'material/generated',
|
|
463
|
+
asset: { kind: 'material' as const, values: {} },
|
|
464
|
+
producer: material,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
guid: AssetGuid.format(guid('019ffa97-3000-7000-8000-000000000003')),
|
|
468
|
+
sourceKey: 'scene/generated',
|
|
469
|
+
asset: { kind: 'scene' as const, entities: [] },
|
|
470
|
+
producer: scene,
|
|
471
|
+
},
|
|
472
|
+
];
|
|
473
|
+
const first = await Promise.all(
|
|
474
|
+
inputs.map(({ producer, ...input }) => producer.produce(input)),
|
|
475
|
+
);
|
|
476
|
+
const second = await Promise.all(
|
|
477
|
+
inputs.map(({ producer, ...input }) => producer.produce(input)),
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
expect(first.map((result) => result.ok)).toEqual([true, true, true]);
|
|
481
|
+
expect(second.map((result) => result.ok)).toEqual([true, true, true]);
|
|
482
|
+
expect(first).toEqual(second);
|
|
483
|
+
for (const result of first) {
|
|
484
|
+
if (!result.ok) continue;
|
|
485
|
+
expect(result.value.payload).not.toHaveProperty('scriptableKind');
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
});
|