// Glitch family: branching discharge across the sky above the track's horizon.
//
// The bolt is a recursive-looking zigzag built from stacked hashed segments,
// re-struck on the quantised grid rather than continuously, so strikes land on
// the music. Between strikes the afterglow decays through feedback, which is
// what makes the dark frames read as "after a flash" rather than as empty.
//
// The flash itself is deliberately LOCAL — the bolt and a halo around it, not
// the frame. A full-frame white flash on every kick is exactly the WCAG 2.3.1
// failure this library is checked against.

export const stormRift = {
    name: 'Storm Rift',
    family: 'glitch',
    kind: 'fragment',
    traits: ['camera', 'space', 'style'],

    params: {
        bolts:    { type: 'int',   range: [1, 5],       default: 2,    uniform: 'u_bolts', bias: 'density' },
        jag:      { type: 'float', range: [0.05, 0.6],  default: 0.25, uniform: 'u_jag' },
        segments: { type: 'float', range: [4, 20],      default: 10,   uniform: 'u_segments', bias: 'density' },
        branch:   { type: 'float', range: [0, 1],       default: 0.45, uniform: 'u_branch' },
        afterglow:{ type: 'float', range: [0, 0.9],     default: 0.55, uniform: 'u_afterglow' },
        cloud:    { type: 'float', range: [0, 1],       default: 0.5,  uniform: 'u_cloud' },
        rate:     { type: 'float', range: [0.2, 3.0],   default: 1.0,  uniform: 'u_rate', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        branch: { feature: 'bandHigh', amount: 0.3, response: 'smooth' },
        cloud:  { feature: 'bandLow',  amount: 0.25, response: 'smooth' },
    },

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

    float horizon = sigHorizonY() * 0.7 - 0.5;

    // Cloud deck above, lit from within.
    float deck = fbm(vec2(p.x * 1.4 + t * 0.15, p.y * 2.2 - t * 0.05), 5);
    vec3 col = mix(pal(0) * 0.06, pal(1) * 0.2, deck * u_cloud * smoothstep(horizon, horizon + 1.2, p.y));

    // Strikes step on the eighth-bar grid. Each era re-rolls every bolt.
    float era = floor(u_barPhase * 8.0) + floor(t) * 8.0;

    for (int b = 0; b < 5; b++) {
        if (b >= u_bolts) break;
        float fb = float(b);
        float boltSeed = hash12(vec2(era, fb * 7.3 + u_seed));

        // Not every bolt fires every era; misfires are what make the ones that
        // land feel like events.
        float fires = step(0.35, boltSeed);
        float age = fract(t * 2.0 + fb * 0.31);
        float intensity = fires * exp(-age * 6.0);
        if (intensity < 0.004) continue;

        // The channel: a piecewise-linear zigzag from the cloud deck down to the
        // horizon, each segment hashed off (era, bolt, segment).
        float x0 = (hash11(boltSeed * 31.0) - 0.5) * 1.8;
        float best = 1e3;

        for (int s = 0; s < 20; s++) {
            if (float(s) >= u_segments) break;
            float f0 = float(s) / u_segments;
            float f1 = float(s + 1) / u_segments;

            float yA = mix(1.1, horizon, f0);
            float yB = mix(1.1, horizon, f1);
            float xA = x0 + (hash11(boltSeed * 17.0 + float(s) * 3.7) - 0.5) * u_jag * (0.3 + f0);
            float xB = x0 + (hash11(boltSeed * 17.0 + float(s + 1) * 3.7) - 0.5) * u_jag * (0.3 + f1);

            // Distance to this segment.
            vec2 a = vec2(xA, yA), bb = vec2(xB, yB);
            vec2 pa = p - a, ba = bb - a;
            float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
            best = min(best, length(pa - ba * h));

            // Branches: a short spur off some joints, in the same hand.
            if (hash11(boltSeed * 53.0 + float(s)) < u_branch * 0.4) {
                vec2 tip = a + vec2((hash11(boltSeed + float(s) * 5.1) - 0.5) * 0.5, -0.12);
                vec2 pb = p - a, bc = tip - a;
                float h2 = clamp(dot(pb, bc) / max(dot(bc, bc), 1e-6), 0.0, 1.0);
                best = min(best, length(pb - bc * h2) + 0.008);
            }
        }

        float core = smoothstep(0.012 + u_sigLine * 0.01, 0.0, best);
        float halo = exp(-best * 12.0);
        col += pal(4) * core * intensity;
        col += pal(3) * halo * intensity * 0.5;
    }

    // Ground catches the light; below the horizon is otherwise near black.
    if (p.y < horizon) {
        col *= 0.25;
        col += pal(2) * exp((p.y - horizon) * 5.0) * 0.15;
    }

    col = max(col, prev(uv) * u_afterglow);
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default stormRift;
