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.
Four parallel Float32Arrays hold x, y, vx, vy for 100 000 particles (structure-of-arrays, walked in lockstep so the hot loop is a streaming read with no object derefs). The field is 4 plane waves — each one the curl of a stream function, so the sum is genuinely divergence-free: v = Σ Cᵢ·cos(kᵢ·x + ωᵢt), with Cᵢ ⊥ kᵢ. In the loop each term costs one multiply-add pair plus one lookup into a 2048-entry Float32Array of cosine values — there is not a single Math.sin/Math.cos call per particle. Velocity is blended toward the field (τ ≈ 70 ms) so particles carry inertia, then integrated, wrapped toroidally to recycle escapees, converted to a speed index, and written as one 32-bit store into a reused ImageData buffer. Each frame that buffer is first decayed ×0.75 globally using a packed-byte trick (v - ((v & 0x00FCFCFC) >>> 2) — no per-channel unpacking), which is what makes the trails, then blitted with a single putImageData. The one thing I expect to be the bottleneck is the per-particle field evaluation + scatter write, i.e. the CPU advection loop, not the blit.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>100,000 particles · evolving curl flow field</title>
<style>
html, body { margin:0; height:100%; background:#04060a; }
body {
display:flex; align-items:center; justify-content:center;
color:#7f93a8;
font:12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
#app { display:flex; flex-direction:column; gap:9px; }
canvas { display:block; background:#000; border:1px solid #131c26; }
#hud { display:flex; gap:26px; font-size:11px; letter-spacing:.14em; text-transform:uppercase; }
#hud b { color:#dff0ff; font-weight:600; font-variant-numeric:tabular-nums; }
</style>
</head>
<body>
<div id="app">
<canvas id="cv" width="900" height="600"></canvas>
<div id="hud">
<span>fps <b id="fps">--</b></span>
<span>particles <b id="cnt">--</b></span>
</div>
</div>
<script>
(function () {
"use strict";
var W = 900, H = 600, AREA = W * H;
var N = 100000;
var cv = document.getElementById("cv");
var ctx = cv.getContext("2d", { alpha: false });
var img = ctx.createImageData(W, H);
/* one 32-bit view over the same bytes: LE => 0xAABBGGRR */
var buf = new Uint32Array(img.data.buffer, img.data.byteOffset, AREA);
/* ---------------- particle state: 4 flat arrays ---------------- */
var px = new Float32Array(N), py = new Float32Array(N);
var vx = new Float32Array(N), vy = new Float32Array(N);
for (var i = 0; i < N; i++) {
px[i] = Math.random() * W;
py[i] = Math.random() * H;
vx[i] = (Math.random() - 0.5) * 40;
vy[i] = (Math.random() - 0.5) * 40;
}
/* ---------------- cosine table: kills all trig in the hot loop ---------------- */
var TAU = Math.PI * 2;
var TB = 2048, TM = TB - 1, IDX = TB / TAU;
var CTAB = new Float32Array(TB);
for (var k = 0; k < TB; k++) CTAB[k] = Math.cos(k * TAU / TB);
/* ---------------- the flow field ----------------
v(x,t) = sum_i C_i * cos( k_i . x + omega_i t )
with C_i perpendicular to k_i => curl of a stream function => div-free.
Directions rotate, phases drift, amplitudes breathe. */
var NT = 4;
var LAM = [920, 560, 310, 170]; // wavelengths, px
var A0 = [0.30, 1.95, -0.80, 2.70]; // initial wave-vector angles
var SPIN = [0.045, -0.070, 0.110, -0.150]; // rad/s, angle drift
var SPD = [62, 42, 26, 15]; // px/s, velocity amplitude
var OMG = [0.21, -0.33, 0.52, -0.71]; // rad/s, phase drift
var kxI = new Float64Array(NT), kyI = new Float64Array(NT);
var cxv = new Float64Array(NT), cyv = new Float64Array(NT);
var off = new Float64Array(NT);
function field(t) {
for (var i = 0; i < NT; i++) {
var a = A0[i] + SPIN[i] * t;
var ca = Math.cos(a), sa = Math.sin(a);
var kk = (TAU / LAM[i]) * IDX; // table steps per pixel
kxI[i] = kk * ca;
kyI[i] = kk * sa;
var s = SPD[i] * (1 + 0.30 * Math.sin((0.09 + 0.06 * i) * t + 1.3 * i));
cxv[i] = -sa * s; // perpendicular to k
cyv[i] = ca * s;
var o = (OMG[i] * t * IDX) % 204800; // 204800 = 100 * TB, so phase-exact
if (o < 0) o += 204800;
off[i] = o + 409600; // keep the index positive => |0 == floor
}
}
/* ---------------- speed -> colour ---------------- */
var PAL = new Uint32Array(256);
for (var p = 0; p < 256; p++) {
var sN = p / 255, r, g, b;
if (sN < 0.55) {
var u1 = sN / 0.55;
r = 2 + 22 * u1; g = 8 + 50 * u1; b = 20 + 92 * u1;
} else {
var u2 = (sN - 0.55) / 0.45;
r = 24 + 86 * u2; g = 58 + 112 * u2; b = 112 + 113 * u2;
}
PAL[p] = (255 << 24) | ((b & 255) << 16) | ((g & 255) << 8) | (r & 255);
}
/* ---------------- main loop ---------------- */
var fpsEl = document.getElementById("fps");
var cntEl = document.getElementById("cnt");
var hist = new Float64Array(60);
var hi = 0, hn = 0;
var lastT = performance.now(), startT = lastT, hudAt = 0;
function tick(now) {
var raw = now - lastT;
lastT = now;
hist[hi] = raw < 100 ? raw : 100; // ms; clamp tab-switch spikes
hi = (hi + 1) % 60;
if (hn < 60) hn++;
var dt = raw * 0.001;
if (dt < 0.001) dt = 0.001; else if (dt > 0.05) dt = 0.05;
field((now - startT) * 0.001);
var b = buf, i, v;
/* 1. ink decays: every channel x0.75 in one packed pass */
for (i = 0; i < AREA; i++) {
v = b[i];
b[i] = 0xFF000000 | (v - ((v & 0x00FCFCFC) >>> 2));
}
/* 2. advect + deposit one pixel per particle */
var k0x = kxI[0], k0y = kyI[0], C0x = cxv[0], C0y = cyv[0], O0 = off[0];
var k1x = kxI[1], k1y = kyI[1], C1x = cxv[1], C1y = cyv[1], O1 = off[1];
var k2x = kxI[2], k2y = kyI[2], C2x = cxv[2], C2y = cyv[2], O2 = off[2];
var k3x = kxI[3], k3y = kyI[3], C3x = cxv[3], C3y = cyv[3], O3 = off[3];
var CT = CTAB, M = TM, P = PAL;
var al = 1 - Math.exp(-dt / 0.07); // inertia blend (frame-rate independent)
var be = 1 - al;
for (i = 0; i < N; i++) {
var x = px[i], y = py[i];
var a0 = CT[((x * k0x + y * k0y + O0) | 0) & M];
var a1 = CT[((x * k1x + y * k1y + O1) | 0) & M];
var a2 = CT[((x * k2x + y * k2y + O2) | 0) & M];
var a3 = CT[((x * k3x + y * k3y + O3) | 0) & M];
var fx = C0x * a0 + C1x * a1 + C2x * a2 + C3x * a3;
var fy = C0y * a0 + C1y * a1 + C2y * a2 + C3y * a3;
var u = vx[i] * be + fx * al;
var w = vy[i] * be + fy * al;
vx[i] = u; vy[i] = w;
var nx = x + u * dt, ny = y + w * dt;
if (nx < 0) nx += W; else if (nx >= W) nx -= W; // recycle at the walls
if (ny < 0) ny += H; else if (ny >= H) ny -= H;
px[i] = nx; py[i] = ny;
var sp = (u * u + w * w) * 0.0155;
if (sp > 255) sp = 255;
b[((ny | 0) * W) + (nx | 0)] = P[sp | 0];
}
ctx.putImageData(img, 0, 0);
if (now - hudAt > 200) {
hudAt = now;
var sum = 0;
for (i = 0; i < hn; i++) sum += hist[i];
fpsEl.textContent = (1000 / (sum / hn)).toFixed(1);
cntEl.textContent = String(px.length);
}
requestAnimationFrame(tick);
}
cntEl.textContent = String(px.length);
requestAnimationFrame(tick);
})();
</script>
</body>
</html>
FPS I expect: 60, vsync-capped, on a 2023 laptop with integrated graphics in Chrome. Raw frame cost is about 7–9 ms, so this is not a marginal 60 — there's roughly 2× headroom before it would fall to 30.
Where the time goes. Roughly 55–60 % update, 40 % draw, and the split is deliberate because I made the draw side of the particle loop cheap (one 32-bit store) and paid for it in the update:
and/shift/sub/or + store. Pure memory bandwidth (~4.3 MB read+write per frame), which is why the bit-packed trick matters: unpacking to four Uint8ClampedArray writes would roughly triple this.putImageData, ~0.7–1 ms (~12 %). 2.16 MB CPU→canvas per frame.field() (16 trig calls per frame), the rAF callback, the HUD write four times a second.The one number I'd watch: if the machine is on battery, or Chrome falls back to software canvas rasterization, the decay pass and the blit both get slower and the frame time roughly doubles — which is exactly the 30 fps cliff.
The first thing that breaks at a million. The per-particle advection loop, not the drawing. 10× the particles is 10× that ~4 ms cost, so it alone becomes ~40–50 ms/frame (≈20 fps), and no amount of render-side cleverness touches it. The second failure is right behind it: 1 M single-pixel deposits into a 540 000-pixel buffer is 2× oversubscription, so the plot pass becomes meaningless smearing as well as slow (~10 ms of near-random writes). The canvas simply cannot resolve a million distinct things at 900 × 600.
What I'd need to go 10× faster. Move the simulation off the CPU entirely: GPGPU particle integration with ping-pong float render targets (WebGL2 float textures, or transform feedback), one texel per particle, position and velocity read and rewritten inside a fragment shader, then drawn with gl.POINTS. The flow field evaluates per-texel on the GPU for free, and the CPU issues three draw calls and uploads nothing. That's the technique — the JS loop cannot be optimized into this regime, it has to be relocated.
Nothing dropped. 100,000 real particles with per-particle position and velocity state, all updated and drawn every frame; the field is a genuine divergence-free curl field that rotates, drifts and breathes over time; escapees are wrapped back in so the count is constant; and both readouts are live — the FPS is the mean of the last 60 measured frame deltas, and the count is px.length, read from the array that actually holds them.