Overview › Code › R62
Code · blind round

R62 · Huffman tree from letter frequencies

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 builds a Huffman code from the letter frequencies of a fixed sample sentence and animates it on a <canvas>. Show the priority queue of nodes, merge the two smallest nodes one step at a time, and draw the growing binary tree with its frequency labels. When the tree is complete, walk it to display the code word of each letter plus the total bits before and after compression. 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)
deepseek-r1:14bERRSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>Huffman Tree Animation</title></head><body style="margin:0;overflow:hidden;background:#f0f0f0;">
<canvas id="c" width=800 height=600></canvas>
<script>
let canvas=document.getElementById('c'), ctx=canvas.getContext('2d');
const sentence='The quick brown fox jumps over the lazy dog.';
const freq={};for(let c of sentence.toLowerCase())if(c.match(/[a-z]/))freq[c]=(freq[c]||0)+1;
const nodes=[];Object.entries(freq).forEach(([k,v])=>nodes.push({char:k,freq:v,left:null,right:null}));

function buildTree(nodes){
    while(nodes.length>1){
        nodes.sort((a,b)=>a.freq - b.freq);
        const left=nodes.shift();
        const right=nodes.shift();
        new Node({...{freq:left.freq+right.freq},left:right.right?right:{...right},right:left});
        nodes.push(new Node({freq:left.freq+right.freq,left,right}));
    }
}

function drawNodes(node,x,y,dx,dy){
    ctx.beginPath();ctx.arc(x,y,10,0,Math.PI*2);ctx.fillStyle='white';ctx.fill();
    if(!node.left&&!node.right){ctx.fillStyle='black';ctx.fillText(node.char.toUpperCase(),x-4,y+3)}
    else{const midX=x+(dx)/2;const midY=y+dy/2 -5;
        ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(midX,midY);
        drawNodes(node.left,midX-10, y+ dy/2,10, -dy/2);
        drawNodes(node.right,midX+10, y+ dy/2,10, -dy/2)}
}

function animate(tree){
    let time=0;let lastTime=time;
    function update(t){time+=t-lastTime;lastTime=t;
        ctx.clearRect(0,0,800,600);drawNodes(tree,400,300,150,Math.pow(2,-time/400));}
    requestAnimationFrame(update);
}

let tree=buildTree([...nodes]);animate(tree);

function Node(opts){return opts;}
</script>
</body></html>
```
mistral-small:24bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Huffman Code Animation</title>
    <style>
        body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; }
        canvas { border: 1px solid black; }
    </style>
</head>
<body>
<canvas id="huffmanCanvas" width="800" height="600"></canvas>
<script>
// Sample sentence
const sentence = "this is an example for huffman encoding";

// Frequency table
let freqTable = {};
sentence.split('').forEach(char => {
    freqTable[char] = (freqTable[char] || 0) + 1;
});

// Priority queue node class
class Node {
    constructor(letter, frequency, left=null, right=null) {
        this.letter = letter;
        this.frequency = frequency;
        this.left = left;
        this.right = right;
    }
}

// Priority queue class
class PQueue {
    constructor() {this.items = []}

    enqueue(element) {this.items.push(element); this.items.sort((a, b) => a.frequency - b.frequency);}
    dequeue() {return this.items.shift();}
    isEmpty() {return this.items.length === 0;}
}

// Build Huffman Tree
function buildHuffmanTree(freqTable) {
    let pq = new PQueue();
    for (const [char, freq] of Object.entries(freqTable)) {
        pq.enqueue(new Node(char, freq));
    }
    while (!pq.isEmpty()) {
        const left = pq.dequeue();
        if (pq.isEmpty()) break;
        const right = pq.dequeue();
        const parent = new Node(null, left.frequency + right.frequency, left, right);
        pq.enqueue(parent);
    }

    return pq.items[0];
}

// Draw the tree
function drawTree(ctx, node, x, y, width) {
    if (!node) return;
    ctx.fillText(node.letter || node.frequency, x, y);
    const childWidth = width / 2;
    if (node.left) drawTree(ctx, node.left, x - childWidth, y + 50, childWidth);
    if (node.right) drawTree(ctx, node.right, x + childWidth, y + 50, childWidth);
}

// Walk the tree to generate Huffman codes
function generateCodes(node, code = '', codes = {}) {
    if (!node.left && !node.right) {
        codes[node.letter] = code;
        return codes;
    }
    if (node.left) generateCodes(node.left, code + '0', codes);
    if (node.right) generateCodes(node.right, code + '1', codes);
    return codes;
}

// Animation
function animateHuffmanTree() {
    const canvas = document.getElementById('huffmanCanvas');
    const ctx = canvas.getContext('2d');
    let tree = buildHuffmanTree(freqTable);

    function drawFrame(frame) {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawTree(ctx, tree, 400, 50, 300);
        requestAnimationFrame(drawFrame);
    }

    let step = 1;
    let steps = Math.floor((Object.keys(freqTable).length - 1) * (Math.random() + 2)); // Random steps
    function animateStep() {
        if (step > steps) return;
        drawTree(ctx, tree, 400, 50, 300);
        step++;
        requestAnimationFrame(animateStep);
    }

    animateStep();

    function walkTree() {
        const codes = generateCodes(tree);
        let totalBitsBefore = sentence.length * 8;
        let totalBitsAfter = sentence.split('').reduce((acc, char) => acc + (codes[char]?.length || 0), 0);

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillText(`Huffman Codes: ${JSON.stringify(codes)}`, 10, 30);
        ctx.fillText(`Total bits before compression: ${totalBitsBefore}`, 10, 60);
        ctx.fillText(`Total bits after compression: ${totalBitsAfter}`, 10, 90);

        setTimeout(() => {
            animateHuffmanTree();
        }, 3000); // Restart with a fresh instance
    }

    requestAnimationFrame(walkTree);
}

animateHuffmanTree();

</script>
</body>
</html>
```
qwen3:14bERRBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Huffman Coding Visualization</title>
    <style>
        body { font-family: monospace; background: #111; color: #eee; display: flex; flex-direction: column; align-items: center; margin: 0; padding-top: 20px; }
        #canvasContainer { border: 1px solid #333; margin-bottom: 20px; background: #000; }
        h2 { color: #4CAF50; }
    </style>
</head>
<body>

    <h2>Huffman Tree Construction</h2>
    <div id="canvasContainer">
        <canvas id="huffmanCanvas" width="900" height="600"></canvas>
    </div>

    <h3>Results</h3>
    <pre id="results"></pre>

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

        function setup(sentence) {
            const freq = {};
            for (const char of sentence) {
                freq[char] = (freq[char] || 0) + 1;
            }

            let nodes = Object.keys(freq).map(char => ({ char: char, freq: freq[char], left: null, right: null, parent: null }));
            nodes.forEach(node => node.isLeaf = true);

            // Initialize the priority queue (array)
            let pq = [...nodes];

            return { pq, initialNodes: nodes };
        }

        function buildTree(pq) {
            const treeNodes = [];
            while (pq.length > 1) {
                pq.sort((a, b) => a.freq - b.freq);
                let left = pq.shift();
                let right = pq.shift();

                let parent = { char: null, freq: left.freq + right.freq, left: left, right: right, isLeaf: false };
                parent.left.parent = parent;
                parent.right.parent = parent;

                treeNodes.push(parent);
                pq.push(parent);
            }
            return pq[0]; // The root
        }

        let state = {};

        function drawTree(root, nodesToDraw) {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            if (!root) return;

            const spacingX = canvas.width / (nodesToDraw.length + 1) * 2.5;
            const levelHeight = 60;
            let currentY = 30;

            function drawNode(node, x, y, depth) {
                const isLeaf = node.isLeaf;
                const label = isLeaf ? `${node.char}:${node.freq}` : `(${node.freq})`;

                // Draw the box for the current node (conceptual representation)
                ctx.strokeStyle = '#4CAF50';
                ctx.fillStyle = 'rgba(76, 175, 80, 0.1)';
                ctx.fillRect(x - 30, y - 20, 60, 40);
                ctx.strokeRect(x - 30, y - 20, 60, 40);

                // Draw label
                ctx.fillStyle = '#fff';
                ctx.fillText(label, x - 25, y + 10);
                ctx.fillText(`(${node.freq})`, x - 30, y - 10);


                if (isLeaf) {
                    ctx.fillStyle = '#2196F3';
                    ctx.fillRect(x - 30, y - 20, 60, 40);
                    ctx.strokeRect(x - 30, y - 20, 60, 40);
                }

                // Recurse for children (simplified layout)
                if (node.left) {
                    drawNode(node.left, x - spacingX * 1.5, y + levelHeight, depth + 1);
                }
                if (node.right) {
                    drawNode(node.right, x + spacingX * 1.5, y + levelHeight, depth + 1);
                }
            }

            // Start drawing from a relative center point for visual effect
            drawNode(root, canvas.width / 2, 40, 0);
        }


        function animateStep() {
            if (!state.currentPQ || state.currentPQ.length <= 1) {
                finalize();
                return;
            }

            // Show the current PQ state and nodes being processed
            drawTree(state.currentPQ[0], [...state.currentPQ]);

            let left = state.currentPQ[0];
            let right = state.currentPQ[1];
            
            if (left && right) {
                let parent = { char: null, freq: left.freq + right.freq, left: left, right: right, isLeaf: false };
                
                // Perform the merge operation
                state.currentPQ.splice(0, 2); // Remove left and right
                state.currentPQ.push(parent);

                // Redraw to show the new parent node taking place
                drawTree(state.currentPQ[0], [...state.currentPQ]);
            }


            animationFrameId = requestAnimationFrame(animateStep);
        }
        
        function traverseAndCalculate(root) {
            const codes = {};
            let totalBits = 0;

            function dfs(node, code) {
                if (node.isLeaf) {
                    codes[node.char] = code || '0'; // Handle single letter case
                    totalBits += node.freq * (code ? code.length : 1);
                } else {
                    if (node.left) dfs(node.left, code + '0');
                    if (node.right) dfs(node.right, code + '1');
                }
            }

            dfs(root, '');
            return { codes: codes, totalBits: totalBits };
        }

        function finalize() {
            cancelAnimationFrame(animationFrameId);
            const root = state.finalRoot;
            if (!root) return;

            const result = traverseAndCalculate(root);
            let message = "<h2>Huffman Codes Generated:</h2>";
            message += "<pre>";
            for (const char in result.codes) {
                message += `${char}: ${result.codes[char]}\n`;
            }
            message += `</pre>`;
            message += `<p><strong>Total Bits Before Compression:</strong> N/A (Fixed Sample)</p>`;
            message += `<p><strong>Total Bits After Compression:</strong> ${result.totalBits}</p>`;

            document.getElementById('results').innerHTML = message;
        }


        function run() {
            const sampleSentence = "the quick brown fox jumps over the lazy dog";
            state = setup(sampleSentence);
            processAnimation();
        }

        function processAnimation() {
            let pq = [...state.initialNodes];
            let history = [];

            // Animation loop simulation (pop, merge, push)
            while (pq.length > 1) {
                history.push([...pq]); // Save state before merge
                
                pq.sort((a, b) => a.freq - b.freq);
                let left = pq.shift();
                let right = pq.shift();

                let parent = { char: null, freq: left.freq + right.freq, left: left, right: right, isLeaf: false };
                left.parent = parent;
                right.parent = parent;

                history.push(parent); // Save the new merged node
                pq.push(parent);
            }
            state.finalRoot = pq[0];
            
            // --- Animation Control ---
            let historyIndex = 0;
            function animate() {
                if (historyIndex < history.length) {
                    const currentPQState = history[historyIndex];
                    state.currentPQ = [...currentPQState];
                    drawTree(null, state.currentPQ); // Draw the conceptual PQ state
                    historyIndex++;
                    animationFrameId = requestAnimationFrame(animate);
                } else {
                    // Animation finished, transition to final drawing and results
                    cancelAnimationFrame(animationFrameId);
                    drawTree(state.finalRoot, [...state.currentPQ]); 
                    finalize();
                }
            }

            animate();
        }


        run();
    </script>
</body>
</html>
```
qwen3.5:9bSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><style>body{background:#111;margin:0;display:flex;justify-content:center;align-items:center;height:100vh;}canvas{border:1px solid #333;background:#222;}</style></head><body><canvas id="huffmanCanvas"></canvas><script>
const canvas = document.getElementById('huffmanCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 900;
canvas.height = 600;

let F = "this is a fixed sample sentence for huffman coding visualization";
let nodeQueue, rootNode, stepCount = 0, maxSteps = 0;
let animationRunning = true;

function countFreq(text) {
    const freq = {};
    for (const char of text.toLowerCase()) {
        if (/[a-z]/.test(char)) {
            freq[char] = (freq[char] || 0) + 1;
        }
    }
    return freq;
}

function Node(freq, char = null, left = null, right = null) {
    this.freq = freq;
    this.char = char;
    this.left = left;
    this.right = right;
}

class MinPriorityQueue {
    constructor() { this.heap = []; }
    compare(a, b) { return a.freq - b.freq; }
    enqueue(node) { this.heap.push(node); this.heap.sort(this.compare); }
    dequeue() { return this.heap.shift(); }
    size() { return this.heap.length; }
}

function initializeHuffman() {
    const freqs = countFreq(F);
    let pq = new MinPriorityQueue();
    for (const char in freqs) {
        pq.enqueue(new Node(freqs[char], char));
    }

    nodeQueue = [...pq.heap];
    rootNode = null;
    stepCount = 0;
    maxSteps = 0;
    
    while (pq.size() > 1) {
        const a = pq.dequeue();
        const b = pq.dequeue();
        const merged = new Node(a.freq + b.freq, null, a, b);
        pq.enqueue(merged);
        rootNode = merged;
        stepCount++;
    }
    if (pq.size() === 1) {
        rootNode = pq.dequeue();
    }
}

function drawTree(node, x, y, depth, parentCode, drawnNodes = [], pathCoords = []) {
    ctx.save();
    let currentX = x;
    let currentY = y;

    if (node) {
        drawnNodes.push({ node: node, x: currentX, y: currentY, code: parentCode });
        pathCoords.push({x: currentX, y: currentY});

        let leftX = currentX - 150;
        let rightX = currentX + 150;

        if (node.left) {
            drawTree(node.left, leftX, currentY + 80, depth + 1, parentCode + '0', drawnNodes, pathCoords);
        }
        if (node.right) {
            drawTree(node.right, rightX, currentY + 80, depth + 1, parentCode + '1', drawnNodes, pathCoords);
        }

    }
    ctx.restore();
    return drawnNodes;
}

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

    if (!rootNode) return;

    // Draw Queue Nodes
    const spacing = 120;
    let startX = 50;
    let yPos = 50;
    ctx.fillStyle = '#444';
    ctx.font = '16px monospace';

    ctx.fillText("Priority Queue:", startX, 30);

    nodeQueue.forEach((node, i) => {
        const x = startX + i * spacing;
        ctx.beginPath();
        ctx.arc(x, yPos, 25, 0, Math.PI * 2);
        ctx.fillStyle = '#333';
        ctx.fill();
        ctx.strokeStyle = '#666';
        ctx.stroke();

        let label = node.char ? `${node.char} (${node.freq})` : `(${node.freq})`;
        ctx.fillStyle = 'white';
        if (node.char) ctx.fillText(label, x - 20, yPos + 5);
        else ctx.fillText(`Freq: ${node.freq}`, x - 35, yPos + 5);
    });

    // Draw Tree Visualization
    const drawn = [];
    drawn.push(...drawTree(rootNode, canvas.width / 2, 30, 0, '', [], []));

    ctx.fillStyle = 'rgba(100, 100, 255, 0.8)';
    ctx.strokeStyle = '#666';
    drawn.forEach(({ node: n, x, y, code }) => {
        ctx.beginPath();
        ctx.arc(x, y, 20, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();

        let label = '';
        if (n.char) {
            label = `${n.char} (${n.freq})`;
            ctx.fillStyle = 'white';
            ctx.fillText(label, x - 35, y + 5);
        } else {
            label = `(${n.freq})`;
            ctx.fillStyle = '#ccc';
            ctx.fillText(label, x - 20, y + 5);
        }
    });

    // Draw Merge Step Indicator
    if (stepCount > 0) {
        const currentPop = nodeQueue[0];
        const currentPush = rootNode; // Simplified: assume the last merged node is the one available
        ctx.fillStyle = 'rgba(255, 165, 0, 0.2)';
        ctx.fillRect(30, canvas.height - 80, 200, 40);
        ctx.fillStyle = '#FFA500';
        ctx.fillText(`Merging step ${stepCount}/${maxSteps}...`, 30, canvas.height - 50);
    }
}

function displayResults(huffCode) {
    let totalBits = 0;
    for (const char in huffCode) {
        totalBits += huffCode[char].length;
    }
    
    ctx.fillStyle = 'white';
    ctx.font = '24px monospace';
    const resultText = `Huffman Codes: ${Object.entries(huffCode).map(([c, code]) => `${c}: ${code}`).join(' | ') || 'N/A'}`;
    ctx.fillText(resultText, 50, 100);

    ctx.font = '20px monospace';
    const bitsSaved = Math.round((F.length * 8 - totalBits) / 8);
    let resultMsg = `Total Bits (Compressed): ${totalBits} | Original Est: ${F.length * 8} | Saved: ${bitsSaved} bytes`;
    ctx.fillText(resultMsg, 50, 150);

    document.getElementById('huffmanCanvas').style.pointerEvents = 'none';
}

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

    drawVisualization();

    setTimeout(() => {
        stepCount++;
        if (stepCount < maxSteps) {
            requestAnimationFrame(animate);
        } else if (rootNode && nodeQueue.length === 0) {
            // Completion: Build code map and display results
            const huffCode = {};
            function traverse(node, code) {
                if (node.char !== null) {
                    huffCode[node.char] = code || '0';
                } else if (node.left) {
                    traverse(node.left, code + '0');
                } else if (node.right) {
                    traverse(node.right, code + '1');
                }
            }
            traverse(rootNode, '');
            displayResults(huffCode);

            // Cycle: Reset and restart visualization after delay
            setTimeout(() => {
                initializeHuffman();
                requestAnimationFrame(animate);
            }, 2000);

        } else if (stepCount > 0 && !rootNode) {
             // Should not happen, but safety break
             animationRunning = false;
        }
    }, 100); // Animation speed control
}

initializeHuffman();
animate();
</script></body></html>
```
qwen3:8bharness
No answer reached us (ERR) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget and the answer stayed empty; the gateway rejects budgets above 3000 tokens Counted as a did-not-finish, but it is not evidence about this model.
qwen3-coder:30bSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head><title>Huffman Visualization</title></head>
<body>
<canvas id="huffmanCanvas" width="800" height="600" style="border:1px solid #ccc; display:block; margin:20px auto;"></canvas>
<div id="info" style="text-align:center; font-family:monospace;"></div>

<script>
const canvas = document.getElementById('huffmanCanvas');
const ctx = canvas.getContext('2d');
let animationRunning = true;
let state = {
    frequencies: {},
    pq: [],
    treeRoot: null,
    stepCount: 0,
    maxSteps: 0,
    interval: 10, // ms per merge step
    animationTimeout: null
};

// --- Fixed Sample Sentence and Initialization ---
const SAMPLE_TEXT = "this is a test sentence for huffman coding visualization";

function getFrequencies(text) {
    let freq = {};
    for (const char of text.toLowerCase()) {
        if (char >= 'a' && char <= 'z') {
            freq[char] = (freq[char] || 0) + 1;
        }
    }
    return freq;
}

class Node { constructor(freq, char=null, left=null, right=null) { this.freq = freq; this.char = char; this.left = left; this.right = right; } }

function buildHuffmanTree(initialFreqs) {
    let pq = [];
    for (const [char, freq] of Object.entries(initialFreqs)) {
        pq.push(new Node(freq, char));
    }
    pq.sort((a, b) => a.freq - b.freq);

    while (pq.length > 1) {
        const left = pq.shift();
        const right = pq.shift();
        const parent = new Node(left.freq + right.freq, null, left, right);
        pq.push(parent);
        pq.sort((a, b) => a.freq - b.freq);
    }
    return { root: pq[0], nodes: JSON.parse(JSON.stringify(initialFreqs)) }; // Return copies for visualization state
}

// --- Visualization Logic ---

function drawTree(root, level = 0, x = 0, y = 50, charMap = {}) {
    if (!root) return;

    const NODE_SIZE = 20;
    const HORIZONTAL_SPACING = 60;
    let currentX = x;

    ctx.save();
    ctx.translate(currentX, y);

    // Draw Node Box and Frequency
    ctx.fillStyle = root.char ? '#4CAF50' : '#2196F3';
    ctx.fillRect(-NODE_SIZE/2, -NODE_SIZE/2, NODE_SIZE, NODE_SIZE);
    ctx.strokeStyle = '#333';
    ctx.strokeRect(-NODE_SIZE/2, -NODE_SIZE/2, NODE_SIZE, NODE_SIZE);

    ctx.fillStyle = '#000';
    ctx.font = '14px Arial';
    ctx.textAlign = 'center';
    ctx.fillText(root.char || `(${root.freq})`, 0, 5); // Label (Char or Frequency)
    ctx.fillText(`F:${root.freq}`, 0, 20);

    // Draw edges and recurse
    if (root.left && root.right) {
        const yNext = y + 100;
        drawTree(root.left, level + 1, x - HORIZONTAL_SPACING / 2, yNext, charMap);
        drawTree(root.right, level + 1, x + HORIZONTAL_SPACING / 2, yNext, charMap);
    }

    ctx.restore();
}

function drawMergingStep(pq) {
    const NODE_SIZE = 20;
    let currentX = 50;

    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#333';
    ctx.font = '14px Arial';
    ctx.textAlign = 'center';

    pq.forEach((node, i) => {
        const xPos = currentX + (i * 150);
        // Draw the node box and label
        ctx.fillStyle = '#2196F3';
        ctx.fillRect(xPos - NODE_SIZE/2, 10, NODE_SIZE, NODE_SIZE);
        ctx.strokeStyle = '#333';
        ctx.strokeRect(xPos - NODE_SIZE/2, 10, NODE_SIZE, NODE_SIZE);

        ctx.fillStyle = '#000';
        ctx.fillText(`F:${node.freq}`, xPos, 50);
    });
}

function drawFinalTree(root) {
    const CANVAS_WIDTH = canvas.width;
    const CANVAS_HEIGHT = canvas.height;

    ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
    ctx.fillStyle = '#333';
    ctx.font = '14px Arial';
    ctx.textAlign = 'center';

    // Simple recursive drawing simulation (Top-down view)
    function drawNode(node, xOffset, yOffset, level) {
        const NODE_SIZE = 20;
        let xPos = CANVAS_WIDTH / 2 + xOffset;
        let yPos = yOffset + level * 80;

        // Draw the node box (showing char and frequency)
        ctx.fillStyle = '#4CAF50';
        ctx.fillRect(xPos - NODE_SIZE/2, yPos - NODE_SIZE/2, NODE_SIZE, NODE_SIZE);
        ctx.strokeStyle = '#333';
        ctx.strokeRect(xPos - NODE_SIZE/2, yPos - NODE_SIZE/2, NODE_SIZE, NODE_SIZE);

        let label = node.char ? `${node.char} (F:${node.freq})` : `(${node.freq})`;
        if (level > 0) {
             label += ' [Merge]';
        }
        ctx.fillStyle = '#000';
        ctx.fillText(label, xPos, yPos + 5);

        // Recurse left/right
        if (node.left && node.right) {
            drawNode(node.left, xOffset - 120, yOffset + 80, level + 1);
            drawNode(node.right, xOffset + 120, yOffset + 80, level + 1);
        }
    }

    // Start drawing from a fixed point (assuming the root represents the full text)
    drawNode(root, 0, 50, 0);
}

function calculateCodesAndStats(root) {
    let codes = {};
    let totalBits = 0;
    let weightedLength = 0;

    function traverse(node, code) {
        if (node.char !== null) {
            codes[node.char] = code || '0'; // Should be assigned correctly during traversal
            weightedLength += node.freq * (code ? code.length -1 : 1);
            totalBits += node.freq * (code ? code.length : 1);
        }

        if (node.left) traverse(node.left, (code || '') + '0');
        if (node.right) traverse(node.right, (code || '') + '1');
    }
    traverse(root, '');
    return { codes: codes, totalBits: Math.round(totalBits), weightedLength: Math.round(weightedLength) };
}

function displayResults(stats) {
    const infoDiv = document.getElementById('info');
    let resultHTML = `<h3>Huffman Coding Complete</h3>`;
    resultHTML += `<p><strong>Original Bits (Fixed 8-bit ASCII):</strong> ${SAMPLE_TEXT.length * 8} bits</p>`;
    resultHTML += `<p><strong>Encoded Bit Length:</strong> ${stats.totalBits} bits</p>`;
    resultHTML += `<p>Compression Ratio: ${(1 - stats.totalBits / (SAMPLE_TEXT.length * 8)).toFixed(2) * 100}% reduction.</p>`;

    // Display codes
    resultHTML += `<h4>Codebook:</h4><pre style="font-size:14px;">`;
    for (const char in stats.codes) {
        resultHTML += `${char}: ${stats.codes[char]}\n`;
    }
    resultHTML += `</pre>`;
    infoDiv.innerHTML = resultHTML;
}

// --- Animation Loop and State Management ---

function animateStep() {
    if (!animationRunning || state.stepCount > state.maxSteps) return;

    const pq = [...state.pq]; // Copy of the current PQ for drawing
    drawMergingStep(pq);
    
    state.stepCount++;
    setTimeout(() => {
        animateStep();
    }, state.interval);
}

function runVisualization() {
    // Reset State
    state = {
        frequencies: getFrequencies(SAMPLE_TEXT),
        pq: [],
        treeRoot: null,
        stepCount: 0,
        maxSteps: 0,
        animationTimeout: null,
        interval: 15 // Slower interval for viewing
    };

    // Step 1: Build Initial PQ (Visualization of initial nodes)
    const initialFreqs = state.frequencies;
    let tempPQ = [];
    for (const [char, freq] of Object.entries(initialFreqs)) {
        tempPQ.push(new Node(freq, char));
    }
    tempPQ.sort((a, b) => a.freq - b.freq);
    state.pq = tempPQ;

    // Step 2: Simulate Huffman Merging Process
    let currentNodes = [...tempPQ];
    let stepCounter = 0;

    while (currentNodes.length > 1) {
        const left = currentNodes.shift();
        const right = currentNodes.shift();
        const parent = new Node(left.freq + right.freq, null, left, right);
        currentNodes.push(parent);
        currentNodes.sort((a, b) => a.freq - b.freq);
        stepCounter++;

        // Record the state for visualization (The PQ before the merge happens next loop)
        state.pq = [...currentNodes]; 
    }

    state.treeRoot = currentNodes[0]; 
    state.maxSteps = stepCounter;

    // Start Animation
    animateStep();
}

function finishAnimation() {
    animationRunning = false;
    if (state.animationTimeout) clearTimeout(state.animationTimeout);

    // Final Drawing and Calculations
    drawFinalTree(state.treeRoot);
    const stats = calculateCodesAndStats(state.treeRoot);
    displayResults(stats);
}


// --- Execution Flow ---

window.onload = () => {
    runVisualization(); // Starts the animation sequence
};
</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.
llama3.1:8bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Huffman Code Animation</title>
<style>
canvas {
  border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="canv" width="400" height="200"></canvas>
<script>
function Letter(f) { this.char = String.fromCharCode(Math.floor(Math.random() * 26 + 97)); this.freq = f; }
Letter.prototype.toString = function() {
  return `<${this.char} : ${this.freq} freq>`;
};
function Node(l, r) { this.left = l; this.right = r; this.freq = (l ? l.freq : 0) + (r ? r.freq : 0); }
Node.prototype.toString = function() {
  return `${(this.left ? this.left.toString() : '')}, ${(this.right ? this.right.toString() : '')}`;
};
function heapify(arr, i) { if (i > 0 && arr[Math.floor((i - 1) / 2)].freq > arr[i].freq) [arr[i], arr[Math.floor((i - 1) / 2)] = [arr[Math.floor((i - 1) / 2)], arr[i]]; heapify(arr, Math.floor((i - 1) / 2)); }
function buildHeap(arr) { for (let i = arr.length / 2; i >= 0; --i) heapify(arr, i); }
function mergeNodes(left, right) {
  let node = new Node(left, right);
  if (!left && !right) return null;
  if (!left || !right) return left ? left : right;
  if (left.freq < right.freq) [node.left, node.right] = [left, right];
  else [node.left, node.right] = [right, left];
  return node;
}
let heap = [], canv = document.getElementById('canv'), ctx = canv.getContext('2d');
const sampleSentence = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
const letterFrequencies = {};
for (let i of sampleSentence) {
  if (!letterFrequencies[i]) letterFrequencies[i] = 0;
  ++letterFrequencies[i];
}
for (let i in letterFrequencies) heap.push(new Letter(letterFrequencies[i]));
buildHeap(heap);
function drawTree(node, x, y) {
  let width = 2 * node.freq + 10; ctx.font = '16px Arial';
  ctx.fillStyle = '#aaa'; ctx.textAlign = 'left'; ctx.textBaseline = 'top';
  if (node.left || node.right) {
    let midX = x + Math.floor(width / 2);
    drawTree(node.left, x, y - 10); drawTree(node.right, midX, y - 10);
    ctx.strokeStyle = '#777'; ctx.lineWidth = 1; ctx.beginPath();
    ctx.moveTo(x, y); ctx.lineTo(midX, y); ctx.stroke(); ctx.closePath();
  }
  if (node.char) ctx.fillStyle = 'blue'; else ctx.fillStyle = 'black';
  ctx.fillText(node.char || `(${x}, ${y})`, x + 5, y - 5);
}
const totalBitsBefore = heap.reduce((acc, c) => acc + c.freq * Math.log2(c.freq), 0).toFixed(2);
document.body.innerHTML += `<p>Initial Tree: <span id="initial-tree"></span><br>Total bits before compression: ${totalBitsBefore}</p>`;
function loop() {
  if (heap.length === 1) {
    let tree = heap[0]; drawTree(tree, 20, canv.height - 30);
    ctx.fillStyle = 'black'; ctx.textAlign = 'left'; ctx.textBaseline = 'top';
    const totalBitsAfter = Object.values(letterFrequencies).reduce((acc, f) => acc + f * new Node().freq.toString().length, 0).toFixed(2);
    document.body.innerHTML += `<br>Total bits after compression: ${totalBitsAfter}<br>Code Word: <span id="code-word"></span>`;
    let i = 0;
    for (let c in letterFrequencies) {
      const node = new Node();
      while (node.freq.toString().length > 1) {
        if (!i--) break; [tree, node] = [node, tree];
        drawTree(tree, 20, canv.height - 30); ctx.clearRect(0, 0, canv.width, canv.height);
        i++;
      }
      const codeWord = [...node.freq.toString()].reverse().join('');
      document.getElementById('code-word').innerHTML += `${c}: ${codeWord}<br>`;
    }
    return;
  }
  let left, right; [left, right] = [heap[0], heap[1]];
  heap.splice(0, 2); heap.push(new Node(left, right));
  buildHeap(heap);
  document.getElementById('initial-tree').innerHTML = heap.toString();
  drawTree(heap[0], 20, canv.height - 30); ctx.clearRect(0, 0, canv.width, canv.height);
  requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
```
qwq:32bSTATLIM
Show code
<think>

Okay, I need to create an HTML file that builds a Huffman tree from a sample sentence and animates it on a canvas. The user wants everything self-contained, no libraries, and under 120 lines. Let's start by breaking down the requirements.

First, the sample sentence. I'll pick a fixed one, maybe "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG". Let me check the letters and their frequencies. I need to count how many times each character appears. Wait, but maybe I should just code that part directly.

The Huffman code process requires a priority queue (min-heap). The nodes will have the character, frequency, and left/right children. The algorithm steps are: create leaves for each character, then repeatedly merge the two smallest nodes until one tree remains.

Now, the animation part. Using canvas, so I need to draw the nodes and their connections as the tree builds. Each step (merging nodes) should be animated. The priority queue should be shown too, perhaps on the side. Since it's requestAnimationFrame, the steps can be timed with a loop that waits between frames.

First, structure the HTML: a canvas element and a script. The script will handle the animation. The Huffman process needs to be broken down into steps that can be animated. Each merge is a step. The steps array will store the nodes to merge, and the current step index.

I need to set up the initial frequencies for the given sentence. Let's code a function to count them. Then create the initial heap. But since the user wants a strict limit on lines, maybe hardcode the frequencies? Wait, the example sentence has spaces too. Let me see:

Sample sentence: "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG". Let's count each character's frequency:

T: appears in "THE", "THE" → 2 times? Wait "THE" occurs twice, so T: 2 each? Let's actually list all letters:

Breaking down the sentence:

T, H, E, (space), Q, U, I, C, K, (space), B, R, O, W, N, (space), F, O, X, (space), J, U, M, P, S, (space), O, V, E, R, (space), T, H, E, (space), L, A, Z, Y, (space), D, O, G.

Wait need to count each occurrence:

T: 2 (from first and last THE)
H: 2 (in THE twice)
E: 3 (first THE, then the end THE, then in "OVER"?)
Wait let me do it properly:

The sentence is: "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"

Breaking into characters (including spaces):

Breaking each word:

THE (T H E)

QUICK (Q U I C K)

BROWN (B R O W N)

FOX (F O X)

JUMPS (J U M P S)

OVER (O V E R)

THE (T H E)

LAZY (L A Z Y)

DOG (D O G)

So spaces are between each word, so the total letters and spaces:

Let me list each character and count:

T: appears in THE (twice) → 2 times? Wait first THE (T), and the last THE again → total 2 T's?

Wait each T is in both THEs. So T: 2×2? Wait no, each "THE" has one T. So first "THE" has T, the second "THE" has another T → total 2 T's.

Similarly, H appears twice (each THE has one H).

E appears in first THE (once), last THE (once), and in "OVER" (the E there). So E: 3 times.

Space: between each word, so how many spaces? The sentence has 8 words? Let's see: THE (1), QUICK (2), BROWN (3), FOX (4), JUMPS (5), OVER (6), THE (7), LAZY (8), DOG (9). Wait 8 spaces between 9 words. So spaces count as 8? Hmm maybe that's part of the counts.

So the frequencies for each character:

Let me list all characters and their counts:

Spaces: 8 instances

Letters:

T: 2 (from THE twice)

H: 2

E: 3 (first THE, last THE, OVER's E)

Q:1, U:2 (in QUICK and JUMPS?), wait in "QUICK", U is first, and in "JUMPS" has U? No, JUMPS is J-U-M-P-S? Yes, so U appears in "QUICK" and "JUMPS"? Wait no, JUMPS is J, U, M, P, S? The U in JUMPS?

Wait "QUICK" has U, I, C, K. The U in JUMPS is another U, so total U is 2?

Wait "JUMPS" is spelled with J-U-M-P-S, so yes. So U is in both QUICK and JUMPS → total 2 U's.

E counts:

First THE's E, last THE's E, and O V E R → E is third in "OVER". So total 3 E's.

Similarly, O appears in BROWN (O), FOX (O?), no, FOX is F-O-X? No, FOX is F-O-X? Wait BROWN has an O, FOX has an O? Wait BROWN is B-R-O-W-N, so O is there. Then in "FOX" F-O-X, so another O. Then in "OVER" has an O? O is the first letter of "OVER"? Yes, so O appears in BROWN (O), FOX (O), and OVER (O) → that's three O's? Wait:

Breaking down each word's letters:

BROWN → B, R, O, W, N → O once.

FOX → F, O, X → another O.

OVER → O, V, E, R → O once.

So O total: 3 (BROWN, FOX, OVER). But also in "DOG" has O? D-O-G → yes. So O is in BROWN, FOX, OVER, and DOG → that's four O's? Wait, let me recount:

Wait the full sentence is "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"

Breaking down all letters including spaces:

T, H, E, space,

Q, U, I, C, K, space,

B, R, O, W, N, space,

F, O, X, space,

J, U, M, P, S, space,

O, V, E, R, space,

T, H, E, space,

L, A, Z, Y, space,

D, O, G.

Wait the last "DOG" is D, O, G, so O is there again. So O appears in BROWN (1), FOX (1), OVER (1), and DOG (1) → total 4 O's.

Hmm, this is getting complicated. Maybe I should code a frequency counter function. But since the user wants code without comments, perhaps hard-coding the frequencies is better for brevity, given the line limit.

Alternatively, code the counting in a helper function. Let's try:

Let me write a helper function to count the frequencies. Let's see:

Sample sentence is in a variable, then split
deepseek-r1:32bBLANKFORMAT
Show code
```html
<!DOCTYPE html><html><head><style>canvas{border:1px solid #ccc;display:block;margin:20px auto;background:#fff}body{background:#f0f0f0;font-family:monospace}</style></head><body>
<canvas id=c></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
ctx.canvas.width=800;ctx.canvas.height=600;

let freq={},s="THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG.",codes={};
for(let c of s)freq[c]= (freq[c]||0)+1;
delete freq[' '];
const nodes = Object.entries(freq).map(([c,f])=>({c,f,left:undefined,right:undefined,parent:undefined,weight:f}));
nodes.sort((a,b)=>a.weight - b.weight);

let animationState={phase:'build',nodesCopy:[],currentNodes:[...nodes],root:undefined,timer:0};
const phases=['build','assign codes','display results'];

function draw() {
  ctx.clearRect(0,0,800,600);
  
  if(animationState.phase === 'build'){
    // Draw priority queue
    ctx.fillStyle='#eee';ctx.fillRect(10,10,380,40);
    ctx.strokeStyle='#ccc';ctx.strokeRect(10,10,380,40);
    
    // Draw nodes in queue
    const nodeWidth=25,nodeHeight=20;
    let x=20,y=20;
    for(let i=0;i<animationState.currentNodes.length;i++){
      ctx.fillRect(x,y,nodeWidth,nodeHeight);
      ctx.fillStyle='black';
      ctx.fillText(animationState.currentNodes[i].weight,x+4,y+13);
      x += nodeWidth + 5; if(x>370){x=20;y+=nodeHeight+5;}
    }
    
    // Draw tree
    const root = animationState.root;
    drawTree(root,410,50);
    
    // Next step
    animationState.timer++;
    if(animationState.timer > 60){
      mergeNodes();
      animationState.timer=0;
    }
  } else if(animationState.phase === 'assign codes'){
    ctx.fillStyle='black';
    ctx.fillText('Assigning Huffman codes...',20,50);
    
    // Walk the tree to assign codes
    function walk(node,path){
      if(node.c) return { [node.c]: path };
      let l=walk(node.left,path+'0'), r=walk(node.right,path+'1');
      return {...l,...r};
    }
    codes = walk(animationState.root,'');
    
    animationState.phase='display results';
  } else {
    ctx.fillStyle='black';
    ctx.fillText('Huffman Codes:',20,50);
    
    // Display codes
    let y=80;
    for(let c in codes){
      ctx.fillText(`${c}: ${codes[c]}`,20,y);
      y +=15;
    }
    
    // Calculate total bits saved
    const originalSize=s.length*8;
    const compressedSize = s.split('').reduce((acc,c)=>acc + codes[c].length,0);
    
    ctx.fillText(`Original: ${originalSize} bits`,20,y+5);
    ctx.fillText(`Compressed: ${compressedSize} bits`,20,y+25);
  }
}

function mergeNodes(){
  if(animationState.currentNodes.length <=1){
    animationState.root = animationState.currentNodes[0];
    // Start next phase
    if(!animationState.nodesCopy.length){
      animationState.phase='assign codes';
    } else {
      animationState.currentNodes = [...animationState.nodesCopy];
      animationState.phase='build';
    }
    return;
  }
  
  const a=animationState.currentNodes.shift();
  const b=animationState.currentNodes.shift();
  
  const parent={c:undefined,left:a,right:b,parent:undefined,weight:a.weight +b.weight};
  a.parent = b.parent = parent;
  
  if(animationState.phase === 'build'){
    animationState.nodesCopy.push(...animationState.currentNodes);
  }
  
  // Insert parent back into the queue
  let inserted=false;
  for(let i=0;i<animationState.currentNodes.length;i++){
    if(parent.weight <= animationState.currentNodes[i].weight){
      animationState.currentNodes.splice(i,0,parent);
      inserted=true;break;
    }
  }
  if(!inserted) animationState.currentNodes.push(parent);
}

function drawTree(node,x,y){
  if(!node) return;
  
  // Draw node
  ctx.fillStyle='#fff';ctx.fillRect(x-12.5,y-12.5,25
command-r:35bBLANKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Huffman Compression Animation</title>
    <style>
        body { margin: 0; }
        canvas { border: 1px solid black; display: block; margin: auto; }
    </style>
</head>
<body>
    <canvas></canvas>

    <script>
        // Huffman code data structure and helper functions
        class Node {
            constructor(char, freq) {
                this.left = null;
                this.right = null;
                this.char = char;
                this.freq = freq;
                this.code = "";  // Code word for each character
            }

            getTotalFreq() {
              let total = this.freq;
              if (this.left) total += this.left.getTotalFreq();
              if (this.right) total += this.right.getTotalFreq();
              return total;
            }
        }

        function createPriorityQueue(nodes) {
          nodes.sort((a, b) => a.freq - b.freq);
          const queue = [];
          for (const node of nodes) {
            if (!queue.length || queue[0].freq > node.freq)
              queue.unshift(node);  // Push to the left if smaller freq
            else {
                let i = 0;
                while (i < queue.length && queue[i].freq <= node.freq)
                  i++;
                queue.splice(i, 0, node);
            }
          }
          return queue;
        }

        function mergeNodes(queue) {
          if (!queue.length || queue.length === 1)
            return null;

          let min1 = queue.shift();
          while (queue[0] && queue[0].freq <= min1.freq)
              min1 = queue.shift();

          let min2 = queue.shift();
          while (queue[0] && queue[0].freq <= min2.freq)
            min2 = queue.shift();

          const newNode = new Node(null, min1.freq + min2.freq);
          if (!newNode.left && !newNode.right) {  // Leaf node
            newNode.char = min1.char;
            newNode.code = '0';
          } else {
            newNode.left = min1;
            newNode.right = min2;

            const codeLengthDiff = Math.abs(min1.code.length - min2.code.length);
            if (codeLengthDiff > 1)
              console.error("Code length difference exceeds 1 bit!");

            // Pad the shorter code with '0' to match lengths for concatenation
            if (min1.code.length < min2.code.length) {
                while (min1.code.length < min2.code.length)
                  min1.code = `0${min1.code}`;
            } else {
              while (min2.code.length < min1.code.length)
                min2.code = `0${min2.code}`;
            }

            newNode.code = `${min1.code}0` +
                          `${min2.code}`.slice(min1.code.length);
          }
          queue.push(newNode);
          mergeNodes(queue);  // Recursive call to continue merging
        }

        // Animation setup and drawing functions
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');

        let nodes, maxFreq;
        let frameNum = 0;
        let lastTime = Date.now();

        function init() {
          // Initialize Huffman tree with sample sentence frequencies
          nodes = createPriorityQueue([
            new Node('a', 8),
            new Node('b', 123),
            new Node('c', 567),
            new Node('d', 901),
            new Node('e', 4321)
          ]);

          maxFreq = nodes[nodes.length - 1].freq;
        }

        function drawTree(node, x, y, dx, dy, level = 0) {
            if (!node) return;

            // Draw node rectangle and label its frequency
            ctx.fillStyle = '#69f';
            const rectWidth = Math.max(5 + (node.freq / maxFreq * 15), 5);
            ctx.fillRect(x, y, rectWidth, level ? dy : 15);

            if (!level) {
              // Write character and frequency in the first row only
              ctx.fillStyle = '#fff';
              const textMetrics = ctx.measureText(` ${node.char}`);
              ctx.fillText(
                ` ${node.char}: ${node.freq}`, x + rectWidth / 2 - textMetrics.width / 2, y + dy);
            }

            // Draw left and right branches to the child nodes
            drawTree(node.left, x - dx / 2, y + dy * .75, dx / 2, dy * level, level+1);
            drawTree(node.right, x + dx / 2, y + dy * .75, dx / 2, dy * (level+1), level+1);
        }

        function animate() {
          const time = Date.now();
          const deltaTime = time - lastTime;

          if (!nodes)
            init();

          // Animate tree construction and draw it on canvas
          if (frameNum < nodes.length + 32) {

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.font = '14px Arial';
            ctx.fillStyle = '#fff';
            const textMetrics = ctx.measureText('Huffman Compression');
            ctx.fillText(`Huffman Compression`,
                         (canvas.width / 2) - textMetrics.width / 2, 35);

            for (let i = 0; i < frameNum; i++) {
              mergeNodes(nodes);
            }

            const dx = canvas.width * .46;
            const dy = (canvas.height - 70) / nodes.length;
            drawTree(nodes[frameNum], dx, 85, 1.2*dx/nodes.length, dy);

            // Update the code word for each character in real-time
            if (frameNum === nodes.length + 31) {
              const totalBits = [];
              let totalBefore = 0;
              let totalAfter = 0;

              ctx.fillText(`Original (${nodes[nodes.length - 1].freq} bytes):`,
                          5, canvas.height - 27);
              nodes.forEach(node => {
                if (!isNaN(node.char)) {
                  const bitsBefore = Math.log2(maxFreq / node.freq) * maxFreq; 
                  totalBefore += bitsBefore;

                  ctx.fillText(`${String.fromCharCode(Number(node.char))}(${bitsBefore} bits)`,
                                5, canvas.height - 17 + nodes.length - Number(node.char)*2);
                }
              });

              ctx.fillText(`Compressed (${totalAfter} bytes):`,
                          5, canvas.height - 3);
              nodes[nodes.length - 1].left.forEach(leaf => {
                totalAfter += leaf.code.length;

                ctx.fillText(`${String
Outside the Brain cluster — same task, same character budget, collected through their own tooling (response times not comparable)
GPT-5.5 (Codex)frontier referenceOKMINIFIED
Show code
<!doctype html><html><head><meta charset="utf-8"><title>Huffman Canvas</title><style>
html,body{margin:0;height:100%;background:#f5f1e8;color:#1b1b1b;font:14px system-ui,Segoe UI,sans-serif;overflow:hidden}canvas{display:block;width:100vw;height:100vh}
</style></head><body><canvas id="c"></canvas><script>
const c=document.getElementById('c'),x=c.getContext('2d'),sample='the quick brown fox jumps over the lazy huffman tree';let W,H,dpr=1,S;
function size(){dpr=devicePixelRatio||1;W=c.width=innerWidth*dpr;H=c.height=innerHeight*dpr;x.setTransform(dpr,0,0,dpr,0,0);W=innerWidth;H=innerHeight}addEventListener('resize',size);size();
class N{constructor(ch,f,l=null,r=null){this.ch=ch;this.f=f;this.l=l;this.r=r;this.id=Math.random();this.x=this.y=this.tx=this.ty=0}}
function fresh(){let s=sample.split('').filter(a=>a!=' '),m={};s.forEach(a=>m[a]=(m[a]||0)+1);Object.keys(m).forEach(k=>m[k]+=Math.floor(Math.random()*3));let q=Object.entries(m).map(([a,b])=>new N(a,b));q.sort((a,b)=>a.f-b.f||a.ch.localeCompare(b.ch));S={q,root:null,steps:[],stage:'merge',t:0,done:0,codes:{},text:s.join(''),hold:0};layout()}
function nodes(n,a=[]){if(!n)return a;a.push(n);nodes(n.l,a);nodes(n.r,a);return a}
function leaves(n,a=[]){if(!n)return a;if(!n.l&&!n.r)a.push(n);leaves(n.l,a);leaves(n.r,a);return a}
function depth(n){return n?1+Math.max(depth(n.l),depth(n.r)):0}
function layout(){let all=[...S.q];if(S.root)all.push(S.root);let forest=[];all.forEach(n=>{if(!forest.some(r=>nodes(r).includes(n)&&r!=n))forest.push(n)});let leafCount=0;forest.forEach(r=>leafCount+=Math.max(1,leaves(r).length));let gap=(W-80)/Math.max(1,leafCount),i=0;
function place(n,d){let ls=leaves(n);if(!n.l&&!n.r)n.tx=40+(i+++.5)*gap;else{place(n.l,d+1);place(n.r,d+1);n.tx=(n.l.tx+n.r.tx)/2}n.ty=160+d*Math.min(70,(H-260)/Math.max(1,depth(S.root||n)))}forest.forEach(r=>place(r,0));S.q.forEach((n,j)=>{n.tx=45+j*58;n.ty=68});nodes(S.root).forEach(n=>{n.x=n.x||n.tx;n.y=n.y||n.ty})}
function step(){if(S.q.length>1){let a=S.q.shift(),b=S.q.shift(),p=new N('',a.f+b.f,a,b);S.root=p;S.q.push(p);S.q.sort((u,v)=>u.f-v.f);layout();S.steps=[a,b,p];S.t=0}else if(S.stage!='codes'){S.stage='codes';walk(S.root,'');let raw=S.text.length*8,zip=0;for(let ch of S.text)zip+=S.codes[ch].length;S.bits=[raw,zip];S.hold=0}}
function walk(n,p){if(!n.l&&!n.r){S.codes[n.ch]=p||'0';return}walk(n.l,p+'0');walk(n.r,p+'1')}
function lerp(a,b,t){return a+(b-a)*t}
function drawNode(n,hot){n.x=lerp(n.x,n.tx,.08);n.y=lerp(n.y,n.ty,.08);x.beginPath();x.arc(n.x,n.y,19,0,7);x.fillStyle=hot?'#e26d5a':n.l?'#2f80ed':'#25855a';x.fill();x.lineWidth=2;x.strokeStyle='#111';x.stroke();x.fillStyle='white';x.textAlign='center';x.textBaseline='middle';x.font='700 12px system-ui';x.fillText((n.ch||'•')+':'+n.f,n.x,n.y)}
function edges(n){if(!n)return;if(n.l){x.strokeStyle='#444';x.lineWidth=2;x.beginPath();x.moveTo(n.x,n.y+18);x.lineTo(n.l.x,n.l.y-18);x.stroke();x.fillStyle='#111';x.fillText('0',(n.x+n.l.x)/2-8,(n.y+n.l.y)/2)}if(n.r){x.beginPath();x.moveTo(n.x,n.y+18);x.lineTo(n.r.x,n.r.y-18);x.stroke();x.fillText('1',(n.x+n.r.x)/2+8,(n.y+n.r.y)/2)}edges(n.l);edges(n.r)}
function frame(){x.clearRect(0,0,W,H);x.fillStyle='#f5f1e8';x.fillRect(0,0,W,H);x.fillStyle='#111';x.font='700 22px system-ui';x.textAlign='left';x.fillText('Huffman coding',24,30);x.font='13px system-ui';x.fillText('Sample: '+S.text,24,52);x.fillText('Priority queue: merge the two smallest frequencies',24,92);if(S.stage=='merge'){S.t++;if(S.t>105)step()}if(S.root)edges(S.root);let hot=new Set(S.steps||[]);[...nodes(S.root),...S.q].filter((v,i,a)=>a.indexOf(v)==i).forEach(n=>drawNode(n,hot.has(n)));if(S.stage=='codes'){x.font='700 16px system-ui';x.fillStyle='#111';x.fillText('Codes',24,H-130);x.font='13px ui-monospace,monospace';let a=Object.keys(S.codes).sort(),col=Math.max(1,Math.floor(W/118));a.forEach((k,i)=>x.fillText(JSON.stringify(k)+': '+S.codes[k],24+(i%col)*118,H-105+Math.floor(i/col)*20));x.font='700 15px system-ui';x.fillText('Before: '+S.bits[0]+' bits   After: '+S.bits[1]+' bits',24,H-22);if(++S.hold>260)fresh()}requestAnimationFrame(frame)}fresh();frame();
</script></body></html>
Claude Sonnet 5frontier referenceOK
Show code
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Huffman</title><style>
html,body{margin:0;background:#0b0f14;height:100%;overflow:hidden}
canvas{display:block;margin:0 auto;background:#0b0f14}
</style></head><body><canvas id="c" width="960" height="600"></canvas><script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
const W=cv.width,H=cv.height;
const SENTS=["the quick brown fox jumps over the lazy dog","pack my box with five dozen liquor jugs","sphinx of black quartz judge my vow","the five boxing wizards jump quickly"];
function freq(s){const f={};for(const c of s.toLowerCase())if(c>='a'&&c<='z')f[c]=(f[c]||0)+1;return f;}
function build(f){
 const nodes=Object.keys(f).map(c=>({char:c,freq:f[c]}));
 let active=nodes.slice();
 const steps=[];
 while(active.length>1){
  active.sort((a,b)=>a.freq-b.freq);
  const snap=active.slice();
  const l=active.shift(),r=active.shift();
  const p={char:null,freq:l.freq+r.freq,left:l,right:r};
  l.parent=p;r.parent=p;
  steps.push({l,r,p,snap});
  active.push(p);
 }
 return {root:active[0],steps,leaves:nodes};
}
function codes(n,pre,out){ if(!n.left){out[n.char]=pre||'0';return;} codes(n.left,pre+'0',out); codes(n.right,pre+'1',out); }
function layout(n,depth,ctr){
 n.depth=depth;
 if(!n.left){n.lx=ctr.n++;n.y=depth;return;}
 layout(n.left,depth+1,ctr);layout(n.right,depth+1,ctr);
 n.lx=(n.left.lx+n.right.lx)/2;n.y=depth;
}
function pathTo(n){const arr=[n];while(n.parent){n=n.parent;arr.push(n);}return arr.reverse();}
let sentence,fq,root,steps,leaves,leafOrder,code,maxDepth,totalChars,f=0;
const QDUR=90,MDUR=55,CDUR=45,SDUR=220;
function init(){
 sentence=SENTS[Math.floor(Math.random()*SENTS.length)];
 fq=freq(sentence);
 const t=build(fq);
 root=t.root;steps=t.steps;leaves=t.leaves;
 totalChars=Object.values(fq).reduce((a,b)=>a+b,0);
 layout(root,0,{n:0});
 code={};codes(root,'',code);
 leafOrder=leaves.slice().sort((a,b)=>a.char<b.char?-1:1);
 maxDepth=0;
 (function md(n){if(n.depth>maxDepth)maxDepth=n.depth;if(n.left){md(n.left);md(n.right);}})(root);
 f=0;
}
init();
function px(n){return leaves.length>1?40+n.lx*(W-80)/(leaves.length-1):W/2;}
function py(n){return maxDepth>0?100+n.y*(H-240)/maxDepth:100;}
function circle(cx,cy,r,fill,stroke){ctx.beginPath();ctx.arc(cx,cy,r,0,7);ctx.fillStyle=fill;ctx.fill();if(stroke){ctx.strokeStyle=stroke;ctx.lineWidth=2;ctx.stroke();}}
function label(t,cx,cy,col,size){ctx.fillStyle=col;ctx.font=(size||13)+'px monospace';ctx.textAlign='center';ctx.fillText(t,cx,cy+4);}
function drawTreeUpTo(k,prog){
 for(let i=0;i<=k&&i<steps.length;i++){
  const{l,r,p}=steps[i];const a=(i===k)?prog:1;
  ctx.globalAlpha=a;ctx.strokeStyle='#4fd1c5';ctx.lineWidth=2;
  ctx.beginPath();ctx.moveTo(px(p),py(p));ctx.lineTo(px(l),py(l));ctx.stroke();
  ctx.beginPath();ctx.moveTo(px(p),py(p));ctx.lineTo(px(r),py(r));ctx.stroke();
  ctx.globalAlpha=1;
  circle(px(p),py(p),16,'#1a2530','#4fd1c5');label(p.freq,px(p),py(p),'#e2e8f0',11);
 }
}
function drawLeaves(){leaves.forEach(n=>{circle(px(n),py(n),18,'#20303d','#63b3ed');label(n.char,px(n),py(n)-6,'#fff',13);label(n.freq,px(n),py(n)+10,'#a0aec0',10);});}
function drawQueueRow(list,y){
 const n=list.length;
 list.forEach((node,i)=>{
  const cx=40+i*((W-80)/Math.max(1,n-1));const hl=i<2&&n>1;
  circle(cx,y,20,hl?'#f6ad55':'#2d3748',hl?'#f6ad55':'#63b3ed');
  label(node.char||'+',cx,y-6,'#fff',13);label(node.freq,cx,y+10,'#cbd5e0',10);
 });
}
function walkHighlight(leaf,t){
 const path=pathTo(leaf);const nshow=Math.max(1,Math.ceil(path.length*t));
 ctx.strokeStyle='#f6e05e';ctx.lineWidth=4;
 for(let i=0;i<Math.min(nshow,path.length)-1;i++){ctx.beginPath();ctx.moveTo(px(path[i]),py(path[i]));ctx.lineTo(px(path[i+1]),py(path[i+1]));ctx.stroke();}
 circle(px(leaf),py(leaf),18,'#f6e05e','#fff');label(leaf.char,px(leaf),py(leaf)-6,'#000',13);
}
function drawCodeList(count){
 ctx.textAlign='left';ctx.font='13px monospace';ctx.fillStyle='#cbd5e0';let yy=60;
 for(let i=0;i<Math.min(count,leafOrder.length);i++){const n=leafOrder[i];ctx.fillText(n.char+' : '+code[n.char]+'  ('+n.freq+')',W-220,yy);yy+=17;}
 ctx.textAlign='center';
}
function drawSummary(){
 const bitsBefore=totalChars*8;let bitsAfter=0;for(const c in code)bitsAfter+=fq[c]*code[c].length;
 const ratio=(100*(1-bitsAfter/bitsBefore)).toFixed(1);
 ctx.textAlign='center';ctx.font='bold 15px monospace';ctx.fillStyle='#68d391';
 ctx.fillText('Before: '+bitsBefore+' bits   After: '+bitsAfter+' bits   Saved: '+ratio+'%',W/2,H-20);
}
function frame(){
 ctx.clearRect(0,0,W,H);
 ctx.textAlign='center';ctx.fillStyle='#fff';ctx.font='bold 20px sans-serif';
 ctx.fillText('Huffman Coding — "'+sentence+'"',W/2,26);
 const mergeTotal=steps.length*MDUR,walkTotal=leafOrder.length*CDUR;
 const e1=QDUR,e2=e1+mergeTotal,e3=e2+walkTotal,e4=e3+SDUR;
 if(f<e1){
  label('Priority Queue',W/2,64,'#63b3ed',16);
  drawQueueRow(leaves.slice().sort((a,b)=>a.freq-b.freq),H-100);
 }else if(f<e2){
  const k=Math.floor((f-e1)/MDUR),t=((f-e1)%MDUR)/MDUR;
  label('Merging step '+(k+1)+' / '+steps.length,W/2,64,'#63b3ed',16);
  drawTreeUpTo(k-1,1);drawLeaves();drawQueueRow(steps[k].snap,H-60);
  if(t>0.4)drawTreeUpTo(k,Math.min(1,(t-0.4)/0.6));
 }else if(f<e3){
  drawTreeUpTo(steps.length-1,1);drawLeaves();
  const idx=Math.floor((f-e2)/CDUR),t=((f-e2)%CDUR)/CDUR;
  label('Decoding walk: building code table',W/2,64,'#63b3ed',16);
  walkHighlight(leafOrder[Math.min(idx,leafOrder.length-1)],t);
  drawCodeList(idx+(t>0.5?1:0));
 }else if(f<e4){
  drawTreeUpTo(steps.length-1,1);drawLeaves();drawCodeList(leafOrder.length);drawSummary();
  label('Huffman tree complete',W/2,64,'#68d391',16);
 }else{init();}
 f++;requestAnimationFrame(frame);
}
frame();
</script></body></html>
Claude Haiku 4.5frontier referenceSTATLIM
Show code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Huffman Coding</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 text = 'the quick brown fox jumps over the lazy dog the fox jumps again';
const freq = {};
for (let c of text) {
  if (c !== ' ') freq[c] = (freq[c] || 0) + 1;
}

class Node {
  constructor(char, f) { this.char = char; this.freq = f; this.left = null; this.right = null; }
}

let heap = Object.entries(freq).map(([c, f]) => new Node(c, f));
let tree = null;
let step = 0;
let time = 0;
let phase = 'build';

function animate() {
  if (phase === 'build') {
    if (heap.length > 1) {
      heap.sort((a, b) => a.freq - b.freq);
      const left = heap.shift();
      const right = heap.shift();
      const parent = new Node(null, left.freq + right.freq);
      parent.left = left;
      parent.right = right;
      heap.push(parent);
      step++;
    } else if (heap.length === 1) {
      tree = heap[0];
      phase = 'show';
      time = 0;
    }
  } else if (phase === 'show' && time > 180) {
    heap = Object.entries(freq).map(([c, f]) => new Node(c, f));
    tree = null;
    step = 0;
    phase = 'build';
    time = 0;
  }

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

  ctx.fillStyle = '#0f0';
  ctx.font = '11px monospace';

  if (phase === 'build') {
    ctx.fillText('Building tree... Step ' + step, 20, 30);
    let y = 60;
    for (let i = 0; i < Math.min(8, heap.length); i++) {
      ctx.fillText(heap[i].char ? heap[i].char + ':' + heap[i].freq : '[' + heap[i].freq + ']', 20, y);
      y += 20;
    }
  }

  if (tree) {
    function drawTree(node, x, y, dx) {
      if (!node) return;
      ctx.fillStyle = '#0f0';
      ctx.beginPath();
      ctx.arc(x, y, 8, 0, 2 * Math.PI);
      ctx.fill();
      ctx.fillStyle = '#000';
      ctx.font = '9px monospace';
      if (node.char) ctx.fillText(node.char, x - 3, y + 3);
      else ctx.fillText(node.freq, x - 4, y + 3);

      if (node.left) {
        ctx.strokeStyle = '#0f0';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x - dx, y + 40);
        ctx.stroke();
        drawTree(node.left, x - dx, y + 40, dx / 2);
      }
      if (node.right) {
        ctx.strokeStyle = '#0f0';
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x + dx, y + 40);
        ctx.stroke();
        drawTree(node.right, x + dx, y + 40, dx / 2);
      }
    }
    drawTree(tree, w / 2, 20, 100);

    ctx.fillStyle = '#0f0';
    ctx.font = '11px monospace';
    ctx.fillText('Huffman Tree Complete', 20, 30);
  }

  time++;
  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
deepseek-r1:14b —20.3 sOK
mistral-small:24b —138.6 sOK
qwen3:14b —35.4 sOK
qwen3.5:9b —60.6 sOK
qwen3:8b ——harness
qwen3-coder:30b —35.8 sOK
gemma4:26b ——harness
llama3.1:8b —139.9 sOK
qwq:32b —142.7 sOK
deepseek-r1:32b —144.1 sOK
command-r:35b —158.7 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">
<title>Huffman Coding — Live Build</title>
<style>
html,body{margin:0;height:100%;background:#04060c;overflow:hidden;display:flex;align-items:center;justify-content:center}
canvas{max-width:100vw;max-height:100vh;display:block}
</style>
</head>
<body>
<canvas id="c" width="1200" height="760"></canvas>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d'),W=1200,H=760,BY=548,LH=52;
const SENTENCES=["huffman coding builds optimal prefix codes","the quick brown fox jumps over lazy dogs","entropy favours the shortest code words","mississippi banks and sweet bananas","a canvas grows one binary tree per second","abracadabra alakazam hocus pocus magic"];
const F=(s,w)=>(w||400)+" "+s+"px ui-monospace,Menlo,Consolas,monospace";
const ease=u=>u<0?0:u>1?1:(u<.5?4*u*u*u:1-Math.pow(-2*u+2,3)/2);
let S,q,all,pair,ph,t,pos,posA,posB,newId,root,codes,revealed,steps,letters;
function sortQ(){q.sort((a,b)=>a.f-b.f||a.id-b.id)}
function layout(rs){const P={};let i=0,tot=0;rs.forEach(r=>tot+=r.leaves);const L=72,R=W-72,sl=(R-L)/Math.max(tot,1);
const walk=n=>{if(!n.l)return P[n.id]={x:L+sl*(i+++.5),y:BY};const a=walk(n.l),b=walk(n.r);return P[n.id]={x:(a.x+b.x)/2,y:Math.min(a.y,b.y)-LH}};
rs.forEach(walk);return P}
function lerpP(A,B,u){const P={};for(const k in B)P[k]=A[k]?{x:A[k].x+(B[k].x-A[k].x)*u,y:A[k].y+(B[k].y-A[k].y)*u}:B[k];return P}
function nodesOf(){const o=[],w=n=>{if(n.l){w(n.l);w(n.r)}o.push(n)};q.forEach(w);return o}
function buildCodes(){codes=[];const rec=(n,c)=>{if(!n.l){codes.push({ch:n.ch,f:n.f,c:c||"0"});return}rec(n.l,c+"0");rec(n.r,c+"1")};rec(root,"");
codes.sort((a,b)=>a.c.length-b.c.length||(a.ch<b.ch?-1:1))}
function init(){S=SENTENCES[Math.random()*SENTENCES.length|0];const m=new Map();letters=0;
for(const ch of S.replace(/[^a-z]/g,"")){m.set(ch,(m.get(ch)||0)+1);letters++}
let uid=0;q=[...m].map(([ch,f])=>({id:uid++,ch:ch,f:f,leaves:1}));nextId=uid;all=q.length;steps=0;
sortQ();pos=layout(q);pair=[q[0],q[1]];ph="hl";t=0;root=null;codes=[];revealed=0;newId=-1}
let nextId=0,last=0;
function update(dt){t+=dt;
if(ph==="hl"&&t>.5){const[a,b]=pair;posA=pos;q=q.filter(n=>n!==a&&n!==b);
const p={id:nextId++,f:a.f+b.f,l:a,r:b,leaves:a.leaves+b.leaves};q.push(p);sortQ();posB=layout(q);newId=p.id;steps++;ph="mv";t=0}
else if(ph==="mv"&&t>.72){pos=posB;newId=-1;t=0;if(q.length<2){root=q[0];buildCodes();ph="codes"}else{pair=[q[0],q[1]];ph="hl"}}
else if(ph==="codes"){revealed=Math.min(codes.length,Math.floor(t/.26));if(t>codes.length*.26+3.6)init()}}
function pathSet(){const s=new Set();if(ph!=="codes"||!revealed)return s;let n=root;s.add(n.id);
for(const b of codes[revealed-1].c){if(!n.l)break;n=b==="0"?n.l:n.r;s.add(n.id)}return s}
function node(P,n,a,hot){const p=P[n.id];if(!p)return;const leaf=!n.l,r=leaf?19:14;g.globalAlpha=a;
const hue=leaf?(n.ch.charCodeAt(0)*37)%360:200;
if(hot){g.beginPath();g.arc(p.x,p.y,r+7+Math.sin(performance.now()/140)*2.5,0,7);g.fillStyle="hsla("+hue+",90%,60%,.22)";g.fill()}
g.beginPath();g.arc(p.x,p.y,r,0,7);g.fillStyle=leaf?"hsla("+hue+",70%,55%,.9)":"#0d1626";g.fill();
g.lineWidth=hot?2.5:1.4;g.strokeStyle=leaf?"hsla("+hue+",90%,78%,.95)":hot?"#ffd479":"#3d5b86";g.stroke();
g.textAlign="center";g.fillStyle=leaf?"#08111c":"#8fd4f0";g.font=F(leaf?17:12,700);g.fillText(leaf?n.ch.toUpperCase():n.f,p.x,p.y+(leaf?6:4));
if(leaf){g.fillStyle="#7f8fa8";g.font=F(12);g.fillText(n.f,p.x,p.y+r+15)}g.globalAlpha=1}
function render(){const gr=g.createLinearGradient(0,0,0,H);gr.addColorStop(0,"#0a1020");gr.addColorStop(1,"#04060c");g.fillStyle=gr;g.fillRect(0,0,W,H);
g.textAlign="left";g.fillStyle="#7ee8fa";g.font=F(23,700);g.fillText("HUFFMAN CODING",40,46);
g.fillStyle="#54637d";g.font=F(14);g.fillText('"'+S+'"   '+letters+" letters, "+all+" symbols",40,70);
g.textAlign="right";g.fillStyle="#9aa7bd";g.font=F(14);
g.fillText(ph==="codes"?"tree complete — walking for code words":"merge "+steps+" / "+(all-1)+"   queue: "+q.length+" nodes",W-40,46);
const u=ph==="mv"?ease(t/.72):1,P=ph==="mv"?lerpP(posA,posB,u):pos,hi=pathSet(),ns=nodesOf();
for(const n of ns){if(!n.l)continue;const a=P[n.id];if(!a)continue;const na=n.id===newId?u:1;
for(const[ch,bit]of[[n.l,"0"],[n.r,"1"]]){const b=P[ch.id];if(!b)continue;const on=hi.has(ch.id)&&hi.has(n.id);
g.globalAlpha=na;g.beginPath();g.moveTo(a.x,a.y);g.lineTo(b.x,b.y);g.strokeStyle=on?"#ffd479":"#26456b";g.lineWidth=on?3:1.6;g.stroke();
g.fillStyle=on?"#ffd479":"#3f6a99";g.font=F(11,700);g.textAlign="center";g.fillText(bit,(a.x+b.x)/2+(bit==="0"?-9:9),(a.y+b.y)/2+4);g.globalAlpha=1}}
const hot=ph==="hl"?new Set(pair.map(n=>n.id)):new Set();
for(const n of ns)node(P,n,n.id===newId?u:1,hot.has(n.id)||hi.has(n.id));
if(ph!=="codes"){g.textAlign="left";g.fillStyle="#4d5d75";g.font=F(12,700);g.fillText("PRIORITY QUEUE — smallest two merge next",40,632);
const n=q.length,w=Math.min(64,(W-80)/n-8);
q.forEach((nd,i)=>{const x=40+i*(w+8),y=648,on=ph==="hl"&&(nd===pair[0]||nd===pair[1]);
g.fillStyle=on?"rgba(255,212,121,.18)":"rgba(126,232,250,.07)";g.strokeStyle=on?"#ffd479":"#1e3550";
g.lineWidth=1.2;g.beginPath();g.roundRect(x,y,w,46,7);g.fill();g.stroke();g.textAlign="center";
g.fillStyle=nd.ch?"hsl("+(nd.ch.charCodeAt(0)*37)%360+",70%,68%)":"#6fa8d6";g.font=F(15,700);g.fillText(nd.ch?nd.ch.toUpperCase():"•",x+w/2,y+21);
g.fillStyle="#8b9ab2";g.font=F(12);g.fillText(nd.f,x+w/2,y+38)});
g.textAlign="left";g.fillStyle="#3d4a5e";g.font=F(12);g.fillText("building tree bottom-up: pop 2 smallest → merge → push back",40,724)}
else{g.textAlign="left";g.fillStyle="#4d5d75";g.font=F(12,700);g.fillText("CODE WORDS (prefix-free, read root → leaf)",40,626);
let bits=0;codes.forEach((c,i)=>{if(i>=revealed)return;bits+=c.f*c.c.length;const col=i/5|0,row=i%5,x=40+col*195,y=650+row*20;
g.fillStyle="hsl("+(c.ch.charCodeAt(0)*37)%360+",70%,66%)";g.font=F(14,700);g.fillText(c.ch.toUpperCase(),x,y);
g.fillStyle="#5f6f88";g.font=F(12);g.fillText("×"+c.f,x+18,y);g.fillStyle=i===revealed-1?"#ffd479":"#8fd4f0";g.font=F(13,700);g.fillText(c.c,x+56,y)});
const before=letters*8;g.textAlign="right";g.font=F(14,700);g.fillStyle="#6f8099";
g.fillText("fixed 8-bit: "+before+" bits",W-40,668);g.fillStyle="#7ee8fa";g.fillText("huffman: "+bits+" bits",W-40,690);
g.fillStyle="#8de08d";g.fillText("saved "+(revealed?Math.round((1-bits/before)*100):0)+"%   avg "+(revealed?(bits/letters).toFixed(2):"0.00")+" bits/letter",W-40,712);
if(revealed>=codes.length){g.fillStyle="#3d4a5e";g.font=F(12);g.fillText("restarting with a new sentence…",W-40,736)}}}
function frame(ts){const dt=Math.min(.05,(ts-last)/1000||0);last=ts;update(dt);render();requestAnimationFrame(frame)}
init();requestAnimationFrame(frame);
</script>
</body>
</html>