Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-core/src/render/decor.rs

//! **The "cyber-toon" decoration kernel** — domain-agnostic, pure-CPU L0 beauty
//! helpers that turn a flat instance graph into a luminous, animated dashboard
//! **with zero GPU / vello dependency**, so they light up identically native AND
//! in the wasm demo. The graph/map skins (and the population showcase) compose
//! these over the [`prim`](super::prim) instances they already lower.
//!
//! Everything here is:
//! - **pure** — a function of its inputs (no globals, no `Instant::now()`); all
//!   animation is driven by an injected `phase`/`t` so snapshots are stable (the
//!   determinism LAW). A test feeds a fixed clock and the picture is reproducible.
//! - **L0-only** — emits [`QuadInstance`]/[`LineInstance`]/[`CircleInstance`]/
//!   [`RingInstance`], the same vocabulary the CPU raster + GPU pipeline both draw,
//!   so the effects work on the always-available CPU lane (`l1-vello` is an
//!   *elevation*, never a requirement).
//! - **domain-agnostic** — it knows nothing about "Person" or "Debt"; a caller
//!   decides which nodes glow, which edges flow, where a shockwave fires.
//!
//! ## The helper menu (maps onto the aesthetic spec)
//! - [`glow_halo`] — a soft colored light-bleed ring behind a node (the glossy
//!   orb look). The CPU bloom substitute for an L1 gaussian glow.
//! - [`ao_shadow`] — a toon drop-shadow / ambient-occlusion disc under a node
//!   (deeper where nodes bunch — clustered nodes pool a darker shadow).
//! - [`dash_line`] — an **animated dash-array** stroke (a `→` flow of segments that
//!   marches along the edge as `phase` advances) — debt flowing toward enforcement.
//! - [`particles_on_edge`] — light-tracer **particles** zipping along an edge
//!   (money moving Loan→Person), positions a pure function of `phase`.
//! - [`pulse`] / [`strobe`] — deterministic 0..1 envelopes for the Debt **pulse**
//!   and PoliceRecord **strobe** (animation timing, injected-clock driven).
//! - [`shockwave_ring`] — a radial ring expanding from a clicked node.
//! - [`bloom_composite`] — a cheap separable box-blur **bloom** post-pass over a
//!   straight-RGBA8 [`Frame`]: bright pixels bleed light into their neighbours so
//!   intersecting neon lines mix into colored "explosions".
//! - [`lerp_rgba`] / [`gradient_by_scalar`] — a 2-stop (or multi-stop) colour ramp
//!   keyed by a scalar in `[0,1]` (the gradient-fill-by-scalar engine primitive).

use super::Frame;
use super::prim::{CircleInstance, LineInstance, QuadInstance, RingInstance, shape};

/// τ (one full animation cycle) — phases are in **cycles** (`0.0..1.0` is one loop),
/// so a caller advances `phase += dt / period` and the look is period-independent.
const TAU: f32 = std::f32::consts::TAU;

// ───────────────────────────────────────────────────────────────────────────
// Colour ramps (gradient-by-scalar engine primitive)
// ───────────────────────────────────────────────────────────────────────────

/// Linear-interpolate two straight RGBA colours at `t ∈ [0,1]`.
#[inline]
pub fn lerp_rgba(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
    let t = t.clamp(0.0, 1.0);
    [
        a[0] + (b[0] - a[0]) * t,
        a[1] + (b[1] - a[1]) * t,
        a[2] + (b[2] - a[2]) * t,
        a[3] + (b[3] - a[3]) * t,
    ]
}

/// Sample a piecewise-linear **multi-stop** gradient (`stops` sorted by position)
/// at `t ∈ [0,1]` — the domain-agnostic "fill-by-scalar" ramp (debt-heat, degree,
/// population). Clamps below the first / above the last stop. `stops` must be
/// non-empty.
pub fn gradient_by_scalar(stops: &[(f32, [f32; 4])], t: f32) -> [f32; 4] {
    debug_assert!(!stops.is_empty(), "gradient needs at least one stop");
    let t = t.clamp(0.0, 1.0);
    if t <= stops[0].0 {
        return stops[0].1;
    }
    for w in stops.windows(2) {
        let (p0, c0) = w[0];
        let (p1, c1) = w[1];
        if t <= p1 {
            let span = (p1 - p0).max(1e-6);
            return lerp_rgba(c0, c1, (t - p0) / span);
        }
    }
    stops[stops.len() - 1].1
}

// ───────────────────────────────────────────────────────────────────────────
// Animation envelopes (injected-clock driven, pure)
// ───────────────────────────────────────────────────────────────────────────

/// A smooth `0..1` **pulse** envelope (a raised cosine) at `phase` cycles —
/// `0 → 1 → 0` once per cycle. The Debt/KfmCase "intense glow" breathing.
#[inline]
pub fn pulse(phase: f32) -> f32 {
    0.5 - 0.5 * (phase * TAU).cos()
}

/// A hard **strobe** envelope at `phase` cycles with duty `duty ∈ (0,1)` — `1.0`
/// for the first `duty` of each cycle, else `0.0` (PoliceRecord hazard flash). A
/// pure step so snapshots at a fixed `phase` are exact.
#[inline]
pub fn strobe(phase: f32, duty: f32) -> f32 {
    if phase.rem_euclid(1.0) < duty.clamp(0.0, 1.0) { 1.0 } else { 0.0 }
}

// ───────────────────────────────────────────────────────────────────────────
// Node beauty: glow halo + ambient-occlusion drop-shadow
// ───────────────────────────────────────────────────────────────────────────

/// A soft colored **glow halo** ring behind a node marker — the CPU light-bleed
/// that makes a node read as a glossy orb. `r` is the marker radius, `intensity`
/// the halo alpha (`0..1`), `spread` the outer-radius multiplier (≥ 1).
#[inline]
pub fn glow_halo(center: [f32; 2], r: f32, color: [f32; 3], intensity: f32, spread: f32) -> RingInstance {
    let spread = spread.max(1.05);
    RingInstance {
        center,
        radius: r * spread,
        inner: r * 1.05,
        color: [color[0], color[1], color[2], intensity.clamp(0.0, 1.0)],
        aa: r * 0.5 + 1.5, // a wide AA band = a soft, blurry bleed (no GPU blur needed)
    }
}

/// A toon **ambient-occlusion drop-shadow** disc under a node — a dark, soft circle
/// offset down-right, so clustered nodes pool a deeper shadow (the "ray-traced"
/// grounding). `r` the marker radius, `depth` the shadow alpha (`0..1`).
#[inline]
pub fn ao_shadow(center: [f32; 2], r: f32, depth: f32) -> CircleInstance {
    CircleInstance {
        center: [center[0] + r * 0.22, center[1] + r * 0.30],
        radius: r * 1.18,
        color: [0.0, 0.0, 0.0, depth.clamp(0.0, 1.0)],
        aa: r * 0.6 + 1.5,
    }
}

/// Build glow + AO for a batch of node markers in one pass (the common case): pull
/// the SQUARE/CIRCLE/DIAMOND node quads out of `quads`, and for each emit an AO
/// shadow (under) + a glow halo (also under, over the shadow). Returns
/// `(ao_quads, glow_quads)` already lowered, so a caller pushes
/// `shadows → glows → original quads` for the layered orb look. `intensity` scales
/// the glow alpha; `ao` the shadow alpha.
pub fn node_decor(
    quads: &[QuadInstance],
    intensity: f32,
    ao: f32,
) -> (Vec<QuadInstance>, Vec<QuadInstance>) {
    let mut shadows = Vec::with_capacity(quads.len());
    let mut glows = Vec::with_capacity(quads.len());
    for q in quads {
        if q.shape == shape::SQUARE || q.shape == shape::CIRCLE || q.shape == shape::DIAMOND {
            let r = q.radius;
            shadows.push(ao_shadow(q.center, r, ao).lower());
            glows.push(glow_halo(q.center, r, [q.color[0], q.color[1], q.color[2]], intensity, 2.1).lower());
        }
    }
    (shadows, glows)
}

// ───────────────────────────────────────────────────────────────────────────
// Edge beauty: animated dash-array + particle tracers
// ───────────────────────────────────────────────────────────────────────────

/// An **animated dash-array** line `a → b`: the segment is chopped into `dash`-long
/// lit pieces separated by `gap`, and the whole pattern **marches** along the
/// direction by `phase` (in dash+gap units) — a directional "flow". Pure in
/// `phase`, so a fixed clock gives a fixed picture. `half_width`/`aa`/`color` as
/// for a [`LineInstance`].
#[allow(clippy::too_many_arguments)] // public signature hosts call: kept as is
pub fn dash_line(
    a: [f32; 2],
    b: [f32; 2],
    half_width: f32,
    aa: f32,
    color: [f32; 4],
    dash: f32,
    gap: f32,
    phase: f32,
) -> Vec<LineInstance> {
    let dash = dash.max(0.5);
    let gap = gap.max(0.0);
    let period = dash + gap;
    let dx = b[0] - a[0];
    let dy = b[1] - a[1];
    let len = (dx * dx + dy * dy).sqrt();
    if len < 1e-3 {
        return Vec::new();
    }
    let (ux, uy) = (dx / len, dy / len);
    // March offset (always backward against the flow so dashes appear to move a→b).
    let mut t = -(phase.rem_euclid(1.0) * period);
    let mut out = Vec::new();
    while t < len {
        let s = t.max(0.0);
        let e = (t + dash).min(len);
        if e > s {
            out.push(LineInstance::round(
                [a[0] + ux * s, a[1] + uy * s],
                [a[0] + ux * e, a[1] + uy * e],
                half_width,
                aa,
                color,
            ));
        }
        t += period;
    }
    out
}

/// **Light-tracer particles** zipping along an edge `a → b`: `n` evenly-spaced
/// discs whose fractional position is `(i/n + phase) mod 1`, so they flow a→b as
/// `phase` advances. Pure in `phase`. `radius`/`color` per particle.
pub fn particles_on_edge(
    a: [f32; 2],
    b: [f32; 2],
    n: usize,
    radius: f32,
    color: [f32; 4],
    phase: f32,
) -> Vec<CircleInstance> {
    if n == 0 {
        return Vec::new();
    }
    (0..n)
        .map(|i| {
            let f = ((i as f32 / n as f32) + phase).rem_euclid(1.0);
            // Brighten toward the head of the trail (a fading comet look).
            let head = 0.55 + 0.45 * f; // dim at the tail, bright at the front
            let c = [color[0], color[1], color[2], (color[3] * head).clamp(0.0, 1.0)];
            CircleInstance {
                center: [a[0] + (b[0] - a[0]) * f, a[1] + (b[1] - a[1]) * f],
                radius: radius * (0.6 + 0.6 * f),
                color: c,
                aa: 1.0,
            }
        })
        .collect()
}

// ───────────────────────────────────────────────────────────────────────────
// Interaction: shockwave
// ───────────────────────────────────────────────────────────────────────────

/// A radial **shockwave** ring expanding from `center` as `t ∈ [0,1]` runs
/// (0 = just fired, 1 = faded out): the ring grows `r0 → r0 + reach` while its
/// alpha fades `1 → 0`. Pure in `t`. The neon-dashboard "click ripple".
#[inline]
pub fn shockwave_ring(center: [f32; 2], r0: f32, reach: f32, color: [f32; 3], t: f32) -> RingInstance {
    let t = t.clamp(0.0, 1.0);
    let radius = r0 + reach * t;
    let thickness = (reach * 0.10).max(2.0);
    RingInstance {
        center,
        radius,
        inner: (radius - thickness).max(0.0),
        color: [color[0], color[1], color[2], (1.0 - t).powf(1.4)],
        aa: 2.0,
    }
}

// ───────────────────────────────────────────────────────────────────────────
// Bloom (CPU post-process)
// ───────────────────────────────────────────────────────────────────────────

/// A cheap separable **bloom** post-pass over a straight-RGBA8 [`Frame`]: extract
/// the pixels brighter than `threshold` (`0..1` luma), blur them with a small
/// box-blur of `radius` px (separable, two passes), and **add** that light back
/// onto the frame scaled by `intensity`. Intersecting neon lines therefore bleed
/// and mix into colored "explosions" — the spec's bloom, with no GPU.
///
/// Pure, in-place, deterministic. `radius` is clamped to a small range so a wasm
/// frame stays snappy. A no-op when `intensity <= 0` or `radius == 0`.
pub fn bloom_composite(frame: &mut Frame, threshold: f32, radius: u32, intensity: f32) {
    if intensity <= 0.0 || radius == 0 || frame.width == 0 || frame.height == 0 {
        return;
    }
    let (w, h) = (frame.width as usize, frame.height as usize);
    let radius = radius.min(8) as i32; // keep the blur cheap (wasm-safe)
    let n = w * h;

    // 1) Bright-pass: keep only pixels whose luma exceeds the threshold.
    let mut bright = vec![0f32; n * 3];
    for (i, px) in frame.rgba.as_chunks::<4>().0.iter().enumerate() {
        let r = px[0] as f32 / 255.0;
        let g = px[1] as f32 / 255.0;
        let b = px[2] as f32 / 255.0;
        let luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
        if luma > threshold {
            // soft knee so the bloom ramps in rather than hard-clips
            let k = ((luma - threshold) / (1.0 - threshold).max(1e-3)).clamp(0.0, 1.0);
            bright[i * 3] = r * k;
            bright[i * 3 + 1] = g * k;
            bright[i * 3 + 2] = b * k;
        }
    }

    // 2) Separable box-blur (horizontal then vertical) of the bright layer.
    let win = (2 * radius + 1) as f32;
    let mut tmp = vec![0f32; n * 3];
    // horizontal
    for y in 0..h {
        for x in 0..w {
            let mut acc = [0f32; 3];
            for d in -radius..=radius {
                let sx = (x as i32 + d).clamp(0, w as i32 - 1) as usize;
                let si = (y * w + sx) * 3;
                acc[0] += bright[si];
                acc[1] += bright[si + 1];
                acc[2] += bright[si + 2];
            }
            let di = (y * w + x) * 3;
            tmp[di] = acc[0] / win;
            tmp[di + 1] = acc[1] / win;
            tmp[di + 2] = acc[2] / win;
        }
    }
    // vertical (back into `bright`)
    for y in 0..h {
        for x in 0..w {
            let mut acc = [0f32; 3];
            for d in -radius..=radius {
                let sy = (y as i32 + d).clamp(0, h as i32 - 1) as usize;
                let si = (sy * w + x) * 3;
                acc[0] += tmp[si];
                acc[1] += tmp[si + 1];
                acc[2] += tmp[si + 2];
            }
            let di = (y * w + x) * 3;
            bright[di] = acc[0] / win;
            bright[di + 1] = acc[1] / win;
            bright[di + 2] = acc[2] / win;
        }
    }

    // 3) Additive composite the blurred light back onto the frame.
    for (i, px) in frame.rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() {
        for k in 0..3 {
            let base = px[k] as f32 / 255.0;
            let add = bright[i * 3 + k] * intensity;
            px[k] = ((base + add) * 255.0).round().clamp(0.0, 255.0) as u8;
        }
        // bloom never makes a transparent pixel opaque on its own, but it lifts
        // alpha where it added real light so the glow is visible over the ground.
        if px[3] == 0 {
            let lit = bright[i * 3] + bright[i * 3 + 1] + bright[i * 3 + 2];
            if lit * intensity > 0.02 {
                px[3] = (lit * intensity * 255.0).round().clamp(0.0, 255.0) as u8;
            }
        }
    }
}

// ───────────────────────────────────────────────────────────────────────────
// The GLOW STROKE ladder — an EverQuest-II-style luminous line, on any renderer
// ───────────────────────────────────────────────────────────────────────────

/// **One pass of a layered glow stroke.** A glowing line is not one stroke, it is a
/// LADDER of them drawn widest-first over the same path: a wide, nearly transparent
/// bleed, a mid halo, and a thin core that is blended toward white so the line reads
/// as *hot* rather than merely coloured.
///
/// Every field is a MULTIPLIER or a fraction, never a pixel count, so the same ladder
/// serves a 1.5 px graph edge and a 7 m map ribbon.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GlowPass {
    /// Stroke width as a multiple of the caller's core width.
    pub width_mul: f32,
    /// Alpha of this pass, `0..1`.
    pub alpha: f32,
    /// How far the ink is blended toward white, `0..1` — `0` keeps the hue, `1` is
    /// pure white. Only the innermost passes carry any; that is the whole "hot core".
    pub white: f32,
}

/// **The ladder** — five passes, widest and faintest first, ending on a near-white
/// core. Read once, projected by every renderer (LAW: one writer).
///
/// # Why five and why these numbers
///
/// The three-pass ladder korp shipped before this — `(9.0, α46) (4.5, α120)
/// (1.6, α255)` at the hue's own colour — did not read as a glow, and the reasons are
/// measurable, not aesthetic:
///
/// * **The widest pass was too narrow.** A halo has to be several times the core to
///   read as light leaving the line; at 5.6× core with α 0.18 it read as a second,
///   thicker line. Here the outermost pass is 14× the core at α 0.09.
/// * **The core kept the hue.** A saturated cyan at α 1.0 is a cyan line. Light is
///   white at the centre and coloured at the edge — that is what a bloom does, and
///   what an eye reads as emission. The inner two passes carry `white` 0.55 / 0.9.
/// * **The alpha budget was linear.** Stacked translucent strokes composite
///   multiplicatively, so a linear ramp piles most of the ink in the middle. The
///   ladder is geometric: 0.09 → 0.16 → 0.30 → 0.62 → 1.0.
pub const GLOW_LADDER: [GlowPass; 5] = [
    GlowPass { width_mul: 14.0, alpha: 0.09, white: 0.0 },
    GlowPass { width_mul: 8.0, alpha: 0.16, white: 0.0 },
    GlowPass { width_mul: 4.2, alpha: 0.30, white: 0.10 },
    GlowPass { width_mul: 2.0, alpha: 0.62, white: 0.55 },
    GlowPass { width_mul: 1.0, alpha: 1.0, white: 0.90 },
];

/// **The ladder lowered to concrete `(width, rgba)` strokes** for one core width and
/// one ink. Widest/faintest first — a caller draws them IN ORDER over the same path.
///
/// `ink` is straight (non-premultiplied) linear RGB in `0..1`. The returned alpha is
/// `pass.alpha`, so a caller that also carries an opacity multiplies it in.
///
/// Pure and deterministic: same inputs, same ladder, on CPU and GPU alike.
#[must_use]
pub fn glow_stroke_passes(core_width: f32, ink: [f32; 3]) -> Vec<(f32, [f32; 4])> {
    glow_stroke_passes_on(core_width, ink, GlowGround::Dark)
}

/// **What the glow is drawn ON.** A luminous line is a contrast effect, and the two
/// grounds need opposite cores — so this is a required input, not a style option.
///
/// MEASURED 2026-09-03 (korp's `tests/golden_trail_pixels.rs`, the same ladder on both
/// papers): over the dark pane the white-hot core reads as emission and the frame is a
/// neon filament. Over the cartographic DAY paper (`#F2EFE9`, which is the pane the owner
/// was actually looking at) the identical ladder composites to `(232, 250, 253)` — a
/// near-white line on near-white paper, i.e. **invisible** — and the low-alpha halo
/// desaturates to grey. The picture was a grey smudge with a hole down the middle.
///
/// Over light paper a glowing line is the *highlighter* look instead: the core is the
/// hue at full saturation and DARKENED, the halo is the hue at higher alpha than the
/// dark-ground ladder needs. Same widths, same reach, same ordering — only the tone
/// flips.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GlowGround {
    /// A dark pane: the core burns toward white.
    Dark,
    /// Light paper: the core deepens toward the saturated hue.
    Light,
}

/// **How far EVERY light-ground pass is pushed toward black** — the halo included.
///
/// A low-alpha saturated hue over cream composites to a pale wash, and a pale wash on
/// pale paper is grey: the first light-ground attempt (halo at the pure hue, core
/// deepened) drew a grey smudge with a blue thread in it. Deepening the whole ladder
/// turns the halo into a coloured **contact shadow** — which is what makes a bright line
/// read as luminous against a bright ground, in EQ2 and on paper alike.
const GLOW_LIGHT_DEEPEN_BASE: f32 = 0.30;
/// …and how much FURTHER the core is deepened, on top of the base — so the ladder still
/// has a hot centre, just a saturated one instead of a white one.
const GLOW_LIGHT_DEEPEN_CORE: f32 = 0.30;
/// Alpha multiplier for the light-ground ladder — a wash that reads on a dark pane is
/// nothing at all on cream.
const GLOW_LIGHT_ALPHA_GAIN: f32 = 2.2;

/// **The ladder lowered to concrete `(width, rgba)` strokes** for one core width, one
/// ink and the ground it is drawn on. Widest/faintest first — a caller draws them IN
/// ORDER over the same path.
///
/// `ink` is straight (non-premultiplied) RGB in `0..1`. The returned alpha is the
/// pass's, so a caller carrying its own opacity multiplies it in.
///
/// Pure and deterministic: same inputs, same ladder, on CPU and GPU alike.
#[must_use]
pub fn glow_stroke_passes_on(
    core_width: f32,
    ink: [f32; 3],
    ground: GlowGround,
) -> Vec<(f32, [f32; 4])> {
    let core = core_width.max(0.1);
    GLOW_LADDER
        .iter()
        .map(|p| {
            let t = p.white.clamp(0.0, 1.0);
            let (rgb, alpha) = match ground {
                // Toward WHITE by `white`: a hot centre inside a coloured bleed.
                GlowGround::Dark => (
                    [
                        ink[0] + (1.0 - ink[0]) * t,
                        ink[1] + (1.0 - ink[1]) * t,
                        ink[2] + (1.0 - ink[2]) * t,
                    ],
                    p.alpha,
                ),
                // Toward BLACK by `white · GLOW_LIGHT_DEEPEN`, and every pass carries
                // more ink: the same shape, inverted for paper.
                GlowGround::Light => {
                    let k = 1.0 - GLOW_LIGHT_DEEPEN_BASE - t * GLOW_LIGHT_DEEPEN_CORE;
                    (
                        [ink[0] * k, ink[1] * k, ink[2] * k],
                        p.alpha * GLOW_LIGHT_ALPHA_GAIN,
                    )
                }
            };
            (core * p.width_mul, [rgb[0], rgb[1], rgb[2], alpha.clamp(0.0, 1.0)])
        })
        .collect()
}

/// **The glow as a CROSS-SECTION — one ribbon, not a stack of strokes.**
///
/// Returns `(half-offset px from the centreline, straight rgba)` from the OUTER edge
/// inward to the centre, for a caller that builds a per-vertex-coloured mesh.
///
/// # Why this exists (owner, 2026-09-03)
///
/// *"the golden path is looking like shit, its not a soft spline its a flat band with
/// some smoth edges. the eqII paths where like a soul / spirit trace shiny and
/// transparent glowing"*
///
/// [`glow_stroke_passes_on`] hands back five concentric STROKES, and every renderer
/// drew them source-over on top of each other. The arithmetic is what he was looking
/// at: composite alpha at the centreline is `1 − Π(1 − αᵢ)`, and the ladder's core pass
/// is `alpha 1.0`, so the middle of the trail is **fully opaque** and only the outermost
/// halo is soft. That is a flat band with smooth edges — paint, not light. Nothing
/// shows through it, which is the opposite of a spirit trace.
///
/// The numbers were never wrong; the way they were APPLIED was. Read the same ladder as
/// a profile across the width and interpolate between the rings in ONE mesh and you get
/// a true falloff: bright at the centreline, zero at the edge, the map visible through
/// the whole thing, and no plateau anywhere because nothing is drawn twice.
///
/// So this is the same `GLOW_LADDER` — the one writer of what a glow is — projected on
/// a different axis. A width multiple becomes an OFFSET and an alpha becomes a VERTEX
/// alpha. The outermost ring is forced to `alpha 0` so the ribbon fades into the ground
/// instead of ending on an edge, which is the other half of "smooth edges": an
/// antialiased boundary is still a boundary.
#[must_use]
pub fn glow_cross_section(core_width: f32, ink: [f32; 3], ground: GlowGround) -> Vec<(f32, [f32; 4])> {
    let passes = glow_stroke_passes_on(core_width, ink, ground);
    let mut out: Vec<(f32, [f32; 4])> = Vec::with_capacity(passes.len() + 1);
    // The widest pass defines the ribbon's edge, and the edge is TRANSPARENT.
    if let Some((w, c)) = passes.first() {
        out.push((w * 0.5, [c[0], c[1], c[2], 0.0]));
    }

    // ── The ACCUMULATION, which is the whole difference between one ribbon and five
    //    stacked strokes ──────────────────────────────────────────────────────────
    //
    // The ladder used to be painted as five separate translucent strokes on top of one
    // another, so at any distance from the centre the eye saw every pass at least that
    // wide, composited. The core was bright because five alphas had accumulated there,
    // not because the innermost pass was strong.
    //
    // A ribbon is ONE draw. Handing each ring its own pass' alpha therefore throws the
    // accumulation away, and the trail comes out far too faint to read — MEASURED by
    // `golden_trail_pixels::the_glow_ladder_beats_the_three_pass_line`, which caught the
    // first version of this function at a core departing the paper by **56** where the
    // stacked ladder reached **204**. That is a trail you cannot see on a map, which is
    // the same complaint the ribbon was built to answer, arrived at from the other side.
    //
    // So each ring carries the OVER-composite of every pass from the outermost down to
    // itself, and the centreline carries all of them — the same arithmetic
    // `glow_core_coverage` reports as one number, done per ring and in colour.
    let mut acc: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
    for (w, c) in &passes {
        acc = over(*c, acc);
        out.push((w * 0.5, acc));
    }
    // …and the centreline itself, at the fully accumulated colour: the innermost pass is
    // a stroke of finite width, so without this the profile would have a flat top exactly
    // as wide as the core. A ribbon wants a POINT of maximum, not a plateau — that
    // plateau is the defect this function exists to remove, in miniature.
    out.push((0.0, acc));
    out
}

/// `src` over `dst`, both premultiplied-by-nothing straight RGBA — the Porter-Duff
/// operator the stacked ladder performed implicitly by drawing one pass after another.
/// Returns straight RGBA again so the mesh can hand it to a vertex.
fn over(src: [f32; 4], dst: [f32; 4]) -> [f32; 4] {
    let (sa, da) = (src[3].clamp(0.0, 1.0), dst[3].clamp(0.0, 1.0));
    let out_a = sa + da * (1.0 - sa);
    if out_a <= f32::EPSILON {
        return [src[0], src[1], src[2], 0.0];
    }
    let mix = |s: f32, d: f32| (s * sa + d * da * (1.0 - sa)) / out_a;
    [
        mix(src[0], dst[0]),
        mix(src[1], dst[1]),
        mix(src[2], dst[2]),
        out_a,
    ]
}

/// **The ink coverage a whole ladder lays down at the line's centre**, `0..1` — the
/// over-operator composite of every pass' alpha. The single number "how bright is the
/// core", so a change to [`GLOW_LADDER`] that quietly dims the line is visible as a
/// number and not only as a picture.
#[must_use]
pub fn glow_core_coverage() -> f32 {
    GLOW_LADDER.iter().fold(0.0, |acc, p| acc + p.alpha.clamp(0.0, 1.0) * (1.0 - acc))
}

/// **How wide the glow reaches**, as a multiple of the core width — the outermost
/// pass. The halo's read is this number; a ladder whose widest pass is ~2× core is a
/// thick line, not a glow.
#[must_use]
pub fn glow_reach_mul() -> f32 {
    GLOW_LADDER.iter().fold(0.0f32, |m, p| m.max(p.width_mul))
}

/// **The flow phase along a path**, `0..1` — where the light-tracer particles sit at
/// clock `t` seconds for a stroke that cycles every `period_s`. Pure in `t` (the
/// determinism LAW); pair it with [`particles_on_edge`] to make a glow stroke *move*.
#[must_use]
pub fn glow_flow_phase(t: f32, period_s: f32) -> f32 {
    (t / period_s.max(1e-3)).rem_euclid(1.0)
}

#[cfg(test)]
mod tests {
    use super::*;

    /// INJECT-ASSERT: the 2-stop / multi-stop ramp lands on the right colours at
    /// the ends and blends in the middle (the gradient-by-scalar primitive).
    #[test]
    fn gradient_by_scalar_ramps_and_clamps() {
        let stops = [(0.0, [0.0, 0.0, 1.0, 1.0]), (1.0, [1.0, 0.0, 0.0, 1.0])];
        assert_eq!(gradient_by_scalar(&stops, -1.0), stops[0].1, "clamps below");
        assert_eq!(gradient_by_scalar(&stops, 2.0), stops[1].1, "clamps above");
        let mid = gradient_by_scalar(&stops, 0.5);
        assert!((mid[0] - 0.5).abs() < 1e-5 && (mid[2] - 0.5).abs() < 1e-5, "mid blends: {mid:?}");
        // A three-stop ramp picks the right segment.
        let three = [(0.0, [0.0; 4]), (0.5, [1.0, 1.0, 1.0, 1.0]), (1.0, [0.0; 4])];
        assert!(gradient_by_scalar(&three, 0.25)[0] > 0.4, "rises to the mid stop");
        assert!(gradient_by_scalar(&three, 0.75)[0] < 0.6, "falls past the mid stop");
    }

    /// INJECT-ASSERT: pulse/strobe are deterministic envelopes — the SAME phase
    /// gives the SAME value (snapshot-stable), and they actually move.
    #[test]
    fn pulse_and_strobe_are_deterministic_envelopes() {
        assert!(pulse(0.0).abs() < 1e-5, "pulse troughs at phase 0");
        assert!((pulse(0.5) - 1.0).abs() < 1e-5, "pulse peaks at half-cycle");
        assert_eq!(pulse(0.25), pulse(0.25), "deterministic");
        assert_eq!(strobe(0.05, 0.5), 1.0, "strobe lit in the duty window");
        assert_eq!(strobe(0.75, 0.5), 0.0, "strobe dark past the duty window");
        assert_eq!(strobe(1.05, 0.5), 1.0, "strobe is periodic (phase wraps)");
    }

    /// INJECT-ASSERT: the animated dash marches — at a different phase the lit
    /// segments sit at different offsets (a real flow, not a static dash).
    #[test]
    fn dash_line_marches_with_phase() {
        let a = [0.0, 0.0];
        let b = [100.0, 0.0];
        let d0 = dash_line(a, b, 2.0, 1.0, [1.0; 4], 8.0, 6.0, 0.0);
        let d1 = dash_line(a, b, 2.0, 1.0, [1.0; 4], 8.0, 6.0, 0.5);
        assert!(!d0.is_empty() && !d1.is_empty(), "the dash produced segments");
        // The pattern marches: the first lit segment ENDS at a different offset
        // (its start may clamp to 0 at both phases, but the lit length shifts).
        assert_ne!(d0[0].b[0], d1[0].b[0], "dashes moved along the edge with phase");
        // Determinism: same phase, same geometry.
        let d0b = dash_line(a, b, 2.0, 1.0, [1.0; 4], 8.0, 6.0, 0.0);
        assert_eq!(d0[0].a, d0b[0].a, "same phase → same dash (start)");
        assert_eq!(d0[0].b, d0b[0].b, "same phase → same dash (end)");
    }

    /// INJECT-ASSERT: particles ride the edge — at phase 0 they sit at i/n, and a
    /// phase bump slides every particle along the segment by the same fraction.
    #[test]
    fn particles_flow_along_the_edge() {
        let a = [0.0, 0.0];
        let b = [200.0, 0.0];
        let p0 = particles_on_edge(a, b, 4, 3.0, [1.0; 4], 0.0);
        let p1 = particles_on_edge(a, b, 4, 3.0, [1.0; 4], 0.25);
        assert_eq!(p0.len(), 4);
        // The first particle starts at the tail and advances a quarter-edge.
        assert!((p0[0].center[0] - 0.0).abs() < 1e-3, "particle 0 at the tail at phase 0");
        assert!((p1[0].center[0] - 50.0).abs() < 1e-3, "particle 0 advanced to x=50 at phase 0.25");
    }

    /// INJECT-ASSERT: the shockwave grows + fades with t (a real ripple), staying
    /// a valid annulus.
    #[test]
    fn shockwave_expands_and_fades() {
        let early = shockwave_ring([50.0, 50.0], 10.0, 100.0, [1.0, 0.4, 0.9], 0.1);
        let late = shockwave_ring([50.0, 50.0], 10.0, 100.0, [1.0, 0.4, 0.9], 0.9);
        assert!(late.radius > early.radius, "the ring expands with t");
        assert!(late.color[3] < early.color[3], "and fades out");
        assert!(early.inner < early.radius, "valid annulus");
    }

    /// INJECT-ASSERT: node_decor emits a shadow + a glow under each node marker
    /// (one of each per drawable quad), AND the glow carries the node's colour.
    #[test]
    fn node_decor_emits_shadow_and_glow_per_node() {
        let q = QuadInstance {
            center: [40.0, 40.0],
            radius: 8.0,
            inner: 2.0,
            color: [0.2, 0.8, 1.0, 1.0],
            aa: 1.0,
            shape: shape::SQUARE,
            _pad: [0.0, 0.0],
        };
        let (shadows, glows) = node_decor(&[q], 0.5, 0.4);
        assert_eq!(shadows.len(), 1, "one AO shadow under the node");
        assert_eq!(glows.len(), 1, "one glow halo behind the node");
        // The glow is the node's hue at the requested intensity; the shadow is dark.
        assert!((glows[0].color[2] - 1.0).abs() < 1e-5, "glow carries the node blue");
        assert!((glows[0].color[3] - 0.5).abs() < 1e-5, "glow at the requested intensity");
        assert!(shadows[0].color[0] < 0.05, "shadow is black");
        assert!(glows[0].radius > q.radius, "glow is larger than the marker (a bleed)");
    }

    /// INJECT-ASSERT: bloom actually spreads light — a single bright pixel on a
    /// dark frame lights up its neighbours after the pass (and the source stays
    /// lit), proving the box-blur bleed ran (not "didn't panic").
    #[test]
    fn bloom_spreads_light_to_neighbours() {
        let (w, h) = (9u32, 9u32);
        let mut rgba = vec![0u8; (w * h * 4) as usize];
        // One opaque white pixel dead centre on a transparent black ground.
        let c = ((4 * w + 4) * 4) as usize;
        rgba[c..c + 4].copy_from_slice(&[255, 255, 255, 255]);
        let mut frame = Frame { width: w, height: h, rgba };

        let neighbour = ((4 * w + 5) * 4) as usize; // one px to the right
        assert_eq!(frame.rgba[neighbour], 0, "neighbour starts dark");
        bloom_composite(&mut frame, 0.5, 2, 1.0);
        assert!(frame.rgba[neighbour] > 0, "bloom bled light onto the neighbour");
        assert!(frame.rgba[neighbour + 3] > 0, "and lifted its alpha so the glow shows");
        // The original bright pixel stays lit.
        assert!(frame.rgba[c] > 200, "the source pixel stays bright");
    }

    /// INJECT-ASSERT: bloom is a no-op when disabled (intensity 0 / radius 0) — the
    /// frame is byte-identical, so toggling bloom off truly removes it.
    #[test]
    fn bloom_disabled_is_a_noop() {
        let (w, h) = (5u32, 5u32);
        let mut rgba = vec![0u8; (w * h * 4) as usize];
        rgba[((2 * w + 2) * 4) as usize..((2 * w + 2) * 4) as usize + 4]
            .copy_from_slice(&[255, 255, 255, 255]);
        let before = rgba.clone();
        let mut frame = Frame { width: w, height: h, rgba };
        bloom_composite(&mut frame, 0.5, 0, 1.0);
        assert_eq!(frame.rgba, before, "radius 0 → no-op");
        bloom_composite(&mut frame, 0.5, 2, 0.0);
        assert_eq!(frame.rgba, before, "intensity 0 → no-op");
    }

    // ── the GLOW STROKE ladder ────────────────────────────────────────────

    /// The ladder that shipped in korp before this lane — the one whose pixels
    /// The owner said were "not glowing souline like in everquest-II yet". Widths in
    /// px over a 1.6 px core, alphas 0..1. Used ONLY as this module's red control.
    fn the_old_three_pass_ladder() -> Vec<(f32, [f32; 4])> {
        let ink = [0.0, 0.95, 1.0];
        [(9.0f32, 46.0f32), (4.5, 120.0), (1.6, 255.0)]
            .iter()
            .map(|&(w, a)| (w, [ink[0], ink[1], ink[2], a / 255.0]))
            .collect()
    }

    /// **RED FIRST — the old ladder fails every gate the new one passes.** Reach,
    /// hot core and coverage are each measured on the LOWERED strokes, so a ladder
    /// edit that quietly dims the line fails here rather than in a screenshot.
    #[test]
    fn the_old_three_pass_ladder_is_not_a_glow() {
        let old = the_old_three_pass_ladder();
        let core_w = 1.6f32;
        let reach = old.iter().fold(0.0f32, |m, &(w, _)| m.max(w)) / core_w;
        assert!(reach < 6.0, "the old halo reached only {reach:.1}x the core — a thick line");
        // Its core keeps the hue: the red channel never lifts off the ink's 0.0.
        let core = old.last().expect("three passes").1;
        assert!(core[0] < 0.2, "the old core is saturated cyan (r={}), not hot white", core[0]);
        // And its coverage is dominated by one opaque pass rather than a ramp.
        let alphas: Vec<f32> = old.iter().map(|p| p.1[3]).collect();
        assert!(alphas[0] > 0.15, "the old outer pass was too opaque to read as bleed: {}", alphas[0]);
    }

    /// **The new ladder IS a glow** — wide bleed, geometric alpha ramp, white-hot core.
    #[test]
    fn the_glow_ladder_reaches_wide_and_burns_white_at_the_core() {
        let ink = [0.0, 0.95, 1.0];
        let passes = glow_stroke_passes(1.6, ink);
        assert_eq!(passes.len(), GLOW_LADDER.len());

        // 1. WIDTH: widest first, and the halo reaches far past the core.
        let widths: Vec<f32> = passes.iter().map(|p| p.0).collect();
        for w in widths.windows(2) {
            assert!(w[0] > w[1], "the ladder is drawn widest-first: {widths:?}");
        }
        assert!((widths[widths.len() - 1] - 1.6).abs() < 1e-4, "the last pass IS the core width");
        assert!(glow_reach_mul() >= 10.0, "the halo reaches {}x the core", glow_reach_mul());

        // 2. ALPHA: a monotone geometric ramp, faint outside, opaque at the core.
        let alphas: Vec<f32> = passes.iter().map(|p| p.1[3]).collect();
        for a in alphas.windows(2) {
            assert!(a[1] > a[0], "alpha rises inward: {alphas:?}");
        }
        assert!(alphas[0] <= 0.10, "the outer bleed is nearly transparent: {}", alphas[0]);
        assert!(alphas[alphas.len() - 1] >= 0.99, "the core is opaque: {:?}", alphas.last());
        // geometric, not linear: each step is a bigger jump than the last.
        let steps: Vec<f32> = alphas.windows(2).map(|w| w[1] - w[0]).collect();
        for s in steps.windows(2) {
            assert!(s[1] > s[0], "the alpha ramp accelerates inward: {steps:?}");
        }

        // 3. COLOUR: the outer passes keep the hue, the core burns toward white.
        let outer = passes[0].1;
        assert!((outer[0] - ink[0]).abs() < 1e-6, "the bleed is the pure hue");
        let core = passes[passes.len() - 1].1;
        assert!(core[0] > 0.85, "the core is white-hot: r={}", core[0]);
        assert!(core[2] > 0.95, "…and still carries the hue's blue: b={}", core[2]);

        // 4. COVERAGE: the stacked passes actually reach opacity at the centre.
        let cov = glow_core_coverage();
        assert!(cov > 0.99, "the ladder covers {cov} at the centre — a solid core");
    }

    /// The flow phase is pure in the clock and wraps — a glow that MOVES without a
    /// hidden `Instant::now()`.
    #[test]
    fn the_flow_phase_is_pure_and_wraps() {
        assert!((glow_flow_phase(0.0, 2.0) - 0.0).abs() < 1e-6);
        assert!((glow_flow_phase(1.0, 2.0) - 0.5).abs() < 1e-6);
        assert!((glow_flow_phase(2.0, 2.0) - 0.0).abs() < 1e-6, "one period wraps to 0");
        assert!(
            (glow_flow_phase(3.7, 2.0) - glow_flow_phase(5.7, 2.0)).abs() < 1e-5,
            "pure and periodic",
        );
        // A zero period cannot panic or divide by zero.
        assert!(glow_flow_phase(1.0, 0.0).is_finite());
    }
}

#[cfg(test)]
mod glow_cross_section_tests {
    use super::*;

    /// **RED-WHEN-BROKEN: the cross-section is a PEAK that fades to nothing, and it is
    /// the same ladder the stroke stack reads** (owner, 2026-09-03).
    ///
    /// Three properties, and each one is a thing the stroke stack got wrong:
    ///
    /// 1. **The edge is transparent.** A stack ends on an antialiased boundary; a glow
    ///    ends on nothing. `alpha == 0` at the widest offset.
    /// 2. **Alpha rises monotonically toward the centreline.** No plateau, anywhere —
    ///    the plateau IS the flat band.
    /// 3. **It is the SAME ladder.** The ring alphas are `GLOW_LADDER`'s, in order, so
    ///    this cannot drift into a second opinion about what a glow looks like (LAW 5).
    #[test]
    fn the_cross_section_peaks_at_the_centre_and_fades_to_nothing() {
        for ground in [GlowGround::Dark, GlowGround::Light] {
            let xs = glow_cross_section(1.6, [0.2, 0.8, 1.0], ground);
            assert!(xs.len() >= 3, "a profile needs an edge, some rings and a centre");

            let (edge_off, edge_c) = xs[0];
            assert_eq!(edge_c[3], 0.0, "{ground:?}: the ribbon fades OUT, it does not end on an edge");
            assert!(edge_off > 0.0, "{ground:?}: the edge is off the centreline");

            let (centre_off, centre_c) = *xs.last().expect("a centreline");
            assert_eq!(centre_off, 0.0, "{ground:?}: the profile ends ON the centreline");

            // Offsets march inward; alpha never falls on the way in.
            let mut prev_off = f32::INFINITY;
            let mut prev_a = -1.0f32;
            for (off, c) in &xs {
                assert!(*off <= prev_off, "{ground:?}: offsets go outer → inner, got {off} after {prev_off}");
                assert!(
                    c[3] >= prev_a - 1e-6,
                    "{ground:?}: alpha DIPS on the way in ({} after {prev_a}) — a dip is a ring, \
                     and rings are what made it read as a band",
                    c[3],
                );
                prev_off = *off;
                prev_a = c[3];
            }
            assert!(
                centre_c[3] > edge_c[3],
                "{ground:?}: the centreline must be brighter than the edge",
            );

            // NON-VACUITY: a profile of one flat colour would satisfy "never falls".
            let alphas: Vec<f32> = xs.iter().map(|(_, c)| c[3]).collect();
            let distinct = {
                let mut v = alphas.clone();
                v.sort_by(f32::total_cmp);
                v.dedup_by(|a, b| (*a - *b).abs() < 1e-6);
                v.len()
            };
            assert!(distinct >= 4, "{ground:?}: a real falloff has steps, got {alphas:?}");
        }
    }

    /// **RED-WHEN-BROKEN: it is a PROJECTION of the stroke ladder, not a second one.**
    ///
    /// The graph tracers read `glow_stroke_passes_on` and the map trail reads this. If
    /// the two ever disagreed about the ladder, one surface would drift from the other
    /// and nobody would notice until a screenshot.
    ///
    /// The colours are NOT the passes' own any more, and that is the point: five
    /// stacked translucent strokes composite, so a single ribbon has to carry the
    /// accumulation or it comes out far fainter than the stack it replaces (measured —
    /// core departure 56 against the stack's 204, which is a trail you cannot see).
    /// The accumulation is recomputed HERE, independently, so a cross-section that
    /// invented its own ladder would still be caught.
    #[test]
    fn the_cross_section_carries_the_stroke_ladders_own_rings() {
        let ink = [0.2, 0.8, 1.0];
        let passes = glow_stroke_passes_on(1.6, ink, GlowGround::Dark);
        let xs = glow_cross_section(1.6, ink, GlowGround::Dark);

        let mut want: [f32; 4] = [0.0, 0.0, 0.0, 0.0];
        for (i, (w, c)) in passes.iter().enumerate() {
            // The independent over-composite, written out rather than called, so this
            // test does not simply re-run the code it is checking.
            let (sa, da) = (c[3], want[3]);
            let oa = sa + da * (1.0 - sa);
            want = [
                (c[0] * sa + want[0] * da * (1.0 - sa)) / oa,
                (c[1] * sa + want[1] * da * (1.0 - sa)) / oa,
                (c[2] * sa + want[2] * da * (1.0 - sa)) / oa,
                oa,
            ];

            let (off, xc) = xs[i + 1];
            assert!(
                (off - w * 0.5).abs() < 1e-6,
                "ring {i}: the offset is the PASS's half-width — the widths come from \
                 the ladder even though the colours accumulate",
            );
            for k in 0..4 {
                assert!(
                    (xc[k] - want[k]).abs() < 1e-5,
                    "ring {i} channel {k}: {} vs the composite {}",
                    xc[k],
                    want[k],
                );
            }
            assert!(
                xc[3] >= c[3] - 1e-6,
                "ring {i}: accumulating can only make the ribbon MORE opaque, not less",
            );
        }

        // And the centreline is the whole ladder — the same number `glow_core_coverage`
        // publishes, which is what makes that function and this one one statement.
        let core = xs.last().expect("a centreline");
        assert!((core.0 - 0.0).abs() < 1e-6, "the centreline sits at offset 0");
        assert!(
            (core.1[3] - glow_core_coverage()).abs() < 1e-5,
            "the centre's alpha is the ladder's own coverage: {} vs {}",
            core.1[3],
            glow_core_coverage(),
        );
    }
}