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.
At gate=1.2 yards and column=0, increasing urgency from 0.3 to 0.9 increases evacuation time. Predicted 0.3: about 72 s, 0.9: about 104 s, difference about +32 s slower at high urgency. This is faster-is-slower: higher desired speed creates denser arches and longer clogs that outweigh the higher free speed.
At urgency=0.9 and gate=1.2 yards, enabling the column (column=0 to column=1) decreases evacuation time. Predicted column=0: about 104 s, column=1: about 83 s, difference about -21 s faster with the column. The column splits the inflow, lowers direct pressure on the gate arch and breaks up a single stable arch into two weaker arches.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Stadium Gate — Crowd Emptying</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#0e1420;color:#e8eef7;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
header{padding:14px 18px 6px;max-width:1020px;margin:0 auto}
header h1{margin:0;font-size:20px;letter-spacing:.3px}
header p{margin:6px 0 0;color:#9fb0c8;font-size:13px;line-height:1.45}
main{max-width:1020px;margin:0 auto;padding:10px 18px 26px}
.panel{background:#162032;border:1px solid #26344d;border-radius:12px;padding:12px 14px;margin:10px 0}
.controls{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;align-items:start}
.ctl{background:#0f1828;border:1px solid #24334e;border-radius:10px;padding:10px 12px}
.ctl label{display:block;font-size:12px;color:#9fb0c8;margin-bottom:6px}
.ctl output{color:#fff;font-weight:700}
input[type=range]{width:100%}
.row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:8px}
button{background:#2f7bff;border:0;color:#fff;border-radius:9px;padding:9px 13px;font-weight:700;cursor:pointer}
button.ghost{background:#22314d;color:#dbe7ff}
button.active{background:#19b36b}
.check{display:flex;gap:8px;align-items:center;font-size:14px}
.check input{width:18px;height:18px}
canvas{width:100%;height:auto;display:block;background:#0a101c;border-radius:12px;border:1px solid #26344d}
.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:10px}
.m{background:#0f1828;border:1px solid #24334e;border-radius:10px;padding:10px 12px}
.m .k{font-size:11px;color:#9fb0c8;text-transform:uppercase;letter-spacing:.6px}
.m .v{font-size:22px;font-weight:800;margin-top:2px}
.m .s{font-size:12px;color:#9fb0c8;margin-top:2px}
.legend{font-size:12px;color:#9fb0c8;line-height:1.5}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin:0 4px -1px 8px}
</style>
</head>
<body>
<header>
<h1>Stadium Section Emptying — One Gate</h1>
<p>Top-down social-force crowd. Everyone runs the same rules: steer to the gate, keep personal space, push on contact. Walls, jambs and the column are solid. Urgency scales desired speed and contact push. Use <span style="color:#fff">#urgency=0.8&gate=1.2&column=1</span> to set start state.</p>
</header>
<main>
<div class="panel controls">
<div class="ctl">
<label>Urgency <output id="oU">0.80</output> (0.0 – 1.0)</label>
<input id="inU" type="range" min="0" max="1" step="0.01" value="0.8">
<div class="s legend">Live: scales desired speed + contact force / friction.</div>
</div>
<div class="ctl">
<label>Gate width <output id="oG">1.2</output> yards</label>
<input id="inG" type="range" min="0.6" max="4" step="0.1" value="1.2">
<div class="s legend">Changing gate resets the crowd.</div>
</div>
<div class="ctl">
<label>Column in front of gate</label>
<div class="check"><input id="inC" type="checkbox"><span id="oC">off — free opening</span></div>
<div class="row">
<button id="bReset">Reset / refill stands</button>
<button id="bPause" class="ghost">Pause</button>
</div>
<div class="row">
<button id="t1" class="ghost active">1×</button>
<button id="t2" class="ghost">2×</button>
<button id="t4" class="ghost">4×</button>
</div>
</div>
</div>
<div class="panel">
<canvas id="cv" width="960" height="704"></canvas>
<div class="metrics">
<div class="m"><div class="k">Sim clock</div><div class="v" id="mTime">0.0s</div><div class="s" id="mState">running</div></div>
<div class="m"><div class="k">Outflow (measured)</div><div class="v"><span id="mFlow">0</span><span style="font-size:13px"> /min</span></div><div class="s">sliding 15 s window on real exits</div></div>
<div class="m"><div class="k">Evacuated</div><div class="v" id="mEvac">0 / 240</div><div class="s" id="mAvg">avg 0 /min</div></div>
<div class="m"><div class="k">Remaining</div><div class="v" id="mRem">240</div><div class="s">empty time held when last person leaves</div></div>
</div>
<p class="legend"><span class="dot" style="background:#3fa9ff"></span>slow<span class="dot" style="background:#ffd23f"></span>mid<span class="dot" style="background:#ff5a5a"></span>fast (speed colour) · green floor marks gate · grey bar is wall · dark disc is column.</p>
</div>
</main>
<script>
(function(){
"use strict";
var W=30, H=22, wallY=17.5, exitY=21.0, cx=W/2;
var N=240;
var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
var inU=document.getElementById('inU'), inG=document.getElementById('inG'), inC=document.getElementById('inC');
var oU=document.getElementById('oU'), oG=document.getElementById('oG'), oC=document.getElementById('oC');
var mTime=document.getElementById('mTime'), mState=document.getElementById('mState');
var mFlow=document.getElementById('mFlow'), mEvac=document.getElementById('mEvac'), mAvg=document.getElementById('mAvg'), mRem=document.getElementById('mRem');
var bReset=document.getElementById('bReset'), bPause=document.getElementById('bPause');
var urgency=0.8, gateYards=1.2, columnOn=false;
var agents=[], exitTimes=[], simTime=0, evacuated=0, running=true, finished=false, finishTime=0, timeScale=1;
var YARD=0.9144;
function gateHalf(){ return Math.max(0.15,(gateYards*YARD)/2); }
function colPos(){ return {x:cx, y:wallY-1.8, r:0.35}; }
function parseHash(){
try{
var h=(location.hash||'').replace(/^#/,'');
if(!h) return;
var parts=h.split('&');
for(var i=0;i<parts.length;i++){
var kv=parts[i].split('=');
if(kv.length<2) continue;
var k=decodeURIComponent(kv[0]).trim().toLowerCase(), v=decodeURIComponent(kv[1]).trim();
if(k==='urgency'){ var u=parseFloat(v); if(isFinite(u)){ urgency=Math.min(1,Math.max(0,u)); } }
else if(k==='gate'){ var g=parseFloat(v); if(isFinite(g)&&g>0.2&&g<12){ gateYards=g; } }
else if(k==='column'){ var c=parseFloat(v); columnOn=(c>=0.5)||v==='true'||v==='1'; if(v==='0'||v==='false') columnOn=false; }
}
}catch(e){}
}
function syncUI(){
inU.value=urgency; oU.textContent=(+urgency).toFixed(2);
var gv=Math.min(4,Math.max(0.6,gateYards));
// allow hash values outside slider by widening display only; physics uses true value
try{ inG.value=gv; }catch(e){}
oG.textContent=(+gateYards).toFixed(2);
inC.checked=!!columnOn; oC.textContent=columnOn?'ON — column placed':'off — free opening';
}
function reset(){
agents=[]; exitTimes=[]; simTime=0; evacuated=0; finished=false; finishTime=0;
var tries=0;
while(agents.length<N && tries<30000){
tries++;
var x=2+Math.random()*26, y=1.2+Math.random()*14.0;
var r=0.20+Math.random()*0.05;
var ok=true;
for(var i=0;i<agents.length;i++){
var a=agents[i], dx=a.x-x, dy=a.y-y;
if(dx*dx+dy*dy < (a.r+r+0.06)*(a.r+r+0.06)){ ok=false; break; }
}
if(!ok) continue;
agents.push({x:x,y:y,vx:0,vy:0,r:r,vf:0.85+Math.random()*0.3,ph:Math.random()*6.283,ax:0,ay:0});
}
mState.textContent='running';
}
function buildGrid(list, cell){
var g={cell:cell,map:{}};
for(var i=0;i<list.length;i++){
var a=list[i];
var k=Math.floor(a.x/cell)+'_'+Math.floor(a.y/cell);
if(!g.map[k]) g.map[k]=[];
g.map[k].push(i);
}
return g;
}
function neighbours(g,list,idx,x,y,rad,cb){
var c=g.cell, bx=Math.floor(x/c), by=Math.floor(y/c), R=Math.ceil(rad/c);
for(var ox=-R;ox<=R;ox++) for(var oy=-R;oy<=R;oy++){
var arr=g.map[(bx+ox)+'_'+(by+oy)];
if(!arr) continue;
for(var k=0;k<arr.length;k++){ var j=arr[k]; if(j===idx) continue; cb(list[j],j); }
}
}
function step(dt){
if(!agents.length){ if(!finished){finished=true;finishTime=simTime;mState.textContent='EMPTY in '+finishTime.toFixed(1)+'s';} return; }
simTime+=dt;
var t=simTime, gh=gateHalf(), col=colPos();
var vBase=0.7+2.5*urgency;
var g=buildGrid(agents,1.0);
var i,a;
for(i=0;i<agents.length;i++){
a=agents[i];
var tx,ty;
if(a.y<wallY-0.4){ tx=cx; ty=wallY; }
else if(a.y<wallY+0.8){ tx=cx; ty=exitY+1.5; }
else { tx=cx; ty=exitY+2.5; }
var dx=tx-a.x, dy=ty-a.y, d=Math.hypot(dx,dy)||1; dx/=d; dy/=d;
var wobAmp=0.16*(1.15-urgency*0.75);
var wob=Math.sin(t*3.1+a.ph)*wobAmp+(Math.random()-0.5)*0.22*(1.15-urgency*0.7);
var ca=Math.cos(wob), sa=Math.sin(wob);
var rx=dx*ca-dy*sa, ry=dx*sa+dy*ca;
var v0=vBase*a.vf;
var dvx=rx*v0, dvy=ry*v0;
var tau=0.5;
var ax=(dvx-a.vx)/tau, ay=(dvy-a.vy)/tau;
var closest=99;
neighbours(g,agents,i,a.x,a.y,1.6,function(b){
var ex=a.x-b.x, ey=a.y-b.y, dd=Math.hypot(ex,ey);
if(dd>1.6||dd<1e-6) return;
if(dd<closest) closest=dd;
var nx=ex/dd, ny=ey/dd;
var rep=2.6*Math.exp((0.7-dd)/0.35);
ax+=nx*rep; ay+=ny*rep;
var rr=a.r+b.r;
if(dd<rr){
var ov=rr-dd;
var kn=180+700*urgency;
ax+=nx*kn*ov; ay+=ny*kn*ov;
var rvx=a.vx-b.vx, rvy=a.vy-b.vy;
var vn=rvx*nx+rvy*ny;
if(vn<0){ ax+=-nx*vn*4.0; ay+=-ny*vn*4.0; }
var tx2=-ny, ty2=nx;
var vt=rvx*tx2+rvy*ty2;
var cf=(2+10*urgency)*ov*20.0;
ax+=-tx2*vt*cf; ay+=-ty2*vt*cf;
}
});
// wall social (stand-off) + politeness: low urgency eases off when crowded
if(closest<0.7){
var s=0.45+0.55*urgency;
ax*=s; ay*=s;
}
// wall proximity repulsion so they slide not scrape
var dw=Math.abs(a.y-wallY);
if(dw<0.6 && a.y<wallY){
var inGate=Math.abs(a.x-cx)<gh;
if(!inGate){
var push=(0.6-dw)*6.0;
ay+=(a.y<wallY?-push:push);
}
}
// column social
if(columnOn){
var cdx=a.x-col.x, cdy=a.y-col.y, cdd=Math.hypot(cdx,cdy);
if(cdd<1.2&&cdd>1e-6){ var m=3.0*Math.exp((0.6-cdd)/0.3); ax+=cdx/cdd*m; ay+=cdy/cdd*m; }
}
a.ax=ax; a.ay=ay;
}
var vmax=3.2+1.3*urgency;
for(i=0;i<agents.length;i++){
a=agents[i];
a.vx+=a.ax*dt; a.vy+=a.ay*dt;
var sp=Math.hypot(a.vx,a.vy);
if(sp>vmax){ a.vx*=vmax/sp; a.vy*=vmax/sp; }
a.x+=a.vx*dt; a.y+=a.vy*dt;
}
// hard non-overlap projection: people-people + walls + column + jambs
for(var it=0;it<3;it++){
var g2=buildGrid(agents,1.0);
var seen={};
for(i=0;i<agents.length;i++){
a=agents[i];
neighbours(g2,agents,i,a.x,a.y,1.0,function(b,j){
var key=i<j?i+'_'+j:j+'_'+i;
if(seen[key]) return; seen[key]=1;
var ex=a.x-b.x, ey=a.y-b.y, dd=Math.hypot(ex,ey), rr=a.r+b.r;
if(dd<rr&&dd>1e-9){
var ov=(rr-dd)/2, nx=ex/dd, ny=ey/dd;
a.x+=nx*ov; a.y+=ny*ov; b.x-=nx*ov; b.y-=ny*ov;
} else if(dd<=1e-9){
a.x+=0.01; b.x-=0.01;
}
});
}
for(i=0;i<agents.length;i++){
a=agents[i];
// outer bounds
if(a.x<a.r) a.x=a.r;
if(a.x>W-a.r) a.x=W-a.r;
if(a.y<a.r) a.y=a.r;
// wall blocks (left/right of gate)
var top=wallY-0.2, bot=wallY+0.2;
if(a.y+ a.r>top && a.y-a.r<bot){
if(a.x<cx-gh && a.x>cx-gh-8){
// left wall: closest point clamp
var cxp=Math.min(Math.max(a.x,0),cx-gh);
// if inside slab push vertically out (nearest face)
var dTop=Math.abs((a.y+a.r)-top), dBot=Math.abs(bot-(a.y-a.r));
// determine if overlapping in x with wall body
if(a.x<cx-gh){
if(a.y<wallY) a.y=top-a.r; else a.y=bot+a.r;
}
} else if(a.x>cx+gh){
if(a.y<wallY) a.y=top-a.r; else a.y=bot+a.r;
} else {
// inside gate span: only collide with jamb sides, not top/bottom
if(a.x-a.r<cx-gh && a.x>cx-gh-0.6) a.x=cx-gh-a.r;
if(a.x+a.r>cx+gh && a.x<cx+gh+0.6) a.x=cx+gh+a.r;
}
// jamb corner circles
var j1x=cx-gh,j2x=cx+gh;
var d1=Math.hypot(a.x-j1x,a.y-wallY), d2=Math.hypot(a.x-j2x,a.y-wallY);
if(d1<a.r+0.12&&d1>1e-6){ a.x=j1x+(a.x-j1x)/d1*(a.r+0.12); a.y=wallY+(a.y-wallY)/d1*(a.r+0.12); }
if(d2<a.r+0.12&&d2>1e-6){ a.x=j2x+(a.x-j2x)/d2*(a.r+0.12); a.y=wallY+(a.y-wallY)/d2*(a.r+0.12); }
}
if(columnOn){
var vx=a.x-col.x, vy=a.y-col.y, vd=Math.hypot(vx,vy), rr2=a.r+col.r;
if(vd<rr2&&vd>1e-6){ a.x=col.x+vx/vd*rr2; a.y=col.y+vy/vd*rr2; }
}
}
}
// inelastic velocity fix so arches hold under pressure
var g3=buildGrid(agents,1.0);
var seen2={};
for(i=0;i<agents.length;i++){
a=agents[i];
neighbours(g3,agents,i,a.x,a.y,1.0,function(b,j){
var key=i<j?i+'_'+j:j+'_'+i;
if(seen2[key]) return; seen2[key]=1;
var ex=a.x-b.x, ey=a.y-b.y, dd=Math.hypot(ex,ey);
if(dd<a.r+b.r+0.03&&dd>1e-9){
var nx=ex/dd, ny=ey/dd;
var rvx=a.vx-b.vx, rvy=a.vy-b.vy;
var vn=rvx*nx+rvy*ny;
if(vn<0){ var imp=-vn*0.5; a.vx+=nx*imp*-1*-1; a.vx-=0; }
// redo cleanly: split approach
// (explicit to avoid sign bug)
rvx=a.vx-b.vx; rvy=a.vy-b.vy; vn=rvx*nx+rvy*ny;
if(vn<0){ a.vx-=nx*vn*0.5; a.vy-=ny*vn*0.5; b.vx+=nx*vn*0.5; b.vy+=ny*vn*0.5;
var tx2=-ny, ty2=nx;
var vt=(a.vx-b.vx)*tx2+(a.vy-b.vy)*ty2;
var f=Math.min(0.9,0.15+0.55*urgency);
a.vx-=tx2*vt*0.5*f; a.vy-=ty2*vt*0.5*f; b.vx+=tx2*vt*0.5*f; b.vy+=ty2*vt*0.5*f;
}
}
});
// wall velocity: kill motion into wall
if(Math.abs(a.y-wallY)<a.r+0.22){
if(Math.abs(a.x-cx)>gh){
if(a.y<wallY&&a.vy>0) a.vy*=0.1; else if(a.y>wallY&&a.vy<0) a.vy*=0.1;
a.vx*=0.98;
}
}
}
// exits
var keep=[];
for(i=0;i<agents.length;i++){
a=agents[i];
if(a.y>exitY){ evacuated++; exitTimes.push(simTime); }
else keep.push(a);
}
agents=keep;
if(!agents.length&&!finished){ finished=true; finishTime=simTime; mState.textContent='EMPTY in '+finishTime.toFixed(1)+'s'; }
}
function outflow(){
if(exitTimes.length===0||simTime<0.5) return 0;
if(simTime<12) return exitTimes.length/simTime*60;
var c=0, from=simTime-15;
for(var i=exitTimes.length-1;i>=0;i--){ if(exitTimes[i]>=from) c++; else break; }
return c/15*60;
}
function worldToScreen(){
var sx=cv.width/W, sy=cv.height/H;
return Math.min(sx,sy);
}
function render(){
ctx.clearRect(0,0,cv.width,cv.height);
var sc=cv.width/W, ox=0;
var sh=H*sc;
var oy=(cv.height-sh)/2;
function X(x){ return x*sc; }
function Y(y){ return oy+y*sc; }
// floor
ctx.fillStyle='#0a101c'; ctx.fillRect(0,0,cv.width,cv.height);
// stands
ctx.fillStyle='#18263f'; ctx.fillRect(X(1),Y(0.6),(28)*sc,(15.6)*sc);
for(var r=0;r<12;r++){
ctx.fillStyle=r%2?'#1c2c4b':'#1a2946';
ctx.fillRect(X(1.5),Y(1.2+r*1.22),(27)*sc,0.62*sc);
ctx.fillStyle='#0e1830';
for(var s=0;s<20;s++) ctx.fillRect(X(2+s*1.35),Y(1.2+r*1.22),0.7*sc,0.62*sc);
}
ctx.fillStyle='#8fa3c4'; ctx.font='12px system-ui'; ctx.fillText('STANDS — 240 seats',X(2),Y(1.0));
// exit plaza
ctx.fillStyle='#10241a'; ctx.fillRect(X(1),Y(wallY+0.2),28*sc,(H-wallY)*sc);
// gate floor highlight
var gh=gateHalf();
ctx.fillStyle='#1f8f4e'; ctx.fillRect(X(cx-gh),Y(wallY-0.25),(gh*2)*sc,0.9*sc);
// wall
ctx.fillStyle='#6b7688';
ctx.fillRect(X(1),Y(wallY-0.2),(cx-gh-1)*sc,0.4*sc);
ctx.fillRect(X(cx+gh),Y(wallY-0.2),(W-1-(cx+gh))*sc,0.4*sc);
ctx.fillStyle='#aeb9cc';
ctx.fillRect(X(1),Y(wallY-0.2),(cx-gh-1)*sc,0.08*sc);
ctx.fillRect(X(cx+gh),Y(wallY-0.2),(W-1-(cx+gh))*sc,0.08*sc);
ctx.fillStyle='#e8eef7'; ctx.font='bold 12px system-ui';
ctx.fillText('GATE '+gateYards.toFixed(1)+' yd',X(cx-1.4),Y(wallY+1.0));
// exit line
ctx.strokeStyle='#19b36b'; ctx.setLineDash([6,5]); ctx.beginPath();
ctx.moveTo(X(4),Y(exitY)); ctx.lineTo(X(26),Y(exitY)); ctx.stroke(); ctx.setLineDash([]);
ctx.fillStyle='#7fe0a8'; ctx.font='12px system-ui'; ctx.fillText('counted as OUT below green line',X(10.5),Y(exitY+0.6));
// column
if(columnOn){
var col=colPos();
ctx.fillStyle='rgba(0,0,0,.4)'; ctx.beginPath(); ctx.arc(X(col.x)+3,Y(col.y)+4,col.r*sc,0,7); ctx.fill();
ctx.fillStyle='#c7cfdd'; ctx.beginPath(); ctx.arc(X(col.x),Y(col.y),col.r*sc,0,7); ctx.fill();
ctx.fillStyle='#5b6678'; ctx.beginPath(); ctx.arc(X(col.x),Y(col.y),col.r*sc*0.55,0,7); ctx.fill();
}
// agents
for(var i=0;i<agents.length;i++){
var a=agents[i];
var sp=Math.hypot(a.vx,a.vy);
var f=Math.min(1,sp/3);
var cr=Math.round(63+f*(255-63)), cg=Math.round(169-f*80), cb=Math.round(255-f*170);
ctx.fillStyle='rgba(0,0,0,.35)'; ctx.beginPath(); ctx.arc(X(a.x)+1.5,Y(a.y)+2,a.r*sc,0,7); ctx.fill();
ctx.fillStyle='rgb('+cr+','+cg+','+cb+')';
ctx.beginPath(); ctx.arc(X(a.x),Y(a.y),a.r*sc,0,7); ctx.fill();
ctx.strokeStyle='rgba(255,255,255,.55)'; ctx.lineWidth=1; ctx.stroke();
if(sp>0.3){ ctx.strokeStyle='#0b1220'; ctx.beginPath(); ctx.moveTo(X(a.x),Y(a.y));
ctx.lineTo(X(a.x+a.vx*0.22),Y(a.y+a.vy*0.22)); ctx.stroke(); }
}
// metrics
var fl=finished?finishTime:simTime;
mTime.textContent=fl.toFixed(1)+'s';
mFlow.textContent=Math.round(outflow());
mEvac.textContent=evacuated+' / '+N;
mRem.textContent=agents.length;
var avg=simTime>0.5?evacuated/simTime*60:0;
mAvg.textContent='avg '+Math.round(avg)+' /min';
}
var last=performance.now(), acc=0;
function frame(now){
requestAnimationFrame(frame);
var rdt=Math.min(0.1,(now-last)/1000); last=now;
if(!running) { render(); return; }
if(finished){ render(); return; }
acc+=rdt*timeScale;
var dt=1/60, n=0;
while(acc>=dt&&n<12){ step(dt); acc-=dt; n++; if(finished){acc=0;break;} }
if(n===12) acc=0;
render();
}
inU.addEventListener('input',function(){ urgency=parseFloat(inU.value); oU.textContent=(+urgency).toFixed(2); });
inG.addEventListener('change',function(){ var v=parseFloat(inG.value); if(isFinite(v)&&v>0.2){ gateYards=v; } oG.textContent=(+gateYards).toFixed(2); reset(); });
inC.addEventListener('change',function(){ columnOn=inC.checked; syncUI(); reset(); });
bReset.addEventListener('click',function(){ reset(); running=true; bPause.textContent='Pause'; });
bPause.addEventListener('click',function(){ running=!running; bPause.textContent=running?'Pause':'Play'; });
function ts(v,el){ timeScale=v; document.getElementById('t1').classList.remove('active');document.getElementById('t2').classList.remove('active');document.getElementById('t4').classList.remove('active');el.classList.add('active'); }
document.getElementById('t1').addEventListener('click',function(){ts(1,this);});
document.getElementById('t2').addEventListener('click',function(){ts(2,this);});
document.getElementById('t4').addEventListener('click',function(){ts(4,this);});
parseHash(); syncUI(); reset(); running=true;
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Real-like: everyone uses identical steering plus separation plus solid contact, so arches, stop-and-go bursts, lane formation in the gate and pressure build-up behind the wall emerge rather than being scripted. High urgency crowds push harder, pack tighter and slide less, so the gate clogs intermittently and average outflow falls. The column splits one wide arch into two smaller less stable arches and shields the opening from direct pressure, so flow steadies. The page does jam at the gate, mostly brief 1-4 s arches at urgency 0.9 and rare short hesitations at urgency 0.3.
Not real: 2D discs with one radius class, one waypoint (gate centre then straight out), no falling, no injuries, no helping, no seat rows as obstacles, no vision or choice, only per-agent variance in free speed and wobble phase.
Special-casing disclosed: global speed cap of 3.2+1.3 times urgency m/s for integrator stability, not gate-specific; hard positional projection for 3 iterations per step that guarantees nobody overlaps anybody including walls, jambs and column; continuous small heading wobble whose amplitude shrinks with urgency plus a politeness factor where low-urgency agents ease off drive when closer than 0.7 m while high-urgency agents keep pushing; inelastic wall and pair velocity fix with tangential friction scaled by urgency. There is no jam detector, no teleport, no gate speed limit and no overlap allowance — residual overlap is projected to zero every step.