47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
16:54Stealth Union Alpha: Is this another cheap GPT model? (better than Luna, cheaper than Terra?)
Sep 16, 2026 · YouTube
16:45DeepSeek V4.1 Flash Is Shockingly Good for the Price
Sep 10, 2026 · YouTube
17:14How much better is GPT-6 Astra over Claude Opus 5? Real Website and Coding Tests
Sep 5, 2026 · YouTube
29:13Implementing a multiplayer game with GPT-6 Astra (+ Gemini 3.8, Opus 5, Sol Pro)
Sep 5, 2026 · YouTube
19:42Meta Muse Spark 1.3 Review: A Huge Leap Forward
Sep 2, 2026 · YouTube
17:31Testing Gemini 3.8 Flash on Real Coding Challenges (vs. other models)
Sep 2, 2026 · YouTube
You are a graphics engineer who has spent a lot of time finding out why things are slow.
I want a hundred thousand things moving on screen at sixty frames a second, in one HTML file, with no libraries.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file. Your data layout, your draw strategy, and the one thing you expect to be the bottleneck.
The single fenced html block.
This is the section I care most about. Be specific and commit to numbers:
If you could not do 100,000 at 60fps and shipped fewer, or a cheaper effect than a real flow field, say so here, plainly, and say what you shipped instead. Write Nothing dropped. if you delivered the brief in full.
I am going to open the file and read the FPS counter.
Then I am going to compare it against your prediction. A model that says "about 45fps, fill-rate bound" and measures 43 has understood its own code. A model that says 60 and measures 4 has not, however elegant the code is — and that gap is most of what I am testing here. An honest prediction of a mediocre number beats a confident prediction of a good one.
Use WebGL2 transform feedback: two GPU buffers hold interleaved position and velocity records, and every frame updates all 100,000 records before drawing all 100,000 as individual points. The flow is the analytic curl of two overlapping, time-varying stream functions, with a slowly changing drift; particles wrap at the edges. Two framebuffer textures accumulate fading trails without relying on preserved canvas contents. JavaScript only submits passes and measures frame intervals. I expect the main cost at this count to be GPU drawing—point blending and the full-canvas trail passes—not JavaScript iteration.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>100,000 — evolving flow</title>
<style>
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; background: #070d13; }
body {
color: #dcebf0;
font: 14px system-ui, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 12px;
}
header {
width: min(900px, 100%);
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 12px;
}
h1 { margin: 0; font-size: 15px; font-weight: 500; }
.meters {
display: flex;
gap: 22px;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.label { color: #8297a4; margin-right: 6px; }
canvas {
display: block;
width: min(900px, 100%);
height: auto;
aspect-ratio: 3 / 2;
background: #000;
border: 1px solid #22323e;
}
#status {
width: min(900px, 100%);
color: #8297a4;
font-size: 12px;
margin-top: 10px;
line-height: 1.5;
}
@media (max-width: 520px) {
header { flex-direction: column; align-items: flex-start; }
}
</style>
</head>
<body>
<header>
<h1>Evolving curl / GPU particles</h1>
<div class="meters">
<span><span class="label">FPS</span><strong id="fps">—</strong></span>
<span><span class="label">Particles</span><strong id="count">—</strong></span>
</div>
</header>
<canvas id="canvas" width="900" height="600"
aria-label="One hundred thousand particles moving through an evolving flow field">
</canvas>
<div id="status">
900 × 600 backing buffer · Every particle updated and drawn every frame ·
FPS = reciprocal of the rolling mean frame interval
</div>
<script>
"use strict";
(() => {
const canvas = document.getElementById("canvas");
const fpsElement = document.getElementById("fps");
const countElement = document.getElementById("count");
const status = document.getElementById("status");
let request = 0;
try {
const gl = canvas.getContext("webgl2", {
alpha: false,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: false,
powerPreference: "high-performance"
});
if (!gl) throw new Error("WebGL2 is required; no reduced-count fallback is used.");
const W = canvas.width;
const H = canvas.height;
const STRIDE = 4 * Float32Array.BYTES_PER_ELEMENT;
function shader(type, source) {
const s = gl.createShader(type);
gl.shaderSource(s, source);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(s);
gl.deleteShader(s);
throw new Error(error);
}
return s;
}
function program(vs, fs, varyings) {
const p = gl.createProgram();
const v = shader(gl.VERTEX_SHADER, vs);
const f = shader(gl.FRAGMENT_SHADER, fs);
gl.attachShader(p, v);
gl.attachShader(p, f);
if (varyings) {
gl.transformFeedbackVaryings(p, varyings, gl.INTERLEAVED_ATTRIBS);
}
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
throw new Error(gl.getProgramInfoLog(p));
}
gl.detachShader(p, v);
gl.detachShader(p, f);
gl.deleteShader(v);
gl.deleteShader(f);
return p;
}
const updateProgram = program(
`#version 300 es
precision highp float;
layout(location=0) in vec4 aState; // x, y, vx, vy
uniform vec2 uSize;
uniform vec4 uPhase;
uniform vec2 uDrift;
uniform float uDt;
uniform float uResponse;
out vec4 nextState;
void main() {
vec2 p = aState.xy;
vec2 v = aState.zw;
// Each term is (d psi / dy, -d psi / dx).
// Therefore the target field is divergence-free.
vec2 q = p * 0.012 + uPhase.xy;
vec2 s = sin(q);
vec2 c = cos(q);
vec2 flow = 70.0 * vec2(s.x * c.y, -c.x * s.y);
q = p * vec2(0.021, 0.017) + uPhase.zw;
s = sin(q);
c = cos(q);
flow += (28.0 / 0.019) *
vec2(0.017 * s.x * c.y, -0.021 * c.x * s.y);
flow += uDrift;
// Inertial response, rather than instantly replacing velocity.
v += (flow - v) * uResponse;
p += v * uDt;
// Recycle the same record at the opposite edge.
p = mod(p + uSize, uSize);
nextState = vec4(p, v);
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}`,
`#version 300 es
precision highp float;
void main() {}
`,
["nextState"]
);
const drawProgram = program(
`#version 300 es
precision highp float;
layout(location=0) in vec4 aState;
uniform vec2 uSize;
out vec3 vColor;
void main() {
vec2 clip = aState.xy / uSize * 2.0 - 1.0;
gl_Position = vec4(clip, 0.0, 1.0);
gl_PointSize = 1.35;
float speed = clamp(length(aState.zw) / 110.0, 0.0, 1.0);
vColor = mix(vec3(0.12, 0.42, 0.62),
vec3(0.60, 0.94, 0.80), speed);
}`,
`#version 300 es
precision highp float;
in vec3 vColor;
out vec4 fragColor;
void main() {
fragColor = vec4(vColor, 0.15);
}`
);
const fadeProgram = program(
`#version 300 es
precision highp float;
out vec2 vUV;
void main() {
vec2 p = vec2(float((gl_VertexID << 1) & 2),
float(gl_VertexID & 2));
vUV = p;
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
}`,
`#version 300 es
precision highp float;
uniform sampler2D uPrevious;
uniform float uFade;
in vec2 vUV;
out vec4 fragColor;
void main() {
vec3 old = texture(uPrevious, vUV).rgb;
// Subtract half a code value so RGBA8 rounding cannot leave
// permanent low-level ghosts.
fragColor = vec4(max(old * uFade - vec3(0.5 / 255.0), 0.0), 1.0);
}`
);
const U = {
size: gl.getUniformLocation(updateProgram, "uSize"),
phase: gl.getUniformLocation(updateProgram, "uPhase"),
drift: gl.getUniformLocation(updateProgram, "uDrift"),
dt: gl.getUniformLocation(updateProgram, "uDt"),
response: gl.getUniformLocation(updateProgram, "uResponse")
};
const fadeLocation = gl.getUniformLocation(fadeProgram, "uFade");
gl.useProgram(updateProgram);
gl.uniform2f(U.size, W, H);
gl.useProgram(drawProgram);
gl.uniform2f(gl.getUniformLocation(drawProgram, "uSize"), W, H);
gl.useProgram(fadeProgram);
gl.uniform1i(gl.getUniformLocation(fadeProgram, "uPrevious"), 0);
function makeParticleBuffer(initialData) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, initialData, gl.DYNAMIC_COPY);
// Derive the record count from the actual allocated GPU storage.
const count = gl.getBufferParameter(
gl.ARRAY_BUFFER, gl.BUFFER_SIZE
) / STRIDE;
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 4, gl.FLOAT, false, STRIDE, 0);
gl.bindVertexArray(null);
return { buffer, vao, count };
}
let initial = new Float32Array(100000 * 4);
for (let i = 0; i < initial.length; i += 4) {
initial[i] = Math.random() * W;
initial[i + 1] = Math.random() * H;
initial[i + 2] = (Math.random() - 0.5) * 30;
initial[i + 3] = (Math.random() - 0.5) * 30;
}
const particles = [
makeParticleBuffer(initial),
makeParticleBuffer(initial)
];
initial = null;
let particleRead = 0;
countElement.textContent = particles[particleRead].count.toLocaleString();
function makeTrailTarget() {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, W, H);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const framebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, texture, 0
);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !==
gl.FRAMEBUFFER_COMPLETE) {
throw new Error("Could not allocate a trail framebuffer.");
}
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
return { texture, framebuffer };
}
const trails = [makeTrailTarget(), makeTrailTarget()];
let trailRead = 0;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const feedback = gl.createTransformFeedback();
const emptyVAO = gl.createVertexArray();
gl.disable(gl.DEPTH_TEST);
gl.disable(gl.CULL_FACE);
gl.disable(gl.DITHER);
gl.viewport(0, 0, W, H);
gl.activeTexture(gl.TEXTURE0);
// A genuine rolling arithmetic mean of rAF frame intervals.
// FPS = 1000 / meanMilliseconds; never average reciprocal FPS.
const intervals = new Float64Array(120);
let intervalIndex = 0;
let intervalCount = 0;
let intervalSum = 0;
let lastTime = null;
let lastHUD = 0;
let simulationTime = 0;
document.addEventListener("visibilitychange", () => {
// Background throttling is not a foreground rendering sample.
lastTime = null;
intervalIndex = intervalCount = intervalSum = 0;
intervals.fill(0);
fpsElement.textContent = "—";
});
canvas.addEventListener("webglcontextlost", event => {
event.preventDefault();
cancelAnimationFrame(request);
fpsElement.textContent = "stopped";
status.textContent = "WebGL context lost. Reload to recreate GPU state.";
});
function frame(now) {
const elapsed = lastTime === null ? null : now - lastTime;
lastTime = now;
if (elapsed !== null && elapsed > 0) {
intervalSum -= intervals[intervalIndex];
intervals[intervalIndex] = elapsed;
intervalSum += elapsed;
intervalIndex = (intervalIndex + 1) % intervals.length;
intervalCount = Math.min(intervalCount + 1, intervals.length);
}
if (now - lastHUD >= 250 && intervalCount) {
fpsElement.textContent =
(1000 * intervalCount / intervalSum).toFixed(1);
countElement.textContent =
particles[particleRead].count.toLocaleString();
lastHUD = now;
}
// Cap physics steps after a stall, but NEVER cap the FPS samples.
const dt = Math.min((elapsed === null ? 16.667 : elapsed) * 0.001,
1 / 30);
simulationTime += dt;
const t = simulationTime;
const source = particles[particleRead];
const destination = particles[particleRead ^ 1];
// Pass 1: update every particle into the other GPU buffer.
gl.useProgram(updateProgram);
gl.uniform1f(U.dt, dt);
gl.uniform1f(U.response, 1 - Math.exp(-2.2 * dt));
gl.uniform4f(
U.phase,
0.23 * t + 0.45 * Math.sin(t * 0.11),
-0.17 * t,
-0.13 * t + 1.7,
0.19 * t + 0.6 * Math.sin(t * 0.073)
);
gl.uniform2f(
U.drift,
15 + 9 * Math.sin(t * 0.09),
10 * Math.cos(t * 0.07)
);
gl.bindVertexArray(source.vao);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, feedback);
gl.bindBufferBase(
gl.TRANSFORM_FEEDBACK_BUFFER, 0, destination.buffer
);
gl.enable(gl.RASTERIZER_DISCARD);
gl.beginTransformFeedback(gl.POINTS);
gl.drawArrays(gl.POINTS, 0, source.count);
gl.endTransformFeedback();
gl.disable(gl.RASTERIZER_DISCARD);
gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, null);
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
particleRead ^= 1;
// Pass 2: fade previous trails into a different texture.
const previousTrail = trails[trailRead];
const nextTrail = trails[trailRead ^ 1];
gl.bindFramebuffer(gl.FRAMEBUFFER, nextTrail.framebuffer);
gl.disable(gl.BLEND);
gl.useProgram(fadeProgram);
gl.uniform1f(fadeLocation, Math.exp(-4.8 * dt));
gl.bindTexture(gl.TEXTURE_2D, previousTrail.texture);
gl.bindVertexArray(emptyVAO);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Pass 3: draw every updated particle, individually, as a point.
gl.useProgram(drawProgram);
gl.bindVertexArray(destination.vao);
gl.enable(gl.BLEND);
gl.blendFuncSeparate(
gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
gl.ONE, gl.ONE_MINUS_SRC_ALPHA
);
gl.drawArrays(gl.POINTS, 0, destination.count);
gl.disable(gl.BLEND);
// Pass 4: present. No preserveDrawingBuffer dependency.
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, nextTrail.framebuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
gl.blitFramebuffer(
0, 0, W, H, 0, 0, W, H,
gl.COLOR_BUFFER_BIT, gl.NEAREST
);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
trailRead ^= 1;
request = requestAnimationFrame(frame);
}
request = requestAnimationFrame(frame);
} catch (error) {
cancelAnimationFrame(request);
fpsElement.textContent = "unavailable";
status.textContent = "Unable to start: " + error.message;
console.error(error);
}
})();
</script>
</body>
</html>
requestAnimationFrame; higher-refresh displays can report more.Nothing dropped.