OverviewCode › R109
Code · blind round

R109 · Moire from two rotating rasters

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 shows a moire effect on a <canvas>: draw two identical fine rasters of concentric rings, one on top of the other, and rotate and shift one of them slowly against the other so the interference bands sweep across the image. Keep the raster fine enough that the bands are clearly visible but the individual rings still resolve. Show the current angle and offset. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running (restart with a fresh random instance when finished). No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.

The answers

Local models (Brain cluster) — identical prompt, shuffled order (the sequence is not a ranking)
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
deepseek-r1:14bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><head>
    <title>moire</title>
    <style>
        body{margin:0;padding:0;display:flex;justify-content:center;align-items:center;height:100vh;background:black}
        canvas{border:1px solid white}
    </style>
</head>
<body>
    <canvas id=c></canvas>
    <script>
        const c=document.getElementById('c'),ctx=c.getContext('2d');
        let w=400,h=400,rings=30,speed=.05,displace=0,alpha=.7;
        
        c.width=w;c.height=h;
        
        function drawRing(r,g,b) {
            ctx.beginPath();
            for(let i=-90; i<270; i+=speed*.1*45) {
                const x=Math.cos(i*Math.PI/180)*r;
                const y=Math.sin(i*Math.PI/180)*r;
                ctx.moveTo(x,y);
                ctx.lineTo(x+speed*.5*(Math.random()-0.5),y+speed*.5*(Math.random()-0.5));
            }
            ctx.strokeStyle=`rgba(${r},${g},${b},${alpha})`;
            ctx.stroke();
        }

        function drawRaster() {
            const cr=ctx.createLinearGradient(0,0,w,h);
            for(let i=0;i<360;i+=2) {cr.addColorStop(i/360,`hsl(${i%360},50%,50%)`);}
            
            ctx.save();
            try {
                for(let r=10;r<w-10;r+=4) {
                    drawRing(r>>1,r>>1,r>>1);
                }
            } finally {ctx.restore();}
        }

        let angle=0,offset=0;
        
        function update() {
            const step=0.02;
            angle=(angle+step)%Math.PI*2;
            offset+=step*speed*5;
            
            ctx.fillStyle='black';ctx.fillRect(0,0,w,h);
            drawRaster();
            ctx.save();
            try {
                ctx.translate(w/2,h/2);
                ctx.rotate(angle);
                ctx.globalAlpha=alpha+Math.sin(offset)*.3;
                drawRing(offset%w*2,offset%w*2,offset%w*2);
            } finally {ctx.restore();}
            
            ctx.fillStyle='white';ctx.font='10px monospace';
            ctx.fillText(`angle: ${angle.toFixed(3)} offset: ${offset.toFixed(3)}`,5,12);
            requestAnimationFrame(update);
        }

        drawRaster();
        update();
    </script>
</body></html>
```
qwen3.5:9bERRBLANK
Show code
<!DOCTYPE html>
<html>
<head>
<title>Moiré Effect Canvas</title>
<style>
body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #111; }
canvas { border: 1px solid #333; }
#info { color: white; position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); font-family: monospace; font-size: 14px; }
</style>
</head>
<body>

<canvas id="moireCanvas" width="600" height="600"></canvas>
<div id="info">Angle: 0.00° | Offset: 0.00</div>

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

let startTime;
let animationFrameId;

function drawRaster(ctx, centerX, centerY, radius, frequencyScale, colorStart) {
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    
    for (let y = 0; y < canvas.height; y++) {
        for (let x = 0; x < canvas.width; x++) {
            const index = (y * canvas.width + x) * 4;
            
            // Distance from center
            const dx = x - centerX;
            const dy = y - centerY;
            const dist = Math.sqrt(dx * dx + dy * dy);

            // Calculate the ring check based on frequencyScale (controls spacing/resolution)
            // We use Math.floor to create discrete rings
            let normalizedDist = Math.floor(dist / frequencyScale); 

            if (Math.abs(normalizedDist - (canvas.width / frequencyScale)) < 0.5) {
                // Ring is present, set color (simulating 'on' state)
                data[index] = 255;     // R
                data[index + 1] = 255; // G
                data[index + 2] = 255; // B
            } else {
                // Gap, set background color (off state)
                data[index] = 0;
                data[index + 1] = 0;
                data[index + 2] = 0;
            }
        }
    }
    ctx.putImageData(imageData, 0, 0);
}

function drawMoiré(time) {
    const W = canvas.width;
    const H = canvas.height;
    const centerX = W / 2;
    const centerY = H / 2;
    const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY);

    // Time-dependent parameters for the moiré effect
    const angle = (time * 0.1) % (Math.PI * 2); // Slow rotation
    const offset = (Math.sin(time * 0.05)) * 0.1;  // Slow shift

    // Clear canvas before drawing
    ctx.clearRect(0, 0, W, H);
    
    // --- Raster 1: Stationary Base Grid ---
    // Frequency determines the spacing and apparent resolution of rings.
    const freqScale = 25; // Controls how many 'rings' fit across the diameter
    drawRaster(ctx, centerX, centerY, maxRadius, freqScale, [255, 255, 255]);

    // --- Raster 2: Rotated and Shifted Grid (Moiré Layer) ---
    
    // To simulate rotation and shift using the distance-based drawing function, 
    // we must transform the coordinates conceptually before checking the 'ring' condition.
    // We will draw a second raster but introduce angular dependencies to achieve the moiré look.

    ctx.save();
    // Translate to center for transformation
    ctx.translate(centerX, centerY);
    // Apply rotation and shift (this helps simulate the relative movement)
    ctx.rotate(angle + Math.PI / 2); // Rotate by angle plus an offset adjustment
    ctx.scale(1 + offset * 0.5, 1); // Scale slightly to introduce stretch/compression effect

    // Re-draw the raster logic using transformed coordinates (x', y') relative to the center (0, 0)
    const imageData = ctx.getImageData(0, 0, W, H);
    const data = imageData.data;

    for (let y = 0; y < canvas.height; y++) {
        for (let x = 0; x < canvas.width; x++) {
            const index = (y * canvas.width + x) * 4;
            
            // Calculate coordinates relative to the center, then apply rotation/shift effects conceptually
            // Since ctx.save()/translate()/rotate() applies transformations to subsequent drawing calls,
            // we must calculate the ring condition based on the transformed space (x', y').
            let x_prime = x - centerX; 
            let y_prime = y - centerY;

            // Apply rotation and shift transformation manually for pixel calculation
            const cosA = Math.cos(angle + Math.PI / 2);
            const sinA = Math.sin(angle + Math.PI / 2);
            
            // Rotated coordinates (x_rot, y_rot)
            let x_rot = x_prime * cosA - y_prime * sinA;
            let y_rot = x_prime * sinA + y_prime * cosA;

            // Apply offset shift to the rotated coordinates
            x_rot += Math.sin(time * 0.1) * 2; // Horizontal modulation
            y_rot -= Math.cos(time * 0.1) * 2; // Vertical modulation

            const dist = Math.sqrt(x_rot * x_rot + y_rot * y_rot);

            // Ring check using the transformed distance (dist) and frequency scale
            let normalizedDist = Math.floor(dist / freqScale);
            
            if (Math.abs(normalizedDist - (canvas.width / freqScale)) < 0.5) {
                data[index] = 255;     // R
                data[index + 1] = 255; // G
                data[index + 2] = 255; // B
            } else {
                // Keep background black (0, 0, 0) so it doesn't overwrite the base layer entirely
                data[index] = 0; data[index+1] = 0; data[index+2] = 0;
            }
        }
    }
    ctx.putImageData(imageData, 0, 0);

    ctx.restore(); // Restore original context state

    // Update info display
    const angleDeg = (angle * 180 / Math.PI).toFixed(2);
    infoDisplay.innerHTML = `Angle: ${angleDeg}° | Offset: ${offset.toFixed(2)}`;

    animationFrameId = requestAnimationFrame(drawMoiré);
}

function animate() {
    if (!startTime) {
        startTime = performance.now();
        drawMoiré(startTime);
    } else {
        const currentTime = performance.now();
        drawMoiré(currentTime - startTime); // Pass elapsed time for consistent animation feel
    }
}

// Start the animation immediately
animate();
</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.
qwen3:14bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Moiré Effect</title>
    <style>
        body { background: #111; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
        canvas { border: 1px solid #333; background: #000; }
        #info { color: #aaa; font-family: monospace; position: absolute; bottom: 10px; left: 10px; }
    </style>
</head>
<body>
    <canvas id="moireCanvas"></canvas>
    <div id="info">Angle: 0.00 | Offset: 0.00</div>

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

        let width, height;
        let angle = Math.random() * 2 * Math.PI;
        let offset = Math.random() * 100;
        let lastTime = 0;

        function resizeCanvas() {
            width = window.innerWidth * 0.8;
            height = width * (Math.min(window.innerHeight, 600) / width); // Keep aspect ratio reasonable
            canvas.width = Math.floor(width);
            canvas.height = Math.floor(height);
        }

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

        function drawMoiré(time) {
            const t = time / 1000;
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = Math.min(centerX, centerY) * 0.95;

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            
            // Drawing logic: Calculate color based on combined interference pattern
            for (let x = 0; x < width; x++) {
                for (let y = 0; y < height; y++) {
                    const dx = x - centerX;
                    const dy = y - centerY;
                    const distSq = dx * dx + dy * dy;

                    // Source A: Standard concentric rings based on radial distance
                    // Frequency control: Controls how tightly packed the initial rings are.
                    let signalA = Math.sin(Math.sqrt(distSq) / 30); 

                    // Source B (shifted and rotated): Incorporates angle and offset for moiré effect
                    // The phase shift due to rotation/offset is key here.
                    let signalB_radial = Math.sin((Math.sqrt(distSq) + Math.cos(dy / 10) * Math.sin(dx / 10)) / 25);
                    let signalB_phase = Math.sin(dx / (30 + offset/10));

                    // Combine signals to create the moiré interference pattern
                    // The combination of sin(r) and a phase-shifted function creates the beating effect.
                    let combinedSignal = (signalA * 0.7 + signalB_radial * 0.8);
                    combinedSignal += Math.cos((dx / 15 + t * 0.2) - angle/10);

                    // Color mapping: Map the combined signal to a color value (0-1 range)
                    let intensity = Math.floor(Math.abs(combinedSignal) * 80 + 128); // Range 48 to 255
                    let r = intensity;
                    let g = Math.floor(intensity * 0.9);
                    let b = Math.floor(intensity * 0.7);

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

            // Update state variables for next frame
            angle += 0.005;
            offset += 0.2;
            if (Math.abs(offset) > 100 || angle > 2 * Math.PI + 1) {
                angle = Math.random() * 2 * Math.PI;
                offset = Math.random() * 150 - 75;
            }

            // Update display info
            infoDiv.innerHTML = `Angle: ${angle.toFixed(3)} | Offset: ${offset.toFixed(2)}`;

            requestAnimationFrame((time) => drawMoiré(time));
        }

        // Start animation loop
        requestAnimationFrame(drawMoiré);
    </script>
</body>
</html>
```
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Moire Effect</title>
<style>body{margin:0;padding:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#000;color:#fff;font-family:Arial,Helvetica,sans-serif}canvas{border:1px solid#fff}</style>
</head>
<body><canvas id="moireCanvas" width="800" height="800"></canvas>

<script>const canvas=document.getElementById('moireCanvas');
const ctx=canvas.getContext('2d');
let angle=0;let offset=0;
function drawRings(ctx,angle,offset){
ctx.clearRect(0,0,canvas.width,canvas.height);
for(let i=-5;i<=5;i++){
let radius=i*100+400;
ctx.beginPath();
ctx.arc(canvas.width/2+Math.cos(angle)*offset,canvas.height/2+Math.sin(angle)*offset,radius,0,2*Math.PI);
ctx.strokeStyle='rgba(255,255,255,0.5)';
ctx.stroke();
}
for(let i=-5;i<=5;i++){
let radius=i*100+400;
ctx.beginPath();
ctx.arc(canvas.width/2-Math.cos(angle)*offset,canvas.height/2-Math.sin(angle)*offset,radius,0,2*Math.PI);
ctx.strokeStyle='rgba(255,255,255,0.5)';
ctx.stroke();
}
}
function animate(){
angle+=0.01; offset+=0.5;
drawRings(ctx,angle,offset);
requestAnimationFrame(animate);
}function randomize(){
angle=Math.random()*Math.PI*2;offset=Math.random()*100-50;
}
randomize();animate();
</script>
</body>
</html>
```
llama3.1:8bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<style>
body {
  margin: 0;
  overflow: hidden;
}
</style>
</head>
<body>
<canvas id="c" width="400" height="400"></canvas>
<script>
let c = document.getElementById("c");
let ctx = c.getContext("2d");
c.width = 400;
c.height = 400;

function draw(r, x, y) {
  for (let i = -r; i <= r; ++i)
    for (let j = -r; j <= r; ++j)
      let d = Math.sqrt(i * i + j * j);
      ctx.fillStyle = d < r ? "black" : "white";
      ctx.fillRect(x + i, y + j, 1, 1);
}

function drawMoire() {
  c.width = c.width;
  ctx.clearRect(0, 0, c.width, c.height);
  let r = 100;
  for (let i = -r; i <= r; ++i)
    for (let j = -r; j <= r; ++j) {
      let d = Math.sqrt(i * i + j * j);
      ctx.fillStyle = d < r ? "black" : "white";
      if (d > 0 && d < r && Math.random() > 0.5)
        ctx.fillRect(200 + i, 200 + j, 1, 1);
    }
}

function drawMoireAnimated(timestamp) {
  let a = timestamp / 1000;
  c.width = c.width;
  ctx.clearRect(0, 0, c.width, c.height);
  let r = 100;
  for (let i = -r; i <= r; ++i)
    for (let j = -r; j <= r; ++j) {
      let d = Math.sqrt(i * i + j * j);
      ctx.fillStyle = d < r ? "black" : "white";
      if (d > 0 && d < r && Math.sin(a + i / 10.0 + j / 20.0) > 0)
        ctx.fillRect(200 + i, 200 + j, 1, 1);
    }
  drawMoire();
  let x = 100 * Math.cos(a);
  let y = 100 * Math.sin(a);
  document.getElementById("status").textContent = "Angle: " + (a * 360 / Math.PI).toFixed(2) + ", Offset: (" + x.toFixed(1) + "," + y.toFixed(1) + ")";
}

let last = performance.now();
function animate() {
  let now = performance.now();
  drawMoireAnimated(now);
  requestAnimationFrame(animate);
}
animate();

drawMoire();
document.getElementById("status").textContent = "Angle: 0.00, Offset: (0.0, 0.0)";
</script>
<div id="status" style="position:absolute;top:10px;left:10px;"></div>
</body>
</html>
```
qwen3-coder:30bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Moiré Effect Canvas</title>
    <style>
        body { margin: 0; background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; }
        canvas { border: 1px solid #333; background: black; }
        #info { color: #999; font-family: monospace; margin-top: 20px; position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); }
    </style>
</head>
<body>
    <canvas id="moireCanvas" width="600" height="600"></canvas>
    <div id="info">Angle (θ): 0.00 rad | Offset (φ): 0.00 px</div>

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

        let angle = 0; // Rotation angle for the second pattern (radians)
        let offset = 0; // Translational offset for the second pattern (pixels)

        // Parameters governing the rings
        const frequency = 3.5;  // How many rings per revolution
        const amplitude = 0.1;   // Depth of the radial variation
        const minRadius = 50;    // Inner radius
        const maxRadius = 290;   // Outer radius

        function drawRings(ctx, centerX, centerY, rotationAngle, translationX) {
            const size = Math.min(centerX, canvas.height - centerY);
            const numCircles = 60; // Number of rings to draw
            const stepSize = (maxRadius - minRadius) / numCircles;

            ctx.strokeStyle = 'rgba(150, 200, 255, 0.4)'; // Blueish color
            ctx.lineWidth = 1;

            for (let i = 0; i < numCircles; i++) {
                const r_base = minRadius + i * stepSize;
                
                // Calculate the modulated radius based on angle and position
                // The radial modulation creates the apparent "ripple" or ring structure.
                let r = r_base - amplitude * Math.sin(i * frequency);

                // Apply transformation effects (Rotation and Shift)
                // We rotate first, then translate/shift the perceived center point for moiré effect.
                const transformedR = r; 

                ctx.beginPath();
                ctx.arc(centerX + translationX, centerY, transformedR, 0, Math.PI * 2);
                ctx.stroke();
            }
        }

        function animate() {
            // Clear canvas for the new frame
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;

            // --- Pattern 1: Static (Reference) ---
            ctx.save();
            ctx.translate(centerX - 5, centerY); // Slight vertical shift for visual separation
            drawRings(ctx, centerX, centerY, 0, 0);
            ctx.restore();


            // --- Pattern 2: Moving/Transformed ---
            ctx.save();
            // Apply rotation and translation to the context before drawing
            ctx.translate(centerX + 5, centerY); // Slight vertical shift for visual separation
            ctx.rotate(angle);
            ctx.translate(-Math.sin(angle) * offset / 3, -Math.cos(angle) * offset / 3);

            // Draw the rings with the applied transformations
            drawRings(ctx, centerX, centerY, angle, offset);
            ctx.restore();


            // --- Update State and Display ---
            
            // Slow rotation (Angle change)
            angle += 0.001; 
            if (angle > Math.PI * 2) {
                angle -= Math.PI * 2;
            }

            // Slow offset (Shift change)
            offset = Math.sin(Date.now() / 800) * 50 + 50; // Oscillates between 0 and 100 pixels

            infoDisplay.innerHTML = `Angle (θ): ${angle.toFixed(2)} rad | Offset (φ): ${offset.toFixed(2)} px`;

            requestAnimationFrame(animate);
        }

        // Start the animation loop
        animate();
    </script>
</body>
</html>
```
command-r:35bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Moire Effect Animation</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: black;
        }
        canvas {
            border: 2px solid #fff;
        }
    </style>
</head>
<body>
    <canvas id="moireCanvas"></canvas>

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

        let angle = 0;
        let offsetX = 0;
        let finished = false;
        const ringCount = Math.floor(Math.random() * 15) + 30; // Random number of rings, min: 30, max: 45
        const radiusStep = canvas.width / (ringCount * 2);

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

            for (let i = 1; i <= ringCount; i++) {
                const radius = i * radiusStep;

                // Draw the first set of rings
                ctx.beginPath();
                ctx.strokeStyle = `hsl(${i * 360 / ringCount}, 100%, 50%)`;
                ctx.lineWidth = 2;
                ctx.arc(offsetX, canvas.height / 2, radius, 0, 2 * Math.PI);
                ctx.stroke();

                // Draw the second set of rings with rotation and offset
                ctx.save();
                ctx.translate(canvas.width / 2 + offsetX, canvas.height / 2);
                ctx.rotate(angle);
                ctx.beginPath();
                ctx.arc(0, 0, radius, 0, 2 * Math.PI);
                ctx.stroke();
                ctx.restore();
            }

            angle += 0.01;
            offsetX += 0.5; // Adjust the speed of horizontal shift

            if (angle > 6.28) {
                finished = true;
                angle = 0;
                offsetX = 0;

                requestAnimationFrame(drawRings);
            } else {
                requestAnimationFrame(drawRings);
            }
        }

        window.addEventListener('load', function () {
            canvas.width = window.innerWidth / 2; // Set canvas width to half of the viewport width
            canvas.height = window.innerHeight - 40; // Adjust height based on viewport

            if (!finished) {
                drawRings();
            }
        });
    </script>
</body>
</html>
```
deepseek-r1:32bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="margin:0;display:flex;justify-content:center;background:#000;height:100vh">
<canvas id=c></canvas>
<script>
var c=document.getElementById('c'),ctx=c.getContext('2d');
c.width=500;c.height=500;
let angle=0,shiftX=0,shiftY=0,time=0,lastFrame=0,rings=[],center={x:250,y:250},numRings=30,ringSpacing=1.5,rotSpeed=0.006,shiftRadius=40;
function drawRings(ctx){
ctx.beginPath();
for(let i=0;i<numRings;i++){
let r=i*ringSpacing;
ctx.arc(center.x,center.y,r,0,2*Math.PI);
}
ctx.strokeStyle='hsla(180,50%,70%,0.5)';
ctx.lineWidth=0.3;ctx.stroke();
}
function drawRotatedRings(ctx){
ctx.save();ctx.translate(shiftX,shiftY);ctx.rotate(angle);
drawRings(ctx);
ctx.restore();
}
function animate(timestamp){
if(!lastFrame)lastFrame=timestamp;
let delta=(timestamp-lastFrame)/1e3,timeFactor=delta/0.5;
angle+=rotSpeed*delta;
shiftX=Math.sin(angle)*shiftRadius;
shiftY=Math.cos(angle)*shiftRadius;
ctx.clearRect(0,0,c.width,c.height);
drawRings(ctx);drawRotatedRings(ctx);
ctx.fillStyle='hsl(180,50%,90%)';
ctx.font='24px sans-serif';
let info=`Angle: ${(angle*180/Math.PI).toFixed(2)}° (${angle.toFixed(3)})\nOffset: ${Math.hypot(shiftX,shiftY).toFixed(1)}`;
ctx.fillText(info,10,c.height-10);
lastFrame=timestamp;
if(time++>1e4/rotSpeed*10)reset();
requestAnimationFrame(animate);
}
function reset(){
center={x:c.width/2,y:c.height/2};
numRings=Math.floor(Math.random()*10+30);
ringSpacing=(Math.random()+0.5)*1.5;
rotSpeed=(Math.random()*0.004)+0.002;
shiftRadius=(Math.random()*20)+30;
let h=360*Math.random();
ctx.strokeStyle='hsla('+h+',50%,70%,0.5)';
angle=Math.random()*Math.PI*2;
lastFrame=0;time=0;
}
reset();animate(0);
</script></body>
</html>
```
qwq:32bBLANKLIM
Show code
<think>

Okay, the user wants me to create an HTML file that displays a moiré effect using a canvas. The main goal is to have two concentric rings that are identical, one on top of the other. The challenge is to rotate and shift one of them so that the interference bands move across the image. Also, the rings should be fine enough so that the interference is visible but the individual rings don't blur into each other.

First, I need to set up the canvas element in the HTML. Since it's self-contained, I have to include all the JavaScript inside the script tags. The moiré effect is created by the interference between two patterns. In this case, concentric circles. So, the approach will be to draw two sets of rings, one rotated and shifted relative to the other.

I'll start by initializing the canvas and getting the 2D context. The canvas size should be reasonable, maybe 400x400 pixels. Then, I need to set up an animation loop using requestAnimationFrame to smoothly animate the rotation and shifting.

The rings need to be fine, so the number of rings should be high. Let's say 100 rings with a small radius increment. The radius can start from a small value, maybe 2, so that they don't overlap too much initially. The angle and offset should be variables that change over time. The angle will rotate one set of rings, and the offset can shift them horizontally and vertically to add another dimension to the interference.

Wait, the user mentioned shifting one of them slowly against the other. Maybe the offset is both rotation and translation. Hmm, rotation is straightforward, but adding a shift (like a small x and y offset) might also help in creating the interference. However, the problem states to rotate and shift one of them, so perhaps shifting is a translation in addition to rotation. But maybe the main effect comes from rotation. Let me think.

Moire patterns between two rotating circles can create moving bands. Let's focus on rotation first. The two sets of rings are drawn: one as the base, and the other rotated by an angle. The angle increases over time, causing the interference to move.

To get the interference, the rings must have similar spacing. If the rings are too close, the moire effect might be too strong and the individual rings won't be resolved. So, spacing is key. Let me calculate the radius increment. Suppose the maximum radius is half the canvas size (200px). If there are 100 rings, each ring's radius increments by 2px (200/100). That gives 100 rings from radius 2 to 202. Wait, maybe start from a smaller radius. Let's see.

Alternatively, maybe 100 rings with a step of 4. That would give 400px max radius, but the canvas is 400px, so the outer rings might be cut off. Maybe 390 max radius? Hmm, perhaps start at 2px and increment by 4px up to 200 rings. Wait, perhaps the radius step should be such that the spacing between rings is small but visible. Maybe radius increases by 1 each step, but that might be too many. Let me think.

Alternatively, using 50 rings with a radius step of 4. So 50*4=200. That might work. Let me try 50 rings starting from 2, increasing by 4 each time. So radius goes from 2 to 200 (since 50*4 = 200, starting at 2 would be 2, 6, 10,... up to 2 + 4*49=200). That way, there are 50 rings, each spaced 4px apart. That should be fine enough to see the interference but still resolve individual rings.

So, in the code, I'll loop from 0 to 49 (for 50 rings) and draw circles with radius starting at 2, stepping by 4 each time. The angle for rotation will be a variable that's updated each frame. The rotation and offset can be applied to the second set of rings.

Wait, to draw the second layer, perhaps we can save the context, rotate it, translate it, draw the rings, then restore. But since we need two layers, one rotated and shifted, maybe the first layer is drawn normally, then the second layer is drawn with a rotation and translation.

Alternatively, perhaps it's better to draw both layers in the same context but with transformations. The animation loop will redraw both layers each frame with updated transformations.

The steps:

1. Setup canvas and context.
2. Initialize variables for angle and offset. The angle will increase over time, perhaps starting from a random number. The offset could be a small shift, but maybe not necessary. The problem says "shift one of them slowly against the other" so maybe a small x and y shift that also changes over time? Or just a rotation angle?

Wait, the problem says "rotate and shift one of them slowly against the other". So both rotation and translation. So perhaps I need to apply both a rotation and a translation (shift) to one of the layers.

The shifting could be a small amount added over time, but looping. Like, the shift could be a sine wave, so it oscillates back and forth. Alternatively, a linear shift with modulo to loop. But perhaps starting with rotation and a small fixed shift each frame.

Alternatively, the shift could be a constant offset added each frame, making it move continuously. But to prevent it from going off-screen, maybe modulo the canvas size.

Hmm, perhaps the shift is a value that increments over time, say, 0.1 pixels per frame, so over time it moves across the screen. The rotation angle would be incrementing as well.

The user also wants to show the current angle and offset, so those values should be displayed in the page, maybe in a div. The angle is in radians, perhaps converted to degrees for the display.

Now, the code structure:

- The canvas element with id 'c'.
- A script that on load starts the animation.
- The animation loop uses requestAnimationFrame.
- In each frame, clear the canvas.
- Draw the first set of rings (unchanged or base layer).
- Save the context, apply transformations (rotation and shift) to the second layer, draw the same rings, then restore.
- Update the angle and shift values incrementally.
- Display the current angle and shift in a div.

Wait, how to apply both rotation and shift. Let me think:

For the second layer, we can first translate the context to a point, then rotate, then translate back? Or maybe rotate around the center?

The center of the canvas is 200,200 (assuming 400x400). To rotate around the center, we need to translate the context to the center, rotate, then translate back. But then also apply the shift.

Alternatively, the steps for the second layer:

context.save();
// translate to center, then shift by (dx, dy), then rotate, then translate back to draw?
Hmm, perhaps it's better to do:

context.translate(200 + shiftX, 200 + shiftY);
context.rotate(angle);
context.translate(-200, -200); // back to original center?

Wait, maybe:

The steps should be:

1. Translate to center (200,200)
2. Apply rotation
3. Apply the shift (maybe shiftX and shiftY from center)
Wait, perhaps:

Actually, the shift can be applied by translating the context before rotation. Hmm, this might get confusing.

Alternatively, the rotation and shift can be applied in a way that the second layer is both rotated and shifted relative to the first.

Let me think in steps:

Suppose the first layer is drawn normally, with rings centered at (200,200).

The second layer needs to be rotated by angle and shifted by (dx, dy). To achieve this:

- Save the context.
- Translate to (shiftX, shiftY) relative to the original center? Or perhaps first translate the context to the center, apply rotation, then translate further for the shift?

Alternatively, the transformations are applied in this order:

- Translate to center (200, 200).
- Rotate by angle.
- Translate by (shiftX, shiftY).
- Then, draw the rings.

But then the entire second layer is rotated around the center, and then shifted by (shiftX, shiftY).

Wait, but shifting after rotation would move the center point. Hmm, perhaps it's better to do:

Save the context.

Translate to the center (200,200).

Apply rotation by angle.

Then translate further by (shiftX, shiftY).

So the center becomes (shiftX, shiftY) relative to the rotated coordinate system?

This might complicate the position.

Alternatively, the shift is applied before rotation. Let me think again.

Alternatively, the shift is a separate translation. For instance, first translate the context by (shiftX, shiftY), then rotate, then draw as usual. But rotation would then be around the shifted center. That might not be desired.

Hmm, perhaps the easiest is to rotate around the center and then apply a small shift. The rotation is around the center, and then a slight shift (like 1 pixel) added each frame to make the pattern move.

Alternatively, the shift can be a random value that slowly drifts. But the problem states to shift slowly against the other, so perhaps the shift is a continuous movement, like a linear function over time.

Alternatively, both rotation and shift can be sinusoidal, but that might complicate.

Alternatively, the shift is a fixed value that increases by a small delta each frame, but wraps around when it exceeds the canvas size.

Alternatively, perhaps the shift is a small constant, like 0.1 per frame, so over time it moves across the canvas.

Wait, perhaps the problem requires to rotate one layer while shifting it. So the rotation angle increases over time, and the shift also increases over time, causing the interference bands to move.

The key is that both transformations (rotation and shift) cause the two layers to interfere, and the interference pattern moves as they change.

Now, implementing this:

The code will have variables for angle and shift. Let's say shift is an object with x and y components. Each frame, angle += 0.01 radians, and shift.x += 0.1, shift.y += 0.05, so they slowly move.

But to prevent shifting off the canvas, perhaps modulo 400 or something. Alternatively, let it drift and eventually reset when it goes beyond, but the user wants to restart with a fresh instance when finished. Wait, the problem says "restart with a fresh random instance when finished". Hmm, but when does it finish? Maybe after a certain period, but I'm not sure. Perhaps the animation is indefinite, but the user wants to reset it when it's done. Maybe the animation runs for a certain duration (like 10 seconds) then restarts.

Alternatively, since there's no user input and it starts immediately, perhaps the animation should loop continuously. But the problem says "restart with a fresh random instance when finished". So maybe after some time, it restarts. How to implement that?

Perhaps, after a certain number of frames (e.g., 1000 frames), generate new parameters (random angle offset, shifts, etc.) and restart.

Alternatively, perhaps the moiré effect can be self-restarting by the angle and shift going through a random cycle. Hmm, maybe not. Let me think of the requirements again.

The user said: "restart with a fresh random instance when finished". So maybe the animation has a certain duration (e.g., rotating 360 degrees), then stops and starts over with new parameters. To do that, track the time or the angle, and when it reaches a certain point, reset.

Alternatively, the animation could run indefinitely, but the user wants it to restart when it "finishes", but maybe the effect is continuous and there's no end, so perhaps the restart is triggered after a fixed time. Hmm, perhaps the problem expects that the animation restarts after a rotation of 2π radians? Not sure, but to fulfill the requirement, perhaps the animation has a duration (like 10 seconds) and then restarts.

But to keep it simple, maybe use a modulo on the angle so it loops around, but also have a counter that after, say, 1000 frames, resets everything. Or, the user might just want that when the animation stops (but it's supposed to keep running), so perhaps the restart is when the angle completes a full rotation. Let me think again.

The problem says "starts immediately and keeps running (restart with a fresh random instance when finished)". Maybe the animation has a duration until the angle reaches a full rotation (2π), then restarts. So after rotating 360 degrees, it restarts.

Alternatively, perhaps the shifting is also a cycle that needs to complete, but maybe it's simpler to have the restart occur after a fixed time, like every 5 seconds.

Alternatively, to make it restart after a certain condition, like when the angle exceeds 2π, then reset angle to 0 and change some parameters. But maybe the user just wants that after the animation has run for a while, it restarts. Since the user says "keeps running", maybe the restart is optional? The problem says "restart with a fresh random instance when finished". Hmm, perhaps the animation should have a random starting angle and shift each time it restarts. So, the initial setup (before the first frame) sets some random parameters, then after some time, reset to new random parameters.

This complicates things, but perhaps the simplest way is to have the animation run indefinitely but when the angle reaches a certain point, it resets. For example, when angle exceeds 4π (two full rotations), then reset angle to 0 and shift to random.

Alternatively, since it's a bit tricky, maybe the user just wants that the animation restarts when the page is reloaded, but that's not under control. Hmm, perhaps the requirement is that when the animation 'finishes' its current run, it restarts. Since there's no natural end, perhaps after a certain time (like 10 seconds), the parameters are reset. So, in code, track the time started, and if current time - start time exceeds duration, reset.

But this adds code for timing. Alternatively, since the problem says "starts immediately and keeps running", perhaps the restart is when the animation has completed a full cycle of rotation. Let me think of the code steps:

Initialize variables: angle = 0, shiftX = 0, shiftY =0 (or some small start). Each frame, add delta to angle and shifts.

Wait, but how to restart? Let me see. Maybe after angle reaches 2π, then reset angle to 0 and shifts to random values. But the shifts might need to be reset too.

Alternatively, perhaps the restart is triggered when the angle completes a full rotation (360 degrees). So when angle >= 2π, then set angle to 0 and randomize shift parameters.

So in code:

if (angle >= 2 * Math.PI) {
  angle = 0;
  shiftX = Math.random() * 20 - 10; // random shift between -10 and +10?
  shiftY = Math.random() * 20 - 10;
}

But this might cause abrupt jumps, but maybe that's acceptable.

Alternatively, the shifts are also randomized each restart. But the problem states "fresh random instance", so the whole setup should be randomized each time.

Hmm, okay, let's proceed.

First, in the setup, when initializing, we can set initial random parameters. But for the restart, we need to reset those.

Alternatively, the animation loop can have a variable indicating whether it's running, and when it's time to restart, it resets all variables.

Alternatively, perhaps it's easier to use a flag and when to reset, just re-initialize the angle and shifts. Let's see.

First, the code structure:

function animate() {
    ctx.clearRect(0,0, w, h);
    // draw first layer
    // save context
    ctx.save();
    // apply transformations for the second layer (rotation and shift)
    ctx.translate(200 + shiftX, 200 + shiftY); // center plus shift
    ctx.rotate(angle);
    ctx.translate(-200, -200); // back to original center?
    // draw the rings again
    // restore
    ctx.restore();
    // update angle and shift
    angle += deltaAngle;
    shiftX += deltaShiftX;
    shiftY += deltaShiftY;
    // check if it's time to restart
    if (angle > Math.PI * 2) {
        angle = 0;
        shiftX = 0;
        shiftY = 0;
        // or randomize?
    }
    // update display of angle and shifts
    document.getElementById('angle').textContent = 'Angle: ' + (angle * 180/Math.PI).toFixed(1) + '°';
    // similarly for shiftX and shiftY
    requestAnimationFrame(animate);
}

Wait, but the user wanted the shift to be part of the movement. Maybe the shifts should be a sine wave, so they oscillate. Alternatively, a linear increase, but with modulo.

Alternatively, the shift could be a fixed small amount added each frame, so over time it moves across the canvas, and when it reaches the edge, it restarts.

Hmm, perhaps to keep it simple, the shift can be a linear function that increases by 0.1 each frame, and when it passes the canvas width, resets to zero. But to create a continuous movement without abrupt jumps, perhaps use modulo.

Alternatively, the shift can be a value between 0 and canvas width, incremented by a small delta each frame, and wrapped using modulo.

Let me think of the variables:

deltaAngle = 0.01; // radians per frame

deltaShiftX = 0.1;
deltaShiftY = 0.1;

But then after some frames, shiftX would be large, causing the pattern to shift, but how to prevent it from going beyond?

Alternatively, use modulo 400 for shiftX and shiftY so they loop back.

shiftX = (shiftX + deltaShiftX) % 400;

Same for shiftY. But that would make them oscillate between 0-400, causing a periodic shift.

Alternatively, the shift could be a random value each frame. Hmm, but the problem requires a smooth shift.

Alternatively, the shifts can be sinusoidal:

shiftX = 50 * Math.sin(angle); // varying with rotation

Similarly for Y.

But this ties the shift to the rotation angle, which might be what the problem wants.

Alternatively, the shift is a function of time, but the problem says to restart when finished.

Hmm, given the time constraints, perhaps proceed with the simplest approach where the animation runs indefinitely, and the restart is when the angle completes a full rotation (2pi), then reset angle and shifts to zero or random.

Alternatively, perhaps the problem doesn't require that much and just wants the animation to run, and the restart is not critical, but the user's requirement is to mention it.

Moving forward, let's focus on drawing the two layers with rotation and shift.

Now, to draw the rings:

The first layer is drawn normally, the second layer is drawn with transformations (rotation and shift).

The rings could be concentric circles, for example.

Assuming the rings are circles, so in code:

function drawRings() {
    for (let i = 0; i < 10; i++) {
        ctx.beginPath();
        ctx.arc(200, 200, 20 + i * 20, 0, Math.PI*2);
        ctx.stroke();
    }
}

This draws concentric circles with radius starting at 20, increasing by 20 each time, up to 10 circles. The center is at (200,200).

Now, in the animation function, first draw the first layer normally, then the second layer with transformations.

Wait, actually, the second layer is drawn after applying the transformations:

In the animate function:

ctx.clearRect(0,0,w,h);

// draw first layer
drawRings();

// save context before applying transformations
ctx.save();

// apply shift and rotation
ctx.translate(shiftX, shiftY); // shift
ctx.rotate(angle);

// draw the second layer
drawRings();

// restore
ctx.restore();

Wait, perhaps the rotation should be around the center. So first translate to center, rotate, then translate back? Or first shift, then rotate?

Hmm, the order is important.

To rotate around the center (200,200), the correct order is:

ctx.translate(200,200);
ctx.rotate(angle);
ctx.translate(-shiftX, -shiftY);
// then draw?

Wait, perhaps I need to think in steps:

Suppose we want to shift the center by (shiftX, shiftY), and then rotate around the original center.

Alternatively:

To achieve rotating around the center (200,200) and then shifting the entire layer by (shiftX, shiftY):

The steps would be:

1. Translate to the center (200,200).

2. Apply rotation.

3. Translate back by (-shiftX, -shiftY) to shift the entire figure.

Wait, perhaps better to think:

The transformations applied are:

- First, translate by (shiftX, shiftY), which moves the entire context.

- Then, rotate around the new origin?

Wait, the transformation order matters. The transformations are applied in reverse order.

For example, if we first rotate then translate, it's different from translate then rotate.

Suppose:

ctx.translate(shiftX, shiftY); // shifts the origin to (shiftX, shiftY)

ctx.rotate(angle); // rotates around the shifted origin?

Wait, no, rotate is around the current origin (which was shifted by translate).

Hence, rotating after shifting will rotate around the shifted origin.

To rotate around the original center (200,200), we need to first translate to the center, then rotate, then translate back, but also include the shift.

Alternatively, perhaps the rotation should be around the center, then the shift applied.

Alternatively, the desired effect is that the second layer is both rotated and shifted relative to the first.

But the exact effect is up to interpretation. For simplicity, maybe shift the center by (shiftX, shiftY), then rotate around the original center.

Wait, perhaps the code should be:

ctx.save();

// shift and rotate
ctx.translate(shiftX, shiftY); // shift the coordinate system
ctx.translate(200, 200); // move to center
ctx.rotate(angle); // rotate around center (200,200)
ctx.translate(-200, -200); // move back

// draw the rings
drawRings();

ctx.restore();

Wait, that might be the right approach:

The process:

1. Translate by (shiftX, shiftY) – this shifts the entire coordinate system.

2. Then, translate to the center (200,200) – now the origin is at (200-shiftX, 200 - shiftY)?

Hmm, maybe I'm overcomplicating.

Alternatively, to rotate around the center (200,200) and then shift the entire image by (shiftX, shiftY):

The correct sequence is:

ctx.translate(shiftX, shiftY); // shift the entire context

ctx.translate(200, 200); // move to the center of the shifted context

ctx.rotate(angle); // rotate around the new center (which is shifted)

ctx.translate(-200, -200); // move back to the original position

But this is getting too complex. Maybe a better way is:

The desired transformation is:

- Rotate the layer around the center (200, 200) by angle.

- Shift the layer by (shiftX, shiftY).

So the order of transformations should be:

Translate to center (200,200), rotate, then translate by (shiftX, shiftY), then translate back.

Wait:

ctx.save();

// Apply rotation around center
ctx.translate(200, 200);
ctx.rotate(angle);
ctx.translate(-200, -200);

// Apply shift
ctx.translate(shiftX, shiftY);

drawRings();

ctx.restore();

But this way:

First, the rotation is around the center (200,200), then the entire figure is shifted by (shiftX, shiftY).

Yes, this should work. Because:

1. Translate to center (200,200)

2. Rotate by angle around that point.

3. Translate back to original position (-200, -200)

4. Then translate by (shiftX, shiftY), which shifts the entire rotated figure.

Thus, the net effect is:

- The figure is first rotated around its center.

- Then shifted by (shiftX, shiftY).

This way, the rotation is around the center, and then the entire rotated figure is shifted.

This seems correct.

Thus, the code for the second layer's transformations is as above.

Now, the drawRings() function draws circles centered at (200,200), so when we rotate them around that center, they stay centered there.

Then shifting with (shiftX, shiftY) moves the entire rotated figure.

This creates the moiré effect between the two layers.

Now, for the parameters:

Let's pick deltaAngle = 0.01 radians per frame (~ 0.57 degrees per frame). At 60 fps, this is about 34 degrees per second, which is manageable.

ShiftX and Y can be incremented by 0.1 per frame, so over time they shift the pattern.

Now, for the restart: the problem says to restart with a fresh instance when finished. Assuming a full rotation (2π) is a 'cycle', so when the angle reaches 2π, reset.

Thus, in the animate function:

if (angle > Math.PI * 2) {
    angle = 0;
    shiftX = 0;
    shiftY = 0;
}

But to make it random each time, perhaps set shiftX and shiftY to random values:

angle = 0;
shiftX = Math.random() * 20 - 10; // between -10 and +10
shiftY = Math.random() * 20 - 10;

This way, each restart randomizes the shift.

Now, coding this:

Variables:

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

var angle = 0;
var shiftX = 0;
var shiftY = 0;

function animate() {
    ctx.clearRect(0,0,w,h);
    
    // Draw first layer (no transformations)
    drawRings();
    
    // Draw second layer with rotation and shift
    ctx.save();
    
    // Rotate around center
    ctx.translate(200, 200);
    ctx.rotate(angle);
    ctx.translate(-200, -200);
    
    // Apply shift
    ctx.translate(shiftX, shiftY);
    
    drawRings();
    
    ctx.restore();
    
    // Update parameters
    angle += 0.01;
    shiftX += 0.1;
    shiftY += 0.05;
    
    // Check for restart
    if (angle > 2 * Math.PI) {
        angle = 0;
        shiftX = Math.random() * 20 - 10;
        shiftY = Math.random() * 20 - 10;
    }
    
    // Update display of angle and shifts
    document.getElementById('angle').textContent = 'Angle: ' + (angle * (180/Math.PI)).toFixed(1) + '°';
    document.getElementById('shift').textContent = 'Shift: (' + shiftX.toFixed(1) + ', ' + shiftY.toFixed(1) + ')';
    
    requestAnimationFrame(animate);
}

function drawRings() {
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    for (let i = 0; i < 10; i++) {
        ctx.beginPath();
        ctx.arc(200, 200, 20 + i * 20, 0, Math.PI*2);
        ctx.stroke();
    }
}

// HTML elements to display info
const angleDisplay = document.getElementById('angle');
const shiftDisplay = document.getElementById('shift');

// Start animation
requestAnimationFrame(animate);

But the user might not want the info display, so maybe omit that part.

Also, in the HTML, a canvas element is required with id 'canvas', and maybe divs for the info.

Alternatively, the problem just wants the code to create the animation, so perhaps the display info is extra.

Also, the problem says "Create" the animation, so the code should be complete.

Putting it all together:

HTML:

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

JS:

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

let angle = 0;
let shiftX = 0;
let shiftY = 0;

function drawRings() {
    ctx.strokeStyle = 'rgba(0,0,0,0.5)';
    for (let i = 0; i < 10; i++) {
        ctx.beginPath();
        ctx.arc(200, 200, 20 + i * 20, 0, Math.PI*2);
        ctx.stroke();
    }
}

function animate() {
    ctx.clearRect(0, 0, 400, 400);
    
    // Draw first layer
    drawRings();
    
    // Draw second layer with transformations
    ctx.save();
    
    // Rotate around center
    ctx.translate(200, 200);
    ctx.rotate(angle);
    ctx.translate(-200, -200);
    
    // Apply shift
    ctx.translate(shiftX, shiftY);
    
    drawRings();
    
    ctx.restore();
    
    // Update parameters
    angle += 0.01;
    shiftX += 0.1;
    shiftY += 0.05;
    
    // Check for restart
    if (angle > 2 * Math.PI) {
        angle = 0;
        shiftX = Math.random() * 20 - 10;
        shiftY = Math.random() * 20 - 10;
    }
    
    requestAnimationFrame(animate);
}

// Start animation
animate(); // Using requestAnimationFrame is better, but using animate() as function.

Wait, better use requestAnimationFrame in a loop:

function animate() {
    // ... the above code
    requestAnimationFrame(animate);
}

animate();

This way it's a proper animation loop.

Testing this code should create two sets of concentric circles, one rotating and shifting relative to the other, creating a moiré pattern. When the angle completes a full rotation, it restarts with new shifts.

This should satisfy the problem's requirements.
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>Moire Rings</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}.hud{position:fixed;left:12px;top:10px;background:#0009;padding:8px 10px;border-radius:6px;line-height:1.4}</style></head><body><canvas id=c></canvas><div class=hud><div>angle: <span id=a></span></div><div>offset: <span id=o></span></div></div><script>
const c=document.getElementById("c"),x=c.getContext("2d"),A=document.getElementById("a"),O=document.getElementById("o"),p=document.createElement("canvas"),q=p.getContext("2d");let w,h,dpr,end,base,span,ph,dir;
function fit(){dpr=devicePixelRatio||1;w=c.width=innerWidth*dpr;h=c.height=innerHeight*dpr;c.style.width=innerWidth+"px";c.style.height=innerHeight+"px";let s=Math.ceil(Math.hypot(w,h)*1.15);p.width=p.height=s;q.setTransform(1,0,0,1,0,0);q.fillStyle="#fff";q.fillRect(0,0,s,s);q.globalCompositeOperation="destination-out";q.lineWidth=Math.max(1,dpr*.8);q.strokeStyle="#000";let cx=s/2,cy=s/2,step=4*dpr;for(let r=step;r<s*.72;r+=step){q.beginPath();q.arc(cx,cy,r,0,7);q.stroke()}q.globalCompositeOperation="source-over";newRun()}
function newRun(){base=(Math.random()-.5)*.4;span=.1+Math.random()*.18;ph=Math.random()*7;dir=Math.random()<.5?-1:1;end=performance.now()+16000+Math.random()*9000}
function layer(t,m){x.save();x.translate(w/2,h/2);x.rotate(m?base+Math.sin(t*.00035+ph)*span:0);x.translate(m?Math.sin(t*.00025)*34*dpr:0,m?Math.cos(t*.00029)*26*dpr:0);x.drawImage(p,-p.width/2,-p.height/2);x.restore()}
function draw(t){if(t>end)newRun();x.setTransform(1,0,0,1,0,0);x.fillStyle="#f7f7f2";x.fillRect(0,0,w,h);x.globalAlpha=.72;layer(t,0);x.globalAlpha=.72;layer(t,1);x.globalAlpha=1;let ang=(base+Math.sin(t*.00035+ph)*span)*180/Math.PI,ox=Math.sin(t*.00025)*34,oy=Math.cos(t*.00029)*26;A.textContent=ang.toFixed(2)+" deg";O.textContent=ox.toFixed(1)+", "+oy.toFixed(1)+" px";requestAnimationFrame(draw)}
addEventListener("resize",fit);fit();requestAnimationFrame(draw);
</script></body></html>
Show code
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Moire</title><style>
html,body{margin:0;background:#0a0a0a;overflow:hidden;height:100%}
canvas{display:block}
#i{position:fixed;top:10px;left:10px;color:#7fffb0;font:13px monospace;text-shadow:0 0 4px #000;pointer-events:none}
</style></head><body>
<canvas id="c"></canvas><div id="i"></div>
<script>
(function(){
var c=document.getElementById('c'),x=c.getContext('2d'),info=document.getElementById('i');
var W,H;
function resize(){W=c.width=innerWidth;H=c.height=innerHeight;}
window.onresize=resize;resize();

function mkLayer(spacing){
 var s=document.createElement('canvas');
 s.width=W;s.height=H;
 var g=s.getContext('2d');
 g.fillStyle='#fff';g.fillRect(0,0,W,H);
 g.strokeStyle='#000';g.lineWidth=1;
 var maxR=Math.hypot(W,H)/2+spacing*2;
 for(var r=spacing;r<maxR;r+=spacing){
  g.beginPath();g.arc(W/2,H/2,r,0,Math.PI*2);g.stroke();
 }
 return s;
}

var params,layer,t0;
function init(){
 var spacing=4+Math.random()*3.5;
 var speed=(0.09+Math.random()*0.16)*(Math.random()<0.5?1:-1);
 var offAmp=15+Math.random()*35;
 var offSpeed=0.15+Math.random()*0.3;
 var phase=Math.random()*Math.PI*2;
 params={spacing:spacing,speed:speed,offAmp:offAmp,offSpeed:offSpeed,phase:phase,dur:15+Math.random()*8};
 layer=mkLayer(spacing);
 t0=performance.now();
}
init();

function frame(now){
 requestAnimationFrame(frame);
 var t=(now-t0)/1000;
 if(t>params.dur){init();t=0;}
 var angle=t*params.speed;
 var ox=Math.sin(t*params.offSpeed+params.phase)*params.offAmp;
 var oy=Math.cos(t*params.offSpeed*0.73+params.phase)*params.offAmp;

 x.globalCompositeOperation='source-over';
 x.fillStyle='#fff';
 x.fillRect(0,0,W,H);

 x.globalCompositeOperation='multiply';
 x.drawImage(layer,0,0);

 x.save();
 x.translate(W/2+ox,H/2+oy);
 x.rotate(angle);
 x.translate(-W/2,-H/2);
 x.drawImage(layer,0,0);
 x.restore();

 x.globalCompositeOperation='source-over';

 var deg=((angle*180/Math.PI)%360+360)%360;
 info.textContent='angle: '+deg.toFixed(1)+'deg   offset: ('+ox.toFixed(1)+', '+oy.toFixed(1)+') px   spacing: '+params.spacing.toFixed(1)+'px';
}
requestAnimationFrame(frame);
})();
</script>
</body></html>
Show code
<!DOCTYPE html><html><head><title>Moire</title></head><body style="margin:0;background:#000;display:flex;justify-content:center;align-items:center;height:100vh"><canvas id="c"></canvas><div id="i" style="position:absolute;top:10px;left:10px;color:#0f0;font-family:mono;font-size:10px"></div><script>
const c=document.getElementById('c'),x=c.getContext('2d'),i=document.getElementById('i');c.width=c.height=500;
function d(){x.fillStyle='#000';x.fillRect(0,0,500,500);x.strokeStyle='#0f0';x.lineWidth=0.5;const t=performance.now()*0.0005,rot=t*0.5,off=t*20;x.save();x.translate(250,250);for(let r=5;r<200;r+=4){x.beginPath();x.arc(0,0,r,0,Math.PI*2);x.stroke();}x.rotate(rot);x.translate(off*Math.cos(rot),off*Math.sin(rot));x.strokeStyle='#0f0';for(let r=5;r<200;r+=4){x.beginPath();x.arc(0,0,r,0,Math.PI*2);x.stroke();}x.restore();i.textContent=`Angle: ${(rot%6.28).toFixed(2)} | Offset: ${(off%100).toFixed(1)}`;}(function a(){d();requestAnimationFrame(a);})();
</script></body></html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:8b harness
deepseek-r1:14b 24.1 sOK
qwen3.5:9b 41.3 sOK
gemma4:26b harness
qwen3:14b 18.9 sOK
mistral-small:24b 82.6 sOK
llama3.1:8b 86.0 sOK
qwen3-coder:30b 20.9 sOK
command-r:35b 54.1 sOK
deepseek-r1:32b 30.2 sOK
qwq:32b 220.3 sOK