Skip to content

fix: terrain-scatter and car-mirror-symmetry quality pass - #248

Merged
TMHSDigital merged 3 commits into
mainfrom
fix/terrain-scatter-car-quality-pass
Sep 25, 2026
Merged

TMHSDigital merged 3 commits into
mainfrom
fix/terrain-scatter-car-quality-pass

Conversation

@TMHSDigital

Copy link
Copy Markdown
Owner

A quality pass on two items: terrain-scatter (Track A, showcase) and car-mirror-symmetry (Track B, gallery example).

How I picked: I laid out all 28 showcase heroes in one mosaic. terrain-scatter read worst: a dirt slab with pebbles dropped on it. car-mirror-symmetry was the weakest unchanged example in the last run (PR #247).

Binaries:

  • .scratch/blender-4.5.11-windows-x64 (reports Blender 4.5.11 LTS)
  • blender-5.1.2-windows-x64 (Blender 5.1.2)
  • blender-5.2.1-windows-x64 (Blender 5.2.1 LTS)

Heroes were rendered on 5.2.1 through scripts/render_hero.py. The inspection renders stayed in .scratch/.

terrain-scatter (Track A)

Inspection sheet: six orthographic views, two three-quarter views, clay, wireframe, four stone-seat close-ups, two slab close-ups and a ground-contact view.

Defects (round 0):

  1. Stones perched on the ground instead of sitting in it. Each stone was seated by sampling the dirt once, at its centre, and sinking its lowest point 35 mm below that.
    • On the slopes this left daylight under the downhill side: visible in ground_contact and as an air gap along the base of joint_stone_seat_2.
    • I measured it by splitting the area round each stone into 8 compass sectors and checking each for a vertex below the ground. No stone had more than 6 of 8 sectors sealed, and two had only 4.
  2. Knife-edge rim. The hill met the vertical walls at a knife edge all the way round, so the tile read as a slice of cake (three-quarter views, joint_slab_corner, front and left orthos).
  3. Grid-planted stones. All nine stones were within about 20% of one size, on a relaxed 3×3 grid; the top ortho reads as a grid at a glance.
  4. Bare sine surface. The terrain was a single smooth sine. Flat shading printed the 21×21 grid on the ground in the clay pass.
  5. Near-white stones in the hero (found in round 1). The lit faces clipped toward white, so they read as sugar lumps.

Generator changes, and why each is structural:

  1. Stones seal all round. The generator now samples the ground under every stone vertex, with a ray cast down onto a snapshot of the dirt taken before any stone is added. It then sinks the stone until every sector's most-buried vertex is below the ground. The rule is derived from the terrain under each stone, so it holds wherever the hill or the scatter moves.
  2. Rounded rim. The top-to-wall rim is bevelled in four segments inside slabify.
    • The first version used two segments and fell short: bevel gives the middle folds twice the angle of the end ones, so 90° splits into 22.5°, 45° and 22.5°.
    • Three segments still left a 40.5° step, because where the hill rises into the edge the fold is wider than 90°.
  3. Varied stone sizes. STONE_SIZES gives each scatter point a size factor from 0.55 to 1.45, and the relaxation spaces each pair of stones by the sum of their own bounds, so large and small stones clear by the same margin.
  4. Ground texture. Two closed-form micro-relief terms (14 mm and 7 mm) are added in the same Geometry Nodes tree. The hill and rim are smooth-shaded; stones and walls stay flat.
  5. Materials. Stones are now mid-grey, and the dirt darkens toward a subsoil colour below the slab lift, in object space, without adding a third material.

Collider: it now hulls a coarse, unbevelled 9-vertex tile (COLLIDER_GRID). On the full-resolution tile, the chamfer and relief pushed the hull to 178 triangles, over its 120 ceiling. The ceiling was not raised.

New budgets. Every value is identical on 4.5.11, 5.1.2 and 5.2.1:

Budget Declared Measured
Sealed sectors per stone 8 / 8 8 / 8
Stone footprint ratio (largest / smallest) ≥ 3.0 9.812
Rim tilt step between adjacent dirt faces ≤ 40° 33.52°

The rim budget measures the step in normal elevation, not the full angle between faces. At the four corners, the two chamfer strips meet at 90° in plan, which is a rounded corner, not a knife edge.

Existing budgets, re-measured:

  • Hygiene is 0 on every axis.
  • Stone overlaps: 0.
  • Stone floor zmin: 0.069 m.
  • Seat offset: −33 mm.
  • LOD ratios: 0.4996 and 0.2193.
  • UV overlap: 0.

Re-fitted: the stated outer AABB height moved from 0.584 m to 0.570 m, because the stones now sink into the ground.

Triangles:

  • Base: 1758 → 2398, inside the unchanged 1400–2800 band. The rim bevel accounts for the 640.
  • Collider: 72 → 36.

Falsifiers. Each new one violates exactly one new budget:

  • --perch-rocks restores the old centre-sample seating: 3 of 8 sectors sealed.
  • --uniform-rocks sets every size factor to 1: footprint ratio 1.851.
  • --sharp-rim skips the bevel: 88.50° step.

--pile-rocks now reports 7 interpenetrating pairs (it was 6).

default --skip-decimate --stray-vert --lift-z --poke-rock --float-rocks --box-rocks --pile-rocks --perch-rocks --uniform-rocks --sharp-rim --output
4.5.11 0 9 15 16 17 18 19 20 21 22 23 0
5.1.2 0 9 15 16 17 18 19 20 21 22 23 0
5.2.1 0 9 15 16 17 18 19 20 21 22 23 0

Inspection rounds: 3.

  • Round 1 fixed the geometry.
  • Round 2 fixed the materials. The subsoil band mostly lands on the shadowed front wall, so it is subtle.
  • Round 3 fixed the camera. The framing gate had passed at exactly fill 0.700, with the tile sitting low (bottom margin 0.033, top 0.267). The camera now aims at the tile's base and sits 10% closer: fill 0.797, minimum margin 0.069, with no deviation=.

Determinism: every measured value matches between repeated runs on 5.2.1 and across the three binaries. Export size differs by 16 bytes on 5.2.1, as before; export size is not a gated budget.

Contact sheet: terrain-scatter.

before after
luma 0.200 0.198
stage 0.192 0.198
warmth +0.129 +0.146
sat 0.141 0.144

It sits inside the band and is legible at thumbnail size.

Asset sheet: terrain-scatter. The tile is rendered alone, neutrally, beside the four pinned asset references. Verdict: passes, narrowly.

  • The hydrant and the jerry can are clearly more designed.
  • The tile is comparable to the rocket and the mech arm.
  • A flat terrain tile is inherently a quieter read than a turned prop.

This is the first showcase asset sheet committed. The renderer is the .scratch/aq/asset_sheet_one.py recipe from #117, extended to find showcase scripts.

Convention change (its own commit, in showcase/README.md):

  • Updated: "Sampled host seat" now covers only the surface sample and the floor clamp; the sealing rule is a new entry. "Scattered parts do not interpenetrate" now spaces each pair by its own bounds.
  • New: "A seated part is sealed all round", "Scatter varies in size", and "A rim over a slope is bounded by its tilt step".

car-mirror-symmetry (Track B)

Defect: the key light is at −X, but the camera stood at +X. The flank it saw was the one the key cannot reach, so the whole visible side rendered near-black. The old hero measured luma 0.138, below the calibration band's 0.151 floor. Revealed by the hero and close_2_WheelRear in the inspection sheet.

Presentation change: the camera is mirrored to −X. The car is mirror-symmetric, which is the very contract it witnesses, so the composition is unchanged and only the lit flank now faces the lens.

  • A bright streak now shows along the cowl crease below the windshield. Test renders with metallic set to 0 and with the taillamp hidden show it is the key light's highlight in a crease that faces it, not an emissive part or a crack in the mesh. I left it, and a paint-roughness change that made no visible difference was reverted.
  • Out-of-band framing: fill 0.772, minimum margin 0.059.

Contract confirmation: the contract, assertions and exit codes are unchanged. I diffed every measured-value line across all 9 runs against the pre-change baseline logs, and there are no differences.

default --no-mirror --output
before, 4.5.11 / 5.1.2 / 5.2.1 0 / 0 / 0 5 / 5 / 5 0 / 0 / 0
after, 4.5.11 / 5.1.2 / 5.2.1 0 / 0 / 0 5 / 5 / 5 0 / 0 / 0

Contact sheet: car-mirror-symmetry.

before after
luma 0.138 0.169
stage 0.148 0.179
warmth +0.002 +0.068
sat 0.247 0.253

It is now inside the band. The existing alt text still describes the still accurately.

Checks

Run locally and passing:

  • check_exit_code_readme.py
  • check_gallery_images.py
  • check_falsifier_targets.py (227 falsifiers across 28 pieces)

build_gallery.py was run last. I read the generated alt text in docs/gallery/index.html and on both detail pages.

🤖 Generated with Claude Code

TMHSDigital and others added 3 commits September 25, 2026 06:39
…dgets

The tile read as a slice of cake with pebbles dropped on it.
- Stones were seated by one dirt sample at their centre, which left daylight
  under the downhill side: no stone was sealed in more than 6 of 8 azimuth
  sectors. Each stone now samples the ground under every vertex and sinks
  until every sector holds a buried vertex (exit 21, --perch-rocks).
- All nine stones were within ~20% of one size. STONE_SIZES gives each a
  factor from 0.55 to 1.45 and the relaxation spaces pairs by their own
  bounds (footprint ratio 9.81 >= 3.0, exit 22, --uniform-rocks).
- The hill met the walls at a knife edge. The rim is bevelled in four
  segments, bounded by the step in normal elevation (33.5 <= 40 deg,
  exit 23, --sharp-rim).
- Two closed-form micro-relief terms in the GN tree; hill smooth-shaded;
  stones mid-grey; dirt darkens to subsoil down the walls.
Base tris 1758 -> 2398. The collider hulls a coarse unbevelled tile (36).
The outer AABB was re-fitted from 0.584 to 0.570 m. Same results on 4.5.11,
5.1.2 and 5.2.1. Includes the asset sheet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
… rim tilt

Three conventions from the terrain-scatter pass: a seated part is sunk until
every azimuth sector holds a buried vertex; scatter carries a size factor and
asserts a footprint-ratio floor; a rim over a slope bounds its step in normal
elevation (not the dihedral, which misreads rounded corners). Relaxation now
spaces each pair by the sum of its own bounds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
The key light is at -X but the camera stood at +X, so the flank it saw was
the one the key cannot reach and the whole visible side rendered
near-black: the hero measured luma 0.138, below the 0.151 calibration floor.
The car is mirror-symmetric, so mirroring the camera keeps the composition;
luma is now 0.169. Render path only. Exit codes unchanged on 4.5.11, 5.1.2
and 5.2.1 (default 0, --no-mirror 5, --output 0), and every measured line
matches the baseline. Shared gallery index rides here.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com>
@TMHSDigital TMHSDigital added the needs-5.1 Opt-in Blender 5.1 smoke on this PR. Default matrix stays 4.5 + 5.2. Auto-label will not apply this. label Sep 25, 2026
@github-actions github-actions Bot added examples Runnable smoke-gated examples under examples/ showcase Budget-conformance props under showcase/ documentation Improvements or additions to documentation labels Sep 25, 2026
@TMHSDigital
TMHSDigital merged commit 418b641 into main Sep 25, 2026
14 checks passed
@TMHSDigital
TMHSDigital deleted the fix/terrain-scatter-car-quality-pass branch September 25, 2026 10:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation examples Runnable smoke-gated examples under examples/ needs-5.1 Opt-in Blender 5.1 smoke on this PR. Default matrix stays 4.5 + 5.2. Auto-label will not apply this. showcase Budget-conformance props under showcase/

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant