Skip to content

feat: add triangulate-tangents example witnessing the mikktspace contract#87

Merged
TMHSDigital merged 1 commit into
mainfrom
feat/triangulate-tangents
Jul 20, 2026
Merged

feat: add triangulate-tangents example witnessing the mikktspace contract#87
TMHSDigital merged 1 commit into
mainfrom
feat/triangulate-tangents

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

Summary

The fourth direction of the game-asset pipeline arc (from the ROADMAP pool): a machined buckler — lathed dome with polar UVs, unwrapped rim/underside strips, fanned back cap — verifying the tangent-space contract a game engine's normal mapping depends on.

Contract witnessed

  1. Deterministic triangulation + engine basis. calc_loop_triangles yields the closed-form count (720 = 2×336 quads + 48-cap fan); every loop tangent is unit (err 1.34e-07) and orthogonal to its normal (err 5.96e-08); bitangent exactly sign * (n × t) (err 0.0). One ngon anywhere and calc_tangents aborts the whole call — the cap is a fan for that reason (caught live during authoring).
  2. Tangents follow the UVs. On smooth-field triangles the per-loop frames match the independently derived edge/UV-delta formula within welding tolerance (measured 2.27e-06, tol 0.15); flips off seams are illegal (0 measured). At UV seams orientation is implementation-defined (42 flips, 9 at chart seams — identical on both versions). Also caught live: planar UVs on the cylindrical rim wall are degenerate — tangents collapsed onto normals (dot 0.998), fixed by unwrapping strips.
  3. The stale-handle hazard (the star finding). A MeshUVLoopLayer handle held across calc_tangents() dangles: on 4.5.11 reads return garbage — measured as 471 phantom flips + 385 phantom seam positions, exit 0, a silent wrong answer. On 5.1.2 the same stale read survives by luck. The mikktspace math is byte-identical on both versions; the apparent 471-vs-9 "version divergence" I first measured was entirely the corrupt handle.

Version witness: mikktspace output identical on 4.5.11 and 5.1.2 (same weld deviations, same seam flips); calc_tangents(uvmap=) signature stable; the lifetime hazard is the only behavioral split.

Falsification evidence (temporary breaks in scratch copies, restored after measurement):

Probe Break Exit Measured
A du/dv swapped in the independent formula (5.1.2) 7 weld err 1.459
B outer profile ring dropped (5.1.2) 3 624 tris vs 720
C re-fetch removed (4.5.11) 0 (silent corruption) 471 phantom flips, 385 phantom seams; same code on 5.1.2 reads correct values by luck

Contact-sheet gate: triangulate-tangents-contact-sheet.webp — verdicts: stage darkness ✓, wedge warmth ✓, subject fill ✓, saturation ✓ (monochrome machined steel, bmesh-gear's sibling), thumbnail legibility ✓ (disc + cradle reads instantly). Mean luminance 139.7 vs calibration 139.4 / 100.1 / 143.4 — mid-range.

Proven by live run vs inspection:

  • Live run, local: check exit 0 on .scratch/blender-5.1.2-windows-x64/blender.exe (Blender 5.1.2, hash ec6e62d40fa9) and .scratch/blender-4.5.11-windows-x64/blender.exe (Blender 4.5.11 LTS, hash 4db51e9d1e1e) — identical printed values (tris=720 unit=1.34e-07 dot=5.96e-08 btn=0 weld=2.27e-06 clean_flips=0 seam_flips=42 chart_flips=9). Render exit 0 on both binaries (EEVEE), pixels iterated (3 drafts: zebra/clip fix, cradle, hot-region check). All probes live as tabled.
  • Live run, CI: both smoke series on the PR head SHA — see checks.
  • Inspection only: nothing claims otherwise.

Test plan

  • Validate, validate-manifest, validate-counts (README substring 27 examples)
  • Blender 5.1 smoke + Blender 4.5 smoke — new step runs examples/triangulate-tangents/triangulate_tangents.py --
  • Ecosystem drift; Socket Security
  • Local binaries: .scratch/blender-5.1.2-windows-x64/blender.exe (5.1.2), .scratch/blender-4.5.11-windows-x64/blender.exe (4.5.11 LTS)

Integration: example dir + README, gallery.json entry (alt 126 chars, no cap truncation), plugin manifest, smoke step, top-level README ("Game asset pipeline" category now 4, counts 26→27), generated pages read character-by-character, ROADMAP drained (subject shipped) and restocked (UV-handle lifetime snippet).

🤖 Generated with Kimi Code

…ract

A game engine's normal mapping lives or dies by the tangent frame: the
basis must be unit and orthogonal, follow the UVs, and carry a consistent
bitangent sign. This example verifies all of it on a machined buckler
with three UV chart styles, against the independently derived edge/UV
formula, plus the two hazards that actually bite: calc_tangents aborting
on any ngon, and a MeshUVLoopLayer handle dangling across the call on 4.5
(471 phantom flips, silent exit 0) while the mikktspace math is
byte-identical on 4.5.11 and 5.1.2.

Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci labels Jul 20, 2026
@TMHSDigital
TMHSDigital merged commit 7656c7f into main Jul 20, 2026
9 checks passed
@TMHSDigital
TMHSDigital deleted the feat/triangulate-tangents branch July 20, 2026 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant