{
  "license": "CC0-1.0",
  "shader": {
    "id": "d621g6zo",
    "title": "Portal Path v1",
    "license": "CC0-1.0",
    "provenance_type": "user_upload",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "render_hash": "0870f89c9bfffe7f",
    "tags": [
      "3D",
      "Raymarching",
      "Particles",
      "environment"
    ],
    "parents": [],
    "author": "jab6zg50vals",
    "author_name": "ogcraig",
    "author_url": "/u/ogcraig",
    "created_at": "2026-08-30T21:47:54.815Z",
    "poster_url": "/posters/d621g6zo.avif",
    "stats": {
      "slops": 1,
      "views": 180,
      "forks": 0
    },
    "self": "/api/v1/shaders/d621g6zo",
    "isf": "/*{\n\"DESCRIPTION\":\"Recursive portal between a tropical lagoon and a rain-slicked neon city. Seamless 16s loop.\",\n\"CREDIT\":\"Craig\",\n\"ISFVSN\":\"2\",\n\"CATEGORIES\":[\"Generator\",\"3D\",\"Raymarching\"],\n\"INPUTS\":[\n{\"NAME\":\"speed\",\"LABEL\":\"Flight Speed\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"phaseOffset\",\"LABEL\":\"Phase Offset\",\"TYPE\":\"float\",\"DEFAULT\":0.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"detail\",\"LABEL\":\"Scene Detail\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"quality\",\"LABEL\":\"March Quality\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.35,\"MAX\":1.0},\n{\"NAME\":\"neonHue\",\"LABEL\":\"Portal Hue\",\"TYPE\":\"float\",\"DEFAULT\":0.99,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"neonSat\",\"LABEL\":\"Portal Sat\",\"TYPE\":\"float\",\"DEFAULT\":0.94,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"neonGain\",\"LABEL\":\"Portal Gain\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"portalFlash\",\"LABEL\":\"Transit Flash\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"fogTropic\",\"LABEL\":\"Haze Lagoon\",\"TYPE\":\"float\",\"DEFAULT\":0.0058,\"MIN\":0.0,\"MAX\":0.03},\n{\"NAME\":\"fogCity\",\"LABEL\":\"Haze City\",\"TYPE\":\"float\",\"DEFAULT\":0.0092,\"MIN\":0.0,\"MAX\":0.03},\n{\"NAME\":\"volAmt\",\"LABEL\":\"Volumetrics\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":2.5},\n{\"NAME\":\"rainAmt\",\"LABEL\":\"City Rain\",\"TYPE\":\"float\",\"DEFAULT\":0.7,\"MIN\":0.0,\"MAX\":2.0},\n{\"NAME\":\"wetness\",\"LABEL\":\"Street Wetness\",\"TYPE\":\"float\",\"DEFAULT\":0.75,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"moteAmt\",\"LABEL\":\"Lagoon Motes\",\"TYPE\":\"float\",\"DEFAULT\":0.35,\"MIN\":0.0,\"MAX\":2.0},\n{\"NAME\":\"partAmt\",\"LABEL\":\"Fly-By Particles\",\"TYPE\":\"float\",\"DEFAULT\":0.9,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"partSize\",\"LABEL\":\"Particle Size\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.2,\"MAX\":3.0},\n{\"NAME\":\"partDens\",\"LABEL\":\"Particle Density\",\"TYPE\":\"float\",\"DEFAULT\":0.4,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"partStreak\",\"LABEL\":\"Particle Streak\",\"TYPE\":\"float\",\"DEFAULT\":0.35,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"partDrift\",\"LABEL\":\"Particle Drift\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"sunAz\",\"LABEL\":\"Sun Azimuth\",\"TYPE\":\"float\",\"DEFAULT\":0.26,\"MIN\":-1.0,\"MAX\":1.0},\n{\"NAME\":\"sunEl\",\"LABEL\":\"Sun Height\",\"TYPE\":\"float\",\"DEFAULT\":0.22,\"MIN\":0.02,\"MAX\":0.7},\n{\"NAME\":\"lensBase\",\"LABEL\":\"Lens (wide)\",\"TYPE\":\"float\",\"DEFAULT\":1.15,\"MIN\":0.6,\"MAX\":2.5},\n{\"NAME\":\"lensPunch\",\"LABEL\":\"Dolly Punch\",\"TYPE\":\"float\",\"DEFAULT\":1.05,\"MIN\":0.0,\"MAX\":2.0},\n{\"NAME\":\"sway\",\"LABEL\":\"Camera Sway\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":2.5},\n{\"NAME\":\"camHeight\",\"LABEL\":\"Camera Height\",\"TYPE\":\"float\",\"DEFAULT\":2.6,\"MIN\":0.8,\"MAX\":8.0},\n{\"NAME\":\"doReflect\",\"LABEL\":\"Reflections\",\"TYPE\":\"bool\",\"DEFAULT\":true},\n{\"NAME\":\"doShadow\",\"LABEL\":\"Soft Shadows\",\"TYPE\":\"bool\",\"DEFAULT\":true},\n{\"NAME\":\"audioMix\",\"LABEL\":\"Audio Blend\",\"TYPE\":\"float\",\"DEFAULT\":0.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"bassIn\",\"LABEL\":\"Bass In\",\"TYPE\":\"float\",\"DEFAULT\":0.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"midIn\",\"LABEL\":\"Mid In\",\"TYPE\":\"float\",\"DEFAULT\":0.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"highIn\",\"LABEL\":\"High In\",\"TYPE\":\"float\",\"DEFAULT\":0.0,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"bassGain\",\"LABEL\":\"Bass Gain\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"midGain\",\"LABEL\":\"Mid Gain\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"highGain\",\"LABEL\":\"High Gain\",\"TYPE\":\"float\",\"DEFAULT\":1.0,\"MIN\":0.0,\"MAX\":3.0},\n{\"NAME\":\"exposure\",\"LABEL\":\"Exposure\",\"TYPE\":\"float\",\"DEFAULT\":1.06,\"MIN\":0.2,\"MAX\":3.0},\n{\"NAME\":\"gammaAmt\",\"LABEL\":\"Gamma\",\"TYPE\":\"float\",\"DEFAULT\":0.9,\"MIN\":0.5,\"MAX\":1.6},\n{\"NAME\":\"vignette\",\"LABEL\":\"Vignette\",\"TYPE\":\"float\",\"DEFAULT\":0.32,\"MIN\":0.0,\"MAX\":1.0},\n{\"NAME\":\"grain\",\"LABEL\":\"Grain\",\"TYPE\":\"float\",\"DEFAULT\":0.016,\"MIN\":0.0,\"MAX\":0.08}\n]\n}*/\n\n// ============================================================================\n//  NEON // JUNGLE  v7  -  ISF port of the Shadertoy v5 raymarcher\n//\n//  NEW IN v7 - FLY-BY PARTICLE FIELD\n//  ---------------------------------\n//  A world-locked point field the camera actually flies through, as opposed to\n//  the screen-space rain / mote layers which only drift down the frame.\n//\n//   - Points live on planes spaced PSPACE apart in world z. Because the field\n//     is anchored to world space, points sweep outward from the vanishing\n//     point, grow as they approach and streak past the lens - real parallax,\n//     no fake velocity term needed.\n//   - Depth-tested against the primary ray's hit distance (TOUT), so points\n//     correctly disappear behind trunks, towers, rock and the door frame, and\n//     stay visible through an open aperture.\n//   - Each plane knows which world it sits in, so points ahead of a doorway\n//     already carry the NEXT world's colour before you reach the gate: warm\n//     pollen in the lagoon, cool spray / ash in the city.\n//   - Points near a door plane pick up NEON, so the field flares as it passes\n//     through the gate with you.\n//   - ZPER/PSPACE = 40 (whole number), so the field tiles with the 16s loop\n//     for free. Bob and sway use integer cycle counts for the same reason.\n//   - Cost is one hash set per plane per pixel - trivial next to the march.\n//     PSLICES x PSPACE = 60 units of depth, exactly one room.\n//\n//  PORT NOTES (Shadertoy -> ISF)\n//  -----------------------------\n//   iTime            -> TIME\n//   iResolution.xy   -> RENDERSIZE\n//   fragCoord        -> gl_FragCoord.xy\n//   mainImage()      -> main() / gl_FragColor\n//   iChannel0 audio  -> bassIn / midIn / highIn float inputs (see main)\n//   #define quality  -> STEPS stays a compile-time cap, the \"quality\" slider\n//                       breaks the loop early (ES-safe: constant loop bounds)\n//   NEON / SUN       -> promoted from const to globals driven by inputs\n//\n//  THE ENVIRONMENTS (the \"in-between\" of each door)\n//  ------------------------------------------------\n//  LAGOON\n//   - Water is depth-shaded. The channel floor height is sampled per water\n//     pixel, so shallows near the banks go turquoise, the channel goes deep,\n//     and a foam band forms wherever the terrain crosses the waterline.\n//   - Lily-pad rafts on the surface (albedo + normal flatten, zero geometry).\n//   - Understory: shrub / fern clumps along the bank, filling the gap between\n//     the waterline and the palm trunks.\n//   - Hanging vines off the canopy - fine vertical lines that catch the door\n//     light and read strongly against the sky when a door is behind them.\n//   - Rock gets stratification banding and a stronger wet/dry waterline.\n//\n//  CITY\n//   - Towers have podium bases and repeating ledge slabs, so the silhouettes\n//     step instead of being flat extrusions. Rooftop tanks on the crowns.\n//   - The city floor is a STREET: cambered asphalt crowned down the middle and\n//     dipping into gutters at the kerb, raised pavements either side, lane\n//     paint, crosswalks, manholes and drain grates.\n//   - Puddles pool in the gutters and low spots, and only those patches\n//     reflect - so the neon still lands on the ground without the whole world\n//     reading as a canal.\n//   - Lantern cables strung across the street, lamp posts along the kerb.\n//   - Tower facades gained ledge glow bands, a scrolling ticker strip and\n//     blocky sign panels on top of the existing window grid.\n//   - Rain: screen-space streaks plus high-frequency ripple on the water, both\n//     masked to the city world by the ray's final world state.\n//\n//  Seamless loop: ZPER/SPEED = 16.0s at speed 1.0.\n// ============================================================================\n\n#define TAU 6.28318530\n\n// ---- quality caps (loop bounds must stay constant) ------------------------\n#define STEPS    190\n#define RSTEPS   60\n#define SHSTEPS  20\n#define PSLICES  20           // particle planes marched per pixel\n\n// ---- layout ---------------------------------------------------------------\nconst float L     = 60.0;     // door spacing - how deep each world is\nconst float ZPER  = 120.0;    // world content period, even multiple of L\nconst float LOOPT = 16.0;     // loop length in seconds\n\n// the door\nconst float PW    = 2.60;     // aperture half width\nconst float PHH   = 4.40;     // aperture half height\nconst float PCY   = 5.60;     // centre height\nconst float PTUBE = 0.075;\nconst float PCR   = 0.12;\n\n// the particle field\nconst float PSPACE = 3.0;     // world z between particle planes. 120/3 = 40\nconst float PCELL  = 1.75;    // lateral cell size on each plane\n\n// ---- globals --------------------------------------------------------------\nfloat PH   = 0.0;\nfloat BASS = 0.0;\nfloat MIDS = 0.0;\nfloat HIGH = 0.0;\nvec3  NEON = vec3(1.60, 0.10, 0.16);\nvec3  SUN  = vec3(0.2540, 0.2235, 0.9410);\nfloat WOUT = 0.0;             // world the ray ended in, for the overlays\nfloat TOUT = 1e5;             // distance the ray ended at, for particle occlusion\n\n// detail tiers off one slider\nbool  D1 = true;              // ledges, shrubs, strata\nbool  D2 = true;              // cables, lanterns, vines\nbool  D3 = true;              // pylons, lily pads, tower signage\n\n// ---- hashing / noise ------------------------------------------------------\nfloat hash11(float n){ return fract(sin(n)*43758.5453123); }\nfloat hash21(vec2 p){ return fract(sin(dot(p, vec2(27.17,113.71)))*43758.5453); }\nfloat hash3(float x, float y, float z){ return hash11(x + y*57.0 + z*113.0); }\n\nvec3 hsv2rgb(vec3 c){\n    vec3 p = abs(fract(c.xxx + vec3(0.0, 2.0/3.0, 1.0/3.0))*6.0 - 3.0);\n    return c.z*mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y);\n}\n\n// value noise with its lattice index wrapped in z - exactly periodic, free\nfloat vnoiseZP(vec3 x, float per){\n    vec3 p = floor(x), f = fract(x);\n    f = f*f*(3.0-2.0*f);\n    float z0 = mod(p.z, per);\n    float z1 = mod(p.z + 1.0, per);\n    float a = mix(mix(hash3(p.x, p.y,     z0), hash3(p.x+1.0, p.y,     z0), f.x),\n                  mix(hash3(p.x, p.y+1.0, z0), hash3(p.x+1.0, p.y+1.0, z0), f.x), f.y);\n    float b = mix(mix(hash3(p.x, p.y,     z1), hash3(p.x+1.0, p.y,     z1), f.x),\n                  mix(hash3(p.x, p.y+1.0, z1), hash3(p.x+1.0, p.y+1.0, z1), f.x), f.y);\n    return mix(a, b, f.z);\n}\n// s0*ZPER must be a whole number so the lattice tiles. 0.025 -> 3 cells.\nfloat fbmZ2(vec3 p, float s0){\n    float per = s0*ZPER;\n    return 0.65*vnoiseZP(p*s0, per) + 0.35*vnoiseZP(p*s0*2.0, per*2.0);\n}\n// non-periodic - only ever used on ray directions, never on positions\nfloat vnoise(vec3 x){\n    vec3 p = floor(x), f = fract(x);\n    f = f*f*(3.0-2.0*f);\n    float n = p.x + p.y*57.0 + 113.0*p.z;\n    return mix(mix(mix(hash11(n),hash11(n+1.0),f.x),\n                   mix(hash11(n+57.0),hash11(n+58.0),f.x), f.y),\n               mix(mix(hash11(n+113.0),hash11(n+114.0),f.x),\n                   mix(hash11(n+170.0),hash11(n+171.0),f.x), f.y), f.z);\n}\nfloat fbm(vec3 p){\n    float a = 0.5, s = 0.0;\n    for(int i=0;i<4;i++){ s += a*vnoise(p); p *= 2.03; a *= 0.5; }\n    return s;\n}\n\nvec2 U(vec2 a, vec2 b){ return (a.x < b.x) ? a : b; }\n\nfloat sdBox(vec3 p, vec3 b){\n    vec3 d = abs(p) - b;\n    return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));\n}\nfloat sdBox2(vec2 p, vec2 b){\n    vec2 d = abs(p) - b;\n    return min(max(d.x,d.y),0.0) + length(max(d,0.0));\n}\nfloat sdRBox2(vec2 p, vec2 b, float r){ return sdBox2(p, b - r) - r; }\nfloat sdCapsule(vec3 p, vec3 a, vec3 b, float r){\n    vec3 pa = p-a, ba = b-a;\n    float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);\n    return length(pa - ba*h) - r;\n}\nfloat sdEllipsoid(vec3 p, vec3 r){\n    float k0 = length(p/r);\n    return k0*(k0 - 1.0)/max(length(p/(r*r)), 1e-5);\n}\nfloat lineMask(float x, float period, float w){\n    float d = abs(fract(x/period - 0.5) - 0.5)*period;\n    return smoothstep(w, 0.0, d);\n}\nfloat doorZ(float z){ return z - floor(z/L + 0.5)*L; }\n\n// ============================================================================\n//  THE DOOR - a thin emissive tube ring, nothing else\n// ============================================================================\nfloat doorProfile(vec2 q){ return sdRBox2(q - vec2(0.0, PCY), vec2(PW, PHH), PCR); }\n\nvec2 mapDoor(vec3 p){\n    float ring = abs(doorProfile(p.xy)) - PTUBE;\n    return vec2(max(ring, abs(doorZ(p.z)) - PTUBE), 30.0);\n}\nbool insideAperture(vec3 p){ return doorProfile(p.xy) < 0.0; }\n\nfloat doorRingDist(vec3 p){\n    float a = abs(doorProfile(p.xy));\n    float b = doorZ(p.z);\n    return sqrt(a*a + b*b);\n}\n\n// ============================================================================\n//  WORLD A - TROPICAL LAGOON\n//  mats: 1 water, 2 rock, 2.5 bark, 3 foliage, 3.4 understory, 3.8 vine\n// ============================================================================\nfloat bankBase(float ax){ return -3.5 + smoothstep(9.0, 32.0, ax)*13.0; }\n\n// Two-tier heightfield: the coarse landform is always evaluated, the fine\n// detail only within a band of the surface.\nfloat lagoonH(vec3 p){\n    float ax   = abs(p.x);\n    float bank = smoothstep( 9.0,  32.0, ax);\n    float dist = smoothstep(55.0, 150.0, ax);     // distant ridges\n    if(bank <= 0.0) return -3.5;                  // open channel floor\n\n    vec3  q  = vec3(p.x, 0.0, p.z);\n    float n1 = fbmZ2(q, 0.025);\n    float h  = -3.5 + bank*(8.0 + 11.0*n1) + dist*(18.0 + 34.0*n1);\n\n    float k = smoothstep(9.0, 4.0, abs(p.y - h));\n    if(k > 0.001){\n        float n2 = vnoiseZP(q*0.05, 6.0);\n        float rg = 1.0 - abs(2.0*vnoiseZP(q*0.15, 18.0) - 1.0);\n        float n3 = vnoiseZP(q*0.40, 48.0);\n        h += k*(bank*((n2 - 0.5)*5.5 + rg*2.2) + (n3 - 0.5)*0.6);\n    }\n    return h;\n}\n\nvec2 mapTropic(vec3 p){\n    vec2 res = vec2(p.y, 1.0);                     // water plane\n\n    if(p.y > 78.0) return U(res, vec2(p.y - 78.0, 2.0));\n\n    res = U(res, vec2((p.y - lagoonH(p))*0.42, 2.0));\n\n    // one lattice, five jobs: trunks, canopy, islets, understory, vines\n    const vec2 CELL = vec2(20.0, 20.0);            // 120/20 = 6 cells, tiles\n    vec2 uv   = vec2(p.x, p.z)/CELL;\n    vec2 base = floor(uv);\n    vec2 sg   = sign(fract(uv) - 0.5);\n    sg += 1.0 - abs(sg);                           // guard: never 0\n\n    float bestT = 1e5, bestF = 1e5, bestI = 1e5, bestU = 1e5, bestV = 1e5;\n    for(int j=0;j<2;j++)\n    for(int k=0;k<2;k++){\n        vec2 id  = base + vec2(float(j)*sg.x, float(k)*sg.y);\n        vec2 idw = vec2(id.x, mod(id.y, ZPER/CELL.y));\n        float r1 = hash21(idw*1.70 +  1.0);\n        float r2 = hash21(idw*3.10 +  7.0);\n        float r3 = hash21(idw*5.30 + 13.0);\n        float r4 = hash21(idw*7.90 + 23.0);\n\n        float cx = (id.x + 0.5)*CELL.x + (r1-0.5)*8.0;\n        float cz = (id.y + 0.5)*CELL.y + (r2-0.5)*8.0;\n        float ax = abs(cx);\n\n        // --- palms on the bank\n        if(r3 < 0.80 && ax > 14.0 && ax < 46.0){\n            vec3  c   = vec3(cx, bankBase(ax) + 0.5, cz);\n            float H   = 5.0 + 6.0*r1;\n            vec3  top = c + vec3((r2-0.5)*2.4, H, (r3-0.5)*2.4);\n            bestT = min(bestT, sdCapsule(p, c - vec3(0.0,9.0,0.0), top, 0.26 + 0.11*r2));\n            bestF = min(bestF, length((p - top - vec3(0.0,1.2,0.0))*vec3(1.0,1.25,1.0))\n                               - (2.1 + 1.5*r1));\n            bestF = min(bestF, length((p - c - vec3(1.8*(r2-0.5), 0.7, 1.8*(r1-0.5)))\n                                      *vec3(1.0,1.6,1.0)) - (1.4 + 0.9*r3));\n\n            // --- vines off the canopy. thin, vertical, and they read hard\n            //     against a lit door behind them\n            if(D2 && r1 > 0.45){\n                vec3 vt = top + vec3(1.5*(r3-0.5), 0.9, 1.5*(r4-0.5));\n                bestV = min(bestV, sdCapsule(p, vt,\n                            vt - vec3(0.25*(r2-0.5), 3.0 + 4.5*r2, 0.0), 0.070));\n            }\n        }\n\n        // --- understory: shrubs and fern clumps down at the waterline, which\n        //     is where v5 had a bare mud gap between water and trunks\n        if(D1 && r2 < 0.80){\n            float sxp = cx + (r3 - 0.5)*9.0;\n            float axs = abs(sxp);\n            if(axs > 8.0 && axs < 34.0){\n                vec3 c2 = vec3(sxp, bankBase(axs) + 0.35 + 1.1*r4, cz + 8.0*(r4-0.5));\n                bestU = min(bestU, sdEllipsoid(p - c2,\n                            vec3(1.7 + 1.6*r2, 1.1 + 1.5*r1, 1.7 + 1.6*r4)));\n                if(r1 > 0.55){\n                    vec3 c3 = c2 + vec3(3.2*(r1-0.5), -0.35, 3.4*(r2-0.5));\n                    bestU = min(bestU, sdEllipsoid(p - c3,\n                                vec3(1.2 + 1.0*r3, 0.85 + 0.9*r4, 1.2 + 1.0*r1)));\n                }\n            }\n        }\n\n        // --- rock islets standing in the water. these sweep past close to\n        //     the lens and are the strongest near-field parallax cue.\n        if(r4 < 0.55 && ax > 7.0 && ax < 34.0){\n            vec3 ic = vec3(cx, -1.2 + 2.6*r4, cz + 6.0*(r3-0.5));\n            vec3 ir = vec3(1.4 + 2.6*r1, 1.6 + 3.4*r4, 1.4 + 2.6*r2);\n            bestI = min(bestI, sdEllipsoid(p - ic, ir));\n        }\n    }\n    bestF -= (fbmZ2(p, 0.80) - 0.40)*0.65;\n    bestI -= (fbmZ2(p, 0.40) - 0.45)*0.85;\n    bestU -= (fbmZ2(p, 0.70) - 0.42)*0.95;\n\n    res = U(res, vec2(bestT*0.85, 2.5));\n    res = U(res, vec2(bestF*0.62, 3.0));\n    res = U(res, vec2(bestI*0.60, 2.0));\n    res = U(res, vec2(bestU*0.58, 3.4));\n    res = U(res, vec2(bestV*0.85, 3.8));\n    return res;\n}\n\n// ============================================================================\n//  WORLD B - FLOODED NEON CITY\n//  mats: 11 street, 12 tower, 13 sign, 14 skybridge, 15 mast,\n//        16 cable, 17 lamp head, 18 post, 19 pavement\n// ============================================================================\nvec2 rowTowers(vec3 p, float xc, float cz, float seed, float sx, float bw, float hmax){\n    float best = 1e5, bestS = 1e5;\n    float zf  = p.z/cz + 0.5;\n    float zi  = floor(zf);\n    float sgz = sign(fract(zf) - 0.5);\n    sgz += 1.0 - abs(sgz);\n\n    for(int k=0;k<2;k++){\n        float id  = zi + float(k)*sgz;\n        float idw = mod(id, ZPER/cz);\n        float h1 = hash21(vec2(idw*1.13 + seed*7.7, sx*3.3 + seed));\n        float h2 = hash21(vec2(idw*2.71 + seed*3.1, sx*9.1 + seed*2.0));\n        float h3 = hash21(vec2(idw*4.37 + seed*5.3, sx*1.7 + seed*3.0));\n        float H  = mix(12.0, hmax, h1);\n        float dz = cz*0.5 - 1.2 - 1.8*h2;\n        float dx = bw*(0.70 + 0.55*h2);\n        vec3  q  = p - vec3(xc + 2.2*h2, 0.0, id*cz);\n\n        // shaft + crown\n        best = min(best, sdBox(vec3(q.x, q.y - H*0.5,   q.z), vec3(dx,      H*0.5, dz)));\n        best = min(best, sdBox(vec3(q.x, q.y - H - 2.8, q.z), vec3(dx*0.55, 2.8,   dz*0.55)));\n\n        if(D1){\n            // podium: the street-level mass. breaks the flat extrusion and\n            // gives the water something to reflect other than a plain wall\n            float ph = 2.6 + 3.4*h3;\n            best = min(best, sdBox(vec3(q.x - dx*0.30, q.y - ph*0.5, q.z),\n                                   vec3(dx*0.92, ph*0.5, dz*1.02)));\n\n            // ledge slabs repeating up the shaft, clamped to the roofline\n            float per = 6.0 + 3.0*h3;\n            float nid = clamp(floor(q.y/per + 0.5), 1.0, max(floor((H - 2.0)/per), 1.0));\n            float ly  = q.y - nid*per;\n            best = min(best, sdBox(vec3(q.x, ly, q.z), vec3(dx + 0.34, 0.17, dz + 0.34)));\n\n            // rooftop tank\n            best = min(best, sdBox(vec3(q.x - dx*0.30, q.y - H - 6.4, q.z + dz*0.25),\n                                   vec3(0.85, 0.85, 0.85)));\n        }\n\n        if(h2 > 0.55){\n            bestS = min(bestS, sdCapsule(q, vec3(0.0, H + 5.2, 0.0),\n                                            vec3(0.0, H + 5.2 + 6.0*h1, 0.0), 0.17));\n        }\n    }\n    return U(vec2(best, 12.0), vec2(bestS, 15.0));\n}\n\n// stripped-down version for the far rows - one box, no crown, no mast\nvec2 rowFar(vec3 p, float xc, float cz, float seed, float sx, float bw, float hmax){\n    float best = 1e5;\n    float zf  = p.z/cz + 0.5;\n    float zi  = floor(zf);\n    float sgz = sign(fract(zf) - 0.5);\n    sgz += 1.0 - abs(sgz);\n    for(int k=0;k<2;k++){\n        float id  = zi + float(k)*sgz;\n        float idw = mod(id, ZPER/cz);\n        float h1 = hash21(vec2(idw*1.13 + seed*7.7, sx*3.3 + seed));\n        float h2 = hash21(vec2(idw*2.71 + seed*3.1, sx*9.1 + seed*2.0));\n        float H  = mix(16.0, hmax, h1);\n        vec3  q  = p - vec3(xc + 4.0*h2, 0.0, id*cz);\n        best = min(best, sdBox(vec3(q.x, q.y - H*0.5, q.z),\n                               vec3(bw*(0.7 + 0.6*h2), H*0.5, cz*0.5 - 2.0 - 3.0*h2)));\n    }\n    return vec2(best, 12.0);\n}\n\n// Lantern cables. A sagging span across the street with beads of light\n// repeating along it. Period 12 (10 per loop), offset 6 so it never lands on\n// a door plane. The curve is extended flat past the span so it buries itself\n// inside the tower rows instead of ending in mid-air.\nvec2 cityCables(vec3 p, float px){\n    const float per = 12.0;\n    float cid = floor((p.z - 6.0)/per + 0.5);\n    float zo  = p.z - (cid*per + 6.0);\n    float h   = hash21(vec2(mod(cid, 10.0), 4.7));\n    float h2  = hash21(vec2(mod(cid, 10.0), 8.3));\n    float y0  = 14.0 + 5.5*h;\n    float sag = 2.2 + 2.0*h2;\n    float u   = clamp(px/12.5, 0.0, 1.0);\n    float yc  = y0 - sag*(1.0 - u*u);\n\n    float cab = length(vec2(p.y - yc, zo)) - 0.075;\n    float lx  = px - floor(px/1.70 + 0.5)*1.70;\n    float lan = length(vec3(lx, p.y - (yc - 0.42), zo)) - 0.195;\n\n    return U(vec2(cab*0.70, 16.0), vec2(lan*0.80, 17.0));\n}\n\n// Street furniture: lamp posts along the kerb, arms reaching over the road.\nvec2 cityPosts(vec3 p, float sx){\n    const float per = 8.0;                          // 120/8 = 15 cells\n    float zf  = p.z/per + 0.5;\n    float zi  = floor(zf);\n    float sgz = sign(fract(zf) - 0.5);\n    sgz += 1.0 - abs(sgz);\n\n    float best = 1e5, bestL = 1e5;\n    for(int k=0;k<2;k++){\n        float id  = zi + float(k)*sgz;\n        float idw = mod(id, 15.0);\n        float h1 = hash21(vec2(idw*1.71, sx*2.3 + 1.0));\n        float h2 = hash21(vec2(idw*3.37, sx*5.7 + 2.0));\n        float h3 = hash21(vec2(idw*7.13, sx*9.1 + 3.0));\n        if(h1 < 0.42) continue;\n\n        float cx = 9.9 + 1.7*h2;                    // standing on the pavement\n        vec3  q  = p - vec3(cx, 0.0, id*per + (h3 - 0.5)*3.0);\n        float H  = 4.2 + 2.6*h1;\n\n        best = min(best, sdCapsule(q, vec3(0.0, -0.4, 0.0), vec3(0.0, H, 0.0),\n                                      0.115 + 0.05*h2));\n        // arm over the roadway + the lit head on the end of it\n        vec3 hd = vec3(-1.10 - 0.85*h2, H - 0.22, 0.0);\n        best = min(best, sdCapsule(q, vec3(0.0, H, 0.0), hd, 0.085));\n        bestL = min(bestL, sdEllipsoid(q - hd - vec3(0.0, 0.10, 0.0),\n                                       vec3(0.32, 0.19, 0.24))*0.70);\n        // kerbside bollard\n        if(h2 > 0.55)\n            best = min(best, sdCapsule(q + vec3(0.75, 0.0, 1.9),\n                                       vec3(0.0, -0.3, 0.0), vec3(0.0, 0.85, 0.0), 0.11));\n    }\n    return U(vec2(best*0.88, 18.0), vec2(bestL*0.88, 17.0));\n}\n\nvec2 mapCity(vec3 p){\n    float px = abs(p.x);\n    float sx = sign(p.x + 0.0001);\n    vec3  pf = vec3(px, p.y, p.z);\n\n    // ROAD SURFACE. Crowned down the centreline and dipping into the gutters\n    // at the kerb, so rain has a low line to pool along.\n    float ys = -0.11*smoothstep(5.4, 9.2, px);\n    vec2 res = vec2((p.y - ys)*0.90, 11.0);\n\n    if(p.y > 86.0) return U(res, vec2(p.y - 86.0, 12.0));\n\n    // raised pavement either side, kerb face included. 2D box extruded in z,\n    // so this is exact and costs almost nothing.\n    res = U(res, vec2(sdBox2(vec2(px - 11.6, p.y - 0.14), vec2(2.20, 0.15))*0.95, 19.0));\n\n    res = U(res, rowTowers(pf,  13.0, 15.0, 1.0, sx,  3.6, 34.0));\n    res = U(res, rowTowers(pf,  29.0, 20.0, 2.0, sx,  5.0, 44.0));\n    res = U(res, rowTowers(pf,  50.0, 24.0, 3.0, sx,  6.5, 52.0));\n    res = U(res, rowFar   (pf,  78.0, 30.0, 4.0, sx,  9.0, 60.0));\n    res = U(res, rowFar   (pf, 115.0, 40.0, 5.0, sx, 13.0, 72.0));\n\n    // skybridges: period 15, offset 7.5 - never lands on a door plane\n    float bo = p.z - (floor((p.z - 7.5)/15.0 + 0.5)*15.0 + 7.5);\n    res = U(res, vec2(sdBox(vec3(p.x, p.y - 18.0, bo), vec3(16.0, 0.9, 1.8)), 14.0));\n\n    // hanging signage: period 10, offset 4\n    float sid = floor((p.z - 4.0)/10.0 + 0.5);\n    float so  = p.z - (sid*10.0 + 4.0);\n    float sh  = 5.0 + 6.0*hash21(vec2(mod(sid, 12.0), sx*5.0));\n    res = U(res, vec2(sdBox(vec3(px - 9.5, p.y - sh, so), vec3(0.20, 2.2, 0.90)), 13.0));\n\n    if(D2) res = U(res, cityCables(pf, px));\n    if(D1) res = U(res, cityPosts(pf, sx));\n\n    return res;\n}\n\n// ---- dispatch -------------------------------------------------------------\nvec2 mapWorld(vec3 p, float w){ return (w < 0.5) ? mapTropic(p) : mapCity(p); }\nvec2 mapAll  (vec3 p, float w){ return U(mapDoor(p), mapWorld(p, w)); }\n\nvec3 calcNormal(vec3 p, float w){\n    vec2 e = vec2(1.0,-1.0)*0.0030;\n    return normalize( e.xyy*mapAll(p+e.xyy,w).x + e.yyx*mapAll(p+e.yyx,w).x +\n                      e.yxy*mapAll(p+e.yxy,w).x + e.xxx*mapAll(p+e.xxx,w).x );\n}\nfloat calcAO(vec3 p, vec3 n, float w){\n    float o = 0.0, sca = 1.0;\n    for(int i=0;i<5;i++){\n        float h = 0.06 + 0.36*float(i);\n        o += (h - mapAll(p + n*h, w).x)*sca;\n        sca *= 0.70;\n    }\n    return clamp(1.0 - 0.80*o, 0.0, 1.0);\n}\nfloat softShadow(vec3 ro, vec3 rd, float w){\n    if(!doShadow) return 1.0;\n    float res = 1.0, t = 0.30;\n    for(int i=0;i<SHSTEPS;i++){\n        float h = mapWorld(ro + rd*t, w).x;\n        res = min(res, 10.0*h/t);\n        t += clamp(h, 0.30, 4.5);\n        if(res < 0.02 || t > 65.0) break;\n    }\n    return clamp(res, 0.0, 1.0);\n}\n\n// ============================================================================\n//  SKIES\n// ============================================================================\nvec3 skyTropic(vec3 rd){\n    float h = max(rd.y, 0.0);\n    vec3 c = mix(vec3(1.20,0.55,0.32), vec3(0.09,0.21,0.50), pow(h,0.45));\n    float s = max(dot(rd, SUN), 0.0);\n    c += vec3(3.00,1.70,0.80)*pow(s, 900.0);\n    c += vec3(1.50,0.64,0.28)*pow(s, 7.0)*0.70;\n\n    float iy = 1.0/max(rd.y, 0.075);\n    vec3  cp = vec3(rd.x*iy, 0.0, rd.z*iy)*0.34 + vec3(0.7*sin(TAU*PH), 0.0, 0.0);\n    float cl = fbm(cp);\n    vec3  cc = mix(vec3(0.30,0.19,0.24), vec3(1.65,1.05,0.75), 0.35 + 0.6*pow(s,1.5));\n    c = mix(c, cc, smoothstep(0.40,0.88,cl)*smoothstep(0.0,0.17,rd.y)*0.88);\n\n    // high cirrus, a second cloud deck so the sky has depth of its own\n    if(D1){\n        vec3 hp = vec3(rd.x*iy, 0.0, rd.z*iy)*0.11 + vec3(1.4*sin(TAU*PH), 0.0, 0.0);\n        float hc = fbm(hp*vec3(1.0,1.0,3.0));\n        c = mix(c, mix(vec3(0.85,0.62,0.58), vec3(1.45,1.02,0.80), pow(s,1.2)),\n                smoothstep(0.52,0.86,hc)*smoothstep(0.05,0.35,rd.y)*0.40);\n    }\n    return c;\n}\n\nvec3 skyCity(vec3 rd){\n    float h = max(rd.y, 0.0);\n    vec3 c = mix(vec3(0.11,0.045,0.16), vec3(0.012,0.016,0.042), pow(h,0.55));\n    c += vec3(0.62,0.13,0.52)*pow(1.0-h, 9.0)*0.9;\n    c += vec3(0.06,0.30,0.52)*pow(1.0-h, 3.0)*0.30;\n    c += vec3(0.35,0.10,0.30)*exp(-h*7.0)*0.55;\n    float st = smoothstep(0.9986, 1.0, hash21(floor(rd.xz*430.0)));\n    c += st*vec3(0.80,0.85,1.0)*smoothstep(0.15,0.6,rd.y);\n\n    // low overcast catching the city glow from underneath\n    if(D1){\n        float iy = 1.0/max(rd.y, 0.09);\n        float cl = fbm(vec3(rd.x*iy, 0.0, rd.z*iy)*0.22 + vec3(0.5*sin(TAU*PH),0.0,0.0));\n        c = mix(c, vec3(0.16,0.055,0.19), smoothstep(0.42,0.85,cl)\n                                          *smoothstep(0.0,0.30,rd.y)*0.75);\n    }\n\n    // far skyline behind the marched rows\n    float ang    = atan(rd.x, max(rd.z, 0.001));\n    float slope  = rd.y/max(length(rd.xz), 0.001);\n    float prof   = 0.055 + 0.110*fbm(vec3(ang*7.0, 0.0, 0.0));\n    float inside = smoothstep(prof + 0.004, prof - 0.004, slope);\n    vec3  far    = vec3(0.024,0.019,0.044);\n    far += step(0.80, hash21(floor(vec2(ang*260.0, slope*900.0))))*vec3(0.55,0.42,0.25)*0.5;\n    return mix(c, far, inside);\n}\nvec3 skyFor(vec3 rd, float w){ return (w < 0.5) ? skyTropic(rd) : skyCity(rd); }\n\n// Aerial perspective. Cheap stand-in for the sky so distance desaturates and\n// shifts hue instead of washing to one flat colour.\nvec3 fogColor(vec3 rd, float w){\n    float up = smoothstep(-0.05, 0.40, rd.y);\n    if(w < 0.5){\n        float s = max(dot(rd, SUN), 0.0);\n        return mix(vec3(0.66,0.48,0.38), vec3(0.34,0.40,0.54), up)\n             + vec3(0.95,0.48,0.22)*pow(s, 4.0)*0.55;\n    }\n    return mix(vec3(0.090,0.048,0.120), vec3(0.028,0.024,0.055), up)\n         + vec3(0.30,0.09,0.28)*0.30;\n}\nvec3 applyFog(vec3 col, vec3 rd, float t, float w){\n    float den = (w < 0.5) ? fogTropic : fogCity;\n    return mix(col, fogColor(rd, w), 1.0 - exp(-t*den));\n}\n\n// ============================================================================\n//  PORTAL-AWARE MARCH\n// ============================================================================\nvec3 trace(vec3 ro, vec3 rd, float w0, float tmax, int maxSteps,\n           out vec3 vol, float volScale)\n{\n    float t = 0.03, w = w0, mat = -1.0;\n    vol = vec3(0.0);\n\n    for(int i=0;i<STEPS;i++){\n        if(i >= maxSteps) break;\n        vec3 p = ro + rd*t;\n        vec2 h = mapAll(p, w);\n\n        // hit resolves before any world toggle, so mat and w always agree\n        if(h.x < 0.0014*t + 0.0015){ mat = h.y; break; }\n\n        // floor the step first, then test the plane against the step actually\n        // taken - otherwise small steps jump a plane without testing it\n        float st = max(h.x*0.66, 0.014);\n\n        if(abs(rd.z) > 0.001){\n            float zn = (rd.z > 0.0) ? (floor(p.z/L) + 1.0)*L : floor(p.z/L)*L;\n            float dp = (zn - p.z)/rd.z;\n            if(dp > 0.0 && dp < st){\n                st = dp + 0.004;\n                if(insideAperture(ro + rd*(t + dp))) w = 1.0 - w;\n            }\n        }\n\n        if(volScale > 0.0){\n            float tr = exp(-t*0.0075);\n            if(w < 0.5){\n                float g  = exp(-max(p.y,0.0)*0.13);\n                float fs = pow(max(dot(rd,SUN),0.0), 6.0);\n                vol += (vec3(0.30,0.27,0.28) + vec3(1.50,0.88,0.46)*fs)\n                       *g*st*0.0060*tr*volScale;\n            }else{\n                float g = exp(-max(p.y,0.0)*0.19);\n                vec3  c = mix(vec3(0.55,0.10,0.45), vec3(0.08,0.34,0.60),\n                              0.5 + 0.5*sin(p.z*0.10472));\n                vol += c*g*st*0.0058*tr*volScale*(0.8 + 0.5*BASS);\n            }\n            // three-lobe door bloom: tight core, mid halo, wide wash. the wide\n            // term is what lets far doors in the chain still read through haze.\n            float dr = doorRingDist(p);\n            vol += NEON*( 1.00/(1.0 +  6.0*dr*dr)\n                        + 0.22/(1.0 +  0.30*dr*dr)\n                        + 0.05/(1.0 +  0.02*dr*dr) )\n                   *st*0.060*tr*volScale*(0.85 + 0.75*BASS);\n        }\n\n        t += st;\n        if(t > tmax) break;\n    }\n    return vec3(t, mat, w);\n}\n\n// ============================================================================\n//  MATERIALS\n// ============================================================================\nfloat wave(vec2 q){\n    float t = TAU*PH;\n    return 0.055*( sin(q.x*2.10   + 1.5*sin(t))\n                 + sin(q.y*2.7227 + 1.3*cos(t))\n                 + 0.5*sin((q.x + q.y)*4.2935 + 2.0*sin(t)) )\n         + 0.020*sin(q.x*7.30 + q.y*5.1313 + 3.0*sin(t));\n}\n// rain chop: integer multipliers on PH keep it loop-exact\nfloat ripple(vec2 q){\n    return 0.010*sin(q.x*9.10 + TAU*PH*40.0)*sin(q.y*8.30 - TAU*PH*33.0)\n         + 0.006*sin((q.x - q.y)*15.7 + TAU*PH*57.0);\n}\nvec3 waterNormal(vec3 p, float w){\n    float e = 0.28;\n    float r = (w > 0.5) ? rainAmt*0.9 : 0.0;\n    float h0 = wave(p.xz)                    + r*ripple(p.xz);\n    float hx = wave(p.xz + vec2(e,0.0))      + r*ripple(p.xz + vec2(e,0.0));\n    float hz = wave(p.xz + vec2(0.0,e))      + r*ripple(p.xz + vec2(0.0,e));\n    return normalize(vec3(-(hx - h0)/e, 1.0, -(hz - h0)/e));\n}\n\n// Puddle field for the city street. Periodic in z via fbmZ2, biased toward\n// the gutters where the camber sends the water.\nfloat puddleMask(vec3 p){\n    float d = fbmZ2(vec3(p.x, 0.0, p.z), 0.40);\n    float m = smoothstep(0.46, 0.62, d);\n    m = max(m, smoothstep(6.2, 9.2, abs(p.x))*0.85);      // gutter pooling\n    return clamp(m*mix(0.35, 1.0, wetness), 0.0, 1.0);\n}\n// shallow standing water: rain rings only, no swell\nvec3 puddleNormal(vec3 p){\n    float e = 0.20;\n    float a = 0.45 + 1.1*rainAmt;\n    float h0 = a*ripple(p.xz)               + 0.30*wave(p.xz*0.55);\n    float hx = a*ripple(p.xz + vec2(e,0.0)) + 0.30*wave((p.xz + vec2(e,0.0))*0.55);\n    float hz = a*ripple(p.xz + vec2(0.0,e)) + 0.30*wave((p.xz + vec2(0.0,e))*0.55);\n    return normalize(vec3(-(hx - h0)/e, 1.0, -(hz - h0)/e));\n}\n\nvec3 doorLight(vec3 p, vec3 n, vec3 alb){\n    vec3  lv = vec3(0.0, PCY, floor(p.z/L + 0.5)*L) - p;\n    float d  = length(lv);\n    return alb*NEON*max(dot(n, lv/max(d,0.001)), 0.0)\n         / (1.0 + 0.045*d*d)*(16.0 + 11.0*BASS);\n}\n\nvec3 shadeTropic(vec3 p, vec3 n, vec3 rd, float mat){\n    vec3 alb;\n    bool water = (mat < 1.5);\n    bool leafy = (mat > 2.7);\n    float foam = 0.0;\n\n    if(water){\n        // depth shading off the actual channel floor: shallows read turquoise,\n        // the channel reads deep, and the crossing point foams\n        float gh  = lagoonH(vec3(p.x, 0.0, p.z));\n        float dep = clamp(-gh, 0.0, 9.0);\n        alb = mix(vec3(0.060,0.210,0.195), vec3(0.011,0.033,0.038),\n                  smoothstep(0.35, 4.5, dep));\n        float fn = fbmZ2(vec3(p.x, 0.0, p.z), 0.90);\n        foam = smoothstep(1.00, 0.02, abs(gh))*smoothstep(0.30, 0.72, fn);\n\n        if(D3){\n            // lily-pad rafts, surface only\n            vec2 lc  = vec2(p.x, mod(p.z, ZPER))/2.40;\n            vec2 lid = floor(lc);\n            vec2 lf  = fract(lc) - 0.5;\n            vec2 jit = (vec2(hash21(lid*2.13 + 1.0), hash21(lid*3.71 + 5.0)) - 0.5)*0.42;\n            float pad = smoothstep(0.36, 0.25, length(lf - jit));\n            pad *= step(0.58, hash21(lid*1.31 + 9.0));\n            pad *= smoothstep(5.0, 11.0, abs(p.x))*smoothstep(31.0, 21.0, abs(p.x));\n            alb = mix(alb, mix(vec3(0.055,0.150,0.045), vec3(0.11,0.24,0.06),\n                               hash21(lid*5.9)), pad*0.92);\n            n = normalize(mix(n, vec3(0.0,1.0,0.0), pad*0.85));\n            water = (pad < 0.5);\n        }\n    }\n    else if(mat < 2.7){                                     // rock / bark\n        float m   = fbmZ2(p, 0.40);\n        vec3  dry = mix(vec3(0.21,0.16,0.12), vec3(0.36,0.30,0.23), m);\n        vec3  wet = mix(vec3(0.06,0.05,0.04), vec3(0.12,0.10,0.08), m);\n        alb = mix(wet, dry, smoothstep(0.05, 2.4, p.y));\n\n        if(D1){\n            // stratification: horizontal banding so the rock stops reading as\n            // one noise blob and gains a sense of scale\n            float band = 0.5 + 0.5*sin(p.y*1.55 + fbmZ2(p, 0.10)*7.0);\n            alb *= 0.80 + 0.40*smoothstep(0.35, 0.75, band);\n            alb += vec3(0.05,0.04,0.03)*smoothstep(0.80, 0.98, band);\n        }\n\n        alb = mix(alb, vec3(0.62,0.60,0.55),\n                  smoothstep(0.60, 0.0, abs(p.y - 0.10))*smoothstep(0.35,0.70,m)*0.55);\n        float moss = smoothstep(0.50,0.92,n.y)*smoothstep(0.40,0.72,fbmZ2(p,0.20))\n                   * smoothstep(0.90, 3.5, p.y);\n        alb = mix(alb, mix(vec3(0.06,0.14,0.045), vec3(0.15,0.29,0.075), m), moss*0.85);\n    }\n    else if(mat < 3.6){                                     // foliage + shrubs\n        float v = fbmZ2(p, 0.80);\n        alb = mix(vec3(0.05,0.16,0.04), vec3(0.20,0.42,0.09), v);\n        alb = mix(alb, vec3(0.30,0.36,0.10), smoothstep(0.62,0.95,v)*0.45);\n        if(mat > 3.2){                                      // understory is\n            alb *= 0.72;                                    // darker, cooler\n            alb = mix(alb, vec3(0.09,0.20,0.10), 0.35);\n        }\n    }\n    else{                                                   // vine\n        alb = mix(vec3(0.10,0.14,0.05), vec3(0.19,0.26,0.08), fbmZ2(p, 0.90));\n    }\n\n    float sh  = softShadow(p + n*0.09, SUN, 0.0);\n    float dif = max(dot(n, SUN), 0.0);\n    vec3  sky = skyTropic(vec3(0.0,1.0,0.0));\n\n    vec3 col  = alb*dif*sh*vec3(2.60,1.80,1.18);\n    col += alb*sky*(0.30 + 0.42*max(n.y,0.0));\n    col += alb*vec3(0.16,0.13,0.09)*max(-n.y,0.0)*0.5;\n    if(leafy) col += alb*max(dot(-n,SUN),0.0)*sh*vec3(1.20,1.50,0.48)*0.85;\n\n    if(water){\n        vec3 hv = normalize(SUN - rd);\n        col += vec3(2.8,2.1,1.5)*pow(max(dot(n,hv),0.0), 260.0)*(0.6 + 1.4*HIGH);\n        col += vec3(0.55,0.62,0.58)*foam*(0.9 + 0.5*HIGH);   // shoreline foam\n    }\n    return col + doorLight(p, n, alb);\n}\n\nvec3 shadeCity(vec3 p, vec3 n, vec3 rd, float mat){\n    vec3 alb = vec3(0.05), emi = vec3(0.0);\n    bool street = (mat < 11.5);\n    float pud = 0.0;\n\n    if(street){\n        // --- asphalt\n        float px2  = abs(p.x);\n        float zz   = mod(p.z, ZPER);\n        float grit = fbmZ2(vec3(p.x, 0.0, p.z), 0.90);\n        float wear = fbmZ2(vec3(p.x, 0.0, p.z), 0.20);\n        alb = mix(vec3(0.028,0.028,0.032), vec3(0.058,0.056,0.060), grit);\n        alb *= 0.80 + 0.35*wear;                       // patching and repairs\n\n        // --- lane paint. dashes are period 4 (30 per loop), so they tile\n        float dash = step(0.5, fract(zz/4.0));\n        float ctr  = smoothstep(0.14, 0.09, abs(px2 - 0.24));\n        float lane = smoothstep(0.12, 0.07, abs(px2 - 4.60))*dash;\n        float edge = smoothstep(0.13, 0.08, abs(px2 - 8.60));\n        float paint = clamp(ctr + lane + edge, 0.0, 1.0)*(0.55 + 0.45*grit);\n        alb = mix(alb, vec3(0.36,0.33,0.20), paint*0.90);\n\n        // --- crosswalk under every cable span (period 12, offset 6)\n        float ccid = floor((p.z - 6.0)/12.0 + 0.5);\n        float czo  = p.z - (ccid*12.0 + 6.0);\n        float cw   = smoothstep(2.05, 1.60, abs(czo))\n                   * step(0.5, fract(px2/1.30))\n                   * step(px2, 8.9);\n        alb = mix(alb, vec3(0.40,0.39,0.35), cw*0.80*(0.6 + 0.4*grit));\n\n        // --- manhole covers and gutter drains\n        vec2 mid = floor(vec2(p.x, zz)/9.0);\n        vec2 mc  = (mid + 0.5)*9.0 + (vec2(hash21(mid*1.7), hash21(mid*3.3)) - 0.5)*5.0;\n        float man = smoothstep(0.62, 0.50, length(vec2(p.x, zz) - mc));\n        alb = mix(alb, vec3(0.075,0.068,0.058)*(0.7 + 0.6*grit), man);\n        float drain = smoothstep(0.35, 0.20, abs(px2 - 8.95))\n                    * step(0.55, fract(zz/16.0))*step(0.5, fract(zz/1.6));\n        alb *= 1.0 - 0.55*drain;\n\n        // --- wet. puddles darken the asphalt and go specular in render()\n        pud = puddleMask(p);\n        alb *= 1.0 - 0.62*pud;\n        alb *= 1.0 - 0.22*wetness;                     // damp sheen everywhere\n    }\n    else if(mat < 12.5){                                    // tower\n        alb = vec3(0.045,0.048,0.060)\n            * (0.7 + 0.6*hash21(floor(vec2(p.x, mod(p.z, ZPER))*0.10)));\n\n        float vert = 1.0 - abs(n.y);\n        float u    = (abs(n.x) > 0.5) ? mod(p.z, ZPER) : p.x;\n        vec2  cid  = vec2(floor(u/2.0), floor(p.y/2.30));\n        vec2  fq   = vec2(fract(u/2.0), fract(p.y/2.30));\n        float pane = smoothstep(0.10,0.18,fq.x)*smoothstep(0.90,0.82,fq.x)\n                   * smoothstep(0.15,0.24,fq.y)*smoothstep(0.86,0.78,fq.y);\n        float on   = step(0.46, hash21(cid + floor(p.x*0.02)));\n        float flk  = step(0.985, hash21(cid + floor(PH*8.0)));\n        vec3  wc   = mix(vec3(1.00,0.72,0.34), vec3(0.35,0.85,1.35), hash21(cid*1.7 + 3.0));\n        emi += vert*pane*on*(1.0-flk)*wc*(1.7 + 1.3*MIDS);\n\n        float spine = lineMask(u, 10.0, 0.10)*vert;\n        emi += spine*mix(vec3(1.4,0.15,0.7), vec3(0.15,1.0,1.4),\n                         step(0.5, hash21(floor(vec2(u/10.0, 0.0)))))*1.7;\n\n        if(D1){\n            // strip lighting tucked under every ledge slab\n            float led = lineMask(p.y - 0.35, 6.8, 0.09);\n            emi += led*vert*vec3(0.35,0.80,1.20)*1.15;\n            // top-of-podium wash where the street light pools\n            emi += vert*smoothstep(6.5, 0.0, p.y)*vec3(0.90,0.35,0.55)*0.55;\n        }\n\n        if(D3){\n            // blocky sign panels bolted to the facade\n            vec2 sid2 = vec2(floor(u/7.0), floor(p.y/9.0));\n            float sgn = step(0.72, hash21(sid2*3.17 + 11.0));\n            vec2  sf  = vec2(fract(u/7.0), fract(p.y/9.0));\n            float box = smoothstep(0.26,0.32,sf.x)*smoothstep(0.74,0.68,sf.x)\n                      * smoothstep(0.30,0.36,sf.y)*smoothstep(0.70,0.64,sf.y);\n            float glyph = step(0.45, hash21(floor(vec2(u*4.0, p.y*4.0))\n                                            + floor(PH*6.0)*0.0));\n            vec3  sc2 = mix(vec3(1.60,0.20,0.55), vec3(0.20,1.10,1.45),\n                            hash21(sid2*7.7 + 2.0));\n            emi += vert*sgn*box*glyph*sc2*(1.4 + 1.6*MIDS);\n\n            // scrolling ticker band, loop-locked (integer cycles per loop)\n            float tb = smoothstep(0.45, 0.0, abs(p.y - 8.6));\n            float tk = step(0.55, hash21(floor(vec2(u*3.0 - PH*120.0, 0.0))));\n            emi += vert*tb*tk*vec3(1.30,0.55,0.10)*1.5;\n        }\n\n        emi += vert*exp(-max(p.y,0.0)*0.36)*vec3(0.55,0.12,0.45)*0.85;\n    }\n    else if(mat < 13.5){                                    // signage\n        vec3 sc = mix(vec3(1.7,0.15,0.70), vec3(0.15,1.15,1.60), step(0.5, fract(p.z*0.05)));\n        emi += sc*(2.8 + 2.2*BASS);\n        alb = vec3(0.02);\n    }\n    else if(mat < 14.5){                                    // skybridge\n        alb = vec3(0.06,0.065,0.08);\n        emi += smoothstep(0.38,0.0,abs(p.y - 17.55))*vec3(0.25,0.9,1.3)*1.6;\n    }\n    else if(mat < 15.5){                                    // mast + beacon\n        alb = vec3(0.05,0.05,0.06);\n        emi += vec3(1.6,0.15,0.12)*smoothstep(0.4,1.0,sin(TAU*PH*4.0))*1.4;\n    }\n    else if(mat < 16.5){                                    // cable\n        alb = vec3(0.03,0.03,0.035);\n    }\n    else if(mat < 17.5){                                    // lantern / lamp\n        float f = hash21(vec2(floor(abs(p.x)/1.7), floor(mod(p.z, ZPER))));\n        vec3  lc2 = mix(vec3(1.70,0.62,0.20), vec3(1.30,0.25,0.55), f);\n        emi += lc2*(2.6 + 2.0*BASS)*(0.85 + 0.15*sin(TAU*PH*6.0 + f*9.0));\n        alb = vec3(0.04);\n    }\n    else if(mat < 18.5){                                    // lamp post, bollard\n        alb = vec3(0.050,0.048,0.052)*(0.7 + 0.6*fbmZ2(p, 0.60));\n        alb = mix(alb*0.55, alb, smoothstep(0.0, 1.4, p.y));   // road grime\n    }\n    else{                                                   // pavement\n        alb = vec3(0.078,0.076,0.082)*(0.72 + 0.55*fbmZ2(p, 0.90));\n        // paving joints, 1.6 grid, z folded so it tiles\n        float j = max(lineMask(p.x, 1.6, 0.045), lineMask(mod(p.z, ZPER), 1.6, 0.045));\n        alb *= 1.0 - 0.42*j;\n        // kerb face catches the road lighting harder than the top does\n        alb *= 0.80 + 0.45*(1.0 - abs(n.y));\n        alb *= 1.0 - 0.18*wetness;\n    }\n\n    vec3 col = alb*vec3(0.045,0.035,0.085);\n    for(int i=0;i<2;i++){\n        float side = (i==0) ? 1.0 : -1.0;\n        vec3  lv = vec3(side*8.9, 5.1, p.z) - p;\n        float d  = length(lv);\n        vec3  lc = (i==0) ? vec3(1.45,0.20,0.80) : vec3(0.15,0.85,1.50);\n        col += alb*lc*max(dot(n, lv/max(d,0.001)),0.0)/(1.0 + 0.09*d*d)*(11.0 + 7.0*BASS);\n    }\n    col += alb*skyCity(vec3(0.0,1.0,0.0))*max(n.y,0.0)*0.55;\n\n    if(street){\n        // sheen: sharp where there is standing water, broad and dull on damp\n        // asphalt. this is what sells \"wet road\" even before the reflection.\n        vec3 hv = normalize(vec3(0.0,1.0,0.2) - rd);\n        float sp = max(dot(n,hv), 0.0);\n        col += vec3(0.55,0.30,0.65)*pow(sp, 90.0)*(0.25 + 0.85*pud);\n        col += vec3(0.30,0.22,0.38)*pow(sp, 12.0)*0.12*wetness;\n        // rain hitting the road throws a fine sparkle\n        col += vec3(0.45,0.38,0.60)*rainAmt\n             * pow(max(dot(n, normalize(vec3(0.0,1.0,0.0) - rd)),0.0), 30.0)*0.18;\n    }\n    return col + emi + doorLight(p, n, alb);\n}\n\nvec3 shadeDoor(vec3 p){\n    float prof = doorProfile(p.xy);\n    float core = smoothstep(PTUBE, 0.0, abs(prof));\n    float puls = 0.90 + 0.18*sin(TAU*PH*8.0 + p.y*2.2) + 1.1*BASS;\n\n    // chase segments running the perimeter - free frame detail, no geometry.\n    // 12 cycles per loop keeps it seamless.\n    float s = (p.x + p.y)*1.30 - PH*12.0*TAU*0.16;\n    float chase = 0.55 + 0.45*sin(s*TAU);\n    puls *= mix(1.0, 0.55 + 0.85*chase, 0.45);\n\n    return mix(NEON*3.4, vec3(2.7,2.3,2.3), core*0.75)*puls;\n}\n\nvec3 shadeHit(vec3 p, vec3 n, vec3 rd, float mat, float w){\n    if(mat > 29.5) return shadeDoor(p);\n    return (w < 0.5) ? shadeTropic(p, n, rd, mat) : shadeCity(p, n, rd, mat);\n}\n\n// ============================================================================\n//  RENDER\n// ============================================================================\nvec3 render(vec3 ro, vec3 rd, float w0, int steps, int rsteps){\n    vec3 vol;\n    vec3 hit = trace(ro, rd, w0, 520.0, steps, vol, volAmt);\n    float t = hit.x, mat = hit.y, w = hit.z;\n    WOUT = w;\n    TOUT = (mat < 0.0) ? 1e5 : t;          // depth for the particle field\n    vec3 col;\n\n    if(mat < 0.0){\n        col = skyFor(rd, w);\n    }else{\n        vec3 p = ro + rd*t;\n        vec3 n = calcNormal(p, w);\n\n        // lagoon water is a full mirror surface; the city street only mirrors\n        // where puddles have collected\n        bool  isWater  = (mat < 1.5);\n        bool  isStreet = (mat > 10.5 && mat < 11.5);\n        float pud = isStreet ? puddleMask(p) : 0.0;\n\n        if(isWater)       n = normalize(mix(n, waterNormal(p, w), 0.88));\n        else if(pud > 0.01) n = normalize(mix(n, puddleNormal(p), 0.80*pud));\n\n        if(mat > 29.5) col = shadeDoor(p);\n        else           col = shadeHit(p, n, rd, mat, w)*calcAO(p, n, w);\n\n        if(doReflect && (isWater || pud > 0.03)){\n            float k = 0.14 + 0.86*pow(1.0 - max(dot(n, -rd), 0.0), 4.0);\n            if(isStreet) k *= pud*0.92;\n            vec3 rvol, rr = reflect(rd, n);\n            vec3 rh = trace(p + n*0.06, rr, w, 300.0, rsteps, rvol, volAmt*0.75);\n            vec3 rc;\n            if(rh.y < 0.0) rc = skyFor(rr, rh.z);\n            else{\n                vec3 p2 = p + n*0.06 + rr*rh.x;\n                rc = (rh.y > 29.5) ? shadeDoor(p2)\n                                   : shadeHit(p2, calcNormal(p2, rh.z), rr, rh.y, rh.z);\n                rc = applyFog(rc, rr, rh.x, rh.z);      // reflections fade too\n            }\n            col = mix(col, rc + rvol, clamp(k, 0.0, 1.0));\n        }\n        col = applyFog(col, rd, t, w);\n    }\n    return col + vol;\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\n// ---- screen-space weather / air ------------------------------------------\n// Both are locked to PH with integer cycle counts so they survive the loop.\nfloat rainLayer(vec2 q, float sc, float cyc, float seed){\n    vec2 g  = vec2(q.x*sc, q.y*sc*0.16 - PH*cyc);\n    vec2 id = floor(g);\n    vec2 f  = fract(g) - 0.5;\n    float h = hash21(id + seed);\n    float streak = smoothstep(0.5, 0.0, abs(f.x - (h - 0.5)*0.55)*11.0)\n                 * smoothstep(0.5, 0.0, abs(f.y)*1.25);\n    return streak*step(0.62, h);\n}\nfloat moteLayer(vec2 q, float sc, float cyc, float seed){\n    vec2 g  = vec2(q.x*sc + sin(PH*TAU + seed)*0.35, q.y*sc - PH*cyc);\n    vec2 id = floor(g);\n    vec2 f  = fract(g) - 0.5;\n    float h = hash21(id + seed);\n    return smoothstep(0.16, 0.0, length(f - (vec2(h, fract(h*7.3)) - 0.5)*0.6))\n         * step(0.78, h);\n}\n\n// ============================================================================\n//  FLY-BY PARTICLE FIELD\n//  ---------------------------------------------------------------------------\n//  World-locked points on planes spaced PSPACE apart in z. For each plane we\n//  solve the ray/plane intersection analytically, drop into a lateral cell\n//  grid and draw one soft point per cell. Because the points are anchored in\n//  world space and the camera is the thing moving, they stream outward from\n//  the vanishing point and blow past the lens with correct parallax - no\n//  velocity term, no simulation, no extra buffer.\n//\n//  Loop safety: ZPER/PSPACE = 40, so the cell hash wraps exactly with the\n//  16s loop. Bob and sway use integer cycle counts on PH.\n//  Occlusion: every point is depth-tested against hitT, so geometry hides\n//  them and an open doorway does not.\n// ============================================================================\nvec3 particleField(vec3 ro, vec3 rd, float hitT, float pxScale){\n    if(partAmt < 0.001 || rd.z < 0.05) return vec3(0.0);\n\n    vec3  acc = vec3(0.0);\n    float z0  = (floor(ro.z/PSPACE) + 1.0)*PSPACE;   // first plane ahead of us\n    float cut = 1.0 - partDens;                      // density thinning\n\n    for(int i=0;i<PSLICES;i++){\n        float zw = z0 + float(i)*PSPACE;\n        float t0 = (zw - ro.z)/rd.z;\n        if(t0 > hitT) break;                         // planes only recede\n\n        vec2 gp = (ro.xy + rd.xy*t0)/PCELL;\n        vec2 id = floor(gp);\n\n        // wrapped plane index - this is what makes the field loop\n        float sid = mod(floor(zw/PSPACE), ZPER/PSPACE);\n\n        float h1 = hash21(id*1.31 + vec2(sid*0.71, sid*1.93) +  3.0);\n        if(h1 < cut) continue;\n        float h2 = hash21(id*2.17 + vec2(sid*1.37, sid*0.53) + 11.0);\n        float h3 = hash21(id*3.71 + vec2(sid*0.29, sid*2.11) + 23.0);\n\n        // scatter the point in z inside its slab, so points cross the lens\n        // continuously instead of arriving in flat waves\n        float t = t0 + (h3 - 0.5)*PSPACE*0.36/max(rd.z, 0.25);\n        if(t < 0.35 || t > hitT) continue;\n\n        // cell-local coords at the jittered depth\n        vec2 pf = (ro.xy + rd.xy*t)/PCELL - id;\n\n        vec2 c = vec2(0.30 + 0.40*h2, 0.30 + 0.40*fract(h2*7.13));\n        c += partDrift*vec2(0.10*sin(TAU*(PH*2.0 + h1)),\n                            0.12*sin(TAU*(PH*3.0 + h2)));       // loop-exact bob\n\n        vec2 d = (pf - c)*PCELL;\n\n        // streak along the on-screen travel direction, which for forward\n        // flight is radially away from the vanishing point\n        vec2  dir = normalize(rd.xy*t + vec2(1e-4, 0.0));\n        float al  = dot(d, dir);\n        float pe  = dot(d, vec2(-dir.y, dir.x));\n        float sk  = 1.0 + partStreak*3.5;\n        float r   = length(vec2(al/sk, pe));\n\n        float core = (0.020 + 0.045*h2)*partSize;\n        float rr   = max(core, t*pxScale*1.15);      // never thinner than a pixel\n\n        float s = 1.0 - smoothstep(0.0, rr, r);\n        s += 0.30*exp(-(r*r)/(rr*rr*9.0));           // soft halo\n\n        // colour by the world this plane sits in, so points beyond a doorway\n        // already wear the next world's palette\n        float w = mod(floor(zw/L), 2.0);\n        vec3  pc = (w < 0.5) ? vec3(1.00,0.80,0.42)*(0.70 + 0.90*HIGH)   // pollen\n                             : vec3(0.55,0.78,1.25)*(0.70 + 0.90*MIDS);  // spray\n        float dz = doorZ(zw);\n        pc = mix(pc, NEON*1.4, exp(-dz*dz*0.02)*0.75);   // gate light spill\n\n        float fade = smoothstep(0.5, 3.0, t)             // no blobs on the lens\n                   * exp(-t*((w < 0.5) ? fogTropic : fogCity)*1.6)\n                   * (0.55 + 0.65*hash21(id + sid));\n        acc += pc*s*fade;\n    }\n    return acc*partAmt*0.30;\n}\n\n// ============================================================================\n//  MAIN\n// ============================================================================\nvoid main(){\n    vec2 fragCoord = gl_FragCoord.xy;\n\n    PH = fract(TIME*speed/LOOPT + phaseOffset);\n\n    D1 = detail > 0.05;\n    D2 = detail > 0.40;\n    D3 = detail > 0.72;\n\n    int steps  = int(float(STEPS)*quality);\n    int rsteps = int(float(RSTEPS)*quality);\n\n    NEON = hsv2rgb(vec3(neonHue, neonSat, 1.0))*1.6*neonGain;\n\n    float ca = sunAz*1.4;\n    SUN = normalize(vec3(sin(ca)*0.97, sunEl, cos(ca)*0.97));\n\n    // ---- audio ------------------------------------------------------------\n    // No FFT texture: not every ISF host declares an \"audioFFT\" uniform, and\n    // sampling one that was never emitted is a compile error. bassIn / midIn /\n    // highIn are plain floats - drive them from the host's audio analysis,\n    // MIDI, OSC, or an LFO. audioMix crossfades between the internal loop-\n    // locked LFO (0) and those inputs (1).\n    //\n    // On a host that DOES support audioFFT, add this input:\n    //   { \"NAME\": \"fft\", \"TYPE\": \"audioFFT\", \"MAX\": 64 }\n    // and read a band with:\n    //   IMG_NORM_PIXEL(fft, vec2(0.03, 0.5)).r\n    vec3 lfo = vec3(0.35 + 0.35*sin(TAU*PH*4.0),\n                    0.30 + 0.30*sin(TAU*PH*3.0 + 1.7),\n                    0.25 + 0.25*sin(TAU*PH*6.0 + 0.4));\n    vec3 ext = vec3(bassIn, midIn, highIn);\n    vec3 lvl = mix(lfo, ext, audioMix)*vec3(bassGain, midGain, highGain);\n    BASS = clamp(lvl.x, 0.0, 2.0);\n    MIDS = clamp(lvl.y, 0.0, 2.0);\n    HIGH = clamp(lvl.z, 0.0, 2.0);\n\n    // ---- forward motion ---------------------------------------------------\n    // g maps 0..1 onto exactly one loop, but its derivative PEAKS at PH = 0.25\n    // and 0.75 - where the doors are. So the camera lingers in each world and\n    // accelerates through the doorway.\n    const float EASE = 0.45;\n    float g  = PH + EASE*sin(TAU*2.0*(PH - 0.25))/(TAU*2.0);\n    float cz = ZPER*g + 30.0;                       // starts mid-room\n    float dz = doorZ(cz);\n\n    // all camera character damps to zero near a door, so every transit is\n    // dead-centre through the aperture\n    float open = smoothstep(0.0, 10.0, abs(dz))*sway;\n\n    vec3 ro = vec3(0.70*sin(TAU*PH*2.0)*open,\n                   camHeight + 0.28*sin(TAU*PH*3.0)*open,\n                   cz);\n    vec3 ta = ro + vec3(0.85*sin(TAU*PH*2.0 + 1.0)*open,\n                        0.55*sin(TAU*PH)*open + 0.55,\n                        4.0);\n\n    vec2 uv = (fragCoord - 0.5*RENDERSIZE.xy)/RENDERSIZE.y;\n    float rl = 0.050*sin(TAU*PH*2.0)*open;\n    uv = mat2(cos(rl), -sin(rl), sin(rl), cos(rl))*uv;\n\n    vec3 fw = normalize(ta - ro);\n    vec3 rt = normalize(cross(vec3(0.0,1.0,0.0), fw));\n    vec3 up = cross(fw, rt);\n\n    // wide lens for perspective, tightening hard at each transit. the tighten\n    // also pushes the frame edges off screen sooner, keeping the swap seamless\n    float lens = lensBase + lensPunch*exp(-0.06*dz*dz);\n    vec3 rd = normalize(uv.x*rt + uv.y*up + lens*fw);\n\n    float w0 = mod(floor(cz/L), 2.0);\n    vec3 col = render(ro, rd, w0, steps, rsteps);\n\n    // ---- fly-by particles, depth-tested against the primary hit -----------\n    // pxScale is the world size of one pixel per unit of distance, so points\n    // stay at least a pixel wide instead of aliasing into fireflies far off.\n    col += particleField(ro, rd, TOUT, 1.0/(lens*RENDERSIZE.y));\n\n    // ---- weather / air, masked by the world the ray actually ended in ------\n    if(WOUT > 0.5){\n        if(rainAmt > 0.001){\n            float r = rainLayer(uv, 42.0, 26.0, 1.0)*0.60\n                    + rainLayer(uv, 76.0, 41.0, 7.0)*0.35\n                    + rainLayer(uv, 22.0, 17.0, 3.0)*0.30;\n            col += vec3(0.55,0.60,0.85)*r*rainAmt*0.16;\n        }\n    }else{\n        if(moteAmt > 0.001){\n            float m = moteLayer(uv, 26.0, 3.0, 2.0)*0.7\n                    + moteLayer(uv, 44.0, 5.0, 9.0)*0.4;\n            col += vec3(1.00,0.82,0.52)*m*moteAmt*0.22*(0.6 + 0.8*HIGH);\n        }\n    }\n\n    // transit flash\n    col += NEON*exp(-2.0*dz*dz)*(0.09 + 0.26*BASS)*portalFlash;\n\n    col = aces(col*exposure);\n    col = pow(col, vec3(gammaAmt));\n    col *= 1.0 - vignette*dot(uv,uv);\n    col += (hash21(fragCoord + PH*137.0) - 0.5)*grain;\n\n    gl_FragColor = vec4(col, 1.0);\n}"
  }
}