{
  "license": "CC0-1.0",
  "shader": {
    "id": "l8ofptgk",
    "title": "RGB Serpents",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop human-loop studio",
    "status": "active",
    "compile_ok": true,
    "render_hash": "00001c3c38300000",
    "tags": [
      "snakes",
      "boids",
      "raymarch",
      "3d",
      "flocking",
      "generative",
      "sdf"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-08-09T04:45:11.884Z",
    "poster_url": "/posters/l8ofptgk.avif",
    "stats": {
      "slops": 1,
      "views": 186,
      "forks": 0
    },
    "self": "/api/v1/shaders/l8ofptgk",
    "isf": "/*{\n  \"DESCRIPTION\": \"Serpent Field: three raymarched snakes flocking in real 3D. A persistent boid solver flies the heads with a bounded turn rate and a lateral swim stroke, a following chain drags the bodies along the exact path each head travelled, and the render pass marches a tapered swept-sphere distance field with parallel-transported scales, mutual coloured bounce light, and volumetric bloom.\",\n  \"CATEGORIES\": [\"generative\", \"3d\", \"raymarch\"],\n  \"INPUTS\": [\n    { \"NAME\": \"speed\", \"TYPE\": \"float\", \"DEFAULT\": 0.90, \"MIN\": 0.0, \"MAX\": 1.8 },\n    { \"NAME\": \"girth\", \"TYPE\": \"float\", \"DEFAULT\": 0.062, \"MIN\": 0.028, \"MAX\": 0.110 },\n    { \"NAME\": \"bodyLength\", \"TYPE\": \"float\", \"DEFAULT\": 1.55, \"MIN\": 0.60, \"MAX\": 2.60 },\n    { \"NAME\": \"flock\", \"TYPE\": \"float\", \"DEFAULT\": 0.72, \"MIN\": 0.0, \"MAX\": 1.5 },\n    { \"NAME\": \"chase\", \"TYPE\": \"float\", \"DEFAULT\": 0.46, \"MIN\": 0.0, \"MAX\": 1.5 },\n    { \"NAME\": \"avoid\", \"TYPE\": \"float\", \"DEFAULT\": 1.00, \"MIN\": 0.0, \"MAX\": 2.0 },\n    { \"NAME\": \"undulate\", \"TYPE\": \"float\", \"DEFAULT\": 0.62, \"MIN\": 0.0, \"MAX\": 1.4 },\n    { \"NAME\": \"swim\", \"TYPE\": \"float\", \"DEFAULT\": 1.20, \"MIN\": 0.2, \"MAX\": 3.0 },\n    { \"NAME\": \"mess\", \"TYPE\": \"float\", \"DEFAULT\": 0.50, \"MIN\": 0.0, \"MAX\": 1.2 },\n    { \"NAME\": \"scaleDepth\", \"TYPE\": \"float\", \"DEFAULT\": 0.62, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"gloss\", \"TYPE\": \"float\", \"DEFAULT\": 0.70, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"glow\", \"TYPE\": \"float\", \"DEFAULT\": 0.60, \"MIN\": 0.0, \"MAX\": 1.5 },\n    { \"NAME\": \"seed\", \"TYPE\": \"float\", \"DEFAULT\": 11.0, \"MIN\": 0.0, \"MAX\": 30.0 }\n  ],\n  \"PASSES\": [\n    { \"TARGET\": \"heads\", \"PERSISTENT\": true, \"FLOAT\": true },\n    { \"TARGET\": \"spine\", \"PERSISTENT\": true, \"FLOAT\": true },\n    { \"TARGET\": \"bounds\", \"FLOAT\": true },\n    {}\n  ]\n}*/\n\n// GLSL ES 1.00 gives fragment stages no default float precision and makes highp\n// optional there, so a conforming host declares `precision mediump float;` -\n// about ten bits of mantissa. This shader integrates head positions across\n// thousands of frames and then raymarches against them, where mediump would\n// both quantize the flight path and band the surface normals. Ask for highp\n// where the hardware advertises it; the guard is required because naming highp\n// on a part that lacks it is a compile error.\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#endif\n\n#define PI 3.14159265359\n#define TAU 6.28318530718\n\n#define SNAKES 3\n#define NODES 33          // spine samples per snake, node 0 is the snout\n#define SEGS 32           // NODES - 1 capsules\n#define GROUPS 4          // bounding spheres per snake\n#define GROUPSPAN 8       // capsules covered by one bounding sphere (GROUPS*GROUPSPAN == SEGS)\n\n#define SCALE_RINGS 15.0  // scales around the circumference; integer keeps the wrap seamless\n#define SCALE_PITCH 46.0  // scale rows per unit of arc length\n\n// How far past the snout the tongue can reach. Declared up here because the\n// bounds pass has to inflate the head's group sphere by it, and that pass is\n// written before the render pass that draws the tongue.\n#define TONGUE_REACH 0.17\n\n// How far ahead the head starts steering around a body. It has to exceed the\n// turn radius - cruise divided by the turn rate, about 0.37 here - or the head\n// gets its warning too late to act on it and only the contact solver saves it.\n#define AVOID_REACH 0.20\n\n// Clear air left between two surfaces by the contact solver. Small enough to\n// read as touching, large enough that neither the march's hit threshold nor\n// half-float position quantisation can close it.\n#define CONTACT_MARGIN 0.014\n\n// Nodes this close along the same body are neighbours, not obstacles.\n#define SELF_SKIP 4.5\n\n// ---------------------------------------------------------------------------\n// Small shared helpers\n// ---------------------------------------------------------------------------\n\nfloat hash11(float p){ return fract(sin(p*127.1 + seed*19.73)*43758.5453); }\n\n// Bounding a 3D quantity has to bound its magnitude, not its components: clamp()\n// on a vec3 bounds it to a cube, so anything saturating gets pushed into the\n// corners and its direction snaps onto the box diagonals. Limiting the length\n// leaves direction untouched.\nvec3 limitLength(vec3 v, float limit){\n  float m = length(v);\n  return m > limit ? v*(limit/max(m, 1e-5)) : v;\n}\n\nvec3 safeNormalize(vec3 v, vec3 fallback){\n  float m = length(v);\n  return m > 1e-5 ? v/m : fallback;\n}\n\n// A divergence-free wander field, written as the exact curl of an analytic sine\n// potential rather than as finite differences of noise. Being divergence free\n// is what stops the heads from piling into the same attracting sheet: the field\n// stirs without ever having a sink for them to fall into.\nvec3 curlField(vec3 p, float t){\n  vec3 a = vec3(p.y*1.7 + t*0.61, p.z*1.3 - t*0.44, p.x*1.9 + t*0.53);\n  vec3 b = vec3(p.z*2.6 - t*0.37, p.x*2.9 + t*0.71, p.y*2.3 - t*0.66);\n  vec3 f = vec3(-1.3*cos(a.y), -1.9*cos(a.z), -1.7*cos(a.x));\n  vec3 g = vec3(-2.9*cos(b.y), -2.3*cos(b.z), -2.6*cos(b.x));\n  return f + g*0.38;\n}\n\n// Body radius along the snake. u = 0 at the snout, 1 at the tip of the tail.\n//\n// The shape of this curve is most of what separates \"snake\" from \"worm\" at a\n// glance. A snake holds a near-constant diameter for the front half and only\n// then tapers, so the taper is deferred rather than applied over the whole\n// body; a plain pow() falloff from the head gives the carrot silhouette that\n// the first render produced. The tail still has to reach an actual point, or\n// it reads as a cut tube.\n// Two failure modes this curve has to avoid, both found by looking at renders:\n//\n//   - If the neck thins faster than the snout bump decays, the profile dips\n//     between node 0 and node 2 and the head detaches into a lollipop ball. The\n//     two terms have to be tuned against each other, not independently.\n//   - If the taper reaches zero exactly at u = 1, the last capsule is a needle\n//     a whole segment long and the tail renders as a stray hair. Ending the\n//     taper past the end of the body leaves a small rounded tip instead.\n//   - A head built as a single bump at u = 0 is one segment long and wider than\n//     it is long, so it renders as a blunt tube end with a cap. A head needs to\n//     span about three segments and have three distinct parts: a narrow snout\n//     tip, a broad skull behind the eyes, and a slight neck.\nfloat radiusAt(float u){\n  u = clamp(u, 0.0, 1.0);\n  float taper = pow(1.0 - smoothstep(0.38, 1.02, u), 0.52);\n  float neck  = 0.86 + 0.14*smoothstep(0.0, 0.12, u);\n  float skull = 0.62*exp(-((u - 0.028)*(u - 0.028))/0.00090);\n  float snoutTaper = 0.42 + 0.58*smoothstep(0.0, 0.045, u);\n  return girth*snoutTaper*(taper*neck + skull*0.45);\n}\n\nfloat segmentLength(){ return bodyLength/float(SEGS); }\n\n// Cruise speed is a constant rather than an input because the body solver needs\n// to know exactly how far the head advanced this frame in order to slide itself\n// forward by the same arc.\n#define CRUISE 0.88\n\nvec3 catmull(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t){\n  vec3 a = 2.0*p1;\n  vec3 b = p2 - p0;\n  vec3 c = 2.0*p0 - 5.0*p1 + 4.0*p2 - p3;\n  vec3 d = -p0 + 3.0*p1 - 3.0*p2 + p3;\n  return 0.5*(a + b*t + c*t*t + d*t*t*t);\n}\n\n// These are linear-space albedos and they have to be chosen against the output\n// transform, not by eye in isolation. The final gamma raises small channel\n// values hard - a linear 0.115 leaves as 0.38 - so an albedo that looks like a\n// deep red as numbers arrives on screen as salmon. Everything read pale pink,\n// mint and periwinkle until the off-channels came down by roughly five times.\nvec3 snakeColor(int s){\n  if(s == 0) return vec3(1.00, 0.022, 0.035);\n  if(s == 1) return vec3(0.030, 0.95, 0.105);\n  return vec3(0.032, 0.105, 1.00);\n}\n\n// ---------------------------------------------------------------------------\n// State access. Every target is RENDERSIZE-sized because the renderer ignores\n// per-pass WIDTH/HEIGHT, so the simulation lives in the first few texels and\n// every other fragment early-outs.\n//   heads : column = snake. row 0 = position + init flag, row 1 = velocity,\n//           row 2 = roll reference (the snake's own \"up\").\n//   spine : column = node, row = snake. xyz = position, w = init flag.\n//   bounds: row 0, column = snake*GROUPS + group. xyz = centre, w = radius.\n// ---------------------------------------------------------------------------\n\nvec4 headCell(int s, int row){\n  return IMG_NORM_PIXEL(heads, (vec2(float(s), float(row)) + 0.5)/RENDERSIZE);\n}\n\nvec4 spineCell(int j, int s){\n  return IMG_NORM_PIXEL(spine, (vec2(float(j), float(s)) + 0.5)/RENDERSIZE);\n}\n\nvec3 spineAtF(float col, int s){\n  return IMG_NORM_PIXEL(spine, (vec2(col, float(s)) + 0.5)/RENDERSIZE).xyz;\n}\n\n// The body as a curve that can be sampled at a fractional node index. Catmull-\n// Rom rather than a straight lerp: the body is resampled from itself every\n// single frame, so any interpolation error is applied dozens of times to the\n// same piece of shape as it travels to the tail. A lerp's chord error alone\n// flattened the swim wave by roughly forty percent on the way down.\nvec3 pathAt(float fi, int s){\n  float m = float(NODES - 1);\n  fi = clamp(fi, 0.0, m);\n  float f0 = floor(fi);\n  float fr = fi - f0;\n  return catmull(spineAtF(max(f0 - 1.0, 0.0), s),\n                 spineAtF(f0, s),\n                 spineAtF(min(f0 + 1.0, m), s),\n                 spineAtF(min(f0 + 2.0, m), s), fr);\n}\n\nvec4 boundCell(int s, int k){\n  return IMG_NORM_PIXEL(bounds, (vec2(float(s*GROUPS + k), 0.0) + 0.5)/RENDERSIZE);\n}\n\n// ---------------------------------------------------------------------------\n// Cold-start pose\n//\n// glslop derives FRAMEINDEX from the requested poster time, so a still rendered\n// at t=8 hands this shader a first-ever frame numbered near 480 and every\n// \"is this frame zero\" test fails. State is therefore seeded from the buffer's\n// own contents (the w flag), and the seed pose is a function of TIME so that a\n// single cold frame at any moment is already a composed picture of three snakes\n// mid-flight rather than three straight rods.\n// ---------------------------------------------------------------------------\n\nvoid seedPose(int s, float t, out vec3 pos, out vec3 dir){\n  float fs = float(s);\n  float ph = fs*(TAU/float(SNAKES)) + hash11(fs + 3.0)*1.7;\n  float orbit = t*0.21 + ph;\n  float rad = 1.12 + 0.20*sin(t*0.23 + ph*2.3);\n  pos = vec3(cos(orbit)*rad,\n             0.46*sin(orbit*0.77 + ph) + 0.22*sin(t*0.17 + ph*1.9),\n             sin(orbit)*rad);\n  dir = normalize(vec3(-sin(orbit)*rad, 0.30*cos(t*0.31 + ph), cos(orbit)*rad));\n}\n\n// A seeded body is laid back along the reverse of the heading with a swim wave\n// already in it, so a cold frame shows serpents rather than sticks.\nvec3 seedNode(int s, int j, float t){\n  vec3 pos, dir;\n  seedPose(s, t, pos, dir);\n  vec3 side = safeNormalize(cross(dir, vec3(0.0, 1.0, 0.0)), vec3(1.0, 0.0, 0.0));\n  vec3 up = cross(side, dir);\n  float L = segmentLength();\n  float fj = float(j);\n  float a = fj*0.62 - t*2.1 + float(s)*1.3;\n  return pos - dir*(L*fj) + side*(sin(a)*L*0.62) + up*(cos(a*0.63)*L*0.26);\n}\n\n// ===========================================================================\n// PASS 0 - the boids\n// ===========================================================================\n\n// Camera state, filtered and stored rather than derived fresh in the render\n// pass. Two reasons it belongs here:\n//\n//   - `extent` is a max over the whole flock, and a max switches which point it\n//     is following. That is continuous in value but not in slope, so the framing\n//     picked up a small kink every time the outermost node changed - invisible\n//     in a still, and a persistent low-level unsteadiness in motion.\n//   - Deriving it per pixel cost the render pass two dozen texture reads per\n//     fragment to compute a number that is identical for the whole frame.\n//\n// It runs off last frame's body, since this pass precedes the body pass. A frame\n// of camera lag is not observable; the filter's own time constants are hundreds\n// of times longer.\nvoid mainCamera(){\n  float dt = clamp(TIMEDELTA, 1.0/240.0, 1.0/30.0)*speed;\n\n  vec3 centre = vec3(0.0);\n  float count = 0.0;\n  for(int o = 0; o < SNAKES; o++){\n    for(int n = 0; n < NODES; n++){\n      vec4 c = spineCell(n, o);\n      if(c.w < 0.5) continue;\n      centre += c.xyz;\n      count += 1.0;\n    }\n  }\n  if(count < 0.5){ gl_FragColor = vec4(0.0, 0.0, 0.0, 4.2); return; }\n  centre /= count;\n\n  float extent = 0.0;\n  for(int o = 0; o < SNAKES; o++){\n    for(int n = 0; n < NODES; n++){\n      vec4 c = spineCell(n, o);\n      if(c.w < 0.5) continue;\n      extent = max(extent, length(c.xyz - centre) + radiusAt(float(n)/float(SEGS)));\n    }\n  }\n\n  // At focal f the half-height visible at distance d is d/(2f), so fitting a\n  // group of radius `extent` needs d >= 2*f*extent - about 3.1 here.\n  float rawDolly = clamp(extent*3.20, 2.9, 7.6);\n  vec3 rawTarget = centre*0.92;\n\n  vec4 prev = headCell(0, 3);\n  // A live dolly is never below its own 2.9 floor, so w is its own cold flag.\n  if(prev.w < 0.5){ gl_FragColor = vec4(rawTarget, rawDolly); return; }\n\n  float kTarget = 1.0 - exp(-dt/0.40);\n  float kDolly = 1.0 - exp(-dt/0.90);\n  gl_FragColor = vec4(mix(prev.xyz, rawTarget, kTarget), mix(prev.w, rawDolly, kDolly));\n}\n\nvoid mainHeads(){\n  float col = floor(gl_FragCoord.x);\n  float row = floor(gl_FragCoord.y);\n  if(row > 2.5){\n    if(row > 3.5 || col > 0.5){ gl_FragColor = vec4(0.0); return; }\n    mainCamera();\n    return;\n  }\n  if(col > float(SNAKES) - 0.5){ gl_FragColor = vec4(0.0); return; }\n\n  int s = int(col);\n  float t = TIME*speed;\n  float dt = clamp(TIMEDELTA, 1.0/240.0, 1.0/30.0)*speed;\n\n  vec4 posCell = headCell(s, 0);\n  vec4 velCell = headCell(s, 1);\n  vec4 rollCell = headCell(s, 2);\n\n  vec3 pos = posCell.xyz;\n  vec3 vel = velCell.xyz;\n  vec3 roll = rollCell.xyz;\n\n  // The swim phase is integrated with the simulation's own clamped dt and kept\n  // as a wrapped fraction of a cycle, rather than being read straight off TIME.\n  //\n  // Driving it from TIME couples the stroke to wall-clock time while the head\n  // position is integrated with a dt that is deliberately clamped for\n  // stability. The two agree at normal frame rates and diverge badly anywhere\n  // else - glslop's poster schedule steps at t/120, which for an eight second\n  // poster is 0.067 per step against a clamp of 0.033. The head then covers\n  // half the distance per stroke cycle that it should, the wavelength halves,\n  // the amplitude halves with it, and the animals arrive at the canonical\n  // renderer visibly straighter than they are in the studio. Accumulating the\n  // phase alongside the position keeps the stroke locked to the motion it is\n  // supposed to describe, at any frame rate and under any host's schedule.\n  //\n  // It is wrapped rather than accumulated without bound because these targets\n  // are RGBA16F: an unwrapped phase would reach a magnitude where the mantissa\n  // can no longer resolve one frame's increment, and the stroke would quietly\n  // freeze after a few minutes of playback.\n  float phase = fract(velCell.w);\n\n  // Contents decide whether this is a cold buffer - never the frame number.\n  if(posCell.w < 0.5){\n    vec3 sp, sd;\n    seedPose(s, t, sp, sd);\n    pos = sp;\n    vel = sd*0.9;\n    roll = safeNormalize(cross(cross(sd, vec3(0.0, 1.0, 0.0)), sd), vec3(0.0, 1.0, 0.0));\n    phase = hash11(float(s)*7.31 + 2.0);\n  }\n\n  phase = fract(phase + swim*dt);\n\n  vec3 vdir = safeNormalize(vel, vec3(0.0, 0.0, 1.0));\n\n  // Roll is parallel transported through time instead of rebuilt per frame from\n  // a world axis. A frame rebuilt from a fixed reference flips whenever the\n  // heading passes that reference, and the flip would snap every scale on the\n  // body a quarter turn in one frame. Transport has no such singularity.\n  roll = safeNormalize(roll - vdir*dot(roll, vdir), vec3(0.0, 1.0, 0.0));\n\n  // Pure transport is free to leave a snake flying belly-up for long stretches,\n  // which reads as an error even though it is physically consistent. A weak\n  // righting pull toward world up fixes it. The pull is faded out as the heading\n  // approaches vertical, where \"up\" stops being a meaningful reference - which\n  // is also the only place the pull could snap rather than drift.\n  vec3 worldUp = vec3(0.0, 1.0, 0.0);\n  float upright = 1.0 - abs(dot(vdir, worldUp));\n  vec3 rightTo = safeNormalize(worldUp - vdir*dot(worldUp, vdir), roll);\n  roll = safeNormalize(mix(roll, rightTo, clamp(upright*2.6*dt, 0.0, 0.10)), roll);\n  vec3 side = safeNormalize(cross(vdir, roll), vec3(1.0, 0.0, 0.0));\n\n  vec3 force = vec3(0.0);\n\n  // Orbit. A tangential push around a slowly tipping axis is what turns three\n  // free boids into a shared circulation instead of three unrelated wanderers.\n  vec3 axis = normalize(vec3(0.22*sin(t*0.11 + float(s)), 1.0, 0.18*cos(t*0.09)));\n  force += cross(axis, pos)*1.15;\n\n  // Containment, plus a weak outward push near the middle so they never all\n  // collapse through the origin at once.\n  float r = length(pos);\n  force -= pos*(smoothstep(0.70, 1.30, r)*4.8);\n  force += safeNormalize(pos, vec3(1.0, 0.0, 0.0))*((1.0 - smoothstep(0.20, 0.62, r))*1.5);\n\n  // Flocking against the other heads.\n  for(int o = 0; o < SNAKES; o++){\n    if(o == s) continue;\n    vec4 oPos = headCell(o, 0);\n    vec4 oVel = headCell(o, 1);\n    if(oPos.w < 0.5) continue;\n    vec3 d = pos - oPos.xyz;\n    float dl = max(length(d), 1e-4);\n    vec3 dn = d/dl;\n    // The equilibrium spacing is where these two balance. Pushed out to a full\n    // body length the three snakes simply orbit as separate objects; bringing it\n    // in to roughly half that is what lets them cross, coil past one another and\n    // light each other up, which is the whole point of flying them as a flock.\n    force += dn*(flock*1.85/(0.20 + dl*dl));                       // separation\n    force -= dn*(flock*1.05*smoothstep(0.35, 1.20, dl));           // cohesion\n    force += oVel.xyz*(flock*0.30);                                // alignment\n  }\n\n  // Cyclic pursuit: red chases green, green chases blue, blue chases red, each\n  // aiming at a point trailing the one ahead rather than at its head.\n  //\n  // Symmetric cohesion alone gives three animals loosely orbiting a common\n  // centre, which reads as three unrelated swimmers that happen to share a\n  // frame. Making the attraction a cycle gives the group an intent: the classic\n  // three-body pursuit spirals into a rotating braid, and because each snake is\n  // aiming behind its target it falls into the wake rather than colliding with\n  // it. Separation still has the final say when they close.\n  int chased = s + 1;\n  if(chased >= SNAKES) chased = 0;\n  vec4 chasedPos = headCell(chased, 0);\n  vec4 chasedVel = headCell(chased, 1);\n  if(chasedPos.w > 0.5){\n    vec3 lead = safeNormalize(chasedVel.xyz, vec3(0.0, 0.0, 1.0));\n    vec3 aim = (chasedPos.xyz - lead*0.55) - pos;\n    force += safeNormalize(aim, vec3(0.0))*(chase*2.30);\n  }\n\n  // Body avoidance - the thing head-to-head separation cannot do.\n  //\n  // A body here is a passive trail: only the head steers, and every node behind\n  // it just slides along the path the head already laid down. So two bodies will\n  // drift straight through one another no matter how well the heads avoid each\n  // other, because nothing in the body is able to move out of the way. The head\n  // has to steer around whole *bodies*, its own included, and it has to start\n  // early enough that a rate-limited turn can still clear them.\n  //\n  // The falloff is quadratic and reaches exactly zero at the reach. A linear\n  // cutoff would put a kink in the steering force every time a node crossed the\n  // boundary, and with ninety-nine nodes streaming past that reads as a\n  // permanent tremor rather than as avoidance.\n  for(int o = 0; o < SNAKES; o++){\n    for(int n = 0; n < NODES; n++){\n      // A snake's own neck is legitimately right behind its own head.\n      if(o == s && float(n) < 7.5) continue;\n      vec4 cell = spineCell(n, o);\n      if(cell.w < 0.5) continue;\n      vec3 d = pos - cell.xyz;\n      float dl = length(d);\n      float reach = AVOID_REACH + radiusAt(float(n)/float(SEGS));\n      if(dl > reach || dl < 1e-4) continue;\n      float k = 1.0 - dl/reach;\n      force += (d/dl)*(k*k*avoid*6.0);\n    }\n  }\n\n  // Wander.\n  force += curlField(pos*0.85, t)*(mess*1.35);\n\n  // Bounded turn rate on the *travel* heading only. Steering by a capped angle\n  // rather than by a capped force is what keeps the flight readable: the head\n  // can always commit to a heading, it just cannot snap to one.\n  vec3 desired = safeNormalize(force, vdir);\n  float cosang = clamp(dot(vdir, desired), -1.0, 1.0);\n  float ang = acos(cosang);\n  float maxTurn = (2.35 + flock*0.6)*dt;\n  float k = ang > 1e-4 ? min(maxTurn/ang, 1.0) : 1.0;\n  vec3 travel = safeNormalize(mix(vdir, desired, k), vdir);\n\n  // The swim stroke, applied after the steer instead of as another force.\n  //\n  // Steering the head from side to side and letting the body inherit the S is\n  // how a real snake works, and it means the undulation needs no per-node frame\n  // - the wave is simply the shape of the path the head left behind. But fed in\n  // as a force it does not survive: a stroke tight enough to fit two waves in a\n  // body needs a turn radius of a few centimetres, which is an angular rate far\n  // above anything a flock should be allowed to steer at. Feeding it in as a\n  // force therefore either gets clipped away by the turn cap, or forces the cap\n  // so high that the flocking itself becomes twitchy. Rotating the already\n  // steered heading decouples the two: the flock decides where the snake is\n  // going, the stroke decides how it gets there.\n  // The lateral weight is a tangent, so it sets the maximum angle the heading\n  // ever departs from travel. That angle has to be large - around fifty degrees\n  // at the default - because the path amplitude it produces is\n  // (angle * wavelength / 2pi), and anything below about twice the body radius\n  // is simply swallowed by the thickness of the animal. At the first attempt's\n  // 24 degrees the wave amplitude came out at 0.043 against a radius of 0.062\n  // and all three snakes rendered as straight rods.\n  float stroke = phase*TAU + float(s)*2.09;\n  vec3 strokeSide = safeNormalize(cross(travel, roll), side);\n  vec3 strokeUp = cross(strokeSide, travel);\n  vec3 heading = safeNormalize(travel + (strokeSide*sin(stroke)\n                                        + strokeUp*(cos(stroke)*0.30))*(undulate*2.00), travel);\n\n  // Constant cruise speed. A varying speed makes the body bunch and stretch; a\n  // constant one lets it hold its length and just flow.\n  pos += heading*(CRUISE*dt);\n\n  // What gets stored is `travel`, the smooth flight direction - NOT the stroked\n  // heading the snake actually moved along.\n  //\n  // Storing the stroked heading makes the stroke an increment on top of itself:\n  // next frame's steer starts from an already-deviated direction and adds\n  // another full deviation to it. Because the stroke phase moves only about\n  // eleven degrees per frame, consecutive frames add near-identical rotations,\n  // which compound into a corkscrew instead of cancelling into a weave. It came\n  // out as a helix of radius 0.022 against a body radius of 0.062 - the wave was\n  // real, and entirely hidden inside the animal. Keeping travel separate makes\n  // the stroke an absolute offset from a smooth path, which is what it is.\n  vel = travel*CRUISE;\n\n  // Contact resolution for the head itself.\n  //\n  // The body solver pushes every node out of every surface, but node 0 is not\n  // its to move - it is written straight from this pass - so the head was the\n  // one part of the animal with steering and no contact response. It is also the\n  // leading edge, the part actually driving into things, which is why it\n  // accounted for essentially all of the residual interpenetration once the\n  // bodies were solved. Correcting it here keeps `heads` and `spine` agreeing.\n  float headRadius = radiusAt(0.0);\n  vec3 headPush = vec3(0.0);\n  for(int o = 0; o < SNAKES; o++){\n    if(spineCell(0, o).w < 0.5) continue;\n    vec3 a = spineCell(0, o).xyz;\n    for(int n = 0; n < SEGS; n++){\n      vec3 b = spineCell(n + 1, o).xyz;\n      if(o == s && float(n) < SELF_SKIP + 3.5){ a = b; continue; }\n      vec3 ba = b - a;\n      float h = clamp(dot(pos - a, ba)/max(dot(ba, ba), 1e-8), 0.0, 1.0);\n      vec3 d = pos - (a + ba*h);\n      float dl = length(d);\n      float need = headRadius + CONTACT_MARGIN\n                 + mix(radiusAt(float(n)/float(SEGS)), radiusAt(float(n + 1)/float(SEGS)), h);\n      if(dl < need && dl > 1e-5) headPush += (d/dl)*(need - dl);\n      a = b;\n    }\n  }\n  pos += headPush*0.42;\n\n  // Hard backstop so a bad frame time can never fling a head out of the scene.\n  if(length(pos) > 1.95) pos = normalize(pos)*1.95;\n\n  if(row < 0.5)      gl_FragColor = vec4(pos, 1.0);\n  else if(row < 1.5) gl_FragColor = vec4(vel, phase);\n  else               gl_FragColor = vec4(roll, 1.0);\n}\n\n// ===========================================================================\n// PASS 1 - the following body\n//\n// The body is not a chain and not a spring. Node j is defined as the point\n// exactly j*segmentLength of true arc back along the path the head has actually\n// travelled, found by walking last frame's curve and accumulating real\n// distance. Everything else was tried first and none of it survives contact\n// with a long body:\n//\n//   - Follow-the-leader (each node pulled to a fixed distance behind the next)\n//     is a pursuit curve, and a pursuit curve low-passes the path it chases.\n//     Compounded over thirty-two links it erased a thirteen-segment swim wave\n//     entirely and rendered all three snakes as rigid rods.\n//   - Resampling the old curve at a fixed index offset convects the shape\n//     correctly but has no control over spacing at all: the correction term\n//     comes out identical for every node, so it slides the whole body without\n//     ever changing the gaps, and the bodies crumpled into clusters of spheres.\n//\n// Measuring real arc length has no such feedback loop to get wrong. The spacing\n// is exact every frame by construction, and it stays exact through frame-rate\n// changes, speed changes and live edits to the body length.\n// ===========================================================================\n\n// Walk back from this frame's head along last frame's curve until `target` of\n// arc length has been covered. The first segment - new head to old head - is\n// the piece of path that was created this frame, and it is straight, so a lerp\n// is exact there. Further back the spline is used instead of the chord, because\n// this resampling happens to the same piece of shape on every frame of its trip\n// to the tail and chord error would quietly eat the wave.\nvec3 bodyPoint(int s, float target){\n  vec3 newHead = headCell(s, 0).xyz;\n  vec3 prev = newHead;\n  float acc = 0.0;\n  for(int i = 0; i < NODES; i++){\n    vec3 p = spineAtF(float(i), s);\n    float seg = length(p - prev);\n    if(acc + seg >= target){\n      float f = (target - acc)/max(seg, 1e-6);\n      if(i == 0) return mix(newHead, p, f);\n      return pathAt(float(i - 1) + f, s);\n    }\n    acc += seg;\n    prev = p;\n  }\n  return prev;\n}\n\nvoid mainSpine(){\n  float col = floor(gl_FragCoord.x);\n  float row = floor(gl_FragCoord.y);\n  if(col > float(NODES) - 0.5 || row > float(SNAKES) - 0.5){ gl_FragColor = vec4(0.0); return; }\n\n  int j = int(col);\n  int s = int(row);\n  float t = TIME*speed;\n  float L = segmentLength();\n\n  // Node 0 is the head itself, taken from this frame's boid pass.\n  if(j == 0){\n    vec4 h = headCell(s, 0);\n    vec3 p = h.w < 0.5 ? seedNode(s, 0, t) : h.xyz;\n    gl_FragColor = vec4(p, 1.0);\n    return;\n  }\n\n  vec4 selfCell = spineCell(j, s);\n  if(selfCell.w < 0.5){ gl_FragColor = vec4(seedNode(s, j, t), 1.0); return; }\n\n  vec3 self = bodyPoint(s, float(j)*L);\n\n  // Contact resolution. Steering gets the heads out of each other's way, but two\n  // bodies that are already overlapping have no steering of their own, and a\n  // tail cannot decide to move. So after the arc-length walk each node is pushed\n  // out of any surface it is inside - other snakes' and, further along, its own.\n  //\n  // This is safe to do *after* the walk precisely because the walk re-measures\n  // true arc length from the stored curve every frame: the displacement becomes\n  // part of the recorded path, next frame's walk re-parameterises over it, and\n  // node spacing stays exact. The deformation then convects toward the tail on\n  // its own, which is what a real snake sliding over another one looks like.\n  // Tested against the other body's *surface*, not against its nodes. Node-to-\n  // node is not enough: two segments can cross between their endpoints with\n  // every node pair still comfortably apart, and that is exactly the case that\n  // renders as one snake passing through another. Measuring to the swept capsule\n  // closes the gap and also gives the correct push direction - away from the\n  // nearest point on the surface rather than away from an arbitrary sample.\n  float ur = float(j)/float(SEGS);\n  float selfRadius = radiusAt(ur);\n  vec3 push = vec3(0.0);\n  for(int o = 0; o < SNAKES; o++){\n    if(spineCell(0, o).w < 0.5) continue;\n    vec3 a = spineCell(0, o).xyz;\n    for(int n = 0; n < SEGS; n++){\n      vec3 b = spineCell(n + 1, o).xyz;\n      // Segments along this same body near this node are neighbours, not\n      // obstacles. Compare as floats: ES 1.00 has no integer overload of abs().\n      if(o == s && abs(float(n) + 0.5 - float(j)) < SELF_SKIP){ a = b; continue; }\n      vec3 ba = b - a;\n      float h = clamp(dot(self - a, ba)/max(dot(ba, ba), 1e-8), 0.0, 1.0);\n      vec3 d = self - (a + ba*h);\n      float dl = length(d);\n      float need = selfRadius + CONTACT_MARGIN\n                 + mix(radiusAt(float(n)/float(SEGS)), radiusAt(float(n + 1)/float(SEGS)), h);\n      if(dl < need && dl > 1e-5) push += (d/dl)*(need - dl);\n      a = b;\n    }\n  }\n\n  // Relaxed rather than applied whole: the node this one is being pushed off is\n  // being pushed off it in the same frame, so resolving the full overlap on both\n  // sides at once overshoots and the two buzz against each other.\n  self += push*0.50;\n\n  gl_FragColor = vec4(self, 1.0);\n}\n\n// ===========================================================================\n// PASS 2 - bounding spheres\n//\n// A single sphere around a whole snake is a useless bound: the body is long and\n// thin, so the sphere is mostly empty and the march pays full price the moment\n// it enters. Four spheres per snake keep the empty-space stepping honest and\n// cost twelve texels a frame.\n// ===========================================================================\n\nvoid mainBounds(){\n  float col = floor(gl_FragCoord.x);\n  float row = floor(gl_FragCoord.y);\n  if(col > float(SNAKES*GROUPS) - 0.5 || row > 0.5){ gl_FragColor = vec4(0.0); return; }\n\n  float fs = floor(col/float(GROUPS));\n  float fk = col - fs*float(GROUPS);\n  int s = int(fs);\n  int k = int(fk);\n\n  vec3 centre = vec3(0.0);\n  for(int i = 0; i <= GROUPSPAN; i++){\n    centre += spineCell(k*GROUPSPAN + i, s).xyz;\n  }\n  centre /= float(GROUPSPAN + 1);\n\n  float rad = 0.0;\n  for(int i = 0; i <= GROUPSPAN; i++){\n    int node = k*GROUPSPAN + i;\n    float u = float(node)/float(SEGS);\n    rad = max(rad, length(spineCell(node, s).xyz - centre) + radiusAt(u));\n  }\n\n  // The head's group has to cover the tongue as well, or the coarse gate in the\n  // march will skip past it.\n  if(k == 0) rad += TONGUE_REACH;\n\n  gl_FragColor = vec4(centre, rad + 0.015);\n}\n\n// ===========================================================================\n// PASS 3 - the render\n// ===========================================================================\n\n// Distance to one tapered capsule. h comes back so the caller can recover where\n// along the segment the closest point sat.\nfloat capsuleDistance(vec3 p, vec3 a, vec3 b, float ra, float rb, out float h){\n  vec3 pa = p - a;\n  vec3 ba = b - a;\n  h = clamp(dot(pa, ba)/max(dot(ba, ba), 1e-8), 0.0, 1.0);\n  return length(pa - ba*h) - mix(ra, rb, h);\n}\n\n// Tongue flick. Timed from TIME rather than from the integrated phase because\n// it is pure decoration - nothing downstream depends on it staying in step with\n// the body's motion, and a poster is entitled to catch it at any point.\nfloat tongueExtend(int s){\n  float fp = fract(TIME*0.31 + hash11(float(s)*3.71 + 5.0));\n  return smoothstep(0.0, 0.13, fp)*smoothstep(0.34, 0.18, fp);\n}\n\n// Exact distance to one snake: the union of capsules that share endpoints is\n// the sphere swept along the polyline, so the surface stays smooth across\n// joints instead of creasing.\nfloat snakeDistance(vec3 p, int s, out float isTongue){\n  float best = 1e9;\n  isTongue = 0.0;\n  vec3 head = spineCell(0, s).xyz;\n  vec3 a = head;\n  vec3 neck = a;\n  for(int j = 0; j < SEGS; j++){\n    vec3 b = spineCell(j + 1, s).xyz;\n    if(j == 0) neck = b;\n    float h;\n    float d = capsuleDistance(p, a, b,\n                              radiusAt(float(j)/float(SEGS)),\n                              radiusAt(float(j + 1)/float(SEGS)), h);\n    best = min(best, d);\n    a = b;\n  }\n\n  // Gate on a sphere around the snout so the tongue costs nothing for the vast\n  // majority of march steps, which are nowhere near a head.\n  float ext = tongueExtend(s);\n  if(ext > 0.01 && length(p - head) < TONGUE_REACH + 0.02){\n    vec3 dir = safeNormalize(head - neck, vec3(0.0, 0.0, 1.0));\n    vec3 side = safeNormalize(cross(dir, vec3(0.0, 1.0, 0.0)), vec3(1.0, 0.0, 0.0));\n    float len = TONGUE_REACH*ext;\n    vec3 root = head + dir*(radiusAt(0.0)*0.55);\n    vec3 split = root + dir*(len*0.58);\n    float h;\n    float dt0 = capsuleDistance(p, root, split, 0.0125, 0.0075, h);\n    vec3 tipA = split + dir*(len*0.42) + side*(len*0.26);\n    vec3 tipB = split + dir*(len*0.42) - side*(len*0.26);\n    float dt1 = min(capsuleDistance(p, split, tipA, 0.0072, 0.0026, h),\n                    capsuleDistance(p, split, tipB, 0.0072, 0.0026, h));\n    float dtongue = min(dt0, dt1);\n    if(dtongue < best){ best = dtongue; isTongue = 1.0; }\n  }\n  return best;\n}\n\n// Coarse-to-fine scene distance. The group spheres give a valid lower bound, so\n// returning one of them when it is still far is conservative - the march simply\n// takes a shorter step than it could have - while skipping twenty capsules.\n#define FINE_GATE 0.075\n\nfloat mapScene(vec3 p, out float id){\n  float best = 1e9;\n  id = -1.0;\n  for(int s = 0; s < SNAKES; s++){\n    float sb = 1e9;\n    for(int k = 0; k < GROUPS; k++){\n      vec4 b = boundCell(s, k);\n      sb = min(sb, length(p - b.xyz) - b.w);\n    }\n    if(sb > best) continue;\n    if(sb > FINE_GATE){ best = sb; continue; }\n    float isTongue;\n    float d = snakeDistance(p, s, isTongue);\n    // The fractional part carries \"this is the tongue, not the body\", so the\n    // shading pass can give it flesh instead of scales without a second march.\n    if(d < best){ best = d; id = float(s) + isTongue*0.5; }\n  }\n  return best;\n}\n\nfloat mapDistance(vec3 p){\n  float id;\n  return mapScene(p, id);\n}\n\n// Surface frame at the hit. Walked once, not per march step: the frame vector is\n// parallel transported down the chain so the scale lattice cannot spin around\n// the body wherever the heading happens to align with a world axis.\nvoid surfaceFrame(vec3 p, int s, out float arc, out vec3 tangent, out vec3 axisU, out vec3 axisV){\n  float bestD = 1e9;\n  arc = 0.0;\n  tangent = vec3(0.0, 0.0, 1.0);\n  axisU = vec3(0.0, 1.0, 0.0);\n\n  vec3 a = spineCell(0, s).xyz;\n  vec3 b0 = spineCell(1, s).xyz;\n  vec3 dir = safeNormalize(b0 - a, vec3(0.0, 0.0, 1.0));\n  vec3 frame = headCell(s, 2).xyz;\n  frame = safeNormalize(frame - dir*dot(frame, dir), vec3(0.0, 1.0, 0.0));\n\n  float L = segmentLength();\n  for(int j = 0; j < SEGS; j++){\n    vec3 b = spineCell(j + 1, s).xyz;\n    dir = safeNormalize(b - a, dir);\n    frame = safeNormalize(frame - dir*dot(frame, dir), frame);\n    float h;\n    float d = capsuleDistance(p, a, b,\n                              radiusAt(float(j)/float(SEGS)),\n                              radiusAt(float(j + 1)/float(SEGS)), h);\n    if(d < bestD){\n      bestD = d;\n      arc = (float(j) + h)*L;\n      tangent = dir;\n      axisU = frame;\n    }\n    a = b;\n  }\n  axisV = cross(tangent, axisU);\n}\n\nvec3 sceneNormal(vec3 p, float scale){\n  vec2 e = vec2(1.0, -1.0)*scale;\n  return normalize(e.xyy*mapDistance(p + e.xyy) +\n                   e.yyx*mapDistance(p + e.yyx) +\n                   e.yxy*mapDistance(p + e.yxy) +\n                   e.xxx*mapDistance(p + e.xxx));\n}\n\nfloat ambientOcclusion(vec3 p, vec3 n){\n  float occ = 0.0;\n  float sca = 1.0;\n  for(int i = 0; i < 5; i++){\n    float h = 0.012 + 0.055*float(i);\n    occ += (h - mapDistance(p + n*h))*sca;\n    sca *= 0.72;\n  }\n  return clamp(1.0 - 2.4*occ, 0.0, 1.0);\n}\n\nfloat softShadow(vec3 ro, vec3 rd){\n  float res = 1.0;\n  float t = 0.035;\n  for(int i = 0; i < 22; i++){\n    float h = mapDistance(ro + rd*t);\n    res = min(res, 9.0*h/t);\n    t += clamp(h, 0.020, 0.20);\n    if(res < 0.005 || t > 4.0) break;\n  }\n  return clamp(res, 0.0, 1.0);\n}\n\n// Span of the ray over the union of every group sphere. Pixels that miss all of\n// them never enter the march at all, which is most of the frame.\nvec2 sceneSpan(vec3 ro, vec3 rd){\n  float tn = 1e9;\n  float tf = -1e9;\n  for(int s = 0; s < SNAKES; s++){\n    for(int k = 0; k < GROUPS; k++){\n      vec4 b = boundCell(s, k);\n      vec3 oc = ro - b.xyz;\n      float hb = dot(oc, rd);\n      float c = dot(oc, oc) - b.w*b.w;\n      float disc = hb*hb - c;\n      if(disc < 0.0) continue;\n      float sq = sqrt(disc);\n      float t0 = -hb - sq;\n      float t1 = -hb + sq;\n      if(t1 < 0.0) continue;\n      tn = min(tn, max(t0, 0.0));\n      tf = max(tf, t1);\n    }\n  }\n  return vec2(tn, tf);\n}\n\n// Volumetric bloom. Sampling the spine twice per segment and accumulating an\n// inverse-square falloff against the ray is far cheaper than a second march and\n// is what makes the bodies read as coloured light sources rather than plastic.\n// Clamping the projection to the surface hit is the occlusion: anything behind\n// the snake in front projects to the hit and lands far from the ray.\n#define BLOOM_REACH 0.85\n\nvec3 bloom(vec3 ro, vec3 rd, float tMax){\n  vec3 sum = vec3(0.0);\n  for(int s = 0; s < SNAKES; s++){\n    float w = 0.0;\n    for(int k = 0; k < GROUPS; k++){\n      // Gate each group of eight segments on its bounding sphere. Without this\n      // every pixel in the frame - overwhelmingly background - pays for all\n      // ninety-six spine samples, and the glow ends up costing more than the\n      // raymarch it decorates.\n      vec4 bnd = boundCell(s, k);\n      vec3 rel0 = bnd.xyz - ro;\n      float tc = clamp(dot(rel0, rd), 0.0, tMax);\n      if(length(rel0 - rd*tc) > bnd.w + BLOOM_REACH) continue;\n\n      for(int i = 0; i < GROUPSPAN; i++){\n        int j = k*GROUPSPAN + i;\n        vec3 a = spineCell(j, s).xyz;\n        vec3 b = spineCell(j + 1, s).xyz;\n        for(int q = 0; q < 2; q++){\n          vec3 pt = mix(a, b, 0.25 + 0.5*float(q));\n          vec3 rel = pt - ro;\n          float t = clamp(dot(rel, rd), 0.0, tMax);\n          float d = length(rel - rd*t);\n          w += radiusAt(float(j)/float(SEGS))/(0.005 + 6.5*d*d);\n        }\n      }\n    }\n    sum += snakeColor(s)*w;\n  }\n  return sum*0.0055;\n}\n\n// The scale lattice, in (angle around the body, arc length along it). The ring\n// count is an integer so the pattern closes on itself at the atan seam, and the\n// half-step row offset gives the staggered overlap real scales have.\nfloat scaleField(float ang01, float arc, out vec2 grad, out float curve){\n  vec2 q = vec2(ang01*SCALE_RINGS, arc*SCALE_PITCH);\n  q.x += 0.5*floor(q.y);\n  vec2 f = q - floor(q) - 0.5;\n  vec2 w = vec2(f.x, f.y*1.30);\n  float d = length(w);\n  grad = w/max(d, 1e-4);\n  curve = smoothstep(0.0, 0.46, d);\n  return smoothstep(0.50, 0.20, d);\n}\n\nvoid mainImage(){\n  vec2 uv = (gl_FragCoord.xy - 0.5*RENDERSIZE)/min(RENDERSIZE.x, RENDERSIZE.y);\n  float t = TIME*speed;\n\n  // Camera. A slow drift plus a slight rise keeps the flock readable from\n  // changing angles without ever cutting away.\n  // The camera frames the flock rather than the world origin: three free\n  // swimmers spread and gather by a factor of two or more, so a fixed orbit\n  // alternates between one snake filling the frame and all three as specks.\n  // Both the look-at and the distance are filtered in the heads pass.\n  vec4 cam = headCell(0, 3);\n  float dolly = cam.w > 0.5 ? cam.w : 4.2;\n  float ca = t*0.085 + 0.7;\n  float cy = 0.62 + 0.42*sin(t*0.061);\n  vec3 ta = cam.xyz + vec3(0.0, 0.05*sin(t*0.09), 0.0);\n  vec3 ro = ta + vec3(sin(ca)*dolly, cy, cos(ca)*dolly);\n  vec3 fw = normalize(ta - ro);\n  vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0)));\n  vec3 up = cross(rt, fw);\n  vec3 rd = normalize(fw*1.55 + rt*uv.x + up*uv.y);\n\n  vec3 background = mix(vec3(0.012, 0.014, 0.026), vec3(0.030, 0.026, 0.052),\n                        smoothstep(-0.6, 0.7, rd.y));\n  // A dead-flat void reads as a missing background rather than as depth. Two\n  // crossed low-frequency waves are enough to give it structure at a level the\n  // eye reads but does not resolve.\n  float haze = sin(rd.x*3.1 + rd.y*2.3)*sin(rd.z*2.7 - rd.y*1.9);\n  background += vec3(0.012, 0.013, 0.024)*(0.5 + 0.5*haze);\n  background *= 1.0 - 0.35*smoothstep(0.35, 1.05, length(uv));\n\n  vec3 colour = background;\n  float tHit = -1.0;\n  float hitId = -1.0;\n\n  vec2 span = sceneSpan(ro, rd);\n  if(span.x < span.y){\n    float tt = span.x;\n    float tEnd = min(span.y, 12.0);\n    for(int i = 0; i < 96; i++){\n      vec3 p = ro + rd*tt;\n      float id;\n      float d = mapScene(p, id);\n      // Widening the hit threshold with distance keeps the silhouettes from\n      // shimmering without softening the near geometry.\n      if(d < 0.0009 + 0.0006*tt){ tHit = tt; hitId = id; break; }\n      tt += d;\n      if(tt > tEnd) break;\n    }\n  }\n\n  if(tHit > 0.0 && hitId >= 0.0){\n    // The whole part is the snake, the half carries the tongue flag.\n    int s = int(floor(hitId + 0.001));\n    float tongueHit = fract(hitId + 0.001) > 0.25 ? 1.0 : 0.0;\n    vec3 p = ro + rd*tHit;\n    vec3 n = sceneNormal(p, 0.0016 + 0.0009*tHit);\n    vec3 base = snakeColor(s);\n\n    float arc;\n    vec3 tangent, axisU, axisV;\n    surfaceFrame(p, s, arc, tangent, axisU, axisV);\n\n    // Radial direction in the transported frame gives both the scale angle and\n    // which side of the animal we are looking at. On the snout and the tail tip\n    // the normal turns to face along the body, the radial component collapses,\n    // and the angle it yields is noise - which is exactly where the eye and the\n    // belly were smearing into a dark blot. Keep the length as a confidence and\n    // fade every angular feature out with it.\n    vec3 radRaw = n - tangent*dot(n, tangent);\n    float radial = length(radRaw);\n    vec3 rad = radial > 1e-4 ? radRaw/radial : axisU;\n    float frameConf = smoothstep(0.18, 0.62, radial);\n    float cu = dot(rad, axisU);\n    float cv = dot(rad, axisV);\n    float ang01 = atan(cv, cu)/TAU + 0.5;\n\n    vec2 grad;\n    float scaleCurve;\n    float scaleMask = scaleField(ang01, arc, grad, scaleCurve);\n\n    // Two places the body lattice must not be drawn. On a cap the angle sweeps a\n    // full turn across a few pixels and the rings converge into a starburst; and\n    // on the head the lattice is simply wrong - a snake's skull is covered in\n    // large smooth scutes, not in the fine rows that run down its body.\n    // Detail level-of-detail. A scale is about 1/SCALE_PITCH across in world\n    // units; once that projects to fewer than a couple of pixels the lattice is\n    // past Nyquist and every frame samples it at a slightly different phase,\n    // which crawls. Fading it out by projected size costs nothing at the sizes\n    // where the detail is actually legible and removes the shimmer on distant\n    // animals and in small previews entirely.\n    float scalePixels = (min(RENDERSIZE.x, RENDERSIZE.y)*1.55)/(SCALE_PITCH*max(tHit, 0.001));\n    float detailLod = smoothstep(1.4, 4.0, scalePixels);\n    float scaleWeight = frameConf*smoothstep(0.050, 0.150, arc)*detailLod;\n    scaleMask = mix(0.55, scaleMask, scaleWeight);\n    scaleCurve = scaleCurve*scaleWeight;\n\n    // Dome each scale, then crease the join. Perturbing only at the cell\n    // boundary - which is the obvious thing to do - engraves a lattice of\n    // grooves and the animal reads as wireframe rather than as overlapping\n    // plates. A real scale is a shallow dome, so the normal has to tilt\n    // progressively across the whole cell, proportional to the distance from\n    // its centre, with the crease as a much smaller term on top.\n    float rim = 4.0*scaleMask*(1.0 - scaleMask);\n    vec3 bitan = normalize(cross(n, tangent));\n    vec3 bump = bitan*grad.x + tangent*grad.y;\n    n = normalize(n - bump*((scaleCurve*0.40 + rim*0.20)*scaleDepth));\n\n    float u = clamp(arc/max(bodyLength, 1e-3), 0.0, 1.0);\n\n    // Belly. cu is signed against the transported \"up\", so the underside picks\n    // up pale plates and the dorsal keeps the saturated colour. The band has to\n    // stay narrow: at a wide threshold the cream wraps around far enough to be\n    // the dominant tone from most angles and the whole animal goes chalky,\n    // which is what the first render did to all three of them.\n    float belly = smoothstep(-0.42, -0.95, cu)*frameConf;\n    vec3 dorsal = base*(0.55 + 0.45*scaleMask);\n    vec3 vent = mix(vec3(0.52, 0.46, 0.34), base, 0.58)*(0.70 + 0.30*scaleMask);\n    vec3 albedo = mix(dorsal, vent, belly*0.90);\n\n    // Dorsal saddles. Two incommensurate frequencies rather than one, so the\n    // blotches never settle into an obvious repeat, and gated to the back so the\n    // flanks stay clean and the belly stays pale - which is how almost every\n    // real snake is marked, and is most of what separates a patterned animal\n    // from a painted tube.\n    float pat = sin(arc*13.0 + float(s)*2.1) + 0.62*sin(arc*29.0 - float(s)*1.3);\n    float saddle = smoothstep(0.10, 1.05, pat);\n    float dorsalness = smoothstep(-0.25, 0.55, cu)*frameConf;\n    albedo *= 1.0 - 0.50*saddle*dorsalness*(1.0 - belly*0.85);\n    // A pale margin where each saddle meets the ground colour.\n    float edge = 4.0*saddle*(1.0 - saddle);\n    albedo += base*edge*dorsalness*0.16;\n    albedo *= 0.90 + 0.17*hash11(floor(arc*SCALE_PITCH) + float(s)*37.0);\n    // Darken the join between plates. The normal bump alone only shows where a\n    // light happens to rake across it; a little occlusion in the groove keeps\n    // the scales legible on the shadow side too.\n    albedo *= 1.0 - 0.30*rim*scaleDepth;\n\n    // Head: a darker cap over the skull and a wet dark eye. The eye sits on the\n    // side of the head (cv near +/-1) and slightly above the midline, so it is\n    // placed against the transported frame rather than against the world.\n    float headMask = 1.0 - smoothstep(0.012, 0.115, arc);\n    albedo *= 1.0 - 0.11*headMask*smoothstep(-0.20, 0.55, cu);\n    float onHead = smoothstep(-0.15, 0.30, cu)*frameConf;\n    float eye = smoothstep(1.0, 0.40, length(vec2((arc - 0.042)/0.017,\n                                                  (abs(cv) - 0.80)/0.30)))*onHead;\n    albedo = mix(albedo, vec3(0.015, 0.012, 0.015), eye*0.95);\n    // A catchlight, placed rather than lit. Without one the dark ellipse reads\n    // as a hole in the head instead of as a wet eye.\n    float glint = smoothstep(1.0, 0.20, length(vec2((arc - 0.037)/0.0060,\n                                                    (abs(cv) - 0.865)/0.115)))*onHead;\n\n    // Tongue: wet dark flesh, no scales, no markings. It borrows the body's\n    // surface frame, which is meaningless out there, so every angular feature\n    // has to be switched off rather than merely recoloured.\n    albedo = mix(albedo, vec3(0.30, 0.018, 0.055), tongueHit);\n    glint *= 1.0 - tongueHit;\n    scaleMask = mix(scaleMask, 0.85, tongueHit);\n\n    vec3 key = normalize(vec3(0.55, 0.78, 0.32));\n    float ndl = max(dot(n, key), 0.0);\n    // Surfaces facing away from the key are already unlit, and the only two\n    // terms `shadow` scales - diffuse and the Blinn specular - are both zero\n    // there anyway, so the shadow march is pure waste on roughly half the\n    // visible surface. Skipping it is exact, not an approximation.\n    float shadow = ndl > 0.0015 ? softShadow(p + n*0.012, key) : 0.0;\n    float occ = ambientOcclusion(p, n);\n\n    vec3 lit = albedo*(ndl*shadow*vec3(1.00, 0.96, 0.90)*1.30);\n\n    // Hemisphere ambient rather than a flat constant, so an unlit belly still\n    // shows which way it is facing.\n    float upness = n.y*0.5 + 0.5;\n    lit += albedo*occ*mix(vec3(0.030, 0.034, 0.052), vec3(0.105, 0.125, 0.180), upness)*1.5;\n\n    // Cool back rim. With a single key and a black void behind them the three\n    // bodies lose their silhouettes wherever they overlap; a dim light from\n    // behind and to the side is what separates them from the dark and from each\n    // other without lifting the whole frame.\n    vec3 backLight = normalize(vec3(-0.45, -0.30, -0.85));\n    lit += albedo*max(dot(n, backLight), 0.0)*vec3(0.24, 0.44, 0.88)*0.55;\n\n    // Mutual coloured bounce. Every snake is a light for the others, which is\n    // the whole reason to make them red, green and blue: the interaction is\n    // visible on the surfaces, not just in the palette.\n    //\n    // This has to be an average over a snake's four group lights, not a sum.\n    // Summed, two neighbours contribute eight lights at up to 0.85 each, which\n    // is a six-fold overbright - and since the neighbours of any red snake are\n    // green and blue, the three primaries reconstruct white. Every body in the\n    // frame turned pale pink. The bounce is meant to tint, not to illuminate.\n    for(int o = 0; o < SNAKES; o++){\n      if(o == s) continue;\n      for(int k = 0; k < GROUPS; k++){\n        vec4 b = boundCell(o, k);\n        vec3 ld = b.xyz - p;\n        float dist = length(ld);\n        ld /= max(dist, 1e-4);\n        float fall = 1.0/(0.20 + dist*dist*3.2);\n        lit += albedo*snakeColor(o)*max(dot(n, ld), 0.0)*fall*(0.45/float(GROUPS));\n      }\n    }\n\n    // Specular, sharpened by the scale mask so the plates read as wet. Kept\n    // deliberately weak: every scale is a dome, so each one presents a range of\n    // normals and some part of nearly every scale catches the highlight. A\n    // strength that looks right on a smooth surface turns the whole animal into\n    // white speckle once the domes are in - which is what actually bleached the\n    // bodies, not the coloured bounce.\n    vec3 hv = normalize(key - rd);\n    float shine = mix(28.0, 150.0, gloss);\n    float spec = pow(max(dot(n, hv), 0.0), shine)*(0.35 + 0.65*scaleMask);\n    lit += vec3(1.0, 0.97, 0.92)*spec*shadow*gloss*0.42;\n\n    // Fresnel rim in the animal's own colour, so silhouettes stay identifiable\n    // where they overlap, with a cool sheen mixed in at the very edge - keratin\n    // scales go silver-blue at grazing angles rather than simply brighter.\n    // Grazing angles get a thin-film hue shift on top of the cool sheen. Kept\n    // to a third of the mix so the animal's identity stays red, green or blue -\n    // the iridescence is a property of the keratin, not a change of species.\n    float fres = pow(1.0 - max(dot(n, -rd), 0.0), 3.4);\n    vec3 sheenHue = 0.5 + 0.5*cos(TAU*(vec3(0.00, 0.33, 0.67) + fres*1.35 + arc*0.9));\n    vec3 sheen = mix(mix(base, vec3(0.55, 0.72, 1.00), fres*0.45), sheenHue, 0.33*gloss);\n    lit += sheen*fres*(0.55 + 0.85*gloss);\n    lit += vec3(0.92, 0.96, 1.00)*glint*0.85;\n\n    // Forward scattering through the thin end of the animal. A tail tapering to\n    // a couple of millimetres of translucent keratin lights up when the key is\n    // behind it, and that single term does more to sell these as bodies with\n    // volume than any amount of extra specular.\n    float thin = 1.0 - smoothstep(0.16, 0.72, radiusAt(u)/max(girth, 1e-4));\n    float through = pow(max(dot(rd, key), 0.0), 3.0);\n    lit += base*through*thin*(0.85 + 0.55*gloss);\n\n    lit *= occ;\n    colour = mix(background, lit, exp(-max(tHit - 2.4, 0.0)*0.16));\n  }\n\n  colour += bloom(ro, rd, tHit > 0.0 ? tHit : 12.0)*glow;\n\n  // Filmic curve then gamma. Working roughly linear up to here is what lets the\n  // three saturated lights sit on top of one another without clipping to white.\n  colour *= 0.92;\n  vec3 a = colour*(2.51*colour + 0.03);\n  vec3 b = colour*(2.43*colour + 0.59) + 0.14;\n  colour = clamp(a/b, 0.0, 1.0);\n  colour = pow(colour, vec3(0.4545));\n\n  // One least-significant bit of noise before quantisation. Most of this frame\n  // is a very dark, very shallow gradient, and eight bits across that range puts\n  // the contour lines wide enough apart to see as bands. Dithering trades them\n  // for noise below the level the eye resolves.\n  float grain = hash11(gl_FragCoord.x*3.7 + gl_FragCoord.y*811.3 + 0.5) - 0.5;\n  colour += grain/255.0;\n\n  gl_FragColor = vec4(colour, 1.0);\n}\n\nvoid main(){\n  if(PASSINDEX == 0){ mainHeads(); return; }\n  if(PASSINDEX == 1){ mainSpine(); return; }\n  if(PASSINDEX == 2){ mainBounds(); return; }\n  mainImage();\n}\n",
    "generator": {
      "model": "claude-opus-5",
      "provider": "anthropic",
      "prompt": null,
      "params": null
    }
  }
}