@developerz.ai/ts23d 0.0.0-stage → 0.1.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 (524) 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 +79 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +380 -2
  16. package/dist/chunk-11v28977.js +1575 -0
  17. package/dist/chunk-11v28977.js.map +30 -0
  18. package/dist/chunk-1ypfs7ga.js +3571 -0
  19. package/dist/chunk-1ypfs7ga.js.map +42 -0
  20. package/dist/chunk-4ddzsqtc.js +464 -0
  21. package/dist/chunk-4ddzsqtc.js.map +19 -0
  22. package/dist/chunk-a8fxakcj.js +19 -0
  23. package/dist/chunk-a8fxakcj.js.map +11 -0
  24. package/dist/chunk-gcyp9b4d.js +755 -0
  25. package/dist/chunk-gcyp9b4d.js.map +22 -0
  26. package/dist/chunk-kgvbpqqw.js +44 -0
  27. package/dist/chunk-kgvbpqqw.js.map +10 -0
  28. package/dist/chunk-kvcep7ft.js +1919 -0
  29. package/dist/chunk-kvcep7ft.js.map +43 -0
  30. package/dist/chunk-ravbb06q.js +89 -0
  31. package/dist/chunk-ravbb06q.js.map +11 -0
  32. package/dist/chunk-w1zyhj40.js +1031 -0
  33. package/dist/chunk-w1zyhj40.js.map +21 -0
  34. package/dist/chunk-wtqhq529.js +2532 -0
  35. package/dist/chunk-wtqhq529.js.map +43 -0
  36. package/dist/chunk-xnt2sdhf.js +836 -0
  37. package/dist/chunk-xnt2sdhf.js.map +17 -0
  38. package/dist/cli/args.d.ts +24 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +307 -0
  41. package/dist/cli/bin.js.map +14 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +49 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/core/asset.d.ts +10 -0
  47. package/dist/core/definition.d.ts +148 -0
  48. package/dist/core/errors.d.ts +17 -0
  49. package/dist/core/family.d.ts +23 -0
  50. package/dist/core/index.d.ts +9 -0
  51. package/dist/core/index.js +69 -0
  52. package/dist/core/index.js.map +9 -0
  53. package/dist/core/mesh.d.ts +35 -0
  54. package/dist/core/part.d.ts +114 -0
  55. package/dist/core/png.d.ts +1 -0
  56. package/dist/core/rng.d.ts +12 -0
  57. package/dist/core/transform.d.ts +20 -0
  58. package/dist/core/vec.d.ts +11 -0
  59. package/dist/core/world.d.ts +16 -0
  60. package/dist/export/bin-writer.d.ts +16 -0
  61. package/dist/export/container.d.ts +3 -0
  62. package/dist/export/glb.d.ts +38 -0
  63. package/dist/export/index.d.ts +4 -0
  64. package/dist/export/index.js +26 -0
  65. package/dist/export/index.js.map +9 -0
  66. package/dist/export/lights.d.ts +8 -0
  67. package/dist/export/materials.d.ts +23 -0
  68. package/dist/export/merge-pivots.d.ts +6 -0
  69. package/dist/export/metadata.d.ts +19 -0
  70. package/dist/export/motion.d.ts +9 -0
  71. package/dist/export/optimize-parts.d.ts +26 -0
  72. package/dist/export/primitive.d.ts +3 -0
  73. package/dist/export/tangents.d.ts +2 -0
  74. package/dist/export/textures.d.ts +22 -0
  75. package/dist/export/version.d.ts +2 -0
  76. package/dist/grammar/index.d.ts +2 -0
  77. package/dist/grammar/index.js +16 -0
  78. package/dist/grammar/index.js.map +9 -0
  79. package/dist/grammar/split.d.ts +21 -0
  80. package/dist/grammar/whyte.d.ts +5 -0
  81. package/dist/index.d.ts +11 -0
  82. package/dist/index.js +411 -0
  83. package/dist/index.js.map +9 -0
  84. package/dist/kinds/building.d.ts +44 -0
  85. package/dist/kinds/faces.d.ts +27 -0
  86. package/dist/kinds/head-armour.d.ts +9 -0
  87. package/dist/kinds/head-beard.d.ts +6 -0
  88. package/dist/kinds/head-bust.d.ts +4 -0
  89. package/dist/kinds/head-extras.d.ts +15 -0
  90. package/dist/kinds/head-face.d.ts +6 -0
  91. package/dist/kinds/head-geom.d.ts +7 -0
  92. package/dist/kinds/head-hair.d.ts +4 -0
  93. package/dist/kinds/head-layout.d.ts +29 -0
  94. package/dist/kinds/head-mesh.d.ts +27 -0
  95. package/dist/kinds/head-params.d.ts +108 -0
  96. package/dist/kinds/head-presets.d.ts +19 -0
  97. package/dist/kinds/head-rim.d.ts +10 -0
  98. package/dist/kinds/head.d.ts +39 -0
  99. package/dist/kinds/hull-lines.d.ts +65 -0
  100. package/dist/kinds/hull-skin.d.ts +6 -0
  101. package/dist/kinds/hull.d.ts +14 -0
  102. package/dist/kinds/index.d.ts +16 -0
  103. package/dist/kinds/index.js +78 -0
  104. package/dist/kinds/index.js.map +9 -0
  105. package/dist/kinds/mass.d.ts +15 -0
  106. package/dist/kinds/openings.d.ts +30 -0
  107. package/dist/kinds/planned.d.ts +6 -0
  108. package/dist/kinds/roof-chimney.d.ts +16 -0
  109. package/dist/kinds/roof-shape.d.ts +64 -0
  110. package/dist/kinds/roof.d.ts +31 -0
  111. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  112. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  113. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  114. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  115. package/dist/kinds/vegetation-params.d.ts +90 -0
  116. package/dist/kinds/vegetation-presets.d.ts +20 -0
  117. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  118. package/dist/kinds/vegetation.d.ts +42 -0
  119. package/dist/kinds/vehicle.d.ts +42 -0
  120. package/dist/material/generator.d.ts +4 -0
  121. package/dist/material/index.d.ts +4 -0
  122. package/dist/material/index.js +38 -0
  123. package/dist/material/index.js.map +9 -0
  124. package/dist/material/paint/bond.d.ts +3 -0
  125. package/dist/material/paint/canvas.d.ts +32 -0
  126. package/dist/material/paint/cell.d.ts +4 -0
  127. package/dist/material/paint/cells-round.d.ts +5 -0
  128. package/dist/material/paint/cells.d.ts +7 -0
  129. package/dist/material/paint/compose.d.ts +5 -0
  130. package/dist/material/paint/concrete.d.ts +3 -0
  131. package/dist/material/paint/courses.d.ts +31 -0
  132. package/dist/material/paint/decode-png.d.ts +6 -0
  133. package/dist/material/paint/default-sheet.d.ts +2 -0
  134. package/dist/material/paint/foliage-broad.d.ts +5 -0
  135. package/dist/material/paint/foliage-common.d.ts +35 -0
  136. package/dist/material/paint/foliage-compound.d.ts +4 -0
  137. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  138. package/dist/material/paint/foliage-raster.d.ts +26 -0
  139. package/dist/material/paint/foliage.d.ts +3 -0
  140. package/dist/material/paint/masks.d.ts +3 -0
  141. package/dist/material/paint/noise.d.ts +9 -0
  142. package/dist/material/paint/normal.d.ts +8 -0
  143. package/dist/material/paint/orm.d.ts +8 -0
  144. package/dist/material/paint/planks.d.ts +3 -0
  145. package/dist/material/paint/plates.d.ts +3 -0
  146. package/dist/material/paint/shapes.d.ts +8 -0
  147. package/dist/material/paint/slate.d.ts +3 -0
  148. package/dist/material/paint/stamp.d.ts +3 -0
  149. package/dist/material/paint/strip.d.ts +3 -0
  150. package/dist/material/paint/surface.d.ts +5 -0
  151. package/dist/material/paint/types.d.ts +35 -0
  152. package/dist/material/paint/validate.d.ts +3 -0
  153. package/dist/material/palette.d.ts +12 -0
  154. package/dist/material/shader-godot.d.ts +2 -0
  155. package/dist/material/shader-three.d.ts +2 -0
  156. package/dist/material/shader.d.ts +46 -0
  157. package/dist/material/trim-sheet.d.ts +22 -0
  158. package/dist/neural/generator.d.ts +25 -0
  159. package/dist/neural/images.d.ts +17 -0
  160. package/dist/neural/index.d.ts +2 -0
  161. package/dist/neural/index.js +14 -0
  162. package/dist/neural/index.js.map +9 -0
  163. package/dist/passes/bake-ao.d.ts +17 -0
  164. package/dist/passes/grime.d.ts +15 -0
  165. package/dist/passes/index.d.ts +6 -0
  166. package/dist/passes/index.js +22 -0
  167. package/dist/passes/index.js.map +9 -0
  168. package/dist/passes/livery.d.ts +19 -0
  169. package/dist/passes/optimize-cache.d.ts +15 -0
  170. package/dist/passes/optimize-weld.d.ts +15 -0
  171. package/dist/passes/optimize.d.ts +28 -0
  172. package/dist/passes/ray-grid.d.ts +23 -0
  173. package/dist/passes/weighted-normals.d.ts +8 -0
  174. package/dist/primitives/beam.d.ts +17 -0
  175. package/dist/primitives/bevel-box.d.ts +13 -0
  176. package/dist/primitives/box.d.ts +16 -0
  177. package/dist/primitives/cell.d.ts +20 -0
  178. package/dist/primitives/check.d.ts +12 -0
  179. package/dist/primitives/closed.d.ts +11 -0
  180. package/dist/primitives/erosion.d.ts +22 -0
  181. package/dist/primitives/font-5x7.d.ts +3 -0
  182. package/dist/primitives/heightfield.d.ts +16 -0
  183. package/dist/primitives/index.d.ts +23 -0
  184. package/dist/primitives/index.js +81 -0
  185. package/dist/primitives/index.js.map +9 -0
  186. package/dist/primitives/lathe-facets.d.ts +8 -0
  187. package/dist/primitives/lathe.d.ts +26 -0
  188. package/dist/primitives/lattice.d.ts +30 -0
  189. package/dist/primitives/loft.d.ts +15 -0
  190. package/dist/primitives/mc-table.d.ts +2 -0
  191. package/dist/primitives/member.d.ts +19 -0
  192. package/dist/primitives/noise.d.ts +34 -0
  193. package/dist/primitives/planar.d.ts +10 -0
  194. package/dist/primitives/prism.d.ts +13 -0
  195. package/dist/primitives/sculpt-edit.d.ts +56 -0
  196. package/dist/primitives/sculpt-tree.d.ts +23 -0
  197. package/dist/primitives/sculpt.d.ts +27 -0
  198. package/dist/primitives/sdf-grid.d.ts +17 -0
  199. package/dist/primitives/sdf-ops.d.ts +37 -0
  200. package/dist/primitives/sdf.d.ts +21 -0
  201. package/dist/primitives/strip-uv.d.ts +11 -0
  202. package/dist/primitives/surface-nets.d.ts +7 -0
  203. package/dist/primitives/sweep.d.ts +19 -0
  204. package/dist/primitives/terrain-color.d.ts +30 -0
  205. package/dist/primitives/terrain-grid.d.ts +36 -0
  206. package/dist/primitives/text-layout.d.ts +22 -0
  207. package/dist/primitives/text.d.ts +27 -0
  208. package/dist/primitives/tiled.d.ts +22 -0
  209. package/dist/primitives/transport.d.ts +17 -0
  210. package/dist/primitives/triangulate.d.ts +2 -0
  211. package/dist/primitives/value-noise.d.ts +10 -0
  212. package/dist/qa/checks.d.ts +11 -0
  213. package/dist/qa/clash.d.ts +2 -0
  214. package/dist/qa/floating.d.ts +5 -0
  215. package/dist/qa/gates.d.ts +12 -0
  216. package/dist/qa/geometry.d.ts +21 -0
  217. package/dist/qa/index.d.ts +12 -0
  218. package/dist/qa/index.js +78 -0
  219. package/dist/qa/index.js.map +9 -0
  220. package/dist/qa/inside.d.ts +12 -0
  221. package/dist/qa/inspect.d.ts +18 -0
  222. package/dist/qa/lods.d.ts +12 -0
  223. package/dist/qa/measures.d.ts +4 -0
  224. package/dist/qa/occlusion.d.ts +3 -0
  225. package/dist/qa/sanity.d.ts +7 -0
  226. package/dist/qa/types.d.ts +12 -0
  227. package/dist/render/beauty.d.ts +40 -0
  228. package/dist/render/camera.d.ts +33 -0
  229. package/dist/render/depth-map.d.ts +38 -0
  230. package/dist/render/font.d.ts +1 -0
  231. package/dist/render/ground.d.ts +13 -0
  232. package/dist/render/index.d.ts +10 -0
  233. package/dist/render/index.js +47 -0
  234. package/dist/render/index.js.map +9 -0
  235. package/dist/render/lighting.d.ts +20 -0
  236. package/dist/render/lod-strip.d.ts +5 -0
  237. package/dist/render/metrics.d.ts +37 -0
  238. package/dist/render/options.d.ts +28 -0
  239. package/dist/render/photo-match.d.ts +19 -0
  240. package/dist/render/raster.d.ts +25 -0
  241. package/dist/render/resolve.d.ts +11 -0
  242. package/dist/render/review-prompt.d.ts +2 -0
  243. package/dist/render/review-warnings.d.ts +34 -0
  244. package/dist/render/review.d.ts +24 -0
  245. package/dist/render/scene.d.ts +18 -0
  246. package/dist/render/shade.d.ts +22 -0
  247. package/dist/render/sheet.d.ts +15 -0
  248. package/dist/render/texture.d.ts +15 -0
  249. package/dist/render/thumbnail.d.ts +8 -0
  250. package/dist/render/vis-buffer.d.ts +18 -0
  251. package/dist/testing/assert.d.ts +21 -0
  252. package/dist/testing/compiled.d.ts +15 -0
  253. package/dist/testing/hash.d.ts +1 -0
  254. package/dist/testing/index.d.ts +4 -0
  255. package/dist/testing/index.js +134 -0
  256. package/dist/testing/index.js.map +13 -0
  257. package/dist/testing/snapshot.d.ts +13 -0
  258. package/docs/README.md +52 -0
  259. package/docs/architecture/README.md +68 -0
  260. package/docs/architecture/adding-a-kind.md +44 -0
  261. package/docs/architecture/adding-a-primitive.md +49 -0
  262. package/docs/architecture/errors.md +47 -0
  263. package/docs/architecture/glb-production.md +214 -0
  264. package/docs/architecture/gltf-capabilities.md +80 -0
  265. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  266. package/docs/decisions/002-typescript-not-json.md +22 -0
  267. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  268. package/docs/decisions/004-z-up-metres.md +21 -0
  269. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  270. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  271. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  272. package/docs/decisions/008-surface-materials.md +21 -0
  273. package/docs/decisions/009-gltf-front-axis.md +21 -0
  274. package/docs/decisions/README.md +15 -0
  275. package/docs/idea/01-vision.md +117 -0
  276. package/docs/idea/02-dsl.md +343 -0
  277. package/docs/idea/03-ir.md +97 -0
  278. package/docs/idea/04-materials.md +114 -0
  279. package/docs/idea/05-agent-loop.md +141 -0
  280. package/docs/idea/06-qa.md +101 -0
  281. package/docs/idea/07-what-fits.md +45 -0
  282. package/docs/idea/08-neural-adapters.md +78 -0
  283. package/docs/idea/09-kinds.md +105 -0
  284. package/docs/landscape.md +49 -0
  285. package/docs/plans/roadmap.md +127 -0
  286. package/examples/build-with-bun.ts +41 -0
  287. package/llms.txt +47 -0
  288. package/package.json +99 -4
  289. package/recipes/README.md +38 -0
  290. package/recipes/asteroid/README.md +46 -0
  291. package/recipes/asteroid/asteroid.ts +336 -0
  292. package/recipes/boulders/README.md +28 -0
  293. package/recipes/boulders/boulders.ts +117 -0
  294. package/recipes/boxcar/README.md +46 -0
  295. package/recipes/boxcar/boxcar.ts +916 -0
  296. package/recipes/cargo_container_scifi/README.md +52 -0
  297. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  298. package/recipes/dropship/README.md +84 -0
  299. package/recipes/dropship/dropship.ts +1144 -0
  300. package/recipes/eiffel_tower/README.md +110 -0
  301. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  302. package/recipes/goblin_head/README.md +62 -0
  303. package/recipes/goblin_head/goblin_head.ts +36 -0
  304. package/recipes/human_head/README.md +64 -0
  305. package/recipes/human_head/human_head.ts +37 -0
  306. package/recipes/loco_440/README.md +32 -0
  307. package/recipes/loco_440/loco_440.ts +1404 -0
  308. package/recipes/material_swatches/README.md +21 -0
  309. package/recipes/material_swatches/material_swatches.ts +183 -0
  310. package/recipes/mountain_range/README.md +45 -0
  311. package/recipes/mountain_range/mountain_range.ts +128 -0
  312. package/recipes/oak/README.md +30 -0
  313. package/recipes/oak/oak.ts +19 -0
  314. package/recipes/orc_head/README.md +66 -0
  315. package/recipes/orc_head/orc_head.ts +39 -0
  316. package/recipes/palm/README.md +30 -0
  317. package/recipes/palm/palm.ts +19 -0
  318. package/recipes/pine/README.md +29 -0
  319. package/recipes/pine/pine.ts +19 -0
  320. package/recipes/signal_gantry/README.md +36 -0
  321. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  322. package/recipes/station_halt/README.md +39 -0
  323. package/recipes/station_halt/station_halt.ts +722 -0
  324. package/recipes/t34/README.md +44 -0
  325. package/recipes/t34/t34.ts +1068 -0
  326. package/recipes/tiger_1/README.md +75 -0
  327. package/recipes/tiger_1/tiger_1.ts +1370 -0
  328. package/recipes/water_tower/README.md +40 -0
  329. package/recipes/water_tower/water_tower.ts +542 -0
  330. package/src/cli/args.ts +112 -0
  331. package/src/cli/bin.ts +7 -0
  332. package/src/cli/load.ts +17 -0
  333. package/src/cli/manifest.ts +89 -0
  334. package/src/cli/render-cmds.ts +66 -0
  335. package/src/cli/run.ts +168 -0
  336. package/src/core/asset.ts +128 -0
  337. package/src/core/definition.ts +149 -0
  338. package/src/core/errors.ts +45 -0
  339. package/src/core/family.ts +50 -0
  340. package/src/core/index.ts +66 -0
  341. package/src/core/mesh.ts +114 -0
  342. package/src/core/part.ts +130 -0
  343. package/src/core/png.ts +49 -0
  344. package/src/core/rng.ts +49 -0
  345. package/src/core/transform.ts +90 -0
  346. package/src/core/vec.ts +26 -0
  347. package/src/core/world.ts +80 -0
  348. package/src/export/bin-writer.ts +79 -0
  349. package/src/export/container.ts +35 -0
  350. package/src/export/glb.ts +200 -0
  351. package/src/export/index.ts +18 -0
  352. package/src/export/lights.ts +49 -0
  353. package/src/export/materials.ts +126 -0
  354. package/src/export/merge-pivots.ts +146 -0
  355. package/src/export/metadata.ts +59 -0
  356. package/src/export/motion.ts +70 -0
  357. package/src/export/optimize-parts.ts +80 -0
  358. package/src/export/primitive.ts +35 -0
  359. package/src/export/tangents.ts +75 -0
  360. package/src/export/textures.ts +88 -0
  361. package/src/export/version.ts +6 -0
  362. package/src/grammar/index.ts +4 -0
  363. package/src/grammar/split.ts +87 -0
  364. package/src/grammar/whyte.ts +20 -0
  365. package/src/index.ts +14 -0
  366. package/src/kinds/building.ts +153 -0
  367. package/src/kinds/faces.ts +138 -0
  368. package/src/kinds/head-armour.ts +151 -0
  369. package/src/kinds/head-beard.ts +133 -0
  370. package/src/kinds/head-bust.ts +221 -0
  371. package/src/kinds/head-extras.ts +150 -0
  372. package/src/kinds/head-face.ts +222 -0
  373. package/src/kinds/head-geom.ts +31 -0
  374. package/src/kinds/head-hair.ts +152 -0
  375. package/src/kinds/head-layout.ts +63 -0
  376. package/src/kinds/head-mesh.ts +157 -0
  377. package/src/kinds/head-params.ts +153 -0
  378. package/src/kinds/head-presets.ts +152 -0
  379. package/src/kinds/head-rim.ts +128 -0
  380. package/src/kinds/head.ts +171 -0
  381. package/src/kinds/hull-lines.ts +160 -0
  382. package/src/kinds/hull-skin.ts +83 -0
  383. package/src/kinds/hull.ts +209 -0
  384. package/src/kinds/index.ts +63 -0
  385. package/src/kinds/mass.ts +110 -0
  386. package/src/kinds/openings.ts +141 -0
  387. package/src/kinds/planned.ts +12 -0
  388. package/src/kinds/roof-chimney.ts +100 -0
  389. package/src/kinds/roof-shape.ts +230 -0
  390. package/src/kinds/roof.ts +250 -0
  391. package/src/kinds/vegetation-fronds.ts +126 -0
  392. package/src/kinds/vegetation-impostor.ts +118 -0
  393. package/src/kinds/vegetation-leaves.ts +168 -0
  394. package/src/kinds/vegetation-mesh.ts +174 -0
  395. package/src/kinds/vegetation-params.ts +147 -0
  396. package/src/kinds/vegetation-presets.ts +184 -0
  397. package/src/kinds/vegetation-skeleton.ts +168 -0
  398. package/src/kinds/vegetation.ts +199 -0
  399. package/src/kinds/vehicle.ts +144 -0
  400. package/src/material/generator.ts +56 -0
  401. package/src/material/index.ts +29 -0
  402. package/src/material/paint/bond.ts +60 -0
  403. package/src/material/paint/canvas.ts +91 -0
  404. package/src/material/paint/cell.ts +39 -0
  405. package/src/material/paint/cells-round.ts +119 -0
  406. package/src/material/paint/cells.ts +111 -0
  407. package/src/material/paint/compose.ts +53 -0
  408. package/src/material/paint/concrete.ts +62 -0
  409. package/src/material/paint/courses.ts +85 -0
  410. package/src/material/paint/decode-png.ts +100 -0
  411. package/src/material/paint/default-sheet.ts +61 -0
  412. package/src/material/paint/foliage-broad.ts +126 -0
  413. package/src/material/paint/foliage-common.ts +93 -0
  414. package/src/material/paint/foliage-compound.ts +135 -0
  415. package/src/material/paint/foliage-conifer.ts +143 -0
  416. package/src/material/paint/foliage-raster.ts +196 -0
  417. package/src/material/paint/foliage.ts +23 -0
  418. package/src/material/paint/masks.ts +44 -0
  419. package/src/material/paint/noise.ts +52 -0
  420. package/src/material/paint/normal.ts +52 -0
  421. package/src/material/paint/orm.ts +95 -0
  422. package/src/material/paint/planks.ts +56 -0
  423. package/src/material/paint/plates.ts +72 -0
  424. package/src/material/paint/shapes.ts +28 -0
  425. package/src/material/paint/slate.ts +52 -0
  426. package/src/material/paint/stamp.ts +40 -0
  427. package/src/material/paint/strip.ts +42 -0
  428. package/src/material/paint/surface.ts +60 -0
  429. package/src/material/paint/types.ts +51 -0
  430. package/src/material/paint/validate.ts +58 -0
  431. package/src/material/palette.ts +43 -0
  432. package/src/material/shader-godot.ts +77 -0
  433. package/src/material/shader-three.ts +103 -0
  434. package/src/material/shader.ts +100 -0
  435. package/src/material/trim-sheet.ts +100 -0
  436. package/src/neural/generator.ts +37 -0
  437. package/src/neural/images.ts +27 -0
  438. package/src/neural/index.ts +10 -0
  439. package/src/passes/bake-ao.ts +183 -0
  440. package/src/passes/grime.ts +37 -0
  441. package/src/passes/index.ts +8 -0
  442. package/src/passes/livery.ts +66 -0
  443. package/src/passes/optimize-cache.ts +175 -0
  444. package/src/passes/optimize-weld.ts +147 -0
  445. package/src/passes/optimize.ts +85 -0
  446. package/src/passes/ray-grid.ts +237 -0
  447. package/src/passes/weighted-normals.ts +125 -0
  448. package/src/primitives/beam.ts +60 -0
  449. package/src/primitives/bevel-box.ts +140 -0
  450. package/src/primitives/box.ts +90 -0
  451. package/src/primitives/cell.ts +115 -0
  452. package/src/primitives/check.ts +49 -0
  453. package/src/primitives/closed.ts +74 -0
  454. package/src/primitives/erosion.ts +183 -0
  455. package/src/primitives/font-5x7.ts +117 -0
  456. package/src/primitives/heightfield.ts +101 -0
  457. package/src/primitives/index.ts +56 -0
  458. package/src/primitives/lathe-facets.ts +45 -0
  459. package/src/primitives/lathe.ts +174 -0
  460. package/src/primitives/lattice.ts +131 -0
  461. package/src/primitives/loft.ts +174 -0
  462. package/src/primitives/mc-table.ts +41 -0
  463. package/src/primitives/member.ts +130 -0
  464. package/src/primitives/noise.ts +157 -0
  465. package/src/primitives/planar.ts +55 -0
  466. package/src/primitives/prism.ts +85 -0
  467. package/src/primitives/sculpt-edit.ts +212 -0
  468. package/src/primitives/sculpt-tree.ts +226 -0
  469. package/src/primitives/sculpt.ts +80 -0
  470. package/src/primitives/sdf-grid.ts +131 -0
  471. package/src/primitives/sdf-ops.ts +145 -0
  472. package/src/primitives/sdf.ts +83 -0
  473. package/src/primitives/strip-uv.ts +26 -0
  474. package/src/primitives/surface-nets.ts +161 -0
  475. package/src/primitives/sweep.ts +162 -0
  476. package/src/primitives/terrain-color.ts +85 -0
  477. package/src/primitives/terrain-grid.ts +137 -0
  478. package/src/primitives/text-layout.ts +85 -0
  479. package/src/primitives/text.ts +197 -0
  480. package/src/primitives/tiled.ts +93 -0
  481. package/src/primitives/transport.ts +85 -0
  482. package/src/primitives/triangulate.ts +42 -0
  483. package/src/primitives/value-noise.ts +62 -0
  484. package/src/qa/checks.ts +110 -0
  485. package/src/qa/clash.ts +177 -0
  486. package/src/qa/floating.ts +63 -0
  487. package/src/qa/gates.ts +126 -0
  488. package/src/qa/geometry.ts +187 -0
  489. package/src/qa/index.ts +28 -0
  490. package/src/qa/inside.ts +114 -0
  491. package/src/qa/inspect.ts +107 -0
  492. package/src/qa/lods.ts +97 -0
  493. package/src/qa/measures.ts +67 -0
  494. package/src/qa/occlusion.ts +166 -0
  495. package/src/qa/sanity.ts +137 -0
  496. package/src/qa/types.ts +17 -0
  497. package/src/render/beauty.ts +115 -0
  498. package/src/render/camera.ts +142 -0
  499. package/src/render/depth-map.ts +191 -0
  500. package/src/render/font.ts +76 -0
  501. package/src/render/ground.ts +87 -0
  502. package/src/render/index.ts +34 -0
  503. package/src/render/lighting.ts +95 -0
  504. package/src/render/lod-strip.ts +34 -0
  505. package/src/render/metrics.ts +147 -0
  506. package/src/render/options.ts +81 -0
  507. package/src/render/photo-match.ts +28 -0
  508. package/src/render/raster.ts +143 -0
  509. package/src/render/resolve.ts +109 -0
  510. package/src/render/review-prompt.ts +59 -0
  511. package/src/render/review-warnings.ts +92 -0
  512. package/src/render/review.ts +103 -0
  513. package/src/render/scene.ts +119 -0
  514. package/src/render/shade.ts +199 -0
  515. package/src/render/sheet.ts +88 -0
  516. package/src/render/texture.ts +97 -0
  517. package/src/render/thumbnail.ts +75 -0
  518. package/src/render/vis-buffer.ts +119 -0
  519. package/src/testing/assert.ts +115 -0
  520. package/src/testing/compiled.ts +31 -0
  521. package/src/testing/hash.ts +14 -0
  522. package/src/testing/index.ts +14 -0
  523. package/src/testing/snapshot.ts +37 -0
  524. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,34 @@
1
+ import type { ExposureStats, MaskStats } from './metrics.js';
2
+ export interface ViewMetrics {
3
+ readonly view: string;
4
+ readonly lod0: MaskStats;
5
+ readonly lod2: MaskStats;
6
+ /** Silhouette IoU LOD0 ↔ LOD2 as seen from afar (64² max-pooled masks, same frame). Low = popping. */
7
+ readonly lodIoU: number;
8
+ /** The same at full mask resolution (256²): lattices and foliage score low here by nature. */
9
+ readonly lodIoUExact: number;
10
+ }
11
+ export interface ReviewWarning {
12
+ readonly code: string;
13
+ readonly message: string;
14
+ readonly fix: string;
15
+ }
16
+ export interface ReviewMetrics {
17
+ readonly asset: string;
18
+ readonly triangles: readonly number[];
19
+ readonly hero: ExposureStats;
20
+ readonly views: readonly ViewMetrics[];
21
+ readonly warnings: readonly ReviewWarning[];
22
+ }
23
+ export declare const REVIEW_THRESHOLDS: {
24
+ /** LOD0 ↔ LOD2 silhouette IoU below this in any view = visible popping. */
25
+ readonly lodIoU: 0.8;
26
+ readonly heroMeanMin: 0.18;
27
+ readonly heroMeanMax: 0.85;
28
+ readonly heroContrastMin: 0.06;
29
+ readonly nearBlackMax: 0.2;
30
+ readonly nearWhiteMax: 0.1;
31
+ /** Asset spanning less of the hero frame than this: something stretches the bounds. */
32
+ readonly heroSpanMin: 0.5;
33
+ };
34
+ export declare function warningsOf(m: Omit<ReviewMetrics, 'warnings'>): ReviewWarning[];
@@ -0,0 +1,24 @@
1
+ import { type Asset } from '../core/index.js';
2
+ import type { PaintedSheet } from '../material/index.js';
3
+ import { type ReviewMetrics } from './review-warnings.js';
4
+ export interface ReviewOptions {
5
+ readonly sheet?: PaintedSheet;
6
+ readonly width?: number;
7
+ readonly height?: number;
8
+ readonly azDeg?: number;
9
+ readonly elDeg?: number;
10
+ readonly fovDeg?: number;
11
+ /** Path of the reference dossier (recipes/<name>/README.md) the prompt points the reviewer at. */
12
+ readonly refs?: string;
13
+ }
14
+ export interface ReviewPack {
15
+ /** File name (inside the review folder) → contents. */
16
+ readonly files: ReadonlyMap<string, Uint8Array | string>;
17
+ readonly metrics: ReviewMetrics;
18
+ }
19
+ /** Two zoom windows centred on the densest cells of the hero's 16 × 9 detail grid, ≥ 4 cells apart. */
20
+ export declare function detailWindows(detail: Uint32Array): {
21
+ x: number;
22
+ y: number;
23
+ }[];
24
+ export declare function reviewPack(asset: Asset, o?: ReviewOptions): ReviewPack;
@@ -0,0 +1,18 @@
1
+ import { type CompiledAsset, type Surface } from '../core/index.js';
2
+ export declare const SURFACE_CODE: Readonly<Record<Surface, number>>;
3
+ export interface Scene {
4
+ readonly positions: Float32Array;
5
+ readonly normals: Float32Array;
6
+ readonly uvs: Float32Array;
7
+ readonly colors: Float32Array;
8
+ /** Three global vertex indices per triangle. */
9
+ readonly indices: Uint32Array;
10
+ /** SURFACE_CODE per triangle. */
11
+ readonly surface: Uint8Array;
12
+ /** Per-triangle tangent frame for the normal map, 6 floats: T = dP/dU, B = −dP/dV (glTF +Y). */
13
+ readonly tangents: Float32Array;
14
+ readonly min: readonly [number, number, number];
15
+ readonly max: readonly [number, number, number];
16
+ readonly triangles: number;
17
+ }
18
+ export declare function sceneOf(asset: CompiledAsset): Scene;
@@ -0,0 +1,22 @@
1
+ import type { PerspectiveCamera } from './camera.js';
2
+ import { type DepthMap } from './depth-map.js';
3
+ import type { Rig } from './lighting.js';
4
+ import type { Scene } from './scene.js';
5
+ import { type SheetTextures } from './texture.js';
6
+ export interface ShadeContext {
7
+ readonly scene: Scene;
8
+ readonly cam: PerspectiveCamera;
9
+ readonly tex: SheetTextures;
10
+ readonly rig: Rig;
11
+ readonly sunMap: DepthMap;
12
+ readonly aoMaps: readonly DepthMap[];
13
+ readonly pcf: readonly Float32Array[];
14
+ /** AO falloff distance, metres: occluders farther than this along a sky direction don't count. */
15
+ readonly reach: number;
16
+ }
17
+ /** Sky openness at p for a surface facing n: cosine-weighted share of unoccluded sky directions. */
18
+ export declare function skyOpen(c: ShadeContext, p: readonly number[], n: readonly number[]): number;
19
+ /** Sun visibility at p (geometric normal n), PCF with one of four rotated tap sets. */
20
+ export declare function sunVisible(c: ShadeContext, p: readonly number[], n: readonly number[], set: number): number;
21
+ /** Linear radiance of triangle `t` at barycentrics (b1, b2) into out[0..2]. */
22
+ export declare function shadeSample(c: ShadeContext, t: number, b1: number, b2: number, mip: number, set: number, out: Float32Array): void;
@@ -0,0 +1,15 @@
1
+ import { type CompiledAsset, type Vec3 } from '../core/index.js';
2
+ import { type View } from './raster.js';
3
+ export declare const VIEWS: readonly View[];
4
+ export declare function bounds(asset: CompiledAsset): {
5
+ centre: Vec3;
6
+ extent: number;
7
+ min: Vec3;
8
+ max: Vec3;
9
+ };
10
+ export interface SheetOptions {
11
+ readonly cell?: number;
12
+ /** The far view's asset — compile the same recipe at LOD 2 and pass it here. */
13
+ readonly far?: CompiledAsset;
14
+ }
15
+ export declare function contactSheet(asset: CompiledAsset, o?: SheetOptions): Uint8Array;
@@ -0,0 +1,15 @@
1
+ import type { PaintedSheet } from '../material/index.js';
2
+ export interface Mip {
3
+ readonly size: number;
4
+ /** 4 floats per texel. */
5
+ readonly data: Float32Array;
6
+ }
7
+ export interface SheetTextures {
8
+ readonly size: number;
9
+ readonly albedo: readonly Mip[];
10
+ readonly normal: readonly Mip[];
11
+ readonly orm: readonly Mip[];
12
+ }
13
+ export declare function sheetTextures(sheet: PaintedSheet): SheetTextures;
14
+ /** Bilinear sample of mip `level` at (u, v) into out[0..3]. */
15
+ export declare function sample(mips: readonly Mip[], level: number, u: number, v: number, out: Float32Array): void;
@@ -0,0 +1,8 @@
1
+ import { type CompiledAsset } from '../core/index.js';
2
+ import { type View } from './raster.js';
3
+ export interface ThumbnailOptions {
4
+ /** Output edge in pixels (square). Default 512. */
5
+ readonly size?: number;
6
+ readonly view?: View;
7
+ }
8
+ export declare function thumbnail(asset: CompiledAsset, o?: ThumbnailOptions): Uint8Array;
@@ -0,0 +1,18 @@
1
+ import type { PerspectiveCamera } from './camera.js';
2
+ import type { Scene } from './scene.js';
3
+ export interface VisBuffer {
4
+ readonly width: number;
5
+ readonly height: number;
6
+ /** Triangle id per pixel, −1 = background. */
7
+ readonly tri: Int32Array;
8
+ /** Perspective-correct barycentrics of vertices 1 and 2 (vertex 0 = 1 − b1 − b2). */
9
+ readonly b1: Float32Array;
10
+ readonly b2: Float32Array;
11
+ /** 1 / view depth (larger = nearer). */
12
+ readonly invw: Float32Array;
13
+ /** Per-triangle texture mip level from its texel : pixel footprint. */
14
+ readonly mip: Uint8Array;
15
+ }
16
+ /** Return false to discard a fragment (alpha-tested cutouts). */
17
+ export type AlphaTest = (tri: number, b1: number, b2: number, mip: number) => boolean;
18
+ export declare function rasteriseVis(s: Scene, cam: PerspectiveCamera, texSize: number, alpha?: AlphaTest): VisBuffer;
@@ -0,0 +1,21 @@
1
+ import type { Asset, Lod, Measure } from '../core/index.js';
2
+ import { type Gate, type LodGate } from '../qa/index.js';
3
+ import { type LodOptions } from './compiled.js';
4
+ export interface QaOptions extends LodOptions {
5
+ /** Per-LOD gates. Default the build's DEFAULT_GATES. */
6
+ readonly gates?: readonly Gate[];
7
+ /** Chain gates over all compiled LODs. Default DEFAULT_LOD_GATES. */
8
+ readonly lodGates?: readonly LodGate[];
9
+ }
10
+ /** The same gate `t3d build` runs: every LOD passes QA, plus the LOD-chain gates. */
11
+ export declare function assertQaPass(asset: Asset, o?: QaOptions): void;
12
+ /** Triangles per LOD at or under `caps` (default: the asset's own budget). */
13
+ export declare function assertBudget(asset: Asset, caps?: readonly [number, number, number], o?: LodOptions): void;
14
+ /** Each selector (name or glob) matches at least one part at every LOD checked. */
15
+ export declare function assertParts(asset: Asset, selectors: readonly string[], o?: LodOptions): void;
16
+ /** One spec dimension at LOD0, the way the `measures` gate checks `real.measures`. */
17
+ export declare function assertMeasure(asset: Asset, m: Measure, o?: {
18
+ lod?: Lod;
19
+ }): number;
20
+ /** Every moving part sweeps clear of everything else (the `clashes` gate), at every LOD. */
21
+ export declare function assertMotionClear(asset: Asset, o?: LodOptions): void;
@@ -0,0 +1,15 @@
1
+ import { type Asset, type CompiledAsset, type Lod } from '../core/index.js';
2
+ import type { Finding } from '../qa/index.js';
3
+ export interface LodOptions {
4
+ /** LODs to check. Default all three. */
5
+ readonly lods?: readonly Lod[];
6
+ readonly seed?: number;
7
+ }
8
+ export declare const ALL_LODS: readonly Lod[];
9
+ export declare const compileLods: (asset: Asset, o?: LodOptions) => CompiledAsset[];
10
+ export declare const describeFinding: (f: Finding, lod?: Lod) => string;
11
+ /** Throw T3D_QA_FAILED listing every finding; no-op when there are none. */
12
+ export declare function failOn(what: string, found: ReadonlyArray<{
13
+ finding: Finding;
14
+ lod?: Lod;
15
+ }>): void;
@@ -0,0 +1 @@
1
+ export declare function hashBytes(bytes: Uint8Array): string;
@@ -0,0 +1,4 @@
1
+ export { assertBudget, assertMeasure, assertMotionClear, assertParts, assertQaPass, type QaOptions, } from './assert.js';
2
+ export { ALL_LODS, compileLods, describeFinding, type LodOptions } from './compiled.js';
3
+ export { hashBytes } from './hash.js';
4
+ export { assertDeterministic, type SnapshotOptions, snapshotHash } from './snapshot.js';
@@ -0,0 +1,134 @@
1
+ import {
2
+ Ts23dError2,
3
+ compile2
4
+ } from "../chunk-4ddzsqtc.js";
5
+ import {
6
+ glob2,
7
+ clashes2,
8
+ measure2,
9
+ DEFAULT_GATES2,
10
+ runQa2,
11
+ DEFAULT_LOD_GATES2,
12
+ runLodQa2
13
+ } from "../chunk-w1zyhj40.js";
14
+ import"../chunk-kvcep7ft.js";
15
+ import"../chunk-xnt2sdhf.js";
16
+ import {
17
+ exportGlb2
18
+ } from "../chunk-gcyp9b4d.js";
19
+
20
+ // src/testing/compiled.ts
21
+ var ALL_LODS = [0, 1, 2];
22
+ var compileLods = (asset, o = {}) => (o.lods ?? ALL_LODS).map((lod) => compile2(asset, { lod, ...o.seed !== undefined ? { seed: o.seed } : {} }));
23
+ var describeFinding = (f, lod) => `${lod !== undefined ? `LOD${lod} ` : ""}${f.gate}${f.part ? ` [${f.part}]` : ""}: ${f.message}`;
24
+ function failOn(what, found) {
25
+ if (found.length === 0)
26
+ return;
27
+ const lines = found.map(({ finding, lod }) => ` - ${describeFinding(finding, lod)}`);
28
+ throw new Ts23dError2("T3D_QA_FAILED", {
29
+ cause: `${what}: ${found.length} finding(s)
30
+ ${lines.join(`
31
+ `)}`,
32
+ fix: found[0]?.finding.fix ?? "read the findings above"
33
+ });
34
+ }
35
+
36
+ // src/testing/assert.ts
37
+ function assertQaPass(asset, o = {}) {
38
+ const lods = compileLods(asset, o);
39
+ const found = lods.flatMap((a) => runQa2(a, o.gates ?? DEFAULT_GATES2).map((finding) => ({ finding, lod: a.lod })));
40
+ const chain = lods.length > 1 ? runLodQa2(lods, o.lodGates ?? DEFAULT_LOD_GATES2) : [];
41
+ failOn(`${asset.name} QA`, [...found, ...chain.map((finding) => ({ finding }))]);
42
+ }
43
+ function assertBudget(asset, caps = asset.budget.triangles, o = {}) {
44
+ failOn(`${asset.name} budget`, compileLods(asset, o).flatMap((a) => {
45
+ const tris = a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
46
+ const cap = caps[a.lod];
47
+ return tris > cap ? [
48
+ {
49
+ lod: a.lod,
50
+ finding: {
51
+ gate: "budget",
52
+ message: `${tris} triangles > ${cap}`,
53
+ fix: "drop detail at this LOD (ctx.lod / ctx.pick) — never raise a cap to make a test pass"
54
+ }
55
+ }
56
+ ] : [];
57
+ }));
58
+ }
59
+ function assertParts(asset, selectors, o = {}) {
60
+ failOn(`${asset.name} parts`, compileLods(asset, o).flatMap((a) => selectors.filter((s) => !a.parts.some((p) => glob2(s).test(p.name))).map((s) => ({
61
+ lod: a.lod,
62
+ finding: {
63
+ gate: "parts",
64
+ message: `no part matches '${s}'`,
65
+ fix: "engines bind to these names: add the part/group at this LOD (gate its geometry, not its pivot)"
66
+ }
67
+ }))));
68
+ }
69
+ function assertMeasure(asset, m, o = {}) {
70
+ const [a] = compileLods(asset, { lods: [o.lod ?? 0] });
71
+ const got = a ? measure2(a, m) : null;
72
+ const tol = m.tolerance ?? 0.02;
73
+ const bad = got === null || Math.abs(got - m.value) / m.value > tol;
74
+ failOn(`${asset.name} measure`, bad ? [
75
+ {
76
+ finding: {
77
+ gate: "measure",
78
+ message: got === null ? `'${m.label}': no part matches ${JSON.stringify(m.parts ?? m.between)}` : `${m.label}: ${got.toFixed(3)} m on ${m.axis.toUpperCase()}, want ${m.value} m (±${tol * 100}%)`,
79
+ fix: "rebuild the part from the spec number; never edit the spec to match the model"
80
+ }
81
+ }
82
+ ] : []);
83
+ return got ?? Number.NaN;
84
+ }
85
+ function assertMotionClear(asset, o = {}) {
86
+ failOn(`${asset.name} motion`, compileLods(asset, o).flatMap((a) => clashes2(a).map((finding) => ({ finding, lod: a.lod }))));
87
+ }
88
+ // src/testing/hash.ts
89
+ function hashBytes(bytes) {
90
+ let h1 = 2166136261;
91
+ let h2 = 3421674724;
92
+ for (let i = 0;i < bytes.length; i++) {
93
+ const b = bytes[i] ?? 0;
94
+ h1 = Math.imul(h1 ^ b, 16777619);
95
+ h2 = Math.imul(h2 ^ b ^ h1 >>> 24, 16777619);
96
+ }
97
+ const hex = (n) => (n >>> 0).toString(16).padStart(8, "0");
98
+ return `${hex(h2)}${hex(h1)}`;
99
+ }
100
+ // src/testing/snapshot.ts
101
+ function snapshotHash(asset, o = {}) {
102
+ const a = compile2(asset, {
103
+ lod: o.lod ?? 0,
104
+ ...o.seed !== undefined ? { seed: o.seed } : {}
105
+ });
106
+ return hashBytes(exportGlb2(a, o.glb ?? {}).glb);
107
+ }
108
+ function assertDeterministic(asset, o = {}) {
109
+ for (const lod of o.lods ?? ALL_LODS) {
110
+ const once = snapshotHash(asset, { lod, ...o.seed !== undefined ? { seed: o.seed } : {} });
111
+ const twice = snapshotHash(asset, { lod, ...o.seed !== undefined ? { seed: o.seed } : {} });
112
+ if (once !== twice)
113
+ throw new Ts23dError2("T3D_QA_FAILED", {
114
+ cause: `${asset.name} LOD${lod} is not byte-deterministic (${once} vs ${twice})`,
115
+ fix: "remove ambient state from build(): read ctx.rng/ctx.seed only — no Math.random, Date, or module-level counters"
116
+ });
117
+ }
118
+ }
119
+ export {
120
+ ALL_LODS,
121
+ assertBudget,
122
+ assertDeterministic,
123
+ assertMeasure,
124
+ assertMotionClear,
125
+ assertParts,
126
+ assertQaPass,
127
+ compileLods,
128
+ describeFinding,
129
+ hashBytes,
130
+ snapshotHash
131
+ };
132
+
133
+ //# debugId=DE0A36904BC8E38564756E2164756E21
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/testing/compiled.ts", "../src/testing/assert.ts", "../src/testing/hash.ts", "../src/testing/snapshot.ts"],
4
+ "sourcesContent": [
5
+ "// Shared plumbing for the assertions: compile an asset at the LODs asked for, and turn QA\n// findings into one thrown Ts23dError an agent can act on (code, every finding, the first fix).\n\nimport { type Asset, type CompiledAsset, compile, type Lod, Ts23dError } from '../core/index.ts';\nimport type { Finding } from '../qa/index.ts';\n\nexport interface LodOptions {\n /** LODs to check. Default all three. */\n readonly lods?: readonly Lod[];\n readonly seed?: number;\n}\n\nexport const ALL_LODS: readonly Lod[] = [0, 1, 2];\n\nexport const compileLods = (asset: Asset, o: LodOptions = {}): CompiledAsset[] =>\n (o.lods ?? ALL_LODS).map((lod) =>\n compile(asset, { lod, ...(o.seed !== undefined ? { seed: o.seed } : {}) }),\n );\n\nexport const describeFinding = (f: Finding, lod?: Lod): string =>\n `${lod !== undefined ? `LOD${lod} ` : ''}${f.gate}${f.part ? ` [${f.part}]` : ''}: ${f.message}`;\n\n/** Throw T3D_QA_FAILED listing every finding; no-op when there are none. */\nexport function failOn(what: string, found: ReadonlyArray<{ finding: Finding; lod?: Lod }>): void {\n if (found.length === 0) return;\n const lines = found.map(({ finding, lod }) => ` - ${describeFinding(finding, lod)}`);\n throw new Ts23dError('T3D_QA_FAILED', {\n cause: `${what}: ${found.length} finding(s)\\n${lines.join('\\n')}`,\n fix: found[0]?.finding.fix ?? 'read the findings above',\n });\n}\n",
6
+ "// Assertions for a recipe's own `bun test` (or any runner): each compiles the asset, checks one\n// property, and throws a Ts23dError with the findings and a fix — no test framework imported.\n// it('passes QA', () => assertQaPass(tiger_1));\n// it('has its pivots', () => assertParts(tiger_1, ['turret', 'gun', 'wheel_l_*']));\n\nimport type { Asset, Lod, Measure } from '../core/index.ts';\nimport {\n clashes,\n DEFAULT_GATES,\n DEFAULT_LOD_GATES,\n type Gate,\n glob,\n type LodGate,\n measure,\n runLodQa,\n runQa,\n} from '../qa/index.ts';\nimport { compileLods, failOn, type LodOptions } from './compiled.ts';\n\nexport interface QaOptions extends LodOptions {\n /** Per-LOD gates. Default the build's DEFAULT_GATES. */\n readonly gates?: readonly Gate[];\n /** Chain gates over all compiled LODs. Default DEFAULT_LOD_GATES. */\n readonly lodGates?: readonly LodGate[];\n}\n\n/** The same gate `t3d build` runs: every LOD passes QA, plus the LOD-chain gates. */\nexport function assertQaPass(asset: Asset, o: QaOptions = {}): void {\n const lods = compileLods(asset, o);\n const found = lods.flatMap((a) =>\n runQa(a, o.gates ?? DEFAULT_GATES).map((finding) => ({ finding, lod: a.lod })),\n );\n const chain = lods.length > 1 ? runLodQa(lods, o.lodGates ?? DEFAULT_LOD_GATES) : [];\n failOn(`${asset.name} QA`, [...found, ...chain.map((finding) => ({ finding }))]);\n}\n\n/** Triangles per LOD at or under `caps` (default: the asset's own budget). */\nexport function assertBudget(\n asset: Asset,\n caps: readonly [number, number, number] = asset.budget.triangles,\n o: LodOptions = {},\n): void {\n failOn(\n `${asset.name} budget`,\n compileLods(asset, o).flatMap((a) => {\n const tris = a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);\n const cap = caps[a.lod];\n return tris > cap\n ? [\n {\n lod: a.lod,\n finding: {\n gate: 'budget',\n message: `${tris} triangles > ${cap}`,\n fix: 'drop detail at this LOD (ctx.lod / ctx.pick) — never raise a cap to make a test pass',\n },\n },\n ]\n : [];\n }),\n );\n}\n\n/** Each selector (name or glob) matches at least one part at every LOD checked. */\nexport function assertParts(asset: Asset, selectors: readonly string[], o: LodOptions = {}): void {\n failOn(\n `${asset.name} parts`,\n compileLods(asset, o).flatMap((a) =>\n selectors\n .filter((s) => !a.parts.some((p) => glob(s).test(p.name)))\n .map((s) => ({\n lod: a.lod,\n finding: {\n gate: 'parts',\n message: `no part matches '${s}'`,\n fix: 'engines bind to these names: add the part/group at this LOD (gate its geometry, not its pivot)',\n },\n })),\n ),\n );\n}\n\n/** One spec dimension at LOD0, the way the `measures` gate checks `real.measures`. */\nexport function assertMeasure(asset: Asset, m: Measure, o: { lod?: Lod } = {}): number {\n const [a] = compileLods(asset, { lods: [o.lod ?? 0] });\n const got = a ? measure(a, m) : null;\n const tol = m.tolerance ?? 0.02;\n const bad = got === null || Math.abs(got - m.value) / m.value > tol;\n failOn(\n `${asset.name} measure`,\n bad\n ? [\n {\n finding: {\n gate: 'measure',\n message:\n got === null\n ? `'${m.label}': no part matches ${JSON.stringify(m.parts ?? m.between)}`\n : `${m.label}: ${got.toFixed(3)} m on ${m.axis.toUpperCase()}, want ${m.value} m (±${tol * 100}%)`,\n fix: 'rebuild the part from the spec number; never edit the spec to match the model',\n },\n },\n ]\n : [],\n );\n return got ?? Number.NaN;\n}\n\n/** Every moving part sweeps clear of everything else (the `clashes` gate), at every LOD. */\nexport function assertMotionClear(asset: Asset, o: LodOptions = {}): void {\n failOn(\n `${asset.name} motion`,\n compileLods(asset, o).flatMap((a) => clashes(a).map((finding) => ({ finding, lod: a.lod }))),\n );\n}\n",
7
+ "// A stable content hash for snapshot tests: 64-bit FNV-1a (two 32-bit lanes), pure JS, so the\n// same bytes give the same hex string on Bun, Node and the browser — no runtime hash API needed.\n\nexport function hashBytes(bytes: Uint8Array): string {\n let h1 = 0x811c9dc5;\n let h2 = 0xcbf29ce4;\n for (let i = 0; i < bytes.length; i++) {\n const b = bytes[i] ?? 0;\n h1 = Math.imul(h1 ^ b, 0x01000193);\n h2 = Math.imul(h2 ^ b ^ (h1 >>> 24), 0x01000193);\n }\n const hex = (n: number): string => (n >>> 0).toString(16).padStart(8, '0');\n return `${hex(h2)}${hex(h1)}`;\n}\n",
8
+ "// Determinism and snapshot helpers: the GLB an asset compiles to, hashed. Pin a hash in a test to\n// catch any unintended geometry change; compile twice to prove the recipe has no ambient state.\n\nimport { type Asset, compile, type Lod, Ts23dError } from '../core/index.ts';\nimport { exportGlb, type GlbOptions } from '../export/index.ts';\nimport type { LodOptions } from './compiled.ts';\nimport { ALL_LODS } from './compiled.ts';\nimport { hashBytes } from './hash.ts';\n\nexport interface SnapshotOptions {\n readonly lod?: Lod;\n readonly seed?: number;\n /** Exporter options; default the plain optimised GLB (no textures, no clips). */\n readonly glb?: GlbOptions;\n}\n\n/** 16-hex-digit content hash of the asset's GLB at one LOD. */\nexport function snapshotHash(asset: Asset, o: SnapshotOptions = {}): string {\n const a = compile(asset, {\n lod: o.lod ?? 0,\n ...(o.seed !== undefined ? { seed: o.seed } : {}),\n });\n return hashBytes(exportGlb(a, o.glb ?? {}).glb);\n}\n\n/** Compile + export twice per LOD; the bytes must match. */\nexport function assertDeterministic(asset: Asset, o: LodOptions = {}): void {\n for (const lod of o.lods ?? ALL_LODS) {\n const once = snapshotHash(asset, { lod, ...(o.seed !== undefined ? { seed: o.seed } : {}) });\n const twice = snapshotHash(asset, { lod, ...(o.seed !== undefined ? { seed: o.seed } : {}) });\n if (once !== twice)\n throw new Ts23dError('T3D_QA_FAILED', {\n cause: `${asset.name} LOD${lod} is not byte-deterministic (${once} vs ${twice})`,\n fix: 'remove ambient state from build(): read ctx.rng/ctx.seed only — no Math.random, Date, or module-level counters',\n });\n }\n}\n"
9
+ ],
10
+ "mappings": "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",
11
+ "debugId": "DE0A36904BC8E38564756E2164756E21",
12
+ "names": []
13
+ }
@@ -0,0 +1,13 @@
1
+ import { type Asset, type Lod } from '../core/index.js';
2
+ import { type GlbOptions } from '../export/index.js';
3
+ import type { LodOptions } from './compiled.js';
4
+ export interface SnapshotOptions {
5
+ readonly lod?: Lod;
6
+ readonly seed?: number;
7
+ /** Exporter options; default the plain optimised GLB (no textures, no clips). */
8
+ readonly glb?: GlbOptions;
9
+ }
10
+ /** 16-hex-digit content hash of the asset's GLB at one LOD. */
11
+ export declare function snapshotHash(asset: Asset, o?: SnapshotOptions): string;
12
+ /** Compile + export twice per LOD; the bytes must match. */
13
+ export declare function assertDeterministic(asset: Asset, o?: LodOptions): void;
package/docs/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # ts23d docs
2
+
3
+ **These docs are versioned with the package:** the copy in `node_modules/@developerz.ai/ts23d/docs/` always matches the installed code, and works offline.
4
+
5
+ **One owning doc per fact.** The idea docs say what and why; ADRs record decisions and the options they rejected; the architecture docs say where things go. The code wins on any conflict. Open an issue when you find one.
6
+
7
+ ## Start here
8
+
9
+ | If you want to… | Read |
10
+ |---|---|
11
+ | Know what this is and isn't | [idea/01-vision.md](idea/01-vision.md) |
12
+ | Write a recipe | [idea/02-dsl.md](idea/02-dsl.md) → [idea/09-kinds.md](idea/09-kinds.md) |
13
+ | Drive it from an agent | [idea/05-agent-loop.md](idea/05-agent-loop.md) |
14
+ | Decide if your asset fits | [idea/07-what-fits.md](idea/07-what-fits.md) · [landscape.md](landscape.md) |
15
+ | Add code to the library | [architecture/README.md](architecture/README.md) · [../CONTRIBUTING.md](../CONTRIBUTING.md) |
16
+ | Decode an error | [architecture/errors.md](architecture/errors.md) |
17
+ | See what's built vs planned | [plans/roadmap.md](plans/roadmap.md) |
18
+ | Release a version | [../PUBLISHING.md](../PUBLISHING.md) |
19
+
20
+ ## Idea: what and why
21
+
22
+ | Doc | Covers |
23
+ |---|---|
24
+ | [01-vision.md](idea/01-vision.md) | The pitch, origin, principles, non-goals |
25
+ | [02-dsl.md](idea/02-dsl.md) | `defineAsset`, the `ctx` API, composition with `ctx.use`, determinism rules |
26
+ | [03-ir.md](idea/03-ir.md) | Mesh IR, parts, units, axes, LODs |
27
+ | [04-materials.md](idea/04-materials.md) | Trim sheet, palettes, vertex colour, the procedural sheet painter, TSL / Godot shader sources |
28
+ | [05-agent-loop.md](idea/05-agent-loop.md) | The write → build → shot → read loop, contact sheet layout |
29
+ | [06-qa.md](idea/06-qa.md) | Gates: shipped vs planned |
30
+ | [07-what-fits.md](idea/07-what-fits.md) | Fit matrix by asset category |
31
+ | [08-neural-adapters.md](idea/08-neural-adapters.md) | Optional local-GPU models for organic shapes, image sources |
32
+ | [09-kinds.md](idea/09-kinds.md) | Domain builders: building, vehicle, vessel, vegetation, creature, scene |
33
+
34
+ ## Decisions
35
+
36
+ Index with status: [decisions/README.md](decisions/README.md).
37
+
38
+ ## Architecture: where things go
39
+
40
+ | Doc | Covers |
41
+ |---|---|
42
+ | [architecture/README.md](architecture/README.md) | Module tiers and the import rule |
43
+ | [architecture/adding-a-primitive.md](architecture/adding-a-primitive.md) | New geometry builder |
44
+ | [architecture/adding-a-kind.md](architecture/adding-a-kind.md) | New domain builder |
45
+ | [architecture/errors.md](architecture/errors.md) | Every `T3D_*` code: cause, fix |
46
+
47
+ ## Other
48
+
49
+ | Doc | Covers |
50
+ |---|---|
51
+ | [plans/roadmap.md](plans/roadmap.md) | M0–M6 milestones |
52
+ | [landscape.md](landscape.md) | Neutral survey of AI 3D generators (as of 2026-10) |
@@ -0,0 +1,68 @@
1
+ # Architecture
2
+
3
+ **Imports only go down a tier.** A module imports from strictly lower tiers, never sideways, never up, and only through the other module's `index.ts`. `scripts/boundaries.ts` enforces it in `bun run verify`; the executable copy of the table below is `scripts/lib/tiers.ts`. Change both together.
4
+
5
+ ## Tiers
6
+
7
+ | Tier | Module | Job |
8
+ |---|---|---|
9
+ | 0 | `core` | Errors, seeded RNG, vector math, the Mesh IR, `defineAsset` / `compile`, mesh transforms (`place`), world transforms |
10
+ | 1 | `grammar` | String mini-languages: split grammar, Whyte notation |
11
+ | 1 | `material` | Trim-sheet layout, palettes, `paintTrimSheet`, TSL and Godot shader sources |
12
+ | 2 | `primitives` | Geometry builders → `Mesh`: `box`, `lathe`, `cylinder`, `prism`, `beam`, `planar`, `sweep`, `loft`, `heightfield`, `triangulate`; planned `tiled`, `cell`, `sdf` |
13
+ | 2 | `neural` | Optional AI adapters: local-GPU mesh generators, `importGlb`, image sources (all planned) |
14
+ | 2 | `passes` | Whole-mesh post-processing: `grime`; planned `optimize` (weld, vertex-cache reorder, ACMR) |
15
+ | 3 | `kinds` | Domain builders: building, vehicle, vegetation (`tree`, `trees`); planned vessel, creature |
16
+ | 3 | `qa` | Gates over a `CompiledAsset` |
17
+ | 3 | `export` | `CompiledAsset` → GLB |
18
+ | 3 | `render` | CPU rasteriser, PNG encoder, contact sheet; planned `photoMatch` |
19
+ | 4 | `cli` | `t3d build / shot / hero / review / list` |
20
+ | 4 | `testing` | assertions for users' recipe tests (subpath `/testing`) |
21
+ | 5 | `src/index.ts` | Re-exports every public module |
22
+
23
+ ```
24
+ src/index.ts (5) cli (4)
25
+ │ │
26
+ ┌──────────┬───┴──────┬──────────┤
27
+ kinds qa export render (3)
28
+ │
29
+ primitives neural passes (2)
30
+ │
31
+ grammar material (1)
32
+ │
33
+ core (0)
34
+ ```
35
+
36
+ Rules the guard prints with a `fix:`:
37
+
38
+ | Violation | Fix |
39
+ |---|---|
40
+ | Import from a same or higher tier | Move the shared code lower, or invert the dependency |
41
+ | Deep import (`../core/mesh.ts`) | Import from `../core/index.ts`; export what you need there |
42
+ | Module missing from the tier table | Add it to `scripts/lib/tiers.ts` and this table |
43
+
44
+ ## Data flow
45
+
46
+ ```
47
+ recipe.ts ─► defineAsset ─► compile(asset, { lod }) ─► CompiledAsset { parts[] }
48
+ ├─► runQa ─► Finding[]
49
+ ├─► toGlb ─► .glb
50
+ └─► contactSheet ─► .png
51
+ ```
52
+
53
+ `Mesh` is the only geometry type crossing modules ([IR](../idea/03-ir.md)).
54
+
55
+ ## Module conventions
56
+
57
+ - `index.ts` per module, one header comment stating the module's job, named re-exports only.
58
+ - Declared-but-unbuilt API lives in a `planned.ts` with its final signature, throwing `notImplemented(what, plan?)`. Recipes and docs can target it; it fails loudly until built.
59
+ - Public subpath per module: `@developerz.ai/ts23d/<module>` ([ADR 006](../decisions/006-single-package-subpath-exports.md)).
60
+
61
+ ## Guides
62
+
63
+ | Doc | |
64
+ |---|---|
65
+ | [adding-a-primitive.md](adding-a-primitive.md) | New geometry builder |
66
+ | [adding-a-kind.md](adding-a-kind.md) | New domain builder |
67
+ | [errors.md](errors.md) | Every `T3D_*` code |
68
+ | [glb-production.md](glb-production.md) | Production GLB rules, validator results, per-engine import table, gap list |
@@ -0,0 +1,44 @@
1
+ # Adding a kind
2
+
3
+ **A kind is a library of builder functions `(ctx, options) => void | value`.** Builders add named parts to the caller's `ctx`; the recipe keeps control of layout, naming and LOD. It lives in `src/kinds/` (tier 3) and may import `core`, `grammar`, `material`, `primitives`, `neural`, `passes`.
4
+
5
+ ## Checklist
6
+
7
+ | Step | Rule |
8
+ |---|---|
9
+ | 1. Signature | If it's in `src/kinds/planned.ts` (today only `creature`), keep that signature and move it to its own file |
10
+ | 2. Duplicate check | Read the shipped kinds first — `building.ts` (`facade`, `setbacks`), `vehicle.ts` (`axle`, `whyteAxles`, `trackRun`, `propeller`), `vegetation.ts` (`tree`, `trees`, `treeReal`), `roof.ts` (`roof`, `roofMesh`), `openings.ts` (`opening`), `mass.ts`, `hull.ts`, `head.ts` (`head`, `HEADS`). Extend one of them; don't re-add what already ships |
11
+ | 3. Domain params | Take the parameters the domain uses: Whyte `2-6-2`, length/beam/draft, species + height, roof pitch in degrees. Not raw coordinates |
12
+ | 4. Names | Every part prefixed by a caller-supplied `prefix`/`name`. Follow the engine contract: `axle_*` / `wheel_*` spin about Y, `prop_*` / `rotor_*` about their own axis, `door_*` hinge at the pivot |
13
+ | 5. Pivots | Movable pieces get a `ctx.group` at the hinge, the mesh inside it. Yaw on the group, motion on the child |
14
+ | 6. LOD | Read `ctx.lod`: drop details, lower segments. Masses survive to LOD2 |
15
+ | 7. Randomness | Only `ctx.rng.fork('<kind>:<prefix>')`, so adding the kind doesn't shift other draws |
16
+ | 8. Real units | Defaults from real references, cited in a comment (e.g. standard gauge 1.435 m) |
17
+ | 9. Export | Named export from `src/kinds/index.ts` |
18
+ | 10. Test | Next to it: part names, part count per LOD, QA passes on a minimal asset using it |
19
+ | 11. Recipe | At least one `recipes/*/*.ts` using it. Its contact sheet is the review artefact |
20
+ | 12. Docs | [09-kinds](../idea/09-kinds.md), [07-what-fits](../idea/07-what-fits.md), [roadmap](../plans/roadmap.md), `CHANGELOG.md` |
21
+
22
+ ## Shape
23
+
24
+ ```ts
25
+ // Vessel helpers: hulls from naval-architecture params, lofted from sections.
26
+
27
+ import type { BuildContext, Vec3 } from '../core/index.ts';
28
+ import { loft } from '../primitives/index.ts';
29
+
30
+ export interface HullOptions {
31
+ readonly length: number; // LOA, metres
32
+ readonly beam: number;
33
+ readonly draft: number;
34
+ readonly freeboard: number;
35
+ readonly color: Vec3;
36
+ }
37
+
38
+ export function hull(ctx: BuildContext, o: HullOptions): void {
39
+ const stations = [24, 12, 6][ctx.lod] ?? 6;
40
+ // sections from sheer + keel curves → loft → ctx.part('hull', …)
41
+ }
42
+ ```
43
+
44
+ A kind that needs a missing primitive adds the primitive first ([adding-a-primitive.md](adding-a-primitive.md)), in its own PR.
@@ -0,0 +1,49 @@
1
+ # Adding a primitive
2
+
3
+ **A primitive is a pure function `(options) => Mesh`.** No `ctx`, no parts, no RNG unless a `seed` is an option. It lives in `src/primitives/` (tier 2) and may import `core` and `material` only.
4
+
5
+ ## Checklist
6
+
7
+ | Step | Rule |
8
+ |---|---|
9
+ | 1. Signature | One file per primitive (`tiled.ts`, `lattice.ts`). Every planned primitive has shipped; a new one is a new file |
10
+ | 2. Options | Readonly interface `XOptions`, exported. Sizes in metres. `color: Vec3` (linear), `strip?: StripId` (default `'plain'`), `at?: Vec3` |
11
+ | 3. Placement | Base at `at` (z = 0 = ground) unless the shape has a natural centre (axles, `lathe` with `axis`). Say which in the doc comment |
12
+ | 4. Geometry | `MeshBuilder`: `vertex({ p, n, uv, color })`, `quad(a, b, c, d)` counter-clockwise from outside |
13
+ | 5. UVs | Map into `stripRect(strip)` by world metres, so texel density matches across assets. V cannot wrap: cut faces into bands of the strip's V metres (see `strip-uv.ts`), or mirror V for organic surfaces. `box` still stretches V over the face; use `tiled` or kinds' `mass` for true-scale walls |
14
+ | 6. LOD | Take a resolution option (`segments`, `resolution`). The recipe picks it from `ctx.lod`; the primitive never reads LOD |
15
+ | 7. Contact | If the shape rests on something, put a vertex at the contact point (see the `lathe` ring offset; the floating-parts gate caught a coarse wheel missing its rail) |
16
+ | 8. Export | Named export from `src/primitives/index.ts` |
17
+ | 9. Test | `x.test.ts` next to it (see `prism.test.ts`): triangle count, bounds, normals point out, deterministic output |
18
+ | 10. Use it | Add or update a recipe in `recipes/` that uses it, so `scripts/recipes.ts` gates it |
19
+ | 11. Docs | Tick it in [roadmap](../plans/roadmap.md); update [07-what-fits](../idea/07-what-fits.md) if fit changed; add to `CHANGELOG.md` under `[Unreleased]` |
20
+
21
+ ## Skeleton
22
+
23
+ Real shipped references: `prism.ts`, `beam.ts`, `planar.ts`, `tiled.ts`, `lattice.ts`.
24
+
25
+ ```ts
26
+ // A quad tiling one trim strip at true scale: walls, roofs, decks.
27
+
28
+ import { cross, length, type Mesh, MeshBuilder, normalize, type Vec3 } from '../core/index.ts';
29
+ import { type StripId, stripRect } from '../material/index.ts';
30
+
31
+ export interface TiledOptions {
32
+ readonly origin: Vec3; // one corner
33
+ readonly u: Vec3; // edge along the strip, metres
34
+ readonly v: Vec3; // the other edge
35
+ readonly color: Vec3;
36
+ readonly strip: StripId;
37
+ }
38
+
39
+ export function tiled(o: TiledOptions): Mesh {
40
+ const rect = stripRect(o.strip);
41
+ const n = normalize(cross(o.u, o.v));
42
+ const reps = length(o.u) / rect.metres; // true-scale tiling along U
43
+ const b = new MeshBuilder();
44
+ // 4 vertices: origin, +u, +u+v, +v; uv u from 0 to reps across the strip's rect
45
+ return b.build();
46
+ }
47
+ ```
48
+
49
+ Done = `bun run verify` green, including the recipe that uses it.