@developerz.ai/ts23d 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (567) hide show
  1. package/.claude/agents/bug-hunter.md +21 -0
  2. package/.claude/agents/docs-author.md +13 -0
  3. package/.claude/agents/dx-auditor.md +14 -0
  4. package/.claude/agents/primitive-author.md +22 -0
  5. package/.claude/agents/recipe-author.md +36 -0
  6. package/.claude/agents/visual-reviewer.md +23 -0
  7. package/.claude/commands/feature.md +28 -0
  8. package/.claude/commands/new-recipe.md +19 -0
  9. package/.claude/commands/release.md +14 -0
  10. package/.claude/commands/verify.md +9 -0
  11. package/.claude/skills/author-recipe/SKILL.md +93 -0
  12. package/CHANGELOG.md +90 -0
  13. package/CLAUDE.md +85 -0
  14. package/LICENSE +21 -0
  15. package/README.md +385 -2
  16. package/dist/chunk-6b05mnmx.js +19 -0
  17. package/dist/chunk-6b05mnmx.js.map +11 -0
  18. package/dist/chunk-7rtfjh9f.js +45 -0
  19. package/dist/chunk-7rtfjh9f.js.map +10 -0
  20. package/dist/chunk-8j4c28jt.js +4085 -0
  21. package/dist/chunk-8j4c28jt.js.map +46 -0
  22. package/dist/chunk-d1fv6hrm.js +2532 -0
  23. package/dist/chunk-d1fv6hrm.js.map +43 -0
  24. package/dist/chunk-dbq4qf52.js +902 -0
  25. package/dist/chunk-dbq4qf52.js.map +25 -0
  26. package/dist/chunk-drna2rny.js +836 -0
  27. package/dist/chunk-drna2rny.js.map +17 -0
  28. package/dist/chunk-dxvypr7y.js +89 -0
  29. package/dist/chunk-dxvypr7y.js.map +11 -0
  30. package/dist/chunk-f6yme003.js +1589 -0
  31. package/dist/chunk-f6yme003.js.map +30 -0
  32. package/dist/chunk-hg7pf34j.js +465 -0
  33. package/dist/chunk-hg7pf34j.js.map +19 -0
  34. package/dist/chunk-jq4e17tj.js +1031 -0
  35. package/dist/chunk-jq4e17tj.js.map +21 -0
  36. package/dist/chunk-y63c6zcv.js +2668 -0
  37. package/dist/chunk-y63c6zcv.js.map +53 -0
  38. package/dist/cli/args.d.ts +30 -0
  39. package/dist/cli/bin.d.ts +2 -0
  40. package/dist/cli/bin.js +348 -0
  41. package/dist/cli/bin.js.map +15 -0
  42. package/dist/cli/load.d.ts +2 -0
  43. package/dist/cli/manifest.d.ts +53 -0
  44. package/dist/cli/render-cmds.d.ts +12 -0
  45. package/dist/cli/run.d.ts +1 -0
  46. package/dist/cli/texture-cache.d.ts +4 -0
  47. package/dist/core/asset.d.ts +10 -0
  48. package/dist/core/definition.d.ts +155 -0
  49. package/dist/core/errors.d.ts +17 -0
  50. package/dist/core/family.d.ts +23 -0
  51. package/dist/core/index.d.ts +9 -0
  52. package/dist/core/index.js +69 -0
  53. package/dist/core/index.js.map +9 -0
  54. package/dist/core/mesh.d.ts +35 -0
  55. package/dist/core/part.d.ts +114 -0
  56. package/dist/core/png.d.ts +1 -0
  57. package/dist/core/rng.d.ts +12 -0
  58. package/dist/core/transform.d.ts +20 -0
  59. package/dist/core/vec.d.ts +11 -0
  60. package/dist/core/world.d.ts +16 -0
  61. package/dist/export/bin-writer.d.ts +16 -0
  62. package/dist/export/container.d.ts +3 -0
  63. package/dist/export/credits.d.ts +4 -0
  64. package/dist/export/glb-options.d.ts +48 -0
  65. package/dist/export/glb.d.ts +8 -0
  66. package/dist/export/hierarchy.d.ts +23 -0
  67. package/dist/export/index.d.ts +5 -0
  68. package/dist/export/index.js +40 -0
  69. package/dist/export/index.js.map +9 -0
  70. package/dist/export/lights.d.ts +8 -0
  71. package/dist/export/lod-coverage.d.ts +12 -0
  72. package/dist/export/materials.d.ts +23 -0
  73. package/dist/export/merge-pivots.d.ts +6 -0
  74. package/dist/export/metadata.d.ts +21 -0
  75. package/dist/export/motion.d.ts +9 -0
  76. package/dist/export/optimize-parts.d.ts +26 -0
  77. package/dist/export/primitive.d.ts +3 -0
  78. package/dist/export/tangents.d.ts +2 -0
  79. package/dist/export/textures.d.ts +22 -0
  80. package/dist/export/version.d.ts +2 -0
  81. package/dist/grammar/index.d.ts +2 -0
  82. package/dist/grammar/index.js +16 -0
  83. package/dist/grammar/index.js.map +9 -0
  84. package/dist/grammar/split.d.ts +21 -0
  85. package/dist/grammar/whyte.d.ts +5 -0
  86. package/dist/index.d.ts +11 -0
  87. package/dist/index.js +457 -0
  88. package/dist/index.js.map +9 -0
  89. package/dist/kinds/building.d.ts +44 -0
  90. package/dist/kinds/faces.d.ts +27 -0
  91. package/dist/kinds/head-armour.d.ts +5 -0
  92. package/dist/kinds/head-beard.d.ts +6 -0
  93. package/dist/kinds/head-bust.d.ts +5 -0
  94. package/dist/kinds/head-extras.d.ts +15 -0
  95. package/dist/kinds/head-face.d.ts +8 -0
  96. package/dist/kinds/head-garb.d.ts +4 -0
  97. package/dist/kinds/head-geom.d.ts +7 -0
  98. package/dist/kinds/head-hair.d.ts +4 -0
  99. package/dist/kinds/head-layout.d.ts +29 -0
  100. package/dist/kinds/head-mesh.d.ts +31 -0
  101. package/dist/kinds/head-mouth.d.ts +4 -0
  102. package/dist/kinds/head-params.d.ts +116 -0
  103. package/dist/kinds/head-presets.d.ts +19 -0
  104. package/dist/kinds/head-rim.d.ts +10 -0
  105. package/dist/kinds/head-tone.d.ts +10 -0
  106. package/dist/kinds/head-torso.d.ts +35 -0
  107. package/dist/kinds/head.d.ts +39 -0
  108. package/dist/kinds/hull-lines.d.ts +65 -0
  109. package/dist/kinds/hull-skin.d.ts +6 -0
  110. package/dist/kinds/hull.d.ts +14 -0
  111. package/dist/kinds/index.d.ts +16 -0
  112. package/dist/kinds/index.js +78 -0
  113. package/dist/kinds/index.js.map +9 -0
  114. package/dist/kinds/mass.d.ts +15 -0
  115. package/dist/kinds/openings.d.ts +30 -0
  116. package/dist/kinds/planned.d.ts +6 -0
  117. package/dist/kinds/roof-chimney.d.ts +16 -0
  118. package/dist/kinds/roof-shape.d.ts +64 -0
  119. package/dist/kinds/roof.d.ts +31 -0
  120. package/dist/kinds/vegetation-fronds.d.ts +15 -0
  121. package/dist/kinds/vegetation-impostor.d.ts +9 -0
  122. package/dist/kinds/vegetation-leaves.d.ts +15 -0
  123. package/dist/kinds/vegetation-mesh.d.ts +21 -0
  124. package/dist/kinds/vegetation-params.d.ts +90 -0
  125. package/dist/kinds/vegetation-presets.d.ts +20 -0
  126. package/dist/kinds/vegetation-skeleton.d.ts +20 -0
  127. package/dist/kinds/vegetation.d.ts +42 -0
  128. package/dist/kinds/vehicle.d.ts +42 -0
  129. package/dist/material/cc0.d.ts +4 -0
  130. package/dist/material/define.d.ts +34 -0
  131. package/dist/material/generator.d.ts +4 -0
  132. package/dist/material/index.d.ts +9 -0
  133. package/dist/material/index.js +70 -0
  134. package/dist/material/index.js.map +9 -0
  135. package/dist/material/library.d.ts +7 -0
  136. package/dist/material/material-def.d.ts +30 -0
  137. package/dist/material/paint/bond.d.ts +3 -0
  138. package/dist/material/paint/canvas.d.ts +39 -0
  139. package/dist/material/paint/cell.d.ts +4 -0
  140. package/dist/material/paint/cells-round.d.ts +5 -0
  141. package/dist/material/paint/cells.d.ts +7 -0
  142. package/dist/material/paint/compose.d.ts +5 -0
  143. package/dist/material/paint/concrete.d.ts +3 -0
  144. package/dist/material/paint/courses.d.ts +31 -0
  145. package/dist/material/paint/decode-png.d.ts +6 -0
  146. package/dist/material/paint/default-sheet.d.ts +4 -0
  147. package/dist/material/paint/fabric.d.ts +4 -0
  148. package/dist/material/paint/foliage-broad.d.ts +5 -0
  149. package/dist/material/paint/foliage-common.d.ts +35 -0
  150. package/dist/material/paint/foliage-compound.d.ts +4 -0
  151. package/dist/material/paint/foliage-conifer.d.ts +5 -0
  152. package/dist/material/paint/foliage-raster.d.ts +26 -0
  153. package/dist/material/paint/foliage.d.ts +3 -0
  154. package/dist/material/paint/image-strip.d.ts +4 -0
  155. package/dist/material/paint/kit.d.ts +5 -0
  156. package/dist/material/paint/masks.d.ts +3 -0
  157. package/dist/material/paint/metal.d.ts +3 -0
  158. package/dist/material/paint/noise.d.ts +9 -0
  159. package/dist/material/paint/normal.d.ts +8 -0
  160. package/dist/material/paint/orm.d.ts +8 -0
  161. package/dist/material/paint/planks.d.ts +3 -0
  162. package/dist/material/paint/plates.d.ts +3 -0
  163. package/dist/material/paint/shapes.d.ts +8 -0
  164. package/dist/material/paint/slate.d.ts +3 -0
  165. package/dist/material/paint/stamp.d.ts +3 -0
  166. package/dist/material/paint/strip.d.ts +3 -0
  167. package/dist/material/paint/surface.d.ts +5 -0
  168. package/dist/material/paint/types.d.ts +65 -0
  169. package/dist/material/paint/validate.d.ts +3 -0
  170. package/dist/material/palette.d.ts +14 -0
  171. package/dist/material/shader-godot.d.ts +2 -0
  172. package/dist/material/shader-three.d.ts +2 -0
  173. package/dist/material/shader.d.ts +46 -0
  174. package/dist/material/slots.d.ts +5 -0
  175. package/dist/material/texture-source.d.ts +34 -0
  176. package/dist/material/trim-sheet.d.ts +38 -0
  177. package/dist/neural/generator.d.ts +25 -0
  178. package/dist/neural/images.d.ts +17 -0
  179. package/dist/neural/index.d.ts +2 -0
  180. package/dist/neural/index.js +14 -0
  181. package/dist/neural/index.js.map +9 -0
  182. package/dist/passes/bake-ao.d.ts +17 -0
  183. package/dist/passes/grime.d.ts +15 -0
  184. package/dist/passes/index.d.ts +6 -0
  185. package/dist/passes/index.js +22 -0
  186. package/dist/passes/index.js.map +9 -0
  187. package/dist/passes/livery.d.ts +19 -0
  188. package/dist/passes/optimize-cache.d.ts +15 -0
  189. package/dist/passes/optimize-weld.d.ts +15 -0
  190. package/dist/passes/optimize.d.ts +28 -0
  191. package/dist/passes/ray-grid.d.ts +23 -0
  192. package/dist/passes/weighted-normals.d.ts +8 -0
  193. package/dist/primitives/beam.d.ts +17 -0
  194. package/dist/primitives/bevel-box.d.ts +13 -0
  195. package/dist/primitives/box.d.ts +16 -0
  196. package/dist/primitives/cell.d.ts +20 -0
  197. package/dist/primitives/check.d.ts +12 -0
  198. package/dist/primitives/closed.d.ts +11 -0
  199. package/dist/primitives/erosion.d.ts +22 -0
  200. package/dist/primitives/font-5x7.d.ts +3 -0
  201. package/dist/primitives/heightfield.d.ts +16 -0
  202. package/dist/primitives/index.d.ts +23 -0
  203. package/dist/primitives/index.js +81 -0
  204. package/dist/primitives/index.js.map +9 -0
  205. package/dist/primitives/lathe-facets.d.ts +8 -0
  206. package/dist/primitives/lathe.d.ts +26 -0
  207. package/dist/primitives/lattice.d.ts +30 -0
  208. package/dist/primitives/loft.d.ts +15 -0
  209. package/dist/primitives/mc-table.d.ts +2 -0
  210. package/dist/primitives/member.d.ts +19 -0
  211. package/dist/primitives/noise.d.ts +34 -0
  212. package/dist/primitives/planar.d.ts +10 -0
  213. package/dist/primitives/prism.d.ts +13 -0
  214. package/dist/primitives/sculpt-edit.d.ts +56 -0
  215. package/dist/primitives/sculpt-tree.d.ts +23 -0
  216. package/dist/primitives/sculpt.d.ts +27 -0
  217. package/dist/primitives/sdf-grid.d.ts +17 -0
  218. package/dist/primitives/sdf-ops.d.ts +37 -0
  219. package/dist/primitives/sdf.d.ts +21 -0
  220. package/dist/primitives/strip-uv.d.ts +11 -0
  221. package/dist/primitives/surface-nets.d.ts +7 -0
  222. package/dist/primitives/sweep.d.ts +19 -0
  223. package/dist/primitives/terrain-color.d.ts +30 -0
  224. package/dist/primitives/terrain-grid.d.ts +36 -0
  225. package/dist/primitives/text-layout.d.ts +22 -0
  226. package/dist/primitives/text.d.ts +27 -0
  227. package/dist/primitives/tiled.d.ts +22 -0
  228. package/dist/primitives/transport.d.ts +17 -0
  229. package/dist/primitives/triangulate.d.ts +2 -0
  230. package/dist/primitives/value-noise.d.ts +10 -0
  231. package/dist/qa/checks.d.ts +11 -0
  232. package/dist/qa/clash.d.ts +2 -0
  233. package/dist/qa/floating.d.ts +5 -0
  234. package/dist/qa/gates.d.ts +12 -0
  235. package/dist/qa/geometry.d.ts +21 -0
  236. package/dist/qa/index.d.ts +12 -0
  237. package/dist/qa/index.js +78 -0
  238. package/dist/qa/index.js.map +9 -0
  239. package/dist/qa/inside.d.ts +12 -0
  240. package/dist/qa/inspect.d.ts +18 -0
  241. package/dist/qa/lods.d.ts +12 -0
  242. package/dist/qa/measures.d.ts +4 -0
  243. package/dist/qa/occlusion.d.ts +3 -0
  244. package/dist/qa/sanity.d.ts +7 -0
  245. package/dist/qa/types.d.ts +12 -0
  246. package/dist/render/beauty.d.ts +40 -0
  247. package/dist/render/camera.d.ts +33 -0
  248. package/dist/render/depth-map.d.ts +38 -0
  249. package/dist/render/font.d.ts +1 -0
  250. package/dist/render/ground.d.ts +13 -0
  251. package/dist/render/index.d.ts +10 -0
  252. package/dist/render/index.js +47 -0
  253. package/dist/render/index.js.map +9 -0
  254. package/dist/render/lighting.d.ts +27 -0
  255. package/dist/render/lod-strip.d.ts +5 -0
  256. package/dist/render/metrics.d.ts +37 -0
  257. package/dist/render/options.d.ts +28 -0
  258. package/dist/render/photo-match.d.ts +19 -0
  259. package/dist/render/raster.d.ts +25 -0
  260. package/dist/render/resolve.d.ts +11 -0
  261. package/dist/render/review-prompt.d.ts +2 -0
  262. package/dist/render/review-warnings.d.ts +34 -0
  263. package/dist/render/review.d.ts +24 -0
  264. package/dist/render/scene.d.ts +18 -0
  265. package/dist/render/shade.d.ts +22 -0
  266. package/dist/render/sheet.d.ts +15 -0
  267. package/dist/render/texture.d.ts +15 -0
  268. package/dist/render/thumbnail.d.ts +8 -0
  269. package/dist/render/vis-buffer.d.ts +18 -0
  270. package/dist/testing/assert.d.ts +21 -0
  271. package/dist/testing/compiled.d.ts +15 -0
  272. package/dist/testing/hash.d.ts +1 -0
  273. package/dist/testing/index.d.ts +4 -0
  274. package/dist/testing/index.js +134 -0
  275. package/dist/testing/index.js.map +13 -0
  276. package/dist/testing/snapshot.d.ts +13 -0
  277. package/docs/README.md +56 -0
  278. package/docs/architecture/README.md +68 -0
  279. package/docs/architecture/adding-a-kind.md +44 -0
  280. package/docs/architecture/adding-a-primitive.md +49 -0
  281. package/docs/architecture/errors.md +47 -0
  282. package/docs/architecture/glb-production.md +214 -0
  283. package/docs/architecture/gltf-capabilities.md +117 -0
  284. package/docs/architecture/materials.md +151 -0
  285. package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
  286. package/docs/decisions/002-typescript-not-json.md +22 -0
  287. package/docs/decisions/003-shared-trim-sheet.md +21 -0
  288. package/docs/decisions/004-z-up-metres.md +21 -0
  289. package/docs/decisions/005-zero-runtime-deps.md +21 -0
  290. package/docs/decisions/006-single-package-subpath-exports.md +20 -0
  291. package/docs/decisions/007-cpu-contact-sheets.md +21 -0
  292. package/docs/decisions/008-surface-materials.md +21 -0
  293. package/docs/decisions/009-gltf-front-axis.md +21 -0
  294. package/docs/decisions/README.md +15 -0
  295. package/docs/idea/01-vision.md +117 -0
  296. package/docs/idea/02-dsl.md +343 -0
  297. package/docs/idea/03-ir.md +97 -0
  298. package/docs/idea/04-materials.md +114 -0
  299. package/docs/idea/05-agent-loop.md +141 -0
  300. package/docs/idea/06-qa.md +101 -0
  301. package/docs/idea/07-what-fits.md +45 -0
  302. package/docs/idea/08-neural-adapters.md +78 -0
  303. package/docs/idea/09-kinds.md +105 -0
  304. package/docs/idea/10-sculpting.md +166 -0
  305. package/docs/idea/11-materials-authoring.md +132 -0
  306. package/docs/idea/12-blender-bridge.md +136 -0
  307. package/docs/idea/13-asset-pipeline-research.md +89 -0
  308. package/docs/landscape.md +49 -0
  309. package/docs/plans/roadmap.md +139 -0
  310. package/examples/build-with-bun.ts +41 -0
  311. package/llms.txt +47 -0
  312. package/package.json +100 -4
  313. package/recipes/README.md +38 -0
  314. package/recipes/asteroid/README.md +46 -0
  315. package/recipes/asteroid/asteroid.ts +336 -0
  316. package/recipes/boulders/README.md +28 -0
  317. package/recipes/boulders/boulders.ts +117 -0
  318. package/recipes/boxcar/README.md +53 -0
  319. package/recipes/boxcar/boxcar.ts +1000 -0
  320. package/recipes/cargo_container_scifi/README.md +52 -0
  321. package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
  322. package/recipes/dropship/README.md +84 -0
  323. package/recipes/dropship/dropship.ts +1144 -0
  324. package/recipes/eiffel_tower/README.md +110 -0
  325. package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
  326. package/recipes/goblin_head/README.md +75 -0
  327. package/recipes/goblin_head/goblin_head.ts +38 -0
  328. package/recipes/human_head/README.md +68 -0
  329. package/recipes/human_head/human_head.ts +38 -0
  330. package/recipes/loco_440/README.md +32 -0
  331. package/recipes/loco_440/loco_440.ts +1404 -0
  332. package/recipes/material_swatches/README.md +21 -0
  333. package/recipes/material_swatches/material_swatches.ts +189 -0
  334. package/recipes/mountain_range/README.md +45 -0
  335. package/recipes/mountain_range/mountain_range.ts +128 -0
  336. package/recipes/oak/README.md +30 -0
  337. package/recipes/oak/oak.ts +19 -0
  338. package/recipes/orc_head/README.md +78 -0
  339. package/recipes/orc_head/orc_head.ts +41 -0
  340. package/recipes/palm/README.md +30 -0
  341. package/recipes/palm/palm.ts +19 -0
  342. package/recipes/pine/README.md +29 -0
  343. package/recipes/pine/pine.ts +19 -0
  344. package/recipes/signal_gantry/README.md +36 -0
  345. package/recipes/signal_gantry/signal_gantry.ts +522 -0
  346. package/recipes/station_halt/README.md +39 -0
  347. package/recipes/station_halt/station_halt.ts +722 -0
  348. package/recipes/t34/README.md +44 -0
  349. package/recipes/t34/t34.ts +1068 -0
  350. package/recipes/tiger_1/README.md +75 -0
  351. package/recipes/tiger_1/tiger_1.ts +1370 -0
  352. package/recipes/water_tower/README.md +40 -0
  353. package/recipes/water_tower/water_tower.ts +542 -0
  354. package/src/cli/args.ts +125 -0
  355. package/src/cli/bin.ts +7 -0
  356. package/src/cli/load.ts +17 -0
  357. package/src/cli/manifest.ts +95 -0
  358. package/src/cli/render-cmds.ts +66 -0
  359. package/src/cli/run.ts +192 -0
  360. package/src/cli/texture-cache.ts +24 -0
  361. package/src/core/asset.ts +129 -0
  362. package/src/core/definition.ts +156 -0
  363. package/src/core/errors.ts +50 -0
  364. package/src/core/family.ts +50 -0
  365. package/src/core/index.ts +66 -0
  366. package/src/core/mesh.ts +114 -0
  367. package/src/core/part.ts +130 -0
  368. package/src/core/png.ts +49 -0
  369. package/src/core/rng.ts +49 -0
  370. package/src/core/transform.ts +90 -0
  371. package/src/core/vec.ts +26 -0
  372. package/src/core/world.ts +80 -0
  373. package/src/export/bin-writer.ts +79 -0
  374. package/src/export/container.ts +35 -0
  375. package/src/export/credits.ts +25 -0
  376. package/src/export/glb-options.ts +53 -0
  377. package/src/export/glb.ts +187 -0
  378. package/src/export/hierarchy.ts +122 -0
  379. package/src/export/index.ts +34 -0
  380. package/src/export/lights.ts +49 -0
  381. package/src/export/lod-coverage.ts +71 -0
  382. package/src/export/materials.ts +126 -0
  383. package/src/export/merge-pivots.ts +146 -0
  384. package/src/export/metadata.ts +62 -0
  385. package/src/export/motion.ts +70 -0
  386. package/src/export/optimize-parts.ts +80 -0
  387. package/src/export/primitive.ts +35 -0
  388. package/src/export/tangents.ts +75 -0
  389. package/src/export/textures.ts +88 -0
  390. package/src/export/version.ts +6 -0
  391. package/src/grammar/index.ts +4 -0
  392. package/src/grammar/split.ts +87 -0
  393. package/src/grammar/whyte.ts +20 -0
  394. package/src/index.ts +14 -0
  395. package/src/kinds/building.ts +153 -0
  396. package/src/kinds/faces.ts +138 -0
  397. package/src/kinds/head-armour.ts +181 -0
  398. package/src/kinds/head-beard.ts +133 -0
  399. package/src/kinds/head-bust.ts +184 -0
  400. package/src/kinds/head-extras.ts +150 -0
  401. package/src/kinds/head-face.ts +242 -0
  402. package/src/kinds/head-garb.ts +181 -0
  403. package/src/kinds/head-geom.ts +31 -0
  404. package/src/kinds/head-hair.ts +193 -0
  405. package/src/kinds/head-layout.ts +63 -0
  406. package/src/kinds/head-mesh.ts +195 -0
  407. package/src/kinds/head-mouth.ts +111 -0
  408. package/src/kinds/head-params.ts +162 -0
  409. package/src/kinds/head-presets.ts +155 -0
  410. package/src/kinds/head-rim.ts +128 -0
  411. package/src/kinds/head-tone.ts +52 -0
  412. package/src/kinds/head-torso.ts +230 -0
  413. package/src/kinds/head.ts +176 -0
  414. package/src/kinds/hull-lines.ts +160 -0
  415. package/src/kinds/hull-skin.ts +83 -0
  416. package/src/kinds/hull.ts +209 -0
  417. package/src/kinds/index.ts +63 -0
  418. package/src/kinds/mass.ts +110 -0
  419. package/src/kinds/openings.ts +141 -0
  420. package/src/kinds/planned.ts +12 -0
  421. package/src/kinds/roof-chimney.ts +100 -0
  422. package/src/kinds/roof-shape.ts +230 -0
  423. package/src/kinds/roof.ts +250 -0
  424. package/src/kinds/vegetation-fronds.ts +126 -0
  425. package/src/kinds/vegetation-impostor.ts +118 -0
  426. package/src/kinds/vegetation-leaves.ts +168 -0
  427. package/src/kinds/vegetation-mesh.ts +174 -0
  428. package/src/kinds/vegetation-params.ts +147 -0
  429. package/src/kinds/vegetation-presets.ts +184 -0
  430. package/src/kinds/vegetation-skeleton.ts +168 -0
  431. package/src/kinds/vegetation.ts +199 -0
  432. package/src/kinds/vehicle.ts +144 -0
  433. package/src/material/cc0.ts +41 -0
  434. package/src/material/define.ts +130 -0
  435. package/src/material/generator.ts +63 -0
  436. package/src/material/index.ts +60 -0
  437. package/src/material/library.ts +196 -0
  438. package/src/material/material-def.ts +84 -0
  439. package/src/material/paint/bond.ts +60 -0
  440. package/src/material/paint/canvas.ts +101 -0
  441. package/src/material/paint/cell.ts +39 -0
  442. package/src/material/paint/cells-round.ts +119 -0
  443. package/src/material/paint/cells.ts +111 -0
  444. package/src/material/paint/compose.ts +53 -0
  445. package/src/material/paint/concrete.ts +62 -0
  446. package/src/material/paint/courses.ts +85 -0
  447. package/src/material/paint/decode-png.ts +106 -0
  448. package/src/material/paint/default-sheet.ts +73 -0
  449. package/src/material/paint/fabric.ts +63 -0
  450. package/src/material/paint/foliage-broad.ts +126 -0
  451. package/src/material/paint/foliage-common.ts +93 -0
  452. package/src/material/paint/foliage-compound.ts +135 -0
  453. package/src/material/paint/foliage-conifer.ts +143 -0
  454. package/src/material/paint/foliage-raster.ts +196 -0
  455. package/src/material/paint/foliage.ts +23 -0
  456. package/src/material/paint/image-strip.ts +88 -0
  457. package/src/material/paint/kit.ts +37 -0
  458. package/src/material/paint/masks.ts +46 -0
  459. package/src/material/paint/metal.ts +109 -0
  460. package/src/material/paint/noise.ts +52 -0
  461. package/src/material/paint/normal.ts +52 -0
  462. package/src/material/paint/orm.ts +108 -0
  463. package/src/material/paint/planks.ts +101 -0
  464. package/src/material/paint/plates.ts +72 -0
  465. package/src/material/paint/shapes.ts +28 -0
  466. package/src/material/paint/slate.ts +52 -0
  467. package/src/material/paint/stamp.ts +40 -0
  468. package/src/material/paint/strip.ts +48 -0
  469. package/src/material/paint/surface.ts +60 -0
  470. package/src/material/paint/types.ts +94 -0
  471. package/src/material/paint/validate.ts +74 -0
  472. package/src/material/palette.ts +46 -0
  473. package/src/material/shader-godot.ts +77 -0
  474. package/src/material/shader-three.ts +103 -0
  475. package/src/material/shader.ts +100 -0
  476. package/src/material/slots.ts +18 -0
  477. package/src/material/texture-source.ts +86 -0
  478. package/src/material/trim-sheet.ts +166 -0
  479. package/src/neural/generator.ts +37 -0
  480. package/src/neural/images.ts +27 -0
  481. package/src/neural/index.ts +10 -0
  482. package/src/passes/bake-ao.ts +183 -0
  483. package/src/passes/grime.ts +37 -0
  484. package/src/passes/index.ts +8 -0
  485. package/src/passes/livery.ts +66 -0
  486. package/src/passes/optimize-cache.ts +175 -0
  487. package/src/passes/optimize-weld.ts +147 -0
  488. package/src/passes/optimize.ts +85 -0
  489. package/src/passes/ray-grid.ts +237 -0
  490. package/src/passes/weighted-normals.ts +125 -0
  491. package/src/primitives/beam.ts +60 -0
  492. package/src/primitives/bevel-box.ts +140 -0
  493. package/src/primitives/box.ts +90 -0
  494. package/src/primitives/cell.ts +115 -0
  495. package/src/primitives/check.ts +49 -0
  496. package/src/primitives/closed.ts +74 -0
  497. package/src/primitives/erosion.ts +183 -0
  498. package/src/primitives/font-5x7.ts +117 -0
  499. package/src/primitives/heightfield.ts +101 -0
  500. package/src/primitives/index.ts +56 -0
  501. package/src/primitives/lathe-facets.ts +45 -0
  502. package/src/primitives/lathe.ts +174 -0
  503. package/src/primitives/lattice.ts +131 -0
  504. package/src/primitives/loft.ts +174 -0
  505. package/src/primitives/mc-table.ts +41 -0
  506. package/src/primitives/member.ts +130 -0
  507. package/src/primitives/noise.ts +157 -0
  508. package/src/primitives/planar.ts +55 -0
  509. package/src/primitives/prism.ts +85 -0
  510. package/src/primitives/sculpt-edit.ts +212 -0
  511. package/src/primitives/sculpt-tree.ts +226 -0
  512. package/src/primitives/sculpt.ts +80 -0
  513. package/src/primitives/sdf-grid.ts +131 -0
  514. package/src/primitives/sdf-ops.ts +145 -0
  515. package/src/primitives/sdf.ts +83 -0
  516. package/src/primitives/strip-uv.ts +26 -0
  517. package/src/primitives/surface-nets.ts +161 -0
  518. package/src/primitives/sweep.ts +162 -0
  519. package/src/primitives/terrain-color.ts +85 -0
  520. package/src/primitives/terrain-grid.ts +137 -0
  521. package/src/primitives/text-layout.ts +85 -0
  522. package/src/primitives/text.ts +197 -0
  523. package/src/primitives/tiled.ts +93 -0
  524. package/src/primitives/transport.ts +85 -0
  525. package/src/primitives/triangulate.ts +42 -0
  526. package/src/primitives/value-noise.ts +62 -0
  527. package/src/qa/checks.ts +110 -0
  528. package/src/qa/clash.ts +177 -0
  529. package/src/qa/floating.ts +63 -0
  530. package/src/qa/gates.ts +126 -0
  531. package/src/qa/geometry.ts +187 -0
  532. package/src/qa/index.ts +28 -0
  533. package/src/qa/inside.ts +114 -0
  534. package/src/qa/inspect.ts +107 -0
  535. package/src/qa/lods.ts +97 -0
  536. package/src/qa/measures.ts +67 -0
  537. package/src/qa/occlusion.ts +166 -0
  538. package/src/qa/sanity.ts +137 -0
  539. package/src/qa/types.ts +17 -0
  540. package/src/render/beauty.ts +115 -0
  541. package/src/render/camera.ts +142 -0
  542. package/src/render/depth-map.ts +191 -0
  543. package/src/render/font.ts +76 -0
  544. package/src/render/ground.ts +87 -0
  545. package/src/render/index.ts +34 -0
  546. package/src/render/lighting.ts +121 -0
  547. package/src/render/lod-strip.ts +34 -0
  548. package/src/render/metrics.ts +147 -0
  549. package/src/render/options.ts +81 -0
  550. package/src/render/photo-match.ts +28 -0
  551. package/src/render/raster.ts +143 -0
  552. package/src/render/resolve.ts +109 -0
  553. package/src/render/review-prompt.ts +59 -0
  554. package/src/render/review-warnings.ts +92 -0
  555. package/src/render/review.ts +103 -0
  556. package/src/render/scene.ts +119 -0
  557. package/src/render/shade.ts +204 -0
  558. package/src/render/sheet.ts +88 -0
  559. package/src/render/texture.ts +97 -0
  560. package/src/render/thumbnail.ts +75 -0
  561. package/src/render/vis-buffer.ts +119 -0
  562. package/src/testing/assert.ts +115 -0
  563. package/src/testing/compiled.ts +31 -0
  564. package/src/testing/hash.ts +14 -0
  565. package/src/testing/index.ts +14 -0
  566. package/src/testing/snapshot.ts +37 -0
  567. package/src/types/gltf-validator.d.ts +13 -0
@@ -0,0 +1,40 @@
1
+ # Railroad water tank — Frisco, Beaumont, Kansas (c. 1886) (`water_tower`)
2
+
3
+ ## Identity
4
+ The St. Louis & San Francisco ("Frisco") 50,000-gallon cypress-stave water tank at Beaumont, KS:
5
+ NRHP-listed (93000843) and HABS-recorded (KS-82), so its dimensions are on paper, and it is the
6
+ textbook American wayside tank. Signature features: a squat stave tub bound by steel hoops (closer
7
+ near the bottom, where the pressure is), a low six-sided shingle roof with a finial, a timber
8
+ trestle of posts on concrete footings with X bracing round a wooden central core, and the
9
+ goose-neck delivery spout over the track (removed at Beaumont in the 1950s; restored here from the
10
+ period type Berg describes).
11
+
12
+ ## Specs
13
+ | Dimension | Value | Source |
14
+ |---|---|---|
15
+ | tank height (staves) | 14 ft (4.267 m) | NRHP nomination 93000843, §7, https://npgallery.nps.gov/GetAsset/f632b7b3-36b4-4a36-be22-47bb617fad0d |
16
+ | tank diameter | 22 ft (6.706 m) | same |
17
+ | superstructure (ground → tank floor) | 14 ft (4.267 m) | same |
18
+ | central core | 10 ft dia × 14 ft high (3.048 × 4.267 m) | same |
19
+ | supports | 8 timber pillars on concrete footings, cross-braced | same |
20
+ | capacity | 50,000 US gal | same (6.706² π/4 × 4.267 m = 150.7 m³ ≈ 39,800 gal of staves at the inside; the quoted figure is nominal) |
21
+ | roof | six-sided, wooden shingles | same |
22
+ | spout | goose-neck delivery spout projecting over the track | Berg, *Buildings and Structures of American Railroads* (1893) p. 116, quoted in the nomination |
23
+ | roof rise, finial, hoop count | ≈ 1.05 m rise, 14 hoops | ESTIMATE, scaled from the photo (tank height as ruler) |
24
+ | spout length / rest angle | 3.4 m, 25° up | ESTIMATE (D&RGW tank practice, railroads procgen recipe station_prop_water_tank_wood) |
25
+
26
+ ## Photos (by view)
27
+ | View | URL | Notes |
28
+ |---|---|---|
29
+ | 3/4 side | https://commons.wikimedia.org/wiki/File:Beaumont_Kansas_Water_Tower.jpg | hoops, hexagonal roof, posts on footings, X braces, core, ladder pole |
30
+ | record | https://en.wikipedia.org/wiki/Beaumont_St._Louis_and_San_Francisco_Railroad_Water_Tank | HABS KS-82 (12 measured drawings) |
31
+ | spout type | https://davesriogrande.net/Structures/WaterTanks/watertanks.htm | D&RG tanks with spouts (Chama double spout, Osier) |
32
+
33
+ ## Parts the engine needs
34
+ `spout` (pivot at the tank outlet, `sway` about Y: lowered over a tender) · `socket` `water_fill`
35
+ (interact) at the spout mouth · `tank`, `trestle`, `core`, `roof`.
36
+
37
+ ## Open questions
38
+ - The current Beaumont trestle (heavy timbers, concrete footings) is a 20th-century replacement; the
39
+ nomination says the original bracing was lighter. The current one is modelled (it is in the photo).
40
+ - Roof rise and hoop count are photo estimates; HABS drawings (loc.gov, 403 to fetchers) would fix them.
@@ -0,0 +1,542 @@
1
+ // Railroad water tank: the Frisco's 50,000-gallon cypress-stave tank at Beaumont, Kansas (c. 1886,
2
+ // NRHP 93000843): a 22 ft × 14 ft stave tub on a 14 ft timber trestle of eight posts round a
3
+ // wooden core, six-sided shingle roof, goose-neck spout over the track (+X). Z up, ground z = 0.
4
+ // Dossier: ./README.md.
5
+
6
+ import {
7
+ beam,
8
+ box,
9
+ clear,
10
+ cylinder,
11
+ defineAsset,
12
+ grime,
13
+ hex,
14
+ lathe,
15
+ type Mesh,
16
+ merge,
17
+ place,
18
+ planar,
19
+ tiled,
20
+ type Vec3,
21
+ } from '@developerz.ai/ts23d';
22
+
23
+ const FT = 0.3048;
24
+ const TANK_R = (22 * FT) / 2; // 3.353
25
+ const TANK_H = 14 * FT; // 4.267
26
+ const FLOOR_Z = 14 * FT; // superstructure height
27
+ const TANK_TOP = FLOOR_Z + TANK_H;
28
+ const CORE_R = (10 * FT) / 2;
29
+ const POST_R = 2.75; // post circle (estimate: posts sit under the tank, inside its rim)
30
+ const POST_W = 0.3;
31
+ const FOOT_H = 0.45;
32
+ const CAP_Z0 = 3.72;
33
+ const CAP_Z1 = 4.02;
34
+ const ROOF_APOTHEM = TANK_R + 0.35;
35
+ const ROOF_RISE = 1.05;
36
+ const SPOUT_L = 3.4;
37
+ const SPOUT_REST = (25 * Math.PI) / 180;
38
+ const PIVOT: Vec3 = [TANK_R + 0.32, 0, FLOOR_Z + 0.55];
39
+
40
+ const cypress = hex('#7a5a3e');
41
+ const timber = hex('#8c7b66');
42
+ const timberDark = hex('#6b5d4d');
43
+ const steel = hex('#2b2826');
44
+ const concrete = hex('#b9b3a6');
45
+ const shingle = hex('#4e463e');
46
+
47
+ /** Ring of `n` flat vertical facets (outward), boards running up: staves, the core's planking. */
48
+ function staveRing(
49
+ r: number,
50
+ z0: number,
51
+ h: number,
52
+ n: number,
53
+ color: Vec3,
54
+ strip: 'wood' | 'plain',
55
+ ): Mesh {
56
+ const out: Mesh[] = [];
57
+ for (let k = 0; k < n; k++) {
58
+ const a0 = (k / n) * Math.PI * 2;
59
+ const a1 = ((k + 1) / n) * Math.PI * 2;
60
+ const p0: Vec3 = [Math.cos(a0) * r, Math.sin(a0) * r, z0];
61
+ const p1: Vec3 = [Math.cos(a1) * r, Math.sin(a1) * r, z0];
62
+ out.push(
63
+ tiled({
64
+ origin: p1,
65
+ u: [0, 0, h],
66
+ v: [p0[0] - p1[0], p0[1] - p1[1], 0],
67
+ color,
68
+ strip,
69
+ ...(strip === 'plain' ? { vMetres: 100 } : {}),
70
+ }),
71
+ );
72
+ }
73
+ return merge(out);
74
+ }
75
+
76
+ /** Axis-aligned box from two corners. */
77
+ const bx = (
78
+ x0: number,
79
+ x1: number,
80
+ y0: number,
81
+ y1: number,
82
+ z0: number,
83
+ z1: number,
84
+ color: Vec3,
85
+ strip: 'wood' | 'iron' | 'concrete' | 'plain' = 'wood',
86
+ bevel = 0,
87
+ ): Mesh => {
88
+ const small = Math.min(Math.abs(x1 - x0), Math.abs(y1 - y0), Math.abs(z1 - z0));
89
+ return box({
90
+ size: [Math.abs(x1 - x0), Math.abs(y1 - y0), Math.abs(z1 - z0)],
91
+ at: [(x0 + x1) / 2, (y0 + y1) / 2, Math.min(z0, z1)],
92
+ color,
93
+ strip,
94
+ ...(bevel > 0 && bevel < small / 2 ? { bevel } : {}),
95
+ });
96
+ };
97
+
98
+ export const water_tower = defineAsset({
99
+ name: 'water_tower',
100
+ kind: 'building',
101
+ description:
102
+ 'Frisco 50,000-gallon cypress-stave railroad water tank (Beaumont, Kansas, c. 1886): hooped tub on an eight-post timber trestle with a central core, six-sided shingle roof, goose-neck spout.',
103
+ tags: ['railroad', 'water-tank', 'lineside', '1880s', 'usa'],
104
+ license: 'CC0-1.0',
105
+ sources: [
106
+ 'https://npgallery.nps.gov/GetAsset/f632b7b3-36b4-4a36-be22-47bb617fad0d',
107
+ 'https://commons.wikimedia.org/wiki/File:Beaumont_Kansas_Water_Tower.jpg',
108
+ 'https://en.wikipedia.org/wiki/Beaumont_St._Louis_and_San_Francisco_Railroad_Water_Tank',
109
+ 'https://davesriogrande.net/Structures/WaterTanks/watertanks.htm',
110
+ ],
111
+ real: {
112
+ // Overall box: tank + roof eave (−X) to the spout mouth (+X); roof apex + finial on Z.
113
+ length: 4.27 + PIVOT[0] + SPOUT_L * Math.cos(SPOUT_REST) + 0.1,
114
+ width: 2 * ROOF_APOTHEM,
115
+ height: TANK_TOP + 0.15 + ROOF_RISE + 0.9,
116
+ measures: [
117
+ {
118
+ label: 'tank diameter (22 ft)',
119
+ value: 22 * FT,
120
+ axis: 'y',
121
+ parts: ['tank'],
122
+ source: 'NRHP 93000843 §7',
123
+ },
124
+ {
125
+ label: 'tank height (14 ft)',
126
+ value: TANK_H,
127
+ axis: 'z',
128
+ parts: ['tank'],
129
+ source: 'NRHP 93000843 §7',
130
+ },
131
+ {
132
+ label: 'superstructure height (14 ft)',
133
+ value: FLOOR_Z,
134
+ axis: 'z',
135
+ parts: ['trestle'],
136
+ source: 'NRHP 93000843 §7',
137
+ },
138
+ {
139
+ label: 'core diameter (10 ft)',
140
+ value: 10 * FT,
141
+ axis: 'y',
142
+ parts: ['core'],
143
+ source: 'NRHP 93000843 §7',
144
+ },
145
+ {
146
+ label: 'core height (14 ft)',
147
+ value: 14 * FT,
148
+ axis: 'z',
149
+ parts: ['core'],
150
+ source: 'NRHP 93000843 §7',
151
+ },
152
+ ],
153
+ },
154
+ budget: { triangles: [7900, 3350, 590] },
155
+ checks: [clear('spout', 'tank', 0.02), clear('spout', 'roof', 0.05)],
156
+ build(ctx) {
157
+ const lod = ctx.lod;
158
+ const bevel = ctx.pick([0.02, 0.01, 0]);
159
+ const facets = ctx.pick([32, 20, 8]);
160
+ const dirt = (m: Mesh): Mesh => grime(m, { ground: 1.4, groundStrength: 0.4 });
161
+
162
+ // ---- trestle: footings, eight posts, cap ring, X bracing, joists under the tank floor.
163
+ const tr: Mesh[] = [];
164
+ const postAt = Array.from({ length: 8 }, (_, k) => {
165
+ const a = (k / 8) * Math.PI * 2 + Math.PI / 8;
166
+ return [Math.cos(a) * POST_R, Math.sin(a) * POST_R] as const;
167
+ });
168
+ for (const [x, y] of postAt) {
169
+ tr.push(bx(x - 0.33, x + 0.33, y - 0.33, y + 0.33, 0, FOOT_H, concrete, 'concrete', bevel));
170
+ tr.push(
171
+ bx(
172
+ x - POST_W / 2,
173
+ x + POST_W / 2,
174
+ y - POST_W / 2,
175
+ y + POST_W / 2,
176
+ FOOT_H,
177
+ CAP_Z0,
178
+ timber,
179
+ 'wood',
180
+ bevel / 2,
181
+ ),
182
+ );
183
+ }
184
+ postAt.forEach(([x, y], k) => {
185
+ const [nx, ny] = postAt[(k + 1) % 8] ?? [x, y];
186
+ // Cap beam post → post (ring), sitting on the post tops.
187
+ tr.push(
188
+ beam({
189
+ from: [x, y, CAP_Z0 + 0.15],
190
+ to: [nx, ny, CAP_Z0 + 0.15],
191
+ width: 0.3,
192
+ height: 0.3,
193
+ color: timberDark,
194
+ strip: 'wood',
195
+ up: [0, 0, 1],
196
+ }),
197
+ );
198
+ if (lod < 2) {
199
+ // X brace on the outer face of the bay, foot of one post to the head of the next.
200
+ const mx = (x + nx) / 2;
201
+ const my = (y + ny) / 2;
202
+ const ml = Math.hypot(mx, my);
203
+ const off = 0.2 / ml;
204
+ const o = (px: number, py: number, z: number): Vec3 => [px + mx * off, py + my * off, z];
205
+ tr.push(
206
+ beam({
207
+ from: o(x, y, FOOT_H + 0.25),
208
+ to: o(nx, ny, CAP_Z0 - 0.1),
209
+ width: 0.2,
210
+ height: 0.1,
211
+ color: timber,
212
+ strip: 'wood',
213
+ up: [mx / ml, my / ml, 0],
214
+ }),
215
+ );
216
+ tr.push(
217
+ beam({
218
+ from: o(nx, ny, FOOT_H + 0.25),
219
+ to: o(x, y, CAP_Z0 - 0.1),
220
+ width: 0.2,
221
+ height: 0.1,
222
+ color: timber,
223
+ strip: 'wood',
224
+ up: [mx / ml, my / ml, 0],
225
+ }),
226
+ );
227
+ if (lod === 0) {
228
+ // Through-bolt at the crossing and at each brace end.
229
+ const bolt = cylinder({
230
+ radius: 0.035,
231
+ length: 0.06,
232
+ segments: 6,
233
+ axis: 'x',
234
+ color: steel,
235
+ });
236
+ const n = [mx / ml, my / ml];
237
+ for (const [px, py, z] of [
238
+ [mx, my, (FOOT_H + 0.25 + CAP_Z0 - 0.1) / 2],
239
+ [x, y, FOOT_H + 0.3],
240
+ [nx, ny, FOOT_H + 0.3],
241
+ [x, y, CAP_Z0 - 0.15],
242
+ [nx, ny, CAP_Z0 - 0.15],
243
+ ] as const) {
244
+ const at = o(px, py, z);
245
+ tr.push(
246
+ place(bolt, {
247
+ at: [at[0] + (n[0] ?? 0) * 0.06, at[1] + (n[1] ?? 0) * 0.06, at[2]],
248
+ yaw: Math.atan2(n[1] ?? 0, n[0] ?? 1),
249
+ }),
250
+ );
251
+ }
252
+ }
253
+ }
254
+ });
255
+ // Joists along X across the tank floor, ends showing past the staves.
256
+ const joistStep = ctx.pick([0.55, 0.9, 1.6]);
257
+ for (let y = -TANK_R + 0.35; y <= TANK_R - 0.3; y += joistStep) {
258
+ const half = Math.sqrt(Math.max(0, TANK_R * TANK_R - y * y)) + 0.15;
259
+ tr.push(bx(-half, half, y - 0.1, y + 0.1, CAP_Z1, FLOOR_Z, timberDark, 'wood'));
260
+ }
261
+ ctx.part('trestle', dirt(merge(tr)));
262
+
263
+ // ---- central core: vertical planking round a 10 ft cylinder, with its access door.
264
+ const coreN = ctx.pick([12, 12, 8]);
265
+ const core: Mesh[] = [
266
+ staveRing(CORE_R, 0, 14 * FT, coreN, timberDark, lod === 2 ? 'plain' : 'wood'),
267
+ ];
268
+ core.push(
269
+ lathe({
270
+ profile: [
271
+ [0, 14 * FT - 0.01],
272
+ [CORE_R, 14 * FT - 0.01],
273
+ [CORE_R, 14 * FT],
274
+ ],
275
+ segments: coreN,
276
+ color: timberDark,
277
+ }),
278
+ ); // lid
279
+ if (lod < 2) {
280
+ const da = Math.PI; // door faces −X, away from the track
281
+ const dx = Math.cos(da) * CORE_R * Math.cos(Math.PI / coreN);
282
+ core.push(bx(dx - 0.06, dx + 0.002, -0.4, 0.4, 0.05, 2.0, timber, 'wood'));
283
+ if (lod === 0) {
284
+ core.push(bx(dx - 0.09, dx - 0.06, -0.45, 0.45, 1.98, 2.06, timberDark, 'wood'));
285
+ for (const z of [0.4, 1.6])
286
+ core.push(bx(dx - 0.08, dx - 0.06, 0.05, 0.38, z, z + 0.05, steel, 'iron')); // strap hinges
287
+ }
288
+ }
289
+ ctx.part('core', dirt(merge(core)));
290
+
291
+ // ---- tank: stave tub, hoops (closer near the bottom), lugs, chime overhang.
292
+ ctx.part(
293
+ 'tank',
294
+ staveRing(TANK_R, FLOOR_Z, TANK_H, facets, cypress, lod === 2 ? 'plain' : 'wood'),
295
+ );
296
+ if (lod < 2) {
297
+ const hoops: Mesh[] = [];
298
+ const n = lod === 0 ? 14 : 7;
299
+ const hoopSegs = ctx.pick([32, 20, 8]);
300
+ for (let i = 0; i < n; i++) {
301
+ // Spacing grows with height: pressure falls off toward the top.
302
+ const t = (i + 0.5) / n;
303
+ const z = FLOOR_Z + 0.12 + (TANK_H - 0.3) * t ** 1.35;
304
+ const rr = TANK_R / Math.cos(Math.PI / facets) + 0.004;
305
+ hoops.push(
306
+ lathe({
307
+ profile: [
308
+ [rr, z],
309
+ [rr + 0.012, z + 0.004],
310
+ [rr + 0.012, z + 0.03],
311
+ [rr, z + 0.034],
312
+ ],
313
+ segments: hoopSegs,
314
+ color: steel,
315
+ strip: 'iron',
316
+ }),
317
+ );
318
+ if (lod === 0) {
319
+ // Lug (the hoop's tightening nut), staggered round the tank.
320
+ const a = (i * 2.399) % (Math.PI * 2);
321
+ hoops.push(
322
+ place(
323
+ box({ size: [0.05, 0.16, 0.07], at: [0, 0, -0.02], color: steel, strip: 'iron' }),
324
+ { at: [Math.cos(a) * (rr + 0.03), Math.sin(a) * (rr + 0.03), z], yaw: a },
325
+ ),
326
+ );
327
+ }
328
+ }
329
+ ctx.part('tank_hoops', merge(hoops));
330
+ }
331
+
332
+ // ---- roof: low six-sided pyramid of shingles, fascia, finial and lightning rod, hatch.
333
+ const roofM: Mesh[] = [];
334
+ const circ = ROOF_APOTHEM / Math.cos(Math.PI / 6);
335
+ const eaveZ = TANK_TOP + 0.15;
336
+ const apex: Vec3 = [0, 0, eaveZ + ROOF_RISE];
337
+ const corner = (k: number, z: number, r = circ): Vec3 => [
338
+ Math.cos((k * Math.PI) / 3) * r,
339
+ Math.sin((k * Math.PI) / 3) * r,
340
+ z,
341
+ ];
342
+ for (let k = 0; k < 6; k++) {
343
+ roofM.push(
344
+ planar({
345
+ points: [corner(k, eaveZ), corner(k + 1, eaveZ), apex],
346
+ color: shingle,
347
+ strip: 'slate',
348
+ }),
349
+ );
350
+ roofM.push(
351
+ tiled({
352
+ origin: corner(k + 1, TANK_TOP),
353
+ u: [corner(k, 0)[0] - corner(k + 1, 0)[0], corner(k, 0)[1] - corner(k + 1, 0)[1], 0],
354
+ v: [0, 0, 0.15],
355
+ color: timberDark,
356
+ strip: 'wood',
357
+ }),
358
+ ); // fascia
359
+ roofM.push(
360
+ planar({
361
+ points: [corner(k + 1, TANK_TOP), corner(k, TANK_TOP), [0, 0, TANK_TOP]],
362
+ color: timberDark,
363
+ strip: 'wood',
364
+ }),
365
+ ); // soffit
366
+ }
367
+ if (lod < 2) {
368
+ roofM.push(
369
+ lathe({
370
+ profile: [
371
+ [0.12, apex[2] - 0.05],
372
+ [0.12, apex[2] + 0.12],
373
+ [0.06, apex[2] + 0.25],
374
+ [0, apex[2] + 0.3],
375
+ ],
376
+ segments: 8,
377
+ color: steel,
378
+ }),
379
+ ); // finial cap
380
+ roofM.push(
381
+ cylinder({
382
+ radius: 0.012,
383
+ length: 0.62,
384
+ segments: 4,
385
+ at: [0, 0, apex[2] + 0.58],
386
+ color: steel,
387
+ }),
388
+ ); // lightning rod
389
+ }
390
+ if (lod === 0) {
391
+ // Hip caps (ridge boards) and a hatch on the −X slope.
392
+ for (let k = 0; k < 6; k++)
393
+ roofM.push(
394
+ beam({
395
+ from: corner(k, eaveZ + 0.02),
396
+ to: [0, 0, apex[2] + 0.02],
397
+ width: 0.1,
398
+ height: 0.04,
399
+ color: timberDark,
400
+ strip: 'wood',
401
+ }),
402
+ );
403
+ const slope = Math.atan2(ROOF_RISE, ROOF_APOTHEM);
404
+ roofM.push(
405
+ place(
406
+ box({ size: [0.7, 0.7, 0.12], at: [0, 0, -0.04], color: timberDark, strip: 'wood' }),
407
+ { at: [-ROOF_APOTHEM * 0.55, 0, eaveZ + ROOF_RISE * 0.45], pitch: -slope },
408
+ ),
409
+ );
410
+ }
411
+ ctx.part('roof', merge(roofM));
412
+
413
+ // ---- outlet + hinge on the track side; the spout swings on it.
414
+ const outlet: Mesh[] = [
415
+ cylinder({
416
+ radius: 0.2,
417
+ length: PIVOT[0] - TANK_R + 0.08,
418
+ segments: ctx.pick([14, 10, 6]),
419
+ axis: 'x',
420
+ at: [(TANK_R - 0.08 + PIVOT[0]) / 2, 0, PIVOT[2]],
421
+ color: steel,
422
+ }),
423
+ ];
424
+ if (lod < 2)
425
+ outlet.push(
426
+ cylinder({
427
+ radius: 0.27,
428
+ length: 0.06,
429
+ segments: ctx.pick([14, 10, 6]),
430
+ axis: 'x',
431
+ at: [TANK_R + 0.03, 0, PIVOT[2]],
432
+ color: steel,
433
+ }),
434
+ ); // flange
435
+ ctx.part('outlet', merge(outlet));
436
+
437
+ ctx.group('spout', {
438
+ at: PIVOT,
439
+ motion: { kind: 'sway', axis: [0, 1, 0], degrees: 15, hz: 0.1 },
440
+ clearOf: ['outlet'], // the hinge knuckle turns inside the outlet flange by design
441
+ });
442
+ const dir: Vec3 = [Math.cos(SPOUT_REST), 0, Math.sin(SPOUT_REST)];
443
+ const along = (d: number, z = 0): Vec3 => [dir[0] * d, 0, dir[2] * d + z];
444
+ const sp: Mesh[] = [
445
+ beam({
446
+ from: along(0),
447
+ to: along(SPOUT_L * 0.72),
448
+ width: 0.4,
449
+ segments: ctx.pick([12, 8, 6]),
450
+ color: steel,
451
+ }),
452
+ beam({
453
+ from: along(SPOUT_L * 0.7),
454
+ to: along(SPOUT_L),
455
+ width: 0.32,
456
+ segments: ctx.pick([12, 8, 6]),
457
+ color: steel,
458
+ }),
459
+ ];
460
+ if (lod < 2) {
461
+ sp.push(cylinder({ radius: 0.25, length: 0.42, segments: 10, axis: 'y', color: steel })); // hinge knuckle
462
+ sp.push(
463
+ place(
464
+ lathe({
465
+ profile: [
466
+ [0.16, 0],
467
+ [0.24, 0.16],
468
+ [0.24, 0.2],
469
+ ],
470
+ segments: 10,
471
+ color: steel,
472
+ }),
473
+ { at: along(SPOUT_L - 0.05), pitch: Math.PI / 2 - SPOUT_REST },
474
+ ),
475
+ );
476
+ for (const d of [0.9, 1.9])
477
+ sp.push(
478
+ place(cylinder({ radius: 0.215, length: 0.05, segments: 10, axis: 'x', color: steel }), {
479
+ at: along(d),
480
+ pitch: -SPOUT_REST,
481
+ }),
482
+ ); // bands
483
+ }
484
+ if (lod === 0) {
485
+ // Pull chain hanging from the mouth (the fireman pulls it down over the tender).
486
+ const tip = along(SPOUT_L - 0.1, -0.2);
487
+ for (let i = 0; i < 9; i++)
488
+ sp.push(
489
+ place(box({ size: [0.03, 0.06, 0.12], color: steel, strip: 'iron' }), {
490
+ at: [tip[0], 0, tip[2] - i * 0.13],
491
+ yaw: (i % 2) * (Math.PI / 2),
492
+ }),
493
+ );
494
+ }
495
+ ctx.part('spout_body', merge(sp), { parent: 'spout' });
496
+ ctx.socket('water_fill', {
497
+ kind: 'interact',
498
+ parent: 'spout',
499
+ at: along(SPOUT_L),
500
+ dir: [0, 0, -1],
501
+ });
502
+
503
+ // ---- ladder up the −X side to the eave, with stays to the tank and the cap ring.
504
+ if (lod < 2) {
505
+ const lx = -(TANK_R + 0.42);
506
+ const top = TANK_TOP - 0.1;
507
+ const lad: Mesh[] = [];
508
+ for (const y of [-0.22, 0.22])
509
+ lad.push(bx(lx - 0.05, lx + 0.05, y - 0.03, y + 0.03, 0, top, timber, 'wood'));
510
+ const rung = ctx.pick([0.3, 0.6, 1]);
511
+ for (let z = 0.3; z < top - 0.1; z += rung)
512
+ lad.push(
513
+ cylinder({
514
+ radius: 0.018,
515
+ length: 0.44,
516
+ segments: lod === 0 ? 6 : 4,
517
+ axis: 'y',
518
+ at: [lx, 0, z],
519
+ color: timber,
520
+ }),
521
+ );
522
+ for (const z of [CAP_Z0 + 0.15, FLOOR_Z + 1.5, TANK_TOP - 0.4])
523
+ for (const y of [-0.22, 0.22]) {
524
+ const r =
525
+ z > FLOOR_Z
526
+ ? TANK_R / Math.cos(Math.PI / facets) - 0.01
527
+ : POST_R * Math.cos(Math.PI / 8) + 0.05;
528
+ lad.push(
529
+ beam({
530
+ from: [lx + 0.05, y, z],
531
+ to: [-r, y * 0.6, z],
532
+ width: 0.05,
533
+ height: 0.03,
534
+ color: steel,
535
+ strip: 'iron',
536
+ }),
537
+ );
538
+ }
539
+ ctx.part('ladder', dirt(merge(lad)));
540
+ }
541
+ },
542
+ });
@@ -0,0 +1,125 @@
1
+ // argv → Args for `t3d`. No dependency, no magic: flags are few and documented in HELP.
2
+ // Bad render flags (--size/--az/--el/--fov) throw T3D_RENDER_OPTIONS with the fix.
3
+
4
+ import { Ts23dError } from '../core/index.ts';
5
+ import { checkBeautyOptions, parseSize } from '../render/index.ts';
6
+
7
+ export interface Args {
8
+ readonly cmd: string;
9
+ readonly files: string[];
10
+ readonly out: string;
11
+ readonly json: boolean;
12
+ readonly animations: boolean;
13
+ /** `build` also renders the contact sheet unless --no-shot. */
14
+ readonly shot: boolean;
15
+ /** Trim-sheet textures: shared files in <out>/ (default), embedded per GLB, or none. */
16
+ readonly sheet: 'external' | 'embed' | 'none';
17
+ readonly sheetSize: 512 | 1024 | 2048;
18
+ /** Which detail levels to build: all three by default, or e.g. only the hero mesh. */
19
+ readonly lods: readonly (0 | 1 | 2)[];
20
+ /**
21
+ * How LODs land on disk: `split` lodN.glb per level · `single` one <asset>.glb with MSFT_lod
22
+ * alternates · `both` (default): split files import everywhere, the single file is for
23
+ * MSFT_lod-aware loaders and one-file pipelines.
24
+ */
25
+ readonly lodFiles: 'split' | 'single' | 'both';
26
+ /** Beauty-render camera and size (hero, review, and build's hero.png); unset = defaults. */
27
+ readonly render: {
28
+ readonly width?: number;
29
+ readonly height?: number;
30
+ readonly azDeg?: number;
31
+ readonly elDeg?: number;
32
+ readonly fovDeg?: number;
33
+ };
34
+ }
35
+
36
+ export const HELP = `usage: t3d <command> <recipe.ts…> [flags]
37
+ build GLB × 3 LODs + hero + contact sheet + manifest.json per asset; exit 1 on any QA finding
38
+ shot contact sheet + manifest.json only (fast look)
39
+ hero beauty render only → <out>/<asset>/hero.png (3/4 product shot, textured, shadowed)
40
+ review AI review pack → <out>/<asset>/review/: hero, sheet, close-ups, LOD strip, review.md
41
+ (checklist prompt for a vision agent) and review.json (image metrics + warnings)
42
+ list the assets each module exports
43
+ flags:
44
+ --out <dir> output root (default: out) → <dir>/<asset>/{lod0,lod1,lod2}.glb hero.png sheet.png manifest.json
45
+ --no-animations skip the baked 'run' clip (default: baked, so viewers can play it)
46
+ --no-shot build without rendering hero.png and the contact sheet
47
+ --size WxH beauty render size (default 1600x900) · --az <deg> camera azimuth from the
48
+ front, default -35 (front-left 3/4) · --el <deg> elevation, default 18 · --fov <deg> 30
49
+ --sheet <mode> external (default): one shared trim_sheet_{albedo,normal,orm}.png in <out>/,
50
+ referenced by every GLB · embed: self-contained GLBs · none: vertex colour only
51
+ --sheet-size n 512 | 1024 | 2048 (default)
52
+ --lods <list> which detail levels to build: 0,1,2 (default) · 0 = hero only · 0,2 = near + far
53
+ --lods <mode> how they are written: both (default) = lod0/1/2.glb + <asset>.glb · split = lodN.glb
54
+ only · single = <asset>.glb only (LOD0 in the scene, LOD1/2 as MSFT_lod alternates
55
+ with screen-coverage switch hints; viewers without MSFT_lod show LOD0 only)
56
+ --json machine-readable output`;
57
+
58
+ export function parseArgs(argv: readonly string[]): Args {
59
+ const files: string[] = [];
60
+ let out = 'out';
61
+ let json = false;
62
+ let animations = true;
63
+ let shot = true;
64
+ let sheet: Args['sheet'] = 'external';
65
+ let sheetSize: Args['sheetSize'] = 2048;
66
+ let lods: Args['lods'] = [0, 1, 2];
67
+ let lodFiles: Args['lodFiles'] = 'both';
68
+ const render: {
69
+ width?: number;
70
+ height?: number;
71
+ azDeg?: number;
72
+ elDeg?: number;
73
+ fovDeg?: number;
74
+ } = {};
75
+ const num = (flag: string, v: string | undefined): number => {
76
+ const n = Number(v);
77
+ if (v === undefined || v.trim() === '' || !Number.isFinite(n))
78
+ throw new Ts23dError('T3D_RENDER_OPTIONS', {
79
+ cause: `${flag} needs a number of degrees, got ${v === undefined ? 'nothing' : `"${v}"`}`,
80
+ fix: `pass e.g. ${flag} ${flag === '--fov' ? 30 : flag === '--el' ? 18 : -35}`,
81
+ });
82
+ return n;
83
+ };
84
+ for (let i = 1; i < argv.length; i++) {
85
+ const a = argv[i] ?? '';
86
+ if (a === '--json') json = true;
87
+ else if (a === '--no-animations') animations = false;
88
+ else if (a === '--animations') animations = true;
89
+ else if (a === '--no-shot') shot = false;
90
+ else if (a === '--out') out = argv[++i] ?? out;
91
+ else if (a === '--sheet') {
92
+ const m = argv[++i];
93
+ sheet = m === 'embed' || m === 'none' ? m : 'external';
94
+ } else if (a === '--lods' && /^(split|single|both)$/.test(argv[i + 1] ?? '')) {
95
+ lodFiles = argv[++i] as Args['lodFiles'];
96
+ } else if (a === '--lods') {
97
+ const picked = (argv[++i] ?? '')
98
+ .split(',')
99
+ .map(Number)
100
+ .filter((n): n is 0 | 1 | 2 => n === 0 || n === 1 || n === 2);
101
+ lods = picked.length ? [...new Set(picked)].sort() : lods;
102
+ } else if (a === '--sheet-size') {
103
+ const n = Number(argv[++i]);
104
+ sheetSize = n === 512 || n === 1024 ? n : 2048;
105
+ } else if (a === '--size') Object.assign(render, parseSize(argv[++i] ?? ''));
106
+ else if (a === '--az') render.azDeg = num(a, argv[++i]);
107
+ else if (a === '--el') render.elDeg = num(a, argv[++i]);
108
+ else if (a === '--fov') render.fovDeg = num(a, argv[++i]);
109
+ else files.push(a);
110
+ }
111
+ checkBeautyOptions(render);
112
+ return {
113
+ cmd: argv[0] ?? 'help',
114
+ files,
115
+ out,
116
+ json,
117
+ animations,
118
+ shot,
119
+ sheet,
120
+ sheetSize,
121
+ lods,
122
+ lodFiles,
123
+ render,
124
+ };
125
+ }