/*{ "DESCRIPTION": "An 8-bit boss fight against something that should not be there, orbited in 3D and drawn on a tube. The scene is actually three-dimensional: the head, every joint of every limb, every fighter and every shell lives at a real xyz, is turned by an orbiting camera and projected with perspective, and depth is resolved per pixel by a hand-rolled z test - which is what a fragment shader has instead of a sort, each element writing only if it both covers the pixel and is nearer than what is already there. That is what makes the orbit convincing rather than decorative: the eyes are placed on the SURFACE of the head in 3D, so as the camera comes round they slide across it, foreshorten and rotate out of sight while others rotate in behind them, and the maw sits at a direction that slowly precesses so it swings toward the camera and away again. A billboarded sprite cannot do any of that. Everything is pixels FIRST: the frame is snapped to a chunky raster and every colour forced into a SIXTEEN ENTRY palette by nearest match, before the CRT touches it - so the tube bends and scans a genuinely indexed low-resolution picture rather than a smooth one wearing a costume. Snapping after the glow is the single most common way to get this wrong. Nothing is antialiased either, on purpose: a 1985 raster had hard edges, so every shape is a step() of its distance field rather than a smoothstep. The fight is closed form and keeps no state between frames. A ship's position is a function of time, so the position it FIRED from is that same function evaluated at the instant it fired - floor(t/period)*period - a shell in flight is a lerp from there toward the beast, and the impact is the same arithmetic run past the end. The guns and the hits therefore agree with nothing remembering anything, the scanner plots the squadron by simply asking where it is, and the phosphor trail behind each fighter is that same function asked about the recent past. The limbs are an incremental WALK rather than a formula: each turns a little at every step so curvature accumulates down its length and it curls, which a closed-form bend cannot do - that can only ever wave the whole limb at once. The beast is built to be wrong: its RADIUS is modulated so the silhouette itself is lumpy rather than merely textured, veins run under the hide, a maw opens ringed with teeth made by radial repetition of a single wedge, suckers ride the underside of every limb, and up to six eyes sit unevenly placed, each blinking on its own clock behind a vertical slit. Behind it all: a vast banded spiral turning in log-polar space, gas built from three sines at different angles rather than two (with two the level sets are parallel lines, so banding them draws a hard diagonal across the frame that reads as a bug), stars scrolling by whole pixels, void creeping in at the corners with a ragged moving edge, and a great eye far back - a vesica, being the intersection of two discs, squashed vertically to make the lid - that opens now and then. The overlay is dense and drawn in the same bent raster as the game, because on a real cabinet the HUD is on the same glass: corner brackets, score, wave counter, a segmented threat meter, labelled HULL and PWR columns with percentage readouts down both flanks, a scanner with two rings, a sweep hand and blips that brighten as the hand passes, a boxed CORE/FLUX/VOID readout panel, an oscilloscope that roughens as damage lands, spinning LOCK brackets, a crawling ticker, and a WARNING that flashes only while something is actually wrong. Text is a real 3x5 bitmap font, one bit per pixel packed into floats, with whole words packed base-42 into a single float - four glyphs fit inside a 24-bit mantissa - both decoded with mod and floor since GLSL ES 1.00 has no bitwise operators. The tube is built to be believable rather than decorative: scanlines use a real beam profile so a bright line is a FATTER line (a flat multiply cannot do that, which is why most scanline filters look like a screen door laid on top), the mask is a proper slot mask with triads staggered every other row and the dark gap between slots rather than plain stripes, halation models light scattering sideways inside the glass which is much wider and softer than phosphor glow, and the glare sits on the glass in FRONT of the picture so it does not move with it. Thirty-seven controls.", "CREDIT": "CC0", "CATEGORIES": ["generator", "8bit", "pixel", "retro", "crt", "game", "space", "hud", "horror", "sdf"], "INPUTS": [ { "NAME": "uSpeed", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uOrbit", "TYPE": "float", "DEFAULT": 0.16, "MIN": -1.0, "MAX": 1.0 }, { "NAME": "uTilt", "TYPE": "float", "DEFAULT": 0.30, "MIN": -1.2, "MAX": 1.2 }, { "NAME": "uDist", "TYPE": "float", "DEFAULT": 3.0, "MIN": 1.4, "MAX": 7.0 }, { "NAME": "uPixels", "TYPE": "float", "DEFAULT": 176.0, "MIN": 48.0, "MAX": 480.0 }, { "NAME": "uSnap", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uTents", "TYPE": "float", "DEFAULT": 7.0, "MIN": 1.0, "MAX": 8.0 }, { "NAME": "uTentLen", "TYPE": "float", "DEFAULT": 0.85, "MIN": 0.1, "MAX": 2.0 }, { "NAME": "uTentR", "TYPE": "float", "DEFAULT": 0.075, "MIN": 0.01, "MAX": 0.2 }, { "NAME": "uSegs", "TYPE": "float", "DEFAULT": 8.0, "MIN": 2.0, "MAX": 8.0 }, { "NAME": "uWrithe", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.5 }, { "NAME": "uBodyR", "TYPE": "float", "DEFAULT": 0.34, "MIN": 0.1, "MAX": 0.9 }, { "NAME": "uEvil", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uShips", "TYPE": "float", "DEFAULT": 5.0, "MIN": 0.0, "MAX": 6.0 }, { "NAME": "uShipSpd", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 3.0 }, { "NAME": "uShipSize", "TYPE": "float", "DEFAULT": 0.085, "MIN": 0.02, "MAX": 0.2 }, { "NAME": "uFireRate", "TYPE": "float", "DEFAULT": 1.3, "MIN": 0.05, "MAX": 5.0 }, { "NAME": "uBulletSpd", "TYPE": "float", "DEFAULT": 1.6, "MIN": 0.2, "MAX": 5.0 }, { "NAME": "uStars", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uNebula", "TYPE": "float", "DEFAULT": 0.8, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uVortex", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uGreatEye", "TYPE": "float", "DEFAULT": 0.7, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uDread", "TYPE": "float", "DEFAULT": 0.65, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uHole", "TYPE": "float", "DEFAULT": 0.85, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uHud", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uHudGlow", "TYPE": "float", "DEFAULT": 0.5, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uScanCount", "TYPE": "float", "DEFAULT": 210.0, "MIN": 40.0, "MAX": 700.0 }, { "NAME": "uScan", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uMask", "TYPE": "float", "DEFAULT": 0.45, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uMaskPitch", "TYPE": "float", "DEFAULT": 3.0, "MIN": 1.0, "MAX": 8.0 }, { "NAME": "uCurve", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.5 }, { "NAME": "uHalation", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 2.0 }, { "NAME": "uNoise", "TYPE": "float", "DEFAULT": 0.05, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uVignette", "TYPE": "float", "DEFAULT": 0.55, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uBezel", "TYPE": "float", "DEFAULT": 1.0, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uGlare", "TYPE": "float", "DEFAULT": 0.28, "MIN": 0.0, "MAX": 1.0 }, { "NAME": "uBright", "TYPE": "float", "DEFAULT": 1.35, "MIN": 0.0, "MAX": 2.0 } ] }*/ // KRAKEN - ISF port of the Shadertoy/WebGL2 version // (GLSL ES 1.00: gl_FragColor, TIME/RENDERSIZE). // // Pixels first and a 16-colour palette, then the tube. The fight keeps no // state: a shell knows where it was fired from because a ship's position is a // function of time. The limbs are an incremental walk, so the curl accumulates. #define PI 3.14159265359 #define TAU 6.28318530718 #define FOCAL 2.2 // Every loop is constant-bound with an early `break`. That matters more here // than usual: the overlay is inlined once per colour gun, and re-running the // whole 3D scene per gun as well put the D3D compiler into an EIGHTY SECOND // specialisation before the first frame — close to the watchdog that kills the // GPU process. The guns now diverge on the overlay only. #define MAXTENT 8 #define MAXSEG 8 #define MAXSHIP 6 #define MAXBULL 3 #define MAXEYE 6 // ------------------------------------------------------------ float hash11(float n){ return fract(sin(n * 78.233) * 43758.5453); } float hash12(vec2 p){ p = fract(p * vec2(123.34, 456.21)); p += dot(p, p + 45.32); return fract(p.x * p.y); } vec2 dirv(float a){ return vec2(cos(a), sin(a)); } mat2 rot2(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); } float sdSeg(vec2 p, vec2 a, vec2 b){ vec2 pa = p - a, ba = b - a; float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-9), 0.0, 1.0); return length(pa - ba * h); } float sdBox(vec2 p, vec2 b){ vec2 d = abs(p) - b; return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)); } float fill(float d){ return step(d, 0.0); } // A direction on the unit sphere from azimuth and elevation. vec3 sph(float th, float ph){ return vec3(cos(ph) * cos(th), sin(ph), cos(ph) * sin(th)); } // ------------------------------------------------------------ // camera and projection // ------------------------------------------------------------ // The camera's four trig terms, computed ONCE. Recomputing cos/sin of the // orbit inside every projection is the obvious way to write this and it is // ruinous: there are of the order of a hundred projections per pixel, so it // costs four hundred transcendentals and — worse — inlines the same trig a // hundred times, which is what put the D3D compiler back into a multi-second // specialisation before the first frame appeared. vec4 camCS(float ca, float ct){ return vec4(cos(ca), sin(ca), cos(ct), sin(ct)); } vec3 camXform(vec3 p, vec4 k){ p = vec3(k.x * p.x + k.y * p.z, p.y, -k.y * p.x + k.x * p.z); return vec3(p.x, k.z * p.y - k.w * p.z, k.w * p.y + k.z * p.z); } // xy = screen position, z = depth from the eye vec3 proj(vec3 p, vec4 k){ vec3 q = camXform(p, k); float z = q.z + uDist; return vec3(q.xy * (FOCAL / max(z, 0.15)), z); } float pscale(float z){ return FOCAL / max(z, 0.15); } // The inverse, so a direction can be given in CAMERA space and converted back // to the world. That is how the maw is kept facing the viewer: specifying it as // a world direction means it spends half the orbit hidden round the back. // The inverse, from the same four terms: cos(-a) = cos(a), sin(-a) = -sin(a). vec3 camInv(vec3 p, vec4 k){ p = vec3(p.x, k.z * p.y + k.w * p.z, -k.w * p.y + k.z * p.z); return vec3(k.x * p.x - k.y * p.z, p.y, k.y * p.x + k.x * p.z); } // The z test a fragment shader has instead of a sort: write only if this // element both covers the pixel and is nearer than what is already there. void put(inout vec3 col, inout float bz, float d, float z, vec3 c){ if (d <= 0.0 && z < bz){ bz = z; col = c; } } // ------------------------------------------------------------ // The palette. Sixteen entries, nearest match — which is what makes blends and // glows resolve to colours that were already on the chip instead of inventing // new ones. Deliberately cosmic: the cold end runs blue-indigo-teal-green so // the deep field has hue to it rather than being grey darkness. // ------------------------------------------------------------ vec3 pal16(int i){ if (i == 0) return vec3(0.020, 0.014, 0.050); // void if (i == 1) return vec3(0.045, 0.070, 0.180); // deep blue if (i == 2) return vec3(0.120, 0.075, 0.290); // indigo if (i == 3) return vec3(0.270, 0.100, 0.380); // violet if (i == 4) return vec3(0.440, 0.150, 0.460); // purple if (i == 5) return vec3(0.660, 0.190, 0.520); // magenta if (i == 6) return vec3(0.870, 0.420, 0.600); // flesh if (i == 7) return vec3(0.310, 0.030, 0.090); // dark blood if (i == 8) return vec3(0.700, 0.090, 0.150); // blood if (i == 9) return vec3(0.930, 0.350, 0.100); // ember if (i == 10) return vec3(0.965, 0.700, 0.170); // amber if (i == 11) return vec3(0.985, 0.940, 0.680); // bone if (i == 12) return vec3(0.985, 0.990, 0.965); // white if (i == 13) return vec3(0.030, 0.230, 0.270); // deep teal if (i == 14) return vec3(0.130, 0.640, 0.760); // cyan return vec3(0.400, 0.960, 0.700); // sick green } vec3 snapPal(vec3 c){ vec3 best = pal16(0); float bd = 1e9; for (int i = 0; i < 16; i++){ vec3 q = pal16(i); vec3 dv = q - c; float d = dot(dv, dv); if (d < bd){ bd = d; best = q; } } return mix(c, best, clamp(uSnap, 0.0, 1.0)); } // ------------------------------------------------------------ // 3x5 font: 0-9, A-Z, then - . % : > and space // ------------------------------------------------------------ float glyphBits(int i){ if (i == 0) return 31599.0; if (i == 1) return 29850.0; if (i == 2) return 29347.0; if (i == 3) return 14499.0; if (i == 4) return 18925.0; if (i == 5) return 14543.0; if (i == 6) return 10958.0; if (i == 7) return 9383.0; if (i == 8) return 10922.0; if (i == 9) return 14762.0; if (i == 10) return 23530.0; if (i == 11) return 15083.0; if (i == 12) return 25166.0; if (i == 13) return 15211.0; if (i == 14) return 29391.0; if (i == 15) return 4815.0; if (i == 16) return 27470.0; if (i == 17) return 23533.0; if (i == 18) return 29847.0; if (i == 19) return 11044.0; if (i == 20) return 23277.0; if (i == 21) return 29257.0; if (i == 22) return 23549.0; if (i == 23) return 24573.0; if (i == 24) return 11114.0; if (i == 25) return 4843.0; if (i == 26) return 19818.0; if (i == 27) return 23275.0; if (i == 28) return 14478.0; if (i == 29) return 9367.0; if (i == 30) return 27501.0; if (i == 31) return 11117.0; if (i == 32) return 24557.0; if (i == 33) return 23213.0; if (i == 34) return 9389.0; if (i == 35) return 29351.0; if (i == 36) return 448.0; if (i == 37) return 8192.0; if (i == 38) return 21157.0; if (i == 39) return 1040.0; if (i == 40) return 5393.0; return 0.0; // space } float glyphAt(vec2 p, vec2 org, float sz, int gi){ vec2 c = vec2(p.x - org.x, org.y - p.y) / sz; if (c.x < 0.0 || c.x >= 3.0 || c.y < 0.0 || c.y >= 5.0) return 0.0; return mod(floor(glyphBits(gi) / exp2(floor(c.y) * 3.0 + floor(c.x))), 2.0); } // Up to four glyphs packed base-42 into one float; 42^4 is 3.1M, well inside a // 24-bit mantissa. NOTE the glyphs are peeled off the LEAST significant end, so // `n` must match how many were packed — pack "E" as 4 glyphs and ask for 1 and // you decode the trailing SPACE, which is exactly how SCORE once read "SCOR". float wordAt(vec2 p, vec2 org, float sz, float pk, int n){ float m = 0.0; float v = pk; for (int k = 0; k < 4; k++){ if (k >= n) break; float gi = mod(v, 42.0); v = floor(v / 42.0); m = max(m, glyphAt(p, org + vec2(float(n - 1 - k) * sz * 4.0, 0.0), sz, int(gi))); } return m; } // Digits peeled off the least significant end too. Dividing by 10^p instead is // the obvious way and it is wrong: 10^p rebuilt as exp2(p*log2 10) is inexact // and the high-order digits start lying. float numAt(vec2 p, vec2 org, float sz, float value, int n){ float m = 0.0; float v = floor(max(value, 0.0)); for (int k = 0; k < 8; k++){ if (k >= n) break; float dgt = mod(v, 10.0); v = floor(v / 10.0); m = max(m, glyphAt(p, org + vec2(float(n - 1 - k) * sz * 4.0, 0.0), sz, int(dgt))); } return m; } // ------------------------------------------------------------ // the fight, in three dimensions // ------------------------------------------------------------ vec3 beastPos(float t){ return vec3(sin(t * 0.37) * 0.06, cos(t * 0.29) * 0.07, sin(t * 0.23) * 0.05); } vec3 shipPos(float fi, float t){ float ph = fi * 2.399; float a = t * uShipSpd * (0.42 + 0.09 * fi) + ph; float r = 1.05 + 0.28 * sin(t * 0.73 + ph) + 0.05 * fi; float el = sin(t * 0.51 + ph * 1.7) * 0.55; // they climb and dive return vec3(cos(a) * r, el, sin(a) * r); } // ------------------------------------------------------------ vec3 game(vec2 uv, float t, float asp, float pxs, out float halo, out float hurtOut){ vec2 P = (floor(uv / pxs) + 0.5) * pxs; vec2 pid = floor(uv / pxs); float ca = t * uOrbit, ct = uTilt; vec4 K = camCS(ca, ct); vec3 bc = beastPos(t); halo = 0.0; // The maw's direction, needed BEFORE the limbs and eyes are placed. Both // used to be positioned without knowing where the mouth was, so a limb // could sprout straight out of it and an eye could sit inside it. vec3 mdir = camInv(normalize(vec3(sin(t * 0.31) * 0.55, sin(t * 0.23) * 0.40, -1.0)), K); // ================= the deep ======================================== vec3 col = vec3(0.020, 0.014, 0.050); float bz = 1e9; // nothing is nearer than the backdrop // A vast banded spiral turning in log-polar space. if (uVortex > 0.001){ vec2 vp = P - vec2(0.15, -0.05); float vr = max(length(vp), 1e-3); float sp = sin(log(vr) * 3.2 - atan(vp.y, vp.x) * 2.0 + t * 0.20 + ca); col += vec3(0.055, 0.030, 0.110) * floor(max(sp, 0.0) * 3.0) / 3.0 * uVortex * smoothstep(0.05, 0.90, vr); } // Gas, in three colours over three angles. THREE sines and not two: with // two the level sets are parallel lines, so banding them draws a hard // diagonal clean across the frame that reads as a bug rather than as cloud. float n1 = sin(P.x * 1.70 + t * 0.05) + sin(P.y * 2.30 - t * 0.04) + sin((P.x * 0.9 + P.y * 1.3) * 2.1 + t * 0.03); float n2 = sin(P.x * 3.10 - t * 0.03) + sin(P.y * 1.40 + t * 0.06) + sin((P.x * 1.4 - P.y * 0.8) * 2.7 - t * 0.02); n1 = n1 / 3.0 * 0.5 + 0.5; n2 = n2 / 3.0 * 0.5 + 0.5; col += vec3(0.085, 0.045, 0.165) * floor(n1 * 4.0) / 4.0 * uNebula; col += vec3(0.020, 0.105, 0.115) * floor(max(n2 - 0.45, 0.0) * 4.0) / 4.0 * uNebula; col += vec3(0.075, 0.100, 0.030) * floor(max(n1 * n2 - 0.55, 0.0) * 4.0) / 4.0 * uNebula; col += vec3(0.090, 0.045, 0.010) * floor(max(n2 - n1 - 0.18, 0.0) * 3.0) / 3.0 * uNebula; // Stars scroll by whole pixels, because that is all a raster can do. col += vec3(0.42, 0.46, 0.62) * step(0.9955, hash12(pid + vec2(floor(t * 5.0), 0.0))) * uStars; col += vec3(0.92, 0.96, 1.00) * step(0.9977, hash12(pid * 1.7 + vec2(floor(t * 11.0), 41.0))) * uStars; // A great eye far back that opens now and then. The shape is a vesica — // the intersection of two discs — squashed vertically to make the lid. if (uGreatEye > 0.001){ float op = smoothstep(0.55, 1.0, sin(t * 0.117) * 0.5 + 0.5); vec2 ep = (P - vec2(-0.62, 0.46)) / vec2(1.0, max(op, 0.001)); float Rr = 0.42; float lens = max(length(ep - vec2(0.0, -Rr * 0.72)) - Rr, length(ep - vec2(0.0, Rr * 0.72)) - Rr); float m = fill(lens) * step(0.02, op); // These three must snap to three DIFFERENT palette entries. Dimming // them all toward the same dark red made the nearest match collapse // sclera and iris together, and the eye became a flat shape. col = mix(col, vec3(0.090, 0.075, 0.165), m * uGreatEye); col = mix(col, vec3(0.360, 0.100, 0.100), m * fill(length(ep) - 0.17) * uGreatEye); col = mix(col, vec3(0.020, 0.014, 0.050), m * fill(sdBox(ep, vec2(0.030, 0.15))) * uGreatEye); } // Void creeping in at the corners — a thing with a ragged moving edge, // not a vignette. if (uDread > 0.001){ float rr = length(P / vec2(asp, 1.0)); float ang = atan(P.y, P.x); float rag = 0.80 + 0.10 * sin(ang * 7.0 + t * 0.5) + 0.05 * sin(ang * 13.0 - t * 0.8); col = mix(col, vec3(0.010, 0.006, 0.026), fill(rag - rr) * uDread); } // ================= the hole ======================================== // Positioned in CAMERA space and converted back to the world, so it stays // behind the beast for the whole orbit instead of swinging round in front // of it. Its depth is greater than the creature's, so the z test lets every // limb pass in front of it without any special casing. if (uHole > 0.001){ vec3 hw3 = camInv(vec3(0.15, 0.10, 2.60), K); vec3 hp3 = proj(hw3, K); float hr = uHole * 1.15 * pscale(hp3.z); float dh = length(P - hp3.xy) - hr; // A thin accretion ring outside the horizon, banded like everything // else so it stays inside the palette, then the horizon itself: no // light, so it is the void colour and nothing is drawn over it. float ring = abs(dh - hr * 0.16) - hr * 0.13; put(col, bz, ring, hp3.z, vec3(0.310, 0.030, 0.090)); put(col, bz, ring + hr * 0.06, hp3.z - 1e-3, vec3(0.930, 0.350, 0.100)); put(col, bz, ring + hr * 0.10, hp3.z - 2e-3, vec3(0.965, 0.700, 0.170)); put(col, bz, dh, hp3.z - 3e-3, vec3(0.020, 0.014, 0.050)); } // ================= the limbs ======================================= // A walk on the SPHERE of directions: azimuth and elevation both turn a // little each step, so the curl accumulates in three dimensions. int NT = int(uTents + 0.5); float fN = float(NT); for (int i = 0; i < MAXTENT; i++){ if (i >= NT) break; float fi = float(i); // Limbs emanate from a RING set back from the mouth, the way a squid's // arms ring its beak — not from an arbitrary direction on the sphere, // which put one straight through the maw whenever the two coincided. vec3 up = abs(mdir.y) < 0.9 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); vec3 ax = normalize(cross(up, mdir)); vec3 ay = cross(mdir, ax); float ring = TAU * fi / fN + t * 0.11; // Close to a right angle from the maw axis on purpose: nearer to it and // the limbs splay toward the viewer and foreshorten into stubs, which is // what happened at 1.18. float back = 1.50 + 0.18 * sin(fi * 2.1); // radians away from the maw vec3 d0 = normalize(cos(back) * mdir + sin(back) * (cos(ring) * ax + sin(ring) * ay)); float th = atan(d0.z, d0.x); float ph = asin(clamp(d0.y, -1.0, 1.0)); vec3 pos = bc + d0 * uBodyR * 1.12; // The bound is a UNIFORM with an early break, not a constant. With a // constant bound and no break ANGLE unrolls every joint into the limb // body, and eight inlined copies of a projection-and-composite is most // of why the D3D specialisation took fifteen seconds before the first // frame ever appeared. int NSEG = int(clamp(uSegs, 2.0, float(MAXSEG)) + 0.5); float st = uTentLen / float(NSEG); vec3 pp = proj(pos, K); for (int k = 0; k < MAXSEG; k++){ if (k >= NSEG) break; float s = float(k) / float(NSEG); th += (sin(t * 2.05 + fi * 2.71 + s * 5.4) * 0.42 + 0.10) * uWrithe; ph += (cos(t * 1.63 + fi * 1.93 + s * 4.1) * 0.34) * uWrithe; vec3 nxt = pos + sph(th, ph) * st; vec3 np = proj(nxt, K); float zz = (pp.z + np.z) * 0.5; float rad = uTentR * (1.0 - 0.72 * s) * pscale(zz); float d = sdSeg(P, pp.xy, np.xy) - rad; put(col, bz, d, zz, vec3(0.120, 0.075, 0.290)); put(col, bz, d + rad * 0.42, zz - 1e-4, vec3(0.270, 0.100, 0.380)); // suckers ride the underside, dropped by the same walk if (uEvil > 0.001){ float sd = length(P - pp.xy) - rad * 0.46; put(col, bz, sd, zz - 2e-4, vec3(0.660, 0.190, 0.520)); } pos = nxt; pp = np; } } // ================= the head ======================================== // Shaded as a real sphere: the offset from the projected centre gives a // normal, and banding the lighting keeps it inside the palette. vec3 hp = proj(bc, K); float hs = uBodyR * pscale(hp.z); vec2 rel = (P - hp.xy) / max(hs, 1e-4); float rr2 = dot(rel, rel); float nz = sqrt(max(1.0 - rr2, 0.0)); vec3 nrm = vec3(rel, nz); float lit = dot(nrm, normalize(vec3(-0.45, 0.55, 0.70))); float lump = 1.0 + uEvil * (0.09 * sin(atan(rel.y, rel.x) * 5.0 + t * 0.7) + 0.05 * sin(atan(rel.y, rel.x) * 9.0 - t * 1.1)); float dHead = (length(rel) - lump) * hs; if (dHead <= 0.0 && hp.z < bz){ bz = hp.z; vec3 c = vec3(0.270, 0.100, 0.380); if (lit > 0.10) c = vec3(0.440, 0.150, 0.460); if (lit > 0.45) c = vec3(0.660, 0.190, 0.520); if (lit > 0.78) c = vec3(0.870, 0.420, 0.600); // veins under the hide if (uEvil > 0.001 && sin(atan(rel.y, rel.x) * 9.0 + length(rel) * 30.0) > 0.88) c = vec3(0.310, 0.030, 0.090); col = c; } // --- the maw, on the surface, in 3D ---------------------------------- // It sits at a direction that slowly precesses, so it swings toward the // camera and away again as the orbit comes round, foreshortening properly // instead of being a decal that always faces front. if (uEvil > 0.001){ vec3 mw = camXform(mdir, K); if (mw.z < 0.35){ // only when it faces us vec3 mp = proj(bc + mdir * uBodyR * 0.62, K); float fs = clamp(-mw.z, 0.15, 1.0); // foreshortening at the limb vec2 mq = (P - mp.xy); mq.x /= max(fs, 0.15); // squash across the sphere float open = 0.50 + 0.50 * sin(t * 1.7); float mr = uBodyR * (0.36 + 0.16 * open) * pscale(mp.z); float r = length(mq); float ang = atan(mq.y, mq.x); put(col, bz, r - mr * 1.18, mp.z - 0.019, vec3(0.700, 0.090, 0.150)); // raw gum put(col, bz, r - mr, mp.z - 0.020, vec3(0.020, 0.014, 0.050)); // throat put(col, bz, r - mr * 0.42, mp.z - 0.021, vec3(0.310, 0.030, 0.090)); // gullet // Two rings of teeth, pointing INWARD from the gum line. Built in // angular space: the wedge's half-width tapers to nothing at the // tip, so each is a triangle standing on the rim. Defining it the // obvious way — a wedge from the centre outward — makes filled pie // slices that swallow the whole mouth, which is what the first // version did. for (int rr = 0; rr < 2; rr++){ float fr = float(rr); float cnt = 13.0 - fr * 4.0; float base = mr * (1.0 - fr * 0.30); float L = mr * (0.40 - fr * 0.10); float tip = base - L; float ta = mod(ang + t * 0.05 + fr * 0.35, TAU / cnt) - TAU / (cnt * 2.0); float u = clamp((r - tip) / max(L, 1e-4), 0.0, 1.0); float hwid = (TAU / cnt) * 0.40 * u; float dT2 = max(abs(ta) - hwid, max(r - base, tip - r)); put(col, bz, dT2, mp.z - 0.030 - fr * 0.001, vec3(0.985, 0.940, 0.680)); } } } // --- eyes, scattered over the whole head ------------------------------ // Placed in 3D on the surface, so as the camera orbits some rotate out of // sight and others rotate in behind them. Each blinks on its own clock and // each has a vertical slit rather than a friendly round pupil. int NE = int(2.0 + uEvil * 4.0); for (int e = 0; e < MAXEYE; e++){ if (e >= NE) break; float fe = float(e); vec3 edir = sph(hash11(fe * 3.1) * TAU, (hash11(fe * 7.7) - 0.5) * 2.4); vec3 ew = camXform(edir, K); if (ew.z > 0.15) continue; // on the far side if (dot(edir, mdir) > 0.42) continue; // and never inside the mouth vec3 ep3 = proj(bc + edir * uBodyR * 0.94, K); float fs = clamp(-ew.z, 0.0, 1.0); // squashes at the limb float es = uBodyR * (0.15 + 0.10 * hash11(fe * 11.3)) * pscale(ep3.z); vec2 eq = P - ep3.xy; eq.x /= max(fs, 0.12); float blink = step(0.08, fract(t * (0.21 + 0.09 * fe) + hash11(fe * 5.5))); if (blink > 0.5){ put(col, bz, length(eq) - es * 1.28, ep3.z - 0.049, vec3(0.310, 0.030, 0.090)); put(col, bz, length(eq) - es, ep3.z - 0.05, vec3(0.965, 0.700, 0.170)); put(col, bz, sdBox(eq, vec2(es * 0.26, es * 0.74)), ep3.z - 0.051, vec3(0.020, 0.014, 0.050)); } } halo += fill(dHead) * 0.30 + exp(-max(dHead, 0.0) * 7.0) * 0.45; // ================= squadron ======================================== int NS = int(uShips + 0.5); float per = 1.0 / max(uFireRate, 0.05); float hurt = 0.0; for (int i = 0; i < MAXSHIP; i++){ if (i >= NS) break; float fi = float(i); vec3 sp = shipPos(fi, t); vec3 sv = shipPos(fi, t + 0.05) - sp; vec3 pj = proj(sp, K); vec3 pv = proj(sp + sv, K) - pj; float sa = atan(pv.y, pv.x); float sc = uShipSize * pscale(pj.z); vec2 q = rot2(-sa) * (P - pj.xy); float body = sdSeg(q, vec2(-sc * 0.9, 0.0), vec2(sc * 1.30, 0.0)) - sc * 0.30; float nose = sdSeg(q, vec2(sc * 0.6, 0.0), vec2(sc * 1.55, 0.0)) - sc * 0.11; vec2 w = q; w.y = abs(w.y); float wing = sdSeg(w, vec2(-sc * 0.15, sc * 0.10), vec2(-sc * 0.85, sc * 1.00)) - sc * 0.15; float tail = sdSeg(w, vec2(-sc * 0.85, sc * 0.05), vec2(-sc * 1.10, sc * 0.45)) - sc * 0.10; float dS = min(min(body, nose), min(wing, tail)); put(col, bz, dS, pj.z, vec3(0.030, 0.230, 0.270)); put(col, bz, dS + sc * 0.20, pj.z - 1e-4, vec3(0.130, 0.640, 0.760)); put(col, bz, dS + sc * 0.34, pj.z - 2e-4, vec3(0.985, 0.990, 0.965)); put(col, bz, length(q - vec2(sc * 0.45, 0.0)) - sc * 0.16, pj.z - 3e-4, vec3(0.400, 0.960, 0.700)); vec2 eq = q; eq.y = abs(eq.y); put(col, bz, length(eq - vec2(-sc * (1.15 + 0.10 * sin(t * 33.0 + fi)), sc * 0.34)) - sc * 0.20, pj.z - 4e-4, vec3(0.930, 0.350, 0.100)); halo += fill(dS) * 0.45; for (int j = 0; j < MAXBULL; j++){ float ft = (floor((t - fi * 0.37) / per) - float(j)) * per + fi * 0.37; vec3 o = shipPos(fi, ft); vec3 bt = beastPos(ft); vec3 dd = bt - o; float len = max(length(dd), 1e-4); vec3 u3 = dd / len; float trav = len - uBodyR; float age = (t - ft) * uBulletSpd; if (age < trav && age > 0.0){ vec3 b0 = proj(o + u3 * age, K); vec3 b1 = proj(o + u3 * max(age - 0.10, 0.0), K); float db = sdSeg(P, b0.xy, b1.xy) - 0.007 * pscale(b0.z); put(col, bz, db, b0.z, vec3(0.965, 0.700, 0.170)); put(col, bz, db + 0.004 * pscale(b0.z), b0.z - 1e-4, vec3(0.985, 0.990, 0.965)); halo += fill(db) * 0.6; } // The window must be SHORT: five ships at 1.3/s land ~6.5 hits a // second, and a long flash keeps two lit at all times so the beast // simply stays white. float since = (t - ft) - trav / uBulletSpd; if (since > 0.0 && since < 0.15){ vec3 hpz = proj(o + u3 * trav, K); float ring = abs(length(P - hpz.xy) - since * 1.6 * pscale(hpz.z)) - 0.014 * pscale(hpz.z); float m = fill(ring) * step(since, 0.12); if (m > 0.5 && hpz.z - 0.06 < bz){ bz = hpz.z - 0.06; col = vec3(0.985, 0.990, 0.965); } halo += m * 0.8; hurt += (0.15 - since) * 1.6; } } } hurtOut = clamp(hurt, 0.0, 1.0); if (dHead <= 0.0) col = mix(col, vec3(0.930, 0.350, 0.100), hurtOut * 0.45); return col; } // ------------------------------------------------------------ // The overlay, on the same bent raster as the game — on a real cabinet the // HUD is on the same glass. Cheap relative to the scene, which is why the // colour guns diverge here rather than over the whole picture. // ------------------------------------------------------------ vec3 hudLayer(vec2 uv, float t, float asp, float pxs, float hurt, out float ink){ vec2 P = (floor(uv / pxs) + 0.5) * pxs; vec2 H = P; int NS = int(uShips + 0.5); vec3 hud = vec3(0.0); ink = 0.0; float ex = asp * 0.905, ey = 0.905; // Nothing thinner than the raster: a stroke narrower than the pixel pitch // falls between pixel centres and is never sampled, so it blinks in and out // with position instead of being drawn at all. float hw = max(pxs * 0.60, 0.0035); float sz = pxs; // ONE raster pixel per font pixel float g4 = sz * 4.0; vec3 GRN = vec3(0.400, 0.960, 0.700); vec3 CYN = vec3(0.130, 0.640, 0.760); vec3 DIM = vec3(0.030, 0.230, 0.270); vec3 AMB = vec3(0.965, 0.700, 0.170); vec3 RED = vec3(0.700, 0.090, 0.150); for (int c = 0; c < 4; c++){ vec2 sg = vec2(mod(float(c), 2.0) * 2.0 - 1.0, floor(float(c) * 0.5) * 2.0 - 1.0); vec2 cp = vec2(ex, ey) * sg; float m = max(fill(sdBox(H - cp + vec2(-sg.x * 0.085, 0.0), vec2(0.085, hw))), fill(sdBox(H - cp + vec2(0.0, -sg.y * 0.060), vec2(hw, 0.060)))); ink = max(ink, m); hud = mix(hud, GRN, m); } // ---- top row ----------------------------------------------------- vec2 tl = vec2(-ex + 0.02, ey - 0.02); float m1 = wordAt(H, tl, sz, 2096667.0, 4); // SCOR m1 = max(m1, wordAt(H, tl + vec2(g4 * 4.0, 0.0), sz, 14.0, 1)); // E, packed as ONE m1 = max(m1, numAt(H, tl + vec2(g4 * 5.5, 0.0), sz, floor(t * 137.0), 6)); ink = max(ink, m1); hud = mix(hud, GRN, m1); // second line: HI score float m1b = wordAt(H, tl + vec2(0.0, -0.068), sz, 732.0, 2); // HI m1b = max(m1b, numAt(H, tl + vec2(g4 * 2.5, -0.068), sz, 428910.0, 6)); ink = max(ink, m1b); hud = mix(hud, DIM * 3.0, m1b); vec2 tr = vec2(ex - g4 * 6.5, ey - 0.02); float m2 = wordAt(H, tr, sz, 2389772.0, 4); // WAVE m2 = max(m2, numAt(H, tr + vec2(g4 * 4.5, 0.0), sz, floor(t * 0.13) + 3.0, 2)); float m2b = wordAt(H, tr + vec2(0.0, -0.068), sz, 2178035.0, 4); // TGT m2b = max(m2b, numAt(H, tr + vec2(g4 * 4.5, -0.068), sz, float(NS), 2)); ink = max(ink, max(m2, m2b)); hud = mix(hud, CYN, max(m2, m2b)); float thr = clamp(0.35 + 0.40 * sin(t * 0.31) + hurt * 0.5, 0.0, 1.0); float m3 = wordAt(H, vec2(-g4 * 2.0, ey - 0.02), sz, 2179703.0, 4); // THRT ink = max(ink, m3); hud = mix(hud, thr > 0.7 ? RED : AMB, m3); for (int k = 0; k < 12; k++){ float fk = float(k) / 12.0; float seg = fill(sdBox(H - vec2(-0.20 + float(k) * 0.034, ey - 0.062), vec2(0.012, 0.009))); ink = max(ink, seg); hud = mix(hud, step(fk, thr) > 0.5 ? mix(AMB, RED, fk) : DIM, seg); } // ---- flanks ------------------------------------------------------ for (int b = 0; b < 2; b++){ float sg = float(b) * 2.0 - 1.0; float lvl = b == 0 ? clamp(1.0 - hurt * 0.7, 0.0, 1.0) : 0.5 + 0.5 * sin(t * 0.9 + 2.0); float x = sg * (ex - 0.045); // Labels sit INBOARD: text grows rightward from its origin, so a // centred label on the right column overhangs the frame however the // inset is chosen. float lx = (b == 0) ? x + 0.022 : x - 0.022 - g4 * 4.0; float lab = wordAt(H, vec2(lx, 0.40), sz, b == 0 ? 1313319.0 : 1909823.0, 4); ink = max(ink, lab); hud = mix(hud, b == 0 ? GRN : CYN, lab); float px2 = (b == 0) ? x + 0.022 : x - 0.022 - g4 * 3.0; float pn = numAt(H, vec2(px2, -0.40), sz, floor(lvl * 99.0), 2); pn = max(pn, wordAt(H, vec2(px2 + g4 * 2.0, -0.40), sz, 38.0, 1)); // % ink = max(ink, pn); hud = mix(hud, AMB, pn); for (int k = 0; k < 12; k++){ float fk = float(k) / 12.0; float seg = fill(sdBox(H - vec2(x, -0.34 + fk * 0.66), vec2(0.014, 0.020))); ink = max(ink, seg); hud = mix(hud, step(fk, lvl) > 0.5 ? (b == 0 ? mix(RED, GRN, fk) : CYN) : DIM, seg); } } // ---- scanner ----------------------------------------------------- vec2 rc = vec2(ex - 0.17, -ey + 0.19); float rr = 0.14; float m4 = min(fill(abs(length(H - rc) - rr) - hw) + fill(abs(length(H - rc) - rr * 0.55) - hw) + fill(min(sdBox(H - rc, vec2(rr, hw)), sdBox(H - rc, vec2(hw, rr)))), 1.0); ink = max(ink, m4); hud = mix(hud, DIM * 3.0, m4); float swA = -t * 1.6; float sw = fill(sdSeg(H, rc, rc + dirv(swA) * rr) - hw); ink = max(ink, sw); hud = mix(hud, GRN, sw); for (int i = 0; i < MAXSHIP; i++){ if (i >= NS) break; vec3 s3 = shipPos(float(i), t); vec2 bq = rc + vec2(s3.x, s3.z) * rr * 0.62; // plan view, not screen float rel = mod(atan(bq.y - rc.y, bq.x - rc.x) - swA, TAU) / TAU; float bl = fill(sdBox(H - bq, vec2(max(hw, 0.009), max(hw, 0.009)))); ink = max(ink, bl); hud = mix(hud, GRN * (0.25 + 0.75 * (1.0 - rel)), bl); } float bl2 = fill(sdBox(H - rc, vec2(0.014, 0.014))); ink = max(ink, bl2); hud = mix(hud, RED, bl2); // ---- boxed readout, bottom left ---------------------------------- // Rows are spaced by MORE than a glyph is tall (5*sz); at less they print // straight on top of one another. vec2 pl = vec2(-ex + 0.030, -ey + 0.355); float pb = fill(abs(sdBox(H - pl - vec2(0.182, -0.135), vec2(0.222, 0.155))) - hw); ink = max(ink, pb); hud = mix(hud, DIM * 3.0, pb); float rows = wordAt(H, pl, sz, 932540.0, 4); // CORE rows = max(rows, numAt(H, pl + vec2(g4 * 4.5, 0.0), sz, 4096.0 + floor(sin(t) * 400.0), 4)); rows = max(rows, wordAt(H, pl + vec2(0.0, -0.070), sz, 1149657.0, 4)); // FLUX rows = max(rows, numAt(H, pl + vec2(g4 * 4.5, -0.070), sz, floor(t * 31.0), 4)); rows = max(rows, wordAt(H, pl + vec2(0.0, -0.140), sz, 2339833.0, 4)); // VOID rows = max(rows, numAt(H, pl + vec2(g4 * 4.5, -0.140), sz, floor(9.0 + hurt * 90.0), 4)); rows = max(rows, wordAt(H, pl + vec2(0.0, -0.210), sz, 1757847.0, 4)); // NULL rows = max(rows, numAt(H, pl + vec2(g4 * 4.5, -0.210), sz, floor(t * 7.0), 4)); ink = max(ink, rows); hud = mix(hud, CYN, rows); // ---- right column of status lines --------------------------------- vec2 rl = vec2(ex - g4 * 9.0, -ey + 0.355); float st = wordAt(H, rl, sz, 2135657.0, 4); // SYS st = max(st, wordAt(H, rl + vec2(g4 * 4.5, 0.0), sz, 1815155.0, 4)); // OK float st2 = wordAt(H, rl + vec2(0.0, -0.070), sz, 1002665.0, 4); // DMG st2 = max(st2, numAt(H, rl + vec2(g4 * 4.5, -0.070), sz, floor(hurt * 99.0), 2)); float st3 = wordAt(H, rl + vec2(0.0, -0.140), sz, 2041661.0, 4); // RNG st3 = max(st3, numAt(H, rl + vec2(g4 * 4.5, -0.140), sz, floor(280.0 + sin(t) * 90.0), 3)); ink = max(ink, max(st, max(st2, st3))); hud = mix(hud, GRN, st); hud = mix(hud, hurt > 0.3 ? RED : AMB, st2); hud = mix(hud, CYN, st3); // ---- oscilloscope ------------------------------------------------- float wy = -ey + 0.055; float wv = sin(H.x * 22.0 - t * 5.0) * 0.020 + sin(H.x * 51.0 + t * 3.1) * 0.012 * (0.4 + hurt * 2.0) + sin(H.x * 97.0 - t * 8.0) * 0.006 * hurt * 3.0; float wave = fill(abs(H.y - (wy + wv)) - hw) * step(abs(H.x), ex - 0.42); ink = max(ink, wave); hud = mix(hud, mix(CYN, RED, hurt), wave); // ---- lock brackets, drawn heavier so they read against the limbs --- float lockR = 0.30 + 0.025 * sin(t * 3.0); float armL = 0.050, armW = max(hw * 1.7, 0.007); vec2 lp = rot2(t * 0.6) * P; float br = 0.0; for (int c = 0; c < 4; c++){ vec2 sg = vec2(mod(float(c), 2.0) * 2.0 - 1.0, floor(float(c) * 0.5) * 2.0 - 1.0); vec2 cp = lp - sg * lockR; br = max(br, fill(sdBox(cp + vec2(sg.x * armL, 0.0), vec2(armL, armW)))); br = max(br, fill(sdBox(cp + vec2(0.0, sg.y * armL), vec2(armW, armL)))); } ink = max(ink, br); hud = mix(hud, RED * (0.75 + 0.25 * step(0.5, fract(t * 2.0))), br); float lk = wordAt(H, vec2(-g4 * 2.0, -lockR - 0.015), sz, 1598708.0, 4); // LOCK ink = max(ink, lk); hud = mix(hud, RED, lk); // ---- WARNING, only while something is actually wrong --------------- if (hurt > 0.02 && fract(t * 6.0) > 0.5){ vec2 wo = vec2(-g4 * 3.5, -ey + 0.26); float wt = max(wordAt(H, wo, sz, 2389613.0, 4), // WARN wordAt(H, wo + vec2(g4 * 4.0, 0.0), sz, 1374869.0, 4)); // ING ink = max(ink, wt); hud = mix(hud, RED, wt); } float tk = fill(sdBox(vec2(mod(H.x * 7.0 + t * 1.4, 1.0) - 0.5, H.y - (ey - 0.100)) * vec2(0.14, 1.0), vec2(0.014, 0.008))); ink = max(ink, tk); hud = mix(hud, DIM * 3.0, tk); return hud; } // ------------------------------------------------------------ void main(){ vec2 fragCoord = gl_FragCoord.xy; vec2 R = RENDERSIZE.xy; float asp = R.x / R.y; float t = TIME * uSpeed; vec2 uv0 = (fragCoord - 0.5 * R) / (0.5 * R.y); vec2 n = uv0 / vec2(asp, 1.0); float r2 = dot(n, n); vec2 nb = n * (1.0 + uCurve * 0.20 * r2); vec2 uv = nb * vec2(asp, 1.0); float pxs = 2.0 / max(uPixels, 16.0); float halo, hurt; vec3 col = game(uv, t, asp, pxs, halo, hurt); // Misconvergence is GONE, and deliberately so. Branching on a uniform does // not save the compiler any work: `if (converge) {3 calls} else {1 call}` // inlines the overlay FOUR times whichever way the branch actually goes, // and the overlay is the largest block of code in the shader. That alone // pushed the D3D specialisation past the driver watchdog, which duly killed // the GPU context. The tube keeps curvature, a beam-profile scanline, a real // slot mask, halation, glare and the bezel — it does not need a fourth copy // of the HUD to look like a tube. float ink = 0.0; if (uHud > 0.001){ vec3 hud = hudLayer(uv, t, asp, pxs, hurt, ink); col = mix(col, hud, ink * uHud); halo += ink * 0.35; } col = snapPal(clamp(col, 0.0, 1.0)); // Halation is light scattering sideways INSIDE the glass — much wider and // softer than phosphor glow, and most of why a lit CRT looks lit. col += vec3(0.55, 0.30, 0.65) * halo * uHalation * 0.15; col += vec3(0.20, 0.55, 0.40) * ink * uHudGlow * 0.28; // Scanlines with a real beam profile: a bright line is a FATTER line, // because the beam blooms with drive. A flat multiply cannot do that, and // that is why most scanline filters look like a screen door laid on top. float lum = clamp(dot(col, vec3(0.299, 0.587, 0.114)), 0.0, 1.0); float sy = fract(nb.y * uScanCount * 0.5) - 0.5; float bw = 0.17 + 0.33 * lum; col *= mix(1.0, exp(-sy * sy / (bw * bw)), uScan); // Slot mask: triads staggered every other row, with the gap between slots. float pit = max(uMaskPitch, 1.0); float rowy = floor(fragCoord.y / (pit * 3.0)); float mx = mod(floor((fragCoord.x + mod(rowy, 2.0) * pit * 1.5) / pit), 3.0); vec3 tri = vec3(mx == 0.0 ? 1.0 : 0.45, mx == 1.0 ? 1.0 : 0.45, mx == 2.0 ? 1.0 : 0.45); float slot = step(0.16, fract(fragCoord.y / (pit * 3.0))); col *= mix(vec3(1.0), tri * mix(0.55, 1.0, slot), uMask); col += (hash12(fragCoord + fract(TIME) * 311.0) - 0.5) * uNoise * 0.30; col *= 1.0 - uVignette * 0.85 * smoothstep(0.30, 1.50, r2); col *= uBright; col += vec3(0.40, 0.44, 0.55) * uGlare * 0.09 * exp(-7.0 * dot(nb - vec2(-0.42, 0.44), nb - vec2(-0.42, 0.44))); float sd = sdBox(nb, vec2(0.985, 0.985) - 0.15) - 0.15; float ins = step(sd, 0.0); col = mix(col, vec3(0.0), (1.0 - ins) * uBezel); col += vec3(0.08, 0.09, 0.11) * fill(abs(sd + 0.012) - 0.028) * ins * uBezel; gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0); }
An 8-bit boss fight against something that should not be there, orbited in 3D and drawn on a tube. The scene is actually three-dimensional: the head, every joint of every limb, every fighter and every shell lives at a real xyz, is turned by an orbiting camera and projected with perspective, and depth is resolved per pixel by a hand-rolled z test - which is what a fragment shader has instead of a sort, each element writing only if it both covers the pixel and is nearer than what is already there. That is what makes the orbit convincing rather than decorative: the eyes are placed on the SURFACE of the head in 3D, so as the camera comes round they slide across it, foreshorten and rotate out of sight while others rotate in behind them, and the maw sits at a direction that slowly precesses so it swings toward the camera and away again. A billboarded sprite cannot do any of that. Everything is pixels FIRST: the frame is snapped to a chunky raster and every colour forced into a SIXTEEN ENTRY palette by nearest match, before the CRT touches it - so the tube bends and scans a genuinely indexed low-resolution picture rather than a smooth one wearing a costume. Snapping after the glow is the single most common way to get this wrong. Nothing is antialiased either, on purpose: a 1985 raster had hard edges, so every shape is a step() of its distance field rather than a smoothstep. The fight is closed form and keeps no state between frames. A ship's position is a function of time, so the position it FIRED from is that same function evaluated at the instant it fired - floor(t/period)*period - a shell in flight is a lerp from there toward the beast, and the impact is the same arithmetic run past the end. The guns and the hits therefore agree with nothing remembering anything, the scanner plots the squadron by simply asking where it is, and the phosphor trail behind each fighter is that same function asked about the recent past. The limbs are an incremental WALK rather than a formula: each turns a little at every step so curvature accumulates down its length and it curls, which a closed-form bend cannot do - that can only ever wave the whole limb at once. The beast is built to be wrong: its RADIUS is modulated so the silhouette itself is lumpy rather than merely textured, veins run under the hide, a maw opens ringed with teeth made by radial repetition of a single wedge, suckers ride the underside of every limb, and up to six eyes sit unevenly placed, each blinking on its own clock behind a vertical slit. Behind it all: a vast banded spiral turning in log-polar space, gas built from three sines at different angles rather than two (with two the level sets are parallel lines, so banding them draws a hard diagonal across the frame that reads as a bug), stars scrolling by whole pixels, void creeping in at the corners with a ragged moving edge, and a great eye far back - a vesica, being the intersection of two discs, squashed vertically to make the lid - that opens now and then. The overlay is dense and drawn in the same bent raster as the game, because on a real cabinet the HUD is on the same glass: corner brackets, score, wave counter, a segmented threat meter, labelled HULL and PWR columns with percentage readouts down both flanks, a scanner with two rings, a sweep hand and blips that brighten as the hand passes, a boxed CORE/FLUX/VOID readout panel, an oscilloscope that roughens as damage lands, spinning LOCK brackets, a crawling ticker, and a WARNING that flashes only while something is actually wrong. Text is a real 3x5 bitmap font, one bit per pixel packed into floats, with whole words packed base-42 into a single float - four glyphs fit inside a 24-bit mantissa - both decoded with mod and floor since GLSL ES 1.00 has no bitwise operators. The tube is built to be believable rather than decorative: scanlines use a real beam profile so a bright line is a FATTER line (a flat multiply cannot do that, which is why most scanline filters look like a screen door laid on top), the mask is a proper slot mask with triads staggered every other row and the dark gap between slots rather than plain stripes, halation models light scattering sideways inside the glass which is much wider and softer than phosphor glow, and the glare sits on the glass in FRONT of the picture so it does not move with it. Thirty-seven controls.
export clip · render a video in your browser
Renders to video in your browser (no upload), stepping the real renderer frame by frame so multipass + feedback come out right. motion auto-animates the shader's own knobs — each export rolls a fresh take (🎲 re-roll, 🔒 to pin one you like). GIF is slower with a tighter length cap; MP4/WebM are faster. Some devices record in real time (noted below if so) — keep the tab visible.
embed · drop this shader into any page
Renders client-side in the reader's browser (CC0 — no attribution required, but the backlink is appreciated).