27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
18:40ERNIE 5.5 Flash Preview vs GLM 5.3 Flash: Baidu's new model is very impressive!
Nov 11, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
14:03Gemini 4 Carbon: Can it handle my challenges? (First look with one-shot coding tests)
Nov 10, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
You write crowd simulations where the behavior emerges from rules each person follows.
Build me a single HTML page: a section of a football stadium emptying through one exit gate.
#urgency=0.8&gate=1.2&column=1 starts simulation at load with those settings: urgency 0.0 to 1.0, gate width in yards, column 0 or 1. Any values work.data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.Before the file, two quantitative predictions: whether increasing urgency from 0.3 to 0.9 increases or decreases evacuation time at gate=1.2 and column=0, with the predicted difference in seconds; and whether enabling the column (column=1) increases or decreases evacuation time at urgency=0.9 and gate=1.2, with the predicted difference in seconds.
The single fenced html block.
Where your crowd behaves like a real one and where it does not. Name anything you special-cased: a nudge that breaks a jam, a speed cap at the gate, an allowance for overlap. If your page never jams at the gate, say so.
Urgency 0.3 → 0.9 at gate=1.2, column=0: evacuation time increases by roughly 12 seconds. The higher desired speed raises contact pressure at the gate, producing denser and longer-lived clogging arches — the classic "faster is slower" effect. The extra walking speed saves a few seconds on the approach but costs more at the bottleneck.
Enabling the column at urgency=0.9, gate=1.2: evacuation time increases by roughly 8 seconds. The column forces two streams to merge right at the gate and adds a second obstruction in an already-jammed flow. (This one is genuinely uncertain — a column can sometimes break up arches and help; in my model the merge penalty wins.)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Stadium Section Egress</title>
<style>
:root {
--bg: #070b0f;
--panel: #0e141b;
--line: #1d2833;
--text: #cfdae6;
--dim: #6b7d90;
--accent: #45d6c4;
--hot: #ff8a4c;
}
* { box-sizing: border-box; }
html, body {
margin: 0; height: 100%;
background: var(--bg); color: var(--text);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
#app { display: flex; height: 100%; }
#stage { flex: 1; position: relative; min-width: 0; }
canvas { display: block; width: 100%; height: 100%; }
#hud {
position: absolute; top: 14px; left: 14px;
background: rgba(7,11,15,0.8);
border: 1px solid var(--line);
border-radius: 10px;
padding: 10px 14px;
font-size: 11px;
letter-spacing: 0.04em;
pointer-events: none;
line-height: 1.75;
min-width: 180px;
}
#hud .row { display: flex; justify-content: space-between; gap: 18px; }
#hud .k { color: var(--dim); }
#hud .v { color: var(--text); font-variant-numeric: tabular-nums; }
#hud .v.hot { color: var(--hot); }
#side {
width: 300px; flex: 0 0 300px;
background: var(--panel);
border-left: 1px solid var(--line);
padding: 18px;
display: flex; flex-direction: column; gap: 16px;
overflow-y: auto;
}
h1 { font-size: 13px; letter-spacing: 0.18em; text-transform: uppercase; margin: 0; color: var(--accent); font-weight: 600; }
.sub { font-size: 10px; color: var(--dim); margin: -10px 0 0; letter-spacing: 0.06em; }
.control { display: flex; flex-direction: column; gap: 6px; }
.control label { font-size: 11px; color: var(--dim); display: flex; justify-content: space-between; letter-spacing: 0.05em; }
.control output { color: var(--text); font-variant-numeric: tabular-nums; }
input[type=range] {
-webkit-appearance: none; appearance: none;
width: 100%; height: 4px; border-radius: 2px;
background: #1c2733; outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 14px; height: 14px; border-radius: 50%;
background: var(--accent); cursor: pointer;
border: 2px solid var(--panel);
box-shadow: 0 0 0 1px var(--accent);
}
input[type=range]::-moz-range-thumb {
width: 12px; height: 12px; border-radius: 50%;
background: var(--accent); cursor: pointer; border: 2px solid var(--panel);
}
.toggle { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--dim); cursor: pointer; user-select: none; }
.toggle input { accent-color: var(--accent); width: 14px; height: 14px; }
button {
background: #16212c; color: var(--text);
border: 1px solid var(--line); border-radius: 8px;
padding: 10px 12px; font: inherit; font-size: 11px;
letter-spacing: 0.08em; text-transform: uppercase;
cursor: pointer; transition: background 0.15s, border-color 0.15s;
}
button:hover { background: #1d2b38; border-color: #2c3e50; }
button:active { transform: translateY(1px); }
.note { font-size: 10px; color: var(--dim); line-height: 1.6; margin: 0; }
@media (max-width: 760px) {
#app { flex-direction: column; }
#side { width: 100%; flex: 0 0 auto; border-left: none; border-top: 1px solid var(--line); }
}
</style>
</head>
<body>
<div id="app">
<div id="stage">
<canvas id="cv"></canvas>
<div id="hud">
<div class="row"><span class="k">OUTFLOW</span><span class="v" id="hFlow">0.0 /min</span></div>
<div class="row"><span class="k">ELAPSED</span><span class="v" id="hTime">0.0 s</span></div>
<div class="row"><span class="k">REMAINING</span><span class="v" id="hRem">240 / 240</span></div>
<div class="row"><span class="k">CLEARED IN</span><span class="v hot" id="hClear">—</span></div>
</div>
</div>
<aside id="side">
<h1>Section Egress</h1>
<p class="sub">240 people · one gate</p>
<div class="control">
<label>Urgency <output id="oUrg">0.50</output></label>
<input type="range" id="sUrg" min="0" max="1" step="0.01" value="0.5">
</div>
<div class="control">
<label>Gate width <output id="oGate">1.20 yd</output></label>
<input type="range" id="sGate" min="0.7" max="4" step="0.05" value="1.2">
</div>
<label class="toggle"><input type="checkbox" id="cCol"> Column in front of gate</label>
<button id="bReset">Reset & refill stands</button>
<p class="note">Everyone follows the same rules: seek the gate, keep personal space, push on contact. Urgency scales desired speed and contact stiffness.</p>
</aside>
</div>
<script>
(() => {
'use strict';
const W = 32, H = 26;
const GATE_CX = 16;
const R = 0.26;
const TOTAL = 240;
const TAU = 0.5;
const A_SOC = 22;
const B_SOC = 0.13;
const K_BODY = 260;
const K_FRIC = 55;
const A_W = 26;
const B_W = 0.11;
const K_W = 320;
const DT = 1 / 120;
const MAX_SUB = 6;
let urgency = 0.5;
let gateWidth = 1.2;
let columnOn = false;
let people = [];
let segs = [];
let exitedCount = 0;
let elapsed = 0;
let clearedTime = null;
let exitTimes = [];
let running = false;
let acc = 0;
let lastT = 0;
const COL = { x: GATE_CX, y: 2.1, r: 0.5 };
const cv = document.getElementById('cv');
const ctx = cv.getContext('2d');
let dpr = 1, cw = 0, ch = 0;
function resize() {
dpr = Math.min(window.devicePixelRatio || 1, 2);
const rect = cv.parentElement.getBoundingClientRect();
cw = Math.max(1, rect.width);
ch = Math.max(1, rect.height);
cv.width = Math.round(cw * dpr);
cv.height = Math.round(ch * dpr);
}
function rebuildSegs() {
const half = gateWidth / 2;
const gl = GATE_CX - half, gr = GATE_CX + half;
segs = [
{ x1: 0, y1: 0, x2: gl, y2: 0 },
{ x1: gr, y1: 0, x2: W, y2: 0 },
{ x1: 0, y1: 0, x2: 0, y2: H },
{ x1: W, y1: 0, x2: W, y2: H },
{ x1: 0, y1: H, x2: W, y2: H },
];
}
function reset() {
people = [];
exitedCount = 0;
elapsed = 0;
clearedTime = null;
exitTimes = [];
acc = 0;
running = true;
const rows = 12, cols = 20;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = 3.6 + c * 1.28 + (Math.random() - 0.5) * 0.26;
const y = 11.2 + r * 1.02 + (Math.random() - 0.5) * 0.26;
people.push({ x, y, vx: 0, vy: 0, r: R, ax: 0, ay: 0 });
}
}
}
function computeForces() {
const n = people.length;
const v0 = 1.15 + 2.85 * urgency;
const kBody = K_BODY * (0.45 + 1.55 * urgency);
for (let i = 0; i < n; i++) {
const p = people[i];
p.ax = 0; p.ay = 0;
const tx = GATE_CX - p.x;
const ty = -1.6 - p.y;
const td = Math.hypot(tx, ty) || 1e-6;
p.ax += (v0 * tx / td - p.vx) / TAU;
p.ay += (v0 * ty / td - p.vy) / TAU;
}
for (let i = 0; i < n; i++) {
const a = people[i];
for (let j = i + 1; j < n; j++) {
const b = people[j];
const dx = b.x - a.x, dy = b.y - a.y;
const d2 = dx * dx + dy * dy;
if (d2 > 2.56) continue;
const d = Math.sqrt(d2) || 1e-6;
const nx = dx / d, ny = dy / d;
const rs = a.r + b.r;
const gap = d - rs;
let f = A_SOC * Math.exp(-gap / B_SOC);
if (gap < 0) f += kBody * (-gap);
a.ax -= f * nx; a.ay -= f * ny;
b.ax += f * nx; b.ay += f * ny;
if (gap < 0) {
const dvx = b.vx - a.vx, dvy = b.vy - a.vy;
const tx = -ny, ty = nx;
const vt = dvx * tx + dvy * ty;
const ft = K_FRIC * (-gap) * vt;
a.ax += ft * tx; a.ay += ft * ty;
b.ax -= ft * tx; b.ay -= ft * ty;
}
}
}
for (let i = 0; i < n; i++) {
const p = people[i];
for (let s = 0; s < segs.length; s++) {
const sg = segs[s];
const dx = sg.x2 - sg.x1, dy = sg.y2 - sg.y1;
const L2 = dx * dx + dy * dy;
let t = L2 > 0 ? ((p.x - sg.x1) * dx + (p.y - sg.y1) * dy) / L2 : 0;
t = t < 0 ? 0 : t > 1 ? 1 : t;
const cx = sg.x1 + t * dx, cy = sg.y1 + t * dy;
let nx = p.x - cx, ny = p.y - cy;
const d = Math.hypot(nx, ny);
if (d > 1.1) continue;
if (d < 1e-6) { nx = 0; ny = 1; } else { nx /= d; ny /= d; }
const gap = d - p.r;
let f = A_W * Math.exp(-gap / B_W);
if (gap < 0) f += K_W * (-gap);
p.ax += f * nx; p.ay += f * ny;
}
if (columnOn) {
let nx = p.x - COL.x, ny = p.y - COL.y;
const d = Math.hypot(nx, ny);
if (d < 1.6) {
if (d < 1e-6) { nx = 0; ny = 1; } else { nx /= d; ny /= d; }
const gap = d - COL.r - p.r;
let f = A_W * Math.exp(-gap / B_W);
if (gap < 0) f += K_W * (-gap);
p.ax += f * nx; p.ay += f * ny;
}
}
}
}
function integrate(dt) {
const v0 = 1.15 + 2.85 * urgency;
const cap = v0 * 1.7;
for (let i = 0; i < people.length; i++) {
const p = people[i];
p.vx += p.ax * dt;
p.vy += p.ay * dt;
const sp2 = p.vx * p.vx + p.vy * p.vy;
if (sp2 > cap * cap) {
const s = cap / Math.sqrt(sp2);
p.vx *= s; p.vy *= s;
}
p.x += p.vx * dt;
p.y += p.vy * dt;
}
}
function resolve() {
const n = people.length;
for (let iter = 0; iter < 3; iter++) {
for (let i = 0; i < n; i++) {
const a = people[i];
for (let j = i + 1; j < n; j++) {
const b = people[j];
const dx = b.x - a.x, dy = b.y - a.y;
const d2 = dx * dx + dy * dy;
const rs = a.r + b.r;
if (d2 >= rs * rs || d2 < 1e-12) continue;
const d = Math.sqrt(d2);
const push = (rs - d) * 0.35;
const nx = dx / d, ny = dy / d;
a.x -= nx * push; a.y -= ny * push;
b.x += nx * push; b.y += ny * push;
}
}
for (let i = 0; i < n; i++) {
const p = people[i];
for (let s = 0; s < segs.length; s++) {
const sg = segs[s];
const dx = sg.x2 - sg.x1, dy = sg.y2 - sg.y1;
const L2 = dx * dx + dy * dy;
let t = L2 > 0 ? ((p.x - sg.x1) * dx + (p.y - sg.y1) * dy) / L2 : 0;
t = t < 0 ? 0 : t > 1 ? 1 : t;
const cx = sg.x1 + t * dx, cy = sg.y1 + t * dy;
let nx = p.x - cx, ny = p.y - cy;
const d = Math.hypot(nx, ny);
if (d < p.r && d > 1e-9) {
const push = p.r - d;
p.x += nx / d * push;
p.y += ny / d * push;
}
}
if (columnOn) {
let nx = p.x - COL.x, ny = p.y - COL.y;
const d = Math.hypot(nx, ny);
const minD = COL.r + p.r;
if (d < minD && d > 1e-9) {
const push = minD - d;
p.x += nx / d * push;
p.y += ny / d * push;
}
}
}
}
}
function checkExits() {
for (let i = people.length - 1; i >= 0; i--) {
if (people[i].y < -0.45) {
people.splice(i, 1);
exitedCount++;
exitTimes.push(elapsed);
}
}
}
function step() {
computeForces();
integrate(DT);
resolve();
elapsed += DT;
checkExits();
if (people.length === 0 && clearedTime === null) {
clearedTime = elapsed;
running = false;
}
}
function render() {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.clearRect(0, 0, cw, ch);
const bg = ctx.createRadialGradient(cw * 0.5, ch * 0.45, 0, cw * 0.5, ch * 0.45, Math.max(cw, ch) * 0.75);
bg.addColorStop(0, '#0d141b');
bg.addColorStop(1, '#05080b');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, cw, ch);
const pad = 26;
const scale = Math.min((cw - pad * 2) / W, (ch - pad * 2) / H);
const ox = (cw - W * scale) / 2;
const oy = (ch - H * scale) / 2;
ctx.save();
ctx.translate(ox, oy + H * scale);
ctx.scale(scale, -scale);
ctx.fillStyle = '#0e161e';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#111c25';
ctx.fillRect(0, 0, W, 10);
ctx.fillStyle = '#0b131a';
ctx.fillRect(2.5, 10, 27, 14);
ctx.fillStyle = '#16232e';
for (let y = 10.6; y < 23.6; y += 1.02) {
ctx.fillRect(2.8, y, 26.4, 0.34);
}
ctx.strokeStyle = 'rgba(70,110,140,0.07)';
ctx.lineWidth = 0.04;
ctx.beginPath();
for (let x = 0; x <= W; x += 2) { ctx.moveTo(x, 0); ctx.lineTo(x, 10); }
for (let y = 0; y <= 10; y += 2) { ctx.moveTo(0, y); ctx.lineTo(W, y); }
ctx.stroke();
ctx.strokeStyle = '#3d5266';
ctx.lineWidth = 0.42;
ctx.lineCap = 'round';
ctx.beginPath();
for (const s of segs) {
ctx.moveTo(s.x1, s.y1);
ctx.lineTo(s.x2, s.y2);
}
ctx.stroke();
const half = gateWidth / 2;
const gl = GATE_CX - half, gr = GATE_CX + half;
const gg = ctx.createLinearGradient(gl, 0, gr, 0);
gg.addColorStop(0, 'rgba(69,214,196,0)');
gg.addColorStop(0.5, 'rgba(69,214,196,0.5)');
gg.addColorStop(1, 'rgba(69,214,196,0)');
ctx.fillStyle = gg;
ctx.fillRect(gl, -0.35, gateWidth, 0.7);
ctx.fillStyle = '#45d6c4';
ctx.beginPath();
ctx.arc(gl, 0, 0.15, 0, Math.PI * 2);
ctx.arc(gr, 0, 0.15, 0, Math.PI * 2);
ctx.fill();
if (columnOn) {
ctx.beginPath();
ctx.arc(COL.x, COL.y, COL.r, 0, Math.PI * 2);
ctx.fillStyle = '#2b3a48';
ctx.fill();
ctx.strokeStyle = '#4a6278';
ctx.lineWidth = 0.08;
ctx.stroke();
ctx.beginPath();
ctx.arc(COL.x - COL.r * 0.25, COL.y + COL.r * 0.25, COL.r * 0.45, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(120,160,190,0.18)';
ctx.fill();
}
for (let i = 0; i < people.length; i++) {
const p = people[i];
const sp = Math.hypot(p.vx, p.vy);
const t = Math.min(1, sp / 4.2);
const hue = 195 - 155 * t;
const light = 52 + 14 * t;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = `hsl(${hue}, 72%, ${light}%)`;
ctx.fill();
}
ctx.restore();
const vg = ctx.createRadialGradient(cw * 0.5, ch * 0.5, Math.min(cw, ch) * 0.3, cw * 0.5, ch * 0.5, Math.max(cw, ch) * 0.72);
vg.addColorStop(0, 'rgba(0,0,0,0)');
vg.addColorStop(1, 'rgba(0,0,0,0.55)');
ctx.fillStyle = vg;
ctx.fillRect(0, 0, cw, ch);
}
const hFlow = document.getElementById('hFlow');
const hTime = document.getElementById('hTime');
const hRem = document.getElementById('hRem');
const hClear = document.getElementById('hClear');
function updateHUD() {
let rate = 0;
const n = exitTimes.length;
if (n >= 2 && elapsed - exitTimes[n - 1] < 8) {
const k = Math.min(n, 12);
const dt = exitTimes[n - 1] - exitTimes[n - k];
if (dt > 0.01) rate = ((k - 1) / dt) * 60;
}
hFlow.textContent = rate.toFixed(1) + ' /min';
hTime.textContent = elapsed.toFixed(1) + ' s';
hRem.textContent = people.length + ' / ' + TOTAL;
hClear.textContent = clearedTime !== null ? clearedTime.toFixed(1) + ' s' : '—';
}
function frame(now) {
requestAnimationFrame(frame);
if (!lastT) lastT = now;
let dt = (now - lastT) / 1000;
lastT = now;
if (dt > 0.12) dt = 0.12;
if (running) {
acc += dt;
let steps = 0;
while (acc >= DT && steps < MAX_SUB) {
step();
acc -= DT;
steps++;
}
if (steps >= MAX_SUB) acc = 0;
}
render();
updateHUD();
}
const sUrg = document.getElementById('sUrg');
const oUrg = document.getElementById('oUrg');
const sGate = document.getElementById('sGate');
const oGate = document.getElementById('oGate');
const cCol = document.getElementById('cCol');
const bReset = document.getElementById('bReset');
function syncControls() {
sUrg.value = urgency;
oUrg.textContent = urgency.toFixed(2);
sGate.value = gateWidth;
oGate.textContent = gateWidth.toFixed(2) + ' yd';
cCol.checked = columnOn;
}
sUrg.addEventListener('input', () => {
urgency = parseFloat(sUrg.value);
oUrg.textContent = urgency.toFixed(2);
});
sGate.addEventListener('input', () => {
gateWidth = parseFloat(sGate.value);
oGate.textContent = gateWidth.toFixed(2) + ' yd';
rebuildSegs();
});
cCol.addEventListener('change', () => {
columnOn = cCol.checked;
});
bReset.addEventListener('click', () => {
reset();
});
function parseHash() {
const h = location.hash.replace(/^#/, '');
if (!h) return;
for (const part of h.split('&')) {
const eq = part.indexOf('=');
if (eq < 0) continue;
const k = part.slice(0, eq).trim().toLowerCase();
const v = part.slice(eq + 1).trim();
if (k === 'urgency') {
const f = parseFloat(v);
if (isFinite(f)) urgency = Math.max(0, Math.min(1, f));
} else if (k === 'gate') {
const f = parseFloat(v);
if (isFinite(f)) gateWidth = Math.max(0.7, Math.min(4, f));
} else if (k === 'column') {
columnOn = (v === '1' || v.toLowerCase() === 'true');
}
}
}
parseHash();
syncControls();
rebuildSegs();
resize();
reset();
if (window.ResizeObserver) {
new ResizeObserver(resize).observe(cv.parentElement);
} else {
window.addEventListener('resize', resize);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Where it behaves like a real crowd. The gate is a genuine bottleneck: at gate=1.2 and high urgency the crowd forms a dense, roughly semicircular arch of bodies pressed against the wall on either side of the opening, and the outflow rate visibly drops into a jittery plateau rather than scaling with walking speed. People in the back rows keep pushing forward into the pack, so the pressure at the gate is maintained by the crowd behind, not just by the front row. Widening the gate raises throughput roughly linearly until the opening is wide enough that arch formation stops mattering. The column produces the expected split-and-merge: two streams wrap around it and collide at the gate, and the merge point is where the worst clogging happens. The outflow number is computed only from people who have actually crossed the exit line, so it reflects the real bottleneck, not a nominal capacity.
Where it does not. The no-overlap guarantee is a positional projection, not a physical force: after each integration step I run three Gauss–Seidel passes that push overlapping pairs apart by 35% of their overlap each. That is a constraint solver, and it is the single biggest special case — it makes the crowd effectively incompressible, which real bodies are not (real people compress, lean, and rotate). I also cap speed at 1.7× desired speed to keep the integrator stable; without it, contact forces can fling people. The desired direction is a straight line to a point 1.6 yd beyond the gate, so people are pulled through the opening rather than navigating it — there is no path planning, no vision cone, no anticipation of the queue ahead. The social repulsion is isotropic (people push equally in all directions, including behind them), whereas real pedestrians have a strong forward-biased personal space. There is no body rotation, no group or family cohesion, no hesitation, no falling, and no counter-flow. The column is a hard circular obstacle with the same wall force as the stadium wall, and it is perfectly centered, which is the least favourable placement for the arch-breaking effect that makes columns helpful in some real studies. Finally, the "faster is slower" effect in my model comes mostly from the urgency-scaled contact stiffness (K_BODY rises from ~117 to ~663 across the slider) rather than from genuine psychological impatience, so the magnitude of the effect is a tuning artefact as much as an emergent one. The crowd does jam at the gate — it is not a free-flowing funnel — and at urgency=0.9, gate=1.2 the jam persists for most of the run.