Skip to content

Commit 33051ef

Browse files
TMHSDigitalclaude
andcommitted
fix(showcase): turn wooden-ladder rungs and give its wood readable grain
Inside the exact 16-shell parts budget: each rung gains one mid-span ring, so the barrel shoulders in at 0.90x (RUNG_SHOULDER) and swells to 1.14x (RUNG_SWELL) - a lathe-turned round instead of a dowel. Tenons unchanged, so clearance/engagement/pitch read the same; breakout 0.03780 -> 0.03787. Wood shader: grain scale 34 -> 95 and streak stretch 0.94 -> 0.985 so streaks run across a 50 mm stile (it rendered flat tan), plus a short-range AO grime term at the rung housings and under the iron sleeves. Evidence (live, local): - Blender 5.2.1 LTS (.scratch): default exit 0, base_tris 2680 (budget 1800-2800), r1 0.5000 r2 0.2194, parts 16, wood 684 / metal 736 faces, bbox unchanged, collider 24; --output framing fill y=0.883. - Blender 4.5.11 LTS (.scratch): default exit 0, base_tris 2680, r2 0.2194. - check_falsifier_targets.py --run, both binaries: all wooden-ladder falsifiers return their codes (9, 15, 15, 16, 17, 18, 19). - Contact sheet: luma 0.211 / stage 0.202 / warmth +0.089 / sat 0.172. - Asset sheet: still the plainest object in the lineup at sheet scale; the turned rungs read up close, not at thumbnail size. Honest verdict: improved, not cleared. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent e2f3b9d commit 33051ef

6 files changed

Lines changed: 56 additions & 18 deletions

File tree

1.83 KB
Loading
1.79 KB
Loading
218 Bytes
Loading

‎showcase/wooden-ladder/README.md‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Wooden ladder
22

33
A showcase piece, not an example. Procedural timber ladder: two stiles
4-
tapered from 64 mm to 50 mm, raked 12°, six turned rungs (22 mm tenon,
5-
barrel thicker only in the clear span), iron ferrules at the top, and
4+
tapered from 64 mm to 50 mm, raked 12°, six turned rungs (22 mm tenon, a
5+
barrel that shoulders in just under its radius and swells 14 % at
6+
mid-span, the profile a lathe leaves on a ladder round), iron ferrules at the top, and
67
level shoe plates under sleeves that follow the rake. Then the shipped
78
pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain,
89
convex collider, Unity glTF export.
@@ -36,30 +37,44 @@ imported). Coplanar-pair counting matches `showcase/signpost`.
3637
Declared as named constants; every gate **recomputes** from the mesh,
3738
materials, UVs, evaluated LOD, collider, or export file.
3839

39-
| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) |
40+
| Axis | Declared | Measured (4.5.11 / 5.2.1; 5.1.2 not re-run after the turned-rung pass) |
4041
| --- | --- | --- |
41-
| Base triangles | 1800–2800 | 2536 / 2536 / 2536 |
42-
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 |
43-
| LOD2 ratio | 0.10–0.35 of base | 0.2192 / 0.2192 / 0.2192 |
44-
| Materials | exactly 2 distinct, ≥280 wood, ≥180 metal | 2 slots, 612 wood, 736 metal |
42+
| Base triangles | 1800–2800 | 2680 / 2680 |
43+
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 |
44+
| LOD2 ratio | 0.10–0.35 of base | 0.2194 / 0.2194 |
45+
| Materials | exactly 2 distinct, ≥280 wood, ≥180 metal | 2 slots, 684 wood, 736 metal |
4546
| UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 |
4647
| Outer AABB | (0.490, 0.376, 1.487) m ± 0.01 | (0.4897, 0.3760, 1.4870) |
4748
| Grounded | `abs(zmin)` ≤ 1e-4, each sole at 0, rail end ≥ 0.012 m | zmin 0, sole 0, rail 0.02037 |
4849
| Mesh hygiene | non-manifold, loose v/e, doubles at 1e-5, zero-area, n-gons, coplanar pairs all 0 | all 0 |
4950
| Parts | 16 shells (2 stiles, 6 rungs, 2 sleeves, 2 cap sleeves, 2 plates, 2 soles) | 16 (2 / 6 / 2 / 2) |
5051
| Rung depth clearance | ≥ 0.003 m inside the stile faces | 0.00800 |
5152
| Tenon engagement | ≥ 0.008 m past the stile inner face | 0.01399 |
52-
| Tenon breakout margin | ≥ 0.008 m short of the stile outer face | 0.03780 |
53+
| Tenon breakout margin | ≥ 0.008 m short of the stile outer face | 0.03787 |
5354
| Shoe bite / cover | bite ≥ 0.010 m, cover ≥ 0.016 m | 0.02000 / 0.03800 |
5455
| **Rung pitch** | 6 rungs; every rung-to-rung gap within 0.002 m of the mean | 6; 0.00000 |
5556
| Collider tris | ≤ 180 | 24 |
56-
| Export | written, size > 0 | 187524 / 187524 / 187504 bytes |
57+
| Export | written, size > 0 | 197604 / 197584 bytes |
5758

5859
Base triangles rose from **936 to 2536**. The previous mesh was two
5960
boxes, six cloned dowels, and four chrome cubes. Turned rungs (tenon
6061
and barrel in one shell) plus ferrule collars and chamfered soles added
6162
the triangles. Nothing was removed as a hidden face.
6263

64+
A later pass took them to **2680**. The bare ladder was still the plainest
65+
object in its asset-sheet lineup, and the parts budget is exact (16
66+
shells), so the detail had to live inside existing shells. Each rung gained
67+
one ring at mid-span (+24 triangles per rung): the barrel shoulders in at
68+
0.90 × its radius (`RUNG_SHOULDER`) and swells to 1.14 × (`RUNG_SWELL`).
69+
That turns a dowel into a lathe-turned round. The tenons are unchanged, so
70+
clearance, engagement and pitch read the same. The breakout margin moved
71+
0.07 mm (0.03780 → 0.03787) because the shell's worst vertex is now measured
72+
across seven rings. The wood shader also changed. The grain noise runs
73+
across the stile's 50 mm width at a readable scale (`WOOD_GRAIN_SCALE`
74+
34 → 95, streak stretch 0.94 → 0.985); before, about one noise period
75+
spanned a stile, so it rendered as flat tan. A short-range AO term darkens
76+
the rung housings and the wood under the iron sleeves.
77+
6378
The joint budgets are measured per rung against each stile, from vertex
6479
positions in the un-raked construction frame, and the reported value is
6580
the worst case. Clearance is the tenon, not the barrel: the barrel is
1.35 KB
Loading

‎showcase/wooden-ladder/wooden_ladder.py‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
BARREL_RATIO = 1.34
6666
RUNG_SEGS = 12
6767
RUNG_TENON = 0.014
68+
RUNG_SHOULDER = 0.90 # barrel radius at the shoulders, x r_barrel
69+
RUNG_SWELL = 1.14 # barrel radius at mid-span, x r_barrel
6870
# Rungs sit at one even pitch: a climber's feet find them blind, so height
6971
# jitter is a defect, not variation. Variation lives in the turning (a few
7072
# percent of barrel) and the wood tone. --drift-rungs restores the old
@@ -75,7 +77,7 @@
7577
# Per-piece wood tone jitter and grain frequency, as in shipping-crate.
7678
PLANK_TONE_JITTER = 0.28
7779
TONE_SEED = 29
78-
WOOD_GRAIN_SCALE = 34.0
80+
WOOD_GRAIN_SCALE = 95.0
7981
BAND_Z0 = 0.0
8082
BAND_H = 0.058
8183
BAND_T = 0.007
@@ -269,16 +271,23 @@ def quad(a, b, c, d):
269271

270272

271273
def add_turned_rung(bm, z, x_left, x_right, tenon, r_tenon, r_barrel, segs, mat_idx):
272-
"""One shell: thin tenon inside each stile, thicker barrel only in the clear span."""
274+
"""One shell: thin tenon inside each stile, thicker barrel only in the clear span.
275+
276+
The barrel is turned, not a plain dowel: it starts at a shoulder just
277+
under the barrel radius and swells to RUNG_SWELL at mid-span, the
278+
profile a lathe leaves on a ladder round (one extra ring per rung).
279+
"""
273280
xs = (
274281
x_left - tenon,
275282
x_left - 0.0015,
276283
x_left + 0.004,
284+
0.5 * (x_left + x_right),
277285
x_right - 0.004,
278286
x_right + 0.0015,
279287
x_right + tenon,
280288
)
281-
rs = (r_tenon, r_tenon, r_barrel, r_barrel, r_tenon, r_tenon)
289+
shoulder = r_barrel * RUNG_SHOULDER
290+
rs = (r_tenon, r_tenon, shoulder, r_barrel * RUNG_SWELL, shoulder, r_tenon, r_tenon)
282291
rings = []
283292
for x, radius in zip(xs, rs):
284293
ring = []
@@ -969,7 +978,7 @@ def wood_material(name):
969978
nt.links.new(gdir.outputs["Vector"], dot.inputs[1])
970979
squash = nt.nodes.new("ShaderNodeMath")
971980
squash.operation = "MULTIPLY"
972-
squash.inputs[1].default_value = 0.94
981+
squash.inputs[1].default_value = 0.985
973982
nt.links.new(dot.outputs["Value"], squash.inputs[0])
974983
along = nt.nodes.new("ShaderNodeVectorMath")
975984
along.operation = "SCALE"
@@ -989,10 +998,10 @@ def wood_material(name):
989998
noise.inputs["Roughness"].default_value = 0.62
990999
nt.links.new(shift.outputs["Vector"], noise.inputs["Vector"])
9911000
ramp = nt.nodes.new("ShaderNodeValToRGB")
992-
ramp.color_ramp.elements[0].position = 0.30
993-
ramp.color_ramp.elements[0].color = (0.10, 0.056, 0.028, 1.0)
994-
ramp.color_ramp.elements[1].position = 0.72
995-
ramp.color_ramp.elements[1].color = (0.33, 0.19, 0.095, 1.0)
1001+
ramp.color_ramp.elements[0].position = 0.38
1002+
ramp.color_ramp.elements[0].color = (0.055, 0.030, 0.014, 1.0)
1003+
ramp.color_ramp.elements[1].position = 0.62
1004+
ramp.color_ramp.elements[1].color = (0.34, 0.20, 0.10, 1.0)
9961005
nt.links.new(noise.outputs["Fac"], ramp.inputs["Fac"])
9971006
gain = nt.nodes.new("ShaderNodeMath")
9981007
gain.operation = "MULTIPLY_ADD"
@@ -1005,7 +1014,21 @@ def wood_material(name):
10051014
_sock(mix.inputs, "Factor_Float").default_value = 1.0
10061015
nt.links.new(ramp.outputs["Color"], _sock(mix.inputs, "A_Color"))
10071016
nt.links.new(gain.outputs["Value"], _sock(mix.inputs, "B_Color"))
1008-
nt.links.new(_sock(mix.outputs, "Result_Color"), bsdf.inputs["Base Color"])
1017+
# handling grime: short-range AO darkens where rungs enter the stiles
1018+
# and under the iron sleeves, so the joints read at thumbnail scale
1019+
ao = nt.nodes.new("ShaderNodeAmbientOcclusion")
1020+
ao.inputs["Distance"].default_value = 0.035
1021+
grime = nt.nodes.new("ShaderNodeMapRange")
1022+
grime.inputs["To Min"].default_value = 0.45
1023+
grime.inputs["To Max"].default_value = 1.0
1024+
nt.links.new(ao.outputs["AO"], grime.inputs["Value"])
1025+
dirt = nt.nodes.new("ShaderNodeMix")
1026+
dirt.data_type = "RGBA"
1027+
dirt.blend_type = "MULTIPLY"
1028+
_sock(dirt.inputs, "Factor_Float").default_value = 1.0
1029+
nt.links.new(_sock(mix.outputs, "Result_Color"), _sock(dirt.inputs, "A_Color"))
1030+
nt.links.new(grime.outputs["Result"], _sock(dirt.inputs, "B_Color"))
1031+
nt.links.new(_sock(dirt.outputs, "Result_Color"), bsdf.inputs["Base Color"])
10091032
rough = nt.nodes.new("ShaderNodeMapRange")
10101033
rough.inputs["To Min"].default_value = 0.72
10111034
rough.inputs["To Max"].default_value = 0.52

0 commit comments

Comments
 (0)