Code · blind round
R81 · Flocking birds with three rules
blind round
4/8 local models answered
Answers below are anonymized and shuffled. Read, judge, pick a favourite — then scroll down and reveal the models.
The field: deepseek-r1:14b · gemma4:26b · llama3.1:8b · mistral-small:24b · qwen3-coder:30b · qwen3:14b · qwen3:8b · qwen3.5:9b — listed alphabetically, which is not the order of the cards below.
2 of the 8 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. Which ones, and why, is spelled out in the reveal.
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 simulates 200 flocking birds on a <canvas> using the three classic rules: separation, alignment and cohesion. Draw each bird as a small triangle pointing in its direction of travel, let flocks form, split and merge on their own, and wrap the birds around the edges. Show the current number of distinct flocks and the average speed. 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 — blind
Local models (Brain cluster) — identical prompt, anonymized order
▶ Playstarts, but the canvas stays empty Show code ```html
<!DOCTYPE html>
<html>
<head>
<style>body{margin:0;padding:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#fff}.canvas-container{position:relative}.info{position:absolute;top-left:5px;color:black;font-family:sans-serif}</style>
</head>
<body>
<div class="info">
<div id="flocks">Flocks: 0</div>
<div id="speed">Speed: 0</div>
</div>
<canvas class="canvas-container" width="800" height="600"></canvas>
<script>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const birds = [];
const flocks = [];
const maxBirds = 200;
let frameCount = 0;
class Bird {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.speed = 1;
this.angle = 0;
}
update() {
const separationForce = this.applySeparation();
const alignmentForce = this.applyAlignment();
const cohesionForce = this.applyCohesion();
let forceX = (separationForce.x + alignmentForce.x + cohesionForce.x) * 0.1;
let forceY = (separationForce.y + alignmentForce.y + cohesionForce.y) * 0.1;
this.vx += forceX;
this.vy += forceY;
const speedLimit = Math.sqrt(this.vx ** 2 + this.vy ** 2);
if (speedLimit > 3) {
this.vx *= 0.8;
this.vy *= 0.8;
}
this.x += this.vx;
this.y += this.vy;
if (this.x < 0) this.x = canvas.width;
if (this.x > canvas.width) this.x = 0;
if (this.y < 0) this.y = canvas.height;
if (this.y > canvas.height) this.y = 0;
}
applySeparation() {
let sumX = 0, sumY = 0;
for (let bird of birds) {
if (bird !== this && distance(this, bird) < 30) {
sumX += (this.x - bird.x);
sumY += (this.y - bird.y);
}
}
return {x: sumX * 0.1, y: sumY * 0.1};
}
applyAlignment() {
let sumX = 0, sumY = 0, count = 0;
for (let bird of birds) {
if (bird !== this && distance(this, bird) < 80) {
sumX += bird.vx;
sumY += bird.vy;
count++;
}
}
return {x: (sumX / count || 0) * 0.1, y: (sumY / count || 0) * 0.1};
}
applyCohesion() {
let sumX = 0, sumY = 0;
for (let bird of birds) {
if (bird !== this && distance(this, bird) < 80) {
sumX += bird.x;
sumY += bird.y;
}
}
const count = birds.length - 1;
return {x: (sumX / count - this.x) * 0.3, y: (sumY / count - this.y) * 0.3};
}
draw() {
ctx.beginPath();
ctx.fillStyle = '#F00';
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + Math.cos(this.angle) * 5, this.y + Math.sin(this.angle) * 8);
ctx.lineTo(this.x - Math.cos(this.angle) * 3, this.y + Math.sin(this.angle) * 8);
ctx.fill();
}
}
function distance(bird1, bird2) {
return Math.sqrt((bird1.x - bird2.x)**2 + (bird1.y - bird2.y)**2);
}
function findFlocks() {
if (!birds.length) return [];
const newFlocks = [];
let currentFlock = [birds[0]];
for (let bird of birds.slice(1)) {
let minDist = Infinity;
let nearestBird = null;
for (let f in newFlocks) {
const flockBirds = newFlocks[f];
let distToFirst = distance(bird, flockBirds[0]);
if (distToFirst < 25 && distToFirst < minDist) {
minDist = distToFirst;
nearestBird = flockBirds[0];
}
}
if (!nearestBird || distance(bird, nearestBird) > 25) {
currentFlock.push(bird);
} else {
newFlocks[newFlocks.length - 1] = [...newFlocks[newFlocks.length - 1], ...currentFlock];
currentFlock = [bird];
}
}
if (currentFlock.length > 0) {
newFlocks.push(currentFlock);
}
return newFlocks;
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let bird of birds) bird.update();
flocks.value = findFlocks().length;
let totalSpeed = 0;
for (let bird of birds) {
totalSpeed += Math.sqrt(bird.vx ** 2 + bird.vy ** 2);
bird.draw();
}
document.getElementById('flocks').textContent = `Flocks: ${flocks.value}`;
document.getElementById('speed').textContent = `Speed: ${(totalSpeed / birds.length).toFixed(1)}`;
if (++frameCount % 60 === 59) {
// Every second, update speeds
for (let bird of birds) {
bird.speed *= 1 + Math.random() * 0.4 - 0.2;
if (bird.speed < 0.5) bird.speed = 0.5;
}
}
requestAnimationFrame(animate);
}
function init() {
for (let i = 0; i < maxBirds; i++) {
birds.push(new Bird());
}
animate();
}
// Start fresh simulation
init();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
</script>
</body>
</html>
```
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
No usable answer reached the harness (ERR). Counted as a did-not-finish.
No usable answer reached the harness (TIMEOUT). Counted as a did-not-finish.
▶ Playstarts, but throws a JavaScript error Show code ```html
<!DOCTYPE html>
<html>
<head>
<title>Flocking Simulation</title>
<style>body {margin:0;overflow:hidden;background-color:#111}canvas {display:block;}</style>
</head>
<body>
<canvas id="flockCanvas"></canvas>
<script>
const canvas = document.getElementById('flockCanvas');
const ctx = canvas.getContext('2d');
let width, height;
let birds = [];
let MAX_BIRDS = 200;
// Constants for flocking rules
const VISION_RADIUS = 50;
const SEPARATION_DISTANCE = 10;
const COHESION_WEIGHT = 0.3;
const ALIGNMENT_WEIGHT = 0.5;
const SEPARATION_WEIGHT = 1.5;
const MAX_FORCE = 0.2;
const NEIGHBOR_COUNT_THRESHOLD = 5;
function init() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
birds = [];
for (let i = 0; i < MAX_BIRDS; i++) {
birds.push(new Bird(Math.random() * width, Math.random() * height));
}
animationLoop();
}
class Vector {
constructor(x, y) {
this.x = x || 0;
this.y = y || 0;
}
add(v) { return new Vector(this.x + v.x, this.y + v.y); }
sub(v) { return new Vector(this.x - v.x, this.y - v.y); }
mult(s) { return new Vector(this.x * s, this.y * s); }
magSq() { return this.x * this.x + this.y * this.y; }
mag() { return Math.sqrt(this.magSq()); }
normalize() {
const m = this.mag();
return m > 0 ? this.mult(1 / m) : new Vector(0, 0);
}
limit(max) {
if (this.magSq() > max * max) {
const ratio = max / Math.sqrt(this.magSq());
return this.mult(ratio);
}
return this;
}
static random2D() {
return new Vector((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 2);
}
}
class Bird {
constructor(x, y) {
this.pos = new Vector(x, y);
this.vel = Vector.random2D().mult(1 + Math.random());
this.acc = new Vector();
this.maxSpeed = 3;
this.maxForce = 0.25;
}
applyForce(force) {
this.acc = this.acc.add(force);
}
flock(allBirds) {
const separation = this.separate(allBirds);
const alignment = this.align(allBirds);
const cohesion = this.cohere(allBirds);
// Weighted combination of forces
this.applyForce(separation.mult(SEPARATION_WEIGHT));
this.applyForce(alignment.mult(ALIGNMENT_WEIGHT));
this.applyForce(cohesion.mult(COHESION_WEIGHT));
}
separate(allBirds) {
let steer = new Vector();
let count = 0;
for (const other of allBirds) {
if (other === this) continue;
const distSq = this.pos.sub(other.pos).magSq();
if (distSq < SEPARATION_DISTANCE * SEPARATION_DISTANCE) {
const diff = this.pos.sub(other.pos);
diff.normalize();
diff.div(1 / Math.sqrt(Math.PI * 2)); // Weight inversely by distance
steer = steer.add(diff);
count++;
}
}
if (count > 0) {
steer = steer.mult(1 / count).normalize().mult(this.maxSpeed);
return this.seek(steer);
}
return new Vector();
}
align(allBirds) {
let sumVel = new Vector();
let count = 0;
for (const other of allBirds) {
if (other === this) continue;
const distSq = this.pos.sub(other.pos).magSq();
if (distSq < VISION_RADIUS * VISION_RADIUS) {
sumVel = sumVel.add(other.vel);
count++;
}
}
if (count > 0) {
const avg = sumVel.mult(1 / count).normalize().mult(this.maxSpeed);
return this.seek(avg);
}
return new Vector();
}
cohere(allBirds) {
let centerOfMass = new Vector();
let count = 0;
for (const other of allBirds) {
if (other === this) continue;
const distSq = this.pos.sub(other.pos).magSq();
if (distSq < VISION_RADIUS * VISION_RADIUS) {
centerOfMass = centerOfMass.add(other.pos);
count++;
}
}
if (count > 0) {
const avgPos = centerOfMass.mult(1 / count).add(this.pos); // Adjust for seeking, but keep it simple: just the target location
const desiredVel = new Vector(avgPos.x - this.pos.x, avgPos.y - this.pos.y).normalize().mult(this.maxSpeed);
return this.seek(desiredVel);
}
return new Vector();
}
// Standard steering force calculation (Desired Velocity - Current Velocity)
seek(targetVector) {
const desired = targetVector;
let steer = desired.sub(this.vel).limit(this.maxForce);
steer.normalize().mult(this.maxSpeed);
return steer;
}
update() {
// Apply forces and calculate new velocity/position
this.applyForce(new Vector(0, 0)); // Reset acceleration before calculation (flock calls handle this)
this.flock(birds);
this.vel = this.vel.add(this.acc);
this.vel = this.vel.limit(this.maxSpeed);
this.pos = this.pos.add(this.vel);
this.acc = new Vector(); // Reset acceleration for next frame
// Wrap boundaries
if (this.pos.x < 0) this.pos.x += width; else if (this.pos.x > width) this.pos.x -= width;
if (this.pos.y < 0) this.pos.y += height; else if (this.pos.y > height) this.pos.y -= height;
}
draw() {
ctx.save();
ctx.translate(this.pos.x, this.pos.y);
ctx.rotate(Math.atan2(this.vel.y, this.vel.x));
ctx.fillStyle = '#FFF';
ctx.beginPath();
// Draw triangle (bird representation)
ctx.moveTo(10, 0);
ctx.lineTo(-5, -7);
ctx.lineTo(-5, 7);
ctx.closePath();
ctx.fill();
ctx.restore();
}
getSpeed() {
return this.vel.mag();
}
}
function drawFlocks(birds) {
const occupied = new Set();
let flockCount = 0;
for (let i = 0; i < birds.length; i++) {
if (occupied.has(i)) continue;
// Simple clustering heuristic to detect flocks
let currentBird = birds[i];
let clusterMembers = [currentBird];
let searchQueue = [i];
let visitedIndices = new Set([i]);
while (searchQueue.length > 0) {
const index = searchQueue.shift();
const bird = birds[index];
for (let j = i + 1; j < birds.length; j++) {
if (!visitedIndices.has(j)) {
const otherBird = birds[j];
const distSq = currentBird.pos.sub(otherBird.pos).magSq();
// Use a slightly larger radius for initial detection
if (distSq < 150 * 150) {
clusterMembers.push(otherBird);
visitedIndices.add(j);
searchQueue.push(j);
}
}
}
}
// Check if the cluster is large enough to count as a flock
if (clusterMembers.length >= NEIGHBOR_COUNT_THRESHOLD) {
flockCount++;
}
}
return flockCount;
}
function loop() {
ctx.fillStyle = 'rgba(17, 17, 17, 0.2)';
ctx.fillRect(0, 0, width, height);
let totalSpeed = 0;
const activeBirds = birds.filter(b => b !== undefined);
for (let i = 0; i < activeBirds.length; i++) {
activeBirds[i].update();
activeBirds[i].draw();
totalSpeed += activeBirds[i].getSpeed();
}
const avgSpeed = totalSpeed / activeBirds.length || 0;
const flockCount = drawFlocks(activeBirds);
// Display metrics
ctx.fillStyle = '#FFF';
ctx.font = '16px Arial';
ctx.fillText(`Flocks: ${flockCount}`, 10, 25);
ctx.fillText(`Avg Speed: ${avgSpeed.toFixed(2)}`, 10, 50);
requestAnimationFrame(loop);
}
// Setup and run
window.addEventListener('resize', () => {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
});
init();
</script>
</body>
</html>
```
▶ Play Show code ```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Boid Simulation</title>
<style>
body { margin: 0; overflow: hidden; background-color: #111; }
canvas { display: block; }
#stats { position: absolute; top: 10px; left: 10px; color: white; font-family: monospace; z-index: 10; background: rgba(0, 0, 0, 0.5); padding: 10px; border-radius: 5px; }
</style>
</head>
<body>
<canvas id="boidCanvas"></canvas>
<div id="stats">Flocks: 0 | Avg Speed: 0.0</div>
<script>
const canvas = document.getElementById('boidCanvas');
const ctx = canvas.getContext('2d');
let WIDTH, HEIGHT;
// Simulation Parameters
const N_BIRDS = 200;
const MAX_SPEED = 5.0;
const MAX_FORCE = 0.1;
const SEPARATION_DISTANCE = 30;
const COHESION_RADIUS = 80;
let birds = [];
let lastTime = 0;
// --- Vector Helper Functions (to save space) ---
function createVector(x, y) { return { x: x || 0, y: y || 0 }; }
function add(v1, v2) { return createVector(v1.x + v2.x, v1.y + v2.y); }
function sub(v1, v2) { return createVector(v1.x - v2.x, v1.y - v2.y); }
function mult(v, s) { return createVector(v.x * s, v.y * s); }
function magSq(v) { return v.x * v.x + v.y * v.y; }
function magnitude(v) { return Math.sqrt(magSq(v)); }
function normalize(v) { const m = magnitude(v); return m ? mult(v, 1 / m) : createVector(0, 0); }
// --- Bird Class/Object Setup ---
class Bird {
constructor() {
this.x = Math.random() * WIDTH;
this.y = Math.random() * HEIGHT;
this.vx = (Math.random() - 0.5) * MAX_SPEED;
this.vy = (Math.random() - 0.5) * MAX_SPEED;
this.accX = 0;
this.accY = 0;
}
applyForce(force) {
this.accX += force.x;
this.accY += force.y;
}
// Calculates the desired steering force based on neighbors and rules
flock(boids) {
let separation = createVector(0, 0);
let alignment = createVector(0, 0);
let cohesion = createVector(0, 0);
let neighborCount = 0;
for (const other of boids) {
if (other === this) continue;
let d = sub(this, other);
let distSq = magSq(d);
let dist = Math.sqrt(distSq);
// Check if within cohesion/alignment radius
if (neighborCount < 10 && dist < COHESION_RADIUS) { // Limit processing to keep it fast
neighborCount++;
// Separation: Steer away from nearby boids
if (dist < SEPARATION_DISTANCE && dist > 0.1) {
let strength = 1 / dist;
separation = add(separation, normalize(d));
}
// Alignment & Cohesion accumulation (always within COHESION_RADIUS)
alignment = add(alignment, other);
cohesion = add(cohesion, other);
}
}
// Separate and average forces
let separationForce = mult(normalize(separation), 1 / Math.max(1, neighborCount));
let alignmentForce = mult(normalize(alignment), neighborCount * MAX_SPEED / COHESION_RADIUS);
let cohesionForce = mult(normalize(cohesion), 1 / Math.max(1, neighborCount)) ;
// Apply rules (Weighted averages)
this.applyForce(mult(separationForce, 1.5)); // Separation weight higher
this.applyForce(mult(alignmentForce, 1.0));
this.applyForce(mult(cohesionForce, 1.0));
// Limit acceleration
let acc = createVector(this.accX, this.accY);
if (magnitude(acc) > MAX_FORCE) {
let factor = MAX_FORCE / magnitude(acc);
acc = mult(acc, factor);
}
}
// Updates position and velocity using accumulated acceleration
update() {
this.vx += this.accX;
this.vy += this.accY;
// Limit speed
let currentSpeed = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
if (currentSpeed > MAX_SPEED) {
let factor = MAX_SPEED / currentSpeed;
this.vx *= factor;
this.vy *= factor;
}
// Apply force damping to prevent infinite acceleration buildup
this.accX *= 0.1;
this.accY *= 0.1;
this.x += this.vx;
this.y += this.vy;
// Wrap around boundaries
if (this.x < 0) this.x = WIDTH;
if (this.x > WIDTH) this.x = 0;
if (this.y < 0) this.y = HEIGHT;
if (this.y > HEIGHT) this.y = 0;
}
// Draws the bird as a triangle pointing in direction of travel
draw() {
ctx.save();
ctx.translate(this.x, this.y);
let angle = Math.atan2(this.vy, this.vx);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(10, 0); // Nose point
ctx.lineTo(-5, -5); // Wing back left
ctx.lineTo(-5, 5); // Wing back right
ctx.closePath();
ctx.fillStyle = 'white';
ctx.fill();
ctx.restore();
}
}
// --- Core Simulation Logic ---
function handleResize() {
WIDTH = canvas.width = window.innerWidth;
HEIGHT = canvas.height = window.innerHeight;
}
function initBirds() {
birds = [];
for (let i = 0; i < N_BIRDS; i++) {
birds.push(new Bird());
}
}
function updateSimulation(time) {
const deltaTime = time - lastTime;
lastTime = time;
// 1. Apply Flocking Rules (Acceleration)
for (let i = 0; i < N_BIRDS; i++) {
birds[i].flock(birds);
}
// 2. Update Physics and Position
for (const bird of birds) {
bird.update();
}
// 3. Drawing and Stats
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (const bird of birds) {
bird.draw();
}
// Calculate Statistics
let totalSpeed = 0;
let flockCount = 0; // Simplified count: Assume a cluster is formed if velocity vectors are close enough to neighbors.
// A robust method requires graph theory, so we'll use a simple proximity check for visualization/estimation.
for (const bird of birds) {
totalSpeed += Math.sqrt(bird.vx * bird.vx + bird.vy * bird.vy);
}
let avgSpeed = totalSpeed / N_BIRDS;
// Simple Flock Estimation: Use a temporary grid/distance metric to count clusters
const visited = new Set();
const FLOCK_SEARCH_DIST_SQ = 250 * 250; // Max distance for initial cluster check
let currentFlocks = new Set();
for (let i = 0; i < N_BIRDS; i++) {
if (visited.has(i)) continue;
const startBird = birds[i];
let neighborsInCluster = [startBird];
let queue = [{ b: startBird, index: i }];
let visitedIndices = new Set([i]);
while (queue.length > 0) {
let current = queue.shift();
const qB = current.b;
const qIdx = current.index;
// Check all remaining unvisited birds for proximity
for(let j=0; j<N_BIRDS; ++j) {
if (!visitedIndices.has(j)) {
const other = birds[j];
let dSq = (qB.x - other.x)**2 + (qB.y - other.y)**2;
if (dSq < FLOCK_SEARCH_DIST_SQ) {
neighborsInCluster.push(other);
visitedIndices.add(j);
queue.push({ b: other, index: j });
}
}
}
}
currentFlocks.add(neighborsInCluster.length > 5 ? 'Active' : 'Small'); // Count if cluster is large enough
// Mark all birds found in this cluster as visited for the next iteration
for (const neighbor of neighborsInCluster) {
visited.add(birds.indexOf(neighbor));
}
}
flockCount = currentFlocks.size;
document.getElementById('stats').innerHTML = `Flocks: ${Math.max(1, Math.round(flockCount))} | Avg Speed: ${avgSpeed.toFixed(2)}`;
requestAnimationFrame(updateSimulation);
}
// --- Initialization ---
window.addEventListener('resize', handleResize);
handleResize();
initBirds();
requestAnimationFrame(updateSimulation);
</script>
</body>
</html>
```
▶ Play Show code ```html
<!DOCTYPE html>
<html>
<head>
<title>Boids Simulation</title>
<style>body { margin: 0; overflow: hidden; background-color: #111; } canvas { display: block; }</style>
</head>
<body>
<canvas id="boidCanvas"></canvas>
<script>
const canvas = document.getElementById('boidCanvas');
const ctx = canvas.getContext('2d');
let W, H;
let birds = [];
// Simulation Parameters
const NUM_BIRDS = 200;
const MAX_SPEED = 3.5;
const MAX_FORCE = 0.1;
const SEPARATION_WEIGHT = 1.5;
const ALIGNMENT_WEIGHT = 1.0;
const COHESION_WEIGHT = 1.0;
function resize() {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
class Bird {
constructor(x, y) {
this.pos = { x: x, y: y };
this.vel = { x: Math.random() * 2 - 1, y: Math.random() * 2 - 1 };
this.acc = { x: 0, y: 0 };
}
applyForce(force) {
this.acc.x += force.x;
this.acc.y += force.y;
}
// Separation: Steer away from nearby birds
separate(boids) {
let steering = { x: 0, y: 0 };
let count = 0;
for (const other of boids) {
if (other === this) continue;
let d = Math.hypot(this.pos.x - other.pos.x, this.pos.y - other.pos.y);
if (d < 50 && d > 1) {
let diffX = this.pos.x - other.pos.x;
let diffY = this.pos.y - other.pos.y;
steering.x += diffX / d;
steering.y += diffY / d;
count++;
}
}
if (count > 0) {
steering.x /= count;
steering.y /= count;
let mag = Math.hypot(steering.x, steering.y);
steering.x /= mag;
steering.y /= mag;
return { x: steering.x * SEPARATION_WEIGHT, y: steering.y * SEPARATION_WEIGHT };
}
return { x: 0, y: 0 };
}
// Alignment: Steer towards average velocity of neighbors
align(boids) {
let avgVel = { x: 0, y: 0 };
let count = 0;
for (const other of boids) {
if (other === this) continue;
let d = Math.hypot(this.pos.x - other.pos.x, this.pos.y - other.pos.y);
if (d < 75) {
avgVel.x += other.vel.x;
avgVel.y += other.vel.y;
count++;
}
}
if (count > 0) {
avgVel.x /= count;
avgVel.y /= count;
let mag = Math.hypot(avgVel.x, avgVel.y);
if (mag > 0) {
avgVel.x /= mag;
avgVel.y /= mag;
return { x: avgVel.x * ALIGNMENT_WEIGHT, y: avgVel.y * ALIGNMENT_WEIGHT };
}
}
return { x: 0, y: 0 };
}
// Cohesion: Steer towards the center of nearby birds
cohese(boids) {
let center = { x: 0, y: 0 };
let count = 0;
for (const other of boids) {
if (other === this) continue;
let d = Math.hypot(this.pos.x - other.pos.x, this.pos.y - other.pos.y);
if (d < 100) {
center.x += other.pos.x;
center.y += other.pos.y;
count++;
}
}
if (count > 0) {
center.x /= count;
center.y /= count;
let desired = { x: center.x, y: center.y };
let steerX = desired.x - this.pos.x;
let steerY = desired.y - this.pos.y;
let dist = Math.hypot(steerX, steerY);
if (dist > 0) {
steerX /= dist;
steerY /= dist;
return { x: steerX * COHESION_WEIGHT, y: steerY * COHESION_WEIGHT };
}
}
return { x: 0, y: 0 };
}
update(boids) {
let separation = this.separate(boids);
let alignment = this.align(boids);
let cohesion = this.cohese(boids);
this.applyForce(separation);
this.applyForce(alignment);
this.applyForce(cohesion);
// Limit acceleration and apply force
let accMag = Math.hypot(this.acc.x, this.acc.y);
if (accMag > MAX_FORCE) {
this.acc.x *= (MAX_FORCE / accMag);
this.acc.y *= (MAX_FORCE / accMag);
}
// Update velocity and position
this.vel.x += this.acc.x;
this.vel.y += this.acc.y;
let velMag = Math.hypot(this.vel.x, this.vel.y);
if (velMag > MAX_SPEED) {
this.vel.x *= (MAX_SPEED / velMag);
this.vel.y *= (MAX_SPEED / velMag);
}
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// Edge wrapping
if (this.pos.x < 0) this.pos.x = W; else if (this.pos.x > W) this.pos.x = 0;
if (this.pos.y < 0) this.pos.y = H; else if (this.pos.y > H) this.pos.y = 0;
// Reset acceleration for next frame
this.acc.x = 0;
this.acc.y = 0;
}
draw() {
ctx.save();
ctx.translate(this.pos.x, this.pos.y);
let angle = Math.atan2(this.vel.y, this.vel.x);
ctx.rotate(angle);
ctx.beginPath();
ctx.moveTo(10, 0); // Tip of the triangle
ctx.lineTo(-5, -5); // Bottom left
ctx.lineTo(-5, 5); // Bottom right
ctx.closePath();
ctx.fillStyle = '#FFF';
ctx.fill();
ctx.restore();
}
}
function initialize() {
birds = [];
for (let i = 0; i < NUM_BIRDS; i++) {
const x = Math.random() * W;
const y = Math.random() * H;
birds.push(new Bird(x, y));
}
}
function calculateMetrics() {
let totalSpeedSq = 0;
for (const b of birds) {
totalSpeedSq += b.vel.x * b.vel.x + b.vel.y * b.vel.y;
}
const avgSpeed = Math.sqrt(totalSpeedSq / NUM_BIRDS);
// Simple flock counting: count groups of birds that are close together
let numFlocks = 0;
let visited = new Array(NUM_BIRDS).fill(false);
for (let i = 0; i < NUM_BIRDS; i++) {
if (!visited[i]) {
numFlocks++;
let seed = [i];
let head = 0;
while (head < seed.length) {
const currentBirdIndex = seed[head++];
visited[currentBirdIndex] = true;
for (let j = currentBirdIndex + 1; j < NUM_BIRDS; j++) {
if (!visited[j]) {
const b1 = birds[currentBirdIndex];
const b2 = birds[j];
const dist = Math.hypot(b1.pos.x - b2.pos.x, b1.pos.y - b2.pos.y);
if (dist < 30) { // Flocking proximity threshold
seed.push(j);
}
}
}
}
}
}
document.getElementById('stats').innerHTML = `Flocks: ${numFlocks}<br>Avg Speed: ${avgSpeed.toFixed(2)}`;
}
function animate() {
ctx.fillStyle = 'rgba(17, 17, 17, 0.5)';
ctx.fillRect(0, 0, W, H);
for (const bird of birds) {
bird.update(birds);
bird.draw();
}
calculateMetrics();
requestAnimationFrame(animate);
}
// Setup HUD element dynamically
document.body.insertAdjacentHTML('beforeend', '<div id="stats" style="position: fixed; top: 10px; left: 10px; color: white; font-family: monospace; background: rgba(0, 0, 0, 0.6); padding: 5px;">Initializing...</div>');
initialize();
animate();
</script>
</body>
</html>
```
Reveal the models — compare the answers first, then open. Names, blind scores and latencies are hidden until you do.