@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
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "kind": "kiln-local-plugin",
4
- "pluginVersion": "1.2.1",
5
- "engineVersion": "1.2.1",
4
+ "pluginVersion": "1.2.3",
5
+ "engineVersion": "1.2.3",
6
6
  "executables": [
7
7
  "bin/kiln-setup-workspace.mjs"
8
8
  ],
9
9
  "files": {
10
10
  ".claude-plugin/icon.png": "sha256:b4b651c402793b7e27cab780f04b14f80daec6d0cd5b7e4e90313930581a54e9",
11
- ".claude-plugin/plugin.json": "sha256:bef717ea5e0c96411c2db93d54e298dc3798512df9909ba74c950a6c1c03f6cb",
11
+ ".claude-plugin/plugin.json": "sha256:af0bc7b37b75ce5e4ab52230eded4d3e4ee999dd324b67665d57d5ce30ef56cf",
12
12
  "bin/kiln-setup-workspace.mjs": "sha256:3d9a30236919a6ca91acb6b26f808cec442e6edc3dd88fe010645b010d76e838",
13
13
  "LICENSE": "sha256:cdba278ebf1d6de618869936226bf03be4887179c128efc7aaa13d22034826a5",
14
- "plugin.json": "sha256:9ef79b862a25ae3efe86c03a37e09cd622912b97a0165a08fdf55cff8b71a010",
14
+ "plugin.json": "sha256:2e646847a58364094a9cd12c602553d73bda68785dfc216dbae9c07dafecd819",
15
15
  "PRIVACY.md": "sha256:5f58d981667e40441e21c2dec411f269a9a67a6141cec61976d27088fb12573b",
16
- "README.md": "sha256:029732d7bb9e04561105727f8e46118d3106f513b4575a8d74d57cbd1bb68e7a",
17
- "runtime.json": "sha256:b6d078f1223cb5ed3a522b0ef5a3cb778e051afc0115237cfd9cfe04c1f42025",
16
+ "README.md": "sha256:fcc6364d1b08689fd531a154383cf752a3d21cb26aa5623ac0acfc11057a9fb3",
17
+ "runtime.json": "sha256:797090aec0b46f4f6220a5b85ab58eebd1b82d7079ece0258c94c975021fdc7f",
18
18
  "skills/kiln-setup-workspace/references/plugin-install.md": "sha256:c338fb792e7fad0364c3e1a13c94c506afa1395489ec1b6e83b36d04728935fb",
19
19
  "skills/kiln-setup-workspace/references/renderer-setup.md": "sha256:b743b1b2827fa814385990e1e5b849391cb427413e9c016d273aa19cebd46031",
20
20
  "skills/kiln-setup-workspace/SKILL.md": "sha256:65ed926b3503a33f4fe793e84e4a045ce36e3967f0e6b340e456a15dd010ce54"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "kiln-engine",
4
- "version": "1.2.1",
4
+ "version": "1.2.3",
5
5
  "description": "Set up a local Kiln workspace to create, inspect and revise editable 3D assets with Claude Code or Codex.",
6
6
  "author": {
7
7
  "name": "Instrukt Labs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instruktlabs/kiln",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "harnesses": [
5
5
  "claude",
6
6
  "codex",
@@ -97,7 +97,7 @@ node kiln.mjs render RETURNED_REF --out asset-v1.glb --views asset-v1.png
97
97
 
98
98
  Replace `RETURNED_REF` with the final reference returned by Kiln. Keep `.kiln/programs`, including its mappings, while using saved references. Pass `--out` whenever you want a GLB: `render` with neither `--out` nor `--views` writes `out.glb` in the current directory.
99
99
 
100
- Add `--json` to CLI `render` for a receipt with the source reference, requirements, output paths, image fidelity and the compact review (`--detail full` only when every instance of a finding matters); read the image file separately. On failure, check `ok` and `files`: a GLB may have been written before a failed image.
100
+ Use `render --json` for the source reference, requirements, paths, fidelity and compact review (`--detail full` only for a closer look). Read images separately. Check `ok` and `files` on every export, even when redirected; a later command can hide failure. Deliver source, GLB and bundles from the same final reference or revision.
101
101
 
102
102
  To save a chosen camera view, write the `capture` object to `cameras.json` and run `node kiln.mjs render RETURNED_REF --capture cameras.json --views hero.png`, the same camera schema and pipeline as MCP; the CLI is the route that writes review PNGs to disk. With `"output": "separate"` it writes `hero.shot-01.png`, `hero.shot-02.png` and so on and lists each in `files`; add `--backdrop light` or `dark` when the grey hides the silhouette. Do not copy image base64 into shell commands.
103
103
 
@@ -31,3 +31,16 @@ shots share the grid. With `"output": "separate"` the CLI writes one file per sh
31
31
  beside the `--views` path (`hero.shot-01.png`, `hero.shot-02.png`, ...) and lists each
32
32
  in `files` with its shot name. MCP delivers the same shots as separate image blocks. `--backdrop neutral|dark|light` sets the backdrop of
33
33
  any `--views` sheet, with or without `--capture`, and wins over the recipe's own `backdrop`.
34
+
35
+ ## Verify exported files
36
+
37
+ Read each command's JSON receipt, including output redirected to a file. Check
38
+ `ok`, the returned reference and each entry in `files`; a GLB can exist even when
39
+ an image failed. A subsequent successful shell command does not establish that
40
+ an earlier export succeeded. Source export refuses to overwrite an existing file;
41
+ choose a new path instead of assuming that the old file was refreshed.
42
+
43
+ After an edit, export from the final reference or exact saved revision. Any source,
44
+ GLB and ZIP you hand over must describe that same revision. Regenerate a bundle
45
+ when its contents changed, or clearly identify it as an older revision rather than
46
+ listing it as the latest delivery.
@@ -17,6 +17,18 @@ and release. Matching endpoints can still produce a path through the payload or
17
17
  support. Keep joints, contact markers and payload motion in the intended hierarchy;
18
18
  repair the geometry or path where the swept volumes collide.
19
19
 
20
+ Measure a resting or mounting frame against the exported geometry, including
21
+ angled feet, supports and small protrusions. A nominal primitive bottom or a
22
+ locator called `ground` can differ from the actual lowest surface after rotation.
23
+ Preserve intentional hanging origins and provide a separate resting socket when
24
+ both placements are useful; do not silently move every asset to its bounds.
25
+
26
+ For directional repeated surfaces, preserve grain direction and texel scale while
27
+ varying the phase between neighboring pieces. Identical knots aligned in columns
28
+ can make separate boards read as one repeated stamp. Compare a single module with
29
+ an assembled span. Random rotation is inappropriate for directional grain and
30
+ must not change named atlas regions or their sampling margins.
31
+
20
32
  For ears, handles or other details attached to a rotated or lofted body, derive
21
33
  their bases from the carrier's local surface and transform them through the same
22
34
  frame. A nearby world-space point can leave a visible gap after the body rotates.
@@ -62,6 +74,15 @@ winding or missing geometry rather than hiding the defect with double-sided shad
62
74
  deliberate thin sheets may need it. Check ceiling and trim extents against the actual sloping roof, including
63
75
  the eaves, rather than its bounding box.
64
76
 
77
+ Choose sidedness deliberately: an inward-facing tunnel lining can use `FrontSide`
78
+ with inward triangle winding; a leaf card viewed from both sides can use
79
+ `DoubleSide`. GLB has no back-only material flag, so Kiln rejects a used `BackSide`
80
+ material instead of exporting it as front-only. Reversing normals alone does not
81
+ reverse winding. Double-sided shading adds neither thickness nor end caps. A
82
+ negative node scale mirrors the front-face convention; baking that reflection into
83
+ vertex positions is a different operation and requires consistent winding and
84
+ normal/tangent data. Inspect both sides after export in the intended consumer.
85
+
65
86
  For a pane or panel fitted into a sloping frame, derive its corners and surface
66
87
  normal in the frame's coordinates. A bar's long axis and a sheet's plane may use
67
88
  different local axes, so copying a slope angle does not align them. Check all
@@ -69,6 +90,16 @@ four transformed corners against the intended frame plane and opening, then
69
90
  inspect an oblique view where an out-of-plane pane is visible. Matching centers
70
91
  or bounding boxes alone cannot establish this fit.
71
92
 
93
+ For blocks or panels around a cylinder, separate their radial positions from
94
+ their orientation. A `boxGeo(length, height, thickness)` has its long axis along
95
+ local X. At `[radius * Math.cos(theta), y, radius * Math.sin(theta)]`, Y rotation
96
+ `-thetaDeg` points that axis radially outward; `90 - thetaDeg` makes it tangent
97
+ to the ring (`theta` is radians, `thetaDeg` is degrees). Derive the orientation
98
+ again when the geometry's long axis or parent frame differs. Check one part at
99
+ each quarter turn before repeating it. For a continuous wall, inspect neighboring
100
+ edges on both its inside and outside, including staggered courses: correct centers
101
+ and tangency alone do not guarantee closure. Preserve intentional openings.
102
+
72
103
  For attached veins, ribs or seams, discover `recipe:surface-detail-v1`. Its strip
73
104
  shares the carrier mesh's sampled boundary vertices and rises along local normals.
74
105
  A shared equation followed by a separately interpolated curve does not establish
@@ -115,11 +146,51 @@ joint each pivot represents before reversing its bend direction. Correct exposed
115
146
  attachment caps, gaps and snapping through the motion, without assuming every
116
147
  stylized creature needs one rigging or deformation technique.
117
148
 
149
+ Choose the mechanism deliberately: rigid armor plates or robot links can follow
150
+ `createJointChain` pivots; a continuous elbow or flexible appendage can use
151
+ `createBoneChain` plus `createSkinnedPart` when the installed Discovery exposes
152
+ them. Both chains carry the same joint roles and local rest frames. Bones are
153
+ opt-in; rigid pivots do not become bones merely because their names say `Joint_`.
154
+
155
+ For explicit skinning, assemble the bones under the asset root in the rest pose
156
+ before binding. Finish topology and UV operations first where practical. Supply
157
+ four indices and weights for every final vertex, including duplicated UV seam
158
+ vertices; use valid indices even for zero-weight lanes. Weights should sum to one.
159
+ `normalizeWeights:true` normalizes a positive sum but cannot infer missing weights
160
+ or repair a zero sum. The helper owns cloned geometry and inverse bind matrices;
161
+ the caller still owns the bones and shared materials. Changing rest geometry or
162
+ reparenting afterward needs deliberate rebinding, not just another animation pose.
163
+
164
+ If export reports `SKIN_*` integrity findings, repair the named mesh, joint or
165
+ vertex range before another render. A missing inverse matrix needs the intended
166
+ rest binding restored; an invalid joint index needs the bone order and influence
167
+ lanes corrected. Normalizing weights cannot repair either. These source-data
168
+ checks also run on review derivatives and in QA warning mode. A valid current-pose
169
+ bound does not prove all animation poses, good deformation or collision clearance.
170
+
171
+ A two-bone ramp is useful for a simple strip or tube, not a general anatomical
172
+ weighting rule: for a chosen local transition interval `[startY, endY]`, with
173
+ `endY > startY`, use `t = Math.max(0, Math.min(1, (y-startY)/(endY-startY)))`,
174
+ indices `[baseIndex, tipIndex, baseIndex, baseIndex]` and weights `[1-t,t,0,0]`.
175
+ Keep the interval, joint placement and profile appropriate to the asset. Inspect
176
+ rest, intermediate and extreme poses under the actual parent transforms; correct
177
+ volume loss, exposed caps and pinching in the geometry/weights. Verify the chosen
178
+ converter preserves skinning: the explicit Three exporter supports this path,
179
+ while the legacy converter remains the default and does not preserve skins.
180
+
118
181
  Before mirroring a hand, inspect its local frame, palm, thumb web and any tool grip.
119
182
  Weak anatomical cues can look reversed without a left/right frame error. Refine the
120
183
  shape while preserving a correct wrist pivot and attachment frame, then check hand
121
184
  clearance through the relevant motion and against the complete carried tool.
122
185
 
186
+ Define the palm normal, finger direction and thumb side in each hand's local frame.
187
+ A grip marker reaching a handle does not establish a natural grip orientation.
188
+ Solve reach with elbow and torso clearance as well as wrist alignment, distributing
189
+ rotation through the forearm when appropriate. Inspect finger wrap and the actual
190
+ skinned wrist and shoulder surfaces between keys; a correct joint frame can still
191
+ produce a stretched sleeve cap. Neutral palm direction and pose belong to the
192
+ asset brief, rather than a universal character default.
193
+
123
194
  For a shield or other broad carried plate, define the intended support pose as
124
195
  well as its mounting frame: which way the plate faces, where the grip lies, and
125
196
  how the forearm runs along the inner surface. Solve the elbow within a plausible
@@ -142,6 +213,12 @@ in the host). Recheck what each
142
213
  lookup controls, clip targets and intermediate poses after export; fewer meshes
143
214
  alone does not establish a successful optimization.
144
215
 
216
+ In custom consolidation, a scalar mesh material applies to every geometry group.
217
+ Group `materialIndex` selects an entry only when `mesh.material` is an array;
218
+ indexing a one-element array for box face groups can silently create default
219
+ materials on some faces. Preserve valid material assignments, then inspect the
220
+ exported faces rather than relying on a successful merge or export.
221
+
145
222
  Budget materials within each rigid group. Reuse the parent's material for tiny
146
223
  fixed details when it gives the intended appearance; a separate material adds a
147
224
  potential draw even when the detail is small. One material does not guarantee
@@ -149,6 +226,15 @@ one draw: vertex layouts, protected parts, transparency and vertex-limit splits
149
226
  also constrain merging. Available `drawDiagnostics` can help locate these costs;
150
227
  read its scope and skip reasons, then measure in the destination.
151
228
 
229
+ Budget skinned mesh count as well as triangles. A small articulated asset can
230
+ still submit many draws when each segment is a separate skin, even if the parts
231
+ share one texture. Where the authoring/export path supports it, combine
232
+ compatible surfaces into a skin with a shared skeleton and material. A skin-aware
233
+ consolidation must preserve bind transforms, joint indices and weights, normals,
234
+ UVs, morph channels and attachment frames. Keep separate skins when their
235
+ materials, skeletons or visibility controls require them. Do not send skins
236
+ through a rigid merge by weakening its rejection checks.
237
+
152
238
  Avoid unintentionally overlapping coplanar faces on separate parts. Merging can
153
239
  change which face wins a depth tie even when geometry and colours are preserved.
154
240
  Use a shared surface, remove hidden duplicate faces or give a deliberate overlay
@@ -164,6 +250,36 @@ material capabilities before choosing a delivery resource, then inspect GPU view
164
250
  and destination filtering/backfaces. Raw `THREE.DataTexture` is not an available
165
251
  authoring route. Solid leaves and custom surfaces remain valid alternatives.
166
252
 
253
+ For original cutouts, `proceduralTexture` V3 authors rectangular RGBA pixels;
254
+ `textureAtlas` combines named fixed regions from V3 recipes or exact saved map
255
+ revisions. Discover their full contracts before authoring. Atlas regions do not
256
+ pack or resize themselves. Reserve nonoverlapping gutters and choose edge copying
257
+ or transparent RGB dilation for the actual filtering and alpha policy.
258
+
259
+ Map unit UVs with
260
+ `remapUVToAtlas(geometry, atlas, 'region-name', {inputOrigin:'bottom-left', mode:'texel-center'})`.
261
+ Choose `inputOrigin` from the source UV convention and `mode` deliberately:
262
+ `texel-center` insets to edge texel centers, while `content-boundary` uses the
263
+ content rectangle boundary. Finish topology and then remap the correct region;
264
+ do not rotate arbitrary charts over a painted atlas and expect the artwork to
265
+ follow. A geometry-wide region binding does not assign separate regions to
266
+ material groups. For repeating grass or directional wood, preserve or project
267
+ UVs and tile a standalone texture instead of forcing every surface into an atlas.
268
+ Neither atlas remapping nor `autoUnwrap` creates painted detail.
269
+
270
+ For repeated boards or other directional surfaces, align UV length with the
271
+ physical grain direction and retain the chosen texel density. Repeating the same
272
+ narrow UV band can miss a texture's authored detail even when the map is bound.
273
+ Use deliberate deterministic offsets when variation is intended, and inspect a
274
+ close perspective view as well as the assembly; a low-contrast surface alone
275
+ does not prove missing textures or a rendering defect.
276
+
277
+ When projecting along a chosen grain axis, use an alternate projection for faces
278
+ whose normal is parallel to that axis; otherwise their UV area can collapse to a
279
+ line. Check exported nondegenerate faces for usable UV area, including end faces
280
+ and caps. A separate visible end-grain cap can be intentional, but does not repair
281
+ the underlying face's UVs or justify claiming every chart is valid.
282
+
167
283
  For lengthwise grain or stripes through bends, discover `recipe:directional-sweep-v1`.
168
284
  It demonstrates sweep side U around the profile and V along path distance. Preserve
169
285
  that convention when editing the path; arbitrary atlas rotations suit baked detail
@@ -327,3 +443,21 @@ box or cylindrical mapping in a geometry-local frame. Fetch its exact Discovery
327
443
  contract for rotated frames, partial angular ranges, caps and seam sampling.
328
444
  `autoUnwrap` is the separate atlas operation. Removed shape unwrap names do not
329
445
  select a preservation or projection fallback.
446
+
447
+ For a packed baked texture, `await autoUnwrap(geometry, options)` returns new
448
+ buffers and leaves the source unchanged. It duplicates seam vertices and remaps
449
+ vertex colors, skin indices/weights, extra UVs, custom per-vertex attributes
450
+ (1..4 components), and position/normal/color morphs. It retains triangle groups,
451
+ draw range and JSON metadata. It replaces UV0 and drops stale tangents; recompute
452
+ tangents after the final UV layout. Skin bindings and animation remain on the
453
+ mesh/skeleton, so verify an exported posed mesh before relying on deformation.
454
+
455
+ Default unwrapping recomputes normals. Use `useNormals: true` to preserve authored
456
+ normals and when normal morphs need their original reference frame. The installed
457
+ xatlas adapter supports at most 65,536 vertices both before and after seam
458
+ splitting; split larger geometry. Instanced data and unsupported morph semantics
459
+ fail explicitly. Read the returned `userData.atlas.width/height`; requested
460
+ `resolution` is a target and the packed image can be rectangular. Unwrapping
461
+ cannot preserve the appearance of a texture painted for the old UV0: rebake it.
462
+ For directional wood grain or a tiled wall, prefer existing primitive UVs or
463
+ `projectUV` rather than an arbitrarily rotated atlas.
@@ -46,7 +46,25 @@ For library materials, use CLI `material create-preset --file options.json`, the
46
46
  `material get MATERIAL_ID REVISION` for the code-ready `portableSpec`. Custom recipes
47
47
  use `material procedural --file draft.json`. These CLI paths do not need an internal
48
48
  `dist/` import or an extra TypeScript loader. Copy the returned spec into source,
49
- compile it with `compilePortableMaterialSpecV2`, and supply the exact pins below.
49
+ compile schemaVersion 2 with `compilePortableMaterialSpecV2` or schemaVersion 3
50
+ with `compilePortableMaterialSpecV3`, and supply the exact pins below.
51
+
52
+ For a texture without geometry, create the material directly; do not invent a
53
+ mesh or project just to save PNG maps. Read `shape:material-draft` with Discovery.
54
+ Unversioned layered drafts keep their existing V1 records. Opt-in
55
+ `schemaVersion:2` material drafts add explicit samplers and a `procedural` V3
56
+ RGBA recipe or `atlas` recipe per map. The material-record version and texture-
57
+ recipe version describe different contracts. V3 recipes support rectangular
58
+ surfaces and alpha; a mask material still needs actual opaque and transparent
59
+ texels to produce a cutout.
60
+
61
+ An atlas resource source names an exact map `resourceId` plus its material
62
+ revision pin. It does not load arbitrary URLs or paths. Save sources first,
63
+ reuse returned identifiers, then create the atlas. The library resolves the
64
+ transitive closure. Export a saved material with
65
+ `node kiln.mjs material export MATERIAL_ID REVISION --out material.json` and
66
+ import that complete payload with `material import --file material.json`;
67
+ copying only the atlas PNG loses editable sources and named regions.
50
68
 
51
69
  ## Create and revise a project when useful
52
70
 
@@ -104,7 +122,7 @@ editable design guidance, not trusted requirements or release authorization.
104
122
  Inspect `kiln_material` with `action: "presets"`, `"list"` or `"get"`. CLI equivalents
105
123
  are `material presets`, `material list`, and `material get ID REVISION`. Creation
106
124
  and import preserve creator, license, recipe/seed and source provenance. Copy the
107
- returned `portableSpec` into source and call `compilePortableMaterialSpecV2(spec)`;
125
+ returned `portableSpec` into source and select its matching V2 or V3 compiler;
108
126
  the host provides the selected immutable map bytes before evaluating source.
109
127
 
110
128
  A pin uses the returned material ID as `resourceId`; both hashes equal its returned
@@ -129,9 +147,21 @@ already pins replaces the project's for that call. Update the lock deliberately
129
147
  changing shared art direction. A missing resource or mismatched hash must be resolved
130
148
  before building; neither source URLs nor material names authorize an implicit download.
131
149
 
150
+ Keep project defaults to a deliberate shared authoring palette, not every material
151
+ used anywhere in a growing inventory. An evaluation currently resolves at most 16
152
+ resources, including transitive dependencies; call-level pins replace matching
153
+ defaults but do not remove unrelated defaults. If that merged context exceeds the
154
+ budget, use an explicit standalone operation with the asset's complete small pin
155
+ set, then link the exact saved revision to the project. Refine the project defaults
156
+ through a deliberate revision update. Saved asset closures retain their own exact
157
+ materials independently of those authoring defaults.
158
+
132
159
  Material records carry physical repeat scale, but the author controls mesh UV scale.
133
160
  Review seams, repeats, roughness and normal strength on representative geometry with
134
161
  actual GPU views. A project palette is guidance, not automatic recoloring.
162
+ Keep explicit nearest sampling for a requested pixel texture local to that
163
+ material. Smooth or physically based materials may need linear filtering and
164
+ mipmaps; neither a project nor an atlas implies one sampling policy.
135
165
 
136
166
  ## Retain and deliver the work
137
167
 
@@ -38,6 +38,19 @@ Check closed, open and moving door states and safe dismounts. A path over a rive
38
38
  needs a real supporting collision surface. Broad-phase bounds alone do not prove
39
39
  that a person can pass through an opening.
40
40
 
41
+ Exercise each one-shot interaction at least twice, including after its action has
42
+ finished and clamped. In Three AnimationMixer, seeking a completed paused action
43
+ does not restart it; explicitly reset/play for a new use while preserving the
44
+ action during the same use. Tie damage or resource consumption to the reviewed
45
+ contact event and check range or interruption again there.
46
+
47
+ Visual gaps and gameplay barriers are different scene contracts. A sparse gate
48
+ or boarded opening can have visible gaps while blocking an actor or melee reach.
49
+ Use the chosen interaction rule and the opening's actual frame for that test;
50
+ a single ray through decorative triangles cannot establish protection. Keep
51
+ project-specific damage, collision and barrier rules out of standalone asset
52
+ requirements. Compare the actor path, visual geometry and interaction result.
53
+
41
54
  For a follow camera, run its actual update and controls during passage checks.
42
55
  A controller-only loop can pass while the rendered camera hides the actor behind
43
56
  an eave or lintel. Inspect the animated head and accessories, not only a clear
@@ -240,6 +253,12 @@ placements the application knows will remain fixed. Keep animated and controlled
240
253
  hierarchies live, and restore or rebuild the cache before placement or variant
241
254
  changes. Check exact world transforms, visibility and culling in the consumer.
242
255
 
256
+ For spatial instance batches, changing a source root's visibility must also
257
+ update its batch membership and culling bounds. Synchronize on a relevant change;
258
+ avoid scanning every immutable source each frame. Test hidden and restored
259
+ instances, preserving interactive or independently moving objects outside the
260
+ static batch.
261
+
243
262
  Hiding draws does not necessarily stop CPU animation. Count evaluated rigs and
244
263
  motion owners as well as visible actors. A projected-error selector may demand
245
264
  exact poses behind the camera. For a qualified shared clip, the consumer can
@@ -19,6 +19,27 @@ Kiln authors metres, +X forward, +Y up, +Z right. Confirm importer conversion in
19
19
 
20
20
  Source refs name source revisions. They do not identify arbitrary externally modified GLB bytes. Track the exact exported file used for integration.
21
21
 
22
+ For a first-person presentation, review the held assembly from its actual animated
23
+ eye frame at the destination field of view and aspect ratio. Inspect ready, lift,
24
+ contact and recovery; an external pose can fit both hands while obscuring the
25
+ crosshair or passing through the camera. Keep any view-specific presentation in
26
+ the asset or consuming scene selected by the brief, not in universal rig defaults.
27
+
28
+ Measure deformed surface vertices as well as contact locators. Planted foot markers
29
+ do not rule out a sole, claw or swinging hand passing below the ground. Sample
30
+ between animation keys and between the usual quarter poses; record the sampling
31
+ density and tolerance rather than claiming a continuous guarantee from a few views.
32
+
33
+ For atlas cutouts, first inspect the exact saved sheet on light, dark and checker
34
+ backgrounds for alpha, edge RGB and region placement. Raw map pixels establish
35
+ image content, not shaded material appearance. Then inspect the exported asset
36
+ with the destination's actual sampler, alpha cutoff, sidedness and lighting,
37
+ including receding and moving views. A still nearest-filtered preview cannot
38
+ clear mip bleed, shimmering or disappearing thin details. Keep material revision,
39
+ atlas region and compiler identity with the reviewed file. Structural UV-domain
40
+ checks cannot prove intended corner orientation without source correspondence;
41
+ use an asymmetric corner fixture when qualifying a mapping pipeline.
42
+
22
43
  `composeSceneGLB` defaults to static composition (`keepAnimations: false`). Explicitly keep animations when required. Its material optimization policy can change scene material organization; choose it deliberately, then inspect the result. Warnings can report skipped inputs, so successful export alone does not prove every required asset was included.
23
44
 
24
45
  Do not invent a universal performance cap. State the destination, tested conditions, and material limits of the evidence.
@@ -40,8 +61,13 @@ closure; flush rest-pose joins can separate or expose caps during motion.
40
61
 
41
62
  For articulated actors carrying equipment, inspect the limb bend as well as hand
42
63
  attachment closure. A hand can reach its target while the elbow folds through the
43
- body. Include headgear and carried items in intermediate-pose clearance checks,
44
- and inspect cables or strings at their moving attachment points. A convincing
64
+ body. Include headgear and carried items in intermediate-pose clearance checks.
65
+ Verify palm/finger orientation and wrist continuity against the full handle.
66
+ Sample the interpolated skinned surfaces as well as joint centres; shoulder
67
+ weights can create a pointed cap or a sleeve crossing the torso despite valid
68
+ attachment distances. Inspect the actual first-person view when the destination
69
+ requires it, without treating camera offsets as a repair for an invalid grip.
70
+ Also inspect cables or strings at their moving attachment points. A convincing
45
71
  draw or tensioning action needs compatible equipment deformation, not just a
46
72
  moving hand and an arbitrarily stretched connector. Keep controller choices and
47
73
  equipment fit in the destination; a scene repair need not change the saved master.
@@ -79,6 +105,15 @@ instancing, so it does not predict complete `full` output or GPU speed. Check
79
105
  Measure actual destination draws by anchor, material and pass after export, then
80
106
  exercise the interactions and attachments those anchors protect.
81
107
 
108
+ For a composed scene, inventory every placed asset family and conditional visible
109
+ state, then verify actual submitted draws from representative views. Report
110
+ pack-only assets separately. A source primitive count is not a visible draw
111
+ count; one mesh may have several material groups, and repeated geometry may be
112
+ instanced into spatial batches. Compare skin counts and material/texture variants
113
+ when a low-triangle asset still contributes many submissions. Separate coverage
114
+ snapshots from timed benchmarks and preserve exact camera, viewport and quality
115
+ settings when comparing runtime derivatives.
116
+
82
117
  Read each observation's `evidence` and sampling/omission counts.
83
118
  `mirroredTangents` identifies static tangent-bearing parts with mirrored transforms;
84
119
  it does not establish a shading defect. `coplanarParts` samples triangle overlaps
@@ -1,43 +1,25 @@
1
1
  /** Exact saved material closure, shared by standalone asset and project delivery. */
2
2
  import type { AssetRecord } from './assets';
3
- import { validateAssetMaterialClosure } from './assets';
3
+ import { assetMaterialRootManifests, validateAssetMaterialClosure } from './assets';
4
4
  import {
5
- canonicalMaterialJson,
6
5
  MATERIAL_LIBRARY_LIMITS,
7
6
  type MaterialLibrary,
8
- type MaterialManifestV1,
9
- type MaterialRecordV1,
7
+ type MaterialManifest,
8
+ type MaterialRecord,
10
9
  } from './material-library';
11
10
  import {
12
11
  decodeMaterialLibraryPayload,
13
- validateMaterialManifestIdentity,
12
+ validateMaterialManifestIdentityAny,
13
+ resolveMaterialRecordClosure,
14
14
  } from './material-library-node';
15
15
 
16
- export function dependencyManifests(asset: AssetRecord): MaterialManifestV1[] {
17
- const result = new Map<string, MaterialManifestV1>();
18
- for (const raw of asset.manifest.build?.dependencies ?? []) {
19
- if (!raw || typeof raw !== 'object' || Array.isArray(raw))
20
- throw new Error('Invalid saved asset dependency');
21
- const dependency = raw as Record<string, unknown>;
22
- if (dependency.kind === 'kiln.material.v1') {
23
- if (dependency.delivery !== 'runtime')
24
- throw new Error('Invalid material dependency delivery');
25
- const manifest = validateMaterialManifestIdentity(dependency.manifest);
26
- result.set(`${manifest.materialId}/${manifest.revisionId}`, manifest);
27
- if (result.size > MATERIAL_LIBRARY_LIMITS.maxPayloadRecords)
28
- throw new Error('Saved asset material dependency count exceeds limit');
29
- } else if (dependency.delivery === 'runtime')
30
- throw new Error(
31
- `Unsupported runtime dependency: ${String(dependency.resourceId ?? 'unknown')}`,
32
- );
33
- else if (dependency.delivery !== 'embedded')
34
- throw new Error('Unsupported saved asset dependency');
35
- }
36
- if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.size)
16
+ export function dependencyManifests(asset: AssetRecord): MaterialManifest[] {
17
+ const result = assetMaterialRootManifests(asset).map(validateMaterialManifestIdentityAny);
18
+ if (asset.manifest.build?.rebuild === 'external-dependencies-required' && !result.length)
37
19
  throw new Error('Saved asset has unrecorded external dependencies');
38
20
  let bytes = 0;
39
21
  let pixels = 0;
40
- for (const manifest of result.values())
22
+ for (const manifest of result)
41
23
  for (const map of manifest.maps) {
42
24
  bytes += map.bytes;
43
25
  pixels += map.width * map.height;
@@ -47,32 +29,43 @@ export function dependencyManifests(asset: AssetRecord): MaterialManifestV1[] {
47
29
  pixels > MATERIAL_LIBRARY_LIMITS.maxPayloadPixels
48
30
  )
49
31
  throw new Error('Saved asset material allocation exceeds limit');
50
- return [...result.values()];
32
+ return result;
51
33
  }
52
34
 
53
35
  export async function resolveSavedAssetMaterials(
54
36
  asset: AssetRecord,
55
37
  library?: MaterialLibrary,
56
- ): Promise<MaterialRecordV1[]> {
38
+ ): Promise<MaterialRecord[]> {
57
39
  const dependencies = dependencyManifests(asset);
58
40
  if (asset.materialResources) {
59
41
  validateAssetMaterialClosure(asset, true);
60
42
  return decodeMaterialLibraryPayload(asset.materialResources);
61
43
  }
62
- return Promise.all(
63
- dependencies.map(async (manifest) => {
64
- try {
65
- if (!library) throw new Error('No material library configured');
66
- const record = await library.read(manifest.materialId, manifest.revisionId);
67
- if (canonicalMaterialJson(record.manifest) !== canonicalMaterialJson(manifest))
68
- throw new Error('Saved dependency manifest mismatch');
69
- return record;
70
- } catch (error) {
71
- throw new Error(
72
- `Locked asset material unavailable: ${manifest.materialId} at ${manifest.revisionId}`,
73
- { cause: error },
74
- );
75
- }
76
- }),
77
- );
44
+ if (!dependencies.length) return [];
45
+ if (!library)
46
+ throw new Error('Locked asset material unavailable: No material library configured');
47
+ const roots = new Map(dependencies.map((m) => [`${m.materialId}/${m.revisionId}`, m]));
48
+ const wrap = async <T>(id: string, revision: string, load: () => Promise<T>): Promise<T> => {
49
+ try {
50
+ return await load();
51
+ } catch (error) {
52
+ throw new Error(`Locked asset material unavailable: ${id} at ${revision}`, { cause: error });
53
+ }
54
+ };
55
+ return resolveMaterialRecordClosure(dependencies, {
56
+ readManifest: async (id, revision) =>
57
+ wrap(id, revision, async () => {
58
+ const root = roots.get(`${id}/${revision}`);
59
+ if (root) return root;
60
+ if (!library.readManifest)
61
+ throw new Error('Material library requires manifest-first dependency capability');
62
+ return library.readManifest(id, revision);
63
+ }),
64
+ read: async (id, revision) =>
65
+ wrap(id, revision, () =>
66
+ library.readRecordFiles
67
+ ? library.readRecordFiles(id, revision)
68
+ : library.read(id, revision),
69
+ ),
70
+ });
78
71
  }
@@ -18,7 +18,11 @@ import {
18
18
  type AssetManifest,
19
19
  type AssetRecord,
20
20
  } from './assets';
21
- import type { MaterialLibrary, MaterialRecordV1 } from './material-library';
21
+ import {
22
+ canonicalMaterialJson,
23
+ type MaterialLibrary,
24
+ type MaterialRecord,
25
+ } from './material-library';
22
26
  import { FileMaterialLibrary, createMaterialLibraryPayload } from './material-library-node';
23
27
  import { dependencyManifests, resolveSavedAssetMaterials } from './asset-materials-node';
24
28
  import { renameDirectoryAtomically } from './atomic-directory';
@@ -230,7 +234,9 @@ export class FileAssetLibrary implements AssetLibrary {
230
234
  (raw) =>
231
235
  raw &&
232
236
  typeof raw === 'object' &&
233
- (raw as Record<string, unknown>).kind === 'kiln.material.v1',
237
+ ['kiln.material.v1', 'kiln.material.v2'].includes(
238
+ String((raw as Record<string, unknown>).kind),
239
+ ),
234
240
  ),
235
241
  ),
236
242
  );
@@ -243,7 +249,7 @@ export class FileAssetLibrary implements AssetLibrary {
243
249
  if (resourceBytes > ASSET_LIMIT)
244
250
  throw new Error('Asset material import exceeds allocation limit');
245
251
  const prepared: AssetRecord[] = [];
246
- const resources = new Map<string, MaterialRecordV1>();
252
+ const resources = new Map<string, MaterialRecord>();
247
253
  // Validate the whole input before writing anything; per-revision commits are atomic.
248
254
  for (const record of records) {
249
255
  await verifyAssetRecord(record);
@@ -256,11 +262,22 @@ export class FileAssetLibrary implements AssetLibrary {
256
262
  (raw) =>
257
263
  raw &&
258
264
  typeof raw === 'object' &&
259
- (raw as Record<string, unknown>).kind === 'kiln.material.v1',
265
+ ['kiln.material.v1', 'kiln.material.v2'].includes(
266
+ String((raw as Record<string, unknown>).kind),
267
+ ),
260
268
  ))
261
269
  ) {
262
270
  const materials = await resolveSavedAssetMaterials(record, this.materials);
263
- for (const material of materials) resources.set(material.manifest.revisionId, material);
271
+ for (const material of materials) {
272
+ const key = `${material.manifest.materialId}/${material.manifest.revisionId}`;
273
+ const previous = resources.get(key);
274
+ if (
275
+ previous &&
276
+ canonicalMaterialJson(previous.manifest) !== canonicalMaterialJson(material.manifest)
277
+ )
278
+ throw new Error('Conflicting asset material dependencies');
279
+ resources.set(key, material);
280
+ }
264
281
  prepared.push({
265
282
  ...record,
266
283
  materialResources: await createMaterialLibraryPayload(materials),