feat: measurable framing gate + restage hygiene/origin heroes into the band#107
Merged
Conversation
Layer 1 framing was the only mandatory render rule with no number attached, so agents eyeballed it and overshot in both directions. examples/gallery_framing.py measures hero fill (0.70-0.90 of frame in at least one axis) and all-elements edge margins (>= 0.02) on a staged scene, defaulting to a silhouette alpha matte (exact for round or angled subjects) with a bbox-projection strategy documented as the cheap alternative for boxy subjects. check_framing prints the measured values in the house log style and exits 10 on violation, render path only, so check-only smoke runtimes are untouched. It also establishes the repo's cross-example sharing mechanism: a __file__-relative sys.path shim that keeps every example standalone-runnable from the repository root. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
Both renders sat outside the Layer 1 band in opposite directions, the failure mode the new helper exists to catch. mesh-hygiene-audit was at 0.391 fill (two small valves in a large empty stage); the camera moves from 38mm at (0,-7.8,1.9) to 50mm at (0,-5.0,1.5), landing fill at 0.803x0.733 with all margins >= 0.05. The backlit through-hole and loose-vert bead staging is untouched and no light shape re-enters the frame. prop-origin-transform was at 0.906 fill with the shipped asset cropped at both side edges and the tops; the camera pulls back from (1.15,-5.35,1.45) to (1.15,-5.9,1.55) only until margins clear, landing fill at 0.816x0.678 with margins >= 0.072. Both render paths now call gallery_framing.check_framing before the beauty render (exit 10, no artifact on violation); check code and closed forms are unchanged, and check-only output is byte-identical to main on 5.1.2 and 4.5.11. Contact sheets recomposited against the pinned calibration set. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
Layer 1 framing now carries its numbers where every run reads them: the 0.70-0.90 fill band in at least one axis, the 2 percent minimum edge margin, the hero-fill versus all-elements-margin distinction, and the instruction to measure with examples/gallery_framing.py rather than eyeball. CLAUDE.md's quality gates gain the matching framing-gate bullet so the standard example prompt inherits the rule by reference, plus one line naming the cross-example import shim. A read-only survey of every shipped hero with the helper found 13 examples outside the band (worklist for a future pass, not fixed here). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
TMHSDigital
added a commit
that referenced
this pull request
Jul 22, 2026
…108) * feat: add deviation opt-out to the framing helper PR #107's survey put 13 of 22 measured examples outside the band, including a pinned calibration member whose edge bleed is the design, so the band gains the same structure the stage rules have: presumption by default, documented deviation when the bleed or the scale is the point. check_framing takes deviation=<reason>: with a non-empty reason string it measures and prints the numbers with the reason but reports instead of enforcing; an empty or whitespace reason raises ValueError so a deviation cannot be taken silently. The docstring also scopes the helper: it measures the (scene, camera) pair it is handed, so multi-scene examples pass the still's scene explicitly, and world/atmosphere-subject examples report rather than enforce because the matte has no renderable hero to isolate. Both current callers keep the enforcing default; verified unchanged on 5.1.2 and 4.5.11 (0.803/0.733 and 0.816/0.678) and falsified both ways (reasoned deviation reports with exit 0; empty reason ValueErrors with exit 1). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * docs: add the framing deviation clause to VISUAL-STYLE and gates Layer 1 framing now mirrors the stage rules' structure: the 70-90 fill band and the 2 percent margin floor remain the presumption, and a composition may deviate when the bleed or the scale is the design — radiating subjects, edge-to-edge fields, world or atmosphere contracts. A deviation requires a one-line README note under a Framing deviation heading and the helper call switches to check_framing(deviation=reason) so it reports with the reason printed rather than enforcing; an undocumented deviation is still a defect, and an empty reason raises. The CLAUDE.md quality-gates bullet inherits the clause by reference. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * docs: reconcile two runtime facts in CLAUDE.md The smoke workflow floats: it resolves the highest published point release of each series at run time, so CI ran Blender 4.5.12 LTS on PR #107 while local .scratch held 4.5.11 — CLAUDE.md now says the series floats and a local point release may lag, with the exact-reported-version rule unchanged. Also records the Playwright lesson from #107 next to the gallery-capture gate: Chromium scroll-anchoring defeats scrollTo(0, 0) after lazy-load, so captures are full-page, not scroll-then-shoot. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * docs: triage the thirteen out-of-band heroes and record the worklist Full-size and card-scale review of each surveyed hero against its README and contract. Two are legitimate deviations and now carry the one-line README Framing deviation note: damped-track-aim (radiating composition — the spikes read as converging from beyond the frame; it is a pinned calibration member and the gallery's reference standard stays) and wave-displace (edge-to-edge field — the fill is the point). sky-texture-sun-elevation is a measurement artifact: the hero is the sky itself, so the matte only saw the cone props; its README now documents report-only. vse-cut-list is in scope — its still renders from the Bay scene with BayCam, and the README records that the (scene, camera) pair must be passed explicitly. The remaining ten are genuine defects (clipped subjects, over-fill, dead space) and land on the ROADMAP candidate pool as a restage worklist with measured numbers. No re-renders in this pass. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> --------- Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
TMHSDigital
added a commit
that referenced
this pull request
Jul 22, 2026
…es) (#109) * fix: target the passed scene in gallery_framing matte renders _matte_render called bpy.ops.render.render(write_still=True) bare, which renders the ACTIVE context scene — silently wrong for any example that stills from a non-context scene (vse-cut-list renders its gallery hero from a dedicated Bay scene via the operator's scene= kwarg). The matte now renders the scene it was handed, matching that idiom. Both current callers use the context scene and measure unchanged (0.803/0.733 on 5.1.2 and 4.5.11); the corrected survey that exposed this now captures each example's rendered scene via a persistent render_pre handler and re-measured all 38 examples against the scene and camera each actually renders from. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: nudge curve-bevel-arc framing into the band and gate it Right cap sat 0.012 from the right edge, under the 0.02 floor. Camera moves from (2.4,-3.4,1.7) to (2.55,-3.62,1.78) — a pure pull-back, no restage — landing fill 0.738x/0.633y with margins L0.219 R0.044 B0.083 T0.283. The render path now calls gallery_framing.check_framing before the beauty render (exit 10); check code and closed forms untouched (eval_verts=1044, eval_faces=1028, x_span=3.292 all print unchanged). Gate falsified at lens 65 (margins FAIL, exit 10, no artifact) and restored. Identical framing numbers on 4.5.11. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: nudge driver-wave framing into the band and gate it Sixteen-column skyline over-filled the frame at 0.969x with side margins 0.016. Camera pulls back from (0,-13.5,2.0) to (0,-15.0,2.0) — pure distance, no restage — landing fill 0.869x/0.333y with margins L0.066 R0.066 B0.367 T0.300. Render path gated with check_framing (exit 10); check values unchanged (driven_range 0.404..2.399, flushed_to_original). Gate falsified at lens 55 (fill 0.994 FAIL, exit 10) and restored. Identical numbers on 4.5.11. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: nudge shader-node-group framing into the band and gate it Sphere pair over-filled the frame at 0.969x with side margins 0.016. Camera pulls back from (0,-7.8,2.6) to (0,-8.7,2.6) — pure distance, no restage — landing fill 0.869x/0.650y with margins L0.066 R0.066 B0.256 T0.094. Render path gated with check_framing (exit 10); check values unchanged (group users=2, distinct instance tints). Gate falsified at lens 72 (fill 1.000 FAIL, exit 10) and restored. Identical numbers on 4.5.11. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: nudge swatch-grid framing into the band and gate it The ortho grid over-filled vertically at 0.944y. ortho_scale 7.8 -> 8.4 (height 4.39 -> 4.73 for the 4.14-unit grid) lands fill 0.769x/0.878y with margins L0.116 R0.116 B0.061 T0.061. verify_png's third/half cell-center sampling tolerates the scale change — distinct_regions=6 and max_pixel=1.000 confirmed at 1280 default and at the smoke invocation (640, cycles, 8 samples) on 5.1.2 and 4.5.11. Render path gated with check_framing (exit 10); falsified at ortho_scale 6.5 (fill 1.000 FAIL, exit 10) and restored. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: lift png-exr-alpha plinth off the bottom edge and gate it The shared display plinth touched the bottom frame edge (margin 0.000). A pull-back first overshot (fill 0.672 under-band), so the fix is an aim drop instead: aim z 1.65 -> 1.38 at unchanged camera distance pitches the composition up, landing fill 0.725x/0.628y with margins L0.122 R0.056 B0.028 T0.150 — distance and composition otherwise untouched. Render path gated with check_framing (exit 10); falsified at lens 64 (fill 0.916 FAIL, exit 10) and restored. Identical numbers on 4.5.11. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: nudge gp-lineart-contour framing into the band and gate it The neon vessel over-filled vertically at 0.978y with its base cropped by the bottom edge. Camera pulls back from (3.4,-4.2,2.4) to (3.95,-4.9,2.62) — pure distance, no restage — landing fill 0.463x/0.867y with margins L0.272 R0.266 B0.044 T0.089 (an intermediate stop landed y at exactly 0.900, on the band ceiling, so the pull-back goes one step further for cross-version slack). Render path gated with check_framing (exit 10); check values unchanged (strokes=10, points=34, restore recovers both). Gate falsified at lens 62 (fill 1.000 FAIL, exit 10) and restored. 4.5.11 matches (0.469x/0.867y). Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * fix: reframe vse-cut-list bay into the band and gate the Bay scene The corrected survey (render_pre capture) measured this hero for the first time: the monitor top was cropped and the desk bled the bottom edge (fill 1.000y, touch B+T) — the #107 survey had read bpy.context.scene, which is the cut-list scene, not the Bay the still renders from. BayCam pulls back from (2.0,-5.2,1.72) to (3.05,-7.9,2.0) with aim z 1.28 -> 1.20, landing fill 0.506x/0.850y with margins L0.247 R0.247 B0.067 T0.083. This was the most-broken of the seven (frame-filling before), so the composition change is correspondingly larger; the bezel, stand, desk, and on-screen cut list are all fully in frame now. The gate runs on the Bay scene (the helper's scene-targeted matte from the previous commit), falsified at lens 66 (fill 1.000 FAIL, exit 10) and restored. check and --check-pixels both exit 0 on 5.1.2 and 4.5.11 with identical pixel witnesses. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * docs: reduce the framing worklist to the recomposition remainder The corrected survey plus the marginal sweep leaves four examples that need real reframing rather than a camera nudge: depsgraph-export (cube amputated), vertex-weight-limit (off-center, base cropped), text-version-stamp (dead lower third), triangulate-tangents (2x-class move-in). The seven marginal cases are marked swept and gated; deviations and the sky-texture artifact stay as documented in #108. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> --------- Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Measurable framing gate + restage of the two out-of-band heroes
Layer 1 has mandated "subject fills 70–90 % of frame in at least one axis; nothing that matters touches the frame edge" as prose. It was the only Layer 1 rule with no number attached, and it was the one that kept failing:
mesh-hygiene-audithad been pulled back to ~39 % fill andprop-origin-transformhad been tightened until the shipped asset cropped both pedestals at the side edges and their tops. This PR gives the rule a closed form, applies it to those two examples, and codifies it so future examples inherit it.Measurement strategy: silhouette matte (default), projection (documented alternative)
New shared helper
examples/gallery_framing.py— the repo's first cross-example import, via a__file__-relativesys.pathshim so every example stays standalone-runnable asblender --background --python examples/<name>/<script>.py --from the repository root (the smoke workflow's exact invocation; verified on both versions below).film_transparent, renders a 320×180 low-sample EEVEE alpha matte, thresholds alpha > 0.5 → true occupied-pixel extent. Exact for round/angled subjects. One matte for the margin union, one for hero-only fill (shared when they coincide).world_to_camera_view. No extra render, but overestimates round/angled subjects — measured here: hygiene hero fill 0.409 (projection) vs 0.391 (silhouette) on the rotated octagonal valves. Documented in the docstring as the choice for boxy subjects when render time matters.The helper reports the two Layer 1 clauses separately: fill (hero only; 0.70–0.90 in at least one axis, neither axis above 0.90) and margin (union of every element that matters — hero, placards, comparison props, overlay markers; all four edges ≥ 0.02; union clearance ⇔ per-element clearance). Measured values print in the house log style (
framing_fill … ok,framing_margins … ok,framing_ok).Enforcement decision: hard failure (exit 10), render path only
A framing violation exits non-zero (10) before the beauty render, so a defective composition ships no artifact. Rationale: framing is a Layer 1 mandatory identity rule and the render path is the gallery-artifact path — it should gate exactly like every other contract here; making it a hard gate is precisely what breaks the eyeball-and-overshoot loop that produced this PR. The coupling concern (non-zero has meant API-contract drift) is contained: the gate runs only on the opt-in
--outputpath. Check-only semantics are untouched — no matte ever renders there, and smoke runtimes are unchanged (measured below).Before/after, measured by the helper (silhouette, 5.1.2)
framing_okframing_okNotes: the prop-origin shipped webp was tighter than current code reproduced (visibly cropped left/right/top) — the code on main already failed the band at 0.906. hygiene moved in 38 mm@(0,−7.8,1.9) → 50 mm@(0,−5.0,1.5) with the backlit through-hole, loose-vert bead, and no-visible-light-shape staging preserved; prop-origin pulled back (1.15,−5.35,1.45) → (1.15,−5.9,1.55) only until margins cleared, conduit part and dark mount well preserved. No check code or closed forms changed; check-only output is byte-identical to main on 5.1.2 and 4.5.11 (diffed).
Falsification evidence (gate proven to fail)
framing_fill x=0.481 … FAIL(margins_ok=True), exit 10, no artifact written. Restored.framing_margins left=0.000 … FAIL,framing_edges touch=left, exit 10, no artifact written. Restored. (First attempt, camera-x shift, was a no-op by construction — TRACK_TO re-aims; recorded for honesty.)Survey of every shipped hero (read-only worklist — not fixed here)
Ran the helper across all 23 examples (each staged by its own unmodified
--outputpath; union-of-renderables approximates hero fill). 22 of 23 measured;vse-cut-listis not matte-surveyable (its still is a sequencer composite, no scene camera). 13 fall outside the band:damped-track-aim(touch L/R/B, fill 1.000 — a pinned calibration member),wave-displace(touch L/R/B, 1.000),depsgraph-export(touch L/B, 0.984),gp-lineart-contour(touch B),png-exr-alpha(touch B),vertex-weight-limit(touch B),curve-bevel-arc(right margin 0.012)driver-wave(0.969x),shader-node-group(0.969x),swatch-grid(0.944y)text-version-stamp(0.663 max),triangulate-tangents(0.661 max),sky-texture-sun-elevation(0.489 max — world-subject deviation example; matte measures only its sun disk, so this one is likely a measurement artifact of a legitimate deviation)Survey caveats: union fill includes placards (slightly inflates); world/VSE/compositor-artifact subjects can't be matte-measured faithfully. The other 9 measured examples are in band, including both restaged heroes.
Contact-sheet gate (Pass 5)
Composites committed and linked below; each pairs the candidate with the pinned set
armature-bend,damped-track-aim,bmesh-gear(reading order TL→BR, candidate last):Per-criterion verdicts — stage darkness ✓ both (near-black floor/wall, horizon in shadow); wedge warmth ✓ both (warm pool consistent with calibration); subject fill ✓ both (comparable to bmesh-gear/armature-bend, no longer odd one out at either extreme); saturation ✓ (brass in family with the gold gear; teal consistent with its prior accepted hero); thumbnail legibility ✓ both (hole+bead / conduit+well read at cell scale). Mean luminance vs calibration 70.8 / 44.5 / 77.7: hygiene 64.4, origin 61.9 — inside the calibration range for both. Neither candidate is sortable as the odd one out.
Visual-pass log
build_gallery.pyregenerated (index + 38 detail pages); serveddocs/, captured index at 1280 and 390 plus both detail pages. Finding: Chromium scroll-anchoring defeatsscrollTo(0,0)viewport captures after lazy-load (scrollY stayed ~3.3–3.5k) — full-page captures + local crops used instead. Both cards correct at both widths; both detail pages show hero, teaches, witnesses callout, run command correctly.Generated HTML read character by character post-regen: both
<img alt>texts (index cards + detail heroes) and both witnesses callouts complete — no truncation, no duplicated words, no generator artifacts.Runtime / binaries
E:/Blender-Developer-Tools/.scratch/blender-5.1.2-windows-x64/blender.exe— reports Blender 5.1.2 (build 2026-05-19)E:/Blender-Developer-Tools/.scratch/blender-4.5.11-windows-x64/blender.exe— reports Blender 4.5.11 LTS (build 2026-06-23)Check-only runtime, old vs new (median-noise sub-second runs): 5.1.2 hygiene 779→753 ms, origin 780→742 ms; 4.5.11 hygiene 596→582 ms, origin 582→587 ms — unchanged; CI smoke jobs invoke exactly these check-only paths, so they do not start rendering. Render path with gate exercised on both versions: exit 0, identical framing numbers (0.803/0.733 and 0.816/0.678 on 4.5.11 too).
Live-run vs inspection-only
Test plan for reviewers
blender --background --python examples/mesh-hygiene-audit/mesh_hygiene_audit.py --→ exit 0, values as above (4.5 LTS and 5.1).prop_origin_transform.py.-- --output out.pngon either →framing_*lines print,framing_ok, exit 0, artifact written.