Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/gallery/assets/park-bench-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/gallery/assets/terrain-scatter-hero.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions docs/gallery/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -964,12 +964,12 @@ <h2><a href="treasure-chest/">treasure-chest</a></h2>
</article>
<article class="card" data-tags="mesh export showcase">
<a class="card-media" href="terrain-scatter/" aria-label="terrain-scatter example detail page">
<img src="assets/terrain-scatter-hero.webp" alt="terrain-scatter — A Geometry Nodes hill tile with masonry scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract." loading="lazy" decoding="async" />
<img src="assets/terrain-scatter-hero.webp" alt="terrain-scatter — A Geometry Nodes hill tile with seated icosphere scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="terrain-scatter/">terrain-scatter</a></h2>
<p class="teaches">A Geometry Nodes hill tile with masonry scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 1194 tris, two materials with 486 stone faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.800×1.800×0.655 m, LOD ratios in band, convex collider 52 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.</p>
<p class="teaches">A Geometry Nodes hill tile with seated icosphere scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 1758 tris, two materials with 720 stone faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.800×1.800×0.552 m, grounded zmin, 9 stones of ≥80 faces seated on sampled dirt Z (offset −35 mm, floor zmin 0.113 m), LOD ratios in band, convex collider 81 tris, hygiene zero, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z 16; --poke-rock 17; --float-rocks 18; --box-rocks 19.</p>
<a class="card-link" href="terrain-scatter/">View example <span aria-hidden="true">&rarr;</span></a>
</div>
</article>
Expand Down Expand Up @@ -1008,12 +1008,12 @@ <h2><a href="cart/">cart</a></h2>
</article>
<article class="card" data-tags="mesh export showcase">
<a class="card-media" href="park-bench/" aria-label="park-bench example detail page">
<img src="assets/park-bench-hero.webp" alt="park-bench — A procedural wrought-iron park bench through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract." loading="lazy" decoding="async" />
<img src="assets/park-bench-hero.webp" alt="park-bench — A procedural wrought-iron park bench with tangent tube scroll feet through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="park-bench/">park-bench</a></h2>
<p class="teaches">A procedural wrought-iron park bench through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 2100 tris, two materials with 510 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.080×0.566×0.853 m, LOD ratios in band (5.2 COLLAPSE more aggressive on LOD2), convex collider 106 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.</p>
<p class="teaches">A procedural wrought-iron park bench with tangent tube scroll feet through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.</p>
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 2520 tris, two materials with 540 metal and 756 wood faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.295×0.632×0.884 m, grounded zmin, 4 named toes, stretcher gap 1.2 mm, slat gap 0.3 mm, seat span 1.272 m, LOD ratios in band, convex collider 82 tris, hygiene zero, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-feet 16; --float-stretcher 17; --gap-slats 18; --narrow-seat 19.</p>
<a class="card-link" href="park-bench/">View example <span aria-hidden="true">&rarr;</span></a>
</div>
</article>
Expand Down
697 changes: 555 additions & 142 deletions docs/gallery/park-bench/index.html

Large diffs are not rendered by default.

475 changes: 389 additions & 86 deletions docs/gallery/terrain-scatter/index.html

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions showcase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ entry in `showcase/gallery.json`, and a rendered still.
vertices as inner ones the moment the host is out of round, which is
how a hoop that visibly gapped on one side still passed. The paired
falsifier makes the wrapper a true circle on an out-of-round host.
- **Sampled host seat (exit 17/18 on scatter).** Instanced scatter
seats by sampling the host surface Z at each instance's XY, biting a
named depth, then clamping instance verts above the slab floor. A
closed-form icosphere (or equivalent) replaces the GN cube so the
scatter is stones, not open crates; a per-shell face floor is the
budget that catches a cube leftover (exit 19). `--poke-rock` skips
the clamp and over-bites; `--float-rocks` seats above the host.
- **Plumb and real-world size (exit 19).** Assert that the axis of a
turned or lofted body is vertical, by comparing the XY centroid of a
bottom slab against a top slab — not the exact `zmin` and `zmax`
Expand Down
8 changes: 4 additions & 4 deletions showcase/gallery.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
{
"name": "terrain-scatter",
"dir": "showcase/terrain-scatter",
"teaches": "A Geometry Nodes hill tile with masonry scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.",
"witnessesFix": "Recomputed: 1194 tris, two materials with 486 stone faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.800×1.800×0.655 m, LOD ratios in band, convex collider 52 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.",
"teaches": "A Geometry Nodes hill tile with seated icosphere scatter through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.",
"witnessesFix": "Recomputed: 1758 tris, two materials with 720 stone faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.800×1.800×0.552 m, grounded zmin, 9 stones of ≥80 faces seated on sampled dirt Z (offset −35 mm, floor zmin 0.113 m), LOD ratios in band, convex collider 81 tris, hygiene zero, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z 16; --poke-rock 17; --float-rocks 18; --box-rocks 19.",
"hero": "docs/gallery/assets/terrain-scatter-hero.webp",
"preview": "showcase/terrain-scatter/preview.webp",
"tags": [
Expand Down Expand Up @@ -140,8 +140,8 @@
{
"name": "park-bench",
"dir": "showcase/park-bench",
"teaches": "A procedural wrought-iron park bench through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.",
"witnessesFix": "Recomputed: 2100 tris, two materials with 510 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.080×0.566×0.853 m, LOD ratios in band (5.2 COLLAPSE more aggressive on LOD2), convex collider 106 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.",
"teaches": "A procedural wrought-iron park bench with tangent tube scroll feet through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.",
"witnessesFix": "Recomputed: 2520 tris, two materials with 540 metal and 756 wood faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.295×0.632×0.884 m, grounded zmin, 4 named toes, stretcher gap 1.2 mm, slat gap 0.3 mm, seat span 1.272 m, LOD ratios in band, convex collider 82 tris, hygiene zero, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-feet 16; --float-stretcher 17; --gap-slats 18; --narrow-seat 19.",
"hero": "docs/gallery/assets/park-bench-hero.webp",
"preview": "showcase/park-bench/preview.webp",
"tags": [
Expand Down
107 changes: 87 additions & 20 deletions showcase/park-bench/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Park bench

A showcase piece, not an example. Procedural wrought-iron park bench
(scrolled legs, slatted seat and back, armrests) then the shipped
pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain,
convex collider, Unity glTF export.
(quarter-circle tube scroll feet tangent to the posts, slatted seat and
back, arm scrolls, wood arm caps) then the shipped pipeline: unique-cell
UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity
glTF export.

Posts, feet, stretchers and arms share named stations (`hx`, `hy`,
`SEAT_Z`, `ARM_Z`, `SCROLL_R`). A foot is a 6-gon tube about
`(hy ± R, R)` so it is tangent to the post at `z=R` and plants at `z=0`.
The H-stretcher meets the posts at those stations; slats insert into the
post volume without sharing corner verts with the seat rails.

It asserts **budget conformance** of the generated result. It does not
witness an API contract. "It rendered without error" is not a check.
Expand All @@ -13,6 +20,11 @@ witness an API contract. "It rendered without error" is not a check.
`bake_normal_high_to_low.py`, `setup_bake_target_image.py`,
`lod_chain.py` / `decimate_to_budget.py`, `convex_hull_collider.py`,
`export_preset_unity.py` (helpers copied, not imported as a package).
Hygiene combinatorics match `examples/mesh-hygiene-audit` (copied, not
imported).

Intended size: 1.40 m sitting width, 0.50 m seat depth, 0.43 m seat
height, 0.64 m arm height; outer AABB 1.295 × 0.632 × 0.884 m.

## Budgets

Expand All @@ -21,39 +33,89 @@ materials, UVs, evaluated LOD, collider, or export file.

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) |
| --- | --- | --- |
| Base triangles | 2000–2200 | 2100 / 2100 / 2100 |
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.4952 |
| LOD2 ratio | 0.10–0.35 of base | 0.2200 / 0.2200 / 0.1400 |
| Materials | exactly 2 distinct, ≥48 metal faces | 2 slots, 510 metal |
| Base triangles | 2200–4200 | 2520 / 2520 / 2520 |
| LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 |
| LOD2 ratio | 0.10–0.35 of base | 0.2198 / 0.2198 / 0.2198 |
| Materials | exactly 2 distinct, ≥48 metal, ≥24 wood | 2 slots, 540 metal / 756 wood |
| UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 |
| Outer AABB | (1.080, 0.566, 0.853) m ± 0.01 | (1.0800, 0.5657, 0.8531), zmin 0 |
| Collider tris | ≤ 140 | 106 |
| Export | written, size > 0 | 159868 / 159868 / 159852 bytes |
| Outer AABB | (1.295, 0.632, 0.884) m ± 0.015 | (1.2945, 0.6314, 0.8840), zmin 0 |
| Collider tris | ≤ 180 | 82 |
| Export | written, size > 0 | 188164 / 188164 / 188148 bytes |

Base triangles rose from **2100 to 2520** in the quality pass: box-segment
scrolls became 6-gon tubes. Outer AABB widened from 1.080 × 0.566 × 0.853 m
to a 1.40 m sitting-width bench.

DECIMATE COLLAPSE triangle counts are **not** guaranteed identical across
series — the gate is a ratio band, not an exact count. This mesh matched
on 4.5.11 / 5.1.2 / 5.2.1. Bake pixels are stochastic; the gate is
`has_data` plus operator `FINISHED`, not byte-identity. Construction uses
no RNG. Export byte counts differ by 16 B on 5.2.1 (glTF serializer), not
a gated axis.

### Hygiene

Recomputed from the generated mesh, not asserted about the script.

| Axis | Declared | Measured (all three) |
| --- | --- | --- |
| Non-manifold edges | 0 | 0 |
| Loose verts / edges | 0 / 0 | 0 / 0 |
| Doubles merged at 1e-5 | 0 | 0 |
| Zero-area faces | 0 | 0 |
| N-gons | 0 | 0 |
| Coplanar disjoint face pairs | 0 | 0 |
| Grounded: `zmin` | within 1e-4 of 0 | 0.0000 |
| Named toes | 4, each `zmin` ≤ 0.002 | 4, 0.00000 |

### Joint fit and seat

| Axis | Declared | Measured (all three) |
| --- | --- | --- |
| Stretcher-to-post BVH gap | ≤ 0.008 m | 0.00120 |
| Slat-to-post BVH gap | ≤ 0.008 m | 0.00028 |
| Seat span vs `2·hx` | ± 0.08 m | 1.2720 vs 1.260 |

DECIMATE COLLAPSE triangle counts are **not** identical across series —
5.2.1 is more aggressive on LOD2. The gate is a ratio band, not an
exact count. Bake pixels are stochastic; the gate is `has_data` plus
operator `FINISHED`, not byte-identity. Construction uses no RNG.
Export byte counts differ by 16 B on 5.2.1 (glTF serializer), not a
gated axis.
### Falsifiers

Each violates one named budget. All were run on 4.5.11, 5.1.2 and 5.2.1
and returned the same code on each.

| Flag | Budget violated | Exit |
| --- | --- | --- |
| `--skip-decimate` | LOD1 ratio band | 9 |
| `--stray-vert` | loose vertex count is 0 | 15 |
| `--lift-z` | bounding box `zmin` is 0 | 16 |
| `--short-feet` | named toes plant at z=0 | 16 |
| `--float-stretcher` | stretcher-to-post BVH gap | 17 |
| `--gap-slats` | slat-to-post BVH gap | 18 |
| `--narrow-seat` | seat span vs `2·hx` | 19 |

`--skip-decimate` skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and
exit 9 fires. That is the named budget the falsifier violates.
`--short-feet` lifts only the outward toe verts. After recenter the post
join is the AABB `zmin`, so the AABB gate would still pass; the named-toe
stations fail.

## Run

```bash
blender --background --python park_bench.py --
blender --background --python park_bench.py -- --skip-decimate
blender --background --python park_bench.py -- --stray-vert
blender --background --python park_bench.py -- --lift-z
blender --background --python park_bench.py -- --short-feet
blender --background --python park_bench.py -- --float-stretcher
blender --background --python park_bench.py -- --gap-slats
blender --background --python park_bench.py -- --narrow-seat
blender --background --python park_bench.py -- --output bench.png
```

Smoke does not pass `--output` or `--skip-decimate`.
Smoke passes no flags.

## Exit codes

File-local. `9` is a valid check code. `10` is reserved for
`gallery_framing.check_framing` on the `--output` path.
`gallery_framing.check_framing` on the `--output` path. `15`–`19` are the
hygiene and joint-fit family.

| Code | Meaning |
| --- | --- |
Expand All @@ -72,3 +134,8 @@ File-local. `9` is a valid check code. `10` is reserved for
| 12 | Bake did not finish or image has no data |
| 13 | Export file missing or empty |
| 14 | `--output` produced no file |
| 15 | Hygiene (`--stray-vert` lands here) |
| 16 | Grounded zmin / named toes (`--lift-z`, `--short-feet`) |
| 17 | Stretcher-to-post gap (`--float-stretcher`) |
| 18 | Slat-to-post gap (`--gap-slats`) |
| 19 | Seat span (`--narrow-seat`) |
Loading
Loading