You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: close checks that could pass without asserting anything, plus gallery-infrastructure debt (#120)
* fix: close checks that could pass without asserting anything
A falsification probe on vertex-color-ao exited 0 when it should have failed:
the depsgraph comparison used zip(src.data, eva.data), which truncates to the
shorter sequence, so a resampled attribute read as a clean 0.0 deviation. That
is a direct hit on this repository's central claim, so all 44 examples were
audited for the same class of defect.
Two checks could not fail at all. gltf-export-roundtrip initialised flip_err
to 0.0 and derived it from a zip over two accessors whose lengths nothing
asserted: deleting the entire TEXCOORD_0 attribute still exited 0. And
lightmap-uv-channel asserted "zero overlapping islands" without ever
establishing that its SAT detector can find one - widening SAT_EPS blinds the
detector, and a blind detector reports zero.
The rest are hardening. Pairings whose lengths are part of the claim now
assert those lengths instead of letting zip() quietly shorten the comparison
(gltf-skin-roundtrip, vertex-weight-limit, vse-cut-list). Checks built
entirely from per-element loops now pin their population first, because a loop
over an empty collection reports success (modular-kit-snap,
socket-attach-points, vertex-color-ao). socket-attach-points additionally
asserts that both branches of its up-axis rule are exercised: that assertion
compared the socket basis against a re-derivation using the same threshold, so
on its own it could not notice the threshold being wrong.
Every guard added here was proven to fire, and all 44 checks pass unchanged on
4.5.11 LTS and 5.1.2.
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
* fix: re-encode gallery WebP that shipped near-lossless
Blender's Image.save() ignores its quality argument and keeps the buffer RGBA,
so the documented recipe was writing near-lossless files. Four assets shipped
that way, totalling 3.95 MB; encoded from the same pixels with Pillow they
total 85 KB. Whole-gallery image weight drops from 5.36 MB to 1.50 MB.
Mean absolute deviation from the committed originals is about 1/255 with under
0.3% of pixels differing by more than 8/255 - ordinary q85 loss, including on
the lightmap atlas board, which is the worst case here (thin saturated island
strips on black). No example was re-rendered; this is an encoding change only.
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
* feat: framing gate reports how far off, not just that it failed
The gate printed FAIL without a magnitude, so converging into the band cost a
render per guess - nine of twenty-seven renders in the last example run went
on that. On-screen extent is very nearly inversely proportional to camera
distance, so the measured-to-target fill ratio is a usable one-step
correction, and margin shortfalls can be named per edge.
Enforcement is unchanged; this is diagnostic output. The suggestion is
labelled as a lower bound when the silhouette leaves the frame, because fill
saturates at 1.000 there and the true extent is unknowable from the
measurement.
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
* fix: report zero-area faces instead of raising on them
measure_edge90 called Vector.angle on face normals without checking length,
and a zero-area face has a zero-length normal, so an asset with one lost every
other measurement to a ValueError. A degenerate face is a real defect worth
hearing about; the count is now reported in the aq_edge90 line and those edges
are skipped rather than crashing the gate.
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
* docs: correct the WebP recipe, Playwright guidance, and stale counts
The WebP recipe told authors to use a Blender call that silently ignores
quality; it now specifies Pillow and gives a size sanity floor.
The Playwright note recommended full-page capture as the workaround for
lazy-loaded gallery images. That does not work: on the 44-card grid a fullPage
capture renders every card blank even with images verified loaded. Replaced
with the method that does work - force eager, scroll into view, viewport
capture - and the failing advice is called out rather than quietly dropped.
The asset floors get an honest status note: no floor has ever bound on a
shipped asset, and one first draft passed all three and was then judged bad by
eye and rebuilt. They are a filter for obvious failures; the asset sheet
decides.
Example counts in CLAUDE.md and AGENTS.md said 40 against a real 44, taken
from the manifest. ROADMAP records that asset sheets committed before the
neutral-rig camera fix used mis-scaled panels and are not comparable.
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
---------
Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namespace`, application handler registration, shader node group with cross-version `interface` API, `foreach_get` bulk vertex read, version-branch skeleton, and USD export with `evaluation_mode='RENDER'`.
84
84
85
-
## Examples (40)
85
+
## Examples (44)
86
86
87
87
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
88
88
deterministic checks (exit non-zero on failure) and optionally rendering a still via
89
-
`--output`. All forty run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
89
+
`--output`. All of them run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
90
90
their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the
91
91
gallery's source of truth. When authoring a new one, copy the anatomy of
92
92
`examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and
@@ -127,7 +127,7 @@ Stage with **explicit paths only** — never `git add -A` or `git add .`. Cursor
127
127
- **Asset-sheet gate (asset-type examples — game props/kits):** composite the hero asset rendered alone (neutral three-quarter view, plain studio lighting, no staging tricks, no labels, no comparison props) beside the pinned asset-quality reference set — currently `collision-hull-proxy`, `custom-normals-shade`, `vertex-weight-limit`, `lod-decimate-chain` — rendered the same way; commit under `docs/gallery/asset-sheets/`, link it in the PR body, and report a verdict. The asset ships only if it is not identifiable as the least-designed object in that lineup — a strong scene can carry a weak model; this gate removes the scene. **This list is the canonical home of the reference set** — update it here when a new asset outclasses a member. The measurable floors behind the gate (naming, material variation, edge treatment) live in `examples/gallery_asset_quality.py` — render path only, same call pattern as `gallery_framing`, exit 11 on violation — with the calibration table and dropped-floor evidence in `docs/VISUAL-STYLE.md` § Asset quality.
128
128
-**Falsification:** every check must be proven to fail once — break the contract, observe the non-zero exit, restore — with the probe and the measured error reported in the PR body. An assertion that cannot fail witnesses nothing.
129
129
-**After gallery regeneration** (`python scripts/build_gallery.py`), read the **generated HTML** character by character — the `<img alt>` text and witnesses callouts in `docs/gallery/index.html` and `docs/gallery/<name>/index.html` — not just `examples/gallery.json`. Precedent: the `teaches.split(".")[0]` bug truncated 14/21 card alts at dotted API paths like `bmesh.ops` while the source JSON looked fine (fixed in PR #68).
130
-
-**Playwright gallery captures:** gallery `<img>` tags lazy-load, and Chromium scroll-anchoring defeats `scrollTo(0, 0)` after images load — take full-page captures and crop locally, not scroll-then-shoot.
130
+
-**Playwright gallery captures:** gallery `<img>` tags lazy-load, so force them first (`document.querySelectorAll('img').forEach(i => i.loading = 'eager')`, then wait). **Scroll the target card into view and take a viewport capture** — `scrollIntoView({block:'center', behavior:'instant'})`, short wait, `browser_take_screenshot` with `fullPage` omitted. A `fullPage` capture is NOT a workaround: on a tall gallery page it renders every card image blank even when the images are verified loaded (`complete === true`, `naturalWidth === 1280`, `opacity === 1`) — measured on the 44-card grid at 1425x4516. Verify load state via `browser_evaluate` rather than trusting the pixels.
Copy file name to clipboardExpand all lines: ROADMAP.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -133,6 +133,16 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
133
133
134
134
-~~Gallery framing recomposition worklist~~**DRAINED**: all four recomposition cases restaged and gated with `check_framing` — depsgraph-export (0.984x/touch→0.812x; cube+dome recentered, aim-based camera), vertex-weight-limit (touch B→0.867y; more frontal camera, lower aim, base in frame), triangulate-tangents (0.334x→0.833y; 2x-class move-in plus Key/Glint trim that also cleared a pre-existing dome blowout), text-version-stamp (0.663x→0.719x; moved in with aim on the stamp center, dead third gone). The seven marginal cases were camera-nudged in the preceding sweep; deviations documented in #108 (damped-track-aim, wave-displace); sky-texture-sun-elevation is a measurement artifact (world subject — report-only if wired). Remaining: nothing on the framing worklist — future examples inherit the gate at authoring time.
135
135
136
+
## Asset sheets committed before 2026-07-25 are not comparable
137
+
138
+
The neutral asset-sheet harness sized its camera from the subject's bounding
139
+
box **diagonal**, which cropped tall subjects and shrank wide ones, so panels
140
+
in one sheet were at different effective scales. It now solves the required
141
+
distance from the real field of view on both axes. Sheets committed before
142
+
this fix (`modular-kit-snap`, `lightmap-uv-channel`) show mis-scaled panels
143
+
and must not be cited as evidence for or against an asset; re-render them if
print(<spanclass="s">f"</span><spanclass="s">ERROR: prim0 TEXCOORD_0 count </span>{len(disk_uv)}<spanclass="s"> != POSITION </span><spanclass="s">"</span>
635
+
<spanclass="s">f"</span><spanclass="s">count </span>{len(disk_pos)}<spanclass="s"> - the UV attribute does not cover </span><spanclass="s">"</span>
print(<spanclass="s">f"</span><spanclass="s">ERROR: prim0 carries only </span>{len(disk_pos)}<spanclass="s"> loops, fewer than </span><spanclass="s">"</span>
640
+
<spanclass="s">f"</span><spanclass="s">the </span>{UV_SAMPLE}<spanclass="s"> the V-flip check samples</span><spanclass="s">"</span>, file=sys.stderr)
print(<spanclass="s">f"</span><spanclass="s">ERROR: on-disk UVs deviate </span>{flip_err:<spanclass="s">.3e</span>}<spanclass="s"> from the V-flipped </span><spanclass="s">"</span>
635
656
<spanclass="s">"authored layout (glTF texture origin is top-left)"</span>, file=sys.stderr)
fail(<spanclass="n">11</span>, <spanclass="s">f"</span><spanclass="s">kit built </span>{len(meshes)}<spanclass="s"> parts, expected </span>{EXPECT_PARTS}<spanclass="s"> - the </span><spanclass="s">"</span>
547
+
<spanclass="s">f"</span><spanclass="s">per-part loops below would iterate over the wrong set</span><spanclass="s">"</span>)
0 commit comments