git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit596c4071fd7c942e39864accbc43870e4b4e588e
parentec7c36d482
authorLucas Galante <[email protected]>
date2026-08-15 20:50
feat: cce-relief draws the real prim under the prediction

The predicted shading band said what relief_shade computes. This adds the
band under it that says what the RENDERER draws: the shape emitted as
actual prims, through the same PaintCtx as everything else, on the same
x-scale.

Together they are the anti-drift device with teeth. A shared constant and a
source-parsing test assert the numbers agree; two bands assert the PICTURES
do. Any divergence — a bug in the port, or a shader change the port has not
tracked — shows up as a visible seam between them instead of as silence.

Measured against the live rendering, the port reproduces the shader to
within a grey level: mean |difference| 0.30 (recess), 0.78 (trough), 0.78
(ridge), 0.91 (stacked inset) across excursions of 46-110 levels, i.e.
under 1% in every case. The residual maxima of 7-10 sit on the steepest
gradients, where the strip's 1px sampling meets the shader's per-pixel SDF
with AA.

Alignment relies on a carve's wall straddling its box edge by ±depth/2:
emitting at depth = `unit` with the edge at the section's own wall centre
puts the real wall over the predicted one. The box is deliberately tall and
wide so only its LEFT wall lands in the band, and the whole thing is
clipped — a free carve's cover quad inflates past its box and would
otherwise paint over the section above and the sliders below.

The swatch's backing quad is forced OPAQUE. page_low_color carries the
plate's own alpha, and over the near-black opening that landed ~4 grey
levels under the predicted band — a constant offset that reads as model
error and is not. The bands must differ only by shading, which is the one
thing they are there to compare.

 src/bin/cce-relief.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 71 insertions(+), 2 deletions(-)

diff --git a/src/bin/cce-relief.rs b/src/bin/cce-relief.rs
index 23f046f..7cac0d9 100644
--- a/src/bin/cce-relief.rs
+++ b/src/bin/cce-relief.rs
@@ -406,7 +406,7 @@ fn draw_section(pc: &mut PaintCtx, rect: Rect, profile: &ProfileKnobs, shape: Sh
     // and the SECTION lays out in what is left. Reserving it up front is what
     // keeps the slab from expanding over it — the slab grows to the bottom of
     // whatever area it is given.
-    let strip_band = SHADE_STRIP_H + 4.0;
+    let strip_band = 2.0 * SHADE_STRIP_H + 6.0;
     let sec_h = rect.height - strip_band;
     // stroke + slab-underside room, plus the bottom gutter
     let avail_h = sec_h - 2.0 * m - UNDERSIDE - GUTTER_B;
@@ -592,9 +592,78 @@ fn draw_section(pc: &mut PaintCtx, rect: Rect, profile: &ProfileKnobs, shape: Sh
             );
             x += step;
         }
+        // THE LIVE SWATCH, directly under the prediction and on the same
+        // x-scale: the shape emitted as REAL prims, through the same PaintCtx
+        // as everything else, so the renderer draws it with the actual shader.
+        //
+        // The two bands are the anti-drift device with teeth. A shared constant
+        // and a parsing test say the numbers agree; these say the PICTURES do.
+        // Any divergence between them is either a bug in relief_shade or a
+        // change in the shader that relief_shade has not tracked, and it shows
+        // up as a visible seam between the bands rather than as silence.
+        //
+        // Alignment: a carve's wall straddles its box edge by ±depth/2, so
+        // emitting at depth = `unit` with the edge at the section's own wall
+        // centre puts the real wall over the predicted one, column for column.
+        let swatch_y = strip_y + strip_h + 2.0;
+        let edge_x = x0 + unit * 0.5;
+        // Tall and wide: only the LEFT wall is meant to land in the band, so
+        // the box's other three edges are pushed well outside it. The clip is
+        // what keeps the cover quad — which inflates past the box — off the
+        // section above and the sliders below.
+        pc.clip(
+            Rect { x: x_l, y: swatch_y, width: x_r - x_l, height: strip_h },
+            |pc| {
+                // OPAQUE: page_low_color carries the plate's own alpha, and
+                // over the near-black opening that lands ~4 grey levels below
+                // the predicted band, which then reads as a constant model
+                // error it is not. The two bands must differ ONLY by shading.
+                pc.quad(
+                    Rect { x: x_l, y: swatch_y, width: x_r - x_l, height: strip_h },
+                    [plate[0], plate[1], plate[2], 1.0],
+                );
+                let tall = Rect {
+                    x: edge_x,
+                    y: swatch_y - 400.0,
+                    width: 4000.0,
+                    height: strip_h + 800.0,
+                };
+                let sq = (0.0, 0.0, 0.0, 0.0);
+                match shape {
+                    Shape::Recess | Shape::Fillet => pc.recess(tall, sq, unit),
+                    Shape::Boss => pc.boss(tall, sq, unit),
+                    Shape::Ridge => pc.ridge(tall, sq, unit),
+                    Shape::Trough => pc.trough(tall, sq, unit),
+                    Shape::InsetPlate => pc.inset_plate(tall, sq, [0.0; 4], unit),
+                    Shape::Groove => {
+                        let cx = x0 + unit * (0.5 + GROOVE_FLOOR * 0.5);
+                        pc.groove(
+                            (cx, swatch_y - 400.0),
+                            (cx, swatch_y + strip_h + 400.0),
+                            GROOVE_FLOOR * unit,
+                            unit,
+                            tall,
+                        );
+                    }
+                    // The pair as inset_plate used to emit it, in order.
+                    Shape::InsetStacked => {
+                        let g = unit * 0.5;
+                        let inner = Rect { x: edge_x + g, ..tall };
+                        pc.recess(
+                            Rect { x: inner.x - g, y: inner.y, width: inner.width + 2.0 * g, height: inner.height },
+                            sq,
+                            unit,
+                        );
+                        pc.boss(inner, sq, unit);
+                    }
+                    Shape::EdgeRoll => pc.plate(tall, sq, plate, unit),
+                }
+            },
+        );
+
         if walls.is_empty() {
             pc.text_with(
-                "plate branch — not the free-carve model".to_string(),
+                "plate branch — predicted band is blank; the swatch is real".to_string(),
                 x_l + 4.0,
                 strip_y + 1.0,
                 10.0,