@instruktlabs/kiln 1.2.2 → 1.2.3

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 (214) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +74 -0
  4. package/README.md +7 -0
  5. package/dist/agent-run.mjs +6392 -1477
  6. package/dist/build.json +23 -23
  7. package/dist/cli.mjs +8453 -3407
  8. package/dist/evaluator-worker.mjs +16608 -12387
  9. package/dist/mcp-engine.mjs +6746 -1725
  10. package/dist/mcp-server.mjs +4 -4
  11. package/dist/viewer/app.js +203 -199
  12. package/dist/viewer/chat.html +209 -205
  13. package/dist/viewer/style.css +10 -0
  14. package/docs/atlas-provenance.md +23 -0
  15. package/docs/community-exporter.md +27 -0
  16. package/docs/export-profiles.md +20 -0
  17. package/docs/material-library.md +9 -5
  18. package/docs/procedural-surfaces-v3.md +276 -0
  19. package/docs/projects-and-live-review.md +1 -1
  20. package/docs/rendering.md +41 -0
  21. package/docs/sdk.md +53 -0
  22. package/docs/tools-operations.md +2 -2
  23. package/docs/tools.md +3 -3
  24. package/lib/asset-materials-node.d.ts +3 -3
  25. package/lib/asset-materials-node.js +29 -33
  26. package/lib/assets-node.d.ts +1 -1
  27. package/lib/assets-node.js +11 -4
  28. package/lib/assets.d.ts +4 -2
  29. package/lib/assets.js +61 -16
  30. package/lib/atlas-provenance.d.ts +86 -0
  31. package/lib/atlas-provenance.js +448 -0
  32. package/lib/character.d.ts +11 -2
  33. package/lib/character.js +90 -2
  34. package/lib/community-exporter.d.ts +3 -2
  35. package/lib/community-exporter.js +139 -3
  36. package/lib/discovery/helper-contracts.js +161 -7
  37. package/lib/discovery/helper-specs.js +49 -4
  38. package/lib/discovery/shapes.js +20 -7
  39. package/lib/engine-identity.d.ts +1 -1
  40. package/lib/engine-identity.js +1 -1
  41. package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
  42. package/lib/evaluator/authoring-diagnostic.js +12 -0
  43. package/lib/evaluator/protocol.d.ts +2 -2
  44. package/lib/evaluator/protocol.js +16 -6
  45. package/lib/evaluator/subprocess.js +2 -2
  46. package/lib/exporter-node.js +9 -1
  47. package/lib/geometry-budget.d.ts +7 -0
  48. package/lib/geometry-budget.js +8 -0
  49. package/lib/gltf-texture-binding.d.ts +6 -0
  50. package/lib/gltf-texture-binding.js +54 -0
  51. package/lib/material-authoring-node.d.ts +6 -0
  52. package/lib/material-authoring-node.js +49 -0
  53. package/lib/material-authoring-schema.d.ts +871 -0
  54. package/lib/material-authoring-schema.js +310 -0
  55. package/lib/material-export.d.ts +5 -0
  56. package/lib/material-export.js +21 -0
  57. package/lib/material-library-node.d.ts +34 -8
  58. package/lib/material-library-node.js +691 -11
  59. package/lib/material-library.d.ts +3704 -4
  60. package/lib/material-library.js +272 -0
  61. package/lib/mcp-core.d.ts +1 -1
  62. package/lib/pinned-material-map.d.ts +33 -0
  63. package/lib/pinned-material-map.js +1 -0
  64. package/lib/portable-material-runtime.d.ts +5 -0
  65. package/lib/portable-material-runtime.js +160 -0
  66. package/lib/portable-sampler-runtime.d.ts +4 -0
  67. package/lib/portable-sampler-runtime.js +26 -0
  68. package/lib/portable-sampler.d.ts +11 -0
  69. package/lib/portable-sampler.js +42 -0
  70. package/lib/primitives.d.ts +15 -3
  71. package/lib/primitives.js +24 -4
  72. package/lib/procedural-material-v2.d.ts +2 -0
  73. package/lib/procedural-material-v2.js +2 -1
  74. package/lib/procedural-material-v3.d.ts +72 -0
  75. package/lib/procedural-material-v3.js +162 -0
  76. package/lib/procedural-texture-v2-compiler.d.ts +31 -0
  77. package/lib/procedural-texture-v2-compiler.js +172 -0
  78. package/lib/procedural-texture-v3-contract.d.ts +125 -0
  79. package/lib/procedural-texture-v3-contract.js +333 -0
  80. package/lib/procedural-texture-v3.d.ts +12 -0
  81. package/lib/procedural-texture-v3.js +165 -0
  82. package/lib/procedural-texture.d.ts +9 -31
  83. package/lib/procedural-texture.js +43 -170
  84. package/lib/project-bundle-node.d.ts +1 -1
  85. package/lib/project-bundle-node.js +70 -13
  86. package/lib/project-bundle.d.ts +91 -3
  87. package/lib/project-bundle.js +48 -7
  88. package/lib/qa/atlas.d.ts +21 -0
  89. package/lib/qa/atlas.js +474 -0
  90. package/lib/qa/index.d.ts +1 -0
  91. package/lib/qa/index.js +1 -0
  92. package/lib/qa/material.js +2 -0
  93. package/lib/qa/skin-integrity.d.ts +18 -0
  94. package/lib/qa/skin-integrity.js +221 -0
  95. package/lib/render-service-host.js +4 -7
  96. package/lib/render.d.ts +3 -3
  97. package/lib/render.js +108 -16
  98. package/lib/skin-binding.d.ts +20 -0
  99. package/lib/skin-binding.js +144 -0
  100. package/lib/texture-atlas-contract.d.ts +97 -0
  101. package/lib/texture-atlas-contract.js +414 -0
  102. package/lib/texture-atlas-pixels.d.ts +73 -0
  103. package/lib/texture-atlas-pixels.js +290 -0
  104. package/lib/texture-atlas.d.ts +10 -0
  105. package/lib/texture-atlas.js +143 -0
  106. package/lib/texture-bake.d.ts +3 -0
  107. package/lib/texture-bake.js +66 -11
  108. package/lib/texture-resolver.d.ts +22 -0
  109. package/lib/texture-resolver.js +24 -0
  110. package/lib/tools/materials.js +8 -7
  111. package/lib/tools/registry.js +5 -16
  112. package/lib/tools/review.js +1 -1
  113. package/lib/uv-data.d.ts +32 -0
  114. package/lib/uv-data.js +172 -0
  115. package/lib/uv-shapes.js +3 -0
  116. package/lib/uv.d.ts +9 -3
  117. package/lib/uv.js +53 -82
  118. package/lib/views/camera.d.ts +3 -3
  119. package/lib/views/camera.js +32 -28
  120. package/lib/views/face-policy.d.ts +9 -0
  121. package/lib/views/face-policy.js +16 -0
  122. package/lib/views/glb.js +218 -24
  123. package/lib/views/pose.d.ts +7 -1
  124. package/lib/views/pose.js +63 -12
  125. package/lib/views/raster.d.ts +6 -2
  126. package/lib/views/raster.js +49 -37
  127. package/lib/views/scene-copy.d.ts +6 -0
  128. package/lib/views/scene-copy.js +73 -0
  129. package/lib/views/scene-raster.js +2 -1
  130. package/lib/views/triangle-coverage.d.ts +8 -0
  131. package/lib/views/triangle-coverage.js +58 -0
  132. package/lib/workspace-node.js +7 -3
  133. package/lib/workspace.d.ts +2 -2
  134. package/package.json +3 -1
  135. package/plugin.json +1 -1
  136. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  137. package/plugins/kiln-engine/README.md +3 -3
  138. package/plugins/kiln-engine/package-provenance.json +6 -6
  139. package/plugins/kiln-engine/plugin.json +1 -1
  140. package/plugins/kiln-engine/runtime.json +1 -1
  141. package/skills/kiln-author-asset/references/geometry-recipes.md +124 -0
  142. package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
  143. package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
  144. package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
  145. package/src/asset-materials-node.ts +38 -45
  146. package/src/assets-node.ts +22 -5
  147. package/src/assets.ts +61 -20
  148. package/src/atlas-provenance.ts +522 -0
  149. package/src/character.ts +107 -2
  150. package/src/community-exporter.ts +143 -3
  151. package/src/discovery/helper-contracts.ts +188 -7
  152. package/src/discovery/helper-specs.ts +69 -4
  153. package/src/discovery/shapes.ts +25 -9
  154. package/src/engine-identity.ts +1 -1
  155. package/src/evaluator/authoring-diagnostic.ts +19 -1
  156. package/src/evaluator/protocol.ts +19 -9
  157. package/src/evaluator/subprocess.ts +2 -2
  158. package/src/exporter-node.ts +12 -1
  159. package/src/generated/mcp-manifest.json +3 -3
  160. package/src/geometry-budget.ts +8 -0
  161. package/src/gltf-texture-binding.ts +60 -0
  162. package/src/material-authoring-node.ts +68 -0
  163. package/src/material-authoring-schema.ts +343 -0
  164. package/src/material-export.ts +25 -0
  165. package/src/material-library-node.ts +840 -16
  166. package/src/material-library.ts +348 -5
  167. package/src/pinned-material-map.ts +43 -0
  168. package/src/portable-material-runtime.ts +200 -0
  169. package/src/portable-sampler-runtime.ts +40 -0
  170. package/src/portable-sampler.ts +54 -0
  171. package/src/primitives.ts +98 -4
  172. package/src/procedural-material-v2.ts +2 -1
  173. package/src/procedural-material-v3.ts +224 -0
  174. package/src/procedural-texture-v2-compiler.ts +234 -0
  175. package/src/procedural-texture-v3-contract.ts +437 -0
  176. package/src/procedural-texture-v3.ts +220 -0
  177. package/src/procedural-texture.ts +61 -229
  178. package/src/project-bundle-node.ts +106 -14
  179. package/src/project-bundle.ts +61 -18
  180. package/src/project-cli.ts +29 -17
  181. package/src/qa/atlas.ts +597 -0
  182. package/src/qa/index.ts +1 -0
  183. package/src/qa/material.ts +2 -0
  184. package/src/qa/skin-integrity.ts +291 -0
  185. package/src/render-service-host.ts +4 -6
  186. package/src/render.ts +133 -15
  187. package/src/skin-binding.ts +225 -0
  188. package/src/texture-atlas-contract.ts +558 -0
  189. package/src/texture-atlas-pixels.ts +456 -0
  190. package/src/texture-atlas.ts +185 -0
  191. package/src/texture-bake.ts +88 -10
  192. package/src/texture-resolver.ts +55 -0
  193. package/src/tools/materials.ts +15 -11
  194. package/src/tools/registry.ts +5 -15
  195. package/src/tools/review.ts +1 -1
  196. package/src/uv-data.ts +238 -0
  197. package/src/uv-shapes.ts +3 -0
  198. package/src/uv.ts +73 -128
  199. package/src/viewer/app.ts +37 -0
  200. package/src/viewer/live-panel.ts +57 -1
  201. package/src/viewer/material-preview-panel.ts +140 -0
  202. package/src/viewer/material-preview.ts +138 -0
  203. package/src/viewer/materials-panel.ts +34 -5
  204. package/src/viewer/navigation.ts +2 -0
  205. package/src/views/camera.ts +37 -40
  206. package/src/views/face-policy.ts +20 -0
  207. package/src/views/glb.ts +294 -17
  208. package/src/views/pose.ts +71 -13
  209. package/src/views/raster.ts +73 -46
  210. package/src/views/scene-copy.ts +86 -0
  211. package/src/views/scene-raster.ts +2 -1
  212. package/src/views/triangle-coverage.ts +78 -0
  213. package/src/workspace-node.ts +11 -3
  214. package/src/workspace.ts +2 -2
@@ -1,30 +1,13 @@
1
- import { validateAssetMaterialClosure } from "./assets.js";
2
- import { canonicalMaterialJson, MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
3
- import { decodeMaterialLibraryPayload, validateMaterialManifestIdentity, } from "./material-library-node.js";
1
+ import { assetMaterialRootManifests, validateAssetMaterialClosure } from "./assets.js";
2
+ import { MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
3
+ import { decodeMaterialLibraryPayload, validateMaterialManifestIdentityAny, resolveMaterialRecordClosure, } from "./material-library-node.js";
4
4
  export function dependencyManifests(asset) {
5
- const result = new Map();
6
- for (const raw of asset.manifest.build?.dependencies ?? []) {
7
- if (!raw || typeof raw !== 'object' || Array.isArray(raw))
8
- throw new Error('Invalid saved asset dependency');
9
- const dependency = raw;
10
- if (dependency.kind === 'kiln.material.v1') {
11
- if (dependency.delivery !== 'runtime')
12
- throw new Error('Invalid material dependency delivery');
13
- const manifest = validateMaterialManifestIdentity(dependency.manifest);
14
- result.set(`${manifest.materialId}/${manifest.revisionId}`, manifest);
15
- if (result.size > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
16
- throw new Error('Saved asset material dependency count exceeds limit');
17
- }
18
- else if (dependency.delivery === 'runtime')
19
- throw new Error(`Unsupported runtime dependency: ${String(dependency.resourceId ?? 'unknown')}`);
20
- else if (dependency.delivery !== 'embedded')
21
- throw new Error('Unsupported saved asset dependency');
22
- }
23
- if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.size)
5
+ const result = assetMaterialRootManifests(asset).map(validateMaterialManifestIdentityAny);
6
+ if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.length)
24
7
  throw new Error('Saved asset has unrecorded external dependencies');
25
8
  let bytes = 0;
26
9
  let pixels = 0;
27
- for (const manifest of result.values())
10
+ for (const manifest of result)
28
11
  for (const map of manifest.maps) {
29
12
  bytes += map.bytes;
30
13
  pixels += map.width * map.height;
@@ -32,7 +15,7 @@ export function dependencyManifests(asset) {
32
15
  if (bytes > MATERIAL_LIBRARY_LIMITS.maxPayloadBytes ||
33
16
  pixels > MATERIAL_LIBRARY_LIMITS.maxPayloadPixels)
34
17
  throw new Error('Saved asset material allocation exceeds limit');
35
- return [...result.values()];
18
+ return result;
36
19
  }
37
20
  export async function resolveSavedAssetMaterials(asset, library) {
38
21
  const dependencies = dependencyManifests(asset);
@@ -40,17 +23,30 @@ export async function resolveSavedAssetMaterials(asset, library) {
40
23
  validateAssetMaterialClosure(asset, true);
41
24
  return decodeMaterialLibraryPayload(asset.materialResources);
42
25
  }
43
- return Promise.all(dependencies.map(async (manifest) => {
26
+ if (!dependencies.length)
27
+ return [];
28
+ if (!library)
29
+ throw new Error('Locked asset material unavailable: No material library configured');
30
+ const roots = new Map(dependencies.map((m) => [`${m.materialId}/${m.revisionId}`, m]));
31
+ const wrap = async (id, revision, load) => {
44
32
  try {
45
- if (!library)
46
- throw new Error('No material library configured');
47
- const record = await library.read(manifest.materialId, manifest.revisionId);
48
- if (canonicalMaterialJson(record.manifest) !== canonicalMaterialJson(manifest))
49
- throw new Error('Saved dependency manifest mismatch');
50
- return record;
33
+ return await load();
51
34
  }
52
35
  catch (error) {
53
- throw new Error(`Locked asset material unavailable: ${manifest.materialId} at ${manifest.revisionId}`, { cause: error });
36
+ throw new Error(`Locked asset material unavailable: ${id} at ${revision}`, { cause: error });
54
37
  }
55
- }));
38
+ };
39
+ return resolveMaterialRecordClosure(dependencies, {
40
+ readManifest: async (id, revision) => wrap(id, revision, async () => {
41
+ const root = roots.get(`${id}/${revision}`);
42
+ if (root)
43
+ return root;
44
+ if (!library.readManifest)
45
+ throw new Error('Material library requires manifest-first dependency capability');
46
+ return library.readManifest(id, revision);
47
+ }),
48
+ read: async (id, revision) => wrap(id, revision, () => library.readRecordFiles
49
+ ? library.readRecordFiles(id, revision)
50
+ : library.read(id, revision)),
51
+ });
56
52
  }
@@ -1,5 +1,5 @@
1
1
  import { type AssetDraft, type AssetLibrary, type AssetManifest, type AssetRecord } from "./assets.js";
2
- import type { MaterialLibrary } from "./material-library.js";
2
+ import { type MaterialLibrary } from "./material-library.js";
3
3
  export { resolveSavedAssetMaterials } from "./asset-materials-node.js";
4
4
  export declare function verifyAssetRecord(record: AssetRecord): Promise<void>;
5
5
  export declare class FileAssetLibrary implements AssetLibrary {
@@ -6,6 +6,7 @@ import { homedir, platform } from 'node:os';
6
6
  import { dirname, join, relative, resolve, sep, win32 } from 'node:path';
7
7
  import { localWorkspaceRoot } from "./workspace-location.js";
8
8
  import { assetIdSchema, assetManifestSchema, ASSET_LIMIT, ASSET_MATERIAL_BYTES, ASSET_MATERIAL_FILE, encodeAssetBundle, validateRecordShape, } from "./assets.js";
9
+ import { canonicalMaterialJson, } from "./material-library.js";
9
10
  import { FileMaterialLibrary, createMaterialLibraryPayload } from "./material-library-node.js";
10
11
  import { dependencyManifests, resolveSavedAssetMaterials } from "./asset-materials-node.js";
11
12
  import { renameDirectoryAtomically } from "./atomic-directory.js";
@@ -201,7 +202,7 @@ export class FileAssetLibrary {
201
202
  assertMaterialAllocation(records.filter((record) => record.materialResources ||
202
203
  record.manifest.build?.dependencies?.some((raw) => raw &&
203
204
  typeof raw === 'object' &&
204
- raw.kind === 'kiln.material.v1')));
205
+ ['kiln.material.v1', 'kiln.material.v2'].includes(String(raw.kind)))));
205
206
  let resourceBytes = 0;
206
207
  for (const record of records) {
207
208
  validateRecordShape(record);
@@ -222,10 +223,16 @@ export class FileAssetLibrary {
222
223
  (this.materials &&
223
224
  record.manifest.build?.dependencies?.some((raw) => raw &&
224
225
  typeof raw === 'object' &&
225
- raw.kind === 'kiln.material.v1'))) {
226
+ ['kiln.material.v1', 'kiln.material.v2'].includes(String(raw.kind))))) {
226
227
  const materials = await resolveSavedAssetMaterials(record, this.materials);
227
- for (const material of materials)
228
- resources.set(material.manifest.revisionId, material);
228
+ for (const material of materials) {
229
+ const key = `${material.manifest.materialId}/${material.manifest.revisionId}`;
230
+ const previous = resources.get(key);
231
+ if (previous &&
232
+ canonicalMaterialJson(previous.manifest) !== canonicalMaterialJson(material.manifest))
233
+ throw new Error('Conflicting asset material dependencies');
234
+ resources.set(key, material);
235
+ }
229
236
  prepared.push({
230
237
  ...record,
231
238
  materialResources: await createMaterialLibraryPayload(materials),
package/lib/assets.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Portable asset records. No filesystem, renderer, or server dependencies. */
2
2
  import { z } from 'zod';
3
- import { type MaterialLibraryPayloadV1 } from "./material-library.js";
3
+ import { type MaterialLibraryPayload, type MaterialManifest } from "./material-library.js";
4
4
  export declare const ASSET_LIMIT: number;
5
5
  export declare const assetIdSchema: z.ZodString;
6
6
  export declare const assetManifestSchema: z.ZodObject<{
@@ -50,7 +50,7 @@ export interface AssetRecord {
50
50
  manifest: AssetManifest;
51
51
  files: Record<string, Uint8Array>;
52
52
  /** Editable delivery closure; not part of the canonical asset manifest or revision identity. */
53
- materialResources?: MaterialLibraryPayloadV1;
53
+ materialResources?: MaterialLibraryPayload;
54
54
  }
55
55
  export interface AssetDraft {
56
56
  name: string;
@@ -82,6 +82,8 @@ export interface AssetLibrary {
82
82
  }
83
83
  export declare const ASSET_MATERIAL_FILE = "materials.kiln.json";
84
84
  export declare const ASSET_MATERIAL_BYTES: number;
85
+ /** Pure declaration boundary shared by portable delivery and host hash verification. */
86
+ export declare function assetMaterialRootManifests(record: AssetRecord): MaterialManifest[];
85
87
  /** Shape and closure checks are portable; hosts verify hashes and PNG/recipe bytes before import. */
86
88
  export declare function validateAssetMaterialClosure(record: AssetRecord, required?: boolean): void;
87
89
  export declare function validateRecordShape(record: AssetRecord): void;
package/lib/assets.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { z } from 'zod';
3
3
  import { BACKDROP_IDS } from "./views/background.js";
4
4
  import { zipSync, unzipSync } from 'three/addons/libs/fflate.module.js';
5
- import { assertMaterialJson, canonicalMaterialJson, materialLibraryPayloadSchema, validateMaterialManifest, MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
5
+ import { assertMaterialJson, canonicalMaterialJson, materialLibraryPayloadAnySchema, materialRecordDependencyPins, validateMaterialManifestAny, MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
6
6
  export const ASSET_LIMIT = 64 * 1024 * 1024;
7
7
  export const assetIdSchema = z.string().regex(/^[a-z][a-z0-9_-]{0,79}$/);
8
8
  const hash = z.string().regex(/^sha256:[a-f0-9]{64}$/);
@@ -47,32 +47,59 @@ export const assetManifestSchema = z.object({
47
47
  });
48
48
  export const ASSET_MATERIAL_FILE = 'materials.kiln.json';
49
49
  export const ASSET_MATERIAL_BYTES = 24 * 1024 * 1024;
50
- /** Shape and closure checks are portable; hosts verify hashes and PNG/recipe bytes before import. */
51
- export function validateAssetMaterialClosure(record, required = false) {
52
- const dependencies = (record.manifest.build?.dependencies ?? []).filter((raw) => raw &&
53
- typeof raw === 'object' &&
54
- raw.kind === 'kiln.material.v1');
55
- const expected = new Map(dependencies.map((dependency) => {
50
+ /** Pure declaration boundary shared by portable delivery and host hash verification. */
51
+ export function assetMaterialRootManifests(record) {
52
+ const roots = new Map();
53
+ for (const raw of record.manifest.build?.dependencies ?? []) {
54
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
55
+ throw new Error('Invalid saved asset dependency');
56
+ const dependency = raw;
57
+ if (dependency.kind !== 'kiln.material.v1' && dependency.kind !== 'kiln.material.v2') {
58
+ if (dependency.delivery === 'runtime')
59
+ throw new Error('Unsupported runtime dependency');
60
+ if (dependency.delivery !== 'embedded')
61
+ throw new Error('Unsupported saved asset dependency');
62
+ continue;
63
+ }
56
64
  if (dependency.delivery !== 'runtime')
57
65
  throw new Error('Invalid saved material dependency');
58
- const manifest = validateMaterialManifest(dependency.manifest);
59
- return [`${manifest.materialId}/${manifest.revisionId}`, canonicalMaterialJson(manifest)];
60
- }));
66
+ const manifest = validateMaterialManifestAny(dependency.manifest);
67
+ if (dependency.kind !== `kiln.material.v${manifest.schemaVersion}`)
68
+ throw new Error('Saved material dependency kind/version mismatch');
69
+ const key = `${manifest.materialId}/${manifest.revisionId}`;
70
+ const existing = roots.get(key);
71
+ if (existing && canonicalMaterialJson(existing) !== canonicalMaterialJson(manifest))
72
+ throw new Error('Conflicting saved material dependency manifests');
73
+ roots.set(key, manifest);
74
+ if (roots.size > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
75
+ throw new Error('Saved asset material dependency count exceeds limit');
76
+ }
77
+ return [...roots.values()];
78
+ }
79
+ /** Shape and closure checks are portable; hosts verify hashes and PNG/recipe bytes before import. */
80
+ export function validateAssetMaterialClosure(record, required = false) {
81
+ const dependencies = assetMaterialRootManifests(record);
82
+ const expected = new Map(dependencies.map((manifest) => [
83
+ `${manifest.materialId}/${manifest.revisionId}`,
84
+ canonicalMaterialJson(manifest),
85
+ ]));
61
86
  if (!record.materialResources) {
62
87
  if (required && expected.size)
63
88
  throw new Error('Editable asset material closure unavailable');
64
89
  return;
65
90
  }
66
91
  assertMaterialJson(record.materialResources);
67
- const payload = materialLibraryPayloadSchema.parse(record.materialResources);
92
+ const payload = materialLibraryPayloadAnySchema.parse(record.materialResources);
68
93
  let bytes = 0;
69
94
  let pixels = 0;
70
- const seen = new Set();
95
+ const available = new Map();
71
96
  for (const wire of payload.records) {
97
+ const manifest = validateMaterialManifestAny(wire.manifest);
72
98
  const key = `${wire.manifest.materialId}/${wire.manifest.revisionId}`;
73
- if (seen.has(key) || expected.get(key) !== canonicalMaterialJson(wire.manifest))
99
+ if (available.has(key) ||
100
+ (expected.has(key) && expected.get(key) !== canonicalMaterialJson(manifest)))
74
101
  throw new Error('Asset material closure mismatch');
75
- seen.add(key);
102
+ available.set(key, manifest);
76
103
  if (Object.keys(wire.files).length !== wire.manifest.maps.length)
77
104
  throw new Error('Asset material map inventory mismatch');
78
105
  for (const map of wire.manifest.maps) {
@@ -82,11 +109,29 @@ export function validateAssetMaterialClosure(record, required = false) {
82
109
  pixels += map.width * map.height;
83
110
  }
84
111
  }
85
- if (seen.size !== expected.size)
86
- throw new Error('Asset material closure incomplete');
87
112
  if (bytes > MATERIAL_LIBRARY_LIMITS.maxPayloadBytes ||
88
113
  pixels > MATERIAL_LIBRARY_LIMITS.maxPayloadPixels)
89
114
  throw new Error('Asset material closure exceeds allocation limits');
115
+ const visited = new Set();
116
+ const active = new Set();
117
+ const visit = (key) => {
118
+ if (active.has(key))
119
+ throw new Error('Asset material closure contains a cycle');
120
+ if (visited.has(key))
121
+ return;
122
+ const manifest = available.get(key);
123
+ if (!manifest)
124
+ throw new Error('Asset material closure incomplete');
125
+ active.add(key);
126
+ for (const pin of materialRecordDependencyPins(manifest))
127
+ visit(`${pin.resourceId}/${pin.revisionId}`);
128
+ active.delete(key);
129
+ visited.add(key);
130
+ };
131
+ for (const key of expected.keys())
132
+ visit(key);
133
+ if (visited.size !== available.size)
134
+ throw new Error('Asset material closure has unrelated records');
90
135
  }
91
136
  const allowedFiles = new Set(['asset.glb', 'source.kiln.js', 'preview.png']);
92
137
  export function validateRecordShape(record) {
@@ -0,0 +1,86 @@
1
+ /** Internal atlas metadata owner. TextureInfo, rather than an image, owns each sampler use. */
2
+ import type { Document, Texture as GltfTexture, TextureInfo } from '@gltf-transform/core';
3
+ import * as THREE from 'three';
4
+ import { type CanonicalTextureAtlasSpecV1 } from "./texture-atlas-contract.js";
5
+ import type { TextureAtlasManifestV1 } from "./texture-atlas-pixels.js";
6
+ export declare const ATLAS_PROVENANCE_VERSION: 'kiln.atlas-provenance.v1';
7
+ export declare const ATLAS_PROVENANCE_LIMITS: Readonly<{
8
+ atlases: 128;
9
+ bytes: 4194304;
10
+ bindings: 1024;
11
+ entryBytes: 262144;
12
+ encodedBytes: 16777216;
13
+ aggregateEncodedBytes: 67108864;
14
+ bakeDecodedBytes: 67108864;
15
+ }>;
16
+ export interface AtlasProvenanceV1 {
17
+ spec: CanonicalTextureAtlasSpecV1;
18
+ manifest: TextureAtlasManifestV1;
19
+ }
20
+ export interface AtlasProvenanceTableV1 {
21
+ version: typeof ATLAS_PROVENANCE_VERSION;
22
+ atlases: (AtlasProvenanceV1 & {
23
+ id: string;
24
+ })[];
25
+ }
26
+ export interface AtlasReferenceV1 {
27
+ version: typeof ATLAS_PROVENANCE_VERSION;
28
+ id: string;
29
+ effectiveSampler?: {
30
+ minFilter: number | null;
31
+ magFilter: number | null;
32
+ wrapS: number;
33
+ wrapT: number;
34
+ };
35
+ finalMime?: string;
36
+ finalImageSha256?: string;
37
+ }
38
+ export interface AtlasBindingV1 {
39
+ version: typeof ATLAS_PROVENANCE_VERSION;
40
+ atlasId: string;
41
+ region: string;
42
+ inputOrigin: 'top-left' | 'bottom-left';
43
+ mode: 'content-boundary' | 'texel-center';
44
+ }
45
+ /** Reads only engine metadata data descriptors; author metadata and encoded arrays are not serialized. */
46
+ export declare function readAtlasTextureProvenance(texture: THREE.Texture): AtlasProvenanceV1 | undefined;
47
+ export declare function validateAtlasProvenanceTable(input: unknown): AtlasProvenanceTableV1;
48
+ export declare function expandAtlasBinding(input: unknown, table: AtlasProvenanceTableV1): {
49
+ manifestHash: `sha256:${string}`;
50
+ specHash: `sha256:${string}`;
51
+ layoutHash: `sha256:${string}`;
52
+ region: string;
53
+ content: import("./texture-atlas-contract.js").AtlasRectangleV1;
54
+ allocation: import("./texture-atlas-contract.js").AtlasRectangleV1;
55
+ inputOrigin: unknown;
56
+ mode: unknown;
57
+ scale: number[];
58
+ offset: number[];
59
+ };
60
+ /** A geometry-wide stamp can be associated only with the exact primary atlas supplied here. */
61
+ export declare function readAtlasGeometryBinding(geometry: THREE.BufferGeometry, provenance: AtlasProvenanceV1): AtlasBindingV1 | undefined;
62
+ export interface AtlasProvenanceCollection {
63
+ table?: AtlasProvenanceTableV1;
64
+ texture(texture: THREE.Texture): AtlasReferenceV1 | undefined;
65
+ binding(geometry: THREE.BufferGeometry): AtlasBindingV1 | undefined;
66
+ }
67
+ export declare function collectAtlasProvenance(root: THREE.Object3D): AtlasProvenanceCollection;
68
+ export declare function atlasDocumentTable(doc: Document): AtlasProvenanceTableV1 | undefined;
69
+ export declare function atlasMaterialUses(doc: Document): {
70
+ texture: GltfTexture;
71
+ info: TextureInfo;
72
+ }[];
73
+ /** Export/optimization only: bind source identity to the final encoded bytes and actual per-use sampler. */
74
+ export declare function refreshAtlasDocumentProvenance(doc: Document): Promise<void>;
75
+ /** Import is validation, never a repair or identity refresh. */
76
+ export declare function validateAtlasDocumentProvenance(doc: Document): Promise<void>;
77
+ /** Host-only owned identity established by the validated GLB loader; no serialized skip flag. */
78
+ export declare function readValidatedAtlasDelivery(texture: THREE.Texture): Readonly<AtlasReferenceV1> | undefined;
79
+ export declare function readAtlasEncodedTexture(texture: THREE.Texture): {
80
+ mime: 'image/png';
81
+ bytes: Uint8Array;
82
+ } | undefined;
83
+ /** Called only after validateAtlasDocumentProvenance; each sampler use gets owned Three state. */
84
+ export declare function restoreAtlasTextureProvenance(texture: THREE.Texture, info: TextureInfo, table: AtlasProvenanceTableV1): void;
85
+ /** Compose before mergeDocuments copies image/geometry buffers or deletes the source scene. */
86
+ export declare function mergeAtlasDocumentProvenance(target: Document, source: Document): Promise<void>;