OverviewCode › R120
Code · blind round

R120 · Scanline fill of a self-intersecting polygon

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 fills a self-intersecting polygon on a <canvas> with a scanline algorithm: for every scanline, find all intersections with the polygon edges, sort them, and fill the spans according to the even-odd rule, so the overlapping regions stay unfilled. Let the polygon rotate and deform slowly, and draw the outline over the fill. Show the scanline count and the number of spans filled in the current frame. 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:14bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Scanline Fill</title>
    <style>body{margin:0;overflow:hidden;background:#111}canvas{display:block}</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const c = document.getElementById('c');
const ctx = c.getContext('2d');
let W, H;

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

let P = []; // Polygon vertices
let time = 0;
let frameCount = 0;

// Initialize a complex, self-intersecting polygon shape
function initPolygon() {
    P = [
        {x: W*0.1, y: H/2}, {x: W*0.4, y: H*0.7}, 
        {x: W*0.6, y: H*0.3}, {x: W*0.9, y: H/2}, 
        {x: W*0.8, y: H*0.1}, {x: W*0.45, y: H*0.5}
    ];
}

// Update polygon vertices with rotation and deformation
function updatePolygon() {
    const s = time * 0.0005;
    const d = Math.sin(time * 0.001);
    P[0] = {x: W*0.1 + Math.cos(s)*W*0.1, y: H/2 + d*30};
    P[1] = {x: W*0.4 + Math.sin(s)*50, y: H*0.7 - Math.cos(time * 0.0008)*30};
    P[2] = {x: W*0.6 + d*20, y: H*0.3 + Math.sin(s)*10};
    P[3] = {x: W*0.9 - Math.cos(s)*W*0.05, y: H/2 - Math.sin(time * 0.0008)*10};
    P[4] = {x: W*0.8 + d*30, y: H*0.1 + Math.cos(s)*10};
    P[5] = {x: W*0.45 - Math.sin(s)*20, y: H/2 + Math.sin(time * 0.0008)*30};
}

// Finds intersection x-coordinate for a line segment (p1, p2) with horizontal line y=y_scan
function intersect(p1, p2, y_scan) {
    if (Math.abs(p1.y - p2.y)) {
        const t = (y_scan - p1.y) / (p2.y - p1.y);
        return p1.x + t * (p2.x - p1.x);
    }
    return null; 
}

function drawFrame() {
    ctx.clearRect(0, 0, W, H);
    let scanlineCount = 0;
    let filledSpans = 0;

    // Iterate through all horizontal scanlines (y)
    for (let y = 0; y < H; y++) {
        scanlineCount++;
        let intersections = [];

        // Find all intersections for the current scanline y
        for (let i = 0; i < P.length; i++) {
            const p1 = P[i];
            const p2 = P[(i + 1) % P.length];
            
            const x_intersect = intersect(p1, p2, y);

            if (x_intersect !== null && x_intersect >= 0 && x_intersect <= W) {
                intersections.push(x_intersect);
            }
        }

        // Sort and remove duplicates for robust span calculation
        let sortedX = [...new Set(intersections)].sort((a, b) => a - b);

        // Apply Even-Odd Rule: Count how many intersections are present between pairs.
        let crossings = 0;
        for (let i = 0; i < sortedX.length; i++) {
            const is_crossing = Math.floor(i / 2) % 2 == 1; // True for the second, fourth, etc., intersection

            if (is_crossing) {
                // Start of a filled span
                let x_start = sortedX[i];
                let next_index = i + 1 < sortedX.length ? i + 1 : i;
                
                if (next_index < sortedX.length) {
                    let x_end = sortedX[next_index];

                    // Draw the filled span (Black fill, even-odd check)
                    ctx.fillStyle = '#336699'; 
                    ctx.fillRect(x_start, y, x_end - x_start, 1); // Width is small for visual effect
                    filledSpans++;
                }
            }
        }
    }

    // Draw the outline (always visible)
    ctx.strokeStyle = 'white';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let i = 0; i < P.length; i++) {
        const p1 = P[i];
        const p2 = P[(i + 1) % P.length];
        if (i === 0) ctx.moveTo(p1.x, p1.y);
        else ctx.lineTo(p1.x, p1.y);
        ctx.lineTo(p2.x, p2.y);
    }
    ctx.closePath();
    ctx.stroke();

    // Display stats
    let info = `Scanlines: ${scanlineCount}<br>Filled Spans: ${filledSpans}`;
    ctx.fillStyle = '#ccc';
    ctx.font = '16px Arial';
    ctx.fillText(info, 10, 25);

    frameCount++;
}

function animate() {
    time += 1;
    updatePolygon();
    drawFrame();
    requestAnimationFrame(animate);
}

initPolygon();
animate();
</script>
</body>
</html>
```
qwen3.5:9bERRBLANKMINIFIEDFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Scanline Fill</title></head>
<body><canvas id="c"></canvas><script>const c=document.getElementById('c');const ctx=c.getContext('2d');c.width=800;c.height=600;let p=[{x:150,y:300},{x:400,y:150},{x:700,y:350},{x:250,y:500},{x:50,y:400}];let v=0;const E=Math.PI/6;function init(){p=[{x:150,y:300},{x:400,y:150},{x:700,y:350},{x:250,y:500},{x:50,y:400}];v=0;ctx.clearRect(0,0,800,600)}function updateP(){p[0].x=150 + 100*Math.sin(v);p[0].y=300+50*Math.cos(v/2);p[1].x=400+150*Math.sin(v*0.8);p[1].y=150;p[2].x=700-50*Math.cos(v*0.5);p[2].y=350+100*Math.sin(v*0.2);p[3].x=250+80*Math.cos(v*1.2);p[3].y=500-70*Math.sin(v*0.9);p[4].x=50+100*Math.sin(v*0.6);p[4].y=400;v+=0.01}function intersect(p1,p2,y){if(Math.abs(p1.y-p2.y)<1e-6)return null;let x=p1.x+(p2.x-p1.x)*(y-p1.y)/(p2.y-p1.y);return Math.max(Math.min(Math.max(x,0),0),800)}function drawScanline(){ctx.fillStyle='#EEE';for(let y=0;y<600;y++){let intersects=[];for(let i=0;i<p.length;i++){intersects.push({x:intersect(p[i],p[(i+1)%p.length],y), edge:[p[i],p[(i+1)%p.length]]});}intersects.sort((a,b)=>a.x-b.x);let count=0;for(let i=0;i<intersects.length;i+=2){if(i+1 < intersects.length){ctx.fillRect(intersects[i].x,y,intersects[i+1].x-intersects[i].x,1);}else{break;}}return {scanlines:Math.floor(600/1),spans:Math.floor(intersects.length/2)};}function drawOutline(){ctx.strokeStyle='#333';ctx.lineWidth=2;for(let i=0;i<p.length;i++){ctx.beginPath();ctx.moveTo(p[i].x,p[i].y);ctx.lineTo(p[(i+1)%p.length].x,p[(i+1)%p.length].y);ctx.stroke();}function loop(){init();updateP();let stats=drawScanline();drawOutline();document.getElementById('s').innerHTML=`<span style="color:green;">Scanlines: ${stats.scanlines}</span> | <span style="color:blue;">Spans Filled: ${stats.spans}</span>`;requestAnimationFrame(loop)}</script><div id="s"></div><script>// Re-run the loop to handle initial state and stats display
let s=document.createElement('div');s.id='s';document.body.appendChild(s);
setInterval(()=>{},100); // Keep console alive during setup
loop();</script></body></html>
```
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Scanline Fill</title></head>
<body onload='start()'>
<style>
canvas{display:block;background:#222;}
</style>
<canvas id=c width=600 height=400></canvas>
<script>
function start(){
let ctx=document.getElementById('c').getContext('2d'),w=ctx.canvas.width,h=ctx.canvas.height,t=0;
const N=8,m={x:300,y:200},v=[];
for(let i=0;i<N;i+=1)v.push({x:i*75+Math.random()*50-25,y:(i%4)*10+Math.random()*40});
function draw(){
t+=.02,t%=6.283;
ctx.clearRect(0,0,w,h);
const pts=v.map(p=>({x:p.x+Math.cos(t+i*.71)*p.y*1.5,y:p.y+Math.sin(t+i*.9)*p.x*.5}));
pts.push(pts[0]);
let scanlines=0,spans=0;
for(let y=0;y<h;y++){
scanlines++;
let intersections=[];
for(let i=0;i<pts.length-1;i+=1){
const p0=pts[i],p1=pts[i+1];
if(p0.y>p1.y){[p0,p1]=[p1,p0];}
if(y>=Math.min(p0.y,p1.y)&&y<=Math.max(p0.y,p1.y)){
let x=(y-p0.y)*(p1.x-p0.x)/(p1.y-p0.y)+p0.x;
intersections.push(x);
}}
intersections.sort((a,b)=>a-b);
for(let i=0;i<intersections.length;i+=2){
ctx.fillStyle='#fff';ctx.fillRect(intersections[i],y,intersections[i+1]-intersections[i]+1,1);spans++;
}
}
ctx.strokeStyle='rgba(255,0,0,.3)';
for(let i=0;i<pts.length-1;i+=1){
const p0=pts[i],p1=pts[i+1];
ctx.beginPath();ctx.moveTo(p0.x,p0.y);ctx.lineTo(p1.x,p1.y);ctx.stroke();
}
ctx.fillStyle='black';ctx.font='14px Arial';
ctx.fillText(`Scanlines: ${scanlines}`,5,20);
ctx.fillText(`Spans Filled: ${spans}`,5,40);
requestAnimationFrame(draw);
}
draw();
}
</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:8bBLANKFORMAT
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>
var c = document.getElementById('c');
var ctx = c.getContext('2d');

var p = [];
for (var i = 0; i < 8; i++) {
    var x = Math.random() * 800;
    var y = Math.random() * 600;
    p.push({x: x, y: y});
}
function lerp(a, b, t) { return a + (b-a)*t; }

function draw() {
    ctx.clearRect(0, 0, c.width, c.height);
    ctx.beginPath();
    var i, ii;
    for (i = 0, ii = p.length-1; i < p.length; ii = i++, i++) {
        var x1 = p[i].x;
        var y1 = p[i].y;
        var x2 = p[ii].x;
        var y2 = p[ii].y;
        ctx.lineTo(x1+Math.sin(Date.now()/1000)*10, y1);
        ctx.lineTo(x2+Math.cos(Date.now()/1000)*20, y2);
    }
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2;
    ctx.stroke();
    
    var x0, y0, x1, y1;
    for (var y = 0; y < c.height; y++) {
        p.sort(function(a,b) { return a.y - b.y; });
        var l0 = {x: Infinity, y: y};
        var l1 = {x: -Infinity, y: y};
        var i;
        for (i = 0; i < p.length; i++) {
            x0 = p[i].x + Math.sin(Date.now()/100)*10;
            y0 = p[i].y;
            if (l0.x === Infinity) l0.x = x0;
            else if (l1.x === -Infinity) l1.x = x0;
            var ii;
            for (ii = i+1; ii < p.length && y <= p[ii].y; ii++) {
                x1 = p[ii].x + Math.cos(Date.now()/100)*20;
                y1 = p[ii].y;
                if ((y1-y) * (l0.y-y) > 0)
                    l0.x = Infinity, l0.y=y, ctx.lineTo(x0, y);
                else if ((y1-y) * (l1.y-y) > 0)
                    l1.x=-Infinity, l1.y=y, ctx.lineTo(x1, y);
                x0 = p[ii].x + Math.sin(Date.now()/100)*10;
                y0 = p[ii].y;
            }
        }
        
        var span_count = 0;
        for (i = 0; i < c.width+1; i++) {
            if ((Math.floor(i)==l0.x || Math.ceil(i) == l0.x) && !(span_count & 1)) ctx.fillStyle = 'white';
            else ctx.fillStyle = 'black';
            span_count++;
            ctx.fillRect(i, y, 1, 1);
        }
    }
    
    var scanline_count = (c.height - 1) * c.width;
    document.getElementById('scanlines').innerHTML = 'Scanline count: ' + Math.floor(scanline_count / 60) + ', Spans filled: ' + ctx.fillStyle;
}

ctx.canvas.addEventListener("click", function() {
    location.reload();
});

requestAnimationFrame(draw);
setInterval(function() { p.sort(function(a,b) { return a.y - b.y; }); }, 16);
</script>
<div id="scanlines"></div>
</body>
</html>
```
qwen3-coder:30bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>Scanline Fill</title></head><body style="margin:0;"><canvas id="c"></canvas><script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let vertices = [];
let time = 0;

function setup() {
    canvas.width = window.innerWidth * 0.95;
    canvas.height = window.innerHeight * 0.8;
    vertices = [];
    const numVertices = Math.floor(Math.random() * 6) + 4; // 4 to 9 vertices
    let centerX = canvas.width / 2;
    let centerY = canvas.height / 2;

    for (let i = 0; i < numVertices; i++) {
        const angle = (i / numVertices) * Math.PI * 2;
        const radius = Math.random() * 150 + 75; // Random base radius
        vertices.push({ x: centerX + Math.cos(angle) * radius, y: centerY + Math.sin(angle) * radius });
    }
}

function updateVertices(t) {
    const cx = canvas.width / 2;
    const cy = canvas.height / 2;
    const scaleFactor = 0.8 + 0.1 * Math.sin(t * 0.05);
    vertices[0].x = cx + Math.cos(t * 0.005) * (150 + 50 * Math.sin(t * 0.02));
    vertices[0].y = cy + Math.sin(t * 0.005) * (150 + 50 * Math.cos(t * 0.02));

    for (let i = 1; i < vertices.length; i++) {
        const p = vertices[i];
        // Deformation: makes the polygon breathe and rotate slightly
        p.x = cx + (Math.sin((i / vertices.length) * Math.PI * 2 + t * 0.001)) * (150 + 30 * Math.cos(t * 0.01));
        p.y = cy + (Math.cos((i / vertices.length) * Math.PI * 2 - t * 0.001)) * (150 + 30 * Math.sin(t * 0.01));
    }
}

function getIntersectionX(p1, p2, y) {
    if (Math.abs(p1.y - p2.y) < 0.1) return null; // Horizontal line or too close
    return p1.x + (p2.x - p1.x) * ((y - p1.y) / (p2.y - p1.y));
}

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

    const numVertices = vertices.length;
    let filledSpans = 0;
    let scanlines = 0;

    // Iterate over all possible scanlines (y coordinates)
    for (let y = 1; y < canvas.height - 1; y++) {
        scanlines++;
        let intersections = [];

        // Check every edge of the polygon
        for (let i = 0; i < numVertices; i++) {
            const p1 = vertices[i];
            const p2 = vertices[(i + 1) % numVertices];

            // Ensure the scanline intersects the vertical span of the edge
            if ((Math.min(p1.y, p2.y) <= y && Math.max(p1.y, p2.y) >= y)) {
                const x = getIntersectionX(p1, p2, y);
                if (x !== null) {
                    intersections.push(x);
                }
            }
        }

        // Sort intersections by X coordinate
        intersections.sort((a, b) => a - b);

        let count = 0;
        ctx.fillStyle = 'rgba(0, 150, 255, 0.3)'; // Fill color
        for (let i = 0; i < intersections.length; i++) {
            const xStart = intersections[i];
            if (count % 2 === 0) { // Even index means inside (1st, 3rd, 5th span...)
                // This is the start of a filled span
                ctx.fillRect(xStart, y, 1, canvas.height); // Dummy rect for calculation
            }
            count++;
        }

        // Re-draw actual spans using even-odd rule
        let currentX = intersections[0];
        for (let i = 0; i < intersections.length - 1; i++) {
            const xLow = intersections[i];
            const xHigh = intersections[i + 1];

            // Even-odd rule: Fill if index is even (0, 2, 4...)
            if (i % 2 === 0) {
                ctx.fillRect(xLow, y, xHigh - xLow, 1); // Draw a single pixel high line segment
                filledSpans++;
            }
        }

    }

    // --- Drawing the Outline ---
    ctx.strokeStyle = '#fff';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let i = 0; i < numVertices; i++) {
        const p = vertices[i];
        if (i === 0) {
            ctx.moveTo(p.x, p.y);
        } else {
            ctx.lineTo(p.x, p.y);
        }
    }
    ctx.closePath();
    ctx.stroke();

    // --- Debug Text ---
    const statsText = `Scanlines: ${scanlines}<br>Spans Filled: ${filledSpans}`;
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
    ctx.font = '14px Arial';
    ctx.fillText(statsText, 10, canvas.height - 10);

    // Request next frame
    time += 1;
    requestAnimationFrame(() => {
        updateVertices(time);
        scanlineFill();
    });
}

// Initialization and start command
window.onload = () => {
    setup();
    requestAnimationFrame(() => scanlineFill());
};
</script></body></html>
```
deepseek-r1:14bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><style>canvas{border:1px solid black}.stats{position:absolute;top:0;left:5px;color:#fff;background:#000;padding:2px}</style></head><body><div class="stats" id="scanlines">Scanlines:<span id="sl">0</span></div><div class="stats" id="spans">Spans filled:<span id="sf">0</span></div><canvas width=600 height=400 id=c></canvas>

<script>
class Polygon {
  constructor() {
    this.points = [];
    const r = Math.PI * 2;
    for (let i = 0; i < 8; i++) 
      this.points.push({x: 100 + Math.cos(i * r / 6) * 70, y: 150 + Math.sin(i * r / 6) * 70});
    this.theta = 0;
  }

  update() {
    const c = document.getElementById('c');
    const scale = .8;
    for (let i in this.points)
      this.points[i] = {x: 300 + Math.cos(this.points[i].x * scale) * 100, y: 200 + Math.sin(this.points[i].y * scale) * 100};
    this.theta += .05;
  }
}

class ScanlineFiller {
  constructor() {this.canvas = document.getElementById('c');}
  
  getIntersections(y, polygon) {
    let intersections = [];
    for (let i = 0; i < polygon.points.length - 1; i++) {
      const p1 = polygon.points[i];
      const p2 = polygon.points[(i+1)%polygon.points.length];
      if (p1.y == p2.y) continue;
      const xIntersect = this.getXAtY(y, p1, p2);
      if (xIntersect != null)
        intersections.push(xIntersect);
    }
    return intersections.sort((a,b)=>a-b);
  }

  getXAtY(y, p1, p2) {
    if ((p1.y > y && p2.y < y) || (p1.y < y && p2.y > y)) {
      const slope = (p2.x - p1.x)/(p2.y - p1.y);
      return p1.x + (y - p1.y)*slope;
    }
    return null;
  }

  scanlineFill() {
    const ctx = this.canvas.getContext('2d');
    const polygon = new Polygon();
    let scanlinesDrawn = 0, filledSpans = 0;

    ctx.fillStyle = '#fff';
    ctx.beginPath();
    this.drawPolygon(ctx, polygon.points);
    ctx.strokeStyle = '#000'; ctx.lineWidth=1;
    
    for (let y = 0; y < this.canvas.height; y++) {
      const intersections = this.getIntersections(y, polygon);
      if (!intersections.length) continue;

      scanlinesDrawn++;
      let prevX = -Infinity;
      for (let i = 0; i < intersections.length; i += 2) {
        const x1 = intersections[i];
        const x2 = intersections[i+1] || Infinity;
        
        if (x1 > x2) continue;
        filledSpans++;
        ctx.fillStyle = '#f00';
        ctx.fillRect(x1, y, x2 - x1, 1);
      }
    }

    ctx.fill();
    document.getElementById('sl').textContent = scanlinesDrawn;
    document.getElementById('sf').textContent = filledSpans;
  }

  drawPolygon(ctx, points) {
    ctx.moveTo(points[0].x, points[0].y);
    for (let i=1; i<points.length; i++)
      ctx.lineTo(points[i].x, points[i].y);
    ctx.closePath();
  }
}

function animate() {
  const filler = new ScanlineFiller();
  filler.scanlineFill();
  requestAnimationFrame(animate);
}

animate();

function sign(a) {return a > 0 ? 1 : -1;}

// The following is a utility function for edge winding order
function getSign(px, py, x1, y1, x2, y2) {
  const ax = px - x1;
  const ay = py - y1;
  const bx = x2 - x1;
  const by = y2 - y1;
  return (ax*by - ay*bx);
}

</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.
command-r:35bOK
Show code
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<title>Scanline Algorithm</title>
		<style>
			body { margin: 0; }
			canvas { display: block; }
		</style>
	</head>

	<body>

		<script>

			const canvas = document.createElement('canvas');
			document.body.append(canvas);
			const ctx = canvas.getContext('2d');

			let w = window.innerWidth, h = window.innerHeight;
			if (w > 800) {
				w = 800;
                h = Math.round(800 * (h / w));
            }
			canvas.width = w;
			canvas.height = h;

			const a_step = 2 * Math.PI / 36, b_step = 1.5 * Math.PI / 36, c_step = .87 * Math.PI / 36, d_step = 4 * Math.PI / 36;

			let a = 0, b = 0, c = 0, d = 0;

			function init() {
				a = Math.random() * (2 * Math.PI);
				b = Math.random() * (2 * Math.PI) - .5;
				c =  1 + Math.random() * (1.3);
				d = 0;
			}

			function drawPoint(x, y) {
                ctx.fillStyle = "rgba(0,0,0,.6)";
                ctx.beginPath();
                ctx.arc(x, y, 5, 0, 2 * Math.PI);
				ctx.fill();
			}

			function draw() {

				init();

				const lines = [];

				for (let i = -1; i < h + 1; i += 1) {

					const xes = [];
					let prev_x, prev_inside = false;

					for (let j = -1; j < w + 1; j += 1) {

						const inside =
							(
								Math.sin(j * Math.cos(a) + i * Math.sin(b)) >
								c * Math.abs(
									Math.sin(i * d + (
										-w / 2 + j
									) * Math.tan(3 * a - b))
								)
							);

						if (inside && !prev_inside) {

							xes.push(prev_x);

                        } else if (!inside && prev_inside) {

                            xes.push(j);
                        }

                        drawPoint(j, i);
						prev_inside = inside;
						prev_x = j;
					}

					if (xes[0] !== -1 && prev_x !== w) {

						xes.push(-1, prev_x);
                    }

                    if (xes.length > 2) {

                        lines.push([i, xes]);
                    }
				}

				ctx.lineWidth = 3;
				ctx.strokeStyle = "#0F0";
                ctx.beginPath();

                let nspans = 0;
				lines.forEach(([y, [l, ...r]]) => {

                    l++;
					while (l < r.length) {

						const x1 = Math.floor(r[l - 1]),
							  x2 = Math.ceil(r[l]);

                        if (x2 > x1 + 1) {

                            nspans += x2 - x1;
                            ctx.moveTo(x1, y);
                            ctx.lineTo(x2, y);
                        }

						l++;
                    }
				});

                ctx.stroke();

				const spanCount = document.createElement('span');
                spanCount.style = "position: absolute; left: 0px; top: 10px; color: black; background-color: white; padding: 2px 5px";
				document.body.append(spanCount);

				const scanlinesCountEl = document.createElement('span');
                scanlinesCountEl.style = "position: absolute; left: 0px; top: 30px; color: black; background-color: white; padding: 2px 5px";
				document.body.append(scanlinesCountEl);

				scanlinesCountEl.textContent = "" + lines.length;
                spanCount.textContent = "" + nspans;

                ctx.fillStyle = 'rgba(0, 153, 255, .1)';

				ctx.translate(-w / 2, -h / 2);
				ctx.scale((Math.PI * 4) / w, (Math.PI * 6) / h);
                ctx.rotate(a + b + d);

                a += a_step;
                b += b_step;
                c += c_step;
                d += d_step;

				ctx.fillRect(-w / 2, -h / 2, w, h);
			}

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

            window.addEventListener('resize', () => {

                if (window.innerWidth > 800) {

                    canvas.width = 800;
                    canvas.height = Math.round(800 * (window.innerHeight / window.innerWidth));
                } else {
					canvas.width = w = window.innerWidth;
					canvas.height = h = window.innerHeight;
				}
			});

			animate();

		</script>

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

Okay, I need to create an HTML file that draws a rotating and deforming polygon using a scanline algorithm. The key points are: the polygon is self-intersecting, the fill uses even-odd rule via scanlines, show scanline count and spans filled per frame, and it should animate smoothly with requestAnimationFrame. Also, it must be self-contained, under 7000 characters, no external libraries, and restart on finish.

First, I need the HTML5 canvas setup. The canvas element is straightforward. Then, the JavaScript part. Let me think about the steps.

1. **Setup Canvas**: Get the canvas and context, set size to something like 500x500. Maybe scale the context to make it easier.

2. **Polygon Definition**: The polygon needs to rotate and deform. Maybe use a regular polygon but rotate its vertices. To deform, perhaps add some random offsets or use a sine wave for the vertices' positions. The vertices should be calculated each frame.

3. **Scanline Algorithm**: For each horizontal line (scanline), find all edges it intersects with the polygon edges. Sort the intersections, then determine which spans to fill using even-odd rule. The even-odd rule alternates filling starting from the first intersection.

4. **Animation Loop**: Use requestAnimationFrame. Each frame, clear the canvas, update polygon vertices, compute fill via scanlines, draw outline, then display stats (scanlines and spans).

5. **Restarting**: When animation is done (maybe after a certain time?), restart by resetting a timer or something. But since it should loop indefinitely, maybe just reset the polygon parameters each time the animation loop restarts.

Let me break it down step by step.

Starting with the HTML structure:

```html
<canvas></canvas>
<script>
// code here
</script>
```

Now, in the script:

Initialize variables. The canvas needs to be accessed via getElementById, so maybe give it an id. Let's say:

```html
<canvas id="c"></canvas>
<script>
var ctx = document.getElementById('c').getContext('2d');
c.width = c.height = 500;
ctx.scale(1,1); // or maybe more?
```

Wait, maybe scaling could be useful, but let's see.

The polygon vertices need to be generated each frame. Let's say the polygon has N vertices. Let's choose N as 6 for a hexagon, but with some deformation. To make it deforming, perhaps each vertex has a displacement based on time.

Define variables:

var verts = [], t = 0, angle = 0, n=6;

Each frame, t increases, angle is updated based on time.

To generate vertices:

for (var i=0; i<n; i++) {
    var a = (i/n)*Math.PI*2 + angle;
    var r = 150 + 50*(Math.sin(t*2 + a)+1); // radius varying with time and angle
    var x = 250 + r*Math.cos(a);
    var y = 250 + r*Math.sin(a);
    verts.push({x,y});
    // add some deformation here?
}

Wait, but that might not create a self-intersecting polygon unless the radius is varying. Alternatively, maybe a star shape or changing the angle?

Alternatively, use a convex polygon but rotate and shift vertices to create intersections. Hmm, perhaps a better approach is to have overlapping edges by moving vertices in/out over time.

Alternatively, use a polygon with vertices that cross over each other as they move. Maybe a simple method is to use a polygon with vertices arranged in a way that edges cross when rotated.

Wait, maybe start with a simple star polygon (like 5-pointed star), which is self-intersecting, but I need to make it rotate and deform. Alternatively, use a polygon with vertices that are all moving in a way that edges cross.

Alternatively, let's make a polygon where each vertex is moving along a circular path but with different phases, leading to intersections. For example, each vertex's angle is offset by some function of time.

Alternatively, to keep it simple, use a random polygon that deforms each frame. But the problem requires it to animate smoothly. Hmm.

Alternatively, let's make the polygon rotate and have each vertex's distance from the center vary sinusoidally, causing the shape to expand/contract and potentially leading to intersections when the radius variations cause edges to cross.

So, let's proceed with that approach.

Initialize the vertices each frame:

n = 6;

for (i=0; i < n; i++) {
    // angle for vertex i
    var a = (i * 2 * Math.PI)/n + angle;
    var r = 150 + 50 * Math.sin(t + i/3) + 30 * Math.sin(2*t + i/2);
    // x and y based on angle and radius
    var x = 250 + r * Math.cos(a);
    var y = 250 + r * Math.sin(a);
    verts.push({x, y});
}

The angle variable can be incremented each frame for rotation. The radius variations with sine functions of t and i will deform the polygon.

But need to ensure that edges cross, so maybe more drastic changes.

Alternatively, perhaps when the radius for some vertices goes negative, but that might complicate.

Alternatively, maybe shift vertices in x or y direction sinusoidally.

But for simplicity, let's go with this approach for now.

Next, the scanline algorithm.

The algorithm steps per frame:

1. Clear the canvas.
2. Generate the polygon vertices.
3. Compute all edges (line segments) of the polygon.
4. For each scanline (y from 0 to height):
   a. Find all edges that intersect with this y.
   b. For each intersecting edge, compute the x-coordinate of intersection.
   c. Sort these x-values.
   d. Using even-odd rule: for every pair, if the count is odd, fill between x[i] and x[i+1] if they are alternating. Wait, even-odd rule requires that regions are filled if entered an odd number of times.

Alternatively, for even-odd, the spans between intersections are filled if the number of intersections up to that point is odd.

Wait, the even-odd rule states that a point is inside if the number of edges crossing a ray from the point to infinity is odd.

For the scanline algorithm, to implement even-odd, the spans are filled between intersections when the count is odd. So, for a sorted list of x-coordinates, every pair of consecutive x's where the index is odd (1st, 3rd, etc.) would be a span to fill?

Wait, actually, the even-odd rule can be handled by alternating between filling and not filling as we traverse the intersections. The first pair (x0, x1) is filled (since count is 1, odd), next pair (x2, x3) would be not filled (count is 2, even), but wait no, the count increases by each edge. Hmm, perhaps it's better to pair the intersections:

Once sorted, the intersections are in order x0, x1, x2, x3,... So between x0 and x1, that's one edge crossing, so fill. Between x1 and x2 is another crossing, so that would be even crossings up to x2, so not filled. Wait, actually, the even-odd alternates each edge. So every pair of intersections (i and i+1) forms a segment. The fill alternates with each pair. So the first pair (0-1) is filled, next pair (2-3) not, etc.

Therefore, for each sorted list of intersections:

for (let i=0; i < intersections.length; i += 2) {
    if there is an i+1, then fill between intersections[i] and intersections[i+1]
}

Wait, but even-odd is about the parity of the number of edges crossed. So the area between the first and second intersection is inside (odd count), between second and third is outside (even), etc. So yes, every even index (0,2,4 etc.) paired with the next odd index (1,3,5) forms a span that is filled if the index is odd in count.

Wait, let me think: for even-odd, a point is inside if the number of edges above it is odd. So for a scanline, each intersection is a crossing. Starting from left, every time you cross an edge, you toggle inside/outside. So the first intersection (x0) is the start, but you are inside only after an odd number of crossings. So between x0 and x1: one crossing, so inside (filled). Then between x1 and x2: two crossings (even), so outside (unfilled). Between x2 and x3: three crossings (odd) filled, etc.

Thus, the filled regions are between x0-x1, x2-x3, x4-x5, etc. So every even index (starting at 0) paired with the next odd index.

Therefore, for the sorted x array, the filled spans are pairs (x[0],x[1]), (x[2],x[3]), etc.

Thus, the algorithm steps for a scanline y:

- collect all edges that cross this y. For each edge, compute if it intersects, and the x at that y.

- collect all x's where the edge crosses the scanline y.

- sort the x's.

- loop through pairs, step by 2, and fill each pair if there is a next x.

Thus, in code:

for each y in 0 to height:

   let intersections = [];

   for each edge (from prev vertex to current vertex):

      let p0 = prev vertex, p1 = current vertex.

      if the edge crosses y:

          compute x where the edge intersects y.

          add to intersections.

   sort intersections.

   count the number of filled spans:

   spans = 0;

   for (let i=0; i<intersections.length; i +=2) {

       let x0 = intersections[i], x1 = intersections[i+1]

       if (x1 !== undefined) {

           fill the horizontal line between x0 and x1 at y.

           spans +=1;

       }

   }

   track total scanlines and spans per frame.

Now, the code:

But how to compute edge intersections with y?

For each edge between (x0,y0) and (x1,y1). To see if the edge crosses the current y.

The standard line equation:

Let me parameterize the edge between two points p0 (x0,y0) and p1 (x1,y1).

We want to find if the edge crosses the horizontal line y = scanY.

The edge can be represented parametrically:

for t from 0 to 1:

x = x0 + t*(x1-x0)

y = y0 + t*(y1-y0)

We want y == scanY. So:

t = (scanY - y0)/(y1 - y0) 

But only if y1 != y0 (vertical edge). If y1 == y0, then the edge is horizontal and either entirely above, below, or on the line.

So steps for edge p0 to p1 and scanY:

if y0 == y1: then the edge is horizontal. It only contributes to the scanline if scanY is exactly y0. In that case, the edge is along the y, so contributes two endpoints?

Wait, but in that case, the line is along the scanline, so each end is part of the intersections. However, two endpoints on the same line would need to be considered as part of the intersections? Hmm, but maybe for edges that are exactly on the scanline, we can treat them as contributing their x coordinates.

But to avoid division by zero and handle vertical edges:

Alternatively, code can be:

for each edge p0, p1:

let y0 = p0.y, y1 = p1.y;

if (y0 > scanY && y1 > scanY) → no (since both above, so edge doesn't cross)

if (y0 < scanY && y1 < scanY) → no (both below)

else → the edge crosses the scanline.

Then compute the intersection x.

But need to compute x at the point where y == scanY.

But when y0 == y1 → edge is horizontal. So if scanY == y0 → then the intersection is the entire edge's x range?

Wait, but in that case, the edge's entire length is on the scanline, so it would contribute two x's: the x0 and x1 of the edge. However, since edges are part of the polygon, overlapping edges would contribute their own intersections. Hmm, but for the purpose of the even-odd rule, the horizontal edge on the scanline would count as two crossings? Because entering and leaving?

Wait, no. If an edge is exactly on the scanline, then it contributes a crossing for each direction. Wait, actually, if a horizontal edge is on the scanline, then it's part of the polygon boundary. The even-odd rule considers the edge as part of the polygon but the crossing is only counted once. Hmm, perhaps edge directions matter? Because the polygon is closed, so maybe when traversing edges, the direction (CW or CCW) affects the crossing count?

Hmm, this might complicate things. To simplify, maybe in this case, treat a horizontal edge on the scanline as contributing two intersections (the two endpoints), but that may lead to double-counting. Alternatively, perhaps it's better to not include exact edges on the scanline unless they cross from below to above or vice versa.

Alternatively, perhaps approximate by ignoring edges exactly on the scanline, but that might lose some cases. Hmm.

Alternatively, in code:

if (y0 < scanY && y1 > scanY) → upward crossing

OR (y0 > scanY && y1 < scanY) → downward crossing

then compute t.

if (y0 == scanY) and (y1 != scanY):

then depending on direction, perhaps the edge starts at scanY and proceeds upwards or downwards.

This is getting a bit involved, but perhaps we can proceed with the following code:

for each edge between p0 and p1:

var y0 = p0.y, y1 = p1.y;

if (y0 === y1) continue; // horizontal edges won't cross? Or need to handle?

Wait, but if the edge is exactly on the scanline (y0 == y1 == scanY), then the entire edge is on the scanline. But in that case, the edge contributes to the crossing count as two points?

Alternatively, perhaps the even-odd rule counts such edges as contributing 1 crossing. Hmm, this is getting too complicated. Maybe for simplicity, let's proceed with the standard approach for non-vertical edges, and ignore horizontal edges for now. Since in the problem statement, the polygon is self-intersecting, but the code needs to handle all cases.

Alternatively, to avoid dealing with horizontal edges, we can just compute the intersections where the edge crosses the scanline from below to above or vice versa.

Wait, here's a standard approach from computational geometry for Bresenham's or scanline algorithm:

The algorithm for edge intersections with a scanline is:

For an edge from (x0,y0) to (x1,y1):

Compute if the edge crosses the current scanline y = currentY.

If it does, compute the x coordinate at that y.

The edge crosses the scanline if (y0 < currentY and y1 > currentY) OR (y0 > currentY and y1 < currentY).

If it does cross, compute t as (currentY - y0)/(y1 - y0). Then x = x0 + t*(x1 - x0).

This way, we find the x coordinate where the edge intersects the scanline.

This method ignores edges that are exactly on the scanline or horizontal. So for edges exactly on the scanline, they don't contribute an intersection. But in that case, their presence might contribute to the even-odd count.

Hmm, perhaps in some cases this might miss intersections, but for the purposes of this question, maybe it's acceptable to proceed this way.

Thus, in code, for each edge:

function getIntersection(p0, p1, y) {

    let y0 = p0.y, y1 = p1.y;

    if (y0 === y1) return null; // horizontal edge, ignore

    if ( (y0 < y && y1 > y) || (y0 > y && y1 < y) ) {

        let t = (y - y0) / (y1 - y0);

        return p0.x + t*(p1.x - p0.x);

    }

    return null;

}

Thus, this function returns the x coordinate where the edge intersects the scanline at y, or null if no crossing.

So for each edge, we can call this function for the current scanline y. If it returns an x value, add it to the intersections array for that scanline.

Once all edges are processed for the scanline, sort the intersections array, then process them in pairs, as explained.

Now, to implement this in JavaScript.

The animation loop:

function animate() {

    requestAnimationFrame(animate);

    // clear canvas

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

    // update time and angle

    t += 0.05;

    angle += 0.03; // rotation speed

    // generate polygon vertices

    let n = 6;

    let verts = [];

    for (let i=0; i < n; i++) {

        let a = (i/n)*Math.PI*2 + angle;

        let r = 150 + 50 * Math.sin(t + i/3) + 30 * Math.sin(2*t + i/2);

        let x = 250 + r * Math.cos(a);

        let y = 250 + r * Math.sin(a);

        verts.push({x,y});

    }

    // close the polygon by adding first vertex again at end?

    // no, edges are between consecutive vertices, so last to first?

    // yes, edges are between i and i+1, and last to first.

    // Now, for each scanline y:

    // to optimize, can loop over all y from 0 to height.

    // but this is O(height * edges) which is slow for large canvas.

    // but for a 600x600 canvas, 600*6 edges per frame may be okay?

    // Alternatively, can do this with a canvas approach.

    // However, in JavaScript, for a 600x600, 600 scanlines each with 6 edges, that is 3600 operations per frame.

    // which might be manageable.

    let totalSpans = 0;

    let totalScanlines = 0;

    for (let y = 0; y < c.height; y++) {

        totalScanlines++;

        let intersections = [];

        for (let i=0; i < n; i++) {

            let p0 = verts[i];

            let p1 = verts[(i+1)%n]; // wrap around for last to first

            let x = getIntersection(p0, p1, y);

            if (x !== null) {

                intersections.push(x);

            }

        }

        intersections.sort( (a,b) => a - b );

        let spansForThisLine = 0;

        for (let i=0; i < intersections.length; i +=2) {

            let x0 = intersections[i];

            let x1 = intersections[i+1];

            if (x1 !== undefined) {

                spansForThisLine +=1;

                // fill the line from Math.floor(x0) to Math.ceil(x1)? Or just as a horizontal line?

                // To draw, use ctx.fillRect(x0, y, x1 -x0, 1)

                // but need to compute integer coordinates.

                ctx.fillRect(x0, y, x1 -x0, 1);

            }

        }

        totalSpans += spansForThisLine;

    }

    // draw polygon outline

    ctx.strokeStyle = '#000';

    ctx.beginPath();

    ctx.moveTo(verts[0].x, verts[0].y);

    for (let i=1; i < n; i++) {

        ctx.lineTo(verts[i].x, verts[i].y);

    }

    ctx.closePath();

    ctx.stroke();

    // display stats

    document.getElementById('stats').innerHTML = 

        `Scanlines: ${totalScanlines}, Spans: ${totalSpans}`;

    // after some time, reset to avoid overflow?

    // or just let it go.

}

Wait, but this approach of looping through every pixel's scanline is going to be very slow in JavaScript, especially for a 600x600 canvas, which would be 600 scanlines * each with 6 edges (6 vertices, so 6 edges) → but for each edge, we have to do the calculation. For 600 scanlines, 6 edges each, that’s 3600 operations per frame. Which is manageable?

But in reality, for each scanline y, we have to process all edges (6 edges) and compute their intersection, then sort the array (of say 6*1=6 elements per scanline). That's manageable.

But in terms of drawing, using ctx.fillRect for each span could be slow, because each fillRect is an individual drawing command, which is expensive. So for each span, we have to issue a fillRect, and there could be many spans per frame.

Perhaps a more efficient way is to use the canvas's lineTo or other path-based methods, but with the scanline approach, it's not straightforward.

Alternative idea: instead of drawing pixel by pixel, can we create a path for the filled regions?

Alternatively, to precompute all the filled regions and draw them as a single path? Not sure.

Alternatively, since the filled regions are horizontal spans on each scanline, perhaps we can use the canvas's fill() with a path that is built by adding each span as a horizontal line. But that might be as slow as the current method.

Hmm, perhaps this is the best way for now, even if it's a bit slow.

Testing this code:

But first, in the code above, the edges are between each consecutive pair including the last to first (due to (i+1) mod n). That is correct.

The function getIntersection is implemented as:

function getIntersection(p0, p1, y) {

    let y0 = p0.y, y1 = p1.y;

    if (y0 === y1) return null;

    if ( (y0 < y && y1 > y) || (y0 > y && y1 < y) ) {

        let t = (y - y0) / (y1 - y0);

        return p0.x + t*(p1.x - p0.x);

    }

    return null;

}

Now, in the animate function, for each edge in the polygon, between i and next vertex.

Wait, in the code:

for (let i=0; i < n; i++) {

    let p0 = verts[i];

    let p1 = verts[(i+1)%n]; // wrap around for last to first

    let x = getIntersection(p0, p1, y);

    if (x !== null) {

        intersections.push(x);

    }

}

So that loops through each edge (since each i is an edge from i to i+1, and the last wraps to 0, so total n edges).

Thus, this part is okay.

Now, the drawing of spans:

ctx.fillRect(x0, y, x1 -x0, 1);

But since x0 and x1 can be floating points, and the canvas is in pixels, this should be okay as fillRect accepts floats.

But the problem is that for each span, we call fillRect, which is a separate command. Thus, for many spans, this could be slow.

Another idea is to build a path:

ctx.beginPath();

for each y and each span's x0, x1:

ctx.rect(x0, y, x1 -x0, 1);

then ctx.fill();

But even that, for each span, adding a rect is O(1), but the total number of rects could be high.

Alternatively, maybe precompute all the spans and collect their coordinates, then draw using a path. But this might not save much.

Alternatively, use a canvas's imageData and manipulate pixel by pixel, but that's probably slower.

Hmm, perhaps for the scope of this problem, even if it's a bit slow, it's okay. The code should work.

Now, the problem mentions to create the code, so we need to write this in a way that can run.

Potential issues:

- The vertices may be outside the canvas, leading to edges that are not drawn properly, but the code should handle it as the fillRect would just not draw anything.

- The edges might be anti-aliased, but the problem doesn't require that.

- The polygon outline is drawn after the filled regions, so the outline will be on top.

Now, to implement this code, let's structure it step by step:

First, set up the canvas:

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

<div id="stats"></div>

Then the JavaScript:

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

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

let t = 0;

function getIntersection(p0, p1, y) {

    let y0 = p0.y, y1 = p1.y;

    if (y0 === y1) return null;

    if ( (y0 < y && y1 > y) || (y0 > y && y1 < y) ) {

        let tParam = (y - y0) / (y1 - y0);

        return p0.x + tParam * (p1.x - p0.x);

    }

    return null;

}

function animate() {

    requestAnimationFrame(animate);

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

    t += 0.05;

    let angle = t * 0.3; // instead of a separate angle variable, perhaps link to t for simplicity? Or use another variable?

    // Wait, in the previous code I had angle += 0.03, but in this code, using let angle = t*0.3 would be better.

    // Let me re-express:

    let angle = t * 0.3;

    const n = 6;

    const verts = [];

    for (let i = 0; i < n; i++) {

        let a = (i / n) * Math.PI * 2 + angle;

        let r = 150 + 50 * Math.sin(t + i / 3) + 30 * Math.sin(2 * t + i / 2);

        let x = 300 + r * Math.cos(a); // centered at 300,300 (assuming 600x600 canvas)

        let y = 300 + r * Math.sin(a);

        verts.push({x, y});

    }

    let totalSpans = 0;

    let totalScanlines = 0;

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

        totalScanlines++;

        let intersections = [];

        for (let i = 0; i < n; i++) {

            const p0 = verts[i];

            const p1 = verts[(i + 1) % n];

            const x = getIntersection(p0, p1, y);

            if (x !== null) {

                intersections.push(x);

            }

        }

        intersections.sort((a, b) => a - b);

        let spansForThisLine = 0;

        for (let i = 0; i < intersections.length; i += 2) {

            const x0 = intersections[i];

            const x1 = intersections[i + 1];

            if (x1 !== undefined) {

                spansForThisLine++;

                ctx.fillRect(x0, y, x1 - x0, 1);

            }

        }

        totalSpans += spansForThisLine;

    }

    // Draw outline

    ctx.strokeStyle = '#000';

    ctx.beginPath();

    ctx.moveTo(verts[0].x, verts[0].y);

    for (let i = 1; i < n; i++) {

        ctx.lineTo(verts[i].x, verts[i].y);

    }

    ctx.closePath();

    ctx.stroke();

    document.getElementById('stats').textContent = `Scanlines: ${totalScanlines}, Spans: ${totalSpans}`;

}

animate();

Wait, in the above code, I adjusted the center from 300,300 (assuming 600x600 canvas). The initial code had 300, but let's make sure that the canvas is 600x600.

Wait, in the HTML, the canvas is set to 600x600, so that's correct.

Also, in the animate function, the angle is now derived from t multiplied by 0.3, so that the rotation increases over time.

Testing this code:

Potential issues:

1. Performance: As mentioned, looping over 600 scanlines and per scanline 6 edges could be slow. Maybe 600 iterations are manageable, but in practice, it might be choppy.

To improve performance, perhaps we can reduce the number of scanlines? Or process in chunks? Alternatively, precompute edges' y ranges and determine their contribution.

But for the purpose of this question, perhaps this is acceptable.

Another optimization: precompute all edges and their parameters once per animation frame?

But each frame, the vertices change due to the rotation and the r function, so edges change every frame.

An alternative approach is to use the canvas's built-in fill, but the polygon might be self-intersecting, leading to incorrect fills. However, if the polygon is convex or non-complex, it would work, but the problem says "Create a rotating polygon...", so maybe it's a star or something which is self-intersecting, so the standard fill would not give the desired result (i.e., the interior as defined by the even-odd rule or non-zero winding), which may not be what the scanline algorithm is doing.

Therefore, the scanline approach is necessary for the desired effect.

Another possible issue: the edges are calculated with floating points, but the drawing is done with fillRect which is okay with floats.

Testing the code:

The code should draw a rotating polygon with vertices whose radius varies sinusoidally with time, leading to a pulsating effect, while rotating.

The stats will show the number of scanlines and spans, which should be 600 and varying based on the polygon's shape.

Potential error in code:

In the animate function, the angle variable is declared as 'let angle = t * 0.3;', but in the original code above, there was a separate 'angle' variable being incremented. Here, we have replaced that with deriving angle from t, which is better as it avoids needing another variable. So that should be okay.

Another potential error: in the initial code, the x and y coordinates were calculated with 300 as center (since the canvas is 600x600, half is 300). That is correct.

Another thing: in the line

const r = 150 + 50 * Math.sin(t + i / 3) + 30 * Math.sin(2 * t + i / 2);

The 'i/3' and 'i/2' terms cause each vertex to have a different phase, leading to a wavy effect.

This should create an interesting shape.

Conclusion:

The code should work. It might be a bit slow, but for the purposes of the problem, it should fulfill the requirements.
deepseek-r1:32bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html><html><head><title>Scanline Polygon Fill</title>
<style>
canvas{border:1px solid #333;background:#000}#stats{color:#fff;position:absolute;left:10px;top:10px;font-family:sans-serif}
</style></head><body>
<canvas id=c></canvas><div id=stats></div>
<script>
const c=document.getElementById('c'),ctx=c.getContext('2d');
let w=window.innerWidth,h=window.innerHeight,poly;
const N_POINTS=30; // Number of points in polygon
let scanlines=0,spans=0;

class Polygon{
constructor(){
this.points=[];
for(let i=0;i<N_POINTS;++i){
let ang=Math.random()*Math.PI*2;
let r=150+80*Math.random();
let x=r*Math.cos(ang);
let y=r*Math.sin(ang);
this.points.push({
x:x,
y:y,
vx:0.005 + 0.003*Math.random()
});
}
}

update(){
for(let p of this.points){
p.x += Math.cos(p.x/20+p.v*10)*0.5;
p.y += Math.sin(p.y/20+p.v*10)*0.5;
p.v=0.005 + 0.003*Math.random();
}
}

getEdges(){
let res=[];
for(let i=0;i<this.points.length;++i){
res.push({
x1:this.points[i].x,
y1:this.points[i].y,
x2:this.points[(i+1)%N_POINTS].x,
y2:this.points[(i+1)%N_POINTS].y
});
}
return res;
}
}

function init(){
poly=new Polygon();
c.width=w=window.innerWidth;
c.height=h=window.innerHeight;
requestAnimationFrame(animate);
}

function animate(t){
poly.update();
ctx.fillStyle='#000';
ctx.fillRect(0,0,w,h);

scanlines=0;spans=0;

// Scanline algorithm
for(let y=0;y<h;y++){
let intersections=[];
const edges=poly.getEdges();

// Find all intersection points on this scanline
edges.forEach(e=>{
let x1=e.x1,y1=e.y1,x2=e.x2,y2=e.y2;
if(y >= Math.min(y1,y2) && y <= Math.max(y1,y2)){
let dx=x2-x1,dy=y2-y1,den=dy;
let t=(y - y1)/den;
intersections.push(x1 + t*dx);
}
});

// Sort intersections
intersections.sort((a,b)=>a-b);

// Fill even-odd spans
let prev=undefined;
for(let i=0;i<intersections.length;i+=2){
if(prev === undefined && i>0) continue;
let a=i < intersections.length ? intersections[i] : w;
let b=(i+1)<intersections.length ? intersections[i+1] : w;
if(a >= b) continue;
spans++;
ctx.fillStyle='#3f5';
ctx.fillRect(a,y,b-a,1);
prev=a;
}

scanlines++;

// Draw outline
ctx.strokeStyle='#fff';
edges.forEach(e=>{
ctx.beginPath();
ctx.moveTo(e.x1,e.y1);
ctx.lineTo(e.x2,e.y2);
ctx.stroke();
});

const statsDiv=document.getElementById('stats');
statsDiv.innerHTML=`
Scanlines: ${scanlines}<br>
Spans filled: ${spans}
`;

requestAnimationFrame(animate);
}

init();

window.addEventListener('resize',init);

</script></body></html>
```
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
GPT-5.5 (Codex)OKMINIFIED
Show code
<!doctype html><html><head><meta charset=utf-8><title>scanline</title><style>html,body{margin:0;height:100%;overflow:hidden;background:#111;color:#eee;font:14px system-ui}canvas{display:block;width:100vw;height:100vh}#i{position:fixed;top:12px;left:12px;background:#0008;padding:8px 10px;border:1px solid #fff3}</style></head><body><canvas id=c></canvas><div id=i></div><script>
const c=document.getElementById("c"),x=c.getContext("2d"),info=document.getElementById("i");let W,H,P,t0=0,dur=22000;
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 fresh(){let n=7+Math.random()*5|0,a=[];for(let k=0;k<n;k++){let ang=k*2*Math.PI/n,r=.22+Math.random()*.23;a.push({a:ang,r:r,ph:Math.random()*7,sp:.35+Math.random()*.8})}let step=2+Math.random()*3|0;if(step==1)step=2;let b=[];for(let i=0,j=0;i<n;i++,j=(j+step)%n)b.push(a[j]);P={p:b,rot:Math.random()*6.28,cx:.5,cy:.53,s:Math.min(W,H),h:170+Math.random()*120}}fresh();
function pts(T){let q=[],s=Math.min(W,H);for(const p of P.p){let rr=p.r*s*(1+.16*Math.sin(T*p.sp+p.ph)),a=p.a+P.rot+T*.22+.12*Math.sin(T*.37+p.ph);q.push([W*P.cx+Math.cos(a)*rr,H*P.cy+Math.sin(a)*rr])}return q}
function draw(ms){let T=ms/1000;if(!t0)t0=ms;if(ms-t0>dur){t0=ms;fresh()}x.clearRect(0,0,W,H);let p=pts(T),ys=p.map(v=>v[1]),min=Math.max(0,Math.floor(Math.min(...ys))),max=Math.min(H-1,Math.ceil(Math.max(...ys)));x.fillStyle=`hsl(${P.h},80%,58%)`;let scans=0,spans=0;x.globalAlpha=.82;for(let y=min;y<=max;y+=2){let xs=[];for(let i=0;i<p.length;i++){let a=p[i],b=p[(i+1)%p.length],ay=a[1],by=b[1];if((ay<=y&&by>y)||(by<=y&&ay>y))xs.push(a[0]+(y-ay)*(b[0]-a[0])/(by-ay))}xs.sort((a,b)=>a-b);for(let i=0;i+1<xs.length;i+=2){let l=Math.max(0,xs[i]),r=Math.min(W,xs[i+1]);if(r>l){x.fillRect(l,y,r-l,2);spans++}}if(xs.length)scans++}x.globalAlpha=1;x.lineWidth=3;x.strokeStyle="#fff";x.beginPath();p.forEach((v,i)=>i?x.lineTo(v[0],v[1]):x.moveTo(v[0],v[1]));x.closePath();x.stroke();x.lineWidth=1;x.strokeStyle="#111a";for(let y=min;y<=max;y+=10){x.beginPath();x.moveTo(0,y+.5);x.lineTo(W,y+.5);x.stroke()}info.textContent=`scanlines: ${scans}  spans: ${spans}`;requestAnimationFrame(draw)}requestAnimationFrame(draw);
</script></body></html>
Show code
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Scanline Fill</title>
<style>
html,body{margin:0;background:#0a0a14;overflow:hidden;height:100%}
canvas{display:block}
#hud{position:fixed;top:8px;left:10px;color:#8fd3ff;font:12px monospace;text-shadow:0 0 4px #000;
 background:rgba(0,0,0,.35);padding:4px 8px;border-radius:4px}
</style></head><body>
<canvas id="c"></canvas><div id="hud"></div>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d'),hud=document.getElementById('hud');
let W,H;
function rs(){W=cv.width=innerWidth;H=cv.height=innerHeight;}
addEventListener('resize',rs);rs();
function rnd(a,b){return a+Math.random()*(b-a);}
function gcd(a,b){return b?gcd(b,a%b):a;}
let poly,t0;
function newPoly(){
 const n=Math.floor(rnd(7,12));
 const cand=[];
 for(let s=2;s<=Math.floor(n/2);s++) if(gcd(s,n)===1) cand.push(s);
 const step=cand.length?cand[Math.floor(Math.random()*cand.length)]:2;
 const base=[];
 for(let i=0;i<n;i++) base.push({sp:rnd(0.25,1.1),ph:rnd(0,Math.PI*2),am:rnd(0.2,0.4),rb:rnd(0.5,0.7)});
 poly={n,step,base,rotSpeed:rnd(-0.35,0.35)};
 t0=performance.now();
}
newPoly();
function getPoints(t){
 const cx=W/2,cy=H/2,R=Math.min(W,H)*0.38;
 const {n,step,base}=poly;
 const rot=t*poly.rotSpeed;
 const order=[];
 for(let i=0;i<n;i++) order.push((i*step)%n);
 const pts=[];
 for(let k=0;k<n;k++){
  const idx=order[k];
  const b=base[idx];
  const rad=R*(b.rb+b.am*Math.sin(t*b.sp+b.ph));
  const ang=(idx/n)*Math.PI*2+rot;
  pts.push([cx+Math.cos(ang)*rad,cy+Math.sin(ang)*rad]);
 }
 return pts;
}
function frame(now){
 const t=(now-t0)/1000;
 if(t>20){newPoly();requestAnimationFrame(frame);return;}
 ctx.fillStyle='#0a0a14';
 ctx.fillRect(0,0,W,H);
 const pts=getPoints(t);
 const n=pts.length;
 const edges=[];
 for(let i=0;i<n;i++){
  const a=pts[i],b=pts[(i+1)%n];
  if(a[1]!==b[1]) edges.push([a[0],a[1],b[0],b[1]]);
 }
 let minY=H,maxY=0;
 for(const p of pts){ if(p[1]<minY)minY=p[1]; if(p[1]>maxY)maxY=p[1]; }
 minY=Math.max(0,Math.floor(minY));
 maxY=Math.min(H,Math.ceil(maxY));
 const step=2;
 let scanCount=0,spanCount=0;
 ctx.fillStyle='rgba(100,210,255,0.55)';
 for(let y=minY;y<maxY;y+=step){
  const ys=y+0.5;
  const xs=[];
  for(const e of edges){
   const x1=e[0],y1=e[1],x2=e[2],y2=e[3];
   if((ys>=y1&&ys<y2)||(ys>=y2&&ys<y1)){
    xs.push(x1+(ys-y1)*(x2-x1)/(y2-y1));
   }
  }
  xs.sort((a,b)=>a-b);
  scanCount++;
  for(let i=0;i+1<xs.length;i+=2){
   const x0=xs[i],x1=xs[i+1];
   if(x1>x0){
    ctx.fillRect(x0,y,x1-x0,step);
    spanCount++;
   }
  }
 }
 ctx.beginPath();
 ctx.moveTo(pts[0][0],pts[0][1]);
 for(let i=1;i<n;i++) ctx.lineTo(pts[i][0],pts[i][1]);
 ctx.closePath();
 ctx.strokeStyle='#eaf6ff';
 ctx.lineWidth=1.5;
 ctx.stroke();
 hud.textContent='scanlines: '+scanCount+'   spans filled: '+spanCount;
 requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</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 26.0 sOK
qwen3.5:9b 38.9 sOK
mistral-small:24b 82.9 sOK
gemma4:26b harness
llama3.1:8b 67.7 sOK
qwen3-coder:30b 24.8 sOK
deepseek-r1:14b 30.0 sOK
qwen3:8b harness
command-r:35b 94.7 sOK
qwq:32b 221.5 sOK
deepseek-r1:32b 65.2 sOK