Skip to content

Commit d759549

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): rebuild triangulate-tangents buckler as a machined shield
The gallery review judged the hero weak. The face was covered in wobbly ring ripples that read as a shading bug, which is the worst look for an example about tangent-space correctness. The object also read as a dish or speaker cone with a murky grey interior. The ripples came from a Generated- coordinate wave texture with distortion 3.0, so they were never UV-driven. The open ring at the apex also rendered as a black hole. The buckler is now one closed lathe mesh: a pointed turned boss, a flange, a cosine dome, a cut groove and a rolled rim. An apex fan closes the top and is counted in the closed-form triangle count (4992). The mesh is smooth-shaded with sharp edges at the machined breaks, so mikktspace now really welds frames. The measured weld is 3.27e-02, inside the unchanged 0.15 tolerance. Materials are assigned per ring: brass boss and rim, blued-steel flange, turned-steel dome, leather back, plus steel rivets. The lathe grooves are a bump read straight off UV v, so they are exact circles. The anisotropic streak follows the UV-map Tangent node. The shield is turned three-quarter so the boss projects and the dome reads convex. The render path now also runs the asset-quality floors (exit 11). Every check is unchanged and passes, with identical numbers on 4.5.11, 5.1.2 and 5.2.1. The falsifiers were re-proven on the new geometry. The VISUAL-STYLE numeric-only precedent sentence now states the actual mechanism. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
1 parent 15ba0a3 commit d759549

11 files changed

Lines changed: 501 additions & 223 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,7 @@ blend skinning read back from the mesh's own deform layer.
916916
</tr>
917917
<tr>
918918
<td width="46%" valign="middle">
919-
<a href="examples/triangulate-tangents/"><img src="examples/triangulate-tangents/preview.webp" alt="Triangulate and tangents: a machined steel buckler on a dark cradle - brushed grooves circulating the boss with an anisotropic sweep - proving the mikktspace tangent field a normal map depends on" /></a>
919+
<a href="examples/triangulate-tangents/"><img src="examples/triangulate-tangents/preview.webp" alt="Triangulate and tangents: a machined steel buckler on a walnut stand - pointed brass boss, domed face cut with concentric lathe grooves, riveted brass rim - proving the mikktspace tangent field a normal map depends on" /></a>
920920
</td>
921921
<td valign="middle">
922922

‎docs/VISUAL-STYLE.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ the default stage.
9494
describe — and the falsification pass is satisfied by naming why no visual
9595
difference exists. Precedents: `vertex-weight-limit` (the prune preserves
9696
the pose by design, so the 4-influence cap is numeric) and
97-
`triangulate-tangents` (the ring sweep is UV/bump-driven, so a
98-
tangent-field break does not move pixels). This is for contracts that
97+
`triangulate-tangents` (the lathe grooves are a bump read off UV v and
98+
the anisotropic streak follows the renderer's own UV-map tangent, so a
99+
break in the `calc_tangents()` loop data the check reads does not move
100+
pixels). This is for contracts that
99101
genuinely cannot be seen, not for scenes that were not designed hard
100102
enough: where a visual witness is possible, it is required.
101103
- Presentation may be staged; evidence may not. When the witnessed artifact
38.1 KB
Loading
-1.89 KB
Loading
-2.33 KB
Loading

‎docs/gallery/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,7 @@ <h2><a href="vertex-weight-limit/">vertex-weight-limit</a></h2>
651651
</article>
652652
<article class="card" data-tags="mesh uv">
653653
<a class="card-media" href="triangulate-tangents/" tabindex="-1" aria-hidden="true">
654-
<img src="assets/triangulate-tangents-hero.webp" alt="A round silver buckler leaning back on a low stand, its face brushed in circular grooves with an anisotropic highlight." loading="lazy" decoding="async" />
654+
<img src="assets/triangulate-tangents-hero.webp" alt="A steel buckler on a walnut stand, turned three-quarter: pointed brass boss, domed face cut with two bands of concentric lathe grooves, brass rim ringed with steel rivets." loading="lazy" decoding="async" />
655655
</a>
656656
<div class="card-body">
657657
<h2><a href="triangulate-tangents/">triangulate-tangents</a></h2>

‎docs/gallery/triangulate-tangents/index.html‎

Lines changed: 234 additions & 103 deletions
Large diffs are not rendered by default.

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@
380380
"name": "triangulate-tangents",
381381
"dir": "examples/triangulate-tangents",
382382
"teaches": "A machined buckler verifying the tangent-space contract a game engine's normal mapping depends on. Deterministic triangulation, unit orthogonal tangent frames, and the edge/UV-delta formula matching mikktspace within welding tolerance.",
383-
"alt": "A round silver buckler leaning back on a low stand, its face brushed in circular grooves with an anisotropic highlight.",
383+
"alt": "A steel buckler on a walnut stand, turned three-quarter: pointed brass boss, domed face cut with two bands of concentric lathe grooves, brass rim ringed with steel rivets.",
384384
"witnessesFix": "calc_tangents aborts on any ngon; planar UVs on a cylindrical wall collapse tangents onto normals; a MeshUVLoopLayer handle held across calc_tangents dangles on 4.5 (471 phantom flips, silent exit 0) while the mikktspace math is byte-identical on both versions.",
385385
"hero": "docs/gallery/assets/triangulate-tangents-hero.webp",
386386
"preview": "examples/triangulate-tangents/preview.webp",

‎examples/triangulate-tangents/README.md‎

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Triangulate + Tangents
22

3-
A runnable example that builds a machined buckler — lathed dome with polar
4-
UVs, unwrapped rim and underside strips, a fanned back cap — and verifies
3+
A runnable example that builds a machined buckler — one closed lathe mesh:
4+
turned boss, flange, domed face and rolled rim with polar UVs, unwrapped rim
5+
and underside strips, fanned back and apex caps — and verifies
56
the tangent-space contract a game engine's normal mapping depends on,
67
following [`mesh-editing-and-bmesh`](../../skills/mesh-editing-and-bmesh/SKILL.md).
78

@@ -14,47 +15,58 @@ the exported normal maps are baked against.
1415
hazards around it.
1516

1617
- **Deterministic triangulation and the engine basis.** `calc_loop_triangles`
17-
yields the closed-form count (two tris per quad + the cap fan, 720), and
18-
every loop tangent is unit length (err 1.3e-07) and exactly orthogonal to
19-
its loop normal (err 6.0e-08), with the bitangent exactly
18+
yields the closed-form count (two tris per quad + the two cap fans, 4992),
19+
and every loop tangent is unit length (err 1.3e-07) and exactly orthogonal
20+
to its loop normal (err 9.9e-08), with the bitangent exactly
2021
`bitangent_sign * (normal x tangent)` (err 0.0). One ngon anywhere in the
21-
mesh and `calc_tangents` **aborts the whole call** — the back cap is an
22-
explicit fan for exactly that reason.
22+
mesh and `calc_tangents` **aborts the whole call** — both caps are
23+
explicit fans for exactly that reason.
2324
- **The tangent frame follows the UVs.** On smooth-field triangles the
2425
per-loop tangents match the independently derived edge/UV-delta formula
25-
within mikktspace's vertex-welding tolerance (measured 2.3e-06, tol 0.15);
26+
within mikktspace's vertex-welding tolerance (measured 3.3e-02, tol 0.15 —
27+
the face is smooth-shaded with sharp edges at the machined breaks, so
28+
mikktspace genuinely welds frames across shared vertices);
2629
a flipped frame inside a smooth field is never legal (0 measured). At UV
27-
seams the frame orientation is implementation-defined — 42 seam flips, 9
28-
at chart seams, identical on both versions — and planar UVs on a
30+
seams the frame orientation is implementation-defined — 150 seam flips, 12
31+
at chart seams, identical on all three versions — and planar UVs on a
2932
cylindrical wall are degenerate: they collapse the tangent onto the
3033
normal (dot 0.998, measured and fixed by unwrapping strips).
3134
- **The reference-lifetime hazard (the real divergence).** A
3235
`MeshUVLoopLayer` handle held across `calc_tangents()` dangles: on
3336
Blender 4.5 reads through it return tangent floats, not UVs — measured
3437
while authoring as a phantom **471 flipped frames and 385 phantom seam
35-
positions, exit 0, a silent wrong answer**. On 5.1 the same stale read
36-
survives by memory-layout luck. The mikktspace math itself is
37-
byte-identical on 4.5.11 and 5.1.2; the apparent version divergence was
38+
positions, exit 0, a silent wrong answer** (on the current buckler: 2829
39+
phantom seam flips over 2497 phantom seam positions, still exit 0). On
40+
5.1 and 5.2 the same stale read survives by memory-layout luck. The
41+
mikktspace math itself is identical on 4.5.11, 5.1.2 and 5.2.1; the
42+
apparent version divergence was
3843
entirely the corrupt handle. Never hold layer handles across
3944
CustomData-reallocating calls — re-fetch by name.
4045

4146
**What each check catches on failure:** a remeshed asset breaking the
4247
triangulation count (probe: dropped profile ring, exit 3, 624 vs 720); a
4348
wrong tangent formula in the independent derivation (probe: swapped du/dv,
44-
exit 7, weld 1.459); and the stale-handle read (probe: no re-fetch — corrupt
45-
measurements on 4.5, correct values on 5.1 by luck).
49+
exit 7, weld 1.460); and the stale-handle read (probe: no re-fetch — corrupt
50+
measurements on 4.5, correct values on 5.1/5.2 by luck).
4651

47-
**Version witness:** mikktspace output is identical on Blender 4.5.11 LTS
48-
and 5.1.2 (same weld deviations, same seam flips). The `calc_tangents(uvmap=)`
52+
**Version witness:** mikktspace output is identical on Blender 4.5.11 LTS,
53+
5.1.2 and 5.2.1 LTS (same weld deviation, same seam flips). The `calc_tangents(uvmap=)`
4954
signature is stable. The lifetime hazard above is the only behavioral split,
5055
and it corrupts measurements rather than raising.
5156

52-
The render shows the buckler on its cradle: brushed grooves circulating the
53-
boss with the anisotropic sweep riding them — the circulating tangent field
54-
made visible. The staging is derived from the posed mesh: the stand rests on
55-
the floor, the rim's lowest vertex sits 1 cm into its top, and a kickstand
56-
strut runs from the buckler's back (found by a local-space ray) to the floor
57-
behind it.
57+
The render shows the buckler turned three-quarter on a walnut stand: a
58+
pointed brass boss on a riveted blued-steel flange, a turned-steel dome, and
59+
a brass rolled rim ringed with steel rivets. Materials are assigned per lathe
60+
ring. The dome's lathe grooves are a bump read straight off UV v (the
61+
radius), so they are exact circles, with no procedural distortion. Its
62+
anisotropic highlight follows the renderer's own UV-map tangent, so it
63+
fans out radially from the boss. This is a numeric-only contract
64+
(docs/VISUAL-STYLE.md): the renderer derives its tangents from the UVs, so a
65+
break in the `calc_tangents()` loop data the check reads does not move
66+
pixels. The staging is derived from the posed mesh: the stand rests on the
67+
floor, the rim's lowest vertex sits 1 cm into its top, and a kickstand strut
68+
runs from the buckler's back (found by a local-space ray) to the floor behind
69+
it, angled away from the camera.
5870

5971
## Run
6072

@@ -73,7 +85,8 @@ blender --background --python triangulate_tangents.py -- --output buckler.png --
7385
## Exit codes
7486

7587
Per-script sequential checks. `9` is a valid check code; there is no rule
76-
against it. `10` is the shared framing helper.
88+
against it. `10` is the shared framing helper, `11` the shared asset-quality
89+
helper.
7790

7891
| Code | Meaning |
7992
| --- | --- |
@@ -87,6 +100,7 @@ against it. `10` is the shared framing helper.
87100
| 7 | Tangents deviate from the edge/UV closed form |
88101
| 8 | Flipped tangent inside a clean triangle |
89102
| 10 | Gallery framing violation; also `--output` produced no file |
103+
| 11 | Asset-quality floor violation (`--output` only; shared helper) |
90104

91105
The `blender-smoke` workflow runs the check on Blender 5.2 LTS and 4.5 LTS
92106
(5.1 on the weekly cron, the `needs-5.1` PR label, or manual dispatch).
-1.78 KB
Loading

0 commit comments

Comments
 (0)