Skip to content

feat: degenerate-bevel-weld example#112

Merged
TMHSDigital merged 1 commit into
mainfrom
feat/degenerate-bevel-weld
Jul 23, 2026
Merged

feat: degenerate-bevel-weld example#112
TMHSDigital merged 1 commit into
mainfrom
feat/degenerate-bevel-weld

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

Degenerate bevel weld — the half-dimension collapse that ships to disk

Second of two examples. Isolates the bevel-threshold hazard found authoring gltf-export-roundtrip (its count check caught a 36-vertex weld on a thin crate part): author a bevel width against one box, reuse it on a thinner one, and at offset == min_dimension / 2 the band pinches into zero-area faces — which then cross the export boundary.

The contract (all closed form or independently re-derived)

A 1.6 × 0.4 × 1.0 slab (min dim 0.4 → half = 0.2), every edge beveled at 3 segments:

  • Threshold: offset 0.10 → 0 zero-area faces; offset 0.20 (== min/2) → exactly 12 == 4 min-axis edges × 3 segments. min_area collapses 3.241e+05× (5.783e-04 → 1.784e-09).
  • Collapse witness: coincident-position verts == 16 == 4 edges × (segments + 1), re-derived by 6-decimal position grouping — the loops an engine merge-by-distance welds.
  • Export crossing: a stdlib re-parse of the GLB recomputes every triangle area from raw POSITION + indices buffers: 32 degenerate triangles ship (12 collapsed faces triangulated — MEASURED regression constant like curve-bevel-arc's EXPECT_VERTS, re-measure recipe in check()) vs 0 for the safe mesh. The exporter ships every loop (384 positions == 384 loops) — nothing warns you.

Probed five regimes to ground these (clamp on/off × safe/half/over, bmesh vs modifier path, OBJ vs GLB, smooth vs flat shading) before locking the contract; the bmesh clamped path gives the cleanest closed forms (12 / 16 / 32). One probe-side bug found and fixed along the way: per-object exports need use_selection or meshes[0] is the wrong mesh.

Falsification evidence

probe expected observed
safe offset pushed to the threshold (0.10 → 0.20) exit 3 exit 3 ("safe bevel produced 12 zero-area faces")
degenerate offset below threshold (0.20 → 0.19) exit 4 exit 4 ("produced 0 zero-area faces, closed form 12")
coincident closed form corrupted (16 → 15) exit 6 exit 6 ("coincident-position count off the closed form")
GLB census corrupted (32 → 24) exit 8 exit 8 ("GLB carries 32 …, expected 24")

All restored; final check exits 0 with degenerate-bevel-weld OK threshold@0.2 collapse 12f/16v ships 32 degenerate tris in the GLB.

Framing (measured, gallery_framing silhouette)

fill 0.794x / 0.356y, margins L0.100 R0.106 B0.133 T0.339 — in band on 5.1.2 and identical on 4.5.11. Gate wired via check_framing (exit 10).

Visual-pass log (3 render iterations)

  1. Full size + helper numbers: d1 in band immediately (0.806x) but trays sat low with a dead top third; d2 raised camera + dropped aim (balance); d3 trimmed seam markers 0.03 → 0.022 so they mark rather than decorate (final).
  2. Contract visible: the dual tray carries working and broken in one frame — soft rounded bevel vs collapsed knife edge, with an emissive seam marker at every zero-area face centroid placed from live mesh data (a change in the data moves the markers).
  3. Thumbnail (preview at 400 px): rounded-vs-knife edge plus markers read in under a second.
  4. Playwright (full-page captures): index at 1280 and 390 plus the detail page — new card renders, teaches/witnesses/README links intact; generated HTML read character by character. The two new examples from this arc sit side by side in the grid with no visual duplication (color fans vs teal trays).
  5. Contact sheet: composite vs armature-bend 70.8 / damped-track-aim 44.5 / bmesh-gear 77.7 — candidate 53.8, inside the calibration range (not a fourth bright card). Stage darkness ✓, wedge warmth ✓, fill comparable ✓, teal metal saturation in family ✓, thumbnail legibility ✓.

Runtime

Local binaries: .scratch/blender-5.1.2-windows-x64/blender.exe (reports Blender 5.1.2), .scratch/blender-4.5.11-windows-x64/blender.exe (reports Blender 4.5.11 LTS). Check-only exit 0 on both with byte-identical output (same counts, same min_area values, same GLB census — no version divergence). Sub-second runtime; the new smoke step is check-only, so smoke runtimes are unaffected. CI floats within series per CLAUDE.md.

Live-run vs inspection-only

  • Live run (5.1.2): five probe scripts (regime exploration), check runs, 4 falsification probes, 3 render iterations + gate, webp conversion, contact sheet, gallery regen, Playwright captures.
  • Live run (4.5.11): probe4 (GLB census), check-only, render path — identical numbers throughout.
  • Inspection only: the claim that engine-side merge-by-distance is where the welded loops bite (witnessed here by the coincident-position census and the shipped degenerate tris, not by running an engine).

Test plan

  1. blender --background --python examples/degenerate-bevel-weld/degenerate_bevel_weld.py -- → exit 0, all five measured lines as above (4.5 LTS and 5.1).
  2. -- --output out.png → framing lines print framing_ok, exit 0, artifact written.
  3. Any of the four falsification edits → the documented exit code.

Isolates the half-dimension bevel collapse found authoring
gltf-export-roundtrip: a slab beveled at offset == min_dim/2 pinches
its band into exactly 12 zero-area faces (4 min-axis edges x 3
segments) with 16 coincident verts (4 x (segments+1)) and min_area down
3.2e5x, while the safe offset yields none. The export angle is the
point: a stdlib re-parse of the shipped GLB recomputes every triangle
area from raw POSITION + indices buffers and counts 32 degenerate
triangles crossing the boundary (MEASURED regression constant,
re-measure recipe in check) versus 0 for the safe mesh — the loops an
engine merge-by-distance welds. Probed five regimes to ground the
closed forms (clamp on/off x safe/half/over, modifier vs bmesh path).
Byte-identical on 4.5.11 and 5.1.2. Falsified: safe offset at threshold
(exit 3), degenerate offset below it (exit 4), coincident closed form
corrupted (exit 6), GLB census off (exit 8). Render path gated with
check_framing (0.794x/0.356y, margins >= 0.100); dual tray with
live-data seam markers. Count prose bumped to 40 across
README/CLAUDE/AGENTS.

Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci labels Jul 23, 2026
@TMHSDigital
TMHSDigital merged commit 3eb3eac into main Jul 23, 2026
9 checks passed
@TMHSDigital
TMHSDigital deleted the feat/degenerate-bevel-weld branch July 23, 2026 23:49
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