// Structural family: a flight through mountains. A real perspective camera
// travelling forward over a raymarched heightfield — peaks rise, pass to either
// side and occlude what is behind them, with a snowline near the top and the
// ridge silhouettes drawn in the song's hand.
//
// Ridge Terrain is the neighbour, and it is the flat version of this: parallax
// bands of 1D noise that slide sideways and can never hide each other, because
// there is no depth for them to hide in. Here the image is a 3D field sampled
// along the ray, so the motion is INTO the frame rather than across it, valleys
// open and close as the camera passes through them, and a near peak eats a far
// one. The massifs themselves are the song's protagonist, extruded: a hollow
// cast makes calderas, a notched one makes star-shaped ridges.

export const mountainFlight = {
    name: 'Mountain Flight',
    family: 'structural',
    kind: 'fragment',
    // Personality: see look/Personality.js. Identity: see look/Identity.js.
    consumes: ['cast', 'ink', 'staging'],
    traits: ['camera', 'space', 'style'],

    params: {
        relief:   { type: 'float', range: [0.15, 1.4],  default: 0.55, uniform: 'u_relief', bias: 'energy' },
        rugged:   { type: 'float', range: [0.15, 1.1],  default: 0.45, uniform: 'u_rug', bias: 'density' },
        ridges:   { type: 'int',   range: [2, 5],       default: 4,    uniform: 'u_oct', bias: 'density' },
        massif:   { type: 'float', range: [0.1, 0.9],   default: 0.5,  uniform: 'u_massif' },
        span:     { type: 'float', range: [4, 16],      default: 8.0,  uniform: 'u_span', bias: 'density' },
        altitude: { type: 'float', range: [0.15, 2.2],  default: 0.7,  uniform: 'u_alt', slowAxis: true },
        snowline: { type: 'float', range: [0.15, 1.1],  default: 0.55, uniform: 'u_snow', bias: 'energy' },
        speed:    { type: 'float', range: [0.1, 2.5],   default: 0.8,  uniform: 'u_speed', bias: 'motion', rate: true },
        palette:  { type: 'palette', count: 6 },
    },

    reactive: {
        relief:   { feature: 'bandLow',  amount: 0.22, response: 'smooth' },
        snowline: { feature: 'bandHigh', amount: 0.2,  response: 'smooth' },
        altitude: { feature: 'bandSub',  amount: 0.15, response: 'smooth' },
    },

    shader: `
// The massif standing in the world cell around w: the song's protagonist, used
// as a PLAN rather than a picture — its footprint is extruded into a mountain.
// Kept strictly inside its own cell so one sample of one cell is the whole
// answer, which is what makes it affordable inside the march.
float massifH(vec2 w) {
    vec2 cell = floor(w / u_span);
    vec2 local = w - (cell + 0.5) * u_span;

    // Which node of the song's lattice this cell got, and how big it stands.
    vec3 node = stageNode(floor(hash12(cell) * 7.0), 8.0);
    vec2 at = clamp(node.xy, -1.0, 1.0) * u_span * 0.18;
    float s = u_span * 0.2 * clamp(u_massif * node.z, 0.08, 1.1);

    float d = castMain((local - at) / s) * s;
    // Flanks fall away from the footprint's edge; the summit is its middle.
    float m = sat(-d / max(s * 0.9, 1e-3));
    return pow(m, 0.7) * u_relief * 1.7;
}

float terrainH(vec2 w) {
    // The song's element size is the size of the whole landscape's vocabulary:
    // a few enormous massifs, or a crowd of small ones.
    vec2 q = w * u_rug / max(stageScale(), 0.25);

    float sum = 0.0, norm = 0.0, amp = 1.0;
    for (int i = 0; i < 5; i++) {
        if (i >= u_oct) break;
        // Ridged, not billowy: mountains have crests.
        float n = 1.0 - abs(vnoise(q) * 2.0 - 1.0);
        sum += n * n * amp;
        norm += amp;
        q = rot(0.63) * q * 2.03;
        amp *= 0.45;
    }
    return (sum / max(norm, 1e-3)) * u_relief + massifH(w);
}

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

    // The camera rides a fixed height over whatever it is flying across, so a
    // tall massif lifts the flight path instead of swallowing it.
    vec3 ro = vec3(u_seed * 3.7, 0.0, t * 4.0);
    ro.y = terrainH(ro.xz) + u_alt * (1.0 + stageScale() * 0.3);

    // The track's horizon decides how far down the camera is looking.
    vec3 rd = normalize(vec3(p.x, p.y - sigHorizonY() * 0.5, 1.6));

    float dist = 0.4;
    float hit = 0.0;
    float gap = 0.0;
    float grazed = 1e3;                 // closest the ray came, per unit distance

    // A march ends on its hit or its far plane, not on a param.
    // lint: fixed-cost
    for (int i = 0; i < 72; i++) {
        vec3 at = ro + rd * dist;
        gap = at.y - terrainH(at.xz);
        grazed = min(grazed, gap / max(dist, 1.0));
        if (gap < 0.002 * dist) { hit = 1.0; break; }
        // Cone stepping: safe near the surface, cheap out at the far end.
        dist += max(gap * 0.5, 0.03 + dist * 0.02);
        if (dist > 46.0) break;
    }

    float fog = sat(dist / 46.0);
    vec3 col;

    if (hit > 0.5) {
        vec3 at = ro + rd * dist;
        float e = 0.02 + dist * 0.006;
        vec3 nrm = normalize(vec3(
            terrainH(at.xz - vec2(e, 0.0)) - terrainH(at.xz + vec2(e, 0.0)),
            2.0 * e,
            terrainH(at.xz - vec2(0.0, e)) - terrainH(at.xz + vec2(0.0, e))));

        vec3 sun = normalize(vec3(0.55, 0.42, -0.5));
        float diff = sat(dot(nrm, sun));

        float elev = sat(at.y / max(u_relief * 2.2, 0.2));
        // Snow lies high, and only where the slope will hold it.
        float snow = smoothstep(u_snow, u_snow + 0.16, elev * (0.45 + 0.75 * nrm.y));

        vec3 rock = mix(pal(2), pal(3), sat(elev * 1.3));
        col = mix(rock, pal(5), snow);
        col *= 0.22 + 0.9 * diff;
        col += pal(1) * 0.12 * sat(nrm.y);      // sky bounce into the flats
    } else {
        col = mix(pal(1) * 0.45, pal(0) * 0.22, sat(rd.y * 2.2 + 0.15));
        fog = 0.85;
        // The ridgeline the sky is cut against, drawn at the song's weight.
        col += pal(4) * inkStroke(grazed * 0.6) * 0.5;
    }

    col = sigAir(col, p, fog);
    return vec4(inkValue(col), 1.0);
}
`,
};

export default mountainFlight;
