Stone Well

showcase/stone-well/

A procedural stone well 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.

witnesses Recomputed: 9380 tris, three materials with face floors, UVs in 0..1 with zero AABB overlap, outer AABB 1.312×1.312×1.761 m, LOD ratios in band, convex collider 258 tris, hygiene zero (loose, non-manifold, doubles, n-gons), zero coplanar cross-shell face pairs, all 12 bottom-course stones on the floor, each post tenoned 18 mm into the measured coping and standing under a roof hip corner, the bucket hanging 0.10 m clear of the coping, grounded zmin, non-empty glTF. --stand-posts exits 15 on the z-fight budget; --float-stone exits 16 on the named-support budget; --shallow-tenon and --drop-bucket exit 18; --turn-posts exits 19 on post placement; --skip-decimate exits 9 on the LOD1 ratio budget; --lift-z exits 16 on the grounded budget.

category Village

tags mesh export showcase

blender --background --python showcase/stone-well/stone_well.py --

A showcase piece, not an example. Procedural round stone well (running-bond bricks, curb, four posts, shingled pyramid roof, windlass, rope, bucket) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.

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).

Budgets#

Declared as named constants; every gate recomputes from the mesh, materials, UVs, evaluated LOD, collider, or export file.

AxisDeclaredMeasured (4.5.11 / 5.1.2 / 5.2.1)
Base triangles8280–95009380 / 9380 / 9380
LOD1 ratio0.32–0.62 of base0.5000 / 0.5000 / 0.5000
LOD2 ratio0.10–0.35 of base0.2198 / 0.2198 / 0.2198
Materialsexactly 3 distinct3
Material facesstone ≥ 3000, wood ≥ 600, metal ≥ 100all above
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(1.312, 1.312, 1.761) m ± 0.01(1.3120, 1.3120, 1.7606)
Groundedbbox min Z within 1e-4 of 00.0000 / 0.0000 / 0.0000
Named supportsall 12 bottom-course stones on the floor within 1e-412/12, worst 0.000000
Hygieneloose V/E, non-manifold, zero-area, doubles @1e-5, n-gons: all 00 / 0 / 0 on every axis
Z-fightingcoplanar cross-shell face pairs = 00 / 0 / 0
Post tenoneach post seats 0.015–0.022 m into the measured coping top0.01800 / 0.01800 / 0.01800
Post placementeach post within 0.02 rad, in plan, of a roof hip corner recomputed from the mesh0.00000
Bucket clearancehangs 0.085–0.115 m clear of the measured coping top0.10000
Material-island gapstone↔wood and metal↔wood min distance ≤ 0.008 m0.00000 / 0.00000 / 0.00000
Collider tris≤ 380258
Exportwritten, size > 0, removed after measuring686472 / 686472 / 686468 bytes (4.5.11 / 5.1.2 / 5.2.1)

DECIMATE COLLAPSE triangle counts are not guaranteed identical across series — the gate is a ratio band, not an exact count. This mesh happened to match on 4.5.11 / 5.1.2 / 5.2.1. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG; the per-piece material tone is drawn from a seeded random.Random(TONE_SEED), the same every run. glTF byte size differs by a few hundred bytes across series; the gate is "written and non-empty", not a byte count, and the file is removed once measured.

Falsifiers#

Each flag breaks one stage so a named budget fails and the piece exits that code.

FlagTarget budgetExit
--skip-decimateLOD1 ratio band (ratio becomes 1.0)9
--lift-zAABB grounded zmin (whole mesh up 0.05 m)16
--stand-postsCoplanar cross-shell pairs — puts the posts back on the axis and stands them on the coping, which is the pair of choices the shipped piece made (0 → 4 pairs)15
--float-stoneNamed supports — lifts one bottom-course stone 4 mm while the other eleven still ground the AABB16
--shallow-tenonPost tenon band — seats the posts only 4 mm, too shallow to be a joint but not flush, so no faces share a plane18
--drop-bucketBucket clearance — lowers the bucket 0.26 m back down the shaft18
--turn-postsPost placement — rotates the post ring one curb facet off the roof's hip corners19

Two of these had to be re-aimed, which is the point of declaring the target:

Run#

blender --background --python stone_well.py --
blender --background --python stone_well.py -- --skip-decimate
blender --background --python stone_well.py -- --lift-z
blender --background --python stone_well.py -- --stand-posts
blender --background --python stone_well.py -- --shallow-tenon
blender --background --python stone_well.py -- --turn-posts
blender --background --python stone_well.py -- --float-stone
blender --background --python stone_well.py -- --drop-bucket
blender --background --python stone_well.py -- --output well.png

Smoke passes none of the falsifier flags and no --output.

Exit codes#

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build / no UV layer
4Base triangle count outside range
5Material count ≠ 3 distinct slots, or a material face floor missed
6UVs outside 0..1
7UV AABB overlap above tolerance
8World AABB off declared outer size
9LOD ratio band (--skip-decimate lands here)
10Framing gate (render path only)
11Collider triangle count above ceiling
12Bake did not finish or image has no data
13Export file missing or empty
14--output produced no file
15Hygiene: loose geometry, non-manifold, zero-area, doubles, n-gons, or coplanar cross-shell face pairs (--stand-posts lands here)
16Bbox min Z not grounded (--lift-z), or a named support off the floor (--float-stone)
17Material-island gap above tolerance (parts meant to touch)
18Post tenon depth outside its band (--shallow-tenon), or bucket clearance outside its band (--drop-bucket)
19A post not under a roof hip corner in plan (--turn-posts lands here)
20Gallery asset-quality violation (render path; the floors' 11 is remapped here)

Source

showcase/stone-well/stone_well.py View the script on GitHub →
A round well of grey stones, each a slightly different shade, under a four-post grained wooden roof, with a bucket on rusted iron hoops hanging inside.