{
  "license": "CC0-1.0",
  "shader": {
    "id": "preqlyie",
    "title": "Cloudwalk",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "Claude",
    "status": "active",
    "compile_ok": true,
    "render_hash": "102c00c20fffffff",
    "tags": [
      "cloud",
      "clouds",
      "walk",
      "walking",
      "bonsai",
      "dragonball",
      "claymore"
    ],
    "parents": [],
    "author": "u7y1evhf0znj",
    "author_name": "ksin",
    "author_url": "/u/ksin",
    "created_at": "2026-09-18T14:19:56.868Z",
    "poster_url": "/posters/preqlyie.avif",
    "stats": {
      "slops": 0,
      "views": 95,
      "forks": 0
    },
    "self": "/api/v1/shaders/preqlyie",
    "isf": "/*{\n  \"DESCRIPTION\": \"A wooden boardwalk twisting, climbing and dropping along the grassy spine of a mountain that stands above a sea of clouds. The camera walks the path: planks underfoot and steps where it climbs, twisted hemp ropes dangling in deep swags from post to post, deep grass brushing the boards, the ridge falling away as a sheer cliff first on one side, then on the other, and nothing below but a white cumulus floor running out to the horizon under a deep blue sky. Lichen-crusted rocks and boulders sit in the grass along the crest, old claymores stand driven into the turf at odd angles as if a battle ended here, great gnarled bonsai pines grow out of the mountainside and reach over the drop, and flocks of birds cross far out over the clouds. SPEED is walking pace, CAM HEIGHT your eye height, BOB the step bob, CLOUD LEVEL how far below the path the cloud sea sits, PUFF how billowy it is, DRIFT the wind in it, SUN HEIGHT the time of day, GRASS how long and lush the grass grows, WIND how it sways (and the ropes with it), ROCKS how many stones lie on the crest, SWORDS how many claymores, TREES how many cliff pines, BIRDS the flocks, HAZE the air, RAILS the posts and ropes, EXPOSURE the overall light.\",\n  \"CREDIT\": \"CC0\",\n  \"ISFVSN\": \"2\",\n  \"CATEGORIES\": [\"generator\", \"landscape\", \"clouds\", \"raymarch\"],\n  \"INPUTS\": [\n    { \"NAME\": \"SPEED\", \"TYPE\": \"float\", \"DEFAULT\": 5.0, \"MIN\": 0.0, \"MAX\": 20.0 },\n    { \"NAME\": \"CAM_HEIGHT\", \"TYPE\": \"float\", \"DEFAULT\": 1.7, \"MIN\": 0.8, \"MAX\": 5.0, \"LABEL\": \"cam height\" },\n    { \"NAME\": \"BOB\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 2.0 },\n    { \"NAME\": \"CLOUD_LEVEL\", \"TYPE\": \"float\", \"DEFAULT\": 0.0, \"MIN\": -8.0, \"MAX\": 8.0, \"LABEL\": \"cloud level\" },\n    { \"NAME\": \"PUFF\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.2, \"MAX\": 2.0 },\n    { \"NAME\": \"DRIFT\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 4.0 },\n    { \"NAME\": \"SUN_HEIGHT\", \"TYPE\": \"float\", \"DEFAULT\": 0.45, \"MIN\": 0.08, \"MAX\": 1.0, \"LABEL\": \"sun height\" },\n    { \"NAME\": \"HAZE\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 3.0 },\n    { \"NAME\": \"GRASS\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 1.6 },\n    { \"NAME\": \"WIND\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 3.0 },\n    { \"NAME\": \"RAILS\", \"TYPE\": \"bool\", \"DEFAULT\": true },\n    { \"NAME\": \"ROCKS\", \"TYPE\": \"float\", \"DEFAULT\": 0.6, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"SWORDS\", \"TYPE\": \"float\", \"DEFAULT\": 0.5, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"TREES\", \"TYPE\": \"float\", \"DEFAULT\": 0.7, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"BIRDS\", \"TYPE\": \"bool\", \"DEFAULT\": true },\n    { \"NAME\": \"EXPOSURE\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.3, \"MAX\": 3.0 }\n  ]\n}*/\n\n// ---------------------------------------------------------------------------\n// The world is built around one curve: the path. pathX(z) winds it left and\n// right, pathY(z) carries it over the rises. The ridge is a heightfield whose\n// crest follows that curve, and the boardwalk, posts and ropes are SDFs laid\n// in the path's own frame (across = dp, along = s). The cloud sea is a second\n// heightfield marched on its own, so the terrain map never pays for it.\n// ---------------------------------------------------------------------------\n\n#define PI 3.14159265\n\nvec3 gRo;     // camera, so thin things can widen to a pixel\nfloat gPx;    // pixel footprint per metre of distance\n\nfloat pathX(float z) { return 14.0 * sin(z * 0.028) + 6.0 * sin(z * 0.067 + 1.3) + 2.0 * sin(z * 0.13 + 0.7); }\nfloat pathDX(float z) { return 0.392 * cos(z * 0.028) + 0.402 * cos(z * 0.067 + 1.3) + 0.26 * cos(z * 0.13 + 0.7); }\nfloat pathDY(float z) { return 0.216 * cos(z * 0.024 + 0.5) + 0.244 * cos(z * 0.061) + 0.18 * cos(z * 0.15 + 2.0); }\nfloat pathY(float z) { return 34.0 + 9.0 * sin(z * 0.024 + 0.5) + 4.0 * sin(z * 0.061) + 1.2 * sin(z * 0.15 + 2.0); }\n\nfloat cloudBase() { return CLOUD_LEVEL; }\n\n// Dave Hoskins' hash: small intermediates, portable enough for texture\nfloat hash21(vec2 p) {\n    vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n    p3 += dot(p3, p3.yzx + 33.33);\n    return fract((p3.x + p3.y) * p3.z);\n}\n\nfloat noise(vec2 p) {\n    vec2 i = floor(p), f = fract(p);\n    vec2 u = f * f * (3.0 - 2.0 * f);\n    return mix(mix(hash21(i), hash21(i + vec2(1.0, 0.0)), u.x),\n               mix(hash21(i + vec2(0.0, 1.0)), hash21(i + vec2(1.0, 1.0)), u.x), u.y);\n}\n\nconst mat2 ROT = mat2(0.8, 0.6, -0.6, 0.8);\n\nfloat fbm3(vec2 p) {\n    float a = 0.5, s = 0.0;\n    for (int i = 0; i < 3; i++) { s += a * noise(p); p = ROT * p * 2.03; a *= 0.5; }\n    return s / 0.875;\n}\n\n// ---------------------------------------------------------------- the ridge\n// Crest is a flat shelf a few metres wide under the boardwalk; beyond it each\n// side drops as a*e^2/(1+b*e), whose slope levels off at a/b, so the cliff is\n// sheer to the eye but still has a bounded gradient for the march. Which side\n// is the cliff swaps slowly along the path.\nfloat gGrass;   // grass length at the last terrain() call\n\nfloat terrain(vec2 xz) {\n    float z = xz.y;\n    float k = pathDX(z);\n    float dd = (xz.x - pathX(z)) / sqrt(1.0 + k * k);   // across the path, not along x\n    float sw = sin(z * 0.011 + 0.4);\n    float aL = 0.6 + 1.0 * smoothstep(-0.3, 0.6, sw);\n    float aR = 0.6 + 1.0 * smoothstep(-0.3, 0.6, -sw);\n    float w = 2.4 + 1.6 * noise(vec2(z * 0.045, 3.1));\n    float e = max(abs(dd) - w, 0.0);\n    float a = dd < 0.0 ? aL : aR;\n    float drop = a * e * e / (1.0 + 0.45 * e);\n    float n = fbm3(xz * 0.16);\n    float lump = (n - 0.5) * (0.5 * smoothstep(1.3, 3.0, abs(dd)) + 0.9 * min(e, 5.0));\n    // shelves and gullies on the fall, so a cliff reads as rock and not as a sheet\n    float gully = (noise(vec2(z * 0.2, xz.x * 0.05)) - 0.5) * 2.2 * smoothstep(0.0, 6.0, e);\n    // grass: long and lush on the crest, thinning on the fall, none on rock or\n    // under the boards (it grows right up to the deck edge and over it a little)\n    float slope = a * e * (2.0 + 0.45 * e) / ((1.0 + 0.45 * e) * (1.0 + 0.45 * e));\n    float lush = 0.55 + 0.6 * noise(xz * 0.35 + 7.0);\n    gGrass = GRASS * 0.36 * lush * smoothstep(1.06, 1.3, abs(dd)) * smoothstep(1.5, 0.7, slope);\n    return pathY(z) - 0.08 - drop + lump + gully;\n}\n\n// ------------------------------------------------------------ the boardwalk\nfloat sdBox(vec3 p, vec3 b) {\n    vec3 q = abs(p) - b;\n    return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);\n}\n\n// path frame of a world point: x = across (m), y = height above the deck\n// top, z = arc position along the path\nvec3 pathFrame(vec3 p) {\n    float k = pathDX(p.z);\n    float dx = p.x - pathX(p.z);\n    float s = p.z + dx * k / (1.0 + k * k);\n    float dp = dx / sqrt(1.0 + k * k);\n    return vec3(dp, p.y - pathY(s) - 0.1, s);\n}\n\nconst float POST_GAP = 2.4;\nconst float PLANK = 0.3;\nconst float ROPE_R = 0.032;\n\n// The ropes hang in deep swags: fixed at the posts, dropping 30 cm (upper)\n// and 22 cm (lower) at mid-span, and swinging sideways a little in the wind.\n// Returns (across offset from the rope line, upper rope dy, lower rope dy).\nvec3 ropeFrame(vec3 q, float sl) {\n    // sl is measured from the nearest POST, so |u| runs 0 at a post to 1 at\n    // mid-span; the swag is a parabola whose bottom is at mid-span\n    float u = 1.0 - abs(sl) / (0.5 * POST_GAP);\n    float sw = 1.0 - u * u;                        // 0 at a post, 1 mid-span\n    float lat = WIND * 0.03 * sw * sin(TIME * 1.3 + q.z * 0.37);\n    float ax = abs(q.x) - 1.12 - lat;\n    return vec3(ax, q.y - 0.95 + 0.3 * sw, q.y - 0.55 + 0.22 * sw);\n}\n\n// returns (distance, material): 1 ground, 2 deck, 3 post, 4 rope (7 grass blade, 8 rock)\n// takes the point already in the path frame, so the normal taps below can\n// skip the path trig; dY is the path's rise per metre there\nvec2 mapWalkQ(vec3 q, float dY, float pix) {\n    // cheap bound: nothing of the walk lives outside this slab\n    float bound = max(abs(q.x) - 2.4, abs(q.y - 1.0) - 1.6);\n    if (bound > 0.4) return vec2(bound, 0.0);\n    vec2 res = vec2(1e5, 0.0);\n    // deck: 2.1 m planks, each laid level at its own height, so where the path\n    // climbs the boards become steps. Own plank and both neighbours, so a\n    // riser ahead is never stepped over.\n    float ci = floor(q.z / PLANK);\n    float deck = 1e5;\n    for (int j = -1; j <= 1; j++) {\n        float sc = (ci + float(j) + 0.5) * PLANK;\n        float yT = dY * (sc - q.z);   // over half a metre the rise is a straight line\n        deck = min(deck, sdBox(vec3(q.x, q.y - yT + 0.09, q.z - sc), vec3(1.05, 0.09, 0.5 * PLANK - 0.004)));\n    }\n    float str = sdBox(vec3(abs(q.x) - 0.85, q.y + 0.3, 0.0), vec3(0.06, 0.12, 1e4));\n    res = vec2(min(deck, str), 2.0);\n    if (RAILS) {\n        float sl = q.z - POST_GAP * floor(q.z / POST_GAP + 0.5);\n        vec3 pp = vec3(abs(q.x) - 1.12, q.y - 0.35, sl);\n        float post = sdBox(pp, vec3(0.06, 0.7, 0.06)) - 0.012;\n        if (post < res.x) res = vec2(post, 3.0);\n        // two hemp ropes a side, slung in a catenary from post to post, each\n        // lashed to the post with a collar of turns\n        // widen to a pixel near by; far off a rope may vanish rather than draw a pale line\n        float rr = pix < 0.05 ? max(ROPE_R, pix * 0.6) : ROPE_R;\n        vec3 ry = ropeFrame(q, sl);\n        // the rope's slope near a post shortens the true distance a little\n        float r1 = (length(vec2(ry.x, ry.y)) - rr) * 0.85;\n        float r2 = (length(vec2(ry.x, ry.z)) - rr) * 0.85;\n        // past ~60 m a rope is a fraction of a pixel and only sparkles: drop it\n        if (pix > 0.06) { r1 = 1e5; r2 = 1e5; }\n        float ax = abs(q.x) - 1.12;\n        float lash = min(sdBox(vec3(ax, q.y - 0.95, sl), vec3(0.085, 0.045, 0.085)),\n                         sdBox(vec3(ax, q.y - 0.55, sl), vec3(0.085, 0.045, 0.085))) - 0.012;\n        float rope = min(min(r1, r2), lash);\n        if (rope < res.x) res = vec2(rope, 4.0);\n    }\n    return res;\n}\n\n// ---------------------------------------------------------------- rocks\n// Rocks live in the path's frame: every 4 m of path, on each side, a slot\n// that may hold a stone somewhere on the grassy crest between the boards and\n// the start of the fall -- mostly knee-high, now and then a boulder. Slots\n// only border each other along the path, and a stone keeps 0.1 m inside its\n// slot, so the distance to the slot's ends + 0.1 is a safe step. Across and\n// along come from the frame, height is world y, so a stone is not sheared by\n// the slope of the path. Beyond 110 m they are skipped (hazed specks by then).\nconst float ROCK_S = 4.0;\nvec3 gRC; vec4 gRP; bool gROk;   // centre (across, world y, along); radius, yaw, flatness, seed\nbool gSword;                      // this slot holds a claymore, not a stone (gRP = -, yaw, tilt, seed)\nfloat gPixS;                      // pixel footprint at the point being evaluated\n\nvoid rockAt(float slot, float side) {\n    gROk = false;\n    gSword = false;\n    vec2 id = vec2(slot, side * 7.0);\n    float h1 = hash21(id + 3.7), h2 = hash21(id + 11.1), h3 = hash21(id + 23.3), h4 = hash21(id + 37.9);\n    float h5 = hash21(id + 51.3);\n    if (h5 < SWORDS * 0.3) {\n        // a claymore driven into the turf: somewhere on the crest, leaning up\n        // to ~22 degrees in its own direction. It keeps 0.9 m from the slot\n        // ends, which covers its lean and the crossguard.\n        float sc = (slot + 0.5) * ROCK_S + (h3 - 0.5) * (ROCK_S - 1.8);\n        float w = 2.4 + 1.6 * noise(vec2(sc * 0.045, 3.1));\n        float dp = 1.6 + h4 * max(w + 0.3 - 1.6, 0.0);\n        float sw = sin(sc * 0.011 + 0.4);\n        float a = 0.6 + 1.0 * smoothstep(-0.3, 0.6, side < 0.0 ? sw : -sw);\n        float e = max(dp - w, 0.0);\n        float gy = pathY(sc) - 0.08 - a * e * e / (1.0 + 0.45 * e);\n        gRC = vec3(side * dp, gy - 0.02, sc);\n        gRP = vec4(-1.0, h1 * 6.2832, 0.06 + 0.32 * h2, h4 * 13.0);\n        gSword = true;\n        gROk = true;\n        return;\n    }\n    if (h1 > ROCKS * 0.8) return;\n    float r = 0.4 + 0.95 * h2 * h2;\n    float sc = (slot + 0.5) * ROCK_S + (h3 - 0.5) * (ROCK_S - 2.0 * r - 0.2);\n    float w = 2.4 + 1.6 * noise(vec2(sc * 0.045, 3.1));\n    float lo = 1.75 + r;\n    float dp = lo + h4 * max(w + 0.2 - lo, 0.0);\n    float sw = sin(sc * 0.011 + 0.4);\n    float a = 0.6 + 1.0 * smoothstep(-0.3, 0.6, side < 0.0 ? sw : -sw);\n    float e = max(dp - w, 0.0);\n    float gy = pathY(sc) - 0.08 - a * e * e / (1.0 + 0.45 * e);\n    gRC = vec3(side * dp, gy - 0.1 - 0.2 * r, sc);\n    gRP = vec4(r, h3 * 6.2832, 0.65 + 0.3 * h4, h2 * 17.0);\n    gROk = true;\n}\n\n// A claymore in its own frame: y runs up the blade from where it enters the\n// ground. Returns (distance, 10 steel / 11 leather grip).\nvec2 swordSDF(vec3 q) {\n    q -= gRC;\n    float cs = cos(gRP.y), sn = sin(gRP.y);\n    q.xz = mat2(cs, sn, -sn, cs) * q.xz;\n    float ct = cos(gRP.z), st = sin(gRP.z);\n    q.xy = mat2(ct, st, -st, ct) * q.xy;\n    const float L = 0.95;                          // blade showing above the turf\n    float th = max(0.007, gPixS * 0.5);            // half thickness, never under half a pixel\n    // blade: flat, bevelled, tapering toward the (buried) point\n    float w = 0.028 + 0.02 * clamp((q.y + 0.4) / (L + 0.4), 0.0, 1.0);\n    float dc = max(abs(q.x) - w, abs(q.z) + abs(q.x) * 0.12 - th);\n    float d = max(dc, abs(q.y - (L - 0.4) * 0.5) - (L + 0.4) * 0.5);\n    // crossguard: quillons sloping down toward the blade, each ending in a ring\n    vec3 g = vec3(abs(q.x), q.y - L, q.z);\n    vec3 ge = vec3(0.23, -0.085, 0.0);\n    float hq = clamp(dot(g, ge) / dot(ge, ge), 0.0, 1.0);\n    d = min(d, length(g - ge * hq) - (0.016 - 0.005 * hq));\n    vec3 rp = g - vec3(0.255, -0.1, 0.0);\n    d = min(d, length(vec2(length(rp.xy) - 0.024, rp.z)) - 0.008);\n    d = min(d, sdBox(vec3(q.x, q.y - L - 0.005, q.z), vec3(0.028, 0.035, 0.02)) - 0.006);\n    // pommel: a wheel\n    vec3 pm = vec3(q.x, q.y - L - 0.43, q.z);\n    d = min(d, max(length(pm.xy) - 0.036, abs(pm.z) - 0.018) - 0.004);\n    // grip, wound with leather\n    float gr = max(length(q.xz) - 0.019 - 0.0015 * sin(q.y * 150.0), abs(q.y - L - 0.21) - 0.18);\n    return gr < d ? vec2(gr, 11.0) : vec2(d, 10.0);\n}\n\nfloat stoneSDF(vec3 q) {\n    q -= gRC;\n    float cs = cos(gRP.y), sn = sin(gRP.y);\n    q.xz = mat2(cs, sn, -sn, cs) * q.xz;\n    float r = gRP.x;\n    vec3 ax = r * vec3(1.0, gRP.z, 0.78);\n    float d = (length(q / ax) - 1.0) * ax.y;\n    // a few flat fractures so it reads as stone, not a pebble\n    d = max(d, dot(q, vec3(0.53, 0.75, 0.4)) - 0.62 * r);\n    d = max(d, dot(q, vec3(-0.66, 0.5, -0.56)) - 0.6 * r);\n    d = max(d, dot(q, vec3(0.1, 0.45, -0.89)) - 0.7 * r);\n    d = max(d, dot(q, vec3(0.8, 0.2, -0.56)) - 0.72 * r);\n    d = max(d, dot(q, vec3(-0.3, 0.9, 0.3)) - 0.55 * r * (1.0 + 0.4 * fract(gRP.w)));\n    d += 0.07 * r * sin(q.x * 7.0 / r + gRP.w) * sin(q.y * 6.0 / r) * sin(q.z * 8.0 / r + gRP.w);\n    return d;\n}\n\n// q: (across, world y, along)\nvec2 scatterSDF(vec3 q) {\n    if (gSword) return swordSDF(q);\n    return vec2(stoneSDF(q), 8.0);\n}\n\nfloat rockSDF(vec3 q) { return scatterSDF(q).x; }\n\n// q from pathFrame, py the world height; returns (distance, 8 rock / 9 step limit)\nvec2 mapRocksQ(vec3 q, float py, float dist) {\n    float band = max(abs(q.x) - 9.0, abs(q.y) - 4.0);\n    if (band > 0.0 || dist > 110.0) return vec2(max(band, 1.0) * 0.8, 9.0);\n    float slot = floor(q.z / ROCK_S);\n    float f = q.z - slot * ROCK_S;\n    float edge = min(f, ROCK_S - f) + 0.1;\n    rockAt(slot, q.x < 0.0 ? -1.0 : 1.0);\n    gPixS = gPx * dist;\n    vec2 d = gROk ? scatterSDF(vec3(q.x, py, q.z)) : vec2(1e5, 8.0);\n    d.x *= 0.8;\n    return d.x < edge ? d : vec2(edge, 9.0);\n}\n\n// ---------------------------------------------------------------- trees\n// Bonsai-shaped cliff pines, one per 36 m slot of path on each side at most.\n// The root starts inside the mountain just past the crest, the trunk bores\n// out through the slope, reaches over the drop, then twists upward, and the\n// branches end in flat, lumpy cloud-pads of needles -- the look of pines on\n// sheer granite. Built in (outward, world y, along) from the root. A tree\n// reaches at most 3 + 8*1.4 m from its slot centre, so a neighbour's tree is\n// at least 3.8 m past the slot end: slot ends + 3.8 m is a safe step, and\n// inside the slot a bounding sphere skips the full SDF until it matters.\nconst float TREE_S = 36.0;\nvec3 gTC; vec4 gTP; bool gTOk;   // root (across, y, along); scale, yaw, mirror, seed\nfloat gTSide;\n\nvoid treeAt(float slot, float side) {\n    gTOk = false;\n    vec2 id = vec2(slot, side * 5.0 + 90.0);\n    float h1 = hash21(id + 1.9), h2 = hash21(id + 8.4), h3 = hash21(id + 15.2), h4 = hash21(id + 29.6);\n    if (h1 > TREES * 0.85) return;\n    float sc = (slot + 0.5) * TREE_S + (h2 - 0.5) * 6.0;\n    float w = 2.4 + 1.6 * noise(vec2(sc * 0.045, 3.1));\n    float dp = w + 0.8 + 2.5 * h3;                 // root, just past the edge of the shelf\n    float sw = sin(sc * 0.011 + 0.4);\n    float a = 0.6 + 1.0 * smoothstep(-0.3, 0.6, side < 0.0 ? sw : -sw);\n    float e = max(dp - w, 0.0);\n    float gy = pathY(sc) - 0.08 - a * e * e / (1.0 + 0.45 * e);\n    gTC = vec3(dp, gy - 1.2, sc);\n    gTP = vec4(0.95 + 0.45 * h4, (h3 - 0.5) * 0.9, h2 < 0.5 ? -1.0 : 1.0, h1 * 31.0);\n    gTSide = side;\n    gTOk = true;\n}\n\n// tapered capsule: a swept sphere whose radius runs r0 -> r1 along the segment\nvec2 limb(vec3 p, vec3 a, vec3 b, float r0, float r1) {\n    vec3 pa = p - a, ba = b - a;\n    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n    return vec2(length(pa - ba * h) - mix(r0, r1, h), h);\n}\n\nfloat pad(vec3 p, vec3 c, vec3 r, float sd) {\n    vec3 q = p - c;\n    float d = (length(q / r) - 1.0) * r.y;\n    // lumpy: clusters of needle tufts along the pad's rim and top\n    return d - 0.14 * r.y * (sin(q.x * 5.1 + sd) * sin(q.z * 4.7 + sd * 1.3) + 0.5 * sin(q.x * 11.0 + q.z * 9.0 + sd));\n}\n\n// the skeleton: trunk in through the slope, out over the drop, then up\n// with a twist; branch ends A-D carry the pads\nconst vec3 P0 = vec3(-2.2, -1.0, 0.0), P1 = vec3(1.8, 0.4, 0.6), P2 = vec3(3.6, 0.9, -0.7);\nconst vec3 P3 = vec3(4.1, 2.9, 0.3), P4 = vec3(3.3, 4.3, -0.2);\nconst vec3 TA = vec3(6.0, 1.4, 1.1), TB = vec3(1.9, 3.5, -1.3), TC = vec3(5.6, 3.6, -0.9), TD = vec3(1.6, 1.2, 1.9);\n\nvec3 treeLocal(vec3 q) {\n    vec3 p = vec3(q.x * gTSide - gTC.x, q.y - gTC.y, (q.z - gTC.z) * gTP.z) / gTP.x;\n    float cy = cos(gTP.y), sy = sin(gTP.y);\n    p.xz = mat2(cy, sy, -sy, cy) * p.xz;\n    return p;\n}\n\n// q: (across, world y, along); returns (distance, 12 bark / 13 needles)\nvec2 treeSDF(vec3 q) {\n    float S = gTP.x;\n    vec3 p = treeLocal(q);\n    float sd = gTP.w;\n    float bark = limb(p, P0, P1, 0.5, 0.36).x;\n    bark = min(bark, limb(p, P1, P2, 0.36, 0.28).x);\n    bark = min(bark, limb(p, P2, P3, 0.28, 0.19).x);\n    bark = min(bark, limb(p, P3, P4, 0.19, 0.11).x);\n    // branches\n    vec3 A = TA, B = TB, C = TC, D = TD;\n    bark = min(bark, limb(p, P2, A, 0.15, 0.06).x);\n    bark = min(bark, limb(p, P3, B, 0.12, 0.05).x);\n    bark = min(bark, limb(p, P3, C, 0.12, 0.05).x);\n    bark = min(bark, limb(p, P1, D, 0.13, 0.05).x);\n    // cloud-pads of needles at the branch ends and the crown\n    float nd = pad(p, A + vec3(0.2, 0.3, 0.0), vec3(1.5, 0.5, 1.2), sd);\n    nd = min(nd, pad(p, B + vec3(-0.1, 0.3, 0.0), vec3(1.2, 0.45, 1.0), sd + 1.0));\n    nd = min(nd, pad(p, C + vec3(0.1, 0.3, 0.0), vec3(1.3, 0.45, 1.1), sd + 2.0));\n    nd = min(nd, pad(p, D + vec3(0.0, 0.3, 0.0), vec3(1.1, 0.4, 0.9), sd + 3.0));\n    nd = min(nd, pad(p, P4 + vec3(0.0, 0.35, 0.0), vec3(1.4, 0.55, 1.2), sd + 4.0));\n    return (bark < nd ? vec2(bark, 12.0) : vec2(nd, 13.0)) * vec2(S * 0.75, 1.0);\n}\n\n// Normal without four full evaluations of the tree: one pass finds the\n// nearest limb or pad and takes its gradient analytically (bumps included).\nfloat gBest; vec3 gBN;\nvoid limbN(vec3 p, vec3 a, vec3 b, float r0, float r1) {\n    vec3 pa = p - a, ba = b - a;\n    float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n    float d = length(pa - ba * h) - mix(r0, r1, h);\n    if (d < gBest) { gBest = d; gBN = normalize(pa - ba * h); }\n}\nvoid padN(vec3 p, vec3 c, vec3 r, float sd) {\n    vec3 q = p - c;\n    float d = (length(q / r) - 1.0) * r.y;\n    float s1 = sin(q.x * 5.1 + sd), s2 = sin(q.z * 4.7 + sd * 1.3), s3 = q.x * 11.0 + q.z * 9.0 + sd;\n    d -= 0.14 * r.y * (s1 * s2 + 0.5 * sin(s3));\n    if (d < gBest) {\n        gBest = d;\n        vec3 gb = 0.14 * r.y * vec3(5.1 * cos(q.x * 5.1 + sd) * s2 + 5.5 * cos(s3), 0.0,\n                                    4.7 * s1 * cos(q.z * 4.7 + sd * 1.3) + 4.5 * cos(s3));\n        gBN = normalize(normalize(q / (r * r)) - gb);\n    }\n}\nvec3 treeNormal(vec3 q) {\n    vec3 p = treeLocal(q);\n    float sd = gTP.w;\n    gBest = 1e5; gBN = vec3(0.0, 1.0, 0.0);\n    limbN(p, P0, P1, 0.5, 0.36); limbN(p, P1, P2, 0.36, 0.28);\n    limbN(p, P2, P3, 0.28, 0.19); limbN(p, P3, P4, 0.19, 0.11);\n    limbN(p, P2, TA, 0.15, 0.06); limbN(p, P3, TB, 0.12, 0.05);\n    limbN(p, P3, TC, 0.12, 0.05); limbN(p, P1, TD, 0.13, 0.05);\n    padN(p, TA + vec3(0.2, 0.3, 0.0), vec3(1.5, 0.5, 1.2), sd);\n    padN(p, TB + vec3(-0.1, 0.3, 0.0), vec3(1.2, 0.45, 1.0), sd + 1.0);\n    padN(p, TC + vec3(0.1, 0.3, 0.0), vec3(1.3, 0.45, 1.1), sd + 2.0);\n    padN(p, TD + vec3(0.0, 0.3, 0.0), vec3(1.1, 0.4, 0.9), sd + 3.0);\n    padN(p, P4 + vec3(0.0, 0.35, 0.0), vec3(1.4, 0.55, 1.2), sd + 4.0);\n    // back out of the tree's frame: undo the yaw, the mirror and the side\n    vec3 n = gBN;\n    float cy = cos(gTP.y), sy = sin(gTP.y);\n    n.xz = mat2(cy, -sy, sy, cy) * n.xz;\n    return vec3(n.x * gTSide, n.y, n.z * gTP.z);\n}\n\nvec2 mapTreesQ(vec3 q, float py, float dist) {\n    float yr = q.y;   // already height above the deck\n    float band = max(abs(q.x) - 24.0, max(-yr - 16.0, yr - 12.0));\n    if (band > 0.0 || dist > 420.0) return vec2(max(band, 2.0) * 0.8, 9.0);\n    float slot = floor(q.z / TREE_S);\n    float f = q.z - slot * TREE_S;\n    float edge = min(f, TREE_S - f) + 3.8;\n    treeAt(slot, q.x < 0.0 ? -1.0 : 1.0);\n    vec2 d = vec2(1e5, 12.0);\n    if (gTOk) {\n        float bs = length(vec3(q.x * gTSide - gTC.x, py - gTC.y, q.z - gTC.z)) - 8.0 * gTP.x;\n        d = bs > 0.3 ? vec2(bs, 9.0) : treeSDF(vec3(q.x, py, q.z));\n    }\n    return d.x < edge ? d : vec2(edge, 9.0);\n}\n\nvec2 mapWalk(vec3 p, float g) {\n    vec3 q = pathFrame(p);\n    float dist = length(p - gRo);\n    vec2 res = mapWalkQ(q, pathDY(q.z), gPx * dist);\n    vec2 rk = mapRocksQ(q, p.y, dist);\n    if (rk.x < res.x) res = rk;\n    // the pines hang out over the drop: their shadows fall down the cliff where\n    // nobody looks, so the shadow march (g = 0) leaves them out\n    if (g < 0.5) return res;\n    vec2 tr = mapTreesQ(q, p.y, dist);\n    return tr.x < res.x ? tr : res;\n}\n\n// g = 1: the ground is the top of the grass; g = 0: the soil itself\nvec2 mapScene(vec3 p, float g) {\n    float h = terrain(p.xz) + g * gGrass;\n    vec2 res = vec2((p.y - h) * 0.21, 1.0);\n    vec2 w = mapWalk(p, g);\n    if (w.x < res.x) res = w;\n    return res;\n}\n\n// Ground and boardwalk take separate normals: each pays only for its own\n// field, which halves the compile against a normal of the whole scene.\nvec3 sceneNormal(vec3 p, float t, float mat) {\n    float e = 0.0015 * max(t, 1.0);\n    if (mat > 11.5) {\n        vec3 q = pathFrame(p);\n        q.y = p.y;\n        treeAt(floor(q.z / TREE_S), q.x < 0.0 ? -1.0 : 1.0);\n        vec3 g = treeNormal(q);\n        float k = pathDX(q.z);\n        float c = inversesqrt(1.0 + k * k);\n        return normalize(vec3(g.x * c + g.z * k * c * c, g.y, -g.x * k * c + g.z * c * c));\n    }\n    if (mat > 7.5) {\n        // taps in (across, world y, along), carried back by the frame's heading\n        vec3 q = pathFrame(p);\n        q.y = p.y;\n        rockAt(floor(q.z / ROCK_S), q.x < 0.0 ? -1.0 : 1.0);\n        gPixS = gPx * t;\n        vec2 kr = vec2(1.0, -1.0);\n        vec3 g = kr.xyy * rockSDF(q + kr.xyy * e) + kr.yyx * rockSDF(q + kr.yyx * e) +\n                 kr.yxy * rockSDF(q + kr.yxy * e) + kr.xxx * rockSDF(q + kr.xxx * e);\n        float k = pathDX(q.z);\n        float c = inversesqrt(1.0 + k * k);\n        return normalize(vec3(g.x * c + g.z * k * c * c, g.y, -g.x * k * c + g.z * c * c));\n    }\n    if (mat < 1.5 || mat > 6.5) {\n        float h0 = terrain(p.xz);\n        return normalize(vec3(h0 - terrain(p.xz + vec2(e, 0.0)), e, h0 - terrain(p.xz + vec2(0.0, e))));\n    }\n    // taps in the path frame, then the gradient carried back to the world by\n    // the frame's Jacobian (heading k, rise dY; curvature neglected)\n    vec3 q = pathFrame(p);\n    float k = pathDX(q.z), dY = pathDY(q.z), pix = gPx * t;\n    vec2 kk = vec2(1.0, -1.0);\n    vec3 g = kk.xyy * mapWalkQ(q + kk.xyy * e, dY, pix).x + kk.yyx * mapWalkQ(q + kk.yyx * e, dY, pix).x +\n             kk.yxy * mapWalkQ(q + kk.yxy * e, dY, pix).x + kk.xxx * mapWalkQ(q + kk.xxx * e, dY, pix).x;\n    float c = inversesqrt(1.0 + k * k);\n    float c2 = c * c;\n    return normalize(vec3(g.x * c + (g.z - g.y * dY) * k * c2, g.y, -g.x * k * c + (g.z - g.y * dY) * c2));\n}\n\nfloat softShadow(vec3 ro, vec3 rd) {\n    float res = 1.0, t = 0.05;\n    for (int i = 0; i < 32; i++) {\n        vec2 hm = mapScene(ro + rd * t, 0.0);\n        float h = hm.x;\n        if (abs(hm.y - 9.0) > 0.5) res = min(res, 24.0 * h / t);\n        t += clamp(h, 0.05, 2.0);\n        if (res < 0.01 || t > 45.0) break;\n    }\n    return clamp(res, 0.0, 1.0);\n}\n\n// ------------------------------------------------------------ the cloud sea\n// Billow noise (|2n-1| per octave) has round crests and sharp creases, which\n// is what the top of a cumulus deck looks like from above.\nfloat cloudH(vec2 xz) {\n    vec2 p = xz * 0.013 + vec2(TIME * 0.004, TIME * 0.0025) * DRIFT;\n    float a = 0.5, s = 0.0;\n    for (int i = 0; i < 5; i++) {\n        s += a * abs(2.0 * noise(p) - 1.0);\n        p = ROT * p * 2.1 + vec2(1.7, 9.2);\n        a *= 0.52;\n    }\n    return cloudBase() + 11.0 * PUFF * s;\n}\n\nfloat cloudTop() { return cloudBase() + 11.0 * PUFF; }\n\n// ---------------------------------------------------------------- the sky\nvec3 sunDir() {\n    float el = SUN_HEIGHT * 1.35;\n    return normalize(vec3(0.9 * cos(el), sin(el), 0.25 * cos(el)));\n}\n\nvec3 hazeCol() { return vec3(0.52, 0.66, 0.88); }\n\nvec3 sky(vec3 rd) {\n    float y = max(rd.y, 0.0);\n    vec3 zen = vec3(0.004, 0.028, 0.5);\n    vec3 mid = vec3(0.03, 0.12, 0.62);\n    vec3 hor = vec3(0.4, 0.56, 0.85);\n    vec3 c = mix(mid, zen, smoothstep(0.1, 0.8, y));\n    c = mix(c, hor, exp(-y * 9.0));\n    c = mix(c, hazeCol(), exp(-y * 60.0) * 0.5);\n    float sd = max(dot(rd, sunDir()), 0.0);\n    c += vec3(1.0, 0.9, 0.7) * (pow(sd, 12.0) * 0.15 + pow(sd, 900.0) * 30.0);\n    return c;\n}\n\n// Far ranges above the cloud floor, drawn as a silhouette against the sky:\n// they are kilometres out, so they never parallax. Ridged 1D noise over\n// azimuth gives the skyline; the facet facing the sun comes from its slope.\nfloat n1(float x) {\n    float i = floor(x), f = fract(x);\n    return mix(hash21(vec2(i, 1.3)), hash21(vec2(i + 1.0, 1.3)), f * f * (3.0 - 2.0 * f));\n}\nfloat range(float az) {\n    float h = 0.0, a = 0.5, x = az * 5.0;\n    for (int i = 0; i < 5; i++) { h += a * (1.0 - abs(2.0 * n1(x) - 1.0)); x *= 2.2; a *= 0.45; }\n    float env = smoothstep(0.35, 0.8, n1(az * 2.1 + 4.0));\n    return env * pow(h, 2.0) * 0.2 - 0.01;\n}\nvec3 peaks(vec3 rd, vec3 col) {\n    float az = atan(rd.x, rd.z);\n    float m = range(az);\n    if (rd.y > m) return col;\n    float sl = (range(az + 0.004) - m) / 0.004;           // skyline slope\n    float face = clamp(0.5 + 0.35 * sl, 0.0, 1.0);         // east flanks catch the sun\n    float depth = clamp((m - rd.y) / max(m, 0.001), 0.0, 1.0);\n    face = mix(face, 0.45, smoothstep(0.1, 0.6, depth));   // the slope only means anything near the crest\n    float snow = smoothstep(0.4, 0.2, depth + 0.25 * (noise(vec2(az * 140.0, rd.y * 160.0)) - 0.5)) * smoothstep(0.02, 0.04, m);\n    vec3 rockc = mix(vec3(0.16, 0.22, 0.4), vec3(0.42, 0.48, 0.62), face);\n    vec3 snowc = mix(vec3(0.5, 0.6, 0.85), vec3(1.2, 1.18, 1.12), face);\n    vec3 c = mix(rockc, snowc, snow);\n    // kilometres of air: most of the colour is the air in front\n    return mix(c, hazeCol() * 1.05, 0.3 + 0.45 * smoothstep(0.0, 1.0, depth));\n}\n\n// ---------------------------------------------------------------- grass\n// The march stops on the TOP of the grass layer; from there a short walk\n// through the layer tests real blades. Each 6 cm cell holds two tapered\n// blades, each leaning its own way and swaying in the wind. A blade never\n// narrows below half a pixel, so distant grass thickens into a canopy\n// instead of sparkling.\nconst float BLADE_C = 0.06;\nfloat gV, gTint;   // height fraction and tint of the blade that was hit\nvec2 gLean;        // this patch's lean: tip offset per metre of height, squared in y\n\n// Grass does not stand straight: clumps lodge and splay in different\n// directions. A smooth lean field (~45 cm patches) shears the cell lookup with\n// height, so whole clumps bend together, harder toward the tips; each blade\n// then adds its own random tilt inside its cell. The field is read once per\n// pixel at the walk's entry point, so the per-step cost is one multiply-add.\nvec2 leanAt(vec2 xz) {\n    float a = noise(xz * 2.2) * 9.42 + noise(xz * 0.7 + 5.3) * 6.2832;\n    float m = 0.12 + 0.22 * noise(xz * 1.8 + 9.1);   // tip offset / height: ~7-19 degrees\n    return m * vec2(cos(a), sin(a));\n}\n\nbool grassBlade(vec3 p, float y, float gh, float pix) {\n    vec2 cc = (p.xz - gLean * (y * y / gh)) / BLADE_C;\n    vec2 cid = floor(cc);\n    vec2 f = cc - cid;\n    float wmin = 1.1 * pix / BLADE_C;\n    for (int b = 0; b < 2; b++) {\n        vec2 id = cid + vec2(float(b) * 57.3, float(b) * 31.1);\n        float h1 = hash21(id), h2 = hash21(id + 13.1), h3 = hash21(id + 27.9), h4 = hash21(id + 41.3);\n        float v = y / (gh * (0.5 + 0.5 * h3));\n        if (v < 1.0) {\n            float ang = h1 * 6.2832;\n            float sway = WIND * 0.1 * sin(TIME * 1.9 + p.x * 0.9 + p.z * 0.6 + h2 * 2.0);\n            vec2 c = vec2(0.32, 0.32) + 0.36 * vec2(h2, h4)\n                   + v * v * ((0.14 + 0.18 * h3) * vec2(cos(ang), sin(ang)) + sway * vec2(1.0, 0.35));\n            float w = max(0.2 * (1.0 - 0.85 * v), wmin);\n            if (length(f - c) < w) { gV = v; gTint = h1; return true; }\n        }\n    }\n    return false;\n}\n\n// ---------------------------------------------------------------- shading\nvec3 shadeScene(vec3 p, vec3 rd, float t, float mat) {\n    vec3 n = sceneNormal(p, t, mat);\n    vec3 L = sunDir();\n    vec3 q = pathFrame(p);\n    float pix = gPx * t;\n    bool isGrass = mat > 6.5 && mat < 7.5;\n    // distant grass: clumps and tussocks of a metre or so, as relief the sun\n    // can model (a colour pattern alone reads flat), plus wind sheen rolling\n    // across the slope where the blades bend and show their pale side\n    vec2 wp = vec2(p.x + p.y * 0.8, p.z - p.y * 0.6);\n    float c0 = 0.5, sheen = 0.0;\n    if (isGrass) {\n        float farN = smoothstep(0.004, 0.02, pix);\n        c0 = fbm3(wp * 0.9);\n        float cx = fbm3((wp + vec2(0.3, 0.0)) * 0.9), cz = fbm3((wp + vec2(0.0, 0.3)) * 0.9);\n        n = normalize(n + vec3(c0 - cx, 0.0, c0 - cz) * (4.0 * farN * min(GRASS, 1.0)));\n        sheen = WIND * smoothstep(0.55, 0.85, noise(wp * 0.11 + vec2(-TIME * 0.35, TIME * 0.12))) * farN;\n    }\n    float dif = max(dot(n, L), 0.0);\n    // pine pads: break the smooth pad into needle tufts the sun can model\n    if (mat > 12.5) {\n        vec2 tp = vec2(p.x + p.y * 0.7, p.z - p.y * 0.5) * 5.0;\n        float t0 = noise(tp), tx = noise(tp + vec2(0.2, 0.0)), tz = noise(tp + vec2(0.0, 0.2));\n        n = normalize(n + vec3(t0 - tx, 0.0, t0 - tz) * 6.0);\n        dif = max(dot(n, L), 0.0);\n    }\n    if (isGrass) dif = clamp(dot(n, L) * 0.7 + 0.3, 0.0, 1.0);   // blades face every way\n    // grass casts from one height, the canopy top: a shadow cast from wherever\n    // the blade walk happened to stop varies with that height and prints bands\n    vec3 so = p + n * 0.04;\n    if (isGrass) { float hs = terrain(p.xz); so.y = hs + gGrass + 0.05; }\n    float sh = dif > 0.0 ? softShadow(so, L) : 0.0;\n    float occ = 1.0;\n    vec3 envAdd = vec3(0.0);\n    float shin = 40.0;\n    vec3 alb;\n    float spec = 0.0;\n    if (mat < 1.5) {\n        // grass, with fine clump noise that fades out before it can shimmer\n        float fine = mix(0.5, noise(p.xz * 7.0), clamp(0.08 / pix, 0.0, 1.0));\n        float blade = mix(0.5, noise(p.xz * vec2(26.0, 31.0)), clamp(0.02 / pix, 0.0, 1.0));\n        fine = fine * 0.7 + blade * 0.6 - 0.15;\n        float mid = fbm3(vec2(p.x + p.y * 0.8, p.z - p.y * 0.6) * 0.16);\n        vec3 g1 = vec3(0.07, 0.19, 0.03), g2 = vec3(0.22, 0.3, 0.06);\n        alb = mix(g1, g2, mid) * (0.75 + 0.5 * fine);\n        // the cliff face: grey earth and rock in vertical streaks, tufts clinging on\n        float rock = smoothstep(0.62, 0.35, n.y);\n        // horizontal strata broken by vertical runoff streaks\n        float hz = p.x * 0.7 + p.z * 0.7;\n        float strata = noise(vec2(hz * 0.08, p.y * 1.4 + 2.0 * noise(vec2(hz * 0.05, p.y * 0.2))));\n        float streak = noise(vec2(hz * 1.6, p.y * 0.12));\n        float cr = smoothstep(0.3, 0.7, strata) * (0.6 + 0.4 * streak);\n        vec3 rc = mix(vec3(0.07, 0.07, 0.065), vec3(0.3, 0.29, 0.26), cr);\n        float tuft = smoothstep(0.55, 0.7, noise(p.xz * 1.3 + p.y * 0.9));\n        alb = mix(alb, rc, rock * (1.0 - 0.7 * tuft));\n        // soil under the grass sees little sky\n        terrain(p.xz);\n        alb *= mix(1.0, 0.3, smoothstep(0.0, 0.12, gGrass));\n        // trampled contact shadow where the deck edge sits in the grass\n        float edge = smoothstep(1.6, 1.05, abs(q.x)) * smoothstep(1.2, 0.0, abs(q.y + 0.1));\n        alb *= 1.0 - 0.45 * edge;\n    } else if (mat < 2.5) {\n        // planks run ACROSS the path; the index comes from arc length\n        float pl = q.z / 0.3;\n        float id = floor(pl);\n        float f = fract(pl);\n        float h = hash21(vec2(id, 7.0));\n        float grain = noise(vec2(q.x * 2.5 + h * 20.0, q.z * 45.0));\n        alb = mix(vec3(0.2, 0.12, 0.065), vec3(0.34, 0.24, 0.15), h) * (0.8 + 0.35 * grain);\n        alb = mix(alb, vec3(0.28, 0.27, 0.25), 0.25 * noise(vec2(q.x * 0.7, q.z * 0.2)));  // silvered by weather\n        float gw = max(0.035, pix / 0.3);\n        float gap = smoothstep(0.0, gw, f) * smoothstep(1.0, 1.0 - gw, f);\n        alb *= mix(1.0, 0.25 + 0.75 * gap, clamp(0.02 / pix, 0.0, 1.0));\n        if (q.y < -0.07) alb *= 0.6;\n        spec = 0.08;\n    } else if (mat < 3.5) {\n        float grain = noise(vec2(q.x * 3.0, q.y * 25.0) + q.z);\n        alb = vec3(0.17, 0.11, 0.07) * (0.75 + 0.4 * grain);\n    } else if (mat < 4.5) {\n        // three-strand hemp: the strands spiral round the rope axis\n        float sl = q.z - POST_GAP * floor(q.z / POST_GAP + 0.5);\n        vec3 ry = ropeFrame(q, sl);\n        vec2 r = vec2(ry.x, abs(ry.y) < abs(ry.z) ? ry.y : ry.z);\n        float th = atan(r.y, r.x);\n        float fade = clamp(0.02 / pix, 0.0, 1.0);\n        float twist = mix(0.0, sin(3.0 * th + q.z * 95.0), fade);\n        float fib = noise(vec2(th * 5.0, q.z * 260.0));\n        alb = vec3(0.52, 0.41, 0.25) * (0.5 + 0.5 * smoothstep(-0.9, 0.7, twist)) * (0.85 + 0.3 * mix(0.5, fib, fade));\n        // the lashing: turns wound round the post\n        if (abs(sl) < 0.1) alb *= 0.75 + 0.25 * mix(0.5, smoothstep(-0.5, 0.5, sin(q.y * 190.0)), fade);\n        occ = 0.85;\n    } else if (mat < 7.5) {\n        // a grass blade: dark at the root, bright young green at the tip\n        vec3 root = vec3(0.012, 0.04, 0.007);\n        vec3 midc = mix(vec3(0.05, 0.19, 0.018), vec3(0.11, 0.23, 0.03), gTint);\n        vec3 tip = mix(vec3(0.12, 0.3, 0.03), vec3(0.24, 0.33, 0.06), gTint * gTint);\n        alb = gV < 0.5 ? mix(root, midc, gV * 2.0) : mix(midc, tip, gV * 2.0 - 1.0);\n        occ = mix(0.3, 1.0, pow(gV, 1.3));\n        // past a few metres a pixel spans many blades: settle to their average,\n        // with meadow-scale patches so the slope does not go flat\n        float far = smoothstep(0.012, 0.05, pix);\n        // noise in xz squashes into contour stripes on a steep slope; fold height in\n        float patchv = fbm3(vec2(p.x + p.y * 0.8, p.z - p.y * 0.6) * 0.16);\n        vec3 avg = mix(vec3(0.035, 0.12, 0.012), vec3(0.085, 0.19, 0.026), smoothstep(0.25, 0.75, patchv));\n        float clump = noise(vec2(p.x + p.y * 0.8, p.z - p.y * 0.6) * 1.7);\n        avg *= 0.62 + 0.3 * clump + 0.1 * (gTint - 0.5);\n        alb = mix(alb, avg, far);\n        occ = mix(occ, 0.8, far) * mix(1.0, 0.55 + 0.6 * c0, far);   // hollows between tussocks\n        alb = mix(alb, vec3(0.2, 0.26, 0.1), 0.35 * min(sheen, 1.0));\n    } else if (mat < 8.5) {\n        // weathered granite: grey mottling, crusts of yellow-green lichen,\n        // moss on the tops, and dark damp soil-staining at the foot\n        vec3 rp = p * 1.3;\n        float m = noise(rp.xz * 1.7 + rp.y * 1.1) * 0.6 + noise(rp.xy * 5.0 + rp.z) * 0.4;\n        alb = mix(vec3(0.17, 0.155, 0.13), vec3(0.4, 0.37, 0.31), m);\n        float lich = smoothstep(0.62, 0.72, noise(vec2(p.x + p.y, p.z - p.y) * 3.5));\n        alb = mix(alb, vec3(0.42, 0.4, 0.22), lich * 0.8);\n        float moss = smoothstep(0.7, 0.95, n.y) * smoothstep(0.6, 0.75, noise(p.xz * 3.1 + 4.0));\n        alb = mix(alb, vec3(0.06, 0.08, 0.025), 0.7 * moss);\n        float hs = terrain(p.xz);\n        alb *= mix(0.4, 1.0, smoothstep(0.0, 0.45, p.y - hs - gGrass * 0.6));\n        spec = 0.06;\n    } else if (mat < 10.5) {\n        // old steel: a dark mirror of sky and cloud, dulled and rust-spotted\n        vec2 rq = vec2(p.x + p.z, p.y);\n        float rust = smoothstep(0.5, 0.78, noise(rq * 9.0) * 0.65 + noise(rq * 31.0) * 0.35);\n        alb = mix(vec3(0.045, 0.047, 0.05), vec3(0.2, 0.085, 0.03), rust);\n        vec3 rf = reflect(rd, n);\n        vec3 env = rf.y > 0.0 ? sky(rf) : mix(vec3(1.05, 1.05, 1.08), hazeCol(), 0.3);\n        float fres = 0.55 + 0.45 * pow(1.0 - max(dot(n, -rd), 0.0), 3.0);\n        envAdd = env * fres * 0.7 * (1.0 - rust);\n        spec = 1.6 * (1.0 - rust);\n        shin = 180.0;\n    } else if (mat < 11.5) {\n        // the grip: dark leather wound in bands\n        float band = sin(p.y * 150.0);\n        alb = vec3(0.07, 0.04, 0.022) * (0.75 + 0.3 * smoothstep(-0.4, 0.6, band));\n        spec = 0.12;\n    } else if (mat < 12.5) {\n        // pine bark: grey-brown plates split by dark fissures that spiral\n        // with the twist of the trunk\n        float pl = noise(vec2((p.x + p.z) * 3.0 + p.y * 1.5, p.y * 7.0 - (p.x - p.z) * 1.2));\n        alb = mix(vec3(0.05, 0.035, 0.028), vec3(0.2, 0.15, 0.11), smoothstep(0.3, 0.65, pl));\n        alb = mix(alb, vec3(0.26, 0.25, 0.22), 0.3 * smoothstep(0.6, 0.8, noise(p.xz * 2.0 + p.y)));\n    } else {\n        // needles: dark pine green in tufts, lighter where the tufts catch\n        // the sun, near black in the pad's underside\n        vec2 tq = vec2(p.x + p.y * 0.7, p.z - p.y * 0.5);\n        float tuft = noise(tq * 5.0) * 0.6 + noise(tq * 17.0) * 0.4;\n        alb = mix(vec3(0.008, 0.03, 0.01), vec3(0.07, 0.15, 0.035), smoothstep(0.25, 0.8, tuft));\n        occ = mix(0.35, 1.0, smoothstep(-0.6, 0.6, n.y));\n    }\n    vec3 sunC = vec3(1.0, 0.93, 0.82) * 3.2;\n    vec3 skyC = vec3(0.16, 0.28, 0.6);\n    float amb = 0.55 + 0.45 * n.y;\n    vec3 col = alb * occ * (sunC * dif * sh + skyC * amb * 1.3 + vec3(0.35, 0.4, 0.45) * 0.25 * clamp(-n.y, 0.0, 1.0));\n    // light through a blade when you look toward the sun\n    if (isGrass) col += vec3(0.3, 0.5, 0.06) * sunC * 0.3 * sh * gV * pow(max(dot(rd, L), 0.0), 3.0);\n    vec3 hv = normalize(L - rd);\n    col += sunC * spec * sh * pow(max(dot(n, hv), 0.0), shin) * dif;\n    col += envAdd;\n    return col;\n}\n\nvec3 shadeCloud(vec3 p, vec3 rd, float t, float far) {\n    vec3 L = sunDir();\n    float e = 0.02 * max(t, 8.0);\n    float h0 = cloudH(p.xz);\n    vec3 n = normalize(vec3(h0 - cloudH(p.xz + vec2(e, 0.0)), e, h0 - cloudH(p.xz + vec2(0.0, e))));\n    n = normalize(mix(n, vec3(0.0, 1.0, 0.0), far));\n    float hn = clamp((p.y - cloudBase()) / (11.0 * PUFF), 0.0, 1.0);\n    float wrap = smoothstep(-0.35, 0.85, dot(n, L));\n    vec3 lit = vec3(1.0, 0.97, 0.92) * 1.7;\n    vec3 shade = vec3(0.22, 0.34, 0.7) * 0.8;\n    vec3 col = mix(shade, lit, wrap * wrap);\n    // deep in the creases the light has been through more cloud\n    col *= mix(0.45, 1.0, smoothstep(0.05, 0.75, hn));\n    // thin silver rim where the sun grazes a crest\n    col += lit * 0.25 * pow(1.0 - max(dot(n, -rd), 0.0), 4.0) * wrap;\n    return col;\n}\n\n// ----------------------------------------------------------------- main\nvoid main() {\n    vec2 uv = (gl_FragCoord.xy - 0.5 * RENDERSIZE.xy) / RENDERSIZE.y;\n\n    // walk the path\n    float s = 118.0 + TIME * SPEED;\n    float stepPh = s / 0.8 * PI;\n    float bob = BOB * min(SPEED, 6.0) / 6.0;\n    vec3 ro = vec3(pathX(s), pathY(s) + 0.1 + CAM_HEIGHT + bob * 0.03 * abs(sin(stepPh)), s);\n    ro.x += bob * 0.015 * sin(stepPh * 0.5);\n    // look at a blend of two points ahead, so tight bends turn the head smoothly\n    vec3 ta = 0.5 * (vec3(pathX(s + 6.0), pathY(s + 6.0), s + 6.0) + vec3(pathX(s + 13.0), pathY(s + 13.0), s + 13.0));\n    ta.y += 0.1 + CAM_HEIGHT - 1.6;\n    vec3 fw = normalize(ta - ro);\n    vec3 rt = normalize(cross(vec3(0.0, 1.0, 0.0), fw));\n    vec3 up = cross(fw, rt);\n    float fl = 1.35;\n    vec3 rd = normalize(uv.x * rt + uv.y * up + fl * fw);\n    gRo = ro;\n    gPx = 1.0 / (RENDERSIZE.y * fl);\n\n    // --- ridge + boardwalk\n    float t = 0.05, ts = -1.0, mat = 0.0;\n    float cb = cloudBase();\n    bool gone = false;      // left the world (below the clouds or out of range)\n    vec2 h = vec2(1.0, 1.0);\n    for (int i = 0; i < 260; i++) {\n        vec3 p = ro + rd * t;\n        h = mapScene(p, 1.0);\n        if (h.x < 0.0006 * t && abs(h.y - 9.0) > 0.5) { ts = t; mat = h.y; break; }\n        if (p.y < cb || t > 510.0) { gone = true; break; }\n        t += max(h.x, 0.0016 * t);\n    }\n    // a grazing ray can spend every step creeping along a slope; it is on the\n    // surface by then, so take it rather than letting the cloud show through\n    if (ts < 0.0 && !gone) {\n        vec3 pe = ro + rd * t;\n        bool stepFlag = abs(h.y - 9.0) < 0.5;\n        if (!stepFlag || pe.y - terrain(pe.xz) - gGrass < 0.02 * t) { ts = t; mat = stepFlag ? 1.0 : h.y; }\n    }\n\n    // --- into the grass: from the layer top, walk down through the blades\n    if (ts > 0.0 && mat < 1.5) {\n        vec3 p0 = ro + rd * ts;\n        float h0 = terrain(p0.xz);\n        float gh = gGrass;\n        vec2 gr = vec2(terrain(p0.xz + vec2(0.05, 0.0)) - h0, terrain(p0.xz + vec2(0.0, 0.05)) - h0) / 0.05;\n        gLean = leanAt(p0.xz) * GRASS;\n        if (gh > 0.01) {\n            // dither where the walk starts, or its fixed steps print contour lines\n            float tg = ts + hash21(gl_FragCoord.xy) * max(0.018, gPx * ts * 0.8), yl = gh;\n            bool done = false, exitUp = false;\n            for (int i = 0; i < 40; i++) {\n                vec3 p = ro + rd * tg;\n                yl = p.y - h0 - dot(gr, p.xz - p0.xz);\n                if (yl < 0.0) { mat = 7.0; gV = 0.08; gTint = 0.3; done = true; break; }   // down among the roots\n                if (yl > gh * 1.02 && tg > ts + 0.05) { exitUp = true; break; }\n                float pix = gPx * tg;\n                if (grassBlade(p, yl, gh, pix)) { mat = 7.0; done = true; break; }\n                tg += max(0.018, pix * 0.8);\n            }\n            // a stone half-buried in the grass: if the walk passed through it,\n            // the stone was what we saw. Three samples along the walk suffice.\n            if (done) {\n                vec3 qr = pathFrame(ro + rd * tg);\n                rockAt(floor(qr.z / ROCK_S), qr.x < 0.0 ? -1.0 : 1.0);\n                if (gROk) {\n                    for (int k = 1; k <= 3; k++) {\n                        float tk = mix(ts, tg, float(k) / 3.0);\n                        vec3 pk = ro + rd * tk;\n                        vec3 qk = pathFrame(pk);\n                        gPixS = gPx * tk;\n                        vec2 sk = scatterSDF(vec3(qk.x, pk.y, qk.z));\n                        if (sk.x < 0.0) { tg = tk; mat = sk.y; break; }\n                    }\n                }\n            }\n            ts = tg;\n            // a long grazing path through the layer is a canopy by then\n            if (!done && !exitUp) {\n                vec2 cp = (ro + rd * tg).xz;\n                mat = 7.0; gV = 0.5 + 0.35 * noise(cp * 3.0); gTint = noise(cp * 1.3 + 5.0);\n            }\n            if (exitUp) {\n                // slipped out over the tips: carry on to whatever is behind\n                ts = -1.0;\n                float t2 = tg;\n                bool out2 = false;\n                float d2 = 1.0;\n                for (int i = 0; i < 80; i++) {\n                    vec3 p = ro + rd * t2;\n                    // ground only: the boards are never behind a tuft from here\n                    d2 = (p.y - terrain(p.xz) - gGrass) * 0.21;\n                    if (d2 < 0.0006 * t2) { ts = t2; mat = 7.0; gV = 0.8; gTint = noise(p.xz * 1.3 + 5.0); break; }\n                    if (p.y < cb || t2 > 510.0) { out2 = true; break; }\n                    t2 += max(d2, 0.001 * t2);\n                }\n                // same grazing rule as the main march\n                if (ts < 0.0 && !out2) { ts = t2; mat = 7.0; gV = 0.8; gTint = 0.5; }\n            }\n        }\n    }\n\n    // --- cloud sea: enter at the top of the deck, then march the heightfield\n    float tc = -1.0, far = 0.0;\n    float top = cloudTop();\n    if (rd.y < 0.0) {\n        float t0 = max((ro.y - top) / -rd.y, 0.0);\n        float tp = (ro.y - cb - 5.0 * PUFF) / -rd.y;   // mean plane, for rays too long to march\n        float tt = t0;\n        for (int i = 0; i < 90; i++) {\n            vec3 p = ro + rd * tt;\n            float d = p.y - cloudH(p.xz);\n            if (d < 0.002 * tt) { tc = tt; break; }\n            tt += max(d * 0.55, 0.004 * tt);\n            if (tt > 1400.0) break;\n        }\n        if (tc < 0.0) { tc = tp; far = 1.0; }\n        far = max(far, smoothstep(500.0, 1400.0, tc));\n    }\n\n    vec3 col;\n    float td;\n    float farFade = 0.0;\n    if (ts > 0.0 && (tc < 0.0 || ts < tc)) {\n        col = shadeScene(ro + rd * ts, rd, ts, mat);\n        td = ts;\n        // the ridge far along the path is a sliver a pixel tall: let it go into\n        // the air completely before the march stops looking for it\n        farFade = smoothstep(150.0, 500.0, ts);\n    } else if (tc > 0.0) {\n        col = shadeCloud(ro + rd * tc, rd, tc, far);\n        td = tc;\n    } else {\n        col = sky(rd);\n        col = peaks(rd, col);\n        td = -1.0;\n    }\n\n    // aerial perspective: blue with distance, a little warmer toward the sun\n    if (td > 0.0) {\n        float fo = 1.0 - exp(-td * 0.0007 * HAZE);\n        vec3 hz = hazeCol() + vec3(0.3, 0.2, 0.05) * pow(max(dot(rd, sunDir()), 0.0), 6.0);\n        col = mix(col, hz * 1.1, fo);\n        // the far ridge is a sliver a pixel tall: dissolve it into the sky that\n        // is behind it, so a thin sliver cannot show as a pale streak\n        col = mix(col, sky(vec3(rd.x, max(rd.y, 0.0), rd.z)), farFade);\n    }\n\n    // --- birds: two loose flocks crossing far out over the clouds. Each bird is\n    // a 2D gull silhouette at its projected position, flapping, hidden behind\n    // anything nearer, and hazed like the rest of the view.\n    if (BIRDS) {\n        float px1 = 1.0 / RENDERSIZE.y;\n        for (int i = 0; i < 14; i++) {\n            float fi = float(i);\n            float fl2 = floor(fi / 7.0), m = fi - fl2 * 7.0 - 3.0;\n            float hA = hash21(vec2(fl2, 3.0)), hB = hash21(vec2(fi, 8.0)), hC = hash21(vec2(fi, 15.0));\n            float dir = fl2 < 0.5 ? 1.0 : -1.0;\n            float xw = mod(TIME * (6.0 + 2.0 * hA) + 90.0 + 300.0 * hA, 320.0) - 160.0;\n            vec3 bp = vec3(ro.x + dir * xw - abs(m) * 2.2 * dir + hB * 1.5,\n                           ro.y - 6.0 + 20.0 * hA - abs(m) * 0.5 + hC * 1.2 + 1.5 * sin(TIME * 0.3 + fi),\n                           ro.z + 70.0 + 70.0 * fl2 + 50.0 * hA + abs(m) * 2.5 + hC * 3.0);\n            vec3 v2 = bp - ro;\n            float zc = dot(v2, fw);\n            float dist = length(v2);\n            vec2 sp = vec2(dot(v2, rt), dot(v2, up)) / max(zc, 1.0) * fl;\n            float size = 0.85 / max(zc, 1.0) * fl;         // half wingspan\n            float sizeE = max(size, 1.6 * px1);\n            vec2 lp = (uv - sp) / sizeE;\n            lp.x *= dir;\n            bool vis = zc > 5.0 && (td < 0.0 || dist < td) && abs(lp.x) < 1.2 && abs(lp.y) < 1.2;\n            if (vis) {\n                float fphase = sin(TIME * (6.0 + 3.0 * hB) + fi * 1.7);\n                float wy = (0.2 + 0.6 * fphase) * abs(lp.x) - 0.45 * fphase * lp.x * lp.x;\n                float d = abs(lp.y - wy) - (0.14 * (1.0 - abs(lp.x)) + 0.035);\n                d = abs(lp.x) > 1.0 ? 1.0 : d;\n                d = min(d, length(lp * vec2(1.0, 2.2)) - 0.13);   // body\n                float a = clamp(0.5 - d * sizeE / px1, 0.0, 1.0) * min(1.0, size / sizeE + 0.3);\n                vec3 bc = mix(vec3(0.03, 0.035, 0.05), hazeCol() * 1.1, 1.0 - exp(-dist * 0.0012 * HAZE));\n                col = mix(col, bc, a);\n            }\n        }\n    }\n\n    col *= EXPOSURE;\n    col = 1.0 - exp(-col * 1.1);\n    col = pow(col, vec3(1.0 / 2.2));\n    // gentle vignette\n    vec2 v = gl_FragCoord.xy / RENDERSIZE.xy - 0.5;\n    col *= 1.0 - 0.25 * dot(v, v);\n    gl_FragColor = vec4(col, 1.0);\n}\n",
    "generator": {
      "model": "Claude-Opus-5",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}