Overview › Physics Lab › R58
Physics Lab · blind round

R58 · 2D wave propagation with reflection

blind round 9/11 local models answered

Every answer below carries the name of the model that wrote it. Blind refers to the scoring: the review was done without knowing which model produced which answer — the published result names them.

2 of the 11 local models delivered nothing through our test harness in this round — for reasons that lie with our setup, not with the models. Those cards are marked DNF like any other, but they are not evidence about those models — the reason is noted on the card itself.

A frontier reference answered the same task (Claude Opus 5) — outside the field and deliberately unscored, further down the page.

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

Local models (Brain cluster) — identical prompt, shuffled order (the sequence is not a ranking)
qwen3:14bSTATFORMAT
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>
```
mistral-small:24bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2D Wave Equation</title>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #000; }
canvas { border: 1px solid #fff; }
</style>
</head>
<body>
<canvas id="waveCanvas" width="840" height="630"></canvas>
<script>
const canvas = document.getElementById('waveCanvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const cellSize = 7;
const rows = Math.floor(height / cellSize);
const cols = Math.floor(width / cellSize);

let field1 = Array.from({ length: rows }, () => Array(cols).fill(0));
let field2 = Array.from({ length: rows }, () => Array(cols).fill(0));

function updateField() {
    for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
            let newValue = (field1[y][x] * -2 + field2[Math.max(y-1, 0)][x] +
                field2[Math.min(y+1, rows-1)][x] + field2[y][Math.max(x-1, 0)] +
                field2[y][Math.min(x+1, cols-1)])
                * 0.97;
            newValue += Math.random() / 5 - 0.1; // damping
            field1[y][x] = newValue;
        }
    }
}

function drawField() {
    for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
            const value = Math.abs(field2[y][x]);
            ctx.fillStyle = field1[y][x] > 0 ? `rgb(255, ${value * 255}, ${value * 255})` : `rgb(${value * 255}, 0, 255)`;
            ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
        }
    }
}

function update() {
    updateField();
    [field1, field2] = [field2, field1];
    drawField();
    requestAnimationFrame(update);
}

let dropTimer = 0;
function addPulse() {
    if (dropTimer++ % 60 === 0) { // Drop pulse every few seconds
        const x = Math.floor(Math.random() * cols);
        const y = Math.floor(Math.random() * rows);
        field1[y][x] += 5; field2[y][x] += 3;
    }
}

function animate() {
    update();
    addPulse();
    requestAnimationFrame(animate);
}
requestAnimationFrame(update);
requestAnimationFrame(addPulse);
</script>
</body>
</html>
```
llama3.1:8bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
    <title>2D Wave Equation Simulation</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>
<body>
<canvas id="canvas" width="120" height="90"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let grid = new Array(120);
for (let i = 0; i < 120; i++) {
    let row = new Array(90);
    for (let j = 0; j < 90; j++) {
        row[j] = Math.random() * 10 - 5;
    }
    grid[i] = row;
}

function update() {
    for (let i = 1; i < 119; i++) {
        for (let j = 0; j < 90; j++) {
            let v = grid[i][j];
            let u = grid[i-1][j];
            let d = grid[i+1][j];
            let l = grid[i][j-1];
            let r = grid[i][j+1];

            if (i > 0 && i < 119) { v += 0.99 * (u + d) / 2 - 0.01 * Math.abs(v); }
            if (j > 0) { v += 0.99 * l - 0.01 * Math.abs(v); }
            if (j < 89) { v += 0.99 * r - 0.01 * Math.abs(v); }

            grid[i][j] = v;
        }
    }

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    let max = 5;
    for (let i = 0; i < 120; i++) {
        for (let j = 0; j < 90; j++) {
            if (grid[i][j] > max) { ctx.fillStyle = 'red'; }
            else if (grid[i][j] < -max) { ctx.fillStyle = 'blue'; }
            else { ctx.fillStyle = 'black'; }
            ctx.beginPath();
            ctx.arc(2 * i, 2 * j + canvas.height / 2, 1, 0, Math.PI * 2);
            ctx.fill();

            grid[i][j] *= 0.99;
        }
    }

}

let pulseX, pulseY;
function animate() {
    update();
    requestAnimationFrame(animate);
}
pulseX = Math.floor(Math.random() * (120 - 10)) + 5;
pulseY = Math.floor(Math.random() * (90 - 10)) + 45;

const drops = [];
setInterval(() => {
    drops.push([pulseX, pulseY]);
}, Math.floor(Math.random() * 5000) + 3000);

animate();
</script>
</body>
</html>
```
deepseek-r1:14bSTATFORMAT
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>
```
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
qwen3.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>
```
qwen3-coder:30bSTATFORMAT
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>
```
gemma4:26bharness
No answer reached us (THIN) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget (3 min 28 s, zero characters of answer); larger budgets are refused by the gateway Counted as a did-not-finish, but it is not evidence about this model.
command-r:35bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2D Wave Simulation</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="waveCanvas"></canvas>

    <script>
        const canvas = document.getElementById('waveCanvas');
        const ctx = canvas.getContext('2d');

        const gridWidth = 120;
        const gridHeight = 90;
        const cellSize = Math.min(window.innerWidth, window.innerHeight) / Math.max(gridWidth, gridHeight);
        const halfCellSize = cellSize * 0.5;
        canvas.width = gridWidth * cellSize;
        canvas.height = gridHeight * cellSize;

        let amplitude = Array.from({ length: gridWidth }, () => new Float64Array(gridHeight));
        let nextAmplitude = Array.from({ length: gridWidth }, () => new Float64Array(gridHeight));

        function dropCircularPulse() {
            const pulseRadius = 5;
            const pulseAmp = 10;

            for (let x = -pulseRadius; x <= pulseRadius; x++) {
                for (let y = -pulseRadius; y <= pulseRadius; y++) {
                    if (x * x + y * y <= pulseRadius * pulseRadius) {
                        amplitude[gridWidth / 2 + x][gridHeight / 2 + y] += pulseAmp;
                    }
                }
            }
        }

        function applyDiscreteWaveEquation() {
            for (let x = 1; x < gridWidth - 1; x++) {
                for (let y = 1; y < gridHeight - 1; y++) {
                    nextAmplitude[x][y] = amplitude[x][y] + 0.5 * (amplitude[x+1][y] + amplitude[x-1][y] + amplitude[x][y+1] + amplitude[x][y-1] - 4 * amplitude[x][y]);
                }
            }

            for (let x = 1; x < gridWidth - 1; x++) {
                for (let y = 1; y < gridHeight - 1; y++) {
                    const dampedAmp = nextAmplitude[x][y] * 0.98; // Damping factor
                    amplitude[x][y] = dampedAmp;
                }
            }
        }

        function renderGrid() {
            for (let x = 0; x < gridWidth; x++) {
                for (let y = 0; y < gridHeight; y++) {
                    const amp = amplitude[x][y];
                    let color = 'hsl(' + Math.min(amp * 125 + 180, 360) + ',100%,' + (Math.abs(amp) / 4 + 50) + '%)';

                    ctx.fillStyle = color;
                    ctx.fillRect(x * cellSize + halfCellSize, y * cellSize + halfCellSize, cellSize - halfCellSize, cellSize - halfCellSize);
                }
            }
        }

        function animate() {
            applyDiscreteWaveEquation();
            renderGrid();

            requestAnimationFrame(animate);
        }

        dropCircularPulse(); // Initial pulse
        animate();
    </script>
</body>
</html>
```
qwq:32bSTAT
Show code
<think>

Okay, let's tackle this problem. The user wants a self-contained HTML file that simulates a 2D wave equation with damping, displaying a grid on a canvas. The key points are: the grid must be at least 120x90 cells, there should be circular pulses dropped randomly every few seconds, waves reflecting off boundaries, interference visible through color, smooth animation with requestAnimationFrame, and no user input.

First, I need to structure the HTML file. The canvas element will be the center of this. Since the grid is 120x90, maybe I can set the canvas size a bit larger so each cell can be seen. But actually, the canvas can be smaller; the grid is just an array. The rendering will map the grid to the canvas pixels. Wait, but the grid is 120x90, so maybe the canvas width and height can match that. Let me think: if each cell is a pixel on the canvas, then the canvas would be 120x90. That's manageable.

Next, the wave simulation. The discrete wave equation with damping. The standard approach is to have two arrays: current and previous states. The next state is calculated based on neighbors, plus damping. The formula would be something like:

next[y][x] = 2 * current[y][x] - previous[y][x] + (current[y][x] + current[y][x+1] + current[y][x-1] + current[y+1][x] + current[y-1][x])/4 * (damping factor). Wait, maybe better to use a standard finite difference method with damping.

Alternatively, the usual formula for 2D wave equation with damping might be:

new[y][x] = 2 * current[y][x] - previous[y][x] + (current[y+1][x] + current[y-1][x] + current[y][x+1] + current[y][x-1] - 4*current[y][x]) * (dt^2 / (dx^2)) ) - damping * current[y][x];

Hmm, but I need to simplify for discrete steps. Maybe the standard approach is to use:

new[y][x] = (current[y][x] * 2 - previous[y][x] + (current[y][x+1] + current[y][x-1] + current[y+1][x] + current[y-1][x] - 4*current[y][x]) ) / some constants?

Wait, perhaps the damping is implemented as multiplying the current term by a factor less than 1 each step. So the standard approach might be:

new[y][x] = (current[y][x] * 2 - previous[y][x] + ( (current[y+1][x] + current[y-1][x] + current[y][x+1] + current[y][x-1] - 4*current[y][x]) ) ) * dampingFactor;

Wait, maybe I need to look up the discrete wave equation with damping.

Alternatively, perhaps the standard equations for each point are:

next[y][x] = 2 * current[y][x] - previous[y][x] + ( (current[y+1][x] + current[y-1][x] + current[y][x+1] + current[y][x-1] - 4*current[y][x]) ) * (dt^2 / (dx^2)^2 ) ) - damping * current[y][x]

But I might be overcomplicating. Let me think of a simpler approach. Maybe damping is introduced by subtracting some fraction of the current value each step. Like:

new[y][x] = (current[y][x] * 2 - previous[y][x] + (neighbors average - current) ) * dampingFactor;

Hmm. Maybe I can parametrize it such that each step, the current is multiplied by a damping factor less than 1. For example, new = (2*current - previous) + ... and then multiply by damping?

Alternatively, I'll use a standard approach where new[y][x] is based on the average of the neighboring cells minus current, plus some term. Let me think of the standard 2D wave equation simulation:

The standard formula for each cell is:

new[y][x] = 2 * current[y][x] - previous[y][x] + (current[y][x-1] + current[y][x+1] + current[y-1][x] + current[y+1][x] - 4*current[y][x]) * (dt^2/c^2)

But in discrete terms, perhaps dt and dx are such that the step is manageable. Since we're using integers, maybe the 1/(dx)^2 term can be represented by a constant factor. Maybe all terms can be scaled so that the main formula is:

new[y][x] = (prev[y][x] * (dampingFactor) ) + current[y][x] * 2 + ... ?

Alternatively, perhaps the damping is applied by multiplying the current value by a factor less than 1 each step. So after computing the new value, it's multiplied by (1 - damping).

Let me see some code examples. Maybe in similar simulations, they use:

new[y][x] = 2 * current[y][x] - previous[y][x] + (current[y][x+1] + current[y][x-1] + current[y+1][x] + current[y-1][x] - 4*current[y][x]) * (1 + damping) ?

No, perhaps damping is subtracted as a term. Alternatively, the standard discrete wave equation with damping can be written as:

next = (prev + 2*current + (neighbors - 4*current)*k ) * damping

Hmm, maybe I can look for a simplified version. Let me think: the main idea is that the wave propagates, and each step the amplitude reduces a bit (damping). Let's try to approximate with a simple formula.

Let me consider that the next value is a combination of the current and previous steps. The standard wave equation with damping would have terms that reduce the amplitude over time. So perhaps:

next[y][x] = (current[y][x] * 2 - previous[y][x]) + ( (current[y][x+1] + current[y][x-1] + current[y+1][x] + current[y-1][x] - 4 * current[y][x]) ) * (some coefficient) ) - (damping * current[y][x]);

Alternatively, perhaps the damping is implemented as:

next[y][x] = (current[y][x] * 2 - previous[y][x] + ... ) * dampingFactor;

Wait, maybe I can refer to some code examples. For example, in some simulations, the damping is applied as a multiplier after the calculation. Let me think of the following approach:

Each step, the new value is computed as:

new = current + (neighbors_avg - current) * speed / (dx)^2, then multiplied by damping.

Alternatively, perhaps the standard step is:

new[y][x] = (2 * current[y][x] - previous[y][x]) + ( (current[y][x+1] + current[y][x-1] + current[y+1][x] + current[y-1][x] - 4 * current[y][x]) ) * (dt)^2 / (dx)^2 )

Then with damping, maybe subtract a small fraction of current each time
deepseek-r1:32bSTATFORMAT
Show code
```html
<!DOCTYPE html><html><head><style>body{margin:0;overflow:hidden;background:#000}</style></head><body><canvas id="c"></canvas><script>
const canvas = document.getElementById('c'), ctx = canvas.getContext('2d');
const W=120,H=90,scale=3,SIZE_W=W*scale,SIZE_H=H*scale;
let grid=[[0]],next=[[0]],x,y,t=0,damping=.995,timeStep=.1;

function initGrid() {
  grid = Array(W).fill().map(()=>Array(H).fill(0));
  next = Array(W).fill().map(()=>Array(H).fill(0));
}

function addPulse() {
  const cx=Math.random()*W, cy=Math.random()*H,r=6;
  for(let i=0;i<W;i++)for(let j=0;j<H;j++){
    const dx=i-cx, dy=j-cy, dist2=dx*dx+dy*dy;
    if(dist2<r*r)grid[i][j]=(Math.cos(Math.sqrt(dist2)/r*Math.PI)-.5)*.1;
  }
}

function wave() {
  for(let i=0;i<W;i++)for(let j=0;j<H;j++){
    const left=i>0?next[i-1][j]:next[1][j]*damping,
          right=i<W-1?next[i+1][j]:next[W-2][j]*damping,
          up=j>0?next[i][j-1]:next[i][1][j]*damping,
          down=j<H-1?next[i][j+1]:next[i][H-2][j]*damping;
    next[i][j] = (left + right + up + down - grid[i][j]) * damping;
  }
  [grid,next] = [next,grid];
}

function draw() {
  ctx.clearRect(0,0,SIZE_W,SIZE_H);
  for(let i=0;i<W;i++)for(let j=0;j<H;j++){
    const a=(grid[i][j]+1)/2*240;
    ctx.fillStyle=`hsl(${a},80%,60%)`;
    ctx.fillRect(i*scale,j*scale,scale-1,scale-1);
  }
}

function animate() {
  t+=timeStep;
  if(t>3){t=0;addPulse();}
  wave();
  draw();
  requestAnimationFrame(animate);
}

canvas.width = SIZE_W; canvas.height = SIZE_H;
initGrid(); addPulse();
animate();
</script></body></html>
```
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
GPT-5.5 (Codex)frontier referenceOK
Show code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Wave Grid</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#05070b}
canvas{display:block;width:100vw;height:100vh;image-rendering:auto}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const W=160,H=110,N=W*H,c=document.getElementById("c"),x=c.getContext("2d");
let u=new Float32Array(N),p=new Float32Array(N),v=new Float32Array(N),img=x.createImageData(W,H);
let last=0,next=0;
function resize(){c.width=innerWidth;c.height=innerHeight}
addEventListener("resize",resize);resize();
function pulse(){
  const cx=10+Math.random()*(W-20),cy=10+Math.random()*(H-20),r=7+Math.random()*5,a=(Math.random()<.5?-1:1)*(1.6+Math.random()*.8);
  for(let y=Math.max(1,cy-r|0);y<Math.min(H-1,cy+r|0);y++)for(let xx=Math.max(1,cx-r|0);xx<Math.min(W-1,cx+r|0);xx++){
    const dx=xx-cx,dy=y-cy,d=dx*dx+dy*dy;
    if(d<r*r)u[y*W+xx]+=a*(1+Math.cos(Math.PI*Math.sqrt(d)/r))*.5;
  }
}
function step(){
  for(let y=1;y<H-1;y++)for(let xx=1;xx<W-1;xx++){
    const i=y*W+xx,lap=u[i-1]+u[i+1]+u[i-W]+u[i+W]-4*u[i];
    v[i]=(2*u[i]-p[i]+.34*lap)*.995;
  }
  for(let xx=0;xx<W;xx++){v[xx]=v[W+xx];v[(H-1)*W+xx]=v[(H-2)*W+xx]}
  for(let y=0;y<H;y++){v[y*W]=v[y*W+1];v[y*W+W-1]=v[y*W+W-2]}
  [p,u,v]=[u,v,p];
}
function draw(t){
  if(!last){last=t;next=t+600;pulse()}
  let n=Math.min(5,Math.max(1,((t-last)/16)|0));last=t;
  while(n--)step();
  if(t>next){pulse();next=t+2400+Math.random()*1800}
  const d=img.data;
  for(let i=0,j=0;i<N;i++,j+=4){
    let a=Math.max(-1,Math.min(1,u[i]*1.2)),m=Math.abs(a),q=255*m;
    d[j]=a>0?q:20+30*m;d[j+1]=30+130*(1-m);d[j+2]=a<0?q:35+80*m;d[j+3]=255;
  }
  x.putImageData(img,0,0);
  x.globalCompositeOperation="screen";x.filter="blur(5px)";x.drawImage(c,0,0,W,H,0,0,c.width,c.height);x.filter="none";x.globalCompositeOperation="source-over";
  requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
</script>
</body>
</html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2D Wave</title>
<style>
html,body{margin:0;padding:0;background:#000;height:100%;overflow:hidden}
canvas{display:block;width:100vw;height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const W=140,H=100;
const canvas=document.getElementById('c');
const ctx=canvas.getContext('2d');
function resize(){canvas.width=innerWidth;canvas.height=innerHeight;}
resize();
addEventListener('resize',resize);

const grid=document.createElement('canvas');
grid.width=W;grid.height=H;
const gctx=grid.getContext('2d');
const img=gctx.createImageData(W,H);

const N=W*H;
let cur=new Float32Array(N);
let prev=new Float32Array(N);
let nxt=new Float32Array(N);
const c2=0.2;
const damp=0.996;

function idx(x,y){return y*W+x;}

function addPulse(){
  const cx=2+Math.floor(Math.random()*(W-4));
  const cy=2+Math.floor(Math.random()*(H-4));
  const r=6+Math.random()*5;
  const amp=1.3;
  const x0=Math.max(1,Math.floor(cx-r));
  const x1=Math.min(W-2,Math.ceil(cx+r));
  const y0=Math.max(1,Math.floor(cy-r));
  const y1=Math.min(H-2,Math.ceil(cy+r));
  for(let y=y0;y<=y1;y++){
    for(let x=x0;x<=x1;x++){
      const d=Math.hypot(x-cx,y-cy);
      if(d<r){
        cur[idx(x,y)]+=amp*Math.cos((d/r)*(Math.PI/2));
      }
    }
  }
}

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=cur[i-1]+cur[i+1]+cur[i-W]+cur[i+W]-4*cur[i];
      nxt[i]=(2*cur[i]-prev[i]+c2*lap)*damp;
    }
  }
  const tmp=prev;
  prev=cur;
  cur=nxt;
  nxt=tmp;
}

function render(){
  const data=img.data;
  for(let i=0;i<N;i++){
    let v=cur[i];
    if(v>1)v=1;
    if(v<-1)v=-1;
    const p=i*4;
    if(v>=0){
      data[p]=30+v*220;
      data[p+1]=40+v*130;
      data[p+2]=50+v*20;
    }else{
      const a=-v;
      data[p]=20;
      data[p+1]=40+a*110;
      data[p+2]=60+a*195;
    }
    data[p+3]=255;
  }
  gctx.putImageData(img,0,0);
  ctx.imageSmoothingEnabled=false;
  ctx.drawImage(grid,0,0,W,H,0,0,canvas.width,canvas.height);
}

let lastPulse=0;
let nextDelay=1500;
addPulse();

function loop(t){
  if(t-lastPulse>nextDelay){
    addPulse();
    lastPulse=t;
    nextDelay=2200+Math.random()*2600;
  }
  step();
  step();
  render();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>
Claude Fable 5frontier referenceOK
Show code
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>2D Wave Equation</title>
<style>html,body{margin:0;background:#000;overflow:hidden}canvas{display:block;margin:0 auto}</style></head>
<body><canvas id="c" width="960" height="720"></canvas>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
const GW=160,GH=120,SC=6;
const off=document.createElement('canvas');off.width=GW;off.height=GH;
const octx=off.getContext('2d');
const img=octx.createImageData(GW,GH);
let u=new Float32Array(GW*GH),up=new Float32Array(GW*GH),un=new Float32Array(GW*GH);
const C2=0.28,DAMP=0.9965;
let frame=0;
function pulse(){
  const cx=8+Math.floor(Math.random()*(GW-16));
  const cy=8+Math.floor(Math.random()*(GH-16));
  const R=6,amp=Math.random()<0.5?1:-1;
  for(let y=cy-R;y<=cy+R;y++)for(let x=cx-R;x<=cx+R;x++){
    const d2=(x-cx)*(x-cx)+(y-cy)*(y-cy);
    if(d2<=R*R)u[y*GW+x]+=amp*3*Math.exp(-d2/8);
  }
}
function step(){
  for(let y=1;y<GH-1;y++){
    const r=y*GW;
    for(let x=1;x<GW-1;x++){
      const i=r+x;
      const lap=u[i-1]+u[i+1]+u[i-GW]+u[i+GW]-4*u[i];
      un[i]=(2*u[i]-up[i]+C2*lap)*DAMP;
    }
  }
  for(let x=0;x<GW;x++){un[x]=0;un[(GH-1)*GW+x]=0;}
  for(let y=0;y<GH;y++){un[y*GW]=0;un[y*GW+GW-1]=0;}
  const t=up;up=u;u=un;un=t;
}
function render(){
  const d=img.data;
  for(let i=0;i<GW*GH;i++){
    let v=u[i];if(v>1)v=1;if(v<-1)v=-1;
    const j=i*4;
    if(v>=0){
      d[j]=30+225*v;d[j+1]=20+140*v;d[j+2]=25+40*v;
    }else{
      v=-v;d[j]=15+20*v;d[j+1]=25+150*v;d[j+2]=45+210*v;
    }
    d[j+3]=255;
  }
  octx.putImageData(img,0,0);
  ctx.imageSmoothingEnabled=true;
  ctx.drawImage(off,0,0,GW*SC,GH*SC);
  ctx.fillStyle="rgba(255,255,255,0.8)";ctx.font="14px monospace";
  ctx.fillText("2D wave equation "+GW+"x"+GH+" · orange = +amp, blue = -amp · random pulse every 2.5s",14,22);
}
function loop(){
  frame++;
  if(frame===1||frame%150===0)pulse();
  step();step();
  render();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body></html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2D Wave Equation</title>
<style>
  body { margin: 0; padding: 10px; font-family: monospace; background: #000; color: #0f0; }
  canvas { display: block; margin: 10px auto; background: #000; border: 1px solid #0f0; }
</style>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const w = canvas.width, h = canvas.height;

const nx = 120, ny = 90;
const dx = w / nx, dy = h / ny;
const dt = 0.1, c = 1, damp = 0.98;

let u = Array(nx * ny).fill(0);
let u_old = Array(nx * ny).fill(0);
let u_new = Array(nx * ny).fill(0);
let time = 0;

function idx(x, y) {
  return Math.max(0, Math.min(nx - 1, x)) * ny + Math.max(0, Math.min(ny - 1, y));
}

function simulate() {
  for (let x = 1; x < nx - 1; x++) {
    for (let y = 1; y < ny - 1; y++) {
      const i = x * ny + y;
      const uxx = (u[idx(x + 1, y)] - 2 * u[i] + u[idx(x - 1, y)]);
      const uyy = (u[idx(x, y + 1)] - 2 * u[i] + u[idx(x, y - 1)]);
      const lapl = (uxx + uyy) / (dx * dx);
      u_new[i] = (2 * u[i] - u_old[i] + c * c * dt * dt * lapl) * damp;
    }
  }

  for (let i = 0; i < nx * ny; i++) {
    u_old[i] = u[i];
    u[i] = u_new[i];
  }

  if (time % 30 === 0) {
    const px = Math.floor(Math.random() * (nx - 20) + 10);
    const py = Math.floor(Math.random() * (ny - 20) + 10);
    for (let x = px - 5; x < px + 5; x++) {
      for (let y = py - 5; y < py + 5; y++) {
        const r2 = (x - px) * (x - px) + (y - py) * (y - py);
        if (r2 < 25) u[x * ny + y] = 2 * Math.exp(-r2 / 10);
      }
    }
  }

  ctx.fillStyle = '#000';
  ctx.fillRect(0, 0, w, h);

  const imgData = ctx.createImageData(w, h);
  const data = imgData.data;

  for (let x = 0; x < nx; x++) {
    for (let y = 0; y < ny; y++) {
      const val = u[x * ny + y];
      let r = 0, g = 0, b = 0;
      if (val > 0) {
        g = Math.min(255, val * 50);
        r = Math.min(255, val * 30);
      } else {
        b = Math.min(255, -val * 50);
        r = Math.min(255, -val * 30);
      }
      const px = Math.floor(x * dx), py = Math.floor(y * dy);
      for (let i = 0; i < dx; i++) {
        for (let j = 0; j < dy; j++) {
          const idx = ((py + j) * w + (px + i)) * 4;
          data[idx] = r; data[idx + 1] = g; data[idx + 2] = b; data[idx + 3] = 255;
        }
      }
    }
  }

  ctx.putImageData(imgData, 0, 0);
  time++;
  requestAnimationFrame(simulate);
}

simulate();
</script>
</body>
</html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:14b —23.5 sOK
mistral-small:24b —97.2 sOK
llama3.1:8b —96.7 sOK
deepseek-r1:14b —75.8 sOK
qwen3:8b ——harness
qwen3.5:9b —40.6 sOK
qwen3-coder:30b —22.6 sOK
gemma4:26b ——harness
command-r:35b —129.6 sOK
qwq:32b —131.1 sOK
deepseek-r1:32b —131.2 sOK

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>