@developerz.ai/ts23d 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/bug-hunter.md +21 -0
- package/.claude/agents/docs-author.md +13 -0
- package/.claude/agents/dx-auditor.md +14 -0
- package/.claude/agents/primitive-author.md +22 -0
- package/.claude/agents/recipe-author.md +36 -0
- package/.claude/agents/visual-reviewer.md +23 -0
- package/.claude/commands/feature.md +28 -0
- package/.claude/commands/new-recipe.md +19 -0
- package/.claude/commands/release.md +14 -0
- package/.claude/commands/verify.md +9 -0
- package/.claude/skills/author-recipe/SKILL.md +93 -0
- package/CHANGELOG.md +79 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +380 -2
- package/dist/chunk-11v28977.js +1575 -0
- package/dist/chunk-11v28977.js.map +30 -0
- package/dist/chunk-1ypfs7ga.js +3571 -0
- package/dist/chunk-1ypfs7ga.js.map +42 -0
- package/dist/chunk-4ddzsqtc.js +464 -0
- package/dist/chunk-4ddzsqtc.js.map +19 -0
- package/dist/chunk-a8fxakcj.js +19 -0
- package/dist/chunk-a8fxakcj.js.map +11 -0
- package/dist/chunk-gcyp9b4d.js +755 -0
- package/dist/chunk-gcyp9b4d.js.map +22 -0
- package/dist/chunk-kgvbpqqw.js +44 -0
- package/dist/chunk-kgvbpqqw.js.map +10 -0
- package/dist/chunk-kvcep7ft.js +1919 -0
- package/dist/chunk-kvcep7ft.js.map +43 -0
- package/dist/chunk-ravbb06q.js +89 -0
- package/dist/chunk-ravbb06q.js.map +11 -0
- package/dist/chunk-w1zyhj40.js +1031 -0
- package/dist/chunk-w1zyhj40.js.map +21 -0
- package/dist/chunk-wtqhq529.js +2532 -0
- package/dist/chunk-wtqhq529.js.map +43 -0
- package/dist/chunk-xnt2sdhf.js +836 -0
- package/dist/chunk-xnt2sdhf.js.map +17 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +307 -0
- package/dist/cli/bin.js.map +14 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +49 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +148 -0
- package/dist/core/errors.d.ts +17 -0
- package/dist/core/family.d.ts +23 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +69 -0
- package/dist/core/index.js.map +9 -0
- package/dist/core/mesh.d.ts +35 -0
- package/dist/core/part.d.ts +114 -0
- package/dist/core/png.d.ts +1 -0
- package/dist/core/rng.d.ts +12 -0
- package/dist/core/transform.d.ts +20 -0
- package/dist/core/vec.d.ts +11 -0
- package/dist/core/world.d.ts +16 -0
- package/dist/export/bin-writer.d.ts +16 -0
- package/dist/export/container.d.ts +3 -0
- package/dist/export/glb.d.ts +38 -0
- package/dist/export/index.d.ts +4 -0
- package/dist/export/index.js +26 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +19 -0
- package/dist/export/motion.d.ts +9 -0
- package/dist/export/optimize-parts.d.ts +26 -0
- package/dist/export/primitive.d.ts +3 -0
- package/dist/export/tangents.d.ts +2 -0
- package/dist/export/textures.d.ts +22 -0
- package/dist/export/version.d.ts +2 -0
- package/dist/grammar/index.d.ts +2 -0
- package/dist/grammar/index.js +16 -0
- package/dist/grammar/index.js.map +9 -0
- package/dist/grammar/split.d.ts +21 -0
- package/dist/grammar/whyte.d.ts +5 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +411 -0
- package/dist/index.js.map +9 -0
- package/dist/kinds/building.d.ts +44 -0
- package/dist/kinds/faces.d.ts +27 -0
- package/dist/kinds/head-armour.d.ts +9 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +4 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +6 -0
- package/dist/kinds/head-geom.d.ts +7 -0
- package/dist/kinds/head-hair.d.ts +4 -0
- package/dist/kinds/head-layout.d.ts +29 -0
- package/dist/kinds/head-mesh.d.ts +27 -0
- package/dist/kinds/head-params.d.ts +108 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -0
- package/dist/kinds/head.d.ts +39 -0
- package/dist/kinds/hull-lines.d.ts +65 -0
- package/dist/kinds/hull-skin.d.ts +6 -0
- package/dist/kinds/hull.d.ts +14 -0
- package/dist/kinds/index.d.ts +16 -0
- package/dist/kinds/index.js +78 -0
- package/dist/kinds/index.js.map +9 -0
- package/dist/kinds/mass.d.ts +15 -0
- package/dist/kinds/openings.d.ts +30 -0
- package/dist/kinds/planned.d.ts +6 -0
- package/dist/kinds/roof-chimney.d.ts +16 -0
- package/dist/kinds/roof-shape.d.ts +64 -0
- package/dist/kinds/roof.d.ts +31 -0
- package/dist/kinds/vegetation-fronds.d.ts +15 -0
- package/dist/kinds/vegetation-impostor.d.ts +9 -0
- package/dist/kinds/vegetation-leaves.d.ts +15 -0
- package/dist/kinds/vegetation-mesh.d.ts +21 -0
- package/dist/kinds/vegetation-params.d.ts +90 -0
- package/dist/kinds/vegetation-presets.d.ts +20 -0
- package/dist/kinds/vegetation-skeleton.d.ts +20 -0
- package/dist/kinds/vegetation.d.ts +42 -0
- package/dist/kinds/vehicle.d.ts +42 -0
- package/dist/material/generator.d.ts +4 -0
- package/dist/material/index.d.ts +4 -0
- package/dist/material/index.js +38 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +32 -0
- package/dist/material/paint/cell.d.ts +4 -0
- package/dist/material/paint/cells-round.d.ts +5 -0
- package/dist/material/paint/cells.d.ts +7 -0
- package/dist/material/paint/compose.d.ts +5 -0
- package/dist/material/paint/concrete.d.ts +3 -0
- package/dist/material/paint/courses.d.ts +31 -0
- package/dist/material/paint/decode-png.d.ts +6 -0
- package/dist/material/paint/default-sheet.d.ts +2 -0
- package/dist/material/paint/foliage-broad.d.ts +5 -0
- package/dist/material/paint/foliage-common.d.ts +35 -0
- package/dist/material/paint/foliage-compound.d.ts +4 -0
- package/dist/material/paint/foliage-conifer.d.ts +5 -0
- package/dist/material/paint/foliage-raster.d.ts +26 -0
- package/dist/material/paint/foliage.d.ts +3 -0
- package/dist/material/paint/masks.d.ts +3 -0
- package/dist/material/paint/noise.d.ts +9 -0
- package/dist/material/paint/normal.d.ts +8 -0
- package/dist/material/paint/orm.d.ts +8 -0
- package/dist/material/paint/planks.d.ts +3 -0
- package/dist/material/paint/plates.d.ts +3 -0
- package/dist/material/paint/shapes.d.ts +8 -0
- package/dist/material/paint/slate.d.ts +3 -0
- package/dist/material/paint/stamp.d.ts +3 -0
- package/dist/material/paint/strip.d.ts +3 -0
- package/dist/material/paint/surface.d.ts +5 -0
- package/dist/material/paint/types.d.ts +35 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +12 -0
- package/dist/material/shader-godot.d.ts +2 -0
- package/dist/material/shader-three.d.ts +2 -0
- package/dist/material/shader.d.ts +46 -0
- package/dist/material/trim-sheet.d.ts +22 -0
- package/dist/neural/generator.d.ts +25 -0
- package/dist/neural/images.d.ts +17 -0
- package/dist/neural/index.d.ts +2 -0
- package/dist/neural/index.js +14 -0
- package/dist/neural/index.js.map +9 -0
- package/dist/passes/bake-ao.d.ts +17 -0
- package/dist/passes/grime.d.ts +15 -0
- package/dist/passes/index.d.ts +6 -0
- package/dist/passes/index.js +22 -0
- package/dist/passes/index.js.map +9 -0
- package/dist/passes/livery.d.ts +19 -0
- package/dist/passes/optimize-cache.d.ts +15 -0
- package/dist/passes/optimize-weld.d.ts +15 -0
- package/dist/passes/optimize.d.ts +28 -0
- package/dist/passes/ray-grid.d.ts +23 -0
- package/dist/passes/weighted-normals.d.ts +8 -0
- package/dist/primitives/beam.d.ts +17 -0
- package/dist/primitives/bevel-box.d.ts +13 -0
- package/dist/primitives/box.d.ts +16 -0
- package/dist/primitives/cell.d.ts +20 -0
- package/dist/primitives/check.d.ts +12 -0
- package/dist/primitives/closed.d.ts +11 -0
- package/dist/primitives/erosion.d.ts +22 -0
- package/dist/primitives/font-5x7.d.ts +3 -0
- package/dist/primitives/heightfield.d.ts +16 -0
- package/dist/primitives/index.d.ts +23 -0
- package/dist/primitives/index.js +81 -0
- package/dist/primitives/index.js.map +9 -0
- package/dist/primitives/lathe-facets.d.ts +8 -0
- package/dist/primitives/lathe.d.ts +26 -0
- package/dist/primitives/lattice.d.ts +30 -0
- package/dist/primitives/loft.d.ts +15 -0
- package/dist/primitives/mc-table.d.ts +2 -0
- package/dist/primitives/member.d.ts +19 -0
- package/dist/primitives/noise.d.ts +34 -0
- package/dist/primitives/planar.d.ts +10 -0
- package/dist/primitives/prism.d.ts +13 -0
- package/dist/primitives/sculpt-edit.d.ts +56 -0
- package/dist/primitives/sculpt-tree.d.ts +23 -0
- package/dist/primitives/sculpt.d.ts +27 -0
- package/dist/primitives/sdf-grid.d.ts +17 -0
- package/dist/primitives/sdf-ops.d.ts +37 -0
- package/dist/primitives/sdf.d.ts +21 -0
- package/dist/primitives/strip-uv.d.ts +11 -0
- package/dist/primitives/surface-nets.d.ts +7 -0
- package/dist/primitives/sweep.d.ts +19 -0
- package/dist/primitives/terrain-color.d.ts +30 -0
- package/dist/primitives/terrain-grid.d.ts +36 -0
- package/dist/primitives/text-layout.d.ts +22 -0
- package/dist/primitives/text.d.ts +27 -0
- package/dist/primitives/tiled.d.ts +22 -0
- package/dist/primitives/transport.d.ts +17 -0
- package/dist/primitives/triangulate.d.ts +2 -0
- package/dist/primitives/value-noise.d.ts +10 -0
- package/dist/qa/checks.d.ts +11 -0
- package/dist/qa/clash.d.ts +2 -0
- package/dist/qa/floating.d.ts +5 -0
- package/dist/qa/gates.d.ts +12 -0
- package/dist/qa/geometry.d.ts +21 -0
- package/dist/qa/index.d.ts +12 -0
- package/dist/qa/index.js +78 -0
- package/dist/qa/index.js.map +9 -0
- package/dist/qa/inside.d.ts +12 -0
- package/dist/qa/inspect.d.ts +18 -0
- package/dist/qa/lods.d.ts +12 -0
- package/dist/qa/measures.d.ts +4 -0
- package/dist/qa/occlusion.d.ts +3 -0
- package/dist/qa/sanity.d.ts +7 -0
- package/dist/qa/types.d.ts +12 -0
- package/dist/render/beauty.d.ts +40 -0
- package/dist/render/camera.d.ts +33 -0
- package/dist/render/depth-map.d.ts +38 -0
- package/dist/render/font.d.ts +1 -0
- package/dist/render/ground.d.ts +13 -0
- package/dist/render/index.d.ts +10 -0
- package/dist/render/index.js +47 -0
- package/dist/render/index.js.map +9 -0
- package/dist/render/lighting.d.ts +20 -0
- package/dist/render/lod-strip.d.ts +5 -0
- package/dist/render/metrics.d.ts +37 -0
- package/dist/render/options.d.ts +28 -0
- package/dist/render/photo-match.d.ts +19 -0
- package/dist/render/raster.d.ts +25 -0
- package/dist/render/resolve.d.ts +11 -0
- package/dist/render/review-prompt.d.ts +2 -0
- package/dist/render/review-warnings.d.ts +34 -0
- package/dist/render/review.d.ts +24 -0
- package/dist/render/scene.d.ts +18 -0
- package/dist/render/shade.d.ts +22 -0
- package/dist/render/sheet.d.ts +15 -0
- package/dist/render/texture.d.ts +15 -0
- package/dist/render/thumbnail.d.ts +8 -0
- package/dist/render/vis-buffer.d.ts +18 -0
- package/dist/testing/assert.d.ts +21 -0
- package/dist/testing/compiled.d.ts +15 -0
- package/dist/testing/hash.d.ts +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +134 -0
- package/dist/testing/index.js.map +13 -0
- package/dist/testing/snapshot.d.ts +13 -0
- package/docs/README.md +52 -0
- package/docs/architecture/README.md +68 -0
- package/docs/architecture/adding-a-kind.md +44 -0
- package/docs/architecture/adding-a-primitive.md +49 -0
- package/docs/architecture/errors.md +47 -0
- package/docs/architecture/glb-production.md +214 -0
- package/docs/architecture/gltf-capabilities.md +80 -0
- package/docs/decisions/001-recipe-compiler-over-ai-meshes.md +27 -0
- package/docs/decisions/002-typescript-not-json.md +22 -0
- package/docs/decisions/003-shared-trim-sheet.md +21 -0
- package/docs/decisions/004-z-up-metres.md +21 -0
- package/docs/decisions/005-zero-runtime-deps.md +21 -0
- package/docs/decisions/006-single-package-subpath-exports.md +20 -0
- package/docs/decisions/007-cpu-contact-sheets.md +21 -0
- package/docs/decisions/008-surface-materials.md +21 -0
- package/docs/decisions/009-gltf-front-axis.md +21 -0
- package/docs/decisions/README.md +15 -0
- package/docs/idea/01-vision.md +117 -0
- package/docs/idea/02-dsl.md +343 -0
- package/docs/idea/03-ir.md +97 -0
- package/docs/idea/04-materials.md +114 -0
- package/docs/idea/05-agent-loop.md +141 -0
- package/docs/idea/06-qa.md +101 -0
- package/docs/idea/07-what-fits.md +45 -0
- package/docs/idea/08-neural-adapters.md +78 -0
- package/docs/idea/09-kinds.md +105 -0
- package/docs/landscape.md +49 -0
- package/docs/plans/roadmap.md +127 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +99 -4
- package/recipes/README.md +38 -0
- package/recipes/asteroid/README.md +46 -0
- package/recipes/asteroid/asteroid.ts +336 -0
- package/recipes/boulders/README.md +28 -0
- package/recipes/boulders/boulders.ts +117 -0
- package/recipes/boxcar/README.md +46 -0
- package/recipes/boxcar/boxcar.ts +916 -0
- package/recipes/cargo_container_scifi/README.md +52 -0
- package/recipes/cargo_container_scifi/cargo_container_scifi.ts +461 -0
- package/recipes/dropship/README.md +84 -0
- package/recipes/dropship/dropship.ts +1144 -0
- package/recipes/eiffel_tower/README.md +110 -0
- package/recipes/eiffel_tower/eiffel_tower.ts +781 -0
- package/recipes/goblin_head/README.md +62 -0
- package/recipes/goblin_head/goblin_head.ts +36 -0
- package/recipes/human_head/README.md +64 -0
- package/recipes/human_head/human_head.ts +37 -0
- package/recipes/loco_440/README.md +32 -0
- package/recipes/loco_440/loco_440.ts +1404 -0
- package/recipes/material_swatches/README.md +21 -0
- package/recipes/material_swatches/material_swatches.ts +183 -0
- package/recipes/mountain_range/README.md +45 -0
- package/recipes/mountain_range/mountain_range.ts +128 -0
- package/recipes/oak/README.md +30 -0
- package/recipes/oak/oak.ts +19 -0
- package/recipes/orc_head/README.md +66 -0
- package/recipes/orc_head/orc_head.ts +39 -0
- package/recipes/palm/README.md +30 -0
- package/recipes/palm/palm.ts +19 -0
- package/recipes/pine/README.md +29 -0
- package/recipes/pine/pine.ts +19 -0
- package/recipes/signal_gantry/README.md +36 -0
- package/recipes/signal_gantry/signal_gantry.ts +522 -0
- package/recipes/station_halt/README.md +39 -0
- package/recipes/station_halt/station_halt.ts +722 -0
- package/recipes/t34/README.md +44 -0
- package/recipes/t34/t34.ts +1068 -0
- package/recipes/tiger_1/README.md +75 -0
- package/recipes/tiger_1/tiger_1.ts +1370 -0
- package/recipes/water_tower/README.md +40 -0
- package/recipes/water_tower/water_tower.ts +542 -0
- package/src/cli/args.ts +112 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +89 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +168 -0
- package/src/core/asset.ts +128 -0
- package/src/core/definition.ts +149 -0
- package/src/core/errors.ts +45 -0
- package/src/core/family.ts +50 -0
- package/src/core/index.ts +66 -0
- package/src/core/mesh.ts +114 -0
- package/src/core/part.ts +130 -0
- package/src/core/png.ts +49 -0
- package/src/core/rng.ts +49 -0
- package/src/core/transform.ts +90 -0
- package/src/core/vec.ts +26 -0
- package/src/core/world.ts +80 -0
- package/src/export/bin-writer.ts +79 -0
- package/src/export/container.ts +35 -0
- package/src/export/glb.ts +200 -0
- package/src/export/index.ts +18 -0
- package/src/export/lights.ts +49 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +59 -0
- package/src/export/motion.ts +70 -0
- package/src/export/optimize-parts.ts +80 -0
- package/src/export/primitive.ts +35 -0
- package/src/export/tangents.ts +75 -0
- package/src/export/textures.ts +88 -0
- package/src/export/version.ts +6 -0
- package/src/grammar/index.ts +4 -0
- package/src/grammar/split.ts +87 -0
- package/src/grammar/whyte.ts +20 -0
- package/src/index.ts +14 -0
- package/src/kinds/building.ts +153 -0
- package/src/kinds/faces.ts +138 -0
- package/src/kinds/head-armour.ts +151 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +221 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +222 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +152 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +157 -0
- package/src/kinds/head-params.ts +153 -0
- package/src/kinds/head-presets.ts +152 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head.ts +171 -0
- package/src/kinds/hull-lines.ts +160 -0
- package/src/kinds/hull-skin.ts +83 -0
- package/src/kinds/hull.ts +209 -0
- package/src/kinds/index.ts +63 -0
- package/src/kinds/mass.ts +110 -0
- package/src/kinds/openings.ts +141 -0
- package/src/kinds/planned.ts +12 -0
- package/src/kinds/roof-chimney.ts +100 -0
- package/src/kinds/roof-shape.ts +230 -0
- package/src/kinds/roof.ts +250 -0
- package/src/kinds/vegetation-fronds.ts +126 -0
- package/src/kinds/vegetation-impostor.ts +118 -0
- package/src/kinds/vegetation-leaves.ts +168 -0
- package/src/kinds/vegetation-mesh.ts +174 -0
- package/src/kinds/vegetation-params.ts +147 -0
- package/src/kinds/vegetation-presets.ts +184 -0
- package/src/kinds/vegetation-skeleton.ts +168 -0
- package/src/kinds/vegetation.ts +199 -0
- package/src/kinds/vehicle.ts +144 -0
- package/src/material/generator.ts +56 -0
- package/src/material/index.ts +29 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +91 -0
- package/src/material/paint/cell.ts +39 -0
- package/src/material/paint/cells-round.ts +119 -0
- package/src/material/paint/cells.ts +111 -0
- package/src/material/paint/compose.ts +53 -0
- package/src/material/paint/concrete.ts +62 -0
- package/src/material/paint/courses.ts +85 -0
- package/src/material/paint/decode-png.ts +100 -0
- package/src/material/paint/default-sheet.ts +61 -0
- package/src/material/paint/foliage-broad.ts +126 -0
- package/src/material/paint/foliage-common.ts +93 -0
- package/src/material/paint/foliage-compound.ts +135 -0
- package/src/material/paint/foliage-conifer.ts +143 -0
- package/src/material/paint/foliage-raster.ts +196 -0
- package/src/material/paint/foliage.ts +23 -0
- package/src/material/paint/masks.ts +44 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +95 -0
- package/src/material/paint/planks.ts +56 -0
- package/src/material/paint/plates.ts +72 -0
- package/src/material/paint/shapes.ts +28 -0
- package/src/material/paint/slate.ts +52 -0
- package/src/material/paint/stamp.ts +40 -0
- package/src/material/paint/strip.ts +42 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +51 -0
- package/src/material/paint/validate.ts +58 -0
- package/src/material/palette.ts +43 -0
- package/src/material/shader-godot.ts +77 -0
- package/src/material/shader-three.ts +103 -0
- package/src/material/shader.ts +100 -0
- package/src/material/trim-sheet.ts +100 -0
- package/src/neural/generator.ts +37 -0
- package/src/neural/images.ts +27 -0
- package/src/neural/index.ts +10 -0
- package/src/passes/bake-ao.ts +183 -0
- package/src/passes/grime.ts +37 -0
- package/src/passes/index.ts +8 -0
- package/src/passes/livery.ts +66 -0
- package/src/passes/optimize-cache.ts +175 -0
- package/src/passes/optimize-weld.ts +147 -0
- package/src/passes/optimize.ts +85 -0
- package/src/passes/ray-grid.ts +237 -0
- package/src/passes/weighted-normals.ts +125 -0
- package/src/primitives/beam.ts +60 -0
- package/src/primitives/bevel-box.ts +140 -0
- package/src/primitives/box.ts +90 -0
- package/src/primitives/cell.ts +115 -0
- package/src/primitives/check.ts +49 -0
- package/src/primitives/closed.ts +74 -0
- package/src/primitives/erosion.ts +183 -0
- package/src/primitives/font-5x7.ts +117 -0
- package/src/primitives/heightfield.ts +101 -0
- package/src/primitives/index.ts +56 -0
- package/src/primitives/lathe-facets.ts +45 -0
- package/src/primitives/lathe.ts +174 -0
- package/src/primitives/lattice.ts +131 -0
- package/src/primitives/loft.ts +174 -0
- package/src/primitives/mc-table.ts +41 -0
- package/src/primitives/member.ts +130 -0
- package/src/primitives/noise.ts +157 -0
- package/src/primitives/planar.ts +55 -0
- package/src/primitives/prism.ts +85 -0
- package/src/primitives/sculpt-edit.ts +212 -0
- package/src/primitives/sculpt-tree.ts +226 -0
- package/src/primitives/sculpt.ts +80 -0
- package/src/primitives/sdf-grid.ts +131 -0
- package/src/primitives/sdf-ops.ts +145 -0
- package/src/primitives/sdf.ts +83 -0
- package/src/primitives/strip-uv.ts +26 -0
- package/src/primitives/surface-nets.ts +161 -0
- package/src/primitives/sweep.ts +162 -0
- package/src/primitives/terrain-color.ts +85 -0
- package/src/primitives/terrain-grid.ts +137 -0
- package/src/primitives/text-layout.ts +85 -0
- package/src/primitives/text.ts +197 -0
- package/src/primitives/tiled.ts +93 -0
- package/src/primitives/transport.ts +85 -0
- package/src/primitives/triangulate.ts +42 -0
- package/src/primitives/value-noise.ts +62 -0
- package/src/qa/checks.ts +110 -0
- package/src/qa/clash.ts +177 -0
- package/src/qa/floating.ts +63 -0
- package/src/qa/gates.ts +126 -0
- package/src/qa/geometry.ts +187 -0
- package/src/qa/index.ts +28 -0
- package/src/qa/inside.ts +114 -0
- package/src/qa/inspect.ts +107 -0
- package/src/qa/lods.ts +97 -0
- package/src/qa/measures.ts +67 -0
- package/src/qa/occlusion.ts +166 -0
- package/src/qa/sanity.ts +137 -0
- package/src/qa/types.ts +17 -0
- package/src/render/beauty.ts +115 -0
- package/src/render/camera.ts +142 -0
- package/src/render/depth-map.ts +191 -0
- package/src/render/font.ts +76 -0
- package/src/render/ground.ts +87 -0
- package/src/render/index.ts +34 -0
- package/src/render/lighting.ts +95 -0
- package/src/render/lod-strip.ts +34 -0
- package/src/render/metrics.ts +147 -0
- package/src/render/options.ts +81 -0
- package/src/render/photo-match.ts +28 -0
- package/src/render/raster.ts +143 -0
- package/src/render/resolve.ts +109 -0
- package/src/render/review-prompt.ts +59 -0
- package/src/render/review-warnings.ts +92 -0
- package/src/render/review.ts +103 -0
- package/src/render/scene.ts +119 -0
- package/src/render/shade.ts +199 -0
- package/src/render/sheet.ts +88 -0
- package/src/render/texture.ts +97 -0
- package/src/render/thumbnail.ts +75 -0
- package/src/render/vis-buffer.ts +119 -0
- package/src/testing/assert.ts +115 -0
- package/src/testing/compiled.ts +31 -0
- package/src/testing/hash.ts +14 -0
- package/src/testing/index.ts +14 -0
- package/src/testing/snapshot.ts +37 -0
- package/src/types/gltf-validator.d.ts +13 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Decisions
|
|
2
|
+
|
|
3
|
+
**One record per one-way door, with the rejected options written down.** Read the matching record before proposing to reopen a decision; reopening requires naming what changed since its "why not". Format: context / decision / alternatives / consequences. Supersede, never edit history.
|
|
4
|
+
|
|
5
|
+
| # | Decision | Status | Date |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| [001](001-recipe-compiler-over-ai-meshes.md) | Assets are compiled from recipes, not generated by AI mesh models | accepted | 2026-10-25 |
|
|
8
|
+
| [002](002-typescript-not-json.md) | Recipes are TypeScript modules, not JSON | accepted | 2026-10-25 |
|
|
9
|
+
| [003](003-shared-trim-sheet.md) | Every asset shares one trim-sheet material | superseded by [008](008-surface-materials.md) | 2026-10-25 |
|
|
10
|
+
| [004](004-z-up-metres.md) | Author in metres, Z up, +X front | accepted; export orientation superseded by [009](009-gltf-front-axis.md) | 2026-10-25 |
|
|
11
|
+
| [005](005-zero-runtime-deps.md) | Zero runtime dependencies | accepted | 2026-10-25 |
|
|
12
|
+
| [006](006-single-package-subpath-exports.md) | One npm package with subpath exports | accepted | 2026-10-25 |
|
|
13
|
+
| [007](007-cpu-contact-sheets.md) | Agent feedback is a CPU-rendered contact sheet | accepted | 2026-10-25 |
|
|
14
|
+
| [008](008-surface-materials.md) | One trim sheet, up to four surface materials | accepted | 2026-10-25 |
|
|
15
|
+
| [009](009-gltf-front-axis.md) | Exported GLBs face glTF +Z (root `[-0.5,-0.5,-0.5,0.5]`) | accepted | 2026-10-25 |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 01 — Vision
|
|
2
|
+
|
|
3
|
+
**Free 3D asset generator for coding agents. The agent writes TypeScript; ts23d compiles it, deterministically, to game-ready glTF for Unity, Unreal, Godot, three.js, Babylon or anything that reads glTF.** Use the LLM you already pay for to write the assets your project needs (vehicles, buildings, props, whole streets), optimised by construction, with a contact sheet so the agent checks its own work.
|
|
4
|
+
|
|
5
|
+
## Scope: ts23d ends at the GLB
|
|
6
|
+
|
|
7
|
+
| In scope | Out of scope |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Recipes → GLB × 3 LODs, the trim-sheet textures, `manifest.json`, contact sheets | Engines, game frameworks, runtime code |
|
|
10
|
+
| Hooks an engine can use: named pivots, sockets, emitter extras, the `run` clip | Driving those hooks: that's your engine code |
|
|
11
|
+
| Engine *import adapters* as data (import settings, collision naming): planned, M3 | Engine plugins or runtimes |
|
|
12
|
+
| An asset gallery that shows every recipe ([site](https://developerz-ai.github.io/ts23d/)) | A playable demo or game |
|
|
13
|
+
|
|
14
|
+
## From prompt to asset
|
|
15
|
+
|
|
16
|
+
| Step | You do | ts23d gives you |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| 1. Install | `bun add @developerz.ai/ts23d`; copy `node_modules/@developerz.ai/ts23d/.claude/skills/author-recipe` into `.claude/skills/` (Claude Code), or point your `AGENTS.md` at `node_modules/@developerz.ai/ts23d/AGENTS.md` (Codex) | The agent knows the loop |
|
|
19
|
+
| 2. Ask | `/new-recipe Tiger I, mid-production 1943, fully stowed` · `/new-recipe 4-4-0 steam loco with tender` · `/new-recipe 1940s city street with townhouses and gas lamps` (townhouse, lamp, street recipes: landing) | A recipe that passes QA, three LODs, textures, a manifest, a contact sheet |
|
|
20
|
+
| 3. Import | Load `out/<asset>/lod0.glb` in your engine | Named nodes (`turret`, `gun`, `wheel_*`), sockets, emitter extras, the `run` clip as hooks ([README](../../README.md#3-use-the-glb-in-your-engine)) |
|
|
21
|
+
|
|
22
|
+
## The pitch
|
|
23
|
+
|
|
24
|
+
| You bring | ts23d gives you |
|
|
25
|
+
|---|---|
|
|
26
|
+
| A coding agent (Claude Code, Codex, any model that writes TS) | A DSL that agent already knows: TypeScript |
|
|
27
|
+
| A description, a reference photo, real dimensions | GLB × 3 LODs, named + pivoted parts, baked loop animations |
|
|
28
|
+
| A machine with Bun | No GPU, no credits, no account, no service |
|
|
29
|
+
| A target engine (yours) | GLBs it imports: one texture set, batched draw calls, LODs, compact core-glTF buffers |
|
|
30
|
+
| Git | Assets as diffable source; same seed → same bytes |
|
|
31
|
+
|
|
32
|
+
## The promise, both halves
|
|
33
|
+
|
|
34
|
+
| Half | Means | How |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| **High definition up close** | Hero detail: rivets, rods, window sashes, real proportions | Code places detail N times for free; `real` dimensions + the `proportions` gate; painted trim sheet |
|
|
37
|
+
| **Cheap to draw** | Games pay per draw call, per vertex fetch, per byte | One shared material, static geometry batched into its pivot, so draw calls = the parts your engine moves (a static building: 1); 3 LODs from one source; u16 indices and colours today; welded, vertex-cache-ordered buffers via `optimize`, an opt-in web profile (quantisation + meshopt) and LOD switch hints planned (M2) |
|
|
38
|
+
|
|
39
|
+
## No Blender for most hard-surface asset production
|
|
40
|
+
|
|
41
|
+
**The DSL does the modelling.** Scoped to asset production; the engine stays yours. For hard-surface assets (vehicles, buildings, props, whole scenes) the LLM writes TypeScript, and ts23d does the rest:
|
|
42
|
+
|
|
43
|
+
| Blender-era step | In ts23d |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Modelling | `primitives` + `kinds`, real units, pivots where parts hinge |
|
|
46
|
+
| LODs | `ctx.lod`: the same recipe re-run at lower detail |
|
|
47
|
+
| UV unwrap | UVs land on the shared trim sheet by world metres, automatically |
|
|
48
|
+
| Batching / joining meshes | `toGlb` merges static geometry into its nearest pivot |
|
|
49
|
+
| Checking | QA gates + contact sheet |
|
|
50
|
+
| Export | `toGlb`, zero dependencies |
|
|
51
|
+
|
|
52
|
+
Blender stays useful for organic sculpting: people, animals, hero rocks. Those come in through the planned [`/neural` adapter or `importGlb`](08-neural-adapters.md).
|
|
53
|
+
|
|
54
|
+
## One asset pipeline, no other libraries
|
|
55
|
+
|
|
56
|
+
**Asset production needs nothing else between "describe the asset" and "a GLB your engine imports".**
|
|
57
|
+
|
|
58
|
+
| Stage | Module | Status (2026-10) | Replaces |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| Geometry | `primitives`, `kinds` | Shipped: `box`, `lathe`, `prism`, `beam`, `planar`, `sweep`, `loft`, `heightfield`, facades, vehicles, Weber–Penn trees. Planned: `sdf`, roofs, hulls, creatures | Blender modelling |
|
|
61
|
+
| Materials and textures | `material` (generator) | Shipped: procedural albedo / normal / ORM trim sheet, embedded or shared PNGs, palettes, `grime`. No KTX2 yet | Substance / texture tools |
|
|
62
|
+
| Optimisation | `passes/optimize`, `export` | Shipped: u16 indices, u16 colours, batching. Planned, M2: weld, vertex-cache reorder, ACMR; opt-in `web` profile (quantisation + meshopt) | meshoptimizer / gltfpack |
|
|
63
|
+
| LODs | `ctx.lod` | Shipped | Decimators |
|
|
64
|
+
| QA | `qa` | Shipped: budget, surface-contact floating parts, motion clashes, names, proportions, spec measures. Silhouette IoU and ACMR planned | Manual review |
|
|
65
|
+
| Renders and photo match | `render` | Contact sheet shipped. Photo match planned, M2 | — |
|
|
66
|
+
| GLB export | `export` | Shipped: core glTF only, faces +Z, seamless loops, trim-sheet textures, 0 glTF-Validator errors (tested) | — |
|
|
67
|
+
| Engine import adapters | `threeMaterialSource` (TSL), `godotShaderSource` | Shipped: shader sources. Planned, M3: import settings and collision naming per engine, as hooks, not runtime | — |
|
|
68
|
+
|
|
69
|
+
Every stage has zero runtime dependencies ([ADR 005](../decisions/005-zero-runtime-deps.md)).
|
|
70
|
+
|
|
71
|
+
## Origin
|
|
72
|
+
|
|
73
|
+
Inspired by an in-house pipeline that started on AI mesh generation (Meshy plus an image model) and moved to a JSON recipe compiler. Why it moved:
|
|
74
|
+
|
|
75
|
+
| Problem with generated meshes | What a recipe compiler does instead |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Credit cost per attempt, per iteration | Free; iteration costs a rebuild |
|
|
78
|
+
| No reproducibility: same prompt, different mesh | Same source + seed → same bytes |
|
|
79
|
+
| One fused mesh, no named parts | `turret`, `gun`, `axle_0`: pivoted, animatable |
|
|
80
|
+
| A texture per asset → a draw call per asset | One shared trim sheet → one material |
|
|
81
|
+
| Every mesh needed repair (holes, scale, pivots, topology) | Built correct: real units, pivots set where parts hinge |
|
|
82
|
+
|
|
83
|
+
ts23d keeps that pipeline's ideas and goes one step further: recipes are **TypeScript, not JSON** ([ADR 002](../decisions/002-typescript-not-json.md)).
|
|
84
|
+
|
|
85
|
+
## Ideas carried over
|
|
86
|
+
|
|
87
|
+
| Idea | Where it lives | Status (2026-10) |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| CGA split grammar for facades: `"pier:0.9 \| {arch:2.5}* \| door:3 \| pier:~"` | `grammar/split.ts`, `kinds/building.ts` | Shipped |
|
|
90
|
+
| Domain-native params: Whyte `4-4-0` for locomotives | `grammar/whyte.ts`, `kinds/vehicle.ts` | Shipped |
|
|
91
|
+
| Masses, then details | Recipe convention; `setbacks` + `facade` | Shipped |
|
|
92
|
+
| Real units (metres) | Everywhere | Shipped |
|
|
93
|
+
| Real dimensions with sources, held by QA | `defineAsset({ real, sources })`, `proportions` + `measures` gates | Shipped |
|
|
94
|
+
| Era/region palettes | `material/palette.ts` | Shipped (4 palettes) |
|
|
95
|
+
| LODs by re-running the recipe at a detail level | `compile(asset, { lod })` | Shipped |
|
|
96
|
+
| A QA gate that fails the build | `qa/gates.ts` | Shipped (7 gates incl. recipe `checks`); more planned |
|
|
97
|
+
| Closed-loop agent iteration with multi-view renders | `render/sheet.ts`, `t3d shot` | Shipped |
|
|
98
|
+
| Weber–Penn botanical skeletons | `kinds/vegetation.ts` → `tree`, `trees` | Shipped |
|
|
99
|
+
| Photo match: photo · model · overlay in one PNG, plus silhouette IoU | `render` → `photoMatch` | Planned, M2 |
|
|
100
|
+
| Reverse direction: a VLM infers recipe params from photos, scored by silhouette IoU | builds on `photoMatch` | Planned, M6 |
|
|
101
|
+
|
|
102
|
+
## Principles
|
|
103
|
+
|
|
104
|
+
- **Code is the asset.** The GLB is a build artefact. Recipes are committed; `out/` is not.
|
|
105
|
+
- **Deterministic.** No ambient entropy, no wall clock. Byte-identical output for the same source + seed.
|
|
106
|
+
- **Errors are instructions.** Every failure carries a `T3D_*` code, the cause and the fix ([errors](../architecture/errors.md)).
|
|
107
|
+
- **The agent is the user.** Contact sheets, `--json`, and fix lines exist for an agent reading them, not for a human at a GUI.
|
|
108
|
+
- **Zero runtime dependencies** ([ADR 005](../decisions/005-zero-runtime-deps.md)).
|
|
109
|
+
- **Honest about fit.** Strong on hard-surface; weak on living things ([what fits](07-what-fits.md)).
|
|
110
|
+
|
|
111
|
+
## Non-goals
|
|
112
|
+
|
|
113
|
+
- Beating image-to-3D models at organic, sculpted forms. Use them, or the planned [neural adapter](08-neural-adapters.md).
|
|
114
|
+
- Engines, game frameworks, gameplay or demo code. ts23d ends at the GLB, textures and manifest.
|
|
115
|
+
- A modelling GUI. The editor is your agent; the viewer is the [gallery](https://developerz-ai.github.io/ts23d/).
|
|
116
|
+
- Photoreal hero assets. Target: game-ready, stylised-to-realistic props at consistent texel density.
|
|
117
|
+
- A hosted service. Everything runs on your machine.
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# 02 — The DSL
|
|
2
|
+
|
|
3
|
+
**An asset is a named export: `defineAsset({ …, build(ctx) })`.** `build` is a plain TypeScript function. It adds named parts to `ctx`; the compiler runs it once per LOD.
|
|
4
|
+
|
|
5
|
+
## Layers
|
|
6
|
+
|
|
7
|
+
**Every layer is plain TypeScript. You extend the DSL by writing functions; there is no plugin API to learn.**
|
|
8
|
+
|
|
9
|
+
| Layer | Shape | Example |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Primitive | `(options) => Mesh` | `box`, `lathe`, `prism`, `beam`, `planar`, `sweep`, `loft`, `heightfield` |
|
|
12
|
+
| Component | `(ctx, params) => void`: adds named parts | your `bogie(ctx, …)`, `chimney(ctx, …)` |
|
|
13
|
+
| Kind | A library of components | `facade`, `setbacks`, `axle`, `trackRun` |
|
|
14
|
+
| Family | `defineFamily({ defaults, build(ctx, p) })` | `t34` with `turret: '1941' \| '1943'` |
|
|
15
|
+
| Asset | `defineAsset(…)` or `family.variant(…)` | `t34_1943` |
|
|
16
|
+
| Scene | An asset that calls `ctx.use(other, …)` | a street, a rail yard |
|
|
17
|
+
|
|
18
|
+
## Primitives (shipped)
|
|
19
|
+
|
|
20
|
+
| Primitive | Key options | For |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `box` | `size`, `at`, `strip`, `omit`, `bevel?` | Hulls, walls, crates; `bevel` = the mid-poly chamfer |
|
|
23
|
+
| `lathe` / `cylinder` | `profile` or `radius`+`length`, `segments`, `axis`, `creaseDeg?`, `uv?: 'metres'`, `faceted?` | Wheels, boilers, domes, n-sided tanks |
|
|
24
|
+
| `prism` | `profile`, `depth`, `plane: 'xy'\|'xz'\|'yz'` | Sloped armour, gables, fins |
|
|
25
|
+
| `beam` | `from`, `to`, `width`, `segments?`, `up?`, `caps?` | Girders, rods, rails |
|
|
26
|
+
| `planar` | `points`, `doubleSided?` | Plates, decks, signs |
|
|
27
|
+
| `sweep` | `section` (polygon or `{ radius, segments }`), `path`, `scale?` (taper), `twist?`, `caps?` | Branches, pipes, handrails, cables. Parallel-transport frames; triangles = 2·S·(P−1) + S per cap |
|
|
28
|
+
| `loft` | `rings` (Vec3 rings), `capStart?`, `capEnd?`, `uv?` | Trunks, fuselages, bottles. `ringXY(centre, rx, ry, …)` builds a ring, with wobble |
|
|
29
|
+
| `heightfield` | `rect`, `cells`, `height(x, y)`, `color`, `skirt?` | Terrain, dioramas; with `terrainGrid` + `hydraulicErosion` / `thermalErosion` + `terrainColor` for real-looking mountains |
|
|
30
|
+
| `tiled` | `origin`, `u`, `v`, `strip`, `segments?` | Walls, roofs, decks at true texture scale |
|
|
31
|
+
| `cell` | `origin`, `u`, `v`, `cell`, `inset?` | Windows, doors, clocks, signs; `inset` adds reveals |
|
|
32
|
+
| `lattice` | `from`, `to`, `depth`, `pattern: warren\|pratt\|howe\|x`, `bays`, `chord`, `web`, `section` | Bridges, towers, cranes, gantries |
|
|
33
|
+
| `text` | `str`, `origin`, `along`, `up`, `height`, `font`, `depth?`, `align?`, `maxWidth?`, `surface?` | Name boards, wagon lettering, hull markings (5×7 font, Latin + Cyrillic) |
|
|
34
|
+
| `sdf` | `field(p)`, `bounds`, `resolution`, `mesher?`, `symmetry?` | Any signed-distance shape; `sd.*` combinators, `valueNoise` |
|
|
35
|
+
| `sculpt` | `edits` (shape, op, blend, color, groups), `resolution`, `mirror?` | Busts, rocks, asteroids: Dreams-style edit lists |
|
|
36
|
+
|
|
37
|
+
Noise for any primitive: `gradientNoise2`, `valueNoise2`, `fbm`, `billow`, `ridged`, `warp`, `terrace` — all seeded from an `Rng`.
|
|
38
|
+
|
|
39
|
+
## Why TypeScript, not JSON
|
|
40
|
+
|
|
41
|
+
| Need | JSON recipe | TS recipe |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| 12 windows per floor, 9 floors | Spell out 108 entries, or invent a repeat syntax | `for` loop |
|
|
44
|
+
| Reuse a wheel across 8 vehicles | Invent `$ref` / templating | Import a function |
|
|
45
|
+
| Vary 10 houses on a street | Invent a variant system | `ctx.use(house, { seed })` |
|
|
46
|
+
| Catch `colr: …` before build | Schema validator, if anyone wrote one | `tsc` red squiggle |
|
|
47
|
+
| Agent fluency | Must learn your schema | Already writes TS all day |
|
|
48
|
+
|
|
49
|
+
Full reasoning and rejected options: [ADR 002](../decisions/002-typescript-not-json.md).
|
|
50
|
+
|
|
51
|
+
## Anatomy
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { box, cylinder, defineAsset, hex } from '@developerz.ai/ts23d';
|
|
55
|
+
|
|
56
|
+
export const lamp_post = defineAsset({
|
|
57
|
+
name: 'lamp_post', // snake_case; becomes the GLB name and root node
|
|
58
|
+
kind: 'prop', // free string; kinds are libraries, not a closed enum
|
|
59
|
+
description: 'cast-iron street lamp',
|
|
60
|
+
sources: ['https://example.org/lamp-drawing.pdf'],
|
|
61
|
+
real: { length: 0.4, width: 0.4, height: 3.5 }, // proportions gate: LOD0 within ±5%
|
|
62
|
+
tags: ['street', 'victorian'],
|
|
63
|
+
budget: { triangles: [800, 300, 60] }, // per LOD; QA fails above
|
|
64
|
+
seed: 1, // default 1
|
|
65
|
+
build(ctx) {
|
|
66
|
+
const iron = hex('#2b2b2e');
|
|
67
|
+
ctx.part('base', box({ size: [0.4, 0.4, 0.3], color: iron, strip: 'iron', omit: ['bottom'] }));
|
|
68
|
+
ctx.part('pole', cylinder({ radius: 0.06, length: 3.2, segments: [12, 8, 6][ctx.lod] ?? 6,
|
|
69
|
+
at: [0, 0, 1.9], color: iron }));
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
One module may export several assets; `t3d` builds every one. No default exports.
|
|
75
|
+
|
|
76
|
+
## `defineAsset` fields
|
|
77
|
+
|
|
78
|
+
| Field | Type | Required | Notes |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| `name` | `string` | yes | `[a-z][a-z0-9_]*`, else `T3D_RECIPE_INVALID` |
|
|
81
|
+
| `kind` | `string` | yes | Written to glTF `extras.kind` |
|
|
82
|
+
| `budget.triangles` | `[lod0, lod1, lod2]` | yes | Enforced by the `triangle-budget` gate |
|
|
83
|
+
| `build` | `(ctx) => void` | yes | Synchronous |
|
|
84
|
+
| `real` | `{ length, width, height, tolerance?, measures? }` | no | Real overall dimensions, metres (X, Y, Z). The `proportions` gate holds LOD0 to them, default ±5% |
|
|
85
|
+
| `real.measures` | `Measure[]` | no | Spec-sheet dimensions checked on LOD0 by the `measures` gate, default ±2%. See below |
|
|
86
|
+
| `sources` | `string[]` | with `real` | Where the numbers came from: spec sheets, rosters, drawings, photos. The type says "required for `real`"; as of 2026-10 nothing enforces it |
|
|
87
|
+
| `checks` | `Check[]` | no | The recipe's own physical intent, run by QA at every LOD ([below](#checks-declared-intent)) |
|
|
88
|
+
| `description`, `tags` | `string`, `string[]` | no | Gallery / search metadata |
|
|
89
|
+
| `seed` | `number` | no | Default `1`; overridden by `compile(…, { seed })` or `ctx.use(…, { seed })` |
|
|
90
|
+
|
|
91
|
+
## The `ctx` API
|
|
92
|
+
|
|
93
|
+
| Member | Does |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `ctx.lod` | `0` hero · `1` mid · `2` far. Read it and drop detail; never decimate |
|
|
96
|
+
| `ctx.seed` | The seed this compile runs with |
|
|
97
|
+
| `ctx.rng` | Seeded PRNG: `next()`, `range(a,b)`, `int(a,b)`, `pick(xs)`, `fork(name)` |
|
|
98
|
+
| `ctx.part(name, mesh, opts?)` | Add a named part. `mesh` may be `null` (empty pivot) |
|
|
99
|
+
| `ctx.group(name, opts?)` | An empty pivot other parts parent to (`part(name, null, opts)`). Pivots and parts with `motion` survive export as nodes; static parts merge into them ([batching](03-ir.md#batching-parts--pivots)) |
|
|
100
|
+
| `ctx.use(asset, opts)` | Instance another asset at this LOD: the composition primitive |
|
|
101
|
+
| `ctx.emitter(name, opts)` | A particle source as a named node ([below](#emitters)) |
|
|
102
|
+
| `ctx.socket(name, opts)` | An attachment point as a named node ([below](#sockets)) |
|
|
103
|
+
|
|
104
|
+
`PartOptions`:
|
|
105
|
+
|
|
106
|
+
| Option | Meaning |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `at` | Pivot in the parent's space. The mesh is authored relative to it |
|
|
109
|
+
| `parent` | Name of an existing part/group. Parents first, else `T3D_PART_UNKNOWN` |
|
|
110
|
+
| `yaw` | Rotation about local Z, radians |
|
|
111
|
+
| `motion` | `{ kind: 'spin', axis, rps }` · `{ kind: 'slide', offset, hz }` · `{ kind: 'sway', axis, degrees, hz }` (turret scan, gun elevation, tree wind, a door). Baked to a 2 s `run` clip only when exporting with animations ([03](03-ir.md#glb-layout)) |
|
|
112
|
+
| `surface` | `'opaque'` (default) · `'cutout'` · `'glass'` · `'translucent'` ([below](#surfaces)) |
|
|
113
|
+
| `clearOf` | Part names this moving part may pass through by design (an axle in its bearing, a crosshead in its guide). Exempts them from the `clashes` gate |
|
|
114
|
+
|
|
115
|
+
Rules the compiler enforces:
|
|
116
|
+
|
|
117
|
+
- Part names are unique per asset (`T3D_PART_DUPLICATE`). Suffix loop indices: `wheel_0`, `wheel_1`.
|
|
118
|
+
- A part can't have both `yaw` and `motion` (`T3D_RECIPE_INVALID`). Yaw the parent group, move the child.
|
|
119
|
+
- Names are snake_case (QA gate `part-names`). Names are the contract with the engine: `axle_*`, `door_*`, `rotor_*`.
|
|
120
|
+
|
|
121
|
+
## Fidelity: `real.measures`
|
|
122
|
+
|
|
123
|
+
**Overall size is not enough; players notice the driver diameter.** Each `Measure` names one number from a source and how to measure it on the model:
|
|
124
|
+
|
|
125
|
+
| Field | Meaning |
|
|
126
|
+
|---|---|
|
|
127
|
+
| `label` | Human name: `'driver diameter'`, `'gauge'` |
|
|
128
|
+
| `value` | Metres, from the source |
|
|
129
|
+
| `axis` | `'x'` · `'y'` · `'z'` |
|
|
130
|
+
| `parts` | Extent of these parts' union along `axis`. Prefix match: `'axle_driver_0'` includes `axle_driver_0_wheel_l` |
|
|
131
|
+
| `between` | Or: centre-to-centre distance of exactly two parts along `axis` (gauge, wheelbase) |
|
|
132
|
+
| `tolerance` | Relative, default 0.02 |
|
|
133
|
+
| `source` | Which of `sources` the number came from |
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
real: {
|
|
137
|
+
length: 6.68, width: 3.0, height: 2.45,
|
|
138
|
+
measures: [
|
|
139
|
+
{ label: 'turret width', value: 2.0, axis: 'y', parts: ['turret_body'], source: 'hull drawing' },
|
|
140
|
+
{ label: 'road wheel diameter', value: 0.83, axis: 'z', parts: ['road_wheel_l_0'] },
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A failing measure means the model is wrong. **Rebuild the part from the number; never edit the spec to pass.** Gate details: [06-qa](06-qa.md).
|
|
146
|
+
|
|
147
|
+
## Detail: build once, place N times
|
|
148
|
+
|
|
149
|
+
`place(mesh, { at, yaw, pitch, roll, scale })` rigidly transforms a mesh; `merge` joins meshes into one part. Author a rivet, hatch or bracket once in local space, then place it along a seam:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { cylinder, merge, place } from '@developerz.ai/ts23d';
|
|
153
|
+
|
|
154
|
+
const rivet = cylinder({ radius: 0.012, length: 0.01, segments: 6, axis: 'x', color: iron });
|
|
155
|
+
const rivets = merge(Array.from({ length: 24 }, (_, i) => place(rivet, { at: [3.0, -1.2 + i * 0.1, 1.1] })));
|
|
156
|
+
if (ctx.lod === 0) ctx.part('glacis_rivets', rivets);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Lower level: `affine(placement)`, `frame(u, v, w, at)` (an explicit basis), `applyAffine(mesh, affine)`.
|
|
160
|
+
|
|
161
|
+
## Families: one build, many assets
|
|
162
|
+
|
|
163
|
+
**A variant is a name plus the parameters that differ.** T-34 1941 vs 1943, a 4-4-0 vs a 2-6-0, oak vs birch: one key change, not a copied file.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { defineFamily } from '@developerz.ai/ts23d';
|
|
167
|
+
|
|
168
|
+
const t34 = defineFamily({
|
|
169
|
+
kind: 'vehicle',
|
|
170
|
+
budget: { triangles: [40000, 10000, 1500] },
|
|
171
|
+
defaults: { turret: '1941' as '1941' | '1943', hullLength: 6.68 },
|
|
172
|
+
build(ctx, p) { /* hull from p.hullLength; turret shape from p.turret */ },
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const t34_1941 = t34.variant({ name: 't34_1941' });
|
|
176
|
+
export const t34_1943 = t34.variant({ name: 't34_1943', params: { turret: '1943' } });
|
|
177
|
+
|
|
178
|
+
// Derive: add params, wrap or replace the parent's detail.
|
|
179
|
+
const t34_85 = t34.extend({
|
|
180
|
+
defaults: { gun: 85 },
|
|
181
|
+
build(ctx, p, base) { base(); /* swap the turret, add the 85 mm gun */ },
|
|
182
|
+
});
|
|
183
|
+
export const t34_85_1944 = t34_85.variant({ name: 't34_85_1944', tags: ['ww2'] });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| API | Does |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `defineFamily({ defaults, build(ctx, params), ...assetFields })` | Typed defaults; every `defineAsset` field except `name` / `build` |
|
|
189
|
+
| `family.variant({ name, params?, ...overrides })` | One `Asset`: defaults merged with `params`; may override `real`, `budget`, `tags`, … |
|
|
190
|
+
| `family.extend({ defaults, build?(ctx, p, base) })` | A new family with more params. Call `base()` to run the parent's build, or don't, to replace it |
|
|
191
|
+
| `family.defaults` | Read-only defaults, for docs and UIs |
|
|
192
|
+
|
|
193
|
+
## Emitters
|
|
194
|
+
|
|
195
|
+
`ctx.emitter(name, opts)` adds an empty named node carrying `extras.ts23d.emitter`. It moves with its parent; the engine binds its own particle system to it. The gallery renders them.
|
|
196
|
+
|
|
197
|
+
| Option | Meaning |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `kind` | `'smoke'` · `'steam'` · `'dust'` · `'muzzle'` · `'fire'` · `'sparks'` |
|
|
200
|
+
| `at`, `parent` | Position in the parent's space; parent it to a turret or stack so it follows |
|
|
201
|
+
| `dir` | Emission direction, default up (+Z) |
|
|
202
|
+
| `rate`, `life` | Particles per second; seconds each lives |
|
|
203
|
+
| `size` | `[start, end]` metres |
|
|
204
|
+
| `color` | Linear RGB start colour; engines fade to transparent |
|
|
205
|
+
| `speed`, `spreadDeg` | Initial speed along `dir`; cone spread |
|
|
206
|
+
| `when` | `'always'` (chimney) · `'moving'` (track dust) · `'event'` (muzzle flash, fired by engine code) |
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
ctx.emitter('exhaust_l', { kind: 'smoke', at: [-3.2, 0.6, 1.2], dir: [-1, 0, 0.5],
|
|
210
|
+
rate: 20, life: 2, size: [0.2, 1.2], when: 'moving' });
|
|
211
|
+
ctx.emitter('muzzle', { kind: 'muzzle', parent: 'gun', at: [2.6, 0, 0], dir: [1, 0, 0],
|
|
212
|
+
rate: 200, life: 0.1, size: [0.3, 0.8], when: 'event' });
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Sockets
|
|
216
|
+
|
|
217
|
+
`ctx.socket(name, { kind, at?, parent?, dir?, data? })`: an empty named node with `extras.ts23d.socket`, for engine code to attach to.
|
|
218
|
+
|
|
219
|
+
| `kind` | Example |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `seat` | Driver, gunner, passenger |
|
|
222
|
+
| `mount` | Weapon hardpoint, `data: { caliber: 88 }` |
|
|
223
|
+
| `camera` | Gunner sight, chase cam, `data: { fov: 60 }` |
|
|
224
|
+
| `audio` | Engine, `data: { clip: 'engine_idle' }` |
|
|
225
|
+
| `attach` · `spawn` · `interact` · `light` | Tow hook, spawn point, door handle, headlamp |
|
|
226
|
+
|
|
227
|
+
`dir` is the facing in the parent's space (default +X). `data` is free-form `string | number | boolean` values.
|
|
228
|
+
|
|
229
|
+
## Surfaces
|
|
230
|
+
|
|
231
|
+
**One texture set, at most four materials.** `surface` on a part picks the shading model; tint stays in vertex colour.
|
|
232
|
+
|
|
233
|
+
| Surface | For | glTF |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `opaque` (default) | Almost everything | `ts23d_trim` |
|
|
236
|
+
| `cutout` | Leaves, fences, grilles | Alpha-tested, double-sided |
|
|
237
|
+
| `glass` | Windows, lenses, clear bottles | `KHR_materials_transmission` + `_volume` + `_ior` (1.5) |
|
|
238
|
+
| `translucent` | Plastic bottles, liquids, paper | Transmission, tinted; engines without it fall back to alpha blend |
|
|
239
|
+
|
|
240
|
+
Batching splits by pivot and surface, so a shampoo bottle (opaque cap, translucent body) is 2 draw calls.
|
|
241
|
+
|
|
242
|
+
## Checks: declared intent
|
|
243
|
+
|
|
244
|
+
**Static intersections need declared intent.** Modellers sink bolts into plates on purpose, so QA can't fail every overlap. The recipe says what must hold, and the `check` gate enforces it at every LOD.
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { clear, noOverlap, symmetric, touches } from '@developerz.ai/ts23d';
|
|
248
|
+
|
|
249
|
+
checks: [
|
|
250
|
+
clear('track_*', 'hull_*', 0.02), // the track never hits the frame; 2 cm running gap
|
|
251
|
+
noOverlap('road_wheel_l_*'), // interleaved wheels don't intersect
|
|
252
|
+
touches('turret_body', 'hull_top'), // the turret sits on its ring
|
|
253
|
+
symmetric('_l', '_r', 'y'), // left matches right across y = 0
|
|
254
|
+
],
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| Builder | Fails when |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `clear(a, b, minGap?)` | Anything in `a` sinks more than 1 cm into `b`'s closed solid (ray-parity point-in-solid; open planar sheets are never solids), or is closer than `minGap` |
|
|
260
|
+
| `noOverlap(glob)` | Any two parts matching `glob` interpenetrate |
|
|
261
|
+
| `touches(a, b, eps = 0.02)` | `a`'s surface is further than `eps` from `b`'s |
|
|
262
|
+
| `symmetric(left, right, axis = 'y')` | A part containing `left` has no `right` mirror, or isn't mirrored across `axis = 0` (2 cm) |
|
|
263
|
+
|
|
264
|
+
Selectors are globs (`*`, `?`) over part names. A custom check is any `(asset: CompiledAsset) => CheckFinding[]`, where `CheckFinding = { message, fix, part? }`.
|
|
265
|
+
|
|
266
|
+
## Composition: `ctx.use`
|
|
267
|
+
|
|
268
|
+
A scene is an asset that uses other assets. Each instance is compiled at the parent's LOD, with its own seed, under a prefixed group:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
import { box, defineAsset, facade, palettes } from '@developerz.ai/ts23d';
|
|
272
|
+
|
|
273
|
+
const { limestone, glass } = palettes.artDeco;
|
|
274
|
+
|
|
275
|
+
export const shop_house = defineAsset({
|
|
276
|
+
name: 'shop_house',
|
|
277
|
+
kind: 'building',
|
|
278
|
+
budget: { triangles: [4000, 1500, 400] },
|
|
279
|
+
build(ctx) {
|
|
280
|
+
const floors = ctx.rng.int(2, 4); // varies per seed
|
|
281
|
+
ctx.part('mass', box({ size: [8, 10, floors * 3.2], color: limestone,
|
|
282
|
+
strip: 'stone', omit: ['bottom'] }));
|
|
283
|
+
if (ctx.lod < 2)
|
|
284
|
+
facade(ctx, { prefix: 'win', at: [0, 0, 0], footprint: [8, 10], floors, floorHeight: 3.2,
|
|
285
|
+
grammar: 'pier:0.6 | {win:1.4 | pier:0.6}* | pier:~',
|
|
286
|
+
ground: 'pier:0.6 | shop:4 | pier:~ | door:1.2 | pier:0.6',
|
|
287
|
+
sides: ['front'], glass });
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export const street = defineAsset({
|
|
292
|
+
name: 'street',
|
|
293
|
+
kind: 'scene',
|
|
294
|
+
budget: { triangles: [40000, 15000, 4000] },
|
|
295
|
+
build(ctx) {
|
|
296
|
+
ctx.part('road', box({ size: [12, 60, 0.05], at: [12, 0, 0],
|
|
297
|
+
color: palettes.modernGlass.concrete, omit: ['bottom'] }));
|
|
298
|
+
for (let i = 0; i < 6; i++)
|
|
299
|
+
ctx.use(shop_house, { prefix: `house_${i}`, at: [0, -25 + i * 10, 0], seed: 100 + i });
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Result: six different houses (2–4 floors each) as `house_0` … `house_5`, with parts `house_3_mass`, `house_3_win_front_1_2` and so on. LOD2 drops every window on every house at once.
|
|
305
|
+
|
|
306
|
+
`UseOptions`: `prefix` (required), `at`, `yaw`, `parent`, `seed` (defaults to the parent's seed).
|
|
307
|
+
|
|
308
|
+
## Determinism rules
|
|
309
|
+
|
|
310
|
+
**Same source + seed + LOD → same bytes.** Breaking any rule below breaks that.
|
|
311
|
+
|
|
312
|
+
| Rule | Why | Enforced by (in this repo) |
|
|
313
|
+
|---|---|---|
|
|
314
|
+
| Never `Math.random()`. Use `ctx.rng` or `createRng(seed)` | Ambient entropy | `scripts/determinism.ts` (static ban in `src/` + `recipes/`); test preload makes `Math.random` throw |
|
|
315
|
+
| No `Date.now()`, `new Date()`, `performance.now()` in library or recipe code | Wall clock | `scripts/determinism.ts` |
|
|
316
|
+
| No `toLocale*String()` | Locale changes bytes; format with `toFixed` | `scripts/determinism.ts` |
|
|
317
|
+
| `ctx.rng.fork('name')` per independent feature | Adding a draw in one part must not shift another | Convention |
|
|
318
|
+
| No env vars, file or network reads in `build` | Ambient state | Convention |
|
|
319
|
+
| `build` is synchronous | No ordering races | The type signature |
|
|
320
|
+
| Iterate arrays, not object key order you didn't create | Stable part order → stable node order → stable bytes | Convention |
|
|
321
|
+
| Generated meshes (neural, imported) go through a content-hash cache | A GPU result is not reproducible; the cached file is | Planned, M5 ([08](08-neural-adapters.md)) |
|
|
322
|
+
|
|
323
|
+
`scripts/recipes.ts` compiles every recipe twice per LOD and fails if the GLB hashes differ. Both run in `bun run verify`. In your own project, copy the rules; the guards ship with this repo, not the package. Cross-machine hash comparison: planned ([06-qa](06-qa.md)).
|
|
324
|
+
|
|
325
|
+
## Library entry points
|
|
326
|
+
|
|
327
|
+
One import gives everything; subpaths load one layer ([ADR 006](../decisions/006-single-package-subpath-exports.md)):
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { defineAsset, box } from '@developerz.ai/ts23d';
|
|
331
|
+
import { split } from '@developerz.ai/ts23d/grammar';
|
|
332
|
+
import { contactSheet } from '@developerz.ai/ts23d/render';
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Programmatic build, without the CLI:
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { compile, contactSheet, runQa, toGlb } from '@developerz.ai/ts23d';
|
|
339
|
+
const lod0 = compile(lamp_post, { lod: 0 });
|
|
340
|
+
const findings = runQa(lod0); // [] = PASS
|
|
341
|
+
const glb = toGlb(lod0); // Uint8Array
|
|
342
|
+
const png = contactSheet(lod0, { far: compile(lamp_post, { lod: 2 }) });
|
|
343
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 03 — The IR
|
|
2
|
+
|
|
3
|
+
**One intermediate representation crosses every stage: an indexed triangle `Mesh`.** Primitives emit it, kinds compose it, QA, the rasteriser and the GLB writer consume it. Nothing else crosses a module boundary. Source: `src/core/mesh.ts`, `src/core/asset.ts`.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
| Convention | Value | Why |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Units | Metres | Real dimensions from reference sheets drop straight in ([ADR 004](../decisions/004-z-up-metres.md)) |
|
|
10
|
+
| Up | +Z | Architecture, vehicles and CAD think in plan (XY) + height |
|
|
11
|
+
| Front | +X | One rule for every asset; the leading end of a loco, the gun of a tank |
|
|
12
|
+
| Left / right | +Y / −Y | Follows from Z-up, X-front, right-handed |
|
|
13
|
+
| Ground | z = 0 | Primitives put their base at `at`; things stand on the ground |
|
|
14
|
+
| glTF output | Y-up, front +Z | Root rotation `[-0.5, -0.5, -0.5, 0.5]`: authoring +X front → glTF +Z, +Z up → +Y, +Y left → +X ([ADR 009](../decisions/009-gltf-front-axis.md)). Part numbers stay Z-up and readable |
|
|
15
|
+
| Rotation | Parts: yaw about local Z. Meshes: any rotation via `place` / `applyAffine` | Part yaw covers turrets, doors, street layout; mesh-level `place` covers sloped plates, angled rods |
|
|
16
|
+
| Colour | Linear RGB per vertex | glTF `COLOR_0` space; `hex()` converts from sRGB |
|
|
17
|
+
|
|
18
|
+
## `Mesh`
|
|
19
|
+
|
|
20
|
+
| Field | Type | Per |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `positions` | `Float32Array` | vertex × 3 |
|
|
23
|
+
| `normals` | `Float32Array` | vertex × 3 |
|
|
24
|
+
| `uvs` | `Float32Array` | vertex × 2, into the shared trim sheet ([04](04-materials.md)) |
|
|
25
|
+
| `colors` | `Float32Array` | vertex × 3, linear RGB |
|
|
26
|
+
| `indices` | `Uint32Array` | triangle × 3, counter-clockwise from outside |
|
|
27
|
+
|
|
28
|
+
Helpers: `MeshBuilder` (`vertex`, `tri`, `quad`, `build`), `merge`, `translate`, `boundsOf`, `triangleCount`. Transforms (`src/core/transform.ts`): `place(mesh, { at, yaw, pitch, roll, scale })`, `affine`, `frame`, `applyAffine`. Rotation order Z (yaw) · Y (pitch) · X (roll); normals are rotated and renormalised, so use uniform scale.
|
|
29
|
+
|
|
30
|
+
## Parts
|
|
31
|
+
|
|
32
|
+
A compiled asset is a flat, ordered list of parts; the hierarchy is in `parent` pointers.
|
|
33
|
+
|
|
34
|
+
| Field | Meaning |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `name` | Unique, snake_case. A pivot's name becomes its glTF node and mesh name; static parts merge into their pivot on export ([batching](#batching-parts--pivots)) |
|
|
37
|
+
| `mesh` | `Mesh` or `null` (a group / pivot) |
|
|
38
|
+
| `at` | Translation in the parent's space = the pivot |
|
|
39
|
+
| `parent` | Part name or `null` (child of the root node) |
|
|
40
|
+
| `yaw` | Rotation about local Z |
|
|
41
|
+
| `motion` | `spin` / `slide` / `sway` / `null`; baked to a looping clip named `run` only with `animations: true` |
|
|
42
|
+
| `emitter` | Particle source config, exported as `extras.ts23d.emitter` on an empty node |
|
|
43
|
+
| `socket` | Attachment point config, exported as `extras.ts23d.socket` on an empty node |
|
|
44
|
+
| `surface` | `opaque` · `cutout` · `glass` · `translucent`; picks one of ≤ 4 shared materials |
|
|
45
|
+
|
|
46
|
+
**Pivots go where the part hinges.** A turret group sits on the ring; the gun group sits at the trunnion; the barrel is offset forward from it. Rotate `gun` in the engine and the barrel elevates correctly. The `t34` recipe shows exactly this.
|
|
47
|
+
|
|
48
|
+
`worldTransforms(parts)` chains translation + yaw down the hierarchy; `toWorld(t, p)` maps a local point. QA and the renderer read world space from there.
|
|
49
|
+
|
|
50
|
+
## LODs
|
|
51
|
+
|
|
52
|
+
| LOD | Use | What a recipe typically does |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 0 | Hero / close | Full detail, high segment counts |
|
|
55
|
+
| 1 | Mid distance | Fewer segments, small parts kept |
|
|
56
|
+
| 2 | Far | Masses only: drop hatches, windows, rivets; 6-segment cylinders |
|
|
57
|
+
|
|
58
|
+
**LODs are re-runs, not decimation.** `compile(asset, { lod })` calls `build` again with `ctx.lod` set. The far LOD keeps named parts, pivots and silhouette because the author chose what to drop. Each LOD has its own triangle budget.
|
|
59
|
+
|
|
60
|
+
## Batching: parts → pivots
|
|
61
|
+
|
|
62
|
+
**`toGlb` merges every static part into its nearest pivot ancestor by default (`mergeToPivots`, `src/export/merge-pivots.ts`).** A pivot is anything with no mesh (a `ctx.group`, `ctx.emitter`, `ctx.socket`) or a part with `motion`. Groups, emitters and sockets always survive batching as named nodes. Batching also splits by surface: opaque geometry merges into the pivot's own mesh; each other surface in use becomes one child `<pivot>_<surface>` (e.g. `body_glass`), one draw call each. Static top-level geometry becomes one `body` node.
|
|
63
|
+
|
|
64
|
+
| Asset | Draw calls after merge |
|
|
65
|
+
|---|---|
|
|
66
|
+
| A static prop or building | 1 (`body`) |
|
|
67
|
+
| A shampoo bottle: opaque cap, translucent body | 2 |
|
|
68
|
+
| A tank | `body` + `turret` + `gun` + one per road wheel (each spins independently) |
|
|
69
|
+
|
|
70
|
+
So: **draw calls = movable pieces with geometry.** Names the engine binds to (`turret`, `gun`, `road_wheel_l_0`) survive; static detail names (`turret_hatch`) don't. QA and `real.measures` run on the unmerged compile, so they still see every part. Pass `toGlb(asset, { keepParts: true })` to keep one node per part (debugging, editors).
|
|
71
|
+
|
|
72
|
+
Planned (M3): bake motion into vertex attributes (pivot + spin axis per vertex) so a shader animates all wheels in one draw call.
|
|
73
|
+
|
|
74
|
+
## GLB layout
|
|
75
|
+
|
|
76
|
+
Validity: every exported shape is checked by the Khronos glTF-Validator in the test suite (`src/export/validator.test.ts`): 0 errors. Production rules and remaining gaps: [glb-production.md](../architecture/glb-production.md).
|
|
77
|
+
|
|
78
|
+
| glTF element | Content |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `scene[0]` | One root node named after the asset, rotated Z-up/+X-front → Y-up/+Z-front |
|
|
81
|
+
| `nodes` | One per **pivot**, `translation` = `at`, `rotation` = yaw quaternion, plus `body` for static top-level geometry. `{ keepParts: true }` keeps one node per part |
|
|
82
|
+
| `meshes` | One per pivot (and surface) with geometry, static parts merged in. `POSITION`, `NORMAL`, `TEXCOORD_0` (float32); `COLOR_0` normalised u16 RGBA; indices u16 when ≤ 65,535 vertices, else u32 |
|
|
83
|
+
| `materials` | At most 4, one per surface in use, all over the same trim sheet: `opaque` (`ts23d_trim`), `cutout` (alpha-test, double-sided), `glass` and `translucent` (`KHR_materials_transmission` + `_volume` + `_ior`; alpha-blend fallback) |
|
|
84
|
+
| `animations` | Two clips: `rest` first (static, so viewers that autoplay the first clip open paused), then `run`: 4 s, every motion rounded to whole cycles (`cyclesOf`) so the loop is seamless, spins ≥ 12 keys per turn, linear, time accessors shared. `toGlb()` exports it only with `{ animations: true }` (glTF has no autoplay flag; games drive pivots from code). `t3d build` bakes it by default so viewers show play controls; `--no-animations` skips it |
|
|
85
|
+
| `extras` | Root: `{ kind, lod, units: 'metres', up }`. Nodes: `ts23d.emitter`, `ts23d.socket` |
|
|
86
|
+
|
|
87
|
+
Textures: the painted trim sheet, bound to every surface material (`--sheet external` by default: shared PNGs next to the GLBs). With `--sheet none` vertex colour alone carries the look.
|
|
88
|
+
|
|
89
|
+
Shipped since: the painted trim sheet (albedo, normal, ORM), embedded or as shared external PNGs, `TANGENT` only when a sheet is bound ([04](04-materials.md)).
|
|
90
|
+
|
|
91
|
+
Planned for the GLB, M2:
|
|
92
|
+
|
|
93
|
+
| Addition | Effect |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `optimize(mesh)` pass: vertex weld + vertex-cache reorder, reports ACMR | Fewer vertices, better GPU post-transform cache use |
|
|
96
|
+
| Opt-in `web` profile: `KHR_mesh_quantization` + `EXT_meshopt_compression` | Smaller files for three.js / Babylon / glTFast. Not default: Godot refuses required extensions, so the default stays core glTF |
|
|
97
|
+
| LOD switch-distance hints in node extras | Engines pick LODs without guessing |
|