@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,126 @@
1
+ // The QA gate: checks a compiled asset must pass before it ships. A failing gate is a build
2
+ // error (exit 1), not a warning — an agent iterates until it prints PASS.
3
+
4
+ import {
5
+ type Bounds,
6
+ boundsOf,
7
+ type CompiledAsset,
8
+ notImplemented,
9
+ toWorld,
10
+ triangleCount,
11
+ worldTransforms,
12
+ } from '../core/index.ts';
13
+ import { runChecks } from './checks.ts';
14
+ import { clashes } from './clash.ts';
15
+ import { floatingParts } from './floating.ts';
16
+ import { measures } from './measures.ts';
17
+ import { hiddenParts } from './occlusion.ts';
18
+ import { nonFinite, normalsMatchWinding, vertexColors } from './sanity.ts';
19
+ import type { Finding, Gate } from './types.ts';
20
+
21
+ const worldBounds = (asset: CompiledAsset): Map<string, Bounds> => {
22
+ const xf = worldTransforms(asset.parts);
23
+ const out = new Map<string, Bounds>();
24
+ for (const p of asset.parts) {
25
+ const t = xf.get(p.name);
26
+ if (!t || !p.mesh || p.mesh.indices.length === 0) continue;
27
+ const b = boundsOf(p.mesh);
28
+ const corners = [0, 1, 2, 3, 4, 5, 6, 7].map((i) =>
29
+ toWorld(t, [
30
+ i & 1 ? b.max[0] : b.min[0],
31
+ i & 2 ? b.max[1] : b.min[1],
32
+ i & 4 ? b.max[2] : b.min[2],
33
+ ]),
34
+ );
35
+ const axis = (k: 0 | 1 | 2, f: (...v: number[]) => number): number =>
36
+ f(...corners.map((c) => c[k]));
37
+ out.set(p.name, {
38
+ min: [axis(0, Math.min), axis(1, Math.min), axis(2, Math.min)],
39
+ max: [axis(0, Math.max), axis(1, Math.max), axis(2, Math.max)],
40
+ });
41
+ }
42
+ return out;
43
+ };
44
+
45
+ export const triangleBudget: Gate = (asset) => {
46
+ const tris = asset.parts.reduce((s, p) => s + (p.mesh ? triangleCount(p.mesh) : 0), 0);
47
+ const cap = asset.budget.triangles[asset.lod];
48
+ return tris > cap
49
+ ? [
50
+ {
51
+ gate: 'triangle-budget',
52
+ message: `LOD${asset.lod} has ${tris} triangles, budget ${cap}`,
53
+ fix: 'drop detail at this LOD (read ctx.lod) or raise the budget with a reason',
54
+ },
55
+ ]
56
+ : [];
57
+ };
58
+
59
+ export const partNames: Gate = (asset) =>
60
+ asset.parts
61
+ .filter((p) => !/^[a-z][a-z0-9_]*$/.test(p.name))
62
+ .map((p) => ({
63
+ gate: 'part-names',
64
+ part: p.name,
65
+ message: `part '${p.name}' is not snake_case`,
66
+ fix: 'rename it — engines bind to node names like axle_0, door_l',
67
+ }));
68
+
69
+ /** Overall bounding box vs the recipe's `real` dimensions: catches a 6 m tank modelled at 7.4 m. */
70
+ export const proportions: Gate = (asset) => {
71
+ const real = asset.real;
72
+ if (!real || asset.lod !== 0) return [];
73
+ const boxes = [...worldBounds(asset).values()];
74
+ const span = (k: 0 | 1 | 2): number =>
75
+ Math.max(...boxes.map((b) => b.max[k])) - Math.min(...boxes.map((b) => b.min[k]));
76
+ const tol = real.tolerance ?? 0.05;
77
+ return (
78
+ [
79
+ ['length', 0, real.length],
80
+ ['width', 1, real.width],
81
+ ['height', 2, real.height],
82
+ ] as const
83
+ ).flatMap(([axis, k, want]) => {
84
+ const got = span(k);
85
+ return Math.abs(got - want) / want > tol
86
+ ? [
87
+ {
88
+ gate: 'proportions',
89
+ message: `${axis} ${got.toFixed(2)} m vs real ${want} m (±${tol * 100}%)`,
90
+ fix: `rescale along ${'XYZ'[k]} — re-derive the lengths from the sources, don't fudge the tolerance`,
91
+ },
92
+ ]
93
+ : [];
94
+ });
95
+ };
96
+
97
+ /** Planned gates, signatures fixed: docs/idea/06-qa.md. */
98
+ export const silhouetteIoU = (_asset: CompiledAsset, _referencePng: Uint8Array): number =>
99
+ notImplemented('silhouette IoU gate');
100
+
101
+ /** The recipe's own `checks`, each finding reported under the gate name `check`. */
102
+ export const recipeChecks: Gate = (asset) =>
103
+ runChecks(asset).map((f) => ({
104
+ gate: 'check',
105
+ ...(f.part ? { part: f.part } : {}),
106
+ message: f.message,
107
+ fix: f.fix,
108
+ }));
109
+
110
+ export const DEFAULT_GATES: readonly Gate[] = [
111
+ triangleBudget,
112
+ floatingParts,
113
+ clashes,
114
+ partNames,
115
+ proportions,
116
+ measures,
117
+ recipeChecks,
118
+ nonFinite,
119
+ vertexColors,
120
+ hiddenParts,
121
+ normalsMatchWinding,
122
+ ];
123
+
124
+ export function runQa(asset: CompiledAsset, gates: readonly Gate[] = DEFAULT_GATES): Finding[] {
125
+ return gates.flatMap((g) => g(asset));
126
+ }
@@ -0,0 +1,187 @@
1
+ // Exact-enough geometry for QA: point↔triangle distance, a triangle spatial hash, and surface
2
+ // samples. Bounding boxes lie (a diagonal strut's box touches everything); surfaces don't.
3
+
4
+ import { boundsOf, type Mesh, type Vec3 } from '../core/index.ts';
5
+
6
+ const at = (m: Mesh, i: number): Vec3 => [
7
+ m.positions[i * 3] ?? 0,
8
+ m.positions[i * 3 + 1] ?? 0,
9
+ m.positions[i * 3 + 2] ?? 0,
10
+ ];
11
+ const sub = (a: Vec3, b: Vec3): Vec3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
12
+ const dot = (a: Vec3, b: Vec3): number => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
13
+ const lerp = (a: Vec3, b: Vec3, t: number): Vec3 => [
14
+ a[0] + (b[0] - a[0]) * t,
15
+ a[1] + (b[1] - a[1]) * t,
16
+ a[2] + (b[2] - a[2]) * t,
17
+ ];
18
+
19
+ export function triangle(m: Mesh, t: number): [Vec3, Vec3, Vec3] {
20
+ return [
21
+ at(m, m.indices[t * 3] ?? 0),
22
+ at(m, m.indices[t * 3 + 1] ?? 0),
23
+ at(m, m.indices[t * 3 + 2] ?? 0),
24
+ ];
25
+ }
26
+
27
+ /** Distance from p to triangle abc (Ericson, Real-Time Collision Detection §5.1.5). */
28
+ export function pointTriangleDistance(p: Vec3, a: Vec3, b: Vec3, c: Vec3): number {
29
+ const ab = sub(b, a);
30
+ const ac = sub(c, a);
31
+ const ap = sub(p, a);
32
+ const d1 = dot(ab, ap);
33
+ const d2 = dot(ac, ap);
34
+ if (d1 <= 0 && d2 <= 0) return Math.hypot(...ap);
35
+ const bp = sub(p, b);
36
+ const d3 = dot(ab, bp);
37
+ const d4 = dot(ac, bp);
38
+ if (d3 >= 0 && d4 <= d3) return Math.hypot(...bp);
39
+ const vc = d1 * d4 - d3 * d2;
40
+ if (vc <= 0 && d1 >= 0 && d3 <= 0) return Math.hypot(...sub(p, lerp(a, b, d1 / (d1 - d3))));
41
+ const cp = sub(p, c);
42
+ const d5 = dot(ab, cp);
43
+ const d6 = dot(ac, cp);
44
+ if (d6 >= 0 && d5 <= d6) return Math.hypot(...cp);
45
+ const vb = d5 * d2 - d1 * d6;
46
+ if (vb <= 0 && d2 >= 0 && d6 <= 0) return Math.hypot(...sub(p, lerp(a, c, d2 / (d2 - d6))));
47
+ const va = d3 * d6 - d5 * d4;
48
+ if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0)
49
+ return Math.hypot(...sub(p, lerp(b, c, (d4 - d3) / (d4 - d3 + (d5 - d6)))));
50
+ const den = 1 / (va + vb + vc);
51
+ const v = vb * den;
52
+ const w = vc * den;
53
+ return Math.hypot(
54
+ ...sub(p, [
55
+ a[0] + ab[0] * v + ac[0] * w,
56
+ a[1] + ab[1] * v + ac[1] * w,
57
+ a[2] + ab[2] * v + ac[2] * w,
58
+ ]),
59
+ );
60
+ }
61
+
62
+ // Numeric cell keys (no string building per query) — collisions only add candidates, never drop.
63
+ const key = (x: number, y: number, z: number): number =>
64
+ ((x * 73856093) ^ (y * 19349663) ^ (z * 83492791)) | 0;
65
+
66
+ /** Triangles bucketed by a uniform grid, so a point only tests its neighbourhood. */
67
+ export class TriangleGrid {
68
+ private readonly cells = new Map<number, number[]>();
69
+ /** Cell edge: 25 cm for props and vehicles, growing with big meshes (a 330 m tower) so one
70
+ * long triangle never lands in 10⁵ cells. */
71
+ private readonly cell: number;
72
+ private readonly tris: Float64Array;
73
+ /** Per-triangle AABB (min xyz, max xyz) for a cheap reject before the exact distance. */
74
+ private readonly boxes: Float64Array;
75
+ private readonly stamp: Int32Array;
76
+ private query = 0;
77
+
78
+ constructor(mesh: Mesh) {
79
+ const n = mesh.indices.length / 3;
80
+ this.tris = new Float64Array(n * 9);
81
+ this.boxes = new Float64Array(n * 6);
82
+ this.stamp = new Int32Array(n);
83
+ const b = boundsOf(mesh);
84
+ const diag = Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]);
85
+ this.cell = Math.max(0.25, diag / 256);
86
+ const CELL = this.cell;
87
+ for (let t = 0; t < n; t++) {
88
+ const [a, b, c] = triangle(mesh, t);
89
+ this.tris.set([...a, ...b, ...c], t * 9);
90
+ for (let k = 0; k < 3; k++) {
91
+ this.boxes[t * 6 + k] = Math.min(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0);
92
+ this.boxes[t * 6 + 3 + k] = Math.max(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0);
93
+ }
94
+ const lo = [0, 1, 2].map((k) => Math.floor(Math.min(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0) / CELL));
95
+ const hi = [0, 1, 2].map((k) => Math.floor(Math.max(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0) / CELL));
96
+ for (let x = lo[0] ?? 0; x <= (hi[0] ?? 0); x++)
97
+ for (let y = lo[1] ?? 0; y <= (hi[1] ?? 0); y++)
98
+ for (let z = lo[2] ?? 0; z <= (hi[2] ?? 0); z++) {
99
+ const k = key(x, y, z);
100
+ const list = this.cells.get(k);
101
+ if (list) list.push(t);
102
+ else this.cells.set(k, [t]);
103
+ }
104
+ }
105
+ }
106
+
107
+ /** Distance from p to the nearest triangle, if one is within `radius`; else Infinity. */
108
+ nearest(p: Vec3, radius: number): number {
109
+ const CELL = this.cell;
110
+ // Only the cells the query sphere overlaps — usually 1–8, not a fixed 27.
111
+ const [x0, y0, z0] = [
112
+ Math.floor((p[0] - radius) / CELL),
113
+ Math.floor((p[1] - radius) / CELL),
114
+ Math.floor((p[2] - radius) / CELL),
115
+ ];
116
+ const [x1, y1, z1] = [
117
+ Math.floor((p[0] + radius) / CELL),
118
+ Math.floor((p[1] + radius) / CELL),
119
+ Math.floor((p[2] + radius) / CELL),
120
+ ];
121
+ const q = ++this.query;
122
+ const T = this.tris;
123
+ const B = this.boxes;
124
+ let best = Infinity;
125
+ for (let x = x0; x <= x1; x++)
126
+ for (let y = y0; y <= y1; y++)
127
+ for (let z = z0; z <= z1; z++) {
128
+ const list = this.cells.get(key(x, y, z));
129
+ if (!list) continue;
130
+ for (const t of list) {
131
+ if (this.stamp[t] === q) continue;
132
+ this.stamp[t] = q;
133
+ const lim = Math.min(best, radius);
134
+ const o6 = t * 6;
135
+ if (
136
+ p[0] < (B[o6] ?? 0) - lim ||
137
+ p[0] > (B[o6 + 3] ?? 0) + lim ||
138
+ p[1] < (B[o6 + 1] ?? 0) - lim ||
139
+ p[1] > (B[o6 + 4] ?? 0) + lim ||
140
+ p[2] < (B[o6 + 2] ?? 0) - lim ||
141
+ p[2] > (B[o6 + 5] ?? 0) + lim
142
+ )
143
+ continue;
144
+ const o = t * 9;
145
+ const d = pointTriangleDistance(
146
+ p,
147
+ [T[o] ?? 0, T[o + 1] ?? 0, T[o + 2] ?? 0],
148
+ [T[o + 3] ?? 0, T[o + 4] ?? 0, T[o + 5] ?? 0],
149
+ [T[o + 6] ?? 0, T[o + 7] ?? 0, T[o + 8] ?? 0],
150
+ );
151
+ if (d < best) best = d;
152
+ }
153
+ }
154
+ return best <= radius ? best : Infinity;
155
+ }
156
+ }
157
+
158
+ // Gates and checks ask for the same mesh's samples many times; compute each (mesh, density) once.
159
+ const sampleCache = new WeakMap<Mesh, Map<string, Vec3[]>>();
160
+
161
+ /** Vertices plus points along every edge every `step` metres (catches crossing members). */
162
+ export function surfaceSamples(m: Mesh, step = 0.05, maxPerEdge = 16): Vec3[] {
163
+ const key = `${step}:${maxPerEdge}`;
164
+ const byMesh = sampleCache.get(m) ?? new Map<string, Vec3[]>();
165
+ sampleCache.set(m, byMesh);
166
+ const hit = byMesh.get(key);
167
+ if (hit) return hit;
168
+ const out = sampleMesh(m, step, maxPerEdge);
169
+ byMesh.set(key, out);
170
+ return out;
171
+ }
172
+
173
+ function sampleMesh(m: Mesh, step: number, maxPerEdge: number): Vec3[] {
174
+ const out: Vec3[] = [];
175
+ for (let i = 0; i < m.positions.length / 3; i++) out.push(at(m, i));
176
+ for (let t = 0; t < m.indices.length / 3; t++) {
177
+ const tri = triangle(m, t);
178
+ for (let e = 0; e < 3; e++) {
179
+ const a = tri[e] ?? tri[0];
180
+ const b = tri[(e + 1) % 3] ?? tri[0];
181
+ const n = Math.min(maxPerEdge, Math.floor(Math.hypot(...sub(b, a)) / step));
182
+ for (let s = 1; s < n; s++) out.push(lerp(a, b, s / n));
183
+ }
184
+ out.push(lerp(lerp(tri[0], tri[1], 0.5), tri[2], 1 / 3));
185
+ }
186
+ return out;
187
+ }
@@ -0,0 +1,28 @@
1
+ // Public API of /qa: the gate every compiled asset passes before it ships.
2
+
3
+ export { clear, noOverlap, runChecks, symmetric, touches } from './checks.ts';
4
+ export { clashes } from './clash.ts';
5
+ export { floatingParts, touching } from './floating.ts';
6
+ export {
7
+ DEFAULT_GATES,
8
+ partNames,
9
+ proportions,
10
+ runQa,
11
+ silhouetteIoU,
12
+ triangleBudget,
13
+ } from './gates.ts';
14
+ export { pointTriangleDistance, surfaceSamples, TriangleGrid } from './geometry.ts';
15
+ export { insideSolid } from './inside.ts';
16
+ export { glob, type Inspector, inspect, type Penetration } from './inspect.ts';
17
+ export {
18
+ DEFAULT_LOD_GATES,
19
+ type LodGate,
20
+ lodPivots,
21
+ lodSilhouette,
22
+ lodTriangles,
23
+ runLodQa,
24
+ } from './lods.ts';
25
+ export { measure, measures } from './measures.ts';
26
+ export { hiddenParts, zFighting } from './occlusion.ts';
27
+ export { degenerateTriangles, nonFinite, normalsMatchWinding, vertexColors } from './sanity.ts';
28
+ export { CLASH_EPS, CONTACT_EPS, type Finding, type Gate } from './types.ts';
@@ -0,0 +1,114 @@
1
+ // Point-in-solid for closed meshes, indexed and allocation-free: axis-aligned rays (+Z, +Y, +X)
2
+ // against triangles bucketed in a 2D grid perpendicular to each ray. An axis-aligned ray hitting a
3
+ // triangle is a 2D point-in-triangle test in the other two axes plus one interpolated depth.
4
+ // Majority of the three rays (short-circuited), so grazing an edge cannot flip the answer.
5
+ // Open sheets (planar decals) are never "solid"; penetration only means something vs closed parts.
6
+
7
+ import { boundsOf, type Mesh, type Vec3 } from '../core/index.ts';
8
+
9
+ /** Column size: 20 cm for props and vehicles, growing with the mesh so a 330 m tower's long
10
+ * members don't land in thousands of columns each (answers never depend on the cell size). */
11
+ const cellFor = (m: Mesh): number => {
12
+ const b = boundsOf(m);
13
+ return Math.max(
14
+ 0.2,
15
+ Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]) / 256,
16
+ );
17
+ };
18
+ // A tiny fixed offset keeps the query point off shared edges/vertices of axis-aligned geometry.
19
+ const NUDGE: Vec3 = [1.37e-5, 2.91e-5, 3.73e-5];
20
+ const OTHER = [
21
+ [1, 2],
22
+ [0, 2],
23
+ [0, 1],
24
+ ] as const;
25
+
26
+ export class SolidIndex {
27
+ private readonly tri: Float64Array;
28
+ private readonly columns: Map<number, number[]>[] = [new Map(), new Map(), new Map()];
29
+ private readonly cell: number;
30
+
31
+ constructor(mesh: Mesh) {
32
+ this.cell = cellFor(mesh);
33
+ const CELL = this.cell;
34
+ const n = mesh.indices.length / 3;
35
+ this.tri = new Float64Array(n * 9);
36
+ for (let t = 0; t < n; t++) {
37
+ for (let v = 0; v < 3; v++) {
38
+ const idx = mesh.indices[t * 3 + v] ?? 0;
39
+ for (let k = 0; k < 3; k++) this.tri[t * 9 + v * 3 + k] = mesh.positions[idx * 3 + k] ?? 0;
40
+ }
41
+ for (const axis of [0, 1, 2] as const) {
42
+ const [i, j] = OTHER[axis];
43
+ const T = this.tri;
44
+ const o = t * 9;
45
+ const a = T[o + i] ?? 0;
46
+ const b = T[o + 3 + i] ?? 0;
47
+ const c = T[o + 6 + i] ?? 0;
48
+ const d = T[o + j] ?? 0;
49
+ const e = T[o + 3 + j] ?? 0;
50
+ const f = T[o + 6 + j] ?? 0;
51
+ const u0 = Math.floor(Math.min(a, b, c) / CELL);
52
+ const u1 = Math.floor(Math.max(a, b, c) / CELL);
53
+ const v0 = Math.floor(Math.min(d, e, f) / CELL);
54
+ const v1 = Math.floor(Math.max(d, e, f) / CELL);
55
+ const map = this.columns[axis];
56
+ if (!map) continue;
57
+ for (let u = u0; u <= u1; u++)
58
+ for (let v = v0; v <= v1; v++) {
59
+ const k = (u * 92837111) ^ (v * 689287499);
60
+ const list = map.get(k);
61
+ if (list) list.push(t);
62
+ else map.set(k, [t]);
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ /** Odd number of crossings of the ray from p along +axis ⇒ inside (for that ray). */
69
+ private parity(px: number, py: number, pz: number, axis: 0 | 1 | 2): boolean {
70
+ const [i, j] = OTHER[axis];
71
+ const p = [px, py, pz];
72
+ const pi = p[i] ?? 0;
73
+ const pj = p[j] ?? 0;
74
+ const pk = p[axis] ?? 0;
75
+ const CELL = this.cell;
76
+ const list = this.columns[axis]?.get(
77
+ (Math.floor(pi / CELL) * 92837111) ^ (Math.floor(pj / CELL) * 689287499),
78
+ );
79
+ if (!list) return false;
80
+ const T = this.tri;
81
+ let n = 0;
82
+ for (const t of list) {
83
+ const o = t * 9;
84
+ const ai = T[o + i] ?? 0;
85
+ const aj = T[o + j] ?? 0;
86
+ const bi = T[o + 3 + i] ?? 0;
87
+ const bj = T[o + 3 + j] ?? 0;
88
+ const ci = T[o + 6 + i] ?? 0;
89
+ const cj = T[o + 6 + j] ?? 0;
90
+ const area = (bi - ai) * (cj - aj) - (bj - aj) * (ci - ai);
91
+ if (Math.abs(area) < 1e-14) continue;
92
+ const w0 = ((bi - pi) * (cj - pj) - (bj - pj) * (ci - pi)) / area;
93
+ const w1 = ((ci - pi) * (aj - pj) - (cj - pj) * (ai - pi)) / area;
94
+ const w2 = 1 - w0 - w1;
95
+ if (w0 < 0 || w1 < 0 || w2 < 0) continue;
96
+ const depth =
97
+ w0 * (T[o + axis] ?? 0) + w1 * (T[o + 3 + axis] ?? 0) + w2 * (T[o + 6 + axis] ?? 0);
98
+ if (depth > pk) n++;
99
+ }
100
+ return n % 2 === 1;
101
+ }
102
+
103
+ inside(point: Vec3): boolean {
104
+ const x = point[0] + NUDGE[0];
105
+ const y = point[1] + NUDGE[1];
106
+ const z = point[2] + NUDGE[2];
107
+ const vz = this.parity(x, y, z, 2);
108
+ const vy = this.parity(x, y, z, 1);
109
+ return vz === vy ? vz : this.parity(x, y, z, 0);
110
+ }
111
+ }
112
+
113
+ /** One-off query; build a SolidIndex yourself when testing many points against one mesh. */
114
+ export const insideSolid = (p: Vec3, m: Mesh): boolean => new SolidIndex(m).inside(p);
@@ -0,0 +1,107 @@
1
+ // Inspector: geometry queries over a compiled asset in world space, for checks, scripts and
2
+ // agents ("how far is the track from the hull?", "does the wheel sink into the fender?").
3
+ // Part selectors are names or globs: 'track_l_*', 'road_wheel_*_3', '*_hatch'.
4
+
5
+ import {
6
+ type Bounds,
7
+ boundsOf,
8
+ type CompiledAsset,
9
+ type Mesh,
10
+ merge,
11
+ worldMeshes,
12
+ } from '../core/index.ts';
13
+ import { surfaceSamples, TriangleGrid } from './geometry.ts';
14
+ import { SolidIndex } from './inside.ts';
15
+
16
+ export const glob = (pattern: string): RegExp =>
17
+ new RegExp(
18
+ `^${pattern
19
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
20
+ .replaceAll('*', '.*')
21
+ .replaceAll('?', '.')}$`,
22
+ );
23
+
24
+ export interface Penetration {
25
+ /** Deepest sampled point of A inside B's solid, metres (0 = none). */
26
+ readonly depth: number;
27
+ readonly samples: number;
28
+ }
29
+
30
+ export interface Inspector {
31
+ readonly asset: CompiledAsset;
32
+ parts(selector: string): string[];
33
+ mesh(selector: string): Mesh | null;
34
+ bounds(selector: string): Bounds | null;
35
+ /** Smallest surface-to-surface distance (0 when touching or intersecting), capped at `max`. */
36
+ distance(a: string, b: string, max?: number): number;
37
+ /** How deep A's surface goes inside B's closed solid. */
38
+ penetration(a: string, b: string): Penetration;
39
+ }
40
+
41
+ export function inspect(asset: CompiledAsset): Inspector {
42
+ const world = worldMeshes(asset.parts);
43
+ const parts = (sel: string): string[] => [...world.keys()].filter((n) => glob(sel).test(n));
44
+ // Checks ask about the same parts over and over: build each merged mesh and index once.
45
+ const meshes = new Map<string, Mesh | null>();
46
+ const grids = new Map<string, TriangleGrid>();
47
+ const solids = new Map<string, SolidIndex>();
48
+ const samples = new Map<string, ReturnType<typeof surfaceSamples>>();
49
+ const mesh = (sel: string): Mesh | null => {
50
+ if (!meshes.has(sel)) {
51
+ const ms = parts(sel).flatMap((n) => world.get(n) ?? []);
52
+ meshes.set(sel, ms.length ? merge(ms) : null);
53
+ }
54
+ return meshes.get(sel) ?? null;
55
+ };
56
+ const cached = <T>(map: Map<string, T>, key: string, make: () => T): T => {
57
+ const hit = map.get(key);
58
+ if (hit !== undefined) return hit;
59
+ const v = make();
60
+ map.set(key, v);
61
+ return v;
62
+ };
63
+ const near = (a: Bounds, b: Bounds, eps: number): boolean =>
64
+ [0, 1, 2].every(
65
+ (k) => (a.min[k] ?? 0) <= (b.max[k] ?? 0) + eps && (b.min[k] ?? 0) <= (a.max[k] ?? 0) + eps,
66
+ );
67
+ return {
68
+ asset,
69
+ parts,
70
+ mesh,
71
+ bounds: (sel) => {
72
+ const m = mesh(sel);
73
+ return m ? boundsOf(m) : null;
74
+ },
75
+ distance(a, b, max = 1) {
76
+ const ma = mesh(a);
77
+ const mb = mesh(b);
78
+ if (!ma || !mb || !near(boundsOf(ma), boundsOf(mb), max)) return max;
79
+ const grid = cached(grids, b, () => new TriangleGrid(mb));
80
+ const pts = cached(samples, a, () => surfaceSamples(ma, 0.05, 8));
81
+ // Grow the search radius: "do they touch?" is answered at 2 cm without scanning metres.
82
+ for (const r of [0.02, 0.1, 0.5, max].filter((x) => x <= max)) {
83
+ let best = Infinity;
84
+ for (const p of pts) best = Math.min(best, grid.nearest(p, Math.min(r, best)));
85
+ if (best <= r) return best;
86
+ }
87
+ return max;
88
+ },
89
+ penetration(a, b) {
90
+ const ma = mesh(a);
91
+ const mb = mesh(b);
92
+ if (!ma || !mb) return { depth: 0, samples: 0 };
93
+ const bb = boundsOf(mb);
94
+ if (!near(boundsOf(ma), bb, 0)) return { depth: 0, samples: 0 };
95
+ const grid = cached(grids, b, () => new TriangleGrid(mb));
96
+ const solid = cached(solids, b, () => new SolidIndex(mb));
97
+ let depth = 0;
98
+ let count = 0;
99
+ for (const p of cached(samples, `${a}#pen`, () => surfaceSamples(ma, 0.1, 3))) {
100
+ if (!near({ min: p, max: p }, bb, 0) || !solid.inside(p)) continue;
101
+ count++;
102
+ depth = Math.max(depth, grid.nearest(p, 0.5));
103
+ }
104
+ return { depth, samples: count };
105
+ },
106
+ };
107
+ }
package/src/qa/lods.ts ADDED
@@ -0,0 +1,97 @@
1
+ // Gates across the LOD chain (they need all three compiled LODs at once): triangle counts fall
2
+ // LOD0 ≥ LOD1 ≥ LOD2, the far LODs keep the near one's silhouette box (no popping when the engine
3
+ // switches), and every pivot the engine binds to (groups, moving parts) survives at every LOD.
4
+
5
+ import { type Bounds, boundsOf, type CompiledAsset, worldMeshes } from '../core/index.ts';
6
+ import type { Finding } from './types.ts';
7
+
8
+ /** A gate over the whole LOD chain, index = LOD. */
9
+ export type LodGate = (lods: readonly CompiledAsset[]) => Finding[];
10
+
11
+ const tris = (a: CompiledAsset): number =>
12
+ a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
13
+
14
+ export const lodTriangles: LodGate = (lods) =>
15
+ lods.slice(1).flatMap((a, i): Finding[] => {
16
+ const prev = lods[i];
17
+ if (!prev || tris(a) <= tris(prev)) return [];
18
+ return [
19
+ {
20
+ gate: 'lod-triangles',
21
+ message: `LOD${a.lod} has ${tris(a)} triangles, more than LOD${prev.lod} (${tris(prev)})`,
22
+ fix: 'each LOD must be cheaper than the one before: read ctx.lod / ctx.pick([...]) and drop segments or detail parts',
23
+ },
24
+ ];
25
+ });
26
+
27
+ function assetBounds(a: CompiledAsset): Bounds | null {
28
+ const all = [...worldMeshes(a.parts).values()].map(boundsOf);
29
+ if (all.length === 0) return null;
30
+ const k = [0, 1, 2] as const;
31
+ const lo = k.map((i) => Math.min(...all.map((b) => b.min[i])));
32
+ const hi = k.map((i) => Math.max(...all.map((b) => b.max[i])));
33
+ return {
34
+ min: [lo[0] ?? 0, lo[1] ?? 0, lo[2] ?? 0],
35
+ max: [hi[0] ?? 0, hi[1] ?? 0, hi[2] ?? 0],
36
+ };
37
+ }
38
+
39
+ /** LOD1/LOD2 overall box within `tol` of LOD0's span per side (min 5 cm). */
40
+ export const lodSilhouette =
41
+ (tol = 0.05): LodGate =>
42
+ (lods) => {
43
+ const ref = lods[0] ? assetBounds(lods[0]) : null;
44
+ if (!ref) return [];
45
+ return lods.slice(1).flatMap((a): Finding[] => {
46
+ const b = assetBounds(a);
47
+ if (!b) return [];
48
+ const off = ([0, 1, 2] as const).flatMap((k) => {
49
+ const lim = Math.max(0.05, (ref.max[k] - ref.min[k]) * tol);
50
+ const d = Math.max(Math.abs(b.min[k] - ref.min[k]), Math.abs(b.max[k] - ref.max[k]));
51
+ return d > lim ? [`${'XYZ'[k]} by ${(d * 100).toFixed(0)} cm`] : [];
52
+ });
53
+ return off.length
54
+ ? [
55
+ {
56
+ gate: 'lod-silhouette',
57
+ message: `LOD${a.lod} outline differs from LOD0 on ${off.join(', ')} — it will pop on LOD switch`,
58
+ fix: 'keep the big masses at every LOD (drop detail, not extent): a far LOD is the same shape with fewer segments',
59
+ },
60
+ ]
61
+ : [];
62
+ });
63
+ };
64
+
65
+ /** Pivots = what an engine binds code to: empty groups and parts with motion. */
66
+ const pivots = (a: CompiledAsset): string[] =>
67
+ a.parts.filter((p) => p.mesh === null || p.motion !== null).map((p) => p.name);
68
+
69
+ export const lodPivots: LodGate = (lods) => {
70
+ const ref = lods[0];
71
+ if (!ref) return [];
72
+ const want = pivots(ref);
73
+ return lods.slice(1).flatMap((a): Finding[] => {
74
+ const have = new Set(a.parts.map((p) => p.name));
75
+ const missing = want.filter((n) => !have.has(n));
76
+ return missing.length
77
+ ? [
78
+ {
79
+ gate: 'lod-pivots',
80
+ message: `LOD${a.lod} drops pivot(s) ${missing.slice(0, 6).join(', ')}${missing.length > 6 ? ` (+${missing.length - 6})` : ''} that LOD0 has — game code driving them breaks at that LOD`,
81
+ fix: 'keep the group/moving part at every LOD and gate only its geometry: ctx.group(name) always, detail meshes behind ctx.lod',
82
+ },
83
+ ]
84
+ : [];
85
+ });
86
+ };
87
+
88
+ /** Default chain gates: only what every gallery recipe passes today. `lodSilhouette` and
89
+ * `lodPivots` are opt-in until the recipes they flag are fixed (see their findings). */
90
+ export const DEFAULT_LOD_GATES: readonly LodGate[] = [lodTriangles];
91
+
92
+ export function runLodQa(
93
+ lods: readonly CompiledAsset[],
94
+ gates: readonly LodGate[] = DEFAULT_LOD_GATES,
95
+ ): Finding[] {
96
+ return gates.flatMap((g) => g(lods));
97
+ }