Skip to content

fix(showcase): hay-bale quality pass with five new hygiene budgets, and exit-pre-sidecar doc accuracy - #205

Merged
TMHSDigital merged 3 commits into
mainfrom
quality-pass-hay-bale-exit-pre
Sep 22, 2026
Merged

TMHSDigital merged 3 commits into
mainfrom
quality-pass-hay-bale-exit-pre

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

Quality pass over two items: one showcase piece (Track A) and one
pathology example (Track C).

Binaries — all local, exact paths and self-reported versions:
.scratch/blender-4.5.11-windows-x64/blender.exe → Blender 4.5.11 LTS ·
.scratch/blender-5.1.2-windows-x64/blender.exe → Blender 5.1.2 ·
.scratch/5.2/blender-5.2.1-windows-x64/blender.exe → Blender 5.2.1 LTS


1. showcase/hay-bale — Track A

Never had a visual quality pass (last touched by #170, a rebuild).
Inspection sheet: 18 renders at 1200 px in .scratch/qp2/hay-bale-r1/
(six orthos, two three-quarters, clay, wireframe overlay, seven joint
close-ups, ground contact), all opened and looked at. Four rounds.

Defects found, and the view that revealed each

# Part Defect Revealed by
1 twine belts Constant-size rectangles on a rounded, bulged loaf: proud at the middle of each face, swallowed at the corners, and vanishing into the loaf 2.9–8.3 mm above the floor instead of wrapping under ortho_bottom (bottom face bare, only two stub ends), ground_contact (belt terminates in mid-air), shell probe zmin=+0.00832 / +0.00294 vs loaf 0.00000
2 belts + loops The two belts 5.4 mm out of step; loop shells likewise. Cause: ridge term sin(x*24) is odd in x, so the stations sampled different surface heights shell probe; ortho_top (loop nubs at different heights)
3 loaf Nothing cinched — 0.2498 m at the belt vs 0.2585 m mid-span, and the ends (0.2469) narrower than the belt station ortho_front, ortho_top, cinch profile probe
4 whole mesh 426/426 faces smooth-shaded — erased every ridge and nap; read as a bar of soap clay_three_quarter, three_quarter_a
5 loaf ends No end nap despite the README claiming one ortho_left
6 long faces Ridge amplitude 7 mm on a 0.9 m subject — below the visible threshold all orthos
7 hitch loops Read as machined rivet heads, not knots ortho_top, wire_three_quarter
8 belt section Razor-edged flat ribbon, reads as masking tape joint_belt0_side
9 loaf bottom Convex base domed down then clamped flat by if v.co.z < 0: v.co.z = 0 ortho_bottom shading gradient

Fixes, and why each is structural

  • Build, bevel and ground the loaf first, then hang the belts. Each
    belt raycasts the finished cross-section at its own station
    (loaf_profile) and follows it at a named bite. Placing belts against
    half-built geometry and shifting everything afterwards is what left
    each one a different distance off the floor. Moving a belt now moves
    its seat with it. (1, 2, 8)
  • Every shaping term is even in x — cos, not sin. An odd term
    cannot produce mirrored members. (2)
  • The cinch is derived from BELT_XS, and the ridge fades out under
    each belt because twine compresses straw flat where it bites. Without
    that fade the ±14 mm ridge swamped the waist. (3, 6)
  • Shading decided per part: flat loaf (straw is a chunky matte
    mass), smooth cord. (4)
  • Straw detail moved to the high mesh — the bake stage already
    exists to carry it. A low mesh dense enough to model flakes directly
    would cost thousands of triangles. (5)
  • Gravity flattens the contact face via a grounded falloff, instead
    of doming down and being clamped. (9)
  • Nap damped to nothing at the face border (the bevel runs after it, and
    displacing border verts flared the corners into spikes) and keyed to
    Z strata, not radial rings — the ring form resolved at BAKE_RES
    into a pixelated bullseye on the hero.

New hygiene budgets — all recomputed from the generated mesh

Budget Exit Declared Measured (4.5.11 / 5.1.2 / 5.2.1)
Cross-shell coplanar z-fight pairs 15 0 0 / 0 / 0
Per-belt wrap zmin 16 ≤ 0.006 m 0.002628 both belts, all three
Shell count 17 exactly 5 5 / 5 / 5
Banded belt seat depth 18 every station in [0.002, 0.020] m 0.00400–0.01676 over 20/20 stations, both belts, all three
Belt mirror symmetry 19 ≤ 5e-4 m 0.000000 / 0.000000 / 0.000000
Cinch depth 19 [0.006, 0.020] m 0.01420 on all three
Loaf X × Z 19 (0.922, 0.409) ± 0.02 (0.9208, 0.4087) on all three

The z-fight count is cross-shell, not merely share-no-vertex: two
quads two steps apart on one flat cap share no vertex and are coplanar
by construction, which read 96 pairs, none of them a hazard.

Exit codes — default and every falsifier, all three binaries

Run 4.5.11 5.1.2 5.2.1 Target budget
default 0 0 0 —
--skip-decimate 9 9 9 LOD1 ratio band
--stray-vert (new) 15 15 15 hygiene: loose vertices
--lift-z 16 16 16 grounded AABB zmin
--float-belts (new) 16 16 16 per-belt wrap zmin
--slack-belt (new) 18 18 18 banded seat depth
--skew-belt (new) 19 19 19 belt mirror symmetry

Two falsifiers were re-aimed rather than widening anything:
--stray-vert parked its vertex above the bale and tripped the bbox
budget (8) first, proving nothing about hygiene — it now sits inside.
--skew-belt used a Z shift that landed a belt face coplanar with a
loaf face and tripped z-fight (15) — it now shifts along X, where the
loaf profile barely changes so every other budget stays green.
tests/check_falsifier_targets.py passes: 166 falsifiers, 26 pieces.

Triangles and determinism

Base 852 → 2012 tris (band widened 750–1100 → 1700–2400; showcase
budgets are meant to grow). Collider 78 → 156, ceiling 400 unchanged.
Every geometric figure is identical on all three binaries; only the
glTF export differs, by 8 B on 5.2.1, which is not a gated axis.

Hero and gates

Framing gate passes, check_framing called with no deviation=:
fill x=0.575 y=0.789 band=0.70..0.90, margins 0.237 / 0.188 / 0.106 / 0.106, no edge touch. Camera moved from 50 mm at 2.1 m to 70 mm
further back
(a 0.92 m subject had the near end reading half again as
large as the far one) and the yaw from −32° to −20° so the long ribbed
flank reads rather than the end.

Contact sheet: docs/gallery/contact-sheets/hay-bale-contact-sheet.webp,
against the pinned set (armature-bend, damped-track-aim,
bmesh-gear).

Criterion Verdict
Stage darkness Pass — mean luminance 75.6 inside the calibration band 44.5–77.7; dark fraction 0.196
Wedge warmth Pass after a fix. The first sheet read as a neutral grey stage against three peers that all carry a warm pool — pulling the lens back had left the wedge falling outside frame. Light brought in and down; re-shot.
Subject fill Pass — 0.789, comparable to bmesh-gear
Saturation Pass — 0.242 vs band 0.150–0.248
Thumbnail legibility Fail as a hay bale. At card size it reads as a cardboard carton or an olive ammo crate.

Stopping honestly

Four inspection rounds; past the three-round limit. Form, binding and
hygiene are right and locked; the surface is not — it still reads as
a wrapped parcel, not straw. That is a material and bake problem, not a
geometry one: straw needs directional fibre and colour break-up that a
256 px tangent-space normal off a smooth high mesh cannot deliver.
Recorded in the piece README under Known gap so the next run does
not re-litigate the silhouette. The stale still was re-rendered because
the geometry changed and leaving it would misrepresent the asset.

Incidental

The piece deleted no temp export, and Blender's TMPDIR resolves to the
working directory on a portable build, so every run left a .glb in the
repo root (39 accumulated during this pass). Fixed here; the same
pattern is in all 28 showcase pieces
and is left for a follow-up
rather than touched in a quality-pass PR.


2. examples/exit-pre-sidecar — Track C

Contract frozen. Documentation only.

Correctness bug found in the docs, not the contract. The module
docstring read "Catalog min_version 5.0 is wrong — the floor is
5.1."
The catalog has carried min_version: 5.1 since the example's
introducing commit (bb5a2df), so the note was never true and would
send a reader to "fix" a correct row. Removed.

Re-verification against all three binaries. The documented behaviour
is correct: hasattr(bpy.app.handlers, "exit_pre") is False on
4.5.11 LTS, True on 5.1.2 and 5.2.1 LTS — and exit_pre is the only
exit- or quit-related name in bpy.app.handlers on the versions that
have it, so there is no older spelling to fall back to.

README improvements: states the contract and the bug class it
catches (atexit for teardown work); adds the 4.5 and 5.2
bpy.app.handlers API links, noting the 4.5 page lists no exit_pre;
explains that exiting 77 on 4.5 is correct by design and what stops
a skip spreading (min_version 5.1, the runner failing a skip above the
floor, summarize.py reddening an all-skip job); and enumerates the
falsifiers in a table with what each breaks, its script exit and how the
harness fails — naming --write-in-main and --atexit-instead as the
two an existence-only check would pass, which is why the catalog row
carries sidecar_contains.

Contract, assertions and exit codes unchanged. Identical before and
after:

Run 4.5.11 5.1.2 5.2.1
default (harness) SKIP PASS PASS
--silent-handler — — harness FAIL
--no-handler — — harness FAIL
--wrong-text — harness FAIL harness FAIL
--write-in-main — — harness FAIL
--atexit-instead — harness FAIL harness FAIL
--force-run (script) 2 — —
default (script) 77 — —

3. showcase/README.md conventions

Three conventions every future piece inherits, each from a defect above
that no existing budget could see: even shaping terms and mirror
symmetry
(19); wrappers follow the host's profile, and the host is
finished first
(16/18), including per-wrapper-shell grounding because
the AABB gate stays green when the host grounds the box; and
station-local seat measurement — never nearest-surface distance in a
concave waist, and bin by rounding, never flooring, or a station on a
bin boundary splits its inner and outer rings. Plus the cross-shell
sharpening of the z-fight budget.


Proven by live run vs. inspection

Live run on all three binaries: every exit code, every measured
budget figure, the version claims in §2, the framing gate, determinism
across versions, and the temp-export cleanup. Inspection only: the
contact-sheet verdicts are my judgement of the committed composite, and
the "reads as a parcel" verdict in §1 is a judgement call, not a
measurement.

Inspection renders stay in .scratch/, which is gitignored.

🤖 Generated with Claude Code

TMHSDigital and others added 3 commits September 22, 2026 08:30
…gets

The inspection sheet found nine defects that every shipped budget passed.
The belts were constant rectangles hung on a rounded, bulged loaf, so
they stood proud at the middle of each face, were swallowed at the
corners, and stopped 2.9-8.3 mm above the floor with a visible notch --
while the AABB zmin gate stayed green because the loaf grounded the box.
The two belts were 5.4 mm out of step because the loaf's ridge term was
odd in x. Nothing cinched: the waist measured 0.1 mm. All 426 faces were
smooth-shaded, which erased every ridge and nap and left a bar of soap.

Fixed structurally, not by nudging:

- Build, bevel and ground the loaf FIRST, then raycast its finished
  cross-section per belt station and follow that profile at a named
  bite. Moving a belt now moves its seat with it.
- Every closed-form shaping term is even in x (cos, not sin), so the two
  stations sample identical surface heights.
- The cinch is derived from BELT_XS, and the ridge fades out under each
  belt because twine compresses straw flat where it bites.
- Shading decided per part: flat loaf, smooth cord.
- Straw detail moved to the high mesh, where the bake stage already
  exists to carry it onto the low mesh.

Five budgets so none of it can come back, all recomputed from the
generated mesh: cross-shell coplanar z-fight pairs (15), per-belt wrap
zmin (16), shell count (17), banded per-station belt seat depth (18),
belt mirror symmetry and cinch depth (19).

Four new falsifiers, each landing on the budget it targets, proven on
4.5.11, 5.1.2 and 5.2.1: --stray-vert 15, --float-belts 16,
--slack-belt 18, --skew-belt 19. --stray-vert parks its vertex inside
the bale because above it the bbox budget fired first; --skew-belt
shifts along X because a Z skew tripped the z-fight budget instead.

Also deletes the temp glTF once its size is measured: Blender's TMPDIR
resolves to the working directory on a portable build, so every run left
a .glb in the repo root.

Geometry is identical on all three binaries. The piece still reads as a
wrapped parcel rather than straw at hero scale; that is recorded in the
README as a material and bake problem, not a geometry one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
…e falsifiers

The module docstring told readers the catalog's min_version was 5.0 and
wrong. It has been 5.1 since the example's introducing commit, so the
note was never true and sent anyone who read it to "fix" a correct
catalog row.

Re-verified the version claim against all three binaries:
hasattr(bpy.app.handlers, "exit_pre") is False on 4.5.11 LTS and True on
5.1.2 and 5.2.1 LTS, and exit_pre is the only exit- or quit-related name
in bpy.app.handlers on the versions that have it -- so there is no older
spelling to fall back to. The documented behaviour is correct.

README also now:

- states the contract and why it matters (atexit is the bug class this
  catches), with the 4.5 and 5.2 bpy.app.handlers API links
- explains that exiting 77 on 4.5 is correct by design, and what stops a
  skip from spreading: min_version 5.1, the runner failing a skip above
  the floor, and summarize.py reddening an all-skip job
- enumerates the falsifiers in a table with what each breaks, its script
  exit and how the harness fails, and names --write-in-main and
  --atexit-instead as the two an existence-only check would pass, which
  is why the catalog row carries sidecar_contains

Contract, assertions and exit codes unchanged. Verified before and after
on 4.5.11, 5.1.2 and 5.2.1: default SKIP/PASS/PASS, all five harness
falsifiers FAIL, --force-run exits 2 on 4.5, plain default exits 77.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
…tions

Three conventions every future piece inherits, each from a defect the
hay-bale pass found that no existing budget could see.

Even shaping terms and mirror symmetry (19): where a prop has mirrored
members, every closed-form term in the host's shaping function must be
even in the mirrored axis. hay-bale used sin(x*24), which is odd, so its
two belt stations sampled different surface heights and the wraps
shipped 5.4 mm out of step. Pair the mirrored shells and compare.

Wrappers follow the host's profile, and the host is finished first
(16/18): build, bevel and ground the host, then hang the wrapper on the
finished surface by raycasting the host's own cross-section. A constant
rectangle on a rounded host stands proud at the middle of each face and
is swallowed at the corners. Assert per wrapper shell that the loop
passes under the host, because the AABB zmin gate stays green when the
host grounds the box.

Seat depth is measured station-locally, along the vertex's own direction
from the station axis, never by nearest-surface distance: a wrapper in a
concave waist has its nearest host face on the bulge shoulder at a
neighbouring station. Bin by rounding to the nearest station, never by
flooring, or a station on a bin boundary splits its inner and outer
rings and the inner-only bin reports the outer offset as the seat depth.

Also sharpens the z-fight budget: count coplanar pairs cross-shell, not
merely over faces sharing no vertex. Two quads two steps apart on one
flat cap share no vertex and are coplanar by construction -- that read
96 pairs on hay-bale, none of them a hazard, which is the same
unsatisfiable-budget trap the surrounding paragraph already warns about.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
@TMHSDigital TMHSDigital added the needs-5.1 Opt-in Blender 5.1 smoke on this PR. Default matrix stays 4.5 + 5.2. Auto-label will not apply this. label Sep 22, 2026
@github-actions github-actions Bot added examples Runnable smoke-gated examples under examples/ showcase Budget-conformance props under showcase/ documentation Improvements or additions to documentation labels Sep 22, 2026
@TMHSDigital
TMHSDigital merged commit 56b7825 into main Sep 22, 2026
13 checks passed
@TMHSDigital
TMHSDigital deleted the quality-pass-hay-bale-exit-pre branch September 22, 2026 12:37
TMHSDigital added a commit that referenced this pull request Sep 22, 2026
…iene budgets (#206)

* fix(showcase): hoop the cart's tyre onto the felloe and round the rim

The tyre's inner radius was set equal to the felloe's outer radius
(`r_mid = RIM_OUTER + TYRE_T/2, radial_t = TYRE_T/2`), which put both
surfaces on one plane for every segment: 32 coplanar cross-shell face
pairs, 16 per wheel. It was visible as speckle on the committed hero
and in a clay pass, and no budget could see it.

Derive the tyre's inner radius from the felloe's outer radius minus a
named interference (TYRE_SEAT) and its tread from the felloe plus its
own thickness, so the band bites into the rim and still owns the ground
contact. Wheel segments 16 -> 24: at 16 the rim read as a polygon and
the facet facing the key light rendered as a hard white plate. Iron
raised from (0.13, rough 0.32) to (0.20, rough 0.50), inside the range
its sibling pieces use, because the tyre was reading as black plastic.

Four new budgets, each recomputed from the generated mesh:

- coplanar cross-shell face pairs = 0 (hay-bale's combinatorics, copied)
- each tyre's own zmin on the floor, not just the AABB's
- tyre seat depth banded per angular station, 48 stations
- the two wheels mirrored in Y within 5e-5 m

Four new falsifiers, each landing on its own code and sized to stay
inside BBOX_TOL so the AABB gate cannot steal the failure: --flush-tyre
(15), --sink-tyre (18), --float-wheel (16), --skew-wheel (19).

Also drops the temp .glb once measured — the leak PR #205 found in
hay-bale is in this piece too.

Base triangles re-fitted to 2760-2960: narrower than the 2470-2900 it
replaces and centred on the measured 2856.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>

* fix(showcase): put the well's posts under the roof corners and tenon them in

Three structural defects, all found by looking at an inspection sheet:

1. The four roof posts stood at 0/90/180/270 degrees while the roof's
   4-gon cone is rotated pi/4, so its corners sit on the diagonals. The
   posts were under the midpoint of each eave: the roof's corners
   cantilevered 0.80 m unsupported, and one post stood dead centre in
   the well mouth in every orthographic view. Posts moved to the
   diagonals, and the windlass, crank and grip turn with them.

2. Each post's bottom face landed exactly on the coping's top face —
   4 coplanar cross-shell face pairs, one per post. Posts are now
   tenoned POST_SEAT into the coping, with POST_TOP held fixed so
   nothing above them moves.

3. EAVE_HALF was derived from the post ring plus an overhang, giving a
   1.64 m roof over a 1.08 m drum: an umbrella, not a well house. It is
   now derived from what the roof must cover — the curb's own radius
   plus a named clearance — which brings the AABB to 1.312 m.

The bucket hung at z=0.68 with only its rim level with the coping:
invisible in the hero and in all six orthographic views. It now hangs
BUCKET_CLEAR above the measured coping top and reads in the still.

Five new budgets, each recomputed from the generated mesh — the tenon
and the bucket clearance measure against the coping top read off the
stone material, not against the constant the builder used:

- coplanar cross-shell face pairs = 0
- all 12 bottom-course stones on the floor, not just the lowest
- post tenon depth banded, per post
- each post within 0.02 rad in plan of a roof hip corner
- bucket clearance banded above the coping

Five new falsifiers. Two needed re-aiming to hit their own budget:
--stand-posts also returns the posts to the axis, because flush alone
at 45 degrees leaves no coping face within COPLANAR_CENTRE_MAX of a
foot; --turn-posts rotates by exactly one curb facet, because any other
angle sets the feet on different coping geometry and the gap gate (17)
steals the failure, and a full 45 degrees swings the crank past the
eave and fails the AABB gate (8).

Collider ceiling 320 -> 380: the convex hull of the resized roof
measures 346. Also drops the temp .glb once measured.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>

* fix(examples): make ngon-triangulate's falsifier actually falsify

`--skip-triangulate` printed an error and returned 4 from its own
branch. The flag announced a failure rather than causing one, and the
handling assertions it exists to falsify never ran. It now skips the
repair and lets those assertions catch the unrepaired mesh, which
reports the real measurement (`ngons=1 tris=0 quads=4`).

The contract, the assertions and the exit codes are unchanged. Proven
identical before and after on 4.5.11 LTS, 5.1.2 and 5.2.1 LTS: default
0, --no-dissolve 3, --skip-triangulate 4.

README: adds the verified `calc_tangents` abort text, which is
byte-identical on all three binaries, a falsifier table naming what
each flag breaks, and 4.5/5.2 API links for calc_tangents,
dissolve_edges and triangulate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>

* docs(showcase): fold this pass's defects into the shared conventions

Seven conventions every future piece inherits, each from a defect in
this pass that no existing budget could see: a band is hooped onto its
host and never set flush against it; a member is tenoned into its seat
and never stood on it, measured against the host read off the mesh;
posts go under a roof's corners, with the corners taken from the pooled
eave vertices rather than one fascia board; a roof is sized from what
it covers, not from what carries it; the subject hangs where it can be
seen; mirrored assemblies pair and compare, with the falsifier sized
inside BBOX_TOL; and segment counts are a silhouette budget.

Also registers this pass's falsifier flags in the shared list and
regenerates the gallery for the two rebuilt pieces.

Co-Authored-By: Claude Opus 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 (1M context) <noreply@anthropic.com>
TMHSDigital added a commit that referenced this pull request Sep 23, 2026
…sifier-table check (#210)

All three READMEs gained falsifier tables in #205 and #206, and the
static checker already parses them (28 pieces, 181 falsifiers), but they
stayed listed in KNOWN_UNDOCUMENTED. That exemption is now a hole: if
one of those tables were deleted, the checker would skip the piece
silently instead of failing it.

Every falsifier was run, not read: the runtime mode reports 19/19 exits
matching their declared budgets on Blender 5.2.1, and a direct sweep
gives the same 19 codes on 4.5.11.

Closes #203

Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation examples Runnable smoke-gated examples under examples/ needs-5.1 Opt-in Blender 5.1 smoke on this PR. Default matrix stays 4.5 + 5.2. Auto-label will not apply this. showcase Budget-conformance props under showcase/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant