// Minimal family: a radial bar sculpture driven by the band split.
//
// This is the one scene that shows the spectrum more or less literally. It reads
// as a music visualiser rather than as an abstraction, which is why it is
// deliberately restrained — thin bars, lots of black — and why it lives in
// `minimal` rather than `geometric`.

export const spectrumSculpture = {
    name: 'Spectrum Sculpture',
    family: 'minimal',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    // Takes the track's surface grain, but lightly — this is drawn, not filmed.
    texture: 0.4,
    consumes: ['ink'],
    traits: ['shape', 'camera', 'style'],

    params: {
        bars:      { type: 'float', range: [8, 96],   default: 40,  uniform: 'u_bars', bias: 'density' },
        // Floor raised from 0.15, where the sculpture was too small to register
        // as anything and the range sweep called the frame dead. A range the
        // generator may sample must not contain a value that renders nothing.
        radius:    { type: 'float', range: [0.28, 0.7], default: 0.35, uniform: 'u_radius' },
        length:    { type: 'float', range: [0.05, 0.6], default: 0.25, uniform: 'u_length', bias: 'energy' },
        thickness: { type: 'float', range: [0.1, 0.9], default: 0.45, uniform: 'u_thickness' },
        // Bar segments per second, NOT turns per second. Rotating by a full turn
        // meant the bar-crossing frequency was bars x rate — at 82 bars that put a
        // slow-looking 0.12 turns/s at 10 Hz of luminance flicker. In segment units
        // the crossing frequency IS the rate, so it stays under the flash ceiling
        // whatever the bar count.
        // Capped at 0.4 by measurement, not by taste: at 0.83 the mirror fold puts
        // this at 4 flashes/s, and at 0.4 it measures 0. Bar count no longer affects
        // it now that rotation is in segment units.
        rotate:    { type: 'float', range: [0, 0.4],  default: 0.2, uniform: 'u_rotate', bias: 'motion', rate: true },
        mirror:    { type: 'bool',                     default: true, uniform: 'u_mirror' },
        palette:   { type: 'palette', count: 5 },
    },

    reactive: {
        // Kept low deliberately: bar length scales the whole ring at once, so a
        // large amount pumps global luminance and measured 4 flashes/s against a
        // ceiling of 3. See engine/flash.js.
        length:    { feature: 'loudness', amount: 0.15, response: 'smooth' },
        thickness: { feature: 'beat',     amount: 0.12, response: 'spike' },
    },

    shader: `
float bandByIndex(float i) {
    if (i < 0.5) return u_bandSub;
    if (i < 1.5) return u_bandLow;
    if (i < 2.5) return u_bandMid;
    if (i < 3.5) return u_bandHigh;
    return u_bandAir;
}

float bandAt(float x) {
    float s = clamp(x, 0.0, 1.0) * 4.0;
    float i = floor(s);
    float f = fract(s);
    f = f * f * (3.0 - 2.0 * f);
    return mix(bandByIndex(i), bandByIndex(i + 1.0), f);
}

vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_rotate + u_seed;
    p = sigCamera(p);

    float bars = u_bars;
    float seg = 6.28318530718 / bars;

    float angle = atan(p.y, p.x) + t * seg;
    // Radial extent measured in the track's SIGNATURE form, not in the cast.
    // This is a metric, not a subject: the cast carries notches and a hollow,
    // and an annulus used as a radius turns the sculpture inside out — it
    // rendered pure black. A scene that reads a form as geometry rather than
    // drawing it wants sigShape, and the migration should leave it alone.
    float radius = sigShape(p) + 1.0;
    float index = floor((angle + 3.14159265) / seg);
    float cellAngle = mod(angle + 3.14159265, seg) / seg;

    // Fold the ring so the two halves mirror; reads as a designed object rather
    // than a spinning readout.
    float slot = u_mirror > 0.5 ? abs(index / bars - 0.5) * 2.0 : index / bars;

    // Band split across the ring, sub at one end and air at the other, INTERPOLATED
    // rather than switched. Hard tier boundaries made every bar jump between bands
    // at the same moment as the ring rotated, which stepped whole-frame luminance
    // and measured 4 flashes/s against a ceiling of 3. Blending removes the step
    // and looks better besides.
    float band = bandAt(slot);

    float height = u_radius + u_length * (0.25 + band);
    float inBar = step(u_radius, radius) * step(radius, height);
    float shape = smoothstep(0.5 - u_thickness * 0.5, 0.5, cellAngle)
                * smoothstep(0.5 + u_thickness * 0.5, 0.5, cellAngle);

    vec3 col = pal(0) * 0.05;
    col += palRamp(slot * 0.7 + 0.15) * inBar * shape * (0.6 + band);

    // Inner ring outline holds the composition together.
    col += pal(2) * inkStroke(radius - u_radius) * 0.35;

    return vec4(inkValue(col), 1.0);
}
`,
};

export default spectrumSculpture;
