@forgeax/engine-gltf 0.1.7 → 0.1.20

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.
Files changed (39) hide show
  1. package/README.md +9 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/transmission-pack-carrier.integration.test.d.ts +2 -0
  4. package/dist/__tests__/transmission-pack-carrier.integration.test.d.ts.map +1 -0
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/check-extensions.d.ts +3 -3
  7. package/dist/check-extensions.d.ts.map +1 -1
  8. package/dist/cli-gltf.mjs +161 -69
  9. package/dist/cli-gltf.mjs.map +1 -1
  10. package/dist/errors.d.ts +7 -0
  11. package/dist/errors.d.ts.map +1 -1
  12. package/dist/gltf-importer.d.ts.map +1 -1
  13. package/dist/importer-entry.mjs +177 -72
  14. package/dist/importer-entry.mjs.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.mjs +177 -72
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/material/parse-material.d.ts +118 -0
  20. package/dist/material/parse-material.d.ts.map +1 -0
  21. package/dist/node-file-entry.mjs +161 -69
  22. package/dist/node-file-entry.mjs.map +1 -1
  23. package/dist/parse-gltf.d.ts +17 -103
  24. package/dist/parse-gltf.d.ts.map +1 -1
  25. package/package.json +12 -12
  26. package/src/__tests__/bridge-material-values.unit.test.ts +46 -0
  27. package/src/__tests__/gltf-error-derived-views.test-d.ts +4 -0
  28. package/src/__tests__/gltf-error-policy-owner.unit.test.ts +26 -178
  29. package/src/__tests__/gltf.unit.test.ts +34 -13
  30. package/src/__tests__/material-pack-refs.integration.test.ts +12 -0
  31. package/src/__tests__/parse-gltf.unit.test.ts +121 -0
  32. package/src/__tests__/transmission-pack-carrier.integration.test.ts +82 -0
  33. package/src/bridge.ts +12 -1
  34. package/src/check-extensions.ts +14 -9
  35. package/src/errors.ts +12 -0
  36. package/src/gltf-importer.ts +6 -0
  37. package/src/index.ts +1 -0
  38. package/src/material/parse-material.ts +342 -0
  39. package/src/parse-gltf.ts +50 -277
package/src/parse-gltf.ts CHANGED
@@ -1,9 +1,5 @@
1
- // parse-gltf.ts - JSON-side glTF importer entry (w17).
2
- //
3
- // w17 lands the full parseGltf main function plus parseGlb / toAssetPack
4
- // siblings. The helpers exported here include both the surface API
5
- // (parseGltf / parseGlb / toAssetPack) and a few mesh / scene utilities
6
- // the main path uses internally.
1
+ // parse-gltf.ts - JSON-side glTF importer entry.
2
+ // The public parse, GLB, asset-pack, mesh, and scene helpers live here.
7
3
 
8
4
  import { mat4, quat, vec3 } from '@forgeax/engine-math';
9
5
  import {
@@ -23,6 +19,14 @@ import { decodeColorAccessor } from './accessor/decode-color.js';
23
19
  import { checkExtensions, type GltfExtensionsJson } from './check-extensions.js';
24
20
  import { Base64DecodeError, dataUriBase64Payload, decodeBase64 } from './data-uri.js';
25
21
  import { err, type GltfError, gltfErr, ok, type Result } from './errors.js';
22
+ import {
23
+ type GltfImageIr,
24
+ type GltfMaterialIr,
25
+ type GltfMaterialJson,
26
+ type GltfSamplerIr,
27
+ type GltfTextureIr,
28
+ parseMaterial,
29
+ } from './material/parse-material.js';
26
30
  import {
27
31
  type GltfBufferViewDecodeCapability,
28
32
  type MeshoptBufferViewJson,
@@ -41,6 +45,21 @@ import {
41
45
  import type { GltfSourceKeyError } from './source-key.js';
42
46
  import { type DecomposedTransform, decomposeNodeTransform } from './transform.js';
43
47
 
48
+ export type {
49
+ GltfImageIr,
50
+ GltfMaterialIr,
51
+ GltfMaterialJson,
52
+ GltfNormalTextureInfoIr,
53
+ GltfOcclusionTextureInfoIr,
54
+ GltfSamplerIr,
55
+ GltfTextureInfoIr,
56
+ GltfTextureIr,
57
+ GltfTextureTransformIr,
58
+ NormalTextureInfoJson,
59
+ OcclusionTextureInfoJson,
60
+ TextureInfoJson,
61
+ } from './material/parse-material.js';
62
+
44
63
  export interface MeshPrimitiveJson {
45
64
  readonly attributes?: Record<string, number>;
46
65
  readonly indices?: number;
@@ -55,12 +74,7 @@ export interface MeshJson {
55
74
  readonly primitives: readonly MeshPrimitiveJson[];
56
75
  }
57
76
 
58
- // === Tier-B v1 IR (GltfDoc) ===
59
- //
60
- // Shape: a denormalised view of the parsed glTF JSON, post-validation,
61
- // suitable for downstream `toAssetPack` and runtime AssetRegistry
62
- // hand-off. Math types are POD (number tuples), no Vec3/Quat brand at
63
- // the boundary (charter proposition 5).
77
+ // Tier-B IR: validated glTF JSON projected into downstream POD records.
64
78
 
65
79
  interface InstancingAttributes {
66
80
  readonly TRANSLATION?: number;
@@ -217,26 +231,26 @@ export interface GltfMeshIr {
217
231
  readonly positions: Float32Array;
218
232
  readonly normals?: Float32Array;
219
233
  readonly texcoord0?: Float32Array;
220
- /** TEXCOORD_1 per-vertex UV set 1 (Float32Array, 2 per vertex). feat-20260629-multi-uv-set-support m1-w2. */
234
+ /** TEXCOORD_1 per-vertex UV set 1. */
221
235
  readonly texcoord1?: Float32Array;
222
- /** TEXCOORD_2 per-vertex UV set 2 (Float32Array, 2 per vertex). */
236
+ /** TEXCOORD_2 per-vertex UV set 2. */
223
237
  readonly texcoord2?: Float32Array;
224
- /** TEXCOORD_3 per-vertex UV set 3 (Float32Array, 2 per vertex). */
238
+ /** TEXCOORD_3 per-vertex UV set 3. */
225
239
  readonly texcoord3?: Float32Array;
226
- /** TEXCOORD_4 per-vertex UV set 4 (Float32Array, 2 per vertex). */
240
+ /** TEXCOORD_4 per-vertex UV set 4. */
227
241
  readonly texcoord4?: Float32Array;
228
- /** TEXCOORD_5 per-vertex UV set 5 (Float32Array, 2 per vertex). */
242
+ /** TEXCOORD_5 per-vertex UV set 5. */
229
243
  readonly texcoord5?: Float32Array;
230
- /** TEXCOORD_6 per-vertex UV set 6 (Float32Array, 2 per vertex). */
244
+ /** TEXCOORD_6 per-vertex UV set 6. */
231
245
  readonly texcoord6?: Float32Array;
232
- /** TEXCOORD_7 per-vertex UV set 7 (Float32Array, 2 per vertex). */
246
+ /** TEXCOORD_7 per-vertex UV set 7. */
233
247
  readonly texcoord7?: Float32Array;
234
248
  readonly tangents?: Float32Array;
235
- /** COLOR_0 importer carrier; bridge projects it to the canonical color attribute. */
249
+ /** COLOR_0 importer carrier. */
236
250
  readonly colors0?: Float32Array;
237
- /** JOINTS_0 per-vertex joint indices (Uint16Array, 4 per vertex). UBYTE source is width-converted to U16 at parse time (D-3). */
251
+ /** JOINTS_0 per-vertex joint indices. */
238
252
  readonly joints0?: Uint16Array;
239
- /** WEIGHTS_0 per-vertex skin weights (Float32Array, 4 per vertex). */
253
+ /** WEIGHTS_0 per-vertex skin weights. */
240
254
  readonly weights0?: Float32Array;
241
255
  readonly morphTargets?: readonly {
242
256
  readonly position?: Float32Array;
@@ -244,109 +258,17 @@ export interface GltfMeshIr {
244
258
  readonly tangent?: Float32Array;
245
259
  }[];
246
260
  readonly morphWeights?: Float32Array;
247
- /**
248
- * Optional per-glTF-spec: when omitted, primitive declares non-indexed
249
- * geometry (vertex buffer is consumed in vertex order, every 3 verts =
250
- * 1 triangle for triangle-list). bridge.ts handles undefined by routing
251
- * MeshAsset.indices to undefined and submesh.indexCount=0 (vertexCount
252
- * carries the draw count for `pass.draw(vertexCount)`).
253
- * bug-20260612 hello-skin visual layered gate: prior shape coerced
254
- * undefined into Uint16Array(0) and tripped MeshAsset.indices !==
255
- * undefined branches downstream into drawIndexed(0).
256
- * U16 (incl. widened U8) or U32 — width preserved from the source accessor;
257
- * bridge.ts narrows U32 to U16 when the merged maxIndex fits.
258
- */
261
+ /** Undefined means non-indexed; otherwise source width is preserved. */
259
262
  readonly indices?: Uint16Array | Uint32Array;
260
263
  readonly materialIndex: number | null;
261
- /**
262
- * Owning glTF mesh index in the original document (`gltf.meshes[meshIndex]`).
263
- * After OOS-7 flattening, multiple GltfMeshIr entries may share the same
264
- * meshIndex (one per primitive). Bridge layer uses this to filter material
265
- * collection per node, fixing the verify-r1 charter-defect where multi
266
- * glTF-mesh documents would silently misalign materials across nodes.
267
- */
264
+ /** Owning glTF mesh index; multiple IR rows may share it after flattening. */
268
265
  readonly meshIndex: number;
269
266
  }
270
267
 
271
- export interface GltfTextureIr {
272
- readonly sampler?: number;
273
- readonly source: number;
274
- readonly name?: string;
275
- }
276
-
277
- export interface GltfTextureTransformIr {
278
- readonly offset?: readonly [number, number];
279
- readonly rotation?: number;
280
- readonly scale?: readonly [number, number];
281
- }
282
-
283
- export interface GltfTextureInfoIr {
284
- readonly texture: number;
285
- readonly sampler?: number;
286
- readonly texCoord?: number;
287
- readonly transform?: GltfTextureTransformIr;
288
- }
289
-
290
- export interface GltfNormalTextureInfoIr extends GltfTextureInfoIr {
291
- readonly scale?: number;
292
- }
293
-
294
- export interface GltfOcclusionTextureInfoIr extends GltfTextureInfoIr {
295
- readonly strength?: number;
296
- }
297
-
298
- export interface GltfImageIr {
299
- readonly uri?: string;
300
- readonly mimeType?: string;
301
- readonly bufferView?: number;
302
- readonly name?: string;
303
- }
304
-
305
- export interface GltfSamplerIr {
306
- readonly magFilter?: number;
307
- readonly minFilter?: number;
308
- readonly wrapS: number;
309
- readonly wrapT: number;
310
- readonly name?: string;
311
- }
312
-
313
- export interface GltfMaterialIr {
314
- readonly name?: string;
315
- readonly baseColorFactor: readonly [number, number, number, number];
316
- /** glTF emissiveFactor; omitted means the glTF default [0, 0, 0]. */
317
- readonly emissiveFactor?: readonly [number, number, number];
318
- readonly baseColorTexture?: GltfTextureInfoIr | number;
319
- /** Texture binding for the emissive map. */
320
- readonly emissiveTexture?: GltfTextureInfoIr | number;
321
- readonly metallicFactor: number;
322
- readonly roughnessFactor: number;
323
- readonly metallicRoughnessTexture?: GltfTextureInfoIr | number;
324
- readonly normalTexture?: GltfNormalTextureInfoIr | number;
325
- readonly occlusionTexture?: GltfOcclusionTextureInfoIr | number;
326
- /**
327
- * glTF `alphaMode` (`'OPAQUE'` | `'MASK'` | `'BLEND'`). Absent -> `'OPAQUE'`
328
- * (glTF spec default). Drives the bridge's transparent / alpha-test routing.
329
- */
330
- readonly alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
331
- /** glTF `alphaCutoff` (MASK-mode threshold; glTF spec default 0.5). */
332
- readonly alphaCutoff?: number;
333
- /**
334
- * glTF `doubleSided`. Absent -> false (the glTF spec default). The bridge
335
- * maps this to a no-cull material pass so reverse-facing glass and cards
336
- * remain visible without changing their authored winding.
337
- */
338
- readonly doubleSided?: boolean;
339
- /**
340
- * baseColorTexture UV-set index (glTF `baseColorTexture.texCoord`). Absent
341
- * -> 0. Selects which interleaved UV set the base-color sampler reads.
342
- */
343
- readonly baseColorTexCoord?: number;
344
- }
345
-
346
268
  export interface NodeInstancingIr {
347
- /** Number of instances. All TRS attribute accessors share this count. */
269
+ /** Number of instances. */
348
270
  readonly count: number;
349
- /** N column-major mat4 transforms packed into one Float32Array (length = N*16). */
271
+ /** Column-major transforms packed into one Float32Array. */
350
272
  readonly transforms: Float32Array;
351
273
  }
352
274
 
@@ -367,16 +289,12 @@ export interface GltfNodeIr {
367
289
  readonly name?: string;
368
290
  readonly transform: DecomposedTransform;
369
291
  readonly meshIndex: number | null;
370
- /** Node-authored morph weights override mesh defaults at instantiation. */
292
+ /** Node-authored morph weights override mesh defaults. */
371
293
  readonly morphWeights?: Float32Array;
372
- /** Valid when node carries `skin` reference. Null when no skin. */
373
294
  readonly skinIndex: number | null;
374
295
  readonly children: readonly number[];
375
- /** Present when node carries EXT_mesh_gpu_instancing (feat-20260518). */
376
296
  readonly instancing?: NodeInstancingIr;
377
- /** glTF camera index. Null when the node does not reference a camera. */
378
297
  readonly camera: number | null;
379
- /** KHR_lights_punctual light index. Null when the node has no light. */
380
298
  readonly lightIndex?: number | null;
381
299
  }
382
300
 
@@ -430,29 +348,6 @@ interface BuffersJson {
430
348
  readonly uri?: string;
431
349
  }
432
350
 
433
- interface TextureTransformJson {
434
- readonly offset?: readonly number[];
435
- readonly rotation?: number;
436
- readonly scale?: readonly number[];
437
- readonly texCoord?: number;
438
- }
439
-
440
- interface TextureInfoJson {
441
- readonly index: number;
442
- readonly texCoord?: number;
443
- readonly extensions?: {
444
- readonly KHR_texture_transform?: TextureTransformJson;
445
- };
446
- }
447
-
448
- interface NormalTextureInfoJson extends TextureInfoJson {
449
- readonly scale?: number;
450
- }
451
-
452
- interface OcclusionTextureInfoJson extends TextureInfoJson {
453
- readonly strength?: number;
454
- }
455
-
456
351
  interface RootGltfJson extends GltfExtensionsJson {
457
352
  readonly asset?: { readonly version?: string };
458
353
  readonly scene?: number;
@@ -501,23 +396,7 @@ interface RootGltfJson extends GltfExtensionsJson {
501
396
  readonly inverseBindMatrices?: number;
502
397
  }>;
503
398
  readonly meshes?: readonly MeshJson[];
504
- readonly materials?: ReadonlyArray<{
505
- readonly name?: string;
506
- readonly pbrMetallicRoughness?: {
507
- readonly baseColorFactor?: readonly number[];
508
- readonly baseColorTexture?: TextureInfoJson;
509
- readonly metallicFactor?: number;
510
- readonly roughnessFactor?: number;
511
- readonly metallicRoughnessTexture?: TextureInfoJson;
512
- };
513
- readonly normalTexture?: NormalTextureInfoJson;
514
- readonly emissiveFactor?: readonly number[];
515
- readonly emissiveTexture?: TextureInfoJson;
516
- readonly occlusionTexture?: OcclusionTextureInfoJson;
517
- readonly alphaMode?: string;
518
- readonly alphaCutoff?: number;
519
- readonly doubleSided?: boolean;
520
- }>;
399
+ readonly materials?: ReadonlyArray<GltfMaterialJson>;
521
400
  readonly textures?: ReadonlyArray<{
522
401
  readonly sampler?: number;
523
402
  readonly source?: number;
@@ -556,37 +435,6 @@ interface RootGltfJson extends GltfExtensionsJson {
556
435
  }>;
557
436
  }
558
437
 
559
- function tuple2(values: readonly number[] | undefined): readonly [number, number] | undefined {
560
- if (values === undefined || values.length < 2) return undefined;
561
- return [values[0] ?? 0, values[1] ?? 0];
562
- }
563
-
564
- function parseTextureInfo(
565
- info: TextureInfoJson,
566
- textures: readonly GltfTextureIr[],
567
- ): GltfTextureInfoIr {
568
- const transformJson = info.extensions?.KHR_texture_transform;
569
- const offset = tuple2(transformJson?.offset);
570
- const scale = tuple2(transformJson?.scale);
571
- const transform =
572
- offset === undefined && transformJson?.rotation === undefined && scale === undefined
573
- ? undefined
574
- : {
575
- ...(offset === undefined ? {} : { offset }),
576
- ...(transformJson?.rotation === undefined ? {} : { rotation: transformJson.rotation }),
577
- ...(scale === undefined ? {} : { scale }),
578
- };
579
- const sampler = textures[info.index]?.sampler;
580
- return {
581
- texture: info.index,
582
- ...(sampler === undefined ? {} : { sampler }),
583
- ...(info.texCoord === undefined && transformJson?.texCoord === undefined
584
- ? {}
585
- : { texCoord: transformJson?.texCoord ?? info.texCoord }),
586
- ...(transform === undefined ? {} : { transform }),
587
- };
588
- }
589
-
590
438
  export type ExternalLoader = (uri: string) => Promise<ArrayBuffer>;
591
439
 
592
440
  async function resolveBuffer(
@@ -757,14 +605,7 @@ async function parseGltfWithBin(
757
605
  buffers.splice(0, buffers.length, ...projected.value.buffers);
758
606
 
759
607
  const meshes: GltfMeshIr[] = [];
760
- // feat-20260608 round-2: surface the original-glTF-mesh primitive counts so
761
- // build-time consumers (gltfImporter scene arm) can hand them to
762
- // gltfDocToSceneAsset's `meshPrimitiveCount` parameter without re-parsing
763
- // the JSON. Without this, importGltf at gltf-importer.ts ~272 walks the
764
- // already-flattened doc.meshes (one entry per primitive) and cannot
765
- // reconstruct the gltfMesh -> primitiveCount map; the bridge then uses
766
- // identity (gltfMeshIdx == flatIdx) and any multi-primitive .gltf imports
767
- // misindex meshes after the first primitive.
608
+ // Preserve original mesh -> primitive counts for the flattened IR.
768
609
  const meshPrimitiveCount = new Map<number, number>();
769
610
  for (let meshIndex = 0; meshIndex < meshesJson.length; meshIndex++) {
770
611
  const meshJson = meshesJson[meshIndex];
@@ -826,9 +667,7 @@ async function parseGltfWithBin(
826
667
  const positions = new Float32Array(positionsDecoded.length);
827
668
  positions.set(positionsDecoded);
828
669
 
829
- // Decode optional vertex attributes: NORMAL (VEC3), TEXCOORD_0 (VEC2),
830
- // TANGENT (VEC4). Each uses the existing decodeAttributeAccessor helper.
831
- // Missing attributes leave the GltfMeshIr field undefined.
670
+ // Decode optional NORMAL, TEXCOORD_0, and TANGENT attributes.
832
671
  const attrs = prim.attributes ?? {};
833
672
 
834
673
  let colors0: Float32Array | undefined;
@@ -1165,12 +1004,6 @@ async function parseGltfWithBin(
1165
1004
  owned.set(src);
1166
1005
  indices = owned;
1167
1006
  } else if (indexDecoded.value.kind === 'u32') {
1168
- // U32 indices ride through end-to-end: MeshAsset.indices is
1169
- // `Uint16Array | Uint32Array`, mesh-bin serializes iwidth=4, and
1170
- // the GPU runtime auto-selects 'uint32' via `instanceof Uint32Array`
1171
- // (createRenderer.ts). bridge.ts merges by value, so small meshes
1172
- // whose maxIndex < 65536 are losslessly narrowed to Uint16 there.
1173
- // Re-allocate over a fresh ArrayBuffer for the same reason as u16.
1174
1007
  const src = indexDecoded.value.data;
1175
1008
  const owned = new Uint32Array(src.length);
1176
1009
  owned.set(src);
@@ -1263,70 +1096,12 @@ async function parseGltfWithBin(
1263
1096
  });
1264
1097
  }
1265
1098
 
1266
- // Materials (Tier-C subset: metallic-roughness PBR + normal/emissive maps).
1267
- const materialsJson = json.materials ?? [];
1099
+ // Materials are normalized by the material parser owner before entering the IR.
1268
1100
  const materials: GltfMaterialIr[] = [];
1269
- for (const matJson of materialsJson) {
1270
- const pbr = matJson.pbrMetallicRoughness;
1271
- const baseColor = pbr?.baseColorFactor ?? [1, 1, 1, 1];
1272
- const baseColor4: readonly [number, number, number, number] = [
1273
- baseColor[0] ?? 1,
1274
- baseColor[1] ?? 1,
1275
- baseColor[2] ?? 1,
1276
- baseColor[3] ?? 1,
1277
- ];
1278
-
1279
- const alphaMode =
1280
- matJson.alphaMode === 'MASK' || matJson.alphaMode === 'BLEND' ? matJson.alphaMode : undefined;
1281
- const alphaCutoff = alphaMode === 'MASK' ? (matJson.alphaCutoff ?? 0.5) : undefined;
1282
- const emissiveFactor = matJson.emissiveFactor;
1283
- const emissiveFactor3: readonly [number, number, number] = [
1284
- emissiveFactor?.[0] ?? 0,
1285
- emissiveFactor?.[1] ?? 0,
1286
- emissiveFactor?.[2] ?? 0,
1287
- ];
1288
- materials.push({
1289
- ...(matJson.name === undefined ? {} : { name: matJson.name }),
1290
- baseColorFactor: baseColor4,
1291
- ...(emissiveFactor === undefined ? {} : { emissiveFactor: emissiveFactor3 }),
1292
- metallicFactor: pbr?.metallicFactor ?? 1.0,
1293
- roughnessFactor: pbr?.roughnessFactor ?? 1.0,
1294
- ...(pbr?.baseColorTexture === undefined
1295
- ? {}
1296
- : { baseColorTexture: parseTextureInfo(pbr.baseColorTexture, textures) }),
1297
- ...(pbr?.metallicRoughnessTexture === undefined
1298
- ? {}
1299
- : { metallicRoughnessTexture: parseTextureInfo(pbr.metallicRoughnessTexture, textures) }),
1300
- ...(matJson.normalTexture === undefined
1301
- ? {}
1302
- : {
1303
- normalTexture: {
1304
- ...parseTextureInfo(matJson.normalTexture, textures),
1305
- ...(matJson.normalTexture.scale === undefined
1306
- ? {}
1307
- : { scale: matJson.normalTexture.scale }),
1308
- },
1309
- }),
1310
- ...(matJson.occlusionTexture === undefined
1311
- ? {}
1312
- : {
1313
- occlusionTexture: {
1314
- ...parseTextureInfo(matJson.occlusionTexture, textures),
1315
- ...(matJson.occlusionTexture.strength === undefined
1316
- ? {}
1317
- : { strength: matJson.occlusionTexture.strength }),
1318
- },
1319
- }),
1320
- ...(matJson.emissiveTexture === undefined
1321
- ? {}
1322
- : { emissiveTexture: parseTextureInfo(matJson.emissiveTexture, textures) }),
1323
- ...(alphaMode === undefined ? {} : { alphaMode }),
1324
- ...(alphaCutoff === undefined ? {} : { alphaCutoff }),
1325
- ...(matJson.doubleSided === true ? { doubleSided: true } : {}),
1326
- ...(pbr?.baseColorTexture?.texCoord === undefined || pbr.baseColorTexture.texCoord === 0
1327
- ? {}
1328
- : { baseColorTexCoord: pbr.baseColorTexture.texCoord }),
1329
- });
1101
+ for (const material of json.materials ?? []) {
1102
+ const parsedMaterial = parseMaterial(material, textures);
1103
+ if (!parsedMaterial.ok) return err(parsedMaterial.error);
1104
+ materials.push(parsedMaterial.value);
1330
1105
  }
1331
1106
 
1332
1107
  // Nodes + diagnostics.
@@ -1725,7 +1500,5 @@ export function toAssetPack(
1725
1500
  return ok({ meta, subAssets: reuse.value.subAssets });
1726
1501
  }
1727
1502
 
1728
- // Suppress unused-import warning; COMPONENT_TYPE is re-exported so
1729
- // downstream consumers can grep the constants table from a single
1730
- // import. (TS strict mode would otherwise drop the binding.)
1503
+ // Keep the component-type table available from this importer module.
1731
1504
  export { COMPONENT_TYPE };