Overview › Code › R63
Code · blind round

R63 · Kruskal's MST with union-find

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.

A frontier reference answered the same task (Claude Opus 5) — outside the field and deliberately unscored, further down the page.

Show task
Create ONE self-contained HTML file (STRICT LIMIT: max 120 lines total) that animates Kruskal's minimum spanning tree on a random graph of 25 nodes drawn on a <canvas>. Sort the edges by weight and add them one at a time: colour an edge green when union-find accepts it and red when it would close a cycle, and tint the nodes by their current component so the components visibly merge. Show the number of accepted edges and the total weight. 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.5:9bBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Kruskal's MST</title></head>
<body>
<canvas id="mstCanvas" width="800" height="600" style="display:block;"></canvas>
<script>
const canvas = document.getElementById('mstCanvas');
const ctx = canvas.getContext('2d');

let N = 25;
let nodes = [];
let edges = [];
let parent, rank;
let edgesToProcess = [];
let currentEdgeIndex = 0;
let acceptedEdgesCount = 0;
let totalWeight = 0;
let animationRunning = true;

function initGraph() {
    nodes = Array.from({ length: N }, (_, i) => ({ x: Math.random() * canvas.width, y: Math.random() * canvas.height }));
    edges = [];
    for (let i = 0; i < N; i++) {
        for (let j = i + 1; j < N; j++) {
            const dx = nodes[i].x - nodes[j].x;
            const dy = nodes[i].y - nodes[j].y;
            const weight = Math.sqrt(dx * dx + dy * dy);
            edges.push({ u: i, v: j, w: weight });
        }
    }
    edges.sort((a, b) => a.w - b.w);

    parent = Array.from({ length: N }, (_, i) => i);
    rank = Array(N).fill(0);
    currentEdgeIndex = 0;
    acceptedEdgesCount = 0;
    totalWeight = 0;
}

function find(i) {
    if (parent[i] === i) return i;
    return parent[i] = find(parent[i]);
}

function union(i, j) {
    let rootI = find(i);
    let rootJ = find(j);
    if (rootI !== rootJ) {
        if (rank[rootI] < rank[rootJ]) {
            parent[rootI] = rootJ;
        } else if (rank[rootI] > rank[rootJ]) {
            parent[rootJ] = rootI;
        } else {
            parent[rootJ] = rootI;
            rank[rootI]++;
        }
        return true;
    }
    return false;
}

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const componentColors = {};
    for (let i = 0; i < N; i++) {
        const root = find(i);
        if (!componentColors[root]) componentColors[root] = 'rgba(50, 150, 255, 0.3)';
        nodes[i].color = componentColors[root];
    }

    ctx.lineWidth = 2;
    for (let i = 0; i < N; i++) {
        ctx.beginPath();
        ctx.arc(nodes[i].x, nodes[i].y, 10, 0, Math.PI * 2);
        ctx.fillStyle = nodes[i].color || 'rgba(50, 150, 255, 0.3)';
        ctx.fill();
    }

    for (let i = 0; i < edges.length; i++) {
        const e = edges[i];
        let color = '#999';
        if (e.accepted) color = 'green';
        if (e.rejected) color = 'red';

        ctx.beginPath();
        ctx.moveTo(nodes[e.u].x, nodes[e.u].y);
        ctx.lineTo(nodes[e.v].x, nodes[e.v].y);
        ctx.strokeStyle = color;
        ctx.lineWidth = e.accepted ? 3 : (e.rejected ? 2 : 1);
        ctx.stroke();
    }
}

function processEdge() {
    if (!animationRunning) return;

    if (currentEdgeIndex >= edges.length) {
        console.log("MST Complete.");
        return; // Stop animation if finished
    }

    const e = edges[currentEdgeIndex];
    let accepted = false;
    let rejected = false;

    accepted = union(e.u, e.v);

    if (accepted) {
        e.accepted = true;
        e.rejected = false;
        acceptedEdgesCount++;
        totalWeight += e.w;
    } else {
        e.accepted = false;
        e.rejected = true;
    }

    currentEdgeIndex++;
    draw();
    document.getElementById('stats').innerHTML = `Accepted Edges: ${acceptedEdgesCount}<br>Total Weight: ${totalWeight.toFixed(2)}`;
}

function animate() {
    if (animationRunning) {
        processEdge();
        requestAnimationFrame(animate);
    }
}

// Setup HTML requirements
document.body.innerHTML = `<canvas id="mstCanvas" width="800" height="600" style="display:block;"><div id="stats" style="position:absolute; top:10px; left:10px; font-family:monospace;">Accepted Edges: 0<br>Total Weight: 0.00</div></canvas>`;

initGraph();
animate();
</script>
</body>
</html>
```
llama3.1:8bERRSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
  <title>Kruskal</title>
  <style>
    canvas {
      border: 1px solid black;
    }
  </style>
</head>
<body>
  <canvas id="c" width="800" height="600"></canvas>
  <script>
    const c = document.getElementById('c');
    const ctx = c.getContext('2d');
    const N = 25;

    let nodes = [], edges = [];
    for (let i = 0; i < N; i++) {
      nodes.push([Math.random() * c.width, Math.random() * c.height]);
    }

    for (let i = 0; i < N - 1; i++) {
      for (let j = i + 1; j < N; j++) {
        edges.push({a: i, b: j, w: Math.random()});
      }
    }

    function sortEdges() {
      edges.sort((a, b) => a.w - b.w);
    }

    let idx = 0;
    let component = new Array(N).fill(0);
    let unioned = new Array(N).fill(false);

    function draw() {
      ctx.clearRect(0, 0, c.width, c.height);
      for (let i = 0; i < N; i++) {
        if (!unioned[i]) {
          const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256);
          ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
          ctx.beginPath();
          ctx.arc(nodes[i][0], nodes[i][1], 10, 0, 2 * Math.PI);
          ctx.fill();
        }
      }

      for (let i = idx - 1; i < edges.length; i++) {
        if (!unioned[edges[i].a] || !unioned[edges[i].b]) continue;

        const r = (unioned[component[edges[i].a]] && unioned[component[edges[i].b]]) ? 255 : 0;
        ctx.strokeStyle = `rgb(${r}, ${255 - r}, 0)`;
        ctx.beginPath();
        ctx.moveTo(nodes[edges[i].a][0], nodes[edges[i].a][1]);
        ctx.lineTo(nodes[edges[i].b][0], nodes[edges[i].b][1]);
        ctx.stroke();

        if (unionFind(component, edges[i].a, edges[i].b)) {
          ctx.strokeStyle = 'green';
          idx++;
        } else {
          ctx.strokeStyle = 'red';
        }
      }

      const acceptedEdges = idx;
      const totalWeight = edges.slice(0, idx).reduce((s, e) => s + (unioned[e.a] && unioned[e.b] ? 0 : e.w), 0);
      ctx.font = '24px Arial';
      ctx.fillStyle = 'black';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      ctx.fillText(`Accepted edges: ${acceptedEdges}`, 10, 10);
      ctx.textAlign = 'right';
      ctx.fillText(`Total weight: ${totalWeight.toFixed(2)}`, c.width - 10, 10);

      requestAnimationFrame(draw);
    }

    function unionFind(component, a, b) {
      if (component[a] == component[b]) return false;
      const rootA = findRoot(component, a), rootB = findRoot(component, b);
      if (rootA != rootB) {
        component[rootA] = rootB;
        return true;
      } else {
        return false;
      }
    }

    function findRoot(component, x) {
      let y = x;
      while (!unioned[y]) y = component[y];
      while (y != component[x]) {
        const temp = component[x];
        component[x] = y;
        x = temp;
      }
      return y;
    }

    function init() {
      sortEdges();
      idx = 0;
      unioned.fill(false);
      draw();
    }

    init();
  </script>
</body>
</html>
```
mistral-small:24bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kruskal's Algorithm Animation</title>
<style>
body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #f0f0f0; }
canvas { border: 1px solid black; }
#info { position: absolute; top: 20px; left: 20px; font-family: Arial, sans-serif; color: black; }
</style>
</head>
<body>
<canvas id="graphCanvas" width="800" height="600"></canvas>
<div id="info">Edges accepted: 0<br>Total weight: 0</div>

<script>
const canvas = document.getElementById('graphCanvas');
const ctx = canvas.getContext('2d');
const nodes = Array.from({ length: 25 }, () => [Math.random() * canvas.width, Math.random() * canvas.height]);
const edges = [];
for (let i = 0; i < nodes.length; i++) {
    for (let j = i + 1; j < nodes.length; j++) {
        const weight = Math.sqrt(Math.pow(nodes[i][0] - nodes[j][0], 2) + Math.pow(nodes[i][1] - nodes[j][1], 2));
        edges.push({ start: i, end: j, weight });
    }
}
edges.sort((a, b) => a.weight - b.weight);

const parent = Array.from({ length: nodes.length }, (_, i) => i);
const rank = Array.from({ length: nodes.length }, () => 0);
function find(x) {
    if (parent[x] !== x) parent[x] = find(parent[x]);
    return parent[x];
}
function union(x, y) {
    const rootX = find(x);
    const rootY = find(y);
    if (rootX !== rootY) {
        if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
        else if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
        else { parent[rootY] = rootX; rank[rootX]++;
    }
}
let acceptedEdges = 0;
let totalWeight = 0;

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (const node of nodes) {
        const component = find(node[2]);
        ctx.fillStyle = `hsl(${component * 360 / nodes.length}, 100%, 50%)`;
        ctx.beginPath();
        ctx.arc(...node.slice(0, 2), 5, 0, 2 * Math.PI);
        ctx.fill();
    }
    for (const edge of edges) {
        const componentStart = find(nodes[edge.start][2]);
        const componentEnd = find(nodes[edge.end][2]);
        if (componentStart === componentEnd) continue;
        if (find(edge.start) !== find(edge.end)) {
            ctx.strokeStyle = 'green';
            union(edge.start, edge.end);
            acceptedEdges++;
            totalWeight += edge.weight;
        } else {
            ctx.strokeStyle = 'red';
        }
        ctx.beginPath();
        ctx.moveTo(...nodes[edge.start]);
        ctx.lineTo(...nodes[edge.end]);
        ctx.stroke();
    }
    document.getElementById('info').innerHTML = `Edges accepted: ${acceptedEdges}<br>Total weight: ${totalWeight.toFixed(2)}`;
}

function animate(i) {
    if (i < edges.length) {
        requestAnimationFrame(() => animate(i + 1));
        draw();
    } else {
        setTimeout(() => window.location.reload(), 3000);
    }
}
animate(0);

</script>
</body>
</html>
```
deepseek-r1:14bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f0f0">
<canvas id=c width=600 height=400></canvas>
<div id=stats style="position:absolute;top:left;left:0;padding:10px;color:black;"></div>
<script>
let c=document.getElementById('c'), ctx=c.getContext('2d');
let stats=document.getElementById('stats');
const nodes=[],edges=[],nodeCount=25,nodeSize=4;
let totalWeight=0,edgeAdded=0;

function generateNodes(){for(let i=0;i<nodeCount;i++){nodes.push({x:Math.random()*c.width,y:Math.random()*c.height,id:i});}}

function createEdges(){nodes.forEach((a,i)=>nodes.slice(i+1).forEach(b=>{let d=Math.hypot(a.x-b.x,a.y-b.y);edges.push({a:i,b:j,d:d,weight:d})}));}

function sortEdges(){edges.sort((a,b)=>a.weight - b.weight);}

class UnionFind{
  constructor(s){this.parent=s;this.rank=new Array(s).fill(1);}
  find(u){return this.parent[u] !==u ? (this.parent[u]=this.find(this.parent[u])) : u;}
  union(u,v){let x=this.find(u),y=this.find(v);if(x===y)return false;if(this.rank[x]<this.rank[y])this.parent[x]=y;else{this.parent[y]=x;if(this.rank[x]===this.rank[y])this.rank[x]++;}return true;}
}

function draw(){ctx.clearRect(0,0,c.width,c.height);nodes.forEach(n=>{let r=this uf.find(n.id)%15; ctx.fillStyle=`hsl(${r*24},${n.x>300?85:65}%,${(n.x>300?70:30)}%)`; ctx.beginPath();ctx.arc(n.x,n.y,nodeSize,0,Math.PI*2);ctx.fill();});edges.forEach(e=>{if(uf.find(e.a)===uf.find(e.b)){let g=e.a^e.b; ctx.strokeStyle='rgba(149,58,153,.5)';}else{let r=e.a^e.b; ctx.strokeStyle= (r?((r&-r>>1)+ (r>>2 ? 16 :0)):'red');}ctx.beginPath();ctx.moveTo(nodes[e.a].x,nodes[e.a].y);ctx.lineTo(nodes[e.b].x,nodes[e.b].y);ctx.stroke();});stats.textContent=`Edges accepted: ${edgeAdded} | Total weight: ${(totalWeight.toFixed(2))}`.replace(/,/,'.');}

let uf=new UnionFind(nodeCount),done=false;
generateNodes();
createEdges();
sortEdges();

function animate(timestamp){if(!done){if(edges.length>0){let e=edges.shift();if(uf.union(e.a,e.b)){ctx.strokeStyle='green';edgeAdded++;totalWeight+=e.d;}else{ctx.strokeStyle='red';}drawEdge(e, () => requestAnimationFrame(animate));}else done=true;stats.textContent+=' | DONE!';}
requestAnimationFrame(animate);
</script>
</body></html>
```
qwen3-coder:30bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Kruskal's MST Animation</title>
    <style>body {margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #222; color: white; font-family: sans-serif;}#canvasContainer {display: flex; gap: 20px;}canvas {border: 1px solid #444; background-color: #111;}</style>
</head>
<body>

<div id="canvasContainer">
    <canvas id="mstCanvas" width="800" height="600"></canvas>
</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
    <div>Accepted Edges: <span id="acceptedCount">0</span> | Total Weight: <span id="totalWeight">0.00</span></div>
    <button onclick="initializeGraph()">Restart</button>
</div>

<script>
let canvas, ctx;
const N = 25;
let nodes = [];
let edges = [];
let parent, rank;
let acceptedEdges = new Set();
let totalWeight = 0;
let edgeIndex = 0;

function initializeGraph() {
    canvas = document.getElementById('mstCanvas');
    ctx = canvas.getContext('2d');
    nodes = [];
    edges = [];
    acceptedEdges.clear();
    totalWeight = 0;
    edgeIndex = 0;

    for (let i = 0; i < N; i++) {
        nodes.push({ x: Math.random() * (canvas.width - 100) + 50, y: Math.random() * (canvas.height - 100) + 50 });
    }

    for (let i = 0; i < N; i++) {
        for (let j = i + 1; j < N; j++) {
            let dist = Math.sqrt(Math.pow(nodes[i].x - nodes[j].x, 2) + Math.pow(nodes[i].y - nodes[j].y, 2));
            edges.push({ u: i, v: j, weight: dist });
        }
    }

    edges.sort((a, b) => a.weight - b.weight);

    parent = Array.from({ length: N }, (_, i) => i);
    rank = new Array(N).fill(0);

    function find(i) {
        if (parent[i] === i) return i;
        return parent[i] = find(parent[i]);
    }

    function union(i, j) {
        let rootI = find(i);
        let rootJ = find(j);
        if (rootI !== rootJ) {
            if (rank[rootI] < rank[rootJ]) {
                parent[rootI] = rootJ;
            } else if (rank[rootI] > rank[rootJ]) {
                parent[rootJ] = rootI;
            } else {
                parent[rootJ] = rootI;
                rank[rootI]++;
            }
            return true;
        }
        return false;
    }

    requestAnimationFrame(animate);
}

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

    // Draw Nodes and Components
    for (let i = 0; i < N; i++) {
        const root = find(i);
        let componentColor = `hsl(${root * 360 / N}, 70%, ${50 + Math.floor(Math.random() * 10)}%)`;
        ctx.fillStyle = componentColor;
        ctx.beginPath();
        ctx.arc(nodes[i].x, nodes[i].y, 8, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#FFF';
        ctx.lineWidth = 1;
        ctx.stroke();
    }

    // Draw Edges (All edges)
    for (let i = 0; i < N; i++) {
        for (let j = i + 1; j < N; j++) {
            const edge = edges[i * N / 2 + Math.floor(j - i) / N]; // Simplified indexing for drawing all pairs, not necessary here since we iterate over `edges` array directly later
        }
    }

    // Draw Current State Edges (The ones processed so far)
    for (let i = 0; i < edgeIndex; i++) {
        const e = edges[i];
        ctx.beginPath();
        ctx.moveTo(nodes[e.u].x, nodes[e.u].y);
        ctx.lineTo(nodes[e.v].x, nodes[e.v].y);

        if (acceptedEdges.has(`${e.u}-${e.v}`)) {
            ctx.strokeStyle = '#4CAF50'; // Green
            ctx.lineWidth = 2;
        } else if (acceptedEdges.has(`${e.v}-${e.u}`)) {
             ctx.strokeStyle = '#4CAF50'; // Should not happen in set key
             ctx.lineWidth = 2;
        } else if (i < edgeIndex) {
            // Red if it was considered but rejected
             ctx.strokeStyle = '#F44336'; // Red
             ctx.lineWidth = 1;
        } else {
            continue;
        }

        ctx.stroke();
    }
}

function animate() {
    if (edgeIndex < edges.length && acceptedEdges.size < N - 1) {
        const e = edges[edgeIndex];
        let accepted = union(e.u, e.v);

        // Update Visualization and State
        if (accepted) {
            acceptedEdges.add(`${Math.min(e.u, e.v)}-${Math.max(e.u, e.v)}`);
            totalWeight += e.weight;
        } else {
             // Mark rejected edges briefly (handled by drawing logic or persistent red glow)
        }

        document.getElementById('acceptedCount').textContent = acceptedEdges.size;
        document.getElementById('totalWeight').textContent = totalWeight.toFixed(2);
        drawGraph();
        edgeIndex++;

        requestAnimationFrame(animate);
    } else {
        // Animation finished or loop condition met (The prompt implies running until complete)
        if (acceptedEdges.size === N - 1 && edgeIndex >= edges.length / 2 + 50) { // Heuristic check for completion
             setTimeout(() => {
                initializeGraph();
            }, 2000);
        }
    }
}

window.onload = initializeGraph;
</script>

</body>
</html>
```
qwen3:14bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Kruskal's MST</title>
    <style>body {margin:0;overflow:hidden;background:#111;}canvas {display:block;background:#222;}</style>
</head>
<body>
<canvas id="mstCanvas"></canvas>
<script>
const canvas = document.getElementById('mstCanvas');
const ctx = canvas.getContext('2d');

let N = 25;
let nodes, edges, parent, rank;
let acceptedCount = 0;
let totalWeight = 0;
let activeEdges = [];
let animationFrameId = null;

canvas.width = window.innerWidth;
canvas.height = window.innerHeight - 30;

function generateGraph() {
    nodes = Array(N).fill(0).map((_, i) => ({ x: Math.random() * canvas.width, y: Math.random() * canvas.height }));
    let edgeSet = new Set();
    for (let i = 0; i < N; i++) {
        for (let j = i + 1; j < N; j++) {
            if (Math.random() < 0.7) { // Adjust density
                const key = [i, j].sort().join('-');
                if (!edgeSet.has(key)) {
                    let weight = Math.floor(Math.random() * 10) + 1;
                    edges.push({ u: i, v: j, weight: weight });
                    edgeSet.add(key);
                }
            }
        }
    }
    edges.sort((a, b) => a.weight - b.weight);
    activeEdges = [...edges];
}

function makeSets() {
    parent = Array(N).fill(0).map((_, i) => i);
    rank = Array(N).fill(0);
}

function findSet(i) {
    if (parent[i] === i) return i;
    return parent[i] = findSet(parent[i]);
}

function unionSets(i, j) {
    let rootI = findSet(i);
    let rootJ = findSet(j);

    if (rootI !== rootJ) {
        if (rank[rootI] < rank[rootJ]) {
            parent[rootI] = rootJ;
        } else if (rank[rootI] > rank[rootJ]) {
            parent[rootJ] = rootI;
        } else {
            parent[rootJ] = rootI;
            rank[rootI]++;
        }
        return true; // Successful union
    }
    return false; // Cycle detected
}

function drawGraph(edge, color) {
    ctx.beginPath();
    ctx.moveTo(nodes[edge.u].x, nodes[edge.u].y);
    ctx.lineTo(nodes[edge.v].x, nodes[edge.v].y);
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.stroke();

    // Draw weight text
    const midX = (nodes[edge.u].x + nodes[edge.v].x) / 2;
    const midY = (nodes[edge.u].y + nodes[edge.v].y) / 2 - 10;
    ctx.fillStyle = '#fff';
    ctx.font = '14px Arial';
    ctx.fillText(edge.weight, midX - 5, midY);
}

function drawComponents() {
    // Reset background for component drawing
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    nodes.forEach((node, i) => {
        let root = findSet(i);
        const components = {};
        for (let j = 0; j < N; j++) {
            const r = findSet(j);
            components[r] = components[r] || [];
            components[r].push(j);
        }

        // Simple coloring based on component root index for visualization
        const componentColor = `hsl(${root * 360 / N}, 70%, ${50 + (i % 4) * 10}%)`;

        ctx.fillStyle = componentColor;
        ctx.beginPath();
        ctx.arc(node.x, node.y, 8, 0, Math.PI * 2);
        ctx.fill();

        // Draw nodes and labels on top of color fill
        ctx.strokeStyle = '#fff';
        ctx.lineWidth = 1;
        ctx.stroke();

        ctx.fillStyle = '#fff';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(i, node.x, node.y);
    });
}

let currentEdgeIndex = 0;

function stepSimulation() {
    if (!activeEdges || activeEdges.length === 0) return null;

    const edge = activeEdges[currentEdgeIndex];
    if (!edge) {
        return false; // Finished processing all edges
    }

    let accepted = unionSets(edge.u, edge.v);
    let color = accepted ? 'lime' : 'red';

    // Draw the processed edge for this step (temporarily over component coloring)
    drawGraph(edge, color);

    if (accepted) {
        acceptedCount++;
        totalWeight += edge.weight;
    }

    currentEdgeIndex++;
    return true; // Still running
}

function animate() {
    // 1. Draw Components first (shows merging state based on UFS array)
    drawComponents();
    
    // 2. Calculate and draw the current edge being processed
    let finished = stepSimulation();

    if (!finished) {
        // If all edges are processed, restart the simulation
        generateGraph();
        acceptedCount = 0;
        totalWeight = 0;
        currentEdgeIndex = 0;
        makeSets();
        document.getElementById('stats').innerHTML = `Finished! MST Weight: ${totalWeight}. Click to Restart.`;
        return;
    }

    // Update stats display
    let statusText = `Accepted Edges: ${acceptedCount} / ${Math.min(N - 1, activeEdges.length)} | Total Weight: ${totalWeight}`;
    if (currentEdgeIndex < activeEdges.length) {
        statusText += `<br>Processing Edge (${activeEdges[currentEdgeIndex].u}-${activeEdges[currentEdgeIndex].v}) W:${activeEdges[currentEdgeIndex].weight} ...`;
    } else {
         statusText = `Finished! MST Weight: ${totalWeight}. Restarting...`;
    }

    document.getElementById('stats').innerHTML = statusText;


    animationFrameId = requestAnimationFrame(animate);
}

// Setup UI and initial run
document.body.insertAdjacentHTML('beforeend', '<div id="stats" style="position: fixed; bottom: 10px; left: 10px; color:#fff; background:rgba(0,0,0,0.7); padding:10px; font-family:monospace;">Initializing Graph...</div>');

function startSimulation() {
    if (animationFrameId) cancelAnimationFrame(animationFrameId);
    generateGraph();
    acceptedCount = 0;
    totalWeight = 0;
    currentEdgeIndex = 0;
    makeSets();
    animate();
}

startSimulation();
</script>
</body>
</html>
```
gemma4:26bharness
No answer reached us (THIN) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget (3 min 28 s, zero characters of answer); larger budgets are refused by the gateway Counted as a did-not-finish, but it is not evidence about this model.
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
deepseek-r1:32bERROKFORMAT
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 c=document.getElementById('c'),w=800,h=600;
let nodes=[],edges=[],count=0,total=0,frame=0,running=true;n=c.getContext('2d');
n.fillStyle='#000';n.fillRect(0,0,w,h);

function initNodes(){
nodes.length=0;for(let i=0;i<25;++i){nodes.push({x:Math.random()*w,y:Math.random()*h,c:i})}
}

function initEdges(){edges=[];count=total=0;n.fillStyle='#fff';n.font='18px Arial';
for(let i=0;i<nodes.length;++i)for(let j=i+1;j<nodes.length;++j){
const dx=nodes[i].x-nodes[j].x,dy=nodes[i].y-nodes[j].y,d=Math.sqrt(dx*dx+dy*dy);
edges.push({u:i,v:j,w:d})
}edges.sort((a,b)=>b.w - a.w)}

initNodes();initEdges();

function drawNode(n,x,y,c){
n.beginPath();n.arc(x,y,5,0,2*Math.PI);n.fillStyle=`hsl(${c*10},70%,60%)`;n.fill();
}

function drawEdge(n,u,v,accept){
const x1=nodes[u].x,y1=nodes[u].y,x2=nodes[v].x,y2=nodes[v].y;
n.beginPath();n.moveTo(x1,y1);n.lineTo(x2,y2);
n.strokeStyle=accept?'lime':'red';n.stroke();
}

function find(u){while(nodes[u].c!=u)u=nodes[nodes[u].c];return u}
function union(u,v){if(find(u)!=find(v))nodes[find(u)].c=find(v)}

function draw(){
requestAnimationFrame(draw);
if(frame>=edges.length){
running=false;frame=0;initNodes();initEdges();}
else if(running){
const e=edges[frame++],u=e.u,v=e.v;
drawNode(n,nodes[u].x,nodes[u].y,find(u));
drawNode(n,nodes[v].x,nodes[v].y,find(v));
if(find(u)!=find(v)){
union(u,v);count++;total+=e.w;
n.fillStyle='lime';drawEdge(n,u,v,true);
}else{
n.strokeStyle='red';drawEdge(n,u,v,false);
}
n.fillStyle='#fff';n.fillText(`Edges: ${count}/24 Total: ${total.toFixed(2)}`,10,20);
}}
draw();
</script>
</body></html>
```
qwq:32bSTAT
Show code
<think>

Alright, I need to create a self-contained HTML file that animates Kruskal's algorithm on a random graph with 25 nodes. Let me start by breaking down the requirements.

First, the HTML must be self-contained with a canvas element. The animation should use requestAnimationFrame for smoothness, no user input, and restart automatically. The graph has 25 nodes, so I need to represent them somehow, maybe by placing them randomly on the canvas.

Kruskal's algorithm requires edges sorted by weight. Each edge should be added one at a time. When an edge is accepted (doesn't form a cycle), it turns green and connects components. If rejected (creates a cycle), it turns red. Nodes should be tinted by their component, showing merges.

I'll start by setting up the canvas and some variables. The nodes need positions, so maybe an array of objects with x and y coordinates. Then edges can be pairs of nodes with a random weight. The edges need to be sorted by weight initially.

For the union-find (disjoint-set) structure, I'll need parent and rank arrays. Functions like find and union will be essential. Each time an edge is processed, we check if the nodes are in different sets. If yes, unite them and add the edge's weight to the total. Otherwise, mark it as rejected.

The animation loop will handle drawing. Nodes should be colored based on their component, so maybe using the component's ID (from the root in union-find) to determine the color. Edges need to be drawn in green or red as they're processed.

Let me structure the code step by step. The canvas setup with the maximum size, then functions to initialize nodes, edges, and the union-find structure. The animation loop will process the next edge at each step, update the state, and redraw everything.

Wait, how to handle the animation timing? Since requestAnimationFrame is used, each frame can process one edge, but I need to control the pace. Maybe a timer or a counter to step through edges every few frames. Alternatively, increment a counter in each frame until all edges are processed, then restart.

Also, the nodes' colors: each component can have a unique color. The root of the component can determine the color. Maybe using HSL where the hue is based on the component's root index. But need to ensure colors are distinct. Alternatively, use a hash of the root's ID to a color.

For the edges, when adding them, if accepted (green), else red. The edges array can have a status (accepted/rejected) and color.

Let me outline the variables:

- nodes: array of objects with x, y, component (initially self)
- edges: array of objects with node1, node2, weight, color (initially undefined, then green/red)
- sortedEdges: edges sorted by weight
- currentEdgeIndex: which edge is being processed

Union-find functions:

function find(node) { ... }
function union(a, b) { ... }

In the animation loop:

- If currentEdgeIndex < edges.length:
   - Get the next edge, check if same component. If not, unite and set color green, add to total weight. Else, set to red.
   - Update nodes' component colors (maybe by root?) but how to track component colors?
   Alternatively, for each node, its color is based on its root's ID. So when drawing, for each node, find the root and use that to determine color.

Wait, each node's color can be determined by the root of its set. So for each node, get the root via find(), then map that root's index to a color. So maybe store a color for each root. Each time a new root is formed, assign a new color.

Hmm, but the roots can change as unions happen. So perhaps whenever a node's root changes, we need to update its color. Maybe each node has a color property, but that might be redundant. Alternatively, compute the color each frame based on the root.

To simplify, when drawing nodes, for each node, find its root, and then use a predefined color palette based on the root's index. But the roots can be any of the nodes, so maybe using the root's index in the nodes array as a seed for a color.

Alternatively, use the root's index modulo some number to pick a color from a predefined array. For example, colors = ['#FF0000', '#00FF00', ...], but with 25 nodes, there might be too many, so maybe use HSL where the hue is based on the root's index.

Let me think of how to represent the components' colors. For each node, when drawing, compute the root via find(), then use the root's index (or some hash) to get a color. Using HSL, the hue can be (rootIndex * 15) % 360 to cycle through colors. That way, different roots have different hues.

So in the drawing function:

for each node in nodes:
   root = find(node)
   hue = (root.id * 15) % 360
   color = 'hsl(' + hue + ',50%,50%)'
   draw the node with that color.

Wait, but how to track the root's ID? Maybe each node has a parent in the union-find, so the root is the find(node). To get the root's index, since nodes are stored in an array, the index can be retrieved by searching? That's inefficient. Alternatively, each node has an index property.

Yes, let's have each node have an index from 0 to 24. Then when finding the root, we can get its index via the root node's index property.

So nodes array is initialized with {x, y, index: i, parent: this, rank:1}, then in find function, the parent is tracked. Wait, but in union-find, typically each node has a parent pointer. Maybe the parent array is separate.

Wait, perhaps better to have a parent array where parent[i] is the parent of node i. Since nodes are in an array from 0 to 24, the index is their identifier.

So, let me restructure:

nodes array is just an array of objects with x, y, index. The parent array and rank array are separate arrays with length 25.

Initialize parent[i] = i, rank[i] = 1.

Then find function:

function find(u) {
   if (parent[u] != u) parent[u] = find(parent[u]);
   return parent[u];
}

Union function:

function union(u, v) {
   const rootU = find(u);
   const rootV = find(v);
   if (rootU === rootV) return false;
   if (rank[rootU] > rank[rootV]) parent[rootV] = rootU;
   else {
      parent[rootU] = rootV;
      if (rank[rootU] === rank[rootV]) rank[rootV]++;
   }
   return true;
}

Edges are created between every pair of nodes? Wait, no. The problem says it's a graph, so edges can be between any nodes, but to make it manageable, perhaps connect each node to a random number of other nodes? Or better, generate all possible edges but that might be too many. Wait, the problem says "random graph", but doesn't specify density. To keep it simple, perhaps generate edges between random pairs with some probability, but need to ensure that the graph is connected? Not sure, but maybe just create all possible edges, but that's 25*24/2=300 edges,
command-r:35bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      /* CSS styles for the canvas and nodes */
      #myCanvas {
        border: 1px solid black;
        width: 500px;
        height: 500px;
      }

      .nodeCircle {
        fill: white;
        stroke: gray;
        stroke-width: 2;
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <!-- HTML canvas element for drawing the graph -->
    <canvas id="myCanvas"></canvas>

    <script>
      /* Global variables, functions and classes */

      class UnionFind {
        constructor(N) {
          this.parent = Array.from({ length: N }, (_, i) => i);
          this.rank = new Uint8Array(N);
        }

        find(x) {
          if (this.parent[x] !== x) this.parent[x] = this.find(this.parent[x]);
          return this.parent[x];
        }

        union(x, y) {
          const xr = this.find(x),
            yr = this.find(y);

          if (xr === yr) return;

          // Union by rank
          if (this.rank[yr] > this.rank[xr]) [xr, yr] = [yr, xr];

          this.parent[yr] = xr;
          if (this.rank[xr] === this.rank[yr]) this.rank[xr]++;
        }

        isConnected(x, y) {
          return this.find(x) === this.find(y);
        }
      }

      const getRandomColor = () => `hsl(${Math.random() * 360},50%,80%)`;
      const drawNode = (ctx, x, y, color) => ctx.fillStyle = color;
      const drawEdge = (ctx, x1, y1, x2, y2, isMST) => {
        ctx.strokeStyle = isMST ? 'green' : 'red';
        return ctx.lineTo(x2, y2);
      }

      /* Animation loop */
      
      let nodes, edges; // Global graph data structures
      const canvas = document.getElementById('myCanvas');
      const ctx = canvas.getContext('2d');
      const uf = new UnionFind(nodes.length);

      function animate() {
        requestAnimationFrame(animate);

        edges.sort((a, b) => a[0] - b[0]); // Sort by weight

        for (let i=edges.length; i--;) {
          let [weight, x1, y1, x2, y2] = edges[i];

          drawEdge(ctx, nodes[x1][0], nodes[x1][1], nodes[x2][0], nodes[x2][1], !uf.isConnected(x1, x2));
          uf.union(x1, x2);
        }
      };

      animate();
    </script>
  </body>
</html>
```
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
GPT-5.5 (Codex)frontier referenceOK
Show code
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kruskal MST</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#111;color:#eee;font:16px system-ui}
canvas{display:block;width:100vw;height:100vh}
#hud{position:fixed;left:16px;top:12px;background:#0009;padding:10px 14px;border-radius:8px;line-height:1.45}
</style>
</head>
<body>
<canvas id="c"></canvas><div id="hud"></div>
<script>
const c=document.getElementById("c"),x=c.getContext("2d"),hud=document.getElementById("hud");
let W,H,n=25,nodes,edges,seen,parent,rank,accepted,total,step,last,wait,doneAt;
const colors=["#4cc9f0","#f72585","#b8f34a","#ffd166","#a78bfa","#06d6a0","#f77f00","#90be6d","#ff6b6b","#00bbf9","#e9c46a","#b5179e","#43aa8b","#f94144","#577590","#caffbf","#fb8500","#80ed99","#bde0fe","#ffafcc","#cdb4db","#2ec4b6","#f4a261","#e76f51","#8ecae6"];
function resize(){c.width=innerWidth*devicePixelRatio;c.height=innerHeight*devicePixelRatio;x.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);W=innerWidth;H=innerHeight}
addEventListener("resize",resize);resize();
function find(a){while(parent[a]!=a){parent[a]=parent[parent[a]];a=parent[a]}return a}
function unite(a,b){a=find(a);b=find(b);if(a==b)return false;if(rank[a]<rank[b])[a,b]=[b,a];parent[b]=a;if(rank[a]==rank[b])rank[a]++;return true}
function compColors(){let map={},k=0;return nodes.map((_,i)=>{let r=find(i);if(map[r]==null)map[r]=k++;return colors[map[r]%colors.length]})}
function fresh(){
 let m=Math.min(W,H),cx=W/2,cy=H/2;nodes=[];edges=[];seen=[];parent=[];rank=[];accepted=0;total=0;step=0;last=0;wait=360;doneAt=0;
 for(let i=0;i<n;i++){let a=i/n*Math.PI*2+Math.random()*.35,r=m*.32+Math.random()*m*.11;nodes.push({x:cx+Math.cos(a)*r,y:cy+Math.sin(a)*r})}
 for(let i=0;i<n;i++){parent[i]=i;rank[i]=0;for(let j=i+1;j<n;j++)if(Math.random()<.16)edges.push(edge(i,j))}
 for(let i=1;i<n;i++)edges.push(edge(i-1,i));
 edges.sort((a,b)=>a.w-b.w);
}
function edge(a,b){let A=nodes[a],B=nodes[b],d=Math.hypot(A.x-B.x,A.y-B.y);return{a,b,w:Math.round(d/8+Math.random()*18)}}
function advance(t){
 if(accepted==n-1||step>=edges.length){doneAt=doneAt||t;if(t-doneAt>1500)fresh();return}
 let e=edges[step++],ok=unite(e.a,e.b);e.ok=ok;seen.push(e);if(ok){accepted++;total+=e.w}
}
function draw(t){
 x.clearRect(0,0,W,H);
 x.fillStyle="#111";x.fillRect(0,0,W,H);
 x.lineCap="round";
 for(const e of edges){let A=nodes[e.a],B=nodes[e.b];x.strokeStyle="#333";x.lineWidth=1;x.beginPath();x.moveTo(A.x,A.y);x.lineTo(B.x,B.y);x.stroke()}
 for(const e of seen){let A=nodes[e.a],B=nodes[e.b];x.strokeStyle=e.ok?"#31d158":"#ff453a";x.lineWidth=e.ok?4:2;x.beginPath();x.moveTo(A.x,A.y);x.lineTo(B.x,B.y);x.stroke()}
 let cc=compColors();
 nodes.forEach((p,i)=>{x.beginPath();x.fillStyle=cc[i];x.strokeStyle="#fff";x.lineWidth=2;x.arc(p.x,p.y,11,0,7);x.fill();x.stroke()});
 let e=edges[Math.min(step,edges.length-1)];
 hud.innerHTML=`Kruskal MST<br>edges accepted: ${accepted}/${n-1}<br>total weight: ${total}<br>next weight: ${e?e.w:"-"}`;
}
function loop(t){if(!last)last=t;if(t-last>wait){advance(t);last=t}draw(t);requestAnimationFrame(loop)}
fresh();requestAnimationFrame(loop);
</script>
</body>
</html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Kruskal MST</title>
<style>
*{margin:0;padding:0}
html,body{background:#0b0f14;overflow:hidden}
canvas{display:block}
</style>
</head><body>
<canvas id="c"></canvas>
<script>
(function(){
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 nodes,edges,parent,rankArr,edgeIdx,accepted,totalW,tick,doneTimer;

function dist(a,b){return Math.hypot(a.x-b.x,a.y-b.y);}

function find(i){
  while(parent[i]!==i){parent[i]=parent[parent[i]];i=parent[i];}
  return i;
}
function union(a,b){
  a=find(a);b=find(b);
  if(a===b)return false;
  if(rankArr[a]<rankArr[b]){const t=a;a=b;b=t;}
  parent[b]=a;
  if(rankArr[a]===rankArr[b])rankArr[a]++;
  return true;
}

function hue(i){const h=(i*2654435761)%360;return h<0?h+360:h;}

function init(){
  const n=25;
  nodes=[];
  for(let i=0;i<n;i++){
    nodes.push({x:60+Math.random()*(W-120),y:60+Math.random()*(H-120)});
  }
  edges=[];
  for(let i=0;i<n;i++){
    for(let j=i+1;j<n;j++){
      edges.push({a:i,b:j,w:dist(nodes[i],nodes[j]),state:0});
    }
  }
  edges.sort((p,q)=>p.w-q.w);
  parent=nodes.map((_,i)=>i);
  rankArr=nodes.map(()=>0);
  edgeIdx=0;accepted=0;totalW=0;tick=0;doneTimer=0;
}
init();

function step(){
  if(edgeIdx<edges.length && accepted<nodes.length-1){
    const e=edges[edgeIdx++];
    if(union(e.a,e.b)){e.state=1;accepted++;totalW+=e.w;}
    else{e.state=2;}
  } else {
    doneTimer++;
    if(doneTimer>150)init();
  }
}

function draw(){
  ctx.fillStyle='#0b0f14';ctx.fillRect(0,0,W,H);
  for(const e of edges){
    if(e.state===0)continue;
    ctx.strokeStyle=e.state===1?'#3ddc73':'rgba(255,80,80,0.35)';
    ctx.lineWidth=e.state===1?2.5:1;
    ctx.beginPath();
    ctx.moveTo(nodes[e.a].x,nodes[e.a].y);
    ctx.lineTo(nodes[e.b].x,nodes[e.b].y);
    ctx.stroke();
  }
  for(let i=0;i<nodes.length;i++){
    const r=find(i);
    ctx.beginPath();
    ctx.fillStyle='hsl('+hue(r)+',70%,55%)';
    ctx.arc(nodes[i].x,nodes[i].y,8,0,Math.PI*2);
    ctx.fill();
    ctx.strokeStyle='#0b0f14';
    ctx.lineWidth=1.5;
    ctx.stroke();
  }
  ctx.fillStyle='#e8eef4';
  ctx.font='16px monospace';
  ctx.fillText('Accepted edges: '+accepted+'/'+(nodes.length-1),16,28);
  ctx.fillText('Total weight: '+totalW.toFixed(1),16,50);
}

function loop(){
  tick++;
  if(tick%2===0)step();
  draw();
  requestAnimationFrame(loop);
}
loop();
})();
</script>
</body></html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Kruskal's MST</title>
<style>
  body { margin: 0; padding: 10px; font-family: monospace; background: #000; color: #0f0; }
  canvas { display: block; margin: 10px auto; background: #111; border: 1px solid #0f0; }
  div { text-align: center; font-size: 12px; }
</style>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<div id="info"></div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const w = canvas.width, h = canvas.height;

const n = 25;
const nodes = [];
const edges = [];

for (let i = 0; i < n; i++) {
  nodes[i] = {
    x: Math.random() * (w - 100) + 50,
    y: Math.random() * (h - 100) + 50,
    comp: i
  };
}

for (let i = 0; i < n; i++) {
  for (let j = i + 1; j < n; j++) {
    const dx = nodes[j].x - nodes[i].x;
    const dy = nodes[j].y - nodes[i].y;
    const w = Math.sqrt(dx * dx + dy * dy);
    edges.push({ i, j, w, color: '#666' });
  }
}

edges.sort((a, b) => a.w - b.w);

let parent = Array(n).fill(0).map((_, i) => i);
let step = 0;
let mstWeight = 0;
let accepted = 0;

function find(x) {
  if (parent[x] !== x) parent[x] = find(parent[x]);
  return parent[x];
}

function union(x, y) {
  parent[find(x)] = find(y);
}

function animate() {
  if (step < edges.length) {
    const edge = edges[step];
    const rx = find(edge.i);
    const ry = find(edge.j);
    if (rx !== ry) {
      union(edge.i, edge.j);
      edge.color = '#0f0';
      mstWeight += edge.w;
      accepted++;
    } else {
      edge.color = '#f00';
    }
    step++;
  }

  ctx.fillStyle = '#111';
  ctx.fillRect(0, 0, w, h);

  for (let e of edges) {
    ctx.strokeStyle = e.color;
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(nodes[e.i].x, nodes[e.i].y);
    ctx.lineTo(nodes[e.j].x, nodes[e.j].y);
    ctx.stroke();
  }

  const compColors = {};
  for (let i = 0; i < n; i++) {
    const c = find(i);
    if (!compColors[c]) compColors[c] = `hsl(${Math.random() * 360}, 100%, 50%)`;
    ctx.fillStyle = compColors[c];
    ctx.beginPath();
    ctx.arc(nodes[i].x, nodes[i].y, 6, 0, 2 * Math.PI);
    ctx.fill();
  }

  ctx.fillStyle = '#0f0';
  ctx.font = '12px monospace';
  ctx.fillText('Edges: ' + step + ' | Accepted: ' + accepted + ' | Weight: ' + Math.round(mstWeight), 20, 30);

  requestAnimationFrame(animate);
}

animate();
</script>
</body>
</html>
Results at a glance— every model in this round with score, response time and status.

Who wrote what

ModelBlind scoreLatencyStatus
qwen3.5:9b —21.1 sOK
llama3.1:8b —8.9 sOK
mistral-small:24b —204.2 sOK
deepseek-r1:14b —118.9 sOK
qwen3-coder:30b —26.9 sOK
qwen3:14b —25.7 sOK
gemma4:26b ——harness
qwen3:8b ——harness
deepseek-r1:32b —147.4 sOK
qwq:32b —148.8 sOK
command-r:35b —125.2 sOK

Frontier reference — outside the blind field

These models answered the identical prompt outside the blind field. They are deliberately not scored and not part of the blind comparison — they are there so you can see where a frontier model lands on the same task.

Collected differently from the blind field: the local models run through a plain API harness, the frontier references through a context-free agent, one shot, no retries. Response times are therefore not comparable and are not shown. On two English tasks the reference answer came back in German — an artefact of that harness, flagged on the affected rounds.
Claude Opus 5frontier reference
Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kruskal's MST</title>
<style>
html,body{margin:0;height:100%;background:#080b11;overflow:hidden}
canvas{display:block}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d'),N=25,D=115;
let w,h,dpr,nodes,edges,par,siz,hue,idx,acc,tot,t0,done;
const ease=p=>p*p*(3-2*p),la=(a,b,k)=>a+((((b-a)%360)+540)%360-180)*k;
function resize(){dpr=Math.min(devicePixelRatio||1,2);w=innerWidth;h=innerHeight;cv.width=w*dpr;cv.height=h*dpr;g.setTransform(dpr,0,0,dpr,0,0);}
function find(i){return par[i]===i?i:par[i]=find(par[i]);}
function gen(){
  const m=68,ty=Math.min(120,h*0.22);nodes=[];
  for(let i=0;i<N;i++){let p,k=0;
    do{p={x:m+Math.random()*Math.max(20,w-2*m),y:ty+Math.random()*Math.max(20,h-ty-m)};k++;}
    while(k<300&&nodes.some(q=>Math.hypot(q.x-p.x,q.y-p.y)<Math.min(w,h)*0.105));
    p.hu=i*360/N;p.th=p.hu;nodes.push(p);}
  const all=[];
  for(let i=0;i<N;i++)for(let j=i+1;j<N;j++)all.push({a:i,b:j,w:Math.round(Math.hypot(nodes[i].x-nodes[j].x,nodes[i].y-nodes[j].y)/6),s:0});
  all.sort((p,q)=>p.w-q.w||p.a-q.a);
  const up=[...Array(N).keys()],f=i=>up[i]===i?i:up[i]=f(up[i]),keep=new Set();
  for(const e of all){const ra=f(e.a),rb=f(e.b);if(ra!==rb){up[ra]=rb;keep.add(e.a+'-'+e.b);}}
  const lim=all[Math.min(all.length,70)-1].w;
  edges=all.filter(e=>e.w<=lim||keep.has(e.a+'-'+e.b));
  par=[...Array(N).keys()];siz=Array(N).fill(1);hue=nodes.map(n=>n.hu);
  idx=0;acc=0;tot=0;done=0;
}
function commit(){
  const e=edges[idx++],ra=find(e.a),rb=find(e.b);
  if(ra===rb){e.s=2;return;}
  let A=ra,B=rb;if(siz[A]<siz[B]){A=rb;B=ra;}
  par[B]=A;siz[A]+=siz[B];e.s=1;acc++;tot+=e.w;
}
function draw(p){
  g.fillStyle='#080b11';g.fillRect(0,0,w,h);
  g.lineCap='round';
  for(const e of edges){
    if(e.s===0)continue;
    const A=nodes[e.a],B=nodes[e.b];
    g.strokeStyle=e.s===1?'rgba(64,230,140,.9)':'rgba(255,72,96,.32)';
    g.lineWidth=e.s===1?2.6:1.2;
    if(e.s===1){g.shadowColor='rgba(64,230,140,.75)';g.shadowBlur=10;}
    g.beginPath();g.moveTo(A.x,A.y);g.lineTo(B.x,B.y);g.stroke();g.shadowBlur=0;
  }
  g.strokeStyle='rgba(150,180,220,.09)';g.lineWidth=1;
  for(let i=idx;i<edges.length;i++){const e=edges[i],A=nodes[e.a],B=nodes[e.b];
    g.beginPath();g.moveTo(A.x,A.y);g.lineTo(B.x,B.y);g.stroke();}
  if(idx<edges.length&&!done){
    const e=edges[idx],A=nodes[e.a],B=nodes[e.b],k=ease(p),cyc=find(e.a)===find(e.b);
    g.strokeStyle=cyc?'rgba(255,120,140,.95)':'rgba(255,255,255,.95)';g.lineWidth=3.4;
    g.shadowColor=cyc?'rgba(255,80,110,.8)':'rgba(255,255,255,.6)';g.shadowBlur=16;
    g.beginPath();g.moveTo(A.x,A.y);g.lineTo(A.x+(B.x-A.x)*k,A.y+(B.y-A.y)*k);g.stroke();g.shadowBlur=0;
    g.fillStyle='#fff';g.beginPath();g.arc(A.x+(B.x-A.x)*k,A.y+(B.y-A.y)*k,3.2,0,7);g.fill();
    g.font='12px ui-monospace,monospace';g.fillStyle='rgba(230,240,255,.85)';g.textAlign='center';
    g.fillText(e.w,(A.x+B.x)/2,(A.y+B.y)/2-8);
  }
  for(let i=0;i<N;i++){
    const n=nodes[i];n.th=hue[find(i)];n.hu=la(n.hu,n.th,.12);
    g.fillStyle='hsl('+n.hu.toFixed(0)+',72%,58%)';
    g.shadowColor='hsl('+n.hu.toFixed(0)+',80%,50%)';g.shadowBlur=14;
    g.beginPath();g.arc(n.x,n.y,9,0,7);g.fill();g.shadowBlur=0;
    g.strokeStyle='rgba(8,11,17,.9)';g.lineWidth=2;g.stroke();
  }
  const comps=new Set();for(let i=0;i<N;i++)comps.add(find(i));
  g.textAlign='left';
  g.font='600 20px ui-sans-serif,system-ui,sans-serif';g.fillStyle='#eaf2ff';
  g.fillText("Kruskal's Minimum Spanning Tree", 28, 40);
  g.font='13px ui-monospace,monospace';g.fillStyle='rgba(190,210,235,.85)';
  g.fillText('edge '+Math.min(idx+1,edges.length)+' / '+edges.length+'   nodes 25   components '+comps.size, 28, 62);
  g.fillStyle='rgba(64,230,140,.95)';g.fillText('accepted  '+acc+' / '+(N-1), 28, 84);
  g.fillStyle='rgba(255,200,120,.95)';g.fillText('total weight  '+tot, 190, 84);
  if(done){g.fillStyle='rgba(255,255,255,.75)';g.fillText('MST complete — restarting…', 400, 84);}
  const bw=Math.min(360,w-56);
  g.fillStyle='rgba(255,255,255,.08)';g.fillRect(28,96,bw,4);
  g.fillStyle='rgba(64,230,140,.8)';g.fillRect(28,96,bw*(idx/edges.length),4);
}
function loop(now){
  if(t0===undefined)t0=now;
  if(done){if(now-done>1700){gen();t0=now;}}
  else{
    while(now-t0>=D&&idx<edges.length){commit();t0+=D;}
    if(idx>=edges.length)done=now;
  }
  draw(idx<edges.length&&!done?Math.min(1,(now-t0)/D):0);
  requestAnimationFrame(loop);
}
addEventListener('resize',()=>{resize();gen();t0=undefined;});
resize();gen();requestAnimationFrame(loop);
</script>
</body>
</html>