Shipping Crate
A procedural shipping crate through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.
showcase/wooden-ladder/
A procedural timber ladder with raked tapered stiles, six turned rungs, ferrule caps, and level shoe plates through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.
Rendered headless by the showcase piece itself. Select it to enlarge.
category Village
blender --background --python showcase/wooden-ladder/wooden_ladder.py --
A showcase piece, not an example. Procedural timber ladder: two stiles tapered from 64 mm to 50 mm, raked 12°, six turned hickory rungs (a barrel that shoulders in just under its radius and swells 14 % at mid-span, the profile a lathe leaves on a ladder round) through-tenoned into oak rails with their end grain showing 2 mm proud of the outer faces, two iron tie-rods under the bottom and top rungs clamped by square nuts over round washers, iron ferrules at the top, and level shoe plates under sleeves that follow the rake. Then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.
Hero. The ladder leans on a render-only barn wall of staggered weathered boards (a Brick texture turned onto the wall plane, with a stretched noise for grain), and a hand-coiled rope hangs over the second-highest rung. The rung is found the way rung_pitch finds rungs: the wooden shells wide in X and short in Z. The coil is a teardrop laid from a three-lobed section, per the showcase rope rule. It hangs forward of the rung, away from the wall. The wall and the coil are staging, not part of the asset, its budgets or its export. The asset sheet judges the bare LadderLow.
It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.
Intended size: stile length 1.50 m, overall about 0.49 × 0.38 × 1.49 m. A household ladder, not a 3 m orchard ladder.
Composes skills mesh-editing-and-bmesh, bake-high-to-low, depsgraph-and-evaluated-data, engine-export-presets, and snippets bake_normal_high_to_low.py, setup_bake_target_image.py, lod_chain.py / decimate_to_budget.py, convex_hull_collider.py, export_preset_unity.py (helpers copied, not imported as a package). Hygiene combinatorics match examples/mesh-hygiene-audit (copied, not imported). Coplanar-pair counting matches showcase/signpost.
Declared as named constants; every gate recomputes from the mesh, materials, UVs, evaluated LOD, collider, or export file.
| Axis | Declared | Measured (4.5.11 / 5.2.1; 5.1.2 not re-run after the tie-rod pass) |
|---|---|---|
| Base triangles | 1800–3100 | 3000 / 3000 |
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 |
| LOD2 ratio | 0.10–0.35 of base | 0.2200 / 0.2200 |
| Materials | exactly 2 distinct, ≥280 wood, ≥180 metal | 2 slots, 684 wood, 976 metal |
| UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 |
| Outer AABB | (0.490, 0.376, 1.487) m ± 0.01 | (0.4903, 0.3760, 1.4870) |
| Grounded | abs(zmin) ≤ 1e-4, each sole at 0, rail end ≥ 0.012 m | zmin 0, sole 0, rail 0.02037 |
| Mesh hygiene | non-manifold, loose v/e, doubles at 1e-5, zero-area, n-gons, coplanar pairs all 0 | all 0 |
| Parts | 26 shells (2 stiles, 6 rungs, 2 sleeves, 2 cap sleeves, 2 plates, 2 soles, 2 tie-rods, 4 washers, 4 nuts) | 26 (2 / 6 / 2 / 2) |
| Rung depth clearance | ≥ 0.003 m inside the stile faces | 0.00800 |
| Tenon engagement | ≥ 0.008 m past the stile inner face | 0.05387 (through) |
| Through-tenon proud | tenon end 0.001–0.004 m past the stile outer face | 0.00199 |
| Shoe bite / cover | bite ≥ 0.010 m, cover ≥ 0.016 m | 0.02000 / 0.03800 |
| Rung pitch | 6 rungs; every rung-to-rung gap within 0.002 m of the mean | 6; 0.00000 |
| Tie-rod seat | 2 rods, 4 washers, 4 nuts; washer into stile and nut into washer 0.0002–0.002 m; on the rod axis within 0.0015 m; thread ≥ 0.003 m proud of each nut | 2 / 4 / 4; bite 0.00049–0.00051; axis 0; thread 0.00450 |
| Collider tris | ≤ 180 | 68 |
| Export | written, size > 0 | 228568 / 228568 / 228560 bytes (4.5.11 / 5.1.2 / 5.2.1) |
Base triangles rose from 936 to 2536. The previous mesh was two boxes, six cloned dowels, and four chrome cubes. Turned rungs (tenon and barrel in one shell) plus ferrule collars and chamfered soles added the triangles. Nothing was removed as a hidden face.
A later pass took them to 2680. The bare ladder was still the plainest object in its asset-sheet lineup, and the parts budget is exact (16 shells), so the detail had to live inside existing shells. Each rung gained one ring at mid-span (+24 triangles per rung): the barrel shoulders in at 0.90 × its radius (RUNG_SHOULDER) and swells to 1.14 × (RUNG_SWELL). That turns a dowel into a lathe-turned round. The tenons are unchanged, so clearance, engagement and pitch read the same. The breakout margin moved 0.07 mm (0.03780 → 0.03787) because the shell's worst vertex is now measured across seven rings. The wood shader also changed. The grain noise runs across the stile's 50 mm width at a readable scale (WOOD_GRAIN_SCALE 34 → 95, streak stretch 0.94 → 0.985); before, about one noise period spanned a stile, so it rendered as flat tan. A short-range AO term darkens the rung housings and the wood under the iron sleeves.
A third pass changed the model itself. The bare ladder was still the plainest object in its asset sheet, because the exact parts budget left every detail inside the existing shells. The parts budget is now 26: two iron tie-rods run through both rails under the bottom and top rungs, each end clamped by a square nut over a round washer bedded on the rail's outer face. That is the rod a wooden ladder carries to stop the rails spreading off the rungs. The triangle ceiling rose 2800 → 3100 for the hardware (+320 triangles; the old ceiling had 120 to spare). The rungs are now through-tenoned: each tenon runs right through its rail and stands 2 mm proud, so the old breakout margin (a blind tenon stopping short of the outer face) became a through-tenon proud band, and the tenon's inboard ring moved to 4 mm inside the rail so the joint audit always samples tenon section in the wood. The shader reads a per-shell Species attribute: rungs are pale hickory against darker oak rails, and end grain (the tenon ends, the rail tops) takes the finish darker. The rung finders now require the wood material, because the tie-rods are as wide as a rung. The new tie-rod seat budget gets its own falsifier, --float-nuts (exit 20).
The joint budgets are measured per rung against each stile, from vertex positions in the un-raked construction frame, and the reported value is the worst case. Clearance is the tenon, not the barrel: the barrel is thicker only between the stiles.
DECIMATE COLLAPSE triangle counts are not identical across series — the gate is a ratio band, not an exact count. On this mesh the three binaries agreed. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction is closed-form; the only RNG is the seeded per-piece wood tone. Bevel inputs are sorted by edge index, so the face order is the same on every run; a Python set of edges handed to the bevel had made it vary. Export byte counts differ on 5.2.1 (glTF serializer), not a gated axis. Euler is 24 (multi-body) and is not gated to 2.
The rung heights were jittered on purpose (up to ±18 mm, RUNG_Z_OFFSETS) and the barrel thickness varied from 0.82× to 1.22×. That is variation put into function: a climber's feet find rungs blind, so an even pitch is part of what a ladder is. The rungs now sit at one pitch, and the turning varies by a few percent. Rung pitch asserts it; --drift-rungs restores the old heights and exits 19 (worst gap 0.03287 m off the mean). Tone and grain now vary per rail and rung instead. The temp .glb is removed after its size is measured.
The hero stood the ladder at its 12° rake with nothing to lean on. The render path now adds a render-only wall section on the side the rails rake toward, with its face through the rail tops, and turns the ladder so that side faces away from the camera.
Each falsifier violates exactly one named budget. Proven on Blender 4.5.11 LTS and 5.2.1 LTS (the binaries' own --version; 5.1.2 not re-run after the tie-rod pass):
| Falsifier | Budget violated | Exit | Measured failure |
|---|---|---|---|
--skip-decimate | LOD1 ratio band | 9 | ratio 1.0000 |
--stray-vert | Mesh hygiene, loose verts | 15 | loose_v=1 |
--twin-sole | Coplanar face pairs | 15 | zfight=6 |
--lift-z | Grounded zmin | 16 | zmin 0.050000 |
--fat-rungs | Rung depth clearance | 17 | clearance 0 |
--short-stile | Shoe bite / cover | 18 | bite 0.070, cover −0.012 |
--drift-rungs | Rung pitch | 19 | worst gap 0.03287 off the mean |
--float-nuts | Tie-rod seat | 20 | seat bite −0.00350, thread 0.00050 proud |
Default exit is 0 on both.
blender --background --python wooden_ladder.py --
blender --background --python wooden_ladder.py -- --skip-decimate
blender --background --python wooden_ladder.py -- --stray-vert
blender --background --python wooden_ladder.py -- --twin-sole
blender --background --python wooden_ladder.py -- --lift-z
blender --background --python wooden_ladder.py -- --fat-rungs
blender --background --python wooden_ladder.py -- --short-stile
blender --background --python wooden_ladder.py -- --drift-rungs
blender --background --python wooden_ladder.py -- --float-nuts
blender --background --python wooden_ladder.py -- --output preview.webp --engine cycles
Smoke passes no flags: no --output, no falsifier.
File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | Mesh did not build / no UV layer |
| 4 | Base triangle count outside range |
| 5 | Material count ≠ 2 distinct slots, or wood/metal faces missing |
| 6 | UVs outside 0..1 |
| 7 | UV AABB overlap above tolerance |
| 8 | World AABB off declared outer size |
| 9 | LOD ratio band (--skip-decimate lands here) |
| 10 | Framing gate (render path only) |
| 11 | Collider triangle count above ceiling |
| 12 | Bake did not finish or image has no data |
| 13 | Export file missing or empty |
| 14 | --output produced no file |
| 15 | Mesh hygiene, including coplanar pairs (--stray-vert, --twin-sole) |
| 16 | Grounded zmin, or a rail end sitting in the tread (--lift-z) |
| 17 | Part count or rung-to-stile joint fit, including through-tenon proud (--fat-rungs) |
| 18 | Shoe bite / cover (--short-stile) |
| 19 | Rung pitch (--drift-rungs) |
| 20 | Tie-rod seat (--float-nuts) |
| 21 | Gallery asset-quality violation (render path; the floors' 11 is remapped here) |