{
  "license": "CC0-1.0",
  "shader": {
    "id": "svq3h8w7",
    "title": "Third Millennium Ordnance",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop-scaffold1",
    "status": "active",
    "compile_ok": true,
    "render_hash": "003818383c1c0000",
    "tags": [
      "neon",
      "feedback",
      "fireworks",
      "particles",
      "sci-fi",
      "star-polygon"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-08-30T18:04:49.073Z",
    "poster_url": "/posters/svq3h8w7.avif",
    "stats": {
      "slops": 0,
      "views": 116,
      "forks": 0
    },
    "self": "/api/v1/shaders/svq3h8w7",
    "isf": "/*{\n  \"DESCRIPTION\": \"Neon feedback fireworks, from the year 3000. A shell rises, decelerates to a dead stop, hangs -- then opens three times. First a perfect great circle, usually seen edge-on. Then the circle splits into two counter-rotating tilted rings and wires itself together with chords into a star polygon that holds for a beat. Then the whole figure unfolds along its own axis into a drifting helix and lets go. The physics is confidently not ours: sparks hang weightless long after they should have fallen, then fall along curves. Two persistent float targets hold the sky's memory -- a crisp trail and a slow luminous residue -- and both are advected every frame by the bursts' OWN velocity field, so each new shell shoves the wake of the last one around and re-lights whatever is still hanging there. Colour is driven by AGE and by which stage a spark is in, never by brightness, so a trail changes hue along its own lifetime instead of collapsing to one colour.\",\n  \"CREDIT\": \"\",\n  \"CATEGORIES\": [\"neon\", \"feedback\", \"fireworks\", \"particles\", \"sci-fi\"],\n  \"INPUTS\": [\n    { \"NAME\": \"cadence\",   \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.35, \"MAX\": 2.5 },\n    { \"NAME\": \"order\",     \"TYPE\": \"long\",  \"DEFAULT\": 5,    \"MIN\": 1,    \"MAX\": 7   },\n    { \"NAME\": \"wireframe\", \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 1.5 },\n    { \"NAME\": \"gravity\",   \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.5 },\n    { \"NAME\": \"hangtime\",  \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.2,  \"MAX\": 2.5 },\n    { \"NAME\": \"memory\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.25, \"MAX\": 3.0 },\n    { \"NAME\": \"residue\",   \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.5 },\n    { \"NAME\": \"wake\",      \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.5 },\n    { \"NAME\": \"huedrift\",  \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.0 },\n    { \"NAME\": \"aether\",    \"TYPE\": \"float\", \"DEFAULT\": 0.45, \"MIN\": 0.0,  \"MAX\": 1.0 },\n    { \"NAME\": \"exposure\",  \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.4,  \"MAX\": 2.5 }\n  ],\n  \"PASSES\": [\n    { \"TARGET\": \"sky\",  \"PERSISTENT\": true, \"FLOAT\": true },\n    { \"TARGET\": \"haze\", \"PERSISTENT\": true, \"FLOAT\": true },\n    { }\n  ]\n}*/\n\n#ifdef GL_FRAGMENT_PRECISION_HIGH\nprecision highp float;\n#endif\n\n// ---------------------------------------------------------------------------\n// pass 0 -> \"sky\"   RGBA16F persistent.  rgb = CRISP trail (fast decay ~0.5 s).\n// pass 1 -> \"haze\"  RGBA16F persistent.  rgb = SLOW luminous residue (~5 s),\n//                   fed by whatever \"sky\" is showing, advected harder, blurred.\n//                   It is nearly black until a later burst re-lights it.\n// pass 2 -> screen: composite, aether lattice, hue-preserving tone-map.\n// Both buffers carry their own CLOCK in alpha: 1.0 + mod(TIME,1.0).\n//\n// Host facts this is built around (all of them cost somebody a build round):\n//  * FRAMEINDEX is derived from the requested time, so it is never 0 on the\n//    render path -> \"uninitialised\" is detected from alpha instead.\n//  * TIMEDELTA lies on the render path (a constant 1/60 while TIME steps by\n//    t/120) -> the real dt is recovered from the stored clock.\n//  * the clock is stored mod 1 because RGBA16F cannot resolve one frame's\n//    increment on top of a TIME near 100.\n//  * every render target is NEAREST -> advection hand-rolls its bilinear read.\n// ---------------------------------------------------------------------------\n\n#define TAU 6.2831853\n#define NSHELL 6\n#define NP 16\n#define NPf 16.0\n\nfloat h11(float n){ return fract(sin(n*57.31 + 11.7)*43758.5453); }\nvec3  h31(float n){ return fract(sin(n*vec3(127.1,311.7,74.7) + vec3(1.7,9.2,4.3))*43758.5453); }\nfloat h21(vec2 q){ return fract(sin(dot(q, vec2(12.9898,78.233)))*43758.5453); }\n\nfloat gau(float x){ return exp(-x*x); }\nfloat lamp(float d, float w){ float x = d/w; return exp(-x*x); }\n\nfloat sdSeg(vec2 p, vec2 a, vec2 b){\n  vec2 pa = p-a, ba = b-a;\n  float h = clamp(dot(pa,ba)/max(dot(ba,ba), 1e-7), 0.0, 1.0);\n  return length(pa - ba*h);\n}\n\nmat3 rotX(float a){ float c=cos(a), s=sin(a); return mat3(1.,0.,0., 0.,c,s, 0.,-s,c); }\nmat3 rotY(float a){ float c=cos(a), s=sin(a); return mat3(c,0.,-s, 0.,1.,0., s,0.,c); }\n\n// Cohesive neon ramp keyed to a hue PHASE. Saturation is pushed hard so that\n// two bursts of different age never average into grey.\nvec3 neon(float h){\n  vec3 c = 0.5 + 0.5*cos(TAU*(h + vec3(0.0, 0.33, 0.67)));\n  c = c*c*(3.0-2.0*c);\n  return c*vec3(1.02, 0.96, 1.08);\n}\n\n// --------------------------- per-shell globals -----------------------------\nvec2  gC;      // burst centre, p-units\nmat3  gM;      // figure orientation\nfloat gHueS;\nfloat gTR, gT0, gT1, gT2, gLife;\nfloat gV0, gDrag, gSpin, gTiltB, gGrav, gHang, gSwirl;\nfloat gFire, gW, gDim, gRmax, gNP;\n\n// Fills the globals for staggered shell slot s; returns that shell's age.\nfloat shellSetup(float s){\n  float P  = 1.30/max(cadence, 0.05);\n  float sl = floor(TIME/P) - s;\n  vec3  a  = h31(sl + 11.0);\n  vec3  b  = h31(sl + 57.0);\n  float c1 = h11(sl + 3.0);\n\n  gFire = step(0.18, c1);                       // some slots stay dark\n  float t0 = sl*P + a.x*P*0.5;\n\n  float ar = min(RENDERSIZE.x/RENDERSIZE.y, 1.9);\n  // sizes biased small so the sky has DEPTH: many far tight bursts, and only\n  // occasionally one that owns the frame.\n  gRmax = 0.048 + b.y*b.y*0.150;\n  // Keep the whole figure in frame: the margin has to grow with the burst, or\n  // the biggest and most structured shells are the ones that get cropped.\n  float m  = 0.17 + 0.90*gRmax;\n  float hx = max(0.5*ar - m, 0.04);\n  float hy = max(0.5     - m, 0.04);\n  gC    = vec2((a.y-0.5)*2.0*hx, mix(-hy, hy, 0.30 + 0.62*b.x));\n  gDrag = 2.0 + b.z*1.4;\n  gV0   = gRmax*gDrag;\n  gW    = 0.0024 + 0.0085*gRmax;                // far bursts draw thinner\n  // Brightness falls off with the SQUARE of the apparent size, like distance\n  // does. A linear falloff leaves far shells bright enough that their own\n  // trail fills their (small) disc solid, and they read as flat coloured\n  // balls instead of faint sparks a long way off.\n  gDim  = 0.08 + 21.7*gRmax*gRmax;\n\n  gSpin  = (a.z-0.5)*3.2;\n  gTiltB = 0.35 + b.x*0.75;\n  gGrav  = gravity*(0.020 + 0.030*a.x);\n  gHang  = hangtime*(0.65 + 0.70*b.z);\n  gSwirl = (c1-0.5)*0.55;\n\n  float hidx = floor(fract(a.z*13.7)*4.0);\n  gHueS = fract(0.55 + hidx*0.25 + (a.y-0.5)*0.05);\n\n  gTR   = 0.75 + a.x*0.35;\n  gT0   = gTR + 0.22 + b.x*0.22;\n  gT1   = gT0 + 0.85 + b.y*0.45;\n  gT2   = gT1 + 0.90 + b.z*0.50;\n  gLife = gT2 + 2.30;\n  // Only the big near shells run the full three-act. A distant one is too far\n  // away to resolve a lattice, so it stays a rotating ring and dies -- which\n  // makes the wire-up a real event rather than something that happens\n  // constantly all over the sky.\n  float big = step(0.105, gRmax);\n  gT1 = mix(300.0, gT1, big);\n  gT2 = mix(300.0, gT2, big);\n  // A far shell also carries FEWER sparks: at that radius 16 of them are closer\n  // together than their own stroke width and merge into one saturated disc.\n  gNP = mix(8.0, NPf, big);\n\n  float age = TIME - t0;\n  gM = rotY(a.z*TAU + 0.35*age)*rotX(0.25 + b.z*1.05);\n  return age;\n}\n\n// Screen position of spark idx of the current shell at absolute age.\nvec2 sparkScreen(float idx, float age, out float bri){\n  float te = max(age - gT0, 0.0);\n  float w1 = smoothstep(gT1, gT1+0.38, age);\n  float w2 = smoothstep(gT2, gT2+0.50, age);\n  float rr = gV0*(1.0 - exp(-gDrag*te))/gDrag;\n\n  float a  = TAU*idx/gNP;\n  float ca = cos(a), sa = sin(a);\n\n  // A -- a great circle. Too perfect to be an explosion, which is the point.\n  vec3 pA = vec3(ca, sa, 0.0)*rr;\n\n  // B -- it splits into two counter-rotating tilted rings.\n  float side = 1.0 - 2.0*mod(idx, 2.0);\n  float bb = a + gSpin*te*side;\n  float ct = cos(gTiltB), st = sin(gTiltB);\n  vec3 pB = vec3(cos(bb), sin(bb)*ct, sin(bb)*st*side)*rr*1.06;\n\n  // C -- and unfolds along its own axis instead of radially.\n  float u  = idx/gNP - 0.5 + 0.5/gNP;\n  float gg = a*0.5 + 2.4*te;\n  // radii are staggered per spark: a helix whose sparks share one radius packs\n  // them all into a few texels and clips to a flat disc\n  float rc = rr*(0.34 + 0.30*fract(idx*0.618));\n  vec3 pC = vec3(cos(gg), sin(gg), 0.0)*rc + vec3(0.0, 0.0, u*rr*2.8);\n\n  vec3 wp = gM*mix(mix(pA, pB, w1), pC, w2);\n\n  // Gravity that is not ours: a dead weightless hang, then a curved fall.\n  float tf = max(te - gHang, 0.0);\n  wp.y -= gGrav*tf*tf;\n  wp.x += gSwirl*sin(1.9*te + a)*tf*0.6;\n\n  float pz = 1.0/(1.0 - 0.32*clamp(wp.z, -2.0, 1.4));\n  bri = pz*pz;\n  return gC + wp.xy*pz;\n}\n\n// --------- the sky's velocity field: the bursts' own expanding fronts -------\nvec2 velField(vec2 p){\n  // a slow thermal so the sky breathes even when nothing is firing\n  vec2 v = vec2(0.012*sin(p.y*3.1 + TIME*0.13), 0.010 + 0.008*cos(p.x*2.7 - TIME*0.11));\n  for (int s=0; s<NSHELL; s++){\n    float age = shellSetup(float(s));\n    if (gFire < 0.5) continue;\n    if (age < gT0 || age > gLife) continue;\n    float te = age - gT0;\n    float rr = gV0*(1.0 - exp(-gDrag*te))/gDrag;\n    vec2  dv = p - gC;\n    float rd = length(dv) + 1e-4;\n    // the pressure front, plus the slower spark front behind it\n    // Keep both fronts NARROW and short-lived. A wide or long-lived tangential\n    // push winds the residue into a permanent vortex within a few seconds --\n    // the classic way feedback turns to soup.\n    float q1 = (rd - (0.20 + 1.2*gRmax)*(1.0 - exp(-2.6*te)))/0.10;\n    float q2 = (rd - rr)/0.09;\n    float en = exp(-te*1.6);\n    float sp = gV0*exp(-gDrag*te) + 0.35*en;\n    vec2  rn = dv/rd;\n    float ff = 0.28*exp(-q1*q1)*exp(-te*3.2) + exp(-q2*q2)*en;\n    // rn is a SINGULARITY at the burst centre: an arbitrary direction at full\n    // magnitude. Left alone it blows the bright hang point outward along every\n    // direction at once and, because the trail blends with max(), the buffer\n    // keeps the whole history -- which is a solid filled disc, not a trail.\n    // Outflow must vanish at r = 0 by symmetry anyway.\n    ff *= smoothstep(0.0, 0.05, rd);\n    v += (rn*sp*0.30 + vec2(-rn.y, rn.x)*sp*0.05*sign(gSpin))*ff;\n  }\n  return v;\n}\n\n// ------------------------- this frame's emission ---------------------------\nvoid emit(vec2 p, float dt, out vec3 core, out vec3 halo){\n  core = vec3(0.0); halo = vec3(0.0);\n  float KK = float(order);\n\n  for (int s=0; s<NSHELL; s++){\n    float age = shellSetup(float(s));\n    if (gFire < 0.5) continue;\n    if (age < 0.0 || age > gLife) continue;\n\n    float te = max(age - gT0, 0.0);\n    float rr = gV0*(1.0 - exp(-gDrag*te))/gDrag;\n    vec2  dv = p - gC;\n    float rd = length(dv) + 1e-4;\n    vec3  hb = neon(gHueS);\n\n    // staging flashes -- also what re-lights the residue in pass 2\n    float fl = gau((age-gT0)/0.10) + 0.80*gau((age-gT1)/0.12) + 0.70*gau((age-gT2)/0.15);\n    // hot and SMALL plus a wide faint lobe. One broad bright lobe clips to a\n    // flat featureless disc at the instant of the break.\n    halo += hb*fl*gDim*(0.60/(1.0 + 1600.0*rd*rd) + 0.10/(1.0 + 190.0*rd*rd));\n\n    // NOTE: there is deliberately NO emissive pressure ring here. An expanding\n    // ring drawn into an accumulating buffer is max-blended in at every radius\n    // it crosses, so it never reads as a moving ring: it leaves a filled disc\n    // while it is small and a hanging soap bubble once it is big, and both own\n    // the frame. The pressure front survives where it actually earns its keep,\n    // in velField() -- shoving the residue outward without drawing anything.\n\n\n    // ------------------------------ rise + hang ------------------------------\n    if (age < gT0){\n      float u  = clamp(age/gTR, 0.0, 1.0);\n      float u0 = clamp((age - dt*1.15)/gTR, 0.0, 1.0);\n      vec2  lp = vec2(gC.x*0.92, -0.70);\n      vec2  A  = mix(lp, gC, 1.0-(1.0-u0)*(1.0-u0));\n      vec2  B  = mix(lp, gC, 1.0-(1.0-u )*(1.0-u ));\n      // Deposit the ascent DIM. The residue inherits whatever brightness the\n      // head had when it passed, so a bright ascent segment leaves a hard laser\n      // stripe across the whole frame that the wake then smears sideways.\n      float d  = sdSeg(p, A, B);\n      float ld = 0.45 + 0.75*gDim;\n      core += hb*ld*0.75*lamp(d, 0.0034);\n      halo += hb*ld*0.05*lamp(d, 0.014);\n      core += hb*ld*1.0*(1.0-u*0.5)*lamp(length(p-B), 0.0042);\n      float hh = smoothstep(gTR-0.10, gT0, age);\n      float dh = length(p - gC);\n      core += hb*ld*(0.2 + 1.25*hh)*lamp(dh, 0.0055);\n      halo += hb*hh*0.30*gDim*lamp(dh, 0.016);\n      continue;\n    }\n\n    // cheap bound: past this radius the spark loop has nothing to add\n    if (rd > rr*1.55 + 0.10) continue;\n\n    float w1   = smoothstep(gT1, gT1+0.38, age);\n    float w2   = smoothstep(gT2, gT2+0.50, age);\n    // keyed off gLife, NOT gT2 -- a shell that never reaches stage B has gT2\n    // parked past the end of time and a gT2-keyed fade would blank it entirely\n    float fade = 1.0 - smoothstep(gLife-1.60, gLife, age);\n    // Hue is driven by AGE and by which stage we are in -- never by density.\n    float hA   = gHueS + huedrift*(0.030*min(te,3.0) + 0.055*w1 + 0.095*w2);\n    vec3  cA   = neon(hA);\n    vec3  cB   = neon(hA + 0.085);\n    // The lattice is a MOMENT, not the whole life: it wires up at the second\n    // break, holds, and is gone by the time the figure unfolds.\n    float chord = w1*(1.0 - w2)*wireframe*(1.0 + 1.4*gau((age-gT1-0.30)/0.45));\n\n    // Sparks resolve out of the fireball rather than existing from frame one:\n    // at the break they are all coincident, and drawn at full strength they sum\n    // to ~40 in one texel, which clips to a flat featureless disc.\n    float spread = smoothstep(0.18, 0.55, 1.0 - exp(-gDrag*te));\n    vec3 sc = vec3(0.0), sh = vec3(0.0);\n\n    for (int i=0; i<NP; i++){\n      float fi = float(i);\n      if (fi >= gNP) break;      // also keeps the driver from fully unrolling\n      float b1, b0, b2;\n      vec2  P1 = sparkScreen(fi, age, b1);\n      vec2  P0 = sparkScreen(fi, age - dt*1.15, b0);\n      float d  = sdSeg(p, P0, P1);\n      vec3  cc = mix(cA, cB, fract(fi*0.618));\n      float tw = 0.72 + 0.45*sin(TIME*9.0 + fi*7.31 + gHueS*61.0);\n      float amp = min(b1, 2.2)*fade*gDim;\n      sc += cc*amp*(1.10*lamp(d, gW) + 0.26*lamp(d, gW*3.1));\n      sc += cc*amp*tw*1.35*lamp(length(p-P1), gW*0.85);   // the glittering head\n      // The wide halo is what FILLS a burst's swept area in the trail buffer.\n      // On a far shell it is fat compared with the burst's own radius, so the\n      // whole thing accumulates into one solid ball. Near shells only.\n      sh += cc*amp*0.15*smoothstep(0.05, 0.15, gRmax)*lamp(d, gW*7.5);\n      if (chord > 0.012){\n        vec2  P2 = sparkScreen(mod(fi + KK, gNP), age, b2);\n        float d2 = sdSeg(p, P1, P2);\n        sc += cc*amp*chord*(0.26*lamp(d2, gW*0.90));\n        sh += cc*amp*chord*0.06*lamp(d2, gW*3.8);\n      }\n    }\n    // Per-shell soft knee, applied BEFORE the sum ever reaches the display\n    // tone-map. The display knee is 1-exp(-l), which is already flat white by\n    // l=3, so a sum of coincident samples is past the shoulder before the\n    // tone-map sees it. This one has a hard ceiling (T+S) and leaves anything\n    // below T untouched, so single sparks keep their brightness and only piles\n    // get compressed. A ring seen edge-on stacks every spark onto one line and\n    // needs this just as much as the fireball at the break does.\n    float m  = max(max(sc.r, sc.g), sc.b);\n    float e  = max(m - 0.95, 0.0);\n    core += sc*spread*((min(m, 0.95) + e/(1.0 + e/1.00))/max(m, 1e-4));\n    float mh = max(max(sh.r, sh.g), sh.b);\n    float eh = max(mh - 0.55, 0.0);\n    halo += sh*spread*((min(mh, 0.55) + eh/(1.0 + eh/0.70))/max(mh, 1e-4));\n  }\n}\n\n// -------- what is burning right now, as a light source for the residue -----\nvec3 relight(vec2 p){\n  vec3 L = vec3(0.0);\n  for (int s=0; s<NSHELL; s++){\n    float age = shellSetup(float(s));\n    if (gFire < 0.5) continue;\n    if (age < 0.0 || age > gLife) continue;\n    float fl = 0.70*gau((age-gT0)/0.16) + 0.55*gau((age-gT1)/0.20) + 0.50*gau((age-gT2)/0.24);\n    fl += 0.26*smoothstep(gT0, gT0+0.5, age)*(1.0 - smoothstep(gLife-1.6, gLife, age));\n    float d = length(p - gC);\n    L += neon(gHueS)*fl*gDim/(1.0 + 20.0*d*d);\n  }\n  return L;\n}\n\n// hand-rolled bilinear -- every render target is allocated NEAREST\nvec3 bilSky(vec2 uv){\n  vec2 ts = 1.0/RENDERSIZE, lo = ts*0.5, hi = 1.0 - ts*0.5;\n  vec2 px = uv*RENDERSIZE - 0.5;\n  vec2 f  = fract(px);\n  vec2 b  = (floor(px) + 0.5)*ts;\n  vec3 a00 = IMG_NORM_PIXEL(sky, clamp(b,                 lo, hi)).rgb;\n  vec3 a10 = IMG_NORM_PIXEL(sky, clamp(b+vec2(ts.x,0.0),  lo, hi)).rgb;\n  vec3 a01 = IMG_NORM_PIXEL(sky, clamp(b+vec2(0.0,ts.y),  lo, hi)).rgb;\n  vec3 a11 = IMG_NORM_PIXEL(sky, clamp(b+ts,              lo, hi)).rgb;\n  return mix(mix(a00,a10,f.x), mix(a01,a11,f.x), f.y);\n}\nvec3 bilHaze(vec2 uv){\n  vec2 ts = 1.0/RENDERSIZE, lo = ts*0.5, hi = 1.0 - ts*0.5;\n  vec2 px = uv*RENDERSIZE - 0.5;\n  vec2 f  = fract(px);\n  vec2 b  = (floor(px) + 0.5)*ts;\n  vec3 a00 = IMG_NORM_PIXEL(haze, clamp(b,                lo, hi)).rgb;\n  vec3 a10 = IMG_NORM_PIXEL(haze, clamp(b+vec2(ts.x,0.0), lo, hi)).rgb;\n  vec3 a01 = IMG_NORM_PIXEL(haze, clamp(b+vec2(0.0,ts.y), lo, hi)).rgb;\n  vec3 a11 = IMG_NORM_PIXEL(haze, clamp(b+ts,             lo, hi)).rgb;\n  return mix(mix(a00,a10,f.x), mix(a01,a11,f.x), f.y);\n}\n\n// a standing lattice in the air. Invisible until residue drifts through it.\nfloat aetherField(vec2 q){\n  float k = 95.0;\n  return ( cos(dot(q, vec2( 1.0, 0.0   ))*k)\n         + cos(dot(q, vec2( 0.5, 0.8660))*k)\n         + cos(dot(q, vec2(-0.5, 0.8660))*k) )/3.0;\n}\n\n// recover the true timestep from a clock stored in a buffer's alpha\nfloat clockDt(float pa, float cur, out float fresh){\n  fresh = step(pa, 0.5);                       // 1.0 when the buffer is cleared\n  float dt = mod(cur - (pa - 1.0) + 1.0, 1.0);\n  return mix(clamp(dt, 0.0004, 0.30), 1.0/60.0, fresh);\n}\n\nvoid main(){\n  vec2 uv  = isf_FragNormCoord;\n  vec2 p   = (gl_FragCoord.xy - 0.5*RENDERSIZE)/RENDERSIZE.y;\n  vec2 asp = RENDERSIZE.y/RENDERSIZE;\n  float cur = mod(TIME, 1.0);\n\n  // ------------------------------- pass 0: trail --------------------------\n  if (PASSINDEX == 0){\n    float fresh;\n    float dt = clockDt(IMG_THIS_PIXEL(sky).a, cur, fresh);\n\n    vec3 core, halo;\n    emit(p, dt, core, halo);\n    vec2 vel = velField(p);\n\n    // CFL-ish guard: never move the field more than a few texels in one step,\n    // so a long frame (or the poster path's fat dt) cannot teleport it.\n    vec2 duv = -vel*dt*wake*asp;\n    duv *= min(1.0, 4.0/max(length(duv*RENDERSIZE), 1e-5));\n    vec3 prev = bilSky(clamp(uv + duv, vec2(0.0), vec2(1.0)));\n\n    // Multiplicative decay only shrinks a smear; the subtractive floor is what\n    // ERODES it, so faint wash reaches true black instead of hanging forever.\n    float lum = max(max(prev.r, prev.g), prev.b);\n    prev *= exp(-dt*(1.90/max(memory,0.05) + 0.30*lum));\n    prev  = max(prev - dt*0.34, vec3(0.0));\n\n    vec3 outc = max(prev, core) + halo*dt*0.55;\n    outc = mix(outc, core, fresh);\n    vec3 hot = max(outc - 1.5, vec3(0.0));\n    outc = min(outc, vec3(1.5)) + hot/(1.0 + 0.5*hot);\n    gl_FragColor = vec4(clamp(outc, 0.0, 8.0), 1.0 + cur);\n    return;\n  }\n\n  // ------------------------ pass 1: the slow residue ----------------------\n  if (PASSINDEX == 1){\n    float fresh;\n    float dt = clockDt(IMG_THIS_PIXEL(haze).a, cur, fresh);\n\n    // the residue is free-floating, so it rides the wake harder than the trail\n    vec2 vel = velField(p);\n    vec2 dvv = -vel*dt*wake*1.7*asp;\n    dvv *= min(1.0, 6.0/max(length(dvv*RENDERSIZE), 1e-5));\n    vec2 src = clamp(uv + dvv, vec2(0.0), vec2(1.0));\n    vec3 hz  = bilHaze(src);\n\n    // soften it: the residue is a cloud, not a wire\n    vec2 ts = 2.2/RENDERSIZE;\n    vec3 bl = ( IMG_NORM_PIXEL(haze, clamp(src+vec2( ts.x,0.0), vec2(0.0), vec2(1.0))).rgb\n              + IMG_NORM_PIXEL(haze, clamp(src+vec2(-ts.x,0.0), vec2(0.0), vec2(1.0))).rgb\n              + IMG_NORM_PIXEL(haze, clamp(src+vec2(0.0, ts.y), vec2(0.0), vec2(1.0))).rgb\n              + IMG_NORM_PIXEL(haze, clamp(src+vec2(0.0,-ts.y), vec2(0.0), vec2(1.0))).rgb )*0.25;\n    hz = mix(hz, bl, clamp(dt*10.0, 0.0, 0.30));\n\n    hz *= exp(-dt*0.55);\n    hz  = max(hz - dt*0.012, vec3(0.0));\n    hz += IMG_THIS_PIXEL(sky).rgb*dt*0.45;      // fed by whatever just burned\n\n    hz = mix(hz, vec3(0.0), fresh);\n    vec3 hot = max(hz - 1.2, vec3(0.0));\n    hz = min(hz, vec3(1.2)) + hot/(1.0 + 0.8*hot);\n    gl_FragColor = vec4(clamp(hz, 0.0, 6.0), 1.0 + cur);\n    return;\n  }\n\n  // ------------------------------ pass 2: screen --------------------------\n  vec3 tr = IMG_THIS_PIXEL(sky).rgb;\n  vec3 hz = IMG_THIS_PIXEL(haze).rgb;\n  vec3 L  = relight(p);\n\n  float lat = mix(1.0, 0.36 + 1.32*pow(0.5 + 0.5*aetherField(p), 1.3), aether);\n\n  // Residue accumulated over many bursts averages toward grey. Pull the\n  // saturation back up and tint what is NOT currently re-lit cold, so the\n  // hanging field reads as cooling plasma rather than smoke.\n  float hzl = dot(hz, vec3(0.30, 0.5, 0.2));\n  hz = max(mix(vec3(hzl), hz, 1.45), vec3(0.0));\n\n  vec3 col = tr*(1.0 + 0.9*L)\n           + hz*residue*(0.26*vec3(0.30,0.55,1.10) + 1.15*L)*lat\n           + L*0.018;\n\n  col += mix(vec3(0.012,0.016,0.034), vec3(0.001,0.002,0.009), smoothstep(0.0,0.8,uv.y));\n\n  col *= 1.50*exposure;\n  col *= 1.0 - 0.50*clamp(pow(length(p)*0.92, 2.4), 0.0, 1.0);\n\n  // hue-preserving tone-map: only the very hottest cores desaturate to white\n  float l = max(max(col.r, col.g), col.b);\n  col = mix(col, vec3(l), smoothstep(3.0, 12.0, l)*0.50);\n  col *= (1.0 - exp(-l))/max(l, 1e-4);\n  col = pow(max(col, 0.0), vec3(0.88));\n  col += (h21(gl_FragCoord.xy) - 0.5)*(1.6/255.0);\n\n  gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}\n",
    "generator": {
      "model": "claude-opus-5",
      "provider": "anthropic",
      "prompt": null,
      "params": null
    }
  }
}