@instruktlabs/kiln 0.0.0-stage → 1.0.0

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 (947) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +19 -0
  3. package/.claude-plugin/plugin.json +30 -0
  4. package/.codex-plugin/plugin.json +37 -0
  5. package/.mcp.json +10 -0
  6. package/CHANGELOG.md +2579 -0
  7. package/CONTRIBUTING.md +130 -0
  8. package/LICENSE +21 -0
  9. package/README.md +443 -2
  10. package/THIRD_PARTY_NOTICES.md +62 -0
  11. package/dist/agent-providers.mjs +310 -0
  12. package/dist/agent-run.mjs +40433 -0
  13. package/dist/build.json +215 -0
  14. package/dist/cli.mjs +48027 -0
  15. package/dist/evaluator-worker.mjs +27572 -0
  16. package/dist/mcp-engine.mjs +43537 -0
  17. package/dist/mcp-server.mjs +2388 -0
  18. package/dist/viewer/app.js +4281 -0
  19. package/dist/viewer/chat.html +4409 -0
  20. package/dist/viewer/index.html +51 -0
  21. package/dist/viewer/style.css +51 -0
  22. package/docs/architecture.md +77 -0
  23. package/docs/cameras.md +181 -0
  24. package/docs/chatgpt.md +48 -0
  25. package/docs/collections.md +195 -0
  26. package/docs/community-exporter.md +95 -0
  27. package/docs/engine-handoff.md +221 -0
  28. package/docs/examples.md +171 -0
  29. package/docs/export-profiles.md +223 -0
  30. package/docs/extending.md +43 -0
  31. package/docs/geometry.md +505 -0
  32. package/docs/google.md +121 -0
  33. package/docs/harnesses.md +372 -0
  34. package/docs/install.md +542 -0
  35. package/docs/material-library.md +133 -0
  36. package/docs/migration.md +809 -0
  37. package/docs/programs.md +123 -0
  38. package/docs/projects-and-live-review.md +94 -0
  39. package/docs/rendering.md +250 -0
  40. package/docs/runtime.md +223 -0
  41. package/docs/sdk.md +137 -0
  42. package/docs/tools.md +1874 -0
  43. package/lib/_types/render-service/src/build-identity.d.mts +24 -0
  44. package/lib/_types/render-service/src/instance.d.mts +47 -0
  45. package/lib/agent/call-budget.d.ts +55 -0
  46. package/lib/agent/call-budget.js +52 -0
  47. package/lib/agent/compaction.d.ts +83 -0
  48. package/lib/agent/compaction.js +124 -0
  49. package/lib/agent/concurrency.d.ts +8 -0
  50. package/lib/agent/concurrency.js +24 -0
  51. package/lib/agent/diff.d.ts +23 -0
  52. package/lib/agent/diff.js +124 -0
  53. package/lib/agent/generate.d.ts +192 -0
  54. package/lib/agent/generate.js +240 -0
  55. package/lib/agent/hooks.d.ts +91 -0
  56. package/lib/agent/hooks.js +158 -0
  57. package/lib/agent/index.d.ts +42 -0
  58. package/lib/agent/index.js +30 -0
  59. package/lib/agent/native-prompt.d.ts +15 -0
  60. package/lib/agent/native-prompt.js +29 -0
  61. package/lib/agent/native-workflow.d.ts +4 -0
  62. package/lib/agent/native-workflow.js +30 -0
  63. package/lib/agent/providers.d.ts +188 -0
  64. package/lib/agent/providers.js +392 -0
  65. package/lib/agent/run.d.ts +72 -0
  66. package/lib/agent/run.js +245 -0
  67. package/lib/agent/skill-resources.d.ts +9 -0
  68. package/lib/agent/skill-resources.js +178 -0
  69. package/lib/agent/split-tool-result-images.d.ts +33 -0
  70. package/lib/agent/split-tool-result-images.js +47 -0
  71. package/lib/agent/stream-start.d.ts +30 -0
  72. package/lib/agent/stream-start.js +32 -0
  73. package/lib/agent/tools.d.ts +12 -0
  74. package/lib/agent/tools.js +77 -0
  75. package/lib/agent/view-render-timeout.d.ts +40 -0
  76. package/lib/agent/view-render-timeout.js +71 -0
  77. package/lib/animation-cubic.d.ts +12 -0
  78. package/lib/animation-cubic.js +44 -0
  79. package/lib/animation-spline.d.ts +50 -0
  80. package/lib/animation-spline.js +168 -0
  81. package/lib/architecture.d.ts +146 -0
  82. package/lib/architecture.js +498 -0
  83. package/lib/arena/adaptive.d.ts +38 -0
  84. package/lib/arena/adaptive.js +87 -0
  85. package/lib/arena/bradley-terry.d.ts +63 -0
  86. package/lib/arena/bradley-terry.js +165 -0
  87. package/lib/arena/index.d.ts +15 -0
  88. package/lib/arena/index.js +13 -0
  89. package/lib/assembly-transform.d.ts +7 -0
  90. package/lib/assembly-transform.js +21 -0
  91. package/lib/assembly.d.ts +105 -0
  92. package/lib/assembly.js +587 -0
  93. package/lib/asset-catalog.d.ts +13 -0
  94. package/lib/asset-catalog.js +21 -0
  95. package/lib/asset-export.d.ts +41 -0
  96. package/lib/asset-export.js +106 -0
  97. package/lib/asset-materials-node.d.ts +5 -0
  98. package/lib/asset-materials-node.js +56 -0
  99. package/lib/asset-widget.d.ts +10 -0
  100. package/lib/asset-widget.js +23 -0
  101. package/lib/assets-node.d.ts +33 -0
  102. package/lib/assets-node.js +348 -0
  103. package/lib/assets-resources.d.ts +33 -0
  104. package/lib/assets-resources.js +109 -0
  105. package/lib/assets.d.ts +91 -0
  106. package/lib/assets.js +205 -0
  107. package/lib/atomic-directory.d.ts +9 -0
  108. package/lib/atomic-directory.js +31 -0
  109. package/lib/authoring-style.d.ts +3 -0
  110. package/lib/authoring-style.js +42 -0
  111. package/lib/build-cache-node.d.ts +12 -0
  112. package/lib/build-cache-node.js +87 -0
  113. package/lib/build-cache.d.ts +24 -0
  114. package/lib/build-cache.js +229 -0
  115. package/lib/character.d.ts +132 -0
  116. package/lib/character.js +612 -0
  117. package/lib/cli-render-mode.d.ts +31 -0
  118. package/lib/cli-render-mode.js +204 -0
  119. package/lib/community-exporter.d.ts +13 -0
  120. package/lib/community-exporter.js +237 -0
  121. package/lib/composer/agent/compaction.d.ts +72 -0
  122. package/lib/composer/agent/compaction.js +103 -0
  123. package/lib/composer/agent/index.d.ts +25 -0
  124. package/lib/composer/agent/index.js +19 -0
  125. package/lib/composer/agent/prompt.d.ts +25 -0
  126. package/lib/composer/agent/prompt.js +100 -0
  127. package/lib/composer/agent/run.d.ts +91 -0
  128. package/lib/composer/agent/run.js +212 -0
  129. package/lib/composer/agent/tools.d.ts +75 -0
  130. package/lib/composer/agent/tools.js +643 -0
  131. package/lib/composer/agent/world-run.d.ts +50 -0
  132. package/lib/composer/agent/world-run.js +295 -0
  133. package/lib/composer/agent/world-tools.d.ts +20 -0
  134. package/lib/composer/agent/world-tools.js +311 -0
  135. package/lib/composer/collision.d.ts +109 -0
  136. package/lib/composer/collision.js +339 -0
  137. package/lib/composer/contract-utils.d.ts +6 -0
  138. package/lib/composer/contract-utils.js +48 -0
  139. package/lib/composer/dsl.d.ts +52 -0
  140. package/lib/composer/dsl.js +147 -0
  141. package/lib/composer/ground.d.ts +22 -0
  142. package/lib/composer/ground.js +9 -0
  143. package/lib/composer/index.d.ts +19 -0
  144. package/lib/composer/index.js +19 -0
  145. package/lib/composer/layout.d.ts +106 -0
  146. package/lib/composer/layout.js +216 -0
  147. package/lib/composer/model.d.ts +298 -0
  148. package/lib/composer/model.js +441 -0
  149. package/lib/composer/overlap.d.ts +89 -0
  150. package/lib/composer/overlap.js +139 -0
  151. package/lib/composer/presentation.d.ts +123 -0
  152. package/lib/composer/presentation.js +256 -0
  153. package/lib/composer/reachability.d.ts +78 -0
  154. package/lib/composer/reachability.js +303 -0
  155. package/lib/composer/render-port.d.ts +293 -0
  156. package/lib/composer/render-port.js +172 -0
  157. package/lib/composer/terrain-runtime.d.ts +73 -0
  158. package/lib/composer/terrain-runtime.js +328 -0
  159. package/lib/composer/terrain.d.ts +43 -0
  160. package/lib/composer/terrain.js +49 -0
  161. package/lib/composer/world-document.d.ts +722 -0
  162. package/lib/composer/world-document.js +857 -0
  163. package/lib/composer/world-integration.d.ts +78 -0
  164. package/lib/composer/world-integration.js +452 -0
  165. package/lib/composer/world-package.d.ts +312 -0
  166. package/lib/composer/world-package.js +209 -0
  167. package/lib/contracts/asset.d.ts +270 -0
  168. package/lib/contracts/asset.js +1616 -0
  169. package/lib/contracts/breadth.d.ts +197 -0
  170. package/lib/contracts/breadth.js +631 -0
  171. package/lib/contracts/environment.d.ts +13 -0
  172. package/lib/contracts/environment.js +67 -0
  173. package/lib/contracts/index.d.ts +5 -0
  174. package/lib/contracts/index.js +5 -0
  175. package/lib/contracts/integration.d.ts +94 -0
  176. package/lib/contracts/integration.js +10 -0
  177. package/lib/contracts/requirements-migration.d.ts +61 -0
  178. package/lib/contracts/requirements-migration.js +401 -0
  179. package/lib/contracts/requirements.d.ts +815 -0
  180. package/lib/contracts/requirements.js +266 -0
  181. package/lib/contracts/semantic.d.ts +124 -0
  182. package/lib/contracts/semantic.js +421 -0
  183. package/lib/deform.d.ts +31 -0
  184. package/lib/deform.js +137 -0
  185. package/lib/deformation-normals.d.ts +3 -0
  186. package/lib/deformation-normals.js +72 -0
  187. package/lib/discovery/articulation-recipes.d.ts +6 -0
  188. package/lib/discovery/articulation-recipes.js +184 -0
  189. package/lib/discovery/catalog-schema.d.ts +175 -0
  190. package/lib/discovery/catalog-schema.js +118 -0
  191. package/lib/discovery/catalog.d.ts +25 -0
  192. package/lib/discovery/catalog.js +84 -0
  193. package/lib/discovery/construction-recipes.d.ts +7 -0
  194. package/lib/discovery/construction-recipes.js +236 -0
  195. package/lib/discovery/helper-contracts.d.ts +19 -0
  196. package/lib/discovery/helper-contracts.js +1940 -0
  197. package/lib/discovery/helper-specs.d.ts +40 -0
  198. package/lib/discovery/helper-specs.js +800 -0
  199. package/lib/discovery/index.d.ts +9 -0
  200. package/lib/discovery/index.js +20 -0
  201. package/lib/discovery/intents.d.ts +3 -0
  202. package/lib/discovery/intents.js +117 -0
  203. package/lib/discovery/lexical-index.d.ts +5 -0
  204. package/lib/discovery/lexical-index.js +67 -0
  205. package/lib/discovery/query-schema.d.ts +35 -0
  206. package/lib/discovery/query-schema.js +66 -0
  207. package/lib/discovery/recipes.d.ts +6 -0
  208. package/lib/discovery/recipes.js +191 -0
  209. package/lib/discovery/service.d.ts +57 -0
  210. package/lib/discovery/service.js +234 -0
  211. package/lib/discovery/shapes.d.ts +6 -0
  212. package/lib/discovery/shapes.js +65 -0
  213. package/lib/discovery/structural-recipes.d.ts +6 -0
  214. package/lib/discovery/structural-recipes.js +193 -0
  215. package/lib/discovery/surface-recipes.d.ts +7 -0
  216. package/lib/discovery/surface-recipes.js +124 -0
  217. package/lib/draw-diagnostics.d.ts +57 -0
  218. package/lib/draw-diagnostics.js +239 -0
  219. package/lib/edit-buffer.d.ts +86 -0
  220. package/lib/edit-buffer.js +99 -0
  221. package/lib/engine-identity.d.ts +44 -0
  222. package/lib/engine-identity.js +41 -0
  223. package/lib/evaluator/authoring-diagnostic.d.ts +38 -0
  224. package/lib/evaluator/authoring-diagnostic.js +128 -0
  225. package/lib/evaluator/handler.d.ts +14 -0
  226. package/lib/evaluator/handler.js +86 -0
  227. package/lib/evaluator/index.d.ts +4 -0
  228. package/lib/evaluator/index.js +4 -0
  229. package/lib/evaluator/isolation.d.ts +45 -0
  230. package/lib/evaluator/isolation.js +362 -0
  231. package/lib/evaluator/probe-invariants.d.ts +2 -0
  232. package/lib/evaluator/probe-invariants.js +7 -0
  233. package/lib/evaluator/probe-worker.d.ts +1 -0
  234. package/lib/evaluator/probe-worker.js +134 -0
  235. package/lib/evaluator/protocol.d.ts +105 -0
  236. package/lib/evaluator/protocol.js +512 -0
  237. package/lib/evaluator/source-check.d.ts +7 -0
  238. package/lib/evaluator/source-check.js +57 -0
  239. package/lib/evaluator/subprocess.d.ts +26 -0
  240. package/lib/evaluator/subprocess.js +212 -0
  241. package/lib/evaluator/transport-worker.d.mts +1 -0
  242. package/lib/evaluator/transport-worker.mjs +3 -0
  243. package/lib/evaluator/worker-urls.d.ts +6 -0
  244. package/lib/evaluator/worker-urls.js +12 -0
  245. package/lib/evaluator/worker.d.ts +1 -0
  246. package/lib/evaluator/worker.js +26 -0
  247. package/lib/exporter-node.d.ts +16 -0
  248. package/lib/exporter-node.js +58 -0
  249. package/lib/gears.d.ts +68 -0
  250. package/lib/gears.js +259 -0
  251. package/lib/geometry-budget.d.ts +15 -0
  252. package/lib/geometry-budget.js +36 -0
  253. package/lib/geometry-catalog.d.ts +5 -0
  254. package/lib/geometry-catalog.js +161 -0
  255. package/lib/geometry-export.d.ts +11 -0
  256. package/lib/geometry-export.js +160 -0
  257. package/lib/geometry.d.ts +46 -0
  258. package/lib/geometry.js +311 -0
  259. package/lib/gltf-io.d.ts +38 -0
  260. package/lib/gltf-io.js +95 -0
  261. package/lib/implicit.d.ts +20 -0
  262. package/lib/implicit.js +58 -0
  263. package/lib/index-policy.d.ts +13 -0
  264. package/lib/index-policy.js +41 -0
  265. package/lib/index.d.ts +5 -0
  266. package/lib/index.js +5 -0
  267. package/lib/inspect.d.ts +76 -0
  268. package/lib/inspect.js +206 -0
  269. package/lib/kit.d.ts +90 -0
  270. package/lib/kit.js +459 -0
  271. package/lib/live-review-node.d.ts +70 -0
  272. package/lib/live-review-node.js +961 -0
  273. package/lib/live-review.d.ts +65 -0
  274. package/lib/live-review.js +1 -0
  275. package/lib/local-runtime.d.ts +25 -0
  276. package/lib/local-runtime.js +272 -0
  277. package/lib/lod-export.d.ts +39 -0
  278. package/lib/lod-export.js +102 -0
  279. package/lib/lod.d.ts +56 -0
  280. package/lib/lod.js +138 -0
  281. package/lib/material-library-node.d.ts +56 -0
  282. package/lib/material-library-node.js +402 -0
  283. package/lib/material-library.d.ts +731 -0
  284. package/lib/material-library.js +425 -0
  285. package/lib/material-metrics.d.ts +72 -0
  286. package/lib/material-metrics.js +276 -0
  287. package/lib/material-presets.d.ts +29 -0
  288. package/lib/material-presets.js +181 -0
  289. package/lib/material-recipe-prompt.d.ts +23 -0
  290. package/lib/material-recipe-prompt.js +72 -0
  291. package/lib/material-recipe-runtime.d.ts +31 -0
  292. package/lib/material-recipe-runtime.js +91 -0
  293. package/lib/material-recipes.d.ts +131 -0
  294. package/lib/material-recipes.js +543 -0
  295. package/lib/material-resources.d.ts +181 -0
  296. package/lib/material-resources.js +416 -0
  297. package/lib/material-texture-library.generated.d.ts +1311 -0
  298. package/lib/material-texture-library.generated.js +11308 -0
  299. package/lib/metrics.d.ts +98 -0
  300. package/lib/metrics.js +210 -0
  301. package/lib/node-visibility.d.ts +31 -0
  302. package/lib/node-visibility.js +105 -0
  303. package/lib/open-shell.d.ts +21 -0
  304. package/lib/open-shell.js +83 -0
  305. package/lib/ops.d.ts +197 -0
  306. package/lib/ops.js +630 -0
  307. package/lib/palette-snap.d.ts +74 -0
  308. package/lib/palette-snap.js +151 -0
  309. package/lib/palette.d.ts +75 -0
  310. package/lib/palette.js +273 -0
  311. package/lib/portable-material-runtime.d.ts +14 -0
  312. package/lib/portable-material-runtime.js +81 -0
  313. package/lib/primitives.d.ts +483 -0
  314. package/lib/primitives.js +1553 -0
  315. package/lib/procedural-material-v2.d.ts +189 -0
  316. package/lib/procedural-material-v2.js +452 -0
  317. package/lib/procedural-texture.d.ts +90 -0
  318. package/lib/procedural-texture.js +306 -0
  319. package/lib/profile.d.ts +190 -0
  320. package/lib/profile.js +366 -0
  321. package/lib/program-store-node.d.ts +14 -0
  322. package/lib/program-store-node.js +137 -0
  323. package/lib/program-store.d.ts +43 -0
  324. package/lib/program-store.js +89 -0
  325. package/lib/project-bundle-node.d.ts +15 -0
  326. package/lib/project-bundle-node.js +175 -0
  327. package/lib/project-bundle.d.ts +65 -0
  328. package/lib/project-bundle.js +322 -0
  329. package/lib/projects-node.d.ts +17 -0
  330. package/lib/projects-node.js +227 -0
  331. package/lib/projects.d.ts +312 -0
  332. package/lib/projects.js +216 -0
  333. package/lib/prompt-api.d.ts +32 -0
  334. package/lib/prompt-api.js +223 -0
  335. package/lib/qa/architecture-corpus.d.ts +31 -0
  336. package/lib/qa/architecture-corpus.js +438 -0
  337. package/lib/qa/architecture-repairs.d.ts +17 -0
  338. package/lib/qa/architecture-repairs.js +73 -0
  339. package/lib/qa/architecture.d.ts +56 -0
  340. package/lib/qa/architecture.js +985 -0
  341. package/lib/qa/benchmark-fixtures.d.ts +42 -0
  342. package/lib/qa/benchmark-fixtures.js +191 -0
  343. package/lib/qa/breadth-corpus.d.ts +17 -0
  344. package/lib/qa/breadth-corpus.js +125 -0
  345. package/lib/qa/breadth-evidence.d.ts +18 -0
  346. package/lib/qa/breadth-evidence.js +514 -0
  347. package/lib/qa/breadth-final.d.ts +46 -0
  348. package/lib/qa/breadth-final.js +359 -0
  349. package/lib/qa/breadth.d.ts +66 -0
  350. package/lib/qa/breadth.js +539 -0
  351. package/lib/qa/character-advisory.d.ts +7 -0
  352. package/lib/qa/character-advisory.js +488 -0
  353. package/lib/qa/character-repairs.d.ts +11 -0
  354. package/lib/qa/character-repairs.js +68 -0
  355. package/lib/qa/character-vehicle-corpus.d.ts +32 -0
  356. package/lib/qa/character-vehicle-corpus.js +525 -0
  357. package/lib/qa/character.d.ts +66 -0
  358. package/lib/qa/character.js +712 -0
  359. package/lib/qa/corpus.d.ts +185 -0
  360. package/lib/qa/corpus.js +396 -0
  361. package/lib/qa/environment.d.ts +44 -0
  362. package/lib/qa/environment.js +725 -0
  363. package/lib/qa/geometry-relations.d.ts +94 -0
  364. package/lib/qa/geometry-relations.js +552 -0
  365. package/lib/qa/gltf.d.ts +29 -0
  366. package/lib/qa/gltf.js +66 -0
  367. package/lib/qa/index.d.ts +32 -0
  368. package/lib/qa/index.js +34 -0
  369. package/lib/qa/lod.d.ts +14 -0
  370. package/lib/qa/lod.js +25 -0
  371. package/lib/qa/material.d.ts +5 -0
  372. package/lib/qa/material.js +223 -0
  373. package/lib/qa/part-connectivity.d.ts +73 -0
  374. package/lib/qa/part-connectivity.js +216 -0
  375. package/lib/qa/prop-environment-prompt.d.ts +6 -0
  376. package/lib/qa/prop-environment-prompt.js +43 -0
  377. package/lib/qa/prop-helper-policy.d.ts +37 -0
  378. package/lib/qa/prop-helper-policy.js +65 -0
  379. package/lib/qa/prop.d.ts +44 -0
  380. package/lib/qa/prop.js +702 -0
  381. package/lib/qa/reference-comparison.d.ts +183 -0
  382. package/lib/qa/reference-comparison.js +420 -0
  383. package/lib/qa/registry.d.ts +159 -0
  384. package/lib/qa/registry.js +406 -0
  385. package/lib/qa/requirements-applicability.d.ts +33 -0
  386. package/lib/qa/requirements-applicability.js +189 -0
  387. package/lib/qa/requirements-report.d.ts +38 -0
  388. package/lib/qa/requirements-report.js +202 -0
  389. package/lib/qa/requirements-run.d.ts +20 -0
  390. package/lib/qa/requirements-run.js +370 -0
  391. package/lib/qa/run.d.ts +54 -0
  392. package/lib/qa/run.js +240 -0
  393. package/lib/qa/self-intersection.d.ts +157 -0
  394. package/lib/qa/self-intersection.js +633 -0
  395. package/lib/qa/sweep.d.ts +4 -0
  396. package/lib/qa/sweep.js +65 -0
  397. package/lib/qa/types.d.ts +90 -0
  398. package/lib/qa/types.js +81 -0
  399. package/lib/qa/universal.d.ts +14 -0
  400. package/lib/qa/universal.js +398 -0
  401. package/lib/qa/vegetation-corpus.d.ts +18 -0
  402. package/lib/qa/vegetation-corpus.js +304 -0
  403. package/lib/qa/vegetation.d.ts +133 -0
  404. package/lib/qa/vegetation.js +933 -0
  405. package/lib/qa/vehicle-advisory.d.ts +8 -0
  406. package/lib/qa/vehicle-advisory.js +395 -0
  407. package/lib/qa/vehicle.d.ts +19 -0
  408. package/lib/qa/vehicle.js +594 -0
  409. package/lib/rebuild-options.d.ts +31 -0
  410. package/lib/rebuild-options.js +13 -0
  411. package/lib/render-capabilities.d.ts +25 -0
  412. package/lib/render-capabilities.js +100 -0
  413. package/lib/render-service-client.d.ts +59 -0
  414. package/lib/render-service-client.js +194 -0
  415. package/lib/render-service-host.d.ts +44 -0
  416. package/lib/render-service-host.js +236 -0
  417. package/lib/render-service-windows.d.ts +12 -0
  418. package/lib/render-service-windows.js +72 -0
  419. package/lib/render.d.ts +565 -0
  420. package/lib/render.js +1978 -0
  421. package/lib/requirements-assets.d.ts +6 -0
  422. package/lib/requirements-assets.js +33 -0
  423. package/lib/requirements-context.d.ts +1658 -0
  424. package/lib/requirements-context.js +126 -0
  425. package/lib/requirements-identity.d.ts +3 -0
  426. package/lib/requirements-identity.js +38 -0
  427. package/lib/requirements-json.d.ts +12 -0
  428. package/lib/requirements-json.js +58 -0
  429. package/lib/requirements-store.d.ts +42 -0
  430. package/lib/requirements-store.js +86 -0
  431. package/lib/requirements.d.ts +4 -0
  432. package/lib/requirements.js +4 -0
  433. package/lib/revision-comparison.d.ts +85 -0
  434. package/lib/revision-comparison.js +334 -0
  435. package/lib/rig-export.d.ts +5 -0
  436. package/lib/rig-export.js +19 -0
  437. package/lib/rigid-merge.d.ts +90 -0
  438. package/lib/rigid-merge.js +585 -0
  439. package/lib/runtime-identity.d.ts +20 -0
  440. package/lib/runtime-identity.js +193 -0
  441. package/lib/solids.d.ts +29 -0
  442. package/lib/solids.js +523 -0
  443. package/lib/source-bindings.d.ts +6 -0
  444. package/lib/source-bindings.js +96 -0
  445. package/lib/source-runtime-checks.d.ts +14 -0
  446. package/lib/source-runtime-checks.js +169 -0
  447. package/lib/sweep-analysis.d.ts +22 -0
  448. package/lib/sweep-analysis.js +109 -0
  449. package/lib/sweep.d.ts +34 -0
  450. package/lib/sweep.js +347 -0
  451. package/lib/texture-bake.d.ts +125 -0
  452. package/lib/texture-bake.js +395 -0
  453. package/lib/texture-contract.d.ts +3 -0
  454. package/lib/texture-contract.js +10 -0
  455. package/lib/texture-resolver.d.ts +22 -0
  456. package/lib/texture-resolver.js +28 -0
  457. package/lib/textures.d.ts +90 -0
  458. package/lib/textures.js +240 -0
  459. package/lib/tools/actions.d.ts +35 -0
  460. package/lib/tools/actions.js +69 -0
  461. package/lib/tools/capture-input.d.ts +373 -0
  462. package/lib/tools/capture-input.js +193 -0
  463. package/lib/tools/discovery.d.ts +3 -0
  464. package/lib/tools/discovery.js +177 -0
  465. package/lib/tools/materials.d.ts +29 -0
  466. package/lib/tools/materials.js +168 -0
  467. package/lib/tools/preview-fidelity.d.ts +6 -0
  468. package/lib/tools/preview-fidelity.js +23 -0
  469. package/lib/tools/program-artifacts.d.ts +30 -0
  470. package/lib/tools/program-artifacts.js +76 -0
  471. package/lib/tools/programs.d.ts +9 -0
  472. package/lib/tools/programs.js +151 -0
  473. package/lib/tools/projects.d.ts +27 -0
  474. package/lib/tools/projects.js +98 -0
  475. package/lib/tools/registry.d.ts +792 -0
  476. package/lib/tools/registry.js +2123 -0
  477. package/lib/tools/review-detail.d.ts +106 -0
  478. package/lib/tools/review-detail.js +740 -0
  479. package/lib/tools/review.d.ts +40 -0
  480. package/lib/tools/review.js +230 -0
  481. package/lib/tools/skill-resource.d.ts +8 -0
  482. package/lib/tools/skill-resource.js +1 -0
  483. package/lib/tools/workspace.d.ts +8 -0
  484. package/lib/tools/workspace.js +87 -0
  485. package/lib/user-data-extras.d.ts +40 -0
  486. package/lib/user-data-extras.js +158 -0
  487. package/lib/user-data-validation.d.ts +18 -0
  488. package/lib/user-data-validation.js +201 -0
  489. package/lib/uv-project.d.ts +14 -0
  490. package/lib/uv-project.js +145 -0
  491. package/lib/uv-shapes.d.ts +11 -0
  492. package/lib/uv-shapes.js +48 -0
  493. package/lib/uv.d.ts +39 -0
  494. package/lib/uv.js +216 -0
  495. package/lib/validation.d.ts +90 -0
  496. package/lib/validation.js +1099 -0
  497. package/lib/vehicle.d.ts +128 -0
  498. package/lib/vehicle.js +487 -0
  499. package/lib/views/annotate.d.ts +41 -0
  500. package/lib/views/annotate.js +131 -0
  501. package/lib/views/architecture.d.ts +22 -0
  502. package/lib/views/architecture.js +55 -0
  503. package/lib/views/background.d.ts +54 -0
  504. package/lib/views/background.js +65 -0
  505. package/lib/views/camera-capture.d.ts +15 -0
  506. package/lib/views/camera-capture.js +101 -0
  507. package/lib/views/camera.d.ts +106 -0
  508. package/lib/views/camera.js +488 -0
  509. package/lib/views/capture-cache.d.ts +48 -0
  510. package/lib/views/capture-cache.js +239 -0
  511. package/lib/views/capture-limits.d.ts +19 -0
  512. package/lib/views/capture-limits.js +61 -0
  513. package/lib/views/capture.d.ts +124 -0
  514. package/lib/views/capture.js +210 -0
  515. package/lib/views/character-capture.d.ts +20 -0
  516. package/lib/views/character-capture.js +180 -0
  517. package/lib/views/character.d.ts +64 -0
  518. package/lib/views/character.js +176 -0
  519. package/lib/views/diagnostic.d.ts +90 -0
  520. package/lib/views/diagnostic.js +627 -0
  521. package/lib/views/evidence-history.d.ts +13 -0
  522. package/lib/views/evidence-history.js +131 -0
  523. package/lib/views/glb.d.ts +36 -0
  524. package/lib/views/glb.js +524 -0
  525. package/lib/views/grid.d.ts +46 -0
  526. package/lib/views/grid.js +81 -0
  527. package/lib/views/index.d.ts +257 -0
  528. package/lib/views/index.js +624 -0
  529. package/lib/views/inspect.d.ts +110 -0
  530. package/lib/views/inspect.js +195 -0
  531. package/lib/views/lod.d.ts +30 -0
  532. package/lib/views/lod.js +79 -0
  533. package/lib/views/measurement.d.ts +47 -0
  534. package/lib/views/measurement.js +60 -0
  535. package/lib/views/part-placement.d.ts +25 -0
  536. package/lib/views/part-placement.js +26 -0
  537. package/lib/views/png.d.ts +23 -0
  538. package/lib/views/png.js +162 -0
  539. package/lib/views/port.d.ts +74 -0
  540. package/lib/views/port.js +250 -0
  541. package/lib/views/pose.d.ts +113 -0
  542. package/lib/views/pose.js +433 -0
  543. package/lib/views/raster.d.ts +164 -0
  544. package/lib/views/raster.js +508 -0
  545. package/lib/views/renderer-id.d.ts +11 -0
  546. package/lib/views/renderer-id.js +29 -0
  547. package/lib/views/scene-raster.d.ts +41 -0
  548. package/lib/views/scene-raster.js +84 -0
  549. package/lib/views/surface-distance.d.ts +51 -0
  550. package/lib/views/surface-distance.js +343 -0
  551. package/lib/views/vehicle.d.ts +57 -0
  552. package/lib/views/vehicle.js +288 -0
  553. package/lib/wall-panels.d.ts +38 -0
  554. package/lib/wall-panels.js +99 -0
  555. package/lib/widget-transfer.d.ts +12 -0
  556. package/lib/widget-transfer.js +117 -0
  557. package/lib/workspace-location.d.ts +6 -0
  558. package/lib/workspace-location.js +16 -0
  559. package/lib/workspace-node.d.ts +15 -0
  560. package/lib/workspace-node.js +74 -0
  561. package/lib/workspace.d.ts +26 -0
  562. package/lib/workspace.js +22 -0
  563. package/lib/written-nodes.d.ts +10 -0
  564. package/lib/written-nodes.js +32 -0
  565. package/package.json +385 -4
  566. package/plugin.json +16 -0
  567. package/plugins/kiln-engine/.claude-plugin/plugin.json +18 -0
  568. package/plugins/kiln-engine/LICENSE +21 -0
  569. package/plugins/kiln-engine/README.md +25 -0
  570. package/plugins/kiln-engine/package-provenance.json +15 -0
  571. package/plugins/kiln-engine/plugin.json +38 -0
  572. package/plugins/kiln-engine/runtime.json +4 -0
  573. package/plugins/kiln-engine/scripts/setup-workspace.mjs +337 -0
  574. package/plugins/kiln-engine/skills/kiln-setup-workspace/SKILL.md +108 -0
  575. package/plugins/kiln-engine/skills/kiln-setup-workspace/references/plugin-install.md +66 -0
  576. package/render-service/README.md +139 -0
  577. package/render-service/package-lock.json +72 -0
  578. package/render-service/package.json +22 -0
  579. package/render-service/src/backdrops.mjs +26 -0
  580. package/render-service/src/bind-policy.mjs +80 -0
  581. package/render-service/src/build-identity.mjs +83 -0
  582. package/render-service/src/cache-identity.mjs +65 -0
  583. package/render-service/src/contract.mjs +494 -0
  584. package/render-service/src/display-output.mjs +18 -0
  585. package/render-service/src/display-transform.mjs +192 -0
  586. package/render-service/src/framing.mjs +114 -0
  587. package/render-service/src/glb-input.mjs +259 -0
  588. package/render-service/src/gpu.mjs +101 -0
  589. package/render-service/src/health-contract.mjs +40 -0
  590. package/render-service/src/instance.mjs +146 -0
  591. package/render-service/src/node-visibility.mjs +49 -0
  592. package/render-service/src/png-input.mjs +77 -0
  593. package/render-service/src/presentation-presets.mjs +216 -0
  594. package/render-service/src/readback.mjs +16 -0
  595. package/render-service/src/register-hooks.mjs +2 -0
  596. package/render-service/src/render-queue.mjs +144 -0
  597. package/render-service/src/renderer.mjs +473 -0
  598. package/render-service/src/request-limits.mjs +72 -0
  599. package/render-service/src/review-tone-mapping.mjs +55 -0
  600. package/render-service/src/server.mjs +387 -0
  601. package/render-service/src/three-alias-hooks.mjs +6 -0
  602. package/scripts/create-workspace.mjs +808 -0
  603. package/scripts/package-plugin.mjs +67 -0
  604. package/skills/kiln-author-asset/SKILL.md +104 -0
  605. package/skills/kiln-author-asset/references/camera-cli.md +33 -0
  606. package/skills/kiln-author-asset/references/camera-recipes.md +73 -0
  607. package/skills/kiln-author-asset/references/export-profiles.md +216 -0
  608. package/skills/kiln-author-asset/references/geometry-recipes.md +329 -0
  609. package/skills/kiln-author-asset/references/program-contract.md +44 -0
  610. package/skills/kiln-author-asset/references/projects-and-materials.md +158 -0
  611. package/skills/kiln-author-asset/references/reusable-frame.kiln.js +32 -0
  612. package/skills/kiln-batch-dispatch/SKILL.md +22 -0
  613. package/skills/kiln-batch-dispatch/references/clean-room-evaluation.md +35 -0
  614. package/skills/kiln-compose-scene/SKILL.md +38 -0
  615. package/skills/kiln-compose-scene/references/composition-api.md +29 -0
  616. package/skills/kiln-compose-scene/references/runtime-scenes.md +363 -0
  617. package/skills/kiln-qa-asset/SKILL.md +99 -0
  618. package/skills/kiln-qa-asset/references/engine-handoff.md +246 -0
  619. package/skills/kiln-qa-asset/references/integration-checks.md +96 -0
  620. package/skills/kiln-refine-asset/SKILL.md +136 -0
  621. package/skills/kiln-refine-asset/references/revision-and-views.md +205 -0
  622. package/skills/kiln-refine-asset/references/revision-cli.md +39 -0
  623. package/skills/kiln-setup-workspace/SKILL.md +108 -0
  624. package/skills/kiln-setup-workspace/references/plugin-install.md +66 -0
  625. package/src/agent/call-budget.ts +106 -0
  626. package/src/agent/compaction.ts +197 -0
  627. package/src/agent/concurrency.ts +30 -0
  628. package/src/agent/diff.ts +141 -0
  629. package/src/agent/generate.ts +448 -0
  630. package/src/agent/hooks.ts +204 -0
  631. package/src/agent/index.ts +114 -0
  632. package/src/agent/native-prompt.ts +51 -0
  633. package/src/agent/native-workflow.ts +33 -0
  634. package/src/agent/providers.ts +529 -0
  635. package/src/agent/run.ts +356 -0
  636. package/src/agent/skill-resources.ts +198 -0
  637. package/src/agent/split-tool-result-images.ts +80 -0
  638. package/src/agent/stream-start.ts +61 -0
  639. package/src/agent/tools.ts +113 -0
  640. package/src/agent/view-render-timeout.ts +110 -0
  641. package/src/animation-cli.ts +251 -0
  642. package/src/animation-cubic.ts +57 -0
  643. package/src/animation-spline.ts +183 -0
  644. package/src/architecture.ts +871 -0
  645. package/src/arena/adaptive.ts +119 -0
  646. package/src/arena/bradley-terry.ts +221 -0
  647. package/src/arena/index.ts +21 -0
  648. package/src/assembly-transform.ts +34 -0
  649. package/src/assembly.ts +762 -0
  650. package/src/asset-catalog.ts +30 -0
  651. package/src/asset-cli.ts +505 -0
  652. package/src/asset-export.ts +141 -0
  653. package/src/asset-materials-node.ts +78 -0
  654. package/src/asset-viewer.ts +160 -0
  655. package/src/asset-widget-uri.ts +9 -0
  656. package/src/asset-widget.ts +38 -0
  657. package/src/assets-node.ts +412 -0
  658. package/src/assets-resources.ts +137 -0
  659. package/src/assets.ts +252 -0
  660. package/src/atomic-directory.ts +45 -0
  661. package/src/authoring-style.ts +49 -0
  662. package/src/breadth-prompt.ts +79 -0
  663. package/src/build-cache-node.ts +99 -0
  664. package/src/build-cache.ts +253 -0
  665. package/src/character.ts +776 -0
  666. package/src/cli-json.ts +29 -0
  667. package/src/cli-output.ts +135 -0
  668. package/src/cli-render-mode.ts +270 -0
  669. package/src/cli.ts +809 -0
  670. package/src/community-exporter.ts +268 -0
  671. package/src/composer/agent/compaction.ts +146 -0
  672. package/src/composer/agent/index.ts +41 -0
  673. package/src/composer/agent/prompt.ts +126 -0
  674. package/src/composer/agent/run.ts +317 -0
  675. package/src/composer/agent/tools.ts +847 -0
  676. package/src/composer/agent/world-run.ts +421 -0
  677. package/src/composer/agent/world-tools.ts +411 -0
  678. package/src/composer/collision.ts +448 -0
  679. package/src/composer/contract-utils.ts +52 -0
  680. package/src/composer/dsl.ts +196 -0
  681. package/src/composer/ground.ts +27 -0
  682. package/src/composer/index.ts +19 -0
  683. package/src/composer/layout.ts +357 -0
  684. package/src/composer/model.ts +688 -0
  685. package/src/composer/overlap.ts +220 -0
  686. package/src/composer/presentation.ts +309 -0
  687. package/src/composer/reachability.ts +407 -0
  688. package/src/composer/render-port.ts +516 -0
  689. package/src/composer/terrain-runtime.ts +415 -0
  690. package/src/composer/terrain.ts +53 -0
  691. package/src/composer/world-document.ts +1019 -0
  692. package/src/composer/world-integration.ts +589 -0
  693. package/src/composer/world-package.ts +255 -0
  694. package/src/contracts/asset.ts +2030 -0
  695. package/src/contracts/breadth.ts +888 -0
  696. package/src/contracts/environment.ts +81 -0
  697. package/src/contracts/index.ts +5 -0
  698. package/src/contracts/integration.ts +100 -0
  699. package/src/contracts/requirements-migration.ts +536 -0
  700. package/src/contracts/requirements.ts +357 -0
  701. package/src/contracts/semantic.ts +544 -0
  702. package/src/deform.ts +215 -0
  703. package/src/deformation-normals.ts +84 -0
  704. package/src/direct-entry.ts +28 -0
  705. package/src/discovery/articulation-recipes.ts +190 -0
  706. package/src/discovery/catalog-schema.ts +125 -0
  707. package/src/discovery/catalog.ts +88 -0
  708. package/src/discovery/construction-recipes.ts +243 -0
  709. package/src/discovery/helper-contracts.ts +2388 -0
  710. package/src/discovery/helper-specs.ts +1046 -0
  711. package/src/discovery/index.ts +36 -0
  712. package/src/discovery/intents.ts +120 -0
  713. package/src/discovery/lexical-index.ts +72 -0
  714. package/src/discovery/query-schema.ts +71 -0
  715. package/src/discovery/recipes.ts +215 -0
  716. package/src/discovery/service.ts +329 -0
  717. package/src/discovery/shapes.ts +120 -0
  718. package/src/discovery/structural-recipes.ts +199 -0
  719. package/src/discovery/surface-recipes.ts +130 -0
  720. package/src/discovery-cli.ts +106 -0
  721. package/src/draw-diagnostics.ts +313 -0
  722. package/src/edit-buffer.ts +134 -0
  723. package/src/edit-cli.ts +125 -0
  724. package/src/engine-identity.ts +50 -0
  725. package/src/evaluator/authoring-diagnostic.ts +168 -0
  726. package/src/evaluator/handler.ts +114 -0
  727. package/src/evaluator/index.ts +50 -0
  728. package/src/evaluator/isolation.ts +454 -0
  729. package/src/evaluator/probe-invariants.ts +9 -0
  730. package/src/evaluator/probe-worker.ts +158 -0
  731. package/src/evaluator/protocol.ts +701 -0
  732. package/src/evaluator/source-check.ts +64 -0
  733. package/src/evaluator/subprocess.ts +294 -0
  734. package/src/evaluator/transport-worker.mjs +8 -0
  735. package/src/evaluator/worker-urls.ts +16 -0
  736. package/src/evaluator/worker.ts +26 -0
  737. package/src/experiments/geometry-acceptance.ts +278 -0
  738. package/src/experiments/geometry-frontier.ts +181 -0
  739. package/src/experiments/program-aliases.ts +96 -0
  740. package/src/experiments/run-w6-material-vegetation.ts +7 -0
  741. package/src/experiments/run-w7-vegetation-lod.ts +6 -0
  742. package/src/experiments/w6-material-vegetation.ts +772 -0
  743. package/src/experiments/w7-vegetation-lod.ts +335 -0
  744. package/src/exporter-node.ts +68 -0
  745. package/src/gears.ts +334 -0
  746. package/src/generated/mcp-manifest.json +2050 -0
  747. package/src/geometry-budget.ts +50 -0
  748. package/src/geometry-catalog.ts +215 -0
  749. package/src/geometry-export.ts +193 -0
  750. package/src/geometry.ts +379 -0
  751. package/src/gltf-io.ts +129 -0
  752. package/src/gltf-validator-shims.d.ts +12 -0
  753. package/src/implicit.ts +106 -0
  754. package/src/index-policy.ts +52 -0
  755. package/src/index.ts +5 -0
  756. package/src/inspect-cli.ts +198 -0
  757. package/src/inspect.ts +290 -0
  758. package/src/kit.ts +547 -0
  759. package/src/live-review-node.ts +1011 -0
  760. package/src/live-review.ts +55 -0
  761. package/src/local-runtime.ts +336 -0
  762. package/src/lod-export.ts +120 -0
  763. package/src/lod.ts +192 -0
  764. package/src/loopback.ts +6 -0
  765. package/src/material-library-node.ts +488 -0
  766. package/src/material-library.ts +481 -0
  767. package/src/material-metrics.ts +392 -0
  768. package/src/material-presets.ts +207 -0
  769. package/src/material-recipe-prompt.ts +99 -0
  770. package/src/material-recipe-runtime.ts +162 -0
  771. package/src/material-recipes.ts +748 -0
  772. package/src/material-resources.ts +640 -0
  773. package/src/material-texture-library.generated.ts +11408 -0
  774. package/src/mcp-core.ts +437 -0
  775. package/src/mcp-engine.ts +383 -0
  776. package/src/mcp-manifest.ts +104 -0
  777. package/src/mcp-server.ts +118 -0
  778. package/src/metrics.ts +277 -0
  779. package/src/migration-cli.ts +105 -0
  780. package/src/migration-rebuild.ts +265 -0
  781. package/src/node-visibility.ts +116 -0
  782. package/src/open-shell.ts +104 -0
  783. package/src/ops.ts +751 -0
  784. package/src/palette-snap.ts +209 -0
  785. package/src/palette.ts +307 -0
  786. package/src/portable-material-runtime.ts +109 -0
  787. package/src/primitives.ts +2299 -0
  788. package/src/procedural-material-v2.ts +720 -0
  789. package/src/procedural-texture.ts +427 -0
  790. package/src/profile.ts +485 -0
  791. package/src/program-store-node.ts +138 -0
  792. package/src/program-store.ts +116 -0
  793. package/src/project-bundle-node.ts +220 -0
  794. package/src/project-bundle.ts +392 -0
  795. package/src/project-cli.ts +248 -0
  796. package/src/projects-node.ts +270 -0
  797. package/src/projects.ts +274 -0
  798. package/src/prompt-api.ts +231 -0
  799. package/src/qa/architecture-corpus.ts +828 -0
  800. package/src/qa/architecture-repairs.ts +92 -0
  801. package/src/qa/architecture.ts +1358 -0
  802. package/src/qa/benchmark-fixtures.ts +441 -0
  803. package/src/qa/breadth-corpus.ts +168 -0
  804. package/src/qa/breadth-evidence.ts +595 -0
  805. package/src/qa/breadth-final.ts +453 -0
  806. package/src/qa/breadth.ts +690 -0
  807. package/src/qa/character-advisory.ts +596 -0
  808. package/src/qa/character-repairs.ts +185 -0
  809. package/src/qa/character-vehicle-corpus.ts +802 -0
  810. package/src/qa/character.ts +998 -0
  811. package/src/qa/corpus.ts +499 -0
  812. package/src/qa/environment.ts +903 -0
  813. package/src/qa/geometry-relations.ts +843 -0
  814. package/src/qa/gltf.ts +103 -0
  815. package/src/qa/index.ts +34 -0
  816. package/src/qa/lod.ts +28 -0
  817. package/src/qa/material.ts +302 -0
  818. package/src/qa/part-connectivity.ts +252 -0
  819. package/src/qa/prop-environment-prompt.ts +58 -0
  820. package/src/qa/prop-helper-policy.ts +118 -0
  821. package/src/qa/prop.ts +828 -0
  822. package/src/qa/reference-comparison.ts +495 -0
  823. package/src/qa/registry.ts +604 -0
  824. package/src/qa/requirements-applicability.ts +241 -0
  825. package/src/qa/requirements-report.ts +280 -0
  826. package/src/qa/requirements-run.ts +470 -0
  827. package/src/qa/run.ts +329 -0
  828. package/src/qa/self-intersection.ts +741 -0
  829. package/src/qa/sweep.ts +68 -0
  830. package/src/qa/types.ts +187 -0
  831. package/src/qa/universal.ts +506 -0
  832. package/src/qa/vegetation-corpus.ts +404 -0
  833. package/src/qa/vegetation.ts +1174 -0
  834. package/src/qa/vehicle-advisory.ts +468 -0
  835. package/src/qa/vehicle.ts +756 -0
  836. package/src/rebuild-options.ts +16 -0
  837. package/src/render-capabilities.ts +140 -0
  838. package/src/render-service-client.ts +297 -0
  839. package/src/render-service-host.ts +278 -0
  840. package/src/render-service-windows.ts +80 -0
  841. package/src/render.ts +2748 -0
  842. package/src/requirements-assets.ts +53 -0
  843. package/src/requirements-context.ts +170 -0
  844. package/src/requirements-file.ts +10 -0
  845. package/src/requirements-identity.ts +43 -0
  846. package/src/requirements-json.ts +65 -0
  847. package/src/requirements-store.ts +150 -0
  848. package/src/requirements.ts +4 -0
  849. package/src/review-cli.ts +81 -0
  850. package/src/revision-comparison.ts +425 -0
  851. package/src/rig-export.ts +29 -0
  852. package/src/rigid-merge.ts +691 -0
  853. package/src/runtime-identity.ts +207 -0
  854. package/src/runtime-support.mjs +20 -0
  855. package/src/service-cli.ts +210 -0
  856. package/src/solids.ts +650 -0
  857. package/src/source-bindings.ts +90 -0
  858. package/src/source-runtime-checks.ts +189 -0
  859. package/src/sweep-analysis.ts +168 -0
  860. package/src/sweep.ts +482 -0
  861. package/src/texture-bake.ts +526 -0
  862. package/src/texture-contract.ts +12 -0
  863. package/src/texture-resolver.ts +61 -0
  864. package/src/textures.ts +345 -0
  865. package/src/tools/actions.ts +124 -0
  866. package/src/tools/capture-input.ts +233 -0
  867. package/src/tools/discovery.ts +194 -0
  868. package/src/tools/materials.ts +206 -0
  869. package/src/tools/preview-fidelity.ts +31 -0
  870. package/src/tools/program-artifacts.ts +107 -0
  871. package/src/tools/programs.ts +190 -0
  872. package/src/tools/projects.ts +141 -0
  873. package/src/tools/registry.ts +3111 -0
  874. package/src/tools/review-detail.ts +788 -0
  875. package/src/tools/review.ts +269 -0
  876. package/src/tools/skill-resource.ts +9 -0
  877. package/src/tools/workspace.ts +112 -0
  878. package/src/user-data-extras.ts +181 -0
  879. package/src/user-data-validation.ts +211 -0
  880. package/src/uv-project.ts +185 -0
  881. package/src/uv-shapes.ts +63 -0
  882. package/src/uv.ts +301 -0
  883. package/src/validation.ts +1257 -0
  884. package/src/vegetation-prompt.ts +109 -0
  885. package/src/vehicle.ts +742 -0
  886. package/src/viewer/app.ts +666 -0
  887. package/src/viewer/attribution.ts +17 -0
  888. package/src/viewer/camera-state.ts +45 -0
  889. package/src/viewer/chat-app.ts +221 -0
  890. package/src/viewer/chat-download.ts +53 -0
  891. package/src/viewer/dashboard.ts +59 -0
  892. package/src/viewer/deep-link.ts +34 -0
  893. package/src/viewer/dom.ts +63 -0
  894. package/src/viewer/framing.ts +55 -0
  895. package/src/viewer/library-state.ts +34 -0
  896. package/src/viewer/live-panel.ts +742 -0
  897. package/src/viewer/live-state.ts +99 -0
  898. package/src/viewer/load-guard.ts +10 -0
  899. package/src/viewer/lod-control.ts +98 -0
  900. package/src/viewer/lod.ts +172 -0
  901. package/src/viewer/material-preset-form.ts +34 -0
  902. package/src/viewer/materials-panel.ts +335 -0
  903. package/src/viewer/membership-panel.ts +122 -0
  904. package/src/viewer/navigation.ts +9 -0
  905. package/src/viewer/performance-control.ts +75 -0
  906. package/src/viewer/performance.ts +61 -0
  907. package/src/viewer/project-editor.ts +37 -0
  908. package/src/viewer/project-membership.ts +20 -0
  909. package/src/viewer/projects-panel.ts +374 -0
  910. package/src/viewer/scene.ts +426 -0
  911. package/src/viewer/visibility.ts +73 -0
  912. package/src/views/annotate.ts +138 -0
  913. package/src/views/architecture.ts +77 -0
  914. package/src/views/background.ts +87 -0
  915. package/src/views/camera-capture.ts +143 -0
  916. package/src/views/camera.ts +622 -0
  917. package/src/views/capture-cache.ts +272 -0
  918. package/src/views/capture-limits.ts +73 -0
  919. package/src/views/capture.ts +300 -0
  920. package/src/views/character-capture.ts +279 -0
  921. package/src/views/character.ts +267 -0
  922. package/src/views/diagnostic.ts +853 -0
  923. package/src/views/evidence-history.ts +166 -0
  924. package/src/views/glb.ts +747 -0
  925. package/src/views/grid.ts +118 -0
  926. package/src/views/index.ts +1071 -0
  927. package/src/views/inspect.ts +296 -0
  928. package/src/views/lod.ts +117 -0
  929. package/src/views/measurement.ts +69 -0
  930. package/src/views/part-placement.ts +50 -0
  931. package/src/views/png.ts +175 -0
  932. package/src/views/port.ts +335 -0
  933. package/src/views/pose.ts +541 -0
  934. package/src/views/raster.ts +627 -0
  935. package/src/views/renderer-id.ts +31 -0
  936. package/src/views/scene-raster.ts +139 -0
  937. package/src/views/surface-distance.ts +403 -0
  938. package/src/views/vehicle.ts +431 -0
  939. package/src/wall-panels.ts +127 -0
  940. package/src/widget-transfer.ts +115 -0
  941. package/src/workspace-cli.ts +41 -0
  942. package/src/workspace-http.ts +219 -0
  943. package/src/workspace-location.ts +20 -0
  944. package/src/workspace-node.ts +91 -0
  945. package/src/workspace.ts +43 -0
  946. package/src/written-nodes.ts +39 -0
  947. package/src/xatlasjs-shims.d.ts +16 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,2579 @@
1
+ # Changelog
2
+
3
+ Changes to `@instruktlabs/kiln` (previously the private `@kiln/engine` package).
4
+ Source releases and installable package publication are separate milestones.
5
+ Published versions and tags are recorded by the npm registry; changing this file
6
+ does not publish a package.
7
+
8
+ ## 1.0.0
9
+
10
+ Changes since the 0.10.0 tagged package release, qualified first through the public
11
+ `1.0.0-rc.1` prerelease. Promotion of the stable archive to npm `latest` requires
12
+ the separate release gates in [the runbook](docs/releasing.md).
13
+ Hosted deployment and vendor directory acceptance remain separate milestones.
14
+
15
+ - **Breaking:** the npm identity is now `@instruktlabs/kiln`, with compiled ESM and
16
+ TypeScript declarations for its public subpaths. The root is a library entrypoint;
17
+ use the `kiln` executable for the CLI. `kiln-mcp` starts the stdio MCP server.
18
+ - Consumer archives contain the maintained installation/API/workflow documentation;
19
+ historical plans, reviews and model traces remain in the repository. Current
20
+ repository links and package publisher metadata point to Instrukt Labs.
21
+ - Installed isolated evaluation resolves compiled workers and readiness probes
22
+ without a TypeScript loader. Host isolation still requires its own readiness proof.
23
+ - The stdio executable cancels pending engine warmup when its client disconnects;
24
+ a tool-list-only session no longer starts unused engine work after closing stdin.
25
+ - Isolated evaluation explicitly requires a user namespace before disabling nested
26
+ namespaces, satisfying Bubblewrap's launch precondition. Unsupported hosts still
27
+ fail closed before source execution.
28
+ - Windows asset saves retry transient directory-rename denials within a bounded
29
+ delay, preserving atomic publication and existing immutable revisions.
30
+ - Source restoration, MCP source resources, reviewed saves and CLI rebuild
31
+ observations preserve an authored UTF-8 BOM instead of silently changing the
32
+ source and its program reference. Invalid source UTF-8 is rejected on those reads.
33
+ - Custom asset-library hosts can reuse the canonical saved-material resolver
34
+ through `@instruktlabs/kiln/assets/node`.
35
+ - The `kiln-engine` local plugin uses the Instrukt Labs marketplace and installs a
36
+ pinned engine outside its disposable cache. It registers setup only; managed
37
+ workspaces own the authoring skills and one MCP server. Upgrades preserve stores
38
+ and refuse conflicts. The npm archive includes the small bundle and both catalogs.
39
+ Replacing an older `kiln@kiln` installation requires the explicit plugin migration;
40
+ public directory submission and hosted access remain separate release work.
41
+
42
+ - Blind-run receipts recognize OpenCode's recorded code-mode MCP calls and AGY's
43
+ generic MCP dispatcher without inferring execution from source or output text.
44
+ Setup and authoring guidance now covers waiting for independent authors, checking
45
+ sloping panel fit, retaining repeated parts during edits and saving child revisions
46
+ before exporting. The eight-asset campaign records its incomplete handoffs and
47
+ visual defects separately from valid exports.
48
+
49
+ - **Breaking (v1 candidate):** `optimize: 'full'` replaces flatten/join with rigid-group
50
+ merging. Its rigid-merge pass preserves node names, hierarchy and transforms, including animated targets,
51
+ named joint pivots, semantic nodes, composition placements and LOD levels. Ordinary
52
+ named mesh nodes can become empty or hold merged geometry. When geometry must remain
53
+ on every named part, use `optimize: 'off'` or `'palette'` with `instance: 'off'`;
54
+ the separate GPU-instancing pass can remove named instance nodes. Shared-buffer expansion is limited
55
+ to 4 KiB per saved draw, and merged buckets split at 65,534 vertices. Results expose
56
+ boundary, lock and rejection diagnostics. New builds record `rigid-v1`; exact
57
+ rebuilds of older unversioned full-mode revisions require their original engine
58
+ or an explicit new authoring revision. Original saved GLBs remain exportable.
59
+ - ORM packing isolates shared images per metallic-roughness/occlusion pair. Materials
60
+ with different AO maps and consumers in other texture slots retain their original
61
+ pixels, UVs and samplers; identical packing pairs reuse the packed image.
62
+ - Scene browser tools share portable executable discovery and clean up temporary
63
+ profiles on launch failure and close. The normal portable scene gate includes
64
+ script tests, and the static server identifies WebP images correctly.
65
+ - Cloudflare Pages deployment has a portable Node preflight that verifies the exact
66
+ launch-mode artifact inventory, sizes and hashes against a clean checkout before
67
+ upload. The PowerShell entry delegates to the same implementation.
68
+ - Inspection and export summaries add bounded per-anchor draw counts, a qualified
69
+ rigid-merge estimate, and sampled mirrored-tangent/coplanar-part observations.
70
+ Compact render output and tool input schemas stay unchanged; estimates and flags
71
+ are advisory, not GPU timings or QA acceptance.
72
+ - Compatible dependency updates include glTF Transform 4.5.1, manifold 3.5.4,
73
+ MCP client/server 2.3.0, Strands 1.19.0 with the compatible AI SDK 6/provider 3
74
+ family, image libraries and tooling. Site/scenes use Vite 8.3.2. Maintainer pins
75
+ move to Node 22.23.3/npm 12.2.0, retaining Bun 1.4.2 and consumer Node compatibility.
76
+ - The exact-input scene release gate verifies public pack hashes and cannot pass
77
+ through missing-fixture skips. Shadow caches observe morph weights, Farm's economy
78
+ tier retains its trailer casters, and Golden Gate reflections exclude dynamic
79
+ traffic/player/fog while preserving vegetation; diagnostic fog hiding persists.
80
+
81
+ - The scenes' dependency pin check resolves the engine, site and render service
82
+ from the integrated checkout instead of hard-coded Windows worktrees. It now
83
+ runs on the Linux hub and relocated checkouts; a filesystem regression test
84
+ verifies collection and detection of an engine pin mismatch. Contributor and
85
+ Troy resume instructions name the current validation and workspace setup commands.
86
+ - The kit pass (`applyKitContract`, `packKitGlb`) no longer folds an occlusion map into the
87
+ metallic-roughness image when the two are read through different UV sets or
88
+ `KHR_texture_transform` values. It used to point the occlusion slot at the
89
+ metallic-roughness UV set as well, so a baked AO map on `TEXCOORD_1` was sampled through
90
+ `TEXCOORD_0`. Such a material keeps its separate occlusion image and UV set, and the
91
+ summary's new `ormSkipped` names the material and the reason; a matching pair packs as
92
+ before, and packing no longer rewrites the occlusion slot's UV set.
93
+ - `src/render.ts` and `src/__tests__/optimize.test.ts` no longer cite
94
+ `docs/kiln-material-consolidation-cycle.md`, which exists on no branch; the `OptimizeMode`
95
+ comment states the rule in place.
96
+ - `docs/runtime.md` documents `KILN_BAKE_OPTIMIZE` and `KILN_BAKE_INSTANCE`: their values,
97
+ that they only set the default for a render that omits `optimize` or `instance`, that the
98
+ MCP and CLI tools pin `optimize: 'off'` (so only `KILN_BAKE_INSTANCE` reaches their GLBs),
99
+ which library renders take the variables, and that a saved revision rebuilds with the
100
+ options its manifest recorded. No behaviour changes.
101
+
102
+ ## 0.10.0
103
+
104
+ **Protocol revision 2026-07-28, and a server that answers before it loads.** The v1
105
+ readiness cycle ([plan](docs/plans/2026-10-01-v1-readiness-cycle.md)) measures Kiln under
106
+ Claude Code, Codex, OpenCode and Antigravity and fixes what the sessions show. Entries
107
+ marked **Breaking** change what a client or an embedder observes.
108
+
109
+ ### MCP transport
110
+
111
+ - **The stdio server serves protocol revision 2026-07-28 beside the 2025 handshake
112
+ revisions.** `@modelcontextprotocol/server` and `client` move to 2.2.0 and `ext-apps` to
113
+ 2.0.3. A 2026-07-28 client sends `server/discover` or any request carrying the per-request
114
+ `_meta` envelope and needs no handshake; a 2025-11-25 or 2025-06-18 client sends
115
+ `initialize` as before. Every 2026-07-28 result carries `resultType` and the server
116
+ identity; `server/discover`, `tools/list`, `resources/list`, `resources/templates/list` and
117
+ the viewer page read are `public` with a one-day `ttlMs`, while asset and project reads
118
+ stay `private` with `ttlMs: 0`.
119
+ - **Breaking:** a request that carries neither a protocol version nor a preceding
120
+ `initialize` is answered `-32602` with both ways in, instead of being served as a 2025
121
+ session. `listChanged` is no longer advertised for tools or resources (the lists are
122
+ fixed for a build) and `subscriptions/listen` acknowledges no list-changed
123
+ notifications. Advertised schemas no longer carry `$schema`.
124
+ - **The entry is thin.** `dist/mcp-server.mjs` bundles the protocol library and a generated
125
+ manifest of the definitions (`src/generated/mcp-manifest.json`, regenerated by the build
126
+ and by `bun run mcp:manifest`), answers `initialize`, `server/discover` and `tools/list`
127
+ from it (229 ms after process start in the startup test on the maintainer's PC, against
128
+ 1.1 to 1.7 s before), and loads `dist/mcp-engine.mjs` on the first call that needs the
129
+ engine, warming it once the harness holds the tool list. **Breaking** for hand-wired
130
+ installs that copied the one file: both bundles must stay together.
131
+ - **Breaking:** a configuration problem no longer kills the server at startup. A stale
132
+ workspace, an invalid `KILN_RENDER` or a missing engine bundle come back as the first tool
133
+ result, naming the next step and no local path; the next call tries again, so a workspace
134
+ repaired while the harness is open needs no restart. A wrong command-line option and an
135
+ unreadable or legacy `--requirements` file still exit 1 before the protocol starts.
136
+ - **Breaking** for embedders: `createKilnMcpServer`, `kilnMcpToolDefs` and `runTool` moved
137
+ from `src/mcp-server.ts` to `src/mcp-engine.ts`, and `createKilnMcpServer` returns the
138
+ low-level `Server`; `MCP_SERVER_NAME`, `MCP_SERVER_VERSION` and `MCP_SERVER_INSTRUCTIONS`
139
+ live in `src/mcp-core.ts`. `src/requirements-json.ts` reads a host binding file without
140
+ zod; `readHostRequirementsFile` validates on top of it as before.
141
+
142
+ ### Tool surface
143
+
144
+ One versioned batch: tool definitions are prompt-cached, so every schema and description
145
+ change lands together. Measured on the packaged manifest: the seventeen advertised
146
+ definitions 39,603 characters as JSON (74,649 before), every input schema within 5,000
147
+ bytes (four were over, the largest 18,788), input schemas 26,475 bytes in all (61,598),
148
+ `src/generated/mcp-manifest.json` 72,659 characters (205,917).
149
+
150
+ - **Breaking:** `kiln_project`, `kiln_material` and `kiln_review` are one flat object each
151
+ with an `action` enum instead of a root `oneOf` (Claude Code dropped all three). Every
152
+ other field is optional in the schema; the server checks what each action needs and names
153
+ the missing fields. The nested `draft`, `patch` and `payload` records are opaque in the
154
+ schema and validated when the action runs; `kiln_discover({ ids: ['shape:project-draft'] })`
155
+ (and `shape:project-patch`, `shape:material-draft`, `shape:material-import`) returns the
156
+ full JSON Schema through the new Discovery kind `shape`. `kiln_project` offers `export`
157
+ only when the host can read project bundles. The field names and defaults are unchanged.
158
+ - **Breaking:** `kiln_edit capture`, `kiln_view_interior capture`, `kiln_inspect shot` and
159
+ `kiln_screenshot_animation shot` are advertised as opaque records; `kiln_render` states the
160
+ capture shape in full, `shape:capture` and `shape:camera-shot` describe it, and each tool
161
+ validates the record as before when it runs. The capture schemas live in
162
+ `src/tools/capture-input.ts`.
163
+ - `kiln_validate`, `kiln_render`, `kiln_edit`, `kiln_inspect`, `kiln_view_interior` and
164
+ `kiln_screenshot_animation` accept `file`, a program file inside the workspace named
165
+ relative to its root, beside `code` and `programRef`; absolute paths and `..` are refused
166
+ with the rule. A code-mode harness can hand Kiln a path instead of quoting a program.
167
+ - The configured project is visible: `kiln_discover` capabilities carry
168
+ `project.configured`, the overview says to read it first, and a project-bound render,
169
+ edit, inspection or save result carries `project: { projectId, revisionId }`.
170
+ - **Breaking:** the server instructions are 692 characters (2,297 before), name no absolute
171
+ path and point at `skills/` beside the server; the list of skills and the offer to copy
172
+ them into `.claude/skills/` or `.agents/skills/` moved out. Workspace selection and
173
+ camera field text is shorter, and every description opens with its purpose.
174
+ - `docs/tools.md` is the packaged manifest pretty-printed (seventeen tools, the advertised
175
+ bytes), no longer the embedding registry's fourteen.
176
+ - `kiln_material` accepts `resourceId`, the name a project's palette and
177
+ `materialDependencies` give a material, as an alias of `materialId` (a Codex session
178
+ passed it twice and was refused both times); the advertised schema and the docs keep
179
+ `materialId`, and a call naming both with different values is refused with the fix.
180
+ - **Breaking** for a call that pins another revision of a project's material: a project
181
+ revision implies its material pins, and a pin the call names replaces the project's or
182
+ the inherited one for the same `resourceId` instead of being refused as a conflict (a
183
+ Codex session repeated the project's five pins on every render, edit, inspect and
184
+ animation call, 1,300 to 3,400 characters of arguments each, because the field said
185
+ project locks could not be replaced). The `materialDependencies` description of the
186
+ six workspace-bound tools says so in the same number of bytes.
187
+
188
+ ### Program handles
189
+
190
+ - A `programRef` the store does not know comes back as a tool result that names the next
191
+ call (`kiln_validate` or `kiln_render` with `code`) and no local path. `kiln_validate`,
192
+ `kiln_render`, `kiln_edit`, `kiln_inspect`, `kiln_view_interior` and
193
+ `kiln_screenshot_animation` state the retention of the handle they return: the packaged
194
+ file store keeps it across sessions and processes without eviction. `ProgramStore` gains
195
+ an optional `retention` sentence for injected stores, and `docs/tools.md` states the
196
+ packaged store's.
197
+
198
+ ### Results
199
+
200
+ The baseline sessions measured a `detail: 'full'` render of 79,078 characters (cut near
201
+ 40,000 before Codex's model saw it; 24% of it line breaks and indentation), 41 copies of
202
+ one finding with the same 300-character repair text, and Agy writing every result over
203
+ about 4,000 characters to a file the model then read back. A default result is now at
204
+ most 20,000 characters and no result is over 40,000; on a 97-part fixture whose every
205
+ part floats, the compact render result is 9,103 characters, `full` 16,734 and `lean`
206
+ 2,018. `src/__tests__/result-contract.test.ts` and `mcp-errors.test.ts` hold the rules.
207
+
208
+ - **Breaking:** every tool result is JSON on one line. A render, edit, inspection,
209
+ interior or animation result leads with `ok`, `acceptance`, `disposition`, up to eight
210
+ `blockers` (grouped by code with the repair text once), the `findings` counts and
211
+ `next`, the sentence naming the next call; the `programRef` follows, then the metrics.
212
+ - **Breaking:** the compact report groups the findings of each dimension by code, most
213
+ severe first: one record per code with `count`, the first affected entity and up to two
214
+ `alsoAffected`, `findingsTotal`, and `findingsOmitted`/`omittedByCode` beyond twelve
215
+ groups. The rule records collapse to `ruleSummary` (`evaluated`, `notEvaluated`;
216
+ `notRequested` rules are not listed). `viewEvidence` carries `current` and the hash-only
217
+ `lastFaithful`, not the faithful history. The part
218
+ preview is 24 paths with `partsNextOffset` and the `kiln_inspect listParts` hint.
219
+ Build warnings are at most twelve of 1,000 characters each with `warningsOmitted`.
220
+ - `detail` on `kiln_render`, `kiln_edit`, `kiln_inspect`, `kiln_view_interior` and
221
+ `kiln_screenshot_animation` takes `lean`, `compact` (default) or `full`. `lean` is the
222
+ verdict, blockers, counts, next step, metrics, a short fidelity receipt and three
223
+ warnings. `full` is every finding, rule and part that fits 40,000 characters, with
224
+ `retainedReport: { operationId, path }` naming `.kiln/review/<operation>/evaluation.json`
225
+ (now pretty-printed) when Live Review records the operation; when the complete report
226
+ does not fit, the warnings are bounded first, then repeated findings group by code
227
+ with every rule kept, then the part list shrinks and the groups halve, and
228
+ `qaReport.detail` reads `full-bounded`. The packaged host reads `KILN_RESULT_DETAIL`
229
+ (`lean` or `compact`) as its default.
230
+ - **Breaking:** a `kiln_edit` result is `ok`, `applied`, `changed` (status, part and
231
+ animation summaries, the first five changes), `next`, the `diff` (2,000 characters in
232
+ `lean`, with `diffOmitted`), `preservation` (not in `lean`) and the compacted `render`.
233
+ The compact `preservation.comparison` is the counts, the artifact before and after and
234
+ up to twelve changes as path, name, status and changed fields; the bounds of each
235
+ change and the scope sentences stay in `full` (a live compact edit of a 49-part asset
236
+ measured 20,069 characters with them, 7,191 of it the comparison). When a compact edit
237
+ would still pass 20,000 characters, its `diff` is shortened first, never below 2,000,
238
+ with `diffOmitted`.
239
+ - **Breaking:** a `kiln_inspect compare` page in `compact` or `lean` is the same shape:
240
+ the counts, the artifact before and after, `offset`, `nextOffset` and each change of
241
+ the page as path, name, status and changed fields. A live compact page of 50 changes
242
+ measured 38,952 characters with their bounds and the scope sentences; `detail: 'full'`
243
+ keeps them.
244
+ - **Breaking:** in `compact` and `lean` detail a per-view receipt (`derivativeReceipts`,
245
+ and `viewFidelity.receipts` of shot and animation results) is its `derivativeLabel`,
246
+ `cameraFidelity`, `captureCache` and only the fidelity fields that differ from the
247
+ result's `viewFidelity`; the camera is in `cameraShots` under the same name. A live
248
+ six-shot compact edit measured 22,417 characters with 4,672 of receipts repeating the
249
+ summary and the cameras; `full` keeps whole receipts.
250
+ - **Breaking:** a compact or lean result rounds every number to six decimals (a micrometre
251
+ on a metre-scale asset); `full` and the retained report keep every digit. The 17-digit
252
+ doubles of bounds, cameras and origins were 4,346 of a live 24,223-character compact
253
+ animation result and 1,514 of a 20,556-character compact six-shot edit.
254
+ - **Breaking:** in `compact` and `lean` detail, frames of an animation that resolve to one
255
+ shot are stated once in `cameraShots` with `frames` counting them and each frame's
256
+ bounds in `poseBounds` (nine frames repeated a 631-character camera and subject, 5,666
257
+ characters); `viewFidelity` states once what every receipt shares (the renderer id, a
258
+ degrade reason); and `viewEvidence.lastFaithful` is omitted when it is the current view.
259
+ When nine phases and many measured parts would still pass 20,000 characters, the leading
260
+ frames of `poseBounds` stay whole and `poseBoundsOmitted` counts the rest, with
261
+ `poseBoundsHint` naming the way to them; `full` keeps every frame that fits 40,000.
262
+ - **Breaking:** a `detail: 'full'` edit is bounded as a whole. A live full edit measured
263
+ 40,533 characters: its render bounded at 40,000 on its own, the 8,177-character diff and
264
+ the 10,973-character comparison on top. The diff shrinks first (to 2,000, with
265
+ `diffOmitted`), then the bounds of each change leave the comparison (the retained report
266
+ keeps them), then the render is bounded again with what is left.
267
+ - **Breaking:** `kiln_edit` no longer echoes the patched source when the program was sent as
268
+ `code`: a live edit of a 13,173-character program came back at 28,539 characters, the
269
+ source beside the render. The retained `programRef` serves it through `kiln_source`;
270
+ `includeCode: true` asks for it, bounded with the rest of the result (`codeOmitted`,
271
+ `codeHint`).
272
+ - An invalid-input error no longer ends an issue sentence twice (`...or file.. Check`), and
273
+ one that names a key the tool does not take lists the keys it does (`kiln_material takes
274
+ action, materialId, ...`): a live session passed a project palette's `resourceId` to
275
+ `kiln_material get` twice and was told only to check the schema. The `materialId`
276
+ description names the palette's `resourceId`. A nested record the image tools enforce
277
+ when they run (`shot`, `capture`) that fails to parse is the same kind of sentence naming
278
+ the record's Discovery shape, as a tool error: a live session put the camera fields at the
279
+ root of a `shot` and received `ok: false` with zod's issue dump under "Fix the error in
280
+ the source".
281
+ - `kiln_assets get` and `restore` without `revisionId` read the asset's newest revision (the
282
+ one no later revision names as its parent), and `kiln_material get` without `revisionId`
283
+ reads the material's only revision, naming the revisions when there are several; both
284
+ match `kiln_project get`. Three live sessions called `get` with the id alone and were
285
+ refused.
286
+ - **Breaking:** `kiln_inspect listParts` returns names and paths; `placement: true` adds
287
+ the world transform, mirroring and bounds in pages of 50. The floating-parts warning
288
+ names the first six parts with their fix and the next 24 by name, and a render result
289
+ carries the export-time structural warnings once (a second pass over the re-imported
290
+ scene repeated every floating part under its primitive name).
291
+ - **Breaking:** `kiln_review list` pages (`offset`, `limit` up to 100, default 20) and
292
+ returns short records (`operationId`, `revision`, `tool`, `status`, timestamps,
293
+ `programRef`, `pinned`, the artifact hash, capture count and fidelity) with `total` and
294
+ `nextOffset`; `get` and `pin` return an operation bounded to 20,000 characters, naming
295
+ the retained file for what was left out. `kiln_discover` detail pages stop at 15,000
296
+ characters and name the ids not returned in `omittedIds`.
297
+ - **Breaking:** a result with `ok: false` is an MCP error (`isError: true`). Every error
298
+ names the next call and no absolute local path: an invalid input is a sentence naming
299
+ the fields and the `shape:` id to read instead of a zod issue dump; an unknown project,
300
+ material, asset or review id names the `list` action; a renderer that is required but
301
+ unavailable names `KILN_RENDER`, `KILN_RENDER_PORT_URL` and `kiln_renderer reprobe`;
302
+ `kiln_review save` names the revision or status to fix; a project import refusing its
303
+ own id says to pass a different `--id`; a shot whose subject names no node, or more
304
+ than one, is answered with the shot's fix (one exact part path from the list in the
305
+ error), not the source's.
306
+ - The CLI `render --json` and `animation --json` receipts carry the compact result by
307
+ default (one receipt measured 51,312 characters at `full`); `--detail lean|compact|full`
308
+ selects the detail. The CLI `material list` prints the summaries `kiln_material list`
309
+ returns, not every manifest (34,116 characters for five pinned materials before).
310
+ - `scripts/tier2-dogfood.mjs` counts the tool steps of an Agy stream (`step_update` with
311
+ `tool_name`, one per step index), so a blind Agy run's receipt no longer says `unknown`.
312
+ - A saved manifest read as a resource (`kiln://assets/.../manifest.json`) is one line, as
313
+ every tool result is; the file on disk stays pretty-printed. CLI `kiln asset <id>`
314
+ without a revision reads the asset's newest revision, as `kiln_assets get` does (it
315
+ refused with "requires asset ID and revision ID"), while `export` still names one exact
316
+ revision; `kiln save --help` prints the save usage alone (a blind OpenCode run read
317
+ 2,275 characters of the assets-and-viewer block for it, and the whole block had been
318
+ 16,077 before the records trim).
319
+
320
+ ### Generated code
321
+
322
+ The follow-ups the owner decided on 2 October 2026 after reading the cycle's report
323
+ (decisions 23 to 28 in the plan) land here, on the same branch, before it merges.
324
+
325
+ - **Breaking:** generated code runs in strict mode. The evaluator prepends `'use strict'`
326
+ to every program and `kiln_validate` parses with the same directive, so a construct only
327
+ strict mode refuses (`with`, a legacy octal literal, a duplicate parameter name) is a
328
+ `SYNTAX_ERROR` with its line before any evaluation; an assignment to an undeclared name
329
+ throws (answered as an unbound variable) instead of creating a host global; and a
330
+ function declared inside a block stays in that block. The source policy allows `this`
331
+ inside object-literal methods, getters, setters and function properties, class methods,
332
+ constructors, accessors, field initializers and static blocks, and in arrows nested
333
+ inside those, where it is the receiver the program built (w29's mesh batcher was an
334
+ object literal whose `add` method read `this`); `this` at the top level or in a plain
335
+ function stays `UNSAFE_GLOBAL_ACCESS`, as does `module`.
336
+ - The generic rejection carries the engine's own advice. A program that throws without
337
+ a closed cause is still answered `Generated asset execution was rejected.` with no
338
+ message, stack or identifier from it; that sentence now continues with a fixed
339
+ engine-owned paragraph saying so and naming the way to read values (`kiln_inspect` on
340
+ a rendered program, not a thrown `Error`) and the way to find the line
341
+ (`kiln_validate`). The scene session that threw an `Error` to print bounding boxes got
342
+ the bare sentence (H30). A rejection with a cause keeps that cause's advice alone; the
343
+ wire message and the adversarial isolation test's exact first sentence are unchanged.
344
+
345
+ ### Knowledge and setup
346
+
347
+ Standing knowledge is read whole at the start of every session and again after each
348
+ compaction, so it is a budget: the generated guide is at most 5,000 characters and a
349
+ `SKILL.md` at most 16,000 characters and 500 lines, enforced by the workspace tests and
350
+ `check:skills`.
351
+
352
+ - **Breaking** for generated workspaces: `AGENTS.md` carries a 4,461-character guide
353
+ (10,716 before) and `CLAUDE.md` is the one-line import `@AGENTS.md`. Claude Code 2.1.287
354
+ loads `AGENTS.md` by default only where no `CLAUDE.md` exists, so the import keeps one
355
+ copy of the guide in context whichever file it reads. A workspace carries one skill
356
+ registry, the directory its harness reads: `.claude/skills/` for Claude Code (whose binary
357
+ also names `.agents/skills`, so the two copies registered every skill twice),
358
+ `.agents/skills/` for codex, hermes, agy, copilot and cursor-agent, and none for OpenCode,
359
+ whose `opencode.json` names `skills/` in `skills.paths`. Existing workspaces are not
360
+ rewritten: `kiln-init --check` reports the second registry a 0.9 workspace carried as
361
+ `retired`, `--upgrade` removes it when unchanged and stops on an edited copy.
362
+ - `kiln-author-asset/SKILL.md` is 15,969 characters (19,030 before). Its delivery paragraph
363
+ offers `node kiln.mjs view` to a person who is present instead of telling the agent to
364
+ launch it: a headless Codex session saved its asset and then sat behind the viewer until
365
+ the 30-minute cap. The author, QA and camera guidance make the compact result the way
366
+ to review and name `detail: "full"` only as a qualified second look: the first wave
367
+ pair asked `full` on every render and edit in both the compact and the lean arm because
368
+ the skills said to pass it "for every finding". `check:skills` now refuses a skill
369
+ sentence that names full detail without "only". `engine-handoff.md` lives once in `kiln-qa-asset` and
370
+ `export-profiles.md` once in `kiln-author-asset`, each named by skill and path from the
371
+ other (a skill's markdown links stay inside the skill, so a workspace that installs one
372
+ skill alone resolves every link); `check:skills` refuses two files under `skills/` with the
373
+ same bytes.
374
+ - Codex: the generated `.codex/config.toml` is read once `$CODEX_HOME/config.toml` marks
375
+ the project trusted (measured on 0.160.0; the generator and `START.md` said Codex read no
376
+ project-local config), `node codex.mjs` applies the same values per run so no trust entry
377
+ is needed, and its `env_vars` forward `KILN_PROJECT`, so a configured project reaches the
378
+ server under Codex as it does elsewhere. `kiln-init --help` lists all seven harnesses.
379
+ - Antigravity: the generated `.agents/mcp_config.json` sets `KILN_RESULT_DETAIL=lean` on
380
+ the server entry. Antigravity writes a result over about 4,000 characters to a file the
381
+ model then reads back, and the wave's paired sessions showed lean winning both measures
382
+ only there (24% fewer result characters, 8% fewer output tokens); the other harnesses'
383
+ entries set nothing and keep the compact default, and a call's own `detail` still wins.
384
+ An existing Antigravity workspace shows the entry as changed managed configuration to
385
+ `kiln-init --check`; `--upgrade` refreshes it when the file is unedited.
386
+ - Records: the plugin manifests say 0.10.0 and `kiln` (the Codex plugin manifest said
387
+ `kiln-oss`), the root `plugin.json` no longer names a schema URL that returns 404,
388
+ `docs/dogfooding.md` and `docs/clean-room.md` name OpenCode's `--standalone` (there is no
389
+ `--pure` in 2.0.14), `docs/harnesses.md` records the measured skill directories and the
390
+ Codex trust rule, and the Tier 2 driver reads the packaged manifest for the tool names it
391
+ counts as workspace MCP use (its hand-kept list lacked `kiln_discover`, `kiln_renderer`,
392
+ `kiln_project`, `kiln_material` and `kiln_review`).
393
+
394
+ ## 0.9.0
395
+
396
+ **Source and site preview for dogfooding.** The v0.9 work is integrated for the
397
+ Cloudflare site rollout. Package publication remains deferred to v1.0 after feedback.
398
+ Individual asset review status is recorded on its page; deployment does not change it.
399
+
400
+ Kiln 0.9.0 adds optional projects, a material library and Live Review to standalone
401
+ authoring, makes the calibrated `review-neutral-v1` rig the default for review views,
402
+ exports declared levels of detail as `MSFT_lod` chains, and fixes the defects authors
403
+ reported while using 0.8. Standalone assets need no project. The [migration notes](docs/migration.md#changes-in-090) list every change an
404
+ existing author will notice; the [foundation checkpoint](docs/plans/2026-09-26-project-foundation.md)
405
+ records how projects, materials and Live Review were qualified.
406
+
407
+ ### Projects, materials and Live Review
408
+
409
+ - **Optional projects.** `kiln_project` and `node kiln.mjs project` create, read and
410
+ update immutable, versioned project records: brief, inventory, design profile,
411
+ references, delivery intent and an exact material dependency lock. Updates name the
412
+ expected revision and report conflicts. Work stays standalone unless an operation
413
+ selects a project (`--project`, MCP `projectId`) or the host configures
414
+ `KILN_PROJECT`; `--no-project` and `projectId: null` override that default. The
415
+ collection named `project` remains only a save destination.
416
+ - **Material library.** `kiln_material` and `node kiln.mjs material` keep immutable
417
+ material revisions with creator, license, recipe, seed and source provenance. Five
418
+ offline procedural presets (`warm-brick`, `wood-grain`, `brushed-metal`,
419
+ `woven-fabric`, `coarse-soil`) each produce base colour, normal and packed
420
+ metallic/roughness maps. Custom procedural drafts, normal-from-height derivation and
421
+ complete payload import and export are supported. Source compiles a returned
422
+ `portableSpec` with `compilePortableMaterialSpecV2`; the exact pins travel as
423
+ `materialDependencies` or `--materials`.
424
+ - **Live Review.** `node kiln.mjs view` opens the local dashboard: Library, Materials,
425
+ Projects and Live Review. Live Review shows the exact retained artifacts, captures
426
+ and fidelity of CLI and MCP operations, with iterations, errors, pinning, comparison
427
+ and reconnection. `kiln_review` lists, gets and pins observed operations and saves an
428
+ exact reviewed operation. `KILN_WORK_ITEM` optionally groups one authoring item across
429
+ sessions. The Measure control records a bounded browser load and frame sample.
430
+ - **Delivery.** `project export --profile editable|runtime` packages a project's linked
431
+ saved revisions; editable packages rebuild offline from source, settings and
432
+ complete material resources, and `asset ... --rebuild` reports artifact hash equality.
433
+ The Library aggregates configured collections with collection-qualified IDs.
434
+ - **Seventeen MCP tools.** The packaged stdio server lists the fourteen program tools
435
+ plus `kiln_project`, `kiln_material` and `kiln_review`. Embeddings advertise those
436
+ three only when their host services are supplied.
437
+
438
+ ### Review lighting and provenance
439
+
440
+ - **`review-neutral-v1` is the default review rig**: a calibrated white rig with
441
+ Review Neutral, a Khronos PBR Neutral variant. A key-facing matte `#C0362C` panel
442
+ reads `(193,51,40)` instead of `(255,177,147)`; mean CIEDE2000 over the ColorChecker
443
+ chart falls from 28.09 to 0.51. `neutral-studio-v1` and `gallery-studio-v1` stay
444
+ selectable by explicit lighting ID, captures without one re-render after the upgrade,
445
+ and composer's unsupported `neutral-studio-v2` default becomes `review-neutral-v1`.
446
+ Do not darken albedo for review lighting.
447
+ - The CLI honours `KILN_RENDER` on every renderer-selecting path: an explicit
448
+ `--render` wins, then `KILN_RENDER`, then auto.
449
+ - Saved builds record the installed engine under every build-cache policy, and an
450
+ absent optional peer is fingerprinted as peer-absent instead of erasing the runtime
451
+ identity.
452
+
453
+ ### Levels of detail
454
+
455
+ - **One tier unless the brief asks.** The authoring skills say so. Tiers are sibling
456
+ groups named with one stem and a level token (`Body_LOD0`, `Body_LOD1`), LOD0 the
457
+ full-detail tier, and each set is declared once with
458
+ `defineLod(levels, { screenCoverage })`: one screen fraction per level, 0 to 1,
459
+ strictly decreasing, where a last value of 0 never culls. `createPart` is unchanged.
460
+ Two or more sibling tiers without a declaration, a gap, a missing LOD0 or a set
461
+ declared inside another tier fail the build with the closed diagnostic `LOD_SET`.
462
+ - **Exported as `MSFT_lod`.** Every GLB Kiln writes from source (the saved `asset.glb`,
463
+ `kiln_export`, `kiln export`, `render --out`, both export profiles, both converters)
464
+ carries each set as one chain: LOD0 stays in the scene with `extensions.MSFT_lod.ids`
465
+ and `extras.MSFT_screencoverage`, the lower levels leave the scene with their subtrees
466
+ and transforms, and materials are untouched. A loader without the extension draws
467
+ LOD0.
468
+ - **LOD0 is the headline.** Triangles and bounds count LOD0 and the parts outside every
469
+ set; `levelsOfDetail` in render results, the integration manifest and CLI receipts
470
+ lists each level's path and triangles.
471
+ - **Review views.** Default sheets in `kiln_render`, `kiln_inspect` and the `kiln_save`
472
+ preview draw LOD0. A shot whose subject is a lower level's `path` draws that level in
473
+ LOD0's place (`visibility: "isolate"` shows it alone), the CPU and GPU views agree,
474
+ and each chain's `drawn` says which level every view drew. No tool input changed.
475
+ - **Viewer.** Library and Live Review show a Level control (LOD0 to the last level,
476
+ LOD0 by default) and every level's triangles for a GLB with chains, imported or
477
+ authored. Expand **Per-part levels** to choose each independent chain, such as a
478
+ medium tractor body with detailed wheels. Mixed selections report the triangles
479
+ actually displayed. Current and pinned Live Review artifacts have their own part
480
+ controls; the current artifact's global selector still sets both views. Selecting
481
+ another observation of identical bytes retains geometry statistics and updates
482
+ the observation identity.
483
+ - **Revision comparison** reads chains: each lower level is compared at the path it
484
+ takes beside LOD0. Switch thresholds are not compared.
485
+
486
+ ### Authoring and review contracts
487
+
488
+ - Part listings include world position, quaternion, scale, mirrored orientation and
489
+ world bounds, including compact results.
490
+ - Node and material `userData` round-trips through glTF extras: plain JSON, at most
491
+ 4 KiB per object and 64 KiB per asset. `kiln*` keys remain engine-owned. Validation
492
+ reports literal problems; builds report omitted dynamic values. Optimization keeps
493
+ metadata-bearing nodes and materials instead of merging away their identities.
494
+ - `visible = false` survives export as `KHR_node_visibility`. Headline metrics and
495
+ category QA exclude hidden subtrees; `hiddenNodes` reports their triangle counts.
496
+ Instancing and flattening preserve the flag and its ancestor scope.
497
+ - `markOpenShell(part, reason)` acknowledges an intentionally unmeasurable open shell
498
+ without suppressing measured overlap or other failures. Overlapping closed components
499
+ use union volume; unsupported inward shells and component budgets remain unmeasured.
500
+ - `CUBICSPLINE`, `EASE_IN`, `EASE_OUT` and `EASE_IN_OUT` tracks export standard cubic
501
+ samplers with computed tangents and play consistently in review. Quaternion loop
502
+ closure treats `q` and `-q` as the same rotation.
503
+ - Source exports index unindexed primitives by welding exact complete vertices.
504
+ `indexPolicy: 'asBuilt'` (local host: `KILN_INDEX_POLICY=asBuilt`) retains helper
505
+ buffers and requires optimization off. Render results and CLI receipts report the
506
+ index pass's accessor payload bytes before/after, separately from GLB file size.
507
+ - Sweep and loft helpers observe tight curvature and intersecting consecutive rings
508
+ with station evidence. The bounded local check replaces unconditional unchecked
509
+ warnings; budget-limited checks remain explicitly partial. Distant segments are
510
+ outside this check.
511
+ - `kiln.capture.v2` adds shot-level `hide` with exact paths or unique names. Version 1
512
+ and legacy captures remain valid. CPU and GPU derivatives apply the same hide list;
513
+ post-loop CPU captures resolve subjects from the exported hierarchy.
514
+ - Instanced capture derivatives and canonical bounds retain every instance transform.
515
+ Overlap QA excludes alternate levels only within the same LOD chain, and category
516
+ diagnostic sheets show the default LOD0 scene. Render-service startup tolerates a
517
+ concurrent winner's bounded warmup; completed captures verify the service instance.
518
+
519
+ ### Fixes from author reports
520
+
521
+ - **Smaller results.** MCP `kiln_render` and `kiln_screenshot_animation` are compact by
522
+ default; `detail: "full"` returns every finding and rule. On the carousel example a
523
+ render result falls from 34,904 to 7,706 characters. `kiln_view_interior`,
524
+ `kiln_inspect` and `kiln_edit` are compact too; the CLI, retained artifacts and Live
525
+ Review keep complete reports.
526
+ - **Sizes and files.** Animation composites take a frame `size` of 128 to 1024 px
527
+ (default 256). Versioned capture shots go up to 2048 px. `--capture` with
528
+ `output: "separate"` writes `<stem>.shot-01.png` and onward.
529
+ - **Animation.** `createClip(name, duration, tracks, { loop })` records loop intent,
530
+ exported as the animation extra `kilnLoopIntent` and checked by review
531
+ (`LOOP_NOT_CLOSED`). GLBs no longer duplicate every clip as the `kilnReviewClipsV1`
532
+ scene extra.
533
+ - **Views.** Saved previews drawn from the persisted GLB record `exactArtifact: true`.
534
+ Isolated capture shots send the GPU only the subject. CPU views draw translucent
535
+ surfaces after opaque ones, farthest first, so glass shows what is behind it. An
536
+ explicit perspective camera without `near` sets it from the nearest geometry.
537
+ - **Geometry and export.** `extrudeProfile` adds no side rings unless `divisions` asks
538
+ (16 by default when twisted). `sweepProfile` keeps hard corners (`creaseAngle` 60)
539
+ and can cap one end. Emissive intensity survives export, above 1 through
540
+ `KHR_materials_emissive_strength`. An imported GLB's `MSFT_lod` chains survive
541
+ import, save, optimisation and export.
542
+ - **Diagnostics.** A build that throws names a closed cause with advice and a
543
+ `Source check:`; `kiln_validate` reports `TEMPORAL_DEAD_ZONE`,
544
+ `MATERIAL_RECIPE_OVERRIDE` and `MATERIAL_RECIPE_ID`. Overlap QA skips LOD
545
+ alternates, tests the likeliest pairs first and groups unmeasurable parts;
546
+ connectivity leaves out LOD1+ parts. Repeated helper notes appear once with a mesh
547
+ count. The instanceability grade says it is informational, the blend-area budget
548
+ names its largest materials, and `kiln_inspect` evaluates without optimisation.
549
+ - **Errors that name candidates.** Ambiguous and missing subjects, unknown camera keys
550
+ (`fovDeg` for `fov`) and missing or shared `surfacePairs` names list what would have
551
+ matched. `surfacePairs` take exact paths or unambiguous node names, as `measure` does.
552
+ Animation `measureParts` entries carry each node's world origin.
553
+ - **Setup and CLI.** A workspace's `kiln.mjs` re-executes under the Node recorded at
554
+ setup, so CLI exports equal `kiln_save` bytes. `.kiln/workspace.json` names the
555
+ bundle `buildIdentity`, apart from the installed `runtimeIdentity`. The viewer
556
+ accepts `localhost` and `[::1]`; Git Bash collection roots are refused on Windows.
557
+ `kiln edit` and `kiln source` accept `--json`, `kiln service start` joins or starts
558
+ the shared renderer, and a render URL naming the shared local socket takes the local
559
+ route.
560
+
561
+ ### Fixes from the 0.9 dogfood runs
562
+
563
+ Three blind setup-and-author runs on the packaged build, two in OpenCode and one in Agy,
564
+ found these before release.
565
+
566
+ - **`kiln_edit` results** lead with `programRef` and `parentRef` and carry a compact
567
+ render; `kiln_render({ programRef, detail: "full" })` returns the complete report.
568
+ - **Discovery.** `ids` take a recipe's bare slug (`material-wood-v1`) as well as
569
+ `recipe:material-wood-v1`, and a material recipe's summary names its
570
+ `materialRecipe("kiln.material.wood.v1")` call. Reviewed aliases let `bench`, `table`
571
+ and `cast iron` find the joined-frame recipe, `arrayLinear` and the painted-metal
572
+ material.
573
+ - **One `--json` rule.** `render`, `source`, `export`, `discover`, `inspect`, `animation`
574
+ and `service status`/`reprobe` print a receipt; commands that print JSON already accept
575
+ the flag; `generate`, `view`, `collections add` and `service start`/`stop` refuse it
576
+ and name the commands that take it. `export --json` names each file with its `bytes`
577
+ and `sha256`.
578
+ - **Exports never replace a file.** `export` and `source --out` fail and name the existing
579
+ file; `render --out` still replaces its own working output.
580
+ - **Setup.** `kiln discover --capabilities` reports the collections and source storage the
581
+ MCP server has, and `kiln-init` creates the `.kiln/programs` store it configures.
582
+ - **Guidance.** The OpenCode configuration check, npm 11's `install-scripts` warning for
583
+ `webgpu`, the part rotation convention and which way an object faces, searching with
584
+ `kiln_discover` rather than a harness's own search, observe findings on intended joins,
585
+ suffixed names for repeated sub-assemblies, a user-level `kiln` server beside
586
+ `kiln_workspace`, and where agy's `--print` prompt goes.
587
+
588
+ ### Output that changes on upgrade
589
+
590
+ Review colours under the new rig (assets darkened for the old rig look too dark);
591
+ untwisted `extrudeProfile` triangle counts; `sweepProfile` shading of triangular, square and
592
+ pentagonal profiles (pass `creaseAngle: 180` for the old smooth look); GLB bytes
593
+ of emissive and animated assets; images from explicit perspective cameras without
594
+ `near`; CPU images through glass; GPU isolated shots; MCP result size and shape
595
+ (`detail: "full"` restores the complete report), including `kiln_edit`, which leads with
596
+ its refs; the wording of refused exports; Discovery ranking for `bench`, `table` and
597
+ `cast iron`. A program with two or more sibling `LOD<n>` tiers fails with `LOD_SET`
598
+ until it declares them with `defineLod` or keeps one tier; once declared, its GLB bytes,
599
+ headline triangles and bounds, and default sheets (LOD0 only) change.
600
+
601
+ ## 0.8.0 (source update)
602
+
603
+ The 0.8.0 source update exposes Original GLB and Runtime GLB downloads in the local
604
+ viewer and public gallery, with companion runtime metadata and unchanged canonical
605
+ files. The public viewer adds native animation selection and pause/play controls.
606
+ Gallery build scripts use the current evaluator API. Site setup text and
607
+ historical-example labels match the upgraded CLI/MCP workflow.
608
+
609
+ External CLI/MCP authoring skills and native Strands workflow instructions are
610
+ separate. Optional native references expose only explicitly selected technical
611
+ files, with no workspace setup, CLI command or delivery instructions.
612
+
613
+ - **One asset workflow across CLI and MCP.** Kiln Discovery replaces
614
+ `kiln_list_primitives`; search ordinary modeling language, browse operations,
615
+ assemblies and optional recipes, then fetch exact contracts. No search model,
616
+ GPU, API key or category selection is required. Matching now normalizes Unicode
617
+ identifiers, retains term/field evidence, labels related and partial results,
618
+ and limits typo expansion to missing vocabulary.
619
+ - **Explicit migration instead of hidden legacy fallbacks.** Retired Discovery
620
+ selectors, ambiguous helper aliases, category-driven execution and the old
621
+ evaluator/native interfaces fail with migration advice. See
622
+ [the migration guide](docs/migration.md) before updating an integration.
623
+ - **Geometry and assembly contracts are sharper.** The unification pass repairs
624
+ scale-sensitive geometry diagnostics, sweeps, CSG precision/attributes, UV
625
+ ownership and assembly reuse. Requirements and QA report unsupported obligations
626
+ as incomplete; successful generation does not certify an asset's physical fit.
627
+ - **Better evidence during review.** Animation review reports endpoint continuity
628
+ separately from loop intent and velocity smoothness. Rendered edits automatically
629
+ compare exported static data and animation channels; source-only edits explicitly
630
+ leave preservation unassessed. Focused inspection provides paged differences,
631
+ subtree summaries and surface measurements.
632
+ - **A shared renderer with explicit fidelity.** Compatible clients share a managed
633
+ service with an idle lifetime; the starting session's exit does not kill other
634
+ clients' work. Incompatible listeners are reported without replacement. The
635
+ optional renderer ships inside Kiln; local startup, remote routes, cancellation,
636
+ admission limits and in-session reprobe share the documented contracts. Material
637
+ fidelity is reported for each view and CPU views remain geometry-only evidence.
638
+ - **Strands is optional.** Its native workflow uses the shared program-reference
639
+ tools, unified rendering and an explicit `kiln_finish` terminal. Native context
640
+ stays outside the shared CLI/MCP skills; SDK/provider dependencies are optional.
641
+ - **Consumer requirements are separate from maintainer pins.** CLI/MCP accept Node
642
+ 20.15.0+ on the 20.x line or 22.2.0+; native Strands requires 22.2.0+. Installed
643
+ commands do not require Bun. Read-only workspace checks respect an existing
644
+ installed interpreter; setup canonicalizes parent directory aliases so macOS temporary
645
+ workspaces remain current after Node resolves their paths. Repair/upgrade remains explicit. OpenCode setup guidance
646
+ reflects its current CLI, and Cline's affected image path has a documented workaround.
647
+
648
+ This source update does not publish a new installable package. Qualification and
649
+ remaining limits are recorded in the [progress checkpoint](docs/plans/2026-09-22-progress-checkpoint.md).
650
+ The development entries below retain earlier changes and may describe intermediate
651
+ interfaces superseded by this version; use the current tool and migration guides.
652
+
653
+ ### Test suite holds under a loaded runner
654
+
655
+ - The test scripts and CI set a 20 s per-test budget instead of Bun's 5 s default. Tests that take
656
+ under a second locally (a cold CLI spawn, the first `sharp` decode in a file) had timed out at 5 s
657
+ on the Windows runner and passed on re-run; a real hang still fails, twenty seconds later.
658
+ - The tier-2 driver's process-group test no longer races node start-up. It stopped the run 50 ms
659
+ after spawn, so on a loaded runner the child died before it had spawned its descendant and the
660
+ orphan spawned afterwards survived. The test now waits for the child to announce its descendant
661
+ before interrupting the group, and a separate test proves the deadline reports itself. Temp
662
+ directories that a just-killed process tree still holds are removed with retries instead of
663
+ failing the run with `EBUSY`.
664
+
665
+ ### Neutral view backdrop, and a named backdrop per capture
666
+
667
+ - **Every contact sheet is now painted on the neutral studio grey `#aab1bc` instead of near-black
668
+ `#1a1a1a`.** The old value was pinned in PR #13 for CPU/GPU parity and never revisited; a dark
669
+ asset on it lost the silhouette, tail and ground contact the render tool asks the model to check.
670
+ The new default was chosen by measuring silhouette-edge contrast over the 86 examples plus a
671
+ near-black test asset on the CPU rasterizer: near-black left 39.7% of edge pixels under a 1.5:1
672
+ contrast ratio, the studio grey 12.8%. It is also the GPU studio preset's background, so a CPU
673
+ fallback sheet and a GPU sheet of the same request now share one colour on every path,
674
+ including exact-camera captures, which used to come back grey from the GPU and near-black from
675
+ the CPU fallback.
676
+ - **`capture.backdrop` accepts `neutral`, `dark` or `light`** on `kiln_render`, `kiln_edit` and
677
+ `kiln_view_interior`. Free colours are deliberately not accepted: comparability across runs
678
+ assumes two sheets of one GLB differ only because the asset does. The chosen id is part of both
679
+ capture-cache keys and is echoed as `capture.backdrop` in every render result. The render
680
+ service accepts the same `backdrop` field in both modes from a mirrored table that the engine
681
+ suite asserts against. The CLI takes it inside a `--capture` recipe or as `--backdrop <id>`,
682
+ which merges into the same capture object and overrides the recipe's own value, so one shared
683
+ recipe can be re-run on another backdrop without editing it.
684
+ - **The GPU backdrop is pixel-exact.** The service clears the scene background into its HDR
685
+ framebuffer and tone-maps it with the asset, so the neutral `#aab1bc` used to read back as
686
+ `(203, 207, 213)` while the CPU rasterizer painted `(170, 177, 188)`. The service now sets the
687
+ backdrop as the linear colour that comes out of the output pass as exactly the table's bytes,
688
+ through an analytic inverse of three's ACES filmic curve and sRGB transfer in
689
+ `render-service/src/display-transform.mjs`, verified against pixels read back from a dawn-d3d12
690
+ device. A GPU sheet and a CPU sheet of the same request now agree on the backdrop pixel for
691
+ pixel. Compositing after the pass was rejected because multisampled edges would have kept a
692
+ one-pixel fringe.
693
+ - **The render service's free-hex `background` field is gone**, in both modes, along with the
694
+ presets' own background colour: the named backdrop table is the only source, so nothing can
695
+ paint a colour the engine would not. `background` in a request is a 400 naming `backdrop`.
696
+ - **`kiln render --render gpu` no longer hangs after starting the renderer itself.** The host
697
+ kills a service it started from an `exit` hook, which suits the long-lived MCP server, but a
698
+ one-shot command never reached `exit`: the child's piped stderr kept the loop alive, so the
699
+ CLI finished its render and then sat forever with a GPU process orphaned behind it. The CLI
700
+ now stops a service it started when the command settles and leaves one it merely found alone.
701
+ For a batch of CLI renders, start the service once by hand so each command joins it.
702
+ - Occupancy `coverage` takes the backdrop it should measure against. Sheets cached from earlier
703
+ versions are keyed by the old request shape and are simply re-rendered once.
704
+ - `kiln_view_interior` echoes `capture` too, so every image result now says which backdrop its
705
+ silhouettes sat on. The guidance in the schema and skills states one rule for every asset
706
+ colour: switch only after a sheet shows a part merging with the grey, to `light` when that part
707
+ is darker than the grey and to `dark` when it is lighter.
708
+ - **`kiln_save` takes the same `backdrop` for the preview it stores**, as does the CLI's
709
+ `kiln save --backdrop <id>`, and the manifest records the one used as `preview.backdrop`. A
710
+ preview used to be painted on the default whatever sheet had been accepted, so a grey asset
711
+ reviewed on `dark` was stored merging with the grey it had just been moved off.
712
+
713
+ ### Experimental community exporter and engine handoff
714
+
715
+ - The established GLB exporter remains the default. Builds containing this integration also
716
+ provide an experimental Three.js exporter for explicit comparison; no separate repository
717
+ is needed. See the [Blender/Unity handoff guide](docs/engine-handoff.md) for scoped CLI/MCP
718
+ activation, restoration of the default, tested import routes and known limitations.
719
+ - Authoring and QA skills include the handoff guide so agents can discover the option when
720
+ destination requirements call for it. Blender/Unity does not automatically select it.
721
+ - Expanded export, GPU, deformation and clean-package checks are documented in the
722
+ [qualification report](docs/evaluation/community-exporter.md). The candidate is not yet
723
+ qualified to replace the default for all supported workflows.
724
+
725
+ ### Saved-asset export profiles
726
+
727
+ - Keep byte-preserving `editable` exports as the default. Opt-in `runtime` exports
728
+ move Kiln's duplicated scene review clips to a versioned, hash-linked sidecar,
729
+ preserving standalone native glTF playback and the canonical saved revision.
730
+ Available through the CLI, MCP and `@kiln/engine/asset-export` library entry.
731
+ [Usage, provenance contract and limits](docs/export-profiles.md).
732
+
733
+ ## 0.7.1: Preserve CLI outputs on failed writes
734
+
735
+ - GLB, preview PNG, and generated source replacements are written to unique sibling
736
+ temporary files and renamed only after the write closes successfully. A failed
737
+ write or rename leaves the previous output intact. Source and asset exports
738
+ continue to refuse overwrites. Concurrent replacements can fail with a sharing
739
+ violation on Windows; the CLI reports the error without deleting the destination.
740
+
741
+ ## The setup defects, and a bind that was never ours to choose — 2026-09-13
742
+
743
+ - **The render service binds loopback now, and widening it costs a token.** `HOST` unset used to
744
+ bind every interface, and the documented manual start -- `npm start`, "nothing else to
745
+ configure" -- did exactly that with `RENDER_SERVICE_TOKEN` unset and only a warning. What sat
746
+ behind it is a `POST /render` that accepts a 48 MB GLB, parses it with three.js and hands the
747
+ buffers to Dawn and a native Vulkan driver, on a queue that renders one frame at a time -- so a
748
+ single caller can hold the GPU indefinitely, and an untrusted binary-asset parser sits in front
749
+ of a kernel driver. That is the shape of CVE-2026-7482, where a crafted model file drove a heap
750
+ overread in a local inference server that operators had put on the public internet by the
751
+ hundred thousand. Their default was loopback; ours was not.
752
+
753
+ The fix is a rule rather than a flag: **the bind address decides whether auth is required.**
754
+ Loopback is free, because the operating system is the boundary. Anything wider requires a token
755
+ and otherwise refuses to boot -- the same choice this file already makes for a software adapter,
756
+ where a driver regression yields a service that will not start rather than one that silently
757
+ does the wrong thing. `RENDER_SERVICE_ALLOW_UNAUTHENTICATED=1` is the explicit waiver, spelled
758
+ so an operator has to type the word. The container image sets `HOST=0.0.0.0` itself, because a
759
+ container binding loopback is unreachable through `-p`. Verified on hardware: a LAN address is
760
+ refused by default, an exposed bind with no token exits 1 before the GPU is even acquired, and
761
+ an exposed bind with one serves while returning 401 without the header.
762
+
763
+ - **Two generated workspaces could not work, and both for the same reason.** A codex workspace
764
+ wrote `.codex/config.toml` that codex never reads -- every source it consults is
765
+ `$CODEX_HOME`-rooted -- so a harness the README lists reached no Kiln tools at all. A hermes
766
+ workspace redirected `HERMES_HOME` to get its MCP servers and skills read, and that one
767
+ variable resolves the config path *and* the credential path, so the run died before its first
768
+ model call. Both now configure **per invocation** through generated launchers: codex via `-c`
769
+ overrides, hermes via `--in` plus the program store riding the environment into the MCP child.
770
+ Neither writes outside the workspace and neither touches authentication. The rule they
771
+ establish: *a workspace may add configuration to an invocation, but it must not replace the home
772
+ that holds credentials.*
773
+
774
+ The hermes diagnosis was wrong before it was right. There is no provider key to inherit --
775
+ `~/.hermes/.env` holds tool toggles and every API key reads "not set", because the provider is a
776
+ subscription OAuth. What the redirect actually lost was `model.default` and `model.provider`.
777
+ Two more things fell out of measuring rather than assuming: `--skills` takes skill *names*, not
778
+ a path, and the documented `--ignore-rules` was suppressing the workspace's own AGENTS.md.
779
+
780
+ - **A launcher test, because that is why nobody saw either one.** `harness-smoke.mjs` invokes each
781
+ CLI directly and never touches the generated launcher, so both defects passed every check. The
782
+ new test asserts the launcher's shape -- that it registers per invocation, parses under the
783
+ interpreter that will run it, and never names the home that holds credentials.
784
+
785
+ - **`arrayLinear` was making copies that were not copies.** It read only `source.position`, so
786
+ every instance stood up straight however the source was laid down -- while `arrayRadial` next
787
+ door set a rotation on every copy. Two dispatched models hit this independently. Copies now
788
+ carry the source's rotation and scale. Separately, `arrayRadial` could only ever orbit the
789
+ parent's origin, which is what its docstring says and not something a caller could work around;
790
+ it takes an optional `center` now. These helpers had no unit tests at all, which is how a
791
+ disagreement between two neighbours survived. **This changes exported geometry** for a program
792
+ that arrays a rotated or scaled source.
793
+
794
+ - **The helper overview stopped making models guess.** A bare `kiln_list_primitives` returned
795
+ names grouped by category and nothing else, and models reliably guessed the JS-conventional
796
+ `createPart(parent, {...})`. The overview now carries the exact signatures of the two helpers
797
+ every program calls, read from the catalog so it cannot drift from what a detail request
798
+ returns.
799
+
800
+ - **`--render gpu` starts the renderer it needs.** It used to throw when nothing was listening
801
+ even though the installation ships a service that could start, because only the MCP server ever
802
+ asked for that. `gpu` means "I asked for a guarantee and would rather know than be quietly
803
+ downgraded"; answering it with a technicality was the defect. `auto` deliberately still does not
804
+ spawn -- a one-shot sheet should not pay a GPU boot. Fixing it also removed a dead `throw` and a
805
+ real crash: the spawn branch read `options.serviceDir` on a path that can now be reached with no
806
+ options at all.
807
+
808
+ - **Which Kiln answered is now a question a tool can settle.** A server named `kiln` may be a
809
+ different installation, and two live examples on one development machine proved it -- an
810
+ extracted 0.6.0 package registered in `~/.cursor/mcp.json` beside a 0.7.0 checkout, and a stale
811
+ cached tool namespace. Both were local leftovers rather than repo defects, but the workspace
812
+ guide has always said "do not substitute it silently" with nothing behind it.
813
+ `kiln_list_primitives {capabilities:true}` now reports `engine.version` and
814
+ `engine.installUrl`, and the guide says to compare them against the workspace manifest.
815
+
816
+ - `zod` 4.6.4 and `webgpu` 0.6.1. The latter was gated on a GPU smoke that has now passed twice:
817
+ `dawn-vulkan` on a GTX 1660 Ti boots and renders through the engine on 0.6.1.
818
+
819
+ ## Seven harnesses, one version, and the defects only a plain prompt could find — 2026-09-13
820
+
821
+ - **Two new harness adapters: `copilot` and `cursor-agent`**, bringing the dispatch table to
822
+ seven. Both were verified end to end rather than written from documentation, and both needed
823
+ a flag that is not obvious: Copilot's `--allow-all-tools` is *required* for non-interactive
824
+ mode by its own help, and Cursor needs three separate grants -- `--force` for tool calls,
825
+ `--approve-mcps` for the server, `--trust` for the workspace -- because an unapproved MCP
826
+ server is gated independently of tool permission.
827
+ - **`create-workspace.mjs` learned both harnesses.** Copilot reads a workspace `.mcp.json`,
828
+ the same filename Claude Code reads, and not the same contents: its own `copilot mcp add`
829
+ writes `type: "local"` plus a tool filter where Claude writes `type: "stdio"` and none. The
830
+ generated config is the spelling Copilot produced for itself. Cursor gets `.cursor/mcp.json`.
831
+ - **Tool names are not portable, and a prompt that names one is a harness dependency.**
832
+ Copilot namespaces every MCP tool as `<server>-<tool>`, so an agent asked for
833
+ `kiln_list_primitives` found nothing by that literal name and correctly reported the tools
834
+ missing -- while `copilot -p` listing its own tools showed all thirteen as
835
+ `kiln_workspace-kiln_*`. `harness-smoke.mjs` now says the prefix may exist.
836
+ - **The smoke brief was measuring the wrong thing.** It told the agent to call
837
+ `kiln_list_primitives` and then "nothing else". A bare call returns a compact overview where
838
+ `createPart` is one name among eighteen, and the overview's own first line says to ask again
839
+ with `{names:[...]}` for signatures. Two of five harnesses guessed the JS-conventional
840
+ `createPart(parent, {name, geo, material})` and were rejected at build time; three wrote the
841
+ real positional form. Asking for the signatures made both failures pass with the same models.
842
+ The engine's opaque rejection is not at fault: nothing from a sandboxed exception may cross
843
+ that boundary, by design.
844
+ - **One version, everywhere a client can read it.** `package.json` moved to 0.7.0 and four
845
+ other declarations did not: `plugin.json`, `.claude-plugin/plugin.json`,
846
+ `.codex-plugin/plugin.json` and `MCP_SERVER_VERSION`. Every MCP client reported
847
+ `kiln v0.6.0` against a 0.7.0 engine, which is worth nothing to a bug reporter citing a
848
+ version. A test now asserts all five agree, so the next bump cannot miss them.
849
+ - **Gemini CLI is excluded, not missing.** Google switched it off for individual tiers on
850
+ 18 June 2026 in favour of Antigravity CLI; it now fails at startup with
851
+ `IneligibleTierError`. `agy` is the replacement and already had an adapter.
852
+ - **`agy` print mode does not run unattended by itself.** Its log says what happens instead:
853
+ `Print mode: soft-denying tool confirmation "CallMcpTool" at step 10`, then exit 0 with no
854
+ program. It needs `--dangerously-skip-permissions`.
855
+ - **Claude Code's allow-list needed the third server spelling.** `mcp__kiln_workspace` is what
856
+ a generated workspace and a user-level registration both use; with only `mcp__kiln` and
857
+ `mcp__plugin_kiln_kiln` listed, a run stopped and asked for `kiln_list_primitives` by name.
858
+ - New: [docs/harnesses.md](docs/harnesses.md), the per-harness install, upgrade, headless-flag
859
+ and MCP-config reference. [docs/dogfooding.md](docs/dogfooding.md) now opens with the method
860
+ -- three tiers, and why a scripted brief is a wiring probe rather than a dogfood.
861
+ - [ROADMAP.md](ROADMAP.md) carries a stabilisation queue for the first time. The two items at
862
+ the top are setup defects rather than engine defects, and both sit on a path the README
863
+ documents: a generated codex workspace cannot see Kiln at all (codex has no project-local
864
+ config mechanism -- every source is `$CODEX_HOME`-rooted), and a generated hermes workspace
865
+ cannot reach a model (its launcher redirects `HERMES_HOME`, and the provider lives in the
866
+ real home). The codex fix is verified but not applied. The rule it establishes: a workspace
867
+ may add configuration to an invocation, and must not replace the home holding credentials.
868
+
869
+ ## Five tools were unusable in VS Code, and had been since 0.7.0's camera work — 2026-09-12
870
+
871
+ - `kiln_render`, `kiln_edit`, `kiln_inspect`, `kiln_view_interior` and
872
+ `kiln_screenshot_animation` all carried a `z.tuple` camera vector. Zod renders that as
873
+ JSON Schema 2020-12: `prefixItems` plus **`items: false`**, meaning "nothing beyond the
874
+ listed positions". VS Code's tool validator tests `items` for truthiness, so `false`
875
+ reads to it as an array with no items and it refuses to register the tool:
876
+ *"Failed to validate tool mcp_kiln_kiln_edit: tool parameters array type must have
877
+ items."* That is the whole authoring loop — no render, no edit.
878
+ - **Not a regression, and not broken anywhere else.** The tuple arrived with the camera
879
+ capture work in `b2eff76` (2026-09-05) and never changed. Claude Code, opencode 1.18.30
880
+ and the Copilot **CLI** 1.0.83 all register 13 of 13; only VS Code Copilot Chat rejects
881
+ any. The two Copilot surfaces disagree with each other.
882
+ - **Kiln was the conformant party.** SEP-1613 is Final: 2020-12 is the default dialect and
883
+ *"Clients MUST support at least JSON Schema 2020-12"*. The fix is still worth taking,
884
+ because tuples are the one construct the two dialects spell irreconcilably — draft-07
885
+ uses `items: [...]`, 2020-12 uses `prefixItems` + `items: false` — while a bounded
886
+ uniform array (`items: {type: number}` with `minItems`/`maxItems`) is valid and
887
+ identical in both. Choosing it removes a class of client incompatibility rather than
888
+ patching one client.
889
+ - It also made the surface smaller: the `tools/list` frame went **33,937 B to 32,081 B**,
890
+ about 464 tokens saved once per session for every harness, and `docs/tools.md` lost 288
891
+ lines of tuple boilerplate.
892
+ - The tuple TypeScript type is preserved by a static assertion, so nothing downstream
893
+ needed a cast. It is deliberately not a zod `.transform`: that reads as equivalent and
894
+ **breaks every non-MCP harness**, because the Strands skin converts with `io: 'output'`
895
+ where zod refuses — "Transforms cannot be represented in JSON Schema" — while the MCP
896
+ SDK converts with `io: 'input'` and never sees it. Only the in-process parity test
897
+ caught that.
898
+ - Confirmed in the client that rejected them: before the fix VS Code logged the error 26
899
+ seconds after our server connected; after it, all 13 `mcp_kiln_*` tools list, `kiln_edit`
900
+ included. Copilot Chat logs only validation failures and never successes, so this rests
901
+ on the before/after, not on a passing log line.
902
+ - Gated two ways: no array in any advertised schema may have a falsy `items`, and a camera
903
+ vector must still reject two, four, and non-numeric members at runtime. The second gate
904
+ exists because the first one alone would pass on a schema that had quietly become an
905
+ unbounded number list.
906
+
907
+ ## Asset links now say how big they are and who they are for — 2026-09-12
908
+
909
+ - A user reported "massive token usage" from VS Code Copilot, attributing it to
910
+ `resource_link` mishandling. Measuring first: `kiln_save`/`kiln_export` put **941 B of
911
+ URIs** on the wire and no file bytes, and a JSON-RPC tap showed the Copilot **CLI**
912
+ never calls `resources/read` at all. So the stated mechanism does not exist in our
913
+ responses — but the report was standing next to something real.
914
+ - Kiln emitted only four of `ResourceLink`'s fields. The spec also carries **`size`** and
915
+ **`annotations.audience`**, which are exactly how a server tells a client "this artifact
916
+ is for the human to download, not for the model to read". Emitting neither left every
917
+ client guessing from a MIME type, including the ones that guess "inline everything".
918
+ Both are now set, with `priority`: `asset.glb` and `preview.png` are `['user']` because
919
+ the model already receives rendered views as image blocks and geometry as metrics;
920
+ `source.kiln.js` is addressed to both.
921
+ - `editable.zip` is no longer advertised. It is a bundle of the files listed beside it, so
922
+ a client resolving every link paid for the same bytes twice — and it was the largest
923
+ entry while being the only derived one. It stays readable at its URI (which is what the
924
+ widget's download button uses) and stays in `downloadUrls`.
925
+ - Net wire cost went **941 B → 1,017 B**. This is not a size win and is not claimed as
926
+ one: it trades 76 bytes for every client being *able* to decide correctly. The `size` is
927
+ compared in the test against the bytes `resources/read` actually returns for that URI,
928
+ because a declared size a client cannot trust is worse than no size at all.
929
+
930
+ ## Post-release polish: the install guide had gone stale — 2026-09-12
931
+
932
+ - `docs/install.md` told readers to use a checkout **"until an updated package is
933
+ released"**, naming `kiln-engine-0.6.0.tgz`, an eight-tool surface, and five tools the
934
+ package lacked. All three were true when written and none survived `v0.7.0`. Verified by
935
+ driving the published tarball's MCP server over stdio: it advertises **13 tools**,
936
+ including all five that section said were missing.
937
+ - Rewritten to link the latest release and the generated tool reference instead of
938
+ restating either. The section was fragile because it duplicated facts that live in gated
939
+ files — `docs/tools.md` is already checked against the registry, so it does not need
940
+ repeating in prose that nothing checks.
941
+ - A test now enforces the rule that follows: install guidance may not hard-code a release
942
+ tarball version, and must point at `releases/latest` and `tools.md`. Scoped to the docs
943
+ that tell a reader how to *obtain* the package; `CONTRIBUTING.md` keeps its
944
+ `kiln-engine-0.6.0.tgz` because there the filename is the evidence for why the version
945
+ policy exists. Both failure modes verified — a pinned version, and a link that stops
946
+ pointing at the latest release.
947
+ - `docs/migration.md` said "keeps the package version at `0.6.0` until release review",
948
+ which stopped being true at the tag.
949
+ - **AGENTS.md now explains the two tool surfaces**, which was a real comprehension trap:
950
+ `registry.ts` holds the in-process loop's four tools and the MCP surface's thirteen, and
951
+ "both skins consume it" read as one shared list. The one differing name,
952
+ `kiln_screenshot`, is **merged rather than missing** — in-process, `kiln_render` returns
953
+ metrics only and `kiln_screenshot` carries the six-view grid, so a cheap structural check
954
+ need not pay for an image; on MCP, `kiln_render` is unified and returns metrics, part
955
+ paths and images together, so a separate screenshot tool would be a second way to ask for
956
+ the same grid.
957
+ - Also measured while checking the new-developer path, in a fresh clone: **~31 seconds from
958
+ nothing to a rendered GLB** (4s clone, 26s install, 1s render), and the documented
959
+ render-service step works verbatim — `npm --prefix render-service ci --ignore-scripts` in
960
+ 2s, then 37 tests pass.
961
+
962
+ ## The Windows failure was a real bug in the alias lock — 2026-09-12
963
+
964
+ - `rejects lost updates from eight independent processes` went red once on a Windows
965
+ runner. The first read of it here was wrong, and the number that refutes it was in the
966
+ log all along: **the test failed in 123 ms**, while both hang guards are 10 and 15
967
+ seconds away. Nothing was slow. A worker died, fast.
968
+ - The only way `compareAndSet` does that is the `throw error` beside its lock, reached
969
+ whenever `mkdir` reports contention with a code other than `EEXIST`. **Windows has
970
+ exactly that case**: directory deletion is not synchronous, so a directory whose last
971
+ handle has not closed sits in pending-delete, where `mkdir` on that name answers `EPERM`
972
+ or `EACCES`. Every release runs `rmdir` in a `finally`, so with eight processes
973
+ contending, one landing in that window is ordinary contention — and it was rethrown.
974
+ - `isLockContention` now recognises those two codes **on Windows only**. Widening it to
975
+ every platform would be the over-fix: on POSIX, `EPERM`/`EACCES` from `mkdir` means the
976
+ parent directory is not writable, and swallowing that would report a permission fault as
977
+ "busy". Both mistakes are pinned by tests — reverting to `EEXIST`-only fails the Windows
978
+ case, over-widening fails the POSIX guard.
979
+ - Lock release stopped being able to decide the call's outcome. `rmdir` has no `force`, and
980
+ on Windows it can fail while a scanner holds the directory — which inside a `finally`
981
+ would replace a precise `Alias conflict` with an unrelated errno, or turn a successful
982
+ write into a failure. Cleanup failures are now swallowed, and a lingering lock degrades
983
+ to "busy" rather than corrupting anything.
984
+ - Asserted on the decision rather than through a real Windows filesystem, so a Linux host
985
+ checks every branch and the platform is a parameter. The worker timings added alongside
986
+ are what made the failure legible in the first place, and they stay: one `FAILED` beside
987
+ seven fast children points at the code, where all eight slow points at the host.
988
+
989
+ ## `--receipt` was relative to the wrong directory — 2026-09-12
990
+
991
+ - `scripts/verify-package-receipt.mjs` resolved `--receipt` against `--root` rather than the
992
+ working directory. Those are **the same directory in the CI step**, which is why the
993
+ original passed its own tests and every CI run — and different everywhere else.
994
+ - Found by using it. Assembling the release from downloaded artifacts puts the receipts in
995
+ a staging directory while `root` is the repository, so every path silently became
996
+ `<repo>/linux-package.json` and all four verifications failed with `ENOENT`. The
997
+ assembly refused to publish, which is what it is for.
998
+ - A path a person types on a command line belongs to where they typed it. `root` now
999
+ locates `package.json` and nothing else.
1000
+ - The test that passed while the bug existed is the finding worth keeping: its fixture had
1001
+ `root` and the receipt in the same temp directory, mirroring the CI step, so it could not
1002
+ tell the two rules apart. It now addresses the receipt explicitly and a second case pins
1003
+ the resolution rule with `root` and the receipt in *different* directories. Both fail if
1004
+ the old behaviour is reintroduced — the doubled path in the error is its signature.
1005
+
1006
+ ## webgpu 0.6.1 is held out of 0.7.0 — 2026-09-12
1007
+
1008
+ - The bump was slated for this release and then held, because reading what is in it
1009
+ changed the decision. `webgpu@0.6.1` was published **the same day**, and
1010
+ `v0.6.0...v0.6.1` bumps the `third_party/dawn` submodule to "latest" alongside a real
1011
+ fix — unmapping a device's buffers when the device is destroyed, with 114 lines of new
1012
+ tests upstream.
1013
+ - The fix is worth having: render-service creates and destroys a device per capture, and
1014
+ buffer unmapping on destroy is the readback path. That is not what holds it.
1015
+ - What holds it is that **nothing here can exercise it.** CI installs render-service with
1016
+ `--ignore-scripts` so the 37 pure tests run without a device — which means they prove
1017
+ the package resolves and nothing more. `render-service/src/`, `package.json` and
1018
+ `package-lock.json` all ship inside the engine tarball, so an untested native bump would
1019
+ be in the release artifact, attested by four receipts that never touched a GPU.
1020
+ - So 0.7.0 ships from what CI has fully proven, and this is the first change after it,
1021
+ gated on the GPU smoke that already holds Phase 4 §7 and the two conformance runs.
1022
+ - Noted while looking, not changed: `render-service/package.json` has an `allowScripts`
1023
+ field keyed `{"webgpu@0.6.0": true}` that **nothing reads** — not Bun's
1024
+ `trustedDependencies`, no `@lavamoat/allow-scripts` in the tree, and the README says
1025
+ plain `npm install`, which runs scripts anyway. It looks like a control over native
1026
+ build execution and is not one. Left for a deliberate decision rather than deleted.
1027
+
1028
+ ## All four platform receipts come from CI now — 2026-09-12
1029
+
1030
+ - The 2026-09-05 release attached `linux-package.json` and `windows-package.json`, but
1031
+ `test:package` ran only in the macOS job — so two of the four platforms a release
1032
+ attests to had **no reproducible source** and were produced by hand. `linux-package`
1033
+ and `windows-package` jobs close that; CI now emits the tarball and all four receipts
1034
+ from one run.
1035
+ - The receipt check left the workflow. It was a ~400-character `node -e` one-liner inside
1036
+ the macOS job, which is tolerable at one platform and not at four: the expected platform
1037
+ differs per job, so copying it is three chances to assert `darwin` on a Linux runner and
1038
+ have the receipt pass anyway. **A receipt that cannot fail is not evidence.** It is now
1039
+ `scripts/verify-package-receipt.mjs`, one line in each of the four jobs.
1040
+ - Extracting it bought two things beyond deduplication. Expected Node and npm versions
1041
+ come from `engines` rather than being written a second time, so `check-toolchain.mjs`
1042
+ stays the single source; and because the receipt records `engineVersion`, each platform
1043
+ receipt is now checked against `package.json` — so a release's receipts are tied to its
1044
+ version rather than merely sitting beside it.
1045
+ - `--tarball` overrides the path the receipt recorded, which is what makes one verifier
1046
+ serve both uses. In the job the receipt names a tarball sitting right there. At
1047
+ release-assembly time the receipts are downloaded artifacts whose absolute paths belong
1048
+ to a runner that no longer exists — but their `tarballSha256` is exactly what must be
1049
+ checked against the file about to be published.
1050
+ - Verified against a real receipt rather than only fixtures: the Linux smoke was run on a
1051
+ Linux host first (16 checks, `engineVersion 0.7.0`), then that receipt was accepted,
1052
+ **rejected when asked to assert `darwin`** — the precise bug copying the one-liner would
1053
+ have caused — and rejected again after one byte was appended to the tarball, naming both
1054
+ hashes.
1055
+ - `REQUIRED_CHECKS` goes to eight, and the gate added two changes ago is what forced that
1056
+ edit: adding a job without deciding whether it gates a merge fails the suite. First time
1057
+ one of this phase's gates caught the next change instead of a past one.
1058
+
1059
+ ## 0.7.0 — the version moves with what ships — 2026-09-12
1060
+
1061
+ - `version` sat at `0.6.0` from the OSS release through **21 shipped changes**, while CI
1062
+ named every tarball from it. Two people could hold `kiln-engine-0.6.0.tgz` and have
1063
+ materially different software — across a three.js major, a history rewrite and a `dist/`
1064
+ re-add. It moves per shipped change now: patch for a fix, minor for changed or added
1065
+ capability, which is the normal case pre-1.0.
1066
+ - **0.7.0 rather than a patch.** What accumulated is behaviour: three.js r186, the
1067
+ CommonJS removal it exposed, 128 lines of previously understated tool schema in
1068
+ `docs/tools.md`, and a `shadows.type` contract that now selects a filter from
1069
+ `basic`/`pcf`/`vsm` instead of naming one that no longer exists.
1070
+ - A bump has to be rebuilt, because `runtimeBuildIdentity` hashes the version into each
1071
+ entry's `identity` in `dist/build.json` — the value a build receipt cites. A test now
1072
+ asserts `package.json` and every `dist/build.json` entry agree.
1073
+ - **Nothing caught that drift before, and this was verified rather than assumed.** With
1074
+ the version at 0.7.0 and `dist/` still recording 0.6.0, the full suite passed — 1860
1075
+ tests, zero failures. `mcp-bundle.test.ts` rebuilds `dist/mcp-server.mjs` and compares
1076
+ bytes, but the version is read from `package.json` at runtime rather than inlined, so
1077
+ the bytes are identical and the stale record is invisible to it. Confirmed by the
1078
+ rebuild: `dist/build.json` was the only file that changed.
1079
+
1080
+ ## The documented offline gate did not run two of the repository's checks — 2026-09-12
1081
+
1082
+ - `bun run test` is `bun test src scripts`, so it never reached `render-service/`. Its 37
1083
+ tests ran only in CI, in the job 13.7 added — so a contributor editing that subsystem
1084
+ got **no local signal at all** and found out from a red pull request. `check:skills`
1085
+ was the same shape in the other direction: a step in CI's `checks` job that the
1086
+ documented gate never named.
1087
+ - The subsystem cannot fold into `bun test src scripts`: it is a separate npm project
1088
+ with its own lockfile and a native dependency. It gets `bun run test:render-service`
1089
+ instead, which needs `npm --prefix render-service ci --ignore-scripts` once. All 37
1090
+ tests are pure — framing arithmetic, PNG readback packing, cache identity, contract and
1091
+ preset validation — so none needs a GPU or the Dawn build, which is the CI job's own
1092
+ recorded reason `--ignore-scripts` suffices.
1093
+ - Both are now in the gate in `AGENTS.md` and `CONTRIBUTING.md`, and a test asserts the
1094
+ general defect rather than the instance: **every `bun run` command the agent guide
1095
+ names must exist as a script**, and the two that were missing must appear in the gate
1096
+ block itself.
1097
+ - Reading the fenced block rather than the whole file is what makes that discriminate.
1098
+ The first version checked "named anywhere in `AGENTS.md`" and stayed green when the
1099
+ line was deleted from the gate — because the prose underneath mentions the same
1100
+ command. Verified three ways after narrowing: a guide naming a script that does not
1101
+ exist, the line dropped from the block, and the script removed from `package.json` all
1102
+ fail.
1103
+
1104
+ ## Three CI jobs reported and blocked nothing — 2026-09-12
1105
+
1106
+ - `build portable Node package` and both `Node package · macOS` jobs run on every push
1107
+ and every pull request, and were required by nothing. Measured before changing
1108
+ anything: **24 of 24 green across the last 8 runs on main**, and a pull request that
1109
+ broke macOS packaging would still have merged.
1110
+ - The test named `every CI job blocks; none of them merely reports` was green throughout,
1111
+ because it checked only `continue-on-error` — the narrower of the two ways a job can
1112
+ fail to block. Its own comment already described the gap it did not cover: *"the other
1113
+ half is a GitHub setting."* For three jobs that setting had simply never been made.
1114
+ - 13.4 set the precedent that a context is required only once it has reported, since a
1115
+ required context that never reports blocks every merge instead of guarding it. It
1116
+ promoted the Windows job on nine runs with seven green; these three clear that bar.
1117
+ - The test now derives every context `ci.yml` can produce — expanding the macOS matrix
1118
+ into its two legs, because GitHub requires the expanded name — and compares it against
1119
+ the list that has to be required. Adding a job without deciding whether it gates a
1120
+ merge now fails the suite. Verified three ways: a new job, a renamed job, and a dropped
1121
+ matrix leg each fail, naming the exact context gained or lost.
1122
+ - **`build the gallery` is deliberately not required.** `pages.yml` is path-filtered, so
1123
+ it stays silent on any pull request touching none of `examples/`, `site/`, `src/`,
1124
+ `scripts/authorship.ts` or `README.md`. Requiring it would block every such merge —
1125
+ the exact trap 13.4 named. The rule is now written down: unconditional workflow,
1126
+ required; conditional workflow, unrequired.
1127
+
1128
+ ## The documented way to convert an old clone did not work — 2026-09-12
1129
+
1130
+ - The 2026-09-10 rewrite moved `refs/tags/oss-2026-09-05` as well as `main`, and a tag
1131
+ is the one ref `git fetch` will not update on its own. So the README's remedy
1132
+ — `git fetch origin && git reset --hard origin/main` — left the old tag in place,
1133
+ holding every removed file reachable. Measured on the clone this was found in:
1134
+ `.git` at 228 MB where a fresh clone is 26 MB, with identical content.
1135
+ - Reproduced by running the documented commands verbatim in a throwaway copy. The two
1136
+ obvious next guesses are not merely ineffective, they are **refused**: plain
1137
+ `git fetch --tags` and even `--prune-tags` both report `would clobber existing tag`,
1138
+ so a reader has no route to the answer without already knowing `--force`.
1139
+ - The instructions now force the tag and then reclaim. Verified end to end on the real
1140
+ stale clone: 228 MB to 23 MB, `fsck` clean, `HEAD` and the working tree hash
1141
+ byte-identical before and after. The plan document already recorded that "the tag was
1142
+ the whole rewrite" for the *push* side; this is the same fact on the clone side, which
1143
+ is the side a reader is on.
1144
+ - `scripts/reliability.test.mjs` pins it, and asserts the **absence** of the broken form
1145
+ as well as the presence of the working one — the short version is what a later
1146
+ simplification reaches for.
1147
+ - Clone figures re-measured: 60 MB full and 49 MB with `--filter=blob:none`, where the
1148
+ README said 52 MB and 48 MB. `.git` grew from 18 MB to 26 MB when the runtime bundles
1149
+ were re-added after the rewrite; the checkout is unchanged at 34 MB. The timing claims
1150
+ are removed rather than restated — they are network-bound and cannot be honestly
1151
+ re-verified from a different link.
1152
+
1153
+ ## Skill bytes are canonical, and the SEP-2640 record is corrected — 2026-09-12
1154
+
1155
+ - Re-read SEP-2640 from the spec text on its PR branch and the working group's own
1156
+ repository, rather than from a page about it. Three things this project had recorded
1157
+ were wrong.
1158
+ - **The design changed, and not cosmetically.** The extension defines `skills/list`,
1159
+ `skills/get`, and an optional `resources/directory/read`. **`skills/activate` is
1160
+ gone**, and with it the bundle, the scoped tools revealed on activation, and
1161
+ progressive disclosure inside the protocol. The SEP now "defines only the transport
1162
+ binding" and delegates the format and disclosure model to the Agent Skills
1163
+ specification.
1164
+ - **The status heuristic was backwards.** The note here said "read the labels, not the
1165
+ prose", after a summary twice reported the SEP as Final. The SEP document's own
1166
+ `Status:` field *does* read Final; the pull request's label still reads `draft`, and
1167
+ under the SEP process the document is the status of record. The note pointed at the
1168
+ weaker signal.
1169
+ - **"Nothing to build against" is no longer true.** The spec depends on nothing beyond
1170
+ base Resources, capability declaration rides SEP-2133, and the pinned
1171
+ `@modelcontextprotocol/sdk@1.30.0` already carries `extensions` in
1172
+ `ServerCapabilitiesSchema` while `setRequestHandler` takes any schema. Conformance
1173
+ tests merged 2026-09-11.
1174
+ - 7.12 still waits, for a different and checkable reason: **nothing public consumes it.**
1175
+ The TypeScript SDK's convenience wrappers PR is closed unmerged, every public host is a
1176
+ prototype in one contributor's forks, Claude Code's is internal and not public, and the
1177
+ GitHub MCP server prototype is closed. Shipping it now adds a third transport no host
1178
+ can call. **Re-check the TypeScript SDK, not the SEP's status.**
1179
+ - The re-read found one real gap. `skills/list` publishes a per-file `digest` and `size`
1180
+ over raw bytes, and this project already publishes sha256 digests of skill artifacts at
1181
+ `/.well-known/agent-skills/index.json` — but `skills/**` was `-text` in
1182
+ `.gitattributes` and carried the same accidental line-ending mix found in
1183
+ `render-service/src`: five files fully CRLF, two mixed, seven LF. It went unnoticed
1184
+ because the frontmatter parser is tolerant of both. Tolerant parsing is right;
1185
+ publishing a digest over whichever ending an editor happened to write is not.
1186
+ - Normalized to LF across `skills/` and both registry copies, and `check:skills` now
1187
+ rejects CRLF in any skill file. Verified by reintroducing one — and then by `git
1188
+ checkout` restoring the pre-normalization bytes and the gate firing on that too, which
1189
+ is the regression path that actually happens.
1190
+ - Two pieces of the eventual work are already in place: the digest derivation extends
1191
+ 7.13's rather than being invented, and `check:skills` has enforced the SEP's
1192
+ name-equals-directory rule since Phase 7.
1193
+
1194
+ ## The published tool reference is correct again — 2026-09-12
1195
+
1196
+ - `docs/tools.md` is generated from the tool registry, and `docs/` is in the package's
1197
+ `files` list — so a stale reference is not an internal note being wrong, it is shipped
1198
+ to every install. It was stale: regenerating moved **128 lines**.
1199
+ - The cause was an in-range dependency bump. `zod` 4.4.3 → 4.6.2 changed
1200
+ `z.toJSONSchema` to emit `items: false`, `minItems` and `maxItems` for fixed-length
1201
+ tuples, so every tuple input in the published reference understated its own schema.
1202
+ - Nothing caught it because the generator's `--check` mode appeared in **no workflow and
1203
+ no test** — the same shape as the render service's 37 tests running nowhere. An
1204
+ offline gate that runs nowhere is worth exactly as much as no gate.
1205
+ - It is a test now rather than a workflow step: the generator exports
1206
+ `toolReferenceMarkdown()` with its CLI behaviour behind a direct-entry guard, and
1207
+ `scripts/tool-reference.test.mjs` compares the published file to the registry. `bun
1208
+ test` already runs on Linux, Windows and both macOS architectures, so this needs no
1209
+ new job — and cannot become a job somebody forgets to add, which is the failure mode
1210
+ being fixed.
1211
+ - Compared whole rather than section by section: the drift that shipped was four added
1212
+ lines inside one nested schema, which any summary comparison would have missed.
1213
+ Verified by dropping a single `minItems` line and watching the test name the file and
1214
+ the command that fixes it.
1215
+
1216
+ ## Every directory that holds code is linted now — 2026-09-12
1217
+
1218
+ - 14.4 left the one-off tools in `scripts/` out and priced the alternative at 26 lint
1219
+ findings, a 1,530-line reflow, and 162 lines of coverage slack. After the ratchet got
1220
+ a scope the third term is **zero** — `scripts/**` is not instrumented at all — so what
1221
+ was left was a diff, and it is taken. **480 files where 442 were.**
1222
+ - Thirty-four format findings and thirty-two real ones, over `.mjs` and `.ts` alike; the
1223
+ `.ts` files under `scripts/` had never been linted either, since the pattern was
1224
+ `src/**/*.ts`.
1225
+ - Twenty-five `useTemplate`, seventeen of them one idiom: `JSON.stringify(…) + '\n'`,
1226
+ the receipt-file shape, across eleven files. A shared helper is the obvious DRY move
1227
+ and is deliberately not taken — `package-plugin.mjs` ships in the package `files`
1228
+ list, so importing one means shipping another file for a cosmetic win.
1229
+ - Three findings were not cosmetic. **Two `any` in the pilot evaluation host**: one read
1230
+ as `previous.config` / `previous?.deadline`, now typed; one feeding a budget estimator
1231
+ through optional chains, now a declared `PilotToolInput` that names exactly the fields
1232
+ the image-cell budget depends on — documentation the estimator did not have. A loop's
1233
+ update step hiding in its condition in `smoke-package.mjs`. And **a literal ESC byte**
1234
+ in `harness.mjs`'s ANSI stripper, invisible in every diff and editor, now `\u001B`.
1235
+ - Also surfaced, and deliberately left for its own change: `docs/tools.md` is stale and
1236
+ its `docs:tools --check` drift checker runs in no workflow and no test. The cause is
1237
+ the `zod` 4.4.3 → 4.6.2 bump — `z.toJSONSchema` now emits `items: false`, `minItems`
1238
+ and `maxItems` for fixed-length tuples — so the published tool reference has
1239
+ understated every tuple schema since. Ledger 14.8.
1240
+
1241
+ ## render-service joins the lint surface — 2026-09-12
1242
+
1243
+ - It was left out because `render-service/src/**` is `-text` in `.gitattributes` and the
1244
+ formatter would rewrite the line endings. Looking at what the attribute protected
1245
+ turned that reason into the finding.
1246
+ - **It was sweeping, not deliberate.** The line arrived in the OSS release commit
1247
+ alongside the other `-text` entries, and what it froze is inconsistent: four files
1248
+ fully CRLF, `renderer.mjs` at 395 of 399 lines, three carrying a single stray CRLF
1249
+ line, four fully LF. One stray CRLF line in an otherwise-LF file is an editor's
1250
+ signature, not a decision.
1251
+ - **Those bytes are genuinely hashed**, so the replacement matters:
1252
+ `fingerprintRendererInputs` walks that directory into the renderer's capture identity.
1253
+ But `-text` only guarantees "whatever was committed", while the `* text=auto eol=lf`
1254
+ rule guarantees what the fingerprint needs — one line ending on every platform. The
1255
+ normalization is a stricter guarantee than the attribute it replaces.
1256
+ - **Nothing re-validates the old digest.** `capture-producer.v1` fingerprints in
1257
+ checked-in provenance receipts record the renderer build a poster was made under; no
1258
+ test recomputes one. 13.6 already edited two of these files and every gate stayed
1259
+ green — a later build hashing differently is the point of recording it.
1260
+ - Eight files to LF, the attribute removed with the reasoning left where the next reader
1261
+ will ask, and the directory linted: **442 files where 416 were.** Twenty-two format
1262
+ findings, then five real ones. One of them, `useIterableCallbackReturn` in
1263
+ `presentation-presets.mjs`, is the same defect 13.5 fixed in the Bradley-Terry fit —
1264
+ a one-expression arrow in a `forEach` returning a value the contract discards — sitting
1265
+ in production render-service code that was outside every lint gate this repo has.
1266
+ - `reliability.test.mjs` pins both include patterns and asserts the `-text` line stays
1267
+ gone. render-service's own 37 tests pass on the normalized sources.
1268
+
1269
+ ## The coverage ratchet has a scope now — 2026-09-12
1270
+
1271
+ - The previous entry blamed 162 lines of lost coverage slack on helper scripts being
1272
+ measured and never executed. The number was right; the mechanism was wrong. Bun
1273
+ instruments only files it loads, and `coveragePathIgnorePatterns` already dropped
1274
+ `**/*.test.mjs`, so those tools were never in the report — there is no `SF:` record
1275
+ for `dispatch-asset`, `upload-posters`, `geometry-experiments` or `harness`.
1276
+ - Exactly **four** files under `scripts/` were instrumented, because tests import them:
1277
+ `evaluation/observe-shipping.mjs`, `build-runtime.mjs`, `evaluation/conditions.ts` and
1278
+ `authorship.ts`. 229 lines against the engine's 45,726 — 0.5% of the denominator, and
1279
+ worth 0.10 points. Two of the four are dense single-expression files, so reflowing
1280
+ them inflated their line counts while their covered lines stayed put. That is where
1281
+ the 162 lines went.
1282
+ - So the finding is narrower and worse than "helper scripts dilute the ratchet": the
1283
+ engine's coverage contract had a **repo-only formatting input**. A number this
1284
+ repository enforces as policy could be moved by reformatting code that does not ship.
1285
+ - `bunfig.toml` now ignores `scripts/**` for coverage. Those tests still run — they are
1286
+ the gates — but their sources leave the denominator, so the ratchet is a statement
1287
+ about the shipped engine and nothing else. Re-measured on the narrowed scope: **95.39%
1288
+ functions, 92.59% lines**.
1289
+ - Narrowing hands back 44 lines of slack no new test earned, so `lines` goes 92 → **92.1**
1290
+ to hold the 224-line margin 13.3 chose rather than pocket it. `functions` stays at 94,
1291
+ where the margin is unchanged in practice. Tightening further is a separate decision.
1292
+ - And the record carries its own scope now. `measuredBaseline` requires `measuredOver`,
1293
+ the gate refuses a baseline without it, and it prints it: `Recorded baseline:
1294
+ functions 95.39%, lines 92.59% (over src/, bun@1.4.2, 2026-09-12)`. A percentage
1295
+ without its scope is as ambiguous as a threshold without a baseline — which is what
1296
+ made the wrong explanation above plausible enough to write down.
1297
+
1298
+ ## The repository's own gates are linted now — 2026-09-12
1299
+
1300
+ - Adding a file to `scripts/` and running `biome check` on its path printed "No files
1301
+ were processed." Biome's `files.includes` was `src/`, `site/src/` and `site/*.ts`, so
1302
+ the directory holding this repository's gates — and which `test:coverage` already
1303
+ measures — sat outside the lint surface. The zero-warning baseline 13.5 established
1304
+ was zero across a subset.
1305
+ - Taken: `scripts/check-*.mjs` and `scripts/**/*.test.mjs` — the checks are now checked.
1306
+ 416 files where 405 were. Ten findings were behind the hole: seven format, and three
1307
+ real ones. A dead `const` in `check-vision.mjs`, easy to miss because the identical
1308
+ line two loops down is load-bearing. Two `useTemplate` on the
1309
+ `JSON.stringify(…) + '\n'` idiom. `reliability.test.mjs` asserts both include
1310
+ patterns, verified by dropping one and watching it fail.
1311
+ - Left, with the cost measured rather than guessed. The rest of `scripts/` is one-off
1312
+ tools, several authored as dense single-expression lines. Taking the whole directory
1313
+ is 26 lint findings, a 1,530-line reflow (3,078 lines to 4,608, pure formatting), and
1314
+ **162 lines of coverage slack** — lines fall 92.49% to 92.14% against a threshold of
1315
+ 92. Measured by taking the full pass and running the gate.
1316
+ - **Corrected below**: the mechanism first given for that third cost was wrong, and it
1317
+ pointed at the wrong fix.
1318
+ - `render-service/` stays out for an unrelated reason: its sources are CRLF by
1319
+ `.gitattributes`, and the formatter would rewrite the line endings.
1320
+ - One incidental finding: Biome prints at most 20 diagnostics by default, so a count
1321
+ read off `bun run lint` is a floor, not a total. The gate still fails either way.
1322
+
1323
+ ## The prompt-cache breakpoint is asserted on the wire — 2026-09-12
1324
+
1325
+ - The two deferred rows in the queue were both deferred *with a reason*. Checking the
1326
+ reasons rather than inheriting them changed both answers.
1327
+ - **The `ai` 7 family is blocked upstream, and money was never the constraint.** The
1328
+ recorded reason was that only `test:live` exercises those paths and it spends money.
1329
+ What actually blocks it: `@strands-agents/sdk@1.17.0` is the latest release, declares
1330
+ peer `@ai-sdk/provider: ^3.0.0`, and types its `VercelModel` on `LanguageModelV3`,
1331
+ while `@openrouter/ai-sdk-provider@3` requires `ai@^7`, which depends on
1332
+ `@ai-sdk/provider@4`. No budget moves that.
1333
+ - And `typecheck` would not have caught it. `@ai-sdk/provider@4` still exports
1334
+ `LanguageModelV3` beside V4, so the import keeps compiling; the break is a v4 model
1335
+ handed to a v3 wrapper, at runtime, on the one path no offline suite drives. There is
1336
+ a gate for it now: `scripts/peer-ranges.test.mjs` walks the installed tree and fails
1337
+ on a peer range the version beside it does not meet.
1338
+ - **It found a mismatch that already exists.** `@strands-agents/sdk` declares peer
1339
+ `@anthropic-ai/sdk: ^0.109.1` — on a 0.x version that means `>=0.109.1 <0.110.0` —
1340
+ and the last dependency refresh took 0.125.0. Fifteen minors past the declared
1341
+ range, moved by a routine in-range update, noticed by nothing. Kept, and listed with
1342
+ its reasoning in the gate's `ACCEPTED` table, so the next one fails while this one is
1343
+ a decision on the record rather than a silence.
1344
+ - **The native cache breakpoint is now asserted against the bytes.** The OpenRouter
1345
+ half already was: it drives `doStream` with `fetch` replaced and reads the outgoing
1346
+ JSON. The native half asserted that `toCachedSystemPrompt` returns
1347
+ `[TextBlock, CachePointBlock]` — the input to the transport, never its output. So the
1348
+ claim the design rests on, that a cache point becomes `cache_control` on the wire,
1349
+ was carried by a comment, on two dependencies that move inside their caret ranges on
1350
+ every refresh. Losing it bills full price for every prefix that should have been a
1351
+ cache read, with every offline gate green.
1352
+ - Both native transports are captured now, offline: the Anthropic client takes a
1353
+ `fetch` that records and throws, Bedrock a `requestHandler` that does the same. Both
1354
+ tests are differential — a plain string goes out with no breakpoint — so the
1355
+ breakpoint has one possible origin. Establishing that took an injected defect, which
1356
+ disproved the first draft's premise: Bedrock does *not* auto-inject a system cache
1357
+ point here, because that needs a `cacheConfig` and Kiln passes none. Patching the
1358
+ installed adapters to drop `cache_control` and the converse `cachePoint` fails
1359
+ exactly the new tests while every pre-existing test stays green — including the one
1360
+ named "the adapter emits `cache_control`", which never checked that it did.
1361
+ - **7.12 / SEP-2640 stays deferred, for a better reason.** PR 2640 is open and still
1362
+ labelled `draft`; a page summary reported it Final for the second time and was wrong
1363
+ again. But the row turns on something downstream: `@modelcontextprotocol/sdk`
1364
+ publishes 1.30.0 as latest, the version already installed, so there is no TypeScript
1365
+ surface for `skill://` resources to build against whatever the SEP does next. Check
1366
+ the SDK's version, not the pull request.
1367
+
1368
+ ## A preset's shadow filter now selects a filter — 2026-09-12
1369
+
1370
+ - Going to compare shadow filters by eye turned up the reason there was nothing to
1371
+ compare: **no shipped presentation preset enables shadows.** `neutral-studio-v1` sets
1372
+ `shadows.enabled: false` to preserve the pre-registry visual, `gallery-studio-v1` is a
1373
+ spread of it, and every light has `castsShadow: false`. So r186 deleting the PCFSoft
1374
+ implementation is not a visible regression here, and yesterday's switch to
1375
+ `PCFShadowMap` corrected a line that never runs. Its claim to keep the log clean was
1376
+ overstated: the assignment sits inside `if (preset.shadows.enabled)`, so three's
1377
+ deprecation warning never fired from this service either.
1378
+ - What was actually wrong is that `shadows.type` described a filter and selected nothing.
1379
+ The validator demanded the exact string `pcf-soft` while the renderer ignored the field
1380
+ and hardcoded a constant — and after r186, the one name the schema accepted was the one
1381
+ name that no longer exists.
1382
+ - It selects a filter now. `SHADOW_FILTERS` is `basic`, `pcf`, `vsm` — what r186 left —
1383
+ and the renderer maps each to its three constant. `vsm` is the soft option that
1384
+ survives, with one catch recorded where someone choosing it will read it: under VSM
1385
+ every shadow receiver also casts, so a ground plane starts casting too.
1386
+ - The names live in the schema that validates them and the constants live in the module
1387
+ that imports three, so the renderer checks at load that the map covers the list. A name
1388
+ the validator accepts and the map lacks would set `shadowMap.type` to `undefined` mid
1389
+ render — not a crash, just a quietly different image. Verified by adding `pcf-soft` back
1390
+ to the list and watching the service refuse to start.
1391
+ - The gallery is not affected either, for a different reason: neither `<Canvas>` in the
1392
+ site sets r3f's `shadows` prop, so `renderer.shadowMap.enabled` stays false there too,
1393
+ and the only shadowing on that surface is drei's `ContactShadows` — its own depth pass to
1394
+ a texture, which never consults `shadowMap.type`. Between the two paths, r186's PCFSoft
1395
+ removal changes nothing anyone can see in this repository.
1396
+ - **`render-service` is in CI now**, which it never was. Thirty-seven tests guarding a
1397
+ shipped subsystem ran on nobody's machine but a maintainer's — including, until this
1398
+ commit, the change above it. It is a separate npm project with a native dependency,
1399
+ which is presumably the reason; but the tests do not need it. All 37 are pure: framing
1400
+ arithmetic, PNG readback packing, cache identity, contract and preset validation, and
1401
+ not one acquires a device. So the job installs with `--ignore-scripts`, skipping the
1402
+ `webgpu` package's Dawn build. Verified against exactly that install with no binding
1403
+ built: 37 pass, 0 fail. The GPU smoke and the two conformance runs stay manual, because
1404
+ those genuinely need a device.
1405
+
1406
+ ## The lint baseline is zero, and stays zero — 2026-09-12
1407
+
1408
+ - `bun run lint` reported **14 warnings and 11 infos** and exited 0. All 25 are fixed;
1409
+ the tree now reports nothing. The point is not tidiness: a baseline everyone agrees to
1410
+ ignore is a baseline the twenty-sixth finding arrives invisible against.
1411
+ - Two of them were not style. `isFinite` in the render inspector is the coercing global,
1412
+ where `Number.isFinite` is not — identical on a `Box3` component, taken because the next
1413
+ caller to pass something looser is the one the coercion silently accepts. And
1414
+ `ids.forEach((id, i) => index.set(id, i))` in the Bradley-Terry fit returned the map
1415
+ from a callback whose contract discards it; the index is built from its pairs instead.
1416
+ - Three `any` bounds in `buildSandboxGlobals` carried
1417
+ `// eslint-disable-next-line @typescript-eslint/no-explicit-any`. This repository lints
1418
+ with Biome and has no ESLint config, so those suppressed nothing while reading as
1419
+ reviewed-and-accepted. Rather than translate them, the `any` is gone: `never[]` is the
1420
+ correct bound for "any parameter list" and `unknown` for "any return", with one narrow
1421
+ assertion where tsc resolves a call through the constraint and can only see `unknown`.
1422
+ - The quaternion fixture's `0.7071` is now `Math.SQRT1_2` — exact, and it says which angle
1423
+ a quarter turn about Y actually is.
1424
+ - **Warnings now fail.** `lint` runs with `--error-on-warnings`, and because that flag
1425
+ does not reach Biome's info severity — where `useTemplate` and several others sit by
1426
+ default — the seven rules this cleanup touched carry an explicit `error` in
1427
+ `biome.json`. Each of the seven was verified by reintroducing its defect and confirming
1428
+ the gate fails and names the rule.
1429
+ - The `useIterableCallbackReturn: "warn"` downgrade is gone with the finding it was
1430
+ written around. A test asserts the seven severities and the `lint` script itself: a rule
1431
+ lowered to keep a build green is indistinguishable from a rule nobody wanted, and that
1432
+ has happened here before.
1433
+ - `CONTRIBUTING.md` says the contract plainly — the tree reports nothing, so any
1434
+ diagnostic a change produces belongs to that change, to fix or to suppress with a
1435
+ `biome-ignore` comment that says why.
1436
+
1437
+ ## The gates stop reporting and start blocking — 2026-09-12
1438
+
1439
+ - **The Windows job blocks.** It landed reporting-only on purpose, because the fault it
1440
+ was added to rule out — a native GLib `g_system_thread_free` / invalid `CloseHandle`
1441
+ inside libvips/sharp under Bun's threading — was intermittent, and a required check
1442
+ that fails at random is worse than no check. Its whole history is now nine runs: seven
1443
+ green, two red, and not one recurrence. Both reds were new code from the session that
1444
+ added the job — a `require('sharp/package.json')` that sharp's `exports` map blocks,
1445
+ and a `new URL('..', import.meta.url).pathname` that yields `/D:/a/kiln/src/` — and
1446
+ neither is reachable from a POSIX host. The failures were true, which is the argument
1447
+ for blocking rather than against it.
1448
+ - Blocking means a hang is a blocked merge rather than a slow report, so the job now has
1449
+ a 30-minute bound. The suite runs there in about two minutes.
1450
+ - A test asserts no CI job carries `continue-on-error` at job level, so this cannot be
1451
+ demoted by one line slipping back in. The single step-level exception is the native
1452
+ dependency inventory, which exists to attach libvips and GLib versions to a run if the
1453
+ fault ever returns and must never be why Windows reports red. Indentation is the
1454
+ distinction and the test reads it as such.
1455
+ - **The coverage ratchet moves to 94% functions / 92% lines**, from 92/91. Measured
1456
+ 95.27% and 92.49% on this tree, which leaves 42 functions and 224 lines of room.
1457
+ - That "42 functions" is the other half of the change: the gate now reports its margin
1458
+ in the unit a person can act on. A percentage says whether the gate passed; turning it
1459
+ into work needs LCOV totals that are not in front of whoever tripped it. Slack rounds
1460
+ down and a shortfall rounds up, so neither reads as more comfortable than it is.
1461
+ - The gate also refuses a threshold set above the baseline it records. That is the one
1462
+ mistake the coverage comparison cannot catch by itself: a threshold above anything
1463
+ ever measured fails in whatever change happens to run next, and reads there as that
1464
+ change's regression. A thresholds file with no recorded baseline is refused for the
1465
+ same reason — a ratchet is raised to a number somebody measured.
1466
+
1467
+ ## Dependencies current in range, and the toolchain gate gets its own test — 2026-09-12
1468
+
1469
+ - In-range refresh, deliberately kept apart from the deferred majors: `ai` 6.0.222 →
1470
+ 6.0.282, `openai` 6.46.0 → 6.49.0, `@ai-sdk/provider` 3.0.14 → 3.0.16,
1471
+ `@aws-sdk/client-bedrock-runtime` 3.1083.0 → 3.1131.0, and in `site/` react and
1472
+ react-dom 19.2.8 → 19.3.0 with their types, plus vite 8.2.2 → 8.3.0.
1473
+ - The `ai` 7 / `@ai-sdk/provider` 4 / `@openrouter/ai-sdk-provider` 3 family stays
1474
+ where it was. Only `test:live` exercises those paths and it spends money, and the
1475
+ deliberate prompt-cache transport asymmetry is exactly what a provider major breaks
1476
+ without a test noticing. Nothing enforces that decision in prose and nothing needs
1477
+ to: the caret ranges cannot resolve to a new major, so the boundary is structural.
1478
+ - The `@types/three` parity rule added yesterday now reads as what it always was — a
1479
+ rule about types, not about three. Every `@types/*` whose runtime package the same
1480
+ manifest pins must sit on that runtime's release line. That is what brought
1481
+ `@types/react` and `@types/react-dom` along with react 19.3: the site compiles its
1482
+ `@react-three/fiber` JSX against those types, and types a release line behind make
1483
+ new API invisible to `tsc` while removed API still typechecks.
1484
+ - **`check-toolchain.mjs` now has a test.** It had grown three times, each extension
1485
+ verified by injecting the defect by hand, once, at the time — the right check made in
1486
+ the wrong place, because nothing stopped a later edit from leaving a rule that could
1487
+ no longer fail. A vacuous gate is worse than no gate, since it is believed. The gate
1488
+ takes an optional `--root`, and each case stages a real copy of the repository's own
1489
+ files, mutates exactly one value, and asserts the gate names it: stale engine pin,
1490
+ prose that installs the wrong Bun, three drifting in either the site or the render
1491
+ service, types off their runtime's line, an Action pinned to a mutable tag, and a
1492
+ Pages workflow left behind. Staging the real files rather than a synthetic fixture is
1493
+ the point: a rule whose phrasing drifts out of step with the document it reads fails
1494
+ the baseline case immediately.
1495
+ - A mutation that changes nothing fails the test. A search string that no longer
1496
+ appears injects no defect, the gate correctly reports a clean tree, and the case goes
1497
+ green having checked nothing — the exact rot the file exists to prevent, so it is
1498
+ caught rather than trusted.
1499
+
1500
+ ## The r186 bump missed the site, and now a gate catches that — 2026-09-12
1501
+
1502
+ - `site/` was still on three 0.185.1 while the engine and `render-service` moved to
1503
+ 0.186.0. Substantive rather than untidy: kiln emits `EXT_mesh_gpu_instancing`, and
1504
+ r186 fixed that extension's custom instance attribute sharing in `GLTFLoader`, so
1505
+ the site was rendering the gallery with an unfixed loader for an extension the
1506
+ engine writes.
1507
+ - `check:toolchain` now asserts `three` is byte-identical in every manifest that pins
1508
+ it, and that `@types/three` tracks its minor. Verified by injecting three defects —
1509
+ the exact drift that shipped, a `render-service` drift, and a stale `@types/three` —
1510
+ and confirming each is caught and names the offending manifest.
1511
+ - The reason this is a gate and not a note: the same class of defect, a value that
1512
+ must agree across files with nothing enforcing it, had been caught in the prose that
1513
+ same morning by a person. Twice in one day is an argument for a check.
1514
+
1515
+ ## Finishing the CommonJS removal the r186 bump started — 2026-09-12
1516
+
1517
+ - The previous entry's fix was incomplete, and looked complete.
1518
+ `src/ops.ts` moved to the ESM build, but `src/__tests__/subdivide-normals.test.ts`
1519
+ still imported the bare specifier — so the CommonJS build kept loading and kept
1520
+ emitting `THREE_CJS_DEPRECATED`, while the production import read as corrected.
1521
+ The whole suite now reports **zero** occurrences of that warning.
1522
+ - `three-subdivide` is pinned **exactly** rather than by caret. The deep path
1523
+ `three-subdivide/build/index.module.js` is legal precisely because the package
1524
+ declares no `exports` map; a minor release that added one would make the path
1525
+ unresolvable, and a caret range would have taken that release silently.
1526
+ - `src/__tests__/three-subdivide-esm.test.ts` guards the whole tree rather than one
1527
+ file, which is the lesson from the partial fix: it walks every `.ts` under `src/`,
1528
+ asserts the ESM build has no `require(` call, and asserts the pin is exact.
1529
+ Verified by injecting the bare specifier into `src/ops.ts` and confirming the guard
1530
+ fails *and names the offending file*, rather than by observing a pass.
1531
+ - Timing, since it reads as urgent and is not: three deprecated the CommonJS build in
1532
+ r186 and has not announced a removal release. Its own precedent — `build/three.js`
1533
+ deprecated at r150, removed at r160 — suggests roughly ten releases of runway. The
1534
+ reason to fix it now is the warning users see today, not the removal.
1535
+
1536
+ ## three.js r186, and the CommonJS load it exposed — 2026-09-11
1537
+
1538
+ - `three` and `@types/three` to 0.186.0 in both the engine and `render-service`.
1539
+ The types were the blocker and they shipped; r186's three named breaking changes
1540
+ — `Source` → `TextureSource`, `toTrianglesDrawMode()` mutating in place,
1541
+ `Object3D.dispose()` needing `super.dispose()` — touch nothing here, checked
1542
+ before the bump rather than discovered by it.
1543
+ - **`three-subdivide` was pulling three in through the deprecated CommonJS build.**
1544
+ It has no `exports` map, so a bare specifier resolves to `main`, which is a UMD
1545
+ bundle doing `require("three")`. r186 did not create this; it made it audible, by
1546
+ emitting `THREE_CJS_DEPRECATED` on stderr — which every `kiln` CLI invocation
1547
+ would then have printed. Fixed by importing the ESM build the package already
1548
+ ships, `three-subdivide/build/index.module.js`, which has zero `require(` calls.
1549
+ `three-subdivide@1.1.5` is the latest release, so there is no upstream fix to
1550
+ wait for, and three has announced the CJS build's removal.
1551
+ - Worth recording because it is tempting and wrong: this does **not** fix a
1552
+ dual-THREE instance hazard. `src/primitives.ts` documents one, and it was
1553
+ measured here rather than assumed — geometry from both the CJS and the ESM build
1554
+ satisfies `instanceof THREE.BufferGeometry`. The `isBufferGeometry` flag tests
1555
+ stay, because they are correct defensively regardless.
1556
+ - **The GPU service was requesting a shadow filter that no longer exists.** r186
1557
+ removed the `PCFSoftShadowMap` implementation while keeping the constant; three
1558
+ warns and substitutes `PCFShadowMap`. `render-service` now names the filter it
1559
+ actually gets. Material conformance returns numbers identical in every region
1560
+ before and after, which is the proof the substitution was already happening.
1561
+ - Verified on hardware, not only offline: 1,831 pass / 0 fail, typecheck clean,
1562
+ lint at the unchanged 14/11 baseline, and `material-conformance` `PASS` on
1563
+ `dawn-vulkan` across all six channels. One number to watch —
1564
+ `normalHalfLumaDelta` moved 1.51 → 1.2 against a threshold of 1, consistent with
1565
+ r186's diffuse energy-conservation change. It passes with less margin than 11.5
1566
+ measured, which is an argument for that row's follow-up rather than for a new
1567
+ threshold.
1568
+
1569
+ ## The guide was pinning a toolchain the gate rejects — 2026-09-11
1570
+
1571
+ - `AGENTS.md`, `CONTRIBUTING.md`, `README.md`, `docs/google.md` and
1572
+ `docs/install.md` still named Bun 1.3.14, Node 22.23.1 and npm 12.0.1 after the
1573
+ bump to 1.4.2/22.23.2/12.0.2. `AGENTS.md` is the guide every harness reads, so
1574
+ the first command a new contributor ran installed a Bun that
1575
+ `bun run check:toolchain` — the next command in the same list — then rejected.
1576
+ - `scripts/check-toolchain.mjs` now asserts those five documents state the
1577
+ supported triple, and that `pages.yml` pins the same Bun as `ci.yml`. The gate
1578
+ had enforced `package.json` and `ci.yml` and nothing else, so both the prose and
1579
+ the second workflow could drift silently; `pages.yml` was correct today only
1580
+ because it was set by hand in the same change. Verified by injecting each of the
1581
+ three defects and confirming a non-zero exit, rather than by observing a pass.
1582
+ - Dated receipts under `docs/evaluation/` are deliberately excluded from the
1583
+ check. They record the toolchain a run actually used, and rewriting one to match
1584
+ a new pin would falsify it.
1585
+
1586
+ ## Skills are fetchable by URL — 2026-09-11
1587
+
1588
+ - The six skills are published at
1589
+ `https://kilnstudio.tools/.well-known/agent-skills/index.json`, per
1590
+ [Cloudflare's Agent Skills Discovery RFC](https://github.com/cloudflare/agent-skills-discovery-rfc)
1591
+ v0.2.0 — which is what OpenCode's `skills.urls` consumes. One `skill-md` entry
1592
+ and five archives for the skills carrying `references/`, each with a sha256 of
1593
+ the artifact's raw bytes so a client can verify what it fetched.
1594
+ - The archives are built by hand as POSIX ustar with every non-content field
1595
+ pinned — mode 0644, uid/gid 0, mtime 0, sorted entries — because the digest is
1596
+ *published*. Neither the system `tar` nor a convenience library gives that by
1597
+ default, and an archive whose bytes move on every build publishes a digest that
1598
+ is wrong the moment it is written. The test asserts those header fields
1599
+ directly rather than only comparing two builds, since two builds agreeing is
1600
+ also what a tar with a coarse clock does inside one second.
1601
+ - Verified end to end: real `tar` extracts the archives with `SKILL.md` at the
1602
+ root and contents byte-identical to `skills/`, and Vite copies the
1603
+ dot-directory into the published output.
1604
+ - **This path carries skills and no MCP server**, so a client that loads them has
1605
+ the workflows and none of the tools they describe. `docs/install.md` says so
1606
+ where it offers the URL.
1607
+ - **Follow-up:** the first deployment served a 404, and neither the build nor
1608
+ Pages was at fault. `actions/upload-pages-artifact` v5 archives with
1609
+ `--exclude=.[^/]*` unless `include-hidden-files` is set, so it strips *every*
1610
+ hidden path — and a well-known URI is under a dotted directory by definition.
1611
+ The artifact had 465 entries and not one dot-entry, while the same build
1612
+ locally produced all seven files. Fixed by setting the flag, verified to add
1613
+ exactly `.well-known` and nothing else.
1614
+
1615
+ ## Bun-only APIs cannot reach a Node bundle unnoticed — 2026-09-11
1616
+
1617
+ - Audited the shipped bundles for the defect class behind the MCP server's
1618
+ `import.meta.main` bug. The bundles were clean of all nine patterns checked —
1619
+ but the audit found the identifier still live in
1620
+ `src/experiments/geometry-acceptance.ts`, which **ships** (`files` carries
1621
+ `src/**/*.ts`). Unbundled under plain `node`, `import.meta.main` is `undefined`,
1622
+ so that guard was always *false* and running the script with node did nothing
1623
+ at all, silently. The same defect as the MCP one with its sign flipped: there,
1624
+ bundling made the guard always *true* and started a server nobody asked for.
1625
+ Both now use `isDirectEntry`, which decides from `process.argv[1]`.
1626
+ - A new guard asserts both halves — no Bun-only API in any committed bundle, and
1627
+ no `import.meta.main` deciding an entry point in shipped source. It was
1628
+ verified by injecting a defect into each and watching it fail, rather than
1629
+ trusted for passing green: the whole reason this class survived is that nothing
1630
+ was looking.
1631
+
1632
+ ## The GPU material check runs for the first time — 2026-09-11
1633
+
1634
+ - `render-service/test/material-conformance.mjs` is the only automated evidence
1635
+ that the GPU path applies textures at all, and it had never passed. It replaces
1636
+ `globalThis.fetch` with a thrower to prove an embedded fixture never crosses a
1637
+ network boundary — but `blob:` is an **in-memory** object URL, and three's
1638
+ `GLTFLoader` mints one per embedded image that `ImageBitmapLoader` reads back
1639
+ through `fetch`. The guard blocked the only path a texture has into the
1640
+ renderer, so the file measured an untextured render and asserted against it:
1641
+ `lumaSpread: 0` on the albedo checker, with the real cause printed as a loader
1642
+ warning nobody read. `blob:` is now exempt, and the guard self-checks that an
1643
+ `https:` fetch still throws.
1644
+ - Its normal-map threshold was `>= 3` and had **never been evaluated**, because
1645
+ the albedo assertion above it always failed first. Recalibrated to `>= 1` from
1646
+ measurement. The small number is the tone curve rather than a weak response:
1647
+ that panel sits at mean luma ~242 of 255 under the fixture's own exposure of
1648
+ 1.38, past the ACES shoulder. Measured 1.51 at exposure 1.38 and 2.61 at 0.9
1649
+ with nothing else changed — which is also the proof that the map applies. An
1650
+ absent normal map reads near zero, the way the albedo checker read exactly 0.
1651
+ - All six channels now verify: albedo, normal, shared ORM, AO, emissive, alpha.
1652
+
1653
+ ## Artifact identity stops tracking the serializer's version — 2026-09-11
1654
+
1655
+ - **`asset.generator` is now a stable `"Kiln"`.** It defaulted to the serializer's
1656
+ own version string, so `glTF-Transform v4.4.1` sat inside every artifact's
1657
+ bytes and therefore inside `artifactHash`. Taking 4.5.0 moved all 83 recorded
1658
+ hashes and failed the gallery build — and the *entire* difference between the
1659
+ two GLBs was that one string. Identical BIN chunk, identical accessor min/max,
1660
+ identical semantic digest. With the generator pinned, **all 86 examples are
1661
+ byte-identical across 4.4.1 and 4.5.0**, including the ones using `palette()`,
1662
+ which 4.5.0 changed. A dependency's version number is provenance about the
1663
+ tool, not identity of the asset.
1664
+ - `@gltf-transform/*` moves to an exact **4.5.0**. Still exact, deliberately —
1665
+ the version-string cause is gone but other serialization changes are not
1666
+ impossible, and a serializer bump should stay a measured decision.
1667
+ - **A poster receipt no longer asserts `artifactHash`.** It asserts `sourceHash`
1668
+ and `imageHash` — this image was rendered from this source — which is what it
1669
+ could always honestly claim. Asserting the container's bytes claimed something
1670
+ glTF never promised: that rebuilding a source on another platform reproduces
1671
+ it byte for byte. The hash stays in the record as provenance, naming the bytes
1672
+ the poster was rendered from on the machine that recorded it, and the prose in
1673
+ all 83 records no longer calls the poster a render of "the exact downloadable
1674
+ GLB".
1675
+ - Within-run integrity checks are untouched and still hash bytes: the gallery
1676
+ index against the files built beside it, the build receipt against the index,
1677
+ and both demo receipts against their own run's GLBs.
1678
+ - **The gallery now builds on Linux.** `bun run site:assets` followed by
1679
+ `site/scripts/verify-assets.mjs` completes — 80 source/GLB pairs, posters, both
1680
+ edit-demo revisions and the geometry example. Before this it failed on the
1681
+ first example.
1682
+ - `pages.yml` now runs `site/scripts`'s tests explicitly. **Correction:** this
1683
+ entry first said those tests had never run in any workflow. They had. `bun run
1684
+ test` is `bun test src scripts`, bun treats those as substring filters on
1685
+ paths, and `site/scripts` matches `scripts` — so both files were already in the
1686
+ suite CI runs. The step stays for a smaller reason: that coverage is
1687
+ incidental, and spelling the script `bun test ./src ./scripts` would drop it
1688
+ silently.
1689
+ - **The gallery no longer builds on Windows.** `pages.yml` is `ubuntu-latest`,
1690
+ which closes Phase 6.
1691
+ - New `scripts/glb-chunk-hashes.mjs` splits a GLB by chunk, and it answered the
1692
+ question Phase 6 opened with. Measured on 8 examples across both platforms at
1693
+ one commit: the JSON chunk diverges in **all 8**, the BIN chunk in **2 of 8**,
1694
+ and totals move by 4, 8 and 264 bytes — digit counts changing, which a
1695
+ formatting difference cannot do, because ECMAScript specifies number-to-string
1696
+ exactly. So the divergence is the float values themselves, most likely the
1697
+ transcendentals, which IEEE-754 does not bit-specify. That means no canonical
1698
+ serialization could ever have fixed it, and narrowing the receipt was the only
1699
+ option that works.
1700
+
1701
+ ## The renderer starts itself, and now ships — 2026-09-11
1702
+
1703
+ - The MCP server **starts the GPU render service on demand**. Installing it
1704
+ (`cd render-service && npm install`) is the whole setup: there is no longer a
1705
+ second process to start first, and nothing to restart to pick one up. The
1706
+ first view that needs PBR shading starts the renderer, the rest of the session
1707
+ reuses it, and it stops when the server does. Measured end to end on the
1708
+ shipped bundle with nothing listening: the first `kiln_render` came back
1709
+ material-faithful from the GPU in 5.4 s, the second in 1.7 s.
1710
+ - A service already listening is **joined, not replaced**, and a session that
1711
+ merely found one does not stop it on the way out -- so one GPU shared by a
1712
+ batch of dispatched agents keeps working, and the agent that finishes first
1713
+ cannot pull the renderer out from under the others.
1714
+ - A machine that did not install the renderer is **unchanged**, deliberately
1715
+ and at the level of the attached port rather than the rendered result: the
1716
+ absence of a render port is what makes an ordinary CPU view read as ordinary
1717
+ instead of as a degrade.
1718
+ - `KILN_RENDER_PORT_URL` and `--render-port` short-circuit all of it, so a
1719
+ hosted or remote GPU is still exactly one flag. `KILN_RENDER_SERVICE_PORT`
1720
+ moves the local one off 8000.
1721
+ - The CLI is deliberately not on this path: a one-shot `kiln render` should not
1722
+ pay a GPU process's startup to draw one sheet.
1723
+ - **`render-service/` now ships in the package.** The MCP server told every model
1724
+ that a GPU renderer "ships as render-service/ in this installation"; that was
1725
+ false for anyone installing from npm, because the directory was in no `files`
1726
+ entry. Its source travels with the package now (104 KB) while its ~94 MB of
1727
+ native dependencies stay an explicit opt-in install.
1728
+ - `webgpu` 0.4.0 -> 0.6.0, smoked on hardware: 36/36 service unit tests, display
1729
+ conformance passed, end-to-end smoke all pass.
1730
+ - A service started **for** you binds loopback; one you start yourself still binds
1731
+ every interface, which is what a container deployment needs. `HOST` is now
1732
+ honoured either way.
1733
+
1734
+ ## Review surfaces say what they mean — 2026-09-11
1735
+
1736
+ - `viewFidelity.exactArtifact` was a hard-coded `false` literal at every
1737
+ `kiln_render` site, never computed. A flag that is always false carries no
1738
+ information, and this one invited the opposite of the truth: a model could
1739
+ read it as "these bytes differ from what I would export" when the bytes are
1740
+ frequently identical. The value is still false -- this surface only ever
1741
+ renders an in-loop build -- but it now carries the reason code
1742
+ `IN_LOOP_BUILD_NOT_PERSISTED`, so the false value is readable rather than
1743
+ ominous.
1744
+ - `kiln_screenshot_animation` was reported to rotate a whole asset where the
1745
+ clip drives one joint. **It does not.** Measured on world matrices rather than
1746
+ pixels: a one-channel clip moves the joint's subtree through a clean
1747
+ 180-degree yaw while the un-animated sibling holds the origin at every phase.
1748
+ An image cannot tell a turret sweeping from a scene spinning, which is why the
1749
+ report was plausible; a regression test now stands where the doubt was.
1750
+ - `arrayLinear` and `arrayRadial` say outright that `count` is the total
1751
+ including the source, which survives as copy 0 -- so `count: 8` gives eight
1752
+ bolts, not nine. The program contract states that
1753
+ `kiln_screenshot_animation` requires `clip` by name, that `frameTimes` is
1754
+ phases in 0..1 rather than seconds, and that a clip that renders frozen means
1755
+ a joint-name mismatch surfaced in `unresolvedTracks`.
1756
+
1757
+ ## Dependency refresh — 2026-09-11
1758
+
1759
+ - Patch and minor bumps with no API surface change: `@types/three` 0.185.4,
1760
+ `acorn` 8.18.0, `manifold-3d` 3.5.3, `sharp` 0.35.4, `zod` 4.6.2,
1761
+ `@anthropic-ai/sdk` 0.125.0, `@biomejs/biome` 2.5.13, `@google/genai` 2.22.0,
1762
+ `@strands-agents/sdk` 1.17.0. `biome.json` follows Biome's own version, which
1763
+ the linter checks and which is what actually failed the gate; one test file
1764
+ picks up the new formatter's line fitting.
1765
+ - **`@gltf-transform/*` is now pinned to exactly 4.4.1**, joining `three` and
1766
+ `gltf-validator`. 4.5.0 changes the bytes of every exported GLB: all 83
1767
+ recorded `artifactHash` values move, and the gallery build fails on a stale
1768
+ poster. A caret range on the library that serializes the artifact means the
1769
+ artifact's identity can change on an unrelated install, which is not a
1770
+ property this project can have. Taking 4.5.0 is a deliberate act with receipts
1771
+ re-recorded in the same pass, not a refresh.
1772
+ - `three` stays at 0.185.1. `@types/three` has no 0.186.x published, and r186
1773
+ renames `Source` to `TextureSource` and makes
1774
+ `BufferGeometryUtils.toTrianglesDrawMode()` mutate in place rather than clone.
1775
+ Taking the runtime without the types would be a net loss.
1776
+
1777
+ ## Toolchain moved to Bun 1.4.2, and the MCP bundle stopped starting itself — 2026-09-11
1778
+
1779
+ - Development toolchain pins move to Bun 1.4.2, Node 22.23.2 and npm 12.0.2.
1780
+ Bun 1.4 also fixes the early-exit behaviour above at its source: the pre-fix
1781
+ CLI, which produced empty output in 6 of 8 concurrent runs on 1.3.14, produced
1782
+ full output in 16 of 16 on 1.4.2. The `withProcessAlive` guard stays -- it
1783
+ holds for anyone still on 1.3.x, and nothing in the runtime contract promises
1784
+ otherwise. The offline suite runs about 18% faster.
1785
+ - **`dist/mcp-server.mjs` started a stdio server when merely imported, and had
1786
+ done so for as long as the bundle has existed.** The source guarded its entry
1787
+ block with `import.meta.main`, a Bun property that no Bun release lowers
1788
+ correctly for a `--target=node` bundle. Every lowering emits
1789
+ `__require.main == __require.module`; under Node ESM both sides are
1790
+ `undefined`, so the guard was always true. Bun 1.4 then stopped emitting the
1791
+ `__require` helper that line still references, which turned the same
1792
+ expression into a `ReferenceError` before the server could speak -- loud where
1793
+ it had been silent, and how this was finally caught. Both entries now decide
1794
+ from `process.argv[1]` through a shared `isDirectEntry`, which needs no
1795
+ lowering and means the same thing on both runtimes.
1796
+
1797
+ ## `kiln` commands no longer exit 0 in silence under Bun — 2026-09-11
1798
+
1799
+ - Under Bun, a CLI command could exit 0 having written nothing at all: no output,
1800
+ no error, no rejection. `kiln save` did it reproducibly under load. The command
1801
+ was not failing, it was still running -- Bun does not register the in-flight work
1802
+ as an active resource, so the event loop went idle and the process exited before
1803
+ `main()` settled. Every entry point was exposed, the generated workspace
1804
+ `kiln.mjs` launcher included, because all of them await `main()` and then set
1805
+ `process.exitCode`. `main` now holds the process open for its own duration.
1806
+ Node was unaffected in practice, but the guard is runtime-agnostic.
1807
+ - Every CLI destination now creates the directories leading to it. `--out` and
1808
+ `--views` failed with a bare `ENOENT` on a missing parent, and failed *after*
1809
+ the build had run and printed a `programRef` -- a successful render followed by
1810
+ an error naming a path but not the directory as the thing to fix. Covers
1811
+ `render --out`, `render --views`, `generate`, `source <ref> --out` and
1812
+ `export --out`.
1813
+
1814
+ ## Usable from a bare clone by any harness; isolate honored on GPU — 2026-09-10
1815
+
1816
+ - A bare clone is now usable by claude, codex, opencode, hermes and agy. The root
1817
+ `.mcp.json` pointed at `${PLUGIN_ROOT}`, which only Antigravity defines, so the
1818
+ server failed to start with MODULE_NOT_FOUND in every other host; it now uses
1819
+ `${CLAUDE_PROJECT_DIR:-.}`, which resolves whether or not the variable is set.
1820
+ `CLAUDE.md` imports `AGENTS.md` rather than duplicating it, and the
1821
+ `kiln-setup-workspace` skill is registered at `.claude/skills/` and
1822
+ `.agents/skills/` so a clone opened for an asset task is told to build a
1823
+ workspace instead of authoring in the engine checkout.
1824
+ - `visibility: 'isolate'` and the legacy `isolate: true` were silently ignored
1825
+ whenever a GPU service was attached. Both hide geometry by clearing
1826
+ `mesh.visible`; the CPU rasterizer culls on that flag, but glTF carries no
1827
+ per-mesh visibility, so the derivative GLB shipped the hidden meshes and the
1828
+ service drew them. `kiln_inspect` was reporting "nothing in this image occludes
1829
+ it" about images where everything still did. The derivative serialization now
1830
+ prunes hidden meshes from a copy, leaving the caller's scene untouched. The
1831
+ only previous test framed a single-mesh scene, where isolation cannot change
1832
+ the image.
1833
+ - The generated workspace guide is organised around the authoring loop rather
1834
+ than a flat tool list, and presents the MCP server and `node kiln.mjs` as equal
1835
+ surfaces that may be mixed, naming the two things that actually differ: where
1836
+ the rendered image lands, and that a CPU view is not material evidence. It also
1837
+ names the GPU render service, resolving the engine path through
1838
+ `.kiln/workspace.json` so `--repair` keeps it accurate, and asks the session to
1839
+ report skills and MCP servers it inherited from user-level configuration.
1840
+ - `START.md` printed a literal `/current/kiln/` in its repair command; it now
1841
+ prints the recorded installation path plus how to recover if that moved.
1842
+
1843
+ ## Smaller clone; gallery images served from R2 — 2026-09-10
1844
+
1845
+ - Gallery renders and launch video are no longer carried in git history. A clone
1846
+ costs 52 MB and 8 seconds rather than 440 MB and 79 seconds, 48 MB with
1847
+ `--filter=blob:none`, and a plugin install no longer pays for 539 MB of
1848
+ repository content twice. Packed history went from 226.06 MiB to 17.41 MiB.
1849
+ - The 88 gallery images moved to Cloudflare R2 byte for byte and are served from
1850
+ `assets.kilnstudio.tools`. Nothing was re-encoded: all 83 poster receipts still
1851
+ verify against the stored objects, and `scripts/verify-posters.mjs` checks that
1852
+ on every gallery build.
1853
+ - **This rewrote history.** All 177 commits survived and the tree is unchanged
1854
+ apart from the removed images and video, but every commit hash changed. Clones
1855
+ predating this cannot fast-forward; see the note in `README.md`. Links pinned to
1856
+ an old commit SHA will stop resolving; links through `main` are unaffected.
1857
+ - The launch video was never what the README served -- that comes from GitHub's
1858
+ asset CDN -- so the README is unaffected. `assets/gallery/` deliberately stays
1859
+ in the repository, so the README grid needs no network.
1860
+ - Re-encoding the posters to shrink them was considered and rejected: image tokens
1861
+ scale with pixel dimensions, not file size, so it would have saved no agent
1862
+ context while voiding all 83 receipts.
1863
+
1864
+ Full record, including everything rejected and why, in
1865
+ [docs/plans/repo-size-and-r2-migration-2026-09-10.md](docs/plans/repo-size-and-r2-migration-2026-09-10.md).
1866
+
1867
+ ## Collections and chat viewing — 2026-09-06
1868
+
1869
+ - Save assets into project or personal collections with immutable revisions,
1870
+ hashed provenance, original source, and portable editable ZIP bundles.
1871
+ - Browse collections and imported GLBs in a local viewer with animation controls,
1872
+ revision inspection, and GLB/source/bundle downloads.
1873
+ - Present saved assets in supporting MCP App clients with `kiln_present`.
1874
+ ChatGPT viewing was verified; native GLB/ZIP attachment delivery remains
1875
+ unverified. Host-provided download links are supported.
1876
+ - Package complete skills independently or bind them to a ChatGPT connector.
1877
+ - Add a canonical-only sitemap and crawler discovery file to the project site.
1878
+
1879
+ See [dogfood results and remaining work](docs/collections-dogfood.md) and
1880
+ [indexing diagnostics](docs/site-indexing.md). Package version remains 0.6.0;
1881
+ this change does not publish a new npm package or promote dogfood assets.
1882
+
1883
+ ## Public OSS snapshot — 2026-09-05
1884
+
1885
+ - Short, immutable source references work across rendering, source reads, edits,
1886
+ CLI exports, and MCP restarts. Full hashes remain compatible.
1887
+ - Project-local setup and skills support authoring, inspection, and refinement
1888
+ through the CLI, MCP server, and TypeScript tools.
1889
+ - Geometry modifiers and configurable asset/part cameras extend the authoring
1890
+ and review workflow.
1891
+ - The redesigned site includes 62 selected assets with source downloads, GLB
1892
+ viewers, attribution, and recorded revision histories where available.
1893
+ - Mechanical Peacock and Deco Radio include documented maintainer repairs.
1894
+ Brass Tellurion and Victorian Greenhouse remain archived outside the gallery.
1895
+ - Package checks cover Windows, Linux, and hosted Apple Silicon/Intel Macs.
1896
+
1897
+ The snapshot retains package version 0.6.0. Earlier development notes follow;
1898
+ their asset counts and workflow descriptions describe those earlier revisions.
1899
+
1900
+ ## [Unreleased]
1901
+
1902
+ ### Changed
1903
+ - **`site/` is a project site now, not a gallery.** The front page was a wall of
1904
+ specimens with a tally of triangles above it, which answered a question nobody
1905
+ arriving from a link was asking. It now opens on what Kiln is, the offline command
1906
+ that proves the engine works with no model and no key, the exact configuration for
1907
+ each of the five harnesses with the gotcha that cost time in each one, and a map of
1908
+ where to look in the repository; the specimens moved behind `#/gallery` and are
1909
+ reached from a strip of eight. The aggregate counts are gone -- a number is a claim
1910
+ about the library, and the library is the thing on the other page. Per-card model
1911
+ attribution stays, because that is not a statistic. `REPO` and `asset()` moved out of
1912
+ `App.tsx` into a leaf module: once `App` imported a page that read one at module
1913
+ scope the cycle turned into a blank screen at first paint, and a module that imports
1914
+ nothing cannot be in a cycle.
1915
+
1916
+ ### Fixed
1917
+ - **The carousel and orrery GIFs no longer show something the programs beside them
1918
+ stopped producing.** Both were flagged in review as showing an earlier revision, and
1919
+ both were regenerated on the GPU port from the current source. Nothing in the repo
1920
+ ties a GIF to the revision it was rendered from, so the staleness was caught by
1921
+ somebody remembering rather than by a gate -- the stills have the same exposure.
1922
+ A frame overlay confirms the camera is fixed across both loops and only the authored
1923
+ motion moves.
1924
+
1925
+ ### Added
1926
+ - **A specimen gallery in `site/`, built by running the programs rather than by
1927
+ committing meshes.** The README's stills are a weak claim about geometry -- a lit
1928
+ render hides topology, and a reader has no way to tell a dense mesh from a clean one.
1929
+ The gallery loads all fifty as real GLBs with orbit controls, a wireframe toggle and
1930
+ the triangle count, draw calls, material count and bounds off the engine's own
1931
+ integration manifest. Nothing in it is checked in: `site/scripts/build-assets.mjs`
1932
+ runs each `examples/*.kiln.js` through the same evaluator the test suite uses and
1933
+ keeps what comes back, so a program and the mesh on the page cannot drift, and a
1934
+ program that stops building fails the deploy. Attribution on each card comes from the
1935
+ program's own header through `scripts/authorship.ts` -- extracted from the authorship
1936
+ test so that the README and the gallery read the headers with one parser instead of
1937
+ two that will eventually disagree. React and three live in `site/` and nowhere else;
1938
+ the engine still has no browser dependency. `.github/workflows/pages.yml` deploys it.
1939
+ - **A fifth skill, `kiln-batch-dispatch`, for the person pointing agents at the problem
1940
+ rather than the agent building the asset.** The other four are read by whoever is
1941
+ holding the geometry; this one covers wiring a harness up, proving the tools are
1942
+ actually reachable before trusting a single result, and running the clean-room
1943
+ dispatcher across models and subjects until you have a library. It exists because
1944
+ the two halves fail as one: a batch launched on unproven wiring produces a pile of
1945
+ empty sandboxes, and no way to tell whether the models were weak or the tools were
1946
+ never there. It also writes down the two limits that cost the most time here -- a
1947
+ harness's default output cap silently truncating a model mid-file, and a run cut off
1948
+ at its deadline still being a success if a usable program is on disk.
1949
+ - **`--category` on the dispatcher, with six experimental values and one honest
1950
+ default.** Every asset ever dispatched from this repository came back
1951
+ `category: 'prop'`, because the brief said so in a string literal. The problem was
1952
+ never that they were props -- most of them are, correctly, and almost anything can be
1953
+ built as one -- but that the field carried no information, so a library could not be
1954
+ sorted by it and the engine's per-category guidance was unreachable. The flag takes
1955
+ any of the seven contract values and rejects anything else in the first second rather
1956
+ than filing an asset under nothing. `prop` stays the default and its brief is
1957
+ deliberately empty, which makes it the least constrained thing the dispatcher can
1958
+ send. The other six append a short note on what tends to go wrong for that kind of
1959
+ subject, and they are a bet rather than a taxonomy: worth reaching for when the
1960
+ subject really is a building or a plant, worth dropping first when a run comes back
1961
+ worse than the same brief as a prop. `src/__tests__/dispatch-categories.test.ts` fails
1962
+ if the list the script carries ever drifts from `ASSET_CATEGORIES`.
1963
+ - **Hermes as a fifth harness, which is the first real evidence that any of this is
1964
+ portable.** The other four are JavaScript CLIs that broadly agree on what a plugin
1965
+ is; Hermes is a Python agent with its own YAML config, its own skill store and its
1966
+ own provider routing, sharing no code and no convention with them. It takes the
1967
+ same `dist/mcp-server.mjs` and the same `skills/` directory unchanged: `hermes mcp
1968
+ add` connects on the spot and enumerates all seven tools, and
1969
+ `hermes config set skills.external_dirs` registers every shipped skill as `local`. It
1970
+ does not read `.claude/skills` out of the working directory the way the others do,
1971
+ and that config key is the whole difference. Smoke-tested green at 97s on a free
1972
+ model. `-z` is the headless mode the dispatcher drives, and since it has no
1973
+ working-directory flag the sandbox is simply the CWD it is spawned in -- which is
1974
+ also where it reads `AGENTS.md`, so the clean room holds without extra work.
1975
+ - **`bun run smoke:harness`, which proves a harness can reach the tools.** Every
1976
+ wiring failure in this project has been silent: `agy plugin validate` reports the
1977
+ MCP server as processed while `agy mcp list` shows none, and a harness that can
1978
+ see the tools but will not grant them answers exactly like one that never had
1979
+ them -- which is also what a model too weak for the job looks like. Three
1980
+ indistinguishable failures with one symptom is a thing worth being able to test,
1981
+ so this sends a short brief into a throwaway directory -- call
1982
+ `kiln_list_primitives`, write a four-line program, validate it -- and then builds
1983
+ what came back with the engine, because the child's own report of success is the
1984
+ only evidence that proves nothing. Measured on one machine: Claude Code 13s,
1985
+ OpenCode 37s, Antigravity 105s, each green. Codex reached the model and stopped
1986
+ at its account's own usage limit, which is the diagnosis the script exists to
1987
+ make -- the wiring is fine and the provider is out. Codex has since been smoked
1988
+ green as well, at 37s, once the flag below let it call a tool at all.
1989
+ - **A fourth vendor in the gallery, and a wider spread of subject.** This step took
1990
+ the heroes to twenty-seven, and the additions were chosen for the range they prove
1991
+ rather than for how they look: a supercarrier, a phased-array air-defence radar,
1992
+ a fighter with a lofted aerofoil and a geostationary comms satellite, which are
1993
+ the subjects where a program has to compute its shape rather than place it, and a
1994
+ Dutch tower windmill written by DeepSeek V4 Flash -- the first asset here from a
1995
+ fourth vendor's model, dispatched clean-room through OpenCode like the Muse Spark
1996
+ ones. The anglerfish was rebuilt: the first was a smooth dark cone with a jaw
1997
+ drawn on it, and organic form is the case this tool is worst at, so it is the one
1998
+ worth showing honestly. Eleven of those twenty-seven were not written by Claude.
1999
+ - **Tangents for non-indexed geometry.** `computeTangentBasis` required an index
2000
+ buffer and warned, per mesh, when there was not one -- so a normal map on any
2001
+ flat-shaded part, anything merged from several geometries, and anything through
2002
+ `.toNonIndexed()` shipped with no TANGENT attribute and a note saying each runtime
2003
+ would guess its own. That is most of what a hand-built asset is made of. A
2004
+ triangle soup is an index buffer that reads 0, 1, 2, ..., so the same accumulation
2005
+ is exactly right for it and every vertex comes out holding its own face's tangent;
2006
+ a test unwelds a box and checks the soup and the indexed copy agree per vertex.
2007
+ `examples/anglerfish.kiln.js` had twelve of those warnings and now has none.
2008
+ - **A measured ceiling on the always-on context cost.** `src/__tests__/mcp-payload.test.ts`
2009
+ now derives the JSON Schema for all seven tools and sums the front matter of every
2010
+ shipped skill, holds each under a budget, and requires the README to quote the real
2011
+ figures. The README argued the surface was small with numbers nothing was checking.
2012
+ - **`kiln_edit`, the refine verb, and the `kiln-refine-asset` skill that drives it.**
2013
+ Every MCP tool took a whole program and rendered it, so changing one line of a
2014
+ finished asset meant re-emitting the entire file and trusting that the other two
2015
+ hundred lines came back unchanged. `kiln_edit` takes exact-string replacements
2016
+ instead: they apply in order, a match that is not unique is refused rather than
2017
+ guessed at, and if any edit fails **nothing** is applied -- so a bad edit costs a
2018
+ retry rather than a program in a state the model never asked for. It returns the
2019
+ patched source, a unified diff, and the six-view sheet together, because the loop
2020
+ is edit-then-look. It is deliberately stateless: the in-process agent keeps a
2021
+ working buffer across turns, but over MCP the host holds the program text, and a
2022
+ second copy inside the server would have been a desync the model cannot see. The
2023
+ buffer itself moved to `src/edit-buffer.ts` so both transports share one
2024
+ implementation -- importing it from `agent/tools.ts` would have pulled
2025
+ `@strands-agents/sdk` into `kiln/tools` and into the MCP bundle, which is the one
2026
+ edge that subpath is documented not to have. Like the unified `kiln_render`, the
2027
+ def sits outside `createKilnToolRegistry`, whose four tools are a frozen bench
2028
+ baseline.
2029
+ - **The scanned texture library doubled, from eight CC0 families to sixteen.** The
2030
+ first eight were all rough exterior surfaces, which left three material recipes --
2031
+ `skin`, `leaf`, and `rubber` -- with nothing photographic to bind at all, and made
2032
+ rust the only metal in the package. Added: brown leather, forest leaf litter,
2033
+ rubber tiles, riveted steel plate, clay roof tiles, cobblestone paving, finished
2034
+ oak veneer, and polished marble. 48 maps, 747,977 embedded bytes, still inside the
2035
+ 1 MiB package budget the test enforces.
2036
+ - **OpenCode support, and clean-room dispatch.** `scripts/dispatch-asset.mjs` gained an
2037
+ `opencode` harness, reaching a dozen vendors' models behind `provider/model` ids.
2038
+ More importantly, every dispatch now runs in its own directory containing only the
2039
+ brief and the Kiln skills. Dispatches previously ran with the harness pointed
2040
+ at this repository, where a child agent could read thirteen finished example
2041
+ programs -- which quietly invalidated the claim those runs were evidence for. The
2042
+ write grant is scoped to the sandbox, and the authoring model is stamped into the
2043
+ program it wrote. `examples/gramophone.kiln.js` is the first asset in the gallery
2044
+ actually built that way -- authored through OpenCode by
2045
+ `muse-spark-1.3-contributor`, a third vendor's model, in a directory holding
2046
+ nothing but the brief and the skills. Every other example predates the sandbox and
2047
+ is attributed without the clean-room claim.
2048
+ - **Animated GIFs of the animated examples, via `scripts/anim-gifs.ts`.** The
2049
+ render port fits its camera to the bounding box it is handed, which is right for
2050
+ a still and wrong for a sequence -- a robot arm's posed extent varies 63% across
2051
+ its cycle, so the asset would appear to breathe as the camera refit itself each
2052
+ frame. Because the projection is orthographic and the port's fit is exact, each
2053
+ frame is put back onto one shared camera afterwards by a scale and a translation
2054
+ that are both computable, so the result is a fixed camera rather than an
2055
+ approximation of one.
2056
+ - **`scripts/check-vision.mjs`, a preflight that refuses to dispatch a blind model.**
2057
+ Kiln's loop is the model looking at its own render, so image input is the one
2058
+ capability that is not optional -- and a text-only model does not fail, it just
2059
+ builds worse assets from the documentation alone. A nine-model batch here was
2060
+ found to contain four text-only models, picked by name on the assumption that a
2061
+ current flagship accepts images. Vision also varies between a vendor's own
2062
+ variants: `glm-5.3-flash` sees images and `glm-5.3` does not. The check reads
2063
+ OpenCode's cached model metadata, costs about two seconds for any number of
2064
+ models, and exits non-zero naming the offenders.
2065
+ - **Browser-safe GLB integration manifest.** Every `renderGLB` / `renderSceneToGLB`
2066
+ result now carries `kiln.integration-manifest.v1`: artifact SHA-256, metre/+Y-up
2067
+ coordinate semantics, world bounds, ground offset, role, render metrics, and
2068
+ structural-validator counts. `inspectGlbIntegration(bytes)` derives the same
2069
+ contract from finished bytes without executing model-authored source.
2070
+ - **Four assets from a sixth model, through a sixth harness.** GPT-6 Astra wrote an
2071
+ orbital station, a rigid airship at its mooring mast, a blast furnace and a Gothic
2072
+ clock tower, dispatched through Codex into the usual clean room. They are the first
2073
+ batch in this repository where every program cleared the zero-structural-warning bar
2074
+ the shipped examples are held to on the first pass, with no hand repair and no
2075
+ promotion-time fix, at 10,212 / 11,764 / 11,964 / 11,664 triangles. The gallery is
2076
+ now fifty programs, thirty-five of them written by something other than Claude.
2077
+
2078
+ ### Fixed
2079
+ - **The Codex harness could not call a single tool, and said so only in the child's
2080
+ transcript.** `codex exec` pins its approval policy to `never`, and a `never` policy
2081
+ does not queue an MCP call for later approval, it refuses it outright -- so four
2082
+ dispatches came back inside a minute each having written nothing but a note that
2083
+ `kiln_list_primitives` was blocked. The dispatcher passed `--sandbox
2084
+ workspace-write`, which grants the filesystem and says nothing about tools;
2085
+ `--approve-for-me` is the flag that does both, selecting the same sandbox and moving
2086
+ approval to automatic review, and the two are mutually exclusive on the command line
2087
+ for exactly that reason. Worth noting alongside it: `codex plugin add` installs this
2088
+ repository's manifest happily and registers neither its MCP servers nor its skills,
2089
+ so the TOML server block is still required -- the same trap `agy` sets, failing the
2090
+ same silent way. Both are now documented per-harness in `docs/install.md`.
2091
+ - **`cylinderUnwrap` now unwraps cylindrically, which it has always claimed to do.**
2092
+ It and `boxUnwrap` were the same function: both preserved existing UVs and both fell
2093
+ back to the same flat projection, so on the one case the file's own header describes
2094
+ -- a curved surface carrying a directional texture -- brick came out as horizontal
2095
+ smears. Built-in `cylinderGeo` never hit it because Three.js ships UVs with it;
2096
+ anything swept or extruded did, and a texture-backed material on unwrapped geometry
2097
+ is a QA blocker rather than a silent miss. It now runs u around the Y axis and v up
2098
+ the height, normalized across the geometry's own angular span so a 22 degree curb
2099
+ stone gets the whole texture rather than six percent of its width, with caps
2100
+ projected flat so they do not sample a single row of texels.
2101
+ - **The well, which was a ring of boxes pretending to be masonry.** A box has parallel
2102
+ sides, so on a circle its neighbours meet it at an angle and the joint opens into a V
2103
+ that widens toward the outside face -- 40 mm of it at the outer radius, sixteen times
2104
+ around, which read as a cog wheel with a dark slot at every tooth. The curb is now cut
2105
+ the way a mason would cut it: wedge stones whose side faces lie on radial planes,
2106
+ inner and outer faces following the curve, in two courses offset by half a step so no
2107
+ joint runs the full height. The shaft is lined with an extruded ring rather than a
2108
+ solid cylinder, since the hole is the point of a well, and there is water at the
2109
+ bottom of it. The roof posts are bedded rather than balanced: a square post stopped on
2110
+ a round wall always reads as overhanging it, because from any angle where the wall
2111
+ curves away it does, so each post now runs past both courses to the plinth and the
2112
+ three stones on its line are cut around it with `boolDiff`. Only those stones lose
2113
+ their instancing -- an angular overlap test picks them out, one per post in the
2114
+ un-skewed course and two in the skewed one where the post straddles a joint.
2115
+ - **The pinball machine's backbox, which stood on nothing.** It was built entirely
2116
+ behind the cabinet's rear face, cantilevered off a 30 mm wall. Sliding it forward onto
2117
+ the cabinet only traded one fault for another: the playfield runs the cabinet's full
2118
+ length, so everything the head gained in support it took out of the glass, and at
2119
+ 380 mm deep it buried the bumpers. The fix was the one a real machine already uses --
2120
+ the cabinet grew the rear bay the head bolts to. It is 1.34 m long instead of 1.14, its
2121
+ rear face and back legs moved with it, the backbox is 200 mm deep rather than 380, and
2122
+ the head now sits on that bay entirely behind the glass. The file's header records the
2123
+ repair, because the gallery makes a claim about who wrote each program and a claim
2124
+ quietly edited afterwards is worse than no claim.
2125
+ - **The vending machine's sold-out column, which read as a hole in the model.** One
2126
+ unlit column was meant to be the asymmetry that stops a 4 x 3 grid looking like a
2127
+ texture. It is, but everything in that column -- the dimmed panel, the bezel shelves,
2128
+ the rails -- is near-black, so instead of an empty rack behind a switched-off light it
2129
+ came out as a solid black rectangle with no shape in it, next to three lit columns. All
2130
+ four columns are stocked now, with an amber PET added as the fourth product, and the
2131
+ asymmetry that survives is the one that was always doing the work: cans in one column,
2132
+ bottles in the other three.
2133
+ - **The vending machine's bin label, which was a flat plate on a round drum.** A 240 mm
2134
+ slab across a 390 mm bin touches it along one line: the middle sinks in and both ends
2135
+ stand 40 mm proud, which from the front reads as a white wing growing out of the side.
2136
+ It is now an annular sector swept up through the label's height -- the same wedge the
2137
+ well's curb stones are cut from -- sitting 6 mm proud all the way round.
2138
+ - **`createPart` now rejects a wrong call instead of dying in the exporter.** The
2139
+ harness smoke run found this the way it was always going to be found: a free model
2140
+ on Hermes wrote `createPart(root, { name, geo, material })` -- the shape most other
2141
+ scene APIs take -- and the run failed 185 seconds later with `undefined is not an
2142
+ object (evaluating 'Object.keys(morphAttributes)')`. Nothing in that sentence names
2143
+ anything the model wrote, so it had no way back and spent its remaining turns
2144
+ guessing. `new THREE.Mesh()` accepts any two arguments at all, which is what let a
2145
+ function reference and a plain object travel that far. The three positional
2146
+ arguments are now checked at the door, and each message says which argument, what
2147
+ arrived, and what the call looks like when it is right: a parent passed first is
2148
+ told the parent belongs in the options object, `boxGeo` without parentheses is told
2149
+ geometry helpers have to be called, an un-awaited `roundedBoxGeo` is told the helper
2150
+ is async and `build()` has to be too, and a hex number where a material belongs is
2151
+ told to wrap it. The same program now fails in under a second with a sentence a
2152
+ model can act on.
2153
+ - **Three hero assets whose geometry was wrong in ways a contact sheet hides.** The
2154
+ ferris wheel braced its two A-frames together at mid-height, and the upper tie sat
2155
+ 1.93 m from the hub inside a 3.60 m rim -- a girder straight through the spokes and
2156
+ the cars. It is now a clearance rule rather than a pair of chosen heights: candidate
2157
+ bracing levels are filtered against the wheel's swept envelope, rim radius plus how
2158
+ far a car hangs below its pin, and only the low portal survives, which is exactly
2159
+ where a real double-A-frame wheel is braced. The vending machine's cans were albedo
2160
+ `0x4a2f0c` at metalness 0.78; a metal has no diffuse term, so its whole appearance
2161
+ is a specular reflection tinted by its albedo, and a near-black albedo under a
2162
+ neutral studio dome returns almost nothing -- the cans rendered as black rectangles
2163
+ and their column read as empty stock. A printed can is a coating over the metal, so
2164
+ it is dielectric like the bottles, and only the bare lid is aluminium. And the
2165
+ lighthouse's keeper's cottage stood a plain rectangular slab on each end wall as a
2166
+ gable, leaving 0.91 m of stone above the slate at both eaves; the gables are now
2167
+ pentagons derived from the roof pitch through `extrudeProfile`, tucked 20 mm under
2168
+ the roof underside so the two cannot fight for the same pixel.
2169
+ - **An external 32,000-token ceiling was ending dispatched runs mid-thought.**
2170
+ OpenCode caps one assistant step at 32,000 output tokens by default, whatever the
2171
+ model publishes. It is not a number this project set, and it took reading its
2172
+ session store to see: across every step ever recorded there, nothing had exceeded
2173
+ 32,000 -- not once, not by a token -- while the models involved declare between
2174
+ 80,000 and 943,718. Runs stopped there mid-sentence, with no file and no error,
2175
+ looking exactly like a model that was not up to the work. Dispatch now sets
2176
+ `OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX` per run from the model's own declared
2177
+ limit, read out of OpenCode's model catalog, so the ceiling is the provider's and
2178
+ never one invented here. `docs/install.md` says how to do the same by hand.
2179
+ - **Removed every default cap on how much work an agent may do.** The generation
2180
+ budget defaulted to 40 model calls, `--max-steps` to 24, and whole-program
2181
+ rewrites after a first successful render to 2. All three were cost guards from a
2182
+ hosted product where the operator paid for the calls. Out here the person running
2183
+ the tool is the person paying for it, and a ceiling they did not choose stops an
2184
+ asset halfway for reasons that have nothing to do with the asset. All three now
2185
+ default to no limit -- zero was already this codebase's "unlimited" value,
2186
+ honoured by the call budget, the step hook and the grade-refine headroom check,
2187
+ so this is a default change rather than new machinery. `--max-steps` also accepts
2188
+ `0` now instead of rejecting the value its own help text recommends, and the
2189
+ rewrite bound became a per-host option so a caller who wants it can still ask.
2190
+ The sandbox byte limits, the procedural texture bounds and the render deadlines
2191
+ stay: those are safety and correctness, not cost.
2192
+ - **Nothing told a model to write the file before it had solved the asset.** The
2193
+ skill's loop said "write the program" at step two and "write the *final* program
2194
+ to a file" at step six, which invites a model to keep the whole thing in its head
2195
+ until it is finished. Some do, and never arrive: measured through OpenCode's
2196
+ session store, four separate runs spent their entire output allowance -- to the
2197
+ token -- on one unbroken reasoning block, hand-solving pin positions and cylinder
2198
+ attachment points in prose until they were cut off mid-number, having written
2199
+ nothing at all. Three different vendors' models failed this way. The loop now
2200
+ says to save a rough version early, says plainly not to solve the geometry
2201
+ analytically first, and names the failure so a model can recognise it. The
2202
+ dispatch brief says the same.
2203
+ - **Attribution rested on the request rather than on what ran.** The dispatcher
2204
+ stamped the model it had *asked* for, which is the one claim the gallery exists
2205
+ to make and the one thing it was not checking. It now reads the model back out
2206
+ of the harness's own output and records that, reporting the difference if the
2207
+ two ever disagree. No harness has been caught substituting a model -- every one
2208
+ of the six OpenCode runs already promoted agrees with its log -- so this closes
2209
+ a gap rather than a leak, and makes "nothing in the gallery is misattributed" a
2210
+ statement backed by a check instead of an assumption.
2211
+ - **Codex could never have authored an asset.** Three faults in its entry in the
2212
+ dispatch table, each of which fired before the model saw the brief, which is why
2213
+ nothing in the gallery came from it and why nothing said so. `--full-auto` is
2214
+ deprecated in favour of an explicit `--sandbox`. A clean room is a bare temp
2215
+ directory, and Codex refuses to start in one as "not inside a trusted directory"
2216
+ unless the git check is waived. And the sandbox was never passed at all: with no
2217
+ `--cd` the child ran in this repository, with every finished example in reach --
2218
+ the exact contamination the sandbox exists to prevent, in the one harness whose
2219
+ results would have been quietly worthless rather than merely absent. Its default
2220
+ model is now unset rather than `gpt-5.1-codex`, which that account is not entitled
2221
+ to and which no repository can guess; dispatch demands `--model` instead of
2222
+ stamping an author it cannot name.
2223
+ - **The dispatcher's usage line omitted OpenCode**, which had written six of the
2224
+ twenty-seven programs in the gallery at the time. It is generated from the harness
2225
+ table now, so it cannot drift from the list it describes again.
2226
+ - **A busy GPU renderer was read as an absent one.** `--render auto` probed
2227
+ `/health` with a 1.5 second budget and treated anything that was not a clean
2228
+ answer as "no service here". The render service is single-threaded, so while it
2229
+ is drawing somebody else's frame it accepts the socket and answers late, and a
2230
+ batch of agents pointed at one GPU had runs silently drop to the CPU rasterizer
2231
+ and judge their materials off a sheet where every textured surface is flat white.
2232
+ Nothing logged a fault, because from the caller's side nothing had failed. The
2233
+ probe now distinguishes the two: a refused connection still falls through at once,
2234
+ which is the point of the short budget on machines with no renderer at all, but a
2235
+ timeout is retried against an 8 second one. `src/__tests__/cli-render-mode.test.ts`
2236
+ holds a stand-in service to both cases and to the closed port.
2237
+ - **Material constructors accepted an options object as their colour.**
2238
+ `gameMaterial(color, options?)` takes the colour first, and a program that wrote
2239
+ `gameMaterial({ color, roughness, metalness })` got a white material at default
2240
+ roughness rather than an error: `THREE.Color.set` ignores an object it does not
2241
+ recognise, so the mistake survives all the way to the render, where it looks like
2242
+ a lighting problem. It cost a real asset an afternoon. All four constructors now
2243
+ refuse a non-colour first argument and say where the settings go.
2244
+ - **A dispatched program that outlived its own run lost its author.**
2245
+ `scripts/dispatch-asset.mjs` recorded the authoring model only on a clean exit,
2246
+ so a run that wrote a finished program and then hit a wall was scored as having
2247
+ produced nothing: the file was still promoted, and stamped `Authored by: null`.
2248
+ Which is worse than an honest partial claim, because it is not a claim at all.
2249
+ Three assets in this gallery were written that way. The check is now "is there a
2250
+ program on disk", asked before the failure is classified rather than inside one
2251
+ branch of it, so it covers every way a run can be cut short instead of the one
2252
+ that was noticed first -- a provider limit, and the dispatcher's own 25 minute
2253
+ deadline, both of which fire long after the program is written. The
2254
+ interruption is carried into the manifest and stated plainly in the file header,
2255
+ because a run that was cut off cannot claim it looked at its own render and
2256
+ revised. Separately, the rate-limit matcher knew `RESOURCE_EXHAUSTED`, `429` and
2257
+ `quota` but not "session limit", which is Claude Code's wording, so a batch that
2258
+ hit one reported three flat failures and gave up instead of falling back or
2259
+ waiting the window out.
2260
+ - **`examples/aircraft-carrier.kiln.js` wore a comb of teeth around its counter.**
2261
+ The hull, the flight deck and the deck-edge netting were runs of axis-aligned
2262
+ slabs, each held at the half-beam or the deck width of its own centre. Wherever
2263
+ the ship is not parallel to its own keel -- the fine entry, the whole counter, the
2264
+ stern round-in where the starboard edge sweeps 21 m outboard in 23 -- every join
2265
+ left an end cap facing aft, and an end cap is lit as a different surface from the
2266
+ plating either side of it. Halving the station spacing only doubled the number of
2267
+ teeth, because the discontinuity was in the normals and not in the size of the
2268
+ step. All three are now lofted along their own planform curves, which share their
2269
+ vertices: no cap to catch the light, no step to refine, and 7,000 fewer triangles
2270
+ than the slabs cost.
2271
+ - **Dispatched Claude runs stopped for permission with nobody to ask.**
2272
+ `--permission-mode acceptEdits` grants writes and nothing else, so a child that
2273
+ reached for `kiln_render` blocked on a prompt no terminal was attached to. Three of
2274
+ four runs in one batch returned after a minute having written no program, no error
2275
+ and exit 0 -- just a courteous note about which permissions they would need, which
2276
+ is the worst shape a failure can take on an unattended path. The sandbox now ships
2277
+ a `.claude/settings.json` allowing the Kiln MCP tools under either install name
2278
+ plus the file tools, scoped to the one throwaway directory rather than reaching for
2279
+ `--dangerously-skip-permissions`.
2280
+ - **A dispatch deadline that could not actually end a run.** `--timeout` fired on
2281
+ schedule, wrote "timed out; killed" into the log, and then called `child.kill()`,
2282
+ which signals the direct child and nothing beneath it. On the path that matters
2283
+ that child is a shell: an npm-installed CLI on Windows is a `.cmd` shim, so the
2284
+ agent itself is a grandchild and survived. It went on holding the stdout pipe
2285
+ open, `close` never fired, and the job hung after having already been declared
2286
+ dead -- three jobs in one batch sat between 14 and 38 minutes past a 24-minute
2287
+ deadline, with a concurrency slot held by each. The deadline now kills the
2288
+ process tree (`taskkill /T` on Windows; elsewhere the child is spawned as its own
2289
+ process-group leader so the group takes the signal), and the promise settles on a
2290
+ 15-second grace timer whether or not `close` ever arrives, so a descendant that
2291
+ refuses to die costs one job rather than the batch.
2292
+ - **Orthographic views now fit what they are looking at.** The GPU render service
2293
+ framed every view with `max(sizeX, sizeY, sizeZ) * 0.72` -- the longest axis
2294
+ whichever way the camera was pointed, padded by a constant -- so anything that was
2295
+ not a cube rendered small in a large frame, and a long object viewed down its own
2296
+ axis was framed for a length nobody could see. Views are now fitted per direction
2297
+ by projecting the bounding box's eight corners onto the camera basis, which is
2298
+ exact for an orthographic projection. This also makes `--render cpu` and
2299
+ `--render gpu` frame identically: the CPU rasterizer has always fitted this way,
2300
+ and the two disagreeing meant the documented side-by-side of the two modes was not
2301
+ comparing the same crop. `render-service/src/framing.mjs` is the whole of it, and
2302
+ it is GPU-free so it is covered by unit tests.
2303
+ - **Unit-length normals out of `lathe()` and `revolveGeo()`.** `THREE.LatheGeometry`
2304
+ copies its running meridian normal into the previous-normal slot *before*
2305
+ normalising it, so the final ring of a revolve carried normals scaled by the length
2306
+ of the last profile segment -- enough to fail `GLTF_ACCESSOR_VECTOR3_NON_UNIT` on
2307
+ export. Both entry points now normalise before returning.
2308
+ - **`kiln_list_primitives` no longer sends its catalog twice.** The result carried
2309
+ all 92 entries as a structured array *and* as the text rendered from that array,
2310
+ and the default MCP serialization pretty-printed both: 90,497 bytes for one call.
2311
+ Harnesses disagree about results that size. OpenCode truncates them, spills the
2312
+ full copy to a file, and hands the model a cut-off catalog -- a dispatched
2313
+ `glm-5.3` run spent twenty-two minutes grepping that spill file at increasing
2314
+ offsets and never wrote a program. `KilnToolDef` gained an optional `text()`
2315
+ extractor (the text analogue of the existing `media()`), so a def whose output
2316
+ already contains a rendered form of itself can send that instead of a JSON dump.
2317
+ In-process callers still get `primitives` from `run()`. Measured after: 36,647
2318
+ bytes, received in full, with the same model correctly enumerating all 92
2319
+ primitives. `src/__tests__/mcp-payload.test.ts` holds the ceiling.
2320
+ - **Unit-length normals out of `subdivide()`.** `three-subdivide` builds each new
2321
+ vertex normal by summing the normals of the faces meeting there and never divides
2322
+ through, so a normal comes back at the length of however many faces touched it --
2323
+ 2/3 after one iteration on a box, 0.35 after two. No raster shows it, because a
2324
+ shader normalises before it lights, but the glTF validator rejects every one of
2325
+ them. Found by a dispatched agent whose printing-press bed was
2326
+ `subdivide(boxGeo(1.5, 0.6, 0.8), 2)`: 1,104 bad normals out of 1,152, and the
2327
+ build stopped at final-glb. Same repair as `lathe()`.
2328
+ `src/__tests__/subdivide-normals.test.ts` also asserts the raw upstream call is
2329
+ still broken, so the workaround can be removed the day that changes.
2330
+ - **Explicit default scenes on exported GLBs.** Generated and composed artifacts now
2331
+ set the glTF default scene, and optimize/palette rewrites repair legacy files whose
2332
+ first scene existed without being selected. Consumers no longer have to guess which
2333
+ scene to mount.
2334
+ - **`kiln_screenshot_animation` no longer rejects every textured asset.** The tool
2335
+ poses the scene and exports it again to render one camera cell, and that export
2336
+ was being submitted to asset QA as though it were a new asset. It is not: those
2337
+ bytes came out of this engine and had already passed. But a texture that has been
2338
+ through a GLB round trip returns as a decoded image without the payload
2339
+ provenance `MAT_TEXTURE_DECODE_FAILED` looks for, so QA blocked the preview --
2340
+ meaning no textured asset could look at its own animation, including
2341
+ `examples/robot-arm.kiln.js`, which is the repository's designated animation
2342
+ example. `renderSceneToGLB` gained a `derivative` option that makes QA observe
2343
+ rather than block for exactly this re-serialization; reports still record their
2344
+ real disposition, and the option is far narrower than the process-wide
2345
+ `KILN_QA_MODE` escape hatch.
2346
+ - **Dispatched agents no longer hang after finishing their work.** `scripts/dispatch-asset.mjs`
2347
+ spawned children with stdin as an open pipe. OpenCode keeps its process alive while stdin is open,
2348
+ so a run would author the asset, write the file, and then sit there until the dispatch timeout
2349
+ killed it and reported a failure. Children are now spawned with stdin closed; the same command
2350
+ that ran to a 75-second kill exits in six.
2351
+ - **The dispatch sandbox is a real one.** It was created under `.dogfood/`, inside this repository,
2352
+ so a child agent's CLI resolved the project root by walking up to `.git` and found the whole repo,
2353
+ `examples/` included. It now lives in the OS temp directory.
2354
+ - **The MCP server no longer needs Bun on the consumer's PATH.** Every published
2355
+ config said `"command": "bun"`, which asks the harness to resolve a name against
2356
+ its own environment. Bun's Windows installer appends to the *User* PATH, and a
2357
+ process only ever sees the environment it was born with, so any harness started
2358
+ before that install died with `exec: "bun": executable file not found in %PATH%`.
2359
+ There is no Bun-specific API on the server's runtime path, so it now ships as
2360
+ `dist/mcp-server.mjs`, a committed Node bundle -- committed because a plugin install
2361
+ is a git clone with no build step. Bun remains the development toolchain and is no
2362
+ longer something a user of the plugin needs.
2363
+
2364
+ ## [0.6.0] -- 2026-07-12
2365
+
2366
+ ### Added
2367
+ - **`measureGlbBounds(bytes)` (H-41).** World-space AABB straight from stored GLB
2368
+ bytes (WebIO + `getBounds`, EXT_mesh_gpu_instancing registered) so consumers that
2369
+ only hold the artifact never need `executeKilnCode` to recover a footprint --
2370
+ Kiln Studio's compose catalog drops its execute-model-code bbox fallback on this.
2371
+ - **Six-grid rear-quarter variant (H-33 arm).** `SIX_VIEWS_REAR_QUARTER` swaps the
2372
+ 3/4 cell to the opposite-rear azimuth (`[-0.7, 0.5, -0.7]`, labeled `3/4 Rear`);
2373
+ `resolveGridViews(variant)` + the `KILN_GRID_VARIANT=rear-quarter` env select it
2374
+ as the `renderViewGrid` default per-process -- no wire or tool-schema change.
2375
+ Explicit `opts.views` always wins; unknown env values fall back to `SIX_VIEWS`.
2376
+ - **Gemini thinking + budget knob (H-43/B1).** The `google` provider now forwards
2377
+ `KilnModelDescriptor.maxTokens` → `generationConfig.maxOutputTokens` and effort
2378
+ keywords → `thinkingConfig.thinkingLevel` ('xhigh'/'max' collapse to 'high';
2379
+ numbers ignored; the KILN_THINKING env stays Anthropic-only). A bare descriptor
2380
+ still sends nothing -- Gemini API defaults (65,536 output, dynamic thinking) are
2381
+ preserved unless a consumer opts in. NOTE for consumers: registry rows that
2382
+ carried a decorative Google `maxTokens` now BIND -- set them to the intended
2383
+ ceiling before upgrading.
2384
+
2385
+ ### Changed
2386
+ - **The texture library's signal floor is now two floors instead of one.** The
2387
+ package-weight test asserted every map's peak channel deviation cleared 2, which
2388
+ worked while all eight families were rough: it was really asking "is this real
2389
+ photography rather than a proof swatch". Extending the library to smooth materials
2390
+ broke it for the right reason -- a polished marble normal map and an oak veneer
2391
+ normal map are *supposed* to be near-flat, and one high floor would have quietly
2392
+ restricted the library to rough surfaces forever. Per map the assertion is now
2393
+ only that the image is not constant; the photography claim is enforced per family,
2394
+ where it belongs.
2395
+ - **OpenRouter reasoning clamp (A7).** `resolveOpenRouterReasoning(thinking,
2396
+ maxTokens?)` now bounds reasoning by the completion budget: numeric budgets cap
2397
+ at 50% of `maxTokens` (dropped entirely when even the 1024 floor exceeds that
2398
+ half), and 'high'/'xhigh' downgrade to 'medium' when OpenRouter's ~80%
2399
+ translation would leave < 8,192 visible tokens (the cycle-2 step-1 MaxTokens
2400
+ deaths: a 32K model at 'high' kept only ~6.4K for visible output). 64K/48K
2401
+ configurations are untouched.
2402
+ - **QA-blocked render errors are actionable (H-40(3)).** `AssetQaBlockedError`'s
2403
+ message now spells out each blocker's human message + authored `repairText`
2404
+ (first 6, then a count) instead of bare rule codes -- this string is exactly what
2405
+ the agent reads when a mid-loop `kiln_render` is QA-blocked, and what the
2406
+ salvage path records for QA-blocked step-cap programs.
2407
+
2408
+ ### Measured
2409
+ - **Per-render QA cost (H-40(4), `scripts/qa-cost-bench.ts`):** registry rules add
2410
+ 1–10 ms p95 per render across all five category fixtures; Khronos validation
2411
+ (unconditional, both modes) adds 1–4 ms p95. Two orders of magnitude under the
2412
+ 1.5 s follow-up threshold -- mid-loop QA stays on unconditionally.
2413
+
2414
+ ## [0.4.0] -- 2026-07-02
2415
+
2416
+ ### Added
2417
+ - **M1c GPU-instancing pass (plan/05 §3.3).** `renderSceneToGLB`/`renderGLB` gain an
2418
+ `instance: 'off' | 'auto' | 'on'` option (default `auto` = only `role: 'fill'` assets)
2419
+ and `optimizeGlbBytes` gains `{ instance, role }` -- the web-tier re-bake seam, so the
2420
+ pass reaches prod with no wire change. Repeated geometry (>= 5 nodes sharing one mesh
2421
+ after dedup) is rewritten to `EXT_mesh_gpu_instancing` batches in the upstream-canonical
2422
+ `dedup → instance → palette` order, cutting draw calls + bytes for fence runs /
2423
+ colonnades / container stacks. **Perf/filesize only -- the A–F grade keys on material
2424
+ count and does not move** (tests assert grade + triangle parity). Skipped for
2425
+ animated/skinned docs and any asset carrying `Joint*` pivot names (Kiln City targets
2426
+ those by name). Results are recorded as `InstancingSummary` (`meta.instancing`,
2427
+ `RenderSceneResult.instancing`, `OptimizeGlbResult.instancing` -- and
2428
+ `OptimizeGlbResult.summary` is now optional: an instancing-only pass has no
2429
+ consolidation summary). All engine glTF IO now registers `EXTMeshGPUInstancing`
2430
+ (new dep `@gltf-transform/extensions`) so re-reads (grade-from-bytes,
2431
+ optimize-from-bytes, palette snap, scene export merge) preserve the batches, and
2432
+ `collectGlbMetrics` is instance-aware (an instanced node counts one draw but N× its
2433
+ triangles -- exactly what a supporting GPU renders).
2434
+ - **M3 composer role/tier awareness (plan/05 §4.1).** `CatalogEntry` gains optional
2435
+ `role` (`CatalogAssetRole` -- the M1d asset taxonomy) + `tier` (A–F); `catalogList()`
2436
+ (and therefore `scene_list_assets`) surfaces them. Placement roles now DEFAULT from
2437
+ the catalog asset's role via `placementRoleForAsset` (wonder/poi → hero, fill → fill,
2438
+ else support) in `place`/`placeExact`/`cluster`/`ring`/`layout` -- an explicit agent
2439
+ role still wins. The composer system prompt teaches the mapping, wonder scale-up, and
2440
+ D/F-tier density budgeting.
2441
+
2442
+ ## [0.3.0] -- 2026-07-02
2443
+
2444
+ ### Added
2445
+ - **Generation-loop transcript compaction (default on).** Before every model call,
2446
+ `runKilnAgent` now strips the image out of each SUPERSEDED render tool result
2447
+ (kiln_render / kiln_screenshot / kiln_view_interior / animation strips), swapping it for
2448
+ a short text placeholder -- only the newest render image rides each request. The prune is
2449
+ surgical, unlike the composer's whole-transcript collapse: no messages are added or
2450
+ removed, toolUseIds are untouched (tool-use/tool-result pairing stays valid on every
2451
+ provider), and the JSON metrics half of each result survives. This was the biggest
2452
+ input-token/cost lever in a multi-render run -- previously every render image rode ALL
2453
+ later model calls. Opt out per run with `imageCompaction: 'off'`. New helpers exported
2454
+ from `agent`: `pruneStaleRenderImages`, `installRenderImageCompaction`,
2455
+ `STALE_RENDER_PLACEHOLDER`.
2456
+ - **M1b grade-aware refine loop (plan/05 §3.2, default on).** After the model finalizes,
2457
+ the run bakes + grades the program exactly as the shipped artifact will be graded
2458
+ (grade-aware `auto` consolidation, matching `generateKilnAsset` and the Studio web-tier
2459
+ re-bake). If it still grades below B for a consolidation-fixable reason -- material
2460
+ sprawl (>3 distinct) or texture sprawl (>4), never a transparency-only demotion (glass
2461
+ caps at C by design) -- and the step budget leaves headroom, ONE bounded feedback turn
2462
+ (grade, material count, offending material list, consolidation directive) is fed back;
2463
+ the refined program is kept only if its grade actually improves. Opt out with
2464
+ `gradeRefine: 'off'`. Emits a `grade_refine` progress event; token usage now accumulates
2465
+ across the extra invoke. New helpers exported from `agent`: `assessProgramGrade`,
2466
+ `shouldGradeRefine`, `buildGradeRefineMessage`, `gradeRank`.
2467
+
2468
+ ### Fixed
2469
+ - **Step-cap abort no longer discards a rendered program.** A run halted by the model-call
2470
+ cap used to return only an error, throwing away the working-buffer program the sink
2471
+ already held. If the captured program renders, the run now returns it with the new
2472
+ `RunKilnAgentResult.capped: true` flag (mirrors the composer's `capped` semantics); a
2473
+ cap with nothing renderable is still an `error`.
2474
+
2475
+ ## [0.2.0] -- 2026-07-02
2476
+
2477
+ The release cut the 0.1.1 composer note promised: formally versions the scene composer
2478
+ surface (`@kiln/engine/composer`, `/composer/agent` -- shipped in 0.1.x without a bump)
2479
+ plus the fixes below.
2480
+
2481
+ ### Fixed
2482
+ - **`auto` consolidation now fires on 4-material assets.** `PALETTE_MIN` dropped 5 → 4,
2483
+ aligning the trigger with the instanceability rubric (grade B tops out at 3 materials,
2484
+ so 4 is the first grade-C count). Previously a 4-material asset graded C and `auto`
2485
+ never consolidated it.
2486
+ - **Uint32 indices for >65,535-vertex geometry.** `bridgeGeometry` always wrote
2487
+ `Uint16Array` indices, silently wrapping values past 65,535 (corrupt GLB). It now
2488
+ selects `Uint32Array` when the vertex count exceeds the Uint16 ceiling; the GLB
2489
+ writer emits the matching `componentType` (5125).
2490
+ - **BYOK `apiKey` reaches Anthropic/OpenAI.** `makeKilnModel` dropped `opts.apiKey` on
2491
+ the `anthropic` and `openai` branches (only google/openrouter passed it through), so
2492
+ BYOK silently fell back to the provider env vars.
2493
+
2494
+ ### Changed
2495
+ - Comments in `agent/tools.ts` / `agent/run.ts` updated: the `unified` tool surface is
2496
+ the production surface (Kiln Studio runs `KILN_TOOL_SURFACE=unified`), no longer
2497
+ "flag-gated until a bench A/B clears it"; `current` remains the library default.
2498
+
2499
+ ### Housekeeping
2500
+ - Added the missing `LICENSE` file (MIT, already declared in `package.json`).
2501
+ - Rewrote the stale `@pixel-forge/core` module headers (`metrics.ts`, `agent/index.ts`,
2502
+ `agent/run.ts`, `palette.ts`) to the `@kiln/engine` reality and removed dangling
2503
+ references to files that do not exist in this repo.
2504
+
2505
+ ## [0.1.1] -- 2026-06-30
2506
+
2507
+ ### Documentation
2508
+ - Clarified that `@kiln/engine` remains private execution infrastructure. External developer access
2509
+ is mediated by Studio `/v1`, private SDK/skills, and future AgentCore Gateway product tools rather
2510
+ than direct engine, raw Forge, or composer harness exposure.
2511
+
2512
+ ### Changed
2513
+ - Treat native `claude-sonnet-5` as adaptive-only for Anthropic thinking controls: numeric
2514
+ `KILN_THINKING` budgets are ignored for this model so the agent harness does not send
2515
+ manual extended-thinking params that Sonnet 5 rejects.
2516
+
2517
+ ### Added -- scene composer (`@kiln/engine/composer`, `/composer/agent`)
2518
+ A THREE-free scene-composition surface: a `PlacementModel` single-source-of-truth with a
2519
+ small scene DSL (`scene()`/`asset()`), terrain-agnostic hierarchy-aware layout, an overlap
2520
+ validator (MTV resolution), a ground sampler, and a `SceneRenderPort`; plus the Strands
2521
+ agent loop (`runKilnComposer`, 14 `scene_*` tools) isolated under `/composer/agent` so the
2522
+ SDK never leaks into the pure core. Transcript compaction collapses to `serialize(model)`
2523
+ past a threshold (the externalized model IS the state), with a soft step-cap backstop and a
2524
+ `scene_layout`-first prompt so large many-asset scenes converge. ~990 lines of new tests.
2525
+
2526
+ > The surface landed without a version bump (Studio consumed it via the committed
2527
+ > `@kiln/engine` 0.1.0 tarball); formally versioned by the 0.2.0 release above.
2528
+
2529
+ ## [0.1.0] -- 2026-06-23
2530
+
2531
+ Initial extraction of the Kiln 3D engine from `pixel-forge/packages/core/src/kiln`
2532
+ (source `d396c10`), file history preserved via `git subtree split`.
2533
+
2534
+ ### Included (the reachable runtime closure of agent/render/palette/views/arena)
2535
+ - `agent/` Strands agent loop · `arena/` ranking math · `render.ts` GLB bake/grade/optimize/snap/composeScene
2536
+ - `primitives.ts` · `solids.ts` (CSG) · `ops.ts` · `gears.ts` · `uv.ts`/`uv-shapes.ts` · `textures.ts`
2537
+ - `validation.ts` (AST) · `inspect.ts` · `metrics.ts` · `palette.ts`/`palette-snap.ts`
2538
+ - `list-primitives.ts` · `prompt.ts`/`prompt-api.ts` · `tools/registry.ts` · `views/` (CPU rasterizer + PNG)
2539
+
2540
+ ### Removed vs pixel-forge core/kiln (what makes it lean)
2541
+ - TIJ pipeline modules `imposter/`, `fbx-ingest/`, `sprite-atlas/`, `retex/`,
2542
+ `photogrammetry/`, `lod/` -- the only modules pulling in Playwright + the heavier
2543
+ image path.
2544
+ - Legacy single-shot `generate.ts` (Studio uses the agent path) and the entangled root
2545
+ `index.ts` barrel (Studio consumes subpaths, not the barrel).
2546
+ - Dropped deps: `@anthropic-ai/*`, `@ai-sdk/{anthropic,google,openai}`, `@google/genai`,
2547
+ `openai`, `@fal-ai/client`, Vercel `ai`, `playwright`, `meshoptimizer`, `xatlas-three`,
2548
+ `@pixel-forge/shared`.
2549
+
2550
+ ### Retained runtime deps (lazy-loaded)
2551
+ - `sharp` -- texture decode in `loadTexture` (`await import('sharp')`).
2552
+ - `xatlasjs` -- UV atlas in `autoUnwrap` (`await import('xatlasjs/dist/node/...')`).
2553
+
2554
+ ### Tests
2555
+ - 36 source test files ported (382 pass / 2 skip / 0 fail offline). Live agent tests
2556
+ gated behind `KILN_SPIKE_LIVE=1` (off in CI). Dropped: the 6 OUT-module test suites,
2557
+ `deps-smoke` (old dep set), `spike`/`top-level-generate` (tested the deleted barrel
2558
+ `generate()` wrapper), and `companions`/`refactor-validation` (tested the legacy
2559
+ `generate.ts` companion aliases `kiln.editCode`/`kiln.refactor`, also pruned).
2560
+ - Provider SDKs (`@anthropic-ai/sdk`, `@google/genai`, `openai`, `ai`,
2561
+ `@aws-sdk/client-bedrock-runtime`) are **devDependencies only** -- needed to exercise
2562
+ the multi-provider `makeKilnModel` factory in `providers.test.ts`; they never enter
2563
+ runtime `dependencies`. The kiln-glb skill-drift gate is `skipIf`-guarded (the skill
2564
+ lives outside the engine repo).
2565
+
2566
+ ### Tooling
2567
+ - **Biome 2.5.1** adopted (lint + format). Source formatted to Biome style (single
2568
+ quotes, 2-space, 100-col); `bun run lint` is green. A baseline of 19 stylistic
2569
+ warnings (`noExplicitAny`/`useTemplate`/`useOptionalChain`/`noGlobalIsFinite`/…) is
2570
+ left visible for a hardening pass; `noNonNullAssertion`/`useLiteralKeys` are off and
2571
+ `useIterableCallbackReturn` is warn (all idiomatic in the extracted code).
2572
+
2573
+ ### Follow-ups (deliberately deferred from WS0 -- see ../plan/03-standards-harness.md)
2574
+ - Burn down the 19 Biome warnings + re-promote `useIterableCallbackReturn` to error.
2575
+ - Tighten `tsconfig` (`exactOptionalPropertyTypes`); currently mirrors pixel-forge core
2576
+ for a zero-drift green typecheck.
2577
+ - Consider `tsup` `dist` build + Vitest golden-image render harness (engine ships TS
2578
+ source today for drop-in parity with how Studio consumes core).
2579
+ - Rename the live-test gate `KILN_SPIKE_LIVE` → `KILN_LIVE`.