// Glitch family: horizontal bands, each holding the image at a different age.
//
// Distinct from every other glitch scene in the library by what it corrupts.
// Scan Tear displaces rows sideways, Pitch Shatter shifts bands vertically,
// Block Mosh drags blocks along a stroke, Signal Decay attacks the signal
// itself. Here NOTHING is displaced: every band shows the same image, just at a
// different moment. The frame is spatially intact and temporally shredded,
// which is a slit-scan — and it is built from the one feedback buffer by giving
// each band its own persistence rather than by keeping a history.
//
// Scaffolded by tools/new-scene.js. See HOWTO-visualizers.md.

export const timeSmear = {
    name: 'Time Smear',
    family: 'glitch',
    kind: 'fragment',
    consumes: ['ink'],
    traits: ['camera', 'style'],

    params: {
        bands:    { type: 'int',   range: [3, 22],     default: 9,    uniform: 'u_bands', bias: 'density' },
        lag:      { type: 'float', range: [0.3, 0.97], default: 0.82, uniform: 'u_lag' },
        stagger:  { type: 'float', range: [0, 1],      default: 0.7,  uniform: 'u_stagger' },
        scale:    { type: 'float', range: [1, 12],     default: 4.2,  uniform: 'u_scale', bias: 'density' },
        speed:    { type: 'float', range: [0.05, 1.2], default: 0.4,  uniform: 'u_speed', bias: 'motion', rate: true },
        reshuffle:{ type: 'float', range: [0, 1],      default: 0.45, uniform: 'u_reshuffle' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        scale: { feature: 'bandHigh', amount: 0.2,  response: 'smooth' },
    },

    shader: `
vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_speed + u_seed;
    vec2 cp = sigCamera(p);

    // The subject. It must stand alone: a scene that only reads prev() is black
    // until feedback converges and is not the same after a seek as after
    // playback. This is a moving figure with enough structure that lagging it
    // is legible — a flat field would smear into nothing.
    float swirl = fbm(cp * u_scale * 0.4 + vec2(sin(t * 0.7), cos(t * 0.5)), 4);
    float arm = sin(atan(cp.y, cp.x) * 3.0 + length(cp) * u_scale - t * 2.5 + swirl * 3.0);
    float figure = sat(0.5 + arm * 0.5) * exp(-dot(cp, cp) * 0.55);

    vec3 fresh = palRamp(0.1 + swirl * 0.4 + figure * 0.3) * (0.25 + figure * 1.1);
    fresh += pal(4) * pow(figure, 5.0) * 0.5;

    // Which band this pixel is in, and how old that band's image is. The
    // assignment re-rolls on the eighth-bar grid, so the shredding steps with
    // the music rather than crawling.
    float era = floor(u_barPhase * 8.0) + floor(t * 2.0) * 8.0;
    float band = floor(uv.y * float(u_bands));
    float rnd = hash12(vec2(band, era * u_reshuffle));

    // Age: 0 means live, 1 means as stale as this scene allows. Staggering by
    // the hash is what makes neighbouring bands disagree about the present.
    float age = mix(band / float(u_bands), rnd, u_stagger);
    float persistence = u_lag * (0.35 + age * 0.65);

    // The band's own history, held by weighting the previous frame against the
    // fresh one. A band at high persistence updates so slowly that it is
    // effectively showing several frames ago.
    vec3 held = prev(uv);
    vec3 col = mix(fresh, held, persistence);

    // Seam between bands, drawn in the track's line weight so the shredding
    // reads as deliberate rather than as a broken renderer.
    float seam = abs(fract(uv.y * float(u_bands)) - 0.5) * 2.0;
    col += pal(3) * smoothstep(0.88, 1.0, seam) * (0.05 + u_sigLine * 0.12);

    // Stale bands lose colour, the way a held frame loses its highlights.
    col = mix(col, vec3(dot(col, vec3(0.299, 0.587, 0.114))), age * 0.35);

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

export default timeSmear;
