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
(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();
})();