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.
Structure-of-arrays Float32Arrays (px, py, vx, vy) hold the 100k particles so the hot loop is a flat, branch-light sweep over contiguous memory with zero per-frame allocation. The flow field is the 2D curl (stream-function) of four traveling, slowly rotating sine waves — divergence-free by construction, so it swirls and reorganizes like fluid instead of settling — sampled once per frame onto a 61×41 grid and bilinearly interpolated per particle. Trails live in a persistent full-res ImageData buffer viewed as a Uint32Array: each frame I fade every pixel through three 256-entry LUTs, then each particle overwrites one pixel from a speed→color palette, and I do a single putImageData blit with the HUD painted on top via fillText. The GPU is essentially idle, so this is a single-threaded CPU problem; the one thing I expect to be the bottleneck is the per-particle integration loop, because it's the only part that scales with N and does ~50 ops plus ~10 typed-array reads per particle.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flow Field · 100,000 particles</title>
<style>
:root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
min-height: 100%;
background: #06080c;
background-image:
radial-gradient(1100px 640px at 50% 12%, #0c1119 0%, rgba(12,17,25,0) 62%),
radial-gradient(900px 520px at 84% 98%, #0a1017 0%, rgba(10,16,23,0) 60%);
color: #c6d1dc;
font-family: ui-sans-serif, system-ui, "Segoe UI", Helvetica, Arial, sans-serif;
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 14px; padding: 24px;
}
header { text-align: center; }
h1 {
margin: 0;
font-size: 15px; font-weight: 700;
letter-spacing: .24em; text-transform: uppercase;
color: #eaf2fa;
}
h1 .dot { color: #45d6c4; }
.sub { margin: 6px 0 0; font-size: 11px; letter-spacing: .1em; color: #5b6a7b; }
canvas {
display: block;
width: 900px; height: 600px;
background: #06080d;
border: 1px solid #1b2531;
border-radius: 6px;
box-shadow: 0 24px 70px rgba(0,0,0,.6), 0 0 0 1px rgba(90,200,255,.05);
}
footer {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 11px; letter-spacing: .03em; color: #45525f;
}
footer b { color: #7f92a4; font-weight: 600; }
footer .sep { color: #2a3542; padding: 0 6px; }
</style>
</head>
<body>
<header>
<h1>Flow Field <span class="dot">·</span> 100,000 Particles</h1>
<p class="sub">curl of four traveling waves — divergence-free, evolving, torus-wrapped</p>
</header>
<canvas id="c" width="900" height="600"></canvas>
<footer>
<b>sim</b> typed-array SoA<span class="sep">/</span><b>field</b> 61×41 grid, bilinear<span class="sep">/</span><b>trails</b> ImageData fade<span class="sep">/</span><b>blit</b> 1× putImageData
</footer>
<script>
'use strict';
(() => {
const W = 900, H = 600;
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
// ---- particles: structure of arrays ----
const N = 100000;
const px = new Float32Array(N), py = new Float32Array(N);
const vx = new Float32Array(N), vy = new Float32Array(N);
for (let i = 0; i < N; i++) {
px[i] = Math.random() * W;
py[i] = Math.random() * H;
vx[i] = (Math.random() - 0.5) * 0.6;
vy[i] = (Math.random() - 0.5) * 0.6;
}
// ---- persistent trail buffer (full-res), faded per-pixel via LUTs ----
const img = ctx.createImageData(W, H);
const buf = new Uint32Array(img.data.buffer);
const FADE = 0.90;
const BG = [6, 9, 15];
const lutR = new Uint8Array(256), lutG = new Uint8Array(256), lutB = new Uint8Array(256);
for (let v = 0; v < 256; v++) {
lutR[v] = (v * FADE + BG[0] * (1 - FADE)) | 0;
lutG[v] = (v * FADE + BG[1] * (1 - FADE)) | 0;
lutB[v] = (v * FADE + BG[2] * (1 - FADE)) | 0;
}
const BG32 = (0xff000000 | (BG[2] << 16) | (BG[1] << 8) | BG[0]) >>> 0;
buf.fill(BG32);
// ---- speed -> color palette (deep blue -> teal -> pale cyan) ----
const PAL = new Uint32Array(256);
const stops = [
[0.00, 12, 26, 60],
[0.30, 22, 84, 132],
[0.60, 44, 172, 188],
[0.85, 128, 226, 220],
[1.00, 232, 250, 255],
];
for (let i = 0; i < 256; i++) {
const t = i / 255;
let a = stops[0], b = stops[stops.length - 1];
for (let s = 0; s < stops.length - 1; s++) {
if (t >= stops[s][0] && t <= stops[s + 1][0]) { a = stops[s]; b = stops[s + 1]; break; }
}
const f = (t - a[0]) / ((b[0] - a[0]) || 1);
const r = (a[1] + (b[1] - a[1]) * f) | 0;
const g = (a[2] + (b[2] - a[2]) * f) | 0;
const bl = (a[3] + (b[3] - a[3]) * f) | 0;
PAL[i] = (0xff000000 | (bl << 16) | (g << 8) | r) >>> 0;
}
// ---- flow field: curl of a scalar potential (divergence-free) ----
const GW = 60, GH = 40;
const GW1 = GW + 1, GH1 = GH + 1;
const FX = new Float32Array(GW1 * GH1);
const FY = new Float32Array(GW1 * GH1);
const CW = W / GW, CH = H / GH;
const INV_CW = 1 / CW, INV_CH = 1 / CH;
const SPEED = 13;
// [wavenumber, amplitude, base angle, phase speed, rotation speed]
const waves = [
{ k: 2 * Math.PI / 420, a: 1.00, th: 0.3, w: 0.00021, r: 0.00004 },
{ k: 2 * Math.PI / 260, a: 0.72, th: 2.1, w: 0.00034, r: -0.00006 },
{ k: 2 * Math.PI / 150, a: 0.46, th: 4.4, w: 0.00052, r: 0.00009 },
{ k: 2 * Math.PI / 88, a: 0.30, th: 1.2, w: 0.00077, r: -0.00012 },
];
const NW = waves.length;
const wkx = new Float32Array(NW), wky = new Float32Array(NW), wph = new Float32Array(NW);
function updateField(t) {
for (let i = 0; i < NW; i++) {
const wv = waves[i];
const th = wv.th + wv.r * t;
wkx[i] = wv.k * Math.cos(th);
wky[i] = wv.k * Math.sin(th);
wph[i] = wv.w * t;
}
const du = 0.20 + 0.12 * Math.sin(t * 0.00013); // slow prevailing drift
const dv = 0.08 + 0.08 * Math.cos(t * 0.00009);
let idx = 0;
for (let gy = 0; gy <= GH; gy++) {
const y = gy * CH;
for (let gx = 0; gx <= GW; gx++, idx++) {
const x = gx * CW;
let u = du, v = dv;
for (let i = 0; i < NW; i++) {
const s = wkx[i] * x + wky[i] * y + wph[i];
u += waves[i].a * wky[i] * Math.cos(s); // dpsi/dy
v -= waves[i].a * wkx[i] * Math.sin(s); // -dpsi/dx
}
FX[idx] = u * SPEED;
FY[idx] = v * SPEED;
}
}
}
// ---- per frame: fade, integrate, plot, blit ----
const RELAX = 0.08;
const SPEED2 = 160;
function step(t) {
updateField(t);
const b = buf, lr = lutR, lg = lutG, lb = lutB;
for (let i = 0; i < b.length; i++) {
const p = b[i];
b[i] = lr[p & 255] | (lg[(p >>> 8) & 255] << 8) | (lb[(p >>> 16) & 255] << 16) | 0xff000000;
}
const fX = FX, fY = FY, stride = GW1, pal = PAL;
for (let i = 0; i < N; i++) {
let x = px[i], y = py[i];
const fx = x * INV_CW, fy = y * INV_CH;
const ix = fx | 0, iy = fy | 0;
const tx = fx - ix, ty = fy - iy;
const j = iy * stride + ix;
const j1 = j + 1, jb = j + stride, jb1 = jb + 1;
const w0 = 1 - tx;
const fu = (fX[j] * w0 + fX[j1] * tx) * (1 - ty) + (fX[jb] * w0 + fX[jb1] * tx) * ty;
const fv = (fY[j] * w0 + fY[j1] * tx) * (1 - ty) + (fY[jb] * w0 + fY[jb1] * tx) * ty;
const nvx = vx[i] + (fu - vx[i]) * RELAX;
const nvy = vy[i] + (fv - vy[i]) * RELAX;
vx[i] = nvx; vy[i] = nvy;
x += nvx; y += nvy;
if (x < 0) x += W; else if (x >= W) x -= W; // torus recycle
if (y < 0) y += H; else if (y >= H) y -= H;
px[i] = x; py[i] = y;
const s = (nvx * nvx + nvy * nvy) * SPEED2;
b[(y | 0) * W + (x | 0)] = pal[s > 255 ? 255 : (s | 0)];
}
ctx.putImageData(img, 0, 0);
}
// ---- honest FPS: rolling average of real rAF deltas ----
let last = performance.now();
let acc = 0, frames = 0;
let fpsStr = '— fps · — ms';
const countStr = px.length.toLocaleString('en-US') + ' particles';
function drawHUD() {
ctx.font = '12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace';
ctx.textBaseline = 'top';
ctx.fillStyle = 'rgba(4,6,10,0.55)';
ctx.fillRect(8, 8, 176, 40);
ctx.fillStyle = '#e6eef6';
ctx.fillText(fpsStr, 16, 14);
ctx.fillStyle = '#5fd8c6';
ctx.fillText(countStr, 16, 30);
}
function frame(now) {
requestAnimationFrame(frame);
let dt = now - last; last = now;
if (dt > 100) dt = 100; // tab was hidden; don't poison the average
acc += dt; frames++;
if (acc >= 400) {
fpsStr = (1000 * frames / acc).toFixed(1) + ' fps · ' + (acc / frames).toFixed(1) + ' ms';
acc = 0; frames = 0;
}
step(now);
drawHUD();
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
putImageData ~1.5ms, HUD ~0.5ms), so a strong P-core machine will read a clean 60.0 and a weak U-series will dip toward 55; 58 is my honest center. It is CPU-bound, not fill-rate-bound — the iGPU only composites one 900×600 blit.putImageData upload (~22%) is a flat 2.16MB, HUD is noise.for loop over the SoA.gl.POINTS with additive blending, trails via an FBO feedback loop. That's the named technique; it takes this to millions of particles with the CPU doing nothing but one drawArrays.Nothing dropped.