@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,1031 @@
1
+ import {
2
+ notImplemented2,
3
+ triangleCount2,
4
+ boundsOf2,
5
+ merge2,
6
+ worldTransforms2,
7
+ toWorld2,
8
+ worldMeshes2
9
+ } from "./chunk-hg7pf34j.js";
10
+
11
+ // src/qa/geometry.ts
12
+ var at = (m, i) => [
13
+ m.positions[i * 3] ?? 0,
14
+ m.positions[i * 3 + 1] ?? 0,
15
+ m.positions[i * 3 + 2] ?? 0
16
+ ];
17
+ var sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
18
+ var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
19
+ var lerp = (a, b, t) => [
20
+ a[0] + (b[0] - a[0]) * t,
21
+ a[1] + (b[1] - a[1]) * t,
22
+ a[2] + (b[2] - a[2]) * t
23
+ ];
24
+ function triangle(m, t) {
25
+ return [
26
+ at(m, m.indices[t * 3] ?? 0),
27
+ at(m, m.indices[t * 3 + 1] ?? 0),
28
+ at(m, m.indices[t * 3 + 2] ?? 0)
29
+ ];
30
+ }
31
+ function pointTriangleDistance2(p, a, b, c) {
32
+ const ab = sub(b, a);
33
+ const ac = sub(c, a);
34
+ const ap = sub(p, a);
35
+ const d1 = dot(ab, ap);
36
+ const d2 = dot(ac, ap);
37
+ if (d1 <= 0 && d2 <= 0)
38
+ return Math.hypot(...ap);
39
+ const bp = sub(p, b);
40
+ const d3 = dot(ab, bp);
41
+ const d4 = dot(ac, bp);
42
+ if (d3 >= 0 && d4 <= d3)
43
+ return Math.hypot(...bp);
44
+ const vc = d1 * d4 - d3 * d2;
45
+ if (vc <= 0 && d1 >= 0 && d3 <= 0)
46
+ return Math.hypot(...sub(p, lerp(a, b, d1 / (d1 - d3))));
47
+ const cp = sub(p, c);
48
+ const d5 = dot(ab, cp);
49
+ const d6 = dot(ac, cp);
50
+ if (d6 >= 0 && d5 <= d6)
51
+ return Math.hypot(...cp);
52
+ const vb = d5 * d2 - d1 * d6;
53
+ if (vb <= 0 && d2 >= 0 && d6 <= 0)
54
+ return Math.hypot(...sub(p, lerp(a, c, d2 / (d2 - d6))));
55
+ const va = d3 * d6 - d5 * d4;
56
+ if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0)
57
+ return Math.hypot(...sub(p, lerp(b, c, (d4 - d3) / (d4 - d3 + (d5 - d6)))));
58
+ const den = 1 / (va + vb + vc);
59
+ const v = vb * den;
60
+ const w = vc * den;
61
+ return Math.hypot(...sub(p, [
62
+ a[0] + ab[0] * v + ac[0] * w,
63
+ a[1] + ab[1] * v + ac[1] * w,
64
+ a[2] + ab[2] * v + ac[2] * w
65
+ ]));
66
+ }
67
+ var key = (x, y, z) => x * 73856093 ^ y * 19349663 ^ z * 83492791 | 0;
68
+
69
+ class TriangleGrid2 {
70
+ cells = new Map;
71
+ cell;
72
+ tris;
73
+ boxes;
74
+ stamp;
75
+ query = 0;
76
+ constructor(mesh) {
77
+ const n = mesh.indices.length / 3;
78
+ this.tris = new Float64Array(n * 9);
79
+ this.boxes = new Float64Array(n * 6);
80
+ this.stamp = new Int32Array(n);
81
+ const b = boundsOf2(mesh);
82
+ const diag = Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]);
83
+ this.cell = Math.max(0.25, diag / 256);
84
+ const CELL = this.cell;
85
+ for (let t = 0;t < n; t++) {
86
+ const [a, b, c] = triangle(mesh, t);
87
+ this.tris.set([...a, ...b, ...c], t * 9);
88
+ for (let k = 0;k < 3; k++) {
89
+ this.boxes[t * 6 + k] = Math.min(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0);
90
+ this.boxes[t * 6 + 3 + k] = Math.max(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0);
91
+ }
92
+ const lo = [0, 1, 2].map((k) => Math.floor(Math.min(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0) / CELL));
93
+ const hi = [0, 1, 2].map((k) => Math.floor(Math.max(a[k] ?? 0, b[k] ?? 0, c[k] ?? 0) / CELL));
94
+ for (let x = lo[0] ?? 0;x <= (hi[0] ?? 0); x++)
95
+ for (let y = lo[1] ?? 0;y <= (hi[1] ?? 0); y++)
96
+ for (let z = lo[2] ?? 0;z <= (hi[2] ?? 0); z++) {
97
+ const k = key(x, y, z);
98
+ const list = this.cells.get(k);
99
+ if (list)
100
+ list.push(t);
101
+ else
102
+ this.cells.set(k, [t]);
103
+ }
104
+ }
105
+ }
106
+ nearest(p, radius) {
107
+ const CELL = this.cell;
108
+ const [x0, y0, z0] = [
109
+ Math.floor((p[0] - radius) / CELL),
110
+ Math.floor((p[1] - radius) / CELL),
111
+ Math.floor((p[2] - radius) / CELL)
112
+ ];
113
+ const [x1, y1, z1] = [
114
+ Math.floor((p[0] + radius) / CELL),
115
+ Math.floor((p[1] + radius) / CELL),
116
+ Math.floor((p[2] + radius) / CELL)
117
+ ];
118
+ const q = ++this.query;
119
+ const T = this.tris;
120
+ const B = this.boxes;
121
+ let best = Infinity;
122
+ for (let x = x0;x <= x1; x++)
123
+ for (let y = y0;y <= y1; y++)
124
+ for (let z = z0;z <= z1; z++) {
125
+ const list = this.cells.get(key(x, y, z));
126
+ if (!list)
127
+ continue;
128
+ for (const t of list) {
129
+ if (this.stamp[t] === q)
130
+ continue;
131
+ this.stamp[t] = q;
132
+ const lim = Math.min(best, radius);
133
+ const o6 = t * 6;
134
+ if (p[0] < (B[o6] ?? 0) - lim || p[0] > (B[o6 + 3] ?? 0) + lim || p[1] < (B[o6 + 1] ?? 0) - lim || p[1] > (B[o6 + 4] ?? 0) + lim || p[2] < (B[o6 + 2] ?? 0) - lim || p[2] > (B[o6 + 5] ?? 0) + lim)
135
+ continue;
136
+ const o = t * 9;
137
+ const d = pointTriangleDistance2(p, [T[o] ?? 0, T[o + 1] ?? 0, T[o + 2] ?? 0], [T[o + 3] ?? 0, T[o + 4] ?? 0, T[o + 5] ?? 0], [T[o + 6] ?? 0, T[o + 7] ?? 0, T[o + 8] ?? 0]);
138
+ if (d < best)
139
+ best = d;
140
+ }
141
+ }
142
+ return best <= radius ? best : Infinity;
143
+ }
144
+ }
145
+ var sampleCache = new WeakMap;
146
+ function surfaceSamples2(m, step = 0.05, maxPerEdge = 16) {
147
+ const key = `${step}:${maxPerEdge}`;
148
+ const byMesh = sampleCache.get(m) ?? new Map;
149
+ sampleCache.set(m, byMesh);
150
+ const hit = byMesh.get(key);
151
+ if (hit)
152
+ return hit;
153
+ const out = sampleMesh(m, step, maxPerEdge);
154
+ byMesh.set(key, out);
155
+ return out;
156
+ }
157
+ function sampleMesh(m, step, maxPerEdge) {
158
+ const out = [];
159
+ for (let i = 0;i < m.positions.length / 3; i++)
160
+ out.push(at(m, i));
161
+ for (let t = 0;t < m.indices.length / 3; t++) {
162
+ const tri = triangle(m, t);
163
+ for (let e = 0;e < 3; e++) {
164
+ const a = tri[e] ?? tri[0];
165
+ const b = tri[(e + 1) % 3] ?? tri[0];
166
+ const n = Math.min(maxPerEdge, Math.floor(Math.hypot(...sub(b, a)) / step));
167
+ for (let s = 1;s < n; s++)
168
+ out.push(lerp(a, b, s / n));
169
+ }
170
+ out.push(lerp(lerp(tri[0], tri[1], 0.5), tri[2], 1 / 3));
171
+ }
172
+ return out;
173
+ }
174
+
175
+ // src/qa/inside.ts
176
+ var cellFor = (m) => {
177
+ const b = boundsOf2(m);
178
+ return Math.max(0.2, Math.hypot(b.max[0] - b.min[0], b.max[1] - b.min[1], b.max[2] - b.min[2]) / 256);
179
+ };
180
+ var NUDGE = [0.0000137, 0.0000291, 0.0000373];
181
+ var OTHER = [
182
+ [1, 2],
183
+ [0, 2],
184
+ [0, 1]
185
+ ];
186
+
187
+ class SolidIndex {
188
+ tri;
189
+ columns = [new Map, new Map, new Map];
190
+ cell;
191
+ constructor(mesh) {
192
+ this.cell = cellFor(mesh);
193
+ const CELL = this.cell;
194
+ const n = mesh.indices.length / 3;
195
+ this.tri = new Float64Array(n * 9);
196
+ for (let t = 0;t < n; t++) {
197
+ for (let v = 0;v < 3; v++) {
198
+ const idx = mesh.indices[t * 3 + v] ?? 0;
199
+ for (let k = 0;k < 3; k++)
200
+ this.tri[t * 9 + v * 3 + k] = mesh.positions[idx * 3 + k] ?? 0;
201
+ }
202
+ for (const axis of [0, 1, 2]) {
203
+ const [i, j] = OTHER[axis];
204
+ const T = this.tri;
205
+ const o = t * 9;
206
+ const a = T[o + i] ?? 0;
207
+ const b = T[o + 3 + i] ?? 0;
208
+ const c = T[o + 6 + i] ?? 0;
209
+ const d = T[o + j] ?? 0;
210
+ const e = T[o + 3 + j] ?? 0;
211
+ const f = T[o + 6 + j] ?? 0;
212
+ const u0 = Math.floor(Math.min(a, b, c) / CELL);
213
+ const u1 = Math.floor(Math.max(a, b, c) / CELL);
214
+ const v0 = Math.floor(Math.min(d, e, f) / CELL);
215
+ const v1 = Math.floor(Math.max(d, e, f) / CELL);
216
+ const map = this.columns[axis];
217
+ if (!map)
218
+ continue;
219
+ for (let u = u0;u <= u1; u++)
220
+ for (let v = v0;v <= v1; v++) {
221
+ const k = u * 92837111 ^ v * 689287499;
222
+ const list = map.get(k);
223
+ if (list)
224
+ list.push(t);
225
+ else
226
+ map.set(k, [t]);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ parity(px, py, pz, axis) {
232
+ const [i, j] = OTHER[axis];
233
+ const p = [px, py, pz];
234
+ const pi = p[i] ?? 0;
235
+ const pj = p[j] ?? 0;
236
+ const pk = p[axis] ?? 0;
237
+ const CELL = this.cell;
238
+ const list = this.columns[axis]?.get(Math.floor(pi / CELL) * 92837111 ^ Math.floor(pj / CELL) * 689287499);
239
+ if (!list)
240
+ return false;
241
+ const T = this.tri;
242
+ let n = 0;
243
+ for (const t of list) {
244
+ const o = t * 9;
245
+ const ai = T[o + i] ?? 0;
246
+ const aj = T[o + j] ?? 0;
247
+ const bi = T[o + 3 + i] ?? 0;
248
+ const bj = T[o + 3 + j] ?? 0;
249
+ const ci = T[o + 6 + i] ?? 0;
250
+ const cj = T[o + 6 + j] ?? 0;
251
+ const area = (bi - ai) * (cj - aj) - (bj - aj) * (ci - ai);
252
+ if (Math.abs(area) < 0.00000000000001)
253
+ continue;
254
+ const w0 = ((bi - pi) * (cj - pj) - (bj - pj) * (ci - pi)) / area;
255
+ const w1 = ((ci - pi) * (aj - pj) - (cj - pj) * (ai - pi)) / area;
256
+ const w2 = 1 - w0 - w1;
257
+ if (w0 < 0 || w1 < 0 || w2 < 0)
258
+ continue;
259
+ const depth = w0 * (T[o + axis] ?? 0) + w1 * (T[o + 3 + axis] ?? 0) + w2 * (T[o + 6 + axis] ?? 0);
260
+ if (depth > pk)
261
+ n++;
262
+ }
263
+ return n % 2 === 1;
264
+ }
265
+ inside(point) {
266
+ const x = point[0] + NUDGE[0];
267
+ const y = point[1] + NUDGE[1];
268
+ const z = point[2] + NUDGE[2];
269
+ const vz = this.parity(x, y, z, 2);
270
+ const vy = this.parity(x, y, z, 1);
271
+ return vz === vy ? vz : this.parity(x, y, z, 0);
272
+ }
273
+ }
274
+ var insideSolid2 = (p, m) => new SolidIndex(m).inside(p);
275
+
276
+ // src/qa/inspect.ts
277
+ var glob2 = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*").replaceAll("?", ".")}$`);
278
+ function inspect2(asset) {
279
+ const world = worldMeshes2(asset.parts);
280
+ const parts = (sel) => [...world.keys()].filter((n) => glob2(sel).test(n));
281
+ const meshes = new Map;
282
+ const grids = new Map;
283
+ const solids = new Map;
284
+ const samples = new Map;
285
+ const mesh = (sel) => {
286
+ if (!meshes.has(sel)) {
287
+ const ms = parts(sel).flatMap((n) => world.get(n) ?? []);
288
+ meshes.set(sel, ms.length ? merge2(ms) : null);
289
+ }
290
+ return meshes.get(sel) ?? null;
291
+ };
292
+ const cached = (map, key, make) => {
293
+ const hit = map.get(key);
294
+ if (hit !== undefined)
295
+ return hit;
296
+ const v = make();
297
+ map.set(key, v);
298
+ return v;
299
+ };
300
+ const near = (a, b, eps) => [0, 1, 2].every((k) => (a.min[k] ?? 0) <= (b.max[k] ?? 0) + eps && (b.min[k] ?? 0) <= (a.max[k] ?? 0) + eps);
301
+ return {
302
+ asset,
303
+ parts,
304
+ mesh,
305
+ bounds: (sel) => {
306
+ const m = mesh(sel);
307
+ return m ? boundsOf2(m) : null;
308
+ },
309
+ distance(a, b, max = 1) {
310
+ const ma = mesh(a);
311
+ const mb = mesh(b);
312
+ if (!ma || !mb || !near(boundsOf2(ma), boundsOf2(mb), max))
313
+ return max;
314
+ const grid = cached(grids, b, () => new TriangleGrid2(mb));
315
+ const pts = cached(samples, a, () => surfaceSamples2(ma, 0.05, 8));
316
+ for (const r of [0.02, 0.1, 0.5, max].filter((x) => x <= max)) {
317
+ let best = Infinity;
318
+ for (const p of pts)
319
+ best = Math.min(best, grid.nearest(p, Math.min(r, best)));
320
+ if (best <= r)
321
+ return best;
322
+ }
323
+ return max;
324
+ },
325
+ penetration(a, b) {
326
+ const ma = mesh(a);
327
+ const mb = mesh(b);
328
+ if (!ma || !mb)
329
+ return { depth: 0, samples: 0 };
330
+ const bb = boundsOf2(mb);
331
+ if (!near(boundsOf2(ma), bb, 0))
332
+ return { depth: 0, samples: 0 };
333
+ const grid = cached(grids, b, () => new TriangleGrid2(mb));
334
+ const solid = cached(solids, b, () => new SolidIndex(mb));
335
+ let depth = 0;
336
+ let count = 0;
337
+ for (const p of cached(samples, `${a}#pen`, () => surfaceSamples2(ma, 0.1, 3))) {
338
+ if (!near({ min: p, max: p }, bb, 0) || !solid.inside(p))
339
+ continue;
340
+ count++;
341
+ depth = Math.max(depth, grid.nearest(p, 0.5));
342
+ }
343
+ return { depth, samples: count };
344
+ }
345
+ };
346
+ }
347
+
348
+ // src/qa/checks.ts
349
+ var inspectors = new WeakMap;
350
+ var inspectorFor = (asset) => {
351
+ const hit = inspectors.get(asset);
352
+ if (hit)
353
+ return hit;
354
+ const made = inspect2(asset);
355
+ inspectors.set(asset, made);
356
+ return made;
357
+ };
358
+ var CLASH = 0.01;
359
+ var clear2 = (a, b, minGap = 0) => (asset) => {
360
+ const q = inspectorFor(asset);
361
+ const out = [];
362
+ for (const pa of q.parts(a)) {
363
+ for (const pb of q.parts(b)) {
364
+ if (pa === pb)
365
+ continue;
366
+ const pen = q.penetration(pa, pb);
367
+ if (pen.depth > CLASH) {
368
+ out.push({
369
+ part: pa,
370
+ message: `'${pa}' sinks ${(pen.depth * 100).toFixed(1)} cm into '${pb}'`,
371
+ fix: `move '${pa}' out of '${pb}' (or shrink one); they are declared clear of each other`
372
+ });
373
+ } else if (minGap > 0) {
374
+ const d = q.distance(pa, pb, minGap);
375
+ if (d < minGap)
376
+ out.push({
377
+ part: pa,
378
+ message: `'${pa}' is ${(d * 100).toFixed(1)} cm from '${pb}', needs ≥ ${minGap * 100} cm`,
379
+ fix: "open the gap — running clearance, mud, suspension travel"
380
+ });
381
+ }
382
+ }
383
+ }
384
+ return out;
385
+ };
386
+ var noOverlap2 = (selector) => (asset) => {
387
+ const names = inspectorFor(asset).parts(selector);
388
+ return names.flatMap((a, i) => names.slice(i + 1).flatMap((b) => clear2(a, b)(asset)));
389
+ };
390
+ var touches2 = (a, b, eps = 0.02) => (asset) => {
391
+ const q = inspectorFor(asset);
392
+ if (q.parts(a).length === 0 || q.parts(b).length === 0)
393
+ return [];
394
+ const d = q.distance(a, b, 1);
395
+ return d <= eps ? [] : [
396
+ {
397
+ part: a,
398
+ message: `'${a}' should touch '${b}' but is ${(d * 100).toFixed(1)} cm away`,
399
+ fix: `seat '${a}' on '${b}'`
400
+ }
401
+ ];
402
+ };
403
+ var symmetric2 = (left, right, axis = "y", tol = 0.02) => (asset) => {
404
+ const q = inspectorFor(asset);
405
+ const k = { x: 0, y: 1, z: 2 }[axis];
406
+ const token = new RegExp(`${left.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?=_|$)`);
407
+ return q.parts("*").filter((n) => token.test(n)).flatMap((l) => {
408
+ const r = l.replace(token, right);
409
+ const bl = q.bounds(l);
410
+ const br = q.bounds(r);
411
+ if (!bl || !br)
412
+ return [{ part: l, message: `'${l}' has no mirror '${r}'`, fix: `add '${r}' or rename` }];
413
+ const off = Math.abs(bl.min[k] + br.max[k]) + Math.abs(bl.max[k] + br.min[k]);
414
+ return off > tol * 2 ? [
415
+ {
416
+ part: l,
417
+ message: `'${l}' and '${r}' are not mirrored across ${axis}=0 (off by ${(off * 50).toFixed(1)} cm)`,
418
+ fix: "build one side and mirror it — place(mesh, { scale: [1, -1, 1] }) flips winding; prefer reusing the same builder with a sign"
419
+ }
420
+ ] : [];
421
+ });
422
+ };
423
+ var runChecks2 = (asset) => asset.checks.flatMap((c) => [...c(asset)]);
424
+ // src/qa/types.ts
425
+ var CONTACT_EPS2 = 0.02;
426
+ var CLASH_EPS2 = 0.01;
427
+
428
+ // src/qa/clash.ts
429
+ var SLICES = 24;
430
+ function subtree(parts, root) {
431
+ const out = new Set([root]);
432
+ for (const p of parts)
433
+ if (p.parent && out.has(p.parent))
434
+ out.add(p.name);
435
+ return out;
436
+ }
437
+ var BINS = 72;
438
+ var TAU = Math.PI * 2;
439
+ function spinVolume(mesh, pivot, axis, range) {
440
+ const len = Math.hypot(...axis) || 1;
441
+ const u = [axis[0] / len, axis[1] / len, axis[2] / len];
442
+ const ref = Math.abs(u[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
443
+ const dotRef = ref[0] * u[0] + ref[1] * u[1] + ref[2] * u[2];
444
+ const e1raw = [ref[0] - dotRef * u[0], ref[1] - dotRef * u[1], ref[2] - dotRef * u[2]];
445
+ const e1l = Math.hypot(...e1raw) || 1;
446
+ const e1 = [e1raw[0] / e1l, e1raw[1] / e1l, e1raw[2] / e1l];
447
+ const e2 = [
448
+ u[1] * e1[2] - u[2] * e1[1],
449
+ u[2] * e1[0] - u[0] * e1[2],
450
+ u[0] * e1[1] - u[1] * e1[0]
451
+ ];
452
+ const coords = (p) => {
453
+ const d = [p[0] - pivot[0], p[1] - pivot[1], p[2] - pivot[2]];
454
+ const a = d[0] * u[0] + d[1] * u[1] + d[2] * u[2];
455
+ const x = d[0] * e1[0] + d[1] * e1[1] + d[2] * e1[2];
456
+ const y = d[0] * e2[0] + d[1] * e2[1] + d[2] * e2[2];
457
+ return [a, Math.hypot(x, y), Math.atan2(y, x)];
458
+ };
459
+ const bin = (phi) => Math.floor((phi % TAU + TAU) % TAU / (TAU / BINS)) % BINS;
460
+ const pts = surfaceSamples2(mesh, 0.05, 8).map(coords);
461
+ const lo = Math.min(...pts.map((p) => p[0]));
462
+ const hi = Math.max(...pts.map((p) => p[0]));
463
+ const span = Math.max(hi - lo, 0.000001);
464
+ const slice = (a) => Math.min(SLICES - 1, Math.floor((a - lo) / span * SLICES));
465
+ const radius = new Float64Array(SLICES * BINS).fill(-1);
466
+ for (const [a, r, phi] of pts) {
467
+ const k = slice(a) * BINS + bin(phi);
468
+ radius[k] = Math.max(radius[k] ?? -1, r);
469
+ }
470
+ const [from, to] = range ?? [0, TAU];
471
+ const steps = range ? Math.ceil((to - from) / (TAU / BINS)) + 2 : BINS;
472
+ const reach = Math.max(...radius) + Math.max(Math.abs(lo), Math.abs(hi));
473
+ return {
474
+ min: [pivot[0] - reach, pivot[1] - reach, pivot[2] - reach],
475
+ max: [pivot[0] + reach, pivot[1] + reach, pivot[2] + reach],
476
+ inside: (p) => {
477
+ const [a, r, phi] = coords(p);
478
+ if (a < lo + CLASH_EPS2 || a > hi - CLASH_EPS2)
479
+ return false;
480
+ const base = slice(a) * BINS;
481
+ for (let i = 0;i < Math.min(steps, BINS); i++) {
482
+ const theta = range ? from - TAU / BINS + i * (TAU / BINS) : i * (TAU / BINS);
483
+ if (r < (radius[base + bin(phi - theta)] ?? -1) - CLASH_EPS2)
484
+ return true;
485
+ }
486
+ return false;
487
+ }
488
+ };
489
+ }
490
+ function slideVolume(mesh, offset, oneWay = false) {
491
+ const b = boundsOf2(mesh);
492
+ const neg = (k) => oneWay ? Math.min(0, offset[k] ?? 0) : -Math.abs(offset[k] ?? 0);
493
+ const pos = (k) => oneWay ? Math.max(0, offset[k] ?? 0) : Math.abs(offset[k] ?? 0);
494
+ const lo = [0, 1, 2].map((k) => (b.min[k] ?? 0) + neg(k) + CLASH_EPS2);
495
+ const hi = [0, 1, 2].map((k) => (b.max[k] ?? 0) + pos(k) - CLASH_EPS2);
496
+ return {
497
+ min: [lo[0] ?? 0, lo[1] ?? 0, lo[2] ?? 0],
498
+ max: [hi[0] ?? 0, hi[1] ?? 0, hi[2] ?? 0],
499
+ inside: (p) => [0, 1, 2].every((k) => (p[k] ?? 0) > (lo[k] ?? 0) && (p[k] ?? 0) < (hi[k] ?? 0))
500
+ };
501
+ }
502
+ var inBox = (p, v) => p[0] >= v.min[0] && p[0] <= v.max[0] && p[1] >= v.min[1] && p[1] <= v.max[1] && p[2] >= v.min[2] && p[2] <= v.max[2];
503
+ var clashes2 = (asset) => {
504
+ const meshes = worldMeshes2(asset.parts);
505
+ const xf = worldTransforms2(asset.parts);
506
+ const findings = [];
507
+ for (const root of asset.parts.filter((p) => p.motion)) {
508
+ const moving = subtree(asset.parts, root.name);
509
+ const body = merge2([...moving].flatMap((n) => meshes.get(n) ?? []));
510
+ const t = xf.get(root.name);
511
+ if (!t || body.indices.length === 0 || !root.motion)
512
+ continue;
513
+ const c = Math.cos(t.yaw);
514
+ const s = Math.sin(t.yaw);
515
+ const rot = (v) => [v[0] * c - v[1] * s, v[0] * s + v[1] * c, v[2]];
516
+ const m = root.motion;
517
+ const peak = m.kind === "sway" ? m.degrees * Math.PI / 180 : 0;
518
+ const vol = m.kind === "slide" ? slideVolume(body, rot(m.offset), m.oneWay) : m.kind === "sway" ? spinVolume(body, t.origin, rot(m.axis), m.oneWay ? [0, peak] : [-peak, peak]) : spinVolume(body, t.origin, rot(m.axis));
519
+ for (const [name, mesh] of meshes) {
520
+ if (moving.has(name) || root.clearOf?.includes(name))
521
+ continue;
522
+ const b = boundsOf2(mesh);
523
+ if (!inBox(b.min, {
524
+ ...vol,
525
+ min: [
526
+ vol.min[0] - (b.max[0] - b.min[0]),
527
+ vol.min[1] - (b.max[1] - b.min[1]),
528
+ vol.min[2] - (b.max[2] - b.min[2])
529
+ ]
530
+ }))
531
+ continue;
532
+ const hits = surfaceSamples2(mesh, 0.1, 4).filter((p) => inBox(p, vol) && vol.inside(p)).length;
533
+ if (hits > 0) {
534
+ findings.push({
535
+ gate: "clashes",
536
+ part: name,
537
+ message: `'${name}' cuts into the swept ${root.motion.kind} of '${root.name}' (${hits} sample${hits === 1 ? "" : "s"} deeper than ${CLASH_EPS2 * 100} cm)`,
538
+ fix: `move/trim '${name}' or '${root.name}' for running clearance; if they are meant to interlock (an axle through its bearing), list it: clearOf: ['${name}'] on '${root.name}'`
539
+ });
540
+ }
541
+ }
542
+ }
543
+ return findings;
544
+ };
545
+ // src/qa/floating.ts
546
+ var near = (a, b, eps) => [0, 1, 2].every((k) => (a.min[k] ?? 0) <= (b.max[k] ?? 0) + eps && (b.min[k] ?? 0) <= (a.max[k] ?? 0) + eps);
547
+ function touching2(a, b, grid, eps = CONTACT_EPS2) {
548
+ const bb = boundsOf2(b);
549
+ for (const p of surfaceSamples2(a, 0.1, 4)) {
550
+ if (!near({ min: p, max: p }, bb, eps))
551
+ continue;
552
+ if (grid.nearest(p, eps) <= eps)
553
+ return true;
554
+ }
555
+ return false;
556
+ }
557
+ var floatingParts2 = (asset) => {
558
+ const meshes = [...worldMeshes2(asset.parts)];
559
+ const bounds = new Map(meshes.map(([n, m]) => [n, boundsOf2(m)]));
560
+ const grids = new Map;
561
+ const grid = (n, m) => {
562
+ const g = grids.get(n) ?? new TriangleGrid2(m);
563
+ grids.set(n, g);
564
+ return g;
565
+ };
566
+ const grounded = new Set(meshes.filter(([n]) => (bounds.get(n)?.min[2] ?? 1) <= CONTACT_EPS2).map(([n]) => n));
567
+ const frontier = [...grounded];
568
+ while (frontier.length > 0) {
569
+ const g = frontier.pop() ?? "";
570
+ const gm = meshes.find(([n]) => n === g)?.[1];
571
+ const gb = bounds.get(g);
572
+ if (!gm || !gb)
573
+ continue;
574
+ for (const [n, m] of meshes) {
575
+ const b = bounds.get(n);
576
+ if (grounded.has(n) || !b || !near(b, gb, CONTACT_EPS2))
577
+ continue;
578
+ if (touching2(m, gm, grid(g, gm)) || touching2(gm, m, grid(n, m))) {
579
+ grounded.add(n);
580
+ frontier.push(n);
581
+ }
582
+ }
583
+ }
584
+ return meshes.filter(([n]) => !grounded.has(n)).map(([n]) => ({
585
+ gate: "floating-parts",
586
+ part: n,
587
+ message: `'${n}' has no surface within ${CONTACT_EPS2 * 100} cm of anything attached to the ground`,
588
+ fix: "move it onto what holds it (check `at`, parent and the LOD-gated detail around it) — bounding boxes touching is not enough"
589
+ }));
590
+ };
591
+ // src/qa/measures.ts
592
+ var AXIS = { x: 0, y: 1, z: 2 };
593
+ function union(bounds) {
594
+ if (bounds.length === 0)
595
+ return null;
596
+ const k = [0, 1, 2];
597
+ return {
598
+ min: k.map((i) => Math.min(...bounds.map((b) => b.min[i]))),
599
+ max: k.map((i) => Math.max(...bounds.map((b) => b.max[i])))
600
+ };
601
+ }
602
+ function measure2(asset, m) {
603
+ const meshes = worldMeshes2(asset.parts);
604
+ const of = (prefix) => union([...meshes].filter(([n]) => n === prefix || n.startsWith(`${prefix}_`)).map(([, mesh]) => boundsOf2(mesh)));
605
+ const k = AXIS[m.axis];
606
+ if (m.between) {
607
+ const [a, b] = [of(m.between[0]), of(m.between[1])];
608
+ if (!a || !b)
609
+ return null;
610
+ return Math.abs((a.min[k] + a.max[k]) / 2 - (b.min[k] + b.max[k]) / 2);
611
+ }
612
+ const u = union((m.parts ?? []).map(of).filter((b) => b !== null));
613
+ return u ? u.max[k] - u.min[k] : null;
614
+ }
615
+ var measures2 = (asset) => {
616
+ if (asset.lod !== 0 || !asset.real?.measures)
617
+ return [];
618
+ return asset.real.measures.flatMap((m) => {
619
+ const got = measure2(asset, m);
620
+ const tol = m.tolerance ?? 0.02;
621
+ if (got === null) {
622
+ return [
623
+ {
624
+ gate: "measures",
625
+ message: `'${m.label}': no part matches ${JSON.stringify(m.parts ?? m.between)}`,
626
+ fix: "name the parts the measure refers to, or fix the names in the measure"
627
+ }
628
+ ];
629
+ }
630
+ return Math.abs(got - m.value) / m.value > tol ? [
631
+ {
632
+ gate: "measures",
633
+ message: `${m.label}: ${got.toFixed(3)} m on ${m.axis.toUpperCase()}, spec ${m.value} m (±${tol * 100}%)${m.source ? ` — ${m.source}` : ""}`,
634
+ fix: "rebuild the part from the spec number; never edit the spec to match the model"
635
+ }
636
+ ] : [];
637
+ });
638
+ };
639
+
640
+ // src/qa/occlusion.ts
641
+ var overlaps = (a, b, eps) => [0, 1, 2].every((k) => (a.min[k] ?? 0) <= (b.max[k] ?? 0) + eps && (b.min[k] ?? 0) <= (a.max[k] ?? 0) + eps);
642
+ var contains = (outer, inner) => [0, 1, 2].every((k) => (outer.min[k] ?? 0) <= (inner.min[k] ?? 0) && (inner.max[k] ?? 0) <= (outer.max[k] ?? 0));
643
+ function faceSamples(m, cap) {
644
+ const tris = m.indices.length / 3;
645
+ const step = Math.max(1, Math.floor(tris / cap));
646
+ const out = [];
647
+ const P = m.positions;
648
+ const v = (i) => [P[i * 3] ?? 0, P[i * 3 + 1] ?? 0, P[i * 3 + 2] ?? 0];
649
+ for (let t = 0;t < tris; t += step) {
650
+ const a = v(m.indices[t * 3] ?? 0);
651
+ const b = v(m.indices[t * 3 + 1] ?? 0);
652
+ const c = v(m.indices[t * 3 + 2] ?? 0);
653
+ const u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
654
+ const w = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
655
+ const g = [
656
+ u[1] * w[2] - u[2] * w[1],
657
+ u[2] * w[0] - u[0] * w[2],
658
+ u[0] * w[1] - u[1] * w[0]
659
+ ];
660
+ const len = Math.hypot(...g);
661
+ if (len < 0.0000000001)
662
+ continue;
663
+ out.push({
664
+ p: [(a[0] + b[0] + c[0]) / 3, (a[1] + b[1] + c[1]) / 3, (a[2] + b[2] + c[2]) / 3],
665
+ n: [g[0] / len, g[1] / len, g[2] / len]
666
+ });
667
+ }
668
+ return out;
669
+ }
670
+ function scene(asset) {
671
+ const mesh = worldMeshes2(asset.parts);
672
+ const bounds = new Map([...mesh].map(([n, m]) => [n, boundsOf2(m)]));
673
+ const grids = new Map;
674
+ const solids = new Map;
675
+ const lazy = (cache, make) => (name) => {
676
+ const hit = cache.get(name);
677
+ if (hit)
678
+ return hit;
679
+ const made = make(mesh.get(name) ?? {
680
+ positions: new Float32Array,
681
+ normals: new Float32Array,
682
+ uvs: new Float32Array,
683
+ colors: new Float32Array,
684
+ indices: new Uint32Array
685
+ });
686
+ cache.set(name, made);
687
+ return made;
688
+ };
689
+ return {
690
+ names: [...mesh.keys()],
691
+ mesh,
692
+ bounds,
693
+ grid: lazy(grids, (m) => new TriangleGrid2(m)),
694
+ solid: lazy(solids, (m) => new SolidIndex(m))
695
+ };
696
+ }
697
+ var ON = 0.0002;
698
+ var PROBE = 0.002;
699
+ var zFighting2 = (asset) => {
700
+ const s = scene(asset);
701
+ const out = [];
702
+ for (const a of s.names) {
703
+ const ba = s.bounds.get(a);
704
+ const ma = s.mesh.get(a);
705
+ if (!ba || !ma)
706
+ continue;
707
+ const samples = faceSamples(ma, 256);
708
+ for (const b of s.names) {
709
+ const bb = s.bounds.get(b);
710
+ if (a === b || !bb || !overlaps(ba, bb, ON))
711
+ continue;
712
+ let hits = 0;
713
+ for (const { p, n } of samples) {
714
+ if (!overlaps({ min: p, max: p }, bb, ON) || s.grid(b).nearest(p, ON) > ON)
715
+ continue;
716
+ const solid = s.solid(b);
717
+ const behind = solid.inside([
718
+ p[0] - n[0] * PROBE,
719
+ p[1] - n[1] * PROBE,
720
+ p[2] - n[2] * PROBE
721
+ ]);
722
+ const front = solid.inside([p[0] + n[0] * PROBE, p[1] + n[1] * PROBE, p[2] + n[2] * PROBE]);
723
+ if (behind && !front)
724
+ hits++;
725
+ }
726
+ if (hits >= 2)
727
+ out.push({
728
+ gate: "z-fighting",
729
+ part: a,
730
+ message: `'${a}' has ${hits} face sample(s) lying in '${b}''s surface, facing the same way — it flickers`,
731
+ fix: `stand '${a}' proud of '${b}' by ≥ 2 mm, or inset it, or cut it from '${b}'; coplanar faces never render cleanly`
732
+ });
733
+ }
734
+ }
735
+ return out;
736
+ };
737
+ var hiddenParts2 = (asset) => {
738
+ const s = scene(asset);
739
+ return s.names.flatMap((a) => {
740
+ const ba = s.bounds.get(a);
741
+ const ma = s.mesh.get(a);
742
+ if (!ba || !ma)
743
+ return [];
744
+ const pts = faceSamples(ma, 64).map((x) => x.p);
745
+ const stride = Math.max(1, Math.floor(ma.positions.length / 192));
746
+ for (let i = 0;i < ma.positions.length / 3; i += stride)
747
+ pts.push([
748
+ ma.positions[i * 3] ?? 0,
749
+ ma.positions[i * 3 + 1] ?? 0,
750
+ ma.positions[i * 3 + 2] ?? 0
751
+ ]);
752
+ const host = s.names.find((b) => {
753
+ const bb = s.bounds.get(b);
754
+ if (b === a || !bb || !contains(bb, ba))
755
+ return false;
756
+ return pts.every((p) => s.solid(b).inside(p)) && pts.every((p) => s.grid(b).nearest(p, PROBE) > PROBE);
757
+ });
758
+ return host ? [
759
+ {
760
+ gate: "hidden-parts",
761
+ part: a,
762
+ message: `'${a}' (${ma.indices.length / 3} tris) is entirely inside '${host}' — nobody can see it`,
763
+ fix: `delete it, gate it behind ctx.lod, or move it out of '${host}'; hidden triangles are wasted budget`
764
+ }
765
+ ] : [];
766
+ });
767
+ };
768
+
769
+ // src/qa/sanity.ts
770
+ var parts = (asset) => asset.parts.flatMap((p) => p.mesh ? [[p, p.mesh]] : []);
771
+ var finite = (a) => {
772
+ for (let i = 0;i < a.length; i++)
773
+ if (!Number.isFinite(a[i] ?? 0))
774
+ return false;
775
+ return true;
776
+ };
777
+ var nonFinite2 = (asset) => parts(asset).flatMap(([p, m]) => {
778
+ const bad = [
779
+ ["positions", m.positions],
780
+ ["normals", m.normals],
781
+ ["uvs", m.uvs],
782
+ ["colors", m.colors]
783
+ ].filter(([, a]) => !finite(a)).map(([n]) => n);
784
+ const maxIndex = m.positions.length / 3;
785
+ const outOfRange = m.indices.some((i) => i >= maxIndex);
786
+ if (outOfRange)
787
+ bad.push("indices");
788
+ return bad.length ? [
789
+ {
790
+ gate: "non-finite",
791
+ part: p.name,
792
+ message: `'${p.name}' has NaN/Infinity or out-of-range data in ${bad.join(", ")}`,
793
+ fix: "look for a zero-length normalize, a divide by a zero count or segment, or an index past the vertex count"
794
+ }
795
+ ] : [];
796
+ });
797
+ var vertexColors2 = (asset) => parts(asset).flatMap(([p, m]) => {
798
+ let lo = Infinity;
799
+ let hi = -Infinity;
800
+ for (const c of m.colors) {
801
+ if (c < lo)
802
+ lo = c;
803
+ if (c > hi)
804
+ hi = c;
805
+ }
806
+ return lo < -0.000001 || hi > 1 + 0.000001 ? [
807
+ {
808
+ gate: "vertex-colors",
809
+ part: p.name,
810
+ message: `'${p.name}' vertex colour spans ${lo.toFixed(3)}…${hi.toFixed(3)}, outside linear [0,1]`,
811
+ fix: "colours are linear 0–1 (use the palette / material helpers); clamp after grime or livery maths"
812
+ }
813
+ ] : [];
814
+ });
815
+ function windingStats(m) {
816
+ const P = m.positions;
817
+ const N = m.normals;
818
+ let flipped = 0;
819
+ let area = 0;
820
+ let flippedArea = 0;
821
+ let counted = 0;
822
+ for (let t = 0;t < m.indices.length; t += 3) {
823
+ const a = (m.indices[t] ?? 0) * 3;
824
+ const b = (m.indices[t + 1] ?? 0) * 3;
825
+ const c = (m.indices[t + 2] ?? 0) * 3;
826
+ const ux = (P[b] ?? 0) - (P[a] ?? 0);
827
+ const uy = (P[b + 1] ?? 0) - (P[a + 1] ?? 0);
828
+ const uz = (P[b + 2] ?? 0) - (P[a + 2] ?? 0);
829
+ const vx = (P[c] ?? 0) - (P[a] ?? 0);
830
+ const vy = (P[c + 1] ?? 0) - (P[a + 1] ?? 0);
831
+ const vz = (P[c + 2] ?? 0) - (P[a + 2] ?? 0);
832
+ const gx = uy * vz - uz * vy;
833
+ const gy = uz * vx - ux * vz;
834
+ const gz = ux * vy - uy * vx;
835
+ const len = Math.hypot(gx, gy, gz);
836
+ if (len < 0.000000000001)
837
+ continue;
838
+ counted++;
839
+ const nx = (N[a] ?? 0) + (N[b] ?? 0) + (N[c] ?? 0);
840
+ const ny = (N[a + 1] ?? 0) + (N[b + 1] ?? 0) + (N[c + 1] ?? 0);
841
+ const nz = (N[a + 2] ?? 0) + (N[b + 2] ?? 0) + (N[c + 2] ?? 0);
842
+ area += len;
843
+ if ((gx * nx + gy * ny + gz * nz) / (len * (Math.hypot(nx, ny, nz) || 1)) < -0.1) {
844
+ flipped++;
845
+ flippedArea += len;
846
+ }
847
+ }
848
+ return { flipped, area, flippedArea, counted };
849
+ }
850
+ var normalsMatchWinding2 = (asset) => parts(asset).flatMap(([p, m]) => {
851
+ const s = windingStats(m);
852
+ return s.flipped > 0 && s.flippedArea > s.area * 0.01 ? [
853
+ {
854
+ gate: "normals-winding",
855
+ part: p.name,
856
+ message: `'${p.name}': ${s.flipped} triangle(s) (${(100 * s.flippedArea / s.area).toFixed(1)}% of its area) have vertex normals opposing their face — inverted winding, or normals smoothed across a hard crease`,
857
+ fix: "split normals at creases (lathe crease angle / weightedNormals hardAngle), or fix the mirrored piece: a negative-scale place() flips winding but not normals"
858
+ }
859
+ ] : [];
860
+ });
861
+ var degenerateTriangles2 = (asset) => parts(asset).flatMap(([p, m]) => {
862
+ const tris = m.indices.length / 3;
863
+ const zero = tris - windingStats(m).counted;
864
+ return zero > tris * 0.05 ? [
865
+ {
866
+ gate: "degenerate-triangles",
867
+ part: p.name,
868
+ message: `'${p.name}': ${zero} of ${tris} triangles have zero area`,
869
+ fix: "a ring/lathe with a zero radius emits fans of slivers: cap it with a point instead, or skip the segment"
870
+ }
871
+ ] : [];
872
+ });
873
+
874
+ // src/qa/gates.ts
875
+ var worldBounds = (asset) => {
876
+ const xf = worldTransforms2(asset.parts);
877
+ const out = new Map;
878
+ for (const p of asset.parts) {
879
+ const t = xf.get(p.name);
880
+ if (!t || !p.mesh || p.mesh.indices.length === 0)
881
+ continue;
882
+ const b = boundsOf2(p.mesh);
883
+ const corners = [0, 1, 2, 3, 4, 5, 6, 7].map((i) => toWorld2(t, [
884
+ i & 1 ? b.max[0] : b.min[0],
885
+ i & 2 ? b.max[1] : b.min[1],
886
+ i & 4 ? b.max[2] : b.min[2]
887
+ ]));
888
+ const axis = (k, f) => f(...corners.map((c) => c[k]));
889
+ out.set(p.name, {
890
+ min: [axis(0, Math.min), axis(1, Math.min), axis(2, Math.min)],
891
+ max: [axis(0, Math.max), axis(1, Math.max), axis(2, Math.max)]
892
+ });
893
+ }
894
+ return out;
895
+ };
896
+ var triangleBudget2 = (asset) => {
897
+ const tris = asset.parts.reduce((s, p) => s + (p.mesh ? triangleCount2(p.mesh) : 0), 0);
898
+ const cap = asset.budget.triangles[asset.lod];
899
+ return tris > cap ? [
900
+ {
901
+ gate: "triangle-budget",
902
+ message: `LOD${asset.lod} has ${tris} triangles, budget ${cap}`,
903
+ fix: "drop detail at this LOD (read ctx.lod) or raise the budget with a reason"
904
+ }
905
+ ] : [];
906
+ };
907
+ var partNames2 = (asset) => asset.parts.filter((p) => !/^[a-z][a-z0-9_]*$/.test(p.name)).map((p) => ({
908
+ gate: "part-names",
909
+ part: p.name,
910
+ message: `part '${p.name}' is not snake_case`,
911
+ fix: "rename it — engines bind to node names like axle_0, door_l"
912
+ }));
913
+ var proportions2 = (asset) => {
914
+ const real = asset.real;
915
+ if (!real || asset.lod !== 0)
916
+ return [];
917
+ const boxes = [...worldBounds(asset).values()];
918
+ const span = (k) => Math.max(...boxes.map((b) => b.max[k])) - Math.min(...boxes.map((b) => b.min[k]));
919
+ const tol = real.tolerance ?? 0.05;
920
+ return [
921
+ ["length", 0, real.length],
922
+ ["width", 1, real.width],
923
+ ["height", 2, real.height]
924
+ ].flatMap(([axis, k, want]) => {
925
+ const got = span(k);
926
+ return Math.abs(got - want) / want > tol ? [
927
+ {
928
+ gate: "proportions",
929
+ message: `${axis} ${got.toFixed(2)} m vs real ${want} m (±${tol * 100}%)`,
930
+ fix: `rescale along ${"XYZ"[k]} — re-derive the lengths from the sources, don't fudge the tolerance`
931
+ }
932
+ ] : [];
933
+ });
934
+ };
935
+ var silhouetteIoU2 = (_asset, _referencePng) => notImplemented2("silhouette IoU gate");
936
+ var recipeChecks = (asset) => runChecks2(asset).map((f) => ({
937
+ gate: "check",
938
+ ...f.part ? { part: f.part } : {},
939
+ message: f.message,
940
+ fix: f.fix
941
+ }));
942
+ var DEFAULT_GATES2 = [
943
+ triangleBudget2,
944
+ floatingParts2,
945
+ clashes2,
946
+ partNames2,
947
+ proportions2,
948
+ measures2,
949
+ recipeChecks,
950
+ nonFinite2,
951
+ vertexColors2,
952
+ hiddenParts2,
953
+ normalsMatchWinding2
954
+ ];
955
+ function runQa2(asset, gates = DEFAULT_GATES2) {
956
+ return gates.flatMap((g) => g(asset));
957
+ }
958
+ // src/qa/lods.ts
959
+ var tris = (a) => a.parts.reduce((s, p) => s + (p.mesh ? p.mesh.indices.length / 3 : 0), 0);
960
+ var lodTriangles2 = (lods) => lods.slice(1).flatMap((a, i) => {
961
+ const prev = lods[i];
962
+ if (!prev || tris(a) <= tris(prev))
963
+ return [];
964
+ return [
965
+ {
966
+ gate: "lod-triangles",
967
+ message: `LOD${a.lod} has ${tris(a)} triangles, more than LOD${prev.lod} (${tris(prev)})`,
968
+ fix: "each LOD must be cheaper than the one before: read ctx.lod / ctx.pick([...]) and drop segments or detail parts"
969
+ }
970
+ ];
971
+ });
972
+ function assetBounds(a) {
973
+ const all = [...worldMeshes2(a.parts).values()].map(boundsOf2);
974
+ if (all.length === 0)
975
+ return null;
976
+ const k = [0, 1, 2];
977
+ const lo = k.map((i) => Math.min(...all.map((b) => b.min[i])));
978
+ const hi = k.map((i) => Math.max(...all.map((b) => b.max[i])));
979
+ return {
980
+ min: [lo[0] ?? 0, lo[1] ?? 0, lo[2] ?? 0],
981
+ max: [hi[0] ?? 0, hi[1] ?? 0, hi[2] ?? 0]
982
+ };
983
+ }
984
+ var lodSilhouette2 = (tol = 0.05) => (lods) => {
985
+ const ref = lods[0] ? assetBounds(lods[0]) : null;
986
+ if (!ref)
987
+ return [];
988
+ return lods.slice(1).flatMap((a) => {
989
+ const b = assetBounds(a);
990
+ if (!b)
991
+ return [];
992
+ const off = [0, 1, 2].flatMap((k) => {
993
+ const lim = Math.max(0.05, (ref.max[k] - ref.min[k]) * tol);
994
+ const d = Math.max(Math.abs(b.min[k] - ref.min[k]), Math.abs(b.max[k] - ref.max[k]));
995
+ return d > lim ? [`${"XYZ"[k]} by ${(d * 100).toFixed(0)} cm`] : [];
996
+ });
997
+ return off.length ? [
998
+ {
999
+ gate: "lod-silhouette",
1000
+ message: `LOD${a.lod} outline differs from LOD0 on ${off.join(", ")} — it will pop on LOD switch`,
1001
+ fix: "keep the big masses at every LOD (drop detail, not extent): a far LOD is the same shape with fewer segments"
1002
+ }
1003
+ ] : [];
1004
+ });
1005
+ };
1006
+ var pivots = (a) => a.parts.filter((p) => p.mesh === null || p.motion !== null).map((p) => p.name);
1007
+ var lodPivots2 = (lods) => {
1008
+ const ref = lods[0];
1009
+ if (!ref)
1010
+ return [];
1011
+ const want = pivots(ref);
1012
+ return lods.slice(1).flatMap((a) => {
1013
+ const have = new Set(a.parts.map((p) => p.name));
1014
+ const missing = want.filter((n) => !have.has(n));
1015
+ return missing.length ? [
1016
+ {
1017
+ gate: "lod-pivots",
1018
+ 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`,
1019
+ fix: "keep the group/moving part at every LOD and gate only its geometry: ctx.group(name) always, detail meshes behind ctx.lod"
1020
+ }
1021
+ ] : [];
1022
+ });
1023
+ };
1024
+ var DEFAULT_LOD_GATES2 = [lodTriangles2];
1025
+ function runLodQa2(lods, gates = DEFAULT_LOD_GATES2) {
1026
+ return gates.flatMap((g) => g(lods));
1027
+ }
1028
+ export { pointTriangleDistance2, TriangleGrid2, surfaceSamples2, insideSolid2, glob2, inspect2, clear2, noOverlap2, touches2, symmetric2, runChecks2, CONTACT_EPS2, CLASH_EPS2, clashes2, touching2, floatingParts2, measure2, measures2, zFighting2, hiddenParts2, nonFinite2, vertexColors2, normalsMatchWinding2, degenerateTriangles2, triangleBudget2, partNames2, proportions2, silhouetteIoU2, DEFAULT_GATES2, runQa2, lodTriangles2, lodSilhouette2, lodPivots2, DEFAULT_LOD_GATES2, runLodQa2 };
1029
+
1030
+ //# debugId=D719F42A62E0985B64756E2164756E21
1031
+ //# sourceMappingURL=chunk-jq4e17tj.js.map