@developerz.ai/ts23d 0.0.0-stage → 0.2.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 (567) hide show
  1. package/.claude/agents/bug-hunter.md +21 -0
  2. package/.claude/agents/docs-author.md +13 -0
  3. package/.claude/agents/dx-auditor.md +14 -0
  4. package/.claude/agents/primitive-author.md +22 -0
  5. package/.claude/agents/recipe-author.md +36 -0
  6. package/.claude/agents/visual-reviewer.md +23 -0
  7. package/.claude/commands/feature.md +28 -0
  8. package/.claude/commands/new-recipe.md +19 -0
  9. package/.claude/commands/release.md +14 -0
  10. package/.claude/commands/verify.md +9 -0
  11. package/.claude/skills/author-recipe/SKILL.md +93 -0
  12. package/CHANGELOG.md +90 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +385 -2
  16. package/dist/chunk-6b05mnmx.js +19 -0
  17. package/dist/chunk-6b05mnmx.js.map +11 -0
  18. package/dist/chunk-7rtfjh9f.js +45 -0
  19. package/dist/chunk-7rtfjh9f.js.map +10 -0
  20. package/dist/chunk-8j4c28jt.js +4085 -0
  21. package/dist/chunk-8j4c28jt.js.map +46 -0
  22. package/dist/chunk-d1fv6hrm.js +2532 -0
  23. package/dist/chunk-d1fv6hrm.js.map +43 -0
  24. package/dist/chunk-dbq4qf52.js +902 -0
  25. package/dist/chunk-dbq4qf52.js.map +25 -0
  26. package/dist/chunk-drna2rny.js +836 -0
  27. package/dist/chunk-drna2rny.js.map +17 -0
  28. package/dist/chunk-dxvypr7y.js +89 -0
  29. package/dist/chunk-dxvypr7y.js.map +11 -0
  30. package/dist/chunk-f6yme003.js +1589 -0
  31. package/dist/chunk-f6yme003.js.map +30 -0
  32. package/dist/chunk-hg7pf34j.js +465 -0
  33. package/dist/chunk-hg7pf34j.js.map +19 -0
  34. package/dist/chunk-jq4e17tj.js +1031 -0
  35. package/dist/chunk-jq4e17tj.js.map +21 -0
  36. package/dist/chunk-y63c6zcv.js +2668 -0
  37. package/dist/chunk-y63c6zcv.js.map +53 -0
  38. package/dist/cli/args.d.ts +30 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +348 -0
  41. package/dist/cli/bin.js.map +15 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +53 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/cli/texture-cache.d.ts +4 -0
  47. package/dist/core/asset.d.ts +10 -0
  48. package/dist/core/definition.d.ts +155 -0
  49. package/dist/core/errors.d.ts +17 -0
  50. package/dist/core/family.d.ts +23 -0
  51. package/dist/core/index.d.ts +9 -0
  52. package/dist/core/index.js +69 -0
  53. package/dist/core/index.js.map +9 -0
  54. package/dist/core/mesh.d.ts +35 -0
  55. package/dist/core/part.d.ts +114 -0
  56. package/dist/core/png.d.ts +1 -0
  57. package/dist/core/rng.d.ts +12 -0
  58. package/dist/core/transform.d.ts +20 -0
  59. package/dist/core/vec.d.ts +11 -0
  60. package/dist/core/world.d.ts +16 -0
  61. package/dist/export/bin-writer.d.ts +16 -0
  62. package/dist/export/container.d.ts +3 -0
  63. package/dist/export/credits.d.ts +4 -0
  64. package/dist/export/glb-options.d.ts +48 -0
  65. package/dist/export/glb.d.ts +8 -0
  66. package/dist/export/hierarchy.d.ts +23 -0
  67. package/dist/export/index.d.ts +5 -0
  68. package/dist/export/index.js +40 -0
  69. package/dist/export/index.js.map +9 -0
  70. package/dist/export/lights.d.ts +8 -0
  71. package/dist/export/lod-coverage.d.ts +12 -0
  72. package/dist/export/materials.d.ts +23 -0
  73. package/dist/export/merge-pivots.d.ts +6 -0
  74. package/dist/export/metadata.d.ts +21 -0
  75. package/dist/export/motion.d.ts +9 -0
  76. package/dist/export/optimize-parts.d.ts +26 -0
  77. package/dist/export/primitive.d.ts +3 -0
  78. package/dist/export/tangents.d.ts +2 -0
  79. package/dist/export/textures.d.ts +22 -0
  80. package/dist/export/version.d.ts +2 -0
  81. package/dist/grammar/index.d.ts +2 -0
  82. package/dist/grammar/index.js +16 -0
  83. package/dist/grammar/index.js.map +9 -0
  84. package/dist/grammar/split.d.ts +21 -0
  85. package/dist/grammar/whyte.d.ts +5 -0
  86. package/dist/index.d.ts +11 -0
  87. package/dist/index.js +457 -0
  88. package/dist/index.js.map +9 -0
  89. package/dist/kinds/building.d.ts +44 -0
  90. package/dist/kinds/faces.d.ts +27 -0
  91. package/dist/kinds/head-armour.d.ts +5 -0
  92. package/dist/kinds/head-beard.d.ts +6 -0
  93. package/dist/kinds/head-bust.d.ts +5 -0
  94. package/dist/kinds/head-extras.d.ts +15 -0
  95. package/dist/kinds/head-face.d.ts +8 -0
  96. package/dist/kinds/head-garb.d.ts +4 -0
  97. package/dist/kinds/head-geom.d.ts +7 -0
  98. package/dist/kinds/head-hair.d.ts +4 -0
  99. package/dist/kinds/head-layout.d.ts +29 -0
  100. package/dist/kinds/head-mesh.d.ts +31 -0
  101. package/dist/kinds/head-mouth.d.ts +4 -0
  102. package/dist/kinds/head-params.d.ts +116 -0
  103. package/dist/kinds/head-presets.d.ts +19 -0
  104. package/dist/kinds/head-rim.d.ts +10 -0
  105. package/dist/kinds/head-tone.d.ts +10 -0
  106. package/dist/kinds/head-torso.d.ts +35 -0
  107. package/dist/kinds/head.d.ts +39 -0
  108. package/dist/kinds/hull-lines.d.ts +65 -0
  109. package/dist/kinds/hull-skin.d.ts +6 -0
  110. package/dist/kinds/hull.d.ts +14 -0
  111. package/dist/kinds/index.d.ts +16 -0
  112. package/dist/kinds/index.js +78 -0
  113. package/dist/kinds/index.js.map +9 -0
  114. package/dist/kinds/mass.d.ts +15 -0
  115. package/dist/kinds/openings.d.ts +30 -0
  116. package/dist/kinds/planned.d.ts +6 -0
  117. package/dist/kinds/roof-chimney.d.ts +16 -0
  118. package/dist/kinds/roof-shape.d.ts +64 -0
  119. package/dist/kinds/roof.d.ts +31 -0
  120. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  121. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  122. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  123. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  124. package/dist/kinds/vegetation-params.d.ts +90 -0
  125. package/dist/kinds/vegetation-presets.d.ts +20 -0
  126. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  127. package/dist/kinds/vegetation.d.ts +42 -0
  128. package/dist/kinds/vehicle.d.ts +42 -0
  129. package/dist/material/cc0.d.ts +4 -0
  130. package/dist/material/define.d.ts +34 -0
  131. package/dist/material/generator.d.ts +4 -0
  132. package/dist/material/index.d.ts +9 -0
  133. package/dist/material/index.js +70 -0
  134. package/dist/material/index.js.map +9 -0
  135. package/dist/material/library.d.ts +7 -0
  136. package/dist/material/material-def.d.ts +30 -0
  137. package/dist/material/paint/bond.d.ts +3 -0
  138. package/dist/material/paint/canvas.d.ts +39 -0
  139. package/dist/material/paint/cell.d.ts +4 -0
  140. package/dist/material/paint/cells-round.d.ts +5 -0
  141. package/dist/material/paint/cells.d.ts +7 -0
  142. package/dist/material/paint/compose.d.ts +5 -0
  143. package/dist/material/paint/concrete.d.ts +3 -0
  144. package/dist/material/paint/courses.d.ts +31 -0
  145. package/dist/material/paint/decode-png.d.ts +6 -0
  146. package/dist/material/paint/default-sheet.d.ts +4 -0
  147. package/dist/material/paint/fabric.d.ts +4 -0
  148. package/dist/material/paint/foliage-broad.d.ts +5 -0
  149. package/dist/material/paint/foliage-common.d.ts +35 -0
  150. package/dist/material/paint/foliage-compound.d.ts +4 -0
  151. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  152. package/dist/material/paint/foliage-raster.d.ts +26 -0
  153. package/dist/material/paint/foliage.d.ts +3 -0
  154. package/dist/material/paint/image-strip.d.ts +4 -0
  155. package/dist/material/paint/kit.d.ts +5 -0
  156. package/dist/material/paint/masks.d.ts +3 -0
  157. package/dist/material/paint/metal.d.ts +3 -0
  158. package/dist/material/paint/noise.d.ts +9 -0
  159. package/dist/material/paint/normal.d.ts +8 -0
  160. package/dist/material/paint/orm.d.ts +8 -0
  161. package/dist/material/paint/planks.d.ts +3 -0
  162. package/dist/material/paint/plates.d.ts +3 -0
  163. package/dist/material/paint/shapes.d.ts +8 -0
  164. package/dist/material/paint/slate.d.ts +3 -0
  165. package/dist/material/paint/stamp.d.ts +3 -0
  166. package/dist/material/paint/strip.d.ts +3 -0
  167. package/dist/material/paint/surface.d.ts +5 -0
  168. package/dist/material/paint/types.d.ts +65 -0
  169. package/dist/material/paint/validate.d.ts +3 -0
  170. package/dist/material/palette.d.ts +14 -0
  171. package/dist/material/shader-godot.d.ts +2 -0
  172. package/dist/material/shader-three.d.ts +2 -0
  173. package/dist/material/shader.d.ts +46 -0
  174. package/dist/material/slots.d.ts +5 -0
  175. package/dist/material/texture-source.d.ts +34 -0
  176. package/dist/material/trim-sheet.d.ts +38 -0
  177. package/dist/neural/generator.d.ts +25 -0
  178. package/dist/neural/images.d.ts +17 -0
  179. package/dist/neural/index.d.ts +2 -0
  180. package/dist/neural/index.js +14 -0
  181. package/dist/neural/index.js.map +9 -0
  182. package/dist/passes/bake-ao.d.ts +17 -0
  183. package/dist/passes/grime.d.ts +15 -0
  184. package/dist/passes/index.d.ts +6 -0
  185. package/dist/passes/index.js +22 -0
  186. package/dist/passes/index.js.map +9 -0
  187. package/dist/passes/livery.d.ts +19 -0
  188. package/dist/passes/optimize-cache.d.ts +15 -0
  189. package/dist/passes/optimize-weld.d.ts +15 -0
  190. package/dist/passes/optimize.d.ts +28 -0
  191. package/dist/passes/ray-grid.d.ts +23 -0
  192. package/dist/passes/weighted-normals.d.ts +8 -0
  193. package/dist/primitives/beam.d.ts +17 -0
  194. package/dist/primitives/bevel-box.d.ts +13 -0
  195. package/dist/primitives/box.d.ts +16 -0
  196. package/dist/primitives/cell.d.ts +20 -0
  197. package/dist/primitives/check.d.ts +12 -0
  198. package/dist/primitives/closed.d.ts +11 -0
  199. package/dist/primitives/erosion.d.ts +22 -0
  200. package/dist/primitives/font-5x7.d.ts +3 -0
  201. package/dist/primitives/heightfield.d.ts +16 -0
  202. package/dist/primitives/index.d.ts +23 -0
  203. package/dist/primitives/index.js +81 -0
  204. package/dist/primitives/index.js.map +9 -0
  205. package/dist/primitives/lathe-facets.d.ts +8 -0
  206. package/dist/primitives/lathe.d.ts +26 -0
  207. package/dist/primitives/lattice.d.ts +30 -0
  208. package/dist/primitives/loft.d.ts +15 -0
  209. package/dist/primitives/mc-table.d.ts +2 -0
  210. package/dist/primitives/member.d.ts +19 -0
  211. package/dist/primitives/noise.d.ts +34 -0
  212. package/dist/primitives/planar.d.ts +10 -0
  213. package/dist/primitives/prism.d.ts +13 -0
  214. package/dist/primitives/sculpt-edit.d.ts +56 -0
  215. package/dist/primitives/sculpt-tree.d.ts +23 -0
  216. package/dist/primitives/sculpt.d.ts +27 -0
  217. package/dist/primitives/sdf-grid.d.ts +17 -0
  218. package/dist/primitives/sdf-ops.d.ts +37 -0
  219. package/dist/primitives/sdf.d.ts +21 -0
  220. package/dist/primitives/strip-uv.d.ts +11 -0
  221. package/dist/primitives/surface-nets.d.ts +7 -0
  222. package/dist/primitives/sweep.d.ts +19 -0
  223. package/dist/primitives/terrain-color.d.ts +30 -0
  224. package/dist/primitives/terrain-grid.d.ts +36 -0
  225. package/dist/primitives/text-layout.d.ts +22 -0
  226. package/dist/primitives/text.d.ts +27 -0
  227. package/dist/primitives/tiled.d.ts +22 -0
  228. package/dist/primitives/transport.d.ts +17 -0
  229. package/dist/primitives/triangulate.d.ts +2 -0
  230. package/dist/primitives/value-noise.d.ts +10 -0
  231. package/dist/qa/checks.d.ts +11 -0
  232. package/dist/qa/clash.d.ts +2 -0
  233. package/dist/qa/floating.d.ts +5 -0
  234. package/dist/qa/gates.d.ts +12 -0
  235. package/dist/qa/geometry.d.ts +21 -0
  236. package/dist/qa/index.d.ts +12 -0
  237. package/dist/qa/index.js +78 -0
  238. package/dist/qa/index.js.map +9 -0
  239. package/dist/qa/inside.d.ts +12 -0
  240. package/dist/qa/inspect.d.ts +18 -0
  241. package/dist/qa/lods.d.ts +12 -0
  242. package/dist/qa/measures.d.ts +4 -0
  243. package/dist/qa/occlusion.d.ts +3 -0
  244. package/dist/qa/sanity.d.ts +7 -0
  245. package/dist/qa/types.d.ts +12 -0
  246. package/dist/render/beauty.d.ts +40 -0
  247. package/dist/render/camera.d.ts +33 -0
  248. package/dist/render/depth-map.d.ts +38 -0
  249. package/dist/render/font.d.ts +1 -0
  250. package/dist/render/ground.d.ts +13 -0
  251. package/dist/render/index.d.ts +10 -0
  252. package/dist/render/index.js +47 -0
  253. package/dist/render/index.js.map +9 -0
  254. package/dist/render/lighting.d.ts +27 -0
  255. package/dist/render/lod-strip.d.ts +5 -0
  256. package/dist/render/metrics.d.ts +37 -0
  257. package/dist/render/options.d.ts +28 -0
  258. package/dist/render/photo-match.d.ts +19 -0
  259. package/dist/render/raster.d.ts +25 -0
  260. package/dist/render/resolve.d.ts +11 -0
  261. package/dist/render/review-prompt.d.ts +2 -0
  262. package/dist/render/review-warnings.d.ts +34 -0
  263. package/dist/render/review.d.ts +24 -0
  264. package/dist/render/scene.d.ts +18 -0
  265. package/dist/render/shade.d.ts +22 -0
  266. package/dist/render/sheet.d.ts +15 -0
  267. package/dist/render/texture.d.ts +15 -0
  268. package/dist/render/thumbnail.d.ts +8 -0
  269. package/dist/render/vis-buffer.d.ts +18 -0
  270. package/dist/testing/assert.d.ts +21 -0
  271. package/dist/testing/compiled.d.ts +15 -0
  272. package/dist/testing/hash.d.ts +1 -0
  273. package/dist/testing/index.d.ts +4 -0
  274. package/dist/testing/index.js +134 -0
  275. package/dist/testing/index.js.map +13 -0
  276. package/dist/testing/snapshot.d.ts +13 -0
  277. package/docs/README.md +56 -0
  278. package/docs/architecture/README.md +68 -0
  279. package/docs/architecture/adding-a-kind.md +44 -0
  280. package/docs/architecture/adding-a-primitive.md +49 -0
  281. package/docs/architecture/errors.md +47 -0
  282. package/docs/architecture/glb-production.md +214 -0
  283. package/docs/architecture/gltf-capabilities.md +117 -0
  284. package/docs/architecture/materials.md +151 -0
  285. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  286. package/docs/decisions/002-typescript-not-json.md +22 -0
  287. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  288. package/docs/decisions/004-z-up-metres.md +21 -0
  289. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  290. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  291. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  292. package/docs/decisions/008-surface-materials.md +21 -0
  293. package/docs/decisions/009-gltf-front-axis.md +21 -0
  294. package/docs/decisions/README.md +15 -0
  295. package/docs/idea/01-vision.md +117 -0
  296. package/docs/idea/02-dsl.md +343 -0
  297. package/docs/idea/03-ir.md +97 -0
  298. package/docs/idea/04-materials.md +114 -0
  299. package/docs/idea/05-agent-loop.md +141 -0
  300. package/docs/idea/06-qa.md +101 -0
  301. package/docs/idea/07-what-fits.md +45 -0
  302. package/docs/idea/08-neural-adapters.md +78 -0
  303. package/docs/idea/09-kinds.md +105 -0
  304. package/docs/idea/10-sculpting.md +166 -0
  305. package/docs/idea/11-materials-authoring.md +132 -0
  306. package/docs/idea/12-blender-bridge.md +136 -0
  307. package/docs/idea/13-asset-pipeline-research.md +89 -0
  308. package/docs/landscape.md +49 -0
  309. package/docs/plans/roadmap.md +139 -0
  310. package/examples/build-with-bun.ts +41 -0
  311. package/llms.txt +47 -0
  312. package/package.json +100 -4
  313. package/recipes/README.md +38 -0
  314. package/recipes/asteroid/README.md +46 -0
  315. package/recipes/asteroid/asteroid.ts +336 -0
  316. package/recipes/boulders/README.md +28 -0
  317. package/recipes/boulders/boulders.ts +117 -0
  318. package/recipes/boxcar/README.md +53 -0
  319. package/recipes/boxcar/boxcar.ts +1000 -0
  320. package/recipes/cargo_container_scifi/README.md +52 -0
  321. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  322. package/recipes/dropship/README.md +84 -0
  323. package/recipes/dropship/dropship.ts +1144 -0
  324. package/recipes/eiffel_tower/README.md +110 -0
  325. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  326. package/recipes/goblin_head/README.md +75 -0
  327. package/recipes/goblin_head/goblin_head.ts +38 -0
  328. package/recipes/human_head/README.md +68 -0
  329. package/recipes/human_head/human_head.ts +38 -0
  330. package/recipes/loco_440/README.md +32 -0
  331. package/recipes/loco_440/loco_440.ts +1404 -0
  332. package/recipes/material_swatches/README.md +21 -0
  333. package/recipes/material_swatches/material_swatches.ts +189 -0
  334. package/recipes/mountain_range/README.md +45 -0
  335. package/recipes/mountain_range/mountain_range.ts +128 -0
  336. package/recipes/oak/README.md +30 -0
  337. package/recipes/oak/oak.ts +19 -0
  338. package/recipes/orc_head/README.md +78 -0
  339. package/recipes/orc_head/orc_head.ts +41 -0
  340. package/recipes/palm/README.md +30 -0
  341. package/recipes/palm/palm.ts +19 -0
  342. package/recipes/pine/README.md +29 -0
  343. package/recipes/pine/pine.ts +19 -0
  344. package/recipes/signal_gantry/README.md +36 -0
  345. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  346. package/recipes/station_halt/README.md +39 -0
  347. package/recipes/station_halt/station_halt.ts +722 -0
  348. package/recipes/t34/README.md +44 -0
  349. package/recipes/t34/t34.ts +1068 -0
  350. package/recipes/tiger_1/README.md +75 -0
  351. package/recipes/tiger_1/tiger_1.ts +1370 -0
  352. package/recipes/water_tower/README.md +40 -0
  353. package/recipes/water_tower/water_tower.ts +542 -0
  354. package/src/cli/args.ts +125 -0
  355. package/src/cli/bin.ts +7 -0
  356. package/src/cli/load.ts +17 -0
  357. package/src/cli/manifest.ts +95 -0
  358. package/src/cli/render-cmds.ts +66 -0
  359. package/src/cli/run.ts +192 -0
  360. package/src/cli/texture-cache.ts +24 -0
  361. package/src/core/asset.ts +129 -0
  362. package/src/core/definition.ts +156 -0
  363. package/src/core/errors.ts +50 -0
  364. package/src/core/family.ts +50 -0
  365. package/src/core/index.ts +66 -0
  366. package/src/core/mesh.ts +114 -0
  367. package/src/core/part.ts +130 -0
  368. package/src/core/png.ts +49 -0
  369. package/src/core/rng.ts +49 -0
  370. package/src/core/transform.ts +90 -0
  371. package/src/core/vec.ts +26 -0
  372. package/src/core/world.ts +80 -0
  373. package/src/export/bin-writer.ts +79 -0
  374. package/src/export/container.ts +35 -0
  375. package/src/export/credits.ts +25 -0
  376. package/src/export/glb-options.ts +53 -0
  377. package/src/export/glb.ts +187 -0
  378. package/src/export/hierarchy.ts +122 -0
  379. package/src/export/index.ts +34 -0
  380. package/src/export/lights.ts +49 -0
  381. package/src/export/lod-coverage.ts +71 -0
  382. package/src/export/materials.ts +126 -0
  383. package/src/export/merge-pivots.ts +146 -0
  384. package/src/export/metadata.ts +62 -0
  385. package/src/export/motion.ts +70 -0
  386. package/src/export/optimize-parts.ts +80 -0
  387. package/src/export/primitive.ts +35 -0
  388. package/src/export/tangents.ts +75 -0
  389. package/src/export/textures.ts +88 -0
  390. package/src/export/version.ts +6 -0
  391. package/src/grammar/index.ts +4 -0
  392. package/src/grammar/split.ts +87 -0
  393. package/src/grammar/whyte.ts +20 -0
  394. package/src/index.ts +14 -0
  395. package/src/kinds/building.ts +153 -0
  396. package/src/kinds/faces.ts +138 -0
  397. package/src/kinds/head-armour.ts +181 -0
  398. package/src/kinds/head-beard.ts +133 -0
  399. package/src/kinds/head-bust.ts +184 -0
  400. package/src/kinds/head-extras.ts +150 -0
  401. package/src/kinds/head-face.ts +242 -0
  402. package/src/kinds/head-garb.ts +181 -0
  403. package/src/kinds/head-geom.ts +31 -0
  404. package/src/kinds/head-hair.ts +193 -0
  405. package/src/kinds/head-layout.ts +63 -0
  406. package/src/kinds/head-mesh.ts +195 -0
  407. package/src/kinds/head-mouth.ts +111 -0
  408. package/src/kinds/head-params.ts +162 -0
  409. package/src/kinds/head-presets.ts +155 -0
  410. package/src/kinds/head-rim.ts +128 -0
  411. package/src/kinds/head-tone.ts +52 -0
  412. package/src/kinds/head-torso.ts +230 -0
  413. package/src/kinds/head.ts +176 -0
  414. package/src/kinds/hull-lines.ts +160 -0
  415. package/src/kinds/hull-skin.ts +83 -0
  416. package/src/kinds/hull.ts +209 -0
  417. package/src/kinds/index.ts +63 -0
  418. package/src/kinds/mass.ts +110 -0
  419. package/src/kinds/openings.ts +141 -0
  420. package/src/kinds/planned.ts +12 -0
  421. package/src/kinds/roof-chimney.ts +100 -0
  422. package/src/kinds/roof-shape.ts +230 -0
  423. package/src/kinds/roof.ts +250 -0
  424. package/src/kinds/vegetation-fronds.ts +126 -0
  425. package/src/kinds/vegetation-impostor.ts +118 -0
  426. package/src/kinds/vegetation-leaves.ts +168 -0
  427. package/src/kinds/vegetation-mesh.ts +174 -0
  428. package/src/kinds/vegetation-params.ts +147 -0
  429. package/src/kinds/vegetation-presets.ts +184 -0
  430. package/src/kinds/vegetation-skeleton.ts +168 -0
  431. package/src/kinds/vegetation.ts +199 -0
  432. package/src/kinds/vehicle.ts +144 -0
  433. package/src/material/cc0.ts +41 -0
  434. package/src/material/define.ts +130 -0
  435. package/src/material/generator.ts +63 -0
  436. package/src/material/index.ts +60 -0
  437. package/src/material/library.ts +196 -0
  438. package/src/material/material-def.ts +84 -0
  439. package/src/material/paint/bond.ts +60 -0
  440. package/src/material/paint/canvas.ts +101 -0
  441. package/src/material/paint/cell.ts +39 -0
  442. package/src/material/paint/cells-round.ts +119 -0
  443. package/src/material/paint/cells.ts +111 -0
  444. package/src/material/paint/compose.ts +53 -0
  445. package/src/material/paint/concrete.ts +62 -0
  446. package/src/material/paint/courses.ts +85 -0
  447. package/src/material/paint/decode-png.ts +106 -0
  448. package/src/material/paint/default-sheet.ts +73 -0
  449. package/src/material/paint/fabric.ts +63 -0
  450. package/src/material/paint/foliage-broad.ts +126 -0
  451. package/src/material/paint/foliage-common.ts +93 -0
  452. package/src/material/paint/foliage-compound.ts +135 -0
  453. package/src/material/paint/foliage-conifer.ts +143 -0
  454. package/src/material/paint/foliage-raster.ts +196 -0
  455. package/src/material/paint/foliage.ts +23 -0
  456. package/src/material/paint/image-strip.ts +88 -0
  457. package/src/material/paint/kit.ts +37 -0
  458. package/src/material/paint/masks.ts +46 -0
  459. package/src/material/paint/metal.ts +109 -0
  460. package/src/material/paint/noise.ts +52 -0
  461. package/src/material/paint/normal.ts +52 -0
  462. package/src/material/paint/orm.ts +108 -0
  463. package/src/material/paint/planks.ts +101 -0
  464. package/src/material/paint/plates.ts +72 -0
  465. package/src/material/paint/shapes.ts +28 -0
  466. package/src/material/paint/slate.ts +52 -0
  467. package/src/material/paint/stamp.ts +40 -0
  468. package/src/material/paint/strip.ts +48 -0
  469. package/src/material/paint/surface.ts +60 -0
  470. package/src/material/paint/types.ts +94 -0
  471. package/src/material/paint/validate.ts +74 -0
  472. package/src/material/palette.ts +46 -0
  473. package/src/material/shader-godot.ts +77 -0
  474. package/src/material/shader-three.ts +103 -0
  475. package/src/material/shader.ts +100 -0
  476. package/src/material/slots.ts +18 -0
  477. package/src/material/texture-source.ts +86 -0
  478. package/src/material/trim-sheet.ts +166 -0
  479. package/src/neural/generator.ts +37 -0
  480. package/src/neural/images.ts +27 -0
  481. package/src/neural/index.ts +10 -0
  482. package/src/passes/bake-ao.ts +183 -0
  483. package/src/passes/grime.ts +37 -0
  484. package/src/passes/index.ts +8 -0
  485. package/src/passes/livery.ts +66 -0
  486. package/src/passes/optimize-cache.ts +175 -0
  487. package/src/passes/optimize-weld.ts +147 -0
  488. package/src/passes/optimize.ts +85 -0
  489. package/src/passes/ray-grid.ts +237 -0
  490. package/src/passes/weighted-normals.ts +125 -0
  491. package/src/primitives/beam.ts +60 -0
  492. package/src/primitives/bevel-box.ts +140 -0
  493. package/src/primitives/box.ts +90 -0
  494. package/src/primitives/cell.ts +115 -0
  495. package/src/primitives/check.ts +49 -0
  496. package/src/primitives/closed.ts +74 -0
  497. package/src/primitives/erosion.ts +183 -0
  498. package/src/primitives/font-5x7.ts +117 -0
  499. package/src/primitives/heightfield.ts +101 -0
  500. package/src/primitives/index.ts +56 -0
  501. package/src/primitives/lathe-facets.ts +45 -0
  502. package/src/primitives/lathe.ts +174 -0
  503. package/src/primitives/lattice.ts +131 -0
  504. package/src/primitives/loft.ts +174 -0
  505. package/src/primitives/mc-table.ts +41 -0
  506. package/src/primitives/member.ts +130 -0
  507. package/src/primitives/noise.ts +157 -0
  508. package/src/primitives/planar.ts +55 -0
  509. package/src/primitives/prism.ts +85 -0
  510. package/src/primitives/sculpt-edit.ts +212 -0
  511. package/src/primitives/sculpt-tree.ts +226 -0
  512. package/src/primitives/sculpt.ts +80 -0
  513. package/src/primitives/sdf-grid.ts +131 -0
  514. package/src/primitives/sdf-ops.ts +145 -0
  515. package/src/primitives/sdf.ts +83 -0
  516. package/src/primitives/strip-uv.ts +26 -0
  517. package/src/primitives/surface-nets.ts +161 -0
  518. package/src/primitives/sweep.ts +162 -0
  519. package/src/primitives/terrain-color.ts +85 -0
  520. package/src/primitives/terrain-grid.ts +137 -0
  521. package/src/primitives/text-layout.ts +85 -0
  522. package/src/primitives/text.ts +197 -0
  523. package/src/primitives/tiled.ts +93 -0
  524. package/src/primitives/transport.ts +85 -0
  525. package/src/primitives/triangulate.ts +42 -0
  526. package/src/primitives/value-noise.ts +62 -0
  527. package/src/qa/checks.ts +110 -0
  528. package/src/qa/clash.ts +177 -0
  529. package/src/qa/floating.ts +63 -0
  530. package/src/qa/gates.ts +126 -0
  531. package/src/qa/geometry.ts +187 -0
  532. package/src/qa/index.ts +28 -0
  533. package/src/qa/inside.ts +114 -0
  534. package/src/qa/inspect.ts +107 -0
  535. package/src/qa/lods.ts +97 -0
  536. package/src/qa/measures.ts +67 -0
  537. package/src/qa/occlusion.ts +166 -0
  538. package/src/qa/sanity.ts +137 -0
  539. package/src/qa/types.ts +17 -0
  540. package/src/render/beauty.ts +115 -0
  541. package/src/render/camera.ts +142 -0
  542. package/src/render/depth-map.ts +191 -0
  543. package/src/render/font.ts +76 -0
  544. package/src/render/ground.ts +87 -0
  545. package/src/render/index.ts +34 -0
  546. package/src/render/lighting.ts +121 -0
  547. package/src/render/lod-strip.ts +34 -0
  548. package/src/render/metrics.ts +147 -0
  549. package/src/render/options.ts +81 -0
  550. package/src/render/photo-match.ts +28 -0
  551. package/src/render/raster.ts +143 -0
  552. package/src/render/resolve.ts +109 -0
  553. package/src/render/review-prompt.ts +59 -0
  554. package/src/render/review-warnings.ts +92 -0
  555. package/src/render/review.ts +103 -0
  556. package/src/render/scene.ts +119 -0
  557. package/src/render/shade.ts +204 -0
  558. package/src/render/sheet.ts +88 -0
  559. package/src/render/texture.ts +97 -0
  560. package/src/render/thumbnail.ts +75 -0
  561. package/src/render/vis-buffer.ts +119 -0
  562. package/src/testing/assert.ts +115 -0
  563. package/src/testing/compiled.ts +31 -0
  564. package/src/testing/hash.ts +14 -0
  565. package/src/testing/index.ts +14 -0
  566. package/src/testing/snapshot.ts +37 -0
  567. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,21 @@
1
+ # 004 — Author in metres, Z up, +X front
2
+
3
+ Status: accepted; export orientation superseded by 009
4
+ Date: 2026-10-25
5
+ Supersedes: —
6
+
7
+ ## Context
8
+ Recipes are written from reference data: drawings, spec sheets, Wikipedia infoboxes. Those are in metres (or convertible), in plan + elevation. glTF is Y-up; engines differ (Unity Y-up left-handed, Unreal Z-up, Godot Y-up, Blender Z-up). Every asset also needs one agreed "front" so a scene can place and orient it.
9
+
10
+ ## Decision
11
+ Author in metres, right-handed, **Z up, +X front**, +Y left, ground at z = 0. The GLB writer applies one −90° X rotation on the root node to meet glTF's Y-up; part nodes keep their Z-up numbers.
12
+
13
+ ## Alternatives considered
14
+ - **Y-up, matching glTF** — why not: plan coordinates become (x, z) and height becomes y, so every architectural and vehicle dimension is transposed while writing. More agent mistakes.
15
+ - **Centimetres or engine units** — why not: reference data is in metres; a factor of 100 is a silent class of bugs.
16
+ - **−Y or +Y front (Blender convention)** — why not: +X front keeps "length along X" for vehicles and ships, matching how specs list L × W × H.
17
+
18
+ ## Consequences
19
+ - Easier: dimensions drop straight in from references; `SIDE -Y` in the contact sheet shows the profile with the front on the right.
20
+ - Harder: one root rotation to remember when reading raw glTF nodes. `extras.up` records it.
21
+ - Revisit when: never, short of a 1.0 → 2.0 break. It's in every recipe.
@@ -0,0 +1,21 @@
1
+ # 005 — Zero runtime dependencies
2
+
3
+ Status: accepted
4
+ Date: 2026-10-25
5
+ Supersedes: —
6
+
7
+ ## Context
8
+ Recipes run in the CLI, in tests and in the browser gallery. Output must be byte-deterministic. Each dependency adds supply-chain risk, version drift that can change output bytes, and install weight. The things we need (a glTF writer, a PNG encoder, a rasteriser, a PRNG) are each a few hundred lines.
9
+
10
+ ## Decision
11
+ `dependencies` is empty. The library writes GLB, PNG (via `node:zlib`, present in Bun and Node) and rasterises on its own. Dev tooling (Biome, TypeScript, lefthook) and the gallery app's `three` + `vite` are dev or workspace-private dependencies. Optional integrations (neural adapters, engine adapters) talk over HTTP or are separate entry points, never required imports.
12
+
13
+ ## Alternatives considered
14
+ - **`@gltf-transform/core` for export** — why not: excellent, but its output bytes move with its versions; we need to own byte determinism.
15
+ - **`sharp` / `canvas` for PNGs** — why not: native binaries, platform-specific installs, heavier than the problem.
16
+ - **`three` for geometry** — why not: pulls a renderer into a compiler, and its geometry classes aren't the IR we want.
17
+
18
+ ## Consequences
19
+ - Easier: `bun add` installs one package; no native builds; nothing upstream changes our bytes.
20
+ - Harder: we maintain a GLB writer, PNG encoder and rasteriser. Each is small and tested.
21
+ - Revisit when: a needed format (Draco, KTX2, meshopt) costs more to own than to depend on. Then add it behind an optional subpath.
@@ -0,0 +1,20 @@
1
+ # 006 — One npm package with subpath exports
2
+
3
+ Status: accepted
4
+ Date: 2026-10-25
5
+ Supersedes: —
6
+
7
+ ## Context
8
+ The library has ten modules (core, grammar, material, primitives, neural, kinds, qa, export, render, cli). Publishing them as separate packages means versioning them in lockstep, one npm OIDC trusted publisher per package, and a bootstrap publish for every new package (a package added after a release run stays unpublished until someone does it by hand). The sibling `ultimate` repo pays that tax across 31 packages.
9
+
10
+ ## Decision
11
+ Publish one package, `@developerz.ai/ts23d`. Modules are subpaths: `@developerz.ai/ts23d/core`, `/grammar`, `/render`, and so on, via an `exports` wildcard. The root re-exports everything. Module boundaries are enforced inside the repo by `scripts/boundaries.ts`, not by package boundaries.
12
+
13
+ ## Alternatives considered
14
+ - **A package per module (`@ts23d/core`, …)** — why not: N trusted publishers, N bootstraps, lockstep versioning machinery, for no user benefit at this size.
15
+ - **One package, root export only** — why not: no way to import one layer (e.g. `/render` in a tool) without the rest in view; worse tree-shaking story.
16
+
17
+ ## Consequences
18
+ - Easier: one version, one tag, one trusted publisher, one bootstrap ([PUBLISHING.md](../../PUBLISHING.md)).
19
+ - Harder: boundaries are a repo lint, not a package graph; a consumer can import any subpath.
20
+ - Revisit when: a module needs its own release cadence or its own heavy optional dependency (e.g. a neural adapter with a native runtime).
@@ -0,0 +1,21 @@
1
+ # 007 — Agent feedback is a CPU-rendered contact sheet
2
+
3
+ Status: accepted
4
+ Date: 2026-10-25
5
+ Supersedes: —
6
+
7
+ ## Context
8
+ The agent loop needs to see the asset. Options are many images (one per view) or one. Each image read costs context and a turn. Proportion errors show only from some angles (height in elevation, footprint in plan). The loop must also run in CI and on headless VPSes with no GPU and no display.
9
+
10
+ ## Decision
11
+ `t3d shot` renders **one** PNG: six labelled orthographic views (3/4 front, front, side, top, 3/4 rear, LOD2 far) on a shared metre grid, with a header giving bounding-box dimensions, grid step and LOD. Rendering is a z-buffered CPU rasteriser with flat Lambert shading over vertex colour: no GPU, no WebGL, no native dependency.
12
+
13
+ ## Alternatives considered
14
+ - **Headless GPU (Puppeteer + three.js, or EGL)** — why not: a browser or GPU driver in CI, non-deterministic pixels across drivers, slow cold start.
15
+ - **One image per view** — why not: six reads instead of one; the agent compares across turns instead of within one image.
16
+ - **Perspective beauty renders** — why not: perspective hides proportion errors; a metre grid under an orthographic view makes them measurable.
17
+
18
+ ## Consequences
19
+ - Easier: one read shows every side; runs anywhere Bun runs; deterministic pixels.
20
+ - Harder: no textures, shadows or AO in the sheet; it judges form, not surface. The gallery shows the real material.
21
+ - Revisit when: surface quality (texture painter, M2) needs judging in the loop. Then add a textured view to the sheet, still on the CPU.
@@ -0,0 +1,21 @@
1
+ # 008 — One trim sheet, up to four surface materials
2
+
3
+ Status: accepted
4
+ Date: 2026-10-25
5
+ Supersedes: 003
6
+
7
+ ## Context
8
+ ADR 003 put every asset on exactly one material. Real assets need glass (windows, lenses), cutouts (leaves, grilles, fences) and translucent parts (bottles, liquids). One opaque material can't shade those, and per-asset materials would bring back the draw-call and texture cost 003 removed.
9
+
10
+ ## Decision
11
+ Keep ONE shared texture set (the trim sheet) and vertex-colour tint. A part picks `surface: 'opaque' | 'cutout' | 'glass' | 'translucent'`; each GLB carries at most four materials, one per surface in use, differing only in shading model. Glass and translucent use `KHR_materials_transmission`, `_volume` and `_ior`, with an alpha-blend fallback. Batching splits per pivot and surface (`<pivot>_<surface>`).
12
+
13
+ ## Alternatives considered
14
+ - **Opaque only (keep 003 as is)** — why not: no windows, foliage or bottles that read correctly.
15
+ - **Per-part materials** — why not: draw calls scale with parts again.
16
+ - **Fake glass via vertex alpha on the opaque material** — why not: engines sort and shade it wrongly; no refraction.
17
+
18
+ ## Consequences
19
+ - Easier: windows, foliage and bottles in the same pipeline; a shampoo bottle is 2 draw calls.
20
+ - Harder: transmission isn't supported everywhere; the fallback looks flatter.
21
+ - Revisit when: an engine target needs a fifth shading model (e.g. emissive-only, subsurface).
@@ -0,0 +1,21 @@
1
+ # 009 — Exported GLBs face glTF +Z
2
+
3
+ Status: accepted
4
+ Date: 2026-10-25
5
+ Supersedes: 004 (export orientation only; authoring stays metres, Z up, +X front)
6
+
7
+ ## Context
8
+ ADR 004 exported with one −90° X rotation on the root (`[-√½, 0, 0, √½]`). That makes glTF Y-up, but maps authoring +X front to glTF **+X**, which the glTF spec defines as the asset's *left* ("the front side of a glTF asset faces +Z, the left side faces +X", [spec §coordinate-system-and-units](https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/Specification.adoc#coordinate-system-and-units)). Every asset faced sideways in engines that honour model-front. Found by [glb-production.md](../architecture/glb-production.md) (M10, G1).
9
+
10
+ ## Decision
11
+ The root node's rotation is `[-0.5, -0.5, -0.5, 0.5]`. It maps authoring +X (front) → glTF +Z, +Z (up) → +Y, +Y (left) → +X, verified numerically in the exporter tests. Authoring conventions from ADR 004 are unchanged; part nodes keep their Z-up numbers under the root.
12
+
13
+ ## Alternatives considered
14
+ - **Keep `[-√½, 0, 0, √½]`** — why not: violates the spec's front axis; assets face left in every engine that uses model-front.
15
+ - **Author in +Z front (or Y-up, +Z front) so no root rotation is needed** — why not: breaks every recipe's readable numbers. Length along X, height along Z is how specs and drawings read; transposing them invites the unit and axis mistakes ADR 004 exists to prevent.
16
+ - **Bake the rotation into every vertex** — why not: part pivots and node translations would no longer match the recipe's numbers, and engines bind to those nodes.
17
+
18
+ ## Consequences
19
+ - Easier: assets face forward in Godot, Unity (glTFast), three.js, Babylon and Unreal without per-asset fixes.
20
+ - Harder: **breaking** for anyone who placed earlier GLBs by hand: rotate them −90° about Y. Raw node data is still Z-up below the root.
21
+ - Revisit when: never, short of a glTF spec change.
@@ -0,0 +1,15 @@
1
+ # Decisions
2
+
3
+ **One record per one-way door, with the rejected options written down.** Read the matching record before proposing to reopen a decision; reopening requires naming what changed since its "why not". Format: context / decision / alternatives / consequences. Supersede, never edit history.
4
+
5
+ | # | Decision | Status | Date |
6
+ |---|---|---|---|
7
+ | [001](001-recipe-compiler-over-ai-meshes.md) | Assets are compiled from recipes, not generated by AI mesh models | accepted | 2026-10-25 |
8
+ | [002](002-typescript-not-json.md) | Recipes are TypeScript modules, not JSON | accepted | 2026-10-25 |
9
+ | [003](003-shared-trim-sheet.md) | Every asset shares one trim-sheet material | superseded by [008](008-surface-materials.md) | 2026-10-25 |
10
+ | [004](004-z-up-metres.md) | Author in metres, Z up, +X front | accepted; export orientation superseded by [009](009-gltf-front-axis.md) | 2026-10-25 |
11
+ | [005](005-zero-runtime-deps.md) | Zero runtime dependencies | accepted | 2026-10-25 |
12
+ | [006](006-single-package-subpath-exports.md) | One npm package with subpath exports | accepted | 2026-10-25 |
13
+ | [007](007-cpu-contact-sheets.md) | Agent feedback is a CPU-rendered contact sheet | accepted | 2026-10-25 |
14
+ | [008](008-surface-materials.md) | One trim sheet, up to four surface materials | accepted | 2026-10-25 |
15
+ | [009](009-gltf-front-axis.md) | Exported GLBs face glTF +Z (root `[-0.5,-0.5,-0.5,0.5]`) | accepted | 2026-10-25 |
@@ -0,0 +1,117 @@
1
+ # 01 — Vision
2
+
3
+ **Free 3D asset generator for coding agents. The agent writes TypeScript; ts23d compiles it, deterministically, to game-ready glTF for Unity, Unreal, Godot, three.js, Babylon or anything that reads glTF.** Use the LLM you already pay for to write the assets your project needs (vehicles, buildings, props, whole streets), optimised by construction, with a contact sheet so the agent checks its own work.
4
+
5
+ ## Scope: ts23d ends at the GLB
6
+
7
+ | In scope | Out of scope |
8
+ |---|---|
9
+ | Recipes → GLB × 3 LODs, the trim-sheet textures, `manifest.json`, contact sheets | Engines, game frameworks, runtime code |
10
+ | Hooks an engine can use: named pivots, sockets, emitter extras, the `run` clip | Driving those hooks: that's your engine code |
11
+ | Engine *import adapters* as data (import settings, collision naming): planned, M3 | Engine plugins or runtimes |
12
+ | An asset gallery that shows every recipe ([site](https://developerz-ai.github.io/ts23d/)) | A playable demo or game |
13
+
14
+ ## From prompt to asset
15
+
16
+ | Step | You do | ts23d gives you |
17
+ |---|---|---|
18
+ | 1. Install | `bun add @developerz.ai/ts23d`; copy `node_modules/@developerz.ai/ts23d/.claude/skills/author-recipe` into `.claude/skills/` (Claude Code), or point your `AGENTS.md` at `node_modules/@developerz.ai/ts23d/AGENTS.md` (Codex) | The agent knows the loop |
19
+ | 2. Ask | `/new-recipe Tiger I, mid-production 1943, fully stowed` · `/new-recipe 4-4-0 steam loco with tender` · `/new-recipe 1940s city street with townhouses and gas lamps` (townhouse, lamp, street recipes: landing) | A recipe that passes QA, three LODs, textures, a manifest, a contact sheet |
20
+ | 3. Import | Load `out/<asset>/lod0.glb` in your engine | Named nodes (`turret`, `gun`, `wheel_*`), sockets, emitter extras, the `run` clip as hooks ([README](../../README.md#3-use-the-glb-in-your-engine)) |
21
+
22
+ ## The pitch
23
+
24
+ | You bring | ts23d gives you |
25
+ |---|---|
26
+ | A coding agent (Claude Code, Codex, any model that writes TS) | A DSL that agent already knows: TypeScript |
27
+ | A description, a reference photo, real dimensions | GLB × 3 LODs, named + pivoted parts, baked loop animations |
28
+ | A machine with Bun | No GPU, no credits, no account, no service |
29
+ | A target engine (yours) | GLBs it imports: one texture set, batched draw calls, LODs, compact core-glTF buffers |
30
+ | Git | Assets as diffable source; same seed → same bytes |
31
+
32
+ ## The promise, both halves
33
+
34
+ | Half | Means | How |
35
+ |---|---|---|
36
+ | **High definition up close** | Hero detail: rivets, rods, window sashes, real proportions | Code places detail N times for free; `real` dimensions + the `proportions` gate; painted trim sheet |
37
+ | **Cheap to draw** | Games pay per draw call, per vertex fetch, per byte | One shared material, static geometry batched into its pivot, so draw calls = the parts your engine moves (a static building: 1); 3 LODs from one source; u16 indices and colours today; welded, vertex-cache-ordered buffers via `optimize`, an opt-in web profile (quantisation + meshopt) and LOD switch hints planned (M2) |
38
+
39
+ ## No Blender for most hard-surface asset production
40
+
41
+ **The DSL does the modelling.** Scoped to asset production; the engine stays yours. For hard-surface assets (vehicles, buildings, props, whole scenes) the LLM writes TypeScript, and ts23d does the rest:
42
+
43
+ | Blender-era step | In ts23d |
44
+ |---|---|
45
+ | Modelling | `primitives` + `kinds`, real units, pivots where parts hinge |
46
+ | LODs | `ctx.lod`: the same recipe re-run at lower detail |
47
+ | UV unwrap | UVs land on the shared trim sheet by world metres, automatically |
48
+ | Batching / joining meshes | `toGlb` merges static geometry into its nearest pivot |
49
+ | Checking | QA gates + contact sheet |
50
+ | Export | `toGlb`, zero dependencies |
51
+
52
+ Blender stays useful for organic sculpting: people, animals, hero rocks. Those come in through the planned [`/neural` adapter or `importGlb`](08-neural-adapters.md).
53
+
54
+ ## One asset pipeline, no other libraries
55
+
56
+ **Asset production needs nothing else between "describe the asset" and "a GLB your engine imports".**
57
+
58
+ | Stage | Module | Status (2026-10) | Replaces |
59
+ |---|---|---|---|
60
+ | Geometry | `primitives`, `kinds` | Shipped: `box`, `lathe`, `prism`, `beam`, `planar`, `sweep`, `loft`, `heightfield`, facades, vehicles, Weber–Penn trees. Planned: `sdf`, roofs, hulls, creatures | Blender modelling |
61
+ | Materials and textures | `material` (generator) | Shipped: procedural albedo / normal / ORM trim sheet, embedded or shared PNGs, palettes, `grime`. No KTX2 yet | Substance / texture tools |
62
+ | Optimisation | `passes/optimize`, `export` | Shipped: u16 indices, u16 colours, batching. Planned, M2: weld, vertex-cache reorder, ACMR; opt-in `web` profile (quantisation + meshopt) | meshoptimizer / gltfpack |
63
+ | LODs | `ctx.lod` | Shipped | Decimators |
64
+ | QA | `qa` | Shipped: budget, surface-contact floating parts, motion clashes, names, proportions, spec measures. Silhouette IoU and ACMR planned | Manual review |
65
+ | Renders and photo match | `render` | Contact sheet shipped. Photo match planned, M2 | — |
66
+ | GLB export | `export` | Shipped: core glTF only, faces +Z, seamless loops, trim-sheet textures, 0 glTF-Validator errors (tested) | — |
67
+ | Engine import adapters | `threeMaterialSource` (TSL), `godotShaderSource` | Shipped: shader sources. Planned, M3: import settings and collision naming per engine, as hooks, not runtime | — |
68
+
69
+ Every stage has zero runtime dependencies ([ADR 005](../decisions/005-zero-runtime-deps.md)).
70
+
71
+ ## Origin
72
+
73
+ Inspired by an in-house pipeline that started on AI mesh generation (Meshy plus an image model) and moved to a JSON recipe compiler. Why it moved:
74
+
75
+ | Problem with generated meshes | What a recipe compiler does instead |
76
+ |---|---|
77
+ | Credit cost per attempt, per iteration | Free; iteration costs a rebuild |
78
+ | No reproducibility: same prompt, different mesh | Same source + seed → same bytes |
79
+ | One fused mesh, no named parts | `turret`, `gun`, `axle_0`: pivoted, animatable |
80
+ | A texture per asset → a draw call per asset | One shared trim sheet → one material |
81
+ | Every mesh needed repair (holes, scale, pivots, topology) | Built correct: real units, pivots set where parts hinge |
82
+
83
+ ts23d keeps that pipeline's ideas and goes one step further: recipes are **TypeScript, not JSON** ([ADR 002](../decisions/002-typescript-not-json.md)).
84
+
85
+ ## Ideas carried over
86
+
87
+ | Idea | Where it lives | Status (2026-10) |
88
+ |---|---|---|
89
+ | CGA split grammar for facades: `"pier:0.9 \| {arch:2.5}* \| door:3 \| pier:~"` | `grammar/split.ts`, `kinds/building.ts` | Shipped |
90
+ | Domain-native params: Whyte `4-4-0` for locomotives | `grammar/whyte.ts`, `kinds/vehicle.ts` | Shipped |
91
+ | Masses, then details | Recipe convention; `setbacks` + `facade` | Shipped |
92
+ | Real units (metres) | Everywhere | Shipped |
93
+ | Real dimensions with sources, held by QA | `defineAsset({ real, sources })`, `proportions` + `measures` gates | Shipped |
94
+ | Era/region palettes | `material/palette.ts` | Shipped (4 palettes) |
95
+ | LODs by re-running the recipe at a detail level | `compile(asset, { lod })` | Shipped |
96
+ | A QA gate that fails the build | `qa/gates.ts` | Shipped (7 gates incl. recipe `checks`); more planned |
97
+ | Closed-loop agent iteration with multi-view renders | `render/sheet.ts`, `t3d shot` | Shipped |
98
+ | Weber–Penn botanical skeletons | `kinds/vegetation.ts` → `tree`, `trees` | Shipped |
99
+ | Photo match: photo · model · overlay in one PNG, plus silhouette IoU | `render` → `photoMatch` | Planned, M2 |
100
+ | Reverse direction: a VLM infers recipe params from photos, scored by silhouette IoU | builds on `photoMatch` | Planned, M6 |
101
+
102
+ ## Principles
103
+
104
+ - **Code is the asset.** The GLB is a build artefact. Recipes are committed; `out/` is not.
105
+ - **Deterministic.** No ambient entropy, no wall clock. Byte-identical output for the same source + seed.
106
+ - **Errors are instructions.** Every failure carries a `T3D_*` code, the cause and the fix ([errors](../architecture/errors.md)).
107
+ - **The agent is the user.** Contact sheets, `--json`, and fix lines exist for an agent reading them, not for a human at a GUI.
108
+ - **Zero runtime dependencies** ([ADR 005](../decisions/005-zero-runtime-deps.md)).
109
+ - **Honest about fit.** Strong on hard-surface; weak on living things ([what fits](07-what-fits.md)).
110
+
111
+ ## Non-goals
112
+
113
+ - Beating image-to-3D models at organic, sculpted forms. Use them, or the planned [neural adapter](08-neural-adapters.md).
114
+ - Engines, game frameworks, gameplay or demo code. ts23d ends at the GLB, textures and manifest.
115
+ - A modelling GUI. The editor is your agent; the viewer is the [gallery](https://developerz-ai.github.io/ts23d/).
116
+ - Photoreal hero assets. Target: game-ready, stylised-to-realistic props at consistent texel density.
117
+ - A hosted service. Everything runs on your machine.
@@ -0,0 +1,343 @@
1
+ # 02 — The DSL
2
+
3
+ **An asset is a named export: `defineAsset({ …, build(ctx) })`.** `build` is a plain TypeScript function. It adds named parts to `ctx`; the compiler runs it once per LOD.
4
+
5
+ ## Layers
6
+
7
+ **Every layer is plain TypeScript. You extend the DSL by writing functions; there is no plugin API to learn.**
8
+
9
+ | Layer | Shape | Example |
10
+ |---|---|---|
11
+ | Primitive | `(options) => Mesh` | `box`, `lathe`, `prism`, `beam`, `planar`, `sweep`, `loft`, `heightfield` |
12
+ | Component | `(ctx, params) => void`: adds named parts | your `bogie(ctx, …)`, `chimney(ctx, …)` |
13
+ | Kind | A library of components | `facade`, `setbacks`, `axle`, `trackRun` |
14
+ | Family | `defineFamily({ defaults, build(ctx, p) })` | `t34` with `turret: '1941' \| '1943'` |
15
+ | Asset | `defineAsset(…)` or `family.variant(…)` | `t34_1943` |
16
+ | Scene | An asset that calls `ctx.use(other, …)` | a street, a rail yard |
17
+
18
+ ## Primitives (shipped)
19
+
20
+ | Primitive | Key options | For |
21
+ |---|---|---|
22
+ | `box` | `size`, `at`, `strip`, `omit`, `bevel?` | Hulls, walls, crates; `bevel` = the mid-poly chamfer |
23
+ | `lathe` / `cylinder` | `profile` or `radius`+`length`, `segments`, `axis`, `creaseDeg?`, `uv?: 'metres'`, `faceted?` | Wheels, boilers, domes, n-sided tanks |
24
+ | `prism` | `profile`, `depth`, `plane: 'xy'\|'xz'\|'yz'` | Sloped armour, gables, fins |
25
+ | `beam` | `from`, `to`, `width`, `segments?`, `up?`, `caps?` | Girders, rods, rails |
26
+ | `planar` | `points`, `doubleSided?` | Plates, decks, signs |
27
+ | `sweep` | `section` (polygon or `{ radius, segments }`), `path`, `scale?` (taper), `twist?`, `caps?` | Branches, pipes, handrails, cables. Parallel-transport frames; triangles = 2·S·(P−1) + S per cap |
28
+ | `loft` | `rings` (Vec3 rings), `capStart?`, `capEnd?`, `uv?` | Trunks, fuselages, bottles. `ringXY(centre, rx, ry, …)` builds a ring, with wobble |
29
+ | `heightfield` | `rect`, `cells`, `height(x, y)`, `color`, `skirt?` | Terrain, dioramas; with `terrainGrid` + `hydraulicErosion` / `thermalErosion` + `terrainColor` for real-looking mountains |
30
+ | `tiled` | `origin`, `u`, `v`, `strip`, `segments?` | Walls, roofs, decks at true texture scale |
31
+ | `cell` | `origin`, `u`, `v`, `cell`, `inset?` | Windows, doors, clocks, signs; `inset` adds reveals |
32
+ | `lattice` | `from`, `to`, `depth`, `pattern: warren\|pratt\|howe\|x`, `bays`, `chord`, `web`, `section` | Bridges, towers, cranes, gantries |
33
+ | `text` | `str`, `origin`, `along`, `up`, `height`, `font`, `depth?`, `align?`, `maxWidth?`, `surface?` | Name boards, wagon lettering, hull markings (5×7 font, Latin + Cyrillic) |
34
+ | `sdf` | `field(p)`, `bounds`, `resolution`, `mesher?`, `symmetry?` | Any signed-distance shape; `sd.*` combinators, `valueNoise` |
35
+ | `sculpt` | `edits` (shape, op, blend, color, groups), `resolution`, `mirror?` | Busts, rocks, asteroids: Dreams-style edit lists |
36
+
37
+ Noise for any primitive: `gradientNoise2`, `valueNoise2`, `fbm`, `billow`, `ridged`, `warp`, `terrace` — all seeded from an `Rng`.
38
+
39
+ ## Why TypeScript, not JSON
40
+
41
+ | Need | JSON recipe | TS recipe |
42
+ |---|---|---|
43
+ | 12 windows per floor, 9 floors | Spell out 108 entries, or invent a repeat syntax | `for` loop |
44
+ | Reuse a wheel across 8 vehicles | Invent `$ref` / templating | Import a function |
45
+ | Vary 10 houses on a street | Invent a variant system | `ctx.use(house, { seed })` |
46
+ | Catch `colr: …` before build | Schema validator, if anyone wrote one | `tsc` red squiggle |
47
+ | Agent fluency | Must learn your schema | Already writes TS all day |
48
+
49
+ Full reasoning and rejected options: [ADR 002](../decisions/002-typescript-not-json.md).
50
+
51
+ ## Anatomy
52
+
53
+ ```ts
54
+ import { box, cylinder, defineAsset, hex } from '@developerz.ai/ts23d';
55
+
56
+ export const lamp_post = defineAsset({
57
+ name: 'lamp_post', // snake_case; becomes the GLB name and root node
58
+ kind: 'prop', // free string; kinds are libraries, not a closed enum
59
+ description: 'cast-iron street lamp',
60
+ sources: ['https://example.org/lamp-drawing.pdf'],
61
+ real: { length: 0.4, width: 0.4, height: 3.5 }, // proportions gate: LOD0 within ±5%
62
+ tags: ['street', 'victorian'],
63
+ budget: { triangles: [800, 300, 60] }, // per LOD; QA fails above
64
+ seed: 1, // default 1
65
+ build(ctx) {
66
+ const iron = hex('#2b2b2e');
67
+ ctx.part('base', box({ size: [0.4, 0.4, 0.3], color: iron, strip: 'iron', omit: ['bottom'] }));
68
+ ctx.part('pole', cylinder({ radius: 0.06, length: 3.2, segments: [12, 8, 6][ctx.lod] ?? 6,
69
+ at: [0, 0, 1.9], color: iron }));
70
+ },
71
+ });
72
+ ```
73
+
74
+ One module may export several assets; `t3d` builds every one. No default exports.
75
+
76
+ ## `defineAsset` fields
77
+
78
+ | Field | Type | Required | Notes |
79
+ |---|---|---|---|
80
+ | `name` | `string` | yes | `[a-z][a-z0-9_]*`, else `T3D_RECIPE_INVALID` |
81
+ | `kind` | `string` | yes | Written to glTF `extras.kind` |
82
+ | `budget.triangles` | `[lod0, lod1, lod2]` | yes | Enforced by the `triangle-budget` gate |
83
+ | `build` | `(ctx) => void` | yes | Synchronous |
84
+ | `real` | `{ length, width, height, tolerance?, measures? }` | no | Real overall dimensions, metres (X, Y, Z). The `proportions` gate holds LOD0 to them, default ±5% |
85
+ | `real.measures` | `Measure[]` | no | Spec-sheet dimensions checked on LOD0 by the `measures` gate, default ±2%. See below |
86
+ | `sources` | `string[]` | with `real` | Where the numbers came from: spec sheets, rosters, drawings, photos. The type says "required for `real`"; as of 2026-10 nothing enforces it |
87
+ | `checks` | `Check[]` | no | The recipe's own physical intent, run by QA at every LOD ([below](#checks-declared-intent)) |
88
+ | `description`, `tags` | `string`, `string[]` | no | Gallery / search metadata |
89
+ | `seed` | `number` | no | Default `1`; overridden by `compile(…, { seed })` or `ctx.use(…, { seed })` |
90
+
91
+ ## The `ctx` API
92
+
93
+ | Member | Does |
94
+ |---|---|
95
+ | `ctx.lod` | `0` hero · `1` mid · `2` far. Read it and drop detail; never decimate |
96
+ | `ctx.seed` | The seed this compile runs with |
97
+ | `ctx.rng` | Seeded PRNG: `next()`, `range(a,b)`, `int(a,b)`, `pick(xs)`, `fork(name)` |
98
+ | `ctx.part(name, mesh, opts?)` | Add a named part. `mesh` may be `null` (empty pivot) |
99
+ | `ctx.group(name, opts?)` | An empty pivot other parts parent to (`part(name, null, opts)`). Pivots and parts with `motion` survive export as nodes; static parts merge into them ([batching](03-ir.md#batching-parts--pivots)) |
100
+ | `ctx.use(asset, opts)` | Instance another asset at this LOD: the composition primitive |
101
+ | `ctx.emitter(name, opts)` | A particle source as a named node ([below](#emitters)) |
102
+ | `ctx.socket(name, opts)` | An attachment point as a named node ([below](#sockets)) |
103
+
104
+ `PartOptions`:
105
+
106
+ | Option | Meaning |
107
+ |---|---|
108
+ | `at` | Pivot in the parent's space. The mesh is authored relative to it |
109
+ | `parent` | Name of an existing part/group. Parents first, else `T3D_PART_UNKNOWN` |
110
+ | `yaw` | Rotation about local Z, radians |
111
+ | `motion` | `{ kind: 'spin', axis, rps }` · `{ kind: 'slide', offset, hz }` · `{ kind: 'sway', axis, degrees, hz }` (turret scan, gun elevation, tree wind, a door). Baked to a 2 s `run` clip only when exporting with animations ([03](03-ir.md#glb-layout)) |
112
+ | `surface` | `'opaque'` (default) · `'cutout'` · `'glass'` · `'translucent'` ([below](#surfaces)) |
113
+ | `clearOf` | Part names this moving part may pass through by design (an axle in its bearing, a crosshead in its guide). Exempts them from the `clashes` gate |
114
+
115
+ Rules the compiler enforces:
116
+
117
+ - Part names are unique per asset (`T3D_PART_DUPLICATE`). Suffix loop indices: `wheel_0`, `wheel_1`.
118
+ - A part can't have both `yaw` and `motion` (`T3D_RECIPE_INVALID`). Yaw the parent group, move the child.
119
+ - Names are snake_case (QA gate `part-names`). Names are the contract with the engine: `axle_*`, `door_*`, `rotor_*`.
120
+
121
+ ## Fidelity: `real.measures`
122
+
123
+ **Overall size is not enough; players notice the driver diameter.** Each `Measure` names one number from a source and how to measure it on the model:
124
+
125
+ | Field | Meaning |
126
+ |---|---|
127
+ | `label` | Human name: `'driver diameter'`, `'gauge'` |
128
+ | `value` | Metres, from the source |
129
+ | `axis` | `'x'` · `'y'` · `'z'` |
130
+ | `parts` | Extent of these parts' union along `axis`. Prefix match: `'axle_driver_0'` includes `axle_driver_0_wheel_l` |
131
+ | `between` | Or: centre-to-centre distance of exactly two parts along `axis` (gauge, wheelbase) |
132
+ | `tolerance` | Relative, default 0.02 |
133
+ | `source` | Which of `sources` the number came from |
134
+
135
+ ```ts
136
+ real: {
137
+ length: 6.68, width: 3.0, height: 2.45,
138
+ measures: [
139
+ { label: 'turret width', value: 2.0, axis: 'y', parts: ['turret_body'], source: 'hull drawing' },
140
+ { label: 'road wheel diameter', value: 0.83, axis: 'z', parts: ['road_wheel_l_0'] },
141
+ ],
142
+ },
143
+ ```
144
+
145
+ A failing measure means the model is wrong. **Rebuild the part from the number; never edit the spec to pass.** Gate details: [06-qa](06-qa.md).
146
+
147
+ ## Detail: build once, place N times
148
+
149
+ `place(mesh, { at, yaw, pitch, roll, scale })` rigidly transforms a mesh; `merge` joins meshes into one part. Author a rivet, hatch or bracket once in local space, then place it along a seam:
150
+
151
+ ```ts
152
+ import { cylinder, merge, place } from '@developerz.ai/ts23d';
153
+
154
+ const rivet = cylinder({ radius: 0.012, length: 0.01, segments: 6, axis: 'x', color: iron });
155
+ const rivets = merge(Array.from({ length: 24 }, (_, i) => place(rivet, { at: [3.0, -1.2 + i * 0.1, 1.1] })));
156
+ if (ctx.lod === 0) ctx.part('glacis_rivets', rivets);
157
+ ```
158
+
159
+ Lower level: `affine(placement)`, `frame(u, v, w, at)` (an explicit basis), `applyAffine(mesh, affine)`.
160
+
161
+ ## Families: one build, many assets
162
+
163
+ **A variant is a name plus the parameters that differ.** T-34 1941 vs 1943, a 4-4-0 vs a 2-6-0, oak vs birch: one key change, not a copied file.
164
+
165
+ ```ts
166
+ import { defineFamily } from '@developerz.ai/ts23d';
167
+
168
+ const t34 = defineFamily({
169
+ kind: 'vehicle',
170
+ budget: { triangles: [40000, 10000, 1500] },
171
+ defaults: { turret: '1941' as '1941' | '1943', hullLength: 6.68 },
172
+ build(ctx, p) { /* hull from p.hullLength; turret shape from p.turret */ },
173
+ });
174
+
175
+ export const t34_1941 = t34.variant({ name: 't34_1941' });
176
+ export const t34_1943 = t34.variant({ name: 't34_1943', params: { turret: '1943' } });
177
+
178
+ // Derive: add params, wrap or replace the parent's detail.
179
+ const t34_85 = t34.extend({
180
+ defaults: { gun: 85 },
181
+ build(ctx, p, base) { base(); /* swap the turret, add the 85 mm gun */ },
182
+ });
183
+ export const t34_85_1944 = t34_85.variant({ name: 't34_85_1944', tags: ['ww2'] });
184
+ ```
185
+
186
+ | API | Does |
187
+ |---|---|
188
+ | `defineFamily({ defaults, build(ctx, params), ...assetFields })` | Typed defaults; every `defineAsset` field except `name` / `build` |
189
+ | `family.variant({ name, params?, ...overrides })` | One `Asset`: defaults merged with `params`; may override `real`, `budget`, `tags`, … |
190
+ | `family.extend({ defaults, build?(ctx, p, base) })` | A new family with more params. Call `base()` to run the parent's build, or don't, to replace it |
191
+ | `family.defaults` | Read-only defaults, for docs and UIs |
192
+
193
+ ## Emitters
194
+
195
+ `ctx.emitter(name, opts)` adds an empty named node carrying `extras.ts23d.emitter`. It moves with its parent; the engine binds its own particle system to it. The gallery renders them.
196
+
197
+ | Option | Meaning |
198
+ |---|---|
199
+ | `kind` | `'smoke'` · `'steam'` · `'dust'` · `'muzzle'` · `'fire'` · `'sparks'` |
200
+ | `at`, `parent` | Position in the parent's space; parent it to a turret or stack so it follows |
201
+ | `dir` | Emission direction, default up (+Z) |
202
+ | `rate`, `life` | Particles per second; seconds each lives |
203
+ | `size` | `[start, end]` metres |
204
+ | `color` | Linear RGB start colour; engines fade to transparent |
205
+ | `speed`, `spreadDeg` | Initial speed along `dir`; cone spread |
206
+ | `when` | `'always'` (chimney) · `'moving'` (track dust) · `'event'` (muzzle flash, fired by engine code) |
207
+
208
+ ```ts
209
+ ctx.emitter('exhaust_l', { kind: 'smoke', at: [-3.2, 0.6, 1.2], dir: [-1, 0, 0.5],
210
+ rate: 20, life: 2, size: [0.2, 1.2], when: 'moving' });
211
+ ctx.emitter('muzzle', { kind: 'muzzle', parent: 'gun', at: [2.6, 0, 0], dir: [1, 0, 0],
212
+ rate: 200, life: 0.1, size: [0.3, 0.8], when: 'event' });
213
+ ```
214
+
215
+ ## Sockets
216
+
217
+ `ctx.socket(name, { kind, at?, parent?, dir?, data? })`: an empty named node with `extras.ts23d.socket`, for engine code to attach to.
218
+
219
+ | `kind` | Example |
220
+ |---|---|
221
+ | `seat` | Driver, gunner, passenger |
222
+ | `mount` | Weapon hardpoint, `data: { caliber: 88 }` |
223
+ | `camera` | Gunner sight, chase cam, `data: { fov: 60 }` |
224
+ | `audio` | Engine, `data: { clip: 'engine_idle' }` |
225
+ | `attach` · `spawn` · `interact` · `light` | Tow hook, spawn point, door handle, headlamp |
226
+
227
+ `dir` is the facing in the parent's space (default +X). `data` is free-form `string | number | boolean` values.
228
+
229
+ ## Surfaces
230
+
231
+ **One texture set, at most four materials.** `surface` on a part picks the shading model; tint stays in vertex colour.
232
+
233
+ | Surface | For | glTF |
234
+ |---|---|---|
235
+ | `opaque` (default) | Almost everything | `ts23d_trim` |
236
+ | `cutout` | Leaves, fences, grilles | Alpha-tested, double-sided |
237
+ | `glass` | Windows, lenses, clear bottles | `KHR_materials_transmission` + `_volume` + `_ior` (1.5) |
238
+ | `translucent` | Plastic bottles, liquids, paper | Transmission, tinted; engines without it fall back to alpha blend |
239
+
240
+ Batching splits by pivot and surface, so a shampoo bottle (opaque cap, translucent body) is 2 draw calls.
241
+
242
+ ## Checks: declared intent
243
+
244
+ **Static intersections need declared intent.** Modellers sink bolts into plates on purpose, so QA can't fail every overlap. The recipe says what must hold, and the `check` gate enforces it at every LOD.
245
+
246
+ ```ts
247
+ import { clear, noOverlap, symmetric, touches } from '@developerz.ai/ts23d';
248
+
249
+ checks: [
250
+ clear('track_*', 'hull_*', 0.02), // the track never hits the frame; 2 cm running gap
251
+ noOverlap('road_wheel_l_*'), // interleaved wheels don't intersect
252
+ touches('turret_body', 'hull_top'), // the turret sits on its ring
253
+ symmetric('_l', '_r', 'y'), // left matches right across y = 0
254
+ ],
255
+ ```
256
+
257
+ | Builder | Fails when |
258
+ |---|---|
259
+ | `clear(a, b, minGap?)` | Anything in `a` sinks more than 1 cm into `b`'s closed solid (ray-parity point-in-solid; open planar sheets are never solids), or is closer than `minGap` |
260
+ | `noOverlap(glob)` | Any two parts matching `glob` interpenetrate |
261
+ | `touches(a, b, eps = 0.02)` | `a`'s surface is further than `eps` from `b`'s |
262
+ | `symmetric(left, right, axis = 'y')` | A part containing `left` has no `right` mirror, or isn't mirrored across `axis = 0` (2 cm) |
263
+
264
+ Selectors are globs (`*`, `?`) over part names. A custom check is any `(asset: CompiledAsset) => CheckFinding[]`, where `CheckFinding = { message, fix, part? }`.
265
+
266
+ ## Composition: `ctx.use`
267
+
268
+ A scene is an asset that uses other assets. Each instance is compiled at the parent's LOD, with its own seed, under a prefixed group:
269
+
270
+ ```ts
271
+ import { box, defineAsset, facade, palettes } from '@developerz.ai/ts23d';
272
+
273
+ const { limestone, glass } = palettes.artDeco;
274
+
275
+ export const shop_house = defineAsset({
276
+ name: 'shop_house',
277
+ kind: 'building',
278
+ budget: { triangles: [4000, 1500, 400] },
279
+ build(ctx) {
280
+ const floors = ctx.rng.int(2, 4); // varies per seed
281
+ ctx.part('mass', box({ size: [8, 10, floors * 3.2], color: limestone,
282
+ strip: 'stone', omit: ['bottom'] }));
283
+ if (ctx.lod < 2)
284
+ facade(ctx, { prefix: 'win', at: [0, 0, 0], footprint: [8, 10], floors, floorHeight: 3.2,
285
+ grammar: 'pier:0.6 | {win:1.4 | pier:0.6}* | pier:~',
286
+ ground: 'pier:0.6 | shop:4 | pier:~ | door:1.2 | pier:0.6',
287
+ sides: ['front'], glass });
288
+ },
289
+ });
290
+
291
+ export const street = defineAsset({
292
+ name: 'street',
293
+ kind: 'scene',
294
+ budget: { triangles: [40000, 15000, 4000] },
295
+ build(ctx) {
296
+ ctx.part('road', box({ size: [12, 60, 0.05], at: [12, 0, 0],
297
+ color: palettes.modernGlass.concrete, omit: ['bottom'] }));
298
+ for (let i = 0; i < 6; i++)
299
+ ctx.use(shop_house, { prefix: `house_${i}`, at: [0, -25 + i * 10, 0], seed: 100 + i });
300
+ },
301
+ });
302
+ ```
303
+
304
+ Result: six different houses (2–4 floors each) as `house_0` … `house_5`, with parts `house_3_mass`, `house_3_win_front_1_2` and so on. LOD2 drops every window on every house at once.
305
+
306
+ `UseOptions`: `prefix` (required), `at`, `yaw`, `parent`, `seed` (defaults to the parent's seed).
307
+
308
+ ## Determinism rules
309
+
310
+ **Same source + seed + LOD → same bytes.** Breaking any rule below breaks that.
311
+
312
+ | Rule | Why | Enforced by (in this repo) |
313
+ |---|---|---|
314
+ | Never `Math.random()`. Use `ctx.rng` or `createRng(seed)` | Ambient entropy | `scripts/determinism.ts` (static ban in `src/` + `recipes/`); test preload makes `Math.random` throw |
315
+ | No `Date.now()`, `new Date()`, `performance.now()` in library or recipe code | Wall clock | `scripts/determinism.ts` |
316
+ | No `toLocale*String()` | Locale changes bytes; format with `toFixed` | `scripts/determinism.ts` |
317
+ | `ctx.rng.fork('name')` per independent feature | Adding a draw in one part must not shift another | Convention |
318
+ | No env vars, file or network reads in `build` | Ambient state | Convention |
319
+ | `build` is synchronous | No ordering races | The type signature |
320
+ | Iterate arrays, not object key order you didn't create | Stable part order → stable node order → stable bytes | Convention |
321
+ | Generated meshes (neural, imported) go through a content-hash cache | A GPU result is not reproducible; the cached file is | Planned, M5 ([08](08-neural-adapters.md)) |
322
+
323
+ `scripts/recipes.ts` compiles every recipe twice per LOD and fails if the GLB hashes differ. Both run in `bun run verify`. In your own project, copy the rules; the guards ship with this repo, not the package. Cross-machine hash comparison: planned ([06-qa](06-qa.md)).
324
+
325
+ ## Library entry points
326
+
327
+ One import gives everything; subpaths load one layer ([ADR 006](../decisions/006-single-package-subpath-exports.md)):
328
+
329
+ ```ts
330
+ import { defineAsset, box } from '@developerz.ai/ts23d';
331
+ import { split } from '@developerz.ai/ts23d/grammar';
332
+ import { contactSheet } from '@developerz.ai/ts23d/render';
333
+ ```
334
+
335
+ Programmatic build, without the CLI:
336
+
337
+ ```ts
338
+ import { compile, contactSheet, runQa, toGlb } from '@developerz.ai/ts23d';
339
+ const lod0 = compile(lamp_post, { lod: 0 });
340
+ const findings = runQa(lod0); // [] = PASS
341
+ const glb = toGlb(lod0); // Uint8Array
342
+ const png = contactSheet(lod0, { far: compile(lamp_post, { lod: 2 }) });
343
+ ```