@plasius/gpu-model-core 0.2.0 → 0.3.0

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/dist/index.cjs CHANGED
@@ -25,6 +25,8 @@ __export(index_exports, {
25
25
  GPU_MODEL_DOCUMENT_LIMITS: () => GPU_MODEL_DOCUMENT_LIMITS,
26
26
  GPU_MODEL_DOCUMENT_SCHEMA_VERSION: () => GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
27
27
  GPU_MODEL_REPAIR_RULES: () => GPU_MODEL_REPAIR_RULES,
28
+ GPU_MODEL_RESOURCE_GRAPH_LIMITS: () => GPU_MODEL_RESOURCE_GRAPH_LIMITS,
29
+ GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION: () => GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
28
30
  GPU_MODEL_RESOURCE_VERIFICATION_LIMITS: () => GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
29
31
  GPU_MODEL_STATIC_DEMO_FEATURE_FLAG: () => GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
30
32
  GPU_MODEL_STATIC_DEMO_LIMITS: () => GPU_MODEL_STATIC_DEMO_LIMITS,
@@ -35,15 +37,19 @@ __export(index_exports, {
35
37
  GPU_MODEL_STATIC_DEMO_PROFILE_VERSION: () => GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
36
38
  GpuModelDiagnosticsError: () => GpuModelDiagnosticsError,
37
39
  GpuModelDocumentError: () => GpuModelDocumentError,
40
+ GpuModelResourceGraphError: () => GpuModelResourceGraphError,
38
41
  assertGpuModelStaticDemoNormalization: () => assertGpuModelStaticDemoNormalization,
39
42
  canCreateGpuModelDocument: () => canCreateGpuModelDocument,
40
43
  createAndVerifyGpuModelDocument: () => createAndVerifyGpuModelDocument,
41
44
  createGpuModelDocument: () => createGpuModelDocument,
45
+ createGpuModelResourceGraph: () => createGpuModelResourceGraph,
42
46
  createGpuModelStaticDemoCompilerInput: () => createGpuModelStaticDemoCompilerInput,
43
47
  encodeGpuModelDocumentCanonical: () => encodeGpuModelDocumentCanonical,
44
48
  evaluateGpuModelDiagnostics: () => evaluateGpuModelDiagnostics,
49
+ gpuModelResourceKey: () => gpuModelResourceKey,
45
50
  hashGpuModelDocumentCanonical: () => hashGpuModelDocumentCanonical,
46
51
  isGpuModelDocument: () => isGpuModelDocument,
52
+ isGpuModelResourceGraph: () => isGpuModelResourceGraph,
47
53
  packageBootstrap: () => packageBootstrap,
48
54
  packageName: () => packageName,
49
55
  verifyGpuModelResource: () => verifyGpuModelResource
@@ -224,9 +230,9 @@ function readRecord(value, path, allowedKeys) {
224
230
  }
225
231
  return value;
226
232
  }
227
- function requireKey(record2, key, path) {
228
- if (!Object.hasOwn(record2, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
229
- return record2[key];
233
+ function requireKey(record3, key, path) {
234
+ if (!Object.hasOwn(record3, key)) return fail("invalid-value", `${path}.${key}`, "required key is missing");
235
+ return record3[key];
230
236
  }
231
237
  function readArray(value, path, maximum, minimum = 0) {
232
238
  if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) {
@@ -305,9 +311,9 @@ function readNumberTuple(value, path, length, minimum, maximum) {
305
311
  const input = readArray(value, path, length, length);
306
312
  return Object.freeze(input.map((entry, index) => readFinite(entry, `${path}[${index}]`, minimum, maximum)));
307
313
  }
308
- function readOptional(record2, key, parser, path) {
309
- if (!Object.hasOwn(record2, key)) return void 0;
310
- return parser(record2[key], `${path}.${key}`);
314
+ function readOptional(record3, key, parser, path) {
315
+ if (!Object.hasOwn(record3, key)) return void 0;
316
+ return parser(record3[key], `${path}.${key}`);
311
317
  }
312
318
  function assignOptional(target, key, value) {
313
319
  if (value !== void 0) Object.assign(target, { [key]: value });
@@ -329,17 +335,17 @@ function readMetadataValue(value, path, depth, budget) {
329
335
  return Object.freeze(input.map((entry, index) => readMetadataValue(entry, `${path}[${index}]`, depth + 1, budget)));
330
336
  }
331
337
  if (typeof value !== "object" || value === null) return fail("invalid-type", path, "metadata value is not JSON-compatible");
332
- const record2 = value;
333
- const prototype = Object.getPrototypeOf(record2);
338
+ const record3 = value;
339
+ const prototype = Object.getPrototypeOf(record3);
334
340
  if (prototype !== Object.prototype && prototype !== null) return fail("invalid-type", path, "metadata objects must be plain objects");
335
- const keys = Reflect.ownKeys(record2);
341
+ const keys = Reflect.ownKeys(record3);
336
342
  if (keys.length > GPU_MODEL_DOCUMENT_LIMITS.metadataObjectKeys) return fail("limit-exceeded", path, "metadata object has too many keys");
337
343
  const output = {};
338
344
  for (const key of keys) {
339
345
  if (typeof key !== "string") return fail("unknown-key", path, "metadata symbol keys are not permitted");
340
346
  if (PROHIBITED_KEYS.has(key)) return fail("unknown-key", `${path}.${key}`, "prohibited metadata key");
341
347
  readString(key, `${path}.[key]`, GPU_MODEL_DOCUMENT_LIMITS.metadataKeyLength, METADATA_KEY);
342
- const descriptor = Object.getOwnPropertyDescriptor(record2, key);
348
+ const descriptor = Object.getOwnPropertyDescriptor(record3, key);
343
349
  if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) return fail("invalid-type", `${path}.${key}`, "metadata properties must be enumerable data properties");
344
350
  output[key] = readMetadataValue(descriptor.value, `${path}.${key}`, depth + 1, budget);
345
351
  }
@@ -352,9 +358,9 @@ function readMetadata(value, path, budget) {
352
358
  }
353
359
  function parseCoordinateSystem(value) {
354
360
  const path = "$.coordinateSystem";
355
- const record2 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
361
+ const record3 = readRecord(value, path, ["unit", "upAxis", "forwardAxis", "handedness", "winding", "origin"]);
356
362
  for (const [key, expected] of Object.entries(CANONICAL_GPU_MODEL_COORDINATE_SYSTEM)) {
357
- if (requireKey(record2, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
363
+ if (requireKey(record3, key, path) !== expected) return fail("invalid-value", `${path}.${key}`, "coordinate system is not canonical");
358
364
  }
359
365
  return CANONICAL_GPU_MODEL_COORDINATE_SYSTEM;
360
366
  }
@@ -366,19 +372,19 @@ function parseMatrix(value, path, requireAffine = true) {
366
372
  return matrix;
367
373
  }
368
374
  function parseBounds(value, path) {
369
- const record2 = readRecord(value, path, ["min", "max"]);
370
- const min = readNumberTuple(requireKey(record2, "min", path), `${path}.min`, 3);
371
- const max = readNumberTuple(requireKey(record2, "max", path), `${path}.max`, 3);
375
+ const record3 = readRecord(value, path, ["min", "max"]);
376
+ const min = readNumberTuple(requireKey(record3, "min", path), `${path}.min`, 3);
377
+ const max = readNumberTuple(requireKey(record3, "max", path), `${path}.max`, 3);
372
378
  for (let index = 0; index < 3; index += 1) {
373
379
  if (min[index] > max[index]) return fail("invalid-value", path, "bounds min cannot exceed max");
374
380
  }
375
381
  return { min, max };
376
382
  }
377
383
  function parseTransformTrs(value, path) {
378
- const record2 = readRecord(value, path, ["translation", "rotation", "scale"]);
379
- const translation = readNumberTuple(requireKey(record2, "translation", path), `${path}.translation`, 3);
380
- const rotation = readNumberTuple(requireKey(record2, "rotation", path), `${path}.rotation`, 4);
381
- const scale = readNumberTuple(requireKey(record2, "scale", path), `${path}.scale`, 3);
384
+ const record3 = readRecord(value, path, ["translation", "rotation", "scale"]);
385
+ const translation = readNumberTuple(requireKey(record3, "translation", path), `${path}.translation`, 3);
386
+ const rotation = readNumberTuple(requireKey(record3, "rotation", path), `${path}.rotation`, 4);
387
+ const scale = readNumberTuple(requireKey(record3, "scale", path), `${path}.scale`, 3);
382
388
  const magnitude = Math.hypot(...rotation);
383
389
  if (!almostEqual(magnitude, 1, 1e-4)) return fail("invalid-value", `${path}.rotation`, "rotation quaternion must be normalized");
384
390
  return { translation, rotation, scale };
@@ -400,25 +406,25 @@ function intrinsicBlobArrayBuffer(blob) {
400
406
  return Reflect.apply(blobArrayBuffer, blob, []);
401
407
  }
402
408
  function parseResourceHeader(value, path) {
403
- const record2 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
404
- const kind = readEnum(requireKey(record2, "kind", path), `${path}.kind`, ["buffer", "image"]);
405
- const mimeType = readString(requireKey(record2, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
409
+ const record3 = readRecord(value, path, ["id", "kind", "contentHash", "byteLength", "mimeType", "payload"]);
410
+ const kind = readEnum(requireKey(record3, "kind", path), `${path}.kind`, ["buffer", "image"]);
411
+ const mimeType = readString(requireKey(record3, "mimeType", path), `${path}.mimeType`, 128, MIME_TYPE).toLowerCase();
406
412
  if (kind === "image" ? !IMAGE_MIME_TYPES.has(mimeType) : !BUFFER_MIME_TYPES.has(mimeType)) {
407
413
  return fail("invalid-value", `${path}.mimeType`, `unsupported ${kind} MIME type`);
408
414
  }
409
- const payload = requireKey(record2, "payload", path);
415
+ const payload = requireKey(record3, "payload", path);
410
416
  if (!(payload instanceof Blob)) return fail("invalid-type", `${path}.payload`, "expected an immutable Blob payload");
411
417
  if (Object.getPrototypeOf(payload) !== Blob.prototype || Reflect.ownKeys(payload).some((key) => typeof key === "string")) {
412
418
  return fail("invalid-type", `${path}.payload`, "expected a native Blob without caller-defined properties");
413
419
  }
414
- const byteLength = readSafeInteger(requireKey(record2, "byteLength", path), `${path}.byteLength`, 1);
420
+ const byteLength = readSafeInteger(requireKey(record3, "byteLength", path), `${path}.byteLength`, 1);
415
421
  if (intrinsicBlobSize(payload) !== byteLength) return fail("payload-length-mismatch", `${path}.payload`, "payload byte length does not match byteLength");
416
422
  const payloadType = intrinsicBlobType(payload);
417
423
  if (payloadType && payloadType.toLowerCase() !== mimeType) return fail("invalid-value", `${path}.payload`, "payload MIME type does not match mimeType");
418
424
  return {
419
- id: readId(requireKey(record2, "id", path), `${path}.id`),
425
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
420
426
  kind,
421
- contentHash: readSha256(requireKey(record2, "contentHash", path), `${path}.contentHash`),
427
+ contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
422
428
  byteLength,
423
429
  mimeType,
424
430
  payload
@@ -427,11 +433,11 @@ function parseResourceHeader(value, path) {
427
433
  function resolveVerificationLimits(options) {
428
434
  const overrides = options?.limits;
429
435
  if (overrides === void 0) return GPU_MODEL_RESOURCE_VERIFICATION_LIMITS;
430
- const record2 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
436
+ const record3 = readRecord(overrides, "$.verification.limits", Object.keys(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS));
431
437
  const output = { ...GPU_MODEL_RESOURCE_VERIFICATION_LIMITS };
432
438
  for (const [key, ceiling] of Object.entries(GPU_MODEL_RESOURCE_VERIFICATION_LIMITS)) {
433
- if (!Object.hasOwn(record2, key)) continue;
434
- const value = readSafeInteger(record2[key], `$.verification.limits.${key}`, 1, ceiling);
439
+ if (!Object.hasOwn(record3, key)) continue;
440
+ const value = readSafeInteger(record3[key], `$.verification.limits.${key}`, 1, ceiling);
435
441
  Object.assign(output, { [key]: value });
436
442
  }
437
443
  if (output.maxImageBytes > output.maxResourceBytes) output.maxImageBytes = output.maxResourceBytes;
@@ -543,13 +549,13 @@ function createVerificationDeadline(timeoutMs, callerSignal, path) {
543
549
  };
544
550
  }
545
551
  function parseInspection(value, path) {
546
- const record2 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
552
+ const record3 = readRecord(value, path, ["valid", "detectedMimeType", "width", "height"]);
547
553
  const output = {
548
- valid: readBoolean(requireKey(record2, "valid", path), `${path}.valid`),
549
- detectedMimeType: readString(requireKey(record2, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
554
+ valid: readBoolean(requireKey(record3, "valid", path), `${path}.valid`),
555
+ detectedMimeType: readString(requireKey(record3, "detectedMimeType", path), `${path}.detectedMimeType`, 128, MIME_TYPE).toLowerCase()
550
556
  };
551
- assignOptional(output, "width", readOptional(record2, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
552
- assignOptional(output, "height", readOptional(record2, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
557
+ assignOptional(output, "width", readOptional(record3, "width", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
558
+ assignOptional(output, "height", readOptional(record3, "height", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
553
559
  return output;
554
560
  }
555
561
  function bytesStartWith(bytes, signature) {
@@ -664,31 +670,31 @@ async function verifyGpuModelResource(value, port, options) {
664
670
  }
665
671
  }
666
672
  function parseNode(value, path, budget) {
667
- const record2 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
668
- const children = Object.freeze(readArray(requireKey(record2, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
673
+ const record3 = readRecord(value, path, ["id", "name", "children", "meshId", "skinId", "localMatrix", "metadata"]);
674
+ const children = Object.freeze(readArray(requireKey(record3, "children", path), `${path}.children`, GPU_MODEL_DOCUMENT_LIMITS.nodes).map((entry, index) => readId(entry, `${path}.children[${index}]`)));
669
675
  consumeBudget(budget, "sceneEdges", children.length, GPU_MODEL_DOCUMENT_LIMITS.sceneEdges, path, "scene edge");
670
676
  ensureUnique(children, `${path}.children`, "duplicate child node");
671
677
  const output = {
672
- id: readId(requireKey(record2, "id", path), `${path}.id`),
678
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
673
679
  children,
674
- localMatrix: parseMatrix(requireKey(record2, "localMatrix", path), `${path}.localMatrix`)
680
+ localMatrix: parseMatrix(requireKey(record3, "localMatrix", path), `${path}.localMatrix`)
675
681
  };
676
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
677
- assignOptional(output, "meshId", readOptional(record2, "meshId", readId, path));
678
- assignOptional(output, "skinId", readOptional(record2, "skinId", readId, path));
679
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
682
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
683
+ assignOptional(output, "meshId", readOptional(record3, "meshId", readId, path));
684
+ assignOptional(output, "skinId", readOptional(record3, "skinId", readId, path));
685
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
680
686
  return output;
681
687
  }
682
688
  function parseAccessor(value, path, budget) {
683
- const record2 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
684
- const componentType = readEnum(requireKey(record2, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
685
- const elementType = readEnum(requireKey(record2, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
686
- const normalized = readOptional(record2, "normalized", readBoolean, path);
689
+ const record3 = readRecord(value, path, ["id", "resourceId", "byteOffset", "byteStride", "count", "componentType", "elementType", "normalized", "min", "max"]);
690
+ const componentType = readEnum(requireKey(record3, "componentType", path), `${path}.componentType`, ["i8", "u8", "i16", "u16", "u32", "f32"]);
691
+ const elementType = readEnum(requireKey(record3, "elementType", path), `${path}.elementType`, ["scalar", "vec2", "vec3", "vec4", "mat2", "mat3", "mat4"]);
692
+ const normalized = readOptional(record3, "normalized", readBoolean, path);
687
693
  if (normalized === true && componentType === "f32") return fail("invalid-value", `${path}.normalized`, "floating-point accessors cannot be normalized");
688
694
  if (elementType.startsWith("mat") && componentType !== "f32") return fail("invalid-value", `${path}.componentType`, "matrix accessors must use f32 components");
689
695
  const expectedComponents = ELEMENT_COMPONENTS[elementType];
690
- const min = readOptional(record2, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
691
- const max = readOptional(record2, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
696
+ const min = readOptional(record3, "min", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
697
+ const max = readOptional(record3, "max", (entry, entryPath) => readNumberTuple(entry, entryPath, expectedComponents), path);
692
698
  if (min === void 0 !== (max === void 0)) return fail("invalid-value", path, "accessor min and max must be supplied together");
693
699
  if (min && max) {
694
700
  const [componentMinimum, componentMaximum, integer] = COMPONENT_RANGES[componentType];
@@ -701,25 +707,25 @@ function parseAccessor(value, path, budget) {
701
707
  }
702
708
  }
703
709
  }
704
- const count = readSafeInteger(requireKey(record2, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
710
+ const count = readSafeInteger(requireKey(record3, "count", path), `${path}.count`, 1, GPU_MODEL_DOCUMENT_LIMITS.weightVertices);
705
711
  consumeBudget(budget, "accessorElements", count * expectedComponents, GPU_MODEL_DOCUMENT_LIMITS.accessorElements, path, "accessor element component");
706
712
  const output = {
707
- id: readId(requireKey(record2, "id", path), `${path}.id`),
708
- resourceId: readId(requireKey(record2, "resourceId", path), `${path}.resourceId`),
709
- byteOffset: readSafeInteger(requireKey(record2, "byteOffset", path), `${path}.byteOffset`),
713
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
714
+ resourceId: readId(requireKey(record3, "resourceId", path), `${path}.resourceId`),
715
+ byteOffset: readSafeInteger(requireKey(record3, "byteOffset", path), `${path}.byteOffset`),
710
716
  count,
711
717
  componentType,
712
718
  elementType
713
719
  };
714
- assignOptional(output, "byteStride", readOptional(record2, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
720
+ assignOptional(output, "byteStride", readOptional(record3, "byteStride", (entry, entryPath) => readSafeInteger(entry, entryPath, 1, 2048), path));
715
721
  assignOptional(output, "normalized", normalized);
716
722
  assignOptional(output, "min", min);
717
723
  assignOptional(output, "max", max);
718
724
  return output;
719
725
  }
720
726
  function parsePrimitive(value, path, budget) {
721
- const record2 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
722
- const attributesInput = readArray(requireKey(record2, "attributes", path), `${path}.attributes`, 64, 1);
727
+ const record3 = readRecord(value, path, ["id", "topology", "attributes", "indicesAccessorId", "materialId", "metadata"]);
728
+ const attributesInput = readArray(requireKey(record3, "attributes", path), `${path}.attributes`, 64, 1);
723
729
  consumeBudget(budget, "primitiveAttributes", attributesInput.length, GPU_MODEL_DOCUMENT_LIMITS.primitiveAttributes, path, "primitive attribute");
724
730
  const attributes = Object.freeze(attributesInput.map((entry, index) => {
725
731
  const entryPath = `${path}.attributes[${index}]`;
@@ -732,53 +738,53 @@ function parsePrimitive(value, path, budget) {
732
738
  ensureUnique(attributes.map(({ semantic }) => semantic), `${path}.attributes`, "duplicate attribute semantic");
733
739
  if (!attributes.some(({ semantic }) => semantic === "POSITION")) return fail("invalid-value", `${path}.attributes`, "primitive requires POSITION");
734
740
  const output = {
735
- id: readId(requireKey(record2, "id", path), `${path}.id`),
736
- topology: readEnum(requireKey(record2, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
741
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
742
+ topology: readEnum(requireKey(record3, "topology", path), `${path}.topology`, ["points", "lines", "line-strip", "triangles", "triangle-strip"]),
737
743
  attributes
738
744
  };
739
- assignOptional(output, "indicesAccessorId", readOptional(record2, "indicesAccessorId", readId, path));
740
- assignOptional(output, "materialId", readOptional(record2, "materialId", readId, path));
741
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
745
+ assignOptional(output, "indicesAccessorId", readOptional(record3, "indicesAccessorId", readId, path));
746
+ assignOptional(output, "materialId", readOptional(record3, "materialId", readId, path));
747
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
742
748
  return output;
743
749
  }
744
750
  function parseMesh(value, path, budget) {
745
- const record2 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
746
- const input = readArray(requireKey(record2, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
751
+ const record3 = readRecord(value, path, ["id", "name", "primitives", "metadata"]);
752
+ const input = readArray(requireKey(record3, "primitives", path), `${path}.primitives`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
747
753
  consumeBudget(budget, "primitives", input.length, GPU_MODEL_DOCUMENT_LIMITS.primitives, path, "primitive");
748
754
  const output = {
749
- id: readId(requireKey(record2, "id", path), `${path}.id`),
755
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
750
756
  primitives: Object.freeze(input.map((entry, index) => parsePrimitive(entry, `${path}.primitives[${index}]`, budget)))
751
757
  };
752
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
753
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
758
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
759
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
754
760
  return output;
755
761
  }
756
762
  function parseTextureTransform(value, path) {
757
- const record2 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
758
- if (Reflect.ownKeys(record2).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
763
+ const record3 = readRecord(value, path, ["offset", "rotationRadians", "scale"]);
764
+ if (Reflect.ownKeys(record3).length === 0) return fail("invalid-value", path, "texture transform cannot be empty");
759
765
  const output = {};
760
- assignOptional(output, "offset", readOptional(record2, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
761
- assignOptional(output, "rotationRadians", readOptional(record2, "rotationRadians", readFinite, path));
762
- assignOptional(output, "scale", readOptional(record2, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
766
+ assignOptional(output, "offset", readOptional(record3, "offset", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
767
+ assignOptional(output, "rotationRadians", readOptional(record3, "rotationRadians", readFinite, path));
768
+ assignOptional(output, "scale", readOptional(record3, "scale", (entry, entryPath) => readNumberTuple(entry, entryPath, 2), path));
763
769
  return output;
764
770
  }
765
771
  function parseTextureBinding(value, path) {
766
- const record2 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
772
+ const record3 = readRecord(value, path, ["textureId", "samplerId", "texCoordSet", "transform", "colorSpace", "intendedUsage"]);
767
773
  const output = {
768
- textureId: readId(requireKey(record2, "textureId", path), `${path}.textureId`),
769
- colorSpace: readEnum(requireKey(record2, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
770
- intendedUsage: readEnum(requireKey(record2, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
774
+ textureId: readId(requireKey(record3, "textureId", path), `${path}.textureId`),
775
+ colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
776
+ intendedUsage: readEnum(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES)
771
777
  };
772
- assignOptional(output, "samplerId", readOptional(record2, "samplerId", readId, path));
773
- assignOptional(output, "texCoordSet", readOptional(record2, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
774
- assignOptional(output, "transform", readOptional(record2, "transform", parseTextureTransform, path));
778
+ assignOptional(output, "samplerId", readOptional(record3, "samplerId", readId, path));
779
+ assignOptional(output, "texCoordSet", readOptional(record3, "texCoordSet", (entry, entryPath) => readSafeInteger(entry, entryPath, 0, 7), path));
780
+ assignOptional(output, "transform", readOptional(record3, "transform", parseTextureTransform, path));
775
781
  return output;
776
782
  }
777
783
  function parseMaterialTextures(value, path) {
778
784
  const slots = Object.keys(MATERIAL_TEXTURE_SLOTS);
779
- const record2 = readRecord(value, path, slots);
785
+ const record3 = readRecord(value, path, slots);
780
786
  const output = {};
781
- for (const slot of slots) assignOptional(output, slot, readOptional(record2, slot, parseTextureBinding, path));
787
+ for (const slot of slots) assignOptional(output, slot, readOptional(record3, slot, parseTextureBinding, path));
782
788
  return output;
783
789
  }
784
790
  function parseMaterial(value, path, budget) {
@@ -804,88 +810,88 @@ function parseMaterial(value, path, budget) {
804
810
  "extensions",
805
811
  "sourceMetadata"
806
812
  ];
807
- const record2 = readRecord(value, path, keys);
808
- const alphaMode = readEnum(requireKey(record2, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
809
- const alphaCutoff = readOptional(record2, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
813
+ const record3 = readRecord(value, path, keys);
814
+ const alphaMode = readEnum(requireKey(record3, "alphaMode", path), `${path}.alphaMode`, ["opaque", "mask", "blend"]);
815
+ const alphaCutoff = readOptional(record3, "alphaCutoff", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path);
810
816
  if (alphaMode === "mask" && alphaCutoff === void 0) return fail("invalid-value", `${path}.alphaCutoff`, "mask materials require alphaCutoff");
811
817
  if (alphaMode !== "mask" && alphaCutoff !== void 0) return fail("invalid-value", `${path}.alphaCutoff`, "alphaCutoff is only valid for mask materials");
812
818
  const output = {
813
- id: readId(requireKey(record2, "id", path), `${path}.id`),
814
- workflow: readEnum(requireKey(record2, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
819
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
820
+ workflow: readEnum(requireKey(record3, "workflow", path), `${path}.workflow`, ["metallic-roughness", "specular-glossiness", "unlit", "custom"]),
815
821
  alphaMode,
816
- baseColorFactor: readNumberTuple(requireKey(record2, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
817
- textures: parseMaterialTextures(requireKey(record2, "textures", path), `${path}.textures`),
818
- extensions: readMetadata(requireKey(record2, "extensions", path), `${path}.extensions`, budget),
819
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
822
+ baseColorFactor: readNumberTuple(requireKey(record3, "baseColorFactor", path), `${path}.baseColorFactor`, 4, 0, 1),
823
+ textures: parseMaterialTextures(requireKey(record3, "textures", path), `${path}.textures`),
824
+ extensions: readMetadata(requireKey(record3, "extensions", path), `${path}.extensions`, budget),
825
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
820
826
  };
821
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
827
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
822
828
  assignOptional(output, "alphaCutoff", alphaCutoff);
823
- assignOptional(output, "doubleSided", readOptional(record2, "doubleSided", readBoolean, path));
829
+ assignOptional(output, "doubleSided", readOptional(record3, "doubleSided", readBoolean, path));
824
830
  for (const key of ["metallicFactor", "roughnessFactor", "transmissionFactor", "clearcoatFactor", "clearcoatRoughnessFactor", "sheenRoughnessFactor", "occlusionStrength"]) {
825
- assignOptional(output, key, readOptional(record2, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
831
+ assignOptional(output, key, readOptional(record3, key, (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
826
832
  }
827
- assignOptional(output, "normalScale", readOptional(record2, "normalScale", readFinite, path));
828
- assignOptional(output, "emissiveFactor", readOptional(record2, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
829
- assignOptional(output, "sheenColorFactor", readOptional(record2, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
833
+ assignOptional(output, "normalScale", readOptional(record3, "normalScale", readFinite, path));
834
+ assignOptional(output, "emissiveFactor", readOptional(record3, "emissiveFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0), path));
835
+ assignOptional(output, "sheenColorFactor", readOptional(record3, "sheenColorFactor", (entry, entryPath) => readNumberTuple(entry, entryPath, 3, 0, 1), path));
830
836
  return output;
831
837
  }
832
838
  function parseTextureSampler(value, path) {
833
- const record2 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
834
- const output = { id: readId(requireKey(record2, "id", path), `${path}.id`) };
835
- assignOptional(output, "wrapS", readOptional(record2, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
836
- assignOptional(output, "wrapT", readOptional(record2, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
837
- assignOptional(output, "minFilter", readOptional(record2, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
838
- assignOptional(output, "magFilter", readOptional(record2, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
839
- assignOptional(output, "mipFilter", readOptional(record2, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
839
+ const record3 = readRecord(value, path, ["id", "wrapS", "wrapT", "minFilter", "magFilter", "mipFilter"]);
840
+ const output = { id: readId(requireKey(record3, "id", path), `${path}.id`) };
841
+ assignOptional(output, "wrapS", readOptional(record3, "wrapS", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
842
+ assignOptional(output, "wrapT", readOptional(record3, "wrapT", (entry, entryPath) => readEnum(entry, entryPath, ["repeat", "mirrored-repeat", "clamp-to-edge"]), path));
843
+ assignOptional(output, "minFilter", readOptional(record3, "minFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
844
+ assignOptional(output, "magFilter", readOptional(record3, "magFilter", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
845
+ assignOptional(output, "mipFilter", readOptional(record3, "mipFilter", (entry, entryPath) => readEnum(entry, entryPath, ["nearest", "linear"]), path));
840
846
  return output;
841
847
  }
842
848
  function parseTexture(value, path, budget) {
843
- const record2 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
844
- const usages = Object.freeze(readArray(requireKey(record2, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
849
+ const record3 = readRecord(value, path, ["id", "fileName", "relativePath", "mimeType", "contentHash", "imageResourceId", "byteLength", "intendedUsage", "colorSpace", "sampler", "exportEmbeddingPolicy", "sourceMetadata"]);
850
+ const usages = Object.freeze(readArray(requireKey(record3, "intendedUsage", path), `${path}.intendedUsage`, TEXTURE_USAGES.length, 1).map((entry, index) => readEnum(entry, `${path}.intendedUsage[${index}]`, TEXTURE_USAGES)));
845
851
  ensureUnique(usages, `${path}.intendedUsage`, "duplicate intended texture usage");
846
852
  const output = {
847
- id: readId(requireKey(record2, "id", path), `${path}.id`),
848
- contentHash: readSha256(requireKey(record2, "contentHash", path), `${path}.contentHash`),
849
- imageResourceId: readId(requireKey(record2, "imageResourceId", path), `${path}.imageResourceId`),
853
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
854
+ contentHash: readSha256(requireKey(record3, "contentHash", path), `${path}.contentHash`),
855
+ imageResourceId: readId(requireKey(record3, "imageResourceId", path), `${path}.imageResourceId`),
850
856
  intendedUsage: usages,
851
- colorSpace: readEnum(requireKey(record2, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
852
- exportEmbeddingPolicy: readEnum(requireKey(record2, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
853
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
857
+ colorSpace: readEnum(requireKey(record3, "colorSpace", path), `${path}.colorSpace`, ["srgb", "linear", "normal-map", "data"]),
858
+ exportEmbeddingPolicy: readEnum(requireKey(record3, "exportEmbeddingPolicy", path), `${path}.exportEmbeddingPolicy`, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]),
859
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
854
860
  };
855
- assignOptional(output, "fileName", readOptional(record2, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
856
- assignOptional(output, "relativePath", readOptional(record2, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
857
- assignOptional(output, "mimeType", readOptional(record2, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
858
- assignOptional(output, "byteLength", readOptional(record2, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
859
- assignOptional(output, "sampler", readOptional(record2, "sampler", parseTextureSampler, path));
861
+ assignOptional(output, "fileName", readOptional(record3, "fileName", (entry, entryPath) => readString(entry, entryPath, 512, FILE_NAME), path));
862
+ assignOptional(output, "relativePath", readOptional(record3, "relativePath", (entry, entryPath) => readString(entry, entryPath, 2048, RELATIVE_PATH), path));
863
+ assignOptional(output, "mimeType", readOptional(record3, "mimeType", (entry, entryPath) => readString(entry, entryPath, 128, MIME_TYPE).toLowerCase(), path));
864
+ assignOptional(output, "byteLength", readOptional(record3, "byteLength", (entry, entryPath) => readSafeInteger(entry, entryPath, 1), path));
865
+ assignOptional(output, "sampler", readOptional(record3, "sampler", parseTextureSampler, path));
860
866
  return output;
861
867
  }
862
868
  function parseSkeleton(value, path, budget) {
863
- const record2 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
864
- const jointIds = Object.freeze(readArray(requireKey(record2, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
865
- const rootJointIds = Object.freeze(readArray(requireKey(record2, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
869
+ const record3 = readRecord(value, path, ["id", "name", "jointIds", "rootJointIds", "sourceMetadata"]);
870
+ const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
871
+ const rootJointIds = Object.freeze(readArray(requireKey(record3, "rootJointIds", path), `${path}.rootJointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.rootJointIds[${index}]`)));
866
872
  consumeBudget(budget, "skinJoints", jointIds.length + rootJointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "rig joint");
867
873
  ensureUnique(jointIds, `${path}.jointIds`, "duplicate skeleton joint");
868
874
  ensureUnique(rootJointIds, `${path}.rootJointIds`, "duplicate root joint");
869
875
  const output = {
870
- id: readId(requireKey(record2, "id", path), `${path}.id`),
876
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
871
877
  jointIds,
872
878
  rootJointIds,
873
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
879
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
874
880
  };
875
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
881
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
876
882
  return output;
877
883
  }
878
884
  function parseJoint(value, path, budget) {
879
- const record2 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
885
+ const record3 = readRecord(value, path, ["id", "name", "nodeId", "parentJointId", "restLocalTransform", "inverseBindMatrix", "sourceMetadata"]);
880
886
  const output = {
881
- id: readId(requireKey(record2, "id", path), `${path}.id`),
882
- nodeId: readId(requireKey(record2, "nodeId", path), `${path}.nodeId`),
883
- restLocalTransform: parseTransformTrs(requireKey(record2, "restLocalTransform", path), `${path}.restLocalTransform`),
884
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
887
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
888
+ nodeId: readId(requireKey(record3, "nodeId", path), `${path}.nodeId`),
889
+ restLocalTransform: parseTransformTrs(requireKey(record3, "restLocalTransform", path), `${path}.restLocalTransform`),
890
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
885
891
  };
886
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
887
- assignOptional(output, "parentJointId", readOptional(record2, "parentJointId", readId, path));
888
- assignOptional(output, "inverseBindMatrix", readOptional(record2, "inverseBindMatrix", parseMatrix, path));
892
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
893
+ assignOptional(output, "parentJointId", readOptional(record3, "parentJointId", readId, path));
894
+ assignOptional(output, "inverseBindMatrix", readOptional(record3, "inverseBindMatrix", parseMatrix, path));
889
895
  return output;
890
896
  }
891
897
  function parseWeightRows(value, path, rows, columns, integer, budget) {
@@ -899,43 +905,43 @@ function parseWeightRows(value, path, rows, columns, integer, budget) {
899
905
  }));
900
906
  }
901
907
  function parseVertexWeightSet(value, path, budget) {
902
- const record2 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
903
- const influences = readSafeInteger(requireKey(record2, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
908
+ const record3 = readRecord(value, path, ["primitiveId", "influencesPerVertex", "joints", "weights"]);
909
+ const influences = readSafeInteger(requireKey(record3, "influencesPerVertex", path), `${path}.influencesPerVertex`, 1, 32);
904
910
  if (influences % 4 !== 0) return fail("invalid-value", `${path}.influencesPerVertex`, "influencesPerVertex must be a multiple of four");
905
- const jointRows = readArray(requireKey(record2, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
906
- const weightRows = readArray(requireKey(record2, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
911
+ const jointRows = readArray(requireKey(record3, "joints", path), `${path}.joints`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
912
+ const weightRows = readArray(requireKey(record3, "weights", path), `${path}.weights`, GPU_MODEL_DOCUMENT_LIMITS.weightVertices, 1);
907
913
  if (jointRows.length !== weightRows.length) return fail("invalid-value", path, "joint and weight row counts must match");
908
914
  return {
909
- primitiveId: readId(requireKey(record2, "primitiveId", path), `${path}.primitiveId`),
915
+ primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`),
910
916
  influencesPerVertex: influences,
911
917
  joints: parseWeightRows(jointRows, `${path}.joints`, jointRows.length, influences, true, budget),
912
918
  weights: parseWeightRows(weightRows, `${path}.weights`, weightRows.length, influences, false, budget)
913
919
  };
914
920
  }
915
921
  function parseSkin(value, path, budget) {
916
- const record2 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
917
- const jointIds = Object.freeze(readArray(requireKey(record2, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
922
+ const record3 = readRecord(value, path, ["id", "skeletonId", "jointIds", "inverseBindMatrices", "weightSets", "sourceMetadata"]);
923
+ const jointIds = Object.freeze(readArray(requireKey(record3, "jointIds", path), `${path}.jointIds`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, 1).map((entry, index) => readId(entry, `${path}.jointIds[${index}]`)));
918
924
  consumeBudget(budget, "skinJoints", jointIds.length, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, path, "skin joint");
919
925
  ensureUnique(jointIds, `${path}.jointIds`, "duplicate skin joint");
920
- const inverseInput = readArray(requireKey(record2, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
926
+ const inverseInput = readArray(requireKey(record3, "inverseBindMatrices", path), `${path}.inverseBindMatrices`, GPU_MODEL_DOCUMENT_LIMITS.skinJoints, jointIds.length);
921
927
  if (inverseInput.length !== jointIds.length) return fail("invalid-value", `${path}.inverseBindMatrices`, "one inverse bind matrix is required per skin joint");
922
- const weightInput = readArray(requireKey(record2, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
928
+ const weightInput = readArray(requireKey(record3, "weightSets", path), `${path}.weightSets`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
923
929
  const weightSets = Object.freeze(weightInput.map((entry, index) => parseVertexWeightSet(entry, `${path}.weightSets[${index}]`, budget)));
924
930
  ensureUnique(weightSets.map(({ primitiveId }) => primitiveId), `${path}.weightSets`, "duplicate primitive weight set");
925
931
  return {
926
- id: readId(requireKey(record2, "id", path), `${path}.id`),
927
- skeletonId: readId(requireKey(record2, "skeletonId", path), `${path}.skeletonId`),
932
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
933
+ skeletonId: readId(requireKey(record3, "skeletonId", path), `${path}.skeletonId`),
928
934
  jointIds,
929
935
  inverseBindMatrices: Object.freeze(inverseInput.map((entry, index) => parseMatrix(entry, `${path}.inverseBindMatrices[${index}]`))),
930
936
  weightSets,
931
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
937
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
932
938
  };
933
939
  }
934
940
  function parseBlendShapeDelta(value, path) {
935
- const record2 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
936
- const output = { primitiveId: readId(requireKey(record2, "primitiveId", path), `${path}.primitiveId`) };
941
+ const record3 = readRecord(value, path, ["primitiveId", "positionAccessorId", "normalAccessorId", "tangentAccessorId"]);
942
+ const output = { primitiveId: readId(requireKey(record3, "primitiveId", path), `${path}.primitiveId`) };
937
943
  for (const key of ["positionAccessorId", "normalAccessorId", "tangentAccessorId"]) {
938
- assignOptional(output, key, readOptional(record2, key, readId, path));
944
+ assignOptional(output, key, readOptional(record3, key, readId, path));
939
945
  }
940
946
  if (!output.positionAccessorId && !output.normalAccessorId && !output.tangentAccessorId) {
941
947
  return fail("invalid-value", path, "blend-shape delta requires at least one accessor");
@@ -943,8 +949,8 @@ function parseBlendShapeDelta(value, path) {
943
949
  return output;
944
950
  }
945
951
  function parseBlendShape(value, path, budget) {
946
- const record2 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
947
- const deltaInput = readArray(requireKey(record2, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
952
+ const record3 = readRecord(value, path, ["id", "meshId", "name", "defaultWeight", "deltas", "sourceMetadata"]);
953
+ const deltaInput = readArray(requireKey(record3, "deltas", path), `${path}.deltas`, GPU_MODEL_DOCUMENT_LIMITS.primitives, 1);
948
954
  consumeBudget(
949
955
  budget,
950
956
  "blendShapeDeltas",
@@ -956,96 +962,96 @@ function parseBlendShape(value, path, budget) {
956
962
  const deltas = Object.freeze(deltaInput.map((entry, index) => parseBlendShapeDelta(entry, `${path}.deltas[${index}]`)));
957
963
  ensureUnique(deltas.map(({ primitiveId }) => primitiveId), `${path}.deltas`, "duplicate blend-shape primitive delta");
958
964
  const output = {
959
- id: readId(requireKey(record2, "id", path), `${path}.id`),
960
- meshId: readId(requireKey(record2, "meshId", path), `${path}.meshId`),
965
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
966
+ meshId: readId(requireKey(record3, "meshId", path), `${path}.meshId`),
961
967
  deltas,
962
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
968
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
963
969
  };
964
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
965
- assignOptional(output, "defaultWeight", readOptional(record2, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
970
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
971
+ assignOptional(output, "defaultWeight", readOptional(record3, "defaultWeight", (entry, entryPath) => readFinite(entry, entryPath, 0, 1), path));
966
972
  return output;
967
973
  }
968
974
  function parseAnimationSampler(value, path, budget) {
969
- const record2 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
970
- const timesInput = readArray(requireKey(record2, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
971
- const valuesInput = readArray(requireKey(record2, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
975
+ const record3 = readRecord(value, path, ["id", "inputAccessorId", "outputAccessorId", "inputTimesSeconds", "outputValues", "interpolation", "sourceMetadata"]);
976
+ const timesInput = readArray(requireKey(record3, "inputTimesSeconds", path), `${path}.inputTimesSeconds`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
977
+ const valuesInput = readArray(requireKey(record3, "outputValues", path), `${path}.outputValues`, GPU_MODEL_DOCUMENT_LIMITS.animationValues, 1);
972
978
  consumeBudget(budget, "animationValues", timesInput.length + valuesInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationValues, path, "animation value");
973
979
  const times = Object.freeze(timesInput.map((entry, index) => readFinite(entry, `${path}.inputTimesSeconds[${index}]`, 0)));
974
980
  for (let index = 1; index < times.length; index += 1) {
975
981
  if (times[index] <= times[index - 1]) return fail("invalid-value", `${path}.inputTimesSeconds`, "animation input times must be strictly increasing");
976
982
  }
977
983
  return {
978
- id: readId(requireKey(record2, "id", path), `${path}.id`),
979
- inputAccessorId: readId(requireKey(record2, "inputAccessorId", path), `${path}.inputAccessorId`),
980
- outputAccessorId: readId(requireKey(record2, "outputAccessorId", path), `${path}.outputAccessorId`),
984
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
985
+ inputAccessorId: readId(requireKey(record3, "inputAccessorId", path), `${path}.inputAccessorId`),
986
+ outputAccessorId: readId(requireKey(record3, "outputAccessorId", path), `${path}.outputAccessorId`),
981
987
  inputTimesSeconds: times,
982
988
  outputValues: Object.freeze(valuesInput.map((entry, index) => readFinite(entry, `${path}.outputValues[${index}]`))),
983
- interpolation: readEnum(requireKey(record2, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
984
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
989
+ interpolation: readEnum(requireKey(record3, "interpolation", path), `${path}.interpolation`, ["step", "linear", "cubic-spline"]),
990
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
985
991
  };
986
992
  }
987
993
  function parseAnimationChannel(value, path, budget) {
988
- const record2 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
994
+ const record3 = readRecord(value, path, ["id", "samplerId", "targetType", "targetId", "sourceMetadata"]);
989
995
  return {
990
- id: readId(requireKey(record2, "id", path), `${path}.id`),
991
- samplerId: readId(requireKey(record2, "samplerId", path), `${path}.samplerId`),
992
- targetType: readEnum(requireKey(record2, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
993
- targetId: readId(requireKey(record2, "targetId", path), `${path}.targetId`),
994
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
996
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
997
+ samplerId: readId(requireKey(record3, "samplerId", path), `${path}.samplerId`),
998
+ targetType: readEnum(requireKey(record3, "targetType", path), `${path}.targetType`, ["node-translation", "node-rotation", "node-scale", "weights"]),
999
+ targetId: readId(requireKey(record3, "targetId", path), `${path}.targetId`),
1000
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
995
1001
  };
996
1002
  }
997
1003
  function parseAnimation(value, path, budget) {
998
- const record2 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
999
- const samplerInput = readArray(requireKey(record2, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
1000
- const channelInput = readArray(requireKey(record2, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
1004
+ const record3 = readRecord(value, path, ["id", "name", "durationSeconds", "samplers", "channels", "sourceMetadata"]);
1005
+ const samplerInput = readArray(requireKey(record3, "samplers", path), `${path}.samplers`, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, 1);
1006
+ const channelInput = readArray(requireKey(record3, "channels", path), `${path}.channels`, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, 1);
1001
1007
  consumeBudget(budget, "animationSamplers", samplerInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationSamplers, path, "animation sampler");
1002
1008
  consumeBudget(budget, "animationChannels", channelInput.length, GPU_MODEL_DOCUMENT_LIMITS.animationChannels, path, "animation channel");
1003
1009
  const output = {
1004
- id: readId(requireKey(record2, "id", path), `${path}.id`),
1005
- durationSeconds: readFinite(requireKey(record2, "durationSeconds", path), `${path}.durationSeconds`, 0),
1010
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
1011
+ durationSeconds: readFinite(requireKey(record3, "durationSeconds", path), `${path}.durationSeconds`, 0),
1006
1012
  samplers: Object.freeze(samplerInput.map((entry, index) => parseAnimationSampler(entry, `${path}.samplers[${index}]`, budget))),
1007
1013
  channels: Object.freeze(channelInput.map((entry, index) => parseAnimationChannel(entry, `${path}.channels[${index}]`, budget))),
1008
- sourceMetadata: readMetadata(requireKey(record2, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
1014
+ sourceMetadata: readMetadata(requireKey(record3, "sourceMetadata", path), `${path}.sourceMetadata`, budget)
1009
1015
  };
1010
- assignOptional(output, "name", readOptional(record2, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
1016
+ assignOptional(output, "name", readOptional(record3, "name", (entry, entryPath) => readString(entry, entryPath, GPU_MODEL_DOCUMENT_LIMITS.nameLength), path));
1011
1017
  return output;
1012
1018
  }
1013
1019
  function parseAnalyticGeometry(value, path, budget) {
1014
- const record2 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
1015
- if (requireKey(record2, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
1020
+ const record3 = readRecord(value, path, ["id", "kind", "sourceResourceId", "sourceUnit", "tessellatedMeshId", "metadata"]);
1021
+ if (requireKey(record3, "kind", path) !== "brep-placeholder") return fail("invalid-value", `${path}.kind`, "unsupported analytic geometry kind");
1016
1022
  const output = {
1017
- id: readId(requireKey(record2, "id", path), `${path}.id`),
1023
+ id: readId(requireKey(record3, "id", path), `${path}.id`),
1018
1024
  kind: "brep-placeholder",
1019
- sourceResourceId: readId(requireKey(record2, "sourceResourceId", path), `${path}.sourceResourceId`),
1020
- sourceUnit: readEnum(requireKey(record2, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
1025
+ sourceResourceId: readId(requireKey(record3, "sourceResourceId", path), `${path}.sourceResourceId`),
1026
+ sourceUnit: readEnum(requireKey(record3, "sourceUnit", path), `${path}.sourceUnit`, ["metre", "millimetre", "centimetre", "inch", "foot", "unitless"])
1021
1027
  };
1022
- assignOptional(output, "tessellatedMeshId", readOptional(record2, "tessellatedMeshId", readId, path));
1023
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1028
+ assignOptional(output, "tessellatedMeshId", readOptional(record3, "tessellatedMeshId", readId, path));
1029
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1024
1030
  return output;
1025
1031
  }
1026
1032
  function parseProvenance(value, budget) {
1027
1033
  const path = "$.provenance";
1028
- const record2 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
1034
+ const record3 = readRecord(value, path, ["sourceFormat", "sourceContentHash", "converterId", "converterVersion", "provider", "sourceIdentifier", "metadata"]);
1029
1035
  const output = {
1030
- sourceFormat: readString(requireKey(record2, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
1031
- sourceContentHash: readSha256(requireKey(record2, "sourceContentHash", path), `${path}.sourceContentHash`),
1032
- converterId: readString(requireKey(record2, "converterId", path), `${path}.converterId`, 128, TOKEN),
1033
- converterVersion: readString(requireKey(record2, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
1036
+ sourceFormat: readString(requireKey(record3, "sourceFormat", path), `${path}.sourceFormat`, 128, TOKEN),
1037
+ sourceContentHash: readSha256(requireKey(record3, "sourceContentHash", path), `${path}.sourceContentHash`),
1038
+ converterId: readString(requireKey(record3, "converterId", path), `${path}.converterId`, 128, TOKEN),
1039
+ converterVersion: readString(requireKey(record3, "converterVersion", path), `${path}.converterVersion`, 128, TOKEN)
1034
1040
  };
1035
- assignOptional(output, "provider", readOptional(record2, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
1036
- assignOptional(output, "sourceIdentifier", readOptional(record2, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
1037
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1041
+ assignOptional(output, "provider", readOptional(record3, "provider", (entry, entryPath) => readString(entry, entryPath, 128, TOKEN), path));
1042
+ assignOptional(output, "sourceIdentifier", readOptional(record3, "sourceIdentifier", (entry, entryPath) => readString(entry, entryPath, 2048), path));
1043
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1038
1044
  return output;
1039
1045
  }
1040
1046
  function parseDiagnostic(value, path, budget) {
1041
- const record2 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
1047
+ const record3 = readRecord(value, path, ["severity", "code", "message", "path", "metadata"]);
1042
1048
  const output = {
1043
- severity: readEnum(requireKey(record2, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
1044
- code: readString(requireKey(record2, "code", path), `${path}.code`, 128, TOKEN),
1045
- message: readString(requireKey(record2, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
1049
+ severity: readEnum(requireKey(record3, "severity", path), `${path}.severity`, ["info", "warning", "error"]),
1050
+ code: readString(requireKey(record3, "code", path), `${path}.code`, 128, TOKEN),
1051
+ message: readString(requireKey(record3, "message", path), `${path}.message`, GPU_MODEL_DOCUMENT_LIMITS.diagnosticMessageLength)
1046
1052
  };
1047
- assignOptional(output, "path", readOptional(record2, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
1048
- assignOptional(output, "metadata", readOptional(record2, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1053
+ assignOptional(output, "path", readOptional(record3, "path", (entry, entryPath) => readString(entry, entryPath, 512), path));
1054
+ assignOptional(output, "metadata", readOptional(record3, "metadata", (entry, entryPath) => readMetadata(entry, entryPath, budget), path));
1049
1055
  return output;
1050
1056
  }
1051
1057
  function ensureUnique(values, path, detail = "duplicate identity") {
@@ -1686,11 +1692,11 @@ var DOCUMENT_KEYS = Object.freeze([
1686
1692
  ]);
1687
1693
  function parseGpuModelDocument(value) {
1688
1694
  const path = "$";
1689
- const record2 = readRecord(value, path, DOCUMENT_KEYS);
1690
- const schemaVersion = requireKey(record2, "schemaVersion", path);
1695
+ const record3 = readRecord(value, path, DOCUMENT_KEYS);
1696
+ const schemaVersion = requireKey(record3, "schemaVersion", path);
1691
1697
  if (schemaVersion !== GPU_MODEL_DOCUMENT_SCHEMA_VERSION) return fail("invalid-value", "$.schemaVersion", "unsupported schema version");
1692
1698
  const parseCollection = (key, maximum, parser, minimum = 0) => {
1693
- const input = readArray(requireKey(record2, key, path), `$.${key}`, maximum, minimum);
1699
+ const input = readArray(requireKey(record3, key, path), `$.${key}`, maximum, minimum);
1694
1700
  return Object.freeze(input.map((entry, index) => parser(entry, `$.${key}[${index}]`)));
1695
1701
  };
1696
1702
  const budget = {
@@ -1711,10 +1717,10 @@ function parseGpuModelDocument(value) {
1711
1717
  geometryWorldAccessorInstances: 0,
1712
1718
  geometryWorldVertices: 0
1713
1719
  };
1714
- const roots = Object.freeze(readArray(requireKey(record2, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
1720
+ const roots = Object.freeze(readArray(requireKey(record3, "roots", path), "$.roots", GPU_MODEL_DOCUMENT_LIMITS.roots, 1).map((entry, index) => readId(entry, `$.roots[${index}]`)));
1715
1721
  const document = {
1716
1722
  schemaVersion,
1717
- coordinateSystem: parseCoordinateSystem(requireKey(record2, "coordinateSystem", path)),
1723
+ coordinateSystem: parseCoordinateSystem(requireKey(record3, "coordinateSystem", path)),
1718
1724
  roots,
1719
1725
  nodes: parseCollection("nodes", GPU_MODEL_DOCUMENT_LIMITS.nodes, (entry, entryPath) => parseNode(entry, entryPath, budget), 1),
1720
1726
  resources: parseCollection("resources", GPU_MODEL_DOCUMENT_LIMITS.resources, parseVerifiedResource, 1),
@@ -1728,10 +1734,10 @@ function parseGpuModelDocument(value) {
1728
1734
  blendShapes: parseCollection("blendShapes", GPU_MODEL_DOCUMENT_LIMITS.blendShapes, (entry, entryPath) => parseBlendShape(entry, entryPath, budget)),
1729
1735
  animations: parseCollection("animations", GPU_MODEL_DOCUMENT_LIMITS.animations, (entry, entryPath) => parseAnimation(entry, entryPath, budget)),
1730
1736
  analyticGeometry: parseCollection("analyticGeometry", GPU_MODEL_DOCUMENT_LIMITS.analyticGeometry, (entry, entryPath) => parseAnalyticGeometry(entry, entryPath, budget)),
1731
- bounds: parseBounds(requireKey(record2, "bounds", path), "$.bounds"),
1732
- provenance: parseProvenance(requireKey(record2, "provenance", path), budget),
1737
+ bounds: parseBounds(requireKey(record3, "bounds", path), "$.bounds"),
1738
+ provenance: parseProvenance(requireKey(record3, "provenance", path), budget),
1733
1739
  diagnostics: parseCollection("diagnostics", GPU_MODEL_DOCUMENT_LIMITS.diagnostics, (entry, entryPath) => parseDiagnostic(entry, entryPath, budget)),
1734
- metadata: readMetadata(requireKey(record2, "metadata", path), "$.metadata", budget)
1740
+ metadata: readMetadata(requireKey(record3, "metadata", path), "$.metadata", budget)
1735
1741
  };
1736
1742
  if (document.meshes.length === 0) return fail("invalid-value", "$.meshes", "v1 document bounds require at least one canonical mesh");
1737
1743
  validateReferences(document, budget);
@@ -1768,8 +1774,8 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
1768
1774
  value.resources.forEach((resource, index) => assertVerifiedResourceEvidence(resource, limits, `$.resources[${index}]`));
1769
1775
  return value;
1770
1776
  }
1771
- const record2 = readRecord(value, "$", DOCUMENT_KEYS);
1772
- const resourceInput = readArray(requireKey(record2, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
1777
+ const record3 = readRecord(value, "$", DOCUMENT_KEYS);
1778
+ const resourceInput = readArray(requireKey(record3, "resources", "$"), "$.resources", GPU_MODEL_DOCUMENT_LIMITS.resources, 1);
1773
1779
  const parsed = resourceInput.map((entry, index) => {
1774
1780
  if (typeof entry === "object" && entry !== null && verifiedResources.has(entry) && verifiedResourceBytes.has(entry)) return entry;
1775
1781
  return parseResourceHeader(entry, `$.resources[${index}]`);
@@ -1782,7 +1788,7 @@ async function createAndVerifyGpuModelDocument(value, port, options) {
1782
1788
  }
1783
1789
  const candidate = {};
1784
1790
  for (const key of DOCUMENT_KEYS) {
1785
- if (Object.hasOwn(record2, key)) candidate[key] = record2[key];
1791
+ if (Object.hasOwn(record3, key)) candidate[key] = record3[key];
1786
1792
  }
1787
1793
  candidate.resources = verified;
1788
1794
  return createGpuModelDocument(candidate);
@@ -1847,11 +1853,11 @@ async function hashGpuModelDocumentCanonical(document) {
1847
1853
  return Array.from(new Uint8Array(digest), (value) => value.toString(16).padStart(2, "0")).join("");
1848
1854
  }
1849
1855
  function resolveStaticDemoLimits(options) {
1850
- const record2 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
1851
- const enabled = readBoolean(requireKey(record2, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
1856
+ const record3 = readRecord(options, "$.options", ["pvoxModelsEnabled", "limits"]);
1857
+ const enabled = readBoolean(requireKey(record3, "pvoxModelsEnabled", "$.options"), "$.options.pvoxModelsEnabled");
1852
1858
  if (!enabled) return fail("feature-disabled", "$.options.pvoxModelsEnabled", `${GPU_MODEL_STATIC_DEMO_FEATURE_FLAG} is disabled`);
1853
- if (!Object.hasOwn(record2, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
1854
- const overrides = readRecord(record2.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
1859
+ if (!Object.hasOwn(record3, "limits")) return GPU_MODEL_STATIC_DEMO_LIMITS;
1860
+ const overrides = readRecord(record3.limits, "$.options.limits", Object.keys(GPU_MODEL_STATIC_DEMO_LIMITS));
1855
1861
  const limits = { ...GPU_MODEL_STATIC_DEMO_LIMITS };
1856
1862
  for (const [key, ceiling] of Object.entries(GPU_MODEL_STATIC_DEMO_LIMITS)) {
1857
1863
  if (Object.hasOwn(overrides, key)) limits[key] = readSafeInteger(overrides[key], `$.options.limits.${key}`, 1, ceiling);
@@ -2220,15 +2226,15 @@ function snapshot(value, depth, budget, ancestors, memberLimit = GPU_MODEL_DIAGN
2220
2226
  return value;
2221
2227
  }
2222
2228
  if (typeof value !== "object" || ancestors.has(value)) fail2();
2223
- const array = Array.isArray(value);
2229
+ const array2 = Array.isArray(value);
2224
2230
  const prototype = Object.getPrototypeOf(value);
2225
- if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
2231
+ if (array2 ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) fail2();
2226
2232
  const keys = Reflect.ownKeys(value);
2227
2233
  const maxMembers = memberLimit;
2228
- if (keys.length > maxMembers + (array ? 1 : 0)) fail2();
2234
+ if (keys.length > maxMembers + (array2 ? 1 : 0)) fail2();
2229
2235
  ancestors.add(value);
2230
2236
  charge(budget, 2);
2231
- if (array) {
2237
+ if (array2) {
2232
2238
  const length = Object.getOwnPropertyDescriptor(value, "length").value;
2233
2239
  if (length > maxMembers || keys.length !== length + 1) fail2();
2234
2240
  const result2 = [];
@@ -2340,6 +2346,197 @@ function evaluateGpuModelDiagnostics(value) {
2340
2346
  return Object.freeze({ mode, standard, accepted, diagnostics: Object.freeze(diagnostics), repairs: Object.freeze(repairs), losses: Object.freeze(losses), ...mode === "forensic" ? { rawSource: args.rawSource } : {} });
2341
2347
  }
2342
2348
 
2349
+ // src/resource-graph.ts
2350
+ var GPU_MODEL_RESOURCE_GRAPH_LIMITS = Object.freeze({
2351
+ nodes: 65536,
2352
+ edges: 262144,
2353
+ packages: 256,
2354
+ pathLength: 1024
2355
+ });
2356
+ var GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION = "plasius.gpu-model-resource-graph/1";
2357
+ var KINDS = ["resource", "accessor", "texture", "material", "skeleton", "skin", "animation", "provenance", "package"];
2358
+ var GpuModelResourceGraphError = class extends TypeError {
2359
+ constructor(code) {
2360
+ super(`Invalid GPU model resource graph: ${code}`);
2361
+ this.code = code;
2362
+ this.name = "GpuModelResourceGraphError";
2363
+ }
2364
+ code;
2365
+ };
2366
+ function fail3(code = "invalid-value") {
2367
+ throw new GpuModelResourceGraphError(code);
2368
+ }
2369
+ function identifier(value) {
2370
+ if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/u.test(value)) fail3();
2371
+ return value;
2372
+ }
2373
+ function choice(value, choices) {
2374
+ if (typeof value !== "string" || !choices.includes(value)) fail3();
2375
+ return value;
2376
+ }
2377
+ function gpuModelResourceKey(kind, id) {
2378
+ return JSON.stringify([choice(kind, KINDS), identifier(id)]);
2379
+ }
2380
+ function record2(value, keys) {
2381
+ if (typeof value !== "object" || value === null || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
2382
+ const names = Reflect.ownKeys(value);
2383
+ if (names.length !== keys.length || names.some((key) => typeof key !== "string" || !keys.includes(key))) fail3();
2384
+ const result = /* @__PURE__ */ Object.create(null);
2385
+ for (const key of keys) {
2386
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
2387
+ if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
2388
+ result[key] = descriptor.value;
2389
+ }
2390
+ return result;
2391
+ }
2392
+ function array(value, maximum) {
2393
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) fail3();
2394
+ if (value.length > maximum) fail3("limit-exceeded");
2395
+ if (Reflect.ownKeys(value).length !== value.length + 1) fail3();
2396
+ const output = [];
2397
+ for (let index = 0; index < value.length; index++) {
2398
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
2399
+ if (!descriptor || !("value" in descriptor) || !descriptor.enumerable) fail3();
2400
+ output.push(descriptor.value);
2401
+ }
2402
+ return output;
2403
+ }
2404
+ function relativePath(value) {
2405
+ if (typeof value !== "string" || value.length > GPU_MODEL_RESOURCE_GRAPH_LIMITS.pathLength || !/^[A-Za-z0-9._/-]+$/u.test(value)) fail3();
2406
+ for (const part of value.split("/")) {
2407
+ if (!part || part.length > 255 || part === "." || part === ".." || part.endsWith(".") || /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part)) fail3();
2408
+ }
2409
+ return value;
2410
+ }
2411
+ function parseLocation(value) {
2412
+ if (typeof value !== "object" || value === null) fail3();
2413
+ const kind = Object.getOwnPropertyDescriptor(value, "kind");
2414
+ if (!kind || !("value" in kind)) fail3();
2415
+ if (kind.value === "embedded") {
2416
+ record2(value, ["kind"]);
2417
+ return Object.freeze({ kind: "embedded" });
2418
+ }
2419
+ const location = record2(value, ["kind", "relativePath"]);
2420
+ if (location.kind !== "external") fail3();
2421
+ return Object.freeze({ kind: "external", relativePath: relativePath(location.relativePath) });
2422
+ }
2423
+ var graphs = /* @__PURE__ */ new WeakSet();
2424
+ function isGpuModelResourceGraph(value) {
2425
+ return typeof value === "object" && value !== null && graphs.has(value);
2426
+ }
2427
+ var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
2428
+ function createGpuModelResourceGraph(document, input) {
2429
+ try {
2430
+ return buildGraph(document, input);
2431
+ } catch (error) {
2432
+ if (error instanceof GpuModelResourceGraphError) throw error;
2433
+ fail3();
2434
+ }
2435
+ }
2436
+ function buildGraph(document, input) {
2437
+ if (!isGpuModelDocument(document)) fail3("unverified-document");
2438
+ const raw = record2(input, ["resources", "packages"]);
2439
+ const resourceInputs = array(raw.resources, GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes);
2440
+ const packageInputs = array(raw.packages, GPU_MODEL_RESOURCE_GRAPH_LIMITS.packages);
2441
+ const nodes = /* @__PURE__ */ new Map();
2442
+ let edgeCount = 0;
2443
+ const add = (kind, id, value, dependencies) => {
2444
+ const key = gpuModelResourceKey(kind, id);
2445
+ if (nodes.has(key)) fail3("duplicate-reference");
2446
+ if (nodes.size >= GPU_MODEL_RESOURCE_GRAPH_LIMITS.nodes) fail3("limit-exceeded");
2447
+ edgeCount += dependencies.length;
2448
+ if (edgeCount > GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges) fail3("limit-exceeded");
2449
+ nodes.set(key, Object.freeze({ kind, id, key, value, dependencies: Object.freeze([...new Set(dependencies)].sort(compare)) }));
2450
+ };
2451
+ const resources = new Map(document.resources.map((resource) => [resource.id, resource]));
2452
+ const paths = /* @__PURE__ */ new Set();
2453
+ for (const entry of resourceInputs) {
2454
+ const binding = record2(entry, ["resourceId", "location", "exportEmbeddingPolicy"]);
2455
+ const id = identifier(binding.resourceId);
2456
+ const resource = resources.get(id);
2457
+ if (!resource) fail3("missing-reference");
2458
+ const location = parseLocation(binding.location);
2459
+ const exportEmbeddingPolicy = choice(binding.exportEmbeddingPolicy, ["inherit-source", "prefer-embedded", "prefer-external", "forbid-embedding"]);
2460
+ if (location.kind === "embedded" && exportEmbeddingPolicy === "forbid-embedding") fail3();
2461
+ if (location.kind === "external") {
2462
+ const path = location.relativePath.toLowerCase();
2463
+ if (paths.has(path)) fail3("duplicate-reference");
2464
+ paths.add(path);
2465
+ }
2466
+ add("resource", id, Object.freeze({ resource, location, exportEmbeddingPolicy }), []);
2467
+ }
2468
+ for (const path of paths) {
2469
+ for (let index = path.indexOf("/"); index !== -1; index = path.indexOf("/", index + 1)) {
2470
+ if (paths.has(path.slice(0, index))) fail3("duplicate-reference");
2471
+ }
2472
+ }
2473
+ if (nodes.size !== resources.size) fail3("missing-reference");
2474
+ addDocumentNodes(document, add);
2475
+ for (const entry of packageInputs) {
2476
+ const pkg = record2(entry, ["id", "members"]);
2477
+ const id = identifier(pkg.id);
2478
+ const members = [];
2479
+ const dependencies = /* @__PURE__ */ new Set();
2480
+ for (const entry2 of array(pkg.members, GPU_MODEL_RESOURCE_GRAPH_LIMITS.edges - edgeCount)) {
2481
+ const reference = record2(entry2, ["kind", "id"]);
2482
+ const member = Object.freeze({ kind: choice(reference.kind, KINDS), id: identifier(reference.id) });
2483
+ const key = gpuModelResourceKey(member.kind, member.id);
2484
+ if (dependencies.has(key)) fail3("duplicate-reference");
2485
+ dependencies.add(key);
2486
+ members.push(member);
2487
+ }
2488
+ members.sort((a, b) => compare(gpuModelResourceKey(a.kind, a.id), gpuModelResourceKey(b.kind, b.id)));
2489
+ add("package", id, Object.freeze({ id, members: Object.freeze(members) }), [...dependencies]);
2490
+ }
2491
+ validateDag(nodes);
2492
+ const result = Object.freeze({ schemaVersion: GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION, nodes: Object.freeze([...nodes.values()].sort((a, b) => compare(a.key, b.key))) });
2493
+ graphs.add(result);
2494
+ return result;
2495
+ }
2496
+ function addDocumentNodes(document, add) {
2497
+ const key = gpuModelResourceKey;
2498
+ for (const value of document.accessors) add("accessor", value.id, value, [key("resource", value.resourceId)]);
2499
+ for (const value of document.textures) add("texture", value.id, value, [key("resource", value.imageResourceId)]);
2500
+ for (const value of document.materials) add("material", value.id, value, Object.values(value.textures).map((binding) => key("texture", binding.textureId)));
2501
+ const joints = new Map(document.joints.map((joint) => [joint.id, joint]));
2502
+ for (const skeleton of document.skeletons) add("skeleton", skeleton.id, Object.freeze({ skeleton, joints: Object.freeze(skeleton.jointIds.map((id) => joints.get(id))) }), []);
2503
+ const primitives = new Map(document.meshes.flatMap((mesh) => mesh.primitives.map((primitive) => [primitive.id, primitive])));
2504
+ for (const value of document.skins) {
2505
+ const dependencies = [key("skeleton", value.skeletonId)];
2506
+ for (const weightSet of value.weightSets) {
2507
+ for (const attribute of primitives.get(weightSet.primitiveId).attributes) {
2508
+ if (/^(JOINTS|WEIGHTS)_/u.test(attribute.semantic)) dependencies.push(key("accessor", attribute.accessorId));
2509
+ }
2510
+ }
2511
+ add("skin", value.id, value, dependencies);
2512
+ }
2513
+ for (const value of document.animations) add("animation", value.id, value, value.samplers.flatMap((sampler) => [key("accessor", sampler.inputAccessorId), key("accessor", sampler.outputAccessorId)]));
2514
+ add("provenance", "source", document.provenance, []);
2515
+ }
2516
+ function validateDag(nodes) {
2517
+ const remaining = /* @__PURE__ */ new Map();
2518
+ const dependents = /* @__PURE__ */ new Map();
2519
+ const queue = [];
2520
+ for (const node of nodes.values()) {
2521
+ remaining.set(node.key, node.dependencies.length);
2522
+ if (!node.dependencies.length) queue.push(node.key);
2523
+ for (const dependency of node.dependencies) {
2524
+ if (!nodes.has(dependency)) fail3("missing-reference");
2525
+ const users = dependents.get(dependency) ?? [];
2526
+ users.push(node.key);
2527
+ dependents.set(dependency, users);
2528
+ }
2529
+ }
2530
+ for (let index = 0; index < queue.length; index++) {
2531
+ for (const dependent of dependents.get(queue[index]) ?? []) {
2532
+ const count = remaining.get(dependent) - 1;
2533
+ remaining.set(dependent, count);
2534
+ if (!count) queue.push(dependent);
2535
+ }
2536
+ }
2537
+ if (queue.length !== nodes.size) fail3("graph-cycle");
2538
+ }
2539
+
2343
2540
  // src/index.ts
2344
2541
  var packageName = "@plasius/gpu-model-core";
2345
2542
  var packageBootstrap = Object.freeze({
@@ -2354,6 +2551,8 @@ var packageBootstrap = Object.freeze({
2354
2551
  GPU_MODEL_DOCUMENT_LIMITS,
2355
2552
  GPU_MODEL_DOCUMENT_SCHEMA_VERSION,
2356
2553
  GPU_MODEL_REPAIR_RULES,
2554
+ GPU_MODEL_RESOURCE_GRAPH_LIMITS,
2555
+ GPU_MODEL_RESOURCE_GRAPH_SCHEMA_VERSION,
2357
2556
  GPU_MODEL_RESOURCE_VERIFICATION_LIMITS,
2358
2557
  GPU_MODEL_STATIC_DEMO_FEATURE_FLAG,
2359
2558
  GPU_MODEL_STATIC_DEMO_LIMITS,
@@ -2364,15 +2563,19 @@ var packageBootstrap = Object.freeze({
2364
2563
  GPU_MODEL_STATIC_DEMO_PROFILE_VERSION,
2365
2564
  GpuModelDiagnosticsError,
2366
2565
  GpuModelDocumentError,
2566
+ GpuModelResourceGraphError,
2367
2567
  assertGpuModelStaticDemoNormalization,
2368
2568
  canCreateGpuModelDocument,
2369
2569
  createAndVerifyGpuModelDocument,
2370
2570
  createGpuModelDocument,
2571
+ createGpuModelResourceGraph,
2371
2572
  createGpuModelStaticDemoCompilerInput,
2372
2573
  encodeGpuModelDocumentCanonical,
2373
2574
  evaluateGpuModelDiagnostics,
2575
+ gpuModelResourceKey,
2374
2576
  hashGpuModelDocumentCanonical,
2375
2577
  isGpuModelDocument,
2578
+ isGpuModelResourceGraph,
2376
2579
  packageBootstrap,
2377
2580
  packageName,
2378
2581
  verifyGpuModelResource