Show HN: Browser viz of OpenAI's "spaghetti" Navier–Stokes vortex (illustration) A browser-based WebGL visualization illustrates the vortex structure proposed in OpenAI's 2026 Navier–Stokes blowup paper, using parametric helices scaled by the paper's leading laws with an illustrative h = 0.005. The demo, posted on Hacker News, allows users to orbit, scrub toward the singularity time T*, and follow the core, but it is explicitly an illustration of the construction, not a numerical reproduction of the proof. 28. Scientific visualization · 2026 Spaghetti vortex About Parametric helices scaled by the 2026 OpenAI paper's leading laws: radius follows tau^ 1/2 , height tau^ 1/2−h , with illustrative h = 0.005 and tau = 1−t/T . Both shrink; radius shrinks faster. Drag to orbit, scrub toward T , or follow the core with a labeled uniform zoom. Color shows normalized radius. Oscillatory corrections are omitted. Illustration of the proposed blowup construction; not a numerical reproduction of the proof. Drag to orbit; auto-rotation stops after the first drag. Play, pause, scrub the approach, or follow the core. Browser APIs - WebGL 1 - GLSL ES 1.0 - Pointer Events - requestAnimationFrame - matchMedia If WebGL is missing, the demo draws a message on the canvas instead of a blank frame. prefers-reduced-motion freezes the first still. Source js function { const PARAMS = { color: " 6ee7b7", core: " f2b45c", trail: 0.35, speed: 1 }; // Leading scales: OpenAI 2026 , section 2.1. h is illustrative. // These scales do not define a velocity field or simulate its corrections. const H = 0.005, MAX S = 12, COUNT = 960, SAMPLES = 24; function tau s { return Math.pow 10, -s ; } function scales s { const t = tau s ; return { radial: Math.pow t, 0.5 , axial: Math.pow t, 0.5 - H , speed: Math.pow t, -0.5 - H }; } function seed i { // Fixed low-discrepancy seeds: reset and reverse seeking need no RNG state. return { phase: i 0.61803398875 % 1, angle: i 2.60258057 % 2 Math.PI , side: i % 2 ? 1 : -1 }; } function position p, s { const k = scales s , cycle = p.phase + s 0.65; const q = cycle - Math.floor cycle ; // An inward helix bends into two axial exits. Recycling is a schematic // seeding device; history segments across a recycle boundary are omitted. const r = 0.14 + 1.36 Math.exp -3.6 q ; const angle = p.angle + s 6 + q 10; return k.radial r Math.cos angle , k.axial p.side 0.08 + 2.1 q q , k.radial r Math.sin angle , r / 1.5, Math.floor cycle ; } function rgb hex { const n = parseInt hex.slice 1 , 16 ; return n 16 & 255 / 255, n 8 & 255 / 255, n & 255 / 255 ; } const canvas = document.getElementById "c" , fallback = document.getElementById "fallback" ; const play = document.getElementById "play" , slider = document.getElementById "approach" ; const follow = document.getElementById "follow" , reset = document.getElementById "reset" ; const timeText = document.getElementById "time" , zoomText = document.getElementById "zoom" , ruler = document.getElementById "ruler" ; document.getElementById "ramp" .style.background = "linear-gradient 90deg," + PARAMS.core + "," + PARAMS.color + " "; let gl; try { gl = canvas.getContext "webgl", { alpha: false, antialias: true } ; } catch { / Fallback below. / } function fail message { fallback.hidden = false; fallback.textContent = message; for const el of play, slider, follow, reset el.disabled = true; // A failed WebGL creation still allows a Canvas 2D explanation. if gl { const ctx = canvas.getContext "2d" ; if ctx { canvas.width = Math.max 1, canvas.clientWidth ; canvas.height = Math.max 1, canvas.clientHeight ; ctx.fillStyle = " 000"; ctx.fillRect 0, 0, canvas.width, canvas.height ; ctx.fillStyle = " c8d7d0"; ctx.font = "12px monospace"; ctx.textAlign = "center"; ctx.fillText "WebGL unavailable", canvas.width / 2, canvas.height / 2 ; } } } if gl { fail "WebGL is unavailable. Enable WebGL to view this illustration." ; return; } const VERT = attribute vec3 a pos; attribute vec2 a style; uniform mat4 u mvp; uniform float u zoom; uniform float u point; varying vec2 v style; varying float v depth; void main { gl Position = u mvp vec4 a pos u zoom, 1.0 ; gl PointSize = u point; v style = a style; v depth = clamp 1.5 - gl Position.w 0.1, 0.25, 1.0 ; } ; const FRAG = precision mediump float; uniform vec3 u outer; uniform vec3 u core; varying vec2 v style; varying float v depth; void main { vec3 color = mix u core, u outer, smoothstep 0.08, 0.85, v style.x ; gl FragColor = vec4 color, v style.y v depth ; } ; function compile type, source { const shader = gl.createShader type ; gl.shaderSource shader, source ; gl.compileShader shader ; if gl.getShaderParameter shader, gl.COMPILE STATUS throw new Error "Could not compile the vortex shader." ; return shader; } // Column-major orbit matrices shared with the gallery's lowpoly example. function mul a, b { const out = new Float32Array 16 ; for let c = 0; c < 4; c++ for let r = 0; r < 4; r++ { out c 4 + r = a r b c 4 + a 4 + r b c 4 + 1 + a 8 + r b c 4 + 2 + a 12 + r b c 4 + 3 ; } return out; } function camera aspect, pitch, yaw { const f = 1 / Math.tan Math.PI / 8 , near = 0.1, far = 50; const proj = new Float32Array f/aspect,0,0,0, 0,f,0,0, 0,0, far+near / near-far ,-1, 0,0,2 far near/ near-far ,0 ; const cx = Math.cos pitch , sx = Math.sin pitch , cy = Math.cos yaw , sy = Math.sin yaw ; const rx = new Float32Array 1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1 ; const ry = new Float32Array cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1 ; const view = new Float32Array 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-8.8 Math.max 1,0.8/aspect ,1 ; return mul proj, mul view, mul rx, ry ; } let prog, buffer, loc; function setup { prog = gl.createProgram ; gl.attachShader prog, compile gl.VERTEX SHADER, VERT ; gl.attachShader prog, compile gl.FRAGMENT SHADER, FRAG ; gl.linkProgram prog ; if gl.getProgramParameter prog, gl.LINK STATUS throw new Error "Could not link the vortex shader." ; buffer = gl.createBuffer ; loc = {}; for const key of "mvp", "zoom", "point", "outer", "core" loc key = gl.getUniformLocation prog, "u " + key ; gl.useProgram prog ; gl.bindBuffer gl.ARRAY BUFFER, buffer ; for const name, size, offset of "a pos", 3, 0 , "a style", 2, 12 { const at = gl.getAttribLocation prog, name ; gl.enableVertexAttribArray at ; gl.vertexAttribPointer at, size, gl.FLOAT, false, 20, offset ; } gl.uniform3fv loc.outer, rgb PARAMS.color ; gl.uniform3fv loc.core, rgb PARAMS.core ; gl.enable gl.BLEND ; gl.blendFunc gl.SRC ALPHA, gl.ONE ; gl.clearColor 0, 0, 0, 1 ; } try { setup ; } catch err { fail err.message ; return; } const motion = matchMedia " prefers-reduced-motion: reduce " ; let s = motion.matches ? 6 : 0.6, playing = motion.matches, auto = motion.matches; let yaw = 0.7, pitch = 0.34, pointer = null, lastX = 0, lastY = 0; let raf = 0, last = 0, lost = false, dirty = true; let count = COUNT, slow = 0; const seeds = Array.from { length: COUNT }, , i = seed i ; // Bounded world-space history ring, regenerated analytically on every seek. // Fixed sample spacing makes history independent of playback frame rate. const history = new Float32Array COUNT SAMPLES 5 ; const vertices = new Float32Array COUNT SAMPLES - 1 2 5 + COUNT 5 ; let lineCount = 0, vertexCount = 0; function rebuild { const step = PARAMS.trail / SAMPLES - 1 , tick = Math.floor s / step , head = tick % SAMPLES; let n = 0; for let i = 0; i < count; i++ { for let age = 0; age < SAMPLES; age++ { const slot = head - age + SAMPLES % SAMPLES; const t = age === 0 ? s : Math.max 0, tick - age + 1 step ; history.set position seeds i , t , i SAMPLES + slot 5 ; } for let age = SAMPLES - 1; age 0; age-- { const a = i SAMPLES + head - age + SAMPLES % SAMPLES 5; const b = i SAMPLES + head - age + 1 + SAMPLES % SAMPLES 5; if history a + 4 == history b + 4 continue; for const at of a, b { vertices n++ = history at ; vertices n++ = history at+1 ; vertices n++ = history at+2 ; vertices n++ = history at+3 ; vertices n++ = 0.16 1 - age / SAMPLES ; } } } lineCount = n / 5; for let i = 0; i < count; i++ { const at = i SAMPLES + head 5; vertices n++ = history at ; vertices n++ = history at+1 ; vertices n++ = history at+2 ; vertices n++ = history at+3 ; vertices n++ = 0.65; } vertexCount = n / 5; dirty = false; } function draw { const dpr = Math.min devicePixelRatio || 1, 2 ; const w = Math.max 1, Math.round canvas.clientWidth dpr , h = Math.max 1, Math.round canvas.clientHeight dpr ; if canvas.width == w || canvas.height == h { canvas.width = w; canvas.height = h; } gl.viewport 0, 0, canvas.width, canvas.height ; gl.clear gl.COLOR BUFFER BIT ; gl.uniformMatrix4fv loc.mvp, false, camera canvas.width / canvas.height, pitch, yaw ; const mag = follow.checked ? 1 / scales s .radial : 1; gl.uniform1f loc.zoom, 1 ; gl.uniform1f loc.point, Math.min 2, dpr 1.3 ; // Reference ticks are in view units; the label converts back to world units. const axes = ; function line a, b { for const p of a, b axes.push ...p, 1, 0.13 ; } line 0,-2.7,0 , 0,2.7,0 ; for let i = -3; i <= 3; i++ { line i 0.5,-2.5,-1.5 , i 0.5,-2.5,1.5 ; line -1.5,-2.5,i 0.5 , 1.5,-2.5,i 0.5 ; } gl.bufferData gl.ARRAY BUFFER, new Float32Array axes , gl.DYNAMIC DRAW ; gl.drawArrays gl.LINES, 0, axes.length / 5 ; if dirty rebuild ; gl.uniform1f loc.zoom, mag ; gl.bufferData gl.ARRAY BUFFER, vertices.subarray 0, vertexCount 5 , gl.DYNAMIC DRAW ; gl.drawArrays gl.LINES, 0, lineCount ; gl.drawArrays gl.POINTS, lineCount, vertexCount - lineCount ; timeText.textContent = "1−t/T = " + tau s .toExponential 2 ; zoomText.textContent = follow.checked ? "FOLLOW · " : "FIXED · " + mag.toExponential 2 + "×"; ruler.textContent = "grid Δ = " + 0.5 / mag .toExponential 1 + " initial units"; slider.value = String s ; slider.setAttribute "aria-valuetext", "s " + s.toFixed 2 + ", remaining time " + tau s .toExponential 2 ; play.textContent = s = MAX S ? "Replay" : playing ? "Pause" : "Play"; play.setAttribute "aria-pressed", String playing ; } function wake { if raf && lost && document.hidden raf = requestAnimationFrame frame ; } function frame now { raf = 0; const dt = last ? Math.min now - last / 1000, 0.05 : 0; last = now; if playing { s = Math.min MAX S, s + dt 0.24 PARAMS.speed ; dirty = true; if s = MAX S playing = false; } if auto yaw += dt 0.09; const start = performance.now ; draw ; // Reduce geometry only after sustained expensive frames, never change seeds. slow = performance.now - start 24 ? slow + 1 : 0; if slow 45 && count 480 { count = 480; dirty = true; slow = 0; } if playing || auto wake ; else last = 0; } play.addEventListener "click", function { if s = MAX S { s = 0.6; dirty = true; } playing = playing; last = 0; wake ; } ; slider.addEventListener "input", function { s = Math.max 0, Math.min MAX S, Number slider.value ; playing = false; dirty = true; last = 0; wake ; } ; follow.addEventListener "change", wake ; reset.addEventListener "click", function { s = motion.matches ? 6 : 0.6; yaw = 0.7; pitch = 0.34; count = COUNT; slow = 0; playing = motion.matches; auto = motion.matches; follow.checked = true; pointer = null; dirty = true; last = 0; wake ; } ; canvas.addEventListener "pointerdown", function e { if pointer == null return; pointer = e.pointerId; lastX = e.clientX; lastY = e.clientY; canvas.setPointerCapture e.pointerId ; } ; canvas.addEventListener "pointermove", function e { if e.pointerId == pointer return; auto = false; yaw += e.clientX - lastX 0.008; pitch = Math.max -1.2, Math.min 1.2, pitch + e.clientY - lastY 0.008 ; lastX = e.clientX; lastY = e.clientY; wake ; } ; for const name of "pointerup", "pointercancel", "lostpointercapture" canvas.addEventListener name, function { pointer = null; } ; motion.addEventListener "change", function { if motion.matches { playing = false; auto = false; } wake ; } ; document.addEventListener "visibilitychange", function { cancelAnimationFrame raf ; raf = 0; last = 0; if document.hidden wake ; } ; new ResizeObserver wake .observe document.getElementById "scene" ; canvas.addEventListener "webglcontextlost", function e { e.preventDefault ; lost = true; cancelAnimationFrame raf ; raf = 0; fail "WebGL context lost. Waiting for the browser to restore it." ; } ; canvas.addEventListener "webglcontextrestored", function { try { setup ; lost = false; dirty = true; last = 0; fallback.hidden = true; for const el of play, slider, follow, reset el.disabled = false; wake ; } catch err { fail err.message ; } } ; wake ; } ;