Wall Torch

showcase/wall-torch/

A procedural wall-mounted torch sconce: a forged plate bolted to six hand-laid stone courses, a level arm and braced cup holding a leaning haft whose pitch rag burns with graded flame tongues, 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: 3912 tris, five materials with 864 stone, 582 metal, 96 wood, 480 flame and 120 pitch faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.304×0.328×0.524 m at zmin 0, LOD ratios in band, convex collider 90 tris, non-empty glTF. Hygiene all zero. Arm level 0.000°, plate 13 mm proud of the stones and 3 mm into them, arm-to-cup gap 0.07 mm, plaque width 0.280 m, bake 20.0 texels per UV cell. --skip-decimate exits 9; --stray-vert 15; --lift-z 16; --float-arm 17; --float-plate 18; --skinny-plaque 19; --droop-arm 20; --sink-plate 21; --low-bake 22.

category Village

tags mesh export showcase

blender --background --python showcase/wall-torch/wall_torch.py --

A showcase piece, not an example. A procedural wall-mounted torch sconce: six irregular courses of hand-laid stone on a dark mortar slab, a forged iron plate bolted to the stones, a level iron arm with a round-bar brace under it, an open beaded iron cup, a leaning wooden haft with a wound pitch rag, and six graded flame tongues. Then the shipped pipeline: unique-cell UVs, a Cycles high-to-low normal bake, an LOD chain, a convex collider and a Unity glTF export.

The plaque back sits on the wall plane Y=0 with zmin at 0. Each stone is set back up to 3 mm from the face and turned up to 1.2° in the wall plane (seeded); the stones behind the plate stay on the face. The plate's seat comes from the face of the facing stones (stone_face_y()), a named bite into it. The cup's mid-height is the arm's height, so the arm is level by construction. It runs from the plate's mid-plane into the cup's wall. The brace leaves the plate below the arm, sags, and rises into the cup's drip boss. The cup sits on the haft's axis, which leans 15° out from the wall. The haft stands on the cup's floor and runs up into the wrap. The flames stand upright from the wrap, whatever the haft does: each tongue is a tube along a curved spine (lathe_spine) that swells from a buried root, drifts out and curls back to a point.

A Heat vertex layer (0..1 along each part) drives the shaders: the flame grades from a pale core at the root to deep red at the tips and toward the rim, whose alpha fades to nothing; embers wake in the top turns of the rag; the haft chars toward the wrap. Stones carry a per-stone tint from cool grey to warm buff and a soot smudge on the upper courses. The render adds a small warm point light between the flame and the stones, so the torch lights its own wall (the street-lantern and campfire precedent); the house key is turned down to let it lead.

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, procedural-materials-and-shaders, 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).

Intended size: a 0.28 m wide, 0.50 m tall plaque; the cup centre 0.24 m out from the wall; the flame tip 0.52 m above the floor, in front of the top courses. Outer AABB 0.304 × 0.328 × 0.524 m.

Budgets#

Declared as named constants. Every gate recomputes its value from the mesh, materials, UVs, evaluated LOD, collider or export file.

AxisDeclaredMeasured (4.5.11 / 5.1.2 / 5.2.1)
Base triangles3600–43003912 / 3912 / 3912
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 5 distinct; ≥24 stone, ≥24 metal, ≥8 flame, ≥10 pitch5 slots; 864 stone, 582 metal, 96 wood, 480 flame, 120 pitch
UVsin 0..1, AABB overlap ≤ 1e-5in range, overlap 0
Outer AABB(0.304, 0.328, 0.524) m ± 0.015(0.3040, 0.3278, 0.5240), zmin 0
Collider tris≤ 18090
Exportwritten, size > 0299868 / 299868 / 299860 bytes (4.5.11 / 5.1.2 / 5.2.1)

Base triangles rose from 2004 to 3912 in the third quality pass, and the band moved from 1800–2250 to 3600–4300. The hero read as programmer art: a grey tile slab with the flame above it, three clipped yellow cones, a smooth orange dowel and a copper-looking cup. The plaque grew from four courses to six irregular ones (0.40 m to 0.50 m tall) so the flame burns in front of the stones instead of above them. The flame became six graded, alpha-fading tongues (480 faces, was 180). The plate gained a stepped boss and dome bolts, the cup a rolled bead, the haft a wandering girth, the wrap bulging turns, and a brace now carries the cup. The envelope grew to 0.328 m deep and 0.524 m tall.

Base triangles rose from 1528 to 2004 in the second quality pass, and the band moved from 1400–1700 to 1800–2250. The solid cup became an open, walled cup with a drip boss. The 26 mm haft stub became a 0.20 m haft with a wrap. The three flame cones became lathed teardrops. The envelope grew from 0.293 m to 0.322 m in depth and from 0.424 m to 0.485 m in height, because the torch now stands up out of its cup.

DECIMATE COLLAPSE triangle counts are not identical across series. The gate is a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction is closed-form and stone tones are seeded, so repeated runs on one binary print identical measurements. Export byte counts differ by a few bytes across series (glTF serializer), not a gated axis.

Hygiene#

Recomputed from the generated mesh, not asserted about the script.

AxisDeclaredMeasured (all three)
Non-manifold edges00
Loose verts / edges0 / 00 / 0
Doubles merged at 1e-500
Zero-area faces00
N-gons00
Coplanar disjoint face pairs00
Grounded: zminwithin 1e-4 of 00.0000

Joint fit, seat and bake#

AxisDeclaredMeasured (all three)
Arm-to-cup BVH gap≤ 0.008 m0.00007
Plate-to-stone BVH gap≤ 0.008 m0.00000
Plaque width vs WALL_W± 0.04 m0.2800 vs 0.280
Arm level (exit 20): the arm shell's long axis against horizontal≤ 1.0°0.000°
Plate seat (exit 21): plate front proud of the facing stones / plate back into their face≥ 0.008 m / 0.001–0.005 m0.01300 / 0.00300
Bake texel density (exit 22): smallest UV cell in baked texels≥ 12 px20.04 px (1024 px bake)

The plate-to-stone gap is measured to any stone shell. The plate is bolted to the face of the stones, not to the backing slab behind them, as it was in the first build.

Falsifiers#

Each violates one named budget. All were run on 4.5.11, 5.1.2 and 5.2.1 and returned the same code on each.

FlagBudget violatedExit
--skip-decimateLOD1 ratio band9
--stray-vertloose vertex count is 015
--lift-zbounding box zmin is 016
--float-armarm-to-cup BVH gap17
--float-plateplate-to-stone BVH gap (measured 0.07700)18
--skinny-plaqueplaque width vs WALL_W19
--droop-armarm level (the old 20 mm drop; measured 9.908°)20
--sink-plateplate seat (the first build's seat; measured proud −0.00400, seat 0.02000)21
--low-bakebake texel density (256 px bake; measured 5.01 px)22

--float-arm shortens the tube to 55 % of the plate-to-cup station vector, and the cup stays put. --float-plate moves only the plate off the stones; the arm root stays at the seated plate, so 17 still passes. --skinny-plaque scales the back plaque and courses to 0.55 · WALL_W.

Run#

blender --background --python wall_torch.py --
blender --background --python wall_torch.py -- --skip-decimate
blender --background --python wall_torch.py -- --stray-vert
blender --background --python wall_torch.py -- --lift-z
blender --background --python wall_torch.py -- --float-arm
blender --background --python wall_torch.py -- --float-plate
blender --background --python wall_torch.py -- --skinny-plaque
blender --background --python wall_torch.py -- --droop-arm
blender --background --python wall_torch.py -- --sink-plate
blender --background --python wall_torch.py -- --low-bake
blender --background --python wall_torch.py -- --output torch.png

Smoke passes no flags.

Exit codes#

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path. 15–19 are the hygiene and joint-fit family. 20–22 are this piece's own.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3Mesh did not build / no UV layer
4Base triangle count outside range
5Material count ≠ 5 distinct slots, or stone/metal/flame/pitch faces missing
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 (--stray-vert lands here)
16Grounded zmin (--lift-z)
17Arm-to-cup gap (--float-arm)
18Plate-to-stone gap (--float-plate)
19Plaque width (--skinny-plaque)
20Arm not level (--droop-arm)
21Plate not seated on the stone face (--sink-plate)
22Bake texel density below floor (--low-bake)
23Gallery asset-quality violation (render path; the floors' 11 is remapped here)

Source

showcase/wall-torch/wall_torch.py View the script on GitHub →
A wall torch on warm-lit stone: a black iron plate, level arm and curved brace hold a cup, from which a leaning charred haft rises to a cluster of orange flame tongues.