OverviewPhysics Lab › R58
Physics Lab · blind round

R58 · 2D wave propagation with reflection

blind round 4/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 2D wave equation on a grid on a <canvas>. Simulate a scalar wave field on a grid of at least 120x90 cells using the discrete wave equation with damping. Drop a circular pulse at a random position every few seconds. Waves must reflect off the boundaries and interfere with each other visibly. Render the field as colour (positive and negative amplitude in different colours). 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:14bBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>2D Wave Simulation</title>
<style>body { margin: 0; background: #000; }</style>
</head>
<body>
<canvas id="waveCanvas"></canvas>
<script>
const canvas = document.getElementById('waveCanvas');
let ctx = canvas.getContext('2d');

// Grid dimensions (must be at least 120x90)
const W = 120; // Width cells
const H = 90;  // Height cells
const PIXEL_SCALE = 4; // Scale factor for visualization size
canvas.width = W * PIXEL_SCALE;
canvas.height = H * PIXEL_SCALE;

// Simulation arrays (U[y][x])
let uCurrent = Array(H).fill(0).map(() => new Float32Array(W));
let uPrev = Array(H).fill(0).map(() => new Float32Array(W));
let uNext = Array(H).fill(0).map(() => new Float32Array(W));

const damping = 0.99; // Damping coefficient
const dt = 0.1;      // Time step / simulation constant
let pulseTimer = 0;

function initialize() {
    for (let y = 0; y < H; y++) {
        uCurrent[y].fill(0);
        uPrev[y].fill(0);
        uNext[y].fill(0);
    }
}

function calculateWaveStep() {
    // Swap uCurrent and uPrev to reuse space, then calculate into uNext
    for (let y = 1; y < H - 1; y++) {
        for (let x = 1; x < W - 1; x++) {
            const laplacian = uCurrent[y + 1][x] + uCurrent[y - 1][x] + uCurrent[y][x + 1] + uCurrent[y][x - 1] - 4 * uCurrent[y][x];
            uNext[y][x] = 2 * uCurrent[y][x] - uPrev[y][x] + dt * (laplacian);
            uNext[y] *= damping; // Apply damping
        }
    }

    // Boundary conditions: Simple reflection/damping at edges
    for(let i=0; i<W; i++) { uNext[0][i] = 0; }
    for(let i=0; i<W; i++) { uNext[H-1][i] = 0; }
    for(let j=0; j<H; j++) { uNext[j][0] = 0; }
    for(let j=0; j<H; j++) { uNext[j][W-1] = 0; }

    // Shift arrays: uPrev <- uCurrent, uCurrent <- uNext, uNext <- copy of uCurrent (or just prepare for next step)
    [uPrev, uCurrent, uNext] = [uCurrent, uNext, uPrev];
}

function pulse(x, y) {
    const energy = 10;
    if (x >= 1 && x <= W - 2 && y >= 1 && y <= H - 2) {
        uCurrent[y][x] += energy;
    }
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;

    for (let y = 0; y < H; y++) {
        for (let x = 0; x < W; x++) {
            const amplitude = uCurrent[y][x];
            // Calculate color based on sine/cosine mapping of amplitude
            // Clamped between [-1, 1] for visualization stability
            const val = Math.min(Math.max(amplitude * 0.2, -1), 1);

            let r, g, b;
            if (val > 0.1) { // Positive amplitude (e.g., Red/Yellow)
                r = Math.floor((val + 1) / 2 * 255);
                g = Math.floor(Math.min(1, val));
                b = Math.floor(Math.max(0, 1 - val));
            } else if (val < -0.1) { // Negative amplitude (e.g., Blue/Cyan)
                r = Math.floor(Math.max(0, 1 + val) * 127);
                g = Math.floor((1 + val) / 2 * 255);
                b = Math.floor((1 - val) / 2 * 255);
            } else { // Near zero (Dark/Black)
                r = g = b = 0;
            }

            const offset = (y * W + x) * PIXEL_SCALE * 4;
            data[offset] = r;     // R
            data[offset + 1] = g; // G
            data[offset + 2] = b; // B
            data[offset + 3] = 255; // A
        }
    }
    ctx.putImageData(imageData, 0, 0);
}

function animate() {
    calculateWaveStep();
    draw();

    // Pulsing logic: Drop pulse every ~150 frames (approx. 2.5 seconds)
    pulseTimer++;
    if (pulseTimer >= 150) {
        const randX = Math.floor(Math.random() * (W - 4)) + 2;
        const randY = Math.floor(Math.random() * (H - 4)) + 2;
        pulse(randX, randY);
        pulseTimer = 0;
    }

    requestAnimationFrame(animate);
}

initialize();
animate();
</script>
</body>
</html>
```
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Cllama3.1:8bDNF
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
MODEL Ddeepseek-r1:14bBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:black;">
<canvas id=c width=640 height=480></canvas>
<script>
class Wave {
  constructor(rows,cols) {
    this.rows = rows
    this.cols = cols
    this.grid = new Float32Array(rows * cols)
    this.next = new Float32Array(rows * cols)
    this.width = cols;this.height=rows;
  }
  dampen() {for(let i=0;i<this.grid.length;i++)this.grid[i] *= .98}
  randomize() {Math.random().toString(36).split('').forEach((c,i)=>this.grid[i]=+(c>0?c+'.':-c)+1)}
  update() {
    for(let i=0;i<this.rows;i++)
      for(let j=0;j<this.cols;j++) {
        const idx = i * this.cols + j;
        let a, b
        if(i==0||i==this.rows-1||j==0||j==this.cols-1) {
          a = -this.grid[idx];
          b = this.grid[idx]
        } else {
          a = this.grid[(idx - this.cols - 1)] + this.grid[(idx + this.cols + 1)] 
              - this.grid[idx * 2] 
              - this.grid[idx];
          b = this.grid[idx]
        }
        this.next[idx] = a + b * .5
      }
    [this.grid,this.next] = [this.next,this.grid].map(a=>new Float32Array([...a]))
  }
  addPulse(x,y) {
    const w = Math.exp(-(x*x+y*y)/10)
    for(let i=0;i<this.rows;i++)
      for(let j=0;j<this.cols;j++) 
        this.grid[i * this.cols + j] += w * Math.cos((i-x)*5) * Math.cos((j-y)*5)
  }
}
const c = document.getElementById('c'), ctx=c.getContext('2d');
const wave = new Wave(120,90);
setInterval(() => wave.addPulse(Math.random()*wave.width,Math.random()*wave.height),2000);
function render() {
  for(let i=0;i<120;i++) 
    for(let j=0;j<90;j++) {
      const idx = i * 90 + j;
      const val = wave.grid[idx];
      ctx.fillStyle = val>0 ? `hsl(${val*30+180},100%,50%)` : `hsl(${Math.abs(val)*30+180},100%,50%)`;
      ctx.fillRect(j * 4, i * 4, 4, 4);
    }
}
function animate() {
  wave.dampen();wave.update();
  render();
  requestAnimationFrame(animate);
}
wave.randomize();
requestAnimationFrame(animate);
</script>
</body></html>
```
MODEL Eqwen3:8bDNF
No usable answer reached the harness (ERR). Counted as a did-not-finish.
MODEL Fqwen3.5:9bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>2D Wave Equation</title></head><body style="margin: 0; background: #000;"><canvas id="c"></canvas><script>const c = document.getElementById('c');
const ctx = c.getContext('2d');
let W = 120, H = 90;
let CELL_SIZE = Math.min(window.innerWidth, window.innerHeight) / Math.max(W, H);

c.width = W * CELL_SIZE;
c.height = H * CELL_SIZE;

const DAMPING = 0.98;
const DX2 = 1;
const DY2 = 1;
let A = Array(H).fill(0).map(() => Array(W).fill(0)); // Current amplitude
let B = Array(H).fill(0).map(() => Array(W).fill(0)); // Next amplitude
let C = Array(H).fill(0).map(() => Array(W).fill(0)); // Previous amplitude

let pulseTimer = 0;
const PULSE_INTERVAL = 120;

function initialize() {
    for (let i = 0; i < H; i++) {
        for (let j = 0; j < W; j++) {
            A[i][j] = B[i][j] = C[i][j] = 0.0;
        }
    }
}

function injectPulse(x, y, strength) {
    for (let i = -1; i <= 1; i++) {
        for (let j = -1; j <= 1; j++) {
            const nx = Math.max(0, Math.min(W - 1, x + j));
            const ny = Math.max(0, Math.min(H - 1, y + i));
            A[ny][nx] += strength * (1 - Math.hypot(nx - x, ny - y) / Math.hypot(W/2, H/2)) * Math.random() * 0.5;
        }
    }
}

function updateField() {
    for (let i = 1; i < H - 1; i++) {
        for (let j = 1; j < W - 1; j++) {
            const laplacian = A[i + 1][j] + A[i - 1][j] + A[i][j + 1] + A[i][j - 1] - 4 * A[i][j];
            B[i][j] = (2 * A[i][j] + C[i][j] + DAMPING * laplacian) / 2;
        }
    }

    for (let i = 0; i < H; i++) {
        for (let j = 0; j < W; j++) {
            // Simple zero-flux boundary condition implementation by clamping or not updating edges significantly.
            if (i === 0 || i === H - 1 || j === 0 || j === W - 1) {
                B[i][j] = A[i][j]; // Keep values stable near boundaries relative to center calculation
            }
        }
    }

    // Swap buffers: C <- A, A <- B, B <- C (conceptually)
    let temp = C;
    C = A;
    A = B;
    B = temp;
}

function renderField() {
    ctx.clearRect(0, 0, c.width, c.height);
    const imageData = ctx.getImageData(0, 0, c.width, c.height);
    const data = imageData.data;

    for (let i = 0; i < H; i++) {
        for (let j = 0; j < W; j++) {
            let val = A[i][j];
            let r, g, b;

            // Color mapping: Positive amplitude -> Red/Yellow, Negative -> Blue
            r = Math.max(0, val) * 15 + 50;
            g = Math.min(255, Math.abs(val)) * 10 - 100;
            b = Math.max(0, -val) * 15 + 50;

            r = Math.floor(Math.min(255, r));
            g = Math.floor(Math.min(255, g));
            b = Math.floor(Math.min(255, b));

            const index = (i * W + j) * 4;
            data[index] = r;     // Red
            data[index + 1] = g; // Green
            data[index + 2] = b; // Blue
            data[index + 3] = 255; // Alpha
        }
    }
    ctx.putImageData(imageData, 0, 0);
}

function loop() {
    if (pulseTimer >= PULSE_INTERVAL) {
        const px = Math.floor(Math.random() * W);
        const py = Math.floor(Math.random() * H);
        injectPulse(px, py, 50 + Math.random() * 50);
        pulseTimer = 0;
    } else {
        pulseTimer++;
    }

    updateField();
    renderField();
    requestAnimationFrame(loop);
}

initialize();
loop();
</script></body></html>
```
MODEL Gqwen3-coder:30bBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Wave Simulation</title></head>
<body>
<canvas id="waveCanvas"></canvas>
<script>
const canvas = document.getElementById('waveCanvas');
const ctx = canvas.getContext('2d');

let N = 120; // Width cells
let M = 90;  // Height cells
let CELL_SIZE = Math.min(window.innerWidth, window.innerHeight) / N;
canvas.width = N * CELL_SIZE;
canvas.height = M * CELL_SIZE;

const DAMPING = 0.995;
const C = 0.1; // Time step constant (controls wave speed/stability)

let currentField = new Array(N).fill(0).map(() => new Float32Array(M));
let previousField = new Array(N).fill(0).map(() => new Float32Array(M));
let nextField = new Array(N).fill(0).map(() => new Float32Array(M));

function initialize() {
    for (let x = 0; x < N; x++) {
        for (let y = 0; y < M; y++) {
            currentField[x][y] = previousField[x][y] = nextField[x][y] = 0;
        }
    }
    setInterval(pulse, 1500); // Pulse every 1.5 seconds
}

function pulse() {
    const px = Math.floor(Math.random() * N / 4) * 2 + N / 8;
    const py = Math.floor(Math.random() * M / 4) * 2 + M / 8;
    const radius = 10;

    for (let x = Math.max(0, px - radius); x < Math.min(N, px + radius); x++) {
        for (let y = Math.max(0, py - radius); y < Math.min(M, py + radius); y++) {
            const distSq = (x - px) ** 2 + (y - py) ** 2;
            if (distSq < radius * radius && currentField[x][y] < 0.5) {
                currentField[x][y] += 1.0 / Math.sqrt(Math.max(1, distSq));
            }
        }
    }
}

function update() {
    for (let x = 1; x < N - 1; x++) {
        for (let y = 1; y < M - 1; y++) {
            // Laplacian approximation: Sum of neighbors minus center * 4
            let laplacian = (currentField[x + 1][y] + currentField[x - 1][y] + currentField[x][y + 1] + currentField[x][y - 1]) - 4 * currentField[x][y];

            // Wave Equation: u(t+1) = 2u(t) - u(t-1) + C^2 * Laplacian
            nextField[x][y] = (2.0 * currentField[x][y] - previousField[x][y] + laplacian * C);

            // Damping and Boundary Handling (Simple reflection/clamping for edges)
            nextField[x][y] *= DAMPING;
        }
    }
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (let x = 0; x < N; x++) {
        for (let y = 0; y < M; y++) {
            let amp = nextField[x][y];
            let color;

            if (amp > 0) {
                // Positive amplitude: Blue -> Cyan
                const ratio = Math.min(1, amp * 5);
                color = `rgb(${Math.floor(255 - ratio*255)}, ${Math.floor(ratio*255)}, ${Math.floor(0)})`;
            } else {
                // Negative amplitude: Red -> Magenta (using negative value magnitude)
                const ratio = Math.min(1, (-amp) * 5);
                color = `rgb(${Math.floor(ratio*255)}, ${Math.floor(0)}, ${Math.floor(255 - ratio*255)})`;
            }

            ctx.fillStyle = color;
            ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
        }
    }
}

function step() {
    // Current becomes Previous
    for (let x = 0; x < N; x++) {
        for (let y = 0; y < M; y++) {
            previousField[x][y] = currentField[x][y];
        }
    }

    // Next becomes Current
    for (let x = 0; x < N; x++) {
        for (let y = 0; y < M; y++) {
            currentField[x][y] = nextField[x][y];
        }
    }

    // Clear Next for the next calculation step
    nextField.forEach(row => row.fill(0));

    update();
    draw();

    requestAnimationFrame(step);
}

initialize();
step();
</script>
</body>
</html>
```
MODEL Hgemma4:26bDNF
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:14b 23.5 sOK
MODEL B mistral-small:24b DNF
MODEL C llama3.1:8b DNF
MODEL D deepseek-r1:14b 75.8 sOK
MODEL E qwen3:8b DNF
MODEL F qwen3.5:9b 40.6 sOK
MODEL G qwen3-coder:30b 22.6 sOK
MODEL H gemma4:26b 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>2D Wave Field</title>
<style>
html,body{margin:0;height:100%;background:#05070d;overflow:hidden}
body{display:flex;align-items:center;justify-content:center}
canvas{width:100vw;height:100vh;display:block;image-rendering:auto}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const W=240,H=180;
const cv=document.getElementById('c'),ctx=cv.getContext('2d',{alpha:false});
const buf=document.createElement('canvas');buf.width=W;buf.height=H;
const bx=buf.getContext('2d',{alpha:false});
const img=bx.createImageData(W,H),data=img.data;
const N=W*H;
let u0=new Float32Array(N),u1=new Float32Array(N),u2=new Float32Array(N);
const C2=0.36,DAMP=0.9985;
function resize(){cv.width=Math.floor(innerWidth*devicePixelRatio);cv.height=Math.floor(innerHeight*devicePixelRatio);ctx.imageSmoothingEnabled=true;}
addEventListener('resize',resize);resize();
function pulse(){
  const cx=8+Math.random()*(W-16),cy=8+Math.random()*(H-16);
  const r=4+Math.random()*7,amp=(Math.random()<0.5?-1:1)*(0.9+Math.random()*0.8);
  const r2=r*r;
  for(let y=Math.max(1,(cy-r-2)|0);y<Math.min(H-1,cy+r+2);y++){
    for(let x=Math.max(1,(cx-r-2)|0);x<Math.min(W-1,cx+r+2);x++){
      const dx=x-cx,dy=y-cy,d2=dx*dx+dy*dy;
      if(d2<r2*2.2){
        const d=Math.sqrt(d2);
        const g=Math.exp(-d2/(2*r2*0.35))*Math.cos(d/r*2.0);
        u1[y*W+x]+=amp*g;u0[y*W+x]+=amp*g*0.92;
      }
    }
  }
}
function step(){
  for(let y=1;y<H-1;y++){
    const row=y*W;
    for(let x=1;x<W-1;x++){
      const i=row+x;
      const lap=u1[i-1]+u1[i+1]+u1[i-W]+u1[i+W]-4*u1[i];
      u2[i]=(2*u1[i]-u0[i]+C2*lap)*DAMP;
    }
  }
  for(let x=0;x<W;x++){u2[x]=u2[x+W];u2[(H-1)*W+x]=u2[(H-2)*W+x];}
  for(let y=0;y<H;y++){u2[y*W]=u2[y*W+1];u2[y*W+W-1]=u2[y*W+W-2];}
  const t=u0;u0=u1;u1=u2;u2=t;
}
function draw(){
  for(let i=0,p=0;i<N;i++,p+=4){
    let v=u1[i]*2.6;
    if(v>1)v=1;else if(v<-1)v=-1;
    const a=Math.abs(v),s=a*a*0.55+a*0.45;
    let r,g,b;
    if(v>=0){
      r=18+237*s*s;
      g=22+205*s;
      b=46+90*s*s*s;
    }else{
      r=12+70*s*s*s;
      g=26+150*s;
      b=54+201*s;
    }
    const glow=s*s*s*s*70;
    data[p]=r+glow;data[p+1]=g+glow;data[p+2]=b+glow;data[p+3]=255;
  }
  bx.putImageData(img,0,0);
  ctx.drawImage(buf,0,0,cv.width,cv.height);
}
let last=performance.now(),acc=0,nextPulse=0.4;
function frame(now){
  let dt=(now-last)/1000;last=now;
  if(dt>0.1)dt=0.1;
  acc+=dt;
  nextPulse-=dt;
  if(nextPulse<=0){pulse();nextPulse=1.6+Math.random()*2.4;}
  let steps=0;
  while(acc>1/120&&steps<4){step();acc-=1/120;steps++;}
  if(steps===0)step();
  draw();
  requestAnimationFrame(frame);
}
pulse();
requestAnimationFrame(frame);
</script>
</body>
</html>