@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
@@ -49,3 +49,13 @@ main{grid-template-columns:216px minmax(0,1fr);min-height:calc(100vh - 80px)}
49
49
  @media(max-width:800px){header{height:70px;padding:0 18px}main{display:block}.workspace-sidebar{height:auto;position:static;padding:12px 15px}#workspace-nav{display:flex;gap:5px}#workspace-nav button{white-space:nowrap;font-size:12px;flex:1;justify-content:center;padding:11px 7px}#workspace-nav button span{display:none}#collections{padding-top:6px}#collections:empty{padding:0}.workspace-panel{padding:25px 18px}.workspace-panel h1{font-size:31px}.project-layout{grid-template-columns:1fr;margin-top:23px}.project-list{display:flex;overflow:auto}.project-list button{min-width:180px}.review-layout{grid-template-columns:1fr}.timeline{display:flex;max-height:190px}.timeline-item{min-width:220px;max-width:260px}.timeline .empty{padding:18px}.panel-title{padding:12px 17px}.profile-grid{grid-template-columns:1fr}.review-viewports.comparing{grid-template-columns:1fr}.heading{gap:15px}.heading>button{font-size:12px;white-space:nowrap}.live-toolbar{align-items:flex-start}.live-toolbar .inline-field{flex-direction:column;align-items:flex-start}.local-dot{display:none}.live-stage{height:380px}}
50
50
  /* Manual LOD review: whole-asset shortcuts plus independent part choices. */
51
51
  .lod-controls{margin:12px 0;font-size:12px}.lod-total{margin:8px 0;color:var(--ink)}.lod-parts{padding:10px 12px;border:1px solid var(--line);border-radius:8px}.lod-parts summary{cursor:pointer;font-weight:600}.lod-parts .subtle{line-height:1.5}.lod-part-fields{display:grid;gap:10px;max-height:280px;overflow:auto;padding:4px 2px}.lod-part-fields .inline-field{justify-content:space-between;flex-wrap:wrap}.lod-part-fields select{max-width:100%}.comparison-lod{padding:12px 16px}.comparison-lod:has(.inline-field[hidden]){display:none}
52
+ .material-card[data-selected-material="true"]{border-color:var(--accent)}
53
+ .material-map{object-fit:contain;image-rendering:pixelated}
54
+ .material-map-inspection{margin:14px 0}.material-inspection{min-width:0;overflow-wrap:anywhere}
55
+ .material-inspection-controls{display:flex;gap:10px;flex-wrap:wrap;margin:14px 0}.material-inspection-controls .inline-field{flex-direction:column;align-items:flex-start}.material-inspection-controls select{max-width:100%;font-size:11px;padding:7px}
56
+ .material-pixel-frame{max-height:360px;overflow:auto;border:1px solid var(--line);border-radius:5px;background:#202020}
57
+ .material-pixel-frame[data-backdrop="checker"]{background-color:#d0d0d0;background-image:conic-gradient(#7a7a7a 25%,#d0d0d0 0 50%,#7a7a7a 0 75%,#d0d0d0 0);background-size:16px 16px}
58
+ .material-pixel-frame[data-backdrop="light"]{background:#f2f2f2}.material-pixel-frame[data-backdrop="dark"]{background:#151515}
59
+ .material-pixel-sheet{position:relative;flex:none;min-width:0}.material-pixel-image{display:block!important;width:100%!important;height:100%!important;max-height:none!important;object-fit:fill!important;image-rendering:pixelated;border-radius:0!important}
60
+ .material-region-overlay{position:absolute;inset:0;pointer-events:none}.material-region-box{position:absolute;border:1px solid #ffce37;overflow:visible}.material-region-box>span{position:absolute;left:0;top:0;font-size:10px;line-height:1.2;background:#151515df;color:#ffefaf;white-space:nowrap;max-width:200px;overflow:hidden;text-overflow:ellipsis}.material-region-box.selected{border:2px solid #fff;box-shadow:0 0 0 2px #111}.material-region-list{display:grid;gap:6px;margin:12px 0}.material-region-list h4{font-size:11px;margin:5px 0}.material-region-list button{text-align:left;font-size:10px;padding:7px;overflow-wrap:anywhere}.material-region-list button[aria-pressed="true"]{border-color:var(--accent)}
61
+ .material-inspection dl{font-size:11px}.material-inspection dt{color:var(--dim);margin-top:7px}.material-inspection dd{margin:0}.live-material-inspection{grid-column:1/-1}.live-material-inspection details{margin-top:12px}.asset-material-inputs{font-size:11px;margin:15px 0}.asset-material-inputs h3{font-size:12px}.asset-material-inputs a{color:var(--accent)}
@@ -0,0 +1,23 @@
1
+ # Atlas provenance in delivered GLBs
2
+
3
+ `src/atlas-provenance.ts` is the internal owner of atlas metadata validation. It uses the existing atlas canonicalizer and manifest validator. It has no Node dependency and its live-scene readers are available to image-free structural QA. These internal helpers are not SDK authoring APIs.
4
+
5
+ The default glTF scene owns `extras.kilnAtlasProvenance`:
6
+
7
+ ```json
8
+ {"version":"kiln.atlas-provenance.v1","atlases":[{"id":"sha256:<manifest hash>","spec":{},"manifest":{}}]}
9
+ ```
10
+
11
+ Each entry contains one complete canonical recipe and validated manifest. Entries deduplicate by exact manifest identity, with a content conflict check. Recipes are never repeated per mesh. Editable source and saved material records remain the authoring owners.
12
+
13
+ Each material-slot `TextureInfo` owns `extras.kilnAtlasReference` with `version`, `id`, `effectiveSampler` (`minFilter`, `magFilter`, `wrapS`, `wrapT` as glTF values), `finalMime`, and `finalImageSha256`. Source collection creates only the version/id pair; final export fills actual sampler and encoded-image identities. The reference belongs to a texture use because one image can serve multiple samplers. glTF Transform preserves TextureInfo extras and does not preserve extras on glTF texture binding objects. Image extras would incorrectly conflate those uses.
14
+
15
+ A primitive using a named remap owns `extras.kilnAtlasBinding`: `version`, `atlasId`, `region`, `inputOrigin`, and `mode`. Export checks the complete source geometry stamp against the primary atlas, including hashes, rectangles, scale and offset. Reimport derives that full geometry stamp from the validated manifest and compact binding. A geometry-wide binding requires its exact primary atlas among the primitive material slots. Companion color/data maps may have different manifest hashes with the same region layout. Manual full-sheet atlas use carries texture references without an inferred named binding. Ordinary textures carry no atlas metadata.
16
+
17
+ Admission allows 128 unique identities, at most 256 KiB per JSON entry, a 4 MiB aggregate table including its envelope/separators, and 1,024 source binding observations. Exported documents independently bound slot references and primitive bindings to 1,024 each. Source graph admission precedes manifest cloning and scene export copies. Distinct atlas texture objects conservatively charge their full RGBA output toward a 64 MiB bake decoded-pixel budget. Encoded-image hashing admits at most 16 MiB per image and 64 MiB in aggregate before hashing. These are metadata/buffer-work bounds, not an operating-system or native codec RSS guarantee. Existing scene traversal remains proportional to the scene.
18
+
19
+ Before first PNG encoding, baking verifies raw RGBA dimensions and SHA-256 against the compiled manifest. Already encoded source maps receive bounded PNG dimension/decode verification. Editing pixels while retaining an atlas stamp fails with a stale-provenance diagnostic. A validated GLB loader establishes a private WeakMap identity for exporter-derived delivery bytes; authored flags cannot establish this identity. Re-export checks those exact bytes against the validated delivery reference rather than comparing legitimate converted normal pixels to the original compiler pixels. The source recipe/pixel identity and final encoded-image identity remain separate. No GLB contains its own final artifact hash; that hash remains in external baked provenance.
20
+
21
+ Both exporters write the same table, references and compact bindings. The optimizer validates input lineage and refreshes the final image identities after its transforms. Composition admits and merges the tables before copying source graphs and deleting their scenes. Import validates final bytes and samplers, never repairs a tampered reference. External transforms that rewrite images must update delivery references through the validated engine export path; retaining old image hashes makes the transformed artifact fail import validation.
22
+
23
+ Structural metadata proves identities, named selectors and remap formulas. It does not prove an author's intended corner orientation after arbitrary within-domain UV edits, visual quality, or inferred physical density. Generic author `kiln*` userData remains excluded; only this owner's validated engine metadata is explicitly promoted.
@@ -57,6 +57,28 @@ partitions are rejected by the shared preflight. Private `userData` is excluded;
57
57
  semantic extras and bounded review clips retain their existing policy. Export copies keep caller
58
58
  texture sources and skeleton references intact.
59
59
 
60
+ Both converters preserve standard magnification/minification filters and S/T wrap
61
+ modes on supported texture bindings, including different samplers sharing image
62
+ bytes. Kiln's loader and procedural texture defaults remain repeat wrapping,
63
+ linear magnification and trilinear minification. The default converter now writes
64
+ these choices explicitly; regenerated GLBs can therefore have different bytes
65
+ and material fingerprints from older exports. Existing saved revisions are
66
+ unchanged. Mipmap generation itself is a consumer policy, not a glTF sampler flag.
67
+
68
+ Already encoded PNG/JPEG images on direct, unflipped texture bindings retain their
69
+ original bytes. This matters for cutout RGB padding under zero alpha and for exact
70
+ partial-alpha color: canvas conversion can erase or round those channels. Derived
71
+ channel maps and explicitly flipped images still require conversion. Raw byte maps
72
+ used for metallic/roughness packing or normal-channel inversion are adapted on the
73
+ export copy; caller images and texture references remain unchanged. For cutout atlas
74
+ delivery, keep the canonical image orientation with `flipY: false`; pixel-exact
75
+ transparent RGB through a requested vertical image flip is not qualified.
76
+
77
+ If the optional native canvas binding cannot load, textured headless export reports
78
+ the dependency and recovery steps. Reinstall with optional dependencies enabled and
79
+ check the binding in the same Node runtime and platform as the Kiln host. It does not
80
+ silently switch converters. Flat-material exports do not require that binding.
81
+
60
82
  This describes serialization capability. It does not automatically extend all authoring helpers,
61
83
  QA rules, preview materials or optimization algorithms. In particular, skin/morph deformation,
62
84
  complex texture transforms, material extensions in every review path, and runtime codec support
@@ -65,6 +87,11 @@ converter rather than bypassing the triangle pipeline's metadata and geometry po
65
87
 
66
88
  ## Qualify a consumer
67
89
 
90
+ Both converters reject used `BackSide` materials because glTF has no back-only
91
+ material flag. See the [portable sidedness contract](export-profiles.md#material-sidedness-at-export)
92
+ for explicit inward geometry and deliberate two-sided sheets. This prevents silent
93
+ front-face substitution; it does not change the default exporter or existing GLBs.
94
+
68
95
  Keep a canonical GLB and name the actual engine, importer, version and render pipeline in results.
69
96
  For example, Unity 6 Built-in RP with glTFast is a different target from Unity URP or an FBX workflow.
70
97
  Tests should check hierarchy, world-space positions, part names, material assignments, texture pixels,
@@ -44,6 +44,26 @@ unsupported UV transforms and destination feature support still need qualificati
44
44
  Neither setting automatically enables mesh merging, geometry simplification, Draco,
45
45
  Meshopt or KTX2 compression. Those require separate choices and validation.
46
46
 
47
+ ## Material sidedness at export
48
+
49
+ glTF represents front-only or double-sided surfaces. Both Kiln converters preserve
50
+ `FrontSide` and `DoubleSide`. A used `BackSide` material now stops a source build
51
+ with `EXPORT_MATERIAL_SIDE_UNSUPPORTED`. The direct scene API names the part and
52
+ material; CLI/MCP carry closed repair advice without authored identifiers across
53
+ the evaluator boundary. Use `kiln_source` with query `BackSide` to locate the
54
+ authored material. This applies
55
+ in both `warn` and `strict` geometry modes and before texture baking or conversion;
56
+ unused material-array slots do not block export. Existing saved GLBs remain readable
57
+ and exporting their exact bytes does not rebuild them.
58
+
59
+ For a one-sided interior surface, author triangles facing inward and use
60
+ `FrontSide`. For a leaf or other deliberate sheet visible from either side, use
61
+ `DoubleSide`. Neither choice supplies thickness or missing caps. Do not flip only
62
+ normals to repair winding. Automatic back-only conversion is not provided: winding,
63
+ normal/tangent basis, groups, shared geometry and deformation data must remain
64
+ consistent. A destination shader override is an explicit runtime choice, separate
65
+ from the portable asset.
66
+
47
67
  ## CLI
48
68
 
49
69
  From an asset workspace, using the IDs returned by `save` or `assets`:
@@ -35,8 +35,9 @@ node kiln.mjs material get RETURNED_MATERIAL_ID RETURNED_REVISION
35
35
  node kiln.mjs material export RETURNED_MATERIAL_ID RETURNED_REVISION --out wood-payload.json
36
36
  ```
37
37
 
38
- Copy the returned `portableSpec` into authored source and use
39
- `compilePortableMaterialSpecV2(portableSpec)` with the exact dependency pin.
38
+ Copy the returned `portableSpec` into authored source with the exact dependency
39
+ pin. Use `compilePortableMaterialSpecV2` for `portableSpec.schemaVersion === 2`
40
+ and `compilePortableMaterialSpecV3` for version 3. Existing presets return V2.
40
41
  Custom draft creation uses CLI `material procedural --file draft.json`, corresponding
41
42
  to MCP `create-procedural`. These paths need no imports from internal `dist/` files
42
43
  or extra TypeScript loader. The public host APIs below are for a configured TypeScript
@@ -44,7 +45,10 @@ toolchain; ordinary Node cannot directly execute TypeScript inside `node_modules
44
45
 
45
46
  `list` returns compact summaries and accepts a `tag`. `get` requires `materialId` and `revisionId`
46
47
  and returns provenance plus a code-ready `portableSpec`. `create-procedural` accepts an editable
47
- draft using the same bounded layer recipes as the existing procedural compiler. An optional
48
+ draft using the same bounded layer recipes as the existing procedural compiler. Opt-in
49
+ drafts with `schemaVersion:2` also support RGBA recipes, named atlases and explicit
50
+ per-map samplers; see the [standalone texture workflow](procedural-surfaces-v3.md#standalone-saved-materials-and-atlases).
51
+ An optional
48
52
  `derive: {"kind":"normal-from-height","strength":2}` on a normal map converts the recipe's
49
53
  height pixels through the existing wrapped normal derivation. `import` accepts a complete
50
54
  normalized payload. None of these operations acquires source images from a provider.
@@ -66,8 +70,8 @@ Pin the returned material ID and revision hash before using its resource IDs. A
66
70
  optional: pass a pin array as MCP `materialDependencies` or CLI `--materials pins.json` for
67
71
  an individual operation, or put the array in a project's `materialDependencies` for shared use.
68
72
  The pin's `resourceId` is the material ID; its `revisionId` and `sha256` both equal the
69
- manifest's immutable `revisionId`. `compilePortableMaterialSpecV2(portableSpec)` then creates the
70
- material inside authored source. The host supplies complete map bytes through `materialResources`
73
+ manifest's immutable `revisionId`. The compiler matching `portableSpec.schemaVersion`
74
+ creates the material inside authored source. The host supplies complete map bytes through `materialResources`
71
75
  before build-cache lookup, including when evaluation uses the default subprocess.
72
76
 
73
77
  Editable standalone asset ZIPs carry the saved dependency closure in `materials.kiln.json`;
@@ -0,0 +1,276 @@
1
+ # Procedural RGBA surfaces V3
2
+
3
+ `proceduralTexture({ schemaVersion: 3, ... })` creates rectangular RGBA pixels
4
+ through the same authored-source helper as V1/V2. The standalone compiler is
5
+ `compileProceduralTextureSpecV3` from the primitives SDK. Its contract helpers
6
+ are `canonicalizeProceduralTextureSpecV3`, `canonicalProceduralTextureJsonV3`
7
+ and `hashProceduralTextureSpecV3`.
8
+
9
+ V1 (absent or `1` version) and V2 remain on their existing compiler, square
10
+ power-of-two dimensions, opaque output, canonical JSON and hashes. An explicitly
11
+ undefined version retains historical V1 behavior; V3 itself accepts strict JSON,
12
+ so undefined fields are invalid. Unknown versions fail before dispatch. Existing
13
+ portable material V2 references remain V2. New portable V3 material references
14
+ and V2 saved material records are opt-in contracts described below.
15
+
16
+ ## Contract
17
+
18
+ ```ts
19
+ {
20
+ schemaVersion: 3,
21
+ width: 4..1024, height: 4..1024, // integers, need not be powers of two
22
+ usage?: 'albedo' | 'emissive' | 'normal' | 'roughness' | 'metalness'
23
+ | 'metallicRoughness' | 'occlusion', // default albedo
24
+ name?: string, // 1..80 visible characters
25
+ background?: [r,g,b,a], // bytes 0..255, default [0,0,0,0]
26
+ operations: OperationV3[], // ordered, zero to 64 entries
27
+ palette?: { colors: RGB[], dither?: 'none' | 'bayer4' }
28
+ }
29
+ ```
30
+
31
+ RGBA channels are straight bytes. RGB is authored encoded color for albedo and
32
+ emissive; other usages retain linear data. Transparent RGBA input canonicalizes
33
+ to `[0,0,0,0]`, and every output pixel with zero alpha has zero RGB. Palette
34
+ mapping and stamps support only albedo/emissive; they reject all data usages.
35
+ Shapes and masks operate on declared bytes without interpreting data channels.
36
+
37
+ Operations have these exact fields:
38
+
39
+ | `op` | Fields |
40
+ | --- | --- |
41
+ | `paint` | `shape`, `rgba`, optional byte `opacity` (default 255) |
42
+ | `mask` | `shape`, optional byte `alpha` (default 255), optional boolean `invert` (default false) |
43
+ | `stamps` | `shape`, `rgba`, `count`, uint32 `seed`, `area`, optional byte `opacity`, optional boolean `periodic` (default false) |
44
+ | `legacy-v2` | `spec` with explicit schemaVersion 2, optional byte `alpha` and byte `opacity` (both default 255) |
45
+
46
+ V3 opacity is a byte, unlike V2's fractional opacity. The legacy layer delegates
47
+ to the existing V2 compiler, including its six operators and first-layer rules.
48
+ Its square size and usage must match the V3 surface; its output RGB is composited
49
+ using the explicitly selected V3 alpha and opacity. It does not reinterpret V2
50
+ blend modes or silently migrate a V2 recipe into V3 operations.
51
+
52
+ ## Coverage and compositing
53
+
54
+ Coordinates describe integer pixel edges; a pixel `(x,y)` samples `(x+0.5,y+0.5)`.
55
+ Shapes clip to the output extent. There is no antialiasing or fractional shape
56
+ coverage in this compiler version.
57
+
58
+ | Shape `kind` | Fields and coverage |
59
+ | --- | --- |
60
+ | `rectangle` | `x,y,width,height`; left/top inclusive, right/bottom exclusive |
61
+ | `ellipse` | `x,y,width,height`; inscribed ellipse, boundary inclusive |
62
+ | `polygon` | `points:[[x,y],...]`; even-odd fill, boundary inclusive, 3..64 vertices; self-intersections use that same fill rule |
63
+ | `line` | `from:[x,y],to:[x,y],width`; capsule with inclusive round caps; coincident endpoints create a disk |
64
+
65
+ Coordinates are integers -4096..4096. Shape widths/heights and line width are
66
+ integers 1..2048. A one-pixel line on an integer edge can cover centers on both
67
+ sides of the edge; width measures continuous distance, not a promised count of
68
+ covered columns. Polygon degeneracies are deterministic rather than repaired.
69
+
70
+ `paint` uses source-over. Define `round(n/d) = floor(n/d + 0.5)`. For source alpha
71
+ `a`, operation opacity `o` and destination alpha `d`:
72
+
73
+ ```text
74
+ s = round(a * o / 255)
75
+ N = s * 255 + d * (255 - s)
76
+ output alpha = round(N / 255)
77
+ output channel = round((source channel * s * 255
78
+ + destination channel * d * (255 - s)) / N)
79
+ ```
80
+
81
+ When `s=0`, painting is inert. RGB division uses the unrounded alpha numerator,
82
+ then alpha and RGB are stored as bytes after each operation. This blends authored
83
+ byte values; it does not linearize encoded colors or introduce floating opacity.
84
+
85
+ `mask` multiplies destination alpha by the mask byte and rounds half-up. Outside
86
+ the covered shape it clears alpha. With `invert:true`, outside retains the mask
87
+ alpha factor and inside clears alpha. It preserves destination RGB while alpha
88
+ remains positive and clears RGB when alpha becomes zero. A mask affects only
89
+ pixels already painted; subsequent paint can add pixels again.
90
+
91
+ ## Palettes and periodic stamps
92
+
93
+ Palette mapping runs once after all operations. `colors` is an ordered array of
94
+ 1..32 RGB integers 0..0xffffff. The nearest color minimizes squared distance in
95
+ encoded RGB bytes; a tie selects the first array entry. Alpha is preserved and
96
+ transparent pixels stay black. `dither:'none'` is the default.
97
+
98
+ `bayer4` repeats this fixed matrix from image origin `(0,0)`:
99
+
100
+ ```text
101
+ 0 8 2 10
102
+ 12 4 14 6
103
+ 3 11 1 9
104
+ 15 7 13 5
105
+ ```
106
+
107
+ Before nearest-color selection, each RGB channel receives integer offset
108
+ `(2 * matrixEntry - 15) * 2`, clipped to 0..255. Palette membership is therefore
109
+ exact, and dither does not add alpha variation or random noise.
110
+
111
+ Stamp `area` is `{x,y,width,height}`, with the same rectangle coordinate bounds.
112
+ Each stamp translates its local shape by two integer offsets within that area.
113
+ The PRNG starts from `(seed ^ 0xa3c59ac3) >>> 0`, substitutes fixed state
114
+ `0x6d2b79f5` if that value is zero, performs Xorshift32 with shifts 13,17,5,
115
+ and consumes X then Y in operation order. Offsets use unsigned state
116
+ modulo area width/height. Counts are 1..256. Seeds use the full unsigned 32-bit
117
+ range, including zero. No clock or random global is involved.
118
+
119
+ `periodic:true` explicitly wraps the translated shape's bounding origin into one
120
+ surface period, then clips copies translated by -period, 0 and +period on each
121
+ axis. Its shape bounds must fit one period on each axis. Each periodic stamp
122
+ counts as nine expanded copies, with at most 256 expanded copies across the
123
+ recipe. Ordinary stamps clip at edges. Periodic placement does not establish
124
+ that other operations tile; inspect repeat neighbors and the resulting patch.
125
+
126
+ ## Admission and identity
127
+
128
+ Validation inspects every object and array data descriptor before field reads.
129
+ Custom prototypes, accessors, symbols, holes, non-index array fields, forbidden
130
+ prototype keys, functions, undefined, nonfinite numbers and unknown schema
131
+ fields are rejected. Admission precedes the output pixel allocation.
132
+
133
+ The limits are 1,048,576 output texels, 64 operations, 64 polygon vertices,
134
+ 256 expanded stamps and 32 palette colors. JSON preflight admits at most
135
+ 16,384 nodes and depth 10, at most 1024 fields per container, and at most
136
+ 262,144 characters of canonical JSON. The only arbitrary text is an 80-character
137
+ name, so this also bounds UTF-8 recipe storage with a small fixed overhead.
138
+
139
+ The conservative work ceiling is 64,000,000 units. It includes output
140
+ initialization, clipped shape tests (each polygon edge counts), full mask scans,
141
+ per-copy stamp bounds, palette comparisons and legacy pattern/noise work. V3
142
+ transient pixel buffers stay below 64MiB. The compiler creates one RGBA output;
143
+ legacy layers temporarily create matching RGBA outputs and palette metadata is
144
+ bounded. These limits do not raise legacy or portable-material budgets.
145
+
146
+ The compiler ID is `kiln.procedural-rgba.v3.1`. V3 recipe hashes are SHA-256 of
147
+ `compilerId + '\n' + canonicalJson`; property order and omitted defaults produce
148
+ the same canonical result. A changed pixel algorithm requires a new compiler ID.
149
+ V3 metadata records that ID, canonical JSON and recipe hash. V2's hashes and
150
+ material-record compiler ID `kiln.procedural-texture.v2` remain unchanged.
151
+
152
+ Failures use closed evaluator diagnostic `PROCEDURAL_TEXTURE_V3_CONTRACT`;
153
+ direct SDK calls also include the rejected field in the exception text.
154
+
155
+ ## Authored-source example
156
+
157
+ ```js
158
+ function build() {
159
+ const root = createRoot('CutoutStudy');
160
+ const texture = proceduralTexture({
161
+ schemaVersion: 3, width: 16, height: 32, name: 'LeafStudy',
162
+ operations: [
163
+ { op: 'paint', shape: { kind: 'ellipse', x: 3, y: 2, width: 10, height: 28 },
164
+ rgba: [66, 100, 42, 255] },
165
+ { op: 'paint', shape: { kind: 'line', from: [8, 4], to: [8, 29], width: 1 },
166
+ rgba: [118, 132, 60, 255] }
167
+ ],
168
+ palette: { colors: [0x42642a, 0x76843c] }
169
+ });
170
+ const material = pbrMaterial({ albedo: texture, alphaMode: 'mask',
171
+ alphaCutoff: 0.5, doubleSided: true, roughness: 1 });
172
+ createPart('Card', planeGeo(0.5, 1), material, { parent: root });
173
+ return root;
174
+ }
175
+ ```
176
+
177
+ Textures keep existing repeat/linear/mipmap defaults. Sampling choices remain
178
+ explicit Three.js texture settings; creating V3 does not select a pixel-art
179
+ policy. The material's alpha mode and sidedness are separate from pixel alpha.
180
+
181
+ ## Standalone saved materials and atlases
182
+
183
+ No mesh or project is required to create or save a procedural texture. The
184
+ existing `kiln_material` action `create-procedural`, named operation
185
+ `kiln_material_create`, and `kiln material procedural --file draft.json` share
186
+ one JSON authoring boundary. An unversioned layered material draft retains its
187
+ V1 record format and existing defaults. An opt-in material draft with
188
+ `schemaVersion:2` accepts V2 layer recipes, V3 RGBA recipes, or named atlases and
189
+ saves a V2 material record. This material-record version is distinct from a
190
+ texture recipe's `schemaVersion`.
191
+
192
+ Each new map supplies exactly one `procedural` or `atlas` recipe, its map slot,
193
+ source provenance, transforms, and an explicit sampler:
194
+
195
+ ```json
196
+ {
197
+ "minFilter": "nearest",
198
+ "magFilter": "nearest",
199
+ "wrapS": "clamp-to-edge",
200
+ "wrapT": "clamp-to-edge"
201
+ }
202
+ ```
203
+
204
+ This example is a choice for a pixel texture, not a global engine default.
205
+ Minification also supports `linear`, `nearest-mipmap-nearest`,
206
+ `nearest-mipmap-linear`, `linear-mipmap-nearest`, and `linear-mipmap-linear`.
207
+ Wrapping supports `repeat`, `mirrored-repeat`, and `clamp-to-edge`.
208
+ Evaluate minification in the consuming scene; nearest magnification alone does
209
+ not establish acceptable motion, distant cutouts, or atlas gutter behavior.
210
+
211
+ Read `shape:material-draft` and `shape:material-import` with Discovery for the
212
+ complete runtime-validated fields. Sources keep explicit creator and license
213
+ records. The JSON authoring surface accepts no file paths, URLs to fetch, or
214
+ raw image bytes. Existing external image records enter through verified import.
215
+
216
+ `textureAtlas(spec)` compiles named, fixed pixel rectangles. The atlas has
217
+ `schemaVersion:1`, width, height, usage, an optional sampler, and regions.
218
+ Coordinates are integer pixels from the top-left. Each region has a stable
219
+ lowercase `name`, a `content` rectangle, a `source`, and optional crop, gutter,
220
+ and edge mode. Default gutters are two pixels; padded allocations must fit
221
+ without overlap. Sources must already match the requested crop size: this
222
+ operation does not pack, resize, rotate, or resample images.
223
+
224
+ A source is either `{kind:'procedural',spec:<V3 recipe>}` or
225
+ `{kind:'resource',resourceId:<exact map id>,pin:{resourceId:<material id>,
226
+ revisionId:<revision hash>,sha256:<same revision hash>}}`. Resource IDs identify
227
+ the saved map, while the pin identifies its immutable material revision.
228
+ Resolution uses only the explicitly injected material library or evaluator
229
+ dependencies, never nearby projects or network requests. The shared dependency
230
+ walker admits the entire metadata graph before loading source PNGs.
231
+
232
+ `copy-edge` copies edge RGBA into the gutter. `mask-rgb-dilate` keeps gutter alpha
233
+ zero and fills RGB from the nearest positive-alpha source texel, with a stable
234
+ tie break. This reduces dark cutout fringes but does not guarantee safe mipmaps
235
+ for every region size or viewing distance.
236
+
237
+ Use `remapUVToAtlas(geometry, atlas, regionName, {inputOrigin,mode})` to map unit
238
+ UVs into a named region on an owned geometry clone. `inputOrigin` is explicit
239
+ `top-left` or `bottom-left`; `mode` is `content-boundary` or `texel-center`. Rich attributes,
240
+ morphs and skin data are retained according to the UV helper's contract. A
241
+ region binding describes that geometry's region; it does not infer independent
242
+ region assignments for material groups.
243
+
244
+ Saved atlas maps retain the canonical recipe, named-region/source manifest,
245
+ compiler identity, encoded-image hash, decoded pixel hash and exact source
246
+ revision pins. `kiln material export` includes the complete verified material
247
+ closure; importing it into an offline workspace restores those revisions.
248
+ Old-only payloads retain schemaVersion 1. Mixed or new records use schemaVersion
249
+ 2 without rewriting old identities. Projects remain optional and pin exact
250
+ saved revisions when explicitly selected.
251
+
252
+ ## Atlas delivery and inspection
253
+
254
+ Both GLB converters retain a bounded `kilnAtlasProvenance` table in scene extras,
255
+ an exact `kilnAtlasReference` on each material texture use, and a compact
256
+ `kilnAtlasBinding` on primitives created from a named remap. These optional extras
257
+ describe Kiln lineage; ordinary glTF textures, UVs and samplers still render in
258
+ consumers that ignore them. Manual full-sheet use needs no named binding.
259
+ The [delivery metadata contract](atlas-provenance.md) specifies the fields,
260
+ validation and bounds.
261
+
262
+ The source manifest identifies compiled RGBA pixels and recipe inputs. The
263
+ delivery reference separately identifies final encoded image bytes and the
264
+ effective sampler for that use. Conversion may legitimately alter pixels, such
265
+ as an explicit normal-channel conversion, so these identities are distinct.
266
+ Import validates the recorded delivery identity; it never silently replaces a
267
+ stale hash. Editing compiled atlas pixels requires recompilation or removal of
268
+ the atlas metadata to make an ordinary manually authored texture.
269
+
270
+ Structural QA checks declared regions, referenced UVs, slots and sampling without
271
+ decoding images. Quantized UV storage has a bounded precision allowance. Work
272
+ limits or unavailable dimensions produce partial coverage; a valid UV domain
273
+ does not prove the intended orientation or visual quality. Inspect exact saved
274
+ maps in Materials, or follow the material links from Library and Live Review.
275
+ Use a material-faithful rendered view and the destination scene for cutout,
276
+ lighting and motion decisions.
@@ -29,7 +29,7 @@ Authoring commands accept `--project` and `--project-revision`. MCP tools use `p
29
29
  node kiln.mjs render asset.kiln.js --no-project --materials pins.json --out asset.glb
30
30
  ```
31
31
 
32
- `pins.json` contains an array of exact `{resourceId, revisionId, sha256}` material dependencies. MCP accepts the same array as `materialDependencies`. These per-call pins work independently of project membership. With a project, additional pins can extend its lock, but conflicting revisions for the same resource are rejected. Copy returned identities rather than constructing them, and preserve the selected dependencies on subsequent calls that evaluate the source.
32
+ `pins.json` contains an array of exact `{resourceId, revisionId, sha256}` material dependencies. MCP accepts the same array as `materialDependencies`. These per-call pins work independently of project membership. With a project, additional pins extend its lock and an explicit pin replaces the project pin for the same resource for that invocation. The saved project revision remains unchanged. Copy returned identities rather than constructing them, and preserve the selected dependencies on subsequent calls that evaluate the source.
33
33
 
34
34
  ## Materials and coherent art direction
35
35
 
package/docs/rendering.md CHANGED
@@ -27,6 +27,47 @@ safety. See `kiln animation --help` for the complete options.
27
27
 
28
28
  CPU views show silhouette, orientation, proportion, and contact. They do not reproduce the asset's PBR materials. Translucent (opacity below 1) surfaces are composited after opaque geometry, farthest first, so parts behind glazing stay visible whatever order the scene lists them in. Use GPU views to review textures, roughness, metalness, and normal relief. GPU output can vary by device and driver.
29
29
 
30
+ GLB review reconstructs skins with up to four joint influences per vertex,
31
+ including normalized weights and inverse bind matrices. CPU triangles and pose
32
+ bounds follow the sampled joint pose rather than the undeformed position buffer.
33
+ Unsupported additional influence sets produce an explicit error. GLB review
34
+ also reconstructs position/normal morph deltas, node-over-mesh default weights,
35
+ and STEP, LINEAR or CUBICSPLINE weight animation before applying skin transforms.
36
+ Missing target attributes contribute zero displacement. Other morph semantics
37
+ retain `GLB_FLAT_MORPH_TARGETS_UNSUPPORTED`; they are not qualified by this path.
38
+ Expanded morph attributes are bounded to 64 MiB per review scene, including
39
+ zero-filled attributes. Weight tracks use the review limits of 4,096 keys and
40
+ 16,384 scalar values. Invalid counts and nonfinite weights/deltas are rejected.
41
+ Morph primitive children receive unique binding-safe names beneath the original
42
+ named node; use that parent for a stable authored-part selection. Copies have
43
+ independent morph influences. Loop-closure evidence remains transform-only and
44
+ reports weight tracks as unassessed rather than labeling them as scale tracks.
45
+ Isolated, two-sided and composed-scene review
46
+ copies own their skeletons and preserve required hidden joint ancestors as
47
+ transforms without drawing their geometry. Include all referenced bones in the
48
+ review root; copying an incomplete hierarchy reports the missing dependency.
49
+ Geometry and textures remain shared read-only, while two-sided overrides own
50
+ their materials. Qualify material-faithful derivatives separately from the
51
+ whole-character CPU view.
52
+
53
+ CPU views honor front-only, back-only and double-sided source materials in both
54
+ camera paths. Facing follows triangle winding after deformation, with the mesh's
55
+ world-transform reflection parity; changing vertex normals does not change culling.
56
+ Visible back faces reverse their flat lighting normal. Disabling culling for a
57
+ diagnostic view acts as a two-sided override. Singular transforms do not flip
58
+ winding; collapsed triangles do not draw. CPU output remains geometry evidence,
59
+ not PBR evidence. Back-only source materials are not directly portable to GLB;
60
+ see [material sidedness at export](export-profiles.md#material-sidedness-at-export).
61
+
62
+ Both CPU camera paths sample pixel centres with top-left shared-edge ownership.
63
+ They evaluate all three edges directly from canonical endpoints, preventing
64
+ rounding gaps and duplicate blending where adjacent triangles meet. Near/far
65
+ clipping uses direction-independent intersections. Interpolation uses unbiased
66
+ edge weights, including reciprocal depth for perspective cameras. This is a
67
+ floating-point geometry preview, not hardware subpixel/MSAA emulation; perimeter
68
+ pixels can differ from GPU views. Degenerate or nonfinite projected triangles
69
+ are discarded, and raster loops stay within the output image.
70
+
30
71
  ## Review lighting
31
72
 
32
73
  New GPU tool captures use **`review-neutral-v1`**. The rig uses the room PMREM
package/docs/sdk.md CHANGED
@@ -80,6 +80,59 @@ and source/revision receipts for reproducible replay. Compatibility of the SDK
80
80
  does not imply identical generated GLB bytes or renderer output across releases.
81
81
  Persisted data and rebuild restrictions follow the [migration contract](migration.md).
82
82
 
83
+ ## Procedural RGBA surfaces
84
+
85
+ The `primitives` entrypoint and authored `proceduralTexture` helper accept an
86
+ explicit V3 recipe for bounded rectangular RGBA images, shape coverage, alpha
87
+ masks, palettes, dithering and seeded stamps. Pure V3 compilation returns pixels,
88
+ canonical JSON and a compiler-qualified recipe hash without creating a mesh.
89
+ Read the [V3 contract](procedural-surfaces-v3.md) for the exact limits and rounding
90
+ rules. V1/V2 remain supported with their existing pixels, hashes and sampler
91
+ defaults. The same entrypoint exposes `textureAtlas` and `remapUVToAtlas` for
92
+ named regions. Portable V3 materials add explicit per-map sampling; opt-in V2
93
+ material records persist recipes, compiled maps and exact dependency pins.
94
+ See the [standalone material and atlas workflow](procedural-surfaces-v3.md#standalone-saved-materials-and-atlases).
95
+
96
+ ## Explicit character skinning
97
+
98
+ The `character` and `primitives` entrypoints expose opt-in `createBoneChain` and
99
+ `createSkinnedPart`; both are also sandbox helpers. `createJointChain` continues
100
+ to return ordinary rigid `Object3D` pivots. Bone chains use the same versioned
101
+ joint descriptors, infer an attached declared parent role and reject ambiguous
102
+ roles/names before attaching nodes. They do not choose anatomy or a body plan.
103
+
104
+ `createSkinnedPart(name, geometry, material, { parent, bones, indices, weights })`
105
+ clones unchanged geometry and binds it to the assembled hierarchy's current rest
106
+ pose. Supply exactly four indices and weights per vertex, in the explicit bone
107
+ array order. Indices must be valid even in zero-weight lanes. By default weights
108
+ must be in 0..1 and sum to one within 1e-5; `normalizeWeights: true` explicitly
109
+ normalizes positive finite sums. Zero sums are rejected. Optional local position,
110
+ Euler rotation (degrees), scale and semantic metadata match `createPart`'s units.
111
+
112
+ The helper owns geometry, influence arrays and inverse bind matrices; bones and
113
+ materials remain shared. Limits are 1024 bones, 262144 vertices and 128 MiB of
114
+ cloned attributes plus new influence storage. These are helper limits, not new
115
+ restrictions on direct Three.js authoring. Existing skin attributes and singular
116
+ bind transforms are rejected. Set up rest transforms before binding; later rest
117
+ edits/reparenting need deliberate rebinding. Animated bounds remain a consumer
118
+ responsibility. The selected Three exporter is required to preserve skins;
119
+ calling these helpers does not change the legacy default or select a converter.
120
+
121
+ `inspectSkinIntegrity(root)` from `@instruktlabs/kiln/qa` reads source bindings
122
+ without updating matrices, normalizing weights or repairing the scene. It returns
123
+ bounded findings, checked counts and current posed world bounds (including position
124
+ morphs). It is not an animation envelope, deformation-quality score or proof of
125
+ collision clearance. Rigid meshes do not acquire skin or body-plan requirements.
126
+
127
+ Export runs this check before bounds, texture baking or conversion. Invalid source
128
+ bindings produce an `AssetQaBlockedError` at the scene stage with mesh/joint or
129
+ vertex-range context and repair advice. This is mandatory input integrity, like
130
+ invalid geometry buffers; QA warning mode and derivative serialization do not
131
+ bypass it. The partial failure report does not claim the remaining QA rules ran.
132
+ The independent final glTF validation remains in place. Source Three skeletons
133
+ need their inverse-bind arrays populated; this does not change glTF's valid
134
+ identity-matrix default when the file omits `inverseBindMatrices`.
135
+
83
136
  ## Execution and dependencies
84
137
 
85
138
  The root SDK is Node-oriented. `/node` adapters use filesystem or server services;
@@ -1123,7 +1123,7 @@ Create and retain one immutable material from a preset or procedural definition.
1123
1123
  "type": "object",
1124
1124
  "properties": {
1125
1125
  "draft": {
1126
- "description": "create-procedural: materialId, name, tileable, sources, maps with layered procedural specs; optional tags, physicalSizeMeters, parameters. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
1126
+ "description": "create-procedural: legacy layered draft, or schemaVersion 2 with explicit samplers and RGBA/atlas recipes. Exact atlas sources must exist in this library. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
1127
1127
  "type": "object",
1128
1128
  "propertyNames": {
1129
1129
  "type": "string"
@@ -1166,7 +1166,7 @@ Import complete normalized material records and embedded PNGs. Does not fetch UR
1166
1166
  "type": "object",
1167
1167
  "properties": {
1168
1168
  "payload": {
1169
- "description": "import: schemaVersion 1 and complete normalized records with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
1169
+ "description": "import: schemaVersion 1 or 2, complete normalized records and their exact dependencies with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
1170
1170
  "type": "object",
1171
1171
  "propertyNames": {
1172
1172
  "type": "string"
package/docs/tools.md CHANGED
@@ -1328,7 +1328,7 @@ Manage optional shared workspace projects: list discovers IDs; get reads the cur
1328
1328
 
1329
1329
  ## kiln_material
1330
1330
 
1331
- Manage optional immutable material resources in this workspace. presets discovers shipped architecture, wood, metal, fabric and ground recipes; create-preset bakes one with an explicit seed, creator and license; list returns compact material/revision summaries; get returns full provenance, hashes, map conventions, physical repeat scale and a code-ready portable material spec; create-procedural bakes bounded editable layer recipes including optional height-derived normals; import accepts complete normalized records with embedded PNG bytes. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before authored evaluation resolves the resources; no project is required.
1331
+ Manage optional immutable material resources in this workspace. presets lists shipped recipes; create-preset uses explicit seed, creator and license; list returns revision summaries; get returns provenance, hashes, sampling and a portable material spec. create-procedural accepts legacy layer drafts or schemaVersion 2 drafts with explicit samplers and bounded RGBA or named atlas recipes. Atlas resource sources require exact saved revisions; import requires complete normalized records and dependencies with embedded PNG bytes. Read shape:material-draft or shape:material-import through kiln_discover for full fields. No action downloads URLs or executes source. Pin the returned materialId/revisionId through per-invocation materialDependencies or project dependencies before evaluation; no project or mesh is required.
1332
1332
 
1333
1333
  <details>
1334
1334
  <summary>Input JSON Schema</summary>
@@ -1431,7 +1431,7 @@ Manage optional immutable material resources in this workspace. presets discover
1431
1431
  "pattern": "^sha256:[a-f0-9]{64}$"
1432
1432
  },
1433
1433
  "draft": {
1434
- "description": "create-procedural: materialId, name, tileable, sources, maps with layered procedural specs; optional tags, physicalSizeMeters, parameters. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
1434
+ "description": "create-procedural: legacy layered draft, or schemaVersion 2 with explicit samplers and RGBA/atlas recipes. Exact atlas sources must exist in this library. Shape: kiln_discover({ ids: ['shape:material-draft'] }).",
1435
1435
  "type": "object",
1436
1436
  "propertyNames": {
1437
1437
  "type": "string"
@@ -1439,7 +1439,7 @@ Manage optional immutable material resources in this workspace. presets discover
1439
1439
  "additionalProperties": {}
1440
1440
  },
1441
1441
  "payload": {
1442
- "description": "import: schemaVersion 1 and complete normalized records with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
1442
+ "description": "import: schemaVersion 1 or 2, complete normalized records and their exact dependencies with embedded PNG bytes. Shape: kiln_discover({ ids: ['shape:material-import'] }).",
1443
1443
  "type": "object",
1444
1444
  "propertyNames": {
1445
1445
  "type": "string"
@@ -1,5 +1,5 @@
1
1
  /** Exact saved material closure, shared by standalone asset and project delivery. */
2
2
  import type { AssetRecord } from "./assets.js";
3
- import { type MaterialLibrary, type MaterialManifestV1, type MaterialRecordV1 } from "./material-library.js";
4
- export declare function dependencyManifests(asset: AssetRecord): MaterialManifestV1[];
5
- export declare function resolveSavedAssetMaterials(asset: AssetRecord, library?: MaterialLibrary): Promise<MaterialRecordV1[]>;
3
+ import { type MaterialLibrary, type MaterialManifest, type MaterialRecord } from "./material-library.js";
4
+ export declare function dependencyManifests(asset: AssetRecord): MaterialManifest[];
5
+ export declare function resolveSavedAssetMaterials(asset: AssetRecord, library?: MaterialLibrary): Promise<MaterialRecord[]>;