// Glitch family: vertical transposition. The frame is cut into vertical bands,
// each of which shifts up or down by a small amount. That alone is a tear; what
// makes it churn is that the offset is re-rolled on the quantised grid and the
// previous frame's transposed image is smeared underneath — so slices translate
// to a new fixed pose each step instead of crawling, and the pitch reads as an
// edit rather than a broken renderer.

export const pitchShatter = {
    name: 'Pitch Shatter',
    family: 'glitch',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    traits: ['camera', 'style'],

    params: {
        slices:   { type: 'float', range: [4, 40],    default: 18,  uniform: 'u_slices', bias: 'density' },
        amp:      { type: 'float', range: [0, 0.35],  default: 0.1, uniform: 'u_amp', bias: 'energy' },
        quantize: { type: 'float', range: [1, 8],     default: 4,   uniform: 'u_quantize' },
        bleed:    { type: 'float', range: [0, 0.9],   default: 0.4, uniform: 'u_bleed' },
        glow:     { type: 'float', range: [0, 1],     default: 0.3, uniform: 'u_glow', bias: 'energy' },
        speed:    { type: 'float', range: [0.05, 0.8], default: 0.25, uniform: 'u_speed', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        amp:   { feature: 'beat',    amount: 0.4, response: 'spike' },
        bleed: { feature: 'flux',    amount: 0.25, response: 'smooth' },
        glow:  { feature: 'bandHigh', amount: 0.2 },
    },

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

    // One shared grid for every discontinuity below: slices re-roll on the
    // quantised step rather than moving continuously frame-to-frame.
    float q = max(u_quantize, 1.0);
    float step_ = floor(u_barPhase * q) + floor(t * 6.0) * q;

    // A vertical slice is a column of pixels; each gets one fixed offset per
    // step, decided by a hash of (slice, step) so it jumps cleanly on the grid.
    float slice = floor(uv.x * u_slices);
    float sliceRand = hash12(vec2(slice, step_ * 31.0));
    float pitch = (sliceRand - 0.5) * 2.0 * u_amp;

    // The base image is always sampled from the un-pitched field so the scene
    // has real content behind the displacement, even on its first frame.
    // Slices are screen space; the field they transpose is filmed at the
    // shot's framing. See framedUv.
    vec2 fuv = framedUv(p);
    vec2 baseUv = vec2(fuv.x, fract(fuv.y - pitch));
    float field = fbm(vec2(baseUv.x * 2.5, baseUv.y * 4.0) + t * 0.4, 4);
    float ramp = fract(field * 2.0 + baseUv.y * 2.0 - t * 0.4);
    vec3 col = palRamp(ramp * 0.7 + slice * 0.02);
    col *= 0.4 + 0.6 * smoothstep(0.1, 0.9, field);

    // The smear: pull the previous frame across the pitch so slices leave a
    // transient ghost as they land, and the corruption feels sludgy.
    vec3 ghost = prev(vec2(uv.x, fract(uv.y + pitch * 0.55)));
    col = mix(col, ghost, u_bleed * (0.4 + sliceRand));

    // A thin scan beam rooting the hard edge of each step, kept local and beat
    // -pinned so the frame never swings in whole-frame luminance.
    float beamY = fract(u_beatPhase) * 2.0 - 1.0;
    float beam = exp(-abs(p.y - beamY * 0.6) * 6.0);
    col += pal(3) * beam * u_slices * 0.001 * (0.5 + u_beat);

    col += pal(int(mod(slice, 5.0))) * (1.0 - sliceRand) * u_glow * 0.3 * sliceRand;
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};