// Structural family: a field of standing pylons receding to the track's
// horizon. The columns converge on a vanishing point, giving real perspective
// depth, and each column carries a crown in the signature form so a hexagonal
// track grows hexagonal pylons. The beat walks a pulse of light down the rows
// rather than flashing the whole frame.

export const pylonGrid = {
    name: 'Pylon Grid',
    family: 'structural',
    kind: 'fragment',
    // Personality: see look/Personality.js.
    // Takes the track's surface grain, but lightly — this is drawn, not filmed.
    texture: 0.4,
    consumes: ['cast', 'ink'],
    traits: ['shape', 'space', 'style'],

    params: {
        columns:  { type: 'float', range: [3, 16],  default: 8,   uniform: 'u_columns', bias: 'density' },
        rows:     { type: 'float', range: [2, 10],  default: 6,   uniform: 'u_rows', bias: 'density' },
        spread:   { type: 'float', range: [0.6, 1.6], default: 1.1, uniform: 'u_spread' },
        height:   { type: 'float', range: [0.3, 1.4], default: 0.85, uniform: 'u_height', bias: 'energy' },
        pulse:    { type: 'float', range: [0, 1.2], default: 0.5, uniform: 'u_pulse', bias: 'energy' },
        speed:    { type: 'float', range: [0.05, 0.8], default: 0.3, uniform: 'u_speed', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 4 },
    },

    reactive: {
        pulse: { feature: 'beat',    amount: 0.4, response: 'smooth' },
        rows:  { feature: 'bandLow', amount: 0.25 },
    },

    shader: `
// Distance to a thin vertical strut from (cx, y0) up to (cx, y1).
float strut(vec2 p, float cx, float y0, float y1, float w) {
    float xd = abs(p.x - cx);
    float yd = max(max(y0 - p.y, p.y - y1), 0.0);
    float d = sqrt(xd * xd + yd * yd);
    return smoothstep(w + 0.004, w - 0.004, d);
}

vec4 scene(vec2 uv, vec2 p) {
    float t = u_time * u_speed + u_seed;
    float hy = sigHorizonY();
    vec3 col = pal(0) * 0.05;

    for (int r = 0; r < 10; r++) {
        if (float(r) >= u_rows) break;
        float fr = float(r);

        // Perspective: the last row sits on the horizon, the first at the bottom
        // of the frame, everything between scaling by its depth fraction.
        float f = fr / max(u_rows - 1.0, 1.0);       // 0 near .. 1 far
        float y0 = mix(-1.08, hy + 0.06, f);          // row baseline
        float scale = mix(1.0, 0.12, f);              // perspective shrink
        float depthShade = mix(1.0, 0.35, f);         // far rows dim

        for (int c = 0; c < 16; c++) {
            if (float(c) >= u_columns) break;
            float fc = float(c);
            float cx = (fc / max(u_columns - 1.0, 1.0) - 0.5) * 2.0 * u_spread * scale;

            float h = u_height * scale;
            float yTop = y0 + h;

            // The beat pulse walks a glow down the depth axis, row by row, so the
            // light travels rather than strobing the whole frame.
            float walk = sat(1.0 - abs(fr - mod(u_beatPhase * 6.0 + t * 0.4, u_rows)));
            float inten = (0.35 + 0.65 * walk) * depthShade * (0.7 + 0.3 * u_beat);

            float w = 0.02 * scale + 0.006;
            col += pal(int(mod(fc, 4.0))) * strut(p, cx, y0, yTop, w) * inten;

            // Crown stamped in the track's signature form.
            vec2 crown = vec2(cx, yTop);
            float size = (0.06 + 0.2 * scale) * (1.0 + u_beat * 0.4);
            col += pal(3) * castForm(p, crown, size) * inten * (0.6 + u_pulse);
            col += pal(int(mod(fr, 4.0))) * exp(-dot(p - crown, p - crown) * 60.0) * u_pulse * depthShade;
        }
    }

    // Ground haze and the shared air between here and the horizon.
    col = sigAir(col, p, smoothstep(0.0, 1.6, abs(p.y - hy)));
    return vec4(inkValue(col), 1.0);
}
`,
};