@forgeax/engine-import 0.1.20 → 0.1.23

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 (91) hide show
  1. package/README.md +91 -3
  2. package/dist/__tests__/ies-contract.unit.test.d.ts +2 -0
  3. package/dist/__tests__/ies-contract.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/ies-producer.unit.test.d.ts +2 -0
  5. package/dist/__tests__/ies-producer.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/mesh-lod-contract.unit.test.d.ts +2 -0
  7. package/dist/__tests__/mesh-lod-contract.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts +2 -0
  9. package/dist/__tests__/mesh-lod-reimport.integration.test.d.ts.map +1 -0
  10. package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts +2 -0
  11. package/dist/__tests__/parameterized-scriptable-pack.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts +2 -0
  13. package/dist/__tests__/scriptable-pack-file-snapshot.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/source-package-publication-lod.integration.test.d.ts +2 -0
  15. package/dist/__tests__/source-package-publication-lod.integration.test.d.ts.map +1 -0
  16. package/dist/browser.d.ts +1 -0
  17. package/dist/browser.d.ts.map +1 -1
  18. package/dist/browser.mjs +230 -3
  19. package/dist/browser.mjs.map +1 -1
  20. package/dist/build-production.d.ts +1 -8
  21. package/dist/build-production.d.ts.map +1 -1
  22. package/dist/ies/ies-importer.d.ts +4 -0
  23. package/dist/ies/ies-importer.d.ts.map +1 -0
  24. package/dist/ies/parse-lm63.d.ts +13 -0
  25. package/dist/ies/parse-lm63.d.ts.map +1 -0
  26. package/dist/ies/resample-type-c.d.ts +4 -0
  27. package/dist/ies/resample-type-c.d.ts.map +1 -0
  28. package/dist/import-runner.d.ts.map +1 -1
  29. package/dist/index.d.ts +11 -6
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.mjs +2550 -1029
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/mesh-bin.d.ts.map +1 -1
  34. package/dist/mesh-bin.mjs +29 -1
  35. package/dist/mesh-bin.mjs.map +1 -1
  36. package/dist/mesh-lod.d.ts +60 -0
  37. package/dist/mesh-lod.d.ts.map +1 -0
  38. package/dist/parameterized-scriptable-pack.d.ts +68 -0
  39. package/dist/parameterized-scriptable-pack.d.ts.map +1 -0
  40. package/dist/scriptable-pack-file-snapshot.d.ts +71 -0
  41. package/dist/scriptable-pack-file-snapshot.d.ts.map +1 -0
  42. package/dist/scriptable-pack-host.d.ts +70 -39
  43. package/dist/scriptable-pack-host.d.ts.map +1 -1
  44. package/dist/scriptable-pack-output-producers.d.ts +1 -0
  45. package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
  46. package/dist/scriptable-pack-staged-snapshot.d.ts +2 -2
  47. package/dist/scriptable-pack-staged-snapshot.d.ts.map +1 -1
  48. package/dist/scriptable-pack.d.ts +13 -26
  49. package/dist/scriptable-pack.d.ts.map +1 -1
  50. package/dist/source-package-errors.d.ts.map +1 -1
  51. package/dist/source-package-publication.d.ts.map +1 -1
  52. package/package.json +8 -7
  53. package/src/__tests__/ies-contract.unit.test.ts +31 -0
  54. package/src/__tests__/ies-producer.unit.test.ts +104 -0
  55. package/src/__tests__/mesh-bin.test.ts +69 -0
  56. package/src/__tests__/mesh-lod-contract.unit.test.ts +92 -0
  57. package/src/__tests__/mesh-lod-reimport.integration.test.ts +15 -0
  58. package/src/__tests__/parameterized-scriptable-pack.unit.test.ts +270 -0
  59. package/src/__tests__/scriptable-pack-file-snapshot.unit.test.ts +177 -0
  60. package/src/__tests__/scriptable-pack-host.unit.test.ts +196 -4
  61. package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +7 -1
  62. package/src/__tests__/scriptable-pack-staged-snapshot.unit.test.ts +43 -12
  63. package/src/__tests__/source-package-publication-lod.integration.test.ts +29 -0
  64. package/src/__tests__/source-package-publication.integration.test.ts +26 -1
  65. package/src/browser.ts +5 -0
  66. package/src/build-production.ts +498 -141
  67. package/src/ies/ies-importer.ts +126 -0
  68. package/src/ies/parse-lm63.ts +98 -0
  69. package/src/ies/resample-type-c.ts +108 -0
  70. package/src/import-runner.ts +58 -0
  71. package/src/index.ts +49 -19
  72. package/src/mesh-bin.ts +38 -0
  73. package/src/mesh-lod.ts +265 -0
  74. package/src/parameterized-scriptable-pack.ts +725 -0
  75. package/src/scriptable-pack-file-snapshot.ts +346 -0
  76. package/src/scriptable-pack-host.ts +790 -343
  77. package/src/scriptable-pack-output-producers.ts +58 -1
  78. package/src/scriptable-pack-staged-snapshot.ts +12 -11
  79. package/src/scriptable-pack.ts +16 -485
  80. package/src/source-package-errors.ts +6 -0
  81. package/src/source-package-publication.ts +32 -2
  82. package/dist/.tsbuildinfo +0 -1
  83. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts +0 -2
  84. package/dist/__tests__/scriptable-pack-product.contract.test.d.ts.map +0 -1
  85. package/dist/__tests__/scriptable-pack.integration.test.d.ts +0 -2
  86. package/dist/__tests__/scriptable-pack.integration.test.d.ts.map +0 -1
  87. package/dist/scriptable-source-package.d.ts +0 -14
  88. package/dist/scriptable-source-package.d.ts.map +0 -1
  89. package/src/__tests__/scriptable-pack-product.contract.test.ts +0 -46
  90. package/src/__tests__/scriptable-pack.integration.test.ts +0 -435
  91. package/src/scriptable-source-package.ts +0 -88
package/dist/index.mjs CHANGED
@@ -1,20 +1,22 @@
1
- import { ok, validateSourceOverrideMap, ImportError, IMPORT_ERROR_HINTS, canonicalizeSourceOverrides, err, AssetError, MATERIAL_TEXTURE_SLOTS, deriveTextureLayout } from '@forgeax/engine-types';
1
+ import { ok, err, validateSourceOverrideMap, ImportError, IMPORT_ERROR_HINTS, canonicalizeSourceOverrides, AssetError, IES_PROFILE_HEIGHT, IES_PROFILE_WIDTH, IES_PROFILE_BYTE_LENGTH, MATERIAL_TEXTURE_SLOTS, deriveTextureLayout } from '@forgeax/engine-types';
2
2
  export { IMPORT_ERROR_HINTS, ImportError } from '@forgeax/engine-types';
3
- import { resolve, isAbsolute, relative, dirname } from 'path';
4
- import { upgradeLegacyAuthoredPack, loadAssetConfig, resolveAssetSource, finalizePackageTransportSource, catalogSourcePathFor, projectCookedPackageEntry, buildCatalogProjection, createRuntimePackPublication, metaPathForGuid } from '@forgeax/engine-pack/build';
5
- import { createHash } from 'crypto';
6
- import { stat, mkdir, writeFile, rename, rm } from 'fs/promises';
3
+ import { resolve, dirname, isAbsolute, relative } from 'path';
4
+ import { resolveAssetSource, finalizePackageTransportSource, packageTransportRevision, catalogSourcePathFor, buildCatalogProjection, projectPackageCatalog, createRuntimePackPublication, metaPathForGuid } from '@forgeax/engine-pack/build';
5
+ import { AssetGuid as AssetGuid$1, PackageId as PackageId$1 } from '@forgeax/engine-pack/guid';
6
+ import { resolvePackParameterValues, PackageId, isValidPackSourceKey, AssetGuid, parseParameterizedPackJson, projectDirectPackJson, isScriptablePackAssetKind, projectScriptablePackSceneComponents, resolvePackParameterInheritance } from '@forgeax/engine-pack/source';
7
+ import { loadParameterizedScriptablePack } from '@forgeax/engine-pack/source-node';
8
+ import { stat, readFile, mkdir, writeFile, rename, rm, readdir } from 'fs/promises';
7
9
  import { createAcceptedPublication, ddcOutputDigest, DdcGenerationSession, DdcLifecycle } from '@forgeax/engine-ddc';
8
- import { AssetGuid } from '@forgeax/engine-pack/guid';
10
+ import { deriveVertexLayoutProjection, normalizeMeshPayload } from '@forgeax/engine-geometry';
11
+ import { BUILTIN_MESH_ASSETS } from '@forgeax/engine-pack/builtin';
9
12
  import { NativeCookerRegistry } from '@forgeax/engine-pack/native-cooker';
10
- import { projectScriptablePackSceneComponents, projectScriptablePackMeta } from '@forgeax/engine-pack/source';
11
- import { inventoryScriptablePackSource } from '@forgeax/engine-pack/source-node';
13
+ import { isEngineMaterial } from '@forgeax/engine-shader';
12
14
  import { externalizeSceneAsset } from '@forgeax/engine-scene';
13
15
  import { sha256 } from '@noble/hashes/sha2.js';
14
16
  import { bytesToHex } from '@noble/hashes/utils.js';
15
- import { deriveVertexLayoutProjection } from '@forgeax/engine-geometry';
16
17
  import { MESH_BIN_HEADER_V4_BYTES, writeMeshBinHeader } from '@forgeax/engine-pack/mesh-bin-contract';
17
- import { isScriptablePackAssetKind } from '@forgeax/engine-pack';
18
+ import { randomUUID, createHash } from 'crypto';
19
+ import { validatePack, isScriptablePackAssetKind as isScriptablePackAssetKind$1 } from '@forgeax/engine-pack';
18
20
 
19
21
  // src/index.ts
20
22
  function invalidProduct(field) {
@@ -42,8 +44,8 @@ async function sha256Hex(bytes2) {
42
44
  if (subtle === void 0) throw new Error("Web Crypto API is required for importer digests");
43
45
  const owned = new Uint8Array(bytes2.byteLength);
44
46
  owned.set(bytes2);
45
- const digest = await subtle.digest("SHA-256", owned.buffer);
46
- return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
47
+ const digest3 = await subtle.digest("SHA-256", owned.buffer);
48
+ return Array.from(new Uint8Array(digest3), (byte) => byte.toString(16).padStart(2, "0")).join("");
47
49
  }
48
50
  async function artifactDigest(bytes2) {
49
51
  return `sha256:${await sha256Hex(bytes2)}`;
@@ -106,25 +108,192 @@ function finalizeImportProducts(product, inputFingerprint) {
106
108
  const products = [];
107
109
  for (const asset of product.assets) {
108
110
  const artifacts = await artifactDescriptors(asset.artifacts);
109
- const digest = await productDigest(asset, artifacts);
111
+ const digest3 = await productDigest(asset, artifacts);
110
112
  products.push({
111
113
  guid: asset.guid,
112
114
  payload: asset.payload,
113
115
  refs: asset.refs.map((ref2) => ref2.guid),
114
116
  artifacts,
115
- digest,
117
+ digest: digest3,
116
118
  receipt: {
117
119
  guid: asset.guid,
118
120
  origin: "sourceMeta",
119
121
  status: "succeeded",
120
122
  inputFingerprint,
121
- outputDigest: digest
123
+ outputDigest: digest3
122
124
  }
123
125
  });
124
126
  }
125
127
  return products;
126
128
  })();
127
129
  }
130
+ function deriveDefaultLodScreenCoverages(levelCount) {
131
+ if (!Number.isInteger(levelCount) || levelCount < 1 || levelCount > 8) {
132
+ throw new RangeError("levelCount must be an integer in [1, 8]");
133
+ }
134
+ return Array.from(
135
+ { length: levelCount - 1 },
136
+ (_, index) => Math.round(0.5 * 0.4 ** index * 1e6) / 1e6
137
+ );
138
+ }
139
+ function validateMeshLodContract(input) {
140
+ if (input.lods.length > 7) {
141
+ return err({
142
+ code: "mesh-lod-contract-invalid",
143
+ reason: "at most seven lower-detail levels are supported"
144
+ });
145
+ }
146
+ if (input.lodHysteresis !== void 0 && (!Number.isFinite(input.lodHysteresis) || input.lodHysteresis < 0 || input.lodHysteresis >= 1)) {
147
+ return err({
148
+ code: "mesh-lod-contract-invalid",
149
+ reason: "lodHysteresis must be finite and in [0, 1)"
150
+ });
151
+ }
152
+ if (input.generation !== void 0 && (!Number.isSafeInteger(input.generation) || input.generation < 0)) {
153
+ return err({
154
+ code: "mesh-lod-contract-invalid",
155
+ reason: "generation must be a non-negative safe integer"
156
+ });
157
+ }
158
+ const guids = /* @__PURE__ */ new Set();
159
+ let previous = 1;
160
+ for (const level of input.lods) {
161
+ if (level.meshGuid.trim() === "" || guids.has(level.meshGuid)) {
162
+ return err({
163
+ code: "mesh-lod-contract-invalid",
164
+ reason: "LOD mesh GUIDs must be unique and non-empty"
165
+ });
166
+ }
167
+ if (!Number.isFinite(level.screenCoverage) || level.screenCoverage <= 0 || level.screenCoverage > 1) {
168
+ return err({
169
+ code: "mesh-lod-contract-invalid",
170
+ reason: "screenCoverage must be finite and in (0, 1]"
171
+ });
172
+ }
173
+ if (level.screenCoverage >= previous) {
174
+ return err({
175
+ code: "mesh-lod-contract-invalid",
176
+ reason: "screenCoverage must strictly decrease by level"
177
+ });
178
+ }
179
+ guids.add(level.meshGuid);
180
+ previous = level.screenCoverage;
181
+ }
182
+ if (input.refs !== void 0) {
183
+ const refs = new Set(input.refs);
184
+ for (const level of input.lods) {
185
+ if (!refs.has(level.meshGuid)) {
186
+ return err({
187
+ code: "mesh-lod-contract-invalid",
188
+ reason: "every lower-detail mesh GUID must be enclosed by root refs"
189
+ });
190
+ }
191
+ }
192
+ }
193
+ if (input.rootMeshGuid !== void 0 && input.refs !== void 0 && !input.refs.includes(input.rootMeshGuid)) {
194
+ return err({
195
+ code: "mesh-lod-contract-invalid",
196
+ reason: "root mesh GUID must be included in refs"
197
+ });
198
+ }
199
+ if (input.rootBounds !== void 0) {
200
+ if (!validBounds(input.rootBounds)) {
201
+ return err({
202
+ code: "mesh-lod-contract-invalid",
203
+ reason: "root bounds must be finite and ordered"
204
+ });
205
+ }
206
+ for (const bounds of input.lodBounds ?? []) {
207
+ if (!validBounds(bounds) || !encloses(input.rootBounds, bounds)) {
208
+ return err({
209
+ code: "mesh-lod-contract-invalid",
210
+ reason: "root bounds must enclose every lower-detail bounds"
211
+ });
212
+ }
213
+ }
214
+ }
215
+ if (input.lodBounds !== void 0 && input.lodBounds.length !== input.lods.length) {
216
+ return err({
217
+ code: "mesh-lod-contract-invalid",
218
+ reason: "lod bounds must cover every lower level"
219
+ });
220
+ }
221
+ if (input.rootMaterialSlots !== void 0 || input.lodMaterialSlots !== void 0) {
222
+ const rootSlots = input.rootMaterialSlots ?? [];
223
+ const lowerSlots = input.lodMaterialSlots ?? [];
224
+ if (lowerSlots.length !== input.lods.length || lowerSlots.some((slots) => !sameSlots(rootSlots, slots))) {
225
+ return err({
226
+ code: "mesh-lod-contract-invalid",
227
+ reason: "lower-detail material slots must preserve root sourceKey order"
228
+ });
229
+ }
230
+ }
231
+ if (input.relations !== void 0 && hasCycle(input.relations)) {
232
+ return err({ code: "mesh-lod-contract-invalid", reason: "LOD relations must be acyclic" });
233
+ }
234
+ return ok({ lods: input.lods });
235
+ }
236
+ function validBounds(bounds) {
237
+ const [minX, minY, minZ] = bounds.min;
238
+ const [maxX, maxY, maxZ] = bounds.max;
239
+ return [minX, minY, minZ, maxX, maxY, maxZ].every(Number.isFinite) && minX <= maxX && minY <= maxY && minZ <= maxZ;
240
+ }
241
+ function encloses(root, child) {
242
+ return child.min[0] >= root.min[0] && child.min[1] >= root.min[1] && child.min[2] >= root.min[2] && child.max[0] <= root.max[0] && child.max[1] <= root.max[1] && child.max[2] <= root.max[2];
243
+ }
244
+ function sameSlots(root, child) {
245
+ return root.length === child.length && root.every((slot, index) => slot.sourceKey === child[index]?.sourceKey);
246
+ }
247
+ function hasCycle(relations) {
248
+ const edges = /* @__PURE__ */ new Map();
249
+ for (const relation of relations)
250
+ edges.set(relation.from, [...edges.get(relation.from) ?? [], relation.to]);
251
+ const visiting = /* @__PURE__ */ new Set();
252
+ const visited = /* @__PURE__ */ new Set();
253
+ const visit = (node) => {
254
+ if (visiting.has(node)) return true;
255
+ if (visited.has(node)) return false;
256
+ visiting.add(node);
257
+ for (const next of edges.get(node) ?? []) if (visit(next)) return true;
258
+ visiting.delete(node);
259
+ visited.add(node);
260
+ return false;
261
+ };
262
+ return [...edges.keys()].some(visit);
263
+ }
264
+ function reconcileMeshLodMeta(previous, next) {
265
+ const overlap = Math.min(previous.length, next.length);
266
+ for (let index = 0; index < overlap; index++) {
267
+ const oldEntry = previous[index];
268
+ const nextEntry = next[index];
269
+ if (oldEntry?.sourceKey !== nextEntry?.sourceKey) {
270
+ return err({
271
+ code: "mesh-lod-topology-change",
272
+ reason: "existing LOD source keys must remain prefix-stable",
273
+ previousIndices: [index],
274
+ nextIndices: [index]
275
+ });
276
+ }
277
+ if (oldEntry?.meshGuid !== nextEntry?.meshGuid) {
278
+ return err({
279
+ code: "mesh-lod-authority-conflict",
280
+ reason: `sourceKey ${nextEntry?.sourceKey ?? "<missing>"} changed mesh GUID`
281
+ });
282
+ }
283
+ }
284
+ const defaults = deriveDefaultLodScreenCoverages(next.length + 1);
285
+ const lods = next.map((entry, index) => ({
286
+ ...entry,
287
+ screenCoverage: previous[index]?.screenCoverage ?? entry.screenCoverage ?? defaults[index] ?? 0
288
+ }));
289
+ const valid = validateMeshLodContract({
290
+ lods: lods.map(({ meshGuid, screenCoverage }) => ({ meshGuid, screenCoverage }))
291
+ });
292
+ if (!valid.ok) return valid;
293
+ return ok({ lods });
294
+ }
295
+
296
+ // src/import-runner.ts
128
297
  var SHADER_RESERVED_IMPORTER_KEY = "shader";
129
298
  function isModuleLoadFailure(e) {
130
299
  if (!(e instanceof Error)) return false;
@@ -135,6 +304,9 @@ function isModuleLoadFailure(e) {
135
304
  const msg = e.message;
136
305
  return msg.includes("Cannot find module") || msg.includes("native addon") || msg.includes(".node");
137
306
  }
307
+ function declarationsSourceKey(declarations, guid) {
308
+ return declarations.find((declaration) => declaration.guid === guid)?.sourceKey;
309
+ }
138
310
  function normaliseForPack(value) {
139
311
  if (value === null || value === void 0) return value;
140
312
  if (value instanceof Float32Array || value instanceof Float64Array || value instanceof Uint8Array || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array) {
@@ -444,6 +616,37 @@ async function runImport(meta, registry, fs) {
444
616
  );
445
617
  }
446
618
  const produced = product.assets;
619
+ for (const asset of produced) {
620
+ if (asset.kind !== "mesh" || asset.payload === null || typeof asset.payload !== "object")
621
+ continue;
622
+ const payload = asset.payload;
623
+ if (payload.lods === void 0) continue;
624
+ const lods = payload.lods.map((level) => ({
625
+ meshGuid: typeof level.meshGuid === "string" ? level.meshGuid : typeof level.mesh === "string" ? level.mesh : JSON.stringify(level.mesh),
626
+ screenCoverage: level.screenCoverage
627
+ }));
628
+ const validated = validateMeshLodContract({
629
+ lods,
630
+ ...payload.lodHysteresis === void 0 ? {} : { lodHysteresis: payload.lodHysteresis }
631
+ });
632
+ if (!validated.ok) {
633
+ return errResult(
634
+ new ImportError({
635
+ code: validated.error.code,
636
+ expected: "MeshAsset LOD facts to satisfy the shared contract",
637
+ hint: IMPORT_ERROR_HINTS[validated.error.code],
638
+ detail: {
639
+ meshLodSourceKey: declarationsSourceKey(meta.subAssets, asset.guid),
640
+ reason: validated.error.reason,
641
+ ...validated.error.code === "mesh-lod-topology-change" ? {
642
+ previousIndices: validated.error.previousIndices,
643
+ nextIndices: validated.error.nextIndices
644
+ } : {}
645
+ }
646
+ })
647
+ );
648
+ }
649
+ }
447
650
  const declared = new Set(meta.subAssets.map((s) => s.guid));
448
651
  const producedGuids = new Set(produced.map((a) => a.guid));
449
652
  const unexpectedGuids = [...producedGuids].filter((g) => !declared.has(g));
@@ -575,105 +778,645 @@ function projectImportProductForBuild(product) {
575
778
  }))
576
779
  };
577
780
  }
578
- function failure(sourceKey, expected, actual) {
579
- return {
580
- code: "mesh-bin-payload-invalid",
581
- subject: "mesh-bin",
582
- sourceKey,
583
- expected,
584
- actual,
585
- recovery: "re-cook the source with its Meta sidecar through the build-time importer"
586
- };
781
+ function record(value) {
782
+ return value !== null && typeof value === "object" && !Array.isArray(value);
587
783
  }
588
- function asAttributeMap(value) {
589
- return value ?? {};
784
+ function isAsset(value) {
785
+ return record(value) && typeof value.kind === "string" && isScriptablePackAssetKind(value.kind);
590
786
  }
591
- function jsonValue(value) {
592
- if (value instanceof Float32Array || value instanceof Uint16Array) return Array.from(value);
593
- if (Array.isArray(value)) return value.map(jsonValue);
787
+ function clone(value) {
788
+ return structuredClone(value);
789
+ }
790
+ function stable(value) {
791
+ if (value instanceof Uint8Array) return JSON.stringify(Array.from(value));
792
+ if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
594
793
  if (value !== null && typeof value === "object") {
595
- return Object.fromEntries(
596
- Object.entries(value).map(([key, nested]) => [key, jsonValue(nested)])
597
- );
794
+ const object = value;
795
+ return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stable(object[key])}`).join(",")}}`;
598
796
  }
599
- return value;
797
+ return JSON.stringify(value) ?? "null";
600
798
  }
601
- function refsMeta(payload, refs) {
602
- const materialSlots = (payload.materialSlots ?? [{ slotName: "Default" }]).map(
603
- (slot, slotIndex) => {
604
- const defaultMaterial = slot.defaultMaterial;
605
- let defaultMaterialRef;
606
- if (defaultMaterial !== void 0) {
607
- const guid = AssetGuid.format(defaultMaterial);
608
- defaultMaterialRef = refs.findIndex((candidate) => candidate.toLowerCase() === guid);
609
- if (defaultMaterialRef < 0) {
610
- throw new Error(
611
- `material slot ${slotIndex} default material ${guid} is absent from refs`
612
- );
613
- }
799
+ async function digest(value) {
800
+ const crypto = globalThis.crypto?.subtle;
801
+ if (crypto === void 0) throw new Error("Web Crypto API is required for Pack fingerprints");
802
+ const bytes2 = await crypto.digest("SHA-256", new TextEncoder().encode(stable(value)));
803
+ return `sha256:${Array.from(new Uint8Array(bytes2), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
804
+ }
805
+ function observedReader(source) {
806
+ const reads = /* @__PURE__ */ new Map();
807
+ const reader = {
808
+ async readByGuid(guid) {
809
+ const key = AssetGuid.format(guid).toLowerCase();
810
+ const cached = reads.get(key);
811
+ if (cached !== void 0) return ok(clone(cached.asset));
812
+ if (source === void 0) {
813
+ return err(
814
+ new AssetError({
815
+ code: "asset-not-found",
816
+ expected: `a published Asset snapshot for content dependency ${key}`,
817
+ hint: "publish the dependency or remove the content read from the Pack build",
818
+ detail: { sourcePath: key }
819
+ })
820
+ );
614
821
  }
615
- return {
616
- slotName: slot.slotName,
617
- ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
618
- ...defaultMaterialRef === void 0 ? {} : { defaultMaterialRef }
619
- };
822
+ const result = await source.readByGuid(guid);
823
+ if (!result.ok) return result;
824
+ const asset = clone(result.value.asset);
825
+ reads.set(key, {
826
+ guid: key,
827
+ asset,
828
+ generation: result.value.generation,
829
+ digest: result.value.digest
830
+ });
831
+ return ok(clone(asset));
620
832
  }
621
- );
833
+ };
834
+ return { reader, reads };
835
+ }
836
+ function buildContext(packageId, values, reader) {
837
+ if (values === void 0) return { packageId, readByGuid: reader.readByGuid };
838
+ return { packageId, values, readByGuid: reader.readByGuid };
839
+ }
840
+ function sourceKeyFailure(sourcePath, sourceKey) {
622
841
  return {
623
- submeshes: payload.submeshes === void 0 || payload.submeshes.length === 0 ? [{ indexOffset: 0, indexCount: payload.indices?.length ?? 0, materialSlot: 0 }] : payload.submeshes,
624
- materialSlots,
625
- ...payload.aabb === void 0 ? {} : { aabb: jsonValue(payload.aabb) },
626
- ...payload.morphTargets === void 0 ? {} : { morphTargets: jsonValue(payload.morphTargets) },
627
- ...payload.morphWeights === void 0 ? {} : { morphWeights: jsonValue(payload.morphWeights) }
842
+ code: "pack-source-key-invalid",
843
+ expected: "a sourceKey matching the Pack source-key grammar",
844
+ hint: "return stable lower-case semantic keys instead of paths or output indexes",
845
+ detail: { sourcePath, sourceKey }
628
846
  };
629
847
  }
630
- function packMeshBinV4(payload, sourceKey, refs = []) {
631
- try {
632
- const vertices = payload.vertices;
633
- const indices = payload.indices;
634
- if (!(vertices instanceof Float32Array)) {
635
- return err(
636
- failure(sourceKey, "Float32Array interleaved vertices", "vertices is not Float32Array")
848
+ function outputValueError(sourcePath, sourceKey, expected, actual) {
849
+ return {
850
+ code: "pack-parameter-invalid",
851
+ expected,
852
+ hint: "repair the build output and rebuild the Pack from a fresh generation",
853
+ detail: { sourcePath, sourceKey, actual: typeof actual === "string" ? actual : typeof actual }
854
+ };
855
+ }
856
+ function referenceError(code, sourcePath, guids) {
857
+ return {
858
+ code,
859
+ expected: code === "pack-output-reference-missing" ? "every output reference to resolve to a local or published AssetGuid" : "removed output GUIDs to have no incoming references",
860
+ hint: code === "pack-output-reference-missing" ? "build or publish the referenced Pack before verifying this output" : "migrate incoming references before publishing the topology change",
861
+ detail: { sourcePath, guids: [...guids].sort() }
862
+ };
863
+ }
864
+ function productError(sourcePath, sourceKey, producer, product) {
865
+ if (!record(product)) {
866
+ return outputValueError(
867
+ sourcePath,
868
+ sourceKey,
869
+ `producer ${producer.kind} to return an asset product object`,
870
+ product
871
+ );
872
+ }
873
+ if (!Array.isArray(product.refs)) {
874
+ return outputValueError(
875
+ sourcePath,
876
+ sourceKey,
877
+ `producer ${producer.kind} to return a refs array`,
878
+ product.refs
879
+ );
880
+ }
881
+ if (!record(product.artifacts)) {
882
+ return outputValueError(
883
+ sourcePath,
884
+ sourceKey,
885
+ `producer ${producer.kind} to return an artifacts object`,
886
+ product.artifacts
887
+ );
888
+ }
889
+ const payload = product.payload;
890
+ if (!record(payload) || payload.kind !== producer.kind) {
891
+ return outputValueError(
892
+ sourcePath,
893
+ sourceKey,
894
+ `producer ${producer.kind} to return a matching payload kind`,
895
+ payload
896
+ );
897
+ }
898
+ for (const ref2 of product.refs) {
899
+ if (!record(ref2) || typeof ref2.guid !== "string" || !AssetGuid.parse(ref2.guid).ok) {
900
+ return outputValueError(
901
+ sourcePath,
902
+ sourceKey,
903
+ "producer refs to contain valid AssetGuid values",
904
+ ref2
637
905
  );
638
906
  }
639
- if (indices !== void 0 && !(indices instanceof Uint16Array || indices instanceof Uint32Array)) {
907
+ }
908
+ for (const [key, artifact] of Object.entries(product.artifacts)) {
909
+ if (key.length === 0 || key.startsWith("/") || key.includes("..") || key.includes("\\") || !record(artifact) || typeof artifact.mediaType !== "string" || !(artifact.bytes instanceof Uint8Array)) {
910
+ return outputValueError(
911
+ sourcePath,
912
+ sourceKey,
913
+ "asset-local artifacts with safe keys and bytes",
914
+ key
915
+ );
916
+ }
917
+ }
918
+ return void 0;
919
+ }
920
+ function errorCode(value) {
921
+ return record(value) && typeof value.code === "string" ? value.code : void 0;
922
+ }
923
+ function normalizedGuidSet(value) {
924
+ return value === void 0 ? void 0 : new Set([...value].map((guid) => guid.toLowerCase()));
925
+ }
926
+ async function buildParameterizedScriptablePack(options) {
927
+ const subjectPackageId = options.subjectPackageId ?? options.definition.packageId;
928
+ const availableGuids = normalizedGuidSet(options.availableGuids);
929
+ const observed = observedReader(options.assetSource);
930
+ let effectiveValues;
931
+ if ("parameters" in options.definition) {
932
+ const resolved = resolvePackParameterValues(
933
+ options.definition,
934
+ options.values ?? {},
935
+ options.inheritedValues
936
+ );
937
+ if (!resolved.ok) return resolved;
938
+ effectiveValues = resolved.value;
939
+ } else if (options.values !== void 0 && Object.keys(options.values).length > 0) {
940
+ return err(
941
+ outputValueError(
942
+ options.sourcePath,
943
+ "$.values",
944
+ "zero-parameter Packs to omit values and instance capabilities",
945
+ options.values
946
+ )
947
+ );
948
+ } else if (options.subjectPackageId !== void 0 && PackageId.format(options.subjectPackageId).toLowerCase() !== PackageId.format(options.definition.packageId).toLowerCase()) {
949
+ return err({
950
+ code: "pack-parent-not-parameterized",
951
+ expected: "a parameterized Pack source for an independent instance packageId",
952
+ hint: "use clone for a zero-parameter Pack instead of building it as an instance",
953
+ detail: {
954
+ sourcePath: options.sourcePath,
955
+ rootPackageId: PackageId.format(options.definition.packageId),
956
+ subjectPackageId: PackageId.format(options.subjectPackageId)
957
+ }
958
+ });
959
+ }
960
+ let built;
961
+ try {
962
+ const context = buildContext(subjectPackageId, effectiveValues, observed.reader);
963
+ built = options.definition.build(context);
964
+ built = await built;
965
+ } catch (cause) {
966
+ return err(
967
+ new ImportError({
968
+ code: "import-internal-error",
969
+ expected: "Pack build to return a structured Result without throwing",
970
+ hint: "repair the authoring function and return err(...) for expected failures",
971
+ detail: {
972
+ reason: `${options.sourcePath}: ${cause instanceof Error ? cause.message : String(cause)}`
973
+ }
974
+ })
975
+ );
976
+ }
977
+ if (!record(built) || typeof built.ok !== "boolean") {
978
+ return err(
979
+ new ImportError({
980
+ code: "import-internal-error",
981
+ expected: "Pack build to return a Result object",
982
+ hint: "return ok(sourceKeyToAsset) or err(structuredError) from the authoring function",
983
+ detail: { reason: `${options.sourcePath}: malformed build result` }
984
+ })
985
+ );
986
+ }
987
+ if (!built.ok) {
988
+ if (errorCode(built.error) !== void 0) return err(built.error);
989
+ return err(
990
+ new ImportError({
991
+ code: "import-internal-error",
992
+ expected: "a structured Pack build error",
993
+ hint: "return an error carrying code, expected, hint and detail",
994
+ detail: { reason: `${options.sourcePath}: ${String(built.error)}` }
995
+ })
996
+ );
997
+ }
998
+ if (!record(built.value)) {
999
+ return err(
1000
+ outputValueError(
1001
+ options.sourcePath,
1002
+ "$",
1003
+ "build to return a sourceKey-to-Asset object",
1004
+ built.value
1005
+ )
1006
+ );
1007
+ }
1008
+ const imported = [];
1009
+ const stagedOutputs = [];
1010
+ const localGuids = /* @__PURE__ */ new Set();
1011
+ for (const sourceKey of Object.keys(built.value).sort()) {
1012
+ if (!isValidPackSourceKey(sourceKey))
1013
+ return err(sourceKeyFailure(options.sourcePath, sourceKey));
1014
+ const asset = built.value[sourceKey];
1015
+ if (!isAsset(asset)) {
640
1016
  return err(
641
- failure(sourceKey, "Uint16Array or Uint32Array indices", "indices has an unsupported type")
1017
+ outputValueError(
1018
+ options.sourcePath,
1019
+ sourceKey,
1020
+ "a concrete Asset with a supported kind",
1021
+ asset
1022
+ )
642
1023
  );
643
1024
  }
644
- const attributes = asAttributeMap(payload.attributes);
645
- const projection = deriveVertexLayoutProjection(attributes);
646
- if (projection.attributes.length === 0 || projection.arrayStride === 0) {
1025
+ const guid = AssetGuid.format(AssetGuid.derive(subjectPackageId, sourceKey));
1026
+ const normalizedGuid = guid.toLowerCase();
1027
+ if (localGuids.has(normalizedGuid) || availableGuids?.has(normalizedGuid)) {
1028
+ return err({
1029
+ code: "pack-guid-collision",
1030
+ expected: "derived output GUIDs to be unique in the global source index",
1031
+ hint: "change the colliding packageId or repair the source index before publishing",
1032
+ detail: { sourcePath: options.sourcePath, guid }
1033
+ });
1034
+ }
1035
+ localGuids.add(normalizedGuid);
1036
+ const producer = options.outputs.get(asset.kind);
1037
+ if (producer === void 0) {
647
1038
  return err(
648
- failure(
1039
+ outputValueError(
1040
+ options.sourcePath,
649
1041
  sourceKey,
650
- "a non-empty canonical geometry projection",
651
- "projection has no attributes"
1042
+ `a registered output producer for ${asset.kind}`,
1043
+ asset.kind
652
1044
  )
653
1045
  );
654
1046
  }
655
- const vertexCount = payload.vertexCount ?? vertices.byteLength / projection.arrayStride;
656
- if (!Number.isSafeInteger(vertexCount) || vertexCount < 0) {
1047
+ let produced;
1048
+ try {
1049
+ produced = await producer.produce({ guid, sourceKey, asset });
1050
+ } catch (cause) {
657
1051
  return err(
658
- failure(sourceKey, "a non-negative safe vertex cardinality", `vertexCount=${vertexCount}`)
1052
+ new ImportError({
1053
+ code: "import-internal-error",
1054
+ expected: `producer ${producer.kind} to return a structured Result without throwing`,
1055
+ hint: "repair the output producer and return err(...) for expected failures",
1056
+ detail: {
1057
+ reason: `${options.sourcePath}:${sourceKey}: ${cause instanceof Error ? cause.message : String(cause)}`
1058
+ }
1059
+ })
659
1060
  );
660
1061
  }
661
- if (vertices.byteLength !== vertexCount * projection.arrayStride) {
1062
+ if (!record(produced) || typeof produced.ok !== "boolean") {
662
1063
  return err(
663
- failure(
1064
+ outputValueError(
1065
+ options.sourcePath,
664
1066
  sourceKey,
665
- `vertices.byteLength=${vertexCount * projection.arrayStride}`,
666
- `vertices.byteLength=${vertices.byteLength}; stride=${projection.arrayStride}`
1067
+ `producer ${producer.kind} to return a Result`,
1068
+ produced
667
1069
  )
668
1070
  );
669
1071
  }
670
- for (const attribute of projection.attributes) {
671
- const value = attributes[attribute.key];
672
- const components = attribute.byteLength / (attribute.format === "uint16x4" ? 2 : 4);
673
- if (value === void 0 || !(value instanceof Float32Array) && !(value instanceof Uint16Array) || value.length !== vertexCount * components) {
674
- return err(
675
- failure(
676
- sourceKey,
1072
+ if (!produced.ok) {
1073
+ if (errorCode(produced.error) !== void 0) return err(produced.error);
1074
+ return err(
1075
+ new ImportError({
1076
+ code: "import-internal-error",
1077
+ expected: `producer ${producer.kind} to return a structured error`,
1078
+ hint: "return an error carrying code, expected, hint and detail",
1079
+ detail: { reason: `${options.sourcePath}:${sourceKey}: ${String(produced.error)}` }
1080
+ })
1081
+ );
1082
+ }
1083
+ const invalidProduct2 = productError(options.sourcePath, sourceKey, producer, produced.value);
1084
+ if (invalidProduct2 !== void 0) return err(invalidProduct2);
1085
+ const product2 = produced.value;
1086
+ imported.push({
1087
+ guid,
1088
+ kind: asset.kind,
1089
+ payload: product2.payload,
1090
+ refs: product2.refs,
1091
+ artifacts: product2.artifacts
1092
+ });
1093
+ stagedOutputs.push({
1094
+ guid: AssetGuid.derive(subjectPackageId, sourceKey),
1095
+ sourceKey,
1096
+ asset: clone(asset),
1097
+ digest: await digest(asset)
1098
+ });
1099
+ }
1100
+ const referenced = /* @__PURE__ */ new Set();
1101
+ for (const asset of imported) {
1102
+ for (const ref2 of asset.refs) {
1103
+ const guid = ref2.guid.toLowerCase();
1104
+ if (!localGuids.has(guid)) referenced.add(guid);
1105
+ }
1106
+ }
1107
+ if (options.deferReferenceValidation !== true) {
1108
+ const missing = [...referenced].filter(
1109
+ (guid) => availableGuids !== void 0 && !availableGuids.has(guid)
1110
+ );
1111
+ if (missing.length > 0)
1112
+ return err(referenceError("pack-output-reference-missing", options.sourcePath, missing));
1113
+ }
1114
+ const externalEvidence = [...observed.reads.values()].sort((left, right) => left.guid.localeCompare(right.guid)).map(
1115
+ (read) => ({
1116
+ guid: read.guid,
1117
+ usage: referenced.has(read.guid) ? "both" : "content",
1118
+ generation: read.generation,
1119
+ digest: read.digest
1120
+ })
1121
+ );
1122
+ const inputFingerprint = await digest({
1123
+ packageId: PackageId.format(subjectPackageId),
1124
+ sourcePath: options.sourcePath,
1125
+ sourceClosure: options.sourceClosure ?? [],
1126
+ values: effectiveValues,
1127
+ externalEvidence: externalEvidence.map(({ guid, usage, digest: evidenceDigest }) => ({
1128
+ guid,
1129
+ usage,
1130
+ digest: evidenceDigest
1131
+ })),
1132
+ authoringContractVersion: options.authoringContractVersion ?? "parameterized-pack/1",
1133
+ producerVersions: options.outputs.versions()
1134
+ });
1135
+ const refs = imported.flatMap((asset) => asset.refs);
1136
+ const artifacts = Object.fromEntries(
1137
+ imported.flatMap(
1138
+ (asset) => Object.entries(asset.artifacts).map(([key, artifact]) => [`${asset.guid}/${key}`, artifact])
1139
+ )
1140
+ );
1141
+ const product = createImportProduct({
1142
+ assets: imported,
1143
+ sourceDependencies: (options.sourceClosure ?? []).map((entry) => entry.path),
1144
+ refs,
1145
+ artifacts,
1146
+ receipts: imported.map((asset) => ({
1147
+ guid: asset.guid,
1148
+ origin: "authoredPack",
1149
+ status: "succeeded",
1150
+ inputFingerprint
1151
+ })),
1152
+ diagnostics: [],
1153
+ sourceRevision: inputFingerprint,
1154
+ sourceKey: options.sourcePath
1155
+ });
1156
+ if (!product.ok) return err(product.error);
1157
+ return ok({
1158
+ product: product.value,
1159
+ stagedOutputs,
1160
+ externalEvidence,
1161
+ inputFingerprint,
1162
+ ...options.publication === void 0 ? {} : { publication: options.publication }
1163
+ });
1164
+ }
1165
+ function stagedSource(staged, fallback) {
1166
+ return {
1167
+ async readByGuid(guid) {
1168
+ const key = AssetGuid.format(guid).toLowerCase();
1169
+ const local = staged.get(key);
1170
+ if (local !== void 0) {
1171
+ return ok({
1172
+ asset: clone(local.asset),
1173
+ generation: 1,
1174
+ digest: local.digest ?? "sha256:staged"
1175
+ });
1176
+ }
1177
+ if (fallback === void 0) {
1178
+ return err({
1179
+ code: "asset-not-found",
1180
+ expected: "a staged or published content dependency",
1181
+ hint: "wait for the dependency subject to materialize",
1182
+ detail: { guid: key }
1183
+ });
1184
+ }
1185
+ return fallback.readByGuid(guid);
1186
+ }
1187
+ };
1188
+ }
1189
+ async function buildParameterizedScriptablePackWorklist(options) {
1190
+ const orderedSubjects = [...options.subjects].sort(
1191
+ (left, right) => left.sourcePath.localeCompare(right.sourcePath)
1192
+ );
1193
+ const pending = new Map(
1194
+ orderedSubjects.map((subject, index) => [`${index}:${subject.sourcePath}`, subject])
1195
+ );
1196
+ const staged = /* @__PURE__ */ new Map();
1197
+ const results = /* @__PURE__ */ new Map();
1198
+ const availableGuids = /* @__PURE__ */ new Set([
1199
+ ...normalizedGuidSet(options.availableGuids) ?? [],
1200
+ ...BUILTIN_MESH_ASSETS.map((asset) => asset.guid.toLowerCase())
1201
+ ]);
1202
+ const maxPasses = options.maxPasses ?? Math.max(1, options.subjects.length + 1);
1203
+ let iterations = 0;
1204
+ for (; iterations < maxPasses && pending.size > 0; iterations += 1) {
1205
+ let progress = false;
1206
+ const waiting = /* @__PURE__ */ new Set();
1207
+ for (const [key, subject] of pending) {
1208
+ const result = await buildParameterizedScriptablePack({
1209
+ definition: subject.definition,
1210
+ sourcePath: subject.sourcePath,
1211
+ ...subject.subjectPackageId === void 0 ? {} : { subjectPackageId: subject.subjectPackageId },
1212
+ ...subject.values === void 0 ? {} : { values: subject.values },
1213
+ ...subject.inheritedValues === void 0 ? {} : { inheritedValues: subject.inheritedValues },
1214
+ ...subject.sourceClosure === void 0 ? {} : { sourceClosure: subject.sourceClosure },
1215
+ outputs: options.outputs,
1216
+ assetSource: stagedSource(staged, options.assetSource),
1217
+ availableGuids: /* @__PURE__ */ new Set([...availableGuids, ...staged.keys()]),
1218
+ deferReferenceValidation: true
1219
+ });
1220
+ if (result.ok) {
1221
+ results.set(key, result.value);
1222
+ for (const output of result.value.stagedOutputs)
1223
+ staged.set(AssetGuid.format(output.guid).toLowerCase(), output);
1224
+ pending.delete(key);
1225
+ progress = true;
1226
+ continue;
1227
+ }
1228
+ if (errorCode(result.error) === "asset-not-found") {
1229
+ const detail = record(result.error) && record(result.error.detail) ? result.error.detail : void 0;
1230
+ const guid = detail !== void 0 && typeof detail.guid === "string" ? detail.guid : void 0;
1231
+ if (guid !== void 0) waiting.add(guid.toLowerCase());
1232
+ continue;
1233
+ }
1234
+ return result;
1235
+ }
1236
+ if (pending.size === 0) break;
1237
+ if (!progress) {
1238
+ return err({
1239
+ code: "pack-content-dependency-stalled",
1240
+ expected: "the content dependency worklist to make progress",
1241
+ hint: "inspect waitingGuids and repair the missing output or content-read cycle, then rebuild",
1242
+ detail: {
1243
+ waitingGuids: [...waiting].sort(),
1244
+ pendingSubjects: [...pending.values()].map((subject) => subject.sourcePath).sort(),
1245
+ iterations: iterations + 1
1246
+ }
1247
+ });
1248
+ }
1249
+ }
1250
+ if (pending.size > 0) {
1251
+ return err({
1252
+ code: "pack-content-dependency-stalled",
1253
+ expected: "the content dependency worklist to finish within its bounded retry budget",
1254
+ hint: "inspect pendingSubjects and waitingGuids, then repair the dependency graph",
1255
+ detail: {
1256
+ pendingSubjects: [...pending.values()].map((subject) => subject.sourcePath).sort(),
1257
+ waitingGuids: [],
1258
+ iterations
1259
+ }
1260
+ });
1261
+ }
1262
+ const knownGuids = /* @__PURE__ */ new Set([...availableGuids, ...staged.keys()]);
1263
+ const missingReferences = /* @__PURE__ */ new Set();
1264
+ for (const product of results.values()) {
1265
+ const contentReads = new Set(product.externalEvidence.map((read) => read.guid.toLowerCase()));
1266
+ for (const asset of product.product.assets) {
1267
+ for (const reference of asset.refs) {
1268
+ const guid = reference.guid.toLowerCase();
1269
+ if (!knownGuids.has(guid) && !contentReads.has(guid)) missingReferences.add(guid);
1270
+ }
1271
+ }
1272
+ }
1273
+ if (missingReferences.size > 0) {
1274
+ return err(
1275
+ referenceError("pack-output-reference-missing", "worklist", [...missingReferences].sort())
1276
+ );
1277
+ }
1278
+ const removed = [...options.incomingRefs?.keys() ?? []].filter(
1279
+ (guid) => !staged.has(guid.toLowerCase())
1280
+ );
1281
+ const referencedRemoved = removed.filter(
1282
+ (guid) => (options.incomingRefs?.get(guid) ?? []).length > 0
1283
+ );
1284
+ if (referencedRemoved.length > 0)
1285
+ return err(referenceError("pack-output-reference-conflict", "worklist", referencedRemoved));
1286
+ return ok({
1287
+ products: [...results.values()].map((result) => result.product),
1288
+ buildProducts: [...results.values()],
1289
+ stagedOutputs: [...staged.values()],
1290
+ iterations: pending.size === 0 && options.subjects.length > 0 ? iterations + 1 : iterations
1291
+ });
1292
+ }
1293
+ function failure(sourceKey, expected, actual) {
1294
+ return {
1295
+ code: "mesh-bin-payload-invalid",
1296
+ subject: "mesh-bin",
1297
+ sourceKey,
1298
+ expected,
1299
+ actual,
1300
+ recovery: "re-cook the source with its Meta sidecar through the build-time importer"
1301
+ };
1302
+ }
1303
+ function asAttributeMap(value) {
1304
+ return value ?? {};
1305
+ }
1306
+ function jsonValue(value) {
1307
+ if (value instanceof Float32Array || value instanceof Uint16Array) return Array.from(value);
1308
+ if (Array.isArray(value)) return value.map(jsonValue);
1309
+ if (value !== null && typeof value === "object") {
1310
+ return Object.fromEntries(
1311
+ Object.entries(value).map(([key, nested]) => [key, jsonValue(nested)])
1312
+ );
1313
+ }
1314
+ return value;
1315
+ }
1316
+ function refsMeta(payload, refs) {
1317
+ const materialSlots = (payload.materialSlots ?? [{ slotName: "Default" }]).map(
1318
+ (slot, slotIndex) => {
1319
+ const defaultMaterial = slot.defaultMaterial;
1320
+ let defaultMaterialRef;
1321
+ if (defaultMaterial !== void 0) {
1322
+ const guid = AssetGuid$1.format(defaultMaterial);
1323
+ defaultMaterialRef = refs.findIndex((candidate) => candidate.toLowerCase() === guid);
1324
+ if (defaultMaterialRef < 0) {
1325
+ throw new Error(
1326
+ `material slot ${slotIndex} default material ${guid} is absent from refs`
1327
+ );
1328
+ }
1329
+ }
1330
+ return {
1331
+ slotName: slot.slotName,
1332
+ ...slot.sourceKey === void 0 ? {} : { sourceKey: slot.sourceKey },
1333
+ ...defaultMaterialRef === void 0 ? {} : { defaultMaterialRef }
1334
+ };
1335
+ }
1336
+ );
1337
+ if (payload.lods !== void 0 && payload.lods.length > 7) {
1338
+ throw new Error("MeshAsset LOD chain supports at most seven lower-detail levels");
1339
+ }
1340
+ let previousCoverage = 1;
1341
+ const seenLodGuids = /* @__PURE__ */ new Set();
1342
+ const lods = payload.lods?.map((lod, lodIndex) => {
1343
+ const guid = AssetGuid$1.format(lod.mesh).toLowerCase();
1344
+ const meshRef = refs.findIndex((candidate) => candidate.toLowerCase() === guid);
1345
+ if (meshRef < 0) {
1346
+ throw new Error(`LOD ${lodIndex} mesh ${guid} is absent from refs`);
1347
+ }
1348
+ if (seenLodGuids.has(guid)) {
1349
+ throw new Error(`LOD ${lodIndex} mesh ${guid} is duplicated`);
1350
+ }
1351
+ if (!Number.isFinite(lod.screenCoverage) || lod.screenCoverage <= 0 || lod.screenCoverage > 1 || lod.screenCoverage >= previousCoverage) {
1352
+ throw new Error(
1353
+ `LOD ${lodIndex} screenCoverage must be finite, in (0, 1], and strictly decreasing`
1354
+ );
1355
+ }
1356
+ seenLodGuids.add(guid);
1357
+ previousCoverage = lod.screenCoverage;
1358
+ return { meshRef, screenCoverage: lod.screenCoverage };
1359
+ });
1360
+ if (payload.lodHysteresis !== void 0 && (!Number.isFinite(payload.lodHysteresis) || payload.lodHysteresis < 0 || payload.lodHysteresis >= 1)) {
1361
+ throw new Error("lodHysteresis must be finite and in [0, 1)");
1362
+ }
1363
+ return {
1364
+ submeshes: payload.submeshes === void 0 || payload.submeshes.length === 0 ? [{ indexOffset: 0, indexCount: payload.indices?.length ?? 0, materialSlot: 0 }] : payload.submeshes,
1365
+ materialSlots,
1366
+ ...payload.aabb === void 0 ? {} : { aabb: jsonValue(payload.aabb) },
1367
+ ...payload.morphTargets === void 0 ? {} : { morphTargets: jsonValue(payload.morphTargets) },
1368
+ ...payload.morphWeights === void 0 ? {} : { morphWeights: jsonValue(payload.morphWeights) },
1369
+ ...lods === void 0 ? {} : { lods },
1370
+ ...payload.lodHysteresis === void 0 ? {} : { lodHysteresis: payload.lodHysteresis }
1371
+ };
1372
+ }
1373
+ function packMeshBinV4(payload, sourceKey, refs = []) {
1374
+ try {
1375
+ const vertices = payload.vertices;
1376
+ const indices = payload.indices;
1377
+ if (!(vertices instanceof Float32Array)) {
1378
+ return err(
1379
+ failure(sourceKey, "Float32Array interleaved vertices", "vertices is not Float32Array")
1380
+ );
1381
+ }
1382
+ if (indices !== void 0 && !(indices instanceof Uint16Array || indices instanceof Uint32Array)) {
1383
+ return err(
1384
+ failure(sourceKey, "Uint16Array or Uint32Array indices", "indices has an unsupported type")
1385
+ );
1386
+ }
1387
+ const attributes = asAttributeMap(payload.attributes);
1388
+ const projection = deriveVertexLayoutProjection(attributes);
1389
+ if (projection.attributes.length === 0 || projection.arrayStride === 0) {
1390
+ return err(
1391
+ failure(
1392
+ sourceKey,
1393
+ "a non-empty canonical geometry projection",
1394
+ "projection has no attributes"
1395
+ )
1396
+ );
1397
+ }
1398
+ const vertexCount = payload.vertexCount ?? vertices.byteLength / projection.arrayStride;
1399
+ if (!Number.isSafeInteger(vertexCount) || vertexCount < 0) {
1400
+ return err(
1401
+ failure(sourceKey, "a non-negative safe vertex cardinality", `vertexCount=${vertexCount}`)
1402
+ );
1403
+ }
1404
+ if (vertices.byteLength !== vertexCount * projection.arrayStride) {
1405
+ return err(
1406
+ failure(
1407
+ sourceKey,
1408
+ `vertices.byteLength=${vertexCount * projection.arrayStride}`,
1409
+ `vertices.byteLength=${vertices.byteLength}; stride=${projection.arrayStride}`
1410
+ )
1411
+ );
1412
+ }
1413
+ for (const attribute of projection.attributes) {
1414
+ const value = attributes[attribute.key];
1415
+ const components = attribute.byteLength / (attribute.format === "uint16x4" ? 2 : 4);
1416
+ if (value === void 0 || !(value instanceof Float32Array) && !(value instanceof Uint16Array) || value.length !== vertexCount * components) {
1417
+ return err(
1418
+ failure(
1419
+ sourceKey,
677
1420
  `${attribute.key} cardinality=${vertexCount * components}`,
678
1421
  `${attribute.key} cardinality=${value?.byteLength ?? "missing"}`
679
1422
  )
@@ -747,41 +1490,16 @@ function packMeshBinV4(payload, sourceKey, refs = []) {
747
1490
  );
748
1491
  }
749
1492
  }
750
- function commonPathPrefix(paths) {
751
- const first = paths[0];
752
- if (first === void 0) return "";
753
- const firstNormalized = first.replaceAll("\\", "/");
754
- const firstDirectory = firstNormalized.slice(0, firstNormalized.lastIndexOf("/"));
755
- const parts = firstDirectory.split("/");
756
- let length = parts.length;
757
- for (const path of paths.slice(1)) {
758
- const normalized = path.replaceAll("\\", "/");
759
- const candidate = normalized.slice(0, normalized.lastIndexOf("/")).split("/");
760
- length = Math.min(length, candidate.length);
761
- for (let index = 0; index < length; index += 1) {
762
- if (parts[index] !== candidate[index]) {
763
- length = index;
764
- break;
765
- }
766
- }
767
- }
768
- return parts.slice(0, length).join("/");
769
- }
770
- function stableSourceClosure(closure) {
771
- const root = commonPathPrefix(closure.map((entry) => entry.path));
772
- return closure.map((entry) => ({
773
- path: entry.path.replaceAll("\\", "/").slice(root.length).replace(/^\/+/, ""),
774
- digest: entry.digest
775
- })).sort((left, right) => left.path.localeCompare(right.path));
776
- }
1493
+
1494
+ // src/scriptable-pack.ts
777
1495
  var AssetOutputProducerRegistry = class {
778
1496
  producers = /* @__PURE__ */ new Map();
779
1497
  register(producer) {
780
1498
  if (producer.kind.trim().length === 0 || producer.version.trim().length === 0) {
781
- throw new TypeError("ScriptablePack output producer kind and version must be non-empty");
1499
+ throw new TypeError("Pack output producer kind and version must be non-empty");
782
1500
  }
783
1501
  if (typeof producer.produce !== "function") {
784
- throw new TypeError(`ScriptablePack output producer ${producer.kind} must expose produce`);
1502
+ throw new TypeError(`Pack output producer ${producer.kind} must expose produce`);
785
1503
  }
786
1504
  this.producers.set(producer.kind, producer);
787
1505
  }
@@ -794,327 +1512,6 @@ var AssetOutputProducerRegistry = class {
794
1512
  );
795
1513
  }
796
1514
  };
797
- function privateClone(value) {
798
- return structuredClone(value);
799
- }
800
- function record(value) {
801
- return value !== null && typeof value === "object" && !Array.isArray(value);
802
- }
803
- function isAsset(value) {
804
- return record(value) && typeof value.kind === "string" && isScriptablePackAssetKind(value.kind);
805
- }
806
- function isStructuredDomainError(value) {
807
- return value !== null && typeof value === "object" && typeof value.code === "string" && typeof value.expected === "string" && typeof value.hint === "string";
808
- }
809
- function remapBuildFailureSource(value, sourcePath) {
810
- if (value === null || typeof value !== "object") return void 0;
811
- const error = value;
812
- if (error.code !== "pack-source-load-failed") return void 0;
813
- if (error.detail === null || typeof error.detail !== "object") return void 0;
814
- const detail = error.detail;
815
- if (detail.phase !== "build") return void 0;
816
- return {
817
- ...value,
818
- detail: { ...detail, sourcePath }
819
- };
820
- }
821
- function observedAssetReader(source) {
822
- const reads = /* @__PURE__ */ new Map();
823
- const reader = {
824
- async readByGuid(guid) {
825
- const key = AssetGuid.format(guid);
826
- const cached = reads.get(key);
827
- if (cached !== void 0) return ok(privateClone(cached.asset));
828
- if (source === void 0) {
829
- return err(
830
- new AssetError({
831
- code: "asset-not-found",
832
- expected: `a host Asset snapshot source for ScriptablePack content read ${key}`,
833
- hint: "configure the standard ScriptablePack asset source or remove the content read"
834
- })
835
- );
836
- }
837
- const result = await source.readByGuid(guid);
838
- if (!result.ok) return result;
839
- const fixed = privateClone(result.value.asset);
840
- reads.set(key, {
841
- guid: key,
842
- asset: fixed,
843
- generation: result.value.generation,
844
- digest: result.value.digest
845
- });
846
- return ok(privateClone(fixed));
847
- }
848
- };
849
- return { reader, reads };
850
- }
851
- function stable(value) {
852
- if (value instanceof Uint8Array) return JSON.stringify(Array.from(value));
853
- if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
854
- if (value !== null && typeof value === "object") {
855
- const record2 = value;
856
- return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stable(record2[key])}`).join(",")}}`;
857
- }
858
- return JSON.stringify(value) ?? "null";
859
- }
860
- async function fingerprint(value) {
861
- const subtle = globalThis.crypto?.subtle;
862
- if (subtle === void 0)
863
- throw new Error("Web Crypto API is required for ScriptablePack fingerprints");
864
- const digest = await subtle.digest("SHA-256", new TextEncoder().encode(stable(value)));
865
- const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join(
866
- ""
867
- );
868
- return `sha256:${hex}`;
869
- }
870
- function outputError(definition, output) {
871
- const declaredKeys = Object.keys(definition.assets);
872
- const outputKeys = Object.keys(output);
873
- const outputKeySet = new Set(outputKeys);
874
- const declaredKeySet = new Set(declaredKeys);
875
- const missingKeys = declaredKeys.filter((key) => !outputKeySet.has(key));
876
- const unexpectedSourceKeys = outputKeys.filter((key) => !declaredKeySet.has(key));
877
- const kindMismatches = declaredKeys.flatMap((sourceKey) => {
878
- const descriptor = definition.assets[sourceKey];
879
- const value = output[sourceKey];
880
- if (descriptor === void 0 || value === void 0) return [];
881
- const actualKind = value !== null && typeof value === "object" && "kind" in value ? String(value.kind) : typeof value;
882
- return actualKind === descriptor.kind ? [] : [{ sourceKey, expected: descriptor.kind, actual: actualKind }];
883
- });
884
- if (missingKeys.length === 0 && unexpectedSourceKeys.length === 0 && kindMismatches.length === 0) {
885
- return void 0;
886
- }
887
- return {
888
- code: "pack-source-output-invalid",
889
- expected: "build output keys and kinds to exactly match definition.assets",
890
- hint: "inspect sourceKey and GUID topology, then rebuild or cold-cook the ScriptablePack",
891
- detail: {
892
- missingGuids: missingKeys.map(
893
- (key) => AssetGuid.format(definition.assets[key]?.guid)
894
- ),
895
- unexpectedSourceKeys,
896
- kindMismatches
897
- }
898
- };
899
- }
900
- function externalClosureError(declared, referenced, read) {
901
- const used = /* @__PURE__ */ new Set([...referenced, ...read]);
902
- const undeclaredReferencedGuids = [...referenced].filter((guid) => !declared.has(guid));
903
- const undeclaredReadGuids = [...read].filter((guid) => !declared.has(guid));
904
- const unusedDeclaredGuids = [...declared].filter((guid) => !used.has(guid));
905
- if (undeclaredReferencedGuids.length === 0 && undeclaredReadGuids.length === 0 && unusedDeclaredGuids.length === 0) {
906
- return void 0;
907
- }
908
- return {
909
- code: "pack-source-external-closure-mismatch",
910
- expected: "externalAssets GUIDs to equal output external refs union AssetReader reads",
911
- hint: "inspect refs and AssetReader reads, repair GUID declarations, then rebuild or cold-cook",
912
- detail: {
913
- undeclaredReferencedGuids: undeclaredReferencedGuids.sort(),
914
- undeclaredReadGuids: undeclaredReadGuids.sort(),
915
- unusedDeclaredGuids: unusedDeclaredGuids.sort()
916
- }
917
- };
918
- }
919
- function productContractError(sourceKey, kind, product) {
920
- const mismatches = [];
921
- if (product.payload.kind !== kind) {
922
- mismatches.push({ sourceKey, expected: kind, actual: product.payload.kind });
923
- }
924
- for (const reference of product.refs) {
925
- if (!AssetGuid.parse(reference.guid).ok) {
926
- mismatches.push({
927
- sourceKey,
928
- expected: "every producer ref to contain a valid Asset GUID",
929
- actual: reference.guid
930
- });
931
- break;
932
- }
933
- }
934
- for (const [artifactKey, artifact] of Object.entries(product.artifacts)) {
935
- if (artifactKey.length === 0 || artifactKey.startsWith("/") || artifactKey.includes("..") || artifactKey.includes("\\") || artifact.mediaType.trim().length === 0 || !(artifact.bytes instanceof Uint8Array)) {
936
- mismatches.push({
937
- sourceKey,
938
- expected: "asset-local artifacts with safe keys, mediaType, and Uint8Array bytes",
939
- actual: artifactKey
940
- });
941
- break;
942
- }
943
- }
944
- if (mismatches.length === 0) return void 0;
945
- return {
946
- code: "pack-source-output-invalid",
947
- expected: "producer output refs and asset-local artifacts to satisfy the Pack v2 contract",
948
- hint: "inspect sourceKey and artifact provenance, repair refs or bytes, then rebuild or cold-cook",
949
- detail: { missingGuids: [], unexpectedSourceKeys: [], kindMismatches: mismatches }
950
- };
951
- }
952
- async function buildScriptablePack(options) {
953
- const observed = observedAssetReader(options.assetSource);
954
- let built;
955
- try {
956
- const build = options.definition.build;
957
- built = await build(observed.reader);
958
- } catch (error) {
959
- return err(
960
- new ImportError({
961
- code: "import-internal-error",
962
- expected: "ScriptablePack build to return a structured Result without throwing",
963
- hint: "fix the build implementation and return a structured failure for expected authoring errors",
964
- detail: { reason: error instanceof Error ? error.message : String(error) }
965
- })
966
- );
967
- }
968
- if (!built.ok) {
969
- const remappedBuildFailure = remapBuildFailureSource(built.error, options.sourcePath);
970
- if (remappedBuildFailure !== void 0) return err(remappedBuildFailure);
971
- if (built.error instanceof ImportError || built.error instanceof AssetError || isStructuredDomainError(built.error)) {
972
- return err(built.error);
973
- }
974
- return err(
975
- new ImportError({
976
- code: "import-internal-error",
977
- expected: "ScriptablePack build failure to use a structured domain error",
978
- hint: "return an error with code, expected, hint, and optional detail fields",
979
- detail: { reason: String(built.error) }
980
- })
981
- );
982
- }
983
- if (!record(built.value)) {
984
- return err(
985
- new ImportError({
986
- code: "import-internal-error",
987
- expected: "ScriptablePack build output to be an object keyed by declared sourceKey",
988
- hint: "return one concrete Asset payload for each declared sourceKey",
989
- detail: { reason: "build output is not an object" }
990
- })
991
- );
992
- }
993
- const output = built.value;
994
- const invalidOutput = outputError(options.definition, output);
995
- if (invalidOutput !== void 0) return err(invalidOutput);
996
- const assets = [];
997
- for (const sourceKey of Object.keys(options.definition.assets).sort()) {
998
- const descriptor = options.definition.assets[sourceKey];
999
- const asset = output[sourceKey];
1000
- if (descriptor === void 0) continue;
1001
- if (!isAsset(asset)) {
1002
- return err(
1003
- new ImportError({
1004
- code: "import-internal-error",
1005
- expected: `a concrete Asset payload for sourceKey ${sourceKey}`,
1006
- hint: "return a durable Asset with a kind from SCRIPTABLE_PACK_ASSET_KINDS",
1007
- detail: { reason: "build output payload is missing or has an unknown kind" }
1008
- })
1009
- );
1010
- }
1011
- const producer = options.outputs.get(descriptor.kind);
1012
- if (producer === void 0) {
1013
- return err({
1014
- code: "pack-source-output-invalid",
1015
- expected: `a domain output producer registered for kind ${descriptor.kind}`,
1016
- hint: "attach the owning producer capability, inspect registration, then rebuild or cold-cook",
1017
- detail: {
1018
- missingGuids: [AssetGuid.format(descriptor.guid)],
1019
- unexpectedSourceKeys: [],
1020
- kindMismatches: []
1021
- }
1022
- });
1023
- }
1024
- const product2 = await producer.produce({
1025
- guid: AssetGuid.format(descriptor.guid),
1026
- sourceKey,
1027
- asset
1028
- });
1029
- if (!product2.ok) return err(product2.error);
1030
- const productError = productContractError(sourceKey, descriptor.kind, product2.value);
1031
- if (productError !== void 0) return err(productError);
1032
- assets.push({
1033
- guid: AssetGuid.format(descriptor.guid),
1034
- kind: descriptor.kind,
1035
- ...descriptor.name === void 0 ? {} : { name: descriptor.name },
1036
- payload: product2.value.payload,
1037
- refs: product2.value.refs,
1038
- artifacts: product2.value.artifacts
1039
- });
1040
- }
1041
- const local = new Set(assets.map((asset) => asset.guid.toLowerCase()));
1042
- const referenced = new Set(
1043
- assets.flatMap((asset) => asset.refs.map((reference) => reference.guid.toLowerCase())).filter((guid) => !local.has(guid))
1044
- );
1045
- const read = new Set([...observed.reads.keys()].map((guid) => guid.toLowerCase()));
1046
- const declared = new Set(
1047
- Object.values(options.definition.externalAssets).map(
1048
- (guid) => AssetGuid.format(guid).toLowerCase()
1049
- )
1050
- );
1051
- const closureError2 = externalClosureError(declared, referenced, read);
1052
- if (closureError2 !== void 0) return err(closureError2);
1053
- const externalEvidence = [...declared].sort().map((guid) => {
1054
- const observedRead = observed.reads.get(guid);
1055
- const isReference = referenced.has(guid);
1056
- return {
1057
- guid,
1058
- usage: observedRead === void 0 ? "reference" : isReference ? "both" : "content",
1059
- ...observedRead === void 0 ? {} : { generation: observedRead.generation, digest: observedRead.digest }
1060
- };
1061
- });
1062
- const fingerprintEvidence = externalEvidence.map(({ guid, usage, digest }) => ({
1063
- guid,
1064
- usage,
1065
- ...digest === void 0 ? {} : { digest }
1066
- }));
1067
- const inputFingerprint = await fingerprint({
1068
- meta: projectScriptablePackMeta(options.definition, options.sourcePath),
1069
- sceneComponents: projectScriptablePackSceneComponents(options.definition.sceneComponents),
1070
- sourceClosure: stableSourceClosure(options.sourceClosure),
1071
- externalEvidence: fingerprintEvidence,
1072
- authoringContractVersion: options.authoringContractVersion,
1073
- producerVersions: options.outputs.versions()
1074
- });
1075
- const refs = assets.flatMap((asset) => asset.refs);
1076
- const artifacts = Object.fromEntries(
1077
- assets.flatMap(
1078
- (asset) => Object.entries(asset.artifacts).map(([key, artifact]) => [`${asset.guid}/${key}`, artifact])
1079
- )
1080
- );
1081
- const product = createImportProduct({
1082
- assets,
1083
- sourceDependencies: options.sourceClosure.map((entry) => entry.path),
1084
- refs,
1085
- artifacts,
1086
- receipts: assets.map((asset) => ({
1087
- guid: asset.guid,
1088
- origin: "sourceMeta",
1089
- status: "succeeded",
1090
- inputFingerprint
1091
- })),
1092
- diagnostics: [],
1093
- sourceRevision: inputFingerprint,
1094
- sourceKey: options.sourcePath
1095
- });
1096
- if (!product.ok) return err(product.error);
1097
- return ok({
1098
- product: product.value,
1099
- stagedOutputs: await Promise.all(
1100
- Object.keys(options.definition.assets).sort().map(async (sourceKey) => {
1101
- const descriptor = options.definition.assets[sourceKey];
1102
- const asset = output[sourceKey];
1103
- if (descriptor === void 0 || !isAsset(asset)) {
1104
- throw new Error(`validated ScriptablePack output ${sourceKey} is not an Asset`);
1105
- }
1106
- return {
1107
- guid: descriptor.guid,
1108
- sourceKey,
1109
- asset: privateClone(asset),
1110
- digest: await fingerprint(asset)
1111
- };
1112
- })
1113
- ),
1114
- externalEvidence,
1115
- inputFingerprint
1116
- });
1117
- }
1118
1515
 
1119
1516
  // src/scriptable-pack-output-producers.ts
1120
1517
  function producerError(input, reason) {
@@ -1127,11 +1524,11 @@ function producerError(input, reason) {
1127
1524
  }
1128
1525
  function formatGuid(value) {
1129
1526
  if (typeof value === "string") {
1130
- const parsed = AssetGuid.parse(value);
1527
+ const parsed = AssetGuid$1.parse(value);
1131
1528
  if (!parsed.ok) throw parsed.error;
1132
- return AssetGuid.format(parsed.value);
1529
+ return AssetGuid$1.format(parsed.value);
1133
1530
  }
1134
- return AssetGuid.format(value);
1531
+ return AssetGuid$1.format(value);
1135
1532
  }
1136
1533
  function canonical(value) {
1137
1534
  if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
@@ -1143,11 +1540,11 @@ function canonical(value) {
1143
1540
  function particleProgramArtifact(effect) {
1144
1541
  const artifactProgram = { format: effect.program.format, emitters: effect.program.emitters };
1145
1542
  const bytes2 = new TextEncoder().encode(canonical(artifactProgram));
1146
- const fingerprint2 = `sha256:${bytesToHex(sha256(bytes2))}`;
1543
+ const fingerprint = `sha256:${bytesToHex(sha256(bytes2))}`;
1147
1544
  return {
1148
- program: { ...effect.program, fingerprint: fingerprint2 },
1545
+ program: { ...effect.program, fingerprint },
1149
1546
  bytes: bytes2,
1150
- fingerprint: fingerprint2
1547
+ fingerprint
1151
1548
  };
1152
1549
  }
1153
1550
  function materialProduct(input) {
@@ -1165,7 +1562,9 @@ function materialProduct(input) {
1165
1562
  return addRef(value, sourceField);
1166
1563
  };
1167
1564
  const textureFields = material.parameters === void 0 ? new Set(MATERIAL_TEXTURE_SLOTS) : new Set(
1168
- material.parameters.filter((parameter) => parameter.type === "texture").map((parameter) => parameter.name)
1565
+ material.parameters.filter(
1566
+ (parameter) => parameter.type === "texture" || parameter.type === "texture_cube"
1567
+ ).map((parameter) => parameter.name)
1169
1568
  );
1170
1569
  const parent = material.parent === void 0 ? void 0 : addRef(material.parent, { fieldName: "parent" });
1171
1570
  const values = {};
@@ -1212,6 +1611,13 @@ function meshProduct(input) {
1212
1611
  sourceField: { fieldName: "materialSlots", arrayIndex: slotIndex }
1213
1612
  });
1214
1613
  }
1614
+ const seenRefs = new Set(refs.map((reference) => reference.guid.toLowerCase()));
1615
+ for (const [lodIndex, lod] of (mesh.lods ?? []).entries()) {
1616
+ const guid = formatGuid(lod.mesh);
1617
+ if (seenRefs.has(guid.toLowerCase())) continue;
1618
+ seenRefs.add(guid.toLowerCase());
1619
+ refs.push({ guid, sourceField: { fieldName: "lods", arrayIndex: lodIndex } });
1620
+ }
1215
1621
  return {
1216
1622
  payload: mesh,
1217
1623
  refs,
@@ -1252,6 +1658,25 @@ function sceneProduct(input, components) {
1252
1658
  artifacts: {}
1253
1659
  };
1254
1660
  }
1661
+ function containsAssetGuid(value) {
1662
+ if (typeof value === "string") return AssetGuid$1.parse(value).ok;
1663
+ if (Array.isArray(value)) return value.some(containsAssetGuid);
1664
+ if (value !== null && typeof value === "object") {
1665
+ return Object.values(value).some(containsAssetGuid);
1666
+ }
1667
+ return false;
1668
+ }
1669
+ function preExternalizedSceneProduct(input) {
1670
+ if (input.asset.kind !== "scene") throw new TypeError("expected SceneAsset");
1671
+ if (containsAssetGuid(input.asset)) {
1672
+ throw new TypeError("direct scene payload must use explicit refs with runtime indices");
1673
+ }
1674
+ return {
1675
+ payload: { ...input.asset, kind: "scene" },
1676
+ refs: [],
1677
+ artifacts: {}
1678
+ };
1679
+ }
1255
1680
  function createSafeProducer(kind, version, product) {
1256
1681
  return {
1257
1682
  kind,
@@ -1360,7 +1785,7 @@ function ordinaryPodProduct(input) {
1360
1785
  case "tileset": {
1361
1786
  const tileset = asset;
1362
1787
  const refs = tileset.atlases.map((atlas, index) => {
1363
- const parsed = AssetGuid.parse(atlas);
1788
+ const parsed = AssetGuid$1.parse(atlas);
1364
1789
  if (!parsed.ok) throw parsed.error;
1365
1790
  return ref(parsed.value, "atlases", index);
1366
1791
  });
@@ -1393,7 +1818,7 @@ function ordinaryPodProduct(input) {
1393
1818
  }
1394
1819
  case "skin": {
1395
1820
  const skin = asset;
1396
- const skeletonGuid = AssetGuid.parse(skin.skeletonGuid);
1821
+ const skeletonGuid = AssetGuid$1.parse(skin.skeletonGuid);
1397
1822
  if (!skeletonGuid.ok) throw skeletonGuid.error;
1398
1823
  return {
1399
1824
  payload: skin,
@@ -1412,7 +1837,7 @@ function ordinaryPodProduct(input) {
1412
1837
  const refs = [];
1413
1838
  const nodes = graph.nodes.map((node, index) => {
1414
1839
  if (node.type !== "clip") return node;
1415
- const parsed = AssetGuid.parse(node.clip);
1840
+ const parsed = AssetGuid$1.parse(node.clip);
1416
1841
  if (!parsed.ok) throw parsed.error;
1417
1842
  const referenceIndex = refs.push(ref(parsed.value, "nodes", index)) - 1;
1418
1843
  return { ...node, clip: referenceIndex };
@@ -1457,6 +1882,20 @@ function ordinaryPodProduct(input) {
1457
1882
  }
1458
1883
  };
1459
1884
  }
1885
+ case "ies-profile": {
1886
+ const profile = asset;
1887
+ return {
1888
+ payload: profile,
1889
+ refs: [],
1890
+ artifacts: {
1891
+ body: {
1892
+ mediaType: "application/octet-stream",
1893
+ assetCodec: { name: "forgeax-ies-profile", version: "1" },
1894
+ bytes: bytes(profile.data)
1895
+ }
1896
+ }
1897
+ };
1898
+ }
1460
1899
  case "material":
1461
1900
  case "mesh":
1462
1901
  case "scene":
@@ -1471,201 +1910,42 @@ var materialAssetOutputProducer = createSafeProducer(
1471
1910
  var meshAssetOutputProducer = createSafeProducer("mesh", "mesh-binary/4", meshProduct);
1472
1911
  var textureAssetOutputProducer = createSafeProducer(
1473
1912
  "texture",
1474
- "texture-pack/1",
1475
- textureProduct
1476
- );
1477
- function createSceneAssetOutputProducer(sceneComponents = []) {
1478
- const schemas = new Map(
1479
- sceneComponents.map((component) => [component.name, component.fields])
1480
- );
1481
- return createSafeProducer("scene", "scene-pack/3", (input) => sceneProduct(input, schemas));
1482
- }
1483
- function createStandardAssetOutputProducerRegistry(sceneComponents = []) {
1484
- const registry = new AssetOutputProducerRegistry();
1485
- registry.register(materialAssetOutputProducer);
1486
- registry.register(meshAssetOutputProducer);
1487
- registry.register(textureAssetOutputProducer);
1488
- registry.register(createSceneAssetOutputProducer(sceneComponents));
1489
- for (const kind of [
1490
- "equirect",
1491
- "sampler",
1492
- "font",
1493
- "render-pipeline",
1494
- "tileset",
1495
- "video",
1496
- "skeleton",
1497
- "skin",
1498
- "animation-clip",
1499
- "animation-graph",
1500
- "audio",
1501
- "particle-effect"
1502
- ]) {
1503
- registry.register(createSafeProducer(kind, "ordinary-pod/1", ordinaryPodProduct));
1504
- }
1505
- return registry;
1506
- }
1507
- function stable2(value) {
1508
- if (ArrayBuffer.isView(value)) {
1509
- return `${value.constructor.name}:${JSON.stringify(Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)))}`;
1510
- }
1511
- if (Array.isArray(value)) return `[${value.map(stable2).join(",")}]`;
1512
- if (value !== null && typeof value === "object") {
1513
- const record2 = value;
1514
- return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stable2(record2[key])}`).join(",")}}`;
1515
- }
1516
- return JSON.stringify(value) ?? "null";
1517
- }
1518
- async function assetDigest(asset) {
1519
- const bytes2 = new TextEncoder().encode(stable2(asset));
1520
- const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes2);
1521
- return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
1522
- }
1523
- function cycleError(stack, owner, guid) {
1524
- const cycleStart = stack.indexOf(owner);
1525
- const cycle = [...stack.slice(cycleStart), owner];
1526
- return {
1527
- code: "pack-source-build-cycle",
1528
- expected: "an acyclic graph of ScriptablePack content dependencies",
1529
- actual: cycle.join(" -> "),
1530
- hint: "break one content read edge or convert it to a reference-only dependency",
1531
- retryable: false,
1532
- recoveryActions: ["inspect-content-dependency-graph", "edit-source"],
1533
- detail: { sourcePath: owner, sourceKey: guid, incomingRefs: cycle }
1534
- };
1535
- }
1536
- function missingOutputError(owner, guid) {
1537
- return {
1538
- code: "pack-source-output-invalid",
1539
- expected: `owner ${owner} to stage every declared output including ${guid}`,
1540
- hint: "return one output for every GUID declared by the staged owner",
1541
- detail: { missingGuids: [guid], unexpectedSourceKeys: [], kindMismatches: [] }
1542
- };
1543
- }
1544
- function createScriptablePackStagedAssetSnapshotSource(options) {
1545
- const owners = options.declaredExternalOutputs === void 0 || options.declaredExternalOutputs.length === 0 ? options.owners : [
1546
- {
1547
- id: "<declared-pack-external>",
1548
- guids: options.declaredExternalOutputs.map((output) => output.guid),
1549
- async build() {
1550
- return ok(options.declaredExternalOutputs ?? []);
1551
- }
1552
- },
1553
- ...options.owners
1554
- ];
1555
- const ownerByGuid = /* @__PURE__ */ new Map();
1556
- for (const owner of owners) {
1557
- if (owner.id.trim().length === 0) throw new TypeError("staged owner id must be non-empty");
1558
- for (const guid of owner.guids) {
1559
- const key = AssetGuid.format(guid).toLowerCase();
1560
- const existing = ownerByGuid.get(key);
1561
- if (existing !== void 0) {
1562
- throw new TypeError(`staged GUID ${key} is owned by both ${existing.id} and ${owner.id}`);
1563
- }
1564
- ownerByGuid.set(key, owner);
1565
- }
1566
- }
1567
- const snapshots = /* @__PURE__ */ new Map();
1568
- const builds = /* @__PURE__ */ new Map();
1569
- const sourceFor = (stack) => ({
1570
- async readByGuid(guid) {
1571
- const key = AssetGuid.format(guid).toLowerCase();
1572
- const cached = snapshots.get(key);
1573
- if (cached !== void 0) return ok(structuredClone(cached));
1574
- const owner = ownerByGuid.get(key);
1575
- if (owner === void 0) {
1576
- return err(
1577
- new AssetError({
1578
- code: "asset-not-imported",
1579
- expected: "a staged local owner for the requested GUID",
1580
- hint: "declare the local ScriptablePack output before rebuilding the generation"
1581
- })
1582
- );
1583
- }
1584
- if (stack.includes(owner.id)) return err(cycleError(stack, owner.id, key));
1585
- let building = builds.get(owner.id);
1586
- if (building === void 0) {
1587
- building = (async () => {
1588
- const built2 = await owner.build(sourceFor([...stack, owner.id]));
1589
- if (!built2.ok) {
1590
- builds.delete(owner.id);
1591
- return built2;
1592
- }
1593
- const next = /* @__PURE__ */ new Map();
1594
- for (const output of built2.value) {
1595
- const outputGuid = AssetGuid.format(output.guid).toLowerCase();
1596
- if (ownerByGuid.get(outputGuid) !== owner) {
1597
- builds.delete(owner.id);
1598
- return err(missingOutputError(owner.id, outputGuid));
1599
- }
1600
- next.set(outputGuid, {
1601
- asset: structuredClone(output.asset),
1602
- generation: options.generation,
1603
- digest: output.digest ?? await assetDigest(output.asset)
1604
- });
1605
- }
1606
- for (const declared of owner.guids) {
1607
- const declaredGuid = AssetGuid.format(declared).toLowerCase();
1608
- if (!next.has(declaredGuid)) {
1609
- builds.delete(owner.id);
1610
- return err(missingOutputError(owner.id, declaredGuid));
1611
- }
1612
- }
1613
- for (const [outputGuid, snapshot] of next) snapshots.set(outputGuid, snapshot);
1614
- return ok(void 0);
1615
- })();
1616
- builds.set(owner.id, building);
1617
- }
1618
- const built = await building;
1619
- if (!built.ok) return built;
1620
- const staged = snapshots.get(key);
1621
- return staged === void 0 ? err(missingOutputError(owner.id, key)) : ok(structuredClone(staged));
1622
- }
1623
- });
1624
- return sourceFor([]);
1913
+ "texture-pack/1",
1914
+ textureProduct
1915
+ );
1916
+ function createSceneAssetOutputProducer(sceneComponents = []) {
1917
+ const schemas = new Map(
1918
+ sceneComponents.map((component) => [component.name, component.fields])
1919
+ );
1920
+ return createSafeProducer("scene", "scene-pack/3", (input) => sceneProduct(input, schemas));
1625
1921
  }
1626
- var META_ARTIFACT_KEY = "scriptable-pack.meta.json";
1627
- function withPrebuiltMeta(product, anchorGuid, meta) {
1628
- const bytes2 = new TextEncoder().encode(`${JSON.stringify(meta)}
1629
- `);
1630
- return {
1631
- ...product,
1632
- assets: product.assets.map(
1633
- (asset) => asset.guid.toLowerCase() === anchorGuid ? {
1634
- ...asset,
1635
- artifacts: {
1636
- ...asset.artifacts,
1637
- [META_ARTIFACT_KEY]: { mediaType: "application/json", bytes: bytes2 }
1638
- }
1639
- } : asset
1640
- )
1641
- };
1922
+ function createPreExternalizedSceneAssetOutputProducer() {
1923
+ return createSafeProducer("scene", "scene-pack/3", preExternalizedSceneProduct);
1642
1924
  }
1643
- async function produceScriptableSourcePackage(options) {
1644
- const built = await buildScriptablePack(options);
1645
- if (!built.ok) return err(built.error);
1646
- const declaredGuids = Object.values(options.definition.assets).map((descriptor) => AssetGuid.format(descriptor.guid).toLowerCase()).sort();
1647
- const anchorGuid = declaredGuids[0];
1648
- if (anchorGuid === void 0) {
1649
- return err({
1650
- code: "pack-source-output-invalid",
1651
- expected: "at least one declared ScriptablePack output",
1652
- hint: "add an output descriptor before building the package",
1653
- detail: { missingGuids: [], unexpectedSourceKeys: [], kindMismatches: [] }
1654
- });
1925
+ function createStandardAssetOutputProducerRegistry(sceneComponents = []) {
1926
+ const registry = new AssetOutputProducerRegistry();
1927
+ registry.register(materialAssetOutputProducer);
1928
+ registry.register(meshAssetOutputProducer);
1929
+ registry.register(textureAssetOutputProducer);
1930
+ registry.register(createSceneAssetOutputProducer(sceneComponents));
1931
+ for (const kind of [
1932
+ "equirect",
1933
+ "sampler",
1934
+ "font",
1935
+ "render-pipeline",
1936
+ "tileset",
1937
+ "video",
1938
+ "skeleton",
1939
+ "skin",
1940
+ "animation-clip",
1941
+ "animation-graph",
1942
+ "audio",
1943
+ "particle-effect",
1944
+ "ies-profile"
1945
+ ]) {
1946
+ registry.register(createSafeProducer(kind, "ordinary-pod/1", ordinaryPodProduct));
1655
1947
  }
1656
- const product = withPrebuiltMeta(
1657
- built.value.product,
1658
- anchorGuid,
1659
- projectScriptablePackMeta(options.definition, options.sourcePath)
1660
- );
1661
- return ok({
1662
- anchorGuid,
1663
- declaredGuids,
1664
- product,
1665
- stagedOutputs: built.value.stagedOutputs,
1666
- inputFingerprint: built.value.inputFingerprint,
1667
- externalEvidence: built.value.externalEvidence
1668
- });
1948
+ return registry;
1669
1949
  }
1670
1950
  function parseProducerReadiness(value) {
1671
1951
  if (value === void 0 || value === "before-consume" || value === "on-demand") {
@@ -1765,108 +2045,18 @@ function canonicalScriptableSourcePath(sourcePath) {
1765
2045
  const markerIndex = normalized.indexOf(marker);
1766
2046
  return markerIndex < 0 ? normalized : normalized.slice(markerIndex + 1);
1767
2047
  }
1768
- function scriptablePackInputs(sourcePaths, declarations, cwd, generationFor, policyFor, sourceIdentityFor) {
1769
- return sourcePaths.flatMap((sourcePath) => {
1770
- const declaration = declarations.get(resolve(cwd, sourcePath));
1771
- if (declaration?.format !== "pack.ts") return [];
1772
- const projected = sourceIdentityFor?.(sourcePath);
1773
- const catalogSourcePath = projected === void 0 || isAbsolute(projected) ? relative(cwd, sourcePath) : projected;
1774
- const displaySourcePath = canonicalScriptableSourcePath(catalogSourcePath);
1775
- return [
1776
- {
1777
- sourcePath: resolve(cwd, sourcePath),
1778
- displaySourcePath,
1779
- definition: declaration.definition,
1780
- sourceClosure: declaration.sourceClosure,
1781
- publicationGeneration: generationFor(displaySourcePath),
1782
- policy: policyFor(displaySourcePath)
1783
- }
1784
- ];
1785
- });
1786
- }
1787
- async function materializePreparedScriptablePack(prepared, paths, sink) {
1788
- const { product, finalized } = prepared;
1789
- await sink.writePackage(paths.packagePath, JSON.stringify(finalized.pack));
1790
- for (const artifact of finalized.artifacts) {
1791
- await sink.writeArtifact(
1792
- paths.artifactPath(artifact.path),
1793
- artifact.bytes,
1794
- artifact.path.endsWith(".json") ? "application/json" : artifact.mediaType
1795
- );
1796
- }
1797
- const receipts = /* @__PURE__ */ new Map();
1798
- for (const guid of product.declaredGuids) {
1799
- const path = paths.receiptPath(guid);
1800
- await sink.writeReceipt(
1801
- path,
1802
- JSON.stringify({
1803
- guid,
1804
- origin: "sourceMeta",
1805
- status: "succeeded",
1806
- inputFingerprint: product.inputFingerprint,
1807
- outputDigest: finalized.digest
1808
- })
1809
- );
1810
- receipts.set(guid, path);
1811
- }
1812
- return receipts;
1813
- }
1814
- async function readCookedAuthoredPack(authoredPack, cookers = [], sourcePath) {
1815
- const parsed = upgradeLegacyAuthoredPack(authoredPack);
1816
- if (parsed.schemaVersion !== "2.0.0" || parsed.assets === void 0) return void 0;
1817
- const registry = new NativeCookerRegistry();
1818
- for (const cooker of cookers) registry.register(cooker);
1819
- const assets = [];
1820
- const refsByGuid = /* @__PURE__ */ new Map();
1821
- let hasCookedAsset = false;
1822
- for (const asset of parsed.assets) {
1823
- const shouldCook = asset.execution === "cooked";
1824
- if (!shouldCook) {
1825
- assets.push({
1826
- guid: asset.guid,
1827
- kind: asset.kind,
1828
- ...asset.name === void 0 ? {} : { name: asset.name },
1829
- ...asset.sourceKey === void 0 ? {} : { sourceKey: asset.sourceKey },
1830
- ...asset.sourceIndex === void 0 ? {} : { sourceIndex: asset.sourceIndex },
1831
- ...asset.relations === void 0 ? {} : { relations: asset.relations },
1832
- payload: asset.payload,
1833
- refs: asset.refs ?? [],
1834
- artifacts: {}
1835
- });
1836
- continue;
1837
- }
1838
- hasCookedAsset = true;
1839
- const result = await registry.runDraft(asset.kind, {
1840
- guid: asset.guid,
1841
- source: asset.payload,
1842
- ...asset.sourceKey === void 0 ? {} : { sourceKey: asset.sourceKey },
1843
- ...sourcePath === void 0 ? {} : { sourcePath },
1844
- refs: asset.refs ?? []
1845
- });
1846
- if (!result.ok) throw result.error;
1847
- const draft = result.value;
1848
- const refs = [...draft.refs];
1849
- refsByGuid.set(asset.guid.toLowerCase(), refs);
1850
- assets.push({
1851
- guid: draft.guid,
1852
- kind: asset.kind,
1853
- ...asset.name === void 0 ? {} : { name: asset.name },
1854
- ...asset.sourceKey === void 0 ? {} : { sourceKey: asset.sourceKey },
1855
- ...asset.sourceIndex === void 0 ? {} : { sourceIndex: asset.sourceIndex },
1856
- ...asset.relations === void 0 ? {} : { relations: asset.relations },
1857
- payload: draft.payload,
1858
- refs,
1859
- artifacts: draft.artifacts
1860
- });
1861
- }
1862
- return hasCookedAsset ? {
1863
- logicalPackage: { schemaVersion: "2.0.0", kind: "internal-text-package", assets },
1864
- refsByGuid
1865
- } : void 0;
1866
- }
1867
2048
  async function declaredPackExternalOutputs(declarations, cookers = [], requiredGuids = [], externalImport) {
1868
2049
  if (requiredGuids.length === 0) return [];
1869
- const required = new Set(requiredGuids.map((guid) => AssetGuid.format(guid).toLowerCase()));
2050
+ const required = new Set(requiredGuids.map((guid) => AssetGuid$1.format(guid).toLowerCase()));
2051
+ const available = new Set(required);
2052
+ for (const declaration of declarations.values()) {
2053
+ if (declaration.format !== "meta.json") continue;
2054
+ for (const asset of declaration.value.subAssets) {
2055
+ const parsed = AssetGuid$1.parse(asset.guid);
2056
+ if (!parsed.ok) throw parsed.error;
2057
+ available.add(AssetGuid$1.format(parsed.value).toLowerCase());
2058
+ }
2059
+ }
1870
2060
  const outputs = [];
1871
2061
  for (const declaration of declarations.values()) {
1872
2062
  if (declaration.format === "meta.json") {
@@ -1874,22 +2064,10 @@ async function declaredPackExternalOutputs(declarations, cookers = [], requiredG
1874
2064
  if (!declaration.value.subAssets.some((asset) => required.has(asset.guid.toLowerCase()))) {
1875
2065
  continue;
1876
2066
  }
1877
- const assetPaths = externalImport.assetPaths ?? loadAssetConfig(process.cwd()).paths;
1878
- const resolved = resolveAssetSource(
1879
- declaration.sourcePath,
1880
- declaration.value.source,
1881
- assetPaths
1882
- );
1883
- if (!resolved.ok) {
1884
- throw new AssetError({
1885
- code: "asset-not-imported",
1886
- expected: `a resolvable source for Meta dependency ${declaration.sourcePath}`,
1887
- hint: "repair the Meta source path before rebuilding the ScriptablePack"
1888
- });
1889
- }
2067
+ const resolved = resolveAssetSource(declaration.sourcePath, declaration.value.source);
1890
2068
  const meta = {
1891
2069
  importer: declaration.value.importer,
1892
- source: resolved.value,
2070
+ source: resolved,
1893
2071
  sourceRevision: declaration.sourceRevision,
1894
2072
  ...declaration.value.packageId === void 0 ? {} : { packageId: declaration.value.packageId },
1895
2073
  ...declaration.value.provenance === void 0 ? {} : { provenance: declaration.value.provenance },
@@ -1913,70 +2091,515 @@ async function declaredPackExternalOutputs(declarations, cookers = [], requiredG
1913
2091
  if (!sourcePackage.ok) {
1914
2092
  throw new AssetError({
1915
2093
  code: "asset-not-imported",
1916
- expected: `the ${declaration.value.importer} importer to produce Meta dependency outputs`,
1917
- hint: `repair ${declaration.sourcePath} before rebuilding the ScriptablePack`
2094
+ expected: "the Meta importer to produce the requested dependency",
2095
+ hint: "repair the Meta source and rerun the Pack build",
2096
+ detail: { sourcePath: declaration.sourcePath }
1918
2097
  });
1919
2098
  }
1920
2099
  for (const asset of sourcePackage.value.product.assets) {
1921
- const key = asset.guid.toLowerCase();
1922
- if (!required.has(key)) continue;
1923
- const parsed = AssetGuid.parse(asset.guid);
2100
+ if (!required.has(asset.guid.toLowerCase())) continue;
2101
+ const parsed = AssetGuid$1.parse(asset.guid);
1924
2102
  if (!parsed.ok) throw parsed.error;
2103
+ const declared = declaration.value.subAssets.find(
2104
+ (candidate) => candidate.guid.toLowerCase() === asset.guid.toLowerCase()
2105
+ );
1925
2106
  outputs.push({
1926
2107
  guid: parsed.value,
1927
- asset: { kind: asset.kind, ...asset.payload }
2108
+ sourceKey: declared?.sourceKey ?? asset.guid,
2109
+ asset: { ...asset.payload, kind: asset.kind }
1928
2110
  });
1929
2111
  }
1930
2112
  continue;
1931
2113
  }
1932
2114
  if (declaration.format !== "pack.json") continue;
1933
- const declaredAssets = declaration.value.assets.filter(
1934
- (asset) => required.has(asset.guid.toLowerCase())
1935
- );
1936
- if (declaredAssets.length === 0) continue;
2115
+ if (declaration.value.schemaVersion === "3.0.0") {
2116
+ const parsed = parseParameterizedPackJson(declaration.value);
2117
+ if (!parsed.ok || parsed.value.format !== "direct") continue;
2118
+ const projected = projectDirectPackJson(parsed.value);
2119
+ if (!projected.ok) continue;
2120
+ const prepared = await prepareDirectPackTransport({
2121
+ projected: projected.value,
2122
+ sourcePath: declaration.sourcePath,
2123
+ sourceRevision: declaration.sourceRevision,
2124
+ availableGuids: available,
2125
+ ...cookers.length === 0 ? {} : { cookers },
2126
+ policy: {
2127
+ base: "/",
2128
+ packagePath: `assets/${projected.value.packageId}.pack.json`,
2129
+ artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`,
2130
+ sink: () => {
2131
+ }
2132
+ }
2133
+ });
2134
+ if (!prepared.ok) throw prepared.error;
2135
+ const cookedByGuid = new Map(
2136
+ prepared.value.finalized.pack.assets.map((asset) => [asset.guid.toLowerCase(), asset])
2137
+ );
2138
+ for (const asset of projected.value.assets) {
2139
+ if (!required.has(asset.guid.toLowerCase())) continue;
2140
+ const parsedGuid = AssetGuid$1.parse(asset.guid);
2141
+ if (!parsedGuid.ok) throw parsedGuid.error;
2142
+ const cooked2 = cookedByGuid.get(asset.guid.toLowerCase());
2143
+ if (cooked2 === void 0) {
2144
+ throw new AssetError({
2145
+ code: "asset-not-imported",
2146
+ expected: "the direct Pack producer to retain every declared output",
2147
+ hint: "repair the direct Pack output and rerun the Pack build",
2148
+ detail: { sourcePath: declaration.sourcePath }
2149
+ });
2150
+ }
2151
+ outputs.push({
2152
+ guid: parsedGuid.value,
2153
+ sourceKey: asset.sourceKey,
2154
+ asset: { kind: cooked2.kind, ...cooked2.payload }
2155
+ });
2156
+ }
2157
+ continue;
2158
+ }
1937
2159
  const cooked = await readCookedAuthoredPack(declaration.value, cookers, declaration.sourcePath);
1938
- for (const asset of cooked?.logicalPackage.assets.filter(
1939
- (item) => required.has(item.guid.toLowerCase())
1940
- ) ?? declaredAssets) {
1941
- const parsed = AssetGuid.parse(asset.guid);
1942
- if (!parsed.ok) throw parsed.error;
1943
- outputs.push({ guid: parsed.value, asset: { kind: asset.kind, ...asset.payload } });
2160
+ for (const asset of cooked?.logicalPackage.assets ?? declaration.value.assets) {
2161
+ if (!required.has(asset.guid.toLowerCase())) continue;
2162
+ const parsedGuid = AssetGuid$1.parse(asset.guid);
2163
+ if (!parsedGuid.ok) throw parsedGuid.error;
2164
+ outputs.push({
2165
+ guid: parsedGuid.value,
2166
+ sourceKey: asset.sourceKey ?? asset.guid,
2167
+ asset: { ...asset.payload, kind: asset.kind }
2168
+ });
2169
+ }
2170
+ }
2171
+ return outputs;
2172
+ }
2173
+ async function readCookedAuthoredPack(authoredPack, cookers = [], sourcePath) {
2174
+ if (authoredPack.schemaVersion !== "2.0.0") return void 0;
2175
+ const registry = new NativeCookerRegistry();
2176
+ for (const cooker of cookers) registry.register(cooker);
2177
+ const assets = [];
2178
+ const refsByGuid = /* @__PURE__ */ new Map();
2179
+ let hasCookedAsset = false;
2180
+ for (const asset of authoredPack.assets) {
2181
+ if (asset.execution !== "cooked") {
2182
+ assets.push({
2183
+ guid: asset.guid,
2184
+ kind: asset.kind,
2185
+ ...asset.name === void 0 ? {} : { name: asset.name },
2186
+ payload: asset.payload,
2187
+ refs: asset.refs,
2188
+ artifacts: {}
2189
+ });
2190
+ continue;
2191
+ }
2192
+ if (isEngineOwnedMaterial(asset)) {
2193
+ assets.push({
2194
+ guid: asset.guid,
2195
+ kind: asset.kind,
2196
+ ...asset.name === void 0 ? {} : { name: asset.name },
2197
+ payload: asset.payload,
2198
+ refs: asset.refs,
2199
+ artifacts: {}
2200
+ });
2201
+ continue;
2202
+ }
2203
+ hasCookedAsset = true;
2204
+ const result = await registry.runDraft(asset.kind, {
2205
+ guid: asset.guid,
2206
+ // Pack v2 stores the asset kind beside payload. Native producer APIs
2207
+ // consume the runtime Asset shape, so reconstruct that one boundary
2208
+ // field before invoking the cooker.
2209
+ source: nativeCookSource(asset),
2210
+ ...asset.sourceKey === void 0 ? {} : { sourceKey: asset.sourceKey },
2211
+ ...sourcePath === void 0 ? {} : { sourcePath },
2212
+ refs: asset.refs
2213
+ });
2214
+ if (!result.ok) throw result.error;
2215
+ const draft = result.value;
2216
+ refsByGuid.set(asset.guid.toLowerCase(), [...draft.refs]);
2217
+ assets.push({
2218
+ guid: draft.guid,
2219
+ kind: asset.kind,
2220
+ ...asset.name === void 0 ? {} : { name: asset.name },
2221
+ payload: draft.payload,
2222
+ refs: [...draft.refs],
2223
+ artifacts: draft.artifacts
2224
+ });
2225
+ }
2226
+ return hasCookedAsset ? {
2227
+ logicalPackage: { assets },
2228
+ refsByGuid
2229
+ } : void 0;
2230
+ }
2231
+ function directReference(guid) {
2232
+ return { guid, sourceField: { fieldName: "refs" } };
2233
+ }
2234
+ function mergeDirectReferences(produced, declared) {
2235
+ const merged = [...produced];
2236
+ const seen = new Set(produced.map((reference) => reference.guid.toLowerCase()));
2237
+ for (const guid of declared) {
2238
+ const parsed = AssetGuid$1.parse(guid);
2239
+ if (!parsed.ok) throw parsed.error;
2240
+ const normalized = AssetGuid$1.format(parsed.value);
2241
+ if (seen.has(normalized.toLowerCase())) continue;
2242
+ seen.add(normalized.toLowerCase());
2243
+ merged.push(directReference(normalized));
2244
+ }
2245
+ return merged;
2246
+ }
2247
+ function directReferenceFailure(sourcePath, guids) {
2248
+ return {
2249
+ code: "pack-output-reference-missing",
2250
+ expected: "every direct Pack reference to resolve to a local or published AssetGuid",
2251
+ hint: "publish the referenced Pack or repair the direct refs before rebuilding the Pack",
2252
+ detail: { sourcePath, guids: [...guids].sort() }
2253
+ };
2254
+ }
2255
+ function directInputFingerprint(sourceRevision, nativeFingerprints) {
2256
+ if (nativeFingerprints.size === 0) return sourceRevision;
2257
+ return `sha256:${packageTransportRevision({
2258
+ sourceRevision,
2259
+ nativeCookers: [...nativeFingerprints.entries()].sort(
2260
+ ([left], [right]) => left.localeCompare(right)
2261
+ )
2262
+ })}`;
2263
+ }
2264
+ function importedAssetArtifacts(assets) {
2265
+ return Object.fromEntries(
2266
+ assets.flatMap(
2267
+ (asset) => Object.entries(asset.artifacts).map(([key, artifact]) => [`${asset.guid}/${key}`, artifact])
2268
+ )
2269
+ );
2270
+ }
2271
+ function rawDirectProduct(input) {
2272
+ const inputFingerprint = `sha256:${packageTransportRevision({
2273
+ packageId: input.projected.packageId,
2274
+ sourceRevision: input.sourceRevision,
2275
+ assets: input.projected.assets
2276
+ })}`;
2277
+ const assets = input.projected.assets.map((asset) => ({
2278
+ guid: asset.guid,
2279
+ kind: asset.kind,
2280
+ ...asset.name === void 0 ? {} : { name: asset.name },
2281
+ payload: directRuntimePayload(asset),
2282
+ refs: asset.refs.map(directReference),
2283
+ artifacts: {}
2284
+ }));
2285
+ return {
2286
+ product: {
2287
+ assets,
2288
+ sourceDependencies: [input.sourcePath],
2289
+ refs: assets.flatMap((asset) => asset.refs),
2290
+ artifacts: importedAssetArtifacts(assets),
2291
+ receipts: assets.map((asset) => ({
2292
+ guid: asset.guid,
2293
+ origin: "authoredPack",
2294
+ status: "succeeded",
2295
+ inputFingerprint
2296
+ })),
2297
+ diagnostics: [],
2298
+ sourceRevision: inputFingerprint,
2299
+ sourceKey: input.sourcePath
2300
+ },
2301
+ stagedOutputs: input.projected.assets.map((asset) => ({
2302
+ guid: directGuid(asset.guid),
2303
+ sourceKey: asset.sourceKey,
2304
+ asset: {
2305
+ ...directRuntimePayload(asset),
2306
+ kind: asset.kind
2307
+ },
2308
+ digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`
2309
+ })),
2310
+ externalEvidence: [],
2311
+ inputFingerprint
2312
+ };
2313
+ }
2314
+ function directGuid(value) {
2315
+ const parsed = AssetGuid$1.parse(value);
2316
+ if (!parsed.ok) throw parsed.error;
2317
+ return parsed.value;
2318
+ }
2319
+ function nativeRefs(refs) {
2320
+ return refs.map((guid) => {
2321
+ const parsed = AssetGuid$1.parse(guid);
2322
+ if (!parsed.ok) throw parsed.error;
2323
+ return { guid: AssetGuid$1.format(parsed.value) };
2324
+ });
2325
+ }
2326
+ function nativeCookSource(asset) {
2327
+ if (asset.kind !== "material" || asset.payload === null || typeof asset.payload !== "object" || Array.isArray(asset.payload)) {
2328
+ return asset.payload;
2329
+ }
2330
+ return { ...asset.payload, kind: asset.kind };
2331
+ }
2332
+ function isEngineOwnedMaterial(asset) {
2333
+ if (asset.kind !== "material" || asset.payload === null || typeof asset.payload !== "object" || Array.isArray(asset.payload)) {
2334
+ return false;
2335
+ }
2336
+ return isEngineMaterial(asset.payload);
2337
+ }
2338
+ function directRuntimePayload(asset) {
2339
+ if (asset.kind !== "ui" || asset.payload === null || typeof asset.payload !== "object" || Array.isArray(asset.payload)) {
2340
+ return asset.payload;
2341
+ }
2342
+ return { ...asset.payload, guid: asset.guid };
2343
+ }
2344
+ function directAssetForProducer(asset) {
2345
+ const payload = {
2346
+ ...directRuntimePayload(asset),
2347
+ kind: asset.kind
2348
+ };
2349
+ if (asset.kind !== "mesh") return payload;
2350
+ return normalizeMeshPayload(payload, asset.refs) ?? payload;
2351
+ }
2352
+ async function prepareDirectPackTransport(input) {
2353
+ const localGuids = new Set(input.projected.assets.map((asset) => asset.guid.toLowerCase()));
2354
+ const availableGuids = new Set(
2355
+ [...input.availableGuids ?? [], ...BUILTIN_MESH_ASSETS.map((asset) => asset.guid)].filter(
2356
+ (guid) => !localGuids.has(guid.toLowerCase())
2357
+ )
2358
+ );
2359
+ const directArtifacts = input.projected.assets.find(
2360
+ (asset) => asset.artifacts !== void 0 && Object.keys(asset.artifacts).length > 0
2361
+ );
2362
+ if (directArtifacts !== void 0) {
2363
+ return err({
2364
+ code: "pack-parameter-invalid",
2365
+ expected: "direct v3 authoring entries to leave artifacts empty for the producer",
2366
+ hint: "remove authored artifact data and let the registered Asset producer create it",
2367
+ detail: { sourcePath: input.sourcePath, sourceKey: directArtifacts.sourceKey }
2368
+ });
2369
+ }
2370
+ const packageId = PackageId$1.parse(input.projected.packageId);
2371
+ if (!packageId.ok) return err(packageId.error);
2372
+ const cookerRegistry = new NativeCookerRegistry();
2373
+ for (const cooker of input.cookers ?? []) cookerRegistry.register(cooker);
2374
+ const nativeDrafts = /* @__PURE__ */ new Map();
2375
+ const nativeFingerprints = /* @__PURE__ */ new Map();
2376
+ for (const asset of input.projected.assets) {
2377
+ if (!isScriptablePackAssetKind(asset.kind)) continue;
2378
+ if (cookerRegistry.get(asset.kind) === void 0) continue;
2379
+ if (isEngineOwnedMaterial(asset)) continue;
2380
+ const draft = await cookerRegistry.runDraft(asset.kind, {
2381
+ guid: asset.guid,
2382
+ // Direct v3 entries keep `kind` at the entry boundary. Reattach it for
2383
+ // producer contracts such as MaterialAsset, whose payload is otherwise
2384
+ // intentionally allowed to omit the discriminant.
2385
+ source: nativeCookSource(asset),
2386
+ sourceKey: asset.sourceKey,
2387
+ sourcePath: input.sourcePath,
2388
+ refs: asset.refs
2389
+ });
2390
+ if (!draft.ok) return err(draft.error);
2391
+ if (draft.value.guid.toLowerCase() !== asset.guid.toLowerCase()) {
2392
+ return err({
2393
+ code: "pack-parameter-invalid",
2394
+ expected: "a direct Pack cooker to preserve the derived AssetGuid",
2395
+ hint: "repair the native cooker output GUID and rebuild the direct Pack",
2396
+ detail: {
2397
+ sourcePath: input.sourcePath,
2398
+ sourceKey: asset.sourceKey,
2399
+ expectedGuid: asset.guid,
2400
+ actualGuid: draft.value.guid
2401
+ }
2402
+ });
2403
+ }
2404
+ nativeDrafts.set(asset.guid.toLowerCase(), draft.value);
2405
+ nativeFingerprints.set(asset.sourceKey, draft.value.inputFingerprint);
2406
+ }
2407
+ const unsupported = input.projected.assets.filter(
2408
+ (asset) => !isScriptablePackAssetKind(asset.kind)
2409
+ );
2410
+ if (unsupported.length > 0 && unsupported.length !== input.projected.assets.length) {
2411
+ return err({
2412
+ code: "pack-parameter-invalid",
2413
+ expected: "a direct Pack to contain only producer-backed Engine Assets or only direct PODs",
2414
+ hint: "split custom direct PODs from producer-backed Assets into separate Packs",
2415
+ detail: {
2416
+ sourcePath: input.sourcePath,
2417
+ unsupportedKinds: [...new Set(unsupported.map((asset) => asset.kind))].sort()
2418
+ }
2419
+ });
2420
+ }
2421
+ if (unsupported.length === input.projected.assets.length) {
2422
+ const raw = rawDirectProduct(input);
2423
+ const known2 = /* @__PURE__ */ new Set([...availableGuids, ...localGuids]);
2424
+ const missing2 = /* @__PURE__ */ new Set();
2425
+ for (const asset of raw.product.assets) {
2426
+ for (const reference of asset.refs) {
2427
+ if (!known2.has(reference.guid.toLowerCase())) missing2.add(reference.guid.toLowerCase());
2428
+ }
2429
+ }
2430
+ if (missing2.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing2]));
2431
+ const finalized2 = await finalizePackageTransportSource(
2432
+ projectImportProductForBuild(raw.product),
2433
+ input.policy
2434
+ );
2435
+ const facts2 = projectParameterizedPackPublication(raw);
2436
+ if (!facts2.ok) return facts2;
2437
+ const displaySourcePath2 = input.displaySourcePath ?? input.sourcePath;
2438
+ const revision2 = await scriptablePackResourceRevision(displaySourcePath2, raw.inputFingerprint, [
2439
+ { path: input.sourcePath, digest: input.sourceRevision }
2440
+ ]);
2441
+ return ok({
2442
+ projected: input.projected,
2443
+ product: raw,
2444
+ finalized: finalized2,
2445
+ facts: facts2.value,
2446
+ revision: revision2
2447
+ });
2448
+ }
2449
+ const definition = {
2450
+ schemaVersion: "2.0.0",
2451
+ packageId: packageId.value,
2452
+ build: () => ok(
2453
+ Object.fromEntries(
2454
+ input.projected.assets.map((asset) => [
2455
+ asset.sourceKey,
2456
+ {
2457
+ ...nativeDrafts.get(asset.guid.toLowerCase())?.payload ?? directAssetForProducer(asset),
2458
+ kind: asset.kind
2459
+ }
2460
+ ])
2461
+ )
2462
+ )
2463
+ };
2464
+ const outputs = createStandardAssetOutputProducerRegistry();
2465
+ outputs.register(createPreExternalizedSceneAssetOutputProducer());
2466
+ const built = await buildParameterizedScriptablePack({
2467
+ definition,
2468
+ sourcePath: input.sourcePath,
2469
+ outputs,
2470
+ sourceClosure: [{ path: input.sourcePath, digest: input.sourceRevision }],
2471
+ availableGuids,
2472
+ deferReferenceValidation: true
2473
+ });
2474
+ if (!built.ok) return built;
2475
+ const declaredByGuid = new Map(
2476
+ input.projected.assets.map((asset) => [asset.guid.toLowerCase(), asset.refs])
2477
+ );
2478
+ const assets = built.value.product.assets.map((asset) => ({
2479
+ ...asset,
2480
+ ...nativeDrafts.has(asset.guid.toLowerCase()) ? {
2481
+ payload: nativeDrafts.get(asset.guid.toLowerCase())?.payload,
2482
+ refs: mergeDirectReferences(
2483
+ nativeRefs(nativeDrafts.get(asset.guid.toLowerCase())?.refs ?? []),
2484
+ declaredByGuid.get(asset.guid.toLowerCase()) ?? []
2485
+ ),
2486
+ artifacts: nativeDrafts.get(asset.guid.toLowerCase())?.artifacts ?? {}
2487
+ } : {
2488
+ refs: mergeDirectReferences(
2489
+ asset.refs,
2490
+ declaredByGuid.get(asset.guid.toLowerCase()) ?? []
2491
+ )
2492
+ }
2493
+ }));
2494
+ const missing = /* @__PURE__ */ new Set();
2495
+ const known = /* @__PURE__ */ new Set([...availableGuids, ...localGuids]);
2496
+ for (const asset of assets) {
2497
+ for (const reference of asset.refs) {
2498
+ if (!known.has(reference.guid.toLowerCase())) missing.add(reference.guid.toLowerCase());
1944
2499
  }
1945
2500
  }
1946
- return outputs;
2501
+ if (missing.size > 0) return err(directReferenceFailure(input.sourcePath, [...missing]));
2502
+ const inputFingerprint = directInputFingerprint(built.value.inputFingerprint, nativeFingerprints);
2503
+ const stagedOutputs = input.projected.assets.map((asset) => ({
2504
+ guid: directGuid(asset.guid),
2505
+ sourceKey: asset.sourceKey,
2506
+ asset: directAssetForProducer(asset),
2507
+ digest: `sha256:${packageTransportRevision({ kind: asset.kind, payload: asset.payload })}`
2508
+ }));
2509
+ const product = {
2510
+ ...built.value,
2511
+ inputFingerprint,
2512
+ stagedOutputs,
2513
+ product: {
2514
+ ...built.value.product,
2515
+ assets,
2516
+ refs: assets.flatMap((asset) => asset.refs),
2517
+ artifacts: importedAssetArtifacts(assets),
2518
+ receipts: built.value.product.receipts.map((receipt) => ({
2519
+ ...receipt,
2520
+ inputFingerprint
2521
+ })),
2522
+ sourceRevision: inputFingerprint
2523
+ }
2524
+ };
2525
+ const finalized = await finalizePackageTransportSource(
2526
+ projectImportProductForBuild(product.product),
2527
+ input.policy
2528
+ );
2529
+ const facts = projectParameterizedPackPublication(product);
2530
+ if (!facts.ok) return facts;
2531
+ const displaySourcePath = input.displaySourcePath ?? input.sourcePath;
2532
+ const revision = await scriptablePackResourceRevision(
2533
+ displaySourcePath,
2534
+ product.inputFingerprint,
2535
+ [{ path: input.sourcePath, digest: input.sourceRevision }]
2536
+ );
2537
+ return ok({
2538
+ projected: input.projected,
2539
+ product,
2540
+ finalized,
2541
+ facts: facts.value,
2542
+ revision
2543
+ });
1947
2544
  }
1948
- async function prepareAuthoredPackTransport(authoredPack, cookers, policyFor, sourcePath) {
1949
- const pack = upgradeLegacyAuthoredPack(authoredPack);
1950
- const firstGuid = pack.assets?.[0]?.guid?.toLowerCase();
1951
- if (pack.schemaVersion !== "2.0.0" || firstGuid === void 0) {
1952
- return { pack, ...firstGuid === void 0 ? {} : { firstGuid } };
2545
+ async function prepareLegacyPackTransport(authoredPack, cookers, policyFor, sourcePath) {
2546
+ const firstGuid = authoredPack.assets[0]?.guid?.toLowerCase();
2547
+ if (authoredPack.schemaVersion !== "2.0.0" || firstGuid === void 0) {
2548
+ return { pack: authoredPack, ...firstGuid === void 0 ? {} : { firstGuid } };
1953
2549
  }
1954
- const cooked = await readCookedAuthoredPack(pack, cookers, sourcePath);
1955
- if (cooked === void 0) return { pack, firstGuid };
2550
+ const cooked = await readCookedAuthoredPack(authoredPack, cookers, sourcePath);
2551
+ if (cooked === void 0) return { pack: authoredPack, firstGuid };
1956
2552
  return {
1957
- pack,
2553
+ pack: authoredPack,
1958
2554
  firstGuid,
1959
2555
  cooked,
1960
2556
  finalized: await finalizePackageTransportSource(cooked.logicalPackage, policyFor(firstGuid))
1961
2557
  };
1962
2558
  }
1963
- function projectScriptablePackPublication(product) {
2559
+ async function materializePreparedParameterizedScriptablePack(prepared, paths, sink) {
2560
+ const { product, finalized } = prepared;
2561
+ await sink.writePackage(paths.packagePath, JSON.stringify(finalized.pack));
2562
+ for (const artifact of finalized.artifacts) {
2563
+ await sink.writeArtifact(
2564
+ paths.artifactPath(artifact.path),
2565
+ artifact.bytes,
2566
+ artifact.path.endsWith(".json") ? "application/json" : artifact.mediaType
2567
+ );
2568
+ }
2569
+ const receipts = /* @__PURE__ */ new Map();
2570
+ for (const asset of product.product.assets) {
2571
+ const path = paths.receiptPath(asset.guid);
2572
+ await sink.writeReceipt(
2573
+ path,
2574
+ JSON.stringify({
2575
+ guid: asset.guid,
2576
+ origin: "sourceMeta",
2577
+ status: "succeeded",
2578
+ inputFingerprint: product.inputFingerprint,
2579
+ outputDigest: finalized.digest
2580
+ })
2581
+ );
2582
+ receipts.set(asset.guid.toLowerCase(), path);
2583
+ }
2584
+ return receipts;
2585
+ }
2586
+ function projectParameterizedPackPublication(product) {
1964
2587
  const assets = new Map(product.product.assets.map((asset) => [asset.guid.toLowerCase(), asset]));
1965
2588
  const outputs = [];
1966
2589
  for (const staged of product.stagedOutputs) {
1967
- const guid = AssetGuid.format(staged.guid).toLowerCase();
2590
+ const guid = AssetGuid$1.format(staged.guid).toLowerCase();
1968
2591
  const asset = assets.get(guid);
1969
- if (asset === void 0 || staged.digest === void 0) {
2592
+ if (asset === void 0 || staged.digest === void 0 || staged.sourceKey === void 0) {
1970
2593
  return err({
1971
2594
  code: "pack-source-output-invalid",
1972
- expected: `ScriptablePack publication output ${guid} to include a product and digest`,
1973
- hint: "repair the ScriptablePack producer output and rebuild",
2595
+ expected: `parameterized Pack publication output ${guid} to include a product, sourceKey, and digest`,
2596
+ hint: "repair the dynamic Pack output and rebuild the current generation",
1974
2597
  detail: { stage: "publication", guid }
1975
2598
  });
1976
2599
  }
1977
2600
  outputs.push({
1978
2601
  guid,
1979
- sourceKey: staged.sourceKey ?? guid,
2602
+ sourceKey: staged.sourceKey,
1980
2603
  kind: asset.kind,
1981
2604
  digest: staged.digest,
1982
2605
  refs: asset.refs.map((reference) => reference.guid)
@@ -1992,130 +2615,115 @@ function projectScriptablePackPublication(product) {
1992
2615
  )
1993
2616
  });
1994
2617
  }
1995
- async function scriptablePackResourceRevision(displaySourcePath, digest, sourceClosure) {
1996
- const observedAt = Math.trunc(
1997
- Math.max(
1998
- ...await Promise.all(sourceClosure.map(async (entry) => (await stat(entry.path)).mtimeMs))
1999
- )
2618
+ function snapshotSourceForStagedOutputs(outputs) {
2619
+ if (outputs.length === 0) return void 0;
2620
+ const byGuid = new Map(
2621
+ outputs.map((output) => [AssetGuid$1.format(output.guid).toLowerCase(), output])
2000
2622
  );
2001
- return { digest, observedAt, rootId: displaySourcePath };
2002
- }
2003
- async function produceScriptablePackProducts(sources, declaredExternalOutputs = []) {
2004
- const entries = /* @__PURE__ */ new Map();
2005
- for (const source of sources) {
2006
- if (entries.has(source.displaySourcePath)) continue;
2007
- entries.set(source.displaySourcePath, {
2008
- source,
2009
- definition: source.definition,
2010
- closure: source.sourceClosure
2011
- });
2012
- }
2013
- const products = /* @__PURE__ */ new Map();
2014
- const owners = [...entries.entries()].map(
2015
- ([displaySourcePath, entry]) => ({
2016
- id: displaySourcePath,
2017
- guids: Object.values(entry.definition.assets).map((asset) => asset.guid),
2018
- async build(source) {
2019
- const produced = await produceScriptableSourcePackage({
2020
- definition: entry.definition,
2021
- sourcePath: displaySourcePath,
2022
- assetSource: source,
2023
- outputs: createStandardAssetOutputProducerRegistry(
2024
- projectScriptablePackSceneComponents(entry.definition.sceneComponents)
2025
- ),
2026
- sourceClosure: entry.closure,
2027
- authoringContractVersion: "scriptable-pack-production/1"
2028
- });
2029
- if (!produced.ok) return produced;
2030
- const observedClosure = await inventoryScriptablePackSource(entry.source.sourcePath);
2031
- if (JSON.stringify(observedClosure) !== JSON.stringify(entry.closure)) {
2032
- return err({
2033
- code: "pack-source-load-failed",
2034
- expected: "the complete ScriptablePack module closure to remain fixed during one build",
2035
- hint: "retry the build after source and helper writes have settled",
2036
- detail: {
2037
- sourcePath: displaySourcePath,
2038
- reason: "source-changed",
2039
- phase: "build",
2040
- diagnostic: "module closure changed while the staged generation was building"
2041
- }
2042
- });
2043
- }
2044
- products.set(displaySourcePath, produced.value);
2045
- return ok(produced.value.stagedOutputs);
2623
+ return {
2624
+ async readByGuid(guid) {
2625
+ const key = AssetGuid$1.format(guid).toLowerCase();
2626
+ const output = byGuid.get(key);
2627
+ if (output === void 0) {
2628
+ return err(
2629
+ new AssetError({
2630
+ code: "asset-not-found",
2631
+ expected: `an available Asset snapshot for ${key}`,
2632
+ hint: "publish the referenced Pack or repair the content dependency"
2633
+ })
2634
+ );
2046
2635
  }
2047
- })
2048
- );
2049
- const generationDigest = createHash("sha256").update(
2050
- JSON.stringify(
2051
- [...entries.values()].flatMap((entry) => entry.closure).sort((a, b) => a.path.localeCompare(b.path))
2052
- )
2053
- ).digest();
2054
- const stagedSource = createScriptablePackStagedAssetSnapshotSource({
2055
- generation: generationDigest.readUInt32BE(0),
2056
- owners,
2057
- declaredExternalOutputs
2058
- });
2059
- const prepared = /* @__PURE__ */ new Map();
2060
- for (const source of sources) {
2061
- const entry = entries.get(source.displaySourcePath);
2062
- if (entry === void 0) {
2063
- return err({
2064
- code: "pack-source-path-invalid",
2065
- expected: "a ScriptablePack source registered in the current inventory",
2066
- actual: source.displaySourcePath,
2067
- hint: "rebuild the source inventory before producing this path",
2068
- retryable: true,
2069
- recoveryActions: ["rebuild-source-inventory"],
2070
- detail: { sourcePath: source.displaySourcePath }
2636
+ return ok({
2637
+ asset: structuredClone(output.asset),
2638
+ generation: 1,
2639
+ digest: output.digest ?? "sha256:staged"
2071
2640
  });
2072
2641
  }
2073
- const first = Object.values(entry.definition.assets)[0];
2074
- if (first === void 0) {
2075
- return err({
2076
- code: "pack-source-output-invalid",
2077
- expected: "at least one declared ScriptablePack output",
2078
- hint: "add an output descriptor before building the package",
2079
- detail: { missingGuids: [], unexpectedSourceKeys: [], kindMismatches: [] }
2080
- });
2642
+ };
2643
+ }
2644
+ function composeParameterizedAssetSource(staged, published) {
2645
+ if (staged === void 0) return published;
2646
+ if (published === void 0) return staged;
2647
+ return {
2648
+ async readByGuid(guid) {
2649
+ const local = await staged.readByGuid(guid);
2650
+ if (local.ok || local.error.code !== "asset-not-found") return local;
2651
+ return published.readByGuid(guid);
2081
2652
  }
2082
- const staged = await stagedSource.readByGuid(first.guid);
2083
- if (!staged.ok) return err(staged.error);
2084
- const produced = products.get(source.displaySourcePath);
2085
- if (produced === void 0) {
2653
+ };
2654
+ }
2655
+ async function produceParameterizedScriptablePackProducts(options) {
2656
+ if (options.sources.length === 0) return ok(/* @__PURE__ */ new Map());
2657
+ const external = options.declaredExternalOutputs ?? [];
2658
+ const assetSource = composeParameterizedAssetSource(
2659
+ snapshotSourceForStagedOutputs(external),
2660
+ options.assetSource
2661
+ );
2662
+ const availableGuids = /* @__PURE__ */ new Set([
2663
+ ...options.availableGuids ?? [],
2664
+ ...external.map((output) => AssetGuid$1.format(output.guid).toLowerCase())
2665
+ ]);
2666
+ const workItems = options.sources.map((source) => ({
2667
+ definition: source.definition,
2668
+ sourcePath: source.sourcePath,
2669
+ ...source.subjectPackageId === void 0 ? {} : { subjectPackageId: source.subjectPackageId },
2670
+ ...source.values === void 0 ? {} : { values: source.values },
2671
+ ...source.inheritedValues === void 0 ? {} : { inheritedValues: source.inheritedValues },
2672
+ sourceClosure: source.sourceClosure
2673
+ }));
2674
+ const worklist = await buildParameterizedScriptablePackWorklist({
2675
+ subjects: workItems,
2676
+ outputs: createStandardAssetOutputProducerRegistry(
2677
+ options.sources.flatMap(
2678
+ (source) => projectScriptablePackSceneComponents(source.definition.sceneComponents)
2679
+ )
2680
+ ),
2681
+ ...assetSource === void 0 ? {} : { assetSource },
2682
+ availableGuids,
2683
+ ...options.incomingRefs === void 0 ? {} : { incomingRefs: options.incomingRefs },
2684
+ ...options.maxPasses === void 0 ? {} : { maxPasses: options.maxPasses }
2685
+ });
2686
+ if (!worklist.ok) return worklist;
2687
+ const productsBySource = new Map(
2688
+ worklist.value.buildProducts.filter((product) => product.product.sourceKey !== void 0).map((product) => [product.product.sourceKey, product])
2689
+ );
2690
+ const prepared = /* @__PURE__ */ new Map();
2691
+ for (const source of options.sources) {
2692
+ const product = productsBySource.get(source.sourcePath);
2693
+ if (product === void 0) {
2086
2694
  return err({
2087
2695
  code: "pack-source-output-invalid",
2088
- expected: "the staged owner build to retain its source-package product",
2089
- hint: "retry the ScriptablePack production attempt",
2090
- detail: {
2091
- missingGuids: [AssetGuid.format(first.guid)],
2092
- unexpectedSourceKeys: [],
2093
- kindMismatches: []
2094
- }
2696
+ expected: "one terminal dynamic Pack product for every source subject",
2697
+ hint: "rerun the bounded generation worklist and inspect its subject map",
2698
+ detail: { sourcePath: source.sourcePath }
2095
2699
  });
2096
2700
  }
2097
- const transportPolicy = typeof source.policy === "function" ? source.policy(produced) : source.policy;
2098
- const logicalPackage = projectImportProductForBuild(produced.product);
2099
- const finalized = await finalizePackageTransportSource(logicalPackage, transportPolicy);
2100
- const facts = projectScriptablePackPublication(produced);
2101
- if (!facts.ok) return err(facts.error);
2701
+ const policy = typeof source.policy === "function" ? source.policy(product) : source.policy;
2702
+ const finalized = await finalizePackageTransportSource(
2703
+ projectImportProductForBuild(product.product),
2704
+ policy
2705
+ );
2706
+ const facts = projectParameterizedPackPublication(product);
2707
+ if (!facts.ok) return facts;
2102
2708
  const revision = await scriptablePackResourceRevision(
2103
2709
  source.displaySourcePath,
2104
- produced.inputFingerprint,
2710
+ product.inputFingerprint,
2105
2711
  source.sourceClosure
2106
2712
  );
2107
2713
  const publication = createAcceptedPublication({
2108
2714
  sourcePath: source.displaySourcePath,
2109
- sourceRevision: produced.inputFingerprint,
2715
+ sourceRevision: product.inputFingerprint,
2110
2716
  generation: source.publicationGeneration,
2111
2717
  digest: finalized.digest,
2112
2718
  packageUrl: finalized.packageUrl,
2113
- inputFingerprint: produced.inputFingerprint,
2719
+ inputFingerprint: product.inputFingerprint,
2114
2720
  outputs: facts.value.outputs,
2115
- externalEvidence: produced.externalEvidence
2721
+ externalEvidence: product.externalEvidence
2116
2722
  });
2117
2723
  prepared.set(source.displaySourcePath, {
2118
- product: produced,
2724
+ product: {
2725
+ ...product
2726
+ },
2119
2727
  finalized,
2120
2728
  facts: facts.value,
2121
2729
  revision,
@@ -2124,6 +2732,22 @@ async function produceScriptablePackProducts(sources, declaredExternalOutputs =
2124
2732
  }
2125
2733
  return ok(prepared);
2126
2734
  }
2735
+ async function scriptablePackResourceRevision(displaySourcePath, digest3, sourceClosure) {
2736
+ const mtimes = await Promise.all(
2737
+ sourceClosure.map(async (entry) => {
2738
+ try {
2739
+ return (await stat(entry.path)).mtimeMs;
2740
+ } catch (error) {
2741
+ if (error !== null && typeof error === "object" && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
2742
+ return 0;
2743
+ }
2744
+ throw error;
2745
+ }
2746
+ })
2747
+ );
2748
+ const observedAt = mtimes.length === 0 ? 0 : Math.trunc(Math.max(...mtimes));
2749
+ return { digest: digest3, observedAt, rootId: displaySourcePath };
2750
+ }
2127
2751
  function normalizeCatalogPath(path) {
2128
2752
  return path.replaceAll("\\", "/").replace(/^\.\//, "");
2129
2753
  }
@@ -2146,120 +2770,273 @@ function scriptableArtifactPath(guid, key) {
2146
2770
  return `${guid.toLowerCase()}/${key.includes(".") ? key : `${key}.bin`}`;
2147
2771
  }
2148
2772
  function runtimePublicationFor(context, input) {
2149
- return createRuntimePackPublication({
2773
+ const publicationInput = {
2150
2774
  pack: input.pack,
2151
2775
  scopeId: context.runtimeBinding?.scopeId ?? "asset-runtime",
2152
2776
  sourcePath: input.sourcePath,
2153
2777
  sourceRevision: input.sourceRevision,
2154
2778
  packageUrl: input.packageUrl,
2155
2779
  ...input.digest === void 0 ? {} : { digest: input.digest },
2780
+ ...input.inputFingerprint === void 0 ? {} : { inputFingerprint: input.inputFingerprint },
2781
+ ...input.outputs === void 0 ? {} : { outputs: input.outputs },
2782
+ ...input.externalEvidence === void 0 ? {} : { externalEvidence: input.externalEvidence },
2156
2783
  generation: context.runtimeBinding?.generation ?? context.generation
2784
+ };
2785
+ if (input.sourceKeys === void 0) return createRuntimePackPublication(publicationInput);
2786
+ const derived = createRuntimePackPublication(publicationInput);
2787
+ const outputs = derived.publication.outputs.map((output) => ({
2788
+ ...output,
2789
+ sourceKey: input.sourceKeys?.get(output.guid.toLowerCase()) ?? output.sourceKey
2790
+ }));
2791
+ return createRuntimePackPublication({ ...publicationInput, outputs });
2792
+ }
2793
+ function sourceKeysFor(declarations) {
2794
+ return new Map(
2795
+ declarations.flatMap(
2796
+ (declaration) => declaration.sourceKey === void 0 ? [] : [[declaration.guid.toLowerCase(), declaration.sourceKey]]
2797
+ )
2798
+ );
2799
+ }
2800
+ function dynamicFailure(context, value) {
2801
+ const candidate = value !== null && typeof value === "object" ? value : {};
2802
+ const error = candidate;
2803
+ throw context.fail({
2804
+ code: typeof error.code === "string" ? error.code : "parameterized-pack-build-failed",
2805
+ expected: typeof error.expected === "string" ? error.expected : "the parameterized Pack generation to produce a valid terminal result",
2806
+ hint: typeof error.hint === "string" ? error.hint : "inspect the parameterized Pack subject and rebuild the current generation",
2807
+ ...error.detail === void 0 ? {} : { detail: error.detail }
2157
2808
  });
2158
2809
  }
2159
- async function buildAuthoredPackages(context) {
2160
- const scriptableSources = [...context.inventory.sourceDeclarations.entries()].filter(([, declaration]) => declaration.format === "pack.ts").map(([sourcePath]) => sourcePath);
2161
- if (scriptableSources.length === 0) return /* @__PURE__ */ new Map();
2162
- const inputs = scriptablePackInputs(
2163
- scriptableSources,
2164
- context.inventory.sourceDeclarations,
2165
- context.cwd,
2166
- () => context.generation,
2167
- () => (product) => ({
2168
- base: context.basePrefix === "" ? "/" : context.basePrefix,
2169
- packagePath: `assets/${product.anchorGuid}.pack.json`,
2170
- artifactPath: scriptableArtifactPath
2171
- }),
2172
- context.fsForImport.sourceIdentityFor
2810
+ function parameterizedCatalogSourcePath(context, sourcePath) {
2811
+ const projected = context.fsForImport.sourceIdentityFor?.(sourcePath);
2812
+ const logical = projected === void 0 || isAbsolute(projected) ? relative(context.cwd, sourcePath) : projected;
2813
+ return canonicalScriptableSourcePath(logical).replaceAll("\\", "/");
2814
+ }
2815
+ async function buildParameterizedPackages(context) {
2816
+ const subjects = /* @__PURE__ */ new Map();
2817
+ for (const [sourcePath, declaration] of context.inventory.sourceDeclarations) {
2818
+ if (declaration.format === "pack.ts-parameterized") {
2819
+ const key = PackageId.format(declaration.definition.packageId).toLowerCase();
2820
+ subjects.set(key, {
2821
+ kind: "source",
2822
+ packageId: declaration.definition.packageId,
2823
+ sourcePath,
2824
+ definition: declaration.definition,
2825
+ sourceClosure: declaration.sourceClosure
2826
+ });
2827
+ continue;
2828
+ }
2829
+ if (declaration.format !== "pack.json" || declaration.value.schemaVersion !== "3.0.0") {
2830
+ continue;
2831
+ }
2832
+ const parsed = parseParameterizedPackJson(declaration.value);
2833
+ if (!parsed.ok) dynamicFailure(context, parsed.error);
2834
+ if (parsed.value.format === "direct") {
2835
+ subjects.set(PackageId.format(parsed.value.packageId).toLowerCase(), {
2836
+ kind: "direct",
2837
+ packageId: parsed.value.packageId,
2838
+ sourcePath
2839
+ });
2840
+ } else {
2841
+ subjects.set(PackageId.format(parsed.value.packageId).toLowerCase(), {
2842
+ kind: "instance",
2843
+ packageId: parsed.value.packageId,
2844
+ parent: parsed.value.parent,
2845
+ values: parsed.value.values,
2846
+ sourcePath
2847
+ });
2848
+ }
2849
+ }
2850
+ const sourceSubject = (packageId) => {
2851
+ const subject = subjects.get(PackageId.format(packageId).toLowerCase());
2852
+ return subject?.kind === "source" ? subject : void 0;
2853
+ };
2854
+ const readSubject = async (packageId) => {
2855
+ const subject = subjects.get(PackageId.format(packageId).toLowerCase());
2856
+ if (subject === void 0) return void 0;
2857
+ if (subject.kind === "source") {
2858
+ return {
2859
+ format: "source",
2860
+ packageId: subject.packageId,
2861
+ parameters: "parameters" in subject.definition ? subject.definition.parameters : []
2862
+ };
2863
+ }
2864
+ if (subject.kind === "instance") {
2865
+ return {
2866
+ format: "instance",
2867
+ packageId: subject.packageId,
2868
+ parent: subject.parent,
2869
+ values: subject.values
2870
+ };
2871
+ }
2872
+ return {
2873
+ format: "direct",
2874
+ packageId: subject.packageId
2875
+ };
2876
+ };
2877
+ const orderedSubjects = [...subjects.values()].sort(
2878
+ (left, right) => left.sourcePath.localeCompare(right.sourcePath)
2173
2879
  );
2174
- const preparedResult = await produceScriptablePackProducts(
2175
- inputs,
2176
- await declaredPackExternalOutputs(
2177
- context.inventory.sourceDeclarations,
2178
- context.cookers,
2179
- inputs.flatMap((input) => Object.values(input.definition.externalAssets)),
2180
- {
2181
- importerRegistry: context.importerRegistry,
2182
- fsForImport: context.fsForImport,
2183
- assetPaths: loadAssetConfig(context.cwd).paths
2880
+ const inputs = [];
2881
+ for (const subject of orderedSubjects) {
2882
+ if (subject.kind === "source") {
2883
+ const packageId = PackageId.format(subject.packageId);
2884
+ const loaded = await loadParameterizedScriptablePack(subject.sourcePath);
2885
+ if (!loaded.ok) dynamicFailure(context, loaded.error);
2886
+ if (PackageId.format(loaded.value.packageId) !== packageId) {
2887
+ dynamicFailure(context, {
2888
+ code: "pack-source-revision-conflict",
2889
+ expected: "the parameterized source packageId to remain fixed during production",
2890
+ hint: "retry after source writes settle and rebuild the current generation",
2891
+ detail: {
2892
+ sourcePath: subject.sourcePath,
2893
+ scannedPackageId: packageId,
2894
+ loadedPackageId: PackageId.format(loaded.value.packageId)
2895
+ }
2896
+ });
2184
2897
  }
2185
- )
2186
- );
2187
- if (!preparedResult.ok) throw context.fail(preparedResult.error);
2188
- const result = /* @__PURE__ */ new Map();
2189
- for (const entry of context.inventory.entries) {
2190
- if (!entry.sourcePath.endsWith(".pack.ts") || result.has(entry.sourcePath)) continue;
2191
- const prepared = preparedResult.value.get(canonicalScriptableSourcePath(entry.sourcePath));
2192
- if (prepared === void 0) {
2193
- throw context.fail({
2194
- code: "catalog-declaration-missing",
2195
- expected: "the ScriptablePack inventory to retain every authored Pack source",
2196
- hint: "rerun the source inventory and rebuild from the accepted declaration set",
2197
- detail: { stage: "scan", sourcePath: entry.sourcePath }
2898
+ inputs.push({
2899
+ sourcePath: subject.sourcePath,
2900
+ displaySourcePath: parameterizedCatalogSourcePath(context, subject.sourcePath),
2901
+ definition: loaded.value,
2902
+ sourceClosure: subject.sourceClosure,
2903
+ publicationGeneration: context.generation,
2904
+ policy: {
2905
+ base: context.basePrefix === "" ? "/" : context.basePrefix,
2906
+ packagePath: `assets/${packageId}.pack.json`,
2907
+ artifactPath: scriptableArtifactPath
2908
+ }
2198
2909
  });
2199
2910
  }
2200
- const { product, finalized, facts, revision, publication } = prepared;
2201
- const packagePath = `assets/${product.anchorGuid}.pack.json`;
2202
- const runtimePublication = createRuntimePackPublication({
2203
- pack: { assets: finalized.pack.assets },
2204
- scopeId: context.runtimeBinding?.scopeId ?? "asset-runtime",
2205
- sourcePath: publication.sourcePath,
2206
- sourceRevision: publication.sourceRevision,
2207
- packageUrl: finalized.packageUrl,
2208
- inputFingerprint: publication.receipt.inputFingerprint,
2209
- digest: finalized.digest,
2210
- generation: context.runtimeBinding?.generation ?? publication.generation,
2211
- outputs: facts.outputs,
2212
- externalEvidence: publication.externalEvidence
2213
- });
2214
- const receiptPaths = await materializePreparedScriptablePack(
2215
- prepared,
2911
+ }
2912
+ for (const subject of orderedSubjects) {
2913
+ if (subject.kind !== "instance") continue;
2914
+ const resolved = await resolvePackParameterInheritance(
2216
2915
  {
2217
- packagePath,
2218
- artifactPath: (path) => `assets/${path}`,
2219
- receiptPath: (guid) => `assets/${guid}.receipt.json`
2916
+ format: "instance",
2917
+ packageId: subject.packageId,
2918
+ parent: subject.parent,
2919
+ values: subject.values
2220
2920
  },
2221
- {
2222
- writePackage: (path) => {
2223
- context.sink.emitFile({
2224
- type: "asset",
2225
- fileName: path,
2226
- originalFileName: `${context.cwd}/${entry.sourcePath}`,
2227
- source: JSON.stringify(runtimePublication.pack)
2228
- });
2229
- },
2230
- writeArtifact: (path, bytes2) => {
2231
- context.sink.emitFile({ type: "asset", fileName: path, source: bytes2 });
2232
- },
2233
- writeReceipt: (path, source) => {
2234
- context.sink.emitFile({ type: "asset", fileName: path, source });
2921
+ readSubject
2922
+ );
2923
+ if (!resolved.ok) dynamicFailure(context, resolved.error);
2924
+ const root = sourceSubject(resolved.value.rootPackageId);
2925
+ if (root === void 0) {
2926
+ dynamicFailure(context, {
2927
+ code: "pack-parent-not-parameterized",
2928
+ expected: "the instance parent chain to terminate at a parameterized source",
2929
+ hint: "point the instance at a parameterized pack.ts source",
2930
+ detail: { packageId: PackageId.format(subject.packageId) }
2931
+ });
2932
+ }
2933
+ const loaded = await loadParameterizedScriptablePack(root.sourcePath);
2934
+ if (!loaded.ok) dynamicFailure(context, loaded.error);
2935
+ if (PackageId.format(loaded.value.packageId) !== PackageId.format(root.packageId)) {
2936
+ dynamicFailure(context, {
2937
+ code: "pack-source-revision-conflict",
2938
+ expected: "the parameterized parent packageId to remain fixed during production",
2939
+ hint: "retry after source writes settle and rebuild the current generation",
2940
+ detail: {
2941
+ sourcePath: root.sourcePath,
2942
+ scannedPackageId: PackageId.format(root.packageId),
2943
+ loadedPackageId: PackageId.format(loaded.value.packageId)
2235
2944
  }
2945
+ });
2946
+ }
2947
+ const packageId = PackageId.format(subject.packageId);
2948
+ inputs.push({
2949
+ sourcePath: subject.sourcePath,
2950
+ displaySourcePath: parameterizedCatalogSourcePath(context, subject.sourcePath),
2951
+ definition: loaded.value,
2952
+ sourceClosure: root.sourceClosure,
2953
+ subjectPackageId: subject.packageId,
2954
+ values: resolved.value.values,
2955
+ publicationGeneration: context.generation,
2956
+ policy: {
2957
+ base: context.basePrefix === "" ? "/" : context.basePrefix,
2958
+ packagePath: `assets/${packageId}.pack.json`,
2959
+ artifactPath: scriptableArtifactPath
2236
2960
  }
2237
- );
2238
- result.set(entry.sourcePath, {
2239
- packageUrl: finalized.packageUrl,
2240
- receiptUrls: new Map(
2241
- [...receiptPaths].map(([guid, path]) => [guid, context.sink.fileUrl(path)])
2242
- ),
2243
- revision,
2244
- publication: runtimePublication.publication,
2245
- refs: facts.refs
2246
2961
  });
2247
2962
  }
2248
- return result;
2963
+ if (inputs.length === 0) return [];
2964
+ const requiredGuids = context.inventory.entries.flatMap((entry) => {
2965
+ const parsed = AssetGuid$1.parse(entry.guid);
2966
+ if (!parsed.ok) dynamicFailure(context, parsed.error);
2967
+ return [parsed.value];
2968
+ });
2969
+ const externalOutputs = await declaredPackExternalOutputs(
2970
+ context.inventory.sourceDeclarations,
2971
+ context.cookers,
2972
+ requiredGuids,
2973
+ {
2974
+ importerRegistry: context.importerRegistry,
2975
+ fsForImport: context.fsForImport
2976
+ }
2977
+ );
2978
+ const availableGuids = new Set(requiredGuids.map((guid) => AssetGuid$1.format(guid).toLowerCase()));
2979
+ const built = await produceParameterizedScriptablePackProducts({
2980
+ sources: inputs,
2981
+ declaredExternalOutputs: externalOutputs,
2982
+ availableGuids
2983
+ });
2984
+ if (!built.ok) dynamicFailure(context, built.error);
2985
+ const bundles = [];
2986
+ for (const input of inputs) {
2987
+ const prepared = built.value.get(input.displaySourcePath);
2988
+ if (prepared === void 0) {
2989
+ dynamicFailure(context, {
2990
+ code: "parameterized-pack-build-failed",
2991
+ expected: "the worklist to return one prepared result per source subject",
2992
+ hint: "rerun the parameterized Pack generation from a clean inventory",
2993
+ detail: { sourcePath: input.sourcePath }
2994
+ });
2995
+ }
2996
+ const packageId = PackageId.format(input.subjectPackageId ?? input.definition.packageId);
2997
+ const stagedByGuid = new Map(
2998
+ prepared.product.stagedOutputs.map((output) => [
2999
+ AssetGuid$1.format(output.guid).toLowerCase(),
3000
+ output
3001
+ ])
3002
+ );
3003
+ const entries = projectPackageCatalog(
3004
+ prepared.product.product.assets.map((asset, sourceIndex) => {
3005
+ const staged = stagedByGuid.get(asset.guid.toLowerCase());
3006
+ return {
3007
+ guid: asset.guid,
3008
+ kind: asset.kind,
3009
+ sourcePath: input.displaySourcePath,
3010
+ sourceIndex,
3011
+ ...staged?.sourceKey === void 0 ? {} : { sourceKey: staged.sourceKey },
3012
+ refs: asset.refs.map((reference) => reference.guid),
3013
+ execution: "cooked",
3014
+ packageId,
3015
+ provenance: { provider: "pack-ts", version: "2.0.0" }
3016
+ };
3017
+ }),
3018
+ `${context.basePrefix === "/" ? "" : context.basePrefix}/${input.displaySourcePath}`
3019
+ );
3020
+ bundles.push({ input, prepared, entries });
3021
+ }
3022
+ return bundles;
2249
3023
  }
2250
- function updateImportedEntries(entries, guids, packageUrl, projection = {}, refsByGuid, publication, sourcePath) {
3024
+ function updateImportedEntries(entries, guids, packageUrl, projection = {}, refsByGuid, publication, sourcePath, receiptUrls, revision) {
2251
3025
  const selected = new Set(guids.map((guid) => guid.toLowerCase()));
2252
3026
  for (let index = 0; index < entries.length; index += 1) {
2253
3027
  const entry = entries[index];
2254
3028
  if (entry === void 0 || !selected.has(entry.guid.toLowerCase())) continue;
2255
3029
  const refs = refsByGuid?.get(entry.guid.toLowerCase());
3030
+ const cookReceiptUrl = receiptUrls?.get(entry.guid.toLowerCase());
2256
3031
  entries[index] = {
2257
3032
  ...entry,
2258
3033
  packageUrl,
2259
3034
  ...projection,
2260
3035
  ...sourcePath === void 0 ? {} : { sourcePath },
2261
3036
  ...publication === void 0 ? {} : { publication },
2262
- ...refs === void 0 ? {} : { refs }
3037
+ ...refs === void 0 ? {} : { refs },
3038
+ ...cookReceiptUrl === void 0 ? {} : { cookReceiptUrl },
3039
+ ...revision === void 0 ? {} : { revision }
2263
3040
  };
2264
3041
  }
2265
3042
  }
@@ -2273,11 +3050,30 @@ async function emitPackDocument(work, options) {
2273
3050
  }
2274
3051
  const referenceId = work.context.sink.emitFile({
2275
3052
  type: "asset",
3053
+ fileName: options.packageName.startsWith("assets/") ? options.packageName : `assets/${options.packageName}`,
2276
3054
  name: options.packageName,
2277
3055
  originalFileName: options.originalFileName,
2278
3056
  source: JSON.stringify(options.pack)
2279
3057
  });
2280
3058
  const packageUrl = work.context.sink.fileUrl(work.context.sink.getFileName(referenceId));
3059
+ const receiptUrls = /* @__PURE__ */ new Map();
3060
+ for (const receipt of options.receipts ?? []) {
3061
+ const guid = receipt.guid.toLowerCase();
3062
+ const receiptReferenceId = work.context.sink.emitFile({
3063
+ type: "asset",
3064
+ fileName: `assets/${guid}.receipt.json`,
3065
+ name: `${guid}.receipt.json`,
3066
+ originalFileName: options.originalFileName,
3067
+ source: JSON.stringify({
3068
+ ...receipt,
3069
+ ...receipt.outputDigest === void 0 && options.receiptOutputDigest === void 0 ? {} : { outputDigest: receipt.outputDigest ?? options.receiptOutputDigest }
3070
+ })
3071
+ });
3072
+ receiptUrls.set(
3073
+ guid,
3074
+ work.context.sink.fileUrl(work.context.sink.getFileName(receiptReferenceId))
3075
+ );
3076
+ }
2281
3077
  updateImportedEntries(
2282
3078
  work.importedEntries,
2283
3079
  options.guids,
@@ -2285,11 +3081,13 @@ async function emitPackDocument(work, options) {
2285
3081
  options.projection,
2286
3082
  options.refsByGuid,
2287
3083
  options.publication,
2288
- options.sourcePath
3084
+ options.sourcePath,
3085
+ receiptUrls,
3086
+ options.revision
2289
3087
  );
2290
3088
  return packageUrl;
2291
3089
  }
2292
- async function emitAuthoredPack(work, guidSeen, entry) {
3090
+ async function emitAuthoredPack(work, guidSeen, entry, availableGuids) {
2293
3091
  const sourceDeclaration = sourceDeclarationForCatalogPath(
2294
3092
  entry.sourcePath,
2295
3093
  work.context.inventory.sourceDeclarations,
@@ -2306,39 +3104,143 @@ async function emitAuthoredPack(work, guidSeen, entry) {
2306
3104
  detail: { stage: "scan", sourcePath: packPath }
2307
3105
  });
2308
3106
  }
2309
- const prepared = await prepareAuthoredPackTransport(
2310
- declaration.value,
3107
+ if (declaration.value.schemaVersion === "3.0.0") {
3108
+ const parsed = parseParameterizedPackJson(declaration.value);
3109
+ if (!parsed.ok) dynamicFailure(work.context, parsed.error);
3110
+ if (parsed.value.format !== "direct") {
3111
+ dynamicFailure(work.context, {
3112
+ code: "catalog-declaration-missing",
3113
+ expected: "a direct v3 Pack declaration for an indexed authored output",
3114
+ hint: "instances are built from their parameterized parent and do not have direct Catalog rows",
3115
+ detail: { stage: "scan", sourcePath: packPath }
3116
+ });
3117
+ }
3118
+ const projected = projectDirectPackJson(parsed.value);
3119
+ if (!projected.ok) dynamicFailure(work.context, projected.error);
3120
+ const prepared2 = await prepareDirectPackTransport({
3121
+ projected: projected.value,
3122
+ sourcePath: packPath,
3123
+ sourceRevision: declaration.sourceRevision,
3124
+ availableGuids,
3125
+ cookers: work.context.cookers,
3126
+ policy: {
3127
+ base: work.context.basePrefix === "" ? "/" : work.context.basePrefix,
3128
+ packagePath: `assets/${projected.value.packageId}.pack.json`,
3129
+ artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`,
3130
+ sink: () => {
3131
+ }
3132
+ }
3133
+ });
3134
+ if (!prepared2.ok) dynamicFailure(work.context, prepared2.error);
3135
+ const { product, finalized, facts, revision } = prepared2.value;
3136
+ const publication = runtimePublicationFor(work.context, {
3137
+ pack: finalized.pack,
3138
+ sourcePath: entry.sourcePath,
3139
+ sourceRevision: product.inputFingerprint,
3140
+ packageUrl: finalized.packageUrl,
3141
+ digest: finalized.digest,
3142
+ inputFingerprint: product.inputFingerprint,
3143
+ outputs: facts.outputs,
3144
+ externalEvidence: product.externalEvidence
3145
+ });
3146
+ await emitPackDocument(work, {
3147
+ pack: publication.pack,
3148
+ artifacts: finalized.artifacts,
3149
+ packageName: `${prepared2.value.projected.packageId}.pack.json`,
3150
+ originalFileName: packPath,
3151
+ guids: product.product.assets.map((asset) => asset.guid),
3152
+ projection: work.context.authoredCookedCurrentProjection,
3153
+ refsByGuid: facts.refs,
3154
+ receipts: product.product.receipts,
3155
+ receiptOutputDigest: finalized.digest,
3156
+ publication: publication.publication,
3157
+ sourcePath: entry.sourcePath,
3158
+ revision
3159
+ });
3160
+ for (const asset of product.product.assets) guidSeen.add(asset.guid.toLowerCase());
3161
+ return;
3162
+ }
3163
+ const legacy = declaration.value;
3164
+ const prepared = await prepareLegacyPackTransport(
3165
+ legacy,
2311
3166
  work.context.cookers,
2312
3167
  (guid) => ({
2313
3168
  base: work.context.basePrefix === "" ? "/" : work.context.basePrefix,
2314
3169
  packagePath: `assets/${guid}.pack.json`,
2315
- artifactPath: (artifactGuid, key) => `${artifactGuid}/${key}.bin`
3170
+ artifactPath: (assetGuid, key) => `${assetGuid}/${key}.bin`
2316
3171
  }),
2317
3172
  packPath
2318
3173
  );
2319
3174
  const outputGuid = prepared.firstGuid ?? entry.guid;
2320
- const authoredPack = prepared.finalized?.pack ?? prepared.pack;
2321
- const runtimePublication = authoredPack.assets === void 0 || authoredPack.assets.length === 0 ? void 0 : runtimePublicationFor(work.context, {
2322
- pack: { assets: authoredPack.assets },
3175
+ const authoredPack = prepared.finalized?.pack ?? {
3176
+ schemaVersion: "2.0.0",
3177
+ kind: "internal-text-package",
3178
+ assets: legacy.assets.map((asset) => ({
3179
+ guid: asset.guid,
3180
+ kind: asset.kind,
3181
+ ...asset.name === void 0 ? {} : { name: asset.name },
3182
+ payload: asset.payload,
3183
+ refs: asset.refs,
3184
+ artifacts: asset.artifacts ?? {}
3185
+ }))
3186
+ };
3187
+ const runtimePublication = runtimePublicationFor(work.context, {
3188
+ pack: authoredPack,
2323
3189
  sourcePath: entry.sourcePath,
2324
3190
  sourceRevision: declaration.sourceRevision,
2325
3191
  packageUrl: prepared.finalized?.packageUrl ?? `${work.context.basePrefix === "/" ? "" : work.context.basePrefix}/assets/${outputGuid}.pack.json`,
2326
3192
  ...prepared.finalized?.digest === void 0 ? {} : { digest: prepared.finalized.digest }
2327
3193
  });
2328
3194
  await emitPackDocument(work, {
2329
- pack: runtimePublication?.pack ?? authoredPack,
3195
+ pack: runtimePublication.pack,
2330
3196
  artifacts: prepared.finalized?.artifacts ?? [],
2331
3197
  packageName: `${outputGuid}.pack.json`,
2332
3198
  originalFileName: packPath,
2333
- guids: prepared.pack.assets?.map((asset) => asset.guid) ?? [outputGuid],
3199
+ guids: authoredPack.assets.map((asset) => asset.guid),
2334
3200
  projection: prepared.finalized === void 0 ? work.context.directCurrentProjection : work.context.authoredCookedCurrentProjection,
2335
- ...prepared.cooked === void 0 ? {} : { refsByGuid: prepared.cooked.refsByGuid },
2336
- ...runtimePublication === void 0 ? {} : { publication: runtimePublication.publication }
3201
+ refsByGuid: new Map(authoredPack.assets.map((asset) => [asset.guid.toLowerCase(), asset.refs])),
3202
+ ...prepared.finalized?.receipts === void 0 ? {} : {
3203
+ receipts: prepared.finalized.receipts,
3204
+ receiptOutputDigest: prepared.finalized.digest
3205
+ },
3206
+ publication: runtimePublication.publication,
3207
+ sourcePath: entry.sourcePath
2337
3208
  });
2338
- for (const guid of prepared.pack.assets?.map((asset) => asset.guid) ?? [outputGuid]) {
3209
+ for (const guid of authoredPack.assets.map((asset) => asset.guid)) {
2339
3210
  guidSeen.add(guid.toLowerCase());
2340
3211
  }
2341
3212
  }
3213
+ async function emitParameterizedPack(work, guidSeen, bundle) {
3214
+ const { input, prepared } = bundle;
3215
+ const packageId = PackageId.format(input.subjectPackageId ?? input.definition.packageId);
3216
+ const runtimePublication = createRuntimePackPublication({
3217
+ pack: { assets: prepared.finalized.pack.assets },
3218
+ scopeId: work.context.runtimeBinding?.scopeId ?? "asset-runtime",
3219
+ sourcePath: input.displaySourcePath,
3220
+ sourceRevision: prepared.product.inputFingerprint,
3221
+ packageUrl: prepared.finalized.packageUrl,
3222
+ inputFingerprint: prepared.product.inputFingerprint,
3223
+ digest: prepared.finalized.digest,
3224
+ generation: work.context.runtimeBinding?.generation ?? prepared.publication.generation,
3225
+ outputs: prepared.facts.outputs,
3226
+ externalEvidence: prepared.product.externalEvidence
3227
+ });
3228
+ await emitPackDocument(work, {
3229
+ pack: runtimePublication.pack,
3230
+ artifacts: prepared.finalized.artifacts,
3231
+ packageName: `${packageId}.pack.json`,
3232
+ originalFileName: input.sourcePath,
3233
+ guids: prepared.product.product.assets.map((asset) => asset.guid),
3234
+ projection: work.context.authoredCookedCurrentProjection,
3235
+ refsByGuid: prepared.facts.refs,
3236
+ receipts: prepared.product.product.receipts,
3237
+ receiptOutputDigest: prepared.finalized.digest,
3238
+ publication: runtimePublication.publication,
3239
+ sourcePath: input.displaySourcePath,
3240
+ revision: prepared.revision
3241
+ });
3242
+ for (const asset of prepared.product.product.assets) guidSeen.add(asset.guid.toLowerCase());
3243
+ }
2342
3244
  async function emitFinalizedOwner(work, metaPath, sourcePath, subAssets, sourcePackage, ownerFinalizer) {
2343
3245
  const ownerGuid = subAssets[0]?.guid;
2344
3246
  if (ownerGuid === void 0) return;
@@ -2407,11 +3309,12 @@ async function emitFinalizedOwner(work, metaPath, sourcePath, subAssets, sourceP
2407
3309
  sourcePath
2408
3310
  });
2409
3311
  }
2410
- async function emitBuildEntry(work, guidSeen, entry) {
3312
+ async function emitBuildEntry(work, guidSeen, entry, availableGuids) {
2411
3313
  const guid = entry.guid.toLowerCase();
2412
3314
  const metaPath = metaPathForGuid(work.context.inventory.declarations, guid);
2413
3315
  if (metaPath === void 0) {
2414
- if (entry.sourcePath.endsWith(".pack.json")) await emitAuthoredPack(work, guidSeen, entry);
3316
+ if (entry.sourcePath.endsWith(".pack.json"))
3317
+ await emitAuthoredPack(work, guidSeen, entry, availableGuids);
2415
3318
  else guidSeen.add(guid);
2416
3319
  return;
2417
3320
  }
@@ -2464,7 +3367,8 @@ async function emitBuildEntry(work, guidSeen, entry) {
2464
3367
  sourcePath: canonicalSourcePath,
2465
3368
  sourceRevision: finalized.sourceRevision,
2466
3369
  packageUrl: finalized.packageUrl,
2467
- digest: finalized.digest
3370
+ digest: finalized.digest,
3371
+ sourceKeys: sourceKeysFor(subAssets)
2468
3372
  });
2469
3373
  await emitPackDocument(work, {
2470
3374
  pack: runtimePublication.pack,
@@ -2481,24 +3385,21 @@ async function emitBuildEntry(work, guidSeen, entry) {
2481
3385
  });
2482
3386
  }
2483
3387
  async function produceBuildAssets(context) {
2484
- const entries = [...context.inventory.entries];
2485
- const authored = await buildAuthoredPackages(context);
2486
- const importedEntries = entries.map((entry) => {
2487
- const scriptable = authored.get(entry.sourcePath);
2488
- if (scriptable === void 0) return entry;
2489
- const cookReceiptUrl = scriptable.receiptUrls.get(entry.guid.toLowerCase());
2490
- return projectCookedPackageEntry(entry, {
2491
- packageUrl: scriptable.packageUrl,
2492
- revision: scriptable.revision,
2493
- refs: scriptable.refs.get(entry.guid.toLowerCase()) ?? [],
2494
- ...cookReceiptUrl === void 0 ? {} : { cookReceiptUrl },
2495
- publication: scriptable.publication
2496
- });
2497
- });
3388
+ const dynamicBundles = await buildParameterizedPackages(context);
3389
+ const entries = [
3390
+ ...context.inventory.entries,
3391
+ ...dynamicBundles.flatMap((bundle) => bundle.entries)
3392
+ ];
3393
+ const availableGuids = new Set(entries.map((entry) => entry.guid.toLowerCase()));
3394
+ const importedEntries = [...entries];
2498
3395
  const guidSeen = /* @__PURE__ */ new Set();
2499
3396
  const work = { importedEntries, context };
3397
+ for (const bundle of dynamicBundles) {
3398
+ await emitParameterizedPack(work, guidSeen, bundle);
3399
+ }
2500
3400
  for (const entry of importedEntries) {
2501
- if (!guidSeen.has(entry.guid.toLowerCase())) await emitBuildEntry(work, guidSeen, entry);
3401
+ if (!guidSeen.has(entry.guid.toLowerCase()))
3402
+ await emitBuildEntry(work, guidSeen, entry, availableGuids);
2502
3403
  }
2503
3404
  return importedEntries;
2504
3405
  }
@@ -2521,39 +3422,291 @@ function catalogImporterPolicy(importer, hostImporterKeys) {
2521
3422
  hint: "remove the source declaration from Pack roots or publish its cooked runtime module"
2522
3423
  };
2523
3424
  }
2524
- if (DEFAULT_CATALOG_IMPORTER_KEYS.includes(importer)) {
3425
+ if (DEFAULT_CATALOG_IMPORTER_KEYS.includes(importer)) {
3426
+ return {
3427
+ disposition: "publish",
3428
+ hostProvided: false,
3429
+ expected: "a standard Catalog provider declaration",
3430
+ hint: "let the provider producer materialize the declared output"
3431
+ };
3432
+ }
3433
+ if (hostImporterKeys.has(importer)) {
3434
+ return {
3435
+ disposition: "publish",
3436
+ hostProvided: true,
3437
+ expected: "a registered host Catalog provider declaration",
3438
+ hint: "let the registered provider materialize the declared output"
3439
+ };
3440
+ }
3441
+ return {
3442
+ disposition: "missing",
3443
+ hostProvided: false,
3444
+ expected: "the sidecar importer must be present in the registered provider set",
3445
+ hint: "wire the provider through the host importer registry before building the Catalog"
3446
+ };
3447
+ }
3448
+ async function buildCatalogResult(roots, base = "/", registeredImporterKeys = /* @__PURE__ */ new Set(), scanOptions = {}, catalogVisibility = () => true, sourceIdentityFor) {
3449
+ const options = {
3450
+ base,
3451
+ scanOptions,
3452
+ importerPolicy: (importer) => catalogImporterPolicy(importer, registeredImporterKeys),
3453
+ visibility: catalogVisibility,
3454
+ ...sourceIdentityFor === void 0 ? {} : { sourceIdentityFor }
3455
+ };
3456
+ return buildCatalogProjection(roots, options);
3457
+ }
3458
+ function numericTokens(source, startLine) {
3459
+ return source.split(/\r?\n/).slice(startLine).join(" ").replaceAll(",", " ").split(/\s+/).filter((token) => token.length > 0).map(Number);
3460
+ }
3461
+ function allFinite(values) {
3462
+ return values.every(Number.isFinite);
3463
+ }
3464
+ function parseLm63TypeC(source) {
3465
+ const lines = source.split(/\r?\n/);
3466
+ const tiltLine = lines.findIndex((line) => /^\s*TILT\s*=/i.test(line));
3467
+ if (tiltLine < 0) return err({ code: "invalid-ies", reason: "missing TILT declaration" });
3468
+ const tiltSource = lines[tiltLine];
3469
+ if (tiltSource === void 0)
3470
+ return err({ code: "invalid-ies", reason: "missing TILT declaration" });
3471
+ const tilt = tiltSource.slice(tiltSource.indexOf("=") + 1).trim().toUpperCase();
3472
+ if (tilt !== "NONE") return err({ code: "unsupported-tilt", reason: `TILT=${tilt}` });
3473
+ const values = numericTokens(source, tiltLine + 1);
3474
+ if (values.length < 12 || !allFinite(values.slice(0, 12))) {
3475
+ return err({ code: "invalid-ies", reason: "numeric header is incomplete or non-finite" });
3476
+ }
3477
+ const verticalCount = values[3];
3478
+ const horizontalCount = values[4];
3479
+ const photometricType = values[5];
3480
+ if (verticalCount === void 0 || horizontalCount === void 0 || photometricType === void 0) {
3481
+ return err({ code: "invalid-ies", reason: "numeric header is incomplete or non-finite" });
3482
+ }
3483
+ if (photometricType !== 1) {
3484
+ return err({
3485
+ code: "unsupported-photometric-type",
3486
+ reason: `photometric type ${photometricType} is not Type C`
3487
+ });
3488
+ }
3489
+ if (!Number.isInteger(verticalCount) || !Number.isInteger(horizontalCount) || verticalCount < 2 || horizontalCount < 1) {
3490
+ return err({ code: "invalid-ies", reason: "angle counts are invalid" });
3491
+ }
3492
+ const angleOffset = 12;
3493
+ const verticalEnd = angleOffset + verticalCount;
3494
+ const horizontalEnd = verticalEnd + horizontalCount;
3495
+ const candelaEnd = horizontalEnd + verticalCount * horizontalCount;
3496
+ if (values.length < candelaEnd) {
3497
+ return err({ code: "invalid-ies", reason: "angle or candela table is incomplete" });
3498
+ }
3499
+ const verticalAnglesDeg = values.slice(angleOffset, verticalEnd);
3500
+ const horizontalAnglesDeg = values.slice(verticalEnd, horizontalEnd);
3501
+ const candelaMultiplier = values[2];
3502
+ if (candelaMultiplier === void 0) {
3503
+ return err({ code: "invalid-ies", reason: "numeric header is incomplete or non-finite" });
3504
+ }
3505
+ const candela = values.slice(horizontalEnd, candelaEnd).map((value) => value * candelaMultiplier);
3506
+ if (!allFinite(verticalAnglesDeg) || !allFinite(horizontalAnglesDeg) || !allFinite(candela) || verticalAnglesDeg[0] !== 0 || verticalAnglesDeg[verticalAnglesDeg.length - 1] !== 180 || horizontalAnglesDeg[0] !== 0) {
3507
+ return err({ code: "invalid-ies", reason: "Type C angles or candela values are invalid" });
3508
+ }
3509
+ return ok({ tilt: "NONE", verticalAnglesDeg, horizontalAnglesDeg, candela });
3510
+ }
3511
+ function horizontalTable(source) {
3512
+ const angles = [...source.horizontalAnglesDeg];
3513
+ const values = [...source.candela];
3514
+ const verticalCount = source.verticalAnglesDeg.length;
3515
+ const lastAngle = angles.at(-1);
3516
+ if (lastAngle === void 0) return { angles, values };
3517
+ if (lastAngle < 360) {
3518
+ for (let index = angles.length - 2; index > 0; index--) {
3519
+ const angle = angles[index];
3520
+ if (angle === void 0) continue;
3521
+ angles.push(360 - angle);
3522
+ const row = source.horizontalAnglesDeg.length - 1 - index;
3523
+ values.push(
3524
+ ...source.candela.slice(row * verticalCount, row * verticalCount + verticalCount)
3525
+ );
3526
+ }
3527
+ }
3528
+ return { angles, values };
3529
+ }
3530
+ function interpolate(values, angles, angle, verticalIndex, verticalCount) {
3531
+ const wrapped = (angle % 360 + 360) % 360;
3532
+ const last = angles.length - 1;
3533
+ const finalAngle = angles[last];
3534
+ const firstAngle = angles[0];
3535
+ if (finalAngle === void 0 || firstAngle === void 0) return Number.NaN;
3536
+ for (let index = 0; index < last; index++) {
3537
+ const left = angles[index];
3538
+ const right = angles[index + 1];
3539
+ if (left === void 0 || right === void 0) continue;
3540
+ if (wrapped < left || wrapped > right) continue;
3541
+ const span2 = right - left;
3542
+ const factor2 = span2 === 0 ? 0 : (wrapped - left) / span2;
3543
+ const a = values[index * verticalCount + verticalIndex] ?? 0;
3544
+ const b = values[(index + 1) * verticalCount + verticalIndex] ?? 0;
3545
+ return a + (b - a) * factor2;
3546
+ }
3547
+ const first = values[verticalIndex] ?? 0;
3548
+ const finalRow = (angles.length - 1) * verticalCount + verticalIndex;
3549
+ const final = values[finalRow] ?? 0;
3550
+ const span = 360 - finalAngle + firstAngle;
3551
+ const factor = span === 0 ? 0 : (wrapped - finalAngle + 360) / span;
3552
+ return final + (first - final) * factor;
3553
+ }
3554
+ function toFloat16(value) {
3555
+ if (value === 0) return 0;
3556
+ const sign = value < 0 ? 32768 : 0;
3557
+ const absolute = Math.abs(value);
3558
+ if (!Number.isFinite(absolute)) return sign | 31744;
3559
+ const exponent = Math.floor(Math.log2(absolute));
3560
+ if (exponent < -14) return sign | Math.round(absolute / 2 ** -24);
3561
+ if (exponent > 15) return sign | 31744;
3562
+ const mantissa = Math.round((absolute / 2 ** exponent - 1) * 1024);
3563
+ return sign | exponent + 15 << 10 | Math.min(mantissa, 1023);
3564
+ }
3565
+ function readFloat16LE(bytes2, offset) {
3566
+ const bits = (bytes2[offset] ?? 0) | (bytes2[offset + 1] ?? 0) << 8;
3567
+ const sign = (bits & 32768) === 0 ? 1 : -1;
3568
+ const exponent = bits >>> 10 & 31;
3569
+ const fraction = bits & 1023;
3570
+ if (exponent === 0) return sign * 2 ** -14 * (fraction / 2 ** 10);
3571
+ if (exponent === 31) return fraction === 0 ? sign * Infinity : Number.NaN;
3572
+ return sign * 2 ** (exponent - 15) * (1 + fraction / 2 ** 10);
3573
+ }
3574
+ function resampleTypeC(source) {
3575
+ const table = horizontalTable(source);
3576
+ const verticalCount = source.verticalAnglesDeg.length;
3577
+ const output = new Uint8Array(IES_PROFILE_WIDTH * IES_PROFILE_HEIGHT * 2);
3578
+ let peak = 0;
3579
+ const samples = [];
3580
+ for (let y = 0; y < IES_PROFILE_HEIGHT; y++) {
3581
+ const vertical = y / (IES_PROFILE_HEIGHT - 1) * 180;
3582
+ let lower = 0;
3583
+ while (lower + 1 < verticalCount && (source.verticalAnglesDeg[lower + 1] ?? 0) < vertical)
3584
+ lower++;
3585
+ const upper = Math.min(lower + 1, verticalCount - 1);
3586
+ const left = source.verticalAnglesDeg[lower] ?? 0;
3587
+ const right = source.verticalAnglesDeg[upper] ?? 0;
3588
+ const factor = right === left ? 0 : (vertical - left) / (right - left);
3589
+ for (let x = 0; x < IES_PROFILE_WIDTH; x++) {
3590
+ const horizontal = x / IES_PROFILE_WIDTH * 360;
3591
+ const low = interpolate(table.values, table.angles, horizontal, lower, verticalCount);
3592
+ const high = interpolate(table.values, table.angles, horizontal, upper, verticalCount);
3593
+ const value = low + (high - low) * factor;
3594
+ samples.push(Math.max(0, value));
3595
+ peak = Math.max(peak, value);
3596
+ }
3597
+ }
3598
+ const divisor = peak > 0 ? peak : 1;
3599
+ for (let index = 0; index < samples.length; index++) {
3600
+ const bits = toFloat16((samples[index] ?? 0) / divisor);
3601
+ output[index * 2] = bits & 255;
3602
+ output[index * 2 + 1] = bits >>> 8;
3603
+ }
3604
+ return output;
3605
+ }
3606
+
3607
+ // src/ies/ies-importer.ts
3608
+ function isRecord(value) {
3609
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3610
+ }
3611
+ function decodeFloat16(bytes2, offset) {
3612
+ const bits = (bytes2[offset] ?? 0) | (bytes2[offset + 1] ?? 0) << 8;
3613
+ const sign = (bits & 32768) === 0 ? 1 : -1;
3614
+ const exponent = bits >>> 10 & 31;
3615
+ const fraction = bits & 1023;
3616
+ if (exponent === 0) return sign * 2 ** -14 * (fraction / 2 ** 10);
3617
+ if (exponent === 31) return fraction === 0 ? sign * Infinity : Number.NaN;
3618
+ return sign * 2 ** (exponent - 15) * (1 + fraction / 2 ** 10);
3619
+ }
3620
+ function validateIesProfilePayload(value) {
3621
+ if (!isRecord(value) || value.kind !== "ies-profile") {
3622
+ return err(new Error("IES payload kind must be ies-profile"));
3623
+ }
3624
+ const data = value.data;
3625
+ if (!(data instanceof Uint8Array) || data.byteLength !== IES_PROFILE_BYTE_LENGTH) {
3626
+ return err(
3627
+ new Error(
3628
+ `IES payload must contain ${IES_PROFILE_WIDTH}x${IES_PROFILE_HEIGHT} little-endian f16 samples`
3629
+ )
3630
+ );
3631
+ }
3632
+ for (let offset = 0; offset < data.byteLength; offset += 2) {
3633
+ if (!Number.isFinite(decodeFloat16(data, offset))) {
3634
+ return err(new Error("IES payload contains a non-finite f16 sample"));
3635
+ }
3636
+ }
3637
+ return ok(value);
3638
+ }
3639
+ function sourceValidationError(ctx, reason) {
3640
+ return new ImportError({
3641
+ code: "source-validation-failed",
3642
+ expected: "LM-63 Type C source with TILT=NONE",
3643
+ hint: IMPORT_ERROR_HINTS["source-validation-failed"],
3644
+ detail: {
3645
+ diagnostics: [
3646
+ {
3647
+ code: "ies-source-invalid",
3648
+ severity: "error",
3649
+ sourcePath: ctx.source,
3650
+ sourceRange: { start: 0, end: 0, line: 1, column: 1 },
3651
+ rule: "LM-63 Type C TILT=NONE",
3652
+ expected: "LM-63 Type C source with TILT=NONE",
3653
+ actual: reason,
3654
+ hint: "repair the IES source and rerun the build-time importer"
3655
+ }
3656
+ ]
3657
+ }
3658
+ });
3659
+ }
3660
+ async function importIes(ctx) {
3661
+ const source = await ctx.readSource();
3662
+ if (!source.ok) {
2525
3663
  return {
2526
- disposition: "publish",
2527
- hostProvided: false,
2528
- expected: "a standard Catalog provider declaration",
2529
- hint: "let the provider producer materialize the declared output"
3664
+ ok: false,
3665
+ error: new ImportError({
3666
+ code: "source-read-failed",
3667
+ expected: `readable IES source at ${ctx.source}`,
3668
+ hint: IMPORT_ERROR_HINTS["source-read-failed"],
3669
+ detail: { source: ctx.source, reason: String(source.error) }
3670
+ })
2530
3671
  };
2531
3672
  }
2532
- if (hostImporterKeys.has(importer)) {
3673
+ const parsed = parseLm63TypeC(new TextDecoder().decode(source.value));
3674
+ if (!parsed.ok) return { ok: false, error: sourceValidationError(ctx, parsed.error.reason) };
3675
+ const declaration = ctx.subAssets.find((asset) => asset.kind === "ies-profile");
3676
+ if (declaration === void 0) {
2533
3677
  return {
2534
- disposition: "publish",
2535
- hostProvided: true,
2536
- expected: "a registered host Catalog provider declaration",
2537
- hint: "let the registered provider materialize the declared output"
3678
+ ok: false,
3679
+ error: sourceValidationError(ctx, "missing ies-profile sub-asset declaration")
2538
3680
  };
2539
3681
  }
3682
+ const data = resampleTypeC(parsed.value);
3683
+ const payload = { kind: "ies-profile", data };
2540
3684
  return {
2541
- disposition: "missing",
2542
- hostProvided: false,
2543
- expected: "the sidecar importer must be present in the registered provider set",
2544
- hint: "wire the provider through the host importer registry before building the Catalog"
2545
- };
2546
- }
2547
- async function buildCatalogResult(roots, base = "/", registeredImporterKeys = /* @__PURE__ */ new Set(), scanOptions = {}, catalogVisibility = () => true, sourceIdentityFor) {
2548
- const options = {
2549
- base,
2550
- scanOptions,
2551
- importerPolicy: (importer) => catalogImporterPolicy(importer, registeredImporterKeys),
2552
- visibility: catalogVisibility,
2553
- ...sourceIdentityFor === void 0 ? {} : { sourceIdentityFor }
3685
+ ok: true,
3686
+ value: {
3687
+ assets: [
3688
+ {
3689
+ guid: declaration.guid,
3690
+ kind: "ies-profile",
3691
+ payload,
3692
+ refs: [],
3693
+ artifacts: {
3694
+ body: {
3695
+ mediaType: "application/octet-stream",
3696
+ assetCodec: { name: "forgeax-ies-profile", version: "1" },
3697
+ bytes: data
3698
+ }
3699
+ }
3700
+ }
3701
+ ],
3702
+ sourceDependencies: [ctx.source]
3703
+ }
2554
3704
  };
2555
- return buildCatalogProjection(roots, options);
2556
3705
  }
3706
+ var iesImporter = {
3707
+ key: "ies",
3708
+ import: importIes
3709
+ };
2557
3710
 
2558
3711
  // src/importer-registry.ts
2559
3712
  var ImporterRegistry = class {
@@ -2612,6 +3765,339 @@ var ImporterRegistry = class {
2612
3765
  }) ?? true;
2613
3766
  }
2614
3767
  };
3768
+ function isRecord2(value) {
3769
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3770
+ }
3771
+ function stable2(value) {
3772
+ if (value instanceof ArrayBuffer) {
3773
+ return `ArrayBuffer:${JSON.stringify(Array.from(new Uint8Array(value)))}`;
3774
+ }
3775
+ if (ArrayBuffer.isView(value)) {
3776
+ return `${value.constructor.name}:${JSON.stringify(
3777
+ Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
3778
+ )}`;
3779
+ }
3780
+ if (Array.isArray(value)) return `[${value.map(stable2).join(",")}]`;
3781
+ if (value !== null && typeof value === "object") {
3782
+ const record2 = value;
3783
+ return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stable2(record2[key])}`).join(",")}}`;
3784
+ }
3785
+ return JSON.stringify(value) ?? "null";
3786
+ }
3787
+ function digest2(value) {
3788
+ return `sha256:${createHash("sha256").update(stable2(value)).digest("hex")}`;
3789
+ }
3790
+ function generationFromDigest(value) {
3791
+ const parsed = Number.parseInt(value.slice("sha256:".length, "sha256:".length + 8), 16);
3792
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 1;
3793
+ }
3794
+ function reasonOf(error) {
3795
+ return error instanceof Error ? error.message : String(error);
3796
+ }
3797
+ async function collectPackFiles(assetRoots) {
3798
+ const files = /* @__PURE__ */ new Set();
3799
+ async function visit(directory) {
3800
+ let entries;
3801
+ try {
3802
+ entries = await readdir(directory, { withFileTypes: true });
3803
+ } catch (error) {
3804
+ return err({
3805
+ code: "scriptable-pack-file-root-unreadable",
3806
+ expected: "every configured ScriptablePack asset root to be readable",
3807
+ hint: "restore the asset root or configure a readable directory, then retry the ScriptablePack build",
3808
+ detail: { root: directory, reason: reasonOf(error) }
3809
+ });
3810
+ }
3811
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
3812
+ const child = resolve(directory, entry.name);
3813
+ if (entry.isDirectory()) {
3814
+ const nested = await visit(child);
3815
+ if (!nested.ok) return nested;
3816
+ } else if (entry.isFile() && entry.name.endsWith(".pack.json")) {
3817
+ files.add(child);
3818
+ }
3819
+ }
3820
+ return ok(void 0);
3821
+ }
3822
+ for (const rawRoot of [...new Set(assetRoots.map((root) => resolve(root)))].sort(
3823
+ (a, b) => a.localeCompare(b)
3824
+ )) {
3825
+ let info;
3826
+ try {
3827
+ info = await stat(rawRoot);
3828
+ } catch (error) {
3829
+ return err({
3830
+ code: "scriptable-pack-file-root-unreadable",
3831
+ expected: "every configured ScriptablePack asset root to exist and be readable",
3832
+ hint: "restore the asset root or configure a readable directory, then retry the ScriptablePack build",
3833
+ detail: { root: rawRoot, reason: reasonOf(error) }
3834
+ });
3835
+ }
3836
+ if (info.isFile()) {
3837
+ if (rawRoot.endsWith(".pack.json")) files.add(rawRoot);
3838
+ continue;
3839
+ }
3840
+ if (!info.isDirectory()) {
3841
+ return err({
3842
+ code: "scriptable-pack-file-root-unreadable",
3843
+ expected: "every configured ScriptablePack asset root to be a file or directory",
3844
+ hint: "configure an asset root containing ordinary .pack.json files, then retry the ScriptablePack build",
3845
+ detail: { root: rawRoot, reason: "root is neither a file nor a directory" }
3846
+ });
3847
+ }
3848
+ const visited = await visit(rawRoot);
3849
+ if (!visited.ok) return visited;
3850
+ }
3851
+ return ok([...files].sort((left, right) => left.localeCompare(right)));
3852
+ }
3853
+ function invalidPack(path, errors) {
3854
+ return {
3855
+ code: "scriptable-pack-file-invalid",
3856
+ expected: "a schema-valid ordinary .pack.json package",
3857
+ hint: "repair the Pack JSON against the package schema, then retry the ScriptablePack build",
3858
+ detail: { path, ajvErrors: errors }
3859
+ };
3860
+ }
3861
+ function assetFromRow(path, row) {
3862
+ if (!isScriptablePackAssetKind$1(row.kind)) {
3863
+ return err({
3864
+ code: "scriptable-pack-file-asset-invalid",
3865
+ expected: "an ordinary ScriptablePack Asset kind from SCRIPTABLE_PACK_ASSET_KINDS",
3866
+ hint: "use an ordinary engine Asset kind or keep the custom payload outside this Asset snapshot source",
3867
+ detail: { path, guid: row.guid, reason: `unsupported kind ${JSON.stringify(row.kind)}` }
3868
+ });
3869
+ }
3870
+ if (!isRecord2(row.payload)) {
3871
+ return err({
3872
+ code: "scriptable-pack-file-asset-invalid",
3873
+ expected: "the Pack row payload to be a JSON object",
3874
+ hint: "write a complete ordinary Asset payload in the .pack.json row, then retry the ScriptablePack build",
3875
+ detail: { path, guid: row.guid, reason: "payload is not an object" }
3876
+ });
3877
+ }
3878
+ const payloadKind = row.payload.kind;
3879
+ if (payloadKind !== void 0 && payloadKind !== row.kind) {
3880
+ return err({
3881
+ code: "scriptable-pack-file-asset-invalid",
3882
+ expected: "the row kind and payload.kind to agree",
3883
+ hint: "repair the duplicated kind discriminant in the .pack.json row, then retry the ScriptablePack build",
3884
+ detail: {
3885
+ path,
3886
+ guid: row.guid,
3887
+ reason: `row kind ${JSON.stringify(row.kind)} does not match payload.kind ${JSON.stringify(payloadKind)}`
3888
+ }
3889
+ });
3890
+ }
3891
+ const asset = { ...row.payload, kind: row.kind };
3892
+ return ok({ asset, digest: digest2(asset) });
3893
+ }
3894
+ async function indexPackFiles(assetRoots) {
3895
+ const files = await collectPackFiles(assetRoots);
3896
+ if (!files.ok) return files;
3897
+ const byGuid = /* @__PURE__ */ new Map();
3898
+ const pathsByGuid = /* @__PURE__ */ new Map();
3899
+ const documents = [];
3900
+ for (const path of files.value) {
3901
+ let body;
3902
+ try {
3903
+ body = await readFile(path, "utf8");
3904
+ } catch (error) {
3905
+ return err({
3906
+ code: "scriptable-pack-file-unreadable",
3907
+ expected: "a readable .pack.json file",
3908
+ hint: "restore the .pack.json file, then retry the ScriptablePack build",
3909
+ detail: { path, reason: reasonOf(error) }
3910
+ });
3911
+ }
3912
+ let parsed;
3913
+ try {
3914
+ parsed = JSON.parse(body);
3915
+ } catch (error) {
3916
+ return err({
3917
+ code: "scriptable-pack-file-json-invalid",
3918
+ expected: "a parseable JSON .pack.json file",
3919
+ hint: "repair or restore the .pack.json file, then retry the ScriptablePack build",
3920
+ detail: { path, reason: reasonOf(error) }
3921
+ });
3922
+ }
3923
+ if (!validatePack(parsed)) {
3924
+ return err(
3925
+ invalidPack(
3926
+ path,
3927
+ (validatePack.errors ?? []).map((error) => ({
3928
+ instancePath: error.instancePath,
3929
+ message: error.message ?? "unknown schema validation error"
3930
+ }))
3931
+ )
3932
+ );
3933
+ }
3934
+ const document = parsed;
3935
+ documents.push(document);
3936
+ for (const row of document.assets) {
3937
+ const existingPath = pathsByGuid.get(row.guid.toLowerCase());
3938
+ if (existingPath !== void 0) {
3939
+ return err({
3940
+ code: "scriptable-pack-file-guid-collision",
3941
+ expected: "each ordinary Pack asset GUID to be declared by exactly one .pack.json file",
3942
+ hint: "remove the duplicate GUID declaration or keep one authoritative .pack.json package, then retry",
3943
+ detail: { guid: row.guid.toLowerCase(), paths: [existingPath, path] }
3944
+ });
3945
+ }
3946
+ const asset = assetFromRow(path, row);
3947
+ if (!asset.ok) return asset;
3948
+ const key = row.guid.toLowerCase();
3949
+ pathsByGuid.set(key, path);
3950
+ byGuid.set(key, asset.value);
3951
+ }
3952
+ }
3953
+ const sourceDigest = digest2(
3954
+ [...documents].sort((left, right) => stable2(left).localeCompare(stable2(right)))
3955
+ );
3956
+ return ok({
3957
+ generation: generationFromDigest(sourceDigest),
3958
+ assets: byGuid
3959
+ });
3960
+ }
3961
+ function createScriptablePackFileAssetSnapshotSource(options) {
3962
+ const index = indexPackFiles(options.assetRoots);
3963
+ return {
3964
+ async readByGuid(guid) {
3965
+ const key = AssetGuid$1.format(guid).toLowerCase();
3966
+ const indexed = await index;
3967
+ if (!indexed.ok) return indexed;
3968
+ const asset = indexed.value.assets.get(key);
3969
+ if (asset === void 0) {
3970
+ return err({
3971
+ code: "asset-not-found",
3972
+ expected: `an ordinary .pack.json asset with GUID ${key} under the configured asset roots`,
3973
+ hint: "add the dependency to an asset root or declare it as a ScriptablePack external reference, then retry",
3974
+ detail: { guid: key }
3975
+ });
3976
+ }
3977
+ return ok({
3978
+ asset: structuredClone(asset.asset),
3979
+ generation: indexed.value.generation,
3980
+ digest: asset.digest
3981
+ });
3982
+ }
3983
+ };
3984
+ }
3985
+ function stable3(value) {
3986
+ if (ArrayBuffer.isView(value)) {
3987
+ return `${value.constructor.name}:${JSON.stringify(Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength)))}`;
3988
+ }
3989
+ if (Array.isArray(value)) return `[${value.map(stable3).join(",")}]`;
3990
+ if (value !== null && typeof value === "object") {
3991
+ const record2 = value;
3992
+ return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stable3(record2[key])}`).join(",")}}`;
3993
+ }
3994
+ return JSON.stringify(value) ?? "null";
3995
+ }
3996
+ async function assetDigest(asset) {
3997
+ const bytes2 = new TextEncoder().encode(stable3(asset));
3998
+ const digest3 = await globalThis.crypto.subtle.digest("SHA-256", bytes2);
3999
+ return `sha256:${Array.from(new Uint8Array(digest3), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
4000
+ }
4001
+ function cycleError(stack, owner, guid) {
4002
+ const cycleStart = stack.indexOf(owner);
4003
+ const cycle = [...stack.slice(cycleStart), owner];
4004
+ return {
4005
+ code: "pack-content-dependency-stalled",
4006
+ expected: "the content dependency worklist to make progress",
4007
+ hint: "inspect the waiting GUID and pending subjects, then break the content-read cycle",
4008
+ detail: { waitingGuids: [guid], pendingSubjects: cycle, iterations: 1 }
4009
+ };
4010
+ }
4011
+ function missingOutputError(owner, guid) {
4012
+ return {
4013
+ code: "pack-source-output-invalid",
4014
+ expected: `owner ${owner} to stage every declared output including ${guid}`,
4015
+ hint: "return one output for every GUID declared by the staged owner",
4016
+ detail: { missingGuids: [guid], unexpectedSourceKeys: [], kindMismatches: [] }
4017
+ };
4018
+ }
4019
+ function createScriptablePackStagedAssetSnapshotSource(options) {
4020
+ const owners = options.declaredExternalOutputs === void 0 || options.declaredExternalOutputs.length === 0 ? options.owners : [
4021
+ {
4022
+ id: "<declared-pack-external>",
4023
+ guids: options.declaredExternalOutputs.map((output) => output.guid),
4024
+ async build() {
4025
+ return ok(options.declaredExternalOutputs ?? []);
4026
+ }
4027
+ },
4028
+ ...options.owners
4029
+ ];
4030
+ const ownerByGuid = /* @__PURE__ */ new Map();
4031
+ for (const owner of owners) {
4032
+ if (owner.id.trim().length === 0) throw new TypeError("staged owner id must be non-empty");
4033
+ for (const guid of owner.guids) {
4034
+ const key = AssetGuid$1.format(guid).toLowerCase();
4035
+ const existing = ownerByGuid.get(key);
4036
+ if (existing !== void 0) {
4037
+ throw new TypeError(`staged GUID ${key} is owned by both ${existing.id} and ${owner.id}`);
4038
+ }
4039
+ ownerByGuid.set(key, owner);
4040
+ }
4041
+ }
4042
+ const snapshots = /* @__PURE__ */ new Map();
4043
+ const builds = /* @__PURE__ */ new Map();
4044
+ const sourceFor = (stack) => ({
4045
+ async readByGuid(guid) {
4046
+ const key = AssetGuid$1.format(guid).toLowerCase();
4047
+ const cached = snapshots.get(key);
4048
+ if (cached !== void 0) return ok(structuredClone(cached));
4049
+ const owner = ownerByGuid.get(key);
4050
+ if (owner === void 0) {
4051
+ return err(
4052
+ new AssetError({
4053
+ code: "asset-not-imported",
4054
+ expected: "a staged local owner for the requested GUID",
4055
+ hint: "declare the local ScriptablePack output before rebuilding the generation"
4056
+ })
4057
+ );
4058
+ }
4059
+ if (stack.includes(owner.id)) return err(cycleError(stack, owner.id, key));
4060
+ let building = builds.get(owner.id);
4061
+ if (building === void 0) {
4062
+ building = (async () => {
4063
+ const built2 = await owner.build(sourceFor([...stack, owner.id]));
4064
+ if (!built2.ok) {
4065
+ builds.delete(owner.id);
4066
+ return built2;
4067
+ }
4068
+ const next = /* @__PURE__ */ new Map();
4069
+ for (const output of built2.value) {
4070
+ const outputGuid = AssetGuid$1.format(output.guid).toLowerCase();
4071
+ if (ownerByGuid.get(outputGuid) !== owner) {
4072
+ builds.delete(owner.id);
4073
+ return err(missingOutputError(owner.id, outputGuid));
4074
+ }
4075
+ next.set(outputGuid, {
4076
+ asset: structuredClone(output.asset),
4077
+ generation: options.generation,
4078
+ digest: output.digest ?? await assetDigest(output.asset)
4079
+ });
4080
+ }
4081
+ for (const declared of owner.guids) {
4082
+ const declaredGuid = AssetGuid$1.format(declared).toLowerCase();
4083
+ if (!next.has(declaredGuid)) {
4084
+ builds.delete(owner.id);
4085
+ return err(missingOutputError(owner.id, declaredGuid));
4086
+ }
4087
+ }
4088
+ for (const [outputGuid, snapshot] of next) snapshots.set(outputGuid, snapshot);
4089
+ return ok(void 0);
4090
+ })();
4091
+ builds.set(owner.id, building);
4092
+ }
4093
+ const built = await building;
4094
+ if (!built.ok) return built;
4095
+ const staged = snapshots.get(key);
4096
+ return staged === void 0 ? err(missingOutputError(owner.id, key)) : ok(structuredClone(staged));
4097
+ }
4098
+ });
4099
+ return sourceFor([]);
4100
+ }
2615
4101
 
2616
4102
  // src/source-package-errors.ts
2617
4103
  var SOURCE_PACKAGE_ERROR_POLICY = {
@@ -2672,6 +4158,9 @@ function importFailureCode(error) {
2672
4158
  case "source-read-failed":
2673
4159
  case "import-internal-error":
2674
4160
  case "mesh-material-slot-topology-change":
4161
+ case "mesh-lod-contract-invalid":
4162
+ case "mesh-lod-topology-change":
4163
+ case "mesh-lod-authority-conflict":
2675
4164
  case "unknown-source-key":
2676
4165
  case "duplicate-source-key":
2677
4166
  case "invalid-source-overrides":
@@ -2685,6 +4174,9 @@ var IMPORT_ERROR_CODES = /* @__PURE__ */ new Set([
2685
4174
  "import-produced-no-assets",
2686
4175
  "guid-mismatch",
2687
4176
  "mesh-material-slot-topology-change",
4177
+ "mesh-lod-contract-invalid",
4178
+ "mesh-lod-topology-change",
4179
+ "mesh-lod-authority-conflict",
2688
4180
  "import-internal-error",
2689
4181
  "source-validation-failed",
2690
4182
  "unknown-source-key",
@@ -2802,7 +4294,7 @@ async function commitSourcePackageDdc(candidate) {
2802
4294
  async function persistTransport(transport) {
2803
4295
  if (transport === void 0) return true;
2804
4296
  const directory = dirname(transport.path);
2805
- const temporary = `${transport.path}.tmp`;
4297
+ const temporary = `${transport.path}.${randomUUID()}.tmp`;
2806
4298
  try {
2807
4299
  await mkdir(directory, { recursive: true });
2808
4300
  if (((await stat(directory)).mode & 146) === 0) {
@@ -2827,8 +4319,8 @@ function importPublicationFailure(error) {
2827
4319
  };
2828
4320
  }
2829
4321
  function projectImportPublication(input, head, observedAt) {
2830
- const digest = head.currentKey ?? input.desiredKey;
2831
- const revision = { digest, observedAt, rootId: input.root };
4322
+ const digest3 = head.currentKey ?? input.desiredKey;
4323
+ const revision = { digest: digest3, observedAt, rootId: input.root };
2832
4324
  const published = new Set(input.publishedGuids.map((guid) => guid.toLowerCase()));
2833
4325
  const catalog = input.nextCatalog.map((row) => {
2834
4326
  if (!published.has(row.guid.toLowerCase())) return row;
@@ -2922,6 +4414,35 @@ async function commitImportPublication(candidate) {
2922
4414
  )
2923
4415
  };
2924
4416
  }
4417
+ const transportPersisted = await persistTransport(candidate.input.transport);
4418
+ if (!transportPersisted) {
4419
+ await restoreImportPublication(candidate);
4420
+ return {
4421
+ ok: false,
4422
+ error: {
4423
+ code: "source-package-publication-invalid",
4424
+ expected: "the sidecar and DDC publication to commit atomically",
4425
+ hint: "repair the sidecar destination, then rebuild or cold-cook the source package",
4426
+ detail: "sidecar transport persistence failed; restored the previous DDC/LKG generation",
4427
+ diagnostic: sourcePackageError(
4428
+ "source-package-publication-invalid",
4429
+ {
4430
+ sourceMeta: "<import-publication>",
4431
+ anchorGuid: candidate.input.guid,
4432
+ affectedGuids: candidate.input.publishedGuids,
4433
+ producer: "source-package/import-publication",
4434
+ importer: "import-publication"
4435
+ },
4436
+ { stage: "route-integrity", reason: "sidecar transport persistence failed" }
4437
+ )
4438
+ },
4439
+ head: await inspectHead(
4440
+ candidate.input.root,
4441
+ candidate.input.guid,
4442
+ candidate.input.desiredKey
4443
+ )
4444
+ };
4445
+ }
2925
4446
  const projected = projectImportPublication(candidate.input, publication.value, Date.now());
2926
4447
  return {
2927
4448
  ok: true,
@@ -2929,7 +4450,7 @@ async function commitImportPublication(candidate) {
2929
4450
  head: publication.value,
2930
4451
  catalog: projected.catalog,
2931
4452
  revision: projected.revision,
2932
- transportPersisted: await persistTransport(candidate.input.transport)
4453
+ transportPersisted
2933
4454
  };
2934
4455
  }
2935
4456
  async function discardImportPublication(candidate) {
@@ -2939,6 +4460,6 @@ async function restoreImportPublication(candidate) {
2939
4460
  await candidate.ddc.session.restoreEntry(candidate.ddc);
2940
4461
  }
2941
4462
 
2942
- export { AssetOutputProducerRegistry, DEFAULT_CATALOG_IMPORTER_KEYS, ImporterRegistry, SHADER_RESERVED_IMPORTER_KEY, buildCatalogResult, buildScriptablePack, canonicalScriptableSourcePath, catalogImporterPolicy, commitImportPublication, containsSourcePackageError, createImportProduct, createSceneAssetOutputProducer, createScriptablePackStagedAssetSnapshotSource, createStandardAssetOutputProducerRegistry, declaredPackExternalOutputs, discardImportPublication, finalizeImportProducts, finalizeSourcePackage, materialAssetOutputProducer, materializePreparedScriptablePack, meshAssetOutputProducer, normaliseForPack, normalizeSourcePackageError, packMeshBinV4, parseProducerReadiness, prepareAuthoredPackTransport, produceBuildAssets, produceScriptablePackProducts, produceScriptableSourcePackage, produceSourcePackage, projectImportProductForBuild, projectScriptablePackPublication, publishImportPublication, readCookedAuthoredPack, restoreImportPublication, runImport, scriptablePackInputs, sourceDeclarationForCatalogPath, sourcePackageAssetsByGuid, sourcePackageError, stageImportPublication, textureAssetOutputProducer };
4463
+ export { AssetOutputProducerRegistry, DEFAULT_CATALOG_IMPORTER_KEYS, ImporterRegistry, SHADER_RESERVED_IMPORTER_KEY, buildCatalogResult, buildParameterizedScriptablePack, buildParameterizedScriptablePackWorklist, canonicalScriptableSourcePath, catalogImporterPolicy, commitImportPublication, containsSourcePackageError, createImportProduct, createPreExternalizedSceneAssetOutputProducer, createSceneAssetOutputProducer, createScriptablePackFileAssetSnapshotSource, createScriptablePackStagedAssetSnapshotSource, createStandardAssetOutputProducerRegistry, declaredPackExternalOutputs, deriveDefaultLodScreenCoverages, discardImportPublication, finalizeImportProducts, finalizeSourcePackage, iesImporter, materialAssetOutputProducer, materializePreparedParameterizedScriptablePack, meshAssetOutputProducer, normaliseForPack, normalizeSourcePackageError, packMeshBinV4, parseLm63TypeC, parseProducerReadiness, prepareDirectPackTransport, prepareLegacyPackTransport, produceBuildAssets, produceParameterizedScriptablePackProducts, produceSourcePackage, projectImportProductForBuild, publishImportPublication, readCookedAuthoredPack, readFloat16LE, reconcileMeshLodMeta, resampleTypeC, restoreImportPublication, runImport, sourceDeclarationForCatalogPath, sourcePackageAssetsByGuid, sourcePackageError, stageImportPublication, textureAssetOutputProducer, validateIesProfilePayload, validateMeshLodContract };
2943
4464
  //# sourceMappingURL=index.mjs.map
2944
4465
  //# sourceMappingURL=index.mjs.map