OverviewCode › R137
Code · blind round

R137 · Cloth mesh hanging and rippling in the wind

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 animates a hanging cloth on a <canvas>: a grid of roughly 15x10 point masses connected to their orthogonal and diagonal neighbours by spring constraints, with the entire top row of points pinned in place so the mesh hangs freely under gravity, integrated with Verlet integration plus several constraint-relaxation passes per frame so the fabric stays taut without stretching. Apply a small continuously oscillating wind force across the whole mesh so the cloth visibly ripples and billows instead of hanging static, and draw the mesh as connected lines or shaded quads. Show the current wind strength. 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-coder:30bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Dynamic Cloth Simulation</title>
<style>body {margin:0;overflow:hidden;}canvas {display:block;background:#111;}</style>
</head>
<body>
<canvas id="c"></canvas>
<script>window.onload = function() {
    const canvas = document.getElementById('c');
    const ctx = canvas.getContext('2d');

    let W = 800, H = 600;
    canvas.width = W;
    canvas.height = H;

    const COLS = 15; // Grid width (X)
    const ROWS = 10; // Grid height (Y)
    const SPACING = 30; // Initial spacing between points
    const ITERATIONS = 6; // Constraint relaxation passes per frame
    const GRAVITY = 9.8 * 0.05;

    let pos = []; // Current positions [x, y]
    let prevPos = []; // Previous positions [px, py]
    let constraints = []; // Links between points (indices)
    let windStrength = 0.1;

    function initialize() {
        pos = [];
        prevPos = [];
        constraints = [];

        for (let r = 0; r < ROWS; r++) {
            for (let c = 0; c < COLS; c++) {
                const i = r * COLS + c;
                pos[i] = [c * SPACING, r * SPACING];
                prevPos[i] = [...pos[i]];

                // Pin top row points (r=0)
                if (r === 0) {
                    // Keep them fixed in space for the simulation anchor
                    pos[i][0] = c * SPACING;
                    pos[i][1] = 20;
                    prevPos[i] = [c * SPACING, 20];
                }
            }
        }

        // 1. Orthogonal constraints (N-S, E-W)
        for (let r = 0; r < ROWS; r++) {
            for (let c = 0; c < COLS - 1; c++) {
                const i1 = r * COLS + c;
                const i2 = r * COLS + c + 1;
                constraints.push({p: [i1, i2], restLength: SPACING});
            }
        }
        for (let c = 0; c < COLS; c++) {
            for (let r = 0; r < ROWS - 1; r++) {
                const i1 = r * COLS + c;
                const i2 = (r + 1) * COLS + c;
                constraints.push({p: [i1, i2], restLength: SPACING});
            }
        }

        // 2. Diagonal constraints
        for (let r = 0; r < ROWS - 1; r++) {
            for (let c = 0; c < COLS - 1; c++) {
                const i_diag1 = r * COLS + c;
                const i_diag2 = (r + 1) * COLS + c + 1;
                constraints.push({p: [i_diag1, i_diag2], restLength: Math.sqrt(SPACING * SPACING * 2)});

                const i_diag3 = r * COLS + c + 1;
                const i_diag4 = (r + 1) * COLS + c;
                constraints.push({p: [i_diag3, i_diag4], restLength: Math.sqrt(SPACING * SPACING * 2)});
            }
        }

        windStrength = 0.1; // Initialize wind display
    }

    function applyForces() {
        const dt = 0.05;
        for (let i = 0; i < pos.length; i++) {
            if (i >= COLS * 1 && i <= COLS * (ROWS - 2) + (COLS - 1)) { // Skip pinned points
                // Apply Gravity
                pos[i][1] += GRAVITY;

                // Apply Wind Force (Oscillating horizontal force)
                const windX = Math.sin(Date.now() / 500) * windStrength * 20;
                pos[i][0] += windX * dt;
            }
        }
    }

    function integrate() {
        for (let i = 0; i < pos.length; i++) {
            if ((i % COLS === 0 && i > 0) || (i % COLS !== 0)) { // Check if point is not pinned (r=0, c>0) or r=0 itself
                // Verlet Integration: newPos = current + (current - previous) + acceleration
                const x = pos[i][0];
                const y = pos[i][1];
                const px = prevPos[i][0];
                const py = prevPos[i][1];

                prevPos[i] = [...pos[i]]; // Store current position as previous
                
                // Update based on accumulated forces (which include gravity/wind applied in applyForces)
                pos[i][0] += (x - px);
                pos[i][1] += (y - py);
            } 
        }
    }

    function solveConstraints() {
        for (let k = 0; k < ITERATIONS; k++) { // Constraint Relaxation Passes
            constraints.forEach(c => {
                const i1 = c.p[0];
                const i2 = c.p[1];
                const restLength = c.restLength;

                const dx = pos[i2][0] - pos[i1][0];
                const dy = pos[i2][1] - pos[i1][1];
                const distance = Math.sqrt(dx * dx + dy * dy);
                const correction = (distance / restLength) - 1;

                // Apply positional corrections
                pos[i1][0] -= dx * 0.5 * correction;
                pos[i1][1] -= dy * 0.5 * correction;
                pos[i2][0] += dx * 0.5 * correction;
                pos[i2][1] += dy * 0.5 * correction;
            });
        }
    }

    function draw() {
        // Clear canvas
        ctx.fillStyle = '#111';
        ctx.fillRect(0, 0, W, H);

        // Draw connections (lines)
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
        ctx.lineWidth = 1;
        constraints.forEach(c => {
            const i1 = c.p[0];
            const i2 = c.p[1];

            ctx.beginPath();
            ctx.moveTo(pos[i1][0], pos[i1][1]);
            ctx.lineTo(pos[i2][0], pos[i2][1]);
            ctx.stroke();
        });

        // Draw points (optional, for debugging/visual clarity)
        /*
        ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
        for(let i=0; i<pos.length; i++) {
            ctx.beginPath();
            ctx.arc(pos[i][0], pos[i][1], 1.5, 0, Math.PI * 2);
            ctx.fill();
        }
        */

        // Display wind strength overlay
        ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
        ctx.font = '14px monospace';
        ctx.fillText(`Wind Strength: ${windStrength.toFixed(3)}`, 10, 20);
    }

    function loop() {
        // 1. Apply forces (Gravity, Wind)
        applyForces();
        
        // 2. Integrate (Move points based on accumulated force/velocity)
        integrate();
        
        // 3. Solve constraints (Pull points back into structural form)
        solveConstraints();

        // 4. Draw results
        draw();

        requestAnimationFrame(loop);
    }

    initialize();
    loop();
};
</script>
</body>
</html>
```
deepseek-r1:14bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head style="display:flex;justify-content:center;align-items:center;height:100vh;margin:0;">
<canvas id=c></canvas>
<div id=w>Wind: 0</div>

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

let width = canvas.width, height = canvas.height;
let points = [], constraints = [];
let wind = 0.1, time = 0;

const setup = () => {
    const cols = 15, rows = 10;
    let cellWidth = width / (cols - 1), cellHeight = height / (rows - 1);
    
    points = Array.from({length: cols * rows}, (_, i) => ({
        x: i % cols * cellWidth + (i < cols ? Math.sin(i) * 50 : 0),
        y: Math.floor(i / cols) * cellHeight,
        vx: 0, vy: 0,
        pinned: i < cols,
        constraints: []
    }));
    
    // Create constraints
    for(let i = 0; i < points.length; i++) {
        let point = points[i];
        
        // Orthogonal neighbors
        if(i % cols > 0) addConstraint(point, points[i - 1]);
        if(i % cols < cols - 1) addConstraint(point, points[i + 1]);
        if(Math.floor(i / cols) > 0) addConstraint(point, points[i - cols]);
        if(Math.floor(i / cols) < rows - 1) addConstraint(point, points[i + cols]);
        
        // Diagonal neighbors
        if(i % cols > 0 && Math.floor(i / cols) > 0) addConstraint(point, points[i - cols - 1]);
        if(i % cols > 0 && Math.floor(i / cols) < rows - 1) addConstraint(point, points[i + cols + 1]);
        if(Math.floor(i / cols) > 0 && i % cols < cols - 1) addConstraint(point, points[i - cols + 1]);
        if(Math.floor(i / cols) < rows - 1 && i % cols < cols - 1) addConstraint(point, points[i + cols - 1]);
    }
};

const addConstraint = (a, b) => {
    a.constraints.push(b);
    b.constraints.push(a);
};

const animate = () => {
    ctx.clearRect(0, 0, width, height);
    
    // Update constraints
    for(let i = points.length - 1; i >= 0; i--) {
        let p = points[i];
        if(!p.pinned) {
            let x = p.x, y = p.y;
            for(const c of p.constraints) {
                const dx = c.x - x, dy = c.y - y;
                const dist = Math.sqrt(dx * dx + dy * dy);
                const force = (dist > 0 ? (1 - dist / 60) : 0);
                
                if(dist > 0) {
                    x += (c.x - x) * force * 0.3;
                    y += (c.y - y) * force * 0.3;
                }
            }
            p.vx *= 0.8, p.vy *= 0.8;
            p.x += p.vx + Math.sin(time) * wind;
            p.y += p.vy;
        }
    }

    // Draw
    ctx.beginPath();
    for(let i = 0; i < points.length; i++) {
        let p1 = points[i], p2;
        for(const c of p1.constraints) {
            if(c.x > p1.x && c.y > p1.y) continue;
            ctx.moveTo(p1.x, p1.y);
            ctx.lineTo(c.x, c.y);
        }
    }
    ctx.strokeStyle = '#000', ctx.lineWidth = 2;
    ctx.stroke();
    
    // Draw wind strength
    ctx.fillStyle = 'rgba(0,0,0,0.75)';
    ctx.fillRect(10, height - 30, width - 20, 20);
    ctx.fillStyle = '#fff';
    ctx.font = '18px sans-serif';
    ctx.fillText(`Wind: ${wind.toFixed(2)}`, 15, height - 25);

    time += 0.05;
};

const resize = () => {
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
    setup();
}

window.addEventListener('resize', resize);
setup();

animate();

setInterval(() => {
    if(Date.now() % 1000 === 0) wind = (Math.random() - 0.5) * 0.4 + 0.8;
}, 1000);

function requestAnimationFrame2(callback) {
    setTimeout(callback, 1000 / 60);
}

while(true) requestAnimationFrame(animate);
</script>
</body></html>
```
qwen3:14bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Simulated Fabric</title>
    <style>
        body { margin: 0; background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden; }
        canvas { border: 1px solid #333; background: #222; }
    </style>
</head>
<body>
    <canvas id="clothCanvas"></canvas>

    <script>
        const canvas = document.getElementById('clothCanvas');
        const ctx = canvas.getContext('2d');
        let W = 700, H = 500;
        canvas.width = W;
        canvas.height = H;

        // Physics Parameters
        const COLS = 15;
        const ROWS = 10;
        const REST_LENGTH = 50; // Target distance between connected points
        const GRAVITY = 0.2;
        const WIND_STRENGTH_BASE = 0.3;
        const DAMPING = 0.98;
        const ITERATIONS = 6; // Constraint passes

        let pos = new Float32Array(COLS * ROWS);
        let prevPos = new Float32Array(COLS * ROWS);
        let windTime = Math.random() * 100;
        
        function initSim() {
            // Initialize positions and previous positions
            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLS; x++) {
                    const i = y * COLS + x;

                    // Initial placement: Slightly offset from the top edge for hanging effect
                    pos[i] = [x * (W / COLS) - (W / 2) + (W / (COLS * 4)), y * (H / ROWS)];
                    prevPos[i] = [...pos[i]];
                }
            }
        }

        function getIndex(x, y) {
            if (x < 0 || x >= COLS || y < 0 || y >= ROWS) return -1;
            return y * COLS + x;
        }

        function updatePhysics() {
            const dt = 1; // Time step multiplier

            // Step 1: Apply Forces (Gravity & Wind) and Store New Positions
            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLS; x++) {
                    const i = getIndex(x, y);
                    if (i === -1) continue;

                    // Apply Gravity
                    pos[i][1] += GRAVITY * dt;

                    // Apply Wind Force: Oscillating force based on time and position (sinusoidal wave)
                    let windForce = WIND_STRENGTH_BASE * Math.sin(windTime + x * 0.2);
                    pos[i][0] += windForce * 0.1;

                    // Update previous position for Verlet integration
                    prevPos[i] = [...pos[i]];
                }
            }

            // Step 2: Constraint Relaxation (Verlet Integration & Spring Pull)
            for (let k = 0; k < ITERATIONS; k++) {
                // Loop over all internal points (excluding the pinned top row y=0)
                for (let y = 1; y < ROWS; y++) {
                    for (let x = 0; x < COLS; x++) {
                        const i = getIndex(x, y);

                        // Check neighbors: Right and Down-Right (only need to check half the connections)
                        [
                            { dx: 1, dy: 0 },    // Horizontal right
                            { dx: 1, dy: 1 }     // Diagonal down-right
                        ].forEach(offset => {
                            const nx = x + offset.dx;
                            const ny = y + offset.dy;

                            if (nx < COLS && ny < ROWS) {
                                const j = getIndex(nx, ny);
                                
                                // Calculate vector difference and distance
                                let dx = pos[j][0] - pos[i][0];
                                let dy = pos[j][1] - pos[i][1];
                                let dist = Math.sqrt(dx * dx + dy * dy);

                                if (dist > 0) {
                                    // Correction factor based on stretch amount
                                    let correction = REST_LENGTH - dist;
                                    let pullX = (dx / dist) * correction * 0.5; // Apply half the force to each point per pass
                                    let pullY = (dy / dist) * correction * 0.5;

                                    // Apply constraint correction
                                    pos[i][0] -= pullX;
                                    pos[i][1] -= pullY;
                                    pos[j][0] += pullX;
                                    pos[j][1] += pullY;
                                }
                            }
                        });

                        // Check vertical neighbor (Down) - Only need to check this if we didn't already cover all verticals
                        if (y + 1 < ROWS && x < COLS) {
                             const j = getIndex(x, y+1);
                             let dx = pos[j][0] - pos[i][0];
                             let dy = pos[j][1] - pos[i][1];
                             let dist = Math.sqrt(dx * dx + dy * dy);

                             if (dist > 0) {
                                let correction = REST_LENGTH - dist;
                                let pullX = (dx / dist) * correction * 0.5; 
                                let pullY = (dy / dist) * correction * 0.5; 

                                pos[i][0] -= pullX;
                                pos[i][1] -= pullY;
                                pos[j][0] += pullX;
                                pos[j][1] += pullY;
                            }
                        }
                    }
                }
            }


             // Step 3: Boundary Condition (Pin the top row)
            for (let x = 0; x < COLS; x++) {
                const i = getIndex(x, 0);
                pos[i][0] = pos[prevPos[i]][0]; // Pin X to its initial X position
                pos[i][1] = prevPos[i][1];     // Pin Y to its initial Y position
            }

            windTime += 0.02;
        }


        function draw() {
            ctx.clearRect(0, 0, W, H);
            
            // Draw Connections (Lines)
            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLS; x++) {
                    const i = getIndex(x, y);
                    if (i === -1) continue;

                    // Check neighbors: Right and Down
                    [
                        { dx: 1, dy: 0 },    // Horizontal right
                        { dx: 0, dy: 1 }     // Vertical down
                    ].forEach(offset => {
                        const nx = x + offset.dx;
                        const ny = y + offset.dy;

                        if (nx < COLS && ny < ROWS) {
                            const j = getIndex(nx, ny);

                            ctx.beginPath();
                            ctx.strokeStyle = 'rgba(150, 180, 220, 0.4)';
                            ctx.moveTo(pos[i][0], pos[i][1]);
                            ctx.lineTo(pos[j][0], pos[j][1]);
                            ctx.stroke();
                        }
                    });

                    // Draw points (optional: for debugging/visibility)
                    /*
                    ctx.beginPath();
                    ctx.fillStyle = '#ccc';
                    ctx.arc(pos[i][0], pos[i][1], 1, 0, Math.PI * 2);
                    ctx.fill();
                    */
                }
            }

             // Draw info text
            ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
            ctx.font = '14px Arial';
            const windText = `Wind Strength: ${Math.abs(WIND_STRENGTH_BASE * Math.sin(windTime + COLS / 3 * 0.2)).toFixed(2)}`;
            ctx.fillText(`Simulation Running | ${windText}`, 10, H - 10);
        }

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

        // Initialization and Start
        initSim();
        loop();
    </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.
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.
llama3.1:8bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<style>
canvas {
  border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<script>
const c = document.getElementById("c");
const ctx = c.getContext("2d");

function vec(x, y) { return [x, y]; }
function add(a, b) {
  const r = a.slice();
  for (let i = 0; i < r.length; i++) {
    r[i] += b[i];
  }
  return r;
}
function sub(a, b) {
  const r = a.slice();
  for (let i = 0; i < r.length; i++) {
    r[i] -= b[i];
  }
  return r;
}
function mul(s, v) {
  const r = [v[0]*s, v[1]*s];
  return r;
}
function mag(v) { return Math.sqrt(v[0]*v[0]+v[1]*v[1]); }
function norm(v) { return mul(1/mag(v), v); }
function dot(a, b) {
  let sum = 0;
  for (let i = 0; i < a.length; i++) {
    sum += a[i] * b[i];
  }
  return sum;
}

function springConstant(s) { return s/10+0.1; }

const N = 15*10;
const points = [];
for (let j = 0; j < 10; j++)
  for (let i = 0; i < 15; i++) {
    const x = mul(i, 40) + 20 - c.width/2;
    const y = mul(j, 30) + 150 - c.height/2;
    points.push({x: x, y: y});
  }
const pinned_points = points.slice(0, 15);
const springs = [];
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    const p1 = points[y*15+x];
    if (y == 0)
      continue;
    const p2 = points[(y-1)*15+x];
    springs.push({p1: p1, p2: p2});
  }
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    const p1 = points[y*15+x];
    if (x == 0)
      continue;
    const p2 = points[y*15+(x-1)];
    springs.push({p1: p1, p2: p2});
  }
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    if (y == 0 || x == 0)
      continue;
    const p1 = points[y*15+x];
    const p2 = points[(y-1)*15+(x-1)];
    springs.push({p1: p1, p2: p2});
  }
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    const p1 = points[y*15+x];
    if (y == 9 || x == 14)
      continue;
    const p2 = points[(y+1)*15+(x+1)];
    springs.push({p1: p1, p2: p2});
  }
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    if (y == 9 || x == 14)
      continue;
    const p1 = points[y*15+x];
    const p2 = points[(y+1)*15+(x-1)];
    springs.push({p1: p1, p2: p2});
  }
for (let y = 0; y < 10; y++)
  for (let x = 0; x < 15; x++) {
    const p1 = points[y*15+x];
    if (x == 14 || y == 9)
      continue;
    const p2 = points[(y+1)*15+(x+1)];
    springs.push({p1: p1, p2: p2});
  }

function wind(v) {
  return mul(Math.sin(Date.now()/1000), v);
}
let time_last = Date.now();
const gravity = [0, 0.01];
const dt = 0.02;
let wind_magnitude = Math.random()*5+1;

function integrate(spring_constant) {
  const velocities = points.map(p => vec(0, 0));
  for (let i = 0; i < springs.length; i++) {
    const s = springs[i];
    const p1 = points[points.indexOf(s.p1)];
    const p2 = points[points.indexOf(s.p2)];
    let k = spring_constant(s);
    if (!pinned_points.includes(p1) && !pinned_points.includes(p2)) {
      const r = sub(p1.x, p2.x), l = mag(r);
      const normal = norm(r);
      const d = dot(velocities[points.indexOf(p1)], normal);
      const f = k*(l-mul(l, 0.9));
      velocities[points.indexOf(p1)] = add(velocities[points.indexOf(p1)],
        mul(f*normal[0], [normal[0], normal[1]]));
      velocities[points.indexOf(p2)] = sub(mul(d*normal[0],
        [normal[0], normal[1]]), mul(f*normal[0], [normal[0], normal[1]]));
    }
  }
  for (let i = 0; i < points.length; i++) {
    velocities[i] = add(velocities[i], gravity);
  }
  const wind_vec = wind(mul(wind_magnitude, [0.5, -0.25]));
  for (let i = 0; i < points.length; i++)
    velocities[i] = add(velocities[i], wind_vec);
  let new_positions = [];
  for (let i = 0; i < points.length; i++) {
    new_positions.push(add(points[i],
      mul(dt, velocities[points.indexOf(points[i])])));
  }
  return {new_positions: new_positions};
}

function constrain() {
  const pos = integrate(springConstant);
  const new_points = pos.new_positions;
  new_points.forEach((p1, i) => points.splice(i, 1, p1));
}
let t = 0;
let running = true;

requestAnimationFrame(main_loop);

function main_loop() {
  if (!running)
    return;
  constrain();
  ctx.clearRect(0, 0, c.width, c.height);
  let wind_magnitude_text = "Wind: "+Math.round(wind_magnitude*10)/10+"%";
  ctx.font="24px Arial";
  ctx.textAlign='left';
  ctx.textBaseline='top';
  ctx.fillStyle = '#000';
  ctx.fillText(wind_magnitude_text, 20, 30);
  for (let i = 0; i < points.length; i++) {
    const p1 = points[i];
    if (pinned_points.includes(p1))
      continue;
    const r = sub(points[(i+1)%N], p1);
    ctx.beginPath();
    ctx.moveTo(p1.x, p1.y);
    ctx.lineTo(p1.x+r[0]/mag(r)*10, p1.y+r[1]/mag(r)*10);
    ctx.stroke();
  }
  t += dt;
  wind_magnitude = Math.max(0.5, Math.min(wind_magnitude+0.001,
    wind_magnitude-0.00001));
}
</script>
</body>
</html>
```
qwen3.5:9bERRSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>Physics Cloth Simulation</title><style>body {margin:0;overflow:hidden;background:#111}canvas {display:block}</style></head><script>
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

let W, H;
const COLS = 15;
const ROWS = 10;
const P_DENSITY = 1.0; // Initial spacing
const SIM_STEPS = 6; // Constraint passes per frame
const WIND_STRENGTH_BASE = 0.02;

let pos, prevPos, isFixed, constraints, windAmplitude = 0;

function init() {
    W = canvas.width = window.innerWidth * 0.8;
    H = canvas.height = window.innerHeight * 0.7;
    canvas.style.width = W + 'px';
    canvas.style.height = H + 'px';

    pos = [];
    prevPos = [];
    isFixed = [];
    constraints = [];

    for (let r = 0; r < ROWS; r++) {
        for (let c = 0; c < COLS; c++) {
            const i = r * COLS + c;
            pos[i] = [c * P_DENSITY, r * P_DENSITY];
            prevPos[i] = [...pos[i]];
            isFixed[i] = (r === 0); // Pin the top row

            // Add constraints for neighbors (springs)
            for (let dr = -1; dr <= 1; dr++) {
                for (let dc = -1; dc <= 1; dc++) {
                    if (dr === 0 && dc === 0) continue;
                    const nr = r + dr;
                    const nc = c + dc;

                    if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS) {
                        // Ensure we only add constraints once per pair
                        if (r * COLS + c < nr * COLS + nc) {
                            constraints.push({ i: r * COLS + c, j: nr * COLS + nc });
                        }
                    }
                }
            }
        }
    }

    windAmplitude = 0; // Reset wind for next run
    animate();
}

function applyForces(dt) {
    const G = [0, 9.8]; // Gravity (downwards)
    for (let i = 0; i < pos.length; i++) {
        if (!isFixed[i]) {
            // Wind force: oscillating horizontal push
            const windX = Math.sin(pos[i][1] * 0.1 + performance.now() * 0.001) * WIND_STRENGTH_BASE * (1 + Math.cos(performance.now() * 0.0005));
            let fx = windX;
            let fy = G[1] * dt;

            // Verlet: x += (x - prevx) + a*dt^2 -> simplified for time-step integration
            pos[i][0] += (pos[i][0] - prevPos[i][0]) + fx * 0.1;
            pos[i][1] += (pos[i][1] - prevPos[i][1]) + fy * 0.1;
        }
    }
}

function solveConstraints() {
    for (let step = 0; step < SIM_STEPS; step++) {
        for (const c of constraints) {
            const i = c.i;
            const j = c.j;

            const p1 = pos[i];
            const p2 = pos[j];
            const dx = p1[0] - p2[0];
            const dy = p1[1] - p2[1];
            const dist = Math.sqrt(dx * dx + dy * dy);

            const targetDist = P_DENSITY; 
            const correctionFactor = (targetDist / dist) * 0.5;

            // Apply positional correction
            pos[i][0] += dx * correctionFactor;
            pos[i][1] += dy * correctionFactor;

            pos[j][0] -= dx * correctionFactor;
            pos[j][1] -= dy * correctionFactor;
        }
    }
}

function updatePhysics(dt) {
    // 1. Store current position as previous position
    for (let i = 0; i < pos.length; i++) {
        prevPos[i] = [...pos[i]];
    }

    // 2. Apply external forces (Gravity + Wind)
    applyForces(dt);

    // 3. Resolve constraints multiple times
    solveConstraints();
}

function drawMesh() {
    ctx.clearRect(0, 0, W, H);
    
    // Draw background/wind info
    ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
    ctx.fillRect(10, 10, 200, 30);
    ctx.fillStyle = '#fff';
    ctx.font = '14px monospace';
    ctx.fillText(`Wind Strength: ${windAmplitude.toFixed(3)}`, 12, 25);

    // Draw connections (springs)
    ctx.strokeStyle = 'rgba(100, 100, 255, 0.6)';
    ctx.lineWidth = 1;
    for (const c of constraints) {
        const i = c.i;
        const j = c.j;

        ctx.beginPath();
        ctx.moveTo(pos[i][0], pos[i][1]);
        ctx.lineTo(pos[j][0], pos[j][1]);
        ctx.stroke();
    }
}

let lastTime = 0;
function animate(time) {
    const dt = (time - lastTime) / 1000; // Time elapsed since last frame in seconds
    lastTime = time;

    updatePhysics(dt);
    drawMesh();

    requestAnimationFrame(animate);
}

window.addEventListener('resize', () => {
    W = canvas.width = window.innerWidth * 0.8;
    H = canvas.height = window.innerHeight * 0.7;
    canvas.style.width = W + 'px';
    canvas.style.height = H + 'px';
});

window.onload = init;
</script></body></html>
```
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hanging Cloth Animation</title>
<style>
body { margin: 0; overflow: hidden; }
#canvas { display: block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const cvs = document.getElementById('canvas');
cvs.width = window.innerWidth;
cvs.height = window.innerHeight;
const ctx = cvs.getContext('2d');

const cols = 15, rows = 10;
const points = [];

function initPoints() {
    for (let j = 0; j < rows; j++) {
        for (let i = 0; i < cols; i++) {
            points.push({ x: i * 40 + Math.random() * 20, y: j * 40 + Math.random() * 20 });
        }
    }
}

function drawPoints() {
    ctx.beginPath();
    for (let p of points) {
        ctx.moveTo(p.x, p.y);
        if (p.index % cols < cols - 1) ctx.lineTo(points[p.index + 1].x, points[p.index + 1].y);
        if (p.index >= cols && p.index % rows > 0) ctx.lineTo(points[p.index - cols].x, points[p.index - cols].y);
    }
    ctx.stroke();
}

function drawLines() {
    for (let i = 0; i < points.length; i++) {
        const p = points[i];
        if (p.index % cols > 0) ctx.lineTo(points[i - 1].x, points[i - 1].y);
        if (i >= cols && p.index % rows > 0) ctx.lineTo(points[i - cols].x, points[i - cols].y);
    }
}

function addForce(p, f) {
    p.vx += f.x;
    p.vy += f.y;
}

function applyGravity() {
    for (let i = 1; i < points.length - cols + 1; i++) {
        const p = points[i];
        if (i % cols !== 0 && i >= cols) addForce(p, { x: 0, y: 0.98 });
    }
}

function applyWind() {
    for (let i of points)
        addForce(i, {x: Math.sin(Date.now()/500)*0.1, y: 0})
}

function updatePoints() {
    const windStrength = Math.sin(Date.now() / 500) * 0.1;
    ctx.clearRect(0, 0, cvs.width, cvs.height);
    applyGravity();
    applyWind();

    for (let p of points) {
        if (p.index < cols)
            continue;

        const oldX = p.x, oldY = p.y;
        p.x += p.vx;
        p.y += p.vy + Math.sin(Date.now() / 500) * 0.1;

        p.vx *= 0.99;
    }

    for (let i = 0; i < points.length - cols + 1; i++) {
        const p = points[i];
        if (i % cols !== 0 && i >= cols)
            constrain(p, {x: oldX, y: oldY});
    }
}

function constrain(p, old) {
    let dx = p.x - old.x,
        dy = p.y - old.y;

    const dist = Math.sqrt(dx * dx + dy * dy);
    if (dist > 40) {
        const ratio = 40 / dist;
        p.x = old.x + dx * ratio;
        p.y = old.y + dy * ratio;
    }
}

function animate() {
    updatePoints();
    ctx.save();
    drawPoints();
    ctx.restore();
    requestAnimationFrame(animate);
}

initPoints();
for (let i = 0; i < points.length; i++)
    points[i].index = i;

animate();

</script>
</body>
</html>
```
qwq:32bBLANKLIM
Show code
<think>

Okay, let's tackle this problem. The user wants an HTML file that animates a hanging cloth using a canvas. The requirements are pretty specific, so I need to make sure I cover all points.

First, the cloth is a grid of 15x10 point masses. Each point is connected to orthogonal and diagonal neighbors with springs. The top row is pinned. The animation uses Verlet integration with multiple constraint relaxation passes. There's also wind force oscillating continuously, and the cloth should ripple. Need to draw lines or quads, show wind strength, and use requestAnimationFrame. No external libraries, so all must be vanilla JS.

Starting with the structure. The HTML file will have a canvas element. The script will handle everything. Let's outline the steps:

1. Setup canvas and context. Make sure it's responsive, but maybe just set a fixed size for simplicity, like 600x400.

2. Define the Cloth class. Each point has position, previous position (for Verlet), and whether it's pinned.

3. The grid is 15 columns (x) by 10 rows (y). The top row (y=0) is pinned. Each point connects to neighbors: up, down, left, right, and diagonals? Wait, the problem says orthogonal and diagonal neighbors. So each point connects to 8 neighbors? Or maybe each point has edges to adjacent points. Wait, need to clarify connections. The problem says "orthogonal and diagonal neighbours". So for each point, the connections would be to all 8 neighbors? But that might be too much. Maybe orthogonal (up, down, left, right) and the four diagonals? Or perhaps just the adjacent in both orthogonal and diagonally?

Hmm, the problem says "connected to their orthogonal and diagonal neighbours by spring constraints". So each point is connected to all adjacent points, including diagonally. That would mean each point (except edges) has 8 connections. But that's a lot. Maybe the problem is referring to orthogonal (orthogonal neighbors, like up, down, left, right) and diagonal (the four diagonals? Or maybe just the adjacent diagonals, like up-left, up-right, etc. But that makes 8 directions. Maybe need to clarify. Alternatively, perhaps just the adjacent points in all directions, including diagonals. Each connection is a spring.

So for the grid, each point (except edges) has 8 springs. But that could be computationally heavy. Alternatively, maybe the problem means each point is connected to their orthogonal neighbors (up, down, left, right) and their diagonal neighbors (like the four diagonals, but maybe just the adjacent ones in the grid). Let's see. The problem says "orthogonal and diagonal neighbours". So, each point is connected to all 8 neighbors? Or maybe just the orthogonal and the diagonals? Maybe it's 4 orthogonal (up, down, left, right) and 4 diagonal (up-left, up-right, etc). But perhaps it's better to think of the springs as each point connected to adjacent in all directions, including diagonals, but with the same spring constants? The problem says "spring constraints" so each connection is a spring with some rest length. The rest length would be the distance between grid points when not stretched.

So, the grid is spaced, say, 20 pixels between each point (depending on canvas size). The initial rest length is that. Wait, the grid is 15x10 points. Let's see, the canvas width is 600, so 15 columns would be 40px per column (600/15=40). Height 400, so 10 rows gives 40px per row. So each point is spaced 40px in x and y directions. So the rest length between adjacent points (orthogonal) is 40px, and diagonals would have sqrt(40² +40²).

So the cloth class will need to initialize these points, set up the springs (constraints). The Verlet integration is done by updating the positions based on the previous position and acceleration (gravity and wind). Then, the constraints are applied multiple times per frame to satisfy them.

Verlet integration formula: new_position = 2*current_position - previous_position + acceleration * dt². But since we're using fixed time steps (using requestAnimationFrame, which is roughly 1/60 per frame?), but maybe we can simplify by assuming delta time is 1, or adjust. Maybe for simplicity, the delta time is 1, so acceleration (like gravity) is applied as a force, divided by mass. Wait, Verlet is typically for constraints, and acceleration is added as a vector.

Wait, the standard Verlet is:

positionNext = positionCurrent * 2 - positionPrevious + (acceleration * dt^2)

So for gravity, the acceleration would be gravity (like [0, 9.8] or whatever), but scaled by dt squared. But in the problem's context, maybe we can use a simplified version where gravity is a constant acceleration, and wind is a force applied each frame.

Alternatively, the problem might have the cloth points treated as particles with Verlet integration, and the constraints are resolved after each step. So the steps would be:

1. Apply external forces (wind and gravity) to compute the acceleration.

But in Verlet, acceleration is applied as part of the position update. Wait, maybe first compute the new position via Verlet, then apply constraints.

Alternatively, the standard approach for Verlet with constraints is:

for each particle:
    if not pinned:
        new_position = 2 * current_position - previous_position + (acceleration * dt^2)
    else:
        new_position = current_position (or whatever the pin requires)

Then, apply constraints (springs) multiple times.

But in this case, the acceleration is from gravity and wind.

Wait, perhaps better to think of it as:

Each frame:

- Compute the acceleration (gravity + wind) for each particle.

- Update the position using Verlet:

new_pos = 2 * current_pos - prev_pos + acceleration * dt^2

- Then, apply constraints (springs) several times (multiple passes) to enforce the distances between connected points.

The wind is a continuous force. The problem says it's a small oscillating wind. So perhaps the force is sinusoidal over time. The wind strength is applied as a horizontal force, varying with sin(time) or something. The current wind strength is displayed, so maybe show a number in the canvas.

The problem says to draw the mesh as connected lines or shaded quads. Maybe lines are easier, connecting each point to its neighbors. Alternatively, quads could be drawn as triangles or rectangles, but lines might be simpler.

Let me outline the steps in code.

First, setup the canvas:

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

Then the script:

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

Set canvas size. Let's set 600x400, so in HTML:

<canvas id="canvas" width="600" height="400"></canvas>

Then the cloth class.

Cloth {

    constructor() {
        this.particles = [];
        this.constraints = [];
        this.width = 15; // columns
        this.height = 10; // rows
        this.spacing = 40; // so 600 /15=40, 400/10=40

        // Initialize particles
        for (var y=0; y<this.height; y++) {
            for (var x=0; x<this.width; x++) {
                let p = new Particle(x * this.spacing, y * this.spacing);
                if (y ==0) p.pinned = true; // top row pinned
                this.particles.push(p);
            }
        }

        // Create constraints between all points and their neighbors
        for (var i=0; i<this.particles.length; i++) {
            var p = this.particles[i];
            // Check all neighbors (orthogonal and diagonal)
            for (var dx=-1; dx<=1; dx++) {
                for (var dy=-1; dy<=1; dy++) {
                    if (dx ==0 && dy ==0) continue; // skip self
                    // find neighbor index
                    let neighborIndex = ...;
                    // calculate neighbor's position in the grid
                    // current x,y of p: x = (i % width), y = (i / width) |0
                    let x = i % this.width;
                    let y = (i / this.width)|0;
                    let nx =x + dx;
                    let ny = y + dy;
                    if (nx >=0 && nx < this.width && ny >=0 && ny < this.height) {
                        let neighborIndex = ny * this.width + nx;
                        this.constraints.push(new Constraint(p, this.particles[neighborIndex], this.spacing * Math.sqrt(dx*dx + dy*dy)));
                    }
                }
            }
        }
    }
}

Wait, but this would create constraints for all 8 directions, but each constraint is between the current particle and the neighbor. However, this would create duplicates because when i and j are neighbors, both (i,j) and (j,i) would be checked. To avoid duplicates, perhaps only check in one direction, e.g., only for dx <=0 or dy <=0, but that might be complicated. Alternatively, since constraints are bidirectional, maybe it's okay to have duplicates as each pair is added twice. Hmm. Alternatively, perhaps just connect each particle to its right and bottom neighbor, and diagonals, so that each connection is only once. Maybe this is manageable. Alternatively, the problem states that the constraints are between orthogonal and diagonal neighbors, so each particle is connected to all 8 neighbors, but in the code, when we loop over all dx and dy (from -1 to 1, excluding 0,0), then for each direction, we add the constraint if that neighbor exists. But each connection will be added twice (once from each particle). That's okay, but it's redundant. Alternatively, to prevent duplicates, but maybe it's not a big deal for a small grid.

But the problem says "connected to their orthogonal and diagonal neighbours". So each particle is connected to all adjacent particles in all 8 directions? That would be a lot of constraints. For 15x10 grid, each interior particle has 8 neighbors, so total constraints would be roughly (15-1)*(10-1)*8? Hmm, but maybe it's manageable.

Alternatively, the problem might have meant orthogonal (up, down, left, right) and diagonal (the four diagonals), but in the grid, each particle is connected to those. So each particle has up to 8 neighbors, but not sure if that's the case.

Alternatively, perhaps the problem means each particle is connected to orthogonal (non-diagonal) and diagonal neighbors. So for example, each particle is connected to the adjacent in all directions, including diagonally. So each connection is a spring with rest length of sqrt( (spacing)^2 + (spacing)^2 ) for diagonals. But in that case, the rest length would vary depending on direction.

Alternatively, perhaps the rest length for orthogonal connections is spacing (40) and diagonal is spacing*sqrt(2) (approx 56.56). That would make sense.

But when creating the constraints, the rest length can be computed based on dx and dy.

So in the code, for each dx and dy (from -1 to 1), except (0,0):

distance between neighbor and current is sqrt( dx^2 + dy^2 ) * spacing.

So the rest length is that distance.

So when creating the constraint between p and neighbor:

var dx = (nx - x);

var dy = (ny - y);

var restLength = this.spacing * Math.sqrt( (dx)^2 + (dy)^2 );

Wait, but dx and dy here are in grid steps (since nx is x + dx_grid, where dx_grid could be -1,0,1). So since each grid step is spacing in pixels, the actual rest length is spacing * sqrt(dx_grid² + dy_grid²).

So that's how the rest length is computed.

Now, the constraints array holds all the connections between particles.

Once that's setup, the simulation proceeds.

Now, Verlet integration:

In each step (each frame):

- Compute the acceleration (wind + gravity)

- Update each particle's position via Verlet.

- Apply constraints multiple times (relaxation passes) to enforce the spring constraints.

Gravity is a downward acceleration. Wind is a horizontal oscillating force.

So each particle has a force from wind and gravity.

But in Verlet, the acceleration is added to the position update.

Wait, the standard Verlet step is:

newPos = 2 * currPos - prevPos + acceleration * dt^2;

Assuming the acceleration is the net force divided by mass, but since we can assume mass=1 and dt fixed.

Assuming the time step is 1 (since delta time in requestAnimationFrame can vary, but for simplicity, let's assume dt=1 and acceleration is the force).

Wait, perhaps the problem allows us to simplify by using dt=1, so acceleration is just the net force (wind + gravity).

Alternatively, since the user just wants it to work, maybe we can set the gravity as a fixed value, like 0.5, and wind as varying.

So for each particle:

if not pinned:

new_pos = 2 * current_pos - previous_position + (gravity + wind_acceleration) * dt²

But since we can set dt to 1, then:

new_pos = 2*current - prev + acceleration.

Wait, perhaps the problem can be simplified by assuming delta squared is 1 (dt=1), so acceleration is added directly:

newPos = 2 * current - prev + acceleration;

where acceleration is (gravity + wind) applied as a vector.

Gravity is a vector (0, 0.5) perhaps. Wind is a horizontal sinusoidal force varying over time.

So, wind strength is a variable that oscillates, say between -0.1 and 0.1, and the actual force is wind_strength * some factor.

So, the wind_acceleration would be (wind_strength * some value, 0).

Now, the constraints are handled by relaxing each spring:

For each constraint between two particles:

the vector between them is current_pos_b - current_pos_a

the distance is the length of this vector.

the desired distance is the rest length (from when they were created)

the difference is (distance - restLength)/distance * 0.5 (some fraction)

then, each particle is moved towards/away from each other by this delta.

But the exact method depends on the constraint relaxation.

The standard approach for constraint relaxation with Verlet is to compute the current distance between two connected particles, then adjust their positions to meet the rest length.

The process for each constraint:

var a = constraint.p1;

var b = constraint.p2;

var dx = b.x - a.x;

var dy = b.y - a.y;

var dist = Math.sqrt(dx*dx + dy*dy);

var delta = (dist - constraint.restLength) / dist;

var adjust = delta * 0.5; // half of the delta for each particle.

// move a and b towards each other (if dist > restLength) or away

a.x -= dx * adjust;

a.y -= dy * adjust;

b.x += dx * adjust;

b.y += dy * adjust;

But this is for a single constraint. We have to apply this for every constraint multiple times per frame to let the constraints propagate.

Typically, multiple passes (like 3-5) are done per frame to better satisfy all constraints.

So, in code:

function applyConstraints() {

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

        var constraint = this.constraints[i];

        var a = constraint.a;

        var b = constraint.b;

        // if either is pinned, skip (since pinned particles shouldn't move)

        if (a.pinned || b.pinned) continue;

        var dx = b.x - a.x;

        var dy = b.y - a.y;

        var dist = Math.sqrt(dx*dx + dy*dy);

        if (dist ==0) continue; // prevent division by zero

        var rest = constraint.restLength;

        var delta = (dist - rest) / dist;

        // adjust both particles

        var adjust = delta * 0.5; // half the delta to each

        a.x -= dx * adjust;

        a.y -= dy * adjust;

        b.x += dx * adjust;

        b.y += dy * adjust;

    }

}

But this would be applied multiple times (like 10 passes?), but each pass goes through all constraints. Alternatively, multiple relaxation passes (like 3-5 times).

Now, the steps for each frame:

1. Update positions via Verlet (applying gravity and wind):

for each particle:

    if (!p.pinned) {

        var oldPos = p.prevPos;

        var newPos = {

            x: 2 * p.pos.x - oldPos.x + (wind.x + gravity.x),

            y: 2 * p.pos.y - oldPos.y + (wind.y + gravity.y)

        };

        p.prevPos = p.pos;

        p.pos = newPos;

    }

Wait, but wind and gravity are vectors. So need to track velocity or acceleration. Wait, in the Verlet step, the acceleration is added as (acceleration * dt²), but if dt is 1, then acceleration is added directly. So, the acceleration is (gravity + wind). So the code would be:

var ax = gravity.x + wind.x;

var ay = gravity.y + wind.y;

newPos.x = 2 * current.x - prev.x + ax;

newPos.y = 2 * current.y - prev.y + ay;

Thus, in code:

for each particle:

if (p.pinned) continue;

var ax = gravity.x + wind.x;

var ay = gravity.y + wind.y;

var newx = 2*p.pos.x - p.prev.x + ax;

var newy = 2*p.pos.y - p.prev.y + ay;

p.prev.x = p.pos.x;

p.pos.x = newx;

Similarly for y.

Wait, but in code:

assuming the particle has a position and previous position:

p.pos: current position

p.prevPos: previous position (before current step)

So after the Verlet step:

new_x = 2 * p.pos.x - p.prevPos.x + ax;

new_y = 2 * p.pos.y - p.prevPos.y + ay;

then set p.prevPos to p.pos, then p.pos to new_x/y.

Wait, the order is:

prevPos is the position from the previous frame, before updating.

After computing new_x and new_y,

p.prevPos becomes the current pos (before update),

then p.pos becomes new_x/y.

Wait, maybe:

temp_prev = p.prev;

new_x = ...;

p.prev = p.pos;

p.pos = new_x;

Yes.

So in code:

for each particle:

if(p.pinned) continue;

// compute new position via Verlet

var ax = wind.x + gravity.x;

var ay = wind.y + gravity.y;

var newx = 2 * p.pos.x - p.prev.x + ax;

var newy = 2 * p.pos.y - p.prev.y + ay;

// update previous and current

p.prev.x = p.pos.x;

p.prev.y = p.pos.y;

p.pos.x = newx;

p.pos.y = newy;

Then, after this, apply constraints multiple times.

Then, after all that, draw the particles connected by constraints.

The wind's strength should be an oscillating value. Let's say it's controlled by a sine wave:

var time = currentAnimationFrame * timeStep;

var wind_strength = Math.sin(time * wind_frequency) * wind_amplitude;

where wind_amplitude is the maximum strength (like 0.1), and frequency controls how fast it oscillates.

Alternatively, since time can be tracked via the frame count.

Alternatively, just use a simple oscillation with a time variable that increments each frame.

Now, the drawing part:

for each constraint, draw a line between the two particles' positions.

Now, putting all together.

Now, let's structure this in code steps.

First, define the Particle class:

class Particle {

    constructor(x, y) {

        this.pos = {x: x, y: y};

        this.prev = {x: x, y: y}; // previous position

        this.pinned = false;

    }

}

Constraint class:

class Constraint {

    constructor(a, b, restLength) {

        this.a = a;

        this.b = b;

        this.restLength = restLength;

    }

}

Now, the Simulation class:

class Simulation {

    constructor() {

        this.particles = [];

        this.constraints = [];

        this.createGrid(); // create grid of particles with constraints

        this.gravity = {x:0, y: 0.5};

        this.wind = {x:0, y:0};

        this.wind_amplitude = 0.1;

        this.wind_freq = 0.5; // adjust this for oscillation speed

        this.time = 0;

    }

    createGrid() {

        // create a 15x10 grid (assuming width 15, height 10)

        var spacing = 20; // pixel spacing

        for (var y=0; y<10; y++) {

            for (var x=0; x<15; x++) {

                var px = x * spacing;

                var py = y * spacing;

                var p = new Particle(px, py);

                this.particles.push(p);

            }

        }

        // connect each particle to all 8 neighbors (diagonal included)

        for (var y=0; y<10; y++) {

            for (var x=0; x<15; x++) {

                var p = this.particles[y*15 + x];

                for (var dy=-1; dy<=1; dy++) {

                    for (var dx=-1; dx<=1; dx++) {

                        if (dx ==0 && dy ==0) continue; // skip self

                        var nx = x + dx;

                        var ny = y + dy;

                        if (nx >=0 && nx <15 && ny >=0 && ny <10) {

                            var other = this.particles[ny*15 + nx];

                            // compute rest length

                            var dx_grid = dx;

                            var dy_grid = dy;

                            var restLength = spacing * Math.sqrt( dx_grid*dx_grid + dy_grid*dy_grid );

                            this.constraints.push( new Constraint(p, other, restLength) );

                        }

                    }

                }

            }

        }

        // pin the first row? Or maybe not, let them all move except some?

        // maybe pin the top row or bottom row to prevent the whole thing from flying away.

        // let's pin the top row (y=0):

        for (var x=0; x<15; x++) {

            this.particles[x].pinned = true;

        }

    }

    update() {

        // compute wind

        this.time += 0.1; // increment time for wind simulation

        var wind_angle = Math.PI / 2; // horizontal wind

        this.wind.x = this.wind_amplitude * Math.sin( this.time * this.wind_freq );

        this.wind.y = 0;

        // apply Verlet integration

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

            var p = this.particles[i];

            if (p.pinned) continue;

            var ax = this.gravity.x + this.wind.x;

            var ay = this.gravity.y + this.wind.y;

            // compute new position

            var newx = 2 * p.pos.x - p.prev.x + ax;

            var newy = 2 * p.pos.y - p.prev.y + ay;

            // update positions

            p.prev.x = p.pos.x;

            p.prev.y = p.pos.y;

            p.pos.x = newx;

            p.pos.y = newy;

        }

        // apply constraints multiple times for relaxation

        for (var pass=0; pass<5; pass++) {

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

                var c = this.constraints[i];

                var a = c.a;

                var b = c.b;

                if (a.pinned || b.pinned) continue;

                var dx = b.pos.x - a.pos.x;

                var dy = b.pos.y - a.pos.y;

                var dist = Math.sqrt( dx*dx + dy*dy );

                if (dist ==0) continue;

                var rest = c.restLength;

                var delta = (dist - rest)/dist;

                var adjust = delta * 0.5;

                a.pos.x -= dx * adjust;

                a.pos.y -= dy * adjust;

                b.pos.x += dx * adjust;

                b.pos.y += dy * adjust;

            }

        }

    }

    draw(ctx) {

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

        ctx.beginPath();

        // draw all constraints as lines between particles

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

            var c = this.constraints[i];

            ctx.moveTo(c.a.pos.x, c.a.pos.y);

            ctx.lineTo(c.b.pos.x, c.b.pos.y);

        }

        ctx.stroke();

        // draw particles as small dots

        ctx.fillStyle = 'black';

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

            var p = this.particles[i];

            ctx.beginPath();

            ctx.arc(p.pos.x, p.pos.y, 2, 0, 2*Math.PI);

            ctx.fill();

        }

    }

}

Then, in the main loop:

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

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

var sim = new Simulation();

function animate() {

    sim.update();

    sim.draw(ctx);

    requestAnimationFrame(animate);

}

animate();

But we need to set the canvas size appropriately. Let's set it to 15*spacing (say spacing=20) so width=300, height=200.

canvas.width = 15*20;

canvas.height = 10*20;

Putting all together, this should create a grid of particles connected by springs, with the top row pinned, gravity pulling down, and wind oscillating horizontally.

The key points here are:

- The grid is created with particles connected to all 8 neighbors, with rest lengths adjusted for diagonal connections.

- Verlet integration is used with gravity and wind acceleration.

- Multiple constraint relaxation passes are done to resolve the springs.

- The top row is pinned to prevent the structure from falling off the screen.

Testing this code might require adjustments, like setting the canvas size correctly and tweaking parameters such as gravity strength, wind_amplitude, and wind_freq for better visualization.

Additionally, in the code above, when creating the constraints, each pair of particles is connected twice (once from each side). To avoid duplicate constraints, perhaps we can only create a constraint between a particle and the particles to its right and below (or some direction), but since the constraints are bidirectional, it's manageable, even if redundant.

Another possible issue is the initial distance between particles and the rest length. The restLength is computed based on the grid steps, which should be correct.

Finally, the wind is applied via a sine wave, so the amplitude can be adjusted. The frequency controls how fast the wind oscillates. The code uses the 'time' variable incremented each frame, scaled by a factor.

This should create a simulation where the grid oscillates horizontally due to wind and sags under gravity, with the top pinned in place.
command-r:35bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Hanging Cloth Animation</title>
    <style>
      body {
        margin: 0;
        overflow: hidden;
        display: flex;
        justify-content: center;
        align-items: center;
      }

      #canvas {
        border: 1px solid black;
      }
    </style>
  </head>

  <body>
    <canvas id="canvas" width="600" height="400"></canvas>

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

      let points = [];
      let windStrength = 0;
      let windDir = -1; // -1 to right, +1 to left
      let gravity = 0.05;
      let damping = 0.98;

      function createPoints() {
        const width = canvas.width;
        const height = canvas.height;

        for (let i = 0; i < 20; i++) {
          points[i] = [];

          // Top row is pinned to form anchor point
          if (i === 0) {
            points[i].push(new Point(width / 4, height - 15));
            for (let j = 1; j < width / 2; j++)
              points[i].push(
                new Point((j + i * (width / 2)), height - 10)
              );
          } else {
            let startX = i === 1 ? Math.random() * 50 : points[i-1][points[i-1].length-1].x;
            for (let j = 0; j < width / 2 + 30*Math.random(); j++)
              points[i].push(new Point(startX, height - Math.random() * 5));
          }
        }
      }

      class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
          this.prevX = x;
          this.prevY = y;
          this.vx = 0;
          this.vy = 0;
        }

        update() {
          const dx = this.x - this.prevX;
          const dy = this.y - this.prevY;

          if (this.vx > windStrength) this.vx -= damping * Math.random();
          else                       this.vx += damping * Math.random();
          
          // Verlet integration with constraint-relaxation passes
          this.x = 2 * this.prevX - this.prevPrevX + dx + this.vx;
          this.y = 2 * this.prevY - this.prevPrevY + dy + this.vy;

          if (this.y > canvas.height) {
            this.y = canvas.height;
            this.vy = -this.vy * damping;
          }
        }

        render() {
          ctx.beginPath();
          ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
          ctx.fillStyle = "black";
          ctx.fill();
          ctx.closePath();
        }
      }

      function drawLines() {
        for (let i = 1; i < points.length; i++) {
          const prevRow = points[i-1];

          for (let j = 0; j < points[i].length; j++) {
            ctx.beginPath();
            ctx.moveTo(points[i][j].x, points[i][j].y);

            // Connect to previous row
            if (j > 0)
              ctx.lineTo(prevRow[j-1].x, prevRow[j-1].y);
            
            // Connect to next row
            if (j < points[i].length - 1 && i !== points.length - 1) {
              ctx.lineTo(points[i+1][j].x, points[i+1][j].y);
            }

            ctx.strokeStyle = "black";
            ctx.stroke();
            ctx.closePath();
          }
        }
      }

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

        for (let i = 1; i < points.length - 1; i++) {
          const prevRow = points[i-1];

          for (let j = 1; j < points[i].length - 1; j++) {
            // Apply constraints and springs between points
            const dx =
              +(points[i][j].x - points[i][j].prevX) * 0.35
              +(points[i-1][j].x - prevRow[j+1].x) * 0.12;

            const dy = 
              +(points[i][j].y - points[i][j].prevY) * 0.35
              +(prevRow[j].y - points[i-1][j+1].y) * 0.12
              + gravity;
            
            // Add wind force
            if (windDir === -1 && points[i][j].x > canvas.width / 4) {
              const maxWind = windStrength * 3;
              dy += Math.max(Math.random() * maxWind, maxWind/2);
            } else if (windDir === +1 && points[i][j].x < 3 * canvas.width / 4) {
              const maxWind = windStrength * 5;
              dy -= Math.max(Math.random() * maxWind - maxWind * 0.7, maxWind);
            }

            // Apply forces to point
            points[i][j].vx += dx;
            points[i][j].vy += dy;
          }
        }

        for (let i = 1; i < points.length; i++) {
          const prevRow = points[i-1];

          for (let j = 0; j < points[i].length; j++) {
            // Constraint relaxation to keep fabric taut
            if (j > 0)
              this.distance(points[i][j], prevRow[j-1]) <= 35
                ? points[i][j].x += dx/2 : points[i][j].y += dy/2;
            
            // Connect to next row
            if (j < points[i].length - 1 && i !== points.length - 1) {
              this.distance(points[i][j], points[i+1][j]) <= 35
                ? points[i][j].x -= dx/2 : points[i][j].y -= dy/2;
            }
          }
        }

        for (let i = 0; i < points.length; i++) {
          for (let j = 0; j < points[i].length; j++) {
            // Verlet integration update
            points[i][j].prevPrevX = points[i][j].prevX;
            points[i][j].prevPrevY = points[i][j].prevY;

            // Update and render each point
            points[i][j].update();
            points[i][j].render();
          }
        }

        drawLines();

        requestAnimationFrame(animate);
      }

      function randomizeWind() {
        windStrength = Math.random() * 0.3 + 0.1; // Wind strength between 0.1 and 0.4
        windDir *= -1; // Random direction left or right
      }

      createPoints();
      animate();
      setInterval(randomizeWind, 250);
    </script>
  </body>
</html>
```
deepseek-r1:32bBLANKFORMAT
Show code
```html
<!DOCTYPE html><html><body style='margin:0;display:flex;justify-content:center;background:#1a1a1a'><div id="wind" style="position:absolute;top:20px;left:20px;color:white"></div><canvas id="c"></canvas>
<script>
class Cloth{
constructor(w=15,h=10,d=20){
this.w=w;this.h=h;this.d=d;
this.particles=[];this.springs=[];
for(let y=0;y<h;y++)
for(let x=0;x<w;x++){
let p={
x:x*d+10,
y:y*d+10,
vx:0,
vy:0,
pinned:y==0
};
this.particles.push(p);
}
for(let i=0;i<this.particles.length;i++){
let p=this.particles[i];
for(let dy=-1;dy<=1;dy++)
for(let dx=-1;dx<=1;dx++){
if(dx||dy){
let j=i+dx+(dy*this.w);
if(j>=0&&j<w*h&&Math.abs(dx)+Math.abs(dy)<=2){
let other=this.particles[j];
if(i<j&&!this.springs.some(s=>s.i==i&&s.j==j))
this.springs.push({i,j,rest:d*Math.sqrt(dx*dx+dy*dy)})
}
}
}
}
}
update(dt){
this.applyWind();
this.applyGravity();
this.solveConstraints();
this.particles.forEach(p=>{
if(!p.pinned){
let nx=p.x+p.vx*dt;
let ny=p.y+p.vy*dt;
p.vx=(nx-p.x)/dt;
p.vy=(ny-p.y)/dt;
p.x=nx;p.y=ny;
}
});
}
applyWind(){
let t=Date.now()/1000;
let strength=0.2*(1+Math.sin(t*0.5))/2;
this.windStrength=strength;
this.particles.forEach(p=>{
if(!p.pinned){
p.vx += Math.sin(t)*strength;
p.vy += Math.cos(t)*strength/2;
}
});
}
applyGravity(){
this.particles.forEach(p=>{
if(!p.pinned)p.vy+=0.1;
});
}
solveConstraints(iterations=4){
for(let i=0;i<iterations;i++)
this.springs.forEach(s=>{
let p1=this.particles[s.i];
let p2=this.particles[s.j];
if(p1.pinned||p2.pinned)return;
let dx=p2.x-p1.x;
let dy=p2.y-p1.y;
let dist=Math.sqrt(dx*dx+dy*dy);
let diff=(dist-s.rest)/10;
p1.x-=dx*diff;
p1.y-=dy*diff;
p2.x+=dx*diff;
p2.y+=dy*diff;
});
}
draw(ctx){
this.particles.forEach((p,i)=>{
let x=p.x,y=p.y;
ctx.beginPath();
for(let dy=-1;dy<=0;dy++)
for(let dx=0;dx<=1;dx++){
let j=i+dx+(dy*this.w);
if(j>=0&&j<i){
let p2=this.particles[j];
ctx.moveTo(x,y);
ctx.lineTo(p2.x,p2.y);
}
}
});
ctx.strokeStyle='#fff';ctx.lineWidth=1;ctx.stroke();
}
}

let canvas=document.getElementById('c');
canvas.width=700;
canvas.height=500;
let ctx=canvas.getContext('2d');
let cloth=new Cloth(15,10);
function animate(){
requestAnimationFrame(animate);
cloth.update(0.016);
ctx.clearRect(0,0,canvas.width,canvas.height);
cloth.draw(ctx);
document.getElementById('wind').textContent=`Wind: ${(cloth.windStrength*2).toFixed(3)}`;
}
animate();
</script></body></html>
```
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
Claude Opus 5frontier referenceOK
Show code
<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Hanging Cloth</title><style>html,body{margin:0;height:100%;background:radial-gradient(circle at 50% 15%,#18243a,#04060b);overflow:hidden}canvas{display:block}#h{position:fixed;left:14px;top:12px;font:12px/1.6 ui-monospace,SFMono-Regular,monospace;color:#8fb4e0;letter-spacing:.08em;text-transform:uppercase}#h b{color:#fff;font-size:15px}#m{width:130px;height:5px;background:#16202f;border-radius:3px;overflow:hidden;margin-top:5px}#mf{height:100%;width:0;background:linear-gradient(90deg,#3aa8ff,#7df0c0);transition:width .1s}</style></head><body><canvas id=c></canvas><div id=h>Wind <b id=w>0.00</b><div id=m><div id=mf></div></div><span id=d></span></div><script>
var c=document.getElementById('c'),g=c.getContext('2d'),W=document.getElementById('w'),M=document.getElementById('mf'),D=document.getElementById('d');
var P,C,T,LIFE,pr,CO=15,RO=10,SP,HU,wind=0;
function rnd(a,b){return a+Math.random()*(b-a)}
function init(){c.width=innerWidth;c.height=innerHeight;T=0;LIFE=rnd(1100,1800);HU=rnd(170,340)|0;
SP=Math.min(c.width/(CO+4),c.height/(RO+6));
var ox=(c.width-(CO-1)*SP)/2,oy=c.height*0.13,i,j;P=[];
for(j=0;j<RO;j++)for(i=0;i<CO;i++){var X=ox+i*SP,Y=oy+j*SP;P.push({x:X,y:Y,px:X,py:Y,ax:X,ay:Y,p:j===0})}
C=[];var id=function(a,b){return b*CO+a},ad=function(a,b){var A=P[a],B=P[b];C.push([a,b,Math.hypot(A.x-B.x,A.y-B.y)])};
for(j=0;j<RO;j++)for(i=0;i<CO;i++){if(i<CO-1)ad(id(i,j),id(i+1,j));if(j<RO-1)ad(id(i,j),id(i,j+1));if(i<CO-1&&j<RO-1){ad(id(i,j),id(i+1,j+1));ad(id(i+1,j),id(i,j+1))}}
pr={a:rnd(.30,.72),f:rnd(.013,.028),k:rnd(.005,.014),g:rnd(.30,.46),s:rnd(.004,.009),q:rnd(.9,1.9)};}
function step(){T++;var n,k,p;
var base=pr.a*(.55+.45*Math.sin(T*pr.s)+.15*Math.sin(T*pr.s*3.1));wind=Math.abs(base);
for(n=0;n<P.length;n++){p=P[n];if(p.p){p.x=p.ax;p.y=p.ay;p.px=p.ax;p.py=p.ay;continue}
var wx=base*(Math.sin(T*pr.f+p.ay*pr.k)+.6*Math.sin(T*pr.f*1.7+p.ax*pr.k*.8)),
wy=base*.32*Math.sin(T*pr.f*pr.q+p.ax*pr.k);
var vx=(p.x-p.px)*.992,vy=(p.y-p.py)*.992;p.px=p.x;p.py=p.y;p.x+=vx+wx;p.y+=vy+pr.g+wy}
for(k=0;k<6;k++)for(n=0;n<C.length;n++){var q=C[n],A=P[q[0]],B=P[q[1]];if(A.p&&B.p)continue;
var dx=B.x-A.x,dy=B.y-A.y,d=Math.sqrt(dx*dx+dy*dy)||1e-4,f=(d-q[2])/d;dx*=f;dy*=f;
var wa=A.p?0:B.p?1:.5,wb=1-wa;A.x+=dx*wa;A.y+=dy*wa;B.x-=dx*wb;B.y-=dy*wb}}
function draw(){var i,j,n;g.clearRect(0,0,c.width,c.height);
for(j=0;j<RO-1;j++)for(i=0;i<CO-1;i++){var a=P[j*CO+i],b=P[j*CO+i+1],e=P[(j+1)*CO+i+1],o=P[(j+1)*CO+i];
var w=Math.hypot(b.x-a.x,b.y-a.y)/SP,h=Math.hypot(o.x-a.x,o.y-a.y)/SP;
var l=Math.max(0,Math.min(1,(w*.75+h*.25-.45)*1.35));
g.fillStyle='hsl('+(HU+j*2.2)+',52%,'+(11+l*48)+'%)';
g.beginPath();g.moveTo(a.x,a.y);g.lineTo(b.x,b.y);g.lineTo(e.x,e.y);g.lineTo(o.x,o.y);g.closePath();g.fill()}
g.strokeStyle='rgba(255,255,255,.12)';g.lineWidth=1;g.beginPath();
for(j=0;j<RO;j++)for(i=0;i<CO;i++){var p=P[j*CO+i];if(i<CO-1){var r=P[j*CO+i+1];g.moveTo(p.x,p.y);g.lineTo(r.x,r.y)}if(j<RO-1){var s=P[(j+1)*CO+i];g.moveTo(p.x,p.y);g.lineTo(s.x,s.y)}}
g.stroke();
g.fillStyle='#e9f2ff';for(i=0;i<CO;i++){var t=P[i];g.fillRect(t.x-2.5,t.y-2.5,5,5)}}
function loop(){step();draw();W.textContent=wind.toFixed(2);
M.style.width=Math.min(100,wind/.8*100)+'%';
D.textContent='verlet '+CO+'x'+RO+' / 6 passes';
if(T>LIFE)init();requestAnimationFrame(loop)}
addEventListener('resize',init);init();loop();
</script></body></html>
GPT-5.5 (Codex)frontier referenceOKMINIFIED
Show code
<!doctype html><html><head><meta charset=utf-8><title>Cloth</title><style>html,body{margin:0;height:100%;overflow:hidden;background:#101417;color:#e9f2f0;font:16px system-ui}canvas{display:block}#w{position:fixed;left:16px;top:14px;padding:8px 11px;background:#0008;border:1px solid #fff2;border-radius:6px}</style></head><body><canvas id=c></canvas><div id=w></div><script>
let cv=c,ctx=cv.getContext('2d'),out=w,W,H,pts,sticks,t=0,seed=Math.random()*9,cols=15,rows=10,space=32,g=.23;
function rnd(){seed=(seed*9301+49297)%233280;return seed/233280}
function size(){W=cv.width=innerWidth;H=cv.height=innerHeight;init()}onresize=size;
function init(){pts=[];sticks=[];let ox=W/2-(cols-1)*space/2,oy=55;for(let y=0;y<rows;y++)for(let x=0;x<cols;x++){let px=ox+x*space+(rnd()-.5)*3,py=oy+y*space+(rnd()-.5)*3;pts.push({x:px,y:py,px,py,p:y==0})}let add=(a,b)=>{let A=pts[a],B=pts[b],dx=A.x-B.x,dy=A.y-B.y;sticks.push([a,b,Math.hypot(dx,dy)])};for(let y=0;y<rows;y++)for(let x=0;x<cols;x++){let i=y*cols+x;if(x<cols-1)add(i,i+1);if(y<rows-1)add(i,i+cols);if(x<cols-1&&y<rows-1){add(i,i+cols+1);add(i+1,i+cols)}}}
function step(){t+=.016;let wind=Math.sin(t*2.1)*.33+Math.sin(t*.7)*.18;for(let p of pts){if(p.p)continue;let vx=(p.x-p.px)*.992,vy=(p.y-p.py)*.992;p.px=p.x;p.py=p.y;p.x+=vx+wind*(.35+.65*Math.sin(p.y*.045+t*3));p.y+=vy+g}for(let k=0;k<7;k++){for(let s of sticks){let a=pts[s[0]],b=pts[s[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1,m=(d-s[2])/d*.5,ox=dx*m,oy=dy*m;if(!a.p){a.x+=ox;a.y+=oy}if(!b.p){b.x-=ox;b.y-=oy}}for(let x=0;x<cols;x++){let p=pts[x],ox=W/2-(cols-1)*space/2;p.x=ox+x*space;p.y=55}}draw(wind);out.textContent='wind '+wind.toFixed(2);requestAnimationFrame(step)}
function draw(wind){ctx.clearRect(0,0,W,H);let grd=ctx.createLinearGradient(0,40,0,H);grd.addColorStop(0,'#172027');grd.addColorStop(1,'#091014');ctx.fillStyle=grd;ctx.fillRect(0,0,W,H);for(let y=0;y<rows-1;y++)for(let x=0;x<cols-1;x++){let a=pts[y*cols+x],b=pts[y*cols+x+1],d=pts[(y+1)*cols+x],e=pts[(y+1)*cols+x+1],sh=((b.x-a.x)+(e.x-d.x))/70;ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.lineTo(e.x,e.y);ctx.lineTo(d.x,d.y);ctx.closePath();ctx.fillStyle='hsl('+(185+wind*35)+',55%,'+(38+sh*18+y*2)+'%)';ctx.fill()}ctx.strokeStyle='#d8fffa99';ctx.lineWidth=1.1;ctx.beginPath();for(let s of sticks){let a=pts[s[0]],b=pts[s[1]];ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y)}ctx.stroke();ctx.fillStyle='#fff';for(let x=0;x<cols;x++){let p=pts[x];ctx.beginPath();ctx.arc(p.x,p.y,3,0,7);ctx.fill()}}
size();requestAnimationFrame(step);
</script></body></html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Cloth</title><style>html,body{margin:0;overflow:hidden;background:#0d1117}canvas{display:block}#w{position:fixed;top:10px;left:12px;color:#dfe6ee;font:14px monospace;z-index:1;text-shadow:0 0 4px #000}</style></head>
<body><div id="w"></div><canvas id="c"></canvas>
<script>
(function(){
const canvas=document.getElementById('c'),ctx=canvas.getContext('2d'),wEl=document.getElementById('w');
let W,H;
function resize(){W=canvas.width=innerWidth;H=canvas.height=innerHeight;}
resize();addEventListener('resize',resize);
const COLS=15,ROWS=10;
let points,constraints,startX,startY,spacing,t,windSeed,gravity;
function idx(x,y){return y*COLS+x;}
function init(){
  t=0;
  windSeed=Math.random()*1000;
  gravity=0.35+Math.random()*0.15;
  spacing=Math.min((W*0.65)/(COLS-1),(H*0.55)/(ROWS-1));
  startX=W/2-(COLS-1)*spacing/2;
  startY=H*0.12;
  points=[];
  for(let y=0;y<ROWS;y++)for(let x=0;x<COLS;x++){
    const px=startX+x*spacing,py=startY+y*spacing;
    points.push({x:px,y:py,ox:px,oy:py,pinned:y===0,ix:x,iy:y});
  }
  constraints=[];
  for(let y=0;y<ROWS;y++)for(let x=0;x<COLS;x++){
    if(x<COLS-1)constraints.push({a:idx(x,y),b:idx(x+1,y),len:spacing,d:1});
    if(y<ROWS-1)constraints.push({a:idx(x,y),b:idx(x,y+1),len:spacing,d:1});
    if(x<COLS-1&&y<ROWS-1){
      const dl=spacing*Math.SQRT2;
      constraints.push({a:idx(x,y),b:idx(x+1,y+1),len:dl,d:0});
      constraints.push({a:idx(x+1,y),b:idx(x,y+1),len:dl,d:0});
    }
  }
}
init();
let windDisp=0;
function step(){
  t++;
  const raw=(Math.sin(t*0.013+windSeed)*0.5+0.5)+0.25*Math.sin(t*0.047+windSeed*2)+0.15*Math.sin(t*0.11+windSeed*3);
  const windMain=Math.max(0,raw)*1.1;
  windDisp=windMain;
  for(const p of points){
    if(p.pinned)continue;
    const vx=(p.x-p.ox)*0.995,vy=(p.y-p.oy)*0.995;
    const nx=p.x+vx,ny=p.y+vy+gravity;
    p.ox=p.x;p.oy=p.y;
    const gust=windMain*(0.6+0.4*Math.sin(p.iy*0.6+t*0.03))*Math.sin(t*0.02+p.ix*0.35+p.iy*0.15);
    p.x=nx+gust*0.7;
    p.y=ny+Math.sin(t*0.019+p.ix*0.25+p.iy*0.1)*0.12;
  }
  for(let i=0;i<6;i++){
    for(const c of constraints){
      const a=points[c.a],b=points[c.b];
      let dx=b.x-a.x,dy=b.y-a.y;
      let dist=Math.sqrt(dx*dx+dy*dy)||0.0001;
      const diff=(dist-c.len)/dist*(c.d?0.5:0.5);
      const ox=dx*diff,oy=dy*diff;
      if(!a.pinned){a.x+=ox;a.y+=oy;}
      if(!b.pinned){b.x-=ox;b.y-=oy;}
    }
  }
}
function draw(){
  ctx.clearRect(0,0,W,H);
  for(let y=0;y<ROWS-1;y++)for(let x=0;x<COLS-1;x++){
    const p00=points[idx(x,y)],p10=points[idx(x+1,y)],p01=points[idx(x,y+1)],p11=points[idx(x+1,y+1)];
    const lx=p11.x-p00.x,ly=p11.y-p00.y;
    const ang=Math.atan2(ly,lx);
    const shade=Math.max(0.1,Math.min(1,0.5+Math.sin(ang*2+x*0.3)*0.35));
    const r=Math.floor(60+shade*90),g=Math.floor(90+shade*110),b=Math.floor(140+shade*110);
    ctx.fillStyle='rgb('+r+','+g+','+b+')';
    ctx.beginPath();
    ctx.moveTo(p00.x,p00.y);ctx.lineTo(p10.x,p10.y);ctx.lineTo(p11.x,p11.y);ctx.lineTo(p01.x,p01.y);
    ctx.closePath();ctx.fill();
  }
  ctx.strokeStyle='rgba(230,240,255,0.22)';
  ctx.lineWidth=1;
  ctx.beginPath();
  for(const c of constraints){
    if(!c.d)continue;
    const a=points[c.a],b=points[c.b];
    ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);
  }
  ctx.stroke();
  wEl.textContent='wind strength: '+windDisp.toFixed(2);
}
function loop(){
  step();draw();
  if(t>2400)init();
  requestAnimationFrame(loop);
}
loop();
})();
</script>
</body></html>
Claude Fable 5frontier referenceOK
Show code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Cloth</title>
<style>
html,body{margin:0;height:100%;background:#0b0e14;overflow:hidden}
canvas{display:block}
#hud{position:fixed;top:12px;left:14px;color:#cfe9ff;font:13px/1.5 monospace;text-shadow:0 1px 4px #000;pointer-events:none}
#bar{width:160px;height:6px;background:#1c2433;border-radius:3px;overflow:hidden;margin-top:4px}
#fill{height:100%;width:0;background:linear-gradient(90deg,#3fa9f5,#9ff);transition:width .08s linear}
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="hud"><div id="txt"></div><div id="bar"><div id="fill"></div></div></div>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
const txt=document.getElementById('txt'),fill=document.getElementById('fill');
let W,H;
function resize(){
const d=Math.min(devicePixelRatio||1,2);
W=innerWidth;H=innerHeight;
cv.width=W*d;cv.height=H*d;
cv.style.width=W+'px';cv.style.height=H+'px';
ctx.setTransform(d,0,0,d,0,0);
}
addEventListener('resize',resize);resize();
const COLS=15,ROWS=10,ITER=7,GRAV=.32,DAMP=.994,LIFE=30;
let pts,cons,windBase,windFreq,windPhase,hue,t0;
function init(){
pts=[];cons=[];
const sp=Math.min(W*.72/(COLS-1),H*.5/(ROWS-1));
const ox=(W-sp*(COLS-1))/2,oy=H*.12;
for(let y=0;y<ROWS;y++)for(let x=0;x<COLS;x++){
const px=ox+x*sp+(y?Math.random()*2-1:0),py=oy+y*sp;
pts.push({x:px,y:py,ox:px,oy:py,pin:y===0});
}
const id=(x,y)=>y*COLS+x;
const link=(a,b)=>{const p=pts[a],q=pts[b];cons.push({a,b,l:Math.hypot(p.x-q.x,p.y-q.y)})};
for(let y=0;y<ROWS;y++)for(let x=0;x<COLS;x++){
if(x<COLS-1)link(id(x,y),id(x+1,y));
if(y<ROWS-1)link(id(x,y),id(x,y+1));
if(x<COLS-1&&y<ROWS-1){link(id(x,y),id(x+1,y+1));link(id(x+1,y),id(x,y+1))}
}
windBase=.05+Math.random()*.07;
windFreq=.5+Math.random()*.9;
windPhase=Math.random()*6.283;
hue=Math.floor(Math.random()*360);
t0=performance.now();
}
function step(t,w){
for(const p of pts){
if(p.pin)continue;
const vx=(p.x-p.ox)*DAMP,vy=(p.y-p.oy)*DAMP;
p.ox=p.x;p.oy=p.y;
const gust=w*(1+.45*Math.sin(p.y*.02+t*2.1)+.3*Math.sin(p.x*.016+t*1.3));
p.x+=vx+gust;
p.y+=vy+GRAV+.18*w*Math.sin(p.x*.012+t*1.7);
}
for(let k=0;k<ITER;k++)for(const c of cons){
const p=pts[c.a],q=pts[c.b];
let dx=q.x-p.x,dy=q.y-p.y;
const d=Math.hypot(dx,dy)||1e-6,f=(d-c.l)/d*.5;
dx*=f;dy*=f;
if(!p.pin){p.x+=dx;p.y+=dy}
if(!q.pin){q.x-=dx;q.y-=dy}
}
}
function draw(){
ctx.fillStyle='#0b0e14';ctx.fillRect(0,0,W,H);
for(let y=0;y<ROWS-1;y++)for(let x=0;x<COLS-1;x++){
const a=pts[y*COLS+x],b=pts[y*COLS+x+1],c=pts[(y+1)*COLS+x+1],d=pts[(y+1)*COLS+x];
const ar=Math.abs((b.x-a.x)*(d.y-a.y)-(d.x-a.x)*(b.y-a.y)+(b.x-c.x)*(d.y-c.y)-(d.x-c.x)*(b.y-c.y))*.5;
const rest=cons[0].l*cons[0].l;
const L=Math.max(18,Math.min(62,18+44*ar/rest));
ctx.beginPath();
ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.lineTo(c.x,c.y);ctx.lineTo(d.x,d.y);
ctx.closePath();
ctx.fillStyle=`hsl(${hue+((x+y)%2?6:0)},62%,${L}%)`;
ctx.fill();
ctx.strokeStyle='rgba(255,255,255,.16)';
ctx.lineWidth=1;
ctx.stroke();
}
ctx.fillStyle='#e8f4ff';
for(let x=0;x<COLS;x++){
const p=pts[x];
ctx.beginPath();ctx.arc(p.x,p.y,3,0,6.283);ctx.fill();
}
}
function loop(now){
const t=(now-t0)/1000;
if(t>LIFE){init();requestAnimationFrame(loop);return}
const w=windBase*(Math.sin(t*windFreq+windPhase)+.6*Math.sin(t*windFreq*2.7+1.3));
step(t,w);
draw();
const mag=Math.abs(w);
txt.textContent='wind '+(w>=0?'→':'←')+' '+(mag*100).toFixed(1)+'  |  restart in '+Math.ceil(LIFE-t)+'s';
fill.style.width=Math.min(100,mag/(windBase*1.6)*100)+'%';
requestAnimationFrame(loop);
}
init();
requestAnimationFrame(loop);
</script>
</body>
</html>
Claude Haiku 4.5frontier referenceOKMINIFIED
Show code
```html
<!DOCTYPE html><html><head><meta charset=utf-8><style>body{margin:0;overflow:hidden;background:#000}#w{position:absolute;top:10px;left:10px;color:#fff;font:12px monospace}</style><body><canvas id=c></canvas><div id=w></div><script>const c=document.getElementById('c'),x=c.getContext('2d'),w=document.getElementById('w'),cols=15,rows=10;c.width=innerWidth;c.height=innerHeight;let p=[],k=[],t=0;const init=()=>{p=[];k=[];t=0;const s=c.width/(cols-1);for(let y=0;y<rows;y++)for(let i=0;i<cols;i++){const px=c.width/2-(cols-1)*s/2+i*s,py=50+y*s;p.push({x:px,y:py,px,py,a:y<1})}for(let y=0;y<rows;y++)for(let i=0;i<cols;i++){const ix=y*cols+i;if(i<cols-1){const ix2=ix+1,dx=p[ix2].x-p[ix].x,dy=p[ix2].y-p[ix].y;k.push({p1:ix,p2:ix2,r:Math.hypot(dx,dy)})}if(y<rows-1){const ix2=(y+1)*cols+i,dx=p[ix2].x-p[ix].x,dy=p[ix2].y-p[ix].y;k.push({p1:ix,p2:ix2,r:Math.hypot(dx,dy)})}if(i<cols-1&&y<rows-1){const ix2=(y+1)*cols+i+1,dx=p[ix2].x-p[ix].x,dy=p[ix2].y-p[ix].y;k.push({p1:ix,p2:ix2,r:Math.hypot(dx,dy)})}if(i>0&&y<rows-1){const ix2=(y+1)*cols+i-1,dx=p[ix2].x-p[ix].x,dy=p[ix2].y-p[ix].y;k.push({p1:ix,p2:ix2,r:Math.hypot(dx,dy)})}}};const u=()=>{const wf=Math.sin(t*0.02)*0.5+0.5;for(let o of p){if(!o.a){const vx=(o.x-o.px)*0.99+wf*0.15,vy=(o.y-o.py)*0.99+0.3;o.px=o.x;o.py=o.y;o.x+=vx;o.y+=vy}}for(let i=0;i<5;i++)for(let e of k){const o=p[e.p1],a=p[e.p2],dx=a.x-o.x,dy=a.y-o.y,d=Math.hypot(dx,dy),g=(d-e.r)/d,hx=dx*g*0.5,hy=dy*g*0.5;if(!o.a){o.x+=hx;o.y+=hy}if(!a.a){a.x-=hx;a.y-=hy}}};const d=()=>{x.fillStyle='#000';x.fillRect(0,0,c.width,c.height);x.strokeStyle='#0f0';x.lineWidth=1;x.beginPath();for(let y=0;y<rows-1;y++)for(let i=0;i<cols-1;i++){const p0=p[y*cols+i],p1=p[y*cols+i+1],p2=p[(y+1)*cols+i+1],p3=p[(y+1)*cols+i];x.moveTo(p0.x,p0.y);x.lineTo(p1.x,p1.y);x.lineTo(p2.x,p2.y);x.lineTo(p3.x,p3.y);x.lineTo(p0.x,p0.y)}x.stroke();const wf=Math.sin(t*0.02)*0.5+0.5;w.textContent=`Wind: ${(wf*100).toFixed(0)}%`};const a=()=>{t++;if(t>36e3)init();u();d();requestAnimationFrame(a)};init();a();</script></body></html>
```
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3-coder:30b 28.9 sOK
deepseek-r1:14b 39.1 sOK
qwen3:14b 39.8 sOK
qwen3:8b harness
gemma4:26b harness
llama3.1:8b 96.5 sOK
qwen3.5:9b 63.2 sOK
mistral-small:24b 125.3 sOK
qwq:32b 222.0 sOK
command-r:35b 264.1 sOK
deepseek-r1:32b 63.9 sOK