OverviewPhysics Lab › R26
Physics Lab

R26 · Projectile ballistics

Archive round — this round predates the blind-first layout, so answers appear with model names attached. Newer rounds keep the models anonymous until you reveal them.
scored 11/13 models answered
Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 120 lines total) that shows projectile motion on a <canvas>: exactly 5 cannonballs fired simultaneously from the same point at 5 different launch angles (between 20° and 75°) with the same initial speed, constant gravity pulling them down, each ball drawing a fading trail of its trajectory, and distance markers along the ground line showing where each ball lands. When all 5 have landed, restart automatically. Smooth requestAnimationFrame animation. No external libraries, no comments needed. Output ONLY the HTML file content, nothing else.

Answers

Local (Brain cluster)
gemma4:26b DNF / pending DNF (infra)
DNF — infrastructure: 9/10 rounds ended in provider errors; its only completed round, R28, scored 8.5
qwen3-coder:30b16.0 sERRBLANK★ 1/10
Show code
<!DOCTYPE html>
<html><head><title></title></head><body><canvas id="c"></canvas><script>const canvas=document.getElementById('c');const ctx=canvas.getContext('2d');canvas.width=800;canvas.height=400;let v0=150;let g=9.8;let startX=50;let angles=[Math.PI * (20+i*23/5)/180 for i in 5];let balls=[];const n=5;for(let i=0;i<n;i++){balls.push({x:startX,y:400-20,vx:v0*Math.cos(angles[i]),vy:v0*Math.sin(angles[i]),history:[],isGrounded:false})}function update(){if(!balls.every(b=>b.isGrounded)){requestAnimationFrame(update)}else{balls.forEach((b,i)=>{b.x=startX;b.y=400-20;b.vx=v0*Math.cos(angles[i]);b.vy=v0*Math.sin(angles[i]);b.history=[]});}ctx.clearRect(0,0,800,400);balls.forEach((b,i)=>{let t=0;if (!b.isGrounded){t+=1/60;const x_new=startX+b.vx*t;const y_new=400-20-(Math.sin(angles[i])*b.vy*t - 0.5*g*t*t);b.x=x_new;b.y=y_new;b.history.push({x:b.x,y:b.y});}else{/*Draw landing marker*/ctx.strokeStyle="red";ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(b.x-50,400-20);ctx.lineTo(b.x+20,400-20);ctx.stroke();}ctx.fillStyle="#ff6347";ctx.beginPath();ctx.arc(b.x,Math.max(10,b.y),5,0,Math.PI*2);ctx.fill();/*Draw trail*/for(let j=0;j<b.history.length;j++){ctx.globalAlpha=(j/(b.history.length)+0.2)*0.8;ctx.fillStyle="#FFAA66";ctx.beginPath();const p=b.history[j];ctx.arc(p.x,p.y,1.5,0,Math.PI*2);ctx.fill();}if (b.isGrounded && Math.abs(b.y-400+20)<2){b.isGrounded=true;b.history=[]}else if (!b.isGrounded){/*Update velocity for next frame projection*/b.vy-=g/60;}});requestAnimationFrame(update)}update();</script></body></html>
qwen3.5:9b32.5 sERRSTATLIM★ 1.5/10
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>Projectile Simulation</title>
<style>
    body { background: #111; display: flex; justify-content: center; align-items: center; margin: 0; height: 100vh; }
    canvas { border: 2px solid #333; background: linear-gradient(to bottom, #6aa84f 0%, #5c9e3b 100%); cursor: crosshair; }
</style>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Setup Dimensions (Scale to match typical cannon launch)
canvas.width = 800;
canvas.height = 400;

let GRAVITY = 9.81 * 0.15; // Scaled gravity factor for visibility
let V0_SCALE = 1.2;       // Initial velocity scale
const GROUND_LEVEL = canvas.height - 50;

const anglesDeg = [20, 35, 45, 60, 70];

let projectiles = [];
let animationFrameId = null;

function initializeProjectiles() {
    projectiles = [];
    for (let i = 0; i < 5; i++) {
        const angleRad = anglesDeg[i] * Math.PI / 180;
        projectiles.push({
            angle: anglesDeg[i],
            x: 20, // Starting X
            y: GROUND_LEVEL + 1, // Start just above ground line
            vx: Math.cos(angleRad) * V0_SCALE,
            vy: -Math.sin(angleRad) * V0_SCALE, // Negative Y because canvas origin is top-left
            t: 0,
            isCrashed: false,
            trail: []
        });
    }
}

function update() {
    let allLanded = true;
    projectiles.forEach(p => {
        if (p.isCrashed) return;

        const dt = 0.1; // Fixed time step for simulation stability

        // Update kinematics
        p.t += dt;
        p.x += p.vx * dt;
        p.vy -= GRAVITY * dt; // v_y changes over time (gravity)
        p.y += p.vy * dt;

        // Store trajectory point for trail drawing
        p.trail.unshift({ x: p.x, y: p.y });

        // Limit trail length to prevent memory overflow
        while (p.trail.length > 150) {
            p.trail.pop();
        }

        // Check collision/landing (Ground level only)
        if (p.y >= GROUND_LEVEL - 2 || p.x > canvas.width + 5) {
             p.isCrashed = true;
            p.y = Math.max(p.y, GROUND_LEVEL); // Snap to ground
            p.x = Math.min(p.x, canvas.width * 1.2);
        }

        if (!p.isCrashed) {
            allLanded = false;
        }
    });

    return allLanded;
}

function drawTrails() {
    ctx.fillStyle = 'rgba(0, 0, 0, 0)'; // Clear path before drawing new frames
    ctx.fillRect(0, 0, canvas.width, canvas.height);
}

function drawProjectilesAndMarkers(allLanded) {
    // Draw Ground Line and Markers first
    ctx.strokeStyle = '#95a526';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(0, GROUND_LEVEL);
    ctx.lineTo(canvas.width * 1.2, GROUND_LEVEL);
    ctx.stroke();

    projectiles.forEach((p, i) => {
        // Draw Landing Marker (if crashed)
        if (p.isCrashed) {
            const markerX = p.x;
            ctx.fillStyle = `hsl(${i * 45}, 70%, 60%)`;
            ctx.beginPath();
            ctx.arc(markerX, GROUND_LEVEL - 1, 8, 0, Math.PI * 2);
            ctx.fill();

            // Label Angle
            ctx.fillStyle = '#333';
            ctx.font = '14px Arial';
            ctx.fillText(`${p.angle}°`, markerX + 15, GROUND_LEVEL - 10);
        } else {
             // Draw Current Position (Cannonball)
            ctx.fillStyle = `hsl(${i * 45}, 70%, 60%)`;
            ctx.beginPath();
            ctx.arc(p.x, p.y, 8, 0, Math.PI * 2);
            ctx.fill();

            // Draw Trail (Fading effect)
            if (p.trail.length > 1) {
                for (let j = 0; j < Math.min(p.trail.length - 1, 50); j++) {
                    const point = p.trail[j];
                    // Fade transparency based on age in the trail array (j=0 is newest, end of array is oldest)
                    let alpha = (j / Math.min(p.trail.length, 50)) * 0.8;
                    alpha = Math.pow(1 - (j / p.trail.length), 2); // Quadratic fade

                    ctx.fillStyle = `rgba(70, 130, 180, ${Math.max(0.05, alpha)})`;
                    ctx.beginPath();
                    ctx.arc(point.x, point.y, 2 + (j / p.trail.length) * 4, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
        }
    });

    if (allLanded && animationFrameId !== null) {
        cancelAnimationFrame(animationFrameId);
        document.querySelector('h1') ? document.querySelector('h1').innerHTML = 'Landed!'; : console.log("All landed!");
        setTimeout(() => {
            initializeProjectiles();
            gameLoop();
        }, 2000); // Wait 2 seconds before restart
    }
}

function gameLoop() {
    ctx.clearRect(0, 0, canvas.width * 1.2, canvas.height);
    drawTrails();
    const allLanded = update();
    drawProjectilesAndMarkers(allLanded);

    if (!allLanded) {
        animationFrameId = requestAnimationFrame(gameLoop);
    }
}

// Start the simulation
initializeProjectiles();
document.querySelector('h1') || document.body; // Simple check if placeholder needed
setTimeout(() => gameLoop(), 50); // Delay start slightly
</script>
<div style="text-align: center; padding-top: 20px;"><h1>Cannon Launch Simulation</h1></div>

</body>
</html>
```
qwen3:14b22.0 sERRBLANK★ 1/10
Show code
<!DOCTYPE html>
<html><head><title>Projectile Motion</title></head><body><canvas id="c"></canvas><script>const c=document.getElementById("c");let ctx=c.getContext("2d");ctx.lineWidth=4;c.width=800;c.height=400;const G=9.8;const V0=15;let balls=[];let time_elapsed=0;const A=[20, 35, 50, 65, 75];function init(){balls=[];time_elapsed=0;$A.forEach((angle,i)=>{balls.push({angle:angle*Math.PI/180,vx:V0*Math.cos(angle*Math.PI/180),vy:V0*Math.sin(angle*Math.PI/180),x:20,y:c.height-50,isActive:true,trail:[{x:20,y:c.height-50}],color:#FF0000,(i+1)*30+(i===4?0:1));});}init();let lastTime=0;function update(time){if(!balls[0]?.isActive && time_elapsed>0){setTimeout(()=>{(balls=[],lastTime=0,update()),500)};return;}const delta=time-lastTime;lastTime=time;for(let i=0;i<balls.length;i++){const b=balls[i];if(!b.isActive)continue;t+=delta/1000;b.x+=b.vx*delta/1000;b.y+=b.vy*delta/1000-(0.5*G*(delta/1000)**2);b.vy-=G*(delta/1000);if(b.y>c.height-1){b.y=c.height-1;b.isActive=false}else{b.trail.push({x:b.x,y:b.y});if(b.trail.length>150)b.trail.shift();}else{b.trail.unshift({x:20+i*30,y:c.height-50});}ctx.fillStyle=getComputedStyle(document.documentElement).getPropertyValue("color").replace('rgb', 'rgba').split(',')[0]';for(let i=0;i<balls.length;i++){const b=balls[i];if(!b||!b.isActive)continue;ctx.beginPath();ctx.strokeStyle=b.color;ctx.lineWidth=3+(1-b.trail.length/200)*3;for(let j=0;j<Math.min(b.trail.length, 50);j++){const p=b.trail[j];ctx.globalAlpha=(j/50);ctx.lineTo(p.x,p.y);ctx.stroke();};ctx.beginPath();ctx.arc(b.x,b.y,4,(getComputedStyle(document.documentElement).getPropertyValue("color").replace('rgb', 'rgba').split(',')[0]'));ctx.fillStyle=getComputedStyle(document.documentElement).getPropertyValue("color");ctx.fill();if(!b.isActive){ctx.fillStyle=b.color;ctx.fillRect(Math.hypot(b.x-20,b.y-(c.height-1)), 0, 50, 10);}}}(time);requestAnimationFrame(update)}</script></body></html>
qwen3:8b DNF / pending DNF (infra)
DNF — infrastructure: 10/10 requests returned status 500
llama3.114.5 sERRBLANK★ 1/10
Show code
```html
<!DOCTYPE html>
<html>
<head>
<script>
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
let ctx = canvas.getContext('2d');

canvas.width = 400;
canvas.height = 300;

ctx.fillStyle = 'black';
ctx.fillRect(0,0,canvas.width,canvas.height);

function ball(x,y,angle,vx,vy,distance){
    this.x = x;
    this.y = y;
    this.angle = angle;
    this.vx = vx;
    this.vy = vy;
    this.distanceCovered = distance;
}

ball.prototype.draw = function(){
    ctx.beginPath();
    ctx.arc(this.x,this.y,3,0,2*Math.PI);
    ctx.fillStyle='white';
    ctx.fill();

    ctx.beginPath();
    ctx.moveTo(this.x,this.y);
    this.x += this.vx;
    this.y += this.vy;
    const previousX = this.x - this.vx * 10 / 100; 
    const previousY = this.y - this.vy * 10 / 100; 

    if(previousX>0 && previousX<canvas.width){
        ctx.lineTo(previousX,previousY);
        this.distanceCovered -= Math.sqrt(Math.pow(previousX-this.x,2) + Math.pow(previousY-this.y,2));
        ctx.strokeStyle='lightgrey';
        ctx.lineWidth=1;
        ctx.stroke();
    }
}

function animate(){
    ctx.fillStyle='#000000'; 
	ctx.fillRect(0,canvas.height-10,canvas.width,canvas.height)

	let balls = [];

	const speed = 3.5; // initial speed of the projectile in pixels per second
	const g = 2.5;	 // constant acceleration due to gravity by second

	for(let j=0;j<5;j++){
        const distanceCovered  = j * canvas.width / 4;
		balls.push(new ball(50,150 + (canvas.height - 10)/2, Math.PI*(35+(j*15)), 
                        speed*Math.cos(((Math.PI)*3/180)*(20+j*15)) ,  
                         -(speed*g+0.1*Math.sqrt((distanceCovered))*Math.sin(((Math.PI)*3/180)*(20+j*15))), distanceCovered));
   }

    for(let j=0;j<balls.length;j++){
        balls[j].draw();
		balls[j].x += balls[j].vx;
		balls[j].y += balls[j].vy;

        if((balls[j].y>canvas.height) || (Math.sqrt(Math.pow(balls[j].x,2) + Math.pow(balls[j].y - 150,2)) > canvas.width/2)){
            delete balls[j];
        }else{
			ctx.beginPath();
			ctx.font="10pt Arial";
			const xx = balls[j].y<canvas.height?balls[j].x-5:balls[j].distanceCovered*Math.sin(Math.atan((balls[j].y-canvas.height)/(canvas.width/2)));
			const yy = canvas.height - 5;
            ctx.fillText(25*j + '°',xx,yy)
        }
    }

    if(balls.some(ball => ball.y > -10 || ball.distanceCovered < canvas.width/2)){
        globalThis.requestAnimationFrame(animate); // animate()
    }else{
		ctx.fillStyle='black';
	    ctx.fillRect(0, 0, canvas.width,canvas.height),

		ctx.font = "36pt Arial";
	    ctx.textAlign = 'center',
		ctx.textBaseline = 'middle'

        ctx.fillText(`CANNONBALLS LANDED... AUTO RELAUNCH`,canvas.width/2,-50)

        for (let x=25; x<=225;x+=25)
            textShadow = 0, this.ctx.fillStyle='lightgrey',this.ctx.fillText(x+'°',x+10,250)

		ctx.font = "48pt Arial";
	    ctx.textAlign = 'center',
		ctx.textBaseline = 'middle'
        ctx.fillText('CLICK TO RELAUNCH...',canvas.width/2,-50),
        }

}

globalThis.requestAnimationFrame(animate); 

</script>
</head>
</html>
```
deepseek-r122.4 sERRBLANK★ 1/10
Show code
```html
<!DOCTYPE html>
<html><head><title>Projectile Motion</title><style>body{margin:0}. canvas{border:1px solid black;}</style></head>
<body><canvas id="c" width=800 height=400></canvas>
<script>
const c = document.getElementById('c'), ctx=c.getContext('2d');
const W=800,H=400,g=0.15,v0=30,angles=[20,30,40,60,75];
let cannonballs=[]; // Each is {x,y,alpha,trailPoints:[]}
for(let a=0;a<angles.length;a++){
    const angle=angles[a]*Math.PI/180;
    cannonballs.push({
        x:W/2,
        y:H-30,
        a:angle,
        trail:[[W/2,H-30]]
    });
}

let allLanded = false, frameCount=0;

function updateCannonballs(){
    if(allLanded){
        cannonballs.forEach(c=>{
            c.x=c.x0; c.y=c.y0;
            c.trail=[c.x0,c.y0];
        });
        allLanded=false;
        return true;
    }
}

function animate() {
    frameCount++;
    ctx.clearRect(0,0,W,H);
    
    for(let i=0;i<cannonballs.length;i++){
        let cb = cannonballs[i];
        if(frameCount>5)
            cb.trail.push([cb.x,cb.y]);
        
        const t=(frameCount)/30;
        const vx=v0*Math.cos(cb.a);
        const vy=v0*Math.sin(cb.a)-g*t;
        cb.x=x0 + vx*t;
        c.y=y0 + (vy*t - 0.5*g*t*t);
        
        if(c y> H-30){
            allLanded = cannonballs.every(c=>c.y>=H-30);
            cb.x= x0; cb.y= y0;
            cb.trail=[x0,y0];
        }
    }

    drawCannonballs();
}

function drawCannonballs() {
    ctx.beginPath();
    for(let i=0;i<cannonballs.length;i++){
        let cb = cannonballs[i];
        trailPoints.forEach(p => drawLine(ctx, p[0],p[1]));
        ctx.beginPath();
        ctx.arc(cb.x,cb.y,5,0,Math.PI*2);
        ctx.fillStyle='blue'; ctx.fill();
        ctx.strokeStyle=$('#666', 2); ctx.stroke();
    }
}

function drawLine(ctx,x,y) {
    if(trailPoints.length>1){
        trailPoints.forEach((p,j)=>j?Ctx.beginPath();draw points j-1 and j}
    });
}

animate();

</script>
</body></html>
```
mistral-small71.7 sERRBLANK★ 1/10
Show code
```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Projectile Motion</title>
    <style>
        canvas { background: #f0f0f0; }
    </style>
</head>
<body>
<canvas id="cannonCanvas" width="600" height="400"></canvas>

<script>
const ctx = document.getElementById('cannonCanvas').getContext('2d');
const g = 9.8;
const u = 50; // initial speed
let balls = [];

function start(){
    balls = [];
    const angles = [20,35,45,60,75];
    for (let angle of angles) {
        let radians = angle * Math.PI / 180;
        balls.push({
            x: u * Math.cos(radians),
            y: -u * Math.sin(radians) + g,
            theta: radians
        });
    }
}

function animate(){
    ctx.clearRect(0,0,600,400);
    for (let ball of balls){
        if (ball.y > 350 || ball.x < -10){
            continue; // Ball out of view.
        }
        ctx.beginPath();
        ctx.arc(150 + ball.x, 387-ball.y, 2, 0, Math.PI * 6);
        if (ball.opacity>0){
            let alpha = ball.alpha/10;
            let gradient = ctx.createRadialGradient(
                150 + ball.x,
                400 - ball.y,
                8,
                150 +ball.x, 387-ball.y,
                2
            );
            graduate.addColorStop(1, 'rgba(0,0,255,'+alpha+')');
        ctx.fillStyle=gradient;
}else{
ctx.fillStyle = "#FF0000";
        }
        ball.opacity --;
    }ctx.fill();
    ball.y += 14 *ball.alpha / 10;
}

function update(){
for (let ball of balls){
if(ball.x>300||ball.y>-350 ) continue; else{
ctx.font='bold 12pt Calibri';
ctx.fillText(Math.round((Math.sin(bx*Math.atan(.9487)+(Math.pow(-2*(b.ang/2), .0764)))))
       +(Math.sqrt(ball.alpha/(15))-0.1) )) ;
}
for (let ball of balls){
        ball.x += 0.33*ball.alpha;
    }
}

function end(){
if(balls.every(b => b.y >= 387)) start(); // Ball landed.
requestAnimationFrame(end);
update();
}

start();

end();
</script>
</body>
</html>```
Cloud
GPT-5.5 (codex)23.2 sOK★ 8.5/10
Show code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Projectile Motion</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#101820;font-family:Arial,sans-serif}
canvas{display:block;width:100vw;height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const c=document.getElementById("c"),x=c.getContext("2d");
let W,H,DPR,ground,start,balls,last=0,scale=0.8;
const angles=[20,34,48,62,75],colors=["#ff595e","#ffca3a","#8ac926","#1982c4","#b5179e"];
const v0=360,g=420;
function resize(){
  DPR=devicePixelRatio||1; W=innerWidth; H=innerHeight;
  c.width=W*DPR; c.height=H*DPR; x.setTransform(DPR,0,0,DPR,0,0);
  ground=H-70; start={x:60,y:ground}; reset();
}
function reset(){
  balls=angles.map((a,i)=>{
    let r=a*Math.PI/180;
    return {a,i,x:start.x,y:start.y,vx:v0*Math.cos(r),vy:-v0*Math.sin(r),trail:[],landed:false,landX:null,t:0};
  });
  last=performance.now();
}
function drawGrid(){
  x.strokeStyle="#2d3b45"; x.lineWidth=1;
  x.beginPath(); x.moveTo(0,ground); x.lineTo(W,ground); x.stroke();
  x.fillStyle="#d8e2e8"; x.font="14px Arial";
  x.fillText("0 m",start.x-8,ground+26);
  for(let m=100;m<900;m+=100){
    let px=start.x+m*scale;
    if(px>W-30) break;
    x.strokeStyle="#51616c"; x.beginPath(); x.moveTo(px,ground-6); x.lineTo(px,ground+6); x.stroke();
    x.fillStyle="#9fb1bc"; x.fillText(m+" m",px-18,ground+26);
  }
}
function cannon(){
  x.fillStyle="#c6d2da"; x.beginPath(); x.arc(start.x,start.y,11,0,Math.PI*2); x.fill();
  x.fillStyle="#8797a1"; x.fillRect(start.x-42,start.y+12,70,14);
}
function step(now){
  let dt=Math.min((now-last)/1000,.033); last=now;
  x.clearRect(0,0,W,H);
  let grd=x.createLinearGradient(0,0,0,H);
  grd.addColorStop(0,"#162634"); grd.addColorStop(1,"#0d141a");
  x.fillStyle=grd; x.fillRect(0,0,W,H);
  drawGrid(); cannon();
  let all=true;
  for(const b of balls){
    if(!b.landed){
      b.t+=dt; b.x=start.x+b.vx*b.t*scale; b.y=start.y+(b.vy*b.t+.5*g*b.t*b.t)*scale;
      b.trail.push({x:b.x,y:b.y});
      if(b.trail.length>70)b.trail.shift();
      if(b.y>=ground){b.y=ground;b.landed=true;b.landX=b.x;b.trail.push({x:b.x,y:b.y});}
    }
    if(!b.landed)all=false;
    for(let i=1;i<b.trail.length;i++){
      x.strokeStyle=colors[b.i]+Math.floor(255*i/b.trail.length).toString(16).padStart(2,"0");
      x.lineWidth=3; x.beginPath(); x.moveTo(b.trail[i-1].x,b.trail[i-1].y); x.lineTo(b.trail[i].x,b.trail[i].y); x.stroke();
    }
    x.fillStyle=colors[b.i]; x.beginPath(); x.arc(b.x,b.y,7,0,Math.PI*2); x.fill();
    if(b.landed){
      x.strokeStyle=colors[b.i]; x.beginPath(); x.moveTo(b.landX,ground-16); x.lineTo(b.landX,ground+16); x.stroke();
      x.fillStyle=colors[b.i]; x.font="13px Arial";
      x.fillText(Math.round((b.landX-start.x)/scale)+" m",b.landX-22,ground-22-14*b.i);
    }
  }
  if(all)setTimeout(reset,900);
  requestAnimationFrame(step);
}
addEventListener("resize",resize);
resize();
requestAnimationFrame(step);
</script>
</body>
</html>
Claude HaikuOK★ 8.5/10
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Projectile Motion</title>
<style>
body { margin: 0; overflow: hidden; background: #1a1a1a; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
canvas.width = 1200;
canvas.height = 600;

const g = 0.5;
const v0 = 15;
const angles = [20, 35, 50, 65, 75];
let balls = [];
let time = 0;
let allLanded = false;

function init() {
  balls = angles.map(angle => {
    const rad = angle * Math.PI / 180;
    return {
      x: 50,
      y: 550,
      vx: v0 * Math.cos(rad),
      vy: -v0 * Math.sin(rad),
      trail: [],
      color: `hsl(${angle * 3}, 100%, 60%)`,
      landed: false
    };
  });
  time = 0;
  allLanded = false;
}

function update() {
  allLanded = true;
  balls.forEach(ball => {
    if (!ball.landed) {
      ball.vy += g;
      ball.x += ball.vx;
      ball.y += ball.vy;
      ball.trail.push({x: ball.x, y: ball.y});
      if (ball.trail.length > 50) ball.trail.shift();
      if (ball.y >= 550) {
        ball.y = 550;
        ball.landed = true;
      } else {
        allLanded = false;
      }
    }
  });
  if (allLanded && time > 60) init();
  time++;
}

function draw() {
  ctx.fillStyle = '#1a1a1a';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  
  ctx.strokeStyle = '#666';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(0, 550);
  ctx.lineTo(1200, 550);
  ctx.stroke();
  
  for (let i = 0; i <= 12; i++) {
    const x = i * 100;
    ctx.strokeStyle = '#444';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(x, 545);
    ctx.lineTo(x, 555);
    ctx.stroke();
    ctx.fillStyle = '#888';
    ctx.font = '12px monospace';
    ctx.fillText(i * 100, x - 15, 575);
  }
  
  balls.forEach(ball => {
    ctx.strokeStyle = ball.color;
    ctx.globalAlpha = 0.3;
    ctx.lineWidth = 2;
    ctx.beginPath();
    ball.trail.forEach((p, i) => {
      if (i === 0) ctx.moveTo(p.x, p.y);
      else ctx.lineTo(p.x, p.y);
    });
    ctx.stroke();
    ctx.globalAlpha = 1;
    
    ctx.fillStyle = ball.color;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, 5, 0, Math.PI * 2);
    ctx.fill();
  });
}

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

init();
loop();
</script>
</body>
</html>
Claude SonnetOK★ 8.5/10
Show code
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Projectiles</title>
<style>body{margin:0;background:#0a0e1a;overflow:hidden}canvas{display:block}</style></head>
<body><canvas id="c"></canvas>
<script>
const cvs=document.getElementById('c'),ctx=cvs.getContext('2d');
function resize(){cvs.width=innerWidth;cvs.height=innerHeight}
resize();addEventListener('resize',resize);
const g=400,v0=280,angles=[20,35,50,65,75];
const colors=['#ff5555','#ffb347','#ffe066','#66ff99','#66ccff'];
let originX,groundY,balls,t0,restarting=false;
function init(){
  originX=80;groundY=cvs.height-60;
  balls=angles.map((a,i)=>({a:a*Math.PI/180,color:colors[i],trail:[],landed:false,landX:0}));
  t0=performance.now();restarting=false;
}
init();
function frame(now){
  const t=(now-t0)/1000;
  ctx.fillStyle='rgba(10,14,26,0.25)';
  ctx.fillRect(0,0,cvs.width,cvs.height);
  ctx.strokeStyle='#334';ctx.beginPath();
  ctx.moveTo(0,groundY);ctx.lineTo(cvs.width,groundY);ctx.stroke();
  let allLanded=true;
  balls.forEach(b=>{
    const vx=v0*Math.cos(b.a),vy=v0*Math.sin(b.a);
    let x=originX+vx*t, y=groundY-(vy*t-0.5*g*t*t);
    if(t>0.05 && y>=groundY && !b.landed){b.landed=true;b.landX=x;}
    if(b.landed){x=b.landX;y=groundY;} else allLanded=false;
    b.trail.push({x,y});
    if(b.trail.length>400)b.trail.shift();
    for(let i=0;i<b.trail.length;i++){
      const p=b.trail[i];
      ctx.globalAlpha=i/b.trail.length;
      ctx.fillStyle=b.color;
      ctx.fillRect(p.x-1.5,p.y-1.5,3,3);
    }
    ctx.globalAlpha=1;
    ctx.beginPath();ctx.arc(x,y,6,0,7);ctx.fillStyle=b.color;ctx.fill();
    if(b.landed){
      ctx.strokeStyle=b.color;
      ctx.beginPath();ctx.moveTo(b.landX,groundY-10);ctx.lineTo(b.landX,groundY+10);ctx.stroke();
      ctx.fillStyle=b.color;ctx.font='12px sans-serif';
      ctx.fillText(Math.round(b.landX-originX)+'m',b.landX-15,groundY+25);
    }
  });
  ctx.beginPath();ctx.arc(originX,groundY,5,0,7);ctx.fillStyle='#fff';ctx.fill();
  if(allLanded && t>1 && !restarting){restarting=true;setTimeout(init,1200);}
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script></body></html>
Claude OpusOK★ 8.5/10
Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Projectile Motion</title>
<style>html,body{margin:0;background:#0b1020;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,ground;
function resize(){W=cv.width=innerWidth;H=cv.height=innerHeight;ground=H-60;}
addEventListener('resize',resize);resize();
const angles=[20,34,48,61,75],speed=Math.min(W,H)*0.9,g=400,ox=80;
const colors=['#ff5a5f','#ffb703','#8ecae6','#90ee90','#c77dff'];
let balls=[];
function reset(){oy=ground;balls=angles.map((a,i)=>{const r=a*Math.PI/180;return{x:ox,y:ground,vx:Math.cos(r)*speed,vy:-Math.sin(r)*speed,trail:[],landed:false,land:0,c:colors[i]};});}
let oy;reset();
let last=performance.now();
function step(t){const dt=Math.min((t-last)/1000,0.033);last=t;
ctx.fillStyle='#0b1020';ctx.fillRect(0,0,W,H);
ctx.strokeStyle='#334';ctx.beginPath();ctx.moveTo(0,ground);ctx.lineTo(W,ground);ctx.stroke();
let allLanded=true;
for(const b of balls){
 if(!b.landed){allLanded=false;
  b.vy+=g*dt;b.x+=b.vx*dt;b.y+=b.vy*dt;
  b.trail.push({x:b.x,y:b.y});if(b.trail.length>120)b.trail.shift();
  if(b.y>=ground){b.y=ground;b.landed=true;b.land=b.x;}
 }
 for(let i=0;i<b.trail.length;i++){const p=b.trail[i];ctx.globalAlpha=i/b.trail.length*0.7;ctx.fillStyle=b.c;ctx.beginPath();ctx.arc(p.x,p.y,2,0,7);ctx.fill();}
 ctx.globalAlpha=1;ctx.fillStyle=b.c;ctx.beginPath();ctx.arc(b.x,b.y,7,0,7);ctx.fill();
 if(b.landed){ctx.strokeStyle=b.c;ctx.beginPath();ctx.moveTo(b.land,ground);ctx.lineTo(b.land,ground+18);ctx.stroke();
  ctx.fillStyle=b.c;ctx.font='12px sans-serif';ctx.fillText(Math.round((b.land-ox)/10)+'m',b.land-10,ground+34);}
}
if(allLanded){if(!balls.doneAt)balls.doneAt=t;if(t-balls.doneAt>1200)reset();}
requestAnimationFrame(step);}
requestAnimationFrame(step);
</script>
</body>
</html>
Claude FableOK★ 8.5/10
Claude Fable – rendered output R26
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Projectile Motion</title>
<style>html,body{margin:0;height:100%;background:#0b1020;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,oy;const ox=60,G=280;
const angles=[20,34,45,60,75],cols=['#ff5252','#ffb300','#66bb6a','#29b6f6','#e040fb'];
let balls=[],waitT=0;
function launch(){const v0=Math.sqrt(0.85*(W-120)*G);
balls=angles.map((a,i)=>{const r=a*Math.PI/180;
return{x:ox,y:oy,vx:v0*Math.cos(r),vy:-v0*Math.sin(r),c:cols[i],ang:a,landed:false,lx:0};});
waitT=0;ctx.fillStyle='#0b1020';ctx.fillRect(0,0,W,H);}
function resize(){W=cv.width=innerWidth;H=cv.height=innerHeight;oy=H-60;launch();}
resize();addEventListener('resize',resize);
let last=performance.now();
function frame(t){requestAnimationFrame(frame);
const dt=Math.min((t-last)/1000,0.033);last=t;
ctx.fillStyle='rgba(11,16,32,0.05)';ctx.fillRect(0,0,W,H);
ctx.strokeStyle='#8899aa';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(0,oy);ctx.lineTo(W,oy);ctx.stroke();
ctx.fillStyle='#cfd8dc';ctx.beginPath();ctx.arc(ox,oy,8,0,7);ctx.fill();
let all=true;
for(const b of balls){
if(!b.landed){all=false;b.vy+=G*dt;b.x+=b.vx*dt;b.y+=b.vy*dt;
if(b.y>=oy&&b.vy>0){b.y=oy;b.landed=true;b.lx=b.x;}}
ctx.fillStyle=b.c;ctx.beginPath();ctx.arc(b.x,b.y,6,0,7);ctx.fill();
if(b.landed){ctx.strokeStyle=b.c;ctx.lineWidth=2;
ctx.beginPath();ctx.moveTo(b.lx,oy);ctx.lineTo(b.lx,oy+14);ctx.stroke();
ctx.fillStyle=b.c;ctx.font='12px monospace';ctx.textAlign='center';
ctx.fillText(b.ang+'°',b.lx,oy+30);
ctx.fillText(Math.round((b.lx-ox)/10)+'m',b.lx,oy+44);}}
if(all){waitT+=dt;if(waitT>1.6)launch();}}
requestAnimationFrame(frame);
</script>
</body>
</html>