Skip to content

Commit 9e9e7ce

Browse files
authored
feat: attribute-domain-shear example + count/numeric-contract housekeeping (#111)
* docs: correct the stale example count in AGENTS.md and CLAUDE.md Both files' prose said 23 examples while README, the plugin manifest, and CI all agree on 38 (derived from .cursor-plugin/plugin.json: 12 skills, 6 rules, 2 templates, 17 snippets, 38 examples — every other count in the two files verified correct as written). Example-shipping commits keep these lines current from here. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * docs: codify the numeric-contract exception in VISUAL-STYLE Layer 1 Two shipped examples have contracts with no visual signature: vertex-weight-limit's pose-preserving prune and triangulate-tangents' mikktspace field both falsify pixel-identical, and both declared it honestly. Layer 1 now makes that an explicit, narrow exception: the README declares the contract numeric, the render depicts subject and setup legibly rather than a break that cannot exist, and falsification is satisfied by naming why. Anchored to the two precedents by name so the exception stays narrow — where a visual witness is possible, it is still required. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> * feat: add attribute-domain-shear example Witnesses what POINT vs CORNER means on Mesh.color_attributes once a mesh has shared vertices: an 8-wedge pinwheel around one raised hub. CORNER authoring holds each wedge's exact color at the hub (err <= 1e-6); the naive per-wedge POINT loop rewrites every shared vertex once per neighbor and the last write wins — hub reads palette[K-1], ring verts read palette[i] except the wrap-around vert 0, and the measured shear equals the palette closed form (0.751031) exactly. Storage sizes asserted per domain (3K loops vs K+1 verts). Byte-identical on 4.5.11 and 5.1.2. Falsified: corner corruption (exit 4), reversed write order (exit 5), ring corruption (exit 6), constant palette collapsing the shear (exit 7). Companion to color-attribute-wheel, not a repackage: that one covers domain sizing and active_color; this one covers what the domains mean on shared topology. Render path gated with check_framing (0.837x/0.478y, margins >= 0.081); dual pinwheel still carries the broken state in-frame — the POINT fan is the falsification variant. Count prose bumped to 39 across README/CLAUDE/AGENTS. Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com> --------- Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent 2c297b3 commit 9e9e7ce

15 files changed

Lines changed: 1206 additions & 9 deletions

File tree

‎.cursor-plugin/plugin.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@
6060
],
6161
"examples": [
6262
"examples/armature-bend",
63+
"examples/attribute-domain-shear",
6364
"examples/bmesh-gear",
6465
"examples/car-mirror-symmetry",
65-
"examples/color-attribute-wheel",
6666
"examples/collision-hull-proxy",
67+
"examples/color-attribute-wheel",
6768
"examples/compositor-glare",
6869
"examples/curve-bevel-arc",
6970
"examples/custom-normals-shade",

‎.github/workflows/blender-smoke.yml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,3 +542,15 @@ jobs:
542542
# non-zero on failure.
543543
xvfb-run -a "$BLENDER" --background \
544544
--python examples/car-mirror-symmetry/car_mirror_symmetry.py --
545+
546+
- name: Shipped example - attribute domain shear (POINT vs CORNER semantics)
547+
run: |
548+
set -euo pipefail
549+
# Check only (no render): an 8-wedge pinwheel around one shared hub
550+
# vertex; asserts CORNER attr sizing (3K loops) and exact per-wedge
551+
# hub colors, POINT sizing (K+1 verts), and that a naive per-wedge
552+
# POINT loop shears to the last write at every shared vertex with
553+
# the measured deviation equal to the palette closed form. Exits
554+
# non-zero on failure.
555+
xvfb-run -a "$BLENDER" --background \
556+
--python examples/attribute-domain-shear/attribute_domain_shear.py --

‎AGENTS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker
2020
classifies it as a `cursor-plugin`. This is content the AI loads when the user
2121
asks Blender questions or works on Blender add-ons in Cursor or Claude Code.
2222

23-
The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 23
23+
The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 39
2424
examples (counts are CI-enforced against README.md and the manifest). The full
2525
inventory tables and per-item purposes live in `CLAUDE.md`. Example anatomy
2626
and authoring rules: copy `examples/bmesh-gear/`; the render look is specified
@@ -35,7 +35,7 @@ Blender-Developer-Tools/
3535
rules/<rule-name>.mdc # 6 rule files
3636
templates/<template-name>/ # 2 starter templates
3737
snippets/<snippet-name>.py # 17 standalone Python snippets
38-
examples/<name>/ # 23 runnable smoke-gated examples (+ gallery.json)
38+
examples/<name>/ # 39 runnable smoke-gated examples (+ gallery.json)
3939
examples/gallery_framing.py # shared Layer 1 framing measurement (render path only)
4040
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
4141
scripts/site/ # vendored landing-page build (build_site.py + template)

‎CLAUDE.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 12 total
2121
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
2222
templates/<template-name>/ - Starter projects, 2 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 17 total
24-
examples/<name>/ - Runnable smoke-gated examples, 23 total (+ gallery.json)
24+
examples/<name>/ - Runnable smoke-gated examples, 39 total (+ gallery.json)
2525
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
2626
scripts/site/ - Vendored landing-page build (Jinja2)
2727
docs/gallery/ - Committed generated gallery pages + hero renders
@@ -82,11 +82,11 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh
8282

8383
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'`.
8484

85-
## Examples (23)
85+
## Examples (39)
8686

8787
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
8888
deterministic checks (exit non-zero on failure) and optionally rendering a still via
89-
`--output`. All twenty-three run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
89+
`--output`. All thirty-nine run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
9090
their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the
9191
gallery's source of truth. When authoring a new one, copy the anatomy of
9292
`examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and

‎README.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</p>
1919

2020
<p align="center">
21-
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>38 examples</strong>
21+
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>39 examples</strong>
2222
</p>
2323

2424
<p align="center">
@@ -739,6 +739,27 @@ Companion to [`depsgraph-export`](examples/depsgraph-export/) (evaluated-vs-
739739
original) and [`shape-key-blend`](examples/shape-key-blend/) (non-destructive
740740
evaluation). Break the mirror and the render is literally half a car.
741741

742+
</td>
743+
</tr>
744+
<tr>
745+
<td width="46%" valign="middle">
746+
<a href="examples/attribute-domain-shear/"><img src="examples/attribute-domain-shear/preview.webp" alt="Attribute domain shear: two eight-petal pinwheels side by side — left CORNER-domain with crisp colored petals to the hub, right naive POINT-domain smeared into a swirl, proving last-write-wins shear at shared vertices" /></a>
747+
</td>
748+
<td valign="middle">
749+
750+
### [attribute-domain-shear](examples/attribute-domain-shear/)
751+
752+
What `POINT` versus `CORNER` **means** on `Mesh.color_attributes` once a mesh
753+
has shared vertices. An eight-wedge pinwheel around one raised hub: CORNER
754+
authoring holds each wedge's exact color (hub corners disagree by face,
755+
err ≤ 1e-6), while the naive per-wedge POINT loop rewrites every shared
756+
vertex once per neighbor and the **last write wins** — the hub reads
757+
palette[K−1], and the measured shear matches the palette closed form
758+
(0.751031) exactly. Companion to
759+
[`color-attribute-wheel`](examples/color-attribute-wheel/) (domain sizing,
760+
`active_color`, the shader Attribute node). The broken state is in-frame:
761+
the right pinwheel is the falsification variant.
762+
742763
</td>
743764
</tr>
744765
</table>

‎ROADMAP.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
100100
- Gallery coverage follow-ups from the GPv3 review: light-linking (`grease-pencil-rosette`, `armature-bend`, `text-version-stamp`, `image-pixels-testcard`, and `vse-cut-list` shipped first)
101101
- ~~UV-layer authoring witness~~ **SHIPPED** as `examples/uv-layer-grid/` — `create_grid(..., calc_uvs=True)` silent no-op without a pre-existing UV layer; closed-form UV fill + explicit assignment fallback; dual-panel render (flat texel (0,0) vs neon checker)
102102
- ~~Image save-format witness~~ **SHIPPED** as `examples/png-exr-alpha/` — float→PNG is RGBA16 and false-unpremultiplies as if associated-alpha (closed-form err 0.98 at RGB 0.02 / a=1/255); OpenEXR preserves float RGBA; byte→PNG is straight RGBA8; `EXR color_mode='RGB'` drops alpha
103-
- Attribute domain witness: writing a POINT-domain color attribute and reading it as if it were CORNER (or vice versa) silently shears colors across shared verts — companion to `color-attribute-wheel`
103+
- ~~Attribute domain witness~~ **SHIPPED** as `examples/attribute-domain-shear/` — POINT vs CORNER semantics on an 8-wedge pinwheel with one shared hub: CORNER authoring exact (hub corners disagree by face, err ≤ 1e-6), naive per-wedge POINT loop shears to the last write at hub + ring verts, measured deviation == palette closed form (0.751031); byte-identical on 4.5.11 and 5.1.2; crisp-vs-smeared dual pinwheel still — companion to `color-attribute-wheel`
104104
- ~~Light-linking collection witness~~ **SHIPPED** as `examples/light-link-studio/` — the API is `obj.light_linking` on the light OBJECT (`ld.light_linking` is an AttributeError on both versions); linked ratio 4.0x at projected-center luminance samples, unlink raises the decoy 244–251% with 0.0% hero drift in the same two-render check; EEVEE Next honors linking too (5.5x on 4.5.11, 5.9x on 5.1.2), Cycles pinned for deterministic samples
105105
- ~~VSE sequences-to-strips witness~~ **SHIPPED** as `examples/vse-cut-list/` — `.sequences` removed on 5.x (4.5 bridges to `.strips`), `new_effect` end kwarg `frame_end=` (4.5) vs `length=` (5.x), `frame_final_*` deprecated in favor of `left_handle`/`right_handle`/`duration`, TRANSFORM effect type removed, GAMMA_CROSS clamps to the source overlap, effect strips consume inputs only when stacked above them, same-scene scene strips render transparent; save/reload round-trip + tiny-render pixel witness
106106
- ~~glTF export round-trip witness~~ **SHIPPED** as `examples/gltf-export-roundtrip/` — `export_yup` bakes `(x,y,z)→(x,z,−y)` into vertex data with no node rotation (probed identical on 4.5.11 and 5.1.2), `export_apply` ships the evaluated mesh (one disk vertex per evaluated loop), TEXCOORD_0 is V-flipped on disk, per-triangle material bindings survive; exporter/importer RNA signatures byte-identical between 4.5.11 and 5.1.2 (guarded against future renames); `Mesh.calc_normals()` removal on 5.x surfaced during authoring
@@ -127,7 +127,6 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
127127
- Bicycle-wheel radial witness: spokes via radial Array with object offset — exact angular spacing, spoke count, hub/rim concentricity, every evaluated spoke endpoint on its computed rim coordinate
128128
- ~~mesh-hygiene-audit witness~~ **SHIPPED** as `examples/mesh-hygiene-audit/` — engine-ingest topology checklist on a stepped street electrical pedestal: no ngons, no loose verts, manifold edges (exactly 2 faces), no zero-area faces (`min_area` printed), positive signed volume, Euler `V−E+F==2` (24/44/22, volume 0.989248); byte-identical on 4.5.11 and 5.1.2; dual-panel dirty vs clean still
129129
- UV atlas pack witness — lightmap/unique UV island packing with non-overlap + utilization closed forms (deferred behind hygiene/origin; overlaps `uv-layer-grid` / `triangulate-tangents` / `gltf-export-roundtrip` UV arc)
130-
- Attribute domain witness: writing a POINT-domain color attribute and reading it as if it were CORNER (or vice versa) silently shears colors across shared verts — companion to `color-attribute-wheel`
131130

132131
- ~~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.
133132

‎docs/VISUAL-STYLE.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ the default stage.
8585
- The image must witness the API: if the contract failed, the render should
8686
visibly break. If the render would look the same whether the code worked or
8787
not, the scene design is wrong, not merely unpolished.
88+
- **Numeric-only contracts are a narrow, declared exception.** Some contracts
89+
have no visual signature: the broken state renders pixel-identical because
90+
the evidence is a count, a cap, or a buffer invariant, not a shape. Then
91+
the example says so in its README, the render's job is to depict the
92+
subject and its setup legibly — so the reader can see what the numbers
93+
describe — and the falsification pass is satisfied by naming why no visual
94+
difference exists. Precedents: `vertex-weight-limit` (the prune preserves
95+
the pose by design, so the 4-influence cap is numeric) and
96+
`triangulate-tangents` (the ring sweep is UV/bump-driven, so a
97+
tangent-field break does not move pixels). This is for contracts that
98+
genuinely cannot be seen, not for scenes that were not designed hard
99+
enough: where a visual witness is possible, it is required.
88100
- Presentation may be staged; evidence may not. When the witnessed artifact
89101
is itself an image (a sequencer frame, a pixel buffer, a baked texture),
90102
the authentic pixels must appear unaltered — mount them in the scene
16.6 KB
Loading

0 commit comments

Comments
 (0)