OverviewCode › R173
Code · blind round

R173 · Metaball lava lamp with heating and cooling blobs

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.

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 7000 characters in total — this is a budget on characters, not on lines, so removing line breaks does not help) that renders a metaball lava lamp on a <canvas>: keep 5-8 soft blobs inside a tall lamp shape, each with position, radius, velocity and temperature. Blobs near the bottom heat up and rise, blobs near the top cool down and sink, so they cycle slowly like wax in a lamp. Render optical merging by summing each blob's field contribution on a coarse grid and filling cells above a threshold, so nearby blobs fuse into one smooth-looking mass; use a warm color gradient based on field strength or height. Show a counter of blobs. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running (restart with a fresh random instance when finished). 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: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:9bBLANK
Show code
<!DOCTYPE html>
<html><head><title>Metaball Lava Lamp</title></head><body style="margin:0;overflow:hidden"><canvas id="c"></canvas><script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');

let W, H;

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

const BLOB_COUNT = 6;
let blobs = [];

class Blob {
    constructor(x, y, r) {
        this.pos = { x: x, y: y };
        this.radius = r;
        this.vel = { x: (Math.random() - 0.5) * 0.1, y: Math.random() * 0.3 + 0.2 }; // Initial slow random drift
        this.temp = Math.random(); // State variable for heating/cooling
        this.maxR = r;
    }

    update(dt) {
        // Temperature-based movement cycle (Simulated buoyancy)
        const gravityFactor = 0.0005;
        const lampCenterY = H * 0.8; // Assume the "lamp" is centered vertically
        let liftForce = 0;

        // Calculate density/temperature gradient effect
        if (this.pos.y > lampCenterY + 50) {
            // Near bottom: Heat up, rise faster
            liftForce = 0.001 * Math.sin((Date.now() / 3000) + this.pos.x/W);
        } else if (this.pos.y < lampCenterY - 50) {
            // Near top: Cool down, sink slower
            liftForce = -0.0008 * Math.cos((Date.now() / 3000));
        }

        // Apply forces and damping
        this.vel.y += (gravityFactor + liftForce) * dt;
        this.pos.x += this.vel.x * dt * 2;
        this.pos.y += this.vel.y * dt * 2;

        // Keep blobs within bounds
        if (this.pos.x < 0 || this.pos.x > W) {
            this.vel.x *= -1;
        }
        if (this.pos.y < 0 || this.pos.y > H) {
            this.vel.y *= -1;
        }

        // Dampening and slow drift
        this.vel.x *= 0.98;
        this.vel.y *= 0.98;
    }
}

function initializeBlobs() {
    blobs = [];
    for (let i = 0; i < BLOB_COUNT; i++) {
        const x = W * (i / BLOB_COUNT) + Math.random() * W * 0.5;
        const y = H * (0.2 + i * 0.1);
        const r = 30 + Math.random() * 20;
        blobs.push(new Blob(x, y, r));
    }
}

// Metaball parameters
const GRID_SIZE = 15; // Coarse resolution for performance
const ITERATIONS = (W / GRID_SIZE).toFixed(Math.max(1, Math.ceil(W/GRID_SIZE*0.8))) * 0.9;

function drawMetaballs() {
    // Reset canvas and create field grid
    ctx.clearRect(0, 0, W, H);
    const grid = [];
    for (let i = 0; i < Math.ceil(W / GRID_SIZE); i++) {
        grid[i] = new Array(Math.ceil(H / GRID_SIZE)).fill(0);
    }

    // Calculate field contribution on the grid
    for (const blob of blobs) {
        const rSq = blob.radius * blob.radius;
        for (let gx = 0; gx < Math.ceil(W / GRID_SIZE); gx++) {
            for (let gy = 0; gy < Math.ceil(H / GRID_SIZE); gy++) {
                const px = gx * GRID_SIZE + GRID_SIZE / 2;
                const py = gy * GRID_SIZE + GRID_SIZE / 2;

                const dx = px - blob.pos.x;
                const dy = py - blob.pos.y;
                const distSq = dx * dx + dy * dy;
                
                // Metaball influence function (1 / (d^2 + epsilon))
                let fieldStrength = 1.0 / (distSq + rSq * 0.5); 
                
                grid[gx][gy] += fieldStrength;
            }
        }
    }

    // Draw the merged shape and color it based on strength/height
    for (let gx = 0; gx < Math.ceil(W / GRID_SIZE); gx++) {
        for (let gy = 0; gy < Math.ceil(H / GRID_SIZE); gy++) {
            const strength = grid[gx][gy];
            if (strength > 0.1) { // Threshold for visibility
                // Color calculation: strong field -> brighter/warmer color
                const alpha = Math.min(1, strength * 0.5);
                let r = Math.floor(255 * (0.3 + 0.7 * strength));
                let g = Math.floor(100 + 150 * strength);
                let b = Math.floor(Math.max(0, 50 - 50 * strength));

                ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
                ctx.fillRect(gx * GRID_SIZE, gy * GRID_SIZE, GRID_SIZE, GRID_SIZE);
            }
        }
    }
}

let lastTime = 0;
function animate(time) {
    const dt = (time - lastTime) / 16.67; // Normalize time step relative to 60fps
    lastTime = time;

    for (const blob of blobs) {
        blob.update(dt);
    }

    drawMetaballs();

    requestAnimationFrame(animate);
}

initializeBlobs();
animate(0);

</script></body></html>
deepseek-r1:14bBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="margin:0;overflow:hidden;background:black;">
<canvas id=c width=300 height=600></canvas>
<script>
class Blob{
  constructor(x,y,r,v,t){
    this.x=x||Math.random()*299;
    this.y=y||Math.random()*(500-100)+100;
    this.r=r||Math.random()+3;
    this.v=v||{dx:0,dy:0};
    this.t=t||Math.random();
  }
  update(){
    if(this.y>200) this.v.dx+=this.t*0.05;
    else this.v.dy-=0.01;
    this.x +=this.v.dx; this.y +=this.v.dy;
    if(this.x<0||this.x>300||this.y<0||this.y>600){
      let r=2-(Math.abs(this.x-300/2)/300)*2;
      this.v.dx *=.95; this.v.dy *=.95;
      this.x +=this.v.dx*2;this.y +=this.v.dy*2;
    }
  }
}
let c=document.getElementById('c'), ctx=c.getContext('2d');
let blobs=[], grid=Array(40).fill().map(()=>Array(20).fill(0));
function init(){blobs.length=5+Math.random()*3|0; for(let i=blobs.length-1;i>=0;i--)blobs[i]=new Blob();}
function updateBlobs(){
  for(let b of blobs)b.update();
}
function drawGrid(){
  grid.forEach((row,yi)=>{
    row.forEach((v,xj)=>{
      if(v>0.3){
        let h=c.height*(yj/20),x=c.width*(xi/40);
        ctx.beginPath();
        ctx.arc(x,h,Math.sqrt(v)*3,0,Math.PI*2);ctx.fill();
      }
    });
  });
}
function render(){
  ctx.fillStyle='black'; ctx.fillRect(0,0,c.width,c.height);
  ctx.fillStyle='#fff';
  for(let b of blobs){
    let {x,y,r}=b;
    grid.forEach((row,yi)=>{
      for(let xi=0;xi<40;xi++){
        let xg=xi*8+ (Math.random()*.5 - .25)*r*2;
        let yg=yi*15 + (Math.random()*.5 -.25)*r*2;
        let d=(x-xg)*(x-xg)+(y-yg)*(y-yg);
        if(d<r*r &&d<grid[xi] ? grid[xi][yi] : 0) grid[xi][yi]=Math.max(v,d>r*r?0:.6/(d));
      }
    });
  }
  drawGrid();
}
let last=Date.now();
function animate(){
  requestAnimationFrame(animate);
  updateBlobs(); render();
  if(Date.now()-last>1e4)init();
}
init(); animate();
document.body.style.backgroundColor = '#00ff88';
</script><div style="position:absolute;left:20px;top:570px;color:white;font-family:sans-serif;">Blobs: <span id=count>${blobs.length}</span></div>
```
gemma4:26bharness
No answer reached us (ERR) — 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.
qwen3:14bBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Metaball Lava Lamp</title>
    <style>body { background: #12121c; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }</style>
</head>
<body>
    <canvas id="lavaCanvas"></canvas>

    <script>
        const canvas = document.getElementById('lavaCanvas');
        const ctx = canvas.getContext('2d');
        let blobs = [];
        let animationFrameId;

        // Constants
        const N_BLOBS = 6;
        const LAMP_W = 150;
        const LAMP_H = 450;
        const GRID_SIZE = 8; // Coarse grid resolution
        const MAX_FIELD = 1.0;

        canvas.width = LAMP_W * 1.2;
        canvas.height = LAMP_H + 50;

        function initializeBlobs() {
            blobs = [];
            for (let i = 0; i < N_BLOBS; i++) {
                blobs.push({
                    x: Math.random() * (LAMP_W - 30) + LAMP_W / 2 - 15,
                    y: Math.random() * (LAMP_H - 60) + 30,
                    r: Math.random() * 15 + 10,
                    vx: (Math.random() - 0.5) * 0.2,
                    vy: (Math.random() - 0.5) * 0.2,
                    temp: Math.random(), // 0 (cold) to 1 (hot)
                    damping: 0.98
                });
            }
        }

        function updatePhysics() {
            const G_EFFECT = 0.005;
            const TEMP_CHANGE_RATE = 0.002;

            for (let i = 0; i < blobs.length; i++) {
                const b = blobs[i];

                // Temperature cycle based on height (y)
                // Bottom (low y): Hotter, faster rise attempt
                // Top (high y): Cooler, slower fall attempt
                b.temp += TEMP_CHANGE_RATE * ((Math.min(1, 1 - b.y / LAMP_H)) + (Math.sin(Date.now() / 5000) * 0.2));
                b.temp = Math.max(0.1, Math.min(1.0, b.temp));

                // Apply simulated temperature effects
                let riseForce = b.temp * G_EFFECT * 1.5; // Stronger lift if hot
                let fallForce = (1 - b.temp) * G_EFFECT * 0.5; // Weaker pull if cold

                b.vy += (riseForce - fallForce);

                // Apply drag and damping
                b.vx *= b.damping;
                b.vy *= b.damping;

                // Update position
                b.x += b.vx;
                b.y += b.vy;

                // Boundary checks (Keep within lamp walls)
                const padding = 20;
                if (b.x < LAMP_W * 0.4 || b.x > LAMP_W * 1.6 - padding) {
                    b.vx *= -0.5;
                    b.x = Math.max(LAMP_W * 0.4 + padding, Math.min(LAMP_W * 1.6 - padding, b.x));
                }

                // Vertical bounds (Keep in lamp)
                if (b.y < 0) { b.y = 0; b.vy = 0; }
                if (b.y > LAMP_H - 50) { b.y = LAMP_H - 50; b.vy = 0; }

                // Reset velocity if hitting boundaries too hard
                if (Math.abs(b.vx) < 0.1 && Math.abs(b.vy) < 0.1 && i % 3 === 0) {
                    b.vx += (Math.random() - 0.5) * 0.2;
                    b.vy += (Math.random() - 0.5) * 0.2;
                }
            }
        }

        function drawMetaballs() {
            const grid = [];
            for(let i=0; i < LAMP_W / GRID_SIZE; i++) {
                grid[i] = [];
                for(let j=0; j < (LAMP_H + 50) / GRID_SIZE; j++) {
                    grid[i][j] = 0; // Field strength accumulator
                }
            }

            // Calculate field contribution for each cell
            const gridW = LAMP_W / GRID_SIZE;
            const gridH = (LAMP_H + 50) / GRID_SIZE;

            for (let i = 0; i < blobs.length; i++) {
                const b = blobs[i];
                const rSq = b.r * b.r;

                for (let gx = 0; gx < gridW; gx++) {
                    for (let gy = 0; gy < gridH; gy++) {
                        const cx = gx * GRID_SIZE + GRID_SIZE / 2;
                        const cy = gy * GRID_SIZE + GRID_SIZE / 2;

                        // Distance calculation
                        const dx = cx - b.x;
                        const dy = cy - b.y;
                        const distSq = dx * dx + dy * dy;

                        if (distSq < rSq) {
                            // Metaball field: Contribution inversely proportional to distance
                            let field = 1.0 / Math.max(Math.pow(rSq, 2), distSq + 1);
                            grid[gx][gy] += field * b.r;
                        }
                    }
                }
            }

            // Drawing Phase
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            for (let gx = 0; gx < gridW; gx++) {
                for (let gy = 0; gy < gridH; gy++) {
                    const fieldStrength = grid[gx][gy];

                    if (fieldStrength > 0.1) {
                        const x = gx * GRID_SIZE;
                        const y = gy * GRID_SIZE;

                        // Color mapping: Warm gradient based on strength and height
                        let intensity = Math.min(1, fieldStrength / MAX_FIELD);
                        let colorFactor = Math.max(0.2, Math.pow(intensity, 0.5)); // Softening the transition

                        // Temperature/Height influence (Redder when hotter/higher)
                        let y_norm = y / canvas.height;
                        let tempInfluence = (Math.sin(y_norm * Math.PI) + 1) / 2.5;

                        const r = Math.floor(20 + colorFactor * 200 * (0.8 + tempInfluence * 0.2));
                        const g = Math.floor(40 + colorFactor * 180 * (0.6 - tempInfluence * 0.2));
                        const b = Math.floor(50);

                        ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
                        ctx.fillRect(x, y, GRID_SIZE, GRID_SIZE);
                    }
                }
            }
        }

        function loop() {
            updatePhysics();
            drawMetaballs();
            animationFrameId = requestAnimationFrame(loop);
        }

        // Initialization sequence
        initializeBlobs();
        loop();

    </script>
</body>
</html>
```
qwen3-coder:30bBLANKFORMAT
Show code
```html
<!DOCTYPE html><html><head><title>Metaball Lava Lamp</title></head><body style="background: #111; margin: 0;"><canvas id="c" width="400" height="600" style="display: block;"></canvas><script>const c = document.getElementById('c');
const ctx = c.getContext('2d');

let blobs = [];
const NUM_BLOBS = 7;
const MAX_RADIUS = 30;
const GRID_SIZE = 15; // Coarse grid for metaballs

function init() {
    blobs = [];
    for (let i = 0; i < NUM_BLOBS; i++) {
        blobs.push({
            x: Math.random() * c.width,
            y: Math.random() * c.height / 2 + 50,
            r: MAX_RADIUS * (0.8 + Math.random() * 0.4),
            vx: (Math.random() - 0.5) * 0.1,
            vy: 0,
            temp: Math.random(), // 0 (cool/sink) to 1 (hot/rise)
        });
    }
    gameLoop();
}

function updateBlob(b) {
    // Temperature logic (Heating at bottom, cooling at top)
    let yRatio = b.y / c.height;
    if (yRatio < 0.3) { // Near the bottom: Heat up
        b.temp = Math.min(1, b.temp + 0.005);
    } else if (yRatio > 0.7) { // Near the top: Cool down
        b.temp = Math.max(0, b.temp - 0.003);
    }

    // Buoyancy/Movement calculation
    let speedFactor = b.temp * 1.5 + 0.5; // Faster if hotter (higher temp)
    let gravityForce = 0.01;
    let drag = 0.98;

    b.vy += (gravityForce - speedFactor * 0.2); // Buoyant force counteracting gravity
    b.vx *= drag;
    b.vy *= drag;

    // Update position
    b.x += b.vx;
    b.y += b.vy;

    // Boundary checks and wrapping/clamping
    if (b.x < 0 || b.x > c.width) {
        b.x = Math.max(0, Math.min(c.width, b.x));
        b.vx *= -0.5;
    }
    if (b.y < 0 || b.y > c.height) {
        b.y = Math.max(0, Math.min(c.height, b.y));
        b.vy *= -0.5;
    }
}

function render() {
    ctx.clearRect(0, 0, c.width, c.height);
    const imageData = ctx.createImageData(c.width, c.height);
    let data = imageData.data;
    const threshold = 15; // Sensitivity of the metaball effect

    // Calculate field strength (Metaballs)
    for (let y = 0; y < c.height; y += GRID_SIZE) {
        for (let x = 0; x < c.width; x += GRID_SIZE) {
            
            let totalField = 0;
            for (const b of blobs) {
                const dx = x - b.x;
                const dy = y - b.y;
                const distSq = dx * dx + dy * dy;
                
                // Metaball field contribution: R^2 / (d^2 + epsilon^2)
                const influence = (b.r**2) / Math.max(1, distSq); 
                totalField += influence;
            }

            if (totalField > threshold) {
                let i = ((y / GRID_SIZE) * (c.width / GRID_SIZE) + x / GRID_SIZE) * 4;
                // Calculate color based on field strength and height (warm gradient)
                let intensity = Math.min(1, totalField / 100);
                let r = Math.floor(255 * intensity * 1.5); // Red increases with heat/field
                let g = Math.floor(255 * intensity * 0.8);
                let b = Math.floor(255 * (1 - intensity) * 0.5);

                // Simple clamping and assignment for the coarse grid cell
                data[i] = r;
                data[i + 1] = g;
                data[i + 2] = b;
                data[i + 3] = 255; // Alpha
            } else {
                 // Draw background if no field contribution (optional, but keeps the structure clean)
                let i = ((y / GRID_SIZE) * (c.width / GRID_SIZE) + x / GRID_SIZE) * 4;
                data[i] = data[i+1] = data[i+2] = 0; // Black background for the cell
                data[i+3] = 0;
            }
        }
    }

    // Due to character limit, we use a simple method: draw image data by scaling up. 
    // We will fill the whole canvas with this coarse grid data.
    ctx.putImageData(imageData, 0, 0);
}


function gameLoop() {
    // Update physics for all blobs
    for (const b of blobs) updateBlob(b);

    // Render metaballs and lava effect
    render();

    requestAnimationFrame(gameLoop);
}

init();
</script></body></html>
```
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><title>Metaball Lava Lamp</title><style>*{margin:0;padding:0;box-sizing:border-box;}body{display:flex;flex-direction:column;align-items:center;font-family:Arial,Helvetica,sans-serif;background:#000;color:#fff;}#canvas{border:1px solid #fff;margin-top:20px;}</style>
</head><body>
<div>Blobs: <span id="blobCount">0</span></div><canvas id="canvas" width="300" height="600"></canvas>
<script>
const canvas=document.getElementById('canvas');
const ctx=canvas.getContext('2d');
const gridSize=10;
const blobCount=7;
let blobs=[];
let grid=[];
let colorGradient;

function init(){
    for(let i=0;i<blobCount;i++){
        blobs.push({
            x:Math.random()*canvas.width,
            y:Math.random()*canvas.height,
            radius:30+Math.random()*50,
            velocityY:(Math.random()-0.5)*2,
            temp:1
        });
    }
    grid=new Array(canvas.width/gridSize).fill().map(()=>new Array(canvas.height/gridSize).fill(0));
}

function update(){
    blobs.forEach(blob=>{
        blob.temp+=0.01*(Math.sin((blob.y/canvas.height)*Math.PI)-0.5);
        if(blob.temp<-2)blob.temp=-2;
        if(blob.temp>2)blob.temp=2;
        blob.velocityY=blob.temp*3;
        blob.y+=blob.velocityY;
        if(blob.y>canvas.height){blob.y=0;blob.x=(Math.random()*canvas.width);}
    });
}

function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    grid.fill().map(()=>new Array(canvas.height/gridSize).fill(0));
    blobs.forEach(blob=>{
        for(let x=-blob.radius;x<blob.radius*2;x++){
            for(let y=-blob.radius;y<blob.radius*2;y++){
                let dx=x-blob.x;
                let dy=y-blob.y;
                if(dx*dx+dy*dy<=blob.radius*blob.radius){
                    let gx=Math.floor((blob.x+x)/gridSize);
                    let gy=Math.floor((blob.y+y)/gridSize);
                    grid[gx][gy]+=(blob.radius-Math.sqrt(dx*dx+dy*dy));
                }
            }
        }
    });

    for(let x=0;x<canvas.width/gridSize;x++){
        for(let y=0;y<canvas.height/gridSize;y++){
            let fieldStrength = grid[x][y];
            if(fieldStrength>0){
                ctx.fillStyle=`rgb(${255-fieldStrength*10},${150+fieldStrength*30},${50+fieldStrength*60})`;
                ctx.fillRect(x*gridSize,y*gridSize,gridSize,gridSize);
            }
        }
    }
}

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

init();animate();

document.getElementById('blobCount').innerText=blobs.length;
</script>
</body></html>
```
llama3.1:8bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Metaball Lava Lamp</title>
<style>
body {
  margin: 0;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
  const canvas = document.createElement('canvas');
  document.body.appendChild(canvas);
  const ctx = canvas.getContext('2d');
  canvas.width = 512;
  canvas.height = 768;

  let blobs = [];
  for (let i = 0; i < 8; i++) {
    const x = Math.random() * canvas.width;
    const y = Math.random() * canvas.height;
    const r = 10 + Math.random() * 20;
    const v = [Math.random(), Math.random()];
    const t = 255 + Math.random() * 130;
    blobs.push({x, y, r, vx: v[0], vy: v[1], t});
  }

  function draw() {
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const grid = Array(32).fill().map(() => Array(512).fill());
    for (const blob of blobs) {
      const [x, y] = [Math.floor(blob.x / 16), Math.floor((canvas.height - blob.y) / 32)];
      let f = 0;
      for (let i = -blob.r; i <= blob.r; i++) {
        for (let j = -blob.r; j <= blob.r; j++) {
          const gX = x + i, gY = y + j;
          if (gX >= grid.length || gX < 0) continue;
          if (gY >= grid[0].length || gY < 0) continue;
          let d = Math.hypot(i * 16, j * 32 - blob.y);
          if (d > blob.r) { f += 1; continue; }
          const t = (blob.t + (i * 16)**2 / blob.r**4) / 255;
          grid[gX][gY] = Math.max(grid[gX][gY], t);
        }
      }
    }

    let count = 0;
    for (let i = 0; i < blobs.length; i++) {
      const [x, y, r] = [blobs[i].x, canvas.height - blobs[i].y, blobs[i].r];
      ctx.fillStyle = `hsl(${(i + blobs[i].t) % 360}, 100%, 50%)`;
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.fill();

      const [gX, gY] = [Math.floor(x / 16), Math.floor(y / 32)];
      if (x >= grid.length || x < 0) continue;
      if (y >= grid[0].length || y < 0) continue;
      count++;
    }

    ctx.fillStyle = 'hsl(30, 100%, 50%)';
    let f = 0;
    for (let i = 0; i < grid.length - 1; i++) {
      for (let j = 0; j < grid[i].length - 1; j++) {
        if (i > 0) { if (grid[i][j] === undefined || grid[i-1][j] === undefined || grid[i][j] < grid[i-1][j]) f++; }
        if (j > 0) { if (grid[i][j] === undefined || grid[i][j-1] === undefined || grid[i][j] < grid[i][j-1]) f++; }
      }
    }

    ctx.fillStyle = `hsl(${(f + 255) % 360}, 100%, 50%)`;
    for (let i = 0; i < grid.length - 1; i++) {
      for (let j = 0; j < grid[i].length - 1; j++) {
        if (grid[i][j] !== undefined && grid[i+1][j] === undefined) { ctx.fillRect(i * 16, j * 32, 16, 32); }
        else if (grid[i][j] !== undefined && i > 0 && grid[i-1][j] !== undefined && grid[i][j] >= grid[i-1][j]) { ctx.fillRect(i * 16, j * 32, 16, 32); }
      }
    }

    const text = `Blobs: ${count}`;
    ctx.fillStyle = 'white';
    ctx.font = '24px sans-serif';
    ctx.textAlign = 'left';
    ctx.textBaseline = 'top';
    ctx.fillText(text, 0, 0);
  }

  function update() {
    for (const blob of blobs) {
      blob.x += blob.vx;
      blob.y += blob.vy;

      if (blob.y > canvas.height) {
        blob.t -= Math.random() * 50 + 10;
        blob.vx = -blob.vx;
        blob.r *= 0.9;
      }
      else if (blob.y < canvas.height / 2) {
        blob.t += Math.random() * 50 + 10;
        blob.vy = -blob.vy;
        blob.r *= 1.05;
      }
    }

    draw();
  }

  let lastTime = performance.now();
  function loop() {
    const now = performance.now();
    if (now < lastTime) { lastTime = now; return; }
    update();
    lastTime = now + 16;
    requestAnimationFrame(loop);
  }

  for (const blob of blobs) {
    blob.y += Math.random() * canvas.height / 2;
  }

  loop();

  function randomizeBlobs() {
    for (const blob of blobs) {
      blob.x = Math.random() * canvas.width;
      blob.y = Math.random() * canvas.height;
      blob.vx = Math.random();
      blob.vy = Math.random();
      blob.t = 255 + Math.random() * 130;
      blob.r *= Math.random() * 0.5 + 1;
    }
  }

  let restartCount = 0;
  function checkRestart() {
    if (blobs.length === 0) { return; }
    const minR = blobs.reduce((m, b) => m < b.r ? m : b.r, Infinity);
    for (const blob of blobs) {
      if (blob.r > minR * 1.5 || blob.t - Math.random() * 50 + 10 < 0) { return; }
    }

    restartCount++;
    randomizeBlobs();
  }

  setImmediate(checkRestart);

  canvas.addEventListener('click', () => {
    const c = performance.now();
    if (c > lastTime + 15000) {
      blobs.length = 0;
      blobs.push(...Array(8).fill().map(() => ({x: Math.random() * canvas.width, y: Math.random() * canvas.height, r: 10 + Math.random() * 20, vx: Math.random(), vy: Math.random(), t: 255 + Math.random() * 130})));
      randomizeBlobs();
    }
  });
});
</script>
</head>
<body></body>
</html>
```
qwq:32bBLANKFORMAT
Show code
<think>

Okay, the user wants me to create a self-contained HTML file that shows a metaball lava lamp effect using a canvas. Let me break down the requirements.

First, the HTML must be self-contained, so everything has to be in one file. I'll need to use the canvas element and write JavaScript inside the script tags. The strict character limit is 7000, so I have to keep it concise.

The main elements are the metaballs (blobs) inside a tall lamp shape. There should be 5-8 blobs. Each blob has position, radius, velocity, and temperature. Their behavior is such that when they're near the bottom, they heat up and rise, and when near the top, they cool and sink, creating a cycle like a lava lamp.

Metaballs are usually rendered by summing their fields on a grid. The optical merging happens when nearby blobs' fields add up above a threshold. So I need to calculate the field for each cell in the grid, then threshold it to render the shape.

The color gradient should be warm, maybe based on field strength or height. A counter for the number of blobs is required. The animation uses requestAnimationFrame, no user input, and restarts when finished. No external libraries, so all logic must be in JavaScript.

Starting with the structure: the HTML will have a canvas and a script. The script will handle setup, animation loop, blob movement, rendering, and the counter.

First, setting up the canvas. The lamp shape is tall, so maybe the canvas is 400x800 pixels? That's a common size, but I can adjust later.

Blobs need properties: position (x,y), radius, velocity (vx, vy), and temperature. The temperature affects their behavior. Maybe the velocity depends on temperature: hotter blobs rise faster, cooler sink. Let's think of a simple model where when a blob is below a certain y threshold, it heats up, increasing its velocity upwards, and above another threshold, it cools, decreasing velocity or making it sink.

The metaball algorithm: for each pixel (or grid cell), compute the sum of 1/(distance^2) from each blob. If the sum exceeds a threshold, that pixel is part of the blob. But for performance, maybe use a grid, not individual pixels. Let's use a grid with a step size (like 10) to reduce calculations. Then, between grid points, interpolate or just fill cells above threshold with color.

Rendering: loop over grid cells, compute field value, if above threshold, draw that cell with a color based on field strength or height. Since it's a lamp, the bottom part is hot, so maybe the color is more red/yellow lower down, and cooler (blue) higher up? Wait, the user said warm colors based on field strength or height. Maybe field strength can be combined with height for the color gradient. Alternatively, use height for the color, so the bottom is warmer.

The counter shows the number of blobs. Since they merge and split? Wait, no, the user says "keep 5-8 soft blobs", so maybe the count is fixed. Wait, the instruction says "Show a counter of blobs". Hmm, maybe the blobs can merge into one, but the counter counts how many are currently above threshold? Not sure. The problem says to "keep 5-8 soft blobs", so perhaps the counter is just the count of the blobs (5-8), but maybe it's the number of distinct peaks? Not sure. The user says to show the counter, so perhaps it's simply the number of active blobs, which is fixed, but the problem says "keep 5-8", so maybe the number is randomized between 5-8 each run. Or maybe the counter is the current number of distinct blobs, which can vary as they merge. Hmm, perhaps the user wants to just display the number of blobs created initially, which is 5-8. The problem says "keep 5-8", so maybe the initial count is set between 5-8. The counter could be a span that shows that number.

Now, the animation loop: in requestAnimationFrame, update each blob's position, velocity, temperature, then recompute the grid, draw the canvas.

Initial setup: create an array of blobs. For each blob, set random positions within the lamp's interior, initial velocities, and temperatures.

Lamp shape: the lamp is a tall container, maybe rectangular with a curved top? To simplify, perhaps a rectangular area with the bottom part wider? Or maybe just a rectangle. The lamp's boundaries need to be considered for blob movement. The blobs should bounce off the edges? Or maybe they are confined within the lamp's shape. Let me think of the lamp as a rectangle from, say, top: 10% to bottom: 90% vertically, with some margins. Wait, the user says "tall lamp shape". Maybe a tall rectangular area, so the canvas is vertical, and the blobs are inside it.

The blob movement: their velocity depends on temperature. When near the bottom (lower y), they become hotter (increase temperature) which increases their upward velocity. When near the top, they cool down, decreasing velocity or making them sink.

Temperature could be a value that changes over time based on position. For example, if a blob's y is below a certain point (like halfway?), it heats up over time, otherwise it cools.

Velocity: The velocity might have an upward component based on temperature. Maybe vx is random for horizontal, and vy is determined by temperature. So, vy could be something like (temperature - some base) * speed. For example, when temperature is high, vy is negative (upward), and when low, vy is positive (downward).

Or, perhaps temperature affects the acceleration. Maybe blobs are subject to gravity, but heating reduces the effect. Not sure. Alternatively, a simple model where each frame, the blob's velocity is adjusted based on current temperature and position.

Alternatively, have the blob's vertical velocity depend on temperature: hotter blobs have a higher upward velocity. The temperature increases when they are near the bottom and decreases when near the top. Let's formalize:

Each frame:

for each blob {

   // Update temperature based on position

   if (blob.y < bottomThreshold) {

      blob.temperature += someAmount;

   } else if (blob.y > topThreshold) {

      blob.temperature -= someAmount;

   }

   // Clamp temperature between min and max

   blob.temperature = Math.max(0, Math.min(blob.temperature, maxTemp));

   // Compute velocity based on temperature

   blob.vy = - (blob.temperature - baseTemp) * speedFactor;

   // Apply velocity

   blob.y += blob.vy;

   blob.x += blob.vx;

   // Constrain position within the lamp

   if (blob.x < left || blob.x > right) {

      blob.vx *= -1; // bounce off walls

   }

   if (blob.y < top || blob.y > bottom) {

      blob.vy *= -1;

   }

}

Wait, but the lamp has a height, so maybe the vertical movement is constrained between top and bottom of the lamp. So the blobs can't go beyond the lamp's boundaries, so they bounce or something. Hmm, but in a lava lamp effect, the blobs usually don't hit walls but cycle. Maybe instead, the lamp's vertical boundaries are where temperature changes.

Alternatively, the lamp is the entire canvas except borders, so the blobs are confined within that area. Hmm, perhaps I need to define the lamp's boundaries as the canvas's area minus a border. Let me think of the canvas as 400x800, and the lamp is from y=100 to y=700, so a 600px tall area.

So, for each blob, their position is constrained between those vertical bounds, and their temperature changes based on their y-position. Let's set a hot zone at the bottom (say, below 400px in a 800px canvas), so blobs below that heat up, and above that cool down.

Wait, the vertical midpoint of the canvas (assuming 800 height) is 400. Let's say that blobs below 400 are in the heating zone, and above are cooling.

Now, the velocity: the hotter a blob is, the higher the upward velocity. So velocity.y = - (temperature - baseTemp) * someFactor. That way, higher temperature means moving up faster. The baseTemp could be a midpoint, so when temp is equal to base, velocity is zero? Not sure, maybe baseTemp is zero, so velocity is proportional to temperature, so higher temp increases upward velocity.

The temperature increases when in the lower half, so their velocity pushes them up, but as they rise, they cool, which slows them down until they sink again.

Okay, next: the metaball rendering. To compute the field on a grid.

The grid approach: divide the canvas into cells (e.g., 20x20). For each cell, compute the sum of 1/(distance)^n from each blob. The distance here can be squared, and the exponent can be something like 2 to make it fall off quickly. The threshold is a value above which the cell is considered part of the blob.

But to implement this efficiently on a grid:

- For each grid cell (x, y):

   total = 0

   for each blob {

      dx = (cell.x - blob.x)

      dy = (cell.y - blob.y)

      dSquared = dx*dx + dy*dy

      if (dSquared == 0) { // avoid division by zero, but since blobs are moving, maybe not needed }

      // Use a power, say 2 for distance squared

      contribution = Blob.radius^2 / (dSquared + 1) // add 1 to prevent division by zero

      total += contribution

   }

   if (total > threshold) then the cell is part of the shape.

The threshold would need to be adjusted so that blobs merge when they are close.

To simplify, maybe use a grid step of 10, so the canvas of 400x800 would have about 40x80 cells, which is manageable.

The color can be based on the total field strength or the y-position. Let's use a gradient that's warmer at the bottom (higher y) and cooler at the top. The color could be a function of y, so the bottom is red, middle orange, top yellow? Or maybe the field strength can be used in combination.

Alternatively, the color is based on the normalized total field and the y-position. For example, the hue can be a mix of the field strength (for intensity) and the vertical position (for warmth). But perhaps the user wants a gradient where the lower parts are warmer (red/yellow) and higher parts cooler (white/transparent?), but I need to see.

Alternatively, the color can be based on the total field value. Higher field values (near the blobs) are more intense colors, while lower are lighter. Maybe using a gradient from white to red/yellow based on the field.

Wait, the user says "warm color gradient based on field strength or height". So maybe combine both: higher field gives a more saturated color, and lower heights (bottom) are warmer. Hmm, perhaps the hue is determined by the y position, and the saturation by the field strength. Or just a color gradient based on the y-coordinate, with the field determining opacity? Not sure. Maybe simplify to a gradient that's reddish at the bottom and yellowish at the top, and the field strength controls the transparency or the exact color shade.

Alternatively, set the color to a gradient that goes from red (hot) to white (cool) based on the y-position, and the field strength determines the opacity or the intensity. For simplicity, perhaps the color is a light color when the field is below threshold, but when above, the color is based on y. Or maybe the color is a mix of red and white based on the field and y.

Alternatively, compute the color as follows:

var r = 255 - (cell.y / height) * 255; // so lower y (bottom) has higher red?

Wait, if y increases down, then bottom is higher y. So maybe:

// for a cell's y position:

var yNorm = cell.y / canvasHeight;

var hue = (yNorm * 120); // 0 (bottom) to 120 (top, which is green?), but could adjust.

Wait, maybe using a temperature-based color. Let's say that the color's temperature (HSL) is set so that lower positions are more red (low hue), and higher positions cooler (higher hue). For example, at the bottom (y=0?), hue 0 (red), and at the top (y=height), hue 120 (green). Not sure, but maybe a gradient from red to yellow to white as they go up.

Alternatively, use HSL where the hue is based on the cell's y-coordinate, and the lightness based on the field strength. For example:

hue = (cell.y / canvasHeight) * 120; // red to yellow (lower numbers)

lightness = 50 + (field / maxField) * 50; // brighter when field is higher.

But this might be computationally heavy. Alternatively, pick a color based on y and just have a solid color for the filled cells. Maybe a gradient from red at the bottom to yellow at the top.

Alternatively, for simplicity, just set the color to a red with opacity based on field strength. But the user said a warm gradient based on field strength or height. Let me think of a simple approach: the color is a function of y, so bottom is red, middle orange, top yellow. The field strength might determine the saturation or the intensity.

Perhaps, for each cell above threshold, color it with a color that is proportional to the cell's y position, but with a fixed color scheme. For example:

// color based on y position:

var yFraction = cell.y / canvasHeight;

var red = 255 - (yFraction * 255);

var green = yFraction * 128;

var blue = 0;

But that might not look warm. Alternatively, use an HSL color where the hue changes from red (0) to yellow (60 degrees) as y increases. Let me see:

In HSL:

- HSL(0, 100%, 50%) is red.

- HSL(60, 100%, 50%) is yellow.

So, if y is from 0 to height (assuming 0 is top?), or bottom is higher y.

Wait, in HTML canvas, the coordinate system has (0,0) at top-left, so y increases downward. So the bottom of the canvas is y=height.

So for a cell at position (x,y):

yFraction = y / height;

hue = 60 * yFraction; // 0 at top (y=0), 60 at bottom (y=height).

Then set the fillStyle to 'hsl(' + hue + ', 100%, 50%)'. That would give red at the bottom and yellow at the top? Wait, no: at y=0, it's 0 degrees (red), and at full y, 60 degrees which is yellow. Wait, but that's the opposite of the lamp's heating. Wait the blobs at the bottom are hotter, so maybe the bottom should be more red, which matches this.

Alternatively, maybe invert the yFraction so that the bottom is higher in hue. Hmm, but the code above is straightforward.

Alternatively, use a pre-defined gradient. Let me think of a simple way. Maybe set the color to a red when lower, yellow when higher. Let's say:

var color = 'rgb(' + (255 - (y * 255)/height) + ', ' + (y * 128)/height + ', 0)';

This would give red at the bottom (y=height: 0, 128, 0?) Wait, let me plug in y=0 (top):

red component: 255 -0 = 255, green 0, blue 0 → red.

At y=height (bottom):

red component 0, green (height * 128)/height = 128, so color is green. Hmm, not yellow. Maybe that's not the right approach.

Alternatively, to make it red at the bottom and yellow at the top:

Wait, at the bottom (y=height), want more red, or the other way around?

Actually, the bottom is hotter, so maybe it should be yellow (hotter colors), and the top cooler, like blue? Wait, the user says "warm color gradient", so maybe starting from red at the bottom, moving to white or yellow as it rises.

Alternatively, perhaps use a simple linear gradient on the canvas that goes from red at the bottom to yellow at the top, and just fill the metaball areas with that gradient's color at each point. But metaball rendering requires per-cell (or per-pixel) color, so that might not be straightforward.

Alternatively, for each cell that is part of the blob, set the color based on y-coordinate using a function.

Alternatively, pick a random color for each blob, but the problem states to have a warm gradient, so probably better to have a consistent color based on position.

Hmm, perhaps the simplest is to use a gradient from red (bottom) to yellow (top), so for a given cell's y position, the color is a mix between red and yellow.

Alternatively, use HSL where the hue is based on y, as I thought before.

But let's proceed with the HSL approach for simplicity in code. Let's set the fillStyle to a string built from y and some constants.

Now, the rendering steps:

Clear the canvas.

Loop over each grid cell, compute the total field contributions from all blobs.

If total > threshold, set the fillStyle based on y, and fill that cell.

But how to fill each cell? Since it's a grid, each cell is a rectangle. The grid step determines the resolution. A step of 10 would mean each cell is 10x10 pixels. The threshold would be a value like 3 or 4, depending on blob sizes.

Alternatively, maybe the threshold is dynamic, but to simplify, set it to 1. Let's see:

Each blob contributes radius^2 / distance squared. So if a blob's radius is, say, 20, then at a distance of 20, the contribution is 20^2/(20^2) = 1. So the threshold could be set to 1.5 to require that the sum of contributions from all blobs at that point must be over 1.5 to be considered part of the shape.

Now, putting this into code:

First, set up the grid:

var step = 10;

for(var y = 0; y < canvasHeight; y += step) {

    for(var x = 0; x < canvasWidth; x += step) {

        compute the field here.

    }

}

But in JavaScript, loops can be slow if the grid is too dense, but with a 10x step on 400x800, it's manageable.

Alternatively, use a canvas pixel manipulation, but that's more complex.

Wait, in the problem statement, is the code to be written in JavaScript? The user probably expects a JavaScript code example for creating the animation with canvas.

So, the plan:

- Create a canvas element.

- Set up some blobs (objects with x, y, radius, maybe velocity).

- Each frame:

   - Clear the canvas.

   - Compute the grid cells' field contributions.

   - For each cell above threshold, draw it with color based on y.

   - Update the blobs' positions.

But to make it an animation, the blobs need to move. The problem says "create" but maybe just a static image? The user didn't specify, but since it's about an animation, perhaps it's better to make it move.

Wait, the user's instruction is to "create" but didn't mention animation. Maybe just a still image.

Wait, the initial problem says "Create a program...", so likely expects code with a canvas that shows the merging effect, possibly with moving blobs.

Alternatively, perhaps just the drawing code. Let me see.

Alternatively, the problem might just want the code to draw the metaballs, which can be static.

Now, putting it all together in code:

First, the HTML setup:

<canvas id="myCanvas"></canvas>

Then the JavaScript code:

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext('2d');

canvas.width = 400;

canvas.height = 400;

// Define blobs

var blobs = [

    { x: 100, y: 100, radius: 20 },

    { x: 300, y: 300, radius: 30 }

];

// Drawing function

function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    const step = 10;

    const threshold = 1.5;

    for(var y = 0; y < canvas.height; y += step) {

        for(var x = 0; x < canvas.width; x += step) {

            let sum = 0;

            for(var i = 0; i < blobs.length; i++) {

                var dx = x - blobs[i].x;

                var dy = y - blobs[i].y;

                var distSquared = dx*dx + dy*dy;

                // Avoid division by zero if blob is exactly at (x,y)

                sum += (blobs[i].radius * blobs[i].radius)/(distSquared + 1);

            }

            if(sum > threshold) {

                // Calculate color based on y

                var yFraction = y / canvas.height;

                var hue = yFraction * 120; // 0 to 120 degrees (red to yellow)

                ctx.fillStyle = 'hsl(' + hue + ', 100%, 50%)';

                ctx.fillRect(x, y, step, step);

            }

        }

    }

}

// Run it once

draw();

But this is static. To animate, add movement:

Add velocity to blobs:

var blobs = [

    { x: 100, y: 100, radius: 20, vx: 2, vy: 1 },

    { x: 300, y: 300, radius: 30, vx: -1, vy: -2 }

];

Then in draw function, update their positions:

function animate() {

    requestAnimationFrame(animate);

    draw();

}

function draw() {

    // clear and draw as before

    // after clearing

    for(var i = 0; i < blobs.length; i++) {

        blobs[i].x += blobs[i].vx;

        blobs[i].y += blobs[i].vy;

        // bounce off edges (optional)

        if(blobs[i].x + blobs[i].radius > canvas.width || blobs[i].x < blobs[i].radius) {

            blobs[i].vx *= -1;

        }

        if(blobs[i].y + blobs[i].radius > canvas.height || blobs[i].y < blobs[i].radius) {

            blobs[i].vy *= -1;

        }

    }

    // then compute grid...

}

Wait, the velocity is applied before drawing.

This way, the blobs move and the metaballs merge when they get close.

Now, the colors are based on y-coordinate, giving a warm gradient from red at the bottom (y=canvas.height) to yellow at the top (y=0).

Wait but in HSL, 0 degrees is red, 60 is yellow, 120 is green. So with hue= yFraction * 120, at bottom (y=400, yFraction=1), hue=120 (green?), which might not be desired.

Hmm, perhaps better to make it 60 degrees at the bottom.

Let me recalculate:

If I want red at the bottom (y=height):

hue should be 0 at bottom, so reverse the fraction.

Wait:

yFraction = (canvas.height - y)/canvas.height;

Then:

hue = (yFraction) * 60; // at y=0 (top), yFraction=1 → 60 (yellow), at y=height (bottom), 0 (red).

Wait, that would mean:

- Top (y=0): 60 degrees → yellow.

- Bottom (y=400): 0 → red.

That's better for the warm gradient: red at the bottom (hot) and yellow at the top (cooling down).

So the code would have:

var yFraction = (canvas.height - y)/canvas.height;

var hue = yFraction * 60;

ctx.fillStyle = 'hsl(' + hue + ', 100%, 50%)'

That would give a warm gradient from red (bottom) to yellow (top).

Alternatively, to get more warmth, maybe extend the hue:

Perhaps up to 30 degrees for more orange?

Wait, 60 is yellow, 30 is orange, 0 is red.

Perhaps set the hue to 150 degrees for blue at the top, but that would be cool. Hmm.

Alternatively, use a range that stays within warm colors. Let's say from 0 (red) to 60 (yellow) at the top. That way, the bottom is red, and it transitions to yellow at the top.

Thus, the formula:

var hue = 60 * (1 - (y / canvas.height));

Wait, let me see:

y = 0 (top): 60 * (1 -0) = 60 → yellow

y = canvas.height (bottom): 60*(1 -1)=0 → red

Yes, that's correct.

So the code would be:

var hue = 60 * (1 - (y / canvas.height));

ctx.fillStyle = 'hsl('+hue+', 100%, 50%)';

This gives a warm gradient.

Now, the threshold: perhaps 1.5 works. The radius is squared divided by distance squared.

Testing with radius 20, the contribution at distance 20 is 400/400 =1, so at threshold 1.5, they need to be closer than about sqrt(400/1.5) ≈ 16.33 distance apart to merge.

Alternatively, adjust threshold as needed for the desired effect.

The final code would look something like this.

Now, putting it all together with the full code structure:

HTML:

<!DOCTYPE html>

<html>

<body>

<canvas id="myCanvas"></canvas>

<script>

// Code here

</script>

</body>

</html>

JavaScript code inside the script tag:

var canvas = document.getElementById('myCanvas');

var ctx = canvas.getContext('2d');

canvas.width = 400;

canvas.height = 400;

// Blobs with initial positions and velocities

var blobs = [

    { x: 100, y: 100, radius: 30, vx: 2, vy: 1 },

    { x: 300, y: 300, radius: 50, vx: -1, vy: -2 }

];

function draw() {

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Update blob positions

    for(var i = 0; i < blobs.length; i++) {

        blobs[i].x += blobs[i].vx;

        blobs[i].y += blobs[i].vy;

        // Bounce off edges

        if(blobs[i].x + blobs[i].radius > canvas.width || blobs[i].x < blobs[i].radius) {

            blobs[i].vx *= -1;

        }

        if(blobs[i].y + blobs[i].radius > canvas.height || blobs[i].y < blobs[i].radius) {

            blobs[i].vy *= -1;

        }

    }

    // Draw metaballs

    const step = 10;

    const threshold = 1.5;

    for(var y = 0; y < canvas.height; y += step) {

        for(var x = 0; x < canvas.width; x += step) {

            let sum = 0;

            for(var i = 0; i < blobs.length; i++) {

                var dx = x - blobs[i].x;

                var dy = y - blobs[i].y;

                var distSquared = dx*dx + dy*dy;

                sum += (blobs[i].radius * blobs[i].radius)/(distSquared + 1);

            }

            if(sum > threshold) {

                // Calculate color based on y-coordinate

                var yFraction = (canvas.height - y) / canvas.height;

                var hue = 60 * yFraction; // Red (0 deg) at bottom to Yellow (60 deg) at top

                ctx.fillStyle = 'hsl('+hue+', 100%, 50%)';

                ctx.fillRect(x, y, step, step);

            }

        }

    }

}

// Animation loop

function animate() {

    requestAnimationFrame(animate);

    draw();

}

animate();

</script>

</body>

</html>

This code should create an animation of two blobs moving around the canvas, causing the color fields to merge when they get near each other, with a warm color gradient from red at the bottom to yellow at the top.

The user might not have specified animation, but since the question is about creating a program, including animation makes it more dynamic. Alternatively, if static, we can remove the velocity and just draw the blobs in place.
deepseek-r1:32bERRBLANKMINIFIEDFORMAT
Show code
```html
<!DOCTYPE html><html><body style="margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#000"><canvas id=c width=400 height=400></canvas>
<script>
let c=document.getElementById('c'),ctx=c.getContext('2d'),w=400,h=400,blobs=[],gridSize=20,threshold=.6,count=5+Math.random()*3|0;blobs.length=count
class Blob{constructor(){this.x=w/2,this.y=h/2+100*(Math.random()-0.5),this.radius=30*(Math.random()+0.5),this.dx=(Math.random()-0.5)*2,this.dy=(Math.random()-0.5)*2,this.temperature=50}update(){let t=this;if(t.y>w/2)t.temperature+=1e-3 else t.temperature-=1e-3;const a=0.1;t.radius=60*(t.temperature/100);if(t.y<w/2){t.dy=Math.min(4,t.dy+a)}else{t.dy=Math.max(-4,t.dy-a)}t.x+=t.dx;t.y+=t.dy;if(t.x<left(t.y))t.x=left(t.y),t.dx*=-.75;if(t.x>right(t.y))t.x=right(t.y),t.dx*=-.75;if(t.y<0||t.y>w)({t.dy*=-.75,t.y=Math.max(0,Math.min(w,t.y))})}draw(){}}
function left(y){return 200-Math.sqrt(16*y)}function right(y){return 200+Math.sqrt(16*y)}
function init(){blobs=[];count=5+Math.random()*3|0;for(let i=0;i<count;i++)blobs.push(new Blob())}
init();
function draw(){ctx.fillStyle='#000';ctx.fillRect(0,0,w,h);ctx.fillStyle='hsl(60,100%,50%)';drawLamp();let grid=new Array(w/gridSize).fill().map(()=>new Array(h/gridSize).fill(0));blobs.forEach(b=>{for(let i=0;i<grid.length;i++){for(let j=0;j<grid[i].length;j++){const x=i*gridSize+gridSize/2,y=j*gridSize+gridSize/2,d=Math.hypot(x-b.x,y-b.y);if(d==0)grid[i][j]=1;else grid[i][j]+=b.radius/(d*d)}});ctx.beginPath();for(let i=0;i<grid.length;i++){for(let j=0;j<grid[i].length;j++){const x=i*gridSize+gridSize/2,y=j*gridSize+gridSize/2;if(grid[i][j]>threshold){drawCell(x,y,Math.min(1,grid[i][j]/2))}}ctx.fill();blobs.forEach(b=>b.update());requestAnimationFrame(draw)}
function drawLamp(){const y=0;ctx.beginPath();ctx.moveTo(left(y),y);for(let y=0;y<w;y+=5)ctx.lineTo(left(y),y);for(let y=w;y>=0;y-=5)ctx.lineTo(right(y),y);ctx.lineTo(w,0);ctx.closePath();ctx.fill()}
function drawCell(x,y,v){const s=gridSize/2;ctx.save();ctx.translate(x,y);ctx.fillStyle=`hsl(${60+v*40},100%,${50+v*10}%)`;ctx.beginPath();ctx.arc(0,0,s,s*2,Math.PI*2);ctx.fill();ctx.restore();}
draw();
</script>
</body></html>
```
command-r:35bERRBLANK
Show code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Metaball Lava Lamp</title>
    <style>
        body {
            margin: 0;
            background: #111;
            overflow: hidden;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="lavaLamp"></canvas>
    <script>
        const canvas = document.getElementById('lavaLamp');
        const ctx = canvas.getContext('2d');

        class Blob {
            constructor(x, y) {
                this.pos = [x, y];
                this.vel = [0, 0];
                this.radius = Math.random() * 3 + 1;
                this.temp = Math.random() * 40 + 20; // Initial temperature
            }

            update(dt) {
                this.pos[0] += this.vel[0] * dt;
                this.pos[1] += this.vel[1] * dt;

                if (this.pos[1] < this.radius || this.pos[1] > canvas.height - this.radius) {
                    this.vel[1] *= -1; // Bounce off top/bottom
                }
            }

            draw(ctx, tempGradient) {
                ctx.beginPath();
                ctx.arc(...this.pos, this.radius, 0, Math.PI * 2);
                ctx.fillStyle = `rgb(${tempGradient[Math.round(this.temp)]})`; 
                ctx.fill();
            }

            getFieldContribution() {
                const contribution = Array(canvas.width).fill(0);

                for (let x = 0; x < canvas.width; x++) {
                    const dx = this.pos[0] - x;
                    const dy = this.pos[1];

                    if (dx * dx + dy * dy > this.radius * this.radius) continue; // Outside blob radius

                    contribution[x] += Math.max(this.radius - Math.sqrt(dx * dx + dy * dy), 0);
                }

                return contribution;
            }
        }

        function drawGrid(ctx, blobs) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            const grid = new Array(canvas.height).fill().map(() => 
                Array(canvas.width).fill(0)
            );

            for (const blob of blobs) {
                const contribution = blob.getFieldContribution();

                for (let x = 0; x < canvas.width; x++) {
                    grid[blob.pos[1]][x] += contribution[x];
                }
            }

            ctx.fillStyle = '#333'; // Dark background for optical merging
            blobs.forEach(b => b.draw(ctx, tempGradient)); // Draw blobs first
            
            const threshold = Math.max(...grid.flat()) / 2; // Half of max value as threshold

            for (let y = 0; y < canvas.height; y++) {
                ctx.fillStyle = `rgb(${tempGradient[Math.round(y)]})`; 
                grid[y].forEach((value, x) => {
                    if (value > threshold) ctx.fillRect(x, y, 1, 1); // Fill cells above threshold
                });
            }
        }

        const tempGradient = [0, 50, 100, 150, 200].map(temp => {
            return (Math.floor(temp * 2.55) << 16) | (Math.floor((255 - temp / 4) * 2.55) << 8) | Math.floor((255 - temp) * 2.55);
        });

        let blobs = [];

        function init() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight * 0.7 + 100; // Make it taller than the screen

            for (let i = 0; i < 6; i++) {
                const x = Math.random() * (canvas.width - 20) + 10; 
                const y = Math.random() * canvas.height / 4 + canvas.height / 4; // Place blobs near the bottom
                blobs.push(new Blob(x, y));
            }

            animate();
        }

        function animate() {
            const dt = 1 / 60; // Fixed time step for smoothness

            blobs.forEach((blob, i) => {
                blob.update(dt);

                if (i === blobs.length - 1) return; // Last blob doesn't affect others

                blob.temp += (blobs[i + 1].pos[1] - blob.pos[1]) * dt / canvas.height; 
                blob.vel[1] -= blob.temp * dt; // Temperature affects vertical velocity
            });

            drawGrid(ctx, blobs);
            
            requestAnimationFrame(animate);
        }

        init();
    </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 referenceOKMINIFIED
Show code
<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Metaball Lava Lamp</title><style>html,body{margin:0;height:100%;overflow:hidden;background:#12080b;color:#ffd7a1;font:14px system-ui}body{display:grid;place-items:center}canvas{width:min(72vw,430px);height:min(94vh,860px);filter:drop-shadow(0 0 28px #f60)}#n{position:fixed;top:14px;left:14px;background:#1b0b10cc;border:1px solid #6b281b;padding:7px 10px;border-radius:7px}</style></head><body><div id=n></div><canvas id=c></canvas><script>
const c=document.getElementById("c"),x=c.getContext("2d"),n=document.getElementById("n");let W,H,bs=[],t=0,last=0,dur=9e4;
function R(a,b){return a+Math.random()*(b-a)}
function size(){let r=devicePixelRatio||1,W0=c.clientWidth,H0=c.clientHeight;c.width=W=W0*r;c.height=H=H0*r}
function lamp(p){let y=p*H,cx=W/2,w=(.27+.2*Math.sin(Math.PI*p))*W;return[cx-w/2,cx+w/2,y]}
function reset(){size();bs=[];let k=5+Math.floor(Math.random()*4);for(let i=0;i<k;i++){let p=R(.18,.82),L=lamp(p),r=R(.055,.105)*W;bs.push({x:R(L[0]+r,L[1]-r),y:p*H,r,vx:R(-.018,.018)*W,vy:R(-.012,.012)*H,temp:R(.25,.85),ph:R(0,7)})}n.textContent=bs.length+" blobs";t=0;last=performance.now()}
function col(q,y){let h=32-22*q+18*(1-y/H),s=93,l=42+28*Math.min(1,q);return`hsl(${h} ${s}% ${l}%)`}
function step(now){let dt=Math.min(32,now-last);last=now;t+=dt;if(t>dur)reset();
x.clearRect(0,0,W,H);let g=Math.max(7,Math.round(W/48));
let grd=x.createLinearGradient(0,0,0,H);grd.addColorStop(0,"#230812");grd.addColorStop(.55,"#61170e");grd.addColorStop(1,"#21080b");x.fillStyle=grd;x.beginPath();x.roundRect(.28*W,.04*H,.44*W,.9*H,W*.22);x.fill();x.save();x.beginPath();x.roundRect(.29*W,.045*H,.42*W,.89*H,W*.2);x.clip();
for(const b of bs){let p=b.y/H;if(p>.74)b.temp=Math.min(1,b.temp+dt*.00009);if(p<.24)b.temp=Math.max(0,b.temp-dt*.00008);let buoy=(.5-b.temp)*dt*.00016*H;b.vy+=buoy;b.vx+=Math.sin(t*.001+b.ph)*dt*.000006*W;b.x+=b.vx*dt*.035;b.y+=b.vy*dt*.035;let L=lamp(b.y/H),m=b.r*.7;if(b.x<L[0]+m){b.x=L[0]+m;b.vx=Math.abs(b.vx)*.7}if(b.x>L[1]-m){b.x=L[1]-m;b.vx=-Math.abs(b.vx)*.7}if(b.y<m){b.y=m;b.vy=Math.abs(b.vy)*.45;b.temp=.15}if(b.y>H-m){b.y=H-m;b.vy=-Math.abs(b.vy)*.45;b.temp=1}b.vx*=.996;b.vy*=.996}
for(let y=.06*H;y<.93*H;y+=g)for(let xx=.3*W;xx<.7*W;xx+=g){let p=y/H,L=lamp(p);if(xx<L[0]||xx>L[1])continue;let f=0;for(const b of bs){let dx=xx-b.x,dy=y-b.y;f+=b.r*b.r/(dx*dx+dy*dy+1)}if(f>1){x.fillStyle=col(Math.min(2.7,f)/2.7,y);x.fillRect(xx-g*.6,y-g*.6,g*1.3,g*1.3)}}
x.globalCompositeOperation="screen";for(const b of bs){let a=x.createRadialGradient(b.x,b.y,0,b.x,b.y,b.r*2);a.addColorStop(0,"#ffb33b88");a.addColorStop(1,"#0000");x.fillStyle=a;x.beginPath();x.arc(b.x,b.y,b.r*2,0,7);x.fill()}x.restore();x.globalCompositeOperation="source-over";x.strokeStyle="#ffc36b88";x.lineWidth=Math.max(2,W*.008);x.beginPath();x.roundRect(.29*W,.045*H,.42*W,.89*H,W*.2);x.stroke();x.fillStyle="#090305";x.fillRect(.37*W,.005*H,.26*W,.055*H);x.fillRect(.35*W,.93*H,.3*W,.045*H);requestAnimationFrame(step)}
onresize=reset;reset();requestAnimationFrame(step);
</script></body></html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:8b harness
qwen3.5:9b 49.6 sOK
deepseek-r1:14b 41.6 sOK
gemma4:26b harness
qwen3:14b 29.6 sOK
qwen3-coder:30b 23.5 sOK
mistral-small:24b 157.8 sOK
llama3.1:8b 119.5 sOK
qwq:32b 222.3 sOK
deepseek-r1:32b 69.0 sOK
command-r:35b 265.0 sOK