@@ -36,7 +36,7 @@
## Overview
-This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 32 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
+This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 33 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capable client) when working on Blender add-ons, geometry nodes scripts, batch pipelines, or animation tooling. There is no build step. Edit the markdown and Python files directly.
@@ -85,7 +85,7 @@ or expand a category below.
@@ -179,6 +179,23 @@ as if associated-alpha — straight-authored dark values at low alpha clamp to w
(closed-form error **0.98** at `(0.02, a=1/255)`). OpenEXR preserves float RGBA;
byte images stay straight 8-bit. Also witnesses `EXR color_mode='RGB'` dropping alpha.
+
+
+
+
+
+
+
+
+### [sky-texture-sun-elevation](examples/sky-texture-sun-elevation/)
+
+World `ShaderNodeTexSky` driving Background Color — the sky contract AI lighting
+code misses across 4.5 → 5.1. `sky_type` is `NISHITA` on 4.5 LTS and
+`MULTIPLE_SCATTERING` on 5.1 (`NISHITA` gone); `dust_density` exists only on 4.5
+(`aerosol_density` on 5.1). Two tiny Cycles EXR zenith probes assert
+`sun_elevation` 8° → 55° brightens zenith (rise **2.25x** / **1.50x**, gate ≥ 1.25).
+Gallery still is a dual-elevation diptych so the contract reads at thumbnail scale.
+
diff --git a/ROADMAP.md b/ROADMAP.md
index f0011c3..d20d9ec 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -114,6 +114,11 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
- Falsy `bpy_prop_collection` trap snippet: an empty collection is falsy, so `editor.strips or editor.sequences` silently falls through to the legacy accessor on an empty timeline — always branch on `hasattr`; likely generalizes across the API (found authoring `vse-cut-list`)
- ~~Collision compound witness~~ **SHIPPED** as `examples/collision-hull-proxy/` — game-prop collision as a compound of convex pieces, each a `bmesh.ops.convex_hull` of a coarse `sec(π/n)`-inflated cage (containment 4.4e-08, watertight, positive signed volume, Euler 2, per-piece 255-face budget: body 70, caps 60×3, compound 250); a hull of the dense render mesh measures 380 faces — over budget — which is why pipelines hull cages; proud details cost cage rows, concave grooves are free; byte-identical on 4.5.11 and 5.1.2
- ~~Custom-normals / shade-by-angle witness~~ **SHIPPED** as `examples/custom-normals-shade/` — the post-4.1 shading contract: `use_auto_smooth`/`use_custom_normals`/`calc_normals` are AttributeError on BOTH 4.5.11 and 5.1.2; `set_sharp_from_angle` sharp sets match an independent dihedral recompute exactly (188/388 edges, 3 meshes); evaluated normal welds/splits exact (0.0 dev); custom split normals survive depsgraph evaluation within int16 quantization (1.407e-04, not float-exact); **divergence**: the legacy `shade_auto_smooth` operator CANCELS headless on 4.5 ("Asset loading is unfinished", mesh untouched, no exception) while 5.1 FINISHES with the Smooth-by-Angle NODES modifier — the data API is the portable path
+- ~~Sky Texture / sun_elevation witness~~ **SHIPPED** as `examples/sky-texture-sun-elevation/` — World `ShaderNodeTexSky` → Background; `sky_type` is `NISHITA` on 4.5 and `MULTIPLE_SCATTERING` on 5.1 (`NISHITA` gone); `dust_density` is 4.5-only (`aerosol_density` on 5.1); zenith EXR rise 8°→55° measured **2.25x** (5.1.2) / **1.50x** (4.5.11), gate ≥ 1.25; dual-elevation gallery diptych
+- Grease Pencil Line Art contour witness: empty GPv3 object + `modifiers.new(..., "LINEART")`; `thickness` on 4.5 vs `radius` on 5.1; evaluated contour stroke counts for a known source mesh
+- Camera DOF + focus_distance witness: `cam.dof.use_dof`, focus plane vs defocus variance on high-freq cards (Stage deviation — depth needs background content)
+- Volumetric scatter optical-depth witness: Volume Scatter density → Beer–Lambert transmittance along a known path (Cycles; Stage deviation)
+- Freestyle SVG / line-set witness: Freestyle line set on a silhouette (full render pass; prefer Line Art first)
- prop-origin-transform witness — origin to base center, `transform_apply` through the data API, delta transforms, `matrix_parent_inverse` so parented children do not teleport; closed forms: post-apply scale exactly (1,1,1), local bbox min Z == 0, world bbox unchanged (builds on `parent-inverse-orrery`, does not duplicate it)
- mesh-hygiene-audit witness — the engine-ingest checklist as executable contract: no ngons, no loose vertices, no non-manifold edges, no zero-area faces, consistent outward winding; closed forms via Euler characteristic and exact edge-face incidence counts
diff --git a/docs/gallery/assets/sky-texture-sun-elevation-hero.webp b/docs/gallery/assets/sky-texture-sun-elevation-hero.webp
new file mode 100644
index 0000000000000000000000000000000000000000..a91c325813848f5cad9b82dc96c71cc1eda23cd8
GIT binary patch
literal 18382
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
World ShaderNodeTexSky driving Background Color — the sky contract across 4.5 LTS and 5.1. sky_type is NISHITA on 4.5 and MULTIPLE_SCATTERING on 5.1 (NISHITA gone); dust_density exists only on 4.5 (aerosol_density on 5.1). Two tiny Cycles OPEN_EXR zenith probes prove sun_elevation 8 deg to 55 deg brightens zenith (rise 2.25x on 5.1.2, 1.50x on 4.5.11, gate >= 1.25).
+
witnesses AI code still assigns sky_type='NISHITA' and dust_density on 5.1 — both are wrong (NISHITA removed; dust_density is AttributeError; use MULTIPLE_SCATTERING + aerosol_density). A near-black Background Strength alone is not a sky: Sky must link into Background Color. Elevation that does not brighten zenith fails the rise gate.