@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.
- 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 +90 -0
- package/CLAUDE.md +85 -0
- package/LICENSE +21 -0
- package/README.md +385 -2
- package/dist/chunk-6b05mnmx.js +19 -0
- package/dist/chunk-6b05mnmx.js.map +11 -0
- package/dist/chunk-7rtfjh9f.js +45 -0
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/chunk-8j4c28jt.js +4085 -0
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/chunk-d1fv6hrm.js +2532 -0
- package/dist/chunk-d1fv6hrm.js.map +43 -0
- package/dist/chunk-dbq4qf52.js +902 -0
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/chunk-drna2rny.js +836 -0
- package/dist/chunk-drna2rny.js.map +17 -0
- package/dist/chunk-dxvypr7y.js +89 -0
- package/dist/chunk-dxvypr7y.js.map +11 -0
- package/dist/chunk-f6yme003.js +1589 -0
- package/dist/chunk-f6yme003.js.map +30 -0
- package/dist/chunk-hg7pf34j.js +465 -0
- package/dist/chunk-hg7pf34j.js.map +19 -0
- package/dist/chunk-jq4e17tj.js +1031 -0
- package/dist/chunk-jq4e17tj.js.map +21 -0
- package/dist/chunk-y63c6zcv.js +2668 -0
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +30 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +348 -0
- package/dist/cli/bin.js.map +15 -0
- package/dist/cli/load.d.ts +2 -0
- package/dist/cli/manifest.d.ts +53 -0
- package/dist/cli/render-cmds.d.ts +12 -0
- package/dist/cli/run.d.ts +1 -0
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/asset.d.ts +10 -0
- package/dist/core/definition.d.ts +155 -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/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +8 -0
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +5 -0
- package/dist/export/index.js +40 -0
- package/dist/export/index.js.map +9 -0
- package/dist/export/lights.d.ts +8 -0
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/materials.d.ts +23 -0
- package/dist/export/merge-pivots.d.ts +6 -0
- package/dist/export/metadata.d.ts +21 -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 +457 -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 +5 -0
- package/dist/kinds/head-beard.d.ts +6 -0
- package/dist/kinds/head-bust.d.ts +5 -0
- package/dist/kinds/head-extras.d.ts +15 -0
- package/dist/kinds/head-face.d.ts +8 -0
- package/dist/kinds/head-garb.d.ts +4 -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 +31 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +116 -0
- package/dist/kinds/head-presets.d.ts +19 -0
- package/dist/kinds/head-rim.d.ts +10 -0
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -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/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +4 -0
- package/dist/material/index.d.ts +9 -0
- package/dist/material/index.js +70 -0
- package/dist/material/index.js.map +9 -0
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/bond.d.ts +3 -0
- package/dist/material/paint/canvas.d.ts +39 -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 +4 -0
- package/dist/material/paint/fabric.d.ts +4 -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/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/masks.d.ts +3 -0
- package/dist/material/paint/metal.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 +65 -0
- package/dist/material/paint/validate.d.ts +3 -0
- package/dist/material/palette.d.ts +14 -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/slots.d.ts +5 -0
- package/dist/material/texture-source.d.ts +34 -0
- package/dist/material/trim-sheet.d.ts +38 -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 +27 -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 +56 -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 +117 -0
- package/docs/architecture/materials.md +151 -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/idea/10-sculpting.md +166 -0
- package/docs/idea/11-materials-authoring.md +132 -0
- package/docs/idea/12-blender-bridge.md +136 -0
- package/docs/idea/13-asset-pipeline-research.md +89 -0
- package/docs/landscape.md +49 -0
- package/docs/plans/roadmap.md +139 -0
- package/examples/build-with-bun.ts +41 -0
- package/llms.txt +47 -0
- package/package.json +100 -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 +53 -0
- package/recipes/boxcar/boxcar.ts +1000 -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 +75 -0
- package/recipes/goblin_head/goblin_head.ts +38 -0
- package/recipes/human_head/README.md +68 -0
- package/recipes/human_head/human_head.ts +38 -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 +189 -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 +78 -0
- package/recipes/orc_head/orc_head.ts +41 -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 +125 -0
- package/src/cli/bin.ts +7 -0
- package/src/cli/load.ts +17 -0
- package/src/cli/manifest.ts +95 -0
- package/src/cli/render-cmds.ts +66 -0
- package/src/cli/run.ts +192 -0
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +129 -0
- package/src/core/definition.ts +156 -0
- package/src/core/errors.ts +50 -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/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +187 -0
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +34 -0
- package/src/export/lights.ts +49 -0
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/materials.ts +126 -0
- package/src/export/merge-pivots.ts +146 -0
- package/src/export/metadata.ts +62 -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 +181 -0
- package/src/kinds/head-beard.ts +133 -0
- package/src/kinds/head-bust.ts +184 -0
- package/src/kinds/head-extras.ts +150 -0
- package/src/kinds/head-face.ts +242 -0
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-geom.ts +31 -0
- package/src/kinds/head-hair.ts +193 -0
- package/src/kinds/head-layout.ts +63 -0
- package/src/kinds/head-mesh.ts +195 -0
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +162 -0
- package/src/kinds/head-presets.ts +155 -0
- package/src/kinds/head-rim.ts +128 -0
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +176 -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/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +63 -0
- package/src/material/index.ts +60 -0
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/bond.ts +60 -0
- package/src/material/paint/canvas.ts +101 -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 +106 -0
- package/src/material/paint/default-sheet.ts +73 -0
- package/src/material/paint/fabric.ts +63 -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/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +46 -0
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/noise.ts +52 -0
- package/src/material/paint/normal.ts +52 -0
- package/src/material/paint/orm.ts +108 -0
- package/src/material/paint/planks.ts +101 -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 +48 -0
- package/src/material/paint/surface.ts +60 -0
- package/src/material/paint/types.ts +94 -0
- package/src/material/paint/validate.ts +74 -0
- package/src/material/palette.ts +46 -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/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +166 -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 +121 -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 +204 -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,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 |
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# 04 — Materials
|
|
2
|
+
|
|
3
|
+
**Every asset shares one texture set: a 1024² trim sheet, tinted by vertex colour, under at most four surface materials (opaque, cutout, glass, translucent).** One texture set for everything (with static geometry batched per pivot, draw calls = the parts the engine moves), equal texel density across assets, and a palette swap is a recipe edit, never a new texture. Source: `src/material/`. Decisions: [ADR 003](../decisions/003-shared-trim-sheet.md), superseded by [ADR 008](../decisions/008-surface-materials.md) (surfaces).
|
|
4
|
+
|
|
5
|
+
## Status (2026-10-25)
|
|
6
|
+
|
|
7
|
+
| Piece | Status |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Trim-sheet layout: 8 strips, 6 decal cells, 11 foliage cells | Shipped |
|
|
10
|
+
| UVs authored against the layout by every primitive | Shipped |
|
|
11
|
+
| Palettes + `hex()` sRGB → linear | Shipped |
|
|
12
|
+
| `grime` pass (vertex-colour wear) | Shipped |
|
|
13
|
+
| ≤ 4 shared materials per GLB (opaque, cutout, glass, translucent) over one sheet | Shipped |
|
|
14
|
+
| `paintTrimSheet`: procedural albedo / normal / ORM | Shipped |
|
|
15
|
+
| Sheet in the GLB: embedded, or shared external PNGs | Shipped |
|
|
16
|
+
| `threeMaterialSource` (TSL) and `godotShaderSource` (.gdshader): wind, window glow, wear | Shipped |
|
|
17
|
+
| KTX2 (`KHR_texture_basisu`) | Planned |
|
|
18
|
+
| `kubeezImages`: image source for decals / texture sources | Planned, M5 |
|
|
19
|
+
|
|
20
|
+
## The trim sheet
|
|
21
|
+
|
|
22
|
+
| Region | Contents | Mapping |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Strips: 8 rows, each 1/16 of V, full U | `plain` `brick` `stone` `slate` `iron` `wood` `glass` `concrete` | Tile along U; one strip rect = 2 m of surface |
|
|
25
|
+
| Cells: 1/8 × 1/8 squares from V = 0.5 | `window_sash` `door_panel` `clock` `smokebox` `sign` `porthole` | Fixed decals; one cell = 1 m |
|
|
26
|
+
|
|
27
|
+
`stripRect(id)` / `cellRect(id)` return `{ u0, v0, u1, v1, metres }`. `metres` is the point: a primitive maps UVs by world size, so a 6 m wall and a 0.4 m crate show bricks at the same size.
|
|
28
|
+
|
|
29
|
+
A primitive takes `strip?: StripId` (default `plain`). Cells are used by the planned `cell()` primitive (M1).
|
|
30
|
+
|
|
31
|
+
## Colour: palettes and vertex colour
|
|
32
|
+
|
|
33
|
+
The sheet is greyscale detail (mortar lines, grain, rivets); hue comes from `COLOR_0`, multiplied over it.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { definePalette, palettes } from '@developerz.ai/ts23d';
|
|
37
|
+
|
|
38
|
+
const livery = { ...palettes.victorianRail, paint: palettes.victorianRail.red }; // repaint = spread
|
|
39
|
+
const custom = definePalette({ hull: '#203040', deck: '#8a6f4e' }); // sRGB hex in, linear out
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| Palette | Keys |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `victorianRail` | `paint` `trim` `iron` `brass` `red` |
|
|
45
|
+
| `artDeco` | `limestone` `steel` `glass` `bronze` |
|
|
46
|
+
| `modernGlass` | `glass` `aluminium` `concrete` |
|
|
47
|
+
| `harbour` | `hull` `deck` `white` `red` |
|
|
48
|
+
|
|
49
|
+
Era/region palettes are data. Add one in `src/material/palette.ts` with the era in the key name.
|
|
50
|
+
|
|
51
|
+
## Wear: passes
|
|
52
|
+
|
|
53
|
+
`/passes` post-processes a finished mesh, so recipes stay about shape. Shipped: `grime(mesh, { ground?, groundStrength?, zones? })` darkens vertex colour below `ground` metres of the mesh's own z, not world z (mud, dust) and inside spherical `zones` (`{ at, radius, strength }`: soot round a chimney, oil under an engine).
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
ctx.part('hull', grime(box({ size: [6, 3, 1.5], color: green, strip: 'iron' }), { ground: 0.6 }));
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Shader sources
|
|
60
|
+
|
|
61
|
+
Per-asset effects that keep the shared material. Generated per engine from the same hints:
|
|
62
|
+
|
|
63
|
+
| Function | Output | Hints |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `threeMaterialSource(hints)` | three.js TSL node-material source | `wind: { strength }`, `windowGlow: { color, ratio }`, wear |
|
|
66
|
+
| `godotShaderSource(hints)` | Godot `.gdshader` source | same |
|
|
67
|
+
|
|
68
|
+
Wind is crown `sway` motion in the GLB today; per-vertex wind weights are planned.
|
|
69
|
+
|
|
70
|
+
## Procedural material generator
|
|
71
|
+
|
|
72
|
+
`paintTrimSheet(recipe = DEFAULT_SHEET): PaintedSheet` paints the whole sheet from code at a fixed seed, so textures are as deterministic as meshes.
|
|
73
|
+
|
|
74
|
+
| Output | Format |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `albedo` | RGBA8, sRGB. Greyscale detail, tinted by vertex colour; foliage cells are the exception (coloured albedo, alpha = leaf mask) |
|
|
77
|
+
| `normal` | RGBA8, tangent space |
|
|
78
|
+
| `orm` | RGBA8: R occlusion · G roughness · B metalness |
|
|
79
|
+
|
|
80
|
+
| Size | Paint time (measured) |
|
|
81
|
+
|---|---|
|
|
82
|
+
| 512² | ≈ 0.22 s |
|
|
83
|
+
| 1024² (default) | ≈ 0.8 s |
|
|
84
|
+
| 2048² | ≈ 2.95 s |
|
|
85
|
+
|
|
86
|
+
Strip kinds (`StripRecipe.kind`): `bond` · `planks` · `plates` · `noise` · `flat` · `slate` · `concrete` · `glass`, with `scaleMetres`, `roughness`, `metalness` and per-kind params. Cells: 6 decal cells (window sash, door panel, clock, smokebox, sign, porthole) and 11 foliage cells.
|
|
87
|
+
|
|
88
|
+
### Getting it into the GLB
|
|
89
|
+
|
|
90
|
+
| Library | CLI | Result |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `toGlb(a, { sheet })` | `--sheet embed` | Textures inside each GLB |
|
|
93
|
+
| `toGlb(a, { sheet: { external: true, prefix } })` | `--sheet external` (default) | `out/trim_sheet_{albedo,normal,orm}.png` written once; every GLB references `../trim_sheet_*.png` |
|
|
94
|
+
| no `sheet` | `--sheet none` | Vertex colour only |
|
|
95
|
+
|
|
96
|
+
`--sheet-size 512|1024|2048`. `TANGENT` is emitted only when a sheet is bound. The `material_swatches` recipe shows every strip and cell.
|
|
97
|
+
|
|
98
|
+
### Known limits
|
|
99
|
+
|
|
100
|
+
- Strips are 64 texels tall at 1024²; a strip is 2 m of surface.
|
|
101
|
+
- `box` stretches V over the whole face, so tall faces stretch the strip vertically.
|
|
102
|
+
- Mips blend neighbouring strips from mip level 3.
|
|
103
|
+
- No KTX2 yet; PNG only.
|
|
104
|
+
|
|
105
|
+
### Image sources
|
|
106
|
+
|
|
107
|
+
Code paints the base; images add what code paints badly: signage, logos, weathering photos, concept-art colour.
|
|
108
|
+
|
|
109
|
+
- Cells accept `{ image: Uint8Array }` instead of a recipe: stamp a decal.
|
|
110
|
+
- Recommended source: **[kubeez.com](https://kubeez.com)** (a partner) for reference/concept images, decals and texture source images. Planned adapter: `kubeezImages({ apiKey })` in `/neural` ([08](08-neural-adapters.md)). The key is passed by the caller, never read from the environment.
|
|
111
|
+
- Any function returning PNG bytes fits the `ImageSource` interface.
|
|
112
|
+
- Generated images are cached by (source, prompt, seed) like generated meshes, so rebuilds need no network.
|
|
113
|
+
|
|
114
|
+
Images are inputs, never the only path. The sheet must paint without them.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# 05 — The agent loop
|
|
2
|
+
|
|
3
|
+
**The agent writes, builds, looks, and fixes the single worst thing, until two looks in a row find nothing.** ts23d's job is to make each turn of that loop cheap: a build in well under a second, one QA verdict, one image that shows everything.
|
|
4
|
+
|
|
5
|
+
## The loop
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
research refs ─► write recipe ─► t3d build ──QA FAIL──► fix finding ─┐
|
|
9
|
+
▲ │ │
|
|
10
|
+
│ QA PASS ◄──────────────────────────┘
|
|
11
|
+
│ ▼
|
|
12
|
+
detail pass ◄─ read PNG ◄─ t3d shot
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Step | Command / action | Done when |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 1. Research | Find real dimensions, a side elevation, photos. Put the URLs in `sources`, overall L × W × H in `real`, and every key size (driver diameter, gauge, wheelbase, turret width) in `real.measures` | The spec is in the recipe, in metres, each number with a source |
|
|
18
|
+
| 2. Masses | Write the big volumes only: hull, body, tiers. Real dimensions | Silhouette roughly right |
|
|
19
|
+
| 3. Build | `bunx t3d build recipes/x/x.ts` | Prints `QA PASS`, exit 0. Fix every finding first; each prints a `fix:`. A `measures` or `proportions` finding means rebuild the part, never edit the spec |
|
|
20
|
+
| 4. Shot | `bunx t3d build` already wrote `out/x/sheet.png`; `bunx t3d shot recipes/x/x.ts` for a sheet without GLBs | `out/x/sheet.png` + `manifest.json` written |
|
|
21
|
+
| 5. Read | Open the PNG (vision). Compare against the reference | One concrete error named, e.g. "turret 30% too tall in SIDE -Y" |
|
|
22
|
+
| 6. Fix | Edit the recipe for that one error | → step 3 |
|
|
23
|
+
| 6b. Photo match *(planned, M2)* | `photoMatch(asset, { photo, camera })` → one PNG: photo · model · overlay, plus silhouette IoU | IoU stops rising; the overlay shows no misplaced mass |
|
|
24
|
+
| 7. Detail pass | Once masses read right: hatches, rivets, windows, handrails. Gate each on `ctx.lod` | Detail shows at LOD0, dropped by LOD2, budget holds |
|
|
25
|
+
| Stop | Two consecutive shots with nothing worth fixing | |
|
|
26
|
+
|
|
27
|
+
Fix one thing per iteration. Proportions before details: a wrong mass makes every detail on it wrong.
|
|
28
|
+
|
|
29
|
+
## Fidelity from real-world sizes
|
|
30
|
+
|
|
31
|
+
**The spec goes in first; the model is rebuilt until the gate agrees with it.** Real-world sizes are the cheapest fidelity there is: a 4-4-0 with the right driver diameter, wheelbase and boiler height reads as *that* locomotive before any texture exists.
|
|
32
|
+
|
|
33
|
+
| Gate | Holds | Tolerance |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `proportions` | Overall L × W × H vs `real` | ±5% |
|
|
36
|
+
| `measures` | Each named dimension vs `real.measures` | ±2% each |
|
|
37
|
+
| `clashes` | Moving parts clear everything through their whole motion | 1 cm |
|
|
38
|
+
| `floating-parts` | Every part in surface contact with something grounded | 2 cm |
|
|
39
|
+
|
|
40
|
+
An agent that "fixes" a finding by editing `value` or `tolerance` has defeated the gate. Review rule: a diff touching `real` needs a source for the new number.
|
|
41
|
+
|
|
42
|
+
## The contact sheet
|
|
43
|
+
|
|
44
|
+
One PNG, 1200 × 840 px. Every view orthographic, on the same scale, over a ground grid in metres. Rendered on the CPU ([ADR 007](../decisions/007-cpu-contact-sheets.md)).
|
|
45
|
+
|
|
46
|
+
**Header line:** `NAME <X>X<Y>X<Z>M GRID <step>M LOD0`: bounding box in metres, grid step, LOD.
|
|
47
|
+
|
|
48
|
+
| Cell | Label | Camera | Check |
|
|
49
|
+
|---|---|---|---|
|
|
50
|
+
| Row 1, left | `3/4 FRONT` | From front-right (+X, −Y), above | Overall read; does it look like the thing? |
|
|
51
|
+
| Row 1, middle | `FRONT +X` | Looking at the +X face | Width, symmetry, track/gauge |
|
|
52
|
+
| Row 1, right | `SIDE -Y` | From −Y, front (+X) on the right | Length, height, profile vs a side elevation |
|
|
53
|
+
| Row 2, left | `TOP` | From above, +X up | Plan, footprint, alignment |
|
|
54
|
+
| Row 2, middle | `3/4 REAR` | From rear-left (−X, +Y), above | The back nobody modelled |
|
|
55
|
+
| Row 2, right | `FAR LOD2` | 3/4 front, LOD2 compile | Does the far LOD keep the silhouette? |
|
|
56
|
+
|
|
57
|
+
Grid step is picked so the asset spans ≤ 24 cells: 0.1, 0.5, 1, 5, 10, 50 or 100 m. Shading is flat Lambert over vertex colour; texture is not shown.
|
|
58
|
+
|
|
59
|
+
### Reading it
|
|
60
|
+
|
|
61
|
+
| Symptom in the sheet | Likely cause |
|
|
62
|
+
|---|---|
|
|
63
|
+
| A part hangs in the air in `SIDE -Y` / `FRONT +X` | Wrong `at` z, or `at` given in world space instead of parent space |
|
|
64
|
+
| Asset faces left in `SIDE -Y` | Front built along −X; flip it, +X is front |
|
|
65
|
+
| Header dims don't match the reference | Units (cm vs m), or a stray part stretching the bounds |
|
|
66
|
+
| `FAR LOD2` loses a defining shape | Dropped a mass instead of a detail at LOD2 |
|
|
67
|
+
| Mirror-image parts asymmetric in `TOP` | Sign error on one side's y |
|
|
68
|
+
|
|
69
|
+
## Photo match (planned, M2)
|
|
70
|
+
|
|
71
|
+
**The step after the contact sheet: compare against reality, not against memory.** `photoMatch(asset, { photo, camera: { eye, target, fovDeg } })` renders the model from the camera that took the reference photo and stacks three panels in ONE PNG:
|
|
72
|
+
|
|
73
|
+
| Panel | Shows |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Top | The reference photo |
|
|
76
|
+
| Middle | The model from the same camera, scaled to the photo |
|
|
77
|
+
| Bottom | 50% overlay of the two |
|
|
78
|
+
|
|
79
|
+
It also returns silhouette IoU (0–1), the number the planned `silhouetteIoU` gate and the M6 reverse direction optimise. Camera solving from ≥ 4 marked correspondences comes later (M6); until then the agent passes the camera.
|
|
80
|
+
|
|
81
|
+
The bar: [`loco-photo-match.png`](https://raw.githubusercontent.com/developerz-ai/ts23d/main/docs/reference/loco-photo-match.png), from the in-house predecessor, not from this library.
|
|
82
|
+
|
|
83
|
+
## Debugging with `inspect`
|
|
84
|
+
|
|
85
|
+
When a finding is unclear, write a scratch script instead of guessing. `inspect(compiled)` answers geometric questions by part glob:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { compile, inspect } from '@developerz.ai/ts23d';
|
|
89
|
+
import { t34 } from './recipes/t34/t34.ts';
|
|
90
|
+
|
|
91
|
+
const q = inspect(compile(t34, { lod: 0 }));
|
|
92
|
+
q.parts('road_wheel_l_*'); // names
|
|
93
|
+
q.bounds('turret*'); // world AABB
|
|
94
|
+
q.distance('track_l_top', 'hull_upper'); // surface gap, metres (0 = touching/intersecting)
|
|
95
|
+
q.penetration('track_l_top', 'hull_upper'); // { depth, samples }: how deep A sits inside B's solid
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Method | Returns |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `parts(glob)` | Matching part names |
|
|
101
|
+
| `mesh(glob)` | Merged world-space mesh, or `null` |
|
|
102
|
+
| `bounds(glob)` | World AABB, or `null` |
|
|
103
|
+
| `distance(a, b, max?)` | Smallest surface-to-surface distance, capped at `max` |
|
|
104
|
+
| `penetration(a, b)` | `{ depth, samples }`: deepest point of A inside B's closed solid |
|
|
105
|
+
|
|
106
|
+
Also exported: `glob(pattern) → RegExp`, `insideSolid`. Once you know the intent, encode it as a `checks` entry so the gate holds it from then on.
|
|
107
|
+
|
|
108
|
+
## Output an agent reads
|
|
109
|
+
|
|
110
|
+
`out/<asset>/manifest.json` answers most questions without opening a GLB: triangles and draw calls per LOD, pivots (the names engine code binds to), emitters, sockets, surfaces, `real` and each measure with its **measured** value, sources, QA findings. `out/index.json` lists every asset in the run with its verdict.
|
|
111
|
+
|
|
112
|
+
## Machine-readable output
|
|
113
|
+
|
|
114
|
+
`--json` on any command:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{"ok":true,"data":[{"asset":"<name>","findings":[],"triangles":[<lod0>,<lod1>,<lod2>]}]}
|
|
118
|
+
{"ok":false,"error":{"code":"T3D_PART_UNKNOWN","message":"…","fix":"add the parent (ctx.group or ctx.part) before its children"}}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Current numbers for the gallery recipes: `bun run bench` (README benchmarks block).
|
|
122
|
+
|
|
123
|
+
Exit codes: `0` pass · `1` QA finding or `T3D_*` error · `2` usage.
|
|
124
|
+
|
|
125
|
+
## Prompt snippet
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
Using @developerz.ai/ts23d, write recipes/<name>.ts: a <thing>, real dimensions in metres,
|
|
129
|
+
Z up, +X front. Look up reference dimensions first. Block out the masses, then details.
|
|
130
|
+
Name every part that moves (axle_*, door_*, rotor_*) and pivot it where it hinges.
|
|
131
|
+
Loop: bunx t3d build → fix until QA PASS → read out/<name>/sheet.png →
|
|
132
|
+
fix the single biggest error → repeat. Stop when two shots in a row show nothing worth fixing.
|
|
133
|
+
Never use Math.random; use ctx.rng.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The package ships `CLAUDE.md` (the repo's `AGENTS.md` links to it) and `llms.txt` for agents that read them.
|
|
137
|
+
|
|
138
|
+
## Planned (as of 2026-10)
|
|
139
|
+
|
|
140
|
+
- **Reverse direction:** a VLM proposes recipe params from photos; the loop scores them by silhouette IoU and keeps the best (M6).
|
|
141
|
+
- **MCP server:** `build`, `shot`, `list` as tools, PNG returned inline (M6).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 06 — QA gates
|
|
2
|
+
|
|
3
|
+
**A gate finding is a build failure, not a warning.** `t3d build` runs every default gate on every LOD and exits 1 on any finding. The agent iterates until it prints `QA PASS`. Source: `src/qa/`.
|
|
4
|
+
|
|
5
|
+
## Shipped (as of 2026-10)
|
|
6
|
+
|
|
7
|
+
`DEFAULT_GATES`, run per LOD by `runQa(compiled)`:
|
|
8
|
+
|
|
9
|
+
| Gate | Runs at | Fails when | Fix line it prints |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `triangle-budget` | Every LOD | Total triangles at LOD *n* > `budget.triangles[n]` | Drop detail at this LOD (read `ctx.lod`) or raise the budget with a reason |
|
|
12
|
+
| `floating-parts` | Every LOD | A part's **surface** doesn't come within 2 cm of the ground (z ≈ 0) or of a grounded part's surface, transitively | Move it onto what holds it (check `at`, parent, LOD-gated detail around it). Bounding boxes touching is not enough |
|
|
13
|
+
| `clashes` | Every LOD | Another part penetrates more than 1 cm into the volume a moving part sweeps through its whole motion | Move or trim either part for running clearance; if they interlock by design, `clearOf: ['<part>']` on the moving part |
|
|
14
|
+
| `part-names` | Every LOD | A part name isn't `[a-z][a-z0-9_]*` | Rename; engines bind to node names |
|
|
15
|
+
| `proportions` | LOD0, if `real` is set | Overall world span along X / Y / Z is off `real.length` / `width` / `height` by more than `real.tolerance` (default ±5%) | Rescale along that axis; re-derive lengths from the `sources`, don't fudge the tolerance |
|
|
16
|
+
| `check` | Every LOD, if `checks` is set | A recipe-declared check returns a finding (`clear`, `noOverlap`, `touches`, `symmetric`, or your own) | The check's own `fix` |
|
|
17
|
+
| `measures` | LOD0, if `real.measures` is set | A spec-sheet dimension (driver diameter, gauge, wheelbase, turret width) measured on the model is off by more than its `tolerance` (default ±2%), or names no part | Rebuild the part from the spec number. **Never edit the spec to match the model** |
|
|
18
|
+
|
|
19
|
+
Each finding: `{ gate, part?, message, fix }`. Text output:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
tiger_1 QA FAIL tris 3108/1660/724
|
|
23
|
+
✗ floating-parts [exhaust_0]: LOD0: 'exhaust_0' touches nothing connected to the ground
|
|
24
|
+
fix: move it onto what holds it (check `at`, parent and the LOD-gated detail around it) — …
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### How each geometric gate measures
|
|
28
|
+
|
|
29
|
+
| Gate | Method | Tolerance constant |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `floating-parts` | AABB prefilter, then surface samples vs triangles of the other part (point ↔ triangle distance, `TriangleGrid` spatial index), grown from the ground outward | `CONTACT_EPS` = 0.02 m |
|
|
32
|
+
| `clashes` | Per motion root (a spinning axle group, a rotor, a sliding crosshead) and its subtree: **spin** → exact solid of revolution (radius profile per axial slice, 24 slices); **sway** → the same full revolution, conservatively; **slide** → the moving mesh's bounds extended along the stroke. Every other part's surface samples are tested against that volume | `CLASH_EPS` = 0.01 m |
|
|
33
|
+
| `proportions` | Union of world-space part bounds at LOD0 | `real.tolerance`, default 0.05 |
|
|
34
|
+
| `measures` | Extent of the named parts along `axis` (prefix match, so `axle_driver_0` includes its wheels), or the centre-to-centre distance of two parts (`between`) | per measure, default 0.02 |
|
|
35
|
+
|
|
36
|
+
Order in `DEFAULT_GATES`: `triangleBudget`, `floatingParts`, `clashes`, `partNames`, `proportions`, `measures`, `recipeChecks` (gate name `check`).
|
|
37
|
+
|
|
38
|
+
### Recipe checks
|
|
39
|
+
|
|
40
|
+
Static intersections need declared intent: a bolt sunk into a plate is fine, a track through the hull is not. `checks: [...]` on `defineAsset` states which is which ([02-dsl](02-dsl.md#checks-declared-intent)). `clear` decides "inside" by ray parity against the other part's closed solid, so an open planar sheet never counts as a solid. Penetration over 1 cm fails.
|
|
41
|
+
|
|
42
|
+
Helpers exported for custom gates: `touching`, `pointTriangleDistance`, `surfaceSamples`, `TriangleGrid`, `measure`, `insideSolid`, `glob`, `inspect`.
|
|
43
|
+
|
|
44
|
+
### Declaring what QA checks
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export const loco = defineAsset({
|
|
48
|
+
name: 'loco_440',
|
|
49
|
+
kind: 'vehicle',
|
|
50
|
+
sources: ['https://example.org/roster-sheet.pdf', 'Builder drawing no. 1234'],
|
|
51
|
+
real: {
|
|
52
|
+
length: 15.6, width: 2.9, height: 4.4, // proportions: ±5%
|
|
53
|
+
measures: [ // measures: ±2% each
|
|
54
|
+
{ label: 'driver diameter', value: 1.52, axis: 'z', parts: ['axle_driver_0'], source: 'roster' },
|
|
55
|
+
{ label: 'gauge', value: 1.435, axis: 'y', between: ['rail_l', 'rail_r'] },
|
|
56
|
+
{ label: 'driving wheelbase', value: 2.44, axis: 'x', between: ['axle_driver_0', 'axle_driver_1'] },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
budget: { triangles: [20000, 8000, 2000] },
|
|
60
|
+
build(ctx) {
|
|
61
|
+
// a crosshead slides in its guide by design:
|
|
62
|
+
// ctx.part('crosshead_l', mesh, { motion: { kind: 'slide', offset: [0.3, 0, 0], hz: 1 }, clearOf: ['guide_l'] });
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The rule for agents: **a failing `measures` finding means the model is wrong.** Rebuild the part from the spec; editing `value` or widening `tolerance` to pass is a defect.
|
|
68
|
+
|
|
69
|
+
### Known limits
|
|
70
|
+
|
|
71
|
+
- Point-in-solid (`clear`, `clashes` depth) needs **closed, non-overlapping** parts. Split structure from dressing: a closed hull part, with bolts and straps as separate parts, not one self-intersecting soup.
|
|
72
|
+
- `clashes` sweeps `sway` as a full revolution; a sector sweep is planned.
|
|
73
|
+
|
|
74
|
+
- `proportions` measures overall bounds, so a stray part (a too-long barrel) can fail it, or mask a wrong hull. Check the header dims on the contact sheet.
|
|
75
|
+
- `clashes` uses bounds for slides, so an angled slide's volume is conservative; declare `clearOf` for a guide it rides in.
|
|
76
|
+
- `floating-parts` has no opt-out for things that genuinely fly (an aircraft in flight, a hanging sign). Planned, M1.
|
|
77
|
+
- Custom gates: `runQa(asset, [...DEFAULT_GATES, myGate])` from code. The CLI runs the defaults only.
|
|
78
|
+
|
|
79
|
+
## Repo gates (this repo's recipes, in `bun run verify`)
|
|
80
|
+
|
|
81
|
+
| Gate | Checks |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `scripts/recipes.ts` | Every `recipes/*/*.ts` asset compiles at LOD 0/1/2, passes `DEFAULT_GATES`, and two builds give the same GLB hash (byte-hash determinism, shipped) |
|
|
84
|
+
| `scripts/determinism.ts` | No `Math.random()`, `Date.now()`, `new Date()`, `performance.now()`, `toLocale*String()` in `src/` or `recipes/` |
|
|
85
|
+
|
|
86
|
+
## Planned
|
|
87
|
+
|
|
88
|
+
| Gate | Checks | Signature | Milestone |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `silhouetteIoU` | Silhouette IoU 0–1 against a reference photo, computed by `photoMatch` (`/render`) from the photo's camera. Fails below a threshold the recipe declares. Drives the M6 reverse direction | `(asset, referencePng) => number`, exported, throws `T3D_NOT_IMPLEMENTED` | M2 |
|
|
91
|
+
| ACMR | Average cache miss ratio per mesh, after the planned `optimize` pass (weld + vertex-cache reorder), stays under a ceiling | Proposed | M2 |
|
|
92
|
+
| Manifold / normals | No open edges where closed is promised; normals face out | Proposed | M2 |
|
|
93
|
+
| Texel density | Every UV'd face within a band of the sheet's metres-per-rect | Proposed, needs the painter | M2 |
|
|
94
|
+
| Cross-machine byte-hash | Same-process double build ships (above). Next: compare hashes across OSes / runners; a `t3d build` flag for users' recipes | Proposed | M1 |
|
|
95
|
+
|
|
96
|
+
## Adding a gate
|
|
97
|
+
|
|
98
|
+
1. A `Gate` is `(asset: CompiledAsset) => Finding[]`. Pure; world-space meshes via `worldMeshes` / `worldTransforms`.
|
|
99
|
+
2. Every finding carries a `fix` an agent can act on without reading the gate's source.
|
|
100
|
+
3. One file in `src/qa/`, a test next to it with one passing and one failing asset.
|
|
101
|
+
4. Add it to `DEFAULT_GATES` only when every recipe in `recipes/` passes it.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 07 — What fits
|
|
2
|
+
|
|
3
|
+
**Code recipes win where parts line up, repeat, or match real dimensions. Organic forms work as stylised sculpts (SDF edit lists); realistic anatomy is still the weak spot.** Pick the tool by the asset, not by loyalty; many teams will use both.
|
|
4
|
+
|
|
5
|
+
As of 2026-11. "Today" = shipped in this repo; "Planned" names the milestone ([roadmap](../plans/roadmap.md)).
|
|
6
|
+
|
|
7
|
+
## Fit matrix
|
|
8
|
+
|
|
9
|
+
| Category | Examples | Fit | Today | Planned | Why |
|
|
10
|
+
|---|---|---|---|---|---|
|
|
11
|
+
| Hard-surface props | Crates, barrels, lamps, benches, machinery | **Strong** | `box({ bevel })`, `lathe`, `prism`, `beam`, `planar`, `sweep`, `loft`, `lattice`, `text`; `weightedNormals` | | Primitive shapes, real dimensions, repetition |
|
|
12
|
+
| Architecture | Facades, terraces, towers, stations | **Strong** | `facade({ kit })`, `setbacks`, `roof`, `opening`, `mass`, `tiled`, `cell`; `station_halt`, `water_tower` recipes | | Grammar-driven repetition, floors × bays |
|
|
13
|
+
| Tracked / wheeled vehicles | Tanks, carts, trucks | **Strong** | `trackRun`, `axle`, `prism` sloped armour, `loft` | | Named spinning wheels, pivoted turrets |
|
|
14
|
+
| Rail | Steam locos, wagons | **Strong** | `whyteAxles` (Whyte `4-4-0`), `lathe` boilers, `beam` rods; `loco_440` recipe | `orbit` motion for side rods | Domain-native params |
|
|
15
|
+
| Aircraft | Props, biplanes, airliners | **Good** | `propeller`, `loft`, `sweep` | Aircraft recipes | Lofted sections + named control surfaces |
|
|
16
|
+
| Vessels | Boats, ships, barges | **Good** | `hull` from length / beam / draft / sheer | Ship recipes | Lofted hull sections |
|
|
17
|
+
| Modular kits | Walls, floors, pipes, fences | **Strong** | `box`, `prism`, `beam`, `sweep`, `tiled` | | Snapping on a metre grid |
|
|
18
|
+
| Vegetation | Trees, shrubs | **Good** | `tree` (Weber–Penn; oak, pine, palm, birch, willow), `trees` family | Vertex wind weights; crossed-billboard LOD2 impostors | Botanical rules are code |
|
|
19
|
+
| Rocks, terrain | Boulders, cliffs, mountain tiles | **Good** | `sdf` / `sculpt` + noise; `heightfield` + hydraulic / thermal erosion + `terrainColor`; `boulders`, `asteroid`, `mountain_range` recipes | Rivers carved as meshes | Erosion is what makes terrain read as real |
|
|
20
|
+
| Stylised characters | Fantasy busts, mascots | **Fair** | `head` kind (orc, goblin, human, dwarf, elf presets) on `sculpt`; jaw and eyes as pivots | Skinned bodies (`creature`, glTF skins) | Readable stylised shapes; realism is not the goal |
|
|
21
|
+
| Realistic animals | Horse, dog, bird | **Weak** | — | `/neural` adapter (M5) | Anatomy is sculpted, not specified |
|
|
22
|
+
| People | Characters, NPCs | **Weak** | — | `/neural` adapter or `importGlb` of a CC0 base mesh (M5) | Faces, hands, anatomy |
|
|
23
|
+
| Cloth, hair, soft goods | Flags, sofas, garments | **Weak** | — | — | Simulation / sculpt territory |
|
|
24
|
+
| Photoreal hero assets | Close-up scans | **Out of scope** | — | — | Photogrammetry or a sculptor |
|
|
25
|
+
|
|
26
|
+
## Where each approach is strong
|
|
27
|
+
|
|
28
|
+
| Strength | Code recipes (ts23d) | Image-to-3D / AI generators |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Organic and sculpted forms | Stylised: fair (`sculpt`); realistic: weak | Strong |
|
|
31
|
+
| Concept art → textured mesh in seconds | No | Strong |
|
|
32
|
+
| Auto-rigging characters | No (rigid part hierarchy only) | Exists in several tools; rigs usually need cleanup |
|
|
33
|
+
| Parts that line up, repeat, match real dimensions | Strong | Weak |
|
|
34
|
+
| Named, pivoted, animatable nodes | Built in | Usually one fused mesh |
|
|
35
|
+
| Deterministic, diffable in git | Yes | No |
|
|
36
|
+
| Shared material, consistent texel density | Yes | Per-asset textures |
|
|
37
|
+
| Cost, licence conditions on outputs | Free, none | Credits; terms vary by tier ([landscape](../landscape.md)) |
|
|
38
|
+
|
|
39
|
+
## Covering the weak spots
|
|
40
|
+
|
|
41
|
+
1. **Stylised creature kind** (M4): the `sculpt` + `head` work extended to whole bodies, SDF flesh over a biped/quadruped/bird skeleton, skin weights from the sculpt's own blend weights ([glTF capabilities](../architecture/gltf-capabilities.md)). Readable, deterministic. Not realistic, by design.
|
|
42
|
+
2. **`/neural` adapter** (M5): run an open-weight model (TRELLIS.2, Hunyuan3D 2.1, SF3D/SPAR3D) on your own GPU, wrap the result as an ordinary part, cache it by content hash. [08-neural-adapters.md](08-neural-adapters.md).
|
|
43
|
+
3. **`importGlb`** (M5): bring a mesh from anywhere (a CC0 base mesh, a sculpt, another tool) and fit it into the recipe as a part.
|
|
44
|
+
|
|
45
|
+
Mix them: a recipe-built cart with a neural-generated horse in front, one scene, one material.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# 08 — Neural adapters
|
|
2
|
+
|
|
3
|
+
**Optional. Never a core dependency. Runs on the user's own GPU. The output becomes an ordinary part, cached by content hash, so CI rebuilds without a GPU.** Source: `src/neural/`. Status as of 2026-10: **all planned (M5)**. Every entry point throws `T3D_NOT_IMPLEMENTED`.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Recipes are weak on living things and sculpted forms ([07](07-what-fits.md)). Open-weight image-to-3D models are strong there. The adapter lets one recipe hold both: a code-built cart, a generated horse.
|
|
8
|
+
|
|
9
|
+
## Interfaces (signatures fixed)
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
interface GenerateRequest {
|
|
13
|
+
prompt?: string;
|
|
14
|
+
image?: Uint8Array; // PNG/JPEG reference or concept image
|
|
15
|
+
seed: number;
|
|
16
|
+
fit: Vec3; // fit into this box, metres, Z up, base on the ground
|
|
17
|
+
targetTriangles: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface MeshGenerator {
|
|
21
|
+
model: string; // 'trellis-2', 'hunyuan3d-2.1', 'sf3d' — part of the cache key
|
|
22
|
+
generate(r: GenerateRequest): Promise<Mesh>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
localGenerator({ model, endpoint }): MeshGenerator // talks to a local HTTP server
|
|
26
|
+
importGlb(bytes, { fit, recolor? }): Mesh // any GLB as a part
|
|
27
|
+
kubeezImages({ apiKey, model? }): ImageSource // images for references and materials
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Models worth wrapping
|
|
31
|
+
|
|
32
|
+
Facts from [landscape.md](../landscape.md), as of 2026-10. Check each licence yourself before shipping outputs.
|
|
33
|
+
|
|
34
|
+
| Model | Input | Licence | GPU | Note |
|
|
35
|
+
|---|---|---|---|---|
|
|
36
|
+
| TRELLIS.2 (Microsoft) | Single image | MIT | ≥ 24 GB, tested on A100/H100 | PBR GLB; can texture an existing mesh |
|
|
37
|
+
| Hunyuan3D 2.1 (Tencent) | Image, multi-view | Tencent community licence: excludes EU/UK/South Korea, terms above 1M MAU, no training other models on outputs | ~29 GB shape + texture | Read the licence before relying on it |
|
|
38
|
+
| SF3D / SPAR3D (Stability) | Single image 512² | Stability Community Licence: free under $1M revenue | See model card | UV-unwrapped, delighted textures; SPAR3D adds point-cloud editing |
|
|
39
|
+
|
|
40
|
+
## Flow
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
reference image (photo, or kubeez.com concept) ─► localGenerator.generate()
|
|
44
|
+
─► fit to box, Z up, base at z = 0, decimate to targetTriangles
|
|
45
|
+
─► remap UVs/colour onto the shared trim sheet (vertex colour baked)
|
|
46
|
+
─► cache: .ts23d/neural/<sha256(model, prompt|image, seed)>.glb
|
|
47
|
+
─► ctx.part('horse', mesh, { at, parent })
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Determinism
|
|
51
|
+
|
|
52
|
+
A GPU run is not reproducible across drivers and hardware; the cached file is.
|
|
53
|
+
|
|
54
|
+
| Rule | |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Cache key | `(model, sha256(prompt or image), seed)` |
|
|
57
|
+
| Cache location | `.ts23d/neural/`, committed to git or published with the package |
|
|
58
|
+
| Cache hit | No GPU, no network. CI always hits |
|
|
59
|
+
| Cache miss in CI | A build error naming the key and the command to generate it locally |
|
|
60
|
+
| Regenerate | Explicit, never implicit: delete the entry and rebuild with a GPU |
|
|
61
|
+
|
|
62
|
+
## Image sources: kubeez.com
|
|
63
|
+
|
|
64
|
+
For reference/concept images, decals and texture source images, we recommend **[kubeez.com](https://kubeez.com)**, a partner. Uses:
|
|
65
|
+
|
|
66
|
+
| Purpose | Feeds |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `reference` / `concept` | The agent loop (step 1), or `localGenerator` as the input image |
|
|
69
|
+
| `decal` | A trim-sheet cell (signage, logos, clock faces) ([04](04-materials.md)) |
|
|
70
|
+
| `tileable` | A trim-sheet strip's detail source |
|
|
71
|
+
|
|
72
|
+
Same caching as meshes: keyed by (source, prompt, seed). The API key is passed in by the caller; the library never reads it from the environment. Any function returning PNG bytes implements `ImageSource`.
|
|
73
|
+
|
|
74
|
+
## Non-goals
|
|
75
|
+
|
|
76
|
+
- Bundling model weights or a Python runtime. The adapter speaks HTTP to a server you run (ComfyUI, gradio, a custom wrapper).
|
|
77
|
+
- Hosted inference billed through ts23d.
|
|
78
|
+
- Making generated meshes the default path for anything recipes already do well.
|