@forgeax/engine-pack 0.1.25 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -426,7 +426,7 @@ When `kind: 'material'`, the `payload` object carries `passes[]` + `values` (fea
426
426
  | `values` | `Record<string, unknown>` | Child-owned values, including per-slot texture coordinates. |
427
427
  | `parent` | `AssetGuid` | Optional serialized parent edge. |
428
428
 
429
- **Validation** is performed by the material schema and build-time cook. The cook resolves the parent chain, validates values and pass programs, and emits a `material-cook/3` record, artifact, references, and receipt. The receipt keeps material, source, layout, program, pipeline, publication, and cook identities together with compiler/WASM provenance and generation counters. Runtime reports a structured missing-cook error instead of compiling a material; stale generations are recooked by the producer.
429
+ **Validation** is performed by the material schema and build-time cook. The cook resolves the parent chain, validates values and pass programs, and emits a `material-cook/4` record, artifact, references, and receipt. The receipt keeps material, source, layout, program, pipeline, publication, and cook identities together with compiler/WASM provenance and generation counters. Runtime reports a structured missing-cook error instead of compiling a material; stale generations are recooked by the producer.
430
430
 
431
431
  ## `AssetGuid` API
432
432
 
package/dist/build.d.ts CHANGED
@@ -4,7 +4,7 @@ export { validateArtifactPath } from './artifact-path.js';
4
4
  export { buildCatalogProjection, type CatalogAuthority, type CatalogBuildError, type CatalogBuildErrorCode, type CatalogBuildProjectionOptions, type CatalogBuildResult, type CatalogImporterPolicy, type CatalogProducerVisibility, catalogSourcePathFor, metaPathForGuid, } from './catalog-builder.js';
5
5
  export { calculateCatalogDelta } from './catalog-delta.js';
6
6
  export { type CatalogOutputDeclaration, type CatalogProducerMeta, type CookedPackageProjection, catalogProjectionFor, currentProjectionFor, findReservedAssetKindConflict, type PackageCatalogInput, projectCookedPackageEntry, projectExternalCatalogEntries, projectPackageCatalog, projectRuntimeCatalogRow, type RuntimeCatalogRowInput, } from './catalog-projection.js';
7
- export { type CookedMaterialRecord, collectMaterialCookRefs, createMaterialArtifactDigest, type MaterialCookArtifact, type MaterialCookIdentityExpectation, type MaterialCookReceipt, type MaterialCookRecordError, type MaterialCookRefs, type MaterialCookVariant, type MaterialCookVariantContext, projectCookedMaterialRecord, serializeCookedMaterialRecord, serializeMaterialCookReceipt, validateCookedMaterialRecord, validateMaterialCookReceipt, } from './evidence/material-cook.js';
7
+ export { type CookedMaterialRecord, collectMaterialCookRefs, createMaterialArtifactDigest, type MaterialCookArtifact, type MaterialCookIdentityExpectation, type MaterialCookProgram, type MaterialCookProgramContext, type MaterialCookReceipt, type MaterialCookRecordError, type MaterialCookRefs, projectCookedMaterialRecord, serializeCookedMaterialRecord, serializeMaterialCookReceipt, validateCookedMaterialRecord, validateMaterialCookReceipt, } from './evidence/material-cook.js';
8
8
  export { buildOfflineAssetEvidence, packageVerification } from './evidence/offline-evidence.js';
9
9
  export { AssetGuid, isValidAssetGuidString, isValidPackSourceKey, PACK_SOURCE_KEY_RE, PackageId, } from './guid.js';
10
10
  export { projectAssetRefs, projectSceneEntityRefs } from './inventory/binding.js';
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
package/dist/build.mjs CHANGED
@@ -10299,8 +10299,7 @@ var STANDARD_ROOT_MODULES = /* @__PURE__ */ new Set([
10299
10299
  "forgeax::default-standard-pbr",
10300
10300
  "forgeax::pbr-skin",
10301
10301
  "forgeax_material::standard",
10302
- "forgeax_material::pbr-skin",
10303
- "forgeax::default-shadow-caster"
10302
+ "forgeax_material::pbr-skin"
10304
10303
  ]);
10305
10304
  function isStandardRootModule(module) {
10306
10305
  return STANDARD_ROOT_MODULES.has(module);
@@ -10379,7 +10378,7 @@ function serializeMaterialCookReceipt(receipt) {
10379
10378
  function invalid(field, actual) {
10380
10379
  return err({
10381
10380
  code: "material-cook-record-invalid",
10382
- expected: "a complete material-cook/3 record with layered identity and provenance",
10381
+ expected: "a complete material-cook/4 record with layered identity and provenance",
10383
10382
  hint: "re-cook the material and publish its record, artifact, references, and receipt together",
10384
10383
  detail: {
10385
10384
  field,
@@ -10440,7 +10439,7 @@ function validateIdentity(value) {
10440
10439
  function validateMaterialCookReceipt(value, expected = {}) {
10441
10440
  if (value === null || typeof value !== "object") return invalid("receipt");
10442
10441
  const candidate = value;
10443
- if (candidate.schemaVersion !== "material-cook/3")
10442
+ if (candidate.schemaVersion !== "material-cook/4")
10444
10443
  return invalid("receipt.schemaVersion", candidate.schemaVersion);
10445
10444
  const identityResult = validateIdentity(candidate.identity);
10446
10445
  if (!identityResult.ok) return identityResult;
@@ -10478,7 +10477,7 @@ function validateMaterialCookReceipt(value, expected = {}) {
10478
10477
  return invalid("receipt.identity.cookIdentity", identity.cookIdentity);
10479
10478
  }
10480
10479
  return ok({
10481
- schemaVersion: "material-cook/3",
10480
+ schemaVersion: "material-cook/4",
10482
10481
  sourceClosure: candidate.sourceClosure,
10483
10482
  profile: candidate.profile,
10484
10483
  compilerVersion: candidate.compilerVersion,
@@ -10492,7 +10491,7 @@ function validateMaterialCookReceipt(value, expected = {}) {
10492
10491
  function validateCookedMaterialRecord(value) {
10493
10492
  if (value === null || typeof value !== "object") return invalid("record");
10494
10493
  const candidate = value;
10495
- if (candidate.schemaVersion !== "material-cook/3")
10494
+ if (candidate.schemaVersion !== "material-cook/4")
10496
10495
  return invalid("schemaVersion", candidate.schemaVersion);
10497
10496
  if (typeof candidate.guid !== "string" || !candidate.guid) return invalid("guid");
10498
10497
  if (candidate.materialGuid !== void 0 && typeof candidate.materialGuid !== "string")
@@ -10501,33 +10500,8 @@ function validateCookedMaterialRecord(value) {
10501
10500
  return invalid("publicationGeneration", candidate.publicationGeneration);
10502
10501
  if (candidate.specializationKey !== void 0 && typeof candidate.specializationKey !== "string")
10503
10502
  return invalid("specializationKey");
10504
- if (candidate.variantContext !== void 0) {
10505
- if (candidate.variantContext === null || typeof candidate.variantContext !== "object")
10506
- return invalid("variantContext");
10507
- const context = candidate.variantContext;
10508
- if (context.backend !== "webgpu" && context.backend !== "webgl2" && context.backend !== "wgpu-native" || context.capability !== "storage-buffer" && context.capability !== "uniform-fallback")
10509
- return invalid("variantContext", candidate.variantContext);
10510
- }
10511
- if (candidate.variants !== void 0) {
10512
- if (!Array.isArray(candidate.variants)) return invalid("variants");
10513
- for (const [index, value2] of candidate.variants.entries()) {
10514
- if (value2 === null || typeof value2 !== "object") return invalid(`variants[${index}]`);
10515
- const variant = value2;
10516
- const context = variant.context;
10517
- if (context === null || typeof context !== "object")
10518
- return invalid(`variants[${index}].context`);
10519
- const contextRecord = context;
10520
- if (contextRecord.backend !== "webgpu" && contextRecord.backend !== "webgl2" && contextRecord.backend !== "wgpu-native" || contextRecord.capability !== "storage-buffer" && contextRecord.capability !== "uniform-fallback")
10521
- return invalid(`variants[${index}].context`, context);
10522
- if (typeof variant.specializationKey !== "string" || variant.specializationKey.length === 0)
10523
- return invalid(`variants[${index}].specializationKey`);
10524
- if (variant.artifact === null || typeof variant.artifact !== "object")
10525
- return invalid(`variants[${index}].artifact`);
10526
- const variantArtifact = variant.artifact;
10527
- if (typeof variantArtifact.mediaType !== "string" || typeof variantArtifact.path !== "string" || typeof variantArtifact.digest !== "string" || normalizeArtifactBytes(variantArtifact.bytes) === void 0)
10528
- return invalid(`variants[${index}].artifact`);
10529
- }
10530
- }
10503
+ if ("artifact" in candidate || "variants" in candidate || "variantContext" in candidate)
10504
+ return invalid("programs", "legacy single-artifact publication");
10531
10505
  if (candidate.artifactDigest !== void 0 && typeof candidate.artifactDigest !== "string")
10532
10506
  return invalid("artifactDigest");
10533
10507
  if (candidate.sourceClosure !== void 0 && (!Array.isArray(candidate.sourceClosure) || candidate.sourceClosure.some((path) => typeof path !== "string")))
@@ -10544,24 +10518,72 @@ function validateCookedMaterialRecord(value) {
10544
10518
  if (candidate.resolved === null || typeof candidate.resolved !== "object")
10545
10519
  return invalid("resolved");
10546
10520
  if (candidate.refs === null || typeof candidate.refs !== "object") return invalid("refs");
10547
- if (candidate.artifact === null || typeof candidate.artifact !== "object")
10548
- return invalid("artifact");
10549
10521
  if (candidate.receipt === null || typeof candidate.receipt !== "object")
10550
10522
  return invalid("receipt");
10551
- const artifact = candidate.artifact;
10552
- if (typeof artifact.digest !== "string" || !Array.isArray(artifact.bytes) && !ArrayBuffer.isView(artifact.bytes)) {
10553
- return invalid("artifact");
10554
- }
10555
- const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
10556
- artifactDigest: artifact.digest
10557
- });
10558
- if (!receiptResult.ok) return receiptResult;
10559
10523
  const resolved = candidate.resolved;
10560
10524
  if (!Array.isArray(resolved.passes)) return invalid("resolved.passes", resolved.passes);
10561
10525
  if (!Array.isArray(resolved.parameters))
10562
10526
  return invalid("resolved.parameters", resolved.parameters);
10563
10527
  if (resolved.values === null || typeof resolved.values !== "object" || Array.isArray(resolved.values))
10564
10528
  return invalid("resolved.values", resolved.values);
10529
+ const passes = resolved.passes;
10530
+ const passNames = /* @__PURE__ */ new Set();
10531
+ for (const [index, pass] of passes.entries()) {
10532
+ if (pass === null || typeof pass !== "object" || typeof pass.name !== "string" || !pass.name || passNames.has(pass.name) || pass.program === null || typeof pass.program !== "object" || typeof pass.program.module !== "string")
10533
+ return invalid(`resolved.passes[${index}]`);
10534
+ passNames.add(pass.name);
10535
+ }
10536
+ if (!Array.isArray(candidate.programs) || candidate.programs.length === 0)
10537
+ return invalid("programs");
10538
+ const programs = [];
10539
+ const programKeys = /* @__PURE__ */ new Set();
10540
+ const selections = /* @__PURE__ */ new Set();
10541
+ const selectedPasses = /* @__PURE__ */ new Set();
10542
+ for (const [index, entry] of candidate.programs.entries()) {
10543
+ const field = `programs[${index}]`;
10544
+ if (entry === null || typeof entry !== "object") return invalid(field);
10545
+ const program = entry;
10546
+ if (typeof program.specializationKey !== "string" || !program.specializationKey || programKeys.has(program.specializationKey))
10547
+ return invalid(`${field}.specializationKey`);
10548
+ programKeys.add(program.specializationKey);
10549
+ if (program.artifact === null || typeof program.artifact !== "object")
10550
+ return invalid(`${field}.artifact`);
10551
+ const artifact = program.artifact;
10552
+ const bytes = normalizeArtifactBytes(artifact.bytes);
10553
+ if (artifact.mediaType !== "text/wgsl" || typeof artifact.path !== "string" || !artifact.path || typeof artifact.digest !== "string" || !artifact.digest || bytes === void 0)
10554
+ return invalid(`${field}.artifact`);
10555
+ if (!Array.isArray(program.selections) || program.selections.length === 0)
10556
+ return invalid(`${field}.selections`);
10557
+ const programSelections = [];
10558
+ for (const [selectionIndex, selection] of program.selections.entries()) {
10559
+ const selectionField = `${field}.selections[${selectionIndex}]`;
10560
+ if (selection === null || typeof selection !== "object" || !passNames.has(selection.pass))
10561
+ return invalid(selectionField);
10562
+ const context = validateMaterialCookProgramContext(selection.context);
10563
+ if (!context.ok)
10564
+ return invalid(
10565
+ `${selectionField}.${context.error.detail.field}`,
10566
+ context.error.detail.actual
10567
+ );
10568
+ const key = JSON.stringify([selection.pass, materialProgramContextKey(context.value)]);
10569
+ if (selections.has(key)) return invalid(selectionField, "ambiguous Pass/context selection");
10570
+ selections.add(key);
10571
+ selectedPasses.add(selection.pass);
10572
+ programSelections.push({ pass: selection.pass, context: context.value });
10573
+ }
10574
+ programs.push({
10575
+ specializationKey: program.specializationKey,
10576
+ artifact: { mediaType: "text/wgsl", path: artifact.path, digest: artifact.digest, bytes },
10577
+ selections: programSelections
10578
+ });
10579
+ }
10580
+ if ([...passNames].some((pass) => !selectedPasses.has(pass)))
10581
+ return invalid("programs.selections", "unpublished Pass");
10582
+ const manifestDigest = createMaterialProgramSetDigest(programs, passes);
10583
+ const receiptResult = validateMaterialCookReceipt(candidate.receipt, {
10584
+ artifactDigest: manifestDigest
10585
+ });
10586
+ if (!receiptResult.ok) return receiptResult;
10565
10587
  let expectedLayerPlanIdentity;
10566
10588
  try {
10567
10589
  expectedLayerPlanIdentity = materialLayerPlanIdentity({
@@ -10576,44 +10598,70 @@ function validateCookedMaterialRecord(value) {
10576
10598
  receiptResult.value.derivedInterface.layerPlanIdentity
10577
10599
  );
10578
10600
  }
10579
- if (candidate.artifactDigest !== void 0 && candidate.artifactDigest !== artifact.digest)
10601
+ if (candidate.artifactDigest !== void 0 && candidate.artifactDigest !== manifestDigest)
10580
10602
  return invalid("artifactDigest", candidate.artifactDigest);
10581
10603
  if (candidate.publicationGeneration !== void 0 && receiptResult.value.identity.cookGeneration !== candidate.publicationGeneration)
10582
10604
  return invalid("receipt.identity.cookGeneration", receiptResult.value.identity.cookGeneration);
10583
- const normalized = {
10605
+ return ok({
10584
10606
  ...candidate,
10585
- artifact: {
10586
- ...artifact,
10587
- bytes: normalizeArtifactBytes(artifact.bytes)
10588
- },
10589
- ...Array.isArray(candidate.variants) ? {
10590
- variants: candidate.variants.map((value2) => {
10591
- const variant = value2;
10592
- const variantArtifact = variant.artifact;
10593
- return {
10594
- ...variant,
10595
- artifact: {
10596
- ...variantArtifact,
10597
- bytes: normalizeArtifactBytes(variantArtifact.bytes)
10598
- }
10599
- };
10600
- })
10601
- } : {},
10607
+ programs,
10602
10608
  receipt: receiptResult.value
10603
- };
10604
- return ok(normalized);
10609
+ });
10605
10610
  }
10606
10611
  function projectCookedMaterialRecord(record2) {
10607
10612
  return {
10608
10613
  resolved: record2.resolved,
10609
10614
  refs: record2.refs,
10610
- artifact: record2.artifact,
10611
- ...record2.variantContext === void 0 ? {} : { variantContext: record2.variantContext },
10612
- ...record2.variants === void 0 ? {} : { variants: record2.variants },
10615
+ programs: record2.programs,
10613
10616
  receipt: record2.receipt,
10614
10617
  schemaVersion: record2.schemaVersion
10615
10618
  };
10616
10619
  }
10620
+ function materialProgramContextKey(context) {
10621
+ return JSON.stringify(jsonValue(context));
10622
+ }
10623
+ function validateMaterialCookProgramContext(value) {
10624
+ if (value === null || typeof value !== "object" || Array.isArray(value))
10625
+ return invalid("context", value);
10626
+ const context = value;
10627
+ const fields = {
10628
+ backend: ["webgpu", "webgl2", "wgpu-native"],
10629
+ capability: ["storage-buffer", "uniform-fallback"],
10630
+ pipeline: ["forward", "deferred"],
10631
+ geometry: ["mesh", "skinned", "sprite"],
10632
+ pass: ["forward", "shadow", "depth"],
10633
+ profile: ["forgeax-material-wgsl-v1"],
10634
+ toolchain: ["naga-oil"],
10635
+ instrumentation: ["none", "validation"]
10636
+ };
10637
+ for (const field of Object.keys(context)) {
10638
+ if (!(field in fields)) return invalid(`context.${field}`, context[field]);
10639
+ }
10640
+ for (const [field, allowed] of Object.entries(fields)) {
10641
+ if (!allowed.includes(context[field]))
10642
+ return invalid(`context.${field}`, context[field]);
10643
+ }
10644
+ return ok({ ...context });
10645
+ }
10646
+ function createMaterialProgramSetDigest(programs, passes) {
10647
+ const manifest = {
10648
+ passes,
10649
+ programs: programs.map((program) => ({
10650
+ specializationKey: program.specializationKey,
10651
+ artifact: {
10652
+ path: program.artifact.path,
10653
+ mediaType: program.artifact.mediaType,
10654
+ digest: program.artifact.digest
10655
+ },
10656
+ selections: [...program.selections].sort(
10657
+ (a, b) => JSON.stringify(jsonValue(a)).localeCompare(JSON.stringify(jsonValue(b)))
10658
+ )
10659
+ })).sort((a, b) => a.specializationKey.localeCompare(b.specializationKey))
10660
+ };
10661
+ return createMaterialArtifactDigest(
10662
+ new TextEncoder().encode(JSON.stringify(jsonValue(manifest)))
10663
+ );
10664
+ }
10617
10665
  function packageEvidence(input) {
10618
10666
  return {
10619
10667
  guid: input.guid,