Skip to content

Commit 2f4f4e0

Browse files
TMHSDigitalclaude
andauthored
fix(examples): rebuild triangulate-tangents buckler as a machined shield (#261)
* 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> * fix(examples): regenerate triangulate-tangents asset sheet against the new reference set Adds a SELECT row for the buckler to scripts/asset_sheet.py. It matches the Buckler lathe mesh and Buckler.Rivets only. The walnut stand and kickstand strut are display staging, not part of the asset, so they are excluded. The asset sheet is re-rendered with the committed tooling against the reference set from #257 (collision-hull-proxy, socket-attach-points, vertex-color-ao, wheelbarrow, apothecary-shelf). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> --------- Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent a217057 commit 2f4f4e0

12 files changed

Lines changed: 502 additions & 223 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,7 @@ those post-limit weights onto the arm as a colour attribute.
917917
</tr>
918918
<tr>
919919
<td width="46%" valign="middle">
920-
<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>
920+
<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>
921921
</td>
922922
<td valign="middle">
923923

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