{
  "license": "CC0-1.0",
  "shader": {
    "id": "p5np1vr7",
    "title": "Symbiont",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "Claude",
    "status": "active",
    "compile_ok": true,
    "render_hash": "0010081c18300000",
    "tags": [
      "gloop",
      "glorp",
      "shlorp",
      "lil guy",
      "organic",
      "colorful",
      "raymarching"
    ],
    "parents": [],
    "author": "u7y1evhf0znj",
    "author_name": "ksin",
    "author_url": "/u/ksin",
    "created_at": "2026-09-08T23:54:37.201Z",
    "poster_url": "/posters/p5np1vr7.avif",
    "stats": {
      "slops": 1,
      "views": 155,
      "forks": 0
    },
    "self": "/api/v1/shaders/p5np1vr7",
    "isf": "/*{\n  \"DESCRIPTION\": \"Gloop\",\n  \"CREDIT\": \"CC0\",\n  \"CATEGORIES\": [\"generator\", \"3d\", \"raymarching\", \"sdf\", \"organic\", \"thorns\"],\n  \"INPUTS\": [\n    { \"NAME\": \"uSpeed\",    \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 3.0  },\n    { \"NAME\": \"uZoom\",     \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.4,  \"MAX\": 2.5  },\n    { \"NAME\": \"uTilt\",     \"TYPE\": \"float\", \"DEFAULT\": 0.55,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uSpin\",     \"TYPE\": \"float\", \"DEFAULT\": 0.16,  \"MIN\": -1.0, \"MAX\": 1.0  },\n    { \"NAME\": \"uThorns\",   \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uLength\",   \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uSharp\",    \"TYPE\": \"float\", \"DEFAULT\": 0.80,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uBlade\",    \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uCrown\",    \"TYPE\": \"float\", \"DEFAULT\": 0.028, \"MIN\": 0.006, \"MAX\": 0.10 },\n    { \"NAME\": \"uCrumple\",  \"TYPE\": \"float\", \"DEFAULT\": 0.65,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uTangle\",   \"TYPE\": \"float\", \"DEFAULT\": 0.80,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uTravel\",   \"TYPE\": \"float\", \"DEFAULT\": 0.80,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uFoam\",     \"TYPE\": \"float\", \"DEFAULT\": 0.55,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uGreen\",    \"TYPE\": \"float\", \"DEFAULT\": 0.80,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uGrass\",    \"TYPE\": \"float\", \"DEFAULT\": 0.75,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uGlow\",     \"TYPE\": \"float\", \"DEFAULT\": 0.85,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uOrbs\",     \"TYPE\": \"float\", \"DEFAULT\": 0.60,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uPods\",     \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uGlass\",    \"TYPE\": \"float\", \"DEFAULT\": 0.40,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uRibs\",     \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uTeeth\",    \"TYPE\": \"float\", \"DEFAULT\": 0.85,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uHue\",      \"TYPE\": \"float\", \"DEFAULT\": 0.30,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"uWander\",   \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.5  },\n    { \"NAME\": \"uCore\",     \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"uHalo\",     \"TYPE\": \"float\", \"DEFAULT\": 0.60,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uKey\",      \"TYPE\": \"float\", \"DEFAULT\": 0.90,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"uRim\",      \"TYPE\": \"float\", \"DEFAULT\": 0.85,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"uWet\",      \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uIris\",     \"TYPE\": \"float\", \"DEFAULT\": 0.60,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uMotion\",   \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"uSway\",     \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uFlare\",    \"TYPE\": \"float\", \"DEFAULT\": 0.70,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uAero\",     \"TYPE\": \"float\", \"DEFAULT\": 0.60,  \"MIN\": 0.0,  \"MAX\": 1.0  },\n    { \"NAME\": \"uSat\",      \"TYPE\": \"float\", \"DEFAULT\": 1.45,  \"MIN\": 0.0,  \"MAX\": 2.5  },\n    { \"NAME\": \"uExposure\", \"TYPE\": \"float\", \"DEFAULT\": 1.00,  \"MIN\": 0.0,  \"MAX\": 3.0  },\n    { \"NAME\": \"uGrain\",    \"TYPE\": \"float\", \"DEFAULT\": 0.04,  \"MIN\": 0.0,  \"MAX\": 1.0  }\n  ]\n}*/\n\n// THORNHEART - ISF port of the Shadertoy/WebGL2 version\n// (GLSL ES 1.00: gl_FragColor, TIME/RENDERSIZE).\n\n#define PI  3.14159265359\n#define TAU 6.28318530718\n\n// How much there is room for. Both tables are filled once per pixel and then\n// read, never recomputed, by every one of the ~150 distance queries a ray makes.\n#define NN  5           // clusters of thorns\n#define NPN 4           // thorns in each of them\n#define NT  (NN * NPN)\n#define NG  NN            // one piece of green per cluster\n#define NGR 2           // blades on each: the first NGRG green, the rest blue\n#define NGRG 1\n#define NBD 7           // beads of metal and glass growing on the surfaces\n#define NORB 5          // small spheres orbiting through the thing\n\n// A thorn is ONE vec4: the angle it leaves at, its lean out of the cluster\n// plane, its length, and the phase of the things travelling along it. Base\n// radius, tip radius, blade flatness and blade facing are all DERIVED from\n// those four. Not tidiness: four vec4s a thorn is 800 floats of dynamically\n// indexed table per thread, past what the register file holds, and every loop\n// touching it becomes a loop over spilled memory.\nvec4 gTh[NT];\n// Three vec4s per cluster: root + how far the cluster opens out of its plane,\n// plane normal + the radius of the sphere that holds every thorn in it, and one\n// axis in the plane. The second axis is the cross product of those two.\n// Four vec4s per cluster: root and how far it opens out of plane, plane normal\n// and the radius that holds everything in it, one axis in the plane and the\n// tighter radius that holds everything except the long blades, and finally how\n// far out of its own plane the cluster reaches at all.\nvec4 gCl[NN * 4];\n// A piece of green: position + radius, and a negative radius means a smooth orb\n// rather than a clump of berries. How hard it glows and how coarse its lobes\n// are both follow from how big it is, which is true of real things too.\nvec4 gGr[NG];\n// A bead: where it is, and how big - a NEGATIVE radius meaning glass rather\n// than metal, so the material costs no storage of its own.\nvec4 gBl[NBD];\nvec4 gOrb[NORB];   // small spheres on turning orbits: centre + radius\nvec3 gCore;     // where the white light sits\nfloat gLS;      // the length dial, folded once\nfloat gT;       // time, and every moving thing in here is a function of it\nfloat gBr;      // one global breath, worked out once rather than per query\n// The twitch: mostly 1, briefly more. It scales AMPLITUDES and nothing else.\n// It used to multiply gT inside the sines as well, to make the sculpture move\n// faster while it flinched, and that is a trap: sin(gT * r * gTw) jumps by\n// gT * r * (gTw - 1) when the twitch fires, and that jump is proportional to\n// how long the shader has been running. It read as a flinch for the first few\n// seconds and as a violent snap a minute in. A rate can only be modulated by\n// integrating it into the phase; multiplying the clock instead moves every\n// moment that has already happened.\nfloat gTw;\n// ---------------------------------------------------------------- hashing\n// Anything that decides SHAPE goes through ihash: integer arithmetic carried\n// in floats, every intermediate a whole number below 2^24, so float32 is exact\n// and two implementations of this shader cannot disagree about what got built.\n// 4091 is prime and 3 does not divide 4090, so n -> n^3 permutes the residues.\n#define IM 4091.0\nfloat ihash(float n){\n  n = mod(n, IM);\n  n = mod(n * 3192.0 + 3096.0, IM); n = mod(mod(n * n, IM) * n, IM);\n  n = mod(n *  745.0 +  531.0, IM); n = mod(mod(n * n, IM) * n, IM);\n  n = mod(n * 1109.0 + 3358.0, IM); n = mod(mod(n * n, IM) * n, IM);\n  return n * (1.0 / IM);\n}\nvec3 ihash3(float n){ return vec3(ihash(n), ihash(n + 1949.0), ihash(n + 3803.0)); }\nvec2 ihash2(float n){\n  float v = floor(ihash(n) * IM);\n  return vec2(mod(v, 64.0), floor(v / 64.0)) * vec2(1.0 / 63.0, 1.0 / 63.0);\n}\n\n// Dave Hoskins' hashes, for grain and for the lace holes. Both only ever see\n// small arguments, which is the condition under which they stay portable.\nfloat h11(float p){ p = fract(p * 0.1031); p *= p + 33.33; p *= p + p; return fract(p); }\n// ------------------------------------------------------------- primitives\nfloat smin(float a, float b, float k){\n  float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);\n  return mix(b, a, h) - k * h * (1.0 - h);\n}\n\n// Rotate about X then about Y. Applied to a WHOLE frame it keeps that frame\n// orthonormal, which is the entire point of it: a frame that is built once and\n// then rotated is continuous, where a frame rebuilt every time from an animated\n// normal is not. Rebuilding needs a reference axis, the reference axis has to be\n// chosen by a conditional, and the conditional flips.\nvec3 rotXY(vec3 v, float ca, float sa, float cb, float sb){\n  v = vec3(v.x, v.y * ca - v.z * sa, v.y * sa + v.z * ca);\n  return vec3(v.x * cb + v.z * sb, v.y, -v.x * sb + v.z * cb);\n}\n\n// A capsule. It is here as a BOUND, not as geometry: a thorn is two cones\n// inside a capsule of its base radius, so the distance to the capsule is\n// always a little less than the distance to the thorn - which is exactly what\n// a lower bound has to be - and it costs half of one cone to find out. The\n// sphere it replaces had to be as big as the thorn is long, and a needle\n// occupies almost none of that.\nfloat sdCapsule(vec3 p, vec3 a, vec3 ba, float r){\n  vec3 pa = p - a;\n  return length(pa - ba * clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0)) - r;\n}\n\n// A swept sphere whose radius runs from r1 to r2 along the segment. iq's exact\n// round cone was thirty lines with two early returns and it appeared five times\n// inside map(), which is thirty-five branchy copies once map is inlined - and\n// that, not any loop, is what stopped the shader linking. This is twelve\n// instructions and no branches. It is not exact: the error is the slope of the\n// taper, under four percent for the sharpest thorn here, and the 0.96 covers\n// every taper in the shader with room left over.\nfloat sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2){\n  vec3  ba = b - a;\n  vec3  pa = p - a;\n  float h  = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n  return (length(pa - ba * h) - mix(r1, r2, h)) * 0.96;\n}\n\n// The four numbers a thorn does not have to store.\nfloat thornR1(float L){ return 0.008 + 0.031 * L; }\nfloat thornR2(float Ln){ return mix(0.016, 0.0062, uSharp) * (0.27 + 0.83 * Ln); }\nfloat thornFlat(float Ln){ return mix(1.0, mix(1.0, 0.24, uBlade),\n           smoothstep(0.85, 1.38, Ln)); }\n// Bigger pieces of green glow harder and have coarser lobes, which is one less\n// thing to carry and is how it works outside a shader too.\nfloat greenGlow(float R){ return 0.45 + 5.0 * R; }\n\n// One rib of the array: a plate through the spine at segment k, rolled further\n// round than the last one and bowed along the spine, so the array reads as a\n// swept fan rather than a stack of spokes. The fixed angular lattice is what\n// makes an array possible at all - segment k is the same segment for every ray\n// that looks at it, so the ribs do not slide as the camera moves.\nfloat ribAt(vec3 p, vec3 A0, vec3 tg, float k, float rad){\n  // Against the outward radial, not against world up. The branch tangent runs\n  // near vertical wherever the curve climbs hardest - dY/da reaches about three\n  // where the radius is under a half - and a cross product against world up\n  // goes degenerate there, which makes the plate spin wildly for no reason. The\n  // radial is never small (the crown's radius never drops below about 0.21) and\n  // is close to perpendicular to the tangent by construction.\n  vec3  e1 = normalize(cross(tg, vec3(A0.x, 0.0, A0.z)) + vec3(0.0, 0.001, 0.0));\n  float rr = k * 0.85 + gT * 0.22 * uMotion;\n  vec3  ax = e1 * cos(rr) + cross(tg, e1) * sin(rr);\n  float rl = rad * (3.1 + 1.2 * sin(k * 1.3)) * uRibs;\n  vec3  q  = p - A0;\n  q -= tg * (0.55 * dot(q, ax) * dot(q, ax) / max(rl, 1e-4));\n  q += tg * dot(q, tg) * (1.0 / 0.55 - 1.0);          // flatten it into a plate\n  // The tip was rad * 0.22, which is under a pixel across once the plate is\n  // half the sculpture away, and a stack of them fringed into hairlines.\n  return sdRoundCone(q, ax * (-rl), ax * rl, rad * 0.75, rad * 0.36) * 0.55 * 0.55;\n}\n\n// A RING: a band encircling the spine, its major radius the branch's OWN\n// radius at that angle, so it stays snug wherever the branch swells. Folding\n// the ANGLE instead - the first attempt - gives radial bars, which is a cog.\n// Five to a station for one query: the AXIAL coordinate is folded onto a\n// lattice and the index clamped, so the fold cannot tile the whole curve.\n//\n// Every dimension carries the envelope, corner radius included. That is the\n// difference between fading and popping: a fixed corner radius is a smallest\n// possible size, and a thing with a smallest possible size can only vanish. At\n// env = 0 this is a circle of no thickness lying on the surface: invisible.\nfloat ringAt(vec3 p, vec3 A0, vec3 tg, float k, float br){\n  float ph  = fract(k * 0.2137 + gT * 0.085 * uMotion);\n  float env = smoothstep(0.0, 0.20, ph) * (1.0 - smoothstep(0.34, 0.62, ph));\n  if (env < 0.0015) return 1e9;\n  vec3  q  = p - A0;\n  float ax = dot(q, tg);\n  float rr = length(q - tg * ax);\n  float pit = 0.032;\n  float fi  = clamp(floor(ax / pit + 0.5), -2.0, 2.0);\n  float az  = ax - pit * fi;\n  // The array tapers at BOTH ends - the outer rings smaller than the middle -\n  // and the profile travels, so scale runs from one end of the array to the\n  // other instead of the whole stack breathing at once. A function of the fold\n  // index, which the fold has already worked out, so it costs two operations.\n  // It never reaches zero, so it cannot pop.\n  float tap = (1.0 - 0.17 * fi * fi)\n   * (0.52 + 0.48 * sin(gT * 1.05 * uMotion - fi * 1.35 + k * 0.7));\n  float w = (0.013 + 0.019 * fract(k * 0.37)) * env * uRibs * tap;  // off the spine\n  float h = (0.007 + 0.011 * fract(k * 0.61)) * env * uRibs * tap;  // along it\n  vec2  e = abs(vec2(rr - (br + w * 0.30), az)) - vec2(w * 0.62, h * 0.62);\n  float d = length(max(e, 0.0)) + min(max(e.x, e.y), 0.0) - 0.34 * min(w, h);\n  return d * 0.70;   // the axial fold is not conservative across a cell edge\n}\n\n// A MOUTH round the spine: a ring of canine teeth with a gum ridge under them.\n// The angular fold that made a cog out of a chrome ring is exactly right here,\n// because teeth ARE radial - twelve of them cost one cone, and the gum under\n// them costs one torus.\n//\n// The array moves. The ring turns, and each tooth pushes out and draws back on\n// a wave keyed to its OWN fold index, so the movement runs round the mouth\n// instead of every tooth pumping at once. Both the teeth and the gum scale\n// from the same envelope, all the way to zero, so a mouth closes into the\n// branch rather than switching off.\nvec2 toothAt(vec3 p, vec3 A0, vec3 tg, float k, float br){\n  float ph  = fract(k * 0.1731 + gT * 0.055 * uMotion + 0.37);\n  float env = smoothstep(0.0, 0.18, ph) * (1.0 - smoothstep(0.28, 0.52, ph));\n  if (env < 0.0015) return vec2(1e9, 12.0);\n  vec3  e1 = normalize(cross(tg, vec3(A0.x, 0.0, A0.z)) + vec3(0.0, 0.001, 0.0));\n  vec3  q  = p - A0;\n  float ax = dot(q, tg);\n  vec2  pl = vec2(dot(q, e1), dot(q, cross(tg, e1)));\n  float rr = length(pl);\n  // the gum: a fleshy ridge sitting on the branch, teeth rooted in it\n  float gw = (0.026 + 0.010 * fract(k * 0.29)) * env * uTeeth;\n  vec2  ge = abs(vec2(rr - (br + gw * 0.30), ax)) - vec2(gw * 0.52, gw * 0.80);\n  float dg = (length(max(ge, 0.0)) + min(max(ge.x, ge.y), 0.0) - 0.30 * gw) * 0.80;\n  float cell = TAU / 12.0;\n  float an   = atan(pl.y, pl.x + 1e-6) + gT * 0.20 * uMotion;\n  float ki   = floor(an / cell + 0.5);\n  float a3   = an - cell * ki;\n  vec2  pr   = vec2(cos(a3), sin(a3)) * rr;\n  float ex   = 0.55 + 0.45 * sin(gT * 1.7 * uMotion + ki * 0.9 + k * 0.5);\n  float tl   = (0.052 + 0.024 * fract(k * 0.41)) * env * ex * uTeeth;\n  float rb2  = (0.0115 + 0.0045 * fract(k * 0.77)) * env * uTeeth;\n  // canted along the spine, and coming to a real point - a canine, not a peg\n  float dt = sdRoundCone(vec3(pr.x, pr.y, ax),\n       vec3(br + gw * 0.10, 0.0, 0.0),\n       vec3(br + gw * 0.10 + tl * 0.92, 0.0, tl * 0.34),\n       rb2, rb2 * 0.09) * 0.60;\n  return dt < dg ? vec2(dt, 12.0) : vec2(dg, 11.0);\n}\n\n// A CRESCENT: a disc with a smaller disc bitten out of it, extruded into a\n// thin plate. They are arrayed along the branch - which is a closed curve,\n// so the array is a loop by construction - and scaled by a wave that\n// TRAVELS along it, so they open and close in sequence rather than all\n// together. Centred on the branch surface, so the horns are always buried\n// in it however big the crescent is: nothing here is ever detached.\n//\n// Every dimension comes off one factor, so a crescent shuts to nothing\n// instead of switching off.\nfloat crescAt(vec3 p, vec3 A0, vec3 tg, float k, float br){\n  float w = 0.5 + 0.5 * sin(k * 0.80 - gT * 1.05 * uMotion);\n  float R = (0.052 + 0.030 * fract(k * 0.53)) * w * w * uRibs;\n  if (R < 0.0012) return 1e9;\n  vec3  e1 = normalize(cross(tg, vec3(0.0, 1.0, 0.02)) + vec3(0.001, 0.0, 0.0));\n  vec3  q  = p - A0 - e1 * br;\n  vec2  pl = vec2(dot(q, e1), dot(q, tg));\n  float d1 = length(pl) - R;\n  float d2 = length(pl + vec2(R * 0.45, 0.0)) - R * 0.82;\n  return max(max(d1, -d2), abs(dot(q, cross(tg, e1))) - R * 0.16) * 0.80;\n}\n\n// ==================================================================== crown\n// One closed curve, written as a radius and a height over the angle. Written\n// that way the nearest piece of it to a point is the piece at the point's OWN\n// angle, so five segments centred there is the entire search and there is no\n// table of the curve to carry - which is what makes room for the thorn table.\n#define DA (TAU / 26.0)\n\nfloat crownR(float a){\n  return 0.46 + gBr + 0.11 * cos(3.0 * a + 0.9 + gT * 0.18 * uMotion)\n    + uCrumple * (0.21 * sin(7.0 * a + 1.3 - gT * 0.29 * uMotion));\n}\nfloat crownY(float a){\n  return 0.46 * sin(2.0 * a + 1.05 + gT * 0.14 * uMotion)\n    + 0.24 * cos(3.0 * a + 0.4 - gT * 0.22 * uMotion)\n    + uCrumple * (0.36 * sin(6.0 * a + 2.6 + gT * 0.33 * uMotion));\n}\nvec3 crownPt(float a){\n  float r = crownR(a);\n  return vec3(cos(a) * r, crownY(a), sin(a) * r);\n}\n// Thick at the nodes, thin between them, so it reads as branch rather than hose.\n// the thinnest the branch ever gets - the constant the capsule chain uses\n#define RBASE (uCrown * 0.30)\nfloat crownRad(float a){\n  // ...and a swelling that runs along the branch like something being\n  // swallowed. Free: this function is evaluated once per strand, not per\n  // segment, so the wave costs a single sine for the whole length of it.\n  return uCrown * (0.30 + 1.55 * abs(sin(1.5 * a + 0.6 + gT * 0.12 * uMotion))\n     + 0.60 * uMotion * pow(0.5 + 0.5 * sin(a * 4.5 - gT * 1.7), 10.0));\n}\n\n// The second strand. Same harmonics at a different phase and two thirds the\n// size, then tipped into another plane - which is what puts a branch across\n// the middle of the wreath instead of leaving a clean hole there.\nvec3 toStrand2(vec3 p){\n  const float cx = 0.4695, sx = 0.8829, cy = 0.7986, sy = 0.6018;\n  p = vec3(p.x, p.y * cx - p.z * sx, p.y * sx + p.z * cx);\n  return vec3(p.x * cy + p.z * sy, p.y, -p.x * sy + p.z * cy);\n}\nvec3 fromStrand2(vec3 p){\n  const float cx = 0.4695, sx = 0.8829, cy = 0.7986, sy = 0.6018;\n  p = vec3(p.x * cy - p.z * sy, p.y, p.x * sy + p.z * cy);\n  return vec3(p.x, p.y * cx + p.z * sx, -p.y * sx + p.z * cx);\n}\nfloat crown2R(float a){ return 0.66 * crownR(a + 2.1); }\nvec3 crown2Pt(float a){\n  float r = crown2R(a);\n  return vec3(cos(a) * r, 0.72 * crownY(a + 1.3), sin(a) * r);\n}\nfloat crown2Rad(float a){ return 0.72 * crownRad(a + 2.1); }\n\n// ===================================================================== build\n// Everything the sculpture is made of, decided once per pixel and then read.\n// Thorns come in clusters: nine nodes on the curve, and every thorn belongs to\n// one of them. Scattered evenly along the branch they read as fur; bunched at\n// nodes they read as thorns.\nvoid build(){\n  gCore = vec3(0.10, 0.16, -0.04)\n  + 0.17 * uMotion * vec3(sin(gT * 0.66), sin(gT * 0.41 + 1.7), cos(gT * 0.37));\n  gLS   = 0.45 + 1.15 * uLength;\n\n  for (int c = 0; c < NN; c++){\n float fc = float(c);\n float an = ihash(fc * 37.0 + 5.0) * TAU;                 // where the cluster is\n // A whole cluster sits on one strand or the other, which keeps the sphere\n // round it tight.\n float on2 = (ihash(fc * 71.0 + 13.0) < 0.45 * uTangle) ? 1.0 : 0.0;\n vec3  C   = mix(crownPt(an), fromStrand2(crown2Pt(an)), on2);\n // Every thorn in a cluster fans out of it around one plane, which is what\n // makes the crossed rosettes - a handful of blades meeting at a point and\n // spreading like a caltrop, instead of a coat of fur all pointing outward.\n // Some clusters keep to their plane, some open out into a burst.\n // The plane a rosette fans out in precesses, and the rosette turns in it -\n // slowly, and at its own rate, so the clusters drift out of step.\n float spin = (ihash(fc * 83.0 + 3.0) - 0.5) * 1.85 * uSway * uMotion;\n // The frame is built ONCE from the hash, time nowhere near it, and then\n // the whole frame is rotated. It used to be the other way round - animate\n // the normal, rebuild the axes from it - and rebuilding needs a reference\n // axis picked by a conditional (abs(Nn.y) < 0.9 ? up : right), so the\n // instant the animated normal crossed that line P1 jumped elsewhere and\n // took every thorn in the rosette with it: EIGHTEEN times in two minutes,\n // on two of the five clusters. No continuous frame field on a sphere\n // exists (hairy ball), so the answer is not a better conditional - it is\n // to never rebuild. The conditional below runs on a STATIC vector.\n vec3  n0 = normalize(ihash3(fc * 53.0 + 9.0) * 2.0 - 1.0 + vec3(0.0, 0.001, 0.0));\n vec3  t0 = normalize(cross(n0, abs(n0.y) < 0.9 ? vec3(0.0, 1.0, 0.0)\n               : vec3(1.0, 0.0, 0.0)));\n float sw = 0.42 * uSway * uMotion;\n float aa = sw * sin(gT * 0.31 + fc), bb = sw * cos(gT * 0.27 + fc * 1.7);\n float ca = cos(aa), sa = sin(aa), cb = cos(bb), sb = sin(bb);\n vec3  Nn = rotXY(n0, ca, sa, cb, sb);\n vec3  P1 = rotXY(t0, ca, sa, cb, sb);\n float op = mix(0.65, 2.60, ihash(fc * 61.0 + 7.0));\n float bound = 0.06, bshort = 0.06, hmax = 0.04;\n\n for (int j = 0; j < NPN; j++){\n   float fi = fc * 16.0 + float(j);\n   gTh[c * NPN + j] = vec4(0.0);                          // dead: length 0\n   if (ihash(fi * 3.0 + 1.0) > uThorns) continue;\n\n   float sp = (float(NPN - 1 - j) + ihash(fi * 29.0 + 5.0)) / float(NPN);\n   float th = ihash(fi * 43.0 + 3.0) * TAU + gT * spin;   // round the rosette\n   vec2  h1 = ihash2(fi * 23.0 + 7.0);\n   // A thorn is a dagger, not an antenna: the base widens with the length,\n   // so a long one is a broad blade coming to a point, not a wire.\n   // Thorns breathe in and out of the cluster, each on its own phase.\n   float Lb = 0.15 + 1.33 * pow(sp, 2.3);          // the length it rests at\n   float Ln = Lb * (1.0 + 0.20 * uMotion * gTw * sin(gT * 1.15 + th * 2.7));\n   // Which thorns carry travellers, and where in their journey they start.\n   // The gate is on the RESTING length, not the breathing one. Testing the\n   // breathing length meant any thorn sitting near the threshold switched\n   // its travellers on and off once a breath - a whole feature blinking in\n   // and out, and one of the things that was popping.\n   float trav = (h1.x < uTravel && Lb > 0.34) ? ihash(fi * 89.0 + 5.0) : -1.0;\n\n   // ...and lean further out of the plane and back again as they do it.\n   float lean = h1.y - 0.5 + 0.27 * uMotion * gTw * sin(gT * 0.95 + th * 1.9);\n   gTh[c * NPN + j] = vec4(th, lean, Ln, trav);\n   float reach = Ln * gLS + thornR1(Ln * gLS) * 1.1 + 0.16;\n   bound = max(bound, reach);\n   if (j >= 3) bshort = max(bshort, reach);\n   // A rosette is a fan, and a fan is much flatter than the sphere around\n   // it. How far out of its own plane a thorn gets is decided entirely by\n   // its lean and how far the cluster opens - no trigonometry, no axis:\n   // the out-of-plane part of a unit direction built as\n   // P1*c + P2*s + Nn*k is just k / sqrt(1 + k*k).\n   float k = abs(lean) * op;\n   // the limbs stick out of the cluster plane as well, so the slab has to\n   // hold them - faded on the same curve map() fades them in on\n   hmax = max(hmax, abs(lean) * 0.13 + reach * min(k, 1.0)\n      + thornR1(Ln * gLS) * 1.35 + 0.03\n      + smoothstep(0.38, 0.58, Ln * gLS) * Ln * gLS * 0.26);\n   // A cluster whose sphere got this big would make the hierarchy in map()\n   // pointless. The check also stops the compiler inlining the whole table.\n   if (bound > 4.0) break;\n }\n {\n   float fg = fc * 4.0;\n   gGr[c] = vec4(0.0);\n   if (ihash(fg * 5.0 + 7.0) <= uGreen){\n   vec3  hh = ihash3(fg * 31.0 + 17.0);\n   vec3  P  = C + (P1 * (hh.x - 0.5) + cross(Nn, P1) * (hh.y - 0.5)\n     + Nn * (hh.z - 0.5)) * 0.36\n      + 0.085 * uMotion * vec3(sin(gT * 0.75 + fg), sin(gT * 0.50 + fg * 2.1),\n          cos(gT * 0.70 + fg * 1.3));\n   float orb = ihash(fg * 7.0 + 13.0) < 0.20 ? -1.0 : 1.0;\n   float rad = (orb < 0.0 ? 0.030 + 0.070 * uOrbs * (0.45 + hh.z)\n        : 0.046 + 0.078 * hh.z)\n    * (1.0 + 0.24 * uMotion * sin(gT * 1.35 + fg * 2.4));\n   gGr[c] = vec4(P, rad * orb);\n   float greach = rad * (orb > 0.0 ? 4.95 : 1.4);\n   bound = max(bound, length(P - C) + greach);\n   hmax  = max(hmax,  abs(dot(P - C, Nn)) + greach);\n   }\n }\n if (uPods > 0.01){\n   bound = max(bound, 0.75);      // the pod hangs this far out on its stalk\n   hmax  = max(hmax,  0.60);      // and it swings out of plane as well\n }\n gCl[c * 4 + 0] = vec4(C, op);\n gCl[c * 4 + 1] = vec4(Nn, bound);\n gCl[c * 4 + 2] = vec4(P1, bshort);\n gCl[c * 4 + 3] = vec4(hmax, 0.0, 0.0, 0.0);\n  }\n\n  // ---- beads of metal and glass, growing on the surfaces -------------------\n  // Each bead lives exactly one GENERATION, the generation number being the\n  // whole floor of its own clock: when one ends the number ticks, every hash\n  // reseeds, and the next arrives somewhere else rather than pulsing in place.\n  // The move is never seen, because the radius is zero at that instant. The\n  // envelope is sin(pi u) to a low power - zero at both ends with no flat spot\n  // and no corner, which is what reads as GROWING rather than switching on.\n  // Placed a radius off the centreline, so it sits half-buried in the branch.\n  for (int b = 0; b < NBD; b++){\n float fb  = float(b);\n float clk = gT * (0.085 + 0.021 * fb) * uMotion + fb * 0.618;\n float u   = fract(clk);\n float gen = floor(clk) * 4.0 + fb * 71.0;\n float a   = ihash(gen + 29.0) * TAU;\n vec3  dir = normalize(ihash3(gen + 113.0) * 2.0 - 1.0 + vec3(0.0, 0.0, 0.001));\n vec3  P   = ihash(gen + 41.0) < 0.42\n     ? fromStrand2(crown2Pt(a) + dir * (crown2Rad(a) * 0.8))\n     : crownPt(a) + dir * (crownRad(a) * 0.8);\n float rad = (0.034 + 0.082 * ihash(gen + 7.0))\n     * pow(max(sin(PI * u), 0.0), 0.62);\n gBl[b] = vec4(P, ihash(gen + 3.0) < 0.5 ? rad : -rad);\n  }\n\n  // ---- small spheres orbiting through the sculpture -----------------------\n  // Each rides a circle whose PLANE turns, on two periods that do not divide\n  // into the orbit's own - so the path is a slow rosette rather than a ring\n  // and it threads a different part of the thing every time round instead of\n  // retracing the same gap. rotXY turns the whole orbit rather than the\n  // sphere's position within it, which is why the path stays a circle and\n  // never develops a corner.\n  for (int b = 0; b < NORB; b++){\n float fb = float(b);\n float a  = gT * (0.30 + 0.17 * ihash(fb * 17.0 + 3.0)) * uMotion + fb * 2.39996;\n float R  = 0.34 + 0.62 * ihash(fb * 29.0 + 7.0);\n float t1 = gT * 0.11 * uMotion + fb * 1.9;\n float t2 = gT * 0.07 * uMotion + fb * 2.7;\n gOrb[b] = vec4(rotXY(vec3(cos(a) * R, 0.0, sin(a) * R),\n       cos(t1), sin(t1), cos(t2), sin(t2)),\n       0.020 + 0.019 * ihash(fb * 41.0 + 11.0));\n  }\n}\n\n// ======================================================================= map\n// x = distance, y = material:\n//   0 thorn        1 green clump  2 green orb   4 the light   5 branch\n//   6 traveller    7 grass        8 blue tendril\n//   9 glass (blades and beads)   10 chrome (ribs, rings and beads)   11 pod\nvec2 map(vec3 p){\n  vec2 res = vec2(1e9, 0.0);\n\n  // ---- the branch, all three strands --------------------------------------\n  // Three segments at the point's own angle. Outside the bounding shell the\n  // distance to that shell is a perfectly good lower bound, and much cheaper.\n  //\n  // Each strand keeps its OWN distance until it is finished. It has to: the\n  // thickness is subtracted from the chain afterwards, so one shared variable\n  // applies every strand's subtraction to the result of the last - which came\n  // out up to three radii too fat.\n  float dC = 1e9, dR = 1e9;\n  vec2  dTe = vec2(1e9, 12.0);\n  float bs = max(abs(length(p.xz) - 0.46) - 0.70, abs(p.y) - 1.47);\n  if (bs < 0.20){\n float ap = atan(p.z, p.x + 1e-5);\n float ai = floor(ap / DA) - 1.0;\n float u0 = ai * DA;\n float d1 = 1e9;\n // hoisted: the rings need the branch's radius to sit snug against it, and\n // the thickness subtraction below needs the same number\n float brad = crownRad(ap);\n vec3  A0 = crownPt(u0);\n for (int i = 0; i < 3; i++){\n   float u1 = u0 + DA;\n   vec3  A1 = crownPt(u1);\n   d1 = min(d1, sdCapsule(p, A0, A1 - A0, RBASE));\n   if (uRibs > 0.01){\n  vec3  tg = normalize(A1 - A0);\n  float kk = ai + float(i);\n  dR = min(dR, ribAt (p, A0, tg, kk, uCrown * 2.0));\n  dR = min(dR, ringAt (p, A0, tg, kk, brad));\n  dR = min(dR, crescAt(p, A0, tg, kk, brad));\n   }\n   // Outside the uRibs gate: the mouths are not metal and should not\n   // disappear when someone turns the chrome off.\n   if (uTeeth > 0.01){\n  vec2 tm = toothAt(p, A0, normalize(A1 - A0), ai + float(i), brad);\n  if (tm.x < dTe.x) dTe = tm;\n   }\n   u0 = u1; A0 = A1;\n   if (d1 < -0.05) break;\n }\n dC = (d1 - (brad - RBASE)) * 0.86;\n  } else dC = bs;\n\n  if (uTangle > 0.02){\n vec3  q  = toStrand2(p);\n float b2 = max(abs(length(q.xz) - 0.30) - 0.52, abs(q.y) - 1.11);\n if (b2 < 0.20){\n   float ap = atan(q.z, q.x + 1e-5);\n   float u0 = (floor(ap / DA) - 1.0) * DA;\n   float rb = RBASE * 0.72 * uTangle;\n   float d2 = 1e9;\n   vec3  A0 = crown2Pt(u0);\n   for (int i = 0; i < 3; i++){\n  float u1 = u0 + DA;\n  vec3  A1 = crown2Pt(u1);\n  d2 = min(d2, sdCapsule(q, A0, A1 - A0, rb));\n  u0 = u1; A0 = A1;\n  if (d2 < -0.05) break;\n   }\n   dC = min(dC, (d2 - (crown2Rad(ap) * uTangle - rb)) * 0.86);\n } else dC = min(dC, b2);\n  }\n\n  // ---- thorns, and the things travelling out along them -------------------\n  float dT = 1e9, dB = 1e9, dTm = 0.0;\n  vec2  gR = vec2(1e9, 1.0);\n  for (int c = 0; c < NN; c++){\n vec4 K0 = gCl[c * 4 + 0];\n vec4 K1 = gCl[c * 4 + 1];\n // The cluster first. Outside its sphere the distance to that sphere is a\n // perfectly good lower bound, and the margin is what keeps a bound from\n // ever being mistaken for a hit: it is only returned when comfortably\n // positive. One length() here buys the rejection of six thorns, and the\n // same length() is then the bound test for each of them.\n vec3  rel = p - K0.xyz;\n float lp  = length(rel);\n // The sphere and the slab are both lower bounds on the distance to\n // anything in this cluster, so the larger of the two is a lower bound as\n // well - and a much better one, because a rosette is flat and a sphere\n // round it is mostly empty.\n float cb  = max(lp - K1.w, abs(dot(rel, K1.xyz)) - gCl[c * 4 + 3].x);\n if (cb > 0.02){ dT = min(dT, cb); continue; }\n vec4 K2 = gCl[c * 4 + 2];\n vec3 Nn = K1.xyz, P1 = K2.xyz;\n vec3 P2 = cross(Nn, P1);\n\n for (int j = 0; j < NPN; j++){\n   // Past the long blades, the tight radius decides whether any of the rest\n   // of this cluster can be reached at all.\n   if (j == 3 && lp - K2.w > 0.02){ dT = min(dT, lp - K2.w); break; }\n   vec4  Th = gTh[c * NPN + j];\n   float Ln = Th.z;\n   if (Ln <= 0.0) continue;\n   float L  = Ln * gLS;\n   float r1 = thornR1(L);\n   if (lp - (L + r1 * 1.1 + 0.16) > 0.02){\n  dT = min(dT, lp - (L + r1 * 1.1 + 0.16));\n  break;\n   }\n   // Three angles are needed here - the one the thorn leaves at, the one it\n   // leaves FROM, and the one its blade faces - and all three are small\n   // integer multiples of the first, so the double and triple angle\n   // identities give the other two for a few multiplies. Two sines a thorn\n   // instead of six. The lean out of the plane is NOT derived from the\n   // angle: deriving that as well correlated every thorn in a cluster and\n   // flattened the rosettes back into fans.\n   float ct = cos(Th.x), st = sin(Th.x);\n   float s3 = st * (3.0 - 4.0 * st * st);             // sin 3t\n   float c3 = ct * (4.0 * ct * ct - 3.0);             // cos 3t\n   float lean = Th.y;\n   vec3  D  = normalize(P1 * ct + P2 * st + Nn * lean * K0.w);\n   // Spread the roots over a small disc in the cluster plane, on the double\n   // angle - decorrelated from the one the thorn points in, and free.\n   vec3  A  = K0.xyz + Nn * (lean * 0.13)\n      + (P1 * (2.0 * ct * ct - 1.0) + P2 * (2.0 * ct * st))\n     * (0.16 * abs(lean));\n   // Limbs reach out sideways from the shaft, so the capsule that bounds\n   // this thorn has to be fat enough to hold them or they get culled and\n   // never drawn. Only the long ones grow limbs, so only the long ones pay\n   // for the fatter bound - which is the whole reason the test is on L.\n   // Faded in with length, never switched on at a threshold: L BREATHES, so\n   // a hard test here would grow and delete every limb on a thorn whose\n   // length happens to sit near the line, once a breath.\n   float lmF = smoothstep(0.38, 0.58, L);\n   float lmR = L * 0.26 * lmF;\n   float bd = sdCapsule(p, A, D * L, r1 * 1.06 + 0.008 + lmR);\n   if (bd > 0.02){ dT = min(dT, bd); continue; }\n   vec3  q  = p - A;\n   // The blade faces a direction of its own - the triple angle, which is\n   // already paid for above.\n   // From the cluster's own normal rather than a conditional on D.y. D\n   // moves, so a reference axis picked by testing it flips - rolling the\n   // blade, and every limb hanging off it, a quarter turn in one frame. D\n   // can never be near Nn: its in-plane part is a unit vector and its\n   // out-of-plane part is at most op, so the two are always at least twenty\n   // degrees apart and this cross is always well conditioned.\n   vec3  e0 = normalize(cross(D, Nn));\n   vec3  E  = e0 * c3 + cross(D, e0) * s3;\n   // Squash one axis to make a blade: stretch the coordinate, shrink the\n   // answer by the same factor, and the field stays safe to march. It does\n   // mean the distance comes back about four times short near a blade, which\n   // a sphere tracer pays for in steps - but clipping the cone with a slab\n   // instead, which keeps the field full size, measured no faster here and\n   // gave a blunter blade, so the honest version wins.\n   float fl = thornFlat(Ln);\n   vec3  qq = q + E * dot(q, E) * (1.0 / fl - 1.0);\n   float dcn = sdRoundCone(qq, vec3(0.0), D * L, r1, thornR2(Ln)) * fl;\n   // Which material a thorn shows costs a compare, not a primitive: the\n   // black collar is simply the part of it near the root, and whether the\n   // whole blade is glass falls out of the angle it left at - a number the\n   // table is already carrying.\n   // The blades of a rosette all leave from nearly one point, so they cross\n   // each other constantly - this is the join that most needed softening.\n   float gls = fract(float(c * NPN + j) * 0.61803) < uGlass ? 9.0 : -1.0;\n   if (dcn < dT){\n  dTm = gls >= 0.0 ? gls\n   : (1.0 - smoothstep(0.05 * L, 0.52 * L, dot(q, D))) * 0.45;\n   }\n   // a small fillet between the blades of a rosette - enough to soften the\n   // crossings, not enough to weld the cluster into a knob\n   dT = smin(dT, dcn, 0.010);\n\n   // ---- limbs off the long parts -------------------------------------\n   // One cone, however many limbs: the point's own distance along the shaft\n   // says which limb is nearest, so a limb is found by INDEX, not by search.\n   // Golden-angle directions never stack up; each bends by a parabola in its\n   // own space, which curves a straight cone for eight instructions. The\n   // bend is a fraction of the limb's OWN length - a constant curvature puts\n   // tip displacement up with the SQUARE of length, and the longest limbs\n   // then swing outside the bound above.\n   if (lmF > 0.002){\n  float pit = L * 0.125;\n  float ki  = clamp(floor(dot(q, D) / pit + 0.5), 1.0, 7.0);\n  float la  = ki * 2.39996 + Th.x * 1.7 + gT * 0.42 * uMotion;\n  vec3  ld  = normalize(e0 * cos(la) + cross(D, e0) * sin(la) + D * 0.62);\n  // ...and they do not hold a length. Because the pulse is a function of\n  // the limb's INDEX as well as time, it runs along the thorn as a wave\n  // rather than beating the whole fan at once - the difference between\n  // something breathing and something flashing. Never reaches zero, so\n  // the bound above still holds.\n  float ll  = (L - pit * ki) * 0.24 * lmF\n      * (0.58 + 0.42 * sin(gT * 0.9 * uMotion + ki * 1.3 + Th.x * 2.0));\n  vec3  ql  = q - D * (pit * ki);\n  float tl  = clamp(dot(ql, ld), 0.0, ll);\n  float bn  = (0.15 + 0.30 * (0.5 + 0.5 * sin(gT * 1.5 + ki * 2.1 + Th.x)))\n      * (0.35 + 0.65 * uMotion) / max(ll, 0.02);\n  ql -= normalize(cross(ld, D) + vec3(0.0, 0.0, 0.001)) * (bn * tl * tl);\n  float dlb = sdRoundCone(ql, vec3(0.0), ld * ll,\n        r1 * 0.44 * lmF, r1 * 0.11 * lmF)\n      / (1.0 + 2.0 * bn * ll) * 0.62;\n  if (dlb < dT) dTm = gls >= 0.0 ? gls : 0.28;\n  dT = smin(dT, dlb, 0.012);\n\n  // ---- a worm winding round the spine ---------------------------\n  // At axial s the helix is at angle s*twist, so again there is no search\n  // and no curve to carry. Using the point's own s overestimates the\n  // distance by the curve's speed, exactly sqrt(1 + (twist*radius)^2);\n  // divide that back out and the field is safe to march. Its window runs\n  // from -half to L+half rather than 0 to L, so the worm has faded out at\n  // both ends of its cycle and is never seen jumping back to the start.\n  // It rides the traveller channel, so it costs no material of its own.\n  float ws = clamp(dot(q, D), 0.0, L);\n  float wh = 0.26 * L;\n  float wc = fract(Th.x * 0.159 + gT * 0.085 * uMotion) * (L + 2.0 * wh) - wh;\n  float wf = 1.0 - smoothstep(0.0, wh, abs(ws - wc));\n  if (wf > 0.004){\n    // Thinner, and ON the stick. It rode at 1.55x the shaft radius,\n    // which is a helix in the air NEAR a thorn rather than a worm\n    // wrapped round one. Two things have to be followed for it to\n    // touch: the shaft TAPERS, so the ride height tapers with it -\n    // and the long thorns are squashed into blades, so the helix is\n    // squashed by the same factor along the same axis, or it lifts\n    // off the flat faces and sinks into the edges.\n    float rw  = (r1 * 0.24 + 0.0022) * wf\n     * (1.0 + 0.22 * sin(ws * 120.0 + gT * 4.0));   // segmented\n    float rh  = mix(r1, thornR2(Ln), ws / max(L, 1e-3)) * 0.94 + rw * 0.72;\n    float twi = 30.0 / max(L, 0.2);\n    float wa  = ws * twi + gT * 1.1 * uMotion + Th.x * 3.0;\n    vec3  hp  = D * ws + (e0 * cos(wa) + cross(D, e0) * sin(wa)) * rh;\n    hp -= E * dot(hp, E) * (1.0 - fl);\n    dB = smin(dB, (length(q - hp) - rw)\n      * inversesqrt(1.0 + twi * rh * twi * rh), 0.006);\n  }\n   }\n\n   // Nothing under the surface is ever shaded, so once we are well inside\n   // one thorn the rest of the cluster cannot matter.\n   if (dT < -0.06) break;\n\n   // Things travelling out along the thorn. Two of them, half a journey\n   // apart, riding the axis from the base to the point: they swell out of\n   // the shaft as they leave, thin with it as the thorn narrows, and are\n   // gone before they reach the tip rather than falling off it. The whole\n   // of one is a sphere on a line - the position, the radius and the fade\n   // are all functions of one number that runs from 0 to 1.\n   if (Th.w >= 0.0){\n  {\n    float u = fract(Th.w + gT * (0.14 + 0.13 * Th.w) * uMotion);\n    // Long ramps at both ends. A short one reads as a pop: the thing\n    // has to be arriving and leaving for most of its journey, not\n    // switching on near the base and off near the point.\n    float fade = smoothstep(0.0, 0.30, u) * (1.0 - smoothstep(0.52, 0.98, u));\n    if (fade < 0.03) continue;\n    vec3  w  = q - D * (u * L);\n    float sD = dot(w, D);\n    // A teardrop, not a pill: rounder in front than behind, so it reads\n    // as something being carried out along the shaft rather than\n    // threaded onto it.\n    w += D * sD * (1.0 / (sD > 0.0 ? 1.40 : 2.70) - 1.0);\n    float rad = (r1 * (1.0 - 0.55 * u) * 1.05 + 0.009) * fade;\n    // and a skin that is not smooth. The slope this adds is radius times\n    // amplitude times frequency, which at this size is about a tenth, so\n    // dividing the field by 1.12 covers it.\n    float lob = 1.0 + 0.15 * sin(dot(w, E) * 30.0 + gT * 3.1)\n         * sin(sD * 26.0 - gT * 2.3);\n    dB = smin(dB, (length(w) - rad * lob) / 1.12, 0.007);\n  }\n   }\n }\n\n // A pod on every cluster: an ovoid on a short stalk, lobed like something\n // that grew rather than something that was placed, swelling and shrinking\n // on its own clock. It hangs in the cluster's own PLANE, which keeps it\n // inside the slab bound the cluster already carries.\n if (uPods > 0.01){\n   float fc = float(c);\n   float pr = (0.075 + 0.045 * sin(fc * 2.7)) * uPods\n      * (0.82 + 0.18 * sin(gT * 1.1 * uMotion + fc * 2.3));\n   // it swings on its stalk as the thing moves\n   vec3  pd = normalize(P1 + Nn * (0.35 * sin(gT * 0.55 * uMotion + fc * 1.7))\n         + cross(Nn, P1) * (0.30 * sin(gT * 0.7 * uMotion + fc)));\n   vec3  pc = K0.xyz + pd * (0.46 + 0.06 * sin(gT * 0.9 * uMotion + fc * 2.1));\n   float pb = length(p - pc) - pr * 1.9;\n   if (pb > 0.02) gR.x = min(gR.x, pb);\n   else {\n  vec3  pq = (p - pc) / pr;\n  pq.y *= 1.55;                                   // an egg, not a ball\n  float lb = sin(pq.x * 5.0) * sin(pq.y * 3.4) * sin(pq.z * 5.0);\n  float dp = (length(pq) - 1.0 - 0.16 * lb) * pr * 0.62;\n  if (dp < gR.x) gR = vec2(dp, 11.0);\n   }\n   // the stalk it hangs from\n   float st = sdCapsule(p - K0.xyz, vec3(0.0), pd * 0.46, 0.011);\n   if (st < gR.x) gR = vec2(st, 11.0);\n }\n\n {\n   vec4  G = gGr[c];\n   if (G.w != 0.0){\n   float R  = abs(G.w);\n   float bg = length(p - G.xyz) - R * (G.w > 0.0 ? 4.95 : 1.34);\n   if (bg > 0.02){ gR.x = min(gR.x, bg); continue; }\n   if (G.w > 0.0){\n  // A clump of berries is a sphere with a lattice of lobes cut into it -\n  // cheaper than a union of forty spheres and it reads the same.\n  vec3  qg = (p - G.xyz) * (1.55 / R) * vec3(1.0, 1.4, 0.75);\n  float b  = sin(qg.x) * sin(qg.y) * sin(qg.z)\n     + 0.55 * sin(qg.y * 2.3 + qg.x) * sin(qg.z * 1.7)\n     + 0.30 * sin(qg.x * 3.1 + qg.z * 2.0);\n  float d  = (length(p - G.xyz) - R * (1.0 + 0.30 * b)) * 0.34;\n  if (d < gR.x) gR = vec2(d, 1.0);\n   } else {\n  float d = length(p - G.xyz) - R;\n  if (d < gR.x) gR = vec2(d, 2.0);\n   }\n\n   // Grass out of the moss. The tuft needs a frame and a spread, and both\n   // come free: the frame from the clump's own position, and the spread from\n   // the golden angle, which is what a plant would use anyway - consecutive\n   // blades land 137.5 degrees apart and never stack up however many there\n   // are. Only the clumps grow it; the smooth beads are not soil.\n   if (uGrass > 0.01 && G.w > 0.0){\n  vec3  up = normalize(G.xyz + vec3(0.0, 0.001, 0.0));\n  vec3  e1 = normalize(cross(up, vec3(0.0, 1.0, 0.02)) + vec3(0.001, 0.0, 0.0));\n  vec3  e2 = cross(up, e1);\n  for (int b = 0; b < NGR; b++){\n    float fb = float(b);\n    float a  = fb * 2.39996 + G.x * 7.0 + G.y * 5.0;\n    float blue = fb >= float(NGRG) ? 1.0 : 0.0;\n    float ln = R * mix(1.10 + 1.25 * fract(fb * 0.37 + G.z),\n        2.30 + 1.70 * fract(fb * 0.51 + G.x), blue) * uGrass;\n    vec3  dir = normalize(e1 * cos(a) + e2 * sin(a)\n         + up * (1.5 + 1.7 * fract(fb * 0.618 + G.z))\n         + (e1 * sin(gT * 1.5 + fb) + e2 * cos(gT * 1.3 + fb))\n        * (0.16 * uMotion));\n    // A blade of grass is not straight and it does not hold still. Bend\n    // the SPACE the cone is measured in by a parabola along its own axis\n    // and one straight cone comes out curved for eight instructions -\n    // where a second cone would have cost forty. The curvature is a\n    // function of time, so the blade whips; the field has to be divided\n    // by the slope the bend adds, which is twice the curvature times the\n    // length.\n    vec3  bax = normalize(cross(dir, up) + vec3(0.0, 0.0, 0.001));\n    // The blue ones curl harder and wave slower - a different plant.\n    float bnd = mix(1.8 + 2.6 * (0.5 + 0.5 * sin(gT * 2.1 + fb * 1.7 + G.x * 6.0)),\n        2.6 + 2.2 * (0.5 + 0.5 * sin(gT * 1.2 + fb * 2.3 + G.y * 5.0)),\n        blue)\n     * (0.35 + 0.65 * uMotion);\n    vec3  qg2 = p - (G.xyz + dir * (R * 0.7));\n    float tg2 = clamp(dot(qg2, dir), 0.0, ln);\n    qg2 -= bax * (bnd * tg2 * tg2);\n    // Sharper: a thinner base and a tip radius near nothing, so a blade\n    // comes to a point instead of ending in a bead.\n    float d = sdRoundCone(qg2, vec3(0.0), dir * ln,\n        R * mix(0.105, 0.125, blue), 0.0038)\n      / (1.0 + 2.0 * bnd * ln);\n    if (d < gR.x) gR.y = blue > 0.5 ? 8.0 : 7.0;\n    gR.x = smin(gR.x, d, R * 0.32);\n    if (gR.x < -0.03) break;   // inside a blade; also stops the unroll\n  }\n   }\n   }\n }\n  }\n\n  // The thorns are welded to the branch, not stuck on it.\n  res = vec2(smin(dT, dC, 0.075), dT < dC ? dTm : 5.0);\n  // the ribs are welded to the spine, not laid against it\n  if (dR < res.x + 0.03){\n float m = res.x;\n res = vec2(smin(res.x, dR, 0.014), dR < m ? 10.0 : res.y);\n  }\n  // The slack on these gates must be WIDER than the smin radius under it.\n  // smin(a, b, k) still differs from a when b - a is less than k, so a gate\n  // that opens at a narrower margin than its own blend radius makes the field\n  // jump by up to k/4 the instant it opens - a seam that flickers as anything\n  // drifts across it. This one opened at 0.02 with a 0.030 blend.\n  if (gR.x < res.x + 0.032){\n float m = res.x;\n res = vec2(smin(res.x, gR.x, 0.030), gR.x < m ? gR.y : res.y);\n  }\n  // and the pair of them into the sculpture, so a traveller sits IN the shaft\n  // rather than on it\n  if (dB < res.x + 0.018){          // 0.018 > the 0.016 blend, as above\n float m = res.x;\n res = vec2(smin(res.x, dB, 0.016), dB < m ? 6.0 : res.y);\n  }\n\n  if (dTe.x < res.x + 0.026){\n float m = res.x;\n res = vec2(smin(res.x, dTe.x, 0.022), dTe.x < m ? dTe.y : res.y);\n  }\n\n  // ---- the beads ----------------------------------------------------------\n  // A sphere is the cheapest thing in here, which is why there can be five of\n  // them for less than one thorn. Welded a little harder than anything else:\n  // the whole point is that they grew out of the surface.\n  vec2 dS = vec2(1e9, 10.0);\n  for (int b = 0; b < NBD; b++){\n vec4  B = gBl[b];\n float r = abs(B.w);\n if (r < 0.002) continue;\n float d = length(p - B.xyz) - r;\n if (d < dS.x) dS = vec2(d, B.w < 0.0 ? 9.0 : 10.0);\n  }\n  // The orbiting spheres ride the same channel, so they cost no merge of\n  // their own - and they arrive welded rather than clipping through, which\n  // is what stops an intersection looking cheap.\n  for (int b = 0; b < NORB; b++){\n vec4  O = gOrb[b];\n float d = length(p - O.xyz) - O.w;\n if (d < dS.x) dS = vec2(d, 2.0);\n  }\n  if (dS.x < res.x + 0.03){\n float m = res.x;\n res = vec2(smin(res.x, dS.x, 0.026), dS.x < m ? dS.y : res.y);\n  }\n\n  // ---- the light itself ---------------------------------------------------\n  float dl = sdRoundCone(p, gCore + vec3(0.0, 0.10, 0.0),\n       gCore - vec3(0.0, 0.10, 0.0), 0.020, 0.011);\n  if (dl < res.x) res = vec2(dl, 4.0);\n  return res;\n}\n\nvec3 normalOf(vec3 p, float d0){\n  const vec2 e = vec2(0.0009, 0.0);\n  return normalize(vec3(map(p + e.xyy).x, map(p + e.yxy).x, map(p + e.yyx).x) - d0);\n}\n\n// Thin needles want small steps, or the ray walks straight through a tip.\nvec3 march(vec3 ro, vec3 rd){\n  float t = 0.6, m = -1.0, d = 1.0;\n  for (int i = 0; i < 130; i++){\n vec2 h = map(ro + rd * t);\n d = h.x;\n if (h.x < 0.0009 * t){ m = h.y; break; }\n t += h.x * 0.88;\n if (t > 11.0) break;\n  }\n  return vec3(t, m, d);\n}\n\n// =================================================================== shading\n// Three lights and no ambient at all. There is nothing around this thing - no\n// floor, no sky, no fill - so every surface that faces away from all three\n// falls to true black, which is what holds the silhouette together.\nconst vec3 KEYC  = vec3(0.68, 0.64, 1.00);   // cold key, over the left shoulder\nconst vec3 RIMC  = vec3(0.66, 0.24, 1.00);   // violet, from behind\nconst vec3 CORC  = vec3(0.86, 0.80, 1.00);   // the light caught in the middle\nconst vec3 GRNC  = vec3(0.38, 1.00, 0.07);   // and what the green gives back\n\nvec3 keyDir(){ return normalize(vec3(-0.42, 0.72, 0.55)); }\nvec3 rimDir(){ return normalize(vec3(0.55, -0.12, -0.82)); }\n\nfloat spec(vec3 n, vec3 v, vec3 l, float sh){\n  return pow(max(dot(n, normalize(l + v)), 0.0), sh);\n}\n\n// The foam. The branch is covered in something between bubbles and spawn, and\n// it is bump only: putting it in the distance field would cost every one of the\n// ~200 map() calls a ray makes, where here it costs four, once, at the hit.\nfloat foamH(vec3 p){\n  p += 0.085 * sin(p.yzx * 7.3) + 0.045 * sin(p.zxy * 17.9 + 1.7);\n  vec3 q = p * 26.0;\n  vec3 r = p * 61.0 + 2.3;\n  float a = sin(q.x) * sin(q.y) * sin(q.z);\n  float b = sin(r.x) * sin(r.y) * sin(r.z);\n  return a * a + 0.30 * b * b;\n}\nvec3 bumpN(vec3 p, vec3 n, float amt){\n  vec2 e = vec2(0.0035, 0.0);\n  p += vec3(0.0, gT * 0.055 * uMotion, 0.0);   // the foam creeps\n  float h0 = foamH(p);\n  vec3 g = vec3(foamH(p + e.xyy) - h0,\n    foamH(p + e.yxy) - h0,\n    foamH(p + e.yyx) - h0) / e.x;\n  g -= n * dot(n, g);\n  return normalize(n - amt * g * 0.0030);\n}\n\nvec3 shade(vec3 p, vec3 rd, float mat, float d0){\n  vec3 n = normalOf(p, d0);\n  vec3 v = -rd;\n  vec3 kd = keyDir();\n\n  // The light source itself is only light.\n  if (mat > 3.5 && mat < 4.5) return CORC * (10.0 + 90.0 * uCore);\n\n  vec3 lv = gCore - p;\n  float ld2 = dot(lv, lv);\n  lv = normalize(lv);\n  float catt = 1.0 / (0.05 + ld2 * 1.6);\n\n  vec3 col = vec3(0.0);\n\n  if (mat < 0.5 || (mat > 4.5 && mat < 5.5)){\n // ---- thorn and branch: near black, and all of it is the reflection -----\n n = bumpN(p, n, mat > 4.5 ? uFoam : uFoam * 0.30);\n float fres = pow(1.0 - max(dot(n, v), 0.0), 5.0);\n // How black this piece of thorn is, carried in the material number itself.\n float blk = mat < 0.5 ? clamp(mat * (1.0 / 0.45), 0.0, 1.0) : 0.0;\n vec3  alb  = mix(vec3(0.042, 0.034, 0.082), vec3(0.0035, 0.0032, 0.0060), blk)\n      * (mat > 4.5 ? 1.30 : 1.0);\n // A dark cellular mottle over all of it, which is most of what stops the\n // blades reading as sheet plastic.\n float mot = 0.80 + 0.28 * (0.5 + 0.5 * sin(p.x * 9.1 + sin(p.y * 5.3) * 2.1)\n            * sin(p.y * 7.3 + p.z * 3.7));\n alb *= mot;\n vec3  sheen = mix(vec3(0.40, 0.28, 1.00), vec3(0.92, 0.94, 1.00), fres);\n float gloss = mix(34.0, 340.0, uWet);\n\n float ndl = max(dot(n, kd), 0.0);\n float sh  = 1.0;\n col += alb * KEYC * ndl * sh * uKey * 2.2;\n col += KEYC * spec(n, v, kd, gloss) * sh * uKey * (0.60 + 4.2 * uWet) * (0.08 + fres)\n   * (1.0 - 0.88 * blk);\n\n col += alb * CORC * max(dot(n, lv), 0.0) * catt * uCore * 1.1;\n col += CORC * spec(n, v, lv, gloss * 1.8) * catt * uCore * (0.40 + 2.4 * uWet)\n    * (0.06 + fres);\n\n col += RIMC * uRim * pow(max(dot(n, rimDir()), 0.0), 2.0) * (0.10 + 0.90 * fres) * 0.95;\n col += sheen * fres * uIris * 0.50 * (1.0 - 0.75 * blk);\n // The one move that makes something read as glass rather than as paint: a\n // sky in the reflection. There is no sky in this scene - it is a gradient\n // in the reflected direction, weighted by the Fresnel term, and it is worth\n // more than any amount of extra specular.\n vec3 rf = reflect(rd, n);\n col += mix(vec3(0.015, 0.050, 0.090), vec3(0.32, 0.74, 0.94),\n      0.5 + 0.5 * rf.y) * (0.04 + 0.96 * fres) * uAero * 0.55 * (1.0 - 0.9 * blk);\n // A violet fill, standing in for everything else in the sculpture. Only the\n // surfaces get it, so the background stays exactly black.\n col += alb * vec3(0.52, 0.32, 0.86) * 0.80;\n  } else if (mat < 1.5){\n // ---- clump of wet berries ---------------------------------------------\n float fres = pow(1.0 - max(dot(n, v), 0.0), 3.0);\n float pulse = 0.80 + 0.20 * sin(gT * 1.4 + p.x * 3.0 + p.y * 2.0);\n float mott = 0.66 + 0.44 * (0.5 + 0.5 * sin(p.x * 13.0 + sin(p.z * 8.0) * 1.9)\n          * sin(p.y * 11.0 + p.x * 4.0));\n vec3  gc   = mix(vec3(0.30, 1.00, 0.10), vec3(0.85, 1.00, 0.16),\n      0.5 + 0.5 * sin(p.x * 6.3 + p.y * 4.1 + p.z * 2.7)) * mott;\n col  = gc * (0.10 + 0.45 * max(dot(n, kd), 0.0)) * uKey;\n col += gc * uGlow * pulse * (0.22 + 0.45 * fres) * 0.48;\n col += gc * CORC * max(dot(n, lv), 0.0) * catt * uCore * 0.9;\n col += vec3(1.0) * spec(n, v, kd, 90.0) * uWet * 1.1;\n col += CORC * spec(n, v, lv, 140.0) * catt * uCore * uWet * 0.8;\n  } else if (mat < 2.5){\n // ---- an orb with a light in it ----------------------------------------\n float f = pow(1.0 - max(dot(n, v), 0.0), 2.5);\n float pulse = 0.85 + 0.15 * sin(gT * 1.1 + p.z * 4.0);\n col  = mix(vec3(0.62, 1.00, 0.20), vec3(0.06, 0.34, 0.02), f)\n   * uGlow * pulse * (1.15 - 0.62 * f);\n col += vec3(0.55, 1.00, 0.30) * pow(1.0 - f, 6.0) * uGlow * 0.9;\n col += vec3(1.0) * spec(n, v, kd, 500.0) * (0.4 + 1.6 * uWet) * uKey;\n col += CORC * spec(n, v, lv, 260.0) * catt * uCore * 1.4;\n  } else if (mat > 11.5){\n // ---- tooth: bone, not chrome. A dry white, and the light comes through\n // the thin end of it the way it does through a real one.\n float f = pow(1.0 - max(dot(n, v), 0.0), 3.0);\n col  = vec3(0.95, 0.94, 0.89) * (0.28 + 0.72 * max(dot(n, kd), 0.0)) * uKey;\n col += vec3(1.00, 0.93, 0.86) * max(dot(n, lv), 0.0) * catt * uCore * 1.15;\n col += vec3(0.86, 0.82, 0.96) * f * 0.40;\n col += vec3(1.0) * spec(n, v, kd, 260.0) * (0.5 + 1.8 * uWet) * uKey;\n  } else if (mat > 10.5){\n // ---- a pod: thin-walled, and most of what you see went through it ------\n float f  = pow(1.0 - max(dot(n, v), 0.0), 2.6);\n float bk = max(dot(-n, kd), 0.0);\n col  = vec3(0.34, 0.06, 0.13) * (0.25 + 0.80 * max(dot(n, kd), 0.0)) * uKey;\n col += vec3(1.00, 0.22, 0.30) * pow(bk, 1.4) * 1.15 * uKey;   // lit through\n col += vec3(0.85, 0.30, 0.42) * f * 0.60;\n col += vec3(1.00, 0.34, 0.30) * max(dot(n, lv), 0.0) * catt * uCore * 1.3;\n col += vec3(1.0) * spec(n, v, kd, 140.0) * (0.3 + 1.4 * uWet) * uKey;\n  } else if (mat > 9.5){\n // ---- polished metal: nothing of its own, all of it reflection -----------\n vec3  rf = reflect(rd, n);\n float f  = pow(1.0 - max(dot(n, v), 0.0), 4.0);\n // A metal has no diffuse to speak of; what you see is the room, and the\n // room here is a gradient. The tint rides on the reflected direction rather\n // than the surface normal, which is what makes it swim as the thing turns.\n vec3 env = mix(vec3(0.030, 0.045, 0.075), vec3(0.62, 0.78, 1.00),\n       0.5 + 0.5 * rf.y);\n env *= mix(vec3(0.72, 0.62, 1.00), vec3(0.55, 1.00, 0.72),\n      0.5 + 0.5 * sin(rf.x * 2.6 + rf.z * 1.9 + 1.4));\n col  = env * (0.45 + 0.75 * f) * (0.70 + 0.8 * uAero);\n col += KEYC * spec(n, v, kd, 900.0) * (1.2 + 4.0 * uWet) * uKey;\n col += KEYC * spec(n, v, kd, 60.0) * 0.22 * uKey;\n col += CORC * spec(n, v, lv, 600.0) * catt * uCore * 3.0;\n col += vec3(0.40, 0.95, 0.55) * pow(f, 2.0) * 0.30;\n  } else if (mat > 8.5){\n // ---- glass: a blade you can see the room through ------------------------\n float f  = pow(1.0 - max(dot(n, v), 0.0), 2.2);\n vec3  rf = reflect(rd, n);\n col  = mix(vec3(0.006, 0.018, 0.032), vec3(0.34, 0.74, 1.00),\n      0.5 + 0.5 * rf.y) * (0.02 + 0.98 * f) * (0.45 + 0.9 * uAero);\n // what is behind it, roughly: the same gradient in the VIEW direction,\n // strongest where you are looking straight through the thinnest part\n col += mix(vec3(0.006, 0.016, 0.028), vec3(0.10, 0.24, 0.34),\n      0.5 + 0.5 * rd.y) * (1.0 - f) * 0.30;\n col += vec3(0.66, 0.90, 1.00) * f * f * 2.60;          // the edge\n col += vec3(0.30, 0.62, 0.85) * pow(f, 6.0) * 3.20;    // and its hot line\n col += vec3(1.0) * spec(n, v, kd, 520.0) * (0.8 + 4.0 * uWet) * uKey;\n col += vec3(0.9, 0.97, 1.0) * spec(n, v, kd, 90.0) * uWet * 0.55;\n col += CORC * spec(n, v, lv, 340.0) * catt * uCore * 2.6;\n  } else if (mat > 7.5){\n // ---- the blue ones: cold, glassy, and lit through --------------------\n float f   = pow(1.0 - max(dot(n, v), 0.0), 2.2);\n float ndl = max(dot(n, kd), 0.0);\n float bk  = max(dot(-n, kd), 0.0);\n col  = vec3(0.05, 0.20, 0.42) * (0.22 + 0.85 * ndl) * uKey;\n col += vec3(0.22, 0.58, 1.00) * pow(bk, 1.7) * 1.05 * uKey;\n col += vec3(0.30, 0.66, 1.00) * f * f * 1.30;\n col += vec3(0.20, 0.55, 0.95) * max(dot(n, lv), 0.0) * catt * uCore * 0.9;\n col += vec3(0.85, 0.95, 1.0) * spec(n, v, kd, 260.0) * (0.4 + 1.8 * uWet) * uKey;\n  } else if (mat > 6.5){\n // ---- grass: thin enough that the light comes through it -----------------\n float f = pow(1.0 - max(dot(n, v), 0.0), 2.0);\n float ndl = max(dot(n, kd), 0.0);\n float bk  = max(dot(-n, kd), 0.0);\n col  = vec3(0.26, 0.70, 0.06) * (0.20 + 0.85 * ndl) * uKey;\n col += vec3(0.62, 1.00, 0.14) * pow(bk, 1.7) * 0.95 * uKey;   // through the blade\n col += vec3(0.30, 0.90, 0.14) * max(dot(n, lv), 0.0) * catt * uCore * 0.8;\n col += vec3(0.20, 0.75, 0.30) * f * 0.45;\n col += vec3(0.8, 1.0, 0.7) * spec(n, v, kd, 90.0) * uWet * 0.5;\n  } else if (mat > 5.5){\n // ---- a traveller: all of it is light -----------------------------------\n float f = pow(1.0 - max(dot(n, v), 0.0), 2.0);\n col  = mix(vec3(0.38, 0.95, 0.12), vec3(0.06, 0.42, 0.03), f)\n   * (0.30 + 0.55 * uGlow);\n col += vec3(0.55, 1.00, 0.35) * spec(n, v, kd, 260.0) * uWet * 0.35;\n col += vec3(0.26, 0.85, 0.18) * f * 0.38;\n  }\n\n  // What the green throws back onto everything near it.\n  for (int i = 0; i < NG; i++){\n vec4 G = gGr[i];\n if (G.w == 0.0) continue;\n vec3  gl = G.xyz - p;\n float g2 = dot(gl, gl);\n if (g2 > 0.40) continue;\n col += GRNC * max(dot(n, normalize(gl)), 0.0) * greenGlow(abs(G.w)) * uGlow\n   * abs(G.w) * 0.30 / (0.050 + g2);\n  }\n\n  return col;\n}\n\n// The halo. One closed form per emitter: the nearest approach of the ray to the\n// light, clamped at whatever the ray hit, so a thorn in front of the light cuts\n// its glow off exactly where it should. No blur pass, no second buffer.\nvec3 halo(vec3 ro, vec3 rd, float tmax){\n  vec3 lv = gCore - ro;\n  float tc = clamp(dot(lv, rd), 0.0, tmax);\n  float dd = dot(ro + rd * tc - gCore, ro + rd * tc - gCore);\n  vec3 acc = CORC * uCore * (0.0018 / (0.0006 + dd * dd * 14.0) + 0.0030 / (0.02 + dd * 2.2));\n\n  for (int i = 0; i < NG; i++){\n vec4 G = gGr[i];\n if (G.w == 0.0) continue;\n float tg = clamp(dot(G.xyz - ro, rd), 0.0, tmax);\n float g2 = dot(ro + rd * tg - G.xyz, ro + rd * tg - G.xyz);\n acc += GRNC * greenGlow(abs(G.w)) * uGlow * G.w * G.w\n   * (0.030 / (0.004 + g2 * g2 * 90.0) + 0.005 / (0.05 + g2));\n  }\n  return acc * uHalo;\n}\n\n// The lens the whole thing is supposedly being filmed through. The light is\n// projected to screen space in closed form - it is one dot product per axis -\n// and then given the streak, the starburst and the ghosts down the line\n// through the centre. `vis` is the depth of whatever this pixel actually hit,\n// so a thorn crossing in front chops the streak where it crosses, which is the\n// difference between a flare that sits in the scene and a sticker over it.\nvec3 flare(vec2 uv, vec2 sp, float vis){\n  vec2  d = uv - sp;\n  float r = length(d);\n  vec3  f = vec3(0.40, 0.62, 1.00) * exp(-abs(d.x) * 5.5) * exp(-abs(d.y) * 130.0) * 0.42;\n  float a = atan(d.y, d.x);\n  f += vec3(0.90, 0.88, 1.00) * exp(-r * 11.0)\n  * (0.20 + 0.80 * pow(abs(cos(a * 3.0)), 10.0)) * 0.22;\n  for (int i = 0; i < 3; i++){\n vec2  g  = sp * (-0.32 - 0.46 * float(i));\n float gr = length(uv - g);\n f += mix(vec3(0.25, 0.90, 0.65), vec3(0.55, 0.70, 1.00), float(i) * 0.5)\n    * exp(-gr * gr * 90.0) * 0.13;\n  }\n  return f * vis * uFlare;\n}\n\n// Not black, quite. A slow teal wash behind everything, which is what the era\n// this is dressed as would have put there - kept faint enough that the\n// silhouette still does the work.\nvec3 backdrop(vec2 uv){\n  float band = 0.5 * sin(uv.x * 1.7 + gT * 0.21) + 0.5 * sin(uv.y * 2.3 - gT * 0.17);\n  return mix(vec3(0.0020, 0.0075, 0.0250), vec3(0.0030, 0.0250, 0.0360),\n    0.5 + 0.5 * band)\n    * exp(-dot(uv, uv) * 1.35) * uAero * 0.55;\n}\n\nvec3 aces(vec3 x){\n  return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);\n}\n\nvoid main(){\n vec2 fragCoord = gl_FragCoord.xy;\n vec2 R2 = RENDERSIZE.xy;\n  gT  = TIME * uSpeed;\n  gBr = 0.055 * uMotion * sin(gT * 0.45);\n  // Nothing alive moves at a constant rate. This sits at 1 almost all the time\n  // and spikes for about a second every twenty, and it scales how FAR the\n  // rosettes throw themselves - never how fast - so the whole sculpture\n  // flinches without the clock ever being touched.\n  gTw = 1.0 + 0.85 * pow(0.5 + 0.5 * sin(gT * 0.29), 12.0);\n  build();\n\n  // The orbit wanders. Three sines whose frequencies are not rational\n  // multiples of each other never come back into phase, so the path never\n  // repeats and never has a corner in it - random to look at, smooth by\n  // construction, which no actual random walk would be.\n  float az = 0.55 + gT * uSpin * 0.22\n     + uWander * (0.62 * sin(gT * 0.131) + 0.43 * sin(gT * 0.211 + 1.7)\n       + 0.28 * sin(gT * 0.347 + 3.1));\n  // Clamped short of the pole. At the default tilt the wander never reaches\n  // this, so the motion has no corner in it - but uTilt at maximum plus a full\n  // wander would carry the camera over the top, where the up vector the basis\n  // is built from becomes parallel to the view and the whole frame inverts.\n  float el = clamp(mix(-0.15, 1.32, uTilt)\n     + uWander * (0.34 * sin(gT * 0.107 + 0.6) + 0.21 * sin(gT * 0.173 + 2.4)),\n     -1.42, 1.42);\n  float dist = (2.90 + uWander * (0.34 * sin(gT * 0.089) + 0.18 * sin(gT * 0.157 + 1.2)))\n    / max(uZoom, 0.05);\n  vec3 ro = vec3(sin(az) * cos(el), sin(el), cos(az) * cos(el)) * dist;\n  vec3 ww = normalize(-ro);\n  vec3 uu = normalize(cross(vec3(0.0, 1.0, 0.0), ww));\n  vec3 vv = cross(ww, uu);\n  float rl = 0.055 * uMotion * sin(gT * 0.27);\n  vec3 uu2 = uu * cos(rl) + vv * sin(rl);\n  vv = vv * cos(rl) - uu * sin(rl);\n  uu = uu2;\n\n  // Where the light lands on the film, for the flare.\n  vec3  rel  = gCore - ro;\n  float zc   = dot(rel, ww);\n  vec2  sp   = zc > 0.05 ? vec2(dot(rel, uu), dot(rel, vv)) * (1.78 / zc)\n       : vec2(1e3);\n  float dLit = length(rel);\n\n  vec2 uv = (2.0 * fragCoord - R2) / R2.y;\n  vec3 rd = normalize(uv.x * uu + uv.y * vv + 1.78 * ww);\n\n  vec3  hit = march(ro, rd);\n  float far = hit.y > -0.5 ? hit.x : 11.0;\n  vec3  col = backdrop(uv);\n  if (hit.y > -0.5) col += shade(ro + rd * hit.x, rd, hit.y, hit.z);\n  col += halo(ro, rd, far);\n  col += flare(uv, sp, smoothstep(-0.35, 0.95, far - dLit)) * uCore;\n\n  col = aces(col * (0.35 + 1.05 * uExposure));\n  col = pow(max(col, 0.0), vec3(0.4545));\n  // Colour that varies across the SCULPTURE, not across the screen. A\n  // rotation about the grey axis is a true hue cycle: luminance is the\n  // component along (1,1,1), so it survives and nothing washes out. What\n  // matters is what the angle is KEYED on - on screen position and time it\n  // slid the whole frame through one hue at once, which is how a violet\n  // sculpture went uniformly pink. On where the ray landed and what it landed\n  // on, every material and every part gets a colour of its own.\n  if (uHue > 0.001){\n // bounded: a background ray runs to 11 and would spin the void\n // through a whole turn of hue on its own\n vec3  hp2 = ro + rd * min(far, 3.4);\n const vec3 kg = vec3(0.57735027);\n float ha = uHue * (gT * 0.09 + hit.y * 0.34\n    + dot(hp2, vec3(0.37, 0.29, 0.44)) + length(hp2) * 0.26);\n float cs = cos(ha), sn = sin(ha);\n col = col * cs + cross(kg, col) * sn + kg * dot(kg, col) * (1.0 - cs);\n  }\n  // Saturation last, after the hue. Pulling colour away from its own\n  // LUMINANCE rather than scaling the channels is what keeps it from\n  // clipping and from shifting hue as it goes: the grey it moves away from\n  // is the colour's own brightness, so only the distance from grey changes.\n  col = mix(vec3(dot(col, vec3(0.2126, 0.7152, 0.0722))), col, uSat);\n  col += (h11(fragCoord.x + fragCoord.y * 1731.0 + floor(gT * 24.0) * 7.13) - 0.5)\n    * uGrain * 0.16;\n  gl_FragColor = vec4(max(col, 0.0), 1.0);\n}\n",
    "generator": {
      "model": "Claude-Opus-5",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}