@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
package/src/cli/bin.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env bun
2
+ // `t3d` — compile TypeScript recipes to GLB, gate them, and shoot contact sheets. `t3d help`
3
+ // prints the commands; src/cli/args.ts holds the flags, src/cli/run.ts the output layout.
4
+
5
+ import { run } from './run.ts';
6
+
7
+ process.exitCode = await run(process.argv.slice(2));
@@ -0,0 +1,17 @@
1
+ // Load a recipe module and collect every exported Asset. Recipes are plain TS modules: the CLI,
2
+ // the site and tests all import them the same way.
3
+
4
+ import { resolve } from 'node:path';
5
+ import { type Asset, isAsset, Ts23dError } from '../core/index.ts';
6
+
7
+ export async function loadAssets(file: string): Promise<Asset[]> {
8
+ const mod: Record<string, unknown> = await import(resolve(file));
9
+ const assets = Object.values(mod).filter(isAsset);
10
+ if (assets.length === 0) {
11
+ throw new Ts23dError('T3D_NO_ASSET_EXPORT', {
12
+ cause: `${file} exports no asset`,
13
+ fix: 'export const my_thing = defineAsset({ … }) — named export, no default',
14
+ });
15
+ }
16
+ return assets;
17
+ }
@@ -0,0 +1,95 @@
1
+ // manifest.json per asset: what an engine pipeline or an agent needs without opening a GLB —
2
+ // files, triangles per LOD, pivots (the names game code binds to), emitters, surfaces, QA,
3
+ // measures with their measured values, sources.
4
+
5
+ import type { Asset, CompiledAsset } from '../core/index.ts';
6
+ import { mergeToPivots } from '../export/index.ts';
7
+ import { type Finding, measure } from '../qa/index.ts';
8
+
9
+ export interface AssetManifest {
10
+ readonly name: string;
11
+ readonly kind: string;
12
+ readonly description?: string;
13
+ readonly tags: readonly string[];
14
+ readonly files: {
15
+ readonly lods: readonly string[];
16
+ /** All built LODs in one GLB (MSFT_lod), when `--lods single|both`. */
17
+ readonly single?: string;
18
+ readonly hero?: string;
19
+ readonly sheet?: string;
20
+ readonly thumb?: string;
21
+ };
22
+ readonly triangles: readonly number[];
23
+ /** MSFT_screencoverage written into `files.single`: where each LOD hands over (last = cull). */
24
+ readonly lodCoverage?: readonly number[];
25
+ readonly drawCalls: readonly number[];
26
+ readonly pivots: readonly { name: string; parent: string | null; motion?: string }[];
27
+ readonly emitters: readonly { name: string; kind: string; when: string }[];
28
+ readonly sockets: readonly { name: string; kind: string; parent: string | null }[];
29
+ readonly surfaces: readonly string[];
30
+ readonly real?: { length: number; width: number; height: number };
31
+ readonly measures: readonly {
32
+ label: string;
33
+ spec: number;
34
+ measured: number | null;
35
+ source?: string;
36
+ }[];
37
+ readonly sources: readonly string[];
38
+ readonly qa: { readonly pass: boolean; readonly findings: readonly Finding[] };
39
+ }
40
+
41
+ const tris = (a: CompiledAsset): number =>
42
+ a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
43
+
44
+ export function manifestOf(
45
+ asset: Asset,
46
+ lods: readonly CompiledAsset[],
47
+ files: AssetManifest['files'],
48
+ findings: readonly Finding[],
49
+ lodCoverage?: readonly number[],
50
+ ): AssetManifest {
51
+ const top = lods[0];
52
+ const merged = top ? mergeToPivots(top) : null;
53
+ return {
54
+ name: asset.name,
55
+ kind: asset.kind,
56
+ ...(asset.description ? { description: asset.description } : {}),
57
+ tags: asset.tags ?? [],
58
+ files,
59
+ triangles: lods.map(tris),
60
+ ...(lodCoverage?.length ? { lodCoverage } : {}),
61
+ // One draw per merged mesh, glowing parts in their own emissive layer (as the GLB writes them).
62
+ drawCalls: lods.map(
63
+ (l) =>
64
+ mergeToPivots(
65
+ l,
66
+ (l.glow ?? []).map((g) => g.parts),
67
+ ).parts.filter((p) => p.mesh).length,
68
+ ),
69
+ pivots: (merged?.parts ?? [])
70
+ .filter((p) => !p.emitter && !p.socket)
71
+ .map((p) => ({
72
+ name: p.name,
73
+ parent: p.parent,
74
+ ...(p.motion ? { motion: p.motion.kind } : {}),
75
+ })),
76
+ emitters: (top?.parts ?? []).flatMap((p) =>
77
+ p.emitter ? [{ name: p.name, kind: p.emitter.kind, when: p.emitter.when ?? 'always' }] : [],
78
+ ),
79
+ sockets: (top?.parts ?? []).flatMap((p) =>
80
+ p.socket ? [{ name: p.name, kind: p.socket.kind, parent: p.parent }] : [],
81
+ ),
82
+ surfaces: [...new Set((top?.parts ?? []).map((p) => p.surface ?? 'opaque'))].sort(),
83
+ ...(asset.real
84
+ ? { real: { length: asset.real.length, width: asset.real.width, height: asset.real.height } }
85
+ : {}),
86
+ measures: (asset.real?.measures ?? []).map((m) => ({
87
+ label: m.label,
88
+ spec: m.value,
89
+ measured: top ? measure(top, m) : null,
90
+ ...(m.source ? { source: m.source } : {}),
91
+ })),
92
+ sources: asset.sources ?? [],
93
+ qa: { pass: findings.length === 0, findings },
94
+ };
95
+ }
@@ -0,0 +1,66 @@
1
+ // `t3d hero` and `t3d review`: beauty renders and the AI review pack, one folder per asset.
2
+ // <out>/<asset>/hero.png
3
+ // <out>/<asset>/review/{hero,sheet,detail_1,detail_2,lods}.png review.md review.json
4
+
5
+ import { mkdir } from 'node:fs/promises';
6
+ import { dirname, join, relative, resolve } from 'node:path';
7
+ import { type Asset, compile } from '../core/index.ts';
8
+ import type { PaintedSheet } from '../material/index.ts';
9
+ import { beauty, type ReviewWarning, reviewPack } from '../render/index.ts';
10
+ import type { Args } from './args.ts';
11
+
12
+ export interface RenderResult {
13
+ readonly name: string;
14
+ readonly files: readonly string[];
15
+ readonly warnings: readonly ReviewWarning[];
16
+ }
17
+
18
+ export async function heroOne(
19
+ asset: Asset,
20
+ args: Args,
21
+ sheet: PaintedSheet,
22
+ ): Promise<RenderResult> {
23
+ const dir = join(args.out, asset.name);
24
+ await mkdir(dir, { recursive: true });
25
+ const path = join(dir, 'hero.png');
26
+ await Bun.write(path, beauty(compile(asset, { lod: 0 }), { ...args.render, sheet }));
27
+ return { name: asset.name, files: [path], warnings: [] };
28
+ }
29
+
30
+ /** The dossier: README.md next to the recipe (recipes/<name>/), else a legacy refs/<name>.md. */
31
+ async function refsFor(name: string, recipe: string): Promise<string | undefined> {
32
+ for (const p of [
33
+ join(dirname(resolve(recipe)), 'README.md'),
34
+ join(dirname(resolve(recipe)), '..', 'refs', `${name}.md`),
35
+ resolve('refs', `${name}.md`),
36
+ ])
37
+ if (await Bun.file(p).exists()) return relative(process.cwd(), p) || p;
38
+ return undefined;
39
+ }
40
+
41
+ export async function reviewOne(
42
+ asset: Asset,
43
+ recipe: string,
44
+ args: Args,
45
+ sheet: PaintedSheet,
46
+ ): Promise<RenderResult> {
47
+ const dir = join(args.out, asset.name, 'review');
48
+ await mkdir(dir, { recursive: true });
49
+ const refs = await refsFor(asset.name, recipe);
50
+ const pack = reviewPack(asset, { ...args.render, sheet, ...(refs ? { refs } : {}) });
51
+ const files: string[] = [];
52
+ for (const [name, bytes] of pack.files) {
53
+ const path = join(dir, name);
54
+ await Bun.write(path, bytes);
55
+ files.push(path);
56
+ }
57
+ return { name: asset.name, files, warnings: pack.metrics.warnings };
58
+ }
59
+
60
+ export function printRendered(r: RenderResult): void {
61
+ console.log(
62
+ `${r.name} ${r.warnings.length ? `${r.warnings.length} warning(s)` : 'no warnings'}`,
63
+ );
64
+ for (const f of r.files) console.log(` → ${f}`);
65
+ for (const w of r.warnings) console.log(` ! ${w.code}: ${w.message}\n fix: ${w.fix}`);
66
+ }
package/src/cli/run.ts ADDED
@@ -0,0 +1,192 @@
1
+ // Command dispatch for `t3d`. Pure function of argv → exit code, so it is testable in-process.
2
+ // Output layout, one folder per asset (stable paths agents and pipelines can rely on):
3
+ // <out>/<asset>/lod0.glb lod1.glb lod2.glb <asset>.glb (all LODs, MSFT_lod) hero.png thumb.png
4
+ // sheet.png manifest.json
5
+ // <out>/<asset>/review/… (t3d review) · <out>/index.json — every asset built, with its QA verdict
6
+
7
+ import { mkdir } from 'node:fs/promises';
8
+ import { join } from 'node:path';
9
+ import { type Asset, compile, type Lod, Ts23dError } from '../core/index.ts';
10
+ import {
11
+ exportLodGlb,
12
+ SHEET_MAPS,
13
+ type SheetBinding,
14
+ sheetFileName,
15
+ sheetPngs,
16
+ toGlb,
17
+ } from '../export/index.ts';
18
+ import {
19
+ DEFAULT_SHEET,
20
+ type PaintedSheet,
21
+ paintTrimSheet,
22
+ provideTextures,
23
+ } from '../material/index.ts';
24
+ import { type Finding, runQa } from '../qa/index.ts';
25
+ import { beauty, contactSheet, thumbnail } from '../render/index.ts';
26
+ import { type Args, HELP, parseArgs } from './args.ts';
27
+ import { loadAssets } from './load.ts';
28
+ import { type AssetManifest, manifestOf } from './manifest.ts';
29
+ import { heroOne, printRendered, type RenderResult, reviewOne } from './render-cmds.ts';
30
+ import { loadTextureCache } from './texture-cache.ts';
31
+
32
+ async function buildOne(
33
+ asset: Asset,
34
+ args: Args,
35
+ texture: SheetBinding | undefined,
36
+ painted: PaintedSheet | undefined,
37
+ ): Promise<AssetManifest> {
38
+ const dir = join(args.out, asset.name);
39
+ await mkdir(dir, { recursive: true });
40
+ // Only the LODs the user asked for: QA, GLBs and the manifest cover exactly those.
41
+ const lods = args.lods.map((lod: Lod) => compile(asset, { lod }));
42
+ const findings: Finding[] = lods.flatMap((a) =>
43
+ runQa(a).map((f) => ({ ...f, message: `LOD${a.lod}: ${f.message}` })),
44
+ );
45
+ const glbs: string[] = [];
46
+ const glbOptions = { animations: args.animations, ...(texture ? { sheet: texture } : {}) };
47
+ let single: string | undefined;
48
+ let coverage: readonly number[] = [];
49
+ if (args.cmd === 'build' && args.lodFiles !== 'single') {
50
+ for (const a of lods) {
51
+ const path = join(dir, `lod${a.lod}.glb`);
52
+ await Bun.write(path, toGlb(a, glbOptions));
53
+ glbs.push(path);
54
+ }
55
+ }
56
+ if (args.cmd === 'build' && args.lodFiles !== 'split') {
57
+ single = join(dir, `${asset.name}.glb`);
58
+ const packed = exportLodGlb(lods, glbOptions);
59
+ coverage = packed.coverage;
60
+ await Bun.write(single, packed.glb);
61
+ }
62
+ let sheet: string | undefined;
63
+ let thumb: string | undefined;
64
+ let hero: string | undefined;
65
+ if (args.cmd === 'build' && args.shot) {
66
+ hero = join(dir, 'hero.png');
67
+ const top = lods[0] ?? compile(asset);
68
+ await Bun.write(hero, beauty(top, { ...args.render, ...(painted ? { sheet: painted } : {}) }));
69
+ }
70
+ if (args.cmd === 'shot' || args.shot) {
71
+ thumb = join(dir, 'thumb.png');
72
+ await Bun.write(thumb, thumbnail(lods[0] ?? compile(asset)));
73
+ sheet = join(dir, 'sheet.png');
74
+ await Bun.write(
75
+ sheet,
76
+ contactSheet(lods[0] ?? compile(asset), { far: lods.at(-1) ?? compile(asset, { lod: 2 }) }),
77
+ );
78
+ }
79
+ const manifest = manifestOf(
80
+ asset,
81
+ lods,
82
+ {
83
+ lods: glbs,
84
+ ...(single ? { single } : {}),
85
+ ...(hero ? { hero } : {}),
86
+ ...(sheet ? { sheet } : {}),
87
+ ...(thumb ? { thumb } : {}),
88
+ },
89
+ findings,
90
+ coverage,
91
+ );
92
+ await Bun.write(join(dir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
93
+ return manifest;
94
+ }
95
+
96
+ function print(m: AssetManifest): void {
97
+ console.log(
98
+ `${m.name} QA ${m.qa.pass ? 'PASS' : 'FAIL'} tris ${m.triangles.join('/')} draws ${m.drawCalls.join('/')}`,
99
+ );
100
+ for (const f of [
101
+ ...m.files.lods,
102
+ ...(m.files.single ? [m.files.single] : []),
103
+ ...(m.files.hero ? [m.files.hero] : []),
104
+ ...(m.files.sheet ? [m.files.sheet] : []),
105
+ ])
106
+ console.log(` → ${f}`);
107
+ for (const f of m.qa.findings)
108
+ console.log(` ✗ ${f.gate}${f.part ? ` [${f.part}]` : ''}: ${f.message}\n fix: ${f.fix}`);
109
+ }
110
+
111
+ const COMMANDS = ['build', 'shot', 'list', 'hero', 'review'];
112
+
113
+ function fail(e: unknown, json: boolean): number {
114
+ if (!(e instanceof Ts23dError)) throw e;
115
+ console.error(
116
+ json ? JSON.stringify({ ok: false, error: e.toJSON() }) : `${e.message}\n fix: ${e.fix}`,
117
+ );
118
+ return 1;
119
+ }
120
+
121
+ export async function run(argv: readonly string[]): Promise<number> {
122
+ let args: Args;
123
+ try {
124
+ args = parseArgs(argv);
125
+ } catch (e) {
126
+ return fail(e, argv.includes('--json'));
127
+ }
128
+ if (!COMMANDS.includes(args.cmd) || args.files.length === 0) {
129
+ console.log(HELP);
130
+ return args.cmd === 'help' ? 0 : 2;
131
+ }
132
+ const built: AssetManifest[] = [];
133
+ const rendered: RenderResult[] = [];
134
+ // Paint the trim sheet once per run (~4 s at 2048²). External mode writes it once into <out>/
135
+ // and every GLB in <out>/<asset>/ references ../trim_sheet_*.png — one texture set for all.
136
+ // The beauty renders sample the same painted sheet.
137
+ const renders = args.cmd === 'hero' || args.cmd === 'review';
138
+ const wantsSheet = (args.cmd === 'build' && args.sheet !== 'none') || renders;
139
+ if (wantsSheet) provideTextures(await loadTextureCache());
140
+ const painted = wantsSheet
141
+ ? paintTrimSheet({ ...DEFAULT_SHEET, size: args.sheetSize })
142
+ : undefined;
143
+ let texture: SheetBinding | undefined;
144
+ if (args.cmd === 'build' && args.sheet !== 'none' && painted) {
145
+ if (args.sheet === 'embed') texture = painted;
146
+ else {
147
+ const pngs = sheetPngs(painted);
148
+ await mkdir(args.out, { recursive: true });
149
+ for (const map of SHEET_MAPS) await Bun.write(join(args.out, sheetFileName(map)), pngs[map]);
150
+ texture = { external: true, prefix: '../trim_sheet' };
151
+ }
152
+ }
153
+ try {
154
+ for (const file of args.files) {
155
+ for (const asset of await loadAssets(file)) {
156
+ if (args.cmd === 'list')
157
+ console.log(
158
+ args.json ? JSON.stringify({ asset: asset.name, file }) : `${asset.name} ${file}`,
159
+ );
160
+ else if (renders && painted)
161
+ rendered.push(
162
+ args.cmd === 'hero'
163
+ ? await heroOne(asset, args, painted)
164
+ : await reviewOne(asset, file, args, painted),
165
+ );
166
+ else built.push(await buildOne(asset, args, texture, painted));
167
+ }
168
+ }
169
+ } catch (e) {
170
+ return fail(e, args.json);
171
+ }
172
+ if (args.cmd === 'list') return 0;
173
+ if (renders) {
174
+ if (args.json) console.log(JSON.stringify({ ok: true, data: rendered }));
175
+ else rendered.forEach(printRendered);
176
+ return 0;
177
+ }
178
+ const ok = built.every((m) => m.qa.pass);
179
+ const index = built.map((m) => ({
180
+ name: m.name,
181
+ qa: m.qa.pass ? 'PASS' : 'FAIL',
182
+ triangles: m.triangles,
183
+ manifest: join(m.name, 'manifest.json'),
184
+ }));
185
+ await Bun.write(
186
+ join(args.out, 'index.json'),
187
+ `${JSON.stringify({ ok, assets: index }, null, 2)}\n`,
188
+ );
189
+ if (args.json) console.log(JSON.stringify({ ok, data: built }));
190
+ else built.forEach(print);
191
+ return ok ? 0 : 1;
192
+ }
@@ -0,0 +1,24 @@
1
+ // The local CC0 texture cache (`bun run textures` fills it): every file re-verified by its sha256
2
+ // name, handed to `provideTextures` before painting. Missing files are fine here; painting a
3
+ // material that needs one throws T3D_TEXTURE_MISSING with the fetch command.
4
+
5
+ import { readdir } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import type { TextureCache } from '../material/index.ts';
8
+
9
+ export const TEXTURE_CACHE = join('node_modules', '.cache', 'ts23d', 'textures');
10
+
11
+ const sha256 = (b: Uint8Array): string => new Bun.CryptoHasher('sha256').update(b).digest('hex');
12
+
13
+ /** Every cached map whose bytes still match its sha256 name. */
14
+ export async function loadTextureCache(dir = TEXTURE_CACHE): Promise<TextureCache> {
15
+ const out = new Map<string, Uint8Array>();
16
+ const names = await readdir(dir).catch(() => [] as string[]);
17
+ for (const name of names.sort()) {
18
+ const m = /^([0-9a-f]{64})\.png$/.exec(name);
19
+ if (!m?.[1]) continue;
20
+ const bytes = new Uint8Array(await Bun.file(join(dir, name)).arrayBuffer());
21
+ if (sha256(bytes) === m[1]) out.set(m[1], bytes);
22
+ }
23
+ return out;
24
+ }
@@ -0,0 +1,129 @@
1
+ // The DSL's root: an asset is a TypeScript function that builds named parts into a scene.
2
+ // Code, not JSON — loops, helpers and composition are the point (docs/decisions/002).
3
+
4
+ import type { Asset, AssetDefinition, AssetMeta, CompiledAsset } from './definition.ts';
5
+ import { Ts23dError } from './errors.ts';
6
+ import type { Mesh } from './mesh.ts';
7
+ import type { Lod, Part, PartOptions, UseOptions } from './part.ts';
8
+ import { createRng } from './rng.ts';
9
+
10
+ export * from './definition.ts';
11
+ export * from './part.ts';
12
+
13
+ export function defineAsset(def: AssetDefinition): Asset {
14
+ if (!/^[a-z][a-z0-9_]*$/.test(def.name)) {
15
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
16
+ cause: `asset name '${def.name}' is not snake_case`,
17
+ fix: 'rename it to [a-z][a-z0-9_]* — it becomes the GLB file name and the root node',
18
+ });
19
+ }
20
+ return { ...def, __ts23d: 'asset' };
21
+ }
22
+
23
+ export const isAsset = (value: unknown): value is Asset =>
24
+ typeof value === 'object' &&
25
+ value !== null &&
26
+ (value as { __ts23d?: unknown }).__ts23d === 'asset';
27
+
28
+ export function compile(asset: Asset, options: { lod?: Lod; seed?: number } = {}): CompiledAsset {
29
+ const lod = options.lod ?? 0;
30
+ const seed = options.seed ?? asset.seed ?? 1;
31
+ const parts = new Map<string, Part>();
32
+ const add = (name: string, mesh: Mesh | null, o: PartOptions = {}): void => {
33
+ if (parts.has(name)) {
34
+ throw new Ts23dError('T3D_PART_DUPLICATE', {
35
+ cause: `part '${name}' was added twice in '${asset.name}'`,
36
+ fix: 'give each part a unique name — suffix the loop index, e.g. wheel_0, wheel_1',
37
+ });
38
+ }
39
+ if (o.parent !== undefined && !parts.has(o.parent)) {
40
+ throw new Ts23dError('T3D_PART_UNKNOWN', {
41
+ cause: `part '${name}' parents to '${o.parent}', which does not exist yet`,
42
+ fix: 'add the parent (ctx.group or ctx.part) before its children',
43
+ });
44
+ }
45
+ if (o.motion && o.yaw) {
46
+ throw new Ts23dError('T3D_RECIPE_INVALID', {
47
+ cause: `part '${name}' sets both yaw and motion`,
48
+ fix: 'put the yaw on a parent ctx.group and the motion on the part inside it',
49
+ });
50
+ }
51
+ parts.set(name, {
52
+ name,
53
+ mesh,
54
+ at: o.at ?? [0, 0, 0],
55
+ parent: o.parent ?? null,
56
+ yaw: o.yaw ?? 0,
57
+ motion: o.motion ?? null,
58
+ ...(o.clearOf ? { clearOf: o.clearOf } : {}),
59
+ ...(o.surface && o.surface !== 'opaque' ? { surface: o.surface } : {}),
60
+ });
61
+ };
62
+ const use = (child: Asset, o: UseOptions): void => {
63
+ const sub = compile(child, { lod, seed: o.seed ?? seed });
64
+ add(o.prefix, null, {
65
+ at: o.at ?? [0, 0, 0],
66
+ yaw: o.yaw ?? 0,
67
+ ...(o.parent ? { parent: o.parent } : {}),
68
+ });
69
+ for (const p of sub.parts) {
70
+ add(`${o.prefix}_${p.name}`, p.mesh, {
71
+ at: p.at,
72
+ yaw: p.yaw,
73
+ parent: p.parent ? `${o.prefix}_${p.parent}` : o.prefix,
74
+ ...(p.motion ? { motion: p.motion } : {}),
75
+ ...(p.surface ? { surface: p.surface } : {}),
76
+ });
77
+ const named = parts.get(`${o.prefix}_${p.name}`);
78
+ if (named && (p.emitter || p.socket)) {
79
+ parts.set(named.name, {
80
+ ...named,
81
+ ...(p.emitter ? { emitter: p.emitter } : {}),
82
+ ...(p.socket ? { socket: p.socket } : {}),
83
+ });
84
+ }
85
+ }
86
+ };
87
+ asset.build({
88
+ lod,
89
+ seed,
90
+ rng: createRng(seed),
91
+ pick: (perLod) => perLod[lod],
92
+ part: add,
93
+ group: (name, o) => add(name, null, o),
94
+ emitter: (name, { at, parent, ...emitter }) => {
95
+ add(name, null, { ...(at ? { at } : {}), ...(parent ? { parent } : {}) });
96
+ const p = parts.get(name);
97
+ if (p) parts.set(name, { ...p, emitter });
98
+ },
99
+ socket: (name, { at, parent, ...socket }) => {
100
+ add(name, null, { ...(at ? { at } : {}), ...(parent ? { parent } : {}) });
101
+ const p = parts.get(name);
102
+ if (p) parts.set(name, { ...p, socket });
103
+ },
104
+ use,
105
+ });
106
+ return {
107
+ name: asset.name,
108
+ kind: asset.kind,
109
+ lod,
110
+ budget: asset.budget,
111
+ real: asset.real ?? null,
112
+ checks: asset.checks ?? [],
113
+ parts: [...parts.values()],
114
+ ...metaOf(asset),
115
+ ...(asset.glow?.length ? { glow: asset.glow } : {}),
116
+ ...(asset.lodCoverage ? { lodCoverage: asset.lodCoverage } : {}),
117
+ };
118
+ }
119
+
120
+ function metaOf(a: Asset): { meta?: AssetMeta } {
121
+ const meta: AssetMeta = {
122
+ ...(a.description ? { description: a.description } : {}),
123
+ ...(a.sources?.length ? { sources: a.sources } : {}),
124
+ ...(a.license ? { license: a.license } : {}),
125
+ ...(a.copyright ? { copyright: a.copyright } : {}),
126
+ ...(a.creators?.length ? { creators: a.creators } : {}),
127
+ };
128
+ return Object.keys(meta).length ? { meta } : {};
129
+ }
@@ -0,0 +1,156 @@
1
+ // The asset contract: what defineAsset takes (AssetDefinition), what build() receives
2
+ // (BuildContext), budgets, real-world dimensions, measures, checks, and the compiled result.
3
+
4
+ import type { Mesh } from './mesh.ts';
5
+ import type { EmitterOptions, Lod, Part, PartOptions, SocketOptions, UseOptions } from './part.ts';
6
+ import type { Rng } from './rng.ts';
7
+
8
+ export interface BuildContext {
9
+ readonly lod: Lod;
10
+ readonly seed: number;
11
+ readonly rng: Rng;
12
+ /** The value for this LOD: `ctx.pick([24, 12, 6])` — segments, counts, sizes per detail level. */
13
+ pick<T>(perLod: readonly [T, T, T]): T;
14
+ /** Add a named part. Names are the contract with the engine: `axle_*`, `door_*`, `rotor_*`. */
15
+ part(name: string, mesh: Mesh | null, options?: PartOptions): void;
16
+ /** An empty pivot that other parts parent to. */
17
+ group(name: string, options?: PartOptions): void;
18
+ /** Declare a particle source (smoke, steam, dust, muzzle…) as a named node. */
19
+ emitter(name: string, options: EmitterOptions): void;
20
+ /** Declare an attachment point (seat, mount, camera, audio, light…) as a named node. */
21
+ socket(name: string, options: SocketOptions): void;
22
+ /** Instance another asset inside this one, compiled at this LOD — the composition primitive. */
23
+ use(asset: Asset, options: UseOptions): void;
24
+ }
25
+
26
+ export interface Budget {
27
+ /** Triangle ceiling per LOD; QA fails the build above it. */
28
+ readonly triangles: readonly [number, number, number];
29
+ }
30
+
31
+ /** Real-world overall dimensions (metres) the QA `proportions` gate holds the model to. */
32
+ export interface RealDimensions {
33
+ /** Along X (front-back). */
34
+ readonly length: number;
35
+ /** Along Y. */
36
+ readonly width: number;
37
+ /** Along Z. */
38
+ readonly height: number;
39
+ /** Allowed relative error per axis; default 0.05. */
40
+ readonly tolerance?: number;
41
+ /** Named spec-sheet dimensions checked against the model (QA `measures`). */
42
+ readonly measures?: readonly Measure[];
43
+ }
44
+
45
+ /**
46
+ * One real-world dimension from a source, checked against the model at LOD0: the world-space
47
+ * extent of `parts` (union of their bounds) along `axis`, or the distance between two parts'
48
+ * centres. E.g. driver diameter = extent of `axle_driver_0` on Z; gauge = centre distance of the
49
+ * two rail parts on Y; turret width = extent of `turret_body` on Y.
50
+ */
51
+ export interface Measure {
52
+ readonly label: string;
53
+ readonly value: number;
54
+ readonly axis: 'x' | 'y' | 'z';
55
+ /** Extent of these parts (prefix match: 'axle_driver_0' includes its wheels). */
56
+ readonly parts?: readonly string[];
57
+ /** Or: distance between the centres of exactly two parts along `axis`. */
58
+ readonly between?: readonly [string, string];
59
+ readonly tolerance?: number;
60
+ /** Where this number comes from (one of `sources`, or a page reference). */
61
+ readonly source?: string;
62
+ }
63
+
64
+ /** One problem a recipe's own check found. QA turns these into findings named `check`. */
65
+ export interface CheckFinding {
66
+ readonly message: string;
67
+ readonly fix: string;
68
+ readonly part?: string;
69
+ }
70
+
71
+ /**
72
+ * A recipe-declared assertion about its own geometry, run by the QA gate at every LOD:
73
+ * "tracks clear the hull", "wheels never overlap", "left matches right". Plain functions —
74
+ * build them with the helpers in /qa (`clear`, `touches`, `noOverlap`, `symmetric`) or write one.
75
+ */
76
+ export type Check = (asset: CompiledAsset) => readonly CheckFinding[];
77
+
78
+ /**
79
+ * Triangle budgets per class (LOD0 / LOD1 / LOD2): a starting point, measured on real recipes.
80
+ * `budget: BUDGETS.vehicle` — raise one with a measured reason, never to hide a regression.
81
+ */
82
+ export const BUDGETS = {
83
+ prop: { triangles: [800, 300, 80] },
84
+ detailedProp: { triangles: [5000, 1500, 300] },
85
+ building: { triangles: [20000, 6000, 800] },
86
+ vehicle: { triangles: [60000, 15000, 1000] },
87
+ hero: { triangles: [150000, 40000, 2000] },
88
+ tree: { triangles: [25000, 6000, 300] },
89
+ scene: { triangles: [500000, 120000, 15000] },
90
+ } as const satisfies Record<string, Budget>;
91
+
92
+ /** A part set that emits light (lamp lenses, lit windows): an emissive material in the GLB. */
93
+ export interface Glow {
94
+ /** Part names or globs: 'headlamp_*', 'window_*'. */
95
+ readonly parts: readonly string[];
96
+ /** Linear RGB 0..1 (`hex('#ffd9a0')`). Default warm white. */
97
+ readonly color?: readonly [number, number, number];
98
+ /** Brightness multiplier (KHR_materials_emissive_strength); 1 = plain glTF. Default 4. */
99
+ readonly strength?: number;
100
+ }
101
+
102
+ /** Provenance written into every GLB: `asset.copyright` + an XMP packet. */
103
+ export interface AssetMeta {
104
+ readonly description?: string;
105
+ readonly sources?: readonly string[];
106
+ /** SPDX id or licence URL, e.g. 'CC0-1.0', 'MIT'. */
107
+ readonly license?: string;
108
+ /** e.g. '© 2026 Jane Doe'. */
109
+ readonly copyright?: string;
110
+ readonly creators?: readonly string[];
111
+ }
112
+
113
+ export interface AssetDefinition {
114
+ readonly name: string;
115
+ readonly kind: string;
116
+ readonly description?: string;
117
+ /** Where the numbers came from: spec sheets, rosters, drawings, photos. Required for `real`. */
118
+ readonly sources?: readonly string[];
119
+ readonly real?: RealDimensions;
120
+ readonly tags?: readonly string[];
121
+ readonly budget: Budget;
122
+ readonly seed?: number;
123
+ /** The recipe's own physical intent, enforced by QA (see Check). */
124
+ readonly checks?: readonly Check[];
125
+ /** SPDX id or licence URL for the generated asset, e.g. 'CC0-1.0'. Written into the GLB. */
126
+ readonly license?: string;
127
+ readonly copyright?: string;
128
+ readonly creators?: readonly string[];
129
+ /** Emissive part sets; `t3d build` writes them as glowing materials. */
130
+ readonly glow?: readonly Glow[];
131
+ /**
132
+ * View-distance hints for the single-file GLB (MSFT_lod `MSFT_screencoverage`): the screen-height
133
+ * fraction below which LOD0 → LOD1, LOD1 → LOD2, and LOD2 is culled (0 = never). Strictly
134
+ * descending, 1 > a > b > c >= 0. Omitted: derived from the triangle counts and size.
135
+ */
136
+ readonly lodCoverage?: readonly [number, number, number];
137
+ build(ctx: BuildContext): void;
138
+ }
139
+
140
+ export interface Asset extends AssetDefinition {
141
+ readonly __ts23d: 'asset';
142
+ }
143
+
144
+ export interface CompiledAsset {
145
+ readonly name: string;
146
+ readonly kind: string;
147
+ readonly lod: Lod;
148
+ readonly budget: Budget;
149
+ readonly real: RealDimensions | null;
150
+ readonly checks: readonly Check[];
151
+ readonly parts: readonly Part[];
152
+ /** Provenance carried from the definition (absent when the recipe declares none). */
153
+ readonly meta?: AssetMeta;
154
+ readonly glow?: readonly Glow[];
155
+ readonly lodCoverage?: readonly [number, number, number];
156
+ }