Overview › Code › R61
Code · blind round

R61 · Convex hull built step by step

blind round 9/11 local models answered

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

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

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

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 120 lines total) that computes the convex hull of 40 random points with the Graham scan and animates the construction on a <canvas>. Draw all points, sort them, then walk the scan one step at a time: show the current candidate segment, and visibly POP points again when a right turn is detected. Draw the finished hull as a closed polygon and show a running step counter. 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:14bSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Graham Scan Animation</title>
<style>body {margin:0;overflow:hidden;}#canvas {display:block;background-color:#111;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let W, H;

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

let points = [];
let P0 = null;
let sortedPoints = [];
let hullStack = [];
let step = 0;
let running = false;
const N_POINTS = 40;

function generatePoints() {
    points = [];
    for (let i = 0; i < N_POINTS; i++) {
        points.push({x: Math.random() * W, y: Math.random() * H});
    }
}

// Cross product / Orientation check
function orientation(p, q, r) {
    const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
    if (Math.abs(val) < 0.1) return 0; // Collinear
    return (val > 0) ? 1 : 2; // 1: Left turn, 2: Right turn
}

// Distance squared helper for sorting tie-breaker
function distSq(p1, p2) {
    return (p1.x - p2.x)**2 + (p1.y - p2.y)**2;
}

// Comparison function for angle sort around P0
function compareAngles(a, b) {
    const o = orientation(P0, a, b);
    if (o == 2) return 1; // b is left of vector P0->a (counter-clockwise order)
    if (o == 1) return -1; // b is right of vector P0->a
    return 0; // Collinear, use distance tie breaker
}

function initializeGrahamScan() {
    P0 = points[0];
    points.sort(compareAngles);
    sortedPoints = points;
    hullStack = [];
    step = 0;
    running = true;
}

// Core logic step: processes the next point if possible
function runStep() {
    if (!running) return;

    if (sortedPoints.length === 0) {
        running = false;
        return;
    }

    if (hullStack.length < 2) {
        hullStack.push(sortedPoints[step]);
        step++;
        return;
    }

    const p_next = sortedPoints[step];
    let top1 = hullStack[hullStack.length - 2];
    let top2 = hullStack[hullStack.length - 1];

    if (orientation(top1, top2, p_next) == 2) { // Right turn detected
        // Pop operation: This point failed the left-turn condition
        hullStack.pop();
    }
    
    hullStack.push(p_next);
    step++;

    if (step >= sortedPoints.length && hullStack.length > 1) {
        // Final closure check/cleanup can happen here if needed, but for visualization, just stop the process loop
        running = false;
    }
}

function draw() {
    ctx.clearRect(0, 0, W, H);

    // 1. Draw all points
    ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
    points.forEach(p => ctx.beginPath());
    points.forEach(p => ctx.arc(p.x, p.y, 3, 0, Math.PI * 2));
    points.forEach(p => ctx.fill());

    // 2. Draw the current state (Hull Stack and Candidate Segment)
    ctx.strokeStyle = '#8affff';
    ctx.lineWidth = 2;
    if (hullStack.length >= 2) {
        for (let i = 0; i < hullStack.length - 1; i++) {
            ctx.beginPath();
            ctx.moveTo(hullStack[i].x, hullStack[i].y);
            ctx.lineTo(hullStack[i+1].x, hullStack[i+1].y);
            ctx.stroke();
        }
    }

    // 3. Draw the candidate segment (last point added vs next point)
    if (sortedPoints[step] && hullStack.length >= 2) {
         const p_next = sortedPoints[step];
         let top1 = hullStack[hullStack.length - 2];
         let top2 = hullStack[hullStack.length - 1];

         if (orientation(top1, top2, p_next) == 2) { // Right turn detected on the next step check
             ctx.strokeStyle = '#ff6b6b'; // Red for failure/pop
             ctx.beginPath();
             ctx.moveTo(top2.x, top2.y);
             ctx.lineTo(p_next.x, p_next.y);
             ctx.stroke();
         } else {
            ctx.strokeStyle = '#ffff8a'; // Yellow for successful check
            ctx.beginPath();
            ctx.moveTo(top2.x, top2.y);
            ctx.lineTo(p_next.x, p_next.y);
            ctx.stroke();
         }
    }

    // 4. Draw the final hull (closed loop)
    if (hullStack.length >= 3 && !running) {
        ctx.beginPath();
        ctx.fillStyle = 'rgba(70, 255, 100, 0.8)';
        ctx.moveTo(hullStack[0].x, hullStack[0].y);
        for (let i = 1; i < hullStack.length; i++) {
            ctx.lineTo(hullStack[i].x, hullStack[i].y);
        }
        ctx.closePath();
        ctx.fill();
    }

    // 5. Draw UI/Status
    ctx.fillStyle = '#fff';
    ctx.font = '16px monospace';
    ctx.fillText(`Step: ${step}/${N_POINTS} | Hull Size: ${hullStack.length}`, 20, 30);
}

function loop() {
    draw();
    if (running) {
        runStep();
        requestAnimationFrame(loop);
    } else if (!window.__finished__) {
        // Animation finished or needs restart
        setTimeout(() => {
            initializeGrahamScan();
            loop();
            window.__finished__ = true; // Flag to prevent continuous re-triggering on interval
        }, 1000); // Wait a second before restarting the loop
    }
}

// --- Initialization Sequence ---
generatePoints();
initializeGrahamScan();
requestAnimationFrame(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.
mistral-small:24bERRSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Convex Hull Animation</title>
<style>
body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
canvas { border: 1px solid black; }
</style>
</head>
<body>
<canvas id="hullCanvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('hullCanvas');
const ctx = canvas.getContext('2d');
const points = [];
const hull = [];

function getRandomPoint() {
  return { x: Math.random() * canvas.width, y: Math.random() * canvas.height };
}

function crossProduct(o, a, b) {
  return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
}

function distanceSquared(a, b) {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return dx * dx + dy * dy;
}

function sortPoints() {
  points.sort((a, b) => a.y === b.y ? a.x - b.x : a.y - b.y);
}

function polarAngle(p0, p1) {
  const y = p1.y - p0.y;
  const x = p1.x - p0.x;
  return Math.atan2(y, x);
}

function angleSort() {
  points.sort((a, b) => {
    const cross = crossProduct(points[0], a, b);
    if (cross === 0)
      return distanceSquared(points[0], a) - distanceSquared(points[0], b);
    return cross;
  });
}

function drawPoint(p) {
  ctx.beginPath();
  ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
  ctx.fillStyle = 'blue';
  ctx.fill();
  ctx.closePath();
}

function drawSegment(a, b) {
  ctx.beginPath();
  ctx.moveTo(a.x, a.y);
  ctx.lineTo(b.x, b.y);
  ctx.strokeStyle = 'red';
  ctx.stroke();
  ctx.closePath();
}

function drawHull() {
  ctx.beginPath();
  hull.forEach((p, i) => {
    if (i === 0)
      ctx.moveTo(p.x, p.y);
    else
      ctx.lineTo(p.x, p.y);
  });
  ctx.closePath();
  ctx.strokeStyle = 'green';
  ctx.stroke();
}

function animateStep(step) {
  requestAnimationFrame(() => {
    if (step > points.length + hull.length - 1)
      restart();
    else
      drawFrame(step);
  });
}

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

  points.forEach(drawPoint);

  for (let i = 0; i < step && i < hull.length; i++) {
    if(i<step-1)
      drawSegment(hull[i],hull[i+1]);
  }

  ctx.fillStyle = 'black';
  ctx.font = '20px Arial';
  ctx.fillText(`Step: ${step}`, 10, 30);

  animateStep(step + 1);
}

function grahamScan() {
  sortPoints();
  const p0 = points[0];
  angleSort();

  hull.push(p0);
  for (let i = 1; i < points.length; i++) {
    while (hull.length > 1 && crossProduct(hull[hull.length - 2], hull[hull.length - 1], points[i]) <= 0) {
      hull.pop();
    }
    hull.push(points[i]);
  }

  drawFrame(0);
}

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

  for (let i = 0; i < 40; i++)
    points.push(getRandomPoint());

  hull.length = 0;

  grahamScan();
}

restart();
</script>
</body>
</html>
```
qwen3-coder:30bERRSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Graham Scan Animation</title>
    <style>body {background: #111; color: white; font-family: sans-serif;} canvas {border: 1px solid #333; background: #000;}#info {margin-top: 10px;}</style>
</head>
<body>
    <canvas id="hullCanvas" width="800" height="600"></canvas>
    <div id="info">Step: <span id="stepCounter">0</span>/40</div>

<script>
    const canvas = document.getElementById('hullCanvas');
    const ctx = canvas.getContext('2d');
    const POINTS_COUNT = 40;
    let points = [];
    let sortedPoints = [];
    let hullStack = [];
    let stepCounter = 0;

    function generateRandomPoints() {
        points = [];
        for (let i = 0; i < POINTS_COUNT; i++) {
            points.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height });
        }
        stepCounter = 0;
    }

    function crossProduct(p1, p2, p3) {
        return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
    }

    function distSq(p1, p2) {
        return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
    }

    function comparePoints(a, b) {
        const cp = crossProduct(points[0], a, b);
        if (cp > 0) return -1; // CCW -> smaller angle/comes first
        if (cp < 0) return 1;  // CW -> larger angle/comes later
        return distSq(points[0], a) <= distSq(points[0], b) ? -1 : 1; // Collinear: closer first
    }

    function initializeScan() {
        if (points.length < 3) return true;

        // 1. Find Pivot (P0): Lowest Y, then leftmost X
        let p0 = points[0];
        let p0Index = 0;
        for(let i=1; i<points.length; i++){
            if(points[i].y < p0.y || (points[i].y === p0.y && points[i].x < p0.x)){
                p0 = points[i];
                p0Index = i;
            }
        }

        // Swap P0 to the start for ease of use
        [points[0], points[p0Index]] = [points[p0Index], points[0]];
        const pivot = p0;

        // 2. Sort remaining points by angle relative to P0
        sortedPoints = points.slice(1).sort(comparePoints);
        sortedPoints.unshift(pivot); // Add pivot back at start

        hullStack = [];
        if (sortedPoints.length > 0) {
            hullStack.push(sortedPoints[0], sortedPoints[1]);
        }
        return true;
    }

    function draw() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        
        // Draw all points
        points.forEach(p => { ctx.fillStyle = '#555'; ctx.beginPath(); ctx.arc(p.x, p.y, 3, 0, Math.PI * 2); ctx.fill(); });

        if (hullStack.length < 2) return;
        
        // Draw current candidate segment (last two points)
        ctx.strokeStyle = '#FFD700';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(hullStack[hullStack.length - 2].x, hullStack[hullStack.length - 2].y);
        ctx.lineTo(hullStack[hullStack.length - 1].x, hullStack[hullStack.length - 1].y);
        ctx.stroke();

        // Draw the current partial hull
        ctx.strokeStyle = '#00FFFF';
        ctx.lineWidth = 3;
        ctx.beginPath();
        for (let i = 0; i < hullStack.length - 1; i++) {
            let p = hullStack[i];
            if (i === 0) ctx.moveTo(p.x, p.y);
            else ctx.lineTo(p.x, p.y);
        }
        ctx.stroke();

        document.getElementById('stepCounter').textContent = stepCounter;
    }

    function scanStep() {
        if (hullStack.length < 2) return false;

        let p3 = sortedPoints[stepCounter]; // The point being considered next

        // Check for right turn: crossProduct(p_i-1, p_i, p_next) <= 0
        while (hullStack.length >= 2 && crossProduct(hullStack[hullStack.length - 2], hullStack[hullStack.length - 1], p3) <= 0) {
            // Right turn or collinear detected: POP the last point
            const poppedPoint = hullStack.pop();

            // Redraw to show pop visually (optional, but good practice)
            draw(); 
            currentSegment = `${hullStack[hullStack.length - 2].x},${hullStack[hullStack.length - 2].y} -> ${p3.x},${p3.y}`;

            // Increment step count for the POP action
            document.getElementById('stepCounter').textContent = Math.max(0, stepCounter) + 1;
        }

        if (crossProduct(hullStack[hullStack.length - 2], hullStack[hullStack.length - 1], p3) > 0) {
             // Left turn: Push the new point onto the stack
            hullStack.push(p3);
            draw();
        }

        stepCounter++;

        if (stepCounter < sortedPoints.length - 1) {
           requestAnimationFrame(scanStep);
        } else {
            // Finalization step: Close the polygon and reset/loop
            setTimeout(() => {
                ctx.strokeStyle = '#008000'; // Green for final hull
                ctx.lineWidth = 4;
                let path = new Path2D();
                for(let i=0; i<hullStack.length; i++){
                    path.lineTo(hullStack[i].x, hullStack[i].y);
                }
                path.closePath();
                ctx.stroke(path);

                // Loop/Reset after a delay
                setTimeout(() => {
                    generateRandomPoints();
                    if (initializeScan()) {
                        draw(); // Draw initial state
                        requestAnimationFrame(scanStep);
                    }
                }, 1500);
            }, 200);
        }
    }

    // --- Initialization ---
    window.onload = function() {
        generateRandomPoints();
        if (initializeScan()) {
             draw(); // Draw initial points and setup
             requestAnimationFrame(scanStep);
        } else {
            document.getElementById('info').textContent = "Need at least 3 points.";
        }
    };

</script>
</body>
</html>
```
qwen3.5:9bERRBLANKLIM
Show code
<!DOCTYPE html>
<html>
<head>
<title>Graham Scan Animation</title>
<style>body {margin:0;overflow:hidden;}canvas {display:block;background:#111;}</style>
</head>
<body>
<canvas id="c"></canvas><script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let W, H;

function initialize() {
    W = canvas.width = 800;
    H = canvas.height = 600;
    if (window.innerWidth < 800 || window.innerHeight < 600) {
        canvas.style.width = `${Math.min(800, window.innerWidth)}px`;
        canvas.style.height = `${Math.min(600, window.innerHeight)}px`;
    }
    points = [];
    for (let i = 0; i < 40; i++) {
        points.push({x: Math.random() * W, y: Math.random() * H});
    }
    hullStack = [];
    stepCounter = 0;
    scanIndex = 0;
    animationRunning = true;
    drawPoints();
    animate();
}

function crossProduct(p1, p2, p3) {
    return (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x);
}

function distSq(p1, p2) {
    return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2);
}

let points;
let hullStack = [];
let stepCounter = 0;
let scanIndex = 0;
let animationRunning = true;

function findPivotAndSort() {
    points.sort((a, b) => a.x - b.x);
    const p0 = points[0];
    const remainingPoints = points.slice(1);
    remainingPoints.sort((a, b) => {
        let cp = crossProduct(p0, a, b);
        if (cp > 0) return -1;
        if (cp < 0) return 1;
        return 0; // Collinear: closer point first
    });
    points = [p0, ...remainingPoints];
}

function drawScene(currentCandidate = null, poppedPoint = null) {
    ctx.clearRect(0, 0, W, H);
    
    // 1. Draw all initial points
    ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
    points.forEach(p => {
        ctx.beginPath();
        ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
        ctx.fill();
    });

    // 2. Draw the current hull stack (candidate)
    if (hullStack.length > 1) {
        ctx.strokeStyle = '#4CAF50';
        ctx.lineWidth = 2;
        ctx.beginPath();
        for(let i=0; i < hullStack.length; i++) {
            const p = hullStack[i];
            if (i === 0) ctx.moveTo(p.x, p.y);
            else ctx.lineTo(p.x, p.y);
        }
        ctx.stroke();
    }

    // 3. Draw the current segment being checked (H[-2] -> H[-1] -> P_i)
    if (currentCandidate && hullStack.length >= 2) {
        const A = hullStack[hullStack.length - 2];
        const B = hullStack[hullStack.length - 1];
        const C = currentCandidate;

        ctx.strokeStyle = '#FFD700';
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(A.x, A.y);
        ctx.lineTo(B.x, B.y);
        ctx.lineTo(C.x, C.y);
        ctx.stroke();
    }

    // 4. Draw the popped point (if applicable)
    if (poppedPoint && !poppedPoint.isInitial) {
         ctx.fillStyle = 'red';
         ctx.beginPath();
         ctx.arc(poppedPoint.x, poppedPoint.y, 3, 0, Math.PI * 2);
         ctx.fill();
    }

    // 5. Draw step counter/status
    ctx.fillStyle = '#FFF';
    ctx.font = '16px Arial';
    ctx.fillText(`Step: ${stepCounter} | Points Left: ${scanIndex < points.length ? (points.length - scanIndex) : 0}`, 10, 25);
}

function grahamScan() {
    if (points.length < 3) return;

    hullStack = [points[0], points[1]]; // Initial stack with first two points

    for (let i = 2; i < points.length; i++) {
        const nextPoint = points[i];
        let popped = null;

        while (hullStack.length >= 2) {
            const p2 = hullStack[hullStack.length - 1];
            const p1 = hullStack[hullStack.length - 2];
            const cp = crossProduct(p1, p2, nextPoint);

            if (cp < 0) { // Right turn detected (POP required)
                popped = p2;
                hullStack.pop();
            } else if (Math.abs(cp) < 1e6) { // Collinear: Keep only the furthest point (already handled by sort/scan structure, but good safeguard)
                 if (distSq(p1, nextPoint) > distSq(p1, p2)) {
                     hullStack.pop();
                     // Note: A full implementation would replace p2 with nextPoint here if collinear and further away.
                 } else {
                    break; 
                 }
            } else { // Left turn (CCW) or straight path
                break;
            }
        }
        hullStack.push(nextPoint);
    }
    return hullStack;
}

function animate() {
    if (!animationRunning) return;

    drawScene();

    if (scanIndex < points.length - 1) {
        const p_i = points[scanIndex + 1];
        stepCounter++;
        
        let poppedPoint = null;
        let currentCandidate = p_i;

        while (hullStack.length >= 2) {
            const p2 = hullStack[hullStack.length - 1];
            const p1 = hullStack[hullStack.length - 2];
            const cp = crossProduct(p1, p2, p_i);

            if (cp < 0) { // Right turn
                poppedPoint = p2;
                hullStack.pop();
            } else if (Math.abs(cp) < 1e6 && hullStack.length > 0) {
                 // Collinear case handling (optional, but safe)
                 if (distSq(p1, p_i) > distSq(p1, p2)) {
                    poppedPoint = p2;
                    hullStack.pop();
                 } else {
                     break;
                 }
            } else { 
                break; // Left turn or collinear and further out (CCW maintained)
            }
        }

        // Update state for next step
        if (!poppedPoint || crossProduct(hullStack[hullStack.length - 2], hullStack[hullStack.length - 1], p_i) >= 0) {
             hullStack.push(p_i);
        } else if (poppedPoint) {
            // If a pop occurred, the stack already reflects it, and we push p_i.
            hullStack.push(p_i);
        }


        scanIndex++;

    } else {
        animationRunning = false;
        drawScene(); // Final draw
        
        ctx.strokeStyle = '#00FFFF';
        ctx.lineWidth = 3;
        ctx.beginPath();
        for (let i = 0; i < hullStack.length; i++) {
            const p = hullStack[i];
            if (i === 0) ctx.moveTo(p.x, p.y);
            else ctx.lineTo(p.x, p.y);
        }
        ctx.closePath(); // Close the loop for visual completion
        ctx.stroke();

        setTimeout(() => {
            initialize(); // Restart the process
        }, 1500);
    }

    requestAnimationFrame(animate);
}

window.onload = initialize;
</script>
</body>
</html>
gemma4:26bharness
No answer reached us (THIN) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget (3 min 28 s, zero characters of answer); larger budgets are refused by the gateway Counted as a did-not-finish, but it is not evidence about this model.
llama3.1:8bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Convex Hull</title>
    <style>
        body {
            font-family: monospace;
        }
        #canvas {
            border: 1px solid black;
        }
    </style>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>

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

    function randomPoint() {
        return [Math.random() * 800, Math.random() * 600];
    }

    let points = Array(40).fill().map(randomPoint);

    function sortPoints(points) {
        return points.map((p, i) => ({ p, i })).sort((a, b) => a.p[0] - b.p[0]).map(({i}) => points[i]);
    }

    const sortedPoints = sortPoints(points);
    let index = 0;
    const hull = [];
    function step() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        for (let i = 0; i < sortedPoints.length; i++) {
            ctx.beginPath();
            ctx.arc(sortedPoints[i][0] * 2 + canvas.width / 2, sortedPoints[i][1] * 2 + canvas.height / 2, 2, 0, 2 * Math.PI);
            ctx.fillStyle = 'black';
            ctx.fill();
        }

        const p1 = hull.slice(-1)[0];
        const p2 = sortedPoints[index];

        ctx.beginPath();
        ctx.moveTo(p1[0] * 2 + canvas.width / 2, p1[1] * 2 + canvas.height / 2);
        ctx.lineTo(p2[0] * 2 + canvas.width / 2, p2[1] * 2 + canvas.height / 2);
        ctx.strokeStyle = 'red';
        ctx.stroke();

        if (hull.length === 0 || crossProduct(p1, p2, hull.slice(-2)[0]) <= 0) {
            ctx.fillStyle = 'red';
            ctx.beginPath();
            ctx.arc(p2[0] * 2 + canvas.width / 2, p2[1] * 2 + canvas.height / 2, 4, 0, 2 * Math.PI);
            ctx.fill();

        } else {
            hull.pop();
            ctx.fillStyle = 'black';
            ctx.beginPath();
            ctx.arc(hull.slice(-1)[0][0] * 2 + canvas.width / 2, hull.slice(-1)[0][1] * 2 + canvas.height / 2, 4, 0, 2 * Math.PI);
            ctx.fill();

        }

        if (index >= sortedPoints.length - 1) return;

        index++;
        setTimeout(step, 10);

    }
    function crossProduct(p1, p2, p3) {
        return (p2[0] - p1[0]) * (p3[1] - p1[1]) - (p2[1] - p1[1]) * (p3[0] - p1[0]);
    }

    function drawHull(points, ctx) {
        ctx.beginPath();
        for (let i = 0; i < points.length; i++) {
            const p = points[i];
            ctx.lineTo(p[0] * 2 + canvas.width / 2, p[1] * 2 + canvas.height / 2);
        }
        ctx.fillStyle = 'green';
        ctx.fill();
    }

    function animate() {
        step();
        drawHull(hull, ctx);

        ctx.font = "24px monospace";
        ctx.textAlign = "left";
        ctx.textBaseline = "top";
        ctx.fillText(`Step: ${index}`, 10, canvas.height / 2);
        if (pointerLockElement.requestPointerLock) {
            document.addEventListener("mousemove", (e) => {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                drawHull(hull, ctx);
                const x = e.clientX - canvas.offsetLeft;
                const y = e.clientY - canvas.offsetTop;

                for (let i = 0; i < sortedPoints.length; i++) {
                    const p = sortedPoints[i];
                    ctx.beginPath();
                    ctx.arc(p[0] * 2 + canvas.width / 2, p[1] * 2 + canvas.height / 2, 4, 0, 2 * Math.PI);
                    if (Math.hypot(x - p[0], y - p[1]) < 8) ctx.fillStyle = 'red';
                    else ctx.fillStyle = 'black';
                    ctx.fill();
                }

            }, true);

        }
        requestAnimationFrame(animate);
    }
    animate();

</script>
```
deepseek-r1:14bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>Convex Hull</title><style>.c{border:2px solid black}</body>
<body style="padding:20px; background:#fff;">
<canvas id=c width=600 height=400></canvas><div id=i style="position:absolute;top:10;left:5;"></div>
<script>
const c=document.getElementById('c'), ctx=c.getContext('2d'),i=document.getElementById('i');
let points,hull,step=0,state='init',animateId;
function randomPoint(){return {x:Math.random()*400+10,y:Math.random()*300+10,color:'#'+(Math.random().toString(16).slice(2,5)).padEnd(3,'a')}}
c.addEventListener('click',e=>{clearInterval(animateId);points=hull=[];state='init';step=0;c.width=c.width;});// dummy

function convexHull(points){
  points.sort((a,b)=>a.x-b.x||a.y-b.y);
  let hull=[];
  for(let p of points){
    while(hull.length>=2&&cross(o(hull[hull.length-2]),o(hull[hull.length-1]),p)<0)hull.pop();
    hull.push(p);
  }
  return hull;
}

function o(p){return [p.x,p.y]}
function cross(a,b,c){return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])}

function draw(){
  ctx.clearRect(0,0,c.width,c.height);
  for(let p of points)ctx.beginPath(),ctx.arc(p.x,p.y,2,0,Math.PI*2),ctx.fillStyle=p.color,ctx.fill();
  if(state=='scan'){
    const h=hull;
    if(step<h.length){
      ctx.beginPath();
      ctx.moveTo(h[step].x,h[step].y);
      ctx.lineTo(h[(step+1)%h.length].x,h[(step+1)%h.length].y);
      ctx.strokeStyle='red',ctx.lineWidth=2,ctx.stroke();
      if(cross(o(h[step-1]),o(h[step]),o(h[step+1]))<0)ctx.fillStyle='white';
    }
  }else if(state=='done')for(let i=0;i<hull.length;i++)ctx.beginPath().moveTo(hull[i].x,hull[i].y).lineTo(hull[(i+1)%hull.length].x,hull[(i+1)%hull.length].y).stroke();
  ctx.fillStyle='black';ctx.font='12px sans-serif';ctx.fillText(`Step ${step}`,5,15);
}

let init=()=>{
  points=[];for(_ of Array(40))points.push(randomPoint());
  hull=convexHull(points);state='scan';step=0;
  animateId=requestAnimationFrame(function f(){
    if(state=='init')return;
    draw();
    if(state=='scan'){
      step++;
      if(step>=hull.length*2){state='done';step=0;}
    }
    requestAnimationFrame(f);
  });
}

document.addEventListener('DOMContentLoaded',init);
</script>
</body></html>
```
qwq:32bSTATLIM
Show code
<think>

Okay, I need to create a self-contained HTML file that uses the Graham scan algorithm to compute the convex hull of 40 random points. The animation should show the process step by step, including sorting the points, walking the scan, popping points when a right turn is detected, and finally drawing the closed polygon. It has to be under 120 lines and use strict standards.

First, I should start by setting up the basic HTML structure with a canvas element. The canvas will handle the drawing. I'll need to use requestAnimationFrame for smooth animation. The script will generate random points, perform the Graham scan step by step, and animate each step.

Let me think about the steps required for the Graham scan:

1. Generate 40 random points. Each point has an x and y coordinate.
2. Find the point with the lowest y-coordinate (and leftmost if there's a tie). This is the starting point.
3. Sort all other points by their polar angle with respect to the starting point. If angles are equal, sort by distance.
4. Perform the scan step: iterate through the sorted points, adding them to the hull. If the last three points make a right turn, remove the middle point from the hull.

For the animation, each step should represent one part of the process. The steps would be:

- Initialization: draw all points.
- Sorting: perhaps show the points being sorted? Or just proceed once sorted.
- Scanning: step through each point in the sorted list, adding to the hull. Each step checks the last three points. If a right turn, pop the previous point.
- Finally, draw the closed hull polygon.

The animation needs a step counter and to restart after completion.

Wait, the user said "show the current candidate segment, and visibly pop points again when a right turn is detected". So during the scan phase, each step moves the candidate segment forward and when a right turn is found, the middle point is popped from the hull array, which needs to be visually shown.

Hmm, the steps might be structured as follows:

- First, draw all points.
- Then sort the points (maybe just sort them once, but the animation could show the sorting steps? But that might complicate it. Alternatively, sort them first and then start the scan steps. Since the user says "sort them" as a step, so perhaps draw the points sorted in the order they should be, but how to animate that? Maybe just after sorting, but since it's an animation, perhaps the sorting itself is not animated but just the result is shown? The user might want the points to be in order, but the main animation is the scan steps.

Alternatively, maybe the sorting is done behind the scenes, and the scan steps are animated step by step. The user wants the process to be shown step by step. The steps should be:

- Start with all points drawn.
- Then, the sorted points are processed one by one, adding to the hull, and when a right turn happens, the previous point is removed (popping from the hull array), which should be shown visually.

So for each step in the scan:

- Add the next point to the hull array.
- Check if the last three points form a right turn (using the cross product).
- If right turn, remove the second to last point (pop it), and this removal is part of this step.

So each step can be one addition or a correction (the pop). The animation should show the current hull being built, with candidate segments, and when a pop happens, the last point is removed.

The step counter should increment each step until the scan is complete, then restart.

So structuring the code:

- The canvas setup: width and height, context, etc.
- The main loop using requestAnimationFrame.
- An array to hold all steps of the process. The steps could be an array representing each action (add, remove) so that the animation can step through them.

Wait, but the Graham scan process can be broken down into steps. Let me think of the algorithm steps:

After sorting the points:

Initialize the hull array with the first two points.

Then for each subsequent point in the sorted list:

- While the last three points in the hull form a right turn, remove the middle point.
- Add the current point to the hull.

Each such addition or removal can be considered a step in the animation. So each step is either adding a point or removing one (because of a right turn detected). The steps array can be built during the Graham scan's processing, so that during animation, each step is executed one at a time.

Therefore, the approach is:

1. Generate the points and compute the sorted order.
2. Simulate the Graham scan steps and record each individual action (add/remove) so that the animation can step through them.
3. The animation frames cycle through the steps, applying each action step-by-step, updating the display each time.

That way, the animation can play through the steps one by one.

So the code structure would be:

- In the setup phase, generate the points, compute the sorted list, and generate an array of steps (each step being an action to add a point, or to pop the last two points when a right turn is detected?).

Wait, no: during the Graham scan processing, for each point after the second, we check the last three points. So each check could result in zero or more pops, followed by adding the current point.

Thus, the steps array can be built by processing each point in order and recording each addition and any pops necessary.

Alternatively, perhaps better to precompute all the steps.

Let me think of code steps:

First, the main function:

function start() {

  // Generate points
  var points = generatePoints(40);

  // Find the starting point (lowest y, then x)
  var start = points[0];
  for (var i = 1; i < points.length; i++) {
    if (points[i].y < start.y || (points[i].y === start.y && points[i].x < start.x)) {
      start = points[i];
    }
  }

  // Sort the other points based on polar angle and distance
  points.sort(comparePoints);

  // Now compute the steps for the Graham scan
  var hull = [];
  var steps = [];
  // Add the first two points to the hull (since they form the initial hull)
  hull.push(start);
  hull.push(points[1]); // Wait, points after start are in the sorted list?

Wait, after sorting, the start is part of the points array? Or is the start removed?

Wait, in Graham scan, after choosing the starting point, the rest of the points (excluding the starting point) are sorted by polar angle and distance. So perhaps during sorting, the start is removed first, then sorted, then concatenated back.

Wait, let me correct:

The process is:

- Find the point with the lowest y-coordinate (and leftmost if tie), called P0.

- Remove P0 from the list of points.

- Sort the remaining points by polar angle with respect to P0. Ties are resolved by distance (farther points come later? Or earlier? Wait, usually, when angles are equal, closer points are placed earlier so that they are popped in case of a right turn, but the exact order might affect the correctness. For Graham scan, when two points have the same angle, the one farther away is placed later, so that during the scan, the closer point is included before the farther ones, but I need to confirm.

Wait, the sorting of the points after P0 is done by angle, and for equal angles, the distance is used so that points with the same angle are placed in
deepseek-r1:32bERRSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
    <style>
        canvas{border:1px solid #ccc;display:block;margin:50px auto;background:#fff}
        body{background:#f0f0f0;font-family:sans-serif}
        #steps{position:absolute;top:20px;left:20px;color:#333;font-size:24px}
    </style>
</head>
<body>
<canvas id="c"></canvas><div id="steps">Step 0</div>
<script>
var c=document.getElementById('c'),ctx=c.getContext('2d'),w=800,h=600,points=[],hull=[],step=0,running=true;

function init(){
    c.width=w;c.height=h;points=genPoints();hull=[];step=0;running=true
}

function genPoints(){
    var pts=[];for(let i=0;i<40;i++)pts.push({x:Math.random()*w,y:Math.random()*h});
    pts.sort((a,b)=>(a.y==b.y?a.x-b.x:a.y-b.y));return pts
}

function cross(o,a,b){
    return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x)
}

function grahamScan(p,i=0,draw=true){
    if(i<points.length||running){
        let o=i>0?hull[hull.length-2]:p[0],a=hull[hull.length-1],b=p[i];
        ctx.beginPath();ctx.strokeStyle='#999';ctx.lineWidth=1;
        p.forEach(pt=>{ctx.beginPath();ctx.arc(pt.x,pt.y,3,0,Math.PI*2);ctx.fill()});
        if(hull.length>1){
            ctx.strokeStyle='#666';
            hull.forEach((h,i)=>i<hull.length-1&&ctx.moveTo(h.x,h.y)||ctx.lineTo(h.x,h.y));
            ctx.stroke()
        }
        ctx.strokeStyle='red';ctx.lineWidth=3;
        if(i==0)ctx.moveTo(p[0].x,p[0].y),ctx.lineTo(p[i].x,p[i].y);
        else{
            ctx.moveTo(o.x,o.y);ctx.lineTo(a.x,a.y);
            let c=cross(o,a,b);
            if(c>=0||hull.length<2){
                hull.push(b);
                ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y)
            }else{if(draw)popPoint()}
        }
        draw&&i>0&&ctx.stroke();
        step++;document.getElementById('steps').innerHTML='Step '+step;
        if(running)setTimeout(grahamScan,25,i+1);
    }else{
        running=false;hull.push(hull[0]);
        ctx.beginPath();ctx.strokeStyle='#333';ctx.lineWidth=2;
        hull.forEach((h,i)=>i>0?ctx.lineTo(h.x,h.y):ctx.moveTo(h.x,h.y));
        ctx.stroke();
        setTimeout(init,2500)
    }
}

function popPoint(){
    hull.pop();
    let o=hull[hull.length-2],a=hull[hull.length-1];
    ctx.beginPath();ctx.strokeStyle='blue';ctx.lineWidth=3;
    ctx.moveTo(o.x,o.y);ctx.lineTo(a.x,a.y);
    ctx.stroke()
}

init();grahamScan(points)
</script>
</body>
</html>
```
command-r:35bSTATLIM
Show code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Convex Hull Animation</title>
    <style>
        body { margin: 0; }
        canvas { border: 1px solid #333; display: block; width: 50vw; height: 70vh; }
    </style>
</head>
<body>

    <canvas id="hullCanvas"></canvas>
    
    <script>

        const canvas = document.getElementById("hullCanvas");
        const ctx = canvas.getContext('2d');
 
        function randomInt(max) {
            return Math.floor(Math.random() * max);
        }
        
        let currentStep = -1;

        // Generate random points
        function generateRandomPoints() {
            const points = [];
            for (let i = 0; i < 40; i++) {
                points.push([randomInt(canvas.width), randomInt(canvas.height)]);
            }
            return points;
        }
        
        let points = generateRandomPoints();

        // Compute Graham Scan
        function computeConvexHull() {

            const stack = [];
            const P = points.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
            
            for (let i = 0;i < P.length && !stack.length || P[i][1] !== stack[stack.length-1][1]; i++) {
                stack.push(P[i])
            }

            while (points.length) points.pop(); // init step counter
            
            let q = stack.shift(), r;
            
            while (stack.length > 1) {
                
                points.push([currentStep, currentStep]); // visible step counter

                if ((r = stack.shift()).indexOf(q[0], r[0]) > 0) [q,r]=[r,q];
                if (points.pop() !== -1) 
                    while ((r=stack.shift()) && q[0] === r[0] || crossProduct(q, r) >= 0 ) stack.push(r);

                points.push([currentStep, currentStep]); // visible step counter
                
                q = r;
            }

            return [q, ...stack];
        }

        function crossProduct(o, a, b) {
            const x1 = a[0] - o[0], y1 = a[1] - o[1];
            const x2 = b[0] - o[0], y2 = b[1] - o[1];
            return (x1*y2)-(x2*y1);
        }

        function draw() {

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            if (!points.length) points = computeConvexHull();

            currentStep++; // increment step counter

            for (const [x, y] of points)
                ctx.fillRect(x-1, y-1, 3, 3); // draw all points
        
            const hull = points[0];
            
            let j = points.findIndex(([i,j]) => i === currentStep && j === currentStep);

            while (points[++j] && !crossProduct(hull, points[j], points[j+1])); // find next right turn

            ctx.strokeStyle = "red";
            for (const [x, y] of points)
                ctx.lineTo(x, y);
            
            if (j) {

                j++;
                
                while ((currentStep < 30 || currentStep % 2 == 0) && !points[++j])
                    ; // wait until step counter is large enough or right turn detected again
            }

            if (j >= points.length - 1)
                ctx.fillStyle = 'green'; // finished
            
            else 
                ctx.stroke();
                
            for (let i=0; i < j + 2; i++) {

                const [x, y] = points[i];
                ctx.beginPath()
                ctx.arc(x, y, 5, 0, Math.PI*2); // draw candidate segment or finished hull
            }
        }
        
        function animate() {
            
            requestAnimationFrame(animate)

            if (currentStep < 31 || currentStep % 2 == 0) 

                draw();
                
            else 
                points = generateRandomPoints();

        }

        animate();

    </script>
</body>
</html>
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
GPT-5.5 (Codex)frontier referenceOK
Show code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Graham Scan</title>
<style>
html,body{margin:0;height:100%;background:#101418;color:#e8eef5;font:16px system-ui,sans-serif;overflow:hidden}
canvas{display:block;width:100vw;height:100vh}
#hud{position:fixed;left:18px;top:14px;background:#0009;padding:10px 13px;border:1px solid #ffffff22}
</style>
</head>
<body>
<canvas id="c"></canvas><div id="hud"></div>
<script>
const c=document.getElementById("c"),x=c.getContext("2d"),hud=document.getElementById("hud");
let W,H,pts,sorted,actions,ai,t0,doneAt,step=0;
function fit(){W=c.width=innerWidth*devicePixelRatio;H=c.height=innerHeight*devicePixelRatio;x.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);W=innerWidth;H=innerHeight}
onresize=fit;fit();
function cross(o,a,b){return(a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x)}
function reset(){
pts=Array.from({length:40},()=>({x:50+Math.random()*(W-100),y:70+Math.random()*(H-120)}));
let p0=pts.reduce((m,p)=>p.y>m.y||p.y==m.y&&p.x<m.x?p:m,pts[0]);
sorted=[...pts].sort((a,b)=>a===p0?-1:b===p0?1:Math.atan2(p0.y-a.y,a.x-p0.x)-Math.atan2(p0.y-b.y,b.x-p0.x)||((a.x-p0.x)**2+(a.y-p0.y)**2)-((b.x-p0.x)**2+(b.y-p0.y)**2));
actions=[{type:"sort",stack:[p0],i:0}];let st=[];
for(let i=0;i<sorted.length;i++){
let p=sorted[i];
while(st.length>=2&&cross(st[st.length-2],st[st.length-1],p)<=0){actions.push({type:"try",stack:[...st],cand:p,i});actions.push({type:"pop",stack:[...st],pop:st[st.length-1],cand:p,i});st.pop()}
actions.push({type:"push",stack:[...st],cand:p,i});st.push(p);actions.push({type:"stack",stack:[...st],i})
}
actions.push({type:"done",stack:[...st],i:sorted.length});ai=0;t0=performance.now();doneAt=0;step=0
}
function line(a,b,col,w=3){x.strokeStyle=col;x.lineWidth=w;x.beginPath();x.moveTo(a.x,a.y);x.lineTo(b.x,b.y);x.stroke()}
function poly(a,closed,col,fill){
if(a.length<2)return;x.beginPath();x.moveTo(a[0].x,a[0].y);for(let i=1;i<a.length;i++)x.lineTo(a[i].x,a[i].y);if(closed)x.closePath();
if(fill){x.fillStyle=fill;x.fill()}x.strokeStyle=col;x.lineWidth=3;x.stroke()
}
function dot(p,col,r=5){x.fillStyle=col;x.beginPath();x.arc(p.x,p.y,r,0,7);x.fill()}
function draw(a,blink){
x.clearRect(0,0,W,H);hud.textContent="Step "+step+" / "+(actions.length-1);
x.fillStyle="#141a20";x.fillRect(0,0,W,H);
if(a.type==="sort"){for(let i=0;i<sorted.length;i++)dot(sorted[i],`hsl(${i*8},80%,62%)`,4.5);return}
pts.forEach(p=>dot(p,"#7d8791",4));poly(a.stack,false,"#58a6ff");
if(a.cand&&a.stack.length)line(a.stack[a.stack.length-1],a.cand,a.type==="pop"&&blink?"#ff3b30":"#ffd166",4);
if(a.type==="pop"&&a.pop)dot(a.pop,blink?"#ff3b30":"#ff9f0a",9);
a.stack.forEach((p,i)=>dot(p,i==a.stack.length-1?"#58a6ff":"#2dd4bf",6));
if(a.cand)dot(a.cand,"#ffd166",7);
if(a.type==="done"){poly(a.stack,true,"#34d399","#34d39922");a.stack.forEach(p=>dot(p,"#34d399",6))}
}
function tick(now){
let a=actions[ai],blink=Math.sin(now/80)>0;
draw(a,blink);
if(now-t0>520){if(ai<actions.length-1){ai++;step++;t0=now}else{doneAt=doneAt||now;if(now-doneAt>1600)reset()}}
requestAnimationFrame(tick)
}
reset();requestAnimationFrame(tick);
</script>
</body>
</html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Graham Scan</title>
<style>
html,body{margin:0;background:#0b0b12;overflow:hidden;height:100%}
canvas{display:block}
</style></head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
let W,H;
function resize(){W=cv.width=innerWidth;H=cv.height=innerHeight;}
addEventListener('resize',resize);resize();
let pts,stack,idx,phase,stepCount,popPoint,lastTime,doneTime;
function cross(o,a,b){return (a.x-o.x)*(b.y-o.y)-(a.y-o.y)*(b.x-o.x);}
function init(){
  pts=[];
  const m=60;
  for(let i=0;i<40;i++) pts.push({x:m+Math.random()*(W-2*m),y:m+Math.random()*(H-2*m)});
  let pivot=pts[0];
  for(const p of pts) if(p.y>pivot.y||(p.y===pivot.y&&p.x<pivot.x)) pivot=p;
  pts.sort((a,b)=>{
    if(a===pivot) return -1;
    if(b===pivot) return 1;
    const ang=p=>Math.atan2(p.y-pivot.y,p.x-pivot.x);
    const aa=ang(a),ab=ang(b);
    if(aa!==ab) return aa-ab;
    const da=(a.x-pivot.x)**2+(a.y-pivot.y)**2;
    const db=(b.x-pivot.x)**2+(b.y-pivot.y)**2;
    return da-db;
  });
  stack=[pts[0],pts[1]];
  idx=2;
  phase='scan';
  stepCount=0;
  popPoint=null;
  doneTime=0;
  lastTime=0;
}
function step(){
  stepCount++;
  if(idx>=pts.length){ phase='done'; return; }
  const p=pts[idx];
  if(stack.length>=2 && cross(stack[stack.length-2],stack[stack.length-1],p)<=0){
    popPoint=stack.pop();
  } else {
    popPoint=null;
    stack.push(p);
    idx++;
  }
}
function draw(){
  ctx.fillStyle='#0b0b12';
  ctx.fillRect(0,0,W,H);
  for(const p of pts){
    ctx.beginPath();
    ctx.arc(p.x,p.y,4,0,Math.PI*2);
    ctx.fillStyle='#6cf';
    ctx.fill();
  }
  if(stack.length>1){
    ctx.beginPath();
    ctx.moveTo(stack[0].x,stack[0].y);
    for(let i=1;i<stack.length;i++) ctx.lineTo(stack[i].x,stack[i].y);
    if(phase==='done') ctx.closePath();
    ctx.strokeStyle=phase==='done' ? '#4f8' : '#fc0';
    ctx.lineWidth=2;
    ctx.stroke();
  }
  if(phase==='scan' && idx<pts.length){
    const p=pts[idx];
    ctx.beginPath();
    ctx.arc(p.x,p.y,7,0,Math.PI*2);
    ctx.strokeStyle='#f0f';
    ctx.lineWidth=2;
    ctx.stroke();
    if(stack.length){
      ctx.beginPath();
      ctx.moveTo(stack[stack.length-1].x,stack[stack.length-1].y);
      ctx.lineTo(p.x,p.y);
      ctx.strokeStyle='#f0f';
      ctx.stroke();
    }
  }
  if(popPoint){
    ctx.beginPath();
    ctx.arc(popPoint.x,popPoint.y,9,0,Math.PI*2);
    ctx.strokeStyle='#f33';
    ctx.lineWidth=3;
    ctx.stroke();
  }
  ctx.fillStyle='#fff';
  ctx.font='16px monospace';
  ctx.fillText('Graham Scan  Step: '+stepCount+'  Hull pts: '+stack.length,20,30);
  ctx.fillText(phase==='done' ? 'Hull complete - restarting...' : 'Scanning point '+idx+' / '+pts.length,20,52);
}
init();
function loop(t){
  if(!lastTime) lastTime=t;
  if(phase==='scan' && t-lastTime>110){ step(); lastTime=t; }
  if(phase==='done'){
    if(!doneTime) doneTime=t;
    if(t-doneTime>2200) init();
  }
  draw();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body></html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Graham Scan Convex Hull</title>
<style>
  body { margin: 0; padding: 10px; font-family: monospace; background: #000; color: #0f0; }
  canvas { display: block; margin: 10px auto; background: #111; border: 1px solid #0f0; }
  div { text-align: center; font-size: 14px; }
</style>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<div id="step">Step: 0</div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const w = canvas.width, h = canvas.height;

let points = [];
let hull = [];
let step = 0;
let sorted = [];
let phase = 'sort';
let time = 0;

function reset() {
  points = [];
  for (let i = 0; i < 40; i++) {
    points.push({ x: Math.random() * (w - 100) + 50, y: Math.random() * (h - 100) + 50 });
  }
  const lowest = points.reduce((a, b) => a.y < b.y ? a : b);
  sorted = points.sort((a, b) => {
    if (a === lowest) return -1;
    if (b === lowest) return 1;
    const cross = (a.x - lowest.x) * (b.y - lowest.y) - (a.y - lowest.y) * (b.x - lowest.x);
    return cross === 0 ? ((a.x - lowest.x) ** 2 + (a.y - lowest.y) ** 2) - ((b.x - lowest.x) ** 2 + (b.y - lowest.y) ** 2) : cross;
  });
  hull = [sorted[0]];
  step = 1;
  phase = 'scan';
  time = 0;
}

reset();

function cross(o, a, b) {
  return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
}

function animate() {
  if (phase === 'scan') {
    if (step < sorted.length) {
      const p = sorted[step];
      while (hull.length > 1 && cross(hull[hull.length - 2], hull[hull.length - 1], p) <= 0) {
        hull.pop();
      }
      hull.push(p);
      step++;
      time++;
    } else if (time > 120) {
      reset();
    }
  }

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

  ctx.fillStyle = '#666';
  for (let p of points) {
    ctx.fillRect(p.x - 2, p.y - 2, 4, 4);
  }

  ctx.fillStyle = '#0f0';
  for (let i = 0; i < Math.min(step, hull.length); i++) {
    ctx.fillRect(hull[i].x - 3, hull[i].y - 3, 6, 6);
  }

  if (hull.length > 1) {
    ctx.strokeStyle = '#0f0';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(hull[0].x, hull[0].y);
    for (let i = 1; i < hull.length; i++) {
      ctx.lineTo(hull[i].x, hull[i].y);
    }
    ctx.closePath();
    ctx.stroke();
  }

  ctx.fillStyle = '#0f0';
  ctx.font = '14px monospace';
  ctx.fillText('Step: ' + step + ' | Hull size: ' + hull.length, 20, 30);

  time++;
  requestAnimationFrame(animate);
}

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

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:14b —36.1 sOK
qwen3:8b ——harness
mistral-small:24b —129.5 sOK
qwen3-coder:30b —26.8 sOK
qwen3.5:9b —60.4 sOK
gemma4:26b ——harness
llama3.1:8b —128.9 sOK
deepseek-r1:14b —127.2 sOK
qwq:32b —148.9 sOK
deepseek-r1:32b —147.8 sOK
command-r:35b —133.2 sOK

Frontier reference — outside the blind field

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

Collected differently from the blind field: the local models run through a plain API harness, the frontier references through a context-free agent, one shot, no retries. Response times are therefore not comparable and are not shown. On two English tasks the reference answer came back in German — an artefact of that harness, flagged on the affected rounds.
Claude Opus 5frontier reference
<meta charset="utf-8">
<title>Graham Scan — Convex Hull Animation</title>
<style>
html,body{margin:0;height:100%;background:#070a12;display:grid;place-items:center}
canvas{background:radial-gradient(circle at 50% 42%,#111c33,#070a12 72%);border:1px solid #1d2c47;border-radius:12px;box-shadow:0 24px 70px #000a;max-width:96vw;height:auto}
</style>
<canvas id="c" width="900" height="600"></canvas>
<script>
const c=document.getElementById('c'),g=c.getContext('2d'),W=900,H=600;
let P,st,i,sortI,phase,steps,pops,flash,tick,hold,piv;
const ang=a=>Math.atan2(piv.y-a.y,a.x-piv.x);
const dist=(a,b)=>Math.hypot(a.x-b.x,a.y-b.y);
const cross=(o,a,b)=>(a.x-o.x)*(o.y-b.y)-(o.y-a.y)*(b.x-o.x);
function reset(){
  P=[];
  for(let k=0;k<40;k++)P.push({x:70+Math.random()*(W-140),y:115+Math.random()*(H-190)});
  piv=P[0];
  for(const q of P)if(q.y>piv.y||(q.y===piv.y&&q.x<piv.x))piv=q;
  P.sort((a,b)=>a===piv?-1:b===piv?1:(ang(a)-ang(b))||(dist(piv,a)-dist(piv,b)));
  st=[P[0]];i=1;sortI=0;phase='sort';steps=0;pops=0;flash=[];tick=0;hold=0;
}
function step(){
  steps++;
  if(phase==='sort'){if(++sortI>=P.length)phase='scan';return;}
  if(phase!=='scan')return;
  if(i>=P.length){phase='done';hold=120;return;}
  const n=st.length;
  if(n>=2&&cross(st[n-2],st[n-1],P[i])<=0){
    const d=st.pop();flash.push({x:d.x,y:d.y,t:1});pops++;
  }else st.push(P[i++]);
}
function dot(p,r,col){g.beginPath();g.arc(p.x,p.y,r,0,7);g.fillStyle=col;g.fill();}
function draw(){
  g.clearRect(0,0,W,H);
  const shown=phase==='sort'?sortI:P.length,fade=phase==='sort'?0.38:0.08;
  g.lineWidth=1;g.strokeStyle='rgba(96,150,230,'+fade+')';
  for(let k=1;k<shown;k++){g.beginPath();g.moveTo(piv.x,piv.y);g.lineTo(P[k].x,P[k].y);g.stroke();}
  if(phase==='sort'&&sortI<P.length){
    const p=P[sortI];
    g.strokeStyle='#ffd166';g.lineWidth=2;g.beginPath();g.moveTo(piv.x,piv.y);g.lineTo(p.x,p.y);g.stroke();
    dot(p,6,'#ffd166');
  }
  for(let k=0;k<P.length;k++){
    const p=P[k],used=phase!=='sort'&&k<i;
    dot(p,used?3.4:3,used?'rgba(150,180,220,.85)':'rgba(110,140,180,.55)');
  }
  if(st.length>1){
    g.beginPath();g.moveTo(st[0].x,st[0].y);
    for(let k=1;k<st.length;k++)g.lineTo(st[k].x,st[k].y);
    if(phase==='done'){g.closePath();g.fillStyle='rgba(80,230,185,.10)';g.fill();}
    g.strokeStyle=phase==='done'?'#5ff0c0':'#4fc3ff';
    g.lineWidth=2.6;g.shadowColor=g.strokeStyle;g.shadowBlur=14;g.stroke();g.shadowBlur=0;
  }
  for(const p of st)dot(p,5,phase==='done'?'#5ff0c0':'#4fc3ff');
  if(phase==='scan'&&i<P.length){
    const top=st[st.length-1],cd=P[i],n=st.length,
      bad=n>=2&&cross(st[n-2],st[n-1],cd)<=0,col=bad?'#ff6b7e':'#ffd166';
    g.setLineDash([7,7]);g.lineWidth=2.2;g.strokeStyle=col;
    g.beginPath();g.moveTo(top.x,top.y);g.lineTo(cd.x,cd.y);g.stroke();g.setLineDash([]);
    dot(cd,6.5,col);
    g.beginPath();g.arc(cd.x,cd.y,10+3*Math.sin(tick*0.18),0,7);
    g.strokeStyle=col;g.lineWidth=1.4;g.stroke();
    if(bad){
      g.strokeStyle='rgba(255,107,126,.85)';g.lineWidth=2;
      g.beginPath();g.moveTo(st[n-2].x,st[n-2].y);g.lineTo(st[n-1].x,st[n-1].y);g.stroke();
    }
  }
  g.font='12px ui-monospace,Menlo,Consolas,monospace';
  for(const f of flash){
    g.beginPath();g.arc(f.x,f.y,7+(1-f.t)*30,0,7);
    g.strokeStyle='rgba(255,90,110,'+f.t.toFixed(2)+')';g.lineWidth=2.2;g.stroke();
    g.fillStyle='rgba(255,140,155,'+f.t.toFixed(2)+')';g.fillText('POP',f.x+12,f.y-14-(1-f.t)*16);
  }
  dot(piv,7,'#ff5ec7');
  g.strokeStyle='rgba(255,94,199,.6)';g.lineWidth=1.5;
  g.beginPath();g.arc(piv.x,piv.y,12,0,7);g.stroke();
  g.fillStyle='#ff9ada';g.fillText('pivot',piv.x-14,piv.y+28);
  g.font='15px ui-monospace,Menlo,Consolas,monospace';
  g.fillStyle='#cfe3ff';g.fillText('GRAHAM SCAN · CONVEX HULL · n = 40',22,32);
  g.font='13px ui-monospace,Menlo,Consolas,monospace';
  const label={sort:'sorting by polar angle',scan:'scanning',done:'hull complete'}[phase];
  g.fillStyle='#7fa6d8';
  g.fillText('phase: '+label,22,56);
  g.fillText('step: '+steps+'    stack: '+st.length+'    pops: '+pops+'    scanned: '+Math.min(i,P.length)+'/'+P.length,22,76);
  if(phase==='done'){
    g.fillStyle='#5ff0c0';
    g.fillText('hull vertices: '+st.length+'  —  restarting with a fresh random set…',22,H-24);
  }
}
function loop(){
  tick++;
  for(const f of flash)f.t-=0.022;
  flash=flash.filter(f=>f.t>0);
  if(phase==='done'){if(--hold<=0)reset();}
  else if(tick%(phase==='sort'?2:7)===0)step();
  draw();
  requestAnimationFrame(loop);
}
reset();loop();
</script>