@instruktlabs/kiln 1.2.1 → 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 (219) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +86 -1
  4. package/README.md +18 -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/install.md +7 -6
  18. package/docs/material-library.md +9 -5
  19. package/docs/migration.md +16 -0
  20. package/docs/procedural-surfaces-v3.md +276 -0
  21. package/docs/projects-and-live-review.md +1 -1
  22. package/docs/rendering.md +41 -0
  23. package/docs/runtime.md +3 -3
  24. package/docs/sdk.md +55 -2
  25. package/docs/tools-operations.md +2 -2
  26. package/docs/tools.md +3 -3
  27. package/lib/asset-materials-node.d.ts +3 -3
  28. package/lib/asset-materials-node.js +29 -33
  29. package/lib/assets-node.d.ts +1 -1
  30. package/lib/assets-node.js +11 -4
  31. package/lib/assets.d.ts +4 -2
  32. package/lib/assets.js +61 -16
  33. package/lib/atlas-provenance.d.ts +86 -0
  34. package/lib/atlas-provenance.js +448 -0
  35. package/lib/character.d.ts +11 -2
  36. package/lib/character.js +90 -2
  37. package/lib/community-exporter.d.ts +3 -2
  38. package/lib/community-exporter.js +139 -3
  39. package/lib/discovery/helper-contracts.js +161 -7
  40. package/lib/discovery/helper-specs.js +49 -4
  41. package/lib/discovery/shapes.js +20 -7
  42. package/lib/engine-identity.d.ts +1 -1
  43. package/lib/engine-identity.js +1 -1
  44. package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
  45. package/lib/evaluator/authoring-diagnostic.js +12 -0
  46. package/lib/evaluator/protocol.d.ts +2 -2
  47. package/lib/evaluator/protocol.js +16 -6
  48. package/lib/evaluator/subprocess.js +2 -2
  49. package/lib/exporter-node.js +9 -1
  50. package/lib/geometry-budget.d.ts +7 -0
  51. package/lib/geometry-budget.js +8 -0
  52. package/lib/gltf-texture-binding.d.ts +6 -0
  53. package/lib/gltf-texture-binding.js +54 -0
  54. package/lib/material-authoring-node.d.ts +6 -0
  55. package/lib/material-authoring-node.js +49 -0
  56. package/lib/material-authoring-schema.d.ts +871 -0
  57. package/lib/material-authoring-schema.js +310 -0
  58. package/lib/material-export.d.ts +5 -0
  59. package/lib/material-export.js +21 -0
  60. package/lib/material-library-node.d.ts +34 -8
  61. package/lib/material-library-node.js +691 -11
  62. package/lib/material-library.d.ts +3704 -4
  63. package/lib/material-library.js +272 -0
  64. package/lib/mcp-core.d.ts +1 -1
  65. package/lib/pinned-material-map.d.ts +33 -0
  66. package/lib/pinned-material-map.js +1 -0
  67. package/lib/portable-material-runtime.d.ts +5 -0
  68. package/lib/portable-material-runtime.js +160 -0
  69. package/lib/portable-sampler-runtime.d.ts +4 -0
  70. package/lib/portable-sampler-runtime.js +26 -0
  71. package/lib/portable-sampler.d.ts +11 -0
  72. package/lib/portable-sampler.js +42 -0
  73. package/lib/primitives.d.ts +15 -3
  74. package/lib/primitives.js +24 -4
  75. package/lib/procedural-material-v2.d.ts +2 -0
  76. package/lib/procedural-material-v2.js +2 -1
  77. package/lib/procedural-material-v3.d.ts +72 -0
  78. package/lib/procedural-material-v3.js +162 -0
  79. package/lib/procedural-texture-v2-compiler.d.ts +31 -0
  80. package/lib/procedural-texture-v2-compiler.js +172 -0
  81. package/lib/procedural-texture-v3-contract.d.ts +125 -0
  82. package/lib/procedural-texture-v3-contract.js +333 -0
  83. package/lib/procedural-texture-v3.d.ts +12 -0
  84. package/lib/procedural-texture-v3.js +165 -0
  85. package/lib/procedural-texture.d.ts +9 -31
  86. package/lib/procedural-texture.js +43 -170
  87. package/lib/project-bundle-node.d.ts +1 -1
  88. package/lib/project-bundle-node.js +70 -13
  89. package/lib/project-bundle.d.ts +91 -3
  90. package/lib/project-bundle.js +48 -7
  91. package/lib/qa/atlas.d.ts +21 -0
  92. package/lib/qa/atlas.js +474 -0
  93. package/lib/qa/index.d.ts +1 -0
  94. package/lib/qa/index.js +1 -0
  95. package/lib/qa/material.js +2 -0
  96. package/lib/qa/skin-integrity.d.ts +18 -0
  97. package/lib/qa/skin-integrity.js +221 -0
  98. package/lib/render-service-host.js +4 -7
  99. package/lib/render.d.ts +3 -3
  100. package/lib/render.js +108 -16
  101. package/lib/skin-binding.d.ts +20 -0
  102. package/lib/skin-binding.js +144 -0
  103. package/lib/texture-atlas-contract.d.ts +97 -0
  104. package/lib/texture-atlas-contract.js +414 -0
  105. package/lib/texture-atlas-pixels.d.ts +73 -0
  106. package/lib/texture-atlas-pixels.js +290 -0
  107. package/lib/texture-atlas.d.ts +10 -0
  108. package/lib/texture-atlas.js +143 -0
  109. package/lib/texture-bake.d.ts +3 -0
  110. package/lib/texture-bake.js +66 -11
  111. package/lib/texture-resolver.d.ts +22 -0
  112. package/lib/texture-resolver.js +24 -0
  113. package/lib/tools/materials.js +8 -7
  114. package/lib/tools/registry.js +5 -16
  115. package/lib/tools/review.js +1 -1
  116. package/lib/uv-data.d.ts +32 -0
  117. package/lib/uv-data.js +172 -0
  118. package/lib/uv-shapes.js +3 -0
  119. package/lib/uv.d.ts +9 -3
  120. package/lib/uv.js +53 -82
  121. package/lib/views/camera.d.ts +3 -3
  122. package/lib/views/camera.js +32 -28
  123. package/lib/views/face-policy.d.ts +9 -0
  124. package/lib/views/face-policy.js +16 -0
  125. package/lib/views/glb.js +218 -24
  126. package/lib/views/pose.d.ts +7 -1
  127. package/lib/views/pose.js +63 -12
  128. package/lib/views/raster.d.ts +6 -2
  129. package/lib/views/raster.js +49 -37
  130. package/lib/views/scene-copy.d.ts +6 -0
  131. package/lib/views/scene-copy.js +73 -0
  132. package/lib/views/scene-raster.js +2 -1
  133. package/lib/views/triangle-coverage.d.ts +8 -0
  134. package/lib/views/triangle-coverage.js +58 -0
  135. package/lib/workspace-node.js +7 -3
  136. package/lib/workspace.d.ts +2 -2
  137. package/package.json +3 -1
  138. package/plugin.json +1 -1
  139. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  140. package/plugins/kiln-engine/README.md +3 -3
  141. package/plugins/kiln-engine/package-provenance.json +6 -6
  142. package/plugins/kiln-engine/plugin.json +1 -1
  143. package/plugins/kiln-engine/runtime.json +1 -1
  144. package/skills/kiln-author-asset/SKILL.md +1 -1
  145. package/skills/kiln-author-asset/references/camera-cli.md +13 -0
  146. package/skills/kiln-author-asset/references/geometry-recipes.md +134 -0
  147. package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
  148. package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
  149. package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
  150. package/src/asset-materials-node.ts +38 -45
  151. package/src/assets-node.ts +22 -5
  152. package/src/assets.ts +61 -20
  153. package/src/atlas-provenance.ts +522 -0
  154. package/src/character.ts +107 -2
  155. package/src/community-exporter.ts +143 -3
  156. package/src/discovery/helper-contracts.ts +188 -7
  157. package/src/discovery/helper-specs.ts +69 -4
  158. package/src/discovery/shapes.ts +25 -9
  159. package/src/engine-identity.ts +1 -1
  160. package/src/evaluator/authoring-diagnostic.ts +19 -1
  161. package/src/evaluator/protocol.ts +19 -9
  162. package/src/evaluator/subprocess.ts +2 -2
  163. package/src/exporter-node.ts +12 -1
  164. package/src/generated/mcp-manifest.json +3 -3
  165. package/src/geometry-budget.ts +8 -0
  166. package/src/gltf-texture-binding.ts +60 -0
  167. package/src/material-authoring-node.ts +68 -0
  168. package/src/material-authoring-schema.ts +343 -0
  169. package/src/material-export.ts +25 -0
  170. package/src/material-library-node.ts +840 -16
  171. package/src/material-library.ts +348 -5
  172. package/src/pinned-material-map.ts +43 -0
  173. package/src/portable-material-runtime.ts +200 -0
  174. package/src/portable-sampler-runtime.ts +40 -0
  175. package/src/portable-sampler.ts +54 -0
  176. package/src/primitives.ts +98 -4
  177. package/src/procedural-material-v2.ts +2 -1
  178. package/src/procedural-material-v3.ts +224 -0
  179. package/src/procedural-texture-v2-compiler.ts +234 -0
  180. package/src/procedural-texture-v3-contract.ts +437 -0
  181. package/src/procedural-texture-v3.ts +220 -0
  182. package/src/procedural-texture.ts +61 -229
  183. package/src/project-bundle-node.ts +106 -14
  184. package/src/project-bundle.ts +61 -18
  185. package/src/project-cli.ts +29 -17
  186. package/src/qa/atlas.ts +597 -0
  187. package/src/qa/index.ts +1 -0
  188. package/src/qa/material.ts +2 -0
  189. package/src/qa/skin-integrity.ts +291 -0
  190. package/src/render-service-host.ts +4 -6
  191. package/src/render.ts +133 -15
  192. package/src/skin-binding.ts +225 -0
  193. package/src/texture-atlas-contract.ts +558 -0
  194. package/src/texture-atlas-pixels.ts +456 -0
  195. package/src/texture-atlas.ts +185 -0
  196. package/src/texture-bake.ts +88 -10
  197. package/src/texture-resolver.ts +55 -0
  198. package/src/tools/materials.ts +15 -11
  199. package/src/tools/registry.ts +5 -15
  200. package/src/tools/review.ts +1 -1
  201. package/src/uv-data.ts +238 -0
  202. package/src/uv-shapes.ts +3 -0
  203. package/src/uv.ts +73 -128
  204. package/src/viewer/app.ts +37 -0
  205. package/src/viewer/live-panel.ts +57 -1
  206. package/src/viewer/material-preview-panel.ts +140 -0
  207. package/src/viewer/material-preview.ts +138 -0
  208. package/src/viewer/materials-panel.ts +34 -5
  209. package/src/viewer/navigation.ts +2 -0
  210. package/src/views/camera.ts +37 -40
  211. package/src/views/face-policy.ts +20 -0
  212. package/src/views/glb.ts +294 -17
  213. package/src/views/pose.ts +71 -13
  214. package/src/views/raster.ts +73 -46
  215. package/src/views/scene-copy.ts +86 -0
  216. package/src/views/scene-raster.ts +2 -1
  217. package/src/views/triangle-coverage.ts +78 -0
  218. package/src/workspace-node.ts +11 -3
  219. package/src/workspace.ts +2 -2
@@ -37,179 +37,52 @@
37
37
  * still catches.
38
38
  */
39
39
  import * as THREE from 'three';
40
- import { ProceduralTextureError, canonicalProceduralTextureJsonV2, canonicalizeProceduralTextureSpecV2, hashProceduralTextureSpecV2, } from "./procedural-material-v2.js";
40
+ import { compileProceduralTextureSpecV2 } from "./procedural-texture-v2-compiler.js";
41
+ import { compileProceduralTextureSpecV3 } from "./procedural-texture-v3.js";
42
+ import { ProceduralTextureError, } from "./procedural-material-v2.js";
41
43
  export { MAX_NOISE_OCTAVES, MAX_PORTABLE_MATERIAL_TEXELS, MAX_PORTABLE_MATERIAL_TEXTURES, MAX_PROCEDURAL_LAYERS, MAX_PROCEDURAL_NAME_LENGTH, MAX_PROCEDURAL_PATTERN_COUNT, MAX_PROCEDURAL_SIZE, MIN_PROCEDURAL_SIZE, PORTABLE_MATERIAL_SPEC_VERSION, PROCEDURAL_TEXTURE_SPEC_VERSION, ProceduralTextureError, canonicalProceduralTextureJsonV2, canonicalizePortableMaterialSpecV2, canonicalizeProceduralTextureSpecV2, hashProceduralTextureSpecV2, migrateProceduralTextureSpecV1, } from "./procedural-material-v2.js";
42
- // -----------------------------------------------------------------------------
43
- // Deterministic value noise
44
- // -----------------------------------------------------------------------------
45
- /**
46
- * Integer hash of a lattice coordinate. Not a cryptographic hash — it only has
47
- * to decorrelate neighbouring integers and return the same value forever.
48
- */
49
- function hash2(x, y, seed) {
50
- let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 1274126177)) | 0;
51
- h = h ^ (h >>> 13);
52
- h = Math.imul(h, 1274126177) | 0;
53
- h = h ^ (h >>> 16);
54
- return (h >>> 0) / 4294967296;
55
- }
56
- const smoothstep = (t) => t * t * (3 - 2 * t);
57
- /**
58
- * Value noise on a lattice that wraps at `period`, so the result tiles.
59
- * `period` is in lattice cells, and both axes use the same one.
60
- */
61
- function valueNoise(u, v, period, seed) {
62
- const x = u * period;
63
- const y = v * period;
64
- const xi = Math.floor(x);
65
- const yi = Math.floor(y);
66
- const xf = x - xi;
67
- const yf = y - yi;
68
- // Wrapping the lattice index (not the sample point) is what makes it tile:
69
- // the cell to the right of the last one is the first one again.
70
- const wrap = (n) => ((n % period) + period) % period;
71
- const x0 = wrap(xi);
72
- const y0 = wrap(yi);
73
- const x1 = wrap(xi + 1);
74
- const y1 = wrap(yi + 1);
75
- const sx = smoothstep(xf);
76
- const sy = smoothstep(yf);
77
- const n00 = hash2(x0, y0, seed);
78
- const n10 = hash2(x1, y0, seed);
79
- const n01 = hash2(x0, y1, seed);
80
- const n11 = hash2(x1, y1, seed);
81
- const top = n00 + sx * (n10 - n00);
82
- const bottom = n01 + sx * (n11 - n01);
83
- return top + sy * (bottom - top);
84
- }
85
- function fbm(u, v, period, octaves, seed) {
86
- let sum = 0;
87
- let amplitude = 1;
88
- let total = 0;
89
- let p = period;
90
- for (let o = 0; o < octaves; o++) {
91
- sum += valueNoise(u, v, p, seed + o * 101) * amplitude;
92
- total += amplitude;
93
- amplitude *= 0.5;
94
- // Doubling the period keeps every octave tileable at the base period.
95
- p *= 2;
96
- }
97
- return total > 0 ? sum / total : 0;
98
- }
99
- // -----------------------------------------------------------------------------
100
- // Layer evaluation
101
- // -----------------------------------------------------------------------------
102
- const rgbOf = (color) => [
103
- (color >> 16) & 0xff,
104
- (color >> 8) & 0xff,
105
- color & 0xff,
106
- ];
107
- const lerp = (a, b, t) => a + (b - a) * t;
108
- /** Rotate normalized coords about the center so angled patterns stay centered. */
109
- function project(u, v, angleDeg) {
110
- const a = (angleDeg * Math.PI) / 180;
111
- return (u - 0.5) * Math.cos(a) + (v - 0.5) * Math.sin(a) + 0.5;
112
- }
113
- /** Evaluate one layer at a normalized coordinate, returning 0-255 RGB. */
114
- function evalLayer(layer, u, v) {
115
- switch (layer.op) {
116
- case 'solid':
117
- return rgbOf(layer.color);
118
- case 'checker': {
119
- const cx = Math.floor(u * layer.squares);
120
- const cy = Math.floor(v * layer.squares);
121
- return rgbOf((cx + cy) % 2 === 0 ? layer.colorA : layer.colorB);
122
- }
123
- case 'stripes': {
124
- const t = project(u, v, layer.angleDeg);
125
- return rgbOf(Math.floor(t * layer.count) % 2 === 0 ? layer.colorA : layer.colorB);
126
- }
127
- case 'gradient': {
128
- const t = Math.min(1, Math.max(0, project(u, v, layer.angleDeg)));
129
- const from = rgbOf(layer.from);
130
- const to = rgbOf(layer.to);
131
- return [lerp(from[0], to[0], t), lerp(from[1], to[1], t), lerp(from[2], to[2], t)];
132
- }
133
- case 'bricks': {
134
- const row = Math.floor(v * layer.rows);
135
- // Offset alternate rows, wrapping into [0,1) so the pattern still tiles.
136
- const shifted = (u + (row % 2 === 0 ? 0 : layer.stagger / layer.cols) + 1) % 1;
137
- const inCol = (shifted * layer.cols) % 1;
138
- const inRow = (v * layer.rows) % 1;
139
- const isMortar = inCol < layer.mortarWidth ||
140
- inCol > 1 - layer.mortarWidth ||
141
- inRow < layer.mortarWidth ||
142
- inRow > 1 - layer.mortarWidth;
143
- return rgbOf(isMortar ? layer.mortar : layer.brick);
144
- }
145
- case 'noise': {
146
- const n = fbm(u, v, layer.scale, layer.octaves, layer.seed);
147
- const a = rgbOf(layer.colorA);
148
- const b = rgbOf(layer.colorB);
149
- return [lerp(a[0], b[0], n), lerp(a[1], b[1], n), lerp(a[2], b[2], n)];
150
- }
151
- }
152
- }
153
- function blendChannel(mode, base, top) {
154
- const b = base / 255;
155
- const t = top / 255;
156
- let out;
157
- switch (mode) {
158
- case 'multiply':
159
- out = b * t;
160
- break;
161
- case 'screen':
162
- out = 1 - (1 - b) * (1 - t);
163
- break;
164
- case 'overlay':
165
- out = b < 0.5 ? 2 * b * t : 1 - 2 * (1 - b) * (1 - t);
166
- break;
167
- default:
168
- out = t;
169
- }
170
- return out * 255;
171
- }
172
- /** Validate, canonicalize, and compile one texture without constructing Three.js state. */
173
- export function compileProceduralTextureSpecV2(input) {
174
- const spec = canonicalizeProceduralTextureSpecV2(input);
175
- const { size, layers } = spec;
176
- const data = new Uint8Array(size * size * 4);
177
- for (let y = 0; y < size; y++) {
178
- // Sample at texel centers so a pattern with N divisions lands on exact
179
- // boundaries instead of drifting half a texel.
180
- const v = (y + 0.5) / size;
181
- for (let x = 0; x < size; x++) {
182
- const u = (x + 0.5) / size;
183
- let r = 0;
184
- let g = 0;
185
- let b = 0;
186
- for (const [i, layer] of layers.entries()) {
187
- const [lr, lg, lb] = evalLayer(layer, u, v);
188
- if (i === 0) {
189
- r = lr;
190
- g = lg;
191
- b = lb;
192
- continue;
193
- }
194
- r = lerp(r, blendChannel(layer.blend, r, lr), layer.opacity);
195
- g = lerp(g, blendChannel(layer.blend, g, lg), layer.opacity);
196
- b = lerp(b, blendChannel(layer.blend, b, lb), layer.opacity);
197
- }
198
- const o = (y * size + x) * 4;
199
- data[o] = Math.max(0, Math.min(255, Math.round(r)));
200
- data[o + 1] = Math.max(0, Math.min(255, Math.round(g)));
201
- data[o + 2] = Math.max(0, Math.min(255, Math.round(b)));
202
- data[o + 3] = 255;
203
- }
204
- }
205
- return {
206
- spec,
207
- pixels: data,
208
- canonicalJson: canonicalProceduralTextureJsonV2(spec),
209
- recipeHash: hashProceduralTextureSpecV2(spec),
210
- };
44
+ export * from "./procedural-texture-v3.js";
45
+ /** Dispatch never invokes an input accessor; absent/undefined retains V1 compatibility. */
46
+ export function proceduralTextureSchemaVersion(input) {
47
+ if (!input || typeof input !== 'object' || Array.isArray(input))
48
+ throw new ProceduralTextureError('proceduralTexture requires a plain JSON object.', 'PROCEDURAL_TEXTURE_V3_CONTRACT');
49
+ const prototype = Object.getPrototypeOf(input);
50
+ if (prototype !== Object.prototype && prototype !== null)
51
+ throw new ProceduralTextureError('proceduralTexture requires a plain JSON object.', 'PROCEDURAL_TEXTURE_V3_CONTRACT');
52
+ const descriptor = Object.getOwnPropertyDescriptor(input, 'schemaVersion');
53
+ if (descriptor && (!descriptor.enumerable || !('value' in descriptor)))
54
+ throw new ProceduralTextureError('proceduralTexture.schemaVersion must be a JSON data field.', 'PROCEDURAL_TEXTURE_V3_CONTRACT');
55
+ const version = descriptor?.value;
56
+ if (version === undefined || version === 1)
57
+ return 1;
58
+ if (version === 2 || version === 3)
59
+ return version;
60
+ throw new ProceduralTextureError('proceduralTexture.schemaVersion must be 1, 2 or 3.', 'PROCEDURAL_TEXTURE_V3_CONTRACT');
211
61
  }
62
+ export { compileProceduralTextureSpecV2 } from "./procedural-texture-v2-compiler.js";
212
63
  export function proceduralTexture(spec) {
64
+ if (proceduralTextureSchemaVersion(spec) === 3) {
65
+ const compiled = compileProceduralTextureSpecV3(spec);
66
+ const { width, height, usage, name } = compiled.spec;
67
+ const tex = new THREE.DataTexture(compiled.pixels, width, height, THREE.RGBAFormat, THREE.UnsignedByteType);
68
+ if (name)
69
+ tex.name = name;
70
+ tex.colorSpace =
71
+ usage === 'albedo' || usage === 'emissive' ? THREE.SRGBColorSpace : THREE.NoColorSpace;
72
+ tex.wrapS = THREE.RepeatWrapping;
73
+ tex.wrapT = THREE.RepeatWrapping;
74
+ tex.minFilter = THREE.LinearMipmapLinearFilter;
75
+ tex.magFilter = THREE.LinearFilter;
76
+ tex.generateMipmaps = true;
77
+ tex.needsUpdate = true;
78
+ tex.userData['kilnProcedural'] = {
79
+ ...compiled.spec,
80
+ canonicalJson: compiled.canonicalJson,
81
+ recipeHash: compiled.recipeHash,
82
+ compilerId: compiled.compilerId,
83
+ };
84
+ return tex;
85
+ }
213
86
  const compiled = compileProceduralTextureSpecV2(spec);
214
87
  const { size, usage, name, layers } = compiled.spec;
215
88
  const tex = new THREE.DataTexture(compiled.pixels, size, size, THREE.RGBAFormat, THREE.UnsignedByteType);
@@ -1,7 +1,7 @@
1
1
  import type { AssetLibrary, AssetRecord } from "./assets.js";
2
2
  import type { FileWorkspace } from "./workspace-node.js";
3
3
  import { type ProjectRevision } from "./projects.js";
4
- export declare function resolveAssetMaterialPayload(workspace: FileWorkspace, asset: AssetRecord): Promise<import("./material-library.js").MaterialLibraryPayloadV1>;
4
+ export declare function resolveAssetMaterialPayload(workspace: FileWorkspace, asset: AssetRecord): Promise<import("./material-library.js").MaterialLibraryPayload>;
5
5
  export declare function exportWorkspaceProjectBundle(workspace: FileWorkspace, library: AssetLibrary, projectId: string, options?: {
6
6
  revisionId?: string;
7
7
  profile?: 'editable' | 'runtime';
@@ -4,9 +4,9 @@ import { link, lstat, mkdir, readFile, realpath, unlink, writeFile } from 'node:
4
4
  import { join, relative, resolve, sep } from 'node:path';
5
5
  import { verifyAssetRecord } from "./assets-node.js";
6
6
  import { projectDraftSchema, projectIdSchema, ProjectExistsError, ProjectNotFoundError, } from "./projects.js";
7
- import { canonicalMaterialJson } from "./material-library.js";
7
+ import { canonicalMaterialJson, MATERIAL_LIBRARY_LIMITS, } from "./material-library.js";
8
8
  import { dependencyManifests, resolveSavedAssetMaterials } from "./asset-materials-node.js";
9
- import { createMaterialLibraryPayload, verifyMaterialRecordV1 } from "./material-library-node.js";
9
+ import { createMaterialLibraryPayload, verifyMaterialRecord, resolveMaterialRecordClosure, validateMaterialManifestIdentityAny, } from "./material-library-node.js";
10
10
  import { PROJECT_BUNDLE_LIMITS, decodeProjectBundle, encodeProjectBundle, projectBundleHash, } from "./project-bundle.js";
11
11
  async function assetMaterials(workspace, asset) {
12
12
  return resolveSavedAssetMaterials(asset, workspace.materials);
@@ -32,25 +32,76 @@ export async function exportWorkspaceProjectBundle(workspace, library, projectId
32
32
  record: await library.read(item.asset.collectionId, item.asset.assetId, item.asset.revisionId),
33
33
  });
34
34
  }
35
- const materials = new Map();
35
+ let materials = [];
36
36
  if (profile === 'editable') {
37
+ const roots = new Map();
38
+ const assetRoots = new Set();
39
+ const embedded = new Map();
40
+ const addRoot = (manifest) => {
41
+ const key = `${manifest.materialId}/${manifest.revisionId}`;
42
+ const previous = roots.get(key);
43
+ if (previous && canonicalMaterialJson(previous) !== canonicalMaterialJson(manifest))
44
+ throw new Error('Conflicting project material identity');
45
+ roots.set(key, manifest);
46
+ };
37
47
  for (const dependency of project.materialDependencies) {
38
48
  if (dependency.sha256 !== dependency.revisionId)
39
49
  throw new Error(`Material lock hash mismatch: ${dependency.resourceId}`);
40
- let record;
50
+ let manifest;
41
51
  try {
42
- record = await workspace.materials.read(dependency.resourceId, dependency.revisionId);
52
+ manifest = await workspace.materials.readManifest(dependency.resourceId, dependency.revisionId);
43
53
  }
44
54
  catch (error) {
45
55
  throw new Error(`Locked project material unavailable: ${dependency.resourceId} at ${dependency.revisionId}`, { cause: error });
46
56
  }
47
- materials.set(`${record.manifest.materialId}/${record.manifest.revisionId}`, record);
57
+ addRoot(manifest);
48
58
  }
49
- for (const asset of assets)
50
- for (const record of await assetMaterials(workspace, asset.record))
51
- materials.set(`${record.manifest.materialId}/${record.manifest.revisionId}`, record);
59
+ for (const asset of assets) {
60
+ for (const manifest of dependencyManifests(asset.record)) {
61
+ addRoot(manifest);
62
+ assetRoots.add(`${manifest.materialId}/${manifest.revisionId}`);
63
+ }
64
+ for (const wire of asset.record.materialResources?.records ?? []) {
65
+ const manifest = validateMaterialManifestIdentityAny(wire.manifest);
66
+ const key = `${manifest.materialId}/${manifest.revisionId}`;
67
+ const previous = embedded.get(key);
68
+ if (previous &&
69
+ (canonicalMaterialJson(previous.manifest) !== canonicalMaterialJson(manifest) ||
70
+ canonicalMaterialJson(previous.files) !== canonicalMaterialJson(wire.files)))
71
+ throw new Error('Conflicting embedded material identity');
72
+ embedded.set(key, { manifest, files: wire.files });
73
+ }
74
+ }
75
+ const lockedRead = async (id, revision, read) => {
76
+ try {
77
+ return await read();
78
+ }
79
+ catch (error) {
80
+ throw new Error(`Locked ${assetRoots.has(`${id}/${revision}`) ? 'asset' : 'project'} material unavailable: ${id} at ${revision}`, { cause: error });
81
+ }
82
+ };
83
+ materials = await resolveMaterialRecordClosure([...roots.values()], {
84
+ readManifest: async (id, revision) => embedded.get(`${id}/${revision}`)?.manifest ??
85
+ lockedRead(id, revision, () => workspace.materials.readManifest(id, revision)),
86
+ read: async (id, revision) => {
87
+ const wire = embedded.get(`${id}/${revision}`);
88
+ if (!wire)
89
+ return lockedRead(id, revision, () => workspace.materials.readRecordFiles(id, revision));
90
+ return {
91
+ manifest: wire.manifest,
92
+ files: Object.fromEntries(Object.entries(wire.files).map(([name, value]) => [
93
+ name,
94
+ new Uint8Array(Buffer.from(value, 'base64')),
95
+ ])),
96
+ };
97
+ },
98
+ }, {
99
+ maxRecords: 1024,
100
+ maxBytes: PROJECT_BUNDLE_LIMITS.maxBytes,
101
+ maxPixels: 1024 * MATERIAL_LIBRARY_LIMITS.maxRecordPixels,
102
+ });
52
103
  }
53
- return encodeProjectBundle({ project, assets, materials: [...materials.values()] }, profile);
104
+ return encodeProjectBundle({ project, assets, materials }, profile);
54
105
  }
55
106
  async function retainArchive(workspace, bytes, digest) {
56
107
  const root = resolve(workspace.root);
@@ -117,7 +168,7 @@ export async function importWorkspaceProjectBundle(workspace, library, bytes, op
117
168
  record,
118
169
  ]));
119
170
  for (const record of decoded.materials)
120
- await verifyMaterialRecordV1(record);
171
+ await verifyMaterialRecord(record, { materials: decoded.materials });
121
172
  for (const asset of decoded.assets) {
122
173
  await verifyAssetRecord(asset.record);
123
174
  for (const manifest of dependencyManifests(asset.record)) {
@@ -166,8 +217,14 @@ export async function importWorkspaceProjectBundle(workspace, library, bytes, op
166
217
  // Nothing above performs writes. Verified immutable resources can safely survive a
167
218
  // later disk failure or project-ID conflict; imports never overwrite other revisions.
168
219
  await retainArchive(workspace, snapshot, digest);
169
- for (let i = 0; i < decoded.materials.length; i += 100)
170
- await workspace.materials.import(decoded.materials.slice(i, i + 100));
220
+ const newMaterialContext = decoded.materials.some((record) => record.manifest.schemaVersion === 2);
221
+ for (let i = 0; i < decoded.materials.length; i += 100) {
222
+ const batch = decoded.materials.slice(i, i + 100);
223
+ if (newMaterialContext)
224
+ await workspace.materials.import(batch, { materials: decoded.materials });
225
+ else
226
+ await workspace.materials.import(batch);
227
+ }
171
228
  const records = [...destinationRecords.values()];
172
229
  for (let i = 0; i < records.length; i += 100)
173
230
  await library.import(options.collectionId, records.slice(i, i + 100));
@@ -2,7 +2,7 @@
2
2
  import { z } from 'zod';
3
3
  import { type AssetRecord } from "./assets.js";
4
4
  import { type ProjectRevision } from "./projects.js";
5
- import { type MaterialRecordV1 } from "./material-library.js";
5
+ import { type MaterialRecord } from "./material-library.js";
6
6
  export declare const PROJECT_BUNDLE_LIMITS: Readonly<{
7
7
  maxBytes: number;
8
8
  maxArchiveBytes: number;
@@ -38,7 +38,95 @@ export declare const projectBundleManifestSchema: z.ZodObject<{
38
38
  bytes: z.ZodNumber;
39
39
  }, z.core.$strict>>;
40
40
  }, z.core.$strict>;
41
- export type ProjectBundleManifest = z.infer<typeof projectBundleManifestSchema>;
41
+ export declare const projectBundleManifestV2Schema: z.ZodObject<{
42
+ profile: z.ZodEnum<{
43
+ editable: "editable";
44
+ runtime: "runtime";
45
+ }>;
46
+ editable: z.ZodBoolean;
47
+ resourceClosure: z.ZodEnum<{
48
+ "embedded-runtime-only": "embedded-runtime-only";
49
+ "normalized-maps-and-procedural-recipes": "normalized-maps-and-procedural-recipes";
50
+ }>;
51
+ referenceMediaIncluded: z.ZodLiteral<false>;
52
+ originalAcquisitionFilesIncluded: z.ZodLiteral<false>;
53
+ assets: z.ZodArray<z.ZodObject<{
54
+ inventoryId: z.ZodString;
55
+ collectionId: z.ZodString;
56
+ assetId: z.ZodString;
57
+ revisionId: z.ZodString;
58
+ canonicalGlbSha256: z.ZodString;
59
+ }, z.core.$strict>>;
60
+ materials: z.ZodArray<z.ZodObject<{
61
+ materialId: z.ZodString;
62
+ revisionId: z.ZodString;
63
+ }, z.core.$strict>>;
64
+ files: z.ZodRecord<z.ZodString, z.ZodObject<{
65
+ sha256: z.ZodString;
66
+ bytes: z.ZodNumber;
67
+ }, z.core.$strict>>;
68
+ version: z.ZodLiteral<"kiln.project-bundle.v2">;
69
+ }, z.core.$strict>;
70
+ export declare const projectBundleManifestAnySchema: z.ZodUnion<readonly [z.ZodObject<{
71
+ version: z.ZodLiteral<"kiln.project-bundle.v1">;
72
+ profile: z.ZodEnum<{
73
+ editable: "editable";
74
+ runtime: "runtime";
75
+ }>;
76
+ editable: z.ZodBoolean;
77
+ resourceClosure: z.ZodEnum<{
78
+ "embedded-runtime-only": "embedded-runtime-only";
79
+ "normalized-maps-and-procedural-recipes": "normalized-maps-and-procedural-recipes";
80
+ }>;
81
+ referenceMediaIncluded: z.ZodLiteral<false>;
82
+ originalAcquisitionFilesIncluded: z.ZodLiteral<false>;
83
+ assets: z.ZodArray<z.ZodObject<{
84
+ inventoryId: z.ZodString;
85
+ collectionId: z.ZodString;
86
+ assetId: z.ZodString;
87
+ revisionId: z.ZodString;
88
+ canonicalGlbSha256: z.ZodString;
89
+ }, z.core.$strict>>;
90
+ materials: z.ZodArray<z.ZodObject<{
91
+ materialId: z.ZodString;
92
+ revisionId: z.ZodString;
93
+ }, z.core.$strict>>;
94
+ files: z.ZodRecord<z.ZodString, z.ZodObject<{
95
+ sha256: z.ZodString;
96
+ bytes: z.ZodNumber;
97
+ }, z.core.$strict>>;
98
+ }, z.core.$strict>, z.ZodObject<{
99
+ profile: z.ZodEnum<{
100
+ editable: "editable";
101
+ runtime: "runtime";
102
+ }>;
103
+ editable: z.ZodBoolean;
104
+ resourceClosure: z.ZodEnum<{
105
+ "embedded-runtime-only": "embedded-runtime-only";
106
+ "normalized-maps-and-procedural-recipes": "normalized-maps-and-procedural-recipes";
107
+ }>;
108
+ referenceMediaIncluded: z.ZodLiteral<false>;
109
+ originalAcquisitionFilesIncluded: z.ZodLiteral<false>;
110
+ assets: z.ZodArray<z.ZodObject<{
111
+ inventoryId: z.ZodString;
112
+ collectionId: z.ZodString;
113
+ assetId: z.ZodString;
114
+ revisionId: z.ZodString;
115
+ canonicalGlbSha256: z.ZodString;
116
+ }, z.core.$strict>>;
117
+ materials: z.ZodArray<z.ZodObject<{
118
+ materialId: z.ZodString;
119
+ revisionId: z.ZodString;
120
+ }, z.core.$strict>>;
121
+ files: z.ZodRecord<z.ZodString, z.ZodObject<{
122
+ sha256: z.ZodString;
123
+ bytes: z.ZodNumber;
124
+ }, z.core.$strict>>;
125
+ version: z.ZodLiteral<"kiln.project-bundle.v2">;
126
+ }, z.core.$strict>]>;
127
+ export type ProjectBundleManifestV1 = z.infer<typeof projectBundleManifestSchema>;
128
+ export type ProjectBundleManifestV2 = z.infer<typeof projectBundleManifestV2Schema>;
129
+ export type ProjectBundleManifest = ProjectBundleManifestV1 | ProjectBundleManifestV2;
42
130
  export interface ProjectBundleAsset {
43
131
  inventoryId: string;
44
132
  collectionId: string;
@@ -46,7 +134,7 @@ export interface ProjectBundleAsset {
46
134
  }
47
135
  export interface ProjectBundleInput {
48
136
  project: ProjectRevision;
49
- materials: MaterialRecordV1[];
137
+ materials: MaterialRecord[];
50
138
  assets: ProjectBundleAsset[];
51
139
  }
52
140
  export interface DecodedProjectBundle extends ProjectBundleInput {
@@ -4,7 +4,7 @@ import { zipSync, unzipSync } from 'three/addons/libs/fflate.module.js';
4
4
  import { assetIdSchema, assetManifestSchema, validateRecordShape, validateAssetGlb, } from "./assets.js";
5
5
  import { exportAssetGlb } from "./asset-export.js";
6
6
  import { MAX_PROJECT_REVISIONS, projectRevisionSchema } from "./projects.js";
7
- import { canonicalMaterialJson, materialManifestSchema, validateMaterialRecordShape, } from "./material-library.js";
7
+ import { canonicalMaterialJson, materialManifestSchema, materialManifestAnySchema, materialRecordDependencyPins, validateMaterialRecordShapeAny, } from "./material-library.js";
8
8
  export const PROJECT_BUNDLE_LIMITS = Object.freeze({
9
9
  maxBytes: 256 * 1024 * 1024,
10
10
  maxArchiveBytes: 264 * 1024 * 1024,
@@ -49,6 +49,13 @@ export const projectBundleManifestSchema = z
49
49
  .strict()),
50
50
  })
51
51
  .strict();
52
+ export const projectBundleManifestV2Schema = projectBundleManifestSchema.extend({
53
+ version: z.literal('kiln.project-bundle.v2'),
54
+ });
55
+ export const projectBundleManifestAnySchema = z.union([
56
+ projectBundleManifestSchema,
57
+ projectBundleManifestV2Schema,
58
+ ]);
52
59
  const encoder = new TextEncoder();
53
60
  const decoder = new TextDecoder('utf-8', { fatal: true });
54
61
  const bytesOf = (value) => encoder.encode(JSON.stringify(value));
@@ -77,7 +84,7 @@ async function verifyAsset(record) {
77
84
  throw new Error('Asset file hash mismatch');
78
85
  }
79
86
  async function verifyMaterial(record) {
80
- const manifest = validateMaterialRecordShape(record);
87
+ const manifest = validateMaterialRecordShapeAny(record);
81
88
  const { revisionId, ...payload } = manifest;
82
89
  if ((await projectBundleHash(encoder.encode(canonicalMaterialJson(payload)))) !== revisionId)
83
90
  throw new Error('Material revision hash mismatch');
@@ -100,13 +107,42 @@ function assertAssetSelection(project, entries) {
100
107
  }
101
108
  }
102
109
  function assertMaterialClosure(project, records) {
103
- const identities = records.map((record) => `${record.manifest.materialId}/${record.manifest.revisionId}`);
110
+ const manifests = records.map((record) => validateMaterialRecordShapeAny(record));
111
+ const identities = manifests.map((manifest) => `${manifest.materialId}/${manifest.revisionId}`);
104
112
  if (new Set(identities).size !== identities.length)
105
113
  throw new Error('Duplicate material revision');
106
114
  for (const dependency of project.materialDependencies)
107
115
  if (dependency.sha256 !== dependency.revisionId ||
108
116
  !identities.includes(`${dependency.resourceId}/${dependency.revisionId}`))
109
117
  throw new Error(`Missing locked material: ${dependency.resourceId}`);
118
+ const byKey = new Map(manifests.map((manifest) => [`${manifest.materialId}/${manifest.revisionId}`, manifest]));
119
+ const visited = new Set();
120
+ for (const key of identities) {
121
+ const active = new Set();
122
+ const stack = [{ key, exit: false }];
123
+ while (stack.length) {
124
+ const frame = stack.pop();
125
+ if (frame.exit) {
126
+ active.delete(frame.key);
127
+ visited.add(frame.key);
128
+ continue;
129
+ }
130
+ if (active.has(frame.key))
131
+ throw new Error('Cyclic project material closure');
132
+ if (visited.has(frame.key))
133
+ continue;
134
+ const manifest = byKey.get(frame.key);
135
+ if (!manifest)
136
+ throw new Error('Missing transitive project material');
137
+ active.add(frame.key);
138
+ stack.push({ key: frame.key, exit: true });
139
+ for (const pin of materialRecordDependencyPins(manifest)) {
140
+ if (pin.sha256 !== pin.revisionId)
141
+ throw new Error('Transitive material lock hash mismatch');
142
+ stack.push({ key: `${pin.resourceId}/${pin.revisionId}`, exit: false });
143
+ }
144
+ }
145
+ }
110
146
  }
111
147
  /** Editable includes normalized rebuild resources; runtime intentionally excludes source/recipes. */
112
148
  export async function encodeProjectBundle(input, profile = 'editable') {
@@ -175,8 +211,11 @@ export async function encodeProjectBundle(input, profile = 'editable') {
175
211
  add(prefix + name, bytes);
176
212
  }
177
213
  }
178
- const manifest = projectBundleManifestSchema.parse({
179
- version: 'kiln.project-bundle.v1',
214
+ const version = profile === 'editable' && input.materials.some((record) => record.manifest.schemaVersion === 2)
215
+ ? 'kiln.project-bundle.v2'
216
+ : 'kiln.project-bundle.v1';
217
+ const manifest = projectBundleManifestAnySchema.parse({
218
+ version,
180
219
  profile,
181
220
  editable: profile === 'editable',
182
221
  resourceClosure: profile === 'editable' ? 'normalized-maps-and-procedural-recipes' : 'embedded-runtime-only',
@@ -232,7 +271,7 @@ export async function decodeProjectBundle(bytes) {
232
271
  }
233
272
  return value;
234
273
  };
235
- const manifest = projectBundleManifestSchema.parse(json('bundle.json', PROJECT_BUNDLE_LIMITS.maxManifestBytes));
274
+ const manifest = projectBundleManifestAnySchema.parse(json('bundle.json', PROJECT_BUNDLE_LIMITS.maxManifestBytes));
236
275
  if (manifest.editable !== (manifest.profile === 'editable') ||
237
276
  manifest.resourceClosure !==
238
277
  (manifest.editable ? 'normalized-maps-and-procedural-recipes' : 'embedded-runtime-only'))
@@ -298,7 +337,9 @@ export async function decodeProjectBundle(bytes) {
298
337
  throw new Error('Runtime profile contains editable material records');
299
338
  for (const identity of manifest.materials) {
300
339
  const prefix = materialPrefix(identity);
301
- const materialManifest = materialManifestSchema.parse(json(`${prefix}manifest.json`, 256 * 1024));
340
+ const materialManifest = (manifest.version === 'kiln.project-bundle.v1'
341
+ ? materialManifestSchema
342
+ : materialManifestAnySchema).parse(json(`${prefix}manifest.json`, 256 * 1024));
302
343
  used.add(`${prefix}manifest.json`);
303
344
  if (materialManifest.materialId !== identity.materialId ||
304
345
  materialManifest.revisionId !== identity.revisionId)
@@ -0,0 +1,21 @@
1
+ import type { QaFinding } from "./types.js";
2
+ export declare const ATLAS_QA_LIMITS: Readonly<{
3
+ nodes: 4096;
4
+ depth: 128;
5
+ textures: 128;
6
+ bindings: 1024;
7
+ uvVisits: 1048576;
8
+ findings: 64;
9
+ metadataBytes: 4194304;
10
+ }>;
11
+ export interface AtlasStructuralInspectionV1 {
12
+ schemaVersion: 1;
13
+ findings: QaFinding[];
14
+ coverage: 'none' | 'complete' | 'partial';
15
+ texturesChecked: number;
16
+ bindingsChecked: number;
17
+ uvVerticesChecked: number;
18
+ nodesChecked: number;
19
+ truncated: boolean;
20
+ }
21
+ export declare function inspectAtlasScene(root: unknown): AtlasStructuralInspectionV1;