Overview › Code › R95
Code · blind round

R95 · Shaded sphere by painter's algorithm

blind round 9/11 local models answered

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

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

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 7000 characters in total — this is a budget on characters, not on lines, so removing line breaks does not help) that renders a rotating sphere on a <canvas> without any 3D library: distribute several hundred points evenly over the sphere surface, rotate them, sort them by depth and draw them back to front as filled discs, shading each one by the angle between its surface normal and a fixed light direction so the lit side is bright and the far side falls into shadow. Show the point count. Use only the 2D canvas context — no WebGL, no libraries. Smooth requestAnimationFrame animation, NO user input, starts immediately and keeps running (restart with a fresh random instance when finished). No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.

The answers

Local models (Brain cluster) — identical prompt, shuffled order (the sequence is not a ranking)
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
qwen3:14bERRBLANK
Show code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Canvas Sphere Renderer</title>
    <style>body { background: #111; margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; }</style>
</head>
<body>
<canvas id="sphereCanvas"></canvas>
<script>
const canvas = document.getElementById('sphereCanvas');
const ctx = canvas.getContext('2d');

let POINT_COUNT = 3500; // Target point count
const R = 150;          // Sphere radius (projection scale)
const PADDING = 4;      // Padding for display text
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerHeight * 0.9;

let points = [];
let angleA = Math.PI / 6; // Rotation around Y axis
let angleB = Math.PI / 12; // Rotation around X axis

function generatePoints() {
    points = [];
    for (let i = 0; i < POINT_COUNT; i++) {
        const phi = Math.acos(Math.random() * 2 - 1);
        const theta = Math.random() * 2 * Math.PI;
        // Initial spherical coordinates on a unit sphere
        points.push({
            x: Math.sin(phi) * Math.cos(theta),
            y: Math.sin(phi) * Math.sin(theta),
            z: Math.cos(phi)
        });
    }
}

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

    // Light direction (fixed relative to the camera/world origin)
    const L = new THREE.Vector3(-0.5, -0.8, 1.0).normalize();

    // Step 1: Transform and calculate depth/shading for all points
    let visiblePoints = [];
    for (const p of points) {
        // Apply rotation transformations
        // Rotation around Y (angleA)
        let x_rot = p.x * Math.cos(angleA) - p.z * Math.sin(angleA);
        let z_temp = p.x * Math.sin(angleA) + p.z * Math.cos(angleA);

        // Rotation around X (angleB)
        let x = x_rot;
        let y = p.y + z_temp * Math.sin(angleB);
        let z = z_temp * Math.cos(angleB) - p.y * Math.sin(angleB);

        // Calculate Normal (N) and Intensity (I)
        const N = { x: x, y: y, z: z }; // Normalized position is the normal
        let intensity = Math.max(0, N.x * L.x + N.y * L.y + N.z * L.z);

        // Simple distance check (points behind camera are discarded)
        if (z > 0.1) { 
            visiblePoints.push({ x: x, y: y, z: z, intensity: Math.min(1.0, intensity) });
        }
    }

    // Step 2: Sort points by depth (Z-coordinate) - Back to Front
    visiblePoints.sort((a, b) => b.z - a.z);

    // Step 3: Draw sorted points
    for (const p of visiblePoints) {
        // Screen projection (Orthographic/Perspective scaling)
        let screenX = p.x * R;
        let screenY = p.y * R;
        
        // Disc size proportional to intensity
        let radius = Math.max(0.5, 1 + p.intensity * 2);
        
        // Calculate color: bright (white/yellow) when lit, dark blue/black in shadow
        const alpha = p.intensity;
        ctx.fillStyle = `rgba(${Math.floor(255 * Math.max(0.3, p.intensity))}, ${Math.floor(100 * Math.min(1.0, p.intensity))} , ${Math.floor(255 - 255 * p.intensity)} , ${alpha})`;
        
        ctx.beginPath();
        ctx.arc(screenX, screenY, radius, 0, Math.PI * 2);
        ctx.fill();
    }

    // Display Point Count
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
    ctx.font = '16px monospace';
    ctx.fillText(`Points: ${visiblePoints.length}`, PADDING, PADDING);
}

function animate() {
    // Update rotation angles smoothly
    angleA += 0.003;
    angleB += 0.002;

    projectAndRender();
    requestAnimationFrame(animate);
}

// Custom Vector class for readability and math (used only internally)
function THREE() { return {
    Vector3: function(x, y, z) { this.x = x || 0; this.y = y || 0; this.z = z || 0; };
    normalize: function() {
        const mag = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        if (mag === 0) return this;
        this.x /= mag; this.y /= mag; this.z /= mag;
        return this;
    }
};

// Initialization
generatePoints();
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.
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rotating Sphere</title>
<style>
body { margin: 0; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; background: #000; color: #fff; font-family: Arial, sans-serif; }
canvas { border: none; }
#pointCount { position: absolute; top: 20px; left: 20px; z-index: 1; }
</style>
</head>
<body>
<div id="pointCount"></div><canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

function random(min, max) {
return min + Math.random() * (max - min);
}

function getRandomPointOnSphere(radius) {
const theta = random(0, 2 * Math.PI);
const phi = Math.acos(random(-1, 1));
return { x: radius * Math.sin(phi) * Math.cos(theta), y: radius * Math.sin(phi) * Math.sin(theta), z: radius * Math.cos(phi) };
}

function generatePoints(numPoints, radius) {
let points = [];
for (let i = 0; i < numPoints; i++) {
points.push(getRandomPointOnSphere(radius));
}
return points;
}

function sortByDepth(points) {
return points.sort((a, b) => b.z - a.z);
}

function drawPoint(ctx, x, y, radius, color) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
}

function rotatePoints(points, angleX, angleY, angleZ) {
const cX = Math.cos(angleX), sX = Math.sin(angleX);
const cY = Math.cos(angleY), sY = Math.sin(angleY);
const cZ = Math.cos(angleZ), sZ = Math.sin(angleZ);

function rotatePoint(p) {
const x1 = p.x * cY + p.z * -sY;
const z1 = p.x * sY + p.z * cY;

const y2 = (cX * z1) + (y1 * -sX);
const x2 = x1 ;

const y3 = (cZ * (-x1)) + (-z1 * -sZ);

return { x: x2, y: y2, z: y3 };
}

return points.map(rotatePoint);
}

function updateCanvas(points) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
points = sortByDepth(points);
points.forEach(p => {
const lightDir = { x: 1, y: 1, z: 1 };
const normal = p;
const dotProduct = (normal.x * lightDir.x + normal.y * lightDir.y + normal.z * lightDir.z) / Math.sqrt(normal.x ** 2 + normal.y ** 2 + normal.z ** 2);
const intensity = Math.max(0, dotProduct);
const color = `rgba(${Math.floor(255 * intensity)}, ${Math.floor(255 * (1 - intensity))}, 0, 1)`;
drawPoint(ctx, p.x + canvas.width / 2, p.y + canvas.height / 2, 1.5, color);
});
}

function animate(angleX = Math.PI / 4, angleY = Math.PI / 4, angleZ = Math.PI / 4) {
const points = generatePoints(300, 100);
let rotationX = .02;
let rotationY = .01;

function frame() {
angleX += rotationX;
angleY += rotationY;
points = rotatePoints(points, angleX, angleY, 0);

updateCanvas(points);
requestAnimationFrame(frame);
}

frame();

document.getElementById('pointCount').innerText = `Point Count: ${points.length}`;
}
animate();
</script>
</body>
</html>
```
llama3.1:8bERRSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<style>
body { margin: 0; }
canvas { border: 1px solid black; width: 600px; height: 400px; }
</style>
</head>
<body>
<script>
function rand() {
  return Math.random()*2-1;
}
let s = 100, w=300, h=200,
    ctx = document.getElementById('c').getContext('2d'),
    points = [], cnt=0;

function makePoints(n) {
  for(let i=0;i<n;i++) {
    var x,y,z,d;
    do {x=w*rand();y=h*rand();z=s*rand(); d=x*x+y*y+z*z}
    while(d>1);
    points.push([x+Math.cos(Math.acos(z/s))*w/2, y+Math.sin(Math.acos(z/s))*h/2]);
  }
}

function rotatePoints() {
  for(let i=0;i<points.length;i++) {
    var x=points[i][0],y=points[i][1];
    points[i] = [x*Math.cos(t)+y*Math.sin(t), -x*Math.sin(t)+y*Math.cos(t)];
  }
}

function sortPoints() {
  for (let i = 0; i < points.length; i++) {
    var min = i;
    for (let j = i + 1; j < points.length; j++) {
      if (points[j][1] < points[min][1]) min = j;
    }
    [points[i], points[min]] = [points[min], points[i]];
  }
}

function drawPoints() {
  ctx.fillStyle='black';
  ctx.fillRect(0,0,w,h);
  for(let i=0;i<points.length;i++) {
    var x=points[i][0]+w/2,
        y=-points[i][1]+h/2,
        r=5,sin=(x*Math.cos(t)+y*Math.sin(t))/Math.sqrt(x*x+y*y+1),
        c=Math.max(0,1-10*sin*(sin+0.3));
    ctx.fillStyle=`rgb(${255*c},${255*c},${255*c})`;
    ctx.beginPath();
    ctx.arc(x,y,r,0,2*Math.PI);
    ctx.fill();
  }
}

function animate() {
  t+=0.01;
  rotatePoints();
  sortPoints();
  drawPoints();
  document.getElementById('c').title=points.length;
  requestAnimationFrame(animate);
}
let t = 0;

makePoints(200);
ctx.translate(w/2,h/2);
ctx.rotate(t);
document.body.appendChild(document.createElement('canvas')).id='c';
animate();
</script>
</body>
</html>
```
qwen3-coder:30bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<canvas id="sphereCanvas"></canvas><script>
const canvas = document.getElementById('sphereCanvas');
const c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

let points = [];
const N_POINTS = 500; // Point count displayed on screen
let rotationAngle = 0;
let lightDirection = { x: 1, y: -0.5 }; // Fixed light source

// Helper function for random point generation (uniform surface distribution)
function generatePoint(r, theta, phi) {
    const r_sph = r * Math.sqrt(Math.random());
    const theta_s = Math.acos(-1 + 2 * Math.random()); // Polar angle (0 to pi)
    const phi_s = Math.random() * 2 * Math.PI;      // Azimuthal angle
    return { x: r_sph * Math.sin(theta_s), y: r_sph * Math.cos(theta_s), z: r_sph * Math.cos(theta_s) };
}

function initialize() {
    points = [];
    const radius = 150;
    for (let i = 0; i < N_POINTS; i++) {
        // Use a slightly modified approach for uniform distribution of coordinates
        const theta = Math.acos(2 * Math.random() - 1);
        const phi = Math.random() * 2 * Math.PI;
        points.push({ x: radius * Math.sin(theta) * Math.cos(phi), y: radius * Math.sin(theta) * Math.sin(phi), z: radius * Math.cos(theta) });
    }
    rotationAngle = 0;
}

function updateAndDraw() {
    c.clearRect(0, 0, canvas.width, canvas.height);

    // Rotation transformation (Simple rotation around Y-axis for effect)
    const cosA = Math.cos(rotationAngle);
    const sinA = Math.sin(rotationAngle);

    let transformedPoints = points.map(p => ({
        x: p.x * cosA + p.z * sinA,
        y: p.y,
        z: -p.x * sinA + p.z * cosA // Note the sign change for z to simulate camera viewing direction
    }));

    // 1. Calculate Depth and Sort (Z-buffer technique)
    let visiblePoints = transformedPoints.map((p, i) => {
        const originalP = points[i];
        const depth = p.z; // Z is the distance from the camera plane
        
        // Normal vector calculation: N = normalize( (x/R, y/R, z/R) )
        let nx = originalP.x / 150;
        let ny = originalP.y / 150;
        let nz = originalP.z / 150;

        // Surface normal components (assuming sphere radius R=150)
        let normalX = nx * cosA + nz * sinA;
        let normalY = ny;
        let normalZ = -nx * sinA + nz * cosA;
        
        // Shading factor: Dot product of Normal and Light Direction (L)
        // L is fixed, so we just use the components.
        let NdotL = normalX * lightDirection.x + normalY * lightDirection.y + normalZ * lightDirection.z;

        return { p: p, depth: depth, n: Math.max(-1, Math.min(1, NdotL)) }; // Clamp shading between 0 and 1
    });

    // Sort by Z (depth) ascending distance means drawing back-to-front requires DESCENDING sort on the z coordinate
    visiblePoints.sort((a, b) => b.depth - a.depth);

    // 2. Drawing Phase
    for (const vp of visiblePoints) {
        const p = vp.p;
        let shade = vp.n * 0.5 + 0.5; // Remap dot product from [-1, 1] to [0, 1] brightness

        // Calculate color based on shading (e.g., dark blue/grey background)
        c.fillStyle = `rgb(${Math.floor(shade * 255)}, ${Math.floor(Math.max(10, shade*100))}, ${Math.floor(Math.min(255, Math.ceil(shade*255)))})`;

        // Project to screen and draw disc
        c.beginPath();
        c.arc(p.x, p.y, 2, 0, Math.PI * 2); // Radius of the disc
        c.fill();
    }

    rotationAngle += 0.01;
    requestAnimationFrame(updateAndDraw);
}

// Initialization and Start
initialize();
updateAndDraw();
</script>
```
deepseek-r1:14bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="background:#000;margin:0;display:flex;justify-content:center;align-items:center;height:100vh">
<canvas id=c></canvas>
<script>
const canvas=document.getElementById('c'), ctx=canvas.getContext('2d');
const W=400,H=400,N=500,points=[],r=80;
function init(){for(let i=0;i<N;i++){let u=Math.random()*Math.PI*2,v=Math.random()*Math.PI;let x=r*Math.cos(u)*Math.sin(v),y=r*Math.sin(u)*Math.sin(v),z=r*Math.cos(v);points.push({x,y,z,u:v,T:i/5})}canvas.width=W(canvas.style.width=''),canvas.height=H}
function deg(c){return c.slice(-1)+'0'+c.slice(0,-1)} // viewport transform
function project(x,y,z,s,cx,W,H,k=200){return{x:k*(cx.x*s+z)/z+W/2,y:k*(cx.y*s+z)/z+H/2}} 
function dot(a,b){return a.x*b.x+a.y*b.y+a.z*b.z}
const light={x:1,y:0,z:0},vup={x:0,y:1,z:0};
let time=0,renderFrame;
function render(){if(!points.length)init();ctx.fillStyle='white';ctx.beginPath();points.sort((a,b)=>a.T-b.T).forEach(p=>{p.x+=Math.sin(time+p.u/5),p.y+=Math.cos(time+p.u/10);const R={x:Math.cos(time+p.u*2),y:p.z/r};const s=dot(R,{x:light.x,y:light.y,z:light.z}*2+1)/2+1;ctx.beginPath();const pos=project(p.x,p.y,p.z,r, {x:W/2,y:H/2}, W, H); ctx.arc(pos.x,pos.y,3*(r/(z+p.z*r)),0,2*Math.PI); ctx.fillStyle=`rgba(1,1,1,${'s'})`.replace('s',s.toString());ctx.fill()}); ctx.font='16px Arial';ctx.fillStyle='white';ctx.fillText(points.length+' points',10,20)} 
function animate(){time+=0.03;requestAnimationFrame(animate);render()}
window.onload=init;
</script>
```

This code creates a rotating sphere with 500 points using only HTML Canvas and JavaScript. The points are distributed evenly over the sphere's surface, shaded based on their angle to a fixed light direction, and rendered in order from farthest to closest to create depth perception. The animation runs smoothly at 60 FPS without any external libraries or dependencies.

Key features:
- Pure JavaScript implementation
- Rotating sphere with shading using dot product lighting
- Points rendered from back to front for proper occlusion
- Smooth animation using requestAnimationFrame
- Dynamic point count display
- No external resources or dependencies
- Optimized for minimal code size

The sphere rotates continuously and restarts when it finishes a full rotation. The points are shaded based on their orientation relative to the light direction, creating a realistic lighting effect.

Note: Character count is kept to 7000 by removing unnecessary characters, combining lines where possible, and using minimal whitespace.
qwen3.5:9bOKFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Spherical Mesh Renderer</title></head>
<body style="background-color:#000;margin:0;overflow:hidden;"><canvas id="c" width="600" height="600" style="display:block;"></canvas><script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let points = [];
let rotationAngleA = 0;
let rotationAngleB = 0;
const POINT_COUNT = 800;

function init() {
    points = [];
    for (let i = 0; i < POINT_COUNT; i++) {
        // Generate uniform points on a unit sphere using spherical coordinates
        let phi = Math.acos(Math.random() * 2 - 1); // Inclination angle [0, pi]
        let theta = Math.random() * 2 * Math.PI;   // Azimuthal angle [0, 2pi]
        const R = 150; // Sphere radius for drawing
        points.push({
            x: R * Math.sin(phi) * Math.cos(theta),
            y: R * Math.sin(phi) * Math.sin(theta),
            z: R * Math.cos(phi)
        });
    }
    rotationAngleA = 0;
    rotationAngleB = 0;
}

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

    // Update rotation angles for animation
    rotationAngleA += 0.015;
    rotationAngleB += 0.02;

    const lightDir = { x: 1, y: -0.8, z: 1 }; // Fixed light source direction (normalized implicitly)
    const drawData = [];

    for (let p of points) {
        // Apply rotations (assuming Y-axis rotation for A and X-axis for B)
        let x = p.x * Math.cos(rotationAngleA) - p.y * Math.sin(rotationAngleA);
        let y = p.x * Math.sin(rotationAngleA) + p.y * Math.cos(rotationAngleA);
        let z = p.z;

        // Second rotation (around the new Y axis: Z -> X, X -> -Z)
        let nx = x;
        let ny = y * Math.cos(rotationAngleB) - z * Math.sin(rotationAngleB);
        let nz = y * Math.sin(rotationAngleB) + z * Math.cos(rotationAngleB);

        // Perspective Projection (simplified: using Z for depth and scaling X/Y by 1/Z)
        const scaleFactor = 25 / (nz + 300); // Add offset to prevent division by zero/instability
        let screenX = canvas.width / 2 + nx * scaleFactor;
        let screenY = canvas.height / 2 - ny * scaleFactor;

        // Depth and Shading Calculation: Use the dot product of normalized point vector (nx, ny, nz) and lightDir
        // Normalizing helps for consistent lighting regardless of radius scaling
        const magnitude = Math.sqrt(nx*nx + ny*ny + nz*nz);
        let p_x = nx / magnitude;
        let p_y = ny / magnitude;
        let p_z = nz / magnitude;

        // Calculate intensity (Lambertian reflectance)
        let dotProduct = Math.max(0, p_x * lightDir.x + p_y * lightDir.y + p_z * lightDir.z);
        
        drawData.push({ 
            x: screenX, y: screenY, depth: nz, intensity: dotProduct 
        });
    }

    // Sort by depth (furthest objects first) to ensure proper occlusion drawing
    drawData.sort((a, b) => b.depth - a.depth);

    // Draw points back-to-front
    ctx.globalCompositeOperation = 'lighter'; 
    for (let d of drawData) {
        const radius = Math.max(0.5, d.intensity * 3); // Max size adjusted by lighting
        let alpha = Math.min(1, d.intensity * 2 + 0.1);
        ctx.fillStyle = `rgba(${Math.floor(d.intensity*255)}, ${Math.floor(d.intensity*255)}, ${Math.floor(d.intensity*255)}, ${alpha})`;
        ctx.beginPath();
        ctx.arc(d.x, d.y, radius, 0, Math.PI * 2);
        ctx.fill();
    }

    // Draw Info Text
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
    ctx.font = '14px Arial';
    ctx.fillText(`Points: ${POINT_COUNT}`, 10, 20);

    requestAnimationFrame(draw);
}

// Start the animation loop
init();
draw();
</script></body></html>
```
deepseek-r1:32bSTATFORMAT
Show code
```html
<!DOCTYPE html><html><body style="display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#000"><canvas id=c></canvas>
<script>
const c=document.getElementById('c'), ctx=c.getContext('2d');
c.width=600;c.height=400;
class SpherePoints{
 constructor(n){
 this.n=n;
 this.r=50;
 this.points=[];
 this.rx=0;
 this.ry=0;
 this.lx=Math.random();
 this.ly=Math.random();
 this.lz=1;
 for(let i=0;i<n;i++){
 let u=Math.random(), v=Math.random();
 let theta=2*Math.PI*u, phi=Math.acos(2*v-1);
 let x=Math.cos(theta)*Math.sin(phi), y=Math.sin(theta)*Math.sin(phi), z=Math.cos(phi);
 this.points.push({x,y,z,rx:0,ry:0});
 }
 }
 rotate(){
 this.rx+=.03;this.ry+=.04;
 for(let p of this.points){
 p.xr=(p.x*Math.cos(this.ry)-p.y*Math.sin(this.ry));
 p.yr=(p.x*Math.sin(this.ry)+p.y*Math.cos(this.ry));
 p.xr2=p.xr;
 p.yr2=(p.yr*Math.cos(this.rx)-p.z*Math.sin(this.rx));
 p.zr=(p.yr*Math.sin(this.rx)+p.z*Math.cos(this.rx));
 p.sx=c.width/2+50*(p.xr2/(p.zr+100));
 p.sy=c.height/2-50*(p.yr/(p.zr+100));
 let dx=p.x-p.lx, dy=p.y-p.ly, dz=p.z-p.lz;
 p.intensity=Math.max(0,(dx*dx+dy*dy+dz*dz)**.5);
 }
 this.points.sort((a,b)=>b.zr - a.zr);
 }
 draw(){
 ctx.fillStyle='#000';ctx.fillRect(0,0,c.width,c.height);
 for(let p of this.points){
 let intensity=(p.xr*p.lx + p.yr*p.ly + p.zr*p.lz)/this.r;
 intensity=Math.max(0,intensity);
 ctx.beginPath();
 ctx.arc(p.sx,p.sy,2,0,Math.PI*2);
 ctx.fillStyle=`rgba(${150+intensity*60},${150+intensity*60},${150+intensity*60},.7)`;
 ctx.fill();
 ctx.strokeStyle='white';
 ctx.stroke();
 }
 ctx.fillStyle='#fff';ctx.font='30px monospace';ctx.fillText(this.n,10,30);
 }
}
let points=new SpherePoints(200),frames=0;
function animate(){
points.rotate();points.draw();
frames++;if(frames==150)location.reload();
requestAnimationFrame(animate);
}animate();
</script></body></html>
```
qwq:32bBLANKLIM
Show code
<think>

Okay, let's tackle this problem. The user wants an HTML file that creates a rotating sphere with points distributed evenly, shaded based on a light direction, and animated using the 2D canvas without any libraries. Hmm, where to start?

First, the HTML structure. I need a canvas element and a script. The script should set up the canvas context, initialize the sphere points, and handle the animation loop with requestAnimationFrame.

Distributing points evenly over a sphere... I remember that using a Fibonacci lattice might be a good method. The formula involves calculating points based on the golden angle. Let me recall that the golden angle is approximately 137.5 degrees or about (sqrt(5)-1)*180 degrees. So for each point, the angle increases by this value. The polar angle (theta) can be calculated using arcsin, maybe? Let me think. The idea is to distribute points evenly by incrementing the angle and using the golden ratio to spread them out.

Wait, Fibonacci sphere algorithm: for i from 0 to N-1, theta = 2*pi*i*phi, where phi is the golden angle (≈2.39996 radians). The polar angle (elevation) is arcsin(1 - (2*i+1)/N). Or maybe using a different approach? I need to make sure the points are as evenly spaced as possible. Let me check the exact formula again.

Alternatively, using spherical coordinates with angles generated in a way that prevents clustering at the poles. The method with theta = 2pi*i*phi and phi = asin((2i+1)/N - 1) might work, but I might have to adjust the indices. Let me try to code that.

Next, rotating the sphere. Each point needs to be rotated in 3D space. Since it's a sphere, rotating around an axis (maybe X and Y axes?) and updating their positions each frame. The rotation matrices can be applied to each point's coordinates.

Then, sorting the points by their depth. Since the camera is looking along a certain direction, probably the Z-axis. Wait, in a typical setup, maybe the view direction is along the negative Z-axis. So the depth could be the Z-coordinate of each point after rotation. The further the point is along the negative Z, the closer to the viewer. Wait, actually, in a standard coordinate system, maybe the depth is (x, y, z) projected? Hmm, perhaps the depth is the distance from the camera. Alternatively, since we're using the 2D canvas, maybe we need to project the 3D points onto 2D, but the sorting is based on the Z-coordinate after rotation. The points need to be drawn back to front, so the ones with the highest Z (farthest from the camera) come first. Wait, actually, the camera is at the origin looking along the negative Z axis. So the depth is the Z coordinate. So higher Z means further away. So to draw back to front, sort points by Z in descending order. So we sort the points based on their rotated Z coordinate (after rotation each frame). Then, when drawing, we draw from back to front so that overlapping points are covered correctly.

Shading each disc: The light direction is fixed, let's say pointing towards (0,1,0) or something. The surface normal at each point on a sphere is the normalized vector from the center to the point, which is the point's coordinates (since the sphere is unit). So the normal vector is (x,y,z). The light direction is a unit vector; the angle between the normal and light direction is the cosine of which would be the dot product of the two vectors. So the brightness is proportional to the dot product. If it's negative, the point is in shadow, so maybe set to a darker color. We can use a gradient from white to dark gray based on this value. But to make it look good, maybe clamp the value to 0 so that the back side is dark.

Now, the steps:

1. Initialize the canvas, get context.
2. Generate points on the sphere using Fibonacci lattice method.
3. Set up rotation angles for the sphere (maybe two rotation angles, like around X and Y axes so the sphere spins smoothly).
4. In the animation loop:
   a. Clear the canvas.
   b. Update rotation angles (increment them each frame).
   c. For each point, apply rotation matrices (rotate around X and Y axes? Or just one axis? Maybe two axes for more dynamic rotation).
   d. Project each rotated point to 2D (but since it's a sphere, maybe just scaling to fit the canvas, but the 2D coordinates are just x and y scaled to the canvas size, ignoring z? Or do we need to do some perspective? Probably not, since it's a simple 2D projection. The depth sorting is based on the z-coordinate after rotation, so projection is just x and y divided by some scaling factor to fit the canvas. Let's say the sphere is centered on the canvas, scaled to a radius of, say, 100 units, so the points' coordinates are scaled accordingly.
   
5. Sort the points by their z-coordinate (after rotation). Higher z means closer to the viewer (since if the camera is at (0,0,0) looking at negative z direction, a point with z = 1 is behind a point with z = -1, so to draw back to front, higher z comes first?)

Wait, if the sphere is rotating, let's say rotating around the x and y axes, so each point's position is transformed by rotation matrices. The z-coordinate of the rotated point determines how far it is from the camera. So the point with a higher z (since camera is at center, looking along negative z) would be further away, so needs to be drawn first so that closer points (with lower z) are on top.

Wait, if the camera is looking along the negative z axis, then the distance from the camera is along the z-axis, so the depth is the z-coordinate. A point with z = -1 is closer than a point with z=0. So to draw back to front, we need to sort by z in descending order. The point with the largest z (most positive) is farthest back, so drawn first, then the next, until the ones closest (smallest z) are drawn last, over the top.

Yes, so in the animation loop:

For each point:

- Apply rotation (maybe two different rotations: first rotate around X axis by angleX, then around Y axis by angleY? Or vice versa? The order might affect the rotation direction, but the key is to have them rotate smoothly.)

Wait, to apply rotation matrices, each point is a vector (x, y, z). The rotation matrices can be applied step by step. Let me think:

Suppose angleX is the rotation around X-axis, angleY around Y, and angleZ around Z? Or perhaps better to have two angles for rotation? Let's say we have two rotation angles, like a and b, for two axes. For simplicity, maybe rotate around X and Y axes each frame with different speeds. So each frame, angleX += deltaX, angleY += deltaY; then apply rotation matrices for those angles.

The rotation matrices:

Rotation around X-axis:

|1 0 0|
|0 cosX sinX|
|0 -sinX cosX|

Rotation around Y-axis:

|cosY 0 -sinY|
|0 1 0|
|sinY 0 cosY|

So to combine rotations, we need to apply them in order. Suppose we first rotate around X, then around Y. The resulting rotation is Y_rotation * X_rotation? Because matrix multiplication is order-sensitive.

Alternatively, perhaps it's simpler to have a rotation for each axis. Maybe start with rotating around the X-axis by angleX, then around Y by angleY, then around Z by angleZ. But for the purposes of this problem, maybe two axes are sufficient. Let's pick two angles, like angleX and angleY, and apply the rotations in a sequence.

But this might get complex. Alternatively, the user might just want the sphere to rotate in a way that it's spinning, so perhaps just rotating around a single axis? But the problem says "rotate them" without specifying the axes. To make it look more dynamic, two axes would be better.

Hmm okay. Let's proceed with two rotation angles. Each frame, increment both angles (like 0.3 and 0.1 radians per frame or something).

Now, for each point (x,y,z):

First, rotate around X-axis by angleX:

newY = y * cos(angleX) - z * sin(angleX)
newZ = y * sin(angleX) + z * cos(angleX)
So new coordinates after X rotation: (x, newY, newZ)

Then, rotate around Y-axis by angleY:

newX = x * cos(angleY) + newZ * sin(angleY)
newZ = -x * sin(angleY) + newZ * cos(angleY)
So after Y rotation, coordinates are (newX, newY, newZ)

Wait, this is getting a bit tangled. Alternatively, perhaps using a rotation matrix for each rotation step.

Alternatively, maybe it's easier to represent the rotation as a series of matrix multiplications. Let me recall:

First, the rotation around X axis by angleX:

The rotation matrix is:

[1, 0, 0,

0, cosX, -sinX,

0, sinX, cosX]

Then the rotation around Y would be:

[cosY, 0, sinY,

0, 1, 0,

-sinY, 0, cosY]

So the combined rotation (applying Y after X) would be the product of the Y matrix multiplied by the X matrix. But perhaps for each point, it's easier to compute step by step.

Alternatively, for each point's coordinates (x,y,z), we first rotate around X, then around Y. Let me compute it step by step.

After rotating around X:

x remains the same.

y becomes y * cosX - z * sinX

z becomes y * sinX + z * cosX (Wait, no. Let me recalculate: the rotation matrix for X-axis rotation. Let me see:

For a rotation around X-axis by angle theta, the matrix is:

[1 0 0;

0 cosθ -sinθ;

0 sinθ cosθ]

So applying to a vector (x, y, z):

newX = x

newY = y cosθ - z sinθ

newZ = y sinθ + z cosθ

Then rotating this result around Y-axis by angle phi:

The rotation around Y-axis by phi has matrix:

[cosφ 0 sinφ;

0 1 0;

-sinφ 0 cosφ]

So applying to (newX, newY, newZ):

newX2 = newX * cosφ + newZ * sinφ

newY2 = newY

newZ2 = -newX * sinφ + newZ * cosφ

So combining both rotations, the new coordinates after X then Y rotations are:

x' = newX2 = (x) * cosφ + (newZ) * sinφ

Wait, but newZ after X rotation is y sinθ + z cosθ. So substituting:

x' = x * cosφ + (y sinθ + z cosθ) * sinφ

y' = newY = y cosθ - z sinθ

z' = -x sinφ + (y sinθ + z cosθ) * cosφ

Hmm, this is getting complicated. Maybe it's easier to represent each rotation step.

Alternatively, perhaps we can just apply the rotations in code step by step for each point.

Anyway, moving on.

Once the points are rotated, we need to project them into 2D. Since we're using a 2D canvas, the projection is orthographic. The screen coordinates can be derived by scaling the x and y coordinates to fit the canvas. Let's say the sphere has a radius of 1 (unit sphere), and after rotation, the points are scaled by a radius (like 100px) and centered on the canvas.

So the center of the canvas is (canvas.width/2, canvas.height/2). The scaling factor would be something like scale = 100. So the x and y coordinates of the rotated point are scaled by scale and added to the center.

Wait, but the depth calculation is based on the z-coordinate after rotation. So for each rotated point, we keep its rotated x, y, z. The depth is the z value, which we sort on.

So steps per frame:

- For each point:

1. Apply rotation around X and Y (or whatever axes, with angles that change each frame)

2. Compute rotated x, y, z.

3. Store the rotated z for depth sorting.

4. Compute the 2D screen coordinates (x and y scaled, centered on the canvas).

Once all points are rotated and their positions computed, sort the points based on their rotated z (descending order)

Then, for each point in the sorted list:

- get the light direction. Let's say the light is at (0, 1, 0), so light vector is (0,1,0). The normal of the point is the original un-rotated position? Wait, no. Wait, the normal of the sphere's surface at a point (x,y,z) is the same as the direction from the center to the point, i.e., (x,y,z). But after rotation, the normal is also rotated. Hmm, this is a problem. Because when you rotate the sphere, the normals also rotate with the points. So the normal vector after rotation is the same as the rotated point's position (since it's a unit sphere). Wait no, the normal at each point is the vector pointing away from the center, so after rotation, the point's position is (x', y', z'), and the normal vector is (x', y', z'). Wait, yes, because rotating the sphere doesn't change the direction of the normal, it's just that the point's position is rotated, so the normal is the rotated position vector. So to get the normal, we can use the rotated x,y,z coordinates of the point.

Wait, but when you rotate the point, the normal vector is also rotated the same way. Because the normal is a vector from the center to the point, so rotating both the point and the normal together.

So the normal vector is (x', y', z'), since after rotation, the point is (x', y', z') and the normal points in that direction.

The light direction is fixed, say (0, 1, 0). Wait, but if the light is fixed, then its direction might need to be in the same rotated coordinate system? Wait, no, the light direction is fixed in world coordinates. Wait, the problem says "a fixed light direction". So the light direction is fixed, like (0,1,0) before any rotations. Wait no, the rotations are applied to the points, so the light direction is in the original coordinate system. Hmm, perhaps the light direction is fixed in the world (not rotating with the sphere). So the light direction is, say, (0, 1, 0). But after the sphere rotates, each point's normal is rotated, so their normals relative to the light direction change.

So the normal vector after rotation is (x', y', z'), and the light direction is (0,1,0). The angle between the normal and the light is the dot product between (x', y', z') and (0,1,0). The dot product is y'. So the brightness would be max(y', 0) because if y' is negative, then it's in shadow. So the brightness factor is y', clamped to 0. So the color is something like (brightness * 255, etc.) but maybe using a gradient, but for simplicity, just use a gray scale where the color intensity is proportional to the max(y',0), scaled to 0-255. Alternatively, using a color like rgba(r, r, r, 1) where r is 128 + 127 * max(y',0). That way, when y' is 1 (directly facing the light), it's white (255), and when y' is negative, it's 0 (black).

Alternatively, use rgba(255 * (y' * 0.5 + 0.5), ...) but that might not be right. Need to think: if the normal is exactly in the direction of the light (y' =1), then the brightness is full. If it's facing away (y' =-1), it's in shadow. So the brightness can be max(y',0) * something. So the brightness is between 0 and 1, so we can multiply by 255 to get the RGB value.

Wait, let me formalize:

dotProduct = normal · lightDirection = (x', y', z') · (0,1,0) = y'

So brightness = Math.max(dotProduct, 0); // so 0 to 1

So the color can be set with fillStyle = 'rgba(' + (255*brightness) + ',' + ... but since we want a single color shading, using grayscale. So fillStyle would be 'rgba(' + (255*brightness) + ',' + (255*brightness) + ',' + (255*brightness) + ',1)'.

But in code, we can compute that as:

ctx.fillStyle = 'rgba(' + (brightness * 255) | 0 + ',' + (brightness * 255) | 0 + ',' + (brightness * 255) | 0 + ',1)';

Alternatively, using a more efficient way, but that's manageable.

Now, the code outline:

- HTML canvas with id 'c'.

- JavaScript:

- Initialize variables:

var points = generatePoints(N), where N is the number of points (like 500 or so).

- rotation angles: angX, angY.

- function generatePoints(n) {

   // implement Fibonacci lattice method here.

   var points = [], phi = Math.PI * (3 - Math.sqrt(5));

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

       var y = 1 - (i/(n-1))*2; // from 1 to -1

       var r = Math.sqrt(1 - y*y);

       var theta = phi*i;

       var x = Math.cos(theta)*r;

       var z = Math.sin(theta)*r;

       points.push( {x:x, y:y, z:z} );

   }

   return points;

}

Wait, but this is the way to generate points on a sphere using the Fibonacci spiral, which is good for even distribution.

Wait, but in the code above, the coordinates are stored as x,y,z for each point. Wait, but in the Fibonacci method:

The standard approach is to generate points on a sphere as follows:

// Golden Angle Increment

var goldenRatio = (1 + Math.sqrt(5)) / 2;

var invPhi = 2 - goldenRatio;

var theta = Math.PI * 2 * i * invPhi;

var y = 1 - (i/(n-1))*2; // y goes from 1 to -1

var r = Math.sqrt(1 - y*y);

var x = Math.cos(theta) * r;

var z = Math.sin(theta) * r;

Yes. So that code is correct.

So each point has x,y,z coordinates on the unit sphere.

Then, in the animation loop:

Each frame:

- Update rotation angles (angX += delta, angY += delta, or something)

Wait, but for the rotation, perhaps we can make it smooth.

But in the code, we can have:

var angX = 0, angY = 0;

Then, in each frame, increment them by a small delta (like 0.01 radians per frame). But need to keep track of time.

Alternatively, use requestAnimationFrame with a callback.

Putting it all together.

Now, the steps in the draw function:

1. Clear the canvas.

2. For each point in points array:

   a. Apply rotation to the point's x,y,z to get x', y', z'.

   Wait, but the points are stored as the original coordinates. So in each frame, we need to recalculate the rotated positions.

   So the points array holds the original coordinates, and each frame we rotate them.

   So perhaps for each point, we store the original coordinates.

   Then, in each frame:

   for each point in points:

       compute x', y', z' based on angX and angY.

       and then store the rotated coordinates for later processing (sorting based on z').

   So can we create an array of objects for each point that includes their rotated x, y, z each frame?

Alternatively, during each frame:

   create a temporary array of objects with rotated positions and the original normal.

Wait, but for the normal, it's the rotated position (x', y', z'). So perhaps we can compute the rotated coordinates and store them in temporary variables.

Alternatively, in code:

loop through each point and compute:

// for each point:

var x = point.x;

var y = point.y;

var z = point.z;

then rotate x,y,z by angX around X, then angY around Y.

Wait, but the rotation order is important. Let's say first rotate around X by angX, then around Y by angY.

Wait, the rotation matrices are applied in the order of X then Y.

So first apply rotation around X-axis by angX, then rotation around Y-axis by angY.

So steps:

First rotation around X by angX:

x1 = x;

y1 = y * Math.cos(angX) - z * Math.sin(angX);

z1 = y * Math.sin(angX) + z * Math.cos(angX);

Then, rotation around Y-axis by angY:

x2 = x1 * Math.cos(angY) + z1 * Math.sin(angY);

y2 = y1;

z2 = -x1 * Math.sin(angY) + z1 * Math.cos(angY);

Thus, the rotated coordinates after both rotations are (x2, y2, z2).

Wait, but the order may be different. Let me verify:

The rotation order is important. The final rotation is the combination of first rotating around X, then around Y.

So first, the point is rotated around X, then the result is rotated around Y.

Thus, the final coordinates are as computed above.

Therefore, the rotated coordinates x', y', z' are (x2, y2, z2).

The normal vector is (x', y', z'), since it's a unit sphere.

So then, the light direction is (0,1,0). So the dot product is y'.

Then, the brightness is Math.max(y', 0).

So for each point, we can compute these values.

Once all points are processed, we need to sort them based on their z' coordinate (the z-coordinate after rotation) to draw them in correct order (front to back? Or back to front?)

Wait, to do proper depth sorting, we should sort them from back to front (so that farther points are drawn first, then closer ones on top). Since z' is the depth, so if z is positive, the point is in front of the camera (assuming camera is at (0,0,0) looking along positive z axis? Wait, maybe the camera is looking along the positive z direction, but in this case, the projection is orthographic. Wait, actually, the projection is just the x and y coordinates after rotation? Or perhaps we need to consider the projection.

Wait, in the projection:

The screen coordinates are computed from the rotated x' and y' coordinates (or x2 and z2? Wait, need to clarify the coordinate system.

Wait, the rotated coordinates are (x2, y2, z2). The projection is orthographic, so the screen coordinates could be calculated by scaling the x and z coordinates? Or x and y?

Wait, in 3D, if the camera is looking along the z-axis, then the projection would be x and y coordinates, but in this case, perhaps our projection is just taking x2 and y2 as screen coordinates, but scaled appropriately. Wait, perhaps the camera is at (0,0,0), looking in the direction of positive z, so the view plane is perpendicular to the z-axis, so the x and y coordinates determine the position on the screen, and the z determines depth. So the screen coordinates would be x', y', and the depth is z'.

Wait, but in that case, the projection would be (x', y') scaled to the canvas. But in our case, we might have to adjust the scaling and centering.

Wait, let me think:

Suppose the sphere is centered at (0,0,0). After rotation, each point has coordinates (x', y', z'). To project onto the 2D canvas, we can consider the x and y coordinates (scaled) as the screen position, and the z is the depth (distance along the line of sight). Thus, for depth sorting, points with higher z (closer to the viewer) should be drawn on top.

Therefore, we need to sort the points in order of increasing z (so that the farther points are drawn first, and closer ones are drawn later, appearing on top). So sort by z' in ascending order (from smallest to largest z, meaning back to front).

Wait, if z' is the coordinate along the line of sight, then higher z' means closer to the viewer. So to sort back to front, we sort by z' ascending (so the smallest z comes first, then next, etc.). Wait no:

Assume z' is the depth, so a point with z' = 5 is farther (behind) a point with z' = 10. So to sort back to front, we need to sort in increasing order of z'. So first, points with smaller z' (further away) are drawn first, then overlapping closer points (higher z') are drawn later, so they are on top.

Yes.

Thus, the sorting key is z' in ascending order.

Therefore, in the code, after calculating the rotated coordinates (x', y', z'), we collect all the points' data (including x', y', z', and brightness) into an array, then sort that array based on z'.

Alternatively, in code:

- Create an array of objects for each point in the current frame.

But to optimize, perhaps during each frame:

- Create an array of objects that contain all the necessary data (x', y', z', brightness).

- Sort this array based on z'.

Then loop through this sorted array and draw each circle (or point) at (x', y'), scaled and centered on the canvas, with the appropriate brightness.

Wait, but how to represent the points on the canvas.

The screen coordinates need to be transformed from the rotated x' and y' to the canvas pixel coordinates.

Suppose the canvas is centered at (canvas.width/2, canvas.height/2), and scaled by a factor, say scale = 100.

So:

screenX = centerX + x' * scale;

screenY = centerY + y' * scale;

Wait, but perhaps using the x' and y' coordinates.

Wait, but in 3D, if the camera is looking along the z-axis, then the x and y coordinates are the ones that determine screen position, and z is depth. So yes, that's correct.

Thus, to project, screenX = rotatedX * scale + centerX;

screenY = rotatedY * scale + centerY;

Thus, the code for each point in the sorted array:

- compute the screen position based on x' and y'.

- set fillStyle based on brightness.

- draw a circle at (screenX, screenY) with some radius (like 2px).

But since points on a sphere, they will have varying sizes depending on perspective? Hmm, in an orthographic projection, all points are same size. To have perspective, we might need to scale with distance, but that complicates things. For simplicity, perhaps just use a fixed radius.

Thus, the code steps:

1. Clear the canvas.

2. For each point in the original points array:

   a. Compute rotated coordinates (x', y', z') based on current angles angX and angY.

   b. Compute brightness as max(y', 0).

   c. Compute screenX and screenY.

   d. Store all these in a temporary array with their z'.

3. Sort the temporary array by z' in ascending order (back to front).

4. Iterate over the temporary array and draw each point as a circle.

Now, the code.

First, setup:

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

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

var points = generatePoints(200); // generate 200 points.

function generatePoints(n) {

    var points = [];

    var invPhi = 1 / ((1 + Math.sqrt(5))/2); // inverse of golden ratio

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

        var theta = 2 * Math.PI * i * invPhi;

        var y = 1 - (i/(n-1))*2;

        var r = Math.sqrt(1 - y*y);

        var x = Math.cos(theta)*r;

        var z = Math.sin(theta)*r;

        points.push( {x:x, y:y, z:z} );

    }

    return points;

}

var angX = 0, angY = 0;

function draw() {

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

    var tempPoints = [];

    // compute rotated points and brightness, etc.

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

        var p = points[i];

        // rotate around X by angX.

        var x1 = p.x;

        var y1 = p.y * Math.cos(angX) - p.z * Math.sin(angX);

        var z1 = p.y * Math.sin(angX) + p.z * Math.cos(angX);

        // rotate around Y by angY.

        var x2 = x1 * Math.cos(angY) + z1 * Math.sin(angY);

        var y2 = y1;

        var z2 = -x1 * Math.sin(angY) + z1 * Math.cos(angY);

        // now x2,y2,z2 are rotated coordinates.

        // compute brightness.

        var brightness = Math.max(y2, 0);

        // compute screen coordinates.

        var scale = 100;

        var centerX = canvas.width/2;

        var centerY = canvas.height/2;

        var sx = x2 * scale + centerX;

        var sy = y2 * scale + centerY;

        // store in temp array.

        tempPoints.push({

            x: sx,

            y: sy,

            z: z2,

            brightness: brightness

        });

    }

    // sort tempPoints by z in ascending order (back to front).

    tempPoints.sort(function(a,b) {

        return a.z - b.z;

    });

    // draw all points.

    ctx.fillStyle = 'white';

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

        var tp = tempPoints[i];

        ctx.beginPath();

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

        ctx.fillStyle = 'hsl(200, 100%, ' + (tp.brightness * 100) + '%)';

        ctx.fill();

    }

    // update angles.

    angX += 0.01;

    angY += 0.02;

    // request next frame.

    requestAnimationFrame(draw);

}

draw();

This should work.

Wait, but the color is using hsl with lightness based on brightness (which is y2). Since brightness is y', which is the rotated y-coordinate. Wait, but in the code, brightness = y2, but we take max(y2,0). So points with y2 negative have zero brightness, and thus are not visible? Hmm, but in the projection, if the light is along the positive y direction, then the brightness depends on the y-coordinate after rotation. So points facing away from the light (y2 negative) are dark.

Alternatively, perhaps the light is a directional light along the positive y-axis, so the brightness is the dot product between the normal (point's direction) and the light direction (0,1,0). Which is exactly y2. So, if we have a white light, then points with y2 negative (facing away from light) are in shadow.

Thus, the code is correct in setting the brightness as max(y2, 0), so that they get a lightness proportional to y2 (but clamped at 0). So dark when y2 <0.

This creates an effect where the sphere is half-lit, and as it rotates, different parts become illuminated.

The color is set to hsl(200, 100%, X%), where X is the brightness (between 0 and 1) times 100.

Thus, the color is a blueish color, with lightness from 0% (black) to 100% (white). So the lit parts are white, and darker otherwise.

The points are drawn as circles with radius 2px.

The angles angX and angY increment gradually, causing the sphere to spin.

The canvas needs to have an id 'myCanvas', and the context is 2d.

To test this code, the HTML should have:

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

But in the code, the centerX and centerY are computed based on the canvas's current size. If the canvas is set via CSS, the actual size might differ, but in this code, using the width and height attributes directly.

This should create a rotating sphere with points lit from the positive y direction, spinning around both axes.

Now, the user asked to "Create a rotating 3D sphere made of points". This code should do that.

But in the code above, I used 200 points. Perhaps adjust the number for better or worse performance.

Also, the initial angle increments (0.01 and 0.02) can be adjusted for faster/slower rotation.

Potential issues:

- The sorting based on z might not be perfect because it's an orthographic projection. In reality, the depth is z2, so sorting on z2 should place farther points behind closer ones.

- The projection assumes that the view is along the z-axis. So the projection uses the x2 and y2 coordinates. But in the code above, after rotating, the coordinates are x2, y2, z2. The x2 and y2 are the screen coordinates (scaled), and z2 is the depth.

- The math for rotation might need to be double-checked for correctness.

Another thing: the initial rotation axes. Are we rotating around X and Y axes correctly? Let me check:

First rotation around X by angX:

The rotation around X axis by angle angX transforms a point (x,y,z) to:

x remains x,

y becomes y*cos(angX) - z*sin(angX),

z becomes y*sin(angX) + z*cos(angX).

Then rotation around Y-axis by angY:

The rotation around Y-axis by angY transforms the previous (x', y', z') to:

x'' = x'*cos(angY) + z'*sin(angY),

y'' = y',

z'' = -x'*sin(angY) + z'*cos(angY).

Yes, that's correct.

Therefore the code is correct in the rotation steps.

Another thing: the 'brightness' is computed as Math.max(y2, 0), where y2 is the rotated y-coordinate (after both rotations). So the light is along the global y-axis? Or along the rotated y-axis?

Wait, the light direction is (0,1,0) in the global coordinate system, before any rotations. Or is it in the rotated coordinate system?

Wait, no. The light direction is fixed in the world coordinates. So the normal vector is the point's direction in the rotated world. Wait, no: the points are rotated, so their normals are along the rotated direction.

So the light direction is (0,1,0) in the original world, but after rotation, the normals have been rotated, so the light direction in the rotated frame is different. Wait, perhaps the light is fixed in the world frame, so we need to compute the normal in the world frame after rotation.

Wait, actually, the light direction is fixed, so to compute the brightness, we need the dot product between the point's normal (which is the direction from the center to the point, i.e., the original point's x,y,z normalized) and the light direction (0,1,0).

Wait a minute, perhaps this is an error in the code.

Wait: the normal of a spherical point is the vector from the center to the point, so in the original coordinates, it's (x,y,z), normalized. Because it's a unit sphere, the normal is exactly the position vector.

However, after applying the rotation (rotating the sphere), the normal vector is rotated with the point. So the normal vector in the new coordinates is (x', y', z'), which is the rotated position. Hence, the light direction in the rotated coordinates is still (0,1,0) ?

Wait no, the light direction is a vector in the original world, so after rotating the sphere, the light direction is fixed. Therefore the normal vector (the direction to the point) in the original world has been rotated, so the correct normal in the rotated system?

Wait, this is getting confusing. Let me think again.

Suppose we have a light at direction (0,1,0) in the unrotated coordinate system. The sphere is rotated by angX and angY, so the points on the sphere have their normals rotated as well. The light direction is fixed, so to compute the brightness, we need to compute the dot product between the rotated normal (which is the rotated point's coordinates) and the light direction (0,1,0) in the original coordinate system?

Or is the light also rotated with the sphere?

Probably the light is fixed, so we have to compute the dot product between the rotated normal and the original light direction.

Wait, actually, the light direction is a global direction. So the normals are the rotated normals of the sphere points. Therefore to compute illumination, we need the dot product between the rotated normal (x2, y2, z2) and the light direction (0,1,0).

Wait, but in the code above, brightness is based on y2, which is exactly that. Because the light direction (original (0,1,0)) dotted with the rotated normal (x2,y2,z2) would be y2. However, if the rotation of the sphere has changed the orientation, then this is the correct way.

Wait, but in the code, the normal is (x2,y2,z2) because after rotation, the point's coordinates are (x2,y2,z2). Therefore, their normals (direction from center) are exactly (x2,y2,z2), since it's a unit sphere.

Therefore, the dot product with the light direction (0,1,0) is indeed y2, so the code is correct.

Thus, the code is correct in computing brightness as y2, but clamped at 0.

Therefore, this code should work as intended.

Another point: the points are drawn as circles with fixed radius. However, in 3D, points closer to the viewer (along the z-axis?) might be larger? But in this orthographic projection, they are not. If we want perspective, we could scale the radius by 1/(z2 + some offset), but that might complicate things and perhaps is beyond the scope. The user just asked for a rotating sphere made of points, so this should suffice.

Therefore, the code should be correct.
command-r:35bOK
Show code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Rotating Sphere</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="sphereCanvas"></canvas>

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

        let width, height;
        let centerX, centerY;
        const radius = 100;
        let theta = 0;
        const numPoints = 500; // Number of points on the sphere surface
        const pointRadius = 4; 
        const lightDir = {x: 0.3, y: -0.6};
        
        function randomInRange(min, max) {
            return min + Math.random() * (max - min);
        }

        class Point {
            constructor(x, y) {
                this.x = x;
                this.y = y;
                this.angleToLight = 0; // Angle between normal and light direction
                this.angle = randomInRange(-Math.PI / 2, Math.PI / 2); // Starting angle for rotation
            }

            rotate() {
                this.angle += 0.1; // Incremental rotation
            }
        }

        function setupPoints() {
            const points = [];

            for (let i = 0; i < numPoints; i++) {
                let theta = randomInRange(0, 2 * Math.PI);
                let phi = Math.acos(randomInRange(-1, 1));

                // Convert spherical coordinates to Cartesian
                const x = radius * Math.sin(phi) * Math.cos(theta);
                const y = radius * Math.sin(phi) * Math.sin(theta);
                const z = radius * Math.cos(phi);

                points.push(new Point(x, y));
            }

            return points;
        }

        function projectPoint(point) {
            point.screenX = centerX + (radius * point.x / width);
            point.screenY = centerY - (radius * point.y / height);
        }

        function sortPointsByDepth() {
            const pointsToSort = points.slice();
            
            return pointsToSort.sort((a, b) => b.z - a.z); // Sort by depth from back to front
        }

        function calculateAngleToLight(point) {
            point.angleToLight = Math.acos(
                lightDir.x * point.x + lightDir.y * point.y
            );
            
            if (isNaN(point.angleToLight)) {
              point.angleToLight = 0; 
            }
        }

        function drawPoint(point) {
            const fillColor = `hsl(${point.angle}, 100%, ${50 - point.angleToLight * 25}%)`;
            
            ctx.beginPath();
            ctx.arc(point.screenX, point.screenY, pointRadius, 0, Math.PI * 2);
            ctx.fillStyle = fillColor;
            ctx.fill();
        }

        function drawSphere() {
            theta += 0.1; // Rotate the sphere

            points.forEach((point) => {
                point.rotate();

                calculateAngleToLight(point);
                
                projectPoint(point);
            });

            sortPointsByDepth().forEach(drawPoint);
            
            requestAnimationFrame(drawSphere);
        }

        let points = setupPoints();
        
        window.onload = function() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
            centerX = width / 2;
            centerY = height / 2;

            ctx.translate(centerX, centerY); // Move origin to the middle of the screen
            
            drawSphere();
        };
    </script>
</body>
</html>
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
Claude Opus 5frontier referenceOK
Show code
<!doctype html>
<html><head><meta charset="utf-8"><title>Shaded sphere, painter's algorithm</title>
<style>
html,body{margin:0;height:100%;background:#04060c;overflow:hidden}
canvas{display:block;width:100vw;height:100vh}
#hud{position:fixed;left:12px;top:11px;font:13px ui-monospace,monospace;color:#cfe3ff;
background:rgba(4,6,12,.6);padding:5px 9px;border-radius:5px}
</style></head><body>
<canvas id="c"></canvas><div id="hud">points</div>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d'),hud=document.getElementById('hud');
let W,H,cx,cy,R;
function fit(){W=cv.width=innerWidth;H=cv.height=innerHeight;cx=W/2;cy=H/2;R=Math.min(W,H)*.33}
fit();addEventListener('resize',fit);

// Fibonacci-Kugel: gleichmaessige Verteilung ohne Pol-Haeufung.
const N=760,pts=[];
const GA=Math.PI*(3-Math.sqrt(5));
for(let i=0;i<N;i++){
  const y=1-(i/(N-1))*2, r=Math.sqrt(Math.max(0,1-y*y)), th=GA*i;
  pts.push([Math.cos(th)*r, y, Math.sin(th)*r]);
}

const L=(()=>{const v=[-.45,.62,.65],m=Math.hypot(...v);return v.map(k=>k/m)})();
const F=900,CAMZ=3.1;

let t0=0;
function frame(tm){
  if(!t0)t0=tm;
  const t=(tm-t0)/1000, ax=t*.42, ay=t*.63;
  const cA=Math.cos(ax),sA=Math.sin(ax),cB=Math.cos(ay),sB=Math.sin(ay);

  g.fillStyle='#04060c';g.fillRect(0,0,W,H);

  const out=[];
  for(const p of pts){
    let [x,y,z]=p;
    let y1=y*cA-z*sA, z1=y*sA+z*cA;
    let x2=x*cB+z1*sB, z2=-x*sB+z1*cB;
    out.push([x2,y1,z2]);
  }
  out.sort((a,b)=>a[2]-b[2]);   // hinten zuerst

  for(const [x,y,z] of out){
    const k=F/(CAMZ-z);
    const sx=cx+x*R*k/F*3.1, sy=cy-y*R*k/F*3.1;
    const lam=Math.max(0,x*L[0]+y*L[1]+z*L[2]);
    const lit=.06+lam*lam*.55+lam*.42;
    const rad=Math.max(.9,(1.9+z*.9)*(R/230));
    g.fillStyle='rgb('+(70+lit*185|0)+','+(120+lit*130|0)+','+(190+lit*65|0)+')';
    g.beginPath();g.arc(sx,sy,rad,0,6.2832);g.fill();
  }

  hud.textContent='points '+N+'  ·  painter sorted  ·  '+(t%6.28).toFixed(2)+' rad';
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script></body></html>
GPT-5.5 (Codex)frontier referenceOKMINIFIED
Show code
<!doctype html><html><head><meta charset=utf-8><title>Sphere</title><style>html,body{margin:0;height:100%;overflow:hidden;background:#07090d;color:#dfe7ff;font:16px system-ui}canvas{display:block;width:100vw;height:100vh}#n{position:fixed;left:16px;top:14px;padding:8px 10px;background:#111827cc;border:1px solid #2c3447;border-radius:6px}</style></head><body><canvas id=c></canvas><div id=n></div><script>
const c=document.getElementById("c"),x=c.getContext("2d"),n=document.getElementById("n");let W,H,D,P,T,R,rx,ry,rz,done;
function fit(){W=c.width=innerWidth*devicePixelRatio;H=c.height=innerHeight*devicePixelRatio;c.style.width=innerWidth+"px";c.style.height=innerHeight+"px";x.setTransform(1,0,0,1,0,0);R=Math.min(W,H)*.34;D=R*3}
onresize=fit;fit();
function reset(){P=[];T=0;done=0;let m=620+Math.random()*260|0,g=Math.PI*(3-Math.sqrt(5));n.textContent=m+" points";for(let i=0;i<m;i++){let z=1-2*(i+.5)/m,r=Math.sqrt(1-z*z),a=i*g+Math.random()*.03;P.push([Math.cos(a)*r,Math.sin(a)*r,z,0,0,0,0])}rx=.006+Math.random()*.007;ry=.009+Math.random()*.008;rz=.003+Math.random()*.005}
reset();
const L=(()=>{let a=[-.45,-.55,.7],l=Math.hypot(...a);return a.map(v=>v/l)})();
function frame(){T++;if(T>2200)reset();x.fillStyle="#07090d";x.fillRect(0,0,W,H);let sx=Math.sin(rx*T),cx=Math.cos(rx*T),sy=Math.sin(ry*T),cy=Math.cos(ry*T),sz=Math.sin(rz*T),cz=Math.cos(rz*T),A=[];
for(const p of P){let X=p[0],Y=p[1]*cx-p[2]*sx,Z=p[1]*sx+p[2]*cx,xx=X*cy+Z*sy,zz=-X*sy+Z*cy,yy=Y*cz-xx*sz;X=Y*sz+xx*cz;Y=yy;let q=D/(D-zz*R),u=W/2+X*R*q,v=H/2+Y*R*q,b=Math.max(0,X*L[0]+Y*L[1]+zz*L[2]),s=(1.7+4.2*q)*(devicePixelRatio||1);A.push([zz,u,v,s,b])}
A.sort((a,b)=>a[0]-b[0]);for(const p of A){let b=p[4],r=32+210*b,g=43+170*b,bl=62+155*b;x.beginPath();x.fillStyle=`rgb(${r|0},${g|0},${bl|0})`;x.arc(p[1],p[2],p[3],0,7);x.fill()}requestAnimationFrame(frame)}
requestAnimationFrame(frame);
</script></body></html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html><html><head><meta charset="utf8"><title>Rotating Sphere</title><style>html,body{margin:0;background:#000;overflow:hidden}canvas{display:block}</style></head><body><canvas id="c"></canvas><script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
let W,H;function resize(){W=cv.width=innerWidth;H=cv.height=innerHeight;}resize();addEventListener('resize',resize);
let pts,n,ax,ay,light,rot,turns;
function init(){
  n=300+Math.floor(Math.random()*400);
  pts=[];
  const ga=Math.PI*(3-Math.sqrt(5));
  for(let i=0;i<n;i++){
    let y=1-(i/(n-1))*2;
    let r=Math.sqrt(1-y*y);
    let th=ga*i;
    pts.push([Math.cos(th)*r,y,Math.sin(th)*r]);
  }
  ax=(Math.random()*0.6+0.3)*(Math.random()<0.5?-1:1);
  ay=(Math.random()*0.6+0.3)*(Math.random()<0.5?-1:1);
  let lv=[Math.random()-0.5,Math.random()-0.5,Math.random()-0.5];
  let ll=Math.hypot(lv[0],lv[1],lv[2])||1;
  light=[lv[0]/ll,lv[1]/ll,lv[2]/ll];
  rot=[0,0];
  turns=0;
}
init();
let last=performance.now();
function frame(now){
  let dt=Math.min((now-last)/1000,0.05);last=now;
  rot[0]+=ax*dt;rot[1]+=ay*dt;
  if(rot[1]>Math.PI*2){rot[1]-=Math.PI*2;turns++;}
  if(rot[1]<-Math.PI*2){rot[1]+=Math.PI*2;turns++;}
  ctx.fillStyle='#05060a';ctx.fillRect(0,0,W,H);
  let cxp=W/2,cyp=H/2;
  let R=Math.min(W,H)*0.36;
  let sa=Math.sin(rot[0]),ca=Math.cos(rot[0]);
  let sb=Math.sin(rot[1]),cb=Math.cos(rot[1]);
  let proj=[];
  for(let i=0;i<n;i++){
    let p=pts[i];
    let x=p[0],y=p[1]*ca-p[2]*sa,z=p[1]*sa+p[2]*ca;
    let x2=x*cb+z*sb,z2=-x*sb+z*cb,y2=y;
    let nx=x2,ny=y2,nz=z2;
    let dot=nx*light[0]+ny*light[1]+nz*light[2];
    let br=Math.max(0.08,dot*0.85+0.25);
    let sx=cxp+nx*R,sy=cyp-ny*R;
    let rad=(2.6+z2*1.1);
    proj.push([sx,sy,z2,br,rad]);
  }
  proj.sort((a,b)=>a[2]-b[2]);
  for(const p of proj){
    let g=Math.max(0,Math.min(255,p[3]*255));
    ctx.beginPath();
    ctx.fillStyle='rgb('+(g*0.55|0)+','+(g*0.75|0)+','+(g|0)+')';
    ctx.arc(p[0],p[1],Math.max(0.5,p[4]),0,6.284);
    ctx.fill();
  }
  ctx.fillStyle='#8fd';ctx.font='16px monospace';
  ctx.fillText('points: '+n,10,24);
  if(turns>=2){init();}
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script></body></html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><style>body{margin:0;background:#000;display:flex;justify-content:center;align-items:center;height:100vh}canvas{border:1px solid #666}#i{position:absolute;top:10px;left:10px;color:#fff;font-family:monospace;font-size:12px}</style></head>
<body>
<canvas id="c"></canvas>
<div id="i">Points: <span id="n">500</span></div>
<script>
const c=document.getElementById('c'),ctx=c.getContext('2d');
c.width=600;c.height=600;
let pts=[],a=[0,0,0];
function gen(n){pts=[];for(let i=0;i<n;i++){const p=Math.acos(1-2*i/n),t=Math.PI*(3-Math.sqrt(5))*i;pts.push({x:Math.sin(p)*Math.cos(t),y:Math.cos(p),z:Math.sin(p)*Math.sin(t)});}document.getElementById('n').textContent=n;}
function rot(p,x,y,z,a){const c=Math.cos(a),s=Math.sin(a);let r=p;if(x)r={x:r.x,y:r.y*c-r.z*s,z:r.y*s+r.z*c};else if(y)r={x:r.x*c+r.z*s,y:r.y,z:-r.x*s+r.z*c};else r={x:r.x*c-r.y*s,y:r.x*s+r.y*c,z:r.z};return r;}
function f(){a[0]+=0.005;a[1]+=0.007;let p=pts.map(q=>{let r=rot(q,1,0,0,a[0]);r=rot(r,0,1,0,a[1]);const x=c.width/2+r.x*120,y=c.height/2+r.y*120;return{x,y,z:r.z,nx:r.x,ny:r.y,nz:r.z};}).sort((m,n)=>m.z-n.z);ctx.fillStyle='#000';ctx.fillRect(0,0,c.width,c.height);const lx=1/Math.sqrt(3),ly=1/Math.sqrt(3),lz=1/Math.sqrt(3);p.forEach(q=>{const d=q.nx*lx+q.ny*ly+q.nz*lz,b=Math.max(0,(d+1)*128)|0;ctx.fillStyle='rgb('+b+','+b+','+b+')';ctx.beginPath();ctx.arc(q.x,q.y,2.5,0,6.28);ctx.fill();});requestAnimationFrame(f);}
gen(500);f();
</script>
</body>
</html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3:8b ——harness
qwen3:14b —41.0 sOK
gemma4:26b ——harness
mistral-small:24b —165.5 sOK
llama3.1:8b —126.1 sOK
qwen3-coder:30b —19.2 sOK
deepseek-r1:14b —57.3 sOK
qwen3.5:9b —46.3 sOK
deepseek-r1:32b —293.9 sOK
qwq:32b —233.4 sOK
command-r:35b —217.7 sOK