// Flow family: streaks advected along a curl-noise field.
//
// Uses the compositor's feedback texture rather than integrating positions, so
// the trails cost nothing in state and a seek still lands correctly once the
// feedback buffer has converged.

export const curlFlow = {
    name: 'Curl Flow',
    family: 'flow',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    traits: ['camera', 'space', 'style'],

    params: {
        scale:    { type: 'float', range: [0.5, 6],   default: 2.0, uniform: 'u_scale', bias: 'density' },
        speed:    { type: 'float', range: [0.02, 0.5], default: 0.12, uniform: 'u_speed', bias: 'motion', rate: true },
        streak:   { type: 'float', range: [0, 1],     default: 0.55, uniform: 'u_streak' },
        contrast: { type: 'float', range: [0.5, 4],   default: 1.6, uniform: 'u_contrast' },
        veins:    { type: 'float', range: [1, 12],    default: 5.0, uniform: 'u_veins', bias: 'density'  },
        // The large-scale structure — see the shader. `channel` is the slow
        // axis rather than `channelAt`, and the difference was measured: MOVING
        // the band preserves the frame's total energy, so a ten-second average
        // of it is nearly the same image wherever it sits. Opening and closing
        // the band changes how much of the frame is lit at all, which is what
        // the eye actually reads as the image changing.
        channel:   { type: 'float', range: [0, 1],      default: 0.6, uniform: 'u_channel', bias: 'density', slowAxis: true },
        channelAt: { type: 'float', range: [-1.1, 1.1], default: 0.0, uniform: 'u_channelAt' },
        channelAngle: { type: 'float', range: [0, 6.283], default: 1.2, uniform: 'u_channelAngle' },
        glow:     { type: 'float', range: [0, 1.2],   default: 0.35, uniform: 'u_glow', bias: 'energy' },
        palette:  { type: 'palette', count: 5 },
    },

    reactive: {
        glow:   { feature: 'beat',    amount: 0.3, response: 'spike' },
        veins:  { feature: 'bandMid', amount: 0.25 },
        streak: { feature: 'flux',    amount: 0.2, response: 'smooth' },
    },

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

    vec2 flow = curl(p * u_scale + vec2(t, -t * 0.7), t * 0.5);
    vec2 q = p + flow * 0.35;

    // Ridged noise gives filament-like veins rather than soft cloud.
    float n = fbm(q * u_scale * 1.4 + t * 0.6, 5);
    float veins = 1.0 - abs(sin(n * u_veins + t * 2.0));
    veins = pow(sat(veins), u_contrast);

    // A CURRENT: one broad band of the frame carries the filaments and the rest
    // runs comparatively bare.
    //
    // Without this the scene is one noise field at one scale, which means it is
    // statistically identical everywhere and at every moment. Measured, that is
    // exactly what made it read as static however fast it moved: the eye finds
    // the statistics in about two seconds and then there is nothing left. Every
    // pixel was moving and the IMAGE never changed.
    //
    // The band never closes fully — 0.22 at its darkest — because an empty half
    // frame is a different failure from a busy one.
    float along = dot(p, vec2(cos(u_channelAngle), sin(u_channelAngle)));
    float band = (along - u_channelAt) * (0.9 + u_channel * 2.0);
    float current = mix(1.0, 0.10 + 0.90 * exp(-band * band), u_channel);
    veins *= current;

    vec3 col = mix(pal(0) * 0.12, pal(1), veins);
    col += pal(2) * pow(veins, 3.0) * u_glow * current;
    col = mix(col, pal(3), sat(length(flow) * 0.4) * 0.35);

    // Feedback trails: the previous frame, pulled slightly along the flow.
    vec3 trail = prev(uv - flow * 0.004);
    col = max(col, trail * u_streak);

    // The current gates the FINAL image rather than only the vein term. The
    // feedback trail and the flow tint both fill the frame on their own, so
    // structuring the veins alone left the composite as uniform as it was.
    col *= mix(1.0, 0.16 + 0.84 * current, u_channel);

    col *= 0.6 + 0.4 * exp(-dot(p, p) * 0.35);
    col = sigAir(col, p, smoothstep(0.0, 1.7, length(p)));
    col += sigGrain(uv);
    return vec4(col, 1.0);
}
`,
};

export default curlFlow;
