@forgeax/engine-pack 0.1.29 → 0.1.31

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 (40) hide show
  1. package/README.md +16 -0
  2. package/dist/build.mjs +65 -9
  3. package/dist/build.mjs.map +1 -1
  4. package/dist/catalog-projection.d.ts +3 -3
  5. package/dist/cli-asset.mjs +1 -1
  6. package/dist/cli-asset.mjs.map +1 -1
  7. package/dist/evidence/material-cook.d.ts +7 -1
  8. package/dist/evidence/material-cook.d.ts.map +1 -1
  9. package/dist/index.mjs +61 -5
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/material-cook.mjs +61 -5
  12. package/dist/material-cook.mjs.map +1 -1
  13. package/dist/pack-authoring-node.mjs +1 -1
  14. package/dist/pack-authoring-node.mjs.map +1 -1
  15. package/dist/pack-authoring.d.ts +8 -2
  16. package/dist/pack-authoring.d.ts.map +1 -1
  17. package/dist/pack-authoring.mjs.map +1 -1
  18. package/dist/runtime-publication.d.ts +2 -0
  19. package/dist/runtime-publication.d.ts.map +1 -1
  20. package/dist/scanner.mjs +1 -1
  21. package/dist/scanner.mjs.map +1 -1
  22. package/dist/scriptable-pack-node.d.ts.map +1 -1
  23. package/dist/scriptable-pack-node.mjs +1 -1
  24. package/dist/scriptable-pack-node.mjs.map +1 -1
  25. package/dist/scriptable-pack-worker.mjs +7 -4
  26. package/dist/scriptable-pack-worker.mjs.map +1 -1
  27. package/dist/scriptable-pack.d.ts +2 -1
  28. package/dist/scriptable-pack.d.ts.map +1 -1
  29. package/dist/scriptable-pack.mjs.map +1 -1
  30. package/package.json +2 -2
  31. package/src/__tests__/material-cook-schema.unit.test.ts +92 -1
  32. package/src/__tests__/runtime-publication.unit.test.ts +36 -0
  33. package/src/__tests__/scriptable-pack-cli.integration.test.ts +35 -1
  34. package/src/evidence/material-cook.ts +79 -5
  35. package/src/pack-authoring.ts +11 -2
  36. package/src/runtime-publication.ts +7 -3
  37. package/src/schema/material-cook.schema.json +107 -0
  38. package/src/scriptable-pack-node.ts +5 -1
  39. package/src/scriptable-pack-worker.ts +15 -4
  40. package/src/scriptable-pack.ts +2 -1
package/README.md CHANGED
@@ -278,6 +278,22 @@ vectors, colors, and asset-GUID constraints. The isolated Pack worker passes
278
278
  only serializable authoring data to the build and never exposes filesystem or
279
279
  runtime state.
280
280
 
281
+ Custom build-only content uses `PackCookSource` in the same output map:
282
+
283
+ ```ts
284
+ { 'volume/main': { kind: 'game-volume', execution: 'cooked', source: { size: values.size } } }
285
+ ```
286
+
287
+ Register the matching `NativeCooker` in the build host. The host supplies the
288
+ derived GUID, source key/path and source data; the cooker returns matching-kind
289
+ metadata, references and artifact bytes. Missing cookers, rewritten GUIDs and
290
+ invalid products fail before publication. Custom sources can coexist with
291
+ ordinary Assets and inherit normal Pack parameters and instance identities.
292
+ They cannot override the closed ordinary Asset kinds. Authoring `readByGuid`
293
+ can explicitly request a `PackCookSource` from the current build generation;
294
+ that is source data, not a decoded runtime asset. Runtime custom kinds still
295
+ need their normal Registry loader/decoder. No cooker enters the player.
296
+
281
297
  `forgeax asset inspect --subject <source.pack.ts> --root <project> --json` executes module initialization, validates the default export, and projects canonical Meta without calling `build`. `@forgeax/engine-pack/source-node` accepts a host executor with `load` and optional `dispose`; `timeoutMs` bounds module initialization and `buildTimeoutMs` bounds one `build(context)` call. A build timeout returns one structured `pack-source-load-failed` Result with `detail.phase: 'build'`, the configured `timeoutMs`, and deterministic cleanup of the isolated worker and compile root.
282
298
 
283
299
  The default worker executes the complete relative TypeScript module closure on the supported Node floor, including Node 22 hosts that do not load `.ts` files directly. It transpiles that closure into a disposable ESM directory, resolves bare imports through the source project's nearest `node_modules`, and removes the directory when the worker is disposed. Bulk producers use the internal `createScriptablePackModuleExecutorPool()` with two recyclable workers; a pooled lease is released after metadata projection or one build, so a generation never retains one live Worker-backed definition per source.
package/dist/build.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ok, err, catalogOperationsFor, authoringCapabilityForAssetKind, MATERIAL_TEXTURE_SLOTS, projectCookProductEvidence, projectAssetEvidence, validateSourceOverrideMap, PACK_ERROR_HINTS, catalogEntryDigest, deriveStandardLayerPlan, AssetError, ImportError } from '@forgeax/engine-types';
1
+ import { ok, err, catalogOperationsFor, authoringCapabilityForAssetKind, MATERIAL_TEXTURE_SLOTS, isMaterialProgramAbi, projectCookProductEvidence, projectAssetEvidence, validateSourceOverrideMap, PACK_ERROR_HINTS, catalogEntryDigest, deriveStandardLayerPlan, AssetError, ImportError } from '@forgeax/engine-types';
2
2
  import { dirname, resolve, isAbsolute, relative, join, basename, sep, extname } from 'path';
3
3
  import { sha1 } from '@noble/hashes/legacy.js';
4
4
  import { uuidv7obj } from 'uuidv7';
@@ -9149,7 +9149,7 @@ function scriptablePackLoadFailure(sourcePath, reason, phase, diagnostic, timeou
9149
9149
  }
9150
9150
  async function loadScriptablePack(sourcePath, options = {}) {
9151
9151
  const timeoutMs = options.timeoutMs ?? 15e3;
9152
- const buildTimeoutMs = options.buildTimeoutMs ?? 5e3;
9152
+ const buildTimeoutMs = options.buildTimeoutMs ?? 15e3;
9153
9153
  const executor = options.executor ?? new WorkerScriptablePackExecutor();
9154
9154
  let disposeReason;
9155
9155
  let timeout;
@@ -10547,6 +10547,9 @@ function validateCookedMaterialRecord(value) {
10547
10547
  const programs = [];
10548
10548
  const programKeys = /* @__PURE__ */ new Set();
10549
10549
  const selections = /* @__PURE__ */ new Set();
10550
+ const submissionSelections = /* @__PURE__ */ new Map();
10551
+ let modernPublication = false;
10552
+ let legacySelectionField;
10550
10553
  const selectedPasses = /* @__PURE__ */ new Set();
10551
10554
  for (const [index, entry] of candidate.programs.entries()) {
10552
10555
  const field = `programs[${index}]`;
@@ -10564,9 +10567,11 @@ function validateCookedMaterialRecord(value) {
10564
10567
  if (!Array.isArray(program.selections) || program.selections.length === 0)
10565
10568
  return invalid(`${field}.selections`);
10566
10569
  const programSelections = [];
10567
- for (const [selectionIndex, selection] of program.selections.entries()) {
10570
+ for (const [selectionIndex, rawSelection] of program.selections.entries()) {
10568
10571
  const selectionField = `${field}.selections[${selectionIndex}]`;
10569
- if (selection === null || typeof selection !== "object" || !passNames.has(selection.pass))
10572
+ if (rawSelection === null || typeof rawSelection !== "object") return invalid(selectionField);
10573
+ const selection = rawSelection;
10574
+ if (typeof selection.pass !== "string" || !passNames.has(selection.pass))
10570
10575
  return invalid(selectionField);
10571
10576
  const context = validateMaterialCookProgramContext(selection.context);
10572
10577
  if (!context.ok)
@@ -10574,11 +10579,51 @@ function validateCookedMaterialRecord(value) {
10574
10579
  `${selectionField}.${context.error.detail.field}`,
10575
10580
  context.error.detail.actual
10576
10581
  );
10577
- const key = JSON.stringify([selection.pass, materialProgramContextKey(context.value)]);
10582
+ const address = selection.address === void 0 ? "direct" : selection.address;
10583
+ if (address !== "direct" && address !== "scene-index")
10584
+ return invalid(`${selectionField}.address`, selection.address);
10585
+ const hasAddressFacts = selection.address !== void 0 || selection.entry !== void 0 || selection.abi !== void 0;
10586
+ modernPublication ||= hasAddressFacts;
10587
+ if (!hasAddressFacts) legacySelectionField ??= selectionField;
10588
+ const rawEntry = selection.entry;
10589
+ const entry2 = typeof rawEntry === "string" ? rawEntry : void 0;
10590
+ if (hasAddressFacts && selection.address === void 0)
10591
+ return invalid(
10592
+ `${selectionField}.address`,
10593
+ "modern ABI selections require an explicit address"
10594
+ );
10595
+ if (hasAddressFacts && (entry2 === void 0 || entry2.length === 0))
10596
+ return invalid(`${selectionField}.entry`, rawEntry);
10597
+ if (hasAddressFacts && !isMaterialProgramAbi(selection.abi))
10598
+ return invalid(`${selectionField}.abi`, selection.abi);
10599
+ if (hasAddressFacts) {
10600
+ const abi = selection.abi;
10601
+ const expectedEntry = address === "direct" ? abi.directEntry : abi.sceneIndexEntry;
10602
+ if (entry2 !== expectedEntry)
10603
+ return invalid(`${selectionField}.entry`, "entry does not match published ABI");
10604
+ const submissionKey = JSON.stringify([
10605
+ selection.pass,
10606
+ materialProgramContextKey(context.value)
10607
+ ]);
10608
+ const addresses = submissionSelections.get(submissionKey) ?? /* @__PURE__ */ new Set();
10609
+ addresses.add(address);
10610
+ submissionSelections.set(submissionKey, addresses);
10611
+ }
10612
+ const key = JSON.stringify([
10613
+ selection.pass,
10614
+ materialProgramContextKey(context.value),
10615
+ address
10616
+ ]);
10578
10617
  if (selections.has(key)) return invalid(selectionField, "ambiguous Pass/context selection");
10579
10618
  selections.add(key);
10580
10619
  selectedPasses.add(selection.pass);
10581
- programSelections.push({ pass: selection.pass, context: context.value });
10620
+ programSelections.push({
10621
+ pass: selection.pass,
10622
+ context: context.value,
10623
+ ...selection.address === void 0 ? {} : { address },
10624
+ ...entry2 === void 0 ? {} : { entry: entry2 },
10625
+ ...selection.abi === void 0 ? {} : { abi: selection.abi }
10626
+ });
10582
10627
  }
10583
10628
  programs.push({
10584
10629
  specializationKey: program.specializationKey,
@@ -10586,6 +10631,17 @@ function validateCookedMaterialRecord(value) {
10586
10631
  selections: programSelections
10587
10632
  });
10588
10633
  }
10634
+ if (modernPublication && legacySelectionField !== void 0) {
10635
+ return invalid(
10636
+ legacySelectionField,
10637
+ "modern material publications require address, entry, and ABI facts on every selection"
10638
+ );
10639
+ }
10640
+ for (const [selectionKey, addresses] of submissionSelections) {
10641
+ if (addresses.size !== 2) {
10642
+ return invalid("programs.selections", `incomplete submission address pair: ${selectionKey}`);
10643
+ }
10644
+ }
10589
10645
  if ([...passNames].some((pass) => !selectedPasses.has(pass)))
10590
10646
  return invalid("programs.selections", "unpublished Pass");
10591
10647
  const manifestDigest = createMaterialProgramSetDigest(programs, passes);
@@ -12767,10 +12823,10 @@ function normalizedAssets(pack) {
12767
12823
  };
12768
12824
  });
12769
12825
  }
12770
- function outputFor(asset) {
12826
+ function outputFor(asset, sourceKey2) {
12771
12827
  return {
12772
12828
  guid: asset.guid.toLowerCase(),
12773
- sourceKey: asset.guid.toLowerCase(),
12829
+ sourceKey: sourceKey2 ?? asset.guid.toLowerCase(),
12774
12830
  kind: asset.kind,
12775
12831
  digest: digest2({
12776
12832
  guid: asset.guid.toLowerCase(),
@@ -12809,7 +12865,7 @@ function createRuntimePackPublication(input) {
12809
12865
  )
12810
12866
  };
12811
12867
  const valueDigest = input.digest ?? digest2(semantic);
12812
- const outputs = input.outputs ?? assets.map(outputFor);
12868
+ const outputs = input.outputs ?? assets.map((asset) => outputFor(asset, input.sourceKeys?.get(asset.guid.toLowerCase())));
12813
12869
  const outputDigest = outputSetDigest(outputs);
12814
12870
  const generation = input.generation ?? publicationGeneration(input.sourceRevision, valueDigest, outputDigest);
12815
12871
  const externalEvidence = input.externalEvidence ?? [];