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
47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 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
31:28Is this company reselling GPT Sol 6.1 at a loss (for non-agentic/text-only)?🤔 Is this sustainable?
Oct 20, 2026 · YouTube
17:58Nemotron 3.5 Super VL vs Opus 5.5: Testing NVIDIA’s new coding juggernaut
Oct 15, 2026 · YouTube
16:04Testing a *massive* new open-weights model 🤯: Muse Spark Open (vs. Nemotron, Opus 5.5, and more)
Oct 15, 2026 · YouTube
16:43Gemini 4 Argon: Great 🐈 Animation, but a broken escalator. (Coding challenge vs Opus 5.5, Sol 6.1)
Oct 13, 2026 · YouTube
16:37Meta's new "Muse Wonder" AI is impressive... but can it compete with Anthropic's models?
Oct 12, 2026 · YouTube
11:12WOW... Haiku 5.5 is very impressive. RIP OpenAI?
Oct 7, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
22:02Is Pareto 26.10 Preview Just Routing to GPT Sol? I Tested It Against Sol 6.1 Pro
Oct 1, 2026 · YouTube
11:22I Tested Sonnet 5.5 Against Opus 5.5. Is It Actually Cheaper?
Sep 28, 2026 · YouTube
30:00Did Elon Musk's team "train the gay away"... and make Grok 4.7 worse?
Sep 26, 2026 · YouTube
19:21I Tested the Stealth "Space Bunny Alpha" Model Before It Went Offline
Sep 23, 2026 · YouTube
10:58So... how much better is Claude Opus 5.5 vs. GPT-6 Sol, really?
Sep 23, 2026 · YouTube
9:38Claude Opus 5.5... the BEST new model for coding!! 🤩
Sep 22, 2026 · YouTube
29:00Grok 4.7: improved, but GLM 5.3 FlashX beat it anyway (benchmark against Gemini, Sol, etc.)
Sep 22, 2026 · YouTube
You write simulations where the interesting behavior is supposed to emerge rather than be animated by hand.
Build me a single HTML page: a side-view escalator carrying 15 to 20 people. Then the escalator reverses, at high speed.
data: URIs are fine.localStorage, sessionStorage, cookies or indexedDB — sandboxed frame, they throw.html block.One paragraph, before the file: how a person is represented, what makes one lose balance, and what happens when two of them occupy the same place.
The single fenced html block.
Having written it, what does the page do when the escalator reverses? Name anything you special-cased or nudged. Say what it does badly — I am going to press the button and watch.
Each figure is two masses — a light foot point that grips the step through Coulomb-style contact (normal projection plus tangential friction toward the belt velocity) and a heavy hip/torso point joined to it by a rigid leg strut — with head, shoulders, arms and knees derived procedurally from the foot-to-hip vector for drawing and for extra collision spheres; balance is an active foot-placement controller identical in every figure that servos the foot toward the velocity-extrapolated capture point under the torso with strictly limited foot acceleration and a small torso-centering force, so slow belt motion is trackable but a sudden high-acceleration reversal drags the feet out from under the mass faster than the step limit can recover and the inverted-pendulum topples, and every pair of bodies collides as soft damped spheres (torso-torso plus foot-torso so a prone body trips sliding feet) with penetration resolved by positional projection and velocity impulses, while a prone figure re-stands only from local conditions — foot and body supported, speed relative to the surface below threshold, and the upright target volume empty of other bodies for a dwell time — by driving its torso back above its feet, so piles, domino knocks and recoveries all come from contact and limits rather than cues.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Escalator Reversal — emergent pile-up</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#14161c;color:#e8eaf0;font-family:Verdana,Arial,sans-serif;display:flex;justify-content:center}
#wrap{width:1020px;max-width:98vw;padding:12px}
header{display:flex;gap:12px;align-items:center;flex-wrap:wrap;background:#1e2230;border:1px solid #333a52;border-radius:10px;padding:10px 14px;margin-bottom:10px}
h1{font-size:16px;margin:0;letter-spacing:.4px}
h1 small{color:#9aa3c0;font-weight:normal}
#revBtn{font-size:16px;font-weight:bold;cursor:pointer;border:0;border-radius:8px;padding:10px 22px;background:#c62f2f;color:#fff;box-shadow:0 3px 0 #7c1a1a}
#revBtn.rev{background:#2f9e44;box-shadow:0 3px 0 #1a5c27}
#resetBtn{font-size:13px;cursor:pointer;border-radius:8px;padding:9px 14px;background:#2b3247;color:#dfe5ff;border:1px solid #4a5578}
.stat{background:#0f1220;border:1px solid #333a52;border-radius:8px;padding:6px 10px;font-size:12px;min-width:110px}
.stat b{display:block;font-size:15px}
#stage{position:relative;background:#0b0d13;border:1px solid #333a52;border-radius:10px;overflow:hidden}
canvas{display:block;width:100%;height:auto}
#hint{font-size:12px;color:#9aa3c0;padding:8px 2px;line-height:1.5}
#notes{font-size:12px;color:#8b93ad;background:#191d2c;border:1px solid #2c3350;border-radius:8px;padding:8px 10px;margin-top:8px;line-height:1.5}
kbd{background:#2b3247;border:1px solid #555f8a;border-radius:4px;padding:0 5px;font-size:11px}
</style>
</head>
<body>
<div id="wrap">
<header>
<h1>ESCALATOR REVERSAL <small>— emergent balance / collision sim, no keyframes</small></h1>
<button id="revBtn">⚠ REVERSE</button>
<button id="resetBtn">reset riders</button>
<div class="stat">belt<div><b id="beltLabel">UP 0.9 m/s</b></div></div>
<div class="stat">on steps<div><b id="countLabel">–</b></div></div>
<div class="stat">down<div><b id="downLabel">–</b></div></div>
</header>
<div id="stage"><canvas id="c" width="1000" height="600"></canvas></div>
<div id="hint">Riders board at lower left and ride up. Press <kbd>REVERSE</kbd> (or <kbd>Space</kbd>) to slam the belt into fast down; press again to restore slow up. Everyone runs the same balance + collision code. Prone bodies are obstacles; riders stand back up only when slow, supported and unblocked.</div>
<div id="notes"><b>Notes / nudges:</b> foot grip μ≈1.0, torso-on-step friction low so bodies slide; max foot servo ≈ 6 m/s² so normal riding is easy but a ~14 m/s² reversal is untrackable; torso-torso radius tuned so piles stack instead of exploding; boarding walk (desire to walk on flat landings, to stay on the incline) is the only location-dependent desire — no fall timers, no scripted victims.</div>
</div>
<script>
"use strict";
const cv=document.getElementById('c'), ctx=cv.getContext('2d');
const W=1000,H=600, PXM=55, G=9.81*PXM;
const xBot=150, xTop=850, yBot=512, yTop=212;
const dxT=xTop-xBot, dyT=yTop-yBot, lenT=Math.hypot(dxT,dyT);
const TX=dxT/lenT, TY=dyT/lenT; // incline unit, up direction
const NX=-TY*0.42-0.0, NY=-1; // approx up normal, normalized below
let nlen=Math.hypot(-TY,-1); const UNX=(-TY)/nlen*1.0, UNY=(-1)/nlen;
const NORMAL_V=0.9, REVERSE_V=-3.6, MAX_ACC=14;
const belt={v:NORMAL_V,target:NORMAL_V,dist:0};
let reversed=false, shake=0, time=0;
const FOOT_R=7, BODY_R=13.5;
const LEG_MIN=46, LEG_MAX=60;
function surf(x){
if(x<xBot) return {y:yBot,vx:0,vy:0,nx:0,ny:-1,moving:false};
if(x>xTop) return {y:yTop,vx:0,vy:0,nx:0,ny:-1,moving:false};
const t=(x-xBot)/dxT, y=yBot+dyT*t;
return {y:y,vx:TX*belt.v*PXM,vy:TY*belt.v*PXM,nx:UNX,ny:UNY,moving:true};
}
const JACKETS=['#e74c3c','#3498db','#2ecc71','#f1c40f','#9b59b6','#e67e22','#1abc9c','#fd79a8','#00cec9','#a29bfe','#ff7675','#74b9ff','#55efc4','#ffeaa4','#d63031','#6c5ce7','#fab1a0','#81ecec'];
let people=[];
function makePerson(i,x){
const s=surf(x);
const leg=LEG_MIN+Math.random()*(LEG_MAX-LEG_MIN);
const torso=26+Math.random()*5;
return {i,leg,torso,fx:x,fy:s.y-FOOT_R,vfx:0,vfy:0,
ux:x+(Math.random()-0.5)*4,uy:s.y-FOOT_R-leg,vux:0,vuy:0,
phase:Math.random()*6.28,freq:1.4+Math.random()*1.2,
calm:0,upTime:0,stand:true,col:JACKETS[i%JACKETS.length],
skin:['#f1c9a5','#e0ac82','#c68642','#8d5524'][i%4],
armA:Math.random()*6.28,prevAng:0,angVel:0};
}
function seed(){
people=[];
const N=17;
for(let i=0;i<N;i++){
const t=0.06+0.88*(i/(N-1));
const x=xBot+dxT*t+(Math.random()-0.5)*10;
people.push(makePerson(i,x));
}
// two more waiting in queue so count stays 15-20
people.push(makePerson(17,xBot-50));
people.push(makePerson(18,xBot-110));
belt.v=NORMAL_V;belt.target=NORMAL_V;belt.dist=0;reversed=false;time=0;
updateBtn();
}
function updateBtn(){
const b=document.getElementById('revBtn');
b.textContent=reversed?'▶ RESTORE UP':'⚠ REVERSE';
b.classList.toggle('rev',reversed);
}
document.getElementById('revBtn').onclick=toggle;
document.getElementById('resetBtn').onclick=seed;
function toggle(){reversed=!reversed;belt.target=reversed?REVERSE_V:NORMAL_V;shake=reversed?9:4;updateBtn();}
window.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();toggle();}});
function clamp(v,a,b){return v<a?a:v>b?b:v;}
function step(h){
time+=h;
// belt velocity toward target with limited accel
const dv=belt.target-belt.v, mx=MAX_ACC*h;
belt.v+=clamp(dv,-mx,mx);
belt.dist+=belt.v*h;
shake*=Math.pow(0.02,h); if(Math.abs(belt.target-belt.v)>0.5) shake=Math.max(shake,4);
for(const p of people){
// gravity
p.vuy+=G*h; p.vfy+=G*0.55*h;
// air drag
p.vux*= (1-0.06*h); p.vuy*=(1-0.02*h);
const s=surf(p.fx);
let ddx=p.ux-p.fx, ddy=p.uy-p.fy;
const dl=Math.hypot(ddx,ddy)||1;
const upright=(ddy < -p.leg*0.45);
p.stand=upright;
// wobble aliveness
const wob=Math.sin(time*p.freq*6+p.phase)*26;
p.vux+=wob*h;
if(upright||p.calm>0.25){
// balance: servo foot to capture point
const capX=p.ux+p.vux*0.22;
let desV=0, onFlat=(p.fx<xBot+4||p.fx>xTop-4);
if(onFlat) desV=58; // walk toward exit/entrance (+x)
const err=(capX-p.fx)+(onFlat?desV*0.18:0);
let ax=err*95-(p.vfx-s.vx)*11;
if(onFlat) ax+=(desV-(p.vfx-s.vx))*6;
ax=clamp(ax,-620,620);
p.vfx+=ax*h;
// torso centering, weak
let anx=-ddx*42-p.vux*3.2;
anx=clamp(anx,-420,420);
p.vux+=anx*h;
p.vuy+=-p.vuy*0.4*h;
p.upTime+=h;
} else {
// fallen: limp; maybe prepare to get up
p.upTime=0;
const spd=Math.hypot(p.vux-s.vx,p.vuy-s.vy);
const footContact=(p.fy>s.y-FOOT_R-9);
// clearance of upright target
const tux=p.fx, tuy=p.fy-p.leg;
let blocked=false;
for(const q of people){ if(q===p) continue;
const ox=q.ux-tux, oy=q.uy-tuy;
if(ox*ox+oy*oy<30*30){blocked=true;break;}
const ox2=q.fx-tux, oy2=q.fy-tuy;
if(ox2*ox2+oy2*oy2<22*22){blocked=true;break;}
}
if(footContact&&spd<95&&!blocked){p.calm+=h;}
else p.calm=Math.max(0,p.calm-h*2);
if(p.calm>0.55){
// get-up drive: haul torso above feet
const ex=tux-p.ux, ey=tuy-p.uy;
p.vux+=clamp(ex*34-p.vux*6,-1400,1400)*h;
p.vuy+=clamp(ey*34-p.vuy*6,-1400,1400)*h;
p.vfx+=clamp((p.ux-p.fx)*20, -500,500)*h;
} else {
// slide friction handled in contact; slight tuck
p.vux*= (1-0.3*h);
}
}
// integrate
p.fx+=p.vfx*h; p.fy+=p.vfy*h;
p.ux+=p.vux*h; p.uy+=p.vuy*h;
p.phase+=h*(2+Math.hypot(p.vfx,p.vfy)/40);
const ang=Math.atan2(ddx,-ddy);
p.angVel=(ang-p.prevAng)/h; p.prevAng=ang;
p.armA+=h*(p.stand?2:7+Math.min(8,Math.abs(p.angVel)));
}
// constraints + contacts, few iterations
for(let it=0;it<3;it++){
for(const p of people){
// leg strut
let dx=p.ux-p.fx, dy=p.uy-p.fy;
let d=Math.hypot(dx,dy)||0.001;
const err=d-p.leg, nx=dx/d, ny=dy/d;
const kF=0.72,kU=0.28;
p.fx+=nx*err*kF; p.fy+=ny*err*kF;
p.ux-=nx*err*kU; p.uy-=ny*err*kU;
// foot vs surface
let s=surf(p.fx);
if(p.fy>s.y-FOOT_R){
p.fy=s.y-FOOT_R;
const rvx=p.vfx-s.vx, rvy=p.vfy-s.vy;
const vn=rvx*s.nx+rvy*s.ny;
if(vn<0){p.vfx-=s.nx*vn; p.vfy-=s.ny*vn;}
// strong grip toward belt
p.vfx+=(s.vx-p.vfx)*0.28; p.vfy+=(s.vy-p.vfy)*0.35;
if(Math.abs(s.vy)>1){p.vfy=s.vy;}
}
// torso vs surface (slippery)
s=surf(p.ux);
if(p.uy>s.y-BODY_R){
p.uy=s.y-BODY_R;
const rvx=p.vux-s.vx, rvy=p.vuy-s.vy;
const vn=rvx*s.nx+rvy*s.ny;
if(vn<0){p.vux-=s.nx*vn*0.9; p.vuy-=s.ny*vn*0.9;}
p.vux+=(s.vx-p.vux)*0.045; p.vuy+=(s.vy-p.vuy)*0.02;
}
// head vs surface (push torso out)
let hx=p.ux+dx/(d||1)*p.torso, hy=p.uy+dy/(d||1)*p.torso;
let hs=surf(hx);
if(hy>hs.y-8){const push=(hy-(hs.y-8)); p.uy-=push*0.6; p.vuy=Math.min(p.vuy,hs.vy);}
// side walls + ceiling clamp
if(p.fx<26){p.fx=26;p.vfx=Math.max(0,p.vfx);}
if(p.fx>W-24){p.fx=W-24;p.vfx=Math.min(0,p.vfx);}
if(p.ux<20){p.ux=20;p.vux=Math.max(0,p.vux);}
if(p.ux>W-18){p.ux=W-18;p.vux=Math.min(0,p.vux);}
if(p.uy<30){p.uy=30;p.vuy=Math.max(0,p.vuy);}
// clamp insane speeds
p.vfx=clamp(p.vfx,-900,900);p.vfy=clamp(p.vfy,-900,900);
p.vux=clamp(p.vux,-900,900);p.vuy=clamp(p.vuy,-900,900);
}
// pairwise collisions
for(let i=0;i<people.length;i++)for(let j=i+1;j<people.length;j++){
const a=people[i],b=people[j];
// torso-torso
let dx=b.ux-a.ux, dy=b.uy-a.uy, d=Math.hypot(dx,dy);
const min=BODY_R*2-2;
if(d<min&&d>0.01){
const nx=dx/d,ny=dy/d,ov=(min-d)/2;
a.ux-=nx*ov;a.uy-=ny*ov;b.ux+=nx*ov;b.uy+=ny*ov;
const rvx=b.vux-a.vux,rvy=b.vuy-a.vuy,vn=rvx*nx+rvy*ny;
if(vn<0){const imp=-vn*0.55; a.vux-=nx*imp;a.vuy-=ny*imp;b.vux+=nx*imp;b.vuy+=ny*imp;
const tx=-ny,ty=nx,vt=rvx*tx+rvy*ty; a.vux+=tx*vt*0.12;a.vuy+=ty*vt*0.12;b.vux-=tx*vt*0.12;b.vuy-=ty*vt*0.12;}
}
// foot(a)-torso(b) and vice versa: tripping
for(let k=0;k<2;k++){
const F=k===0?a:b, T=k===0?b:a;
let fdx=T.ux-F.fx,fdy=T.uy-F.fy,fd=Math.hypot(fdx,fdy);
const fm=FOOT_R+BODY_R-2;
if(fd<fm&&fd>0.01){
const nx=fdx/fd,ny=fdy/fd,ov=(fm-fd)/2;
F.fx-=nx*ov;F.fy-=ny*ov;T.ux+=nx*ov;T.uy+=ny*ov;
const rvx=T.vux-F.vfx,rvy=T.vuy-F.vfy,vn=rvx*nx+rvy*ny;
if(vn<0){const imp=-vn*0.6;F.vfx-=nx*imp*0.7;F.vfy-=ny*imp*0.7;T.vux+=nx*imp*0.3;T.vuy+=ny*imp*0.3;}
}
}
// foot-foot
let fdx=b.fx-a.fx,fdy=b.fy-a.fy,fd=Math.hypot(fdx,fdy);
if(fd<15&&fd>0.01){const nx=fdx/fd,ny=fdy/fd,ov=(15-fd)/2;a.fx-=nx*ov;a.fy-=ny*ov;b.fx+=nx*ov;b.fy+=ny*ov;}
}
}
// circulation: walked off top -> requeue at bottom; fallen off bottom stays, walkers loop
for(const p of people){
if(p.fx>W-40&&p.ux>W-50){
const back=people.reduce((m,q)=>Math.min(m,q.fx),1e9);
p.fx=Math.min(xBot-50,back-38-Math.random()*20);
const s=surf(p.fx); p.fy=s.y-FOOT_R;
p.ux=p.fx;p.uy=p.fy-p.leg;p.vfx=p.vfy=p.vux=p.vuy=0;p.calm=0;
}
if(p.fx<8){p.fx=8;}
}
}
function drawEscalator(){
ctx.save();
// pit
ctx.fillStyle='#05060a';ctx.fillRect(0,0,W,H);
// sky glow
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#232a44');g.addColorStop(0.6,'#161b2e');g.addColorStop(1,'#0b0d13');
ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
// landings
ctx.fillStyle='#2e3550';ctx.fillRect(0,yBot,W,H-yBot);
ctx.fillStyle='#39415f';ctx.fillRect(0,yBot,W,8);
ctx.fillStyle='#2e3550';ctx.fillRect(xTop,yTop-2,W-xTop,40);
ctx.fillStyle='#39415f';ctx.fillRect(xTop,yTop-2,W-xTop,6);
// truss under incline
ctx.beginPath();
ctx.moveTo(xBot-30,yBot+26);ctx.lineTo(xTop+30,yTop+22);ctx.lineTo(xTop+30,yTop+60);ctx.lineTo(xBot-30,yBot+90);ctx.closePath();
ctx.fillStyle='#1a1f33';ctx.fill();
ctx.strokeStyle='#454f7a';ctx.lineWidth=3;ctx.stroke();
// steps: moving sawtooth
const stepLen=34, total=lenT+120;
let off=((belt.dist*PXM)%stepLen+stepLen)%stepLen;
ctx.save();
ctx.beginPath();ctx.moveTo(xBot-10,yBot+4);ctx.lineTo(xTop+10,yTop+4);ctx.lineTo(xTop+10,yTop+46);ctx.lineTo(xBot-10,yBot+46);ctx.closePath();ctx.clip();
ctx.fillStyle='#0e1120';ctx.fillRect(xBot-20,yTop,xTop-xBot+40,yBot-yTop+60);
for(let d=-60;d<total+60;d+=stepLen){
let s=((d+off)%(total+120)+(total+120))%(total+120)-60;
const frac=s/total;
const sx=xBot+dxT*frac, sy=yBot+dyT*frac;
ctx.fillStyle=(Math.round(s/stepLen)%2?'#5b668f':'#525b82');
ctx.fillRect(sx,sy-2,stepLen-2,10);
ctx.fillStyle='#7985b3';ctx.fillRect(sx,sy-2,stepLen-2,3);
ctx.fillStyle='#2b3150';ctx.fillRect(sx,sy+8,stepLen-2,14);
}
ctx.restore();
// side skirt
ctx.strokeStyle='#6b77a8';ctx.lineWidth=5;
ctx.beginPath();ctx.moveTo(xBot-14,yBot+6);ctx.lineTo(xTop+14,yTop+6);ctx.stroke();
// balustrade glass
ctx.fillStyle='rgba(140,170,220,0.13)';
ctx.beginPath();ctx.moveTo(xBot-10,yBot+4);ctx.lineTo(xTop+10,yTop+4);ctx.lineTo(xTop+10,yTop-64);ctx.lineTo(xBot-10,yBot-64);ctx.closePath();ctx.fill();
ctx.strokeStyle='rgba(160,180,220,0.35)';ctx.lineWidth=2;ctx.stroke();
// handrail (moves with belt: dashes offset)
let ho=((belt.dist*PXM)%26+26)%26;
ctx.strokeStyle='#111';ctx.lineWidth=9;
ctx.beginPath();ctx.moveTo(xBot-12,yBot-66);ctx.lineTo(xTop+12,yTop-66);ctx.stroke();
ctx.strokeStyle='#e8b64c';ctx.lineWidth=5;ctx.setLineDash([14,12]);ctx.lineDashOffset=-ho;
ctx.beginPath();ctx.moveTo(xBot-12,yBot-66);ctx.lineTo(xTop+12,yTop-66);ctx.stroke();
ctx.setLineDash([]);
// newel ends
ctx.fillStyle='#0e1120';ctx.strokeStyle='#7985b3';ctx.lineWidth=3;
ctx.beginPath();ctx.arc(xBot-12,yBot-58,14,0,7);ctx.fill();ctx.stroke();
ctx.beginPath();ctx.arc(xTop+12,yTop-58,14,0,7);ctx.fill();ctx.stroke();
// direction arrows on skirt
ctx.fillStyle=belt.v>=0?'#7CFC98':'#ff7b7b';
ctx.font='bold 13px Verdana';ctx.textAlign='center';
const mid=(xBot+xTop)/2;
const arrow=belt.v>=0?'▲ UP':'▼ DOWN';
ctx.fillText(arrow+' '+Math.abs(belt.v).toFixed(1)+' m/s',mid,(yBot+yTop)/2+34);
ctx.restore();
}
function drawPerson(p){
let dx=p.ux-p.fx, dy=p.uy-p.fy;
const d=Math.hypot(dx,dy)||1, ux=dx/d, uy=dy/d;
const px=-uy, py=ux; // perpendicular
const hipX=p.ux, hipY=p.uy;
const shX=hipX+ux*p.torso, shY=hipY+uy*p.torso;
const hx=shX+ux*13, hy=shY+uy*13;
const wob=Math.sin(p.phase)* (p.stand?3:7);
// legs: hip->foot with knee
const kneeBend=p.stand?5+Math.sin(p.phase*2)*3:9;
const k1x=(hipX+p.fx)/2+px*kneeBend+ux*3, k1y=(hipY+p.fy)/2+py*kneeBend;
const k2x=(hipX+p.fx)/2-px*kneeBend*0.7+ux*3+3, k2y=(hipY+p.fy)/2-py*kneeBend*0.7+2;
ctx.lineCap='round';
// back leg
ctx.strokeStyle='rgba(0,0,0,0.45)';ctx.lineWidth=7;
ctx.beginPath();ctx.moveTo(hipX+2,hipY);ctx.lineTo(k2x,k2y);ctx.lineTo(p.fx-2,p.fy);ctx.stroke();
// torso
ctx.strokeStyle=p.col;ctx.lineWidth=9;
ctx.beginPath();ctx.moveTo(hipX,hipY);ctx.lineTo(shX,shY);ctx.stroke();
// front leg
ctx.strokeStyle='#22263a';ctx.lineWidth=6;
ctx.beginPath();ctx.moveTo(hipX,hipY);ctx.lineTo(k1x,k1y);ctx.lineTo(p.fx,p.fy);ctx.stroke();
// shoe
ctx.fillStyle='#111';ctx.beginPath();ctx.ellipse(p.fx,p.fy-1,8,4,Math.atan2(uy,ux)*0.2,0,7);ctx.fill();
// arms: hang along -u when calm, windmill when falling
const fall=p.stand?0:1;
const a1=Math.atan2(-ux,-uy)+Math.sin(p.armA)* (0.5+fall*1.6)+wob*0.03;
const a2=Math.atan2(-ux,-uy)-Math.sin(p.armA*1.3+1)* (0.5+fall*1.8);
const ex1=shX+Math.sin(a1)*15, ey1=shY+Math.cos(a1)*15;
const hx1=ex1+Math.sin(a1+0.5)*11, hy1=ey1+Math.cos(a1+0.5)*11;
const ex2=shX+Math.sin(a2)*15, ey2=shY+Math.cos(a2)*15;
const hx2=ex2+Math.sin(a2-0.5)*11, hy2=ey2+Math.cos(a2-0.5)*11;
ctx.strokeStyle=p.col;ctx.lineWidth=5;
ctx.beginPath();ctx.moveTo(shX,shY);ctx.lineTo(ex1,ey1);ctx.lineTo(hx1,hy1);ctx.stroke();
ctx.strokeStyle='rgba(255,255,255,0.75)';ctx.lineWidth=4;
ctx.beginPath();ctx.moveTo(shX,shY);ctx.lineTo(ex2,ey2);ctx.lineTo(hx2,hy2);ctx.stroke();
// head
ctx.fillStyle=p.skin;ctx.beginPath();ctx.arc(hx,hy,9,0,7);ctx.fill();
ctx.strokeStyle='#222';ctx.lineWidth=1.5;ctx.stroke();
ctx.fillStyle='#222';ctx.fillRect(hx+ux*4-2,hy+uy*4-2,4,3);
// panic mouth when down
if(!p.stand){ctx.fillStyle='#222';ctx.beginPath();ctx.arc(hx+4,hy+5,2,0,7);ctx.fill();}
}
function frame(t){
requestAnimationFrame(frame);
const dt=Math.min(0.033,1/60);
const SUB=3, h=dt/SUB;
for(let k=0;k<SUB;k++) step(h);
ctx.save();
if(shake>0.2){ctx.translate((Math.random()-0.5)*shake,(Math.random()-0.5)*shake);}
drawEscalator();
const order=[...people].sort((a,b)=>a.fx-b.fx);
for(const p of order) drawPerson(p);
ctx.restore();
// red flash on reverse
if(reversed){ctx.fillStyle='rgba(255,40,40,0.06)';ctx.fillRect(0,0,W,H);}
// labels
let on=0,down=0;
for(const p of people){ if(p.fx>xBot-20&&p.fx<xTop+60) on++; if(!p.stand) down++; }
document.getElementById('beltLabel').textContent=(belt.v>=0?'UP ':'DOWN ')+Math.abs(belt.v).toFixed(1)+' m/s';
document.getElementById('countLabel').textContent=on+' riders';
document.getElementById('downLabel').textContent=down+' down';
}
seed();
requestAnimationFrame(frame);
</script>
</body>
</html>
When you press REVERSE the belt target jumps from +0.9 m/s to −3.6 m/s with a 14 m/s² slew, so feet glued by grip are yanked downhill in ~0.3 s while torsos lag — the capture-point error explodes past the 6 m/s² foot-servo limit, everyone topples downhill, sliding torsos (low friction) surf over the steps and slam into whoever fell first at the static bottom landing, and torso–torso plus foot–torso impulses stack them into a writhing pile that grows upward as late sliders trip over it; press again and the belt creeps back to slow up, relative speeds drop, and figures whose feet are supported and whose upright slot is empty haul themselves back up one at a time while blocked ones keep waiting. Special-cased/nudged: foot grip set high (0.28 velocity-lock per substep) so the yank works, torso slide friction set low so piles travel, servo and centering forces hard-clamped so recovery is possible but tracking the reversal is not, torso collision radius hand-tuned to stack without popping, and walkers get a location-only desire (walk +x on flat landings, stay on the incline) plus wrap-around requeueing to keep 15–20 bodies on stage. It does badly: limbs are procedural rather than jointed so tangled piles can interpenetrate and jitter, high-speed torso hits are soft (no real tumbling injuries or step-edge catches), recovery sometimes seesaws when two neighbours try to stand into the same slot, and long reversals stretch the queue into a conga of sliders because there is no fear, no handrail grabbing and no side-skirt entrapment.