OverviewPhysics Lab › R57
Physics Lab · blind round

R57 · Hanging rope with Verlet integration

blind round 3/8 local models answered

Answers below are anonymized and shuffled. Read, judge, pick a favourite — then scroll down and reveal the models.

The reveal also holds a frontier reference on the same task (Claude Opus 5) — kept out of sight here so it cannot steer your judgement.

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 120 lines total) that shows a hanging rope simulated with Verlet integration on a <canvas>. Exactly 40 point masses connected by distance constraints, both ends anchored at the top with a gap between them so the rope hangs in a catenary. Apply gravity and run several constraint relaxation passes per frame so the rope does not stretch. Give the rope a sideways impulse every few seconds so it visibly swings and settles. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running. No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.

The answers — blind

Local models (Brain cluster) — identical prompt, anonymized order
MODEL Aqwen3.5:9bBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Verlet Rope Simulation</title></head>
<body><canvas id="c"></canvas><script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;

const N = 40;
const K_PASSES = 15;
const G = 0.3; // Gravity constant
const DT = 0.1; // Time step
let particles = [];
let constraints = [];
let impulseTimer = 0;

function init() {
    for (let i = 0; i < N; i++) {
        particles.push({ x: canvas.width / 2, y: 50 + (i * 12) });
    }

    // Anchor the ends (Particles 0 and N-1 are fixed or constrained to anchors)
    const anchorA = { x: 100, y: 80 };
    const anchorB = { x: canvas.width - 100, y: 80 };

    // Initialize positions based on a slightly curved path
    for (let i = 0; i < N; i++) {
        particles[i].x = anchorA.x + (anchorB.x - anchorA.x) * (i / (N - 1));
        particles[i].y = anchorA.y + Math.sin(i / 5) * 50;
    }

    // Create distance constraints between adjacent particles
    for (let i = 0; i < N - 1; i++) {
        constraints.push({ p1: i, p2: i + 1, length: Math.hypot(particles[i+1].x - particles[i].x, particles[i+1].y - particles[i].y) });
    }

    // Fix the first and last particles to their anchors
    particles[0] = { x: anchorA.x, y: anchorA.y, px: anchorA.x, py: anchorA.y };
    particles[N - 1] = { x: anchorB.x, y: anchorB.y, px: anchorB.x, py: anchorB.y };
}

function updatePhysics() {
    const p = particles;
    for (let i = 0; i < N; i++) {
        if (i === 0 || i === N - 1) continue; // Anchored points do not move via Verlet
        
        const nextX = p[i].x + (p[i].x - p[i].px) * 0.95 + G * DT * 2;
        const nextY = p[i].y + (p[i].y - p[i].py) * 0.95 + G * DT * 2;

        p[i].px = p[i].x;
        p[i].py = p[i].y;
        p[i].x = nextX;
        p[i].y = nextY;
    }

    // Apply impulse if time is right
    impulseTimer += 0.1;
    if (Math.sin(impulseTimer) > Math.cos(impulseTimer) && Math.floor(impulseTimer / 2) % 4 === 0) {
        const forceX = Math.sin(particles[N/2].y * 0.1) * 50;
        for (let i = 1; i < N - 2; i++) {
            particles[i].x += forceX / 4;
        }
    }

    // Constraint relaxation passes (PBD)
    for (let pass = 0; pass < K_PASSES; pass++) {
        constraints.forEach(c => {
            const p1 = particles[c.p1];
            const p2 = particles[c.p2];
            const dx = p2.x - p1.x;
            const dy = p2.y - p1.y;
            const dist = Math.hypot(dx, dy);
            const correction = (dist - c.length) / dist;

            p1.x -= dx * 0.5 * correction;
            p1.y -= dy * 0.5 * correction;
            p2.x += dx * 0.5 * correction;
            p2.y += dy * 0.5 * correction;
        });

        // Re-apply anchor constraints after relaxation
        particles[0].x = particles[0].px;
        particles[0].y = particles[0].py;
        particles[N - 1].x = particles[N - 1].px;
        particles[N - 1].y = particles[N - 1].py;
    }

    // Update previous positions for next frame (crucial step)
    for (let i = 0; i < N; i++) {
        if (!(i === 0 || i === N - 1)) {
            particles[i].px = p[i].x - ((p[i].x - p[i].px) * 0.95); // Approximation for Verlet update using solved positions
            particles[i].py = p[i].y - ((p[i].y - p[i].py) * 0.95);
        }
    }
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let i = 0; i < N; i++) {
        const p = particles[i];
        ctx.beginPath();
        ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
        ctx.fillStyle = '#333';
        ctx.fill();

        if (i > 0) {
            const prevP = particles[i-1];
            ctx.beginPath();
            ctx.moveTo(prevP.x, prevP.y);
            ctx.lineTo(p.x, p.y);
            ctx.strokeStyle = '#aaa';
            ctx.lineWidth = 2;
            ctx.stroke();
        }
    }

    requestAnimationFrame(animate);
}

function animate() {
    updatePhysics();
    draw();
}

init();
animate();
</script></body></html>
```
MODEL Bgemma4:26bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Cdeepseek-r1:14bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Dqwen3-coder:30bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Verlet Rope</title>
<style>body{margin:0;overflow:hidden}canvas{display:block}</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W, H;

function resize() {
    W = canvas.width = window.innerWidth;
    H = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();

const P = 40;
const dt = 1/60;
const GRAVITY = 9.8 / 2000; // Scaled gravity
const K_PASSES = 15;

let p = []; // Current positions
let oldP = []; // Previous positions
let fixedPoints = [];

for (let i = 0; i < P; i++) {
    p[i] = [W * 0.4 + i * (W * 0.6 / P), 50];
    oldP[i] = [...p[i]];
}

const anchor1 = p[0];
const anchor2 = p[P - 1];
fixedPoints.push(anchor1, anchor2); // Anchor the ends at the top

function update() {
    let impulseTime = Math.sin(Date.now() / 3000) * 5;
    
    for (let i = 0; i < P; i++) {
        if (i === 0 || i === P - 1) continue;

        // Verlet integration step: p_new = 2*p_current - p_old + acc*dt^2
        let vx = (p[i][0] - oldP[i][0]) / dt;
        let vy = (p[i][1] - oldP[i][1]) / dt;

        // Apply forces (Gravity + Impulse)
        let ax = impulseTime * 0.5;
        let ay = GRAVITY + Math.sin(Date.now() / 800) * 0.3;

        oldP[i] = [...p[i]];
        p[i][0] += vx * dt + ax * dt * dt;
        p[i][1] += vy * dt + ay * dt * dt;
    }

    // Constraint Solving (Distance Constraints)
    for(let k=0; k<K_PASSES; k++) {
        for (let i = 1; i < P; i++) {
            const p1 = [...p[i - 1]];
            const p2 = [...p[i]];
            const dx = p2[0] - p1[0];
            const dy = p2[1] - p1[1];
            const dist = Math.sqrt(dx*dx + dy*dy);
            
            if (dist === 0) continue;

            const correction = 0.01 * (1 - dist / 50); // Target length ~50
            p[i][0] += dx / dist * correction;
            p[i][1] += dy / dist * correction;
        }
    }

    // Anchor constraints (Locking the ends)
    p[0] = fixedPoints[0];
    p[P - 1] = fixedPoints[1];
}

function draw() {
    ctx.clearRect(0, 0, W, H);
    ctx.beginPath();
    for (let i = 0; i < P; i++) {
        const x = p[i][0];
        const y = p[i][1];

        // Draw rod segment
        if (i > 0) {
            ctx.strokeStyle = '#444';
            ctx.lineWidth = 2;
            ctx.moveTo(p[i-1][0], p[i-1][1]);
            ctx.lineTo(x, y);
            ctx.stroke();
        }

        // Draw point mass
        ctx.fillStyle = '#333';
        ctx.beginPath();
        ctx.arc(x, y, 2, 0, Math.PI * 2);
        ctx.fill();
    }
}

function loop() {
    update();
    draw();
    requestAnimationFrame(loop);
}

loop();
</script>
</body>
</html>
```
MODEL Eqwen3:14bBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Verlet Rope Simulation</title>
<style>body { margin: 0; overflow: hidden; background-color: #111; }</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W, H;

function resizeCanvas() {
    W = window.innerWidth;
    H = window.innerHeight * 0.8;
    canvas.width = W;
    canvas.height = H;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();

const N_MASSES = 40;
const ITERATIONS = 10;
const GRAVITY = 9.8 / 60; // Adjusted for frame rate
const SPRING_STRENGTH = 0.8;
let px = new Array(N_MASSES);
let pv = new Array(N_MASSES);

for (let i = 0; i < N_MASSES; i++) {
    px[i] = [W * 0.2 + (i - N_MASSES / 2) * 4, H * 0.1];
    pv[i] = [...px[i]];
}

function simulate() {
    for (let i = 0; i < N_MASSES; i++) {
        const p = px[i];
        const v = pv[i];
        // Verlet integration prediction step
        p[0] += (p[0] - v[0]) * 0.99 + GRAVITY * 16; // damping and gravity
        p[1] += (p[1] - v[1]) * 0.99 + GRAVITY;

        // Boundary constraints
        if (p[0] < 0 || p[0] > W) p[0] = Math.max(0, Math.min(W, p[0]));
        if (p[1] < 0 || p[1] > H) {
            p[1] = H;
            pv[i][1] *= -0.5; // Bounce dampening
        }
    }

    // Constraint relaxation passes
    for (let k = 0; k < ITERATIONS; k++) {
        // Anchor constraints (Ends of the rope)
        px[0][1] = H * 0.1;
        pv[0][0] = 0; pv[0][1] = 0;

        px[N_MASSES - 1][1] = H * 0.1;
        pv[N_MASSES - 1][0] = 0; pv[N_MASSES - 1][1] = 0;

        // Distance constraints (Springs)
        for (let i = 0; i < N_MASSES - 1; i++) {
            const p1 = px[i];
            const p2 = px[i + 1];
            const dx = p2[0] - p1[0];
            const dy = p2[1] - p1[1];
            const dist = Math.sqrt(dx * dx + dy * dy);
            const targetDist = 4; // Fixed distance

            const correctionX = (dist - targetDist) / dist * dx;
            const correctionY = (dist - targetDist) / dist * dy;

            px[i][0] -= correctionX * 0.5; px[i][1] -= correctionY * 0.5;
            px[i + 1][0] += correctionX * 0.5; px[i + 1][1] += correctionY * 0.5;
        }
    }

    // Update previous positions and velocities for next frame
    for (let i = 0; i < N_MASSES; i++) {
        pv[i] = [...px[i]];
    }
}

function draw() {
    ctx.fillStyle = 'rgba(17, 17, 34, 0.2)';
    ctx.fillRect(0, 0, W, H);
    ctx.strokeStyle = '#aaa';
    ctx.lineWidth = 2;

    for (let i = 0; i < N_MASSES - 1; i++) {
        const p1 = px[i];
        const p2 = px[i + 1];
        ctx.beginPath();
        ctx.moveTo(p1[0], p1[1]);
        ctx.lineTo(p2[0], p2[1]);
        ctx.stroke();
    }

    // Draw masses (optional visualization)
    for (let i = 0; i < N_MASSES; i++) {
        const p = px[i];
        ctx.fillStyle = '#fff';
        ctx.beginPath();
        ctx.arc(p[0], p[1], 2, 0, Math.PI * 2);
        ctx.fill();
    }
}

let lastImpulseTime = 0;
function applyImpulse() {
    const impulseTime = Date.now();
    if (impulseTime - lastImpulseTime > 3000) {
        // Apply a horizontal kick to masses 10 through N_MASSES/2
        for (let i = Math.floor(N_MASSES * 0.2); i < Math.ceil(N_MASSES * 0.8); i++) {
            px[i][0] += 40 + Math.random() * 10; // Small kick
        }
        lastImpulseTime = impulseTime;
    }
}

function animate() {
    applyImpulse();
    simulate();
    draw();
    requestAnimationFrame(animate);
}

animate();
</script>
</body>
</html>
```
MODEL Fqwen3:8bDNF
No usable answer reached the harness (ERR). Counted as a did-not-finish.
MODEL Gllama3.1:8bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
Reveal the models— compare the answers first, then open. Names, blind scores and latencies are hidden until you do.

Who wrote what

Blind labelModelBlind scoreLatencyStatus
MODEL A qwen3.5:9b 25.1 sOK
MODEL B gemma4:26b DNF
MODEL C deepseek-r1:14b DNF
MODEL D qwen3-coder:30b 20.6 sOK
MODEL E qwen3:14b 24.5 sOK
MODEL F qwen3:8b DNF
MODEL G llama3.1:8b DNF
MODEL H mistral-small:24b DNF

Frontier reference — outside the blind field

These models answered the identical prompt outside the blind field. They are deliberately not scored and not part of the blind comparison — they are there so you can see where a frontier model lands on the same task.

Collected differently from the blind field: the local models run through a plain API harness, the frontier references through a context-free agent, one shot, no retries. Response times are therefore not comparable and are not shown. On two English tasks the reference answer came back in German — an artefact of that harness, flagged on the affected rounds.
Claude Opus 5frontier reference
Show code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Verlet Rope</title>
<style>
html,body{margin:0;height:100%;background:#0b0e14;overflow:hidden}
canvas{display:block;width:100vw;height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const c=document.getElementById('c'),x=c.getContext('2d');
const N=40,ITER=12,G=1400,DAMP=0.995;
let W=0,H=0,dpr=1,pts=[],segLen=0,ax=0,ay=0,bx=0,by=0,t=0,nextKick=1.2;
function setup(){
  dpr=Math.min(window.devicePixelRatio||1,2);
  W=c.clientWidth;H=c.clientHeight;
  c.width=W*dpr;c.height=H*dpr;x.setTransform(dpr,0,0,dpr,0,0);
  const gap=Math.min(W*0.55,520);
  ax=W/2-gap/2;ay=H*0.18;bx=W/2+gap/2;by=H*0.18;
  const slack=Math.min(gap*1.55,H*0.72);
  segLen=slack/(N-1);
  pts=[];
  for(let i=0;i<N;i++){
    const u=i/(N-1);
    const sag=Math.sin(u*Math.PI)*slack*0.42;
    const px=ax+(bx-ax)*u,py=ay+(by-ay)*u+sag;
    pts.push({x:px,y:py,px:px,py:py});
  }
  t=0;nextKick=1.0;
}
function constrain(){
  pts[0].x=ax;pts[0].y=ay;
  pts[N-1].x=bx;pts[N-1].y=by;
  for(let k=0;k<ITER;k++){
    for(let i=0;i<N-1;i++){
      const a=pts[i],b=pts[i+1];
      let dx=b.x-a.x,dy=b.y-a.y;
      let d=Math.hypot(dx,dy)||1e-6;
      const diff=(d-segLen)/d*0.5;
      const ox=dx*diff,oy=dy*diff;
      if(i!==0){a.x+=ox;a.y+=oy;}else{a.x=ax;a.y=ay;}
      if(i+1!==N-1){b.x-=ox;b.y-=oy;}else{b.x=bx;b.y=by;}
    }
    pts[0].x=ax;pts[0].y=ay;
    pts[N-1].x=bx;pts[N-1].y=by;
  }
}
function step(dt){
  for(let i=1;i<N-1;i++){
    const p=pts[i];
    const vx=(p.x-p.px)*DAMP,vy=(p.y-p.py)*DAMP;
    p.px=p.x;p.py=p.y;
    p.x+=vx;p.y+=vy+G*dt*dt;
  }
  constrain();
}
function kick(){
  const dir=Math.random()<0.5?-1:1;
  const power=(9+Math.random()*9)*dir;
  for(let i=1;i<N-1;i++){
    const u=i/(N-1);
    const w=Math.sin(u*Math.PI);
    pts[i].px-=power*w;
    pts[i].py-=power*w*0.35*Math.random();
  }
}
function draw(){
  const g=x.createLinearGradient(0,0,0,H);
  g.addColorStop(0,'#0b0e14');g.addColorStop(1,'#141a26');
  x.fillStyle=g;x.fillRect(0,0,W,H);
  x.lineCap='round';x.lineJoin='round';
  x.strokeStyle='rgba(0,0,0,0.45)';x.lineWidth=11;
  x.beginPath();x.moveTo(pts[0].x,pts[0].y+5);
  for(let i=1;i<N;i++)x.lineTo(pts[i].x,pts[i].y+5);
  x.stroke();
  const rg=x.createLinearGradient(ax,ay,bx,by+200);
  rg.addColorStop(0,'#f0b96b');rg.addColorStop(0.5,'#d98d3f');rg.addColorStop(1,'#f0b96b');
  x.strokeStyle=rg;x.lineWidth=7;
  x.beginPath();x.moveTo(pts[0].x,pts[0].y);
  for(let i=1;i<N;i++)x.lineTo(pts[i].x,pts[i].y);
  x.stroke();
  x.strokeStyle='rgba(255,240,210,0.35)';x.lineWidth=2;
  x.beginPath();x.moveTo(pts[0].x,pts[0].y-1.5);
  for(let i=1;i<N;i++)x.lineTo(pts[i].x,pts[i].y-1.5);
  x.stroke();
  x.fillStyle='#e8e2d6';
  for(let i=0;i<N;i++){x.beginPath();x.arc(pts[i].x,pts[i].y,1.8,0,6.2832);x.fill();}
  x.fillStyle='#8fa3bf';
  [[ax,ay],[bx,by]].forEach(p=>{x.beginPath();x.arc(p[0],p[1],7,0,6.2832);x.fill();});
}
let last=performance.now();
function loop(now){
  let dt=(now-last)/1000;last=now;
  if(dt>0.05)dt=0.05;
  t+=dt;
  if(t>=nextKick){kick();nextKick=t+3+Math.random()*2;}
  const sub=2,sdt=dt/sub;
  for(let s=0;s<sub;s++)step(sdt);
  draw();
  requestAnimationFrame(loop);
}
window.addEventListener('resize',setup);
setup();
requestAnimationFrame(loop);
</script>
</body>
</html>