Overview › Code › R73
Code · blind round

R73 · KMP string search with prefix table

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 the Knuth-Morris-Pratt string search on a <canvas>. First build the prefix table for a short pattern and draw it as a labelled row, then slide the pattern along a longer text: highlight the compared characters, and when a mismatch occurs show the pattern JUMPING by the prefix-table amount instead of shifting by one. Mark every match found and show the number of character comparisons. 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:9bSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KMP Animation</title>
    <style>body { margin: 0; background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; } canvas { border: 2px solid #333; background: #000; }</style>
</head>
<body>
    <canvas id="kmpCanvas"></canvas>
    <script>
        const canvas = document.getElementById('kmpCanvas');
        const ctx = canvas.getContext('2d');

        // Constants for drawing
        const P_LEN = 5; // Pattern length
        const T_LEN = 100; // Text length
        canvas.width = 900;
        canvas.height = 100;

        let text = "ABABDABACADABABCABAB";
        let pattern = "ABABA";
        let lps = [];

        // State variables
        let i = 0, j = 0; // Pointers for T and P
        let comparisonCount = 0;
        let matchFound = false;
        let animationState = 'PREP'; // PREP -> LPS_DISPLAY -> SEARCH
        let lastShiftAmount = 0;

        // --- Utility Functions ---

        function calculateLPS(pat) {
            const m = pat.length;
            lps = Array(m).fill(0);
            let length = 0;
            let k = 1;
            for (let i = 1; i < m; i++) {
                while (k > 0 && pat[i] !== pat[k]) {
                    k = lps[k - 1];
                }
                if (pat[i] === pat[k]) {
                    lps[i] = k + 1;
                    k++;
                } else {
                    lps[i] = k;
                }
                length = Math.max(length, lps[i]);
            }
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const charWidth = 25;
            const offset = (canvas.width - (T_LEN * charWidth)) / 2;

            // Draw Text T
            let textOutput = "";
            for (let k = 0; k < T_LEN; k++) {
                textOutput += `<span style="color: white; margin-right: 4px;">${text[k]}</span>`;
            }
            ctx.font = "24px monospace";
            ctx.fillStyle = "#AAA";
            ctx.fillText(`Text: ${text.substring(0, T_LEN)}`, 10, 35);

            // Draw Pattern P
            let patternOutput = "";
            for (let k = 0; k < pattern.length; k++) {
                patternOutput += `<span style="color: white; margin-right: 4px;">${pattern[k]}</span>`;
            }
            ctx.fillText(`Pattern: ${pattern}`, 10, 75);

            // Draw LPS Table (Labels)
            let lpsStr = "LPS Array: ";
            for (let k = 0; k < pattern.length; k++) {
                lpsStr += `${lps[k] === 0 ? '0' : lps[k].toString().padStart(2, ' ') || ''} `;
            }
            ctx.font = "14px monospace";
            ctx.fillStyle = "#888";
            ctx.fillText(lpsStr, 250, 35);

            // Draw comparison highlights and match markers
            for (let k = 0; k < T_LEN; k++) {
                const x = offset + k * charWidth;
                // Highlight current text character being compared
                if (k === i && j > 0 || (i >= 0 && j === 0)) {
                    ctx.fillStyle = '#FFD700'; // Gold highlight for comparison focus
                    ctx.fillRect(x, 25, charWidth - 4, 25);
                } else {
                    ctx.fillStyle = 'transparent';
                    ctx.fillRect(x, 25, charWidth - 4, 25);
                }
            }

            // Draw Match Marker (if found)
            if (matchFound) {
                 ctx.font = "30px monospace";
                 ctx.fillStyle = '#00FF7F'; // Green for match
                 ctx.fillText("MATCH FOUND!", 50, 120);

                // Draw index tracking
                ctx.fillStyle = 'red';
                ctx.font = "24px monospace";
                ctx.fillText(`Matches: ${Math.floor(comparisonCount / 5)} | Comparisons: ${comparisonCount}`, 350, 120);
            } else {
                 // Draw index tracking
                ctx.fillStyle = '#888';
                ctx.font = "24px monospace";
                ctx.fillText(`Matches: 0 | Comparisons: ${comparisonCount}`, 350, 120);
            }

        }


        function kmpStep() {
            if (animationState === 'PREP') {
                calculateLPS(pattern);
                animationState = 'LPS_DISPLAY';
                draw();
                return requestAnimationFrame(kmpStep);
            }

            // --- KMP Search Logic (The core animation step) ---
            let comparisonMade = false; 

            if (j < pattern.length && i < text.length) {
                comparisonMade = true;
                const match = text[i] === pattern[j];
                comparisonCount++;

                // Highlight the current matching position on P
                ctx.fillStyle = '#FF4500'; 
                ctx.fillRect((canvas.width - (T_LEN * 25)) / 2 + i * 25, 25, 25, 25);

                if (match) {
                    i++;
                    j++;
                    if (j === pattern.length) {
                        // SUCCESS: Full match found
                        animationState = 'MATCH';
                        matchFound = true;
                        lastShiftAmount = j - pattern.length + 1; // Record start index
                    } else if (i < text.length) {
                         // Continue animation on next frame
                    }
                } else {
                    // MISMATCH: Shift required
                    comparisonMade = true;
                    if (j !== 0) {
                        // Calculate jump using LPS array
                        let newJ = lps[j - 1];
                        lastShiftAmount = j - newJ; // The shift amount conceptually

                        // Reset pointers for the next attempt
                        j = newJ;
                        i += lastShiftAmount;

                    } else {
                        // First character mismatch, just move text pointer
                        j = 0;
                        i++;
                    }
                }
            }

            draw();

            if (i >= text.length) {
                animationState = 'FINISHED';
                return;
            }

            // Throttle the animation for visibility
            setTimeout(() => {
                requestAnimationFrame(kmpStep);
            }, 150); // Animation speed delay
        }

        // Initial call to start the process
        kmpStep();
    </script>
</body>
</html>
```
qwen3-coder:30bSTATLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<title>KMP Animation</title>
<style>body{background:#111;color:white;font-family:monospace;margin:0;display:flex;justify-content:center;align-items:center;min-height:100vh;}canvas{border:2px solid #444;}</style>
</head>
<body>
<canvas id="kmpCanvas" width="800" height="150"></canvas>
<script>
const canvas = document.getElementById('kmpCanvas');
const ctx = canvas.getContext('2d');

let text, pattern;
let lps;
let state = { 
    t_idx: 0, p_idx: 0, matchFound: false, comparisons: 0, totalTime: 0, finished: false
};

// --- Helper Functions ---

function computeLPSArray(P) {
    const m = P.length;
    const lpsA = new Array(m).fill(0);
    let length = 0; // length of the previous longest prefix suffix
    let i = 1;
    while (i < m) {
        if (P[i] === P[length]) {
            length++;
            lpsA[i] = length;
            i++;
        } else {
            if (length !== 0) {
                length = lpsA[length - 1];
            } else {
                lpsA[i] = 0;
                i++;
            }
        }
    }
    return lpsA;
}

function initialize(newText, newPattern) {
    text = newText || "ABABDABACDABABCABAB";
    pattern = newPattern || "ABABCABAB";
    lps = computeLPSArray(pattern);
    state.t_idx = 0;
    state.p_idx = 0;
    state.matchFound = false;
    state.comparisons = 0;
    state.totalTime = 0;
    state.finished = false;
}

// --- Drawing and Animation Logic ---

function drawScene() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.font = "24px monospace";
    const lineHeight = 35;

    // Draw Text (Background)
    let textY = 20;
    ctx.fillStyle = '#333';
    ctx.fillText(text, 10, textY);
    
    // Draw Pattern (Search Indicator)
    let patternX = 10 + ((state.t_idx - state.p_idx + 1) * 30);
    if (patternX < 10) patternX = 10;
    ctx.fillStyle = '#555';
    ctx.fillText(pattern, patternX, textY);

    // Highlight current comparison point
    let highlightX = 10 + (state.t_idx * 30);
    if (highlightX >= canvas.width - 5) highlightX = canvas.width - 6; // Clamp for end of line
    ctx.fillStyle = 'rgba(255, 255, 0, 0.4)';
    ctx.fillRect(Math.max(0, highlightX), 10, 30, 35);

    // Draw Status Info
    let statusY = textY + lineHeight + 10;
    ctx.fillStyle = 'white';
    ctx.fillText(`Comparisons: ${state.comparisons} | Matches Found: ${state.matchFound ? '!!!' : ''}`, 10, statusY);

    if (state.finished) {
        ctx.font = "30px monospace";
        ctx.fillStyle = '#99ff99';
        ctx.fillText("SEARCH COMPLETE", 10, statusY + 40);
    }
}


function kmpStep() {
    if (state.finished) return false;

    let t_char, p_char;
    let newPIdx = state.p_idx;
    let jumpDistance = 0;

    // Check for end of text
    if (state.t_idx >= text.length) {
        state.finished = true;
        return false;
    }

    t_char = text[state.t_idx];
    p_char = pattern[state.p_idx];
    
    // The actual comparison logic
    if (t_char === p_char) {
        state.comparisons++;
        state.t_idx++;
        newPIdx++;

        if (newPIdx === pattern.length) {
            // Match found!
            state.matchFound = true;
            console.log(`Match at index ${state.t_idx - 1}`);
            
            // Reset for next search (or keep searching if we want overlapping matches)
            let matchStartIdx = state.t_idx - pattern.length;
            if (matchStartIdx > 0) { // Avoid infinite loop on empty text/pattern
                state.p_idx = lps[pattern.length - 1]; 
                state.t_idx += (lps[pattern.length - 1] - state.p_idx);
            } else {
                 state.p_idx = 0;
                 state.t_idx++; // Simple advance
            }

        } else if (newPIdx > 0) {
             // Successful match, just move pointers
        }

    } else {
        // Mismatch handling
        state.comparisons++;
        let prevPIdx = state.p_idx;
        
        if (prevPIdx !== 0) {
            // Jump using LPS table
            newPIdx = lps[prevPIdx - 1];
        } else {
            // No jump possible, just advance text pointer
            newPIdx = 0;
        }

        state.p_idx = newPIdx;
        state.t_idx++; // Always advance the text pointer on mismatch
    }

    state.p_idx = Math.min(state.p_idx, pattern.length);
    state.t_idx = Math.min(state.t_idx, text.length);

    return true;
}


function animate() {
    drawScene();
    if (kmpStep()) {
        requestAnimationFrame(animate);
    } else if (!state.finished) {
         // Handle case where kmpStep might stop prematurely
    }
}

// --- Startup ---
initialize(); 
animate();
</script>
</body>
</html>
```
gemma4:26bharness
No answer reached us (THIN) — and the reason lies with our setup, not with the model: reasoning consumed the entire token budget (3 min 28 s, zero characters of answer); larger budgets are refused by the gateway Counted as a did-not-finish, but it is not evidence about this model.
qwen3:14bOKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>KMP Algorithm Visualization</title>
    <style>body {background: #1a1a2e; color: white; font-family: monospace;} canvas {border: 1px solid #4c3b79; display: block; margin: 20px auto; background: #2a2a4a;}</style>
</head>
<body>
<canvas id="kmpCanvas"></canvas>

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

    let T, P; // Text and Pattern
    let lT, lP;
    let pi;   // Failure function (prefix table)

    let k = 0; // Current text index
    let j = 0; // Current pattern index
    let comparisons = 0;
    let matches = 0;
    let animationRunning = true;

    const CANVAS_WIDTH = 900;
    const CANVAS_HEIGHT = 150;
    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    function generateRandom(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
    function generateString(length) { let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; let result = ""; for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(generateRandom(0, 25))); return result; }

    // KMP Prefix Function Generation
    function computePrefixTable(pattern) {
        const m = pattern.length;
        pi = new Array(m).fill(0);
        let length = 0; // len of the previous longest prefix suffix
        for (let i = 1; i < m; i++) {
            while (length > 0 && pattern[i] !== pattern[length]) {
                length = pi[length - 1];
            }
            if (pattern[i] === pattern[length]) {
                length++;
            }
            pi[i] = length;
        }
    }

    // Initialization function
    function initialize() {
        T = generateString(Math.min(70, Math.floor(CANVAS_WIDTH / 15))); // Text length based on canvas width
        P = generateString(Math.max(3, Math.min(8, T.length - 2))); // Pattern length
        lT = T.length;
        lP = P.length;

        computePrefixTable(P);
        k = 0; j = 0; comparisons = 0; matches = 0;
        animationRunning = true;
    }

    // Drawing function
    function draw() {
        ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

        const textCharSize = CANVAS_WIDTH / lT;
        const patternCharSize = (CANVAS_WIDTH / lP) * 1.2; // Make pattern slightly bigger for visibility

        // Draw Text
        ctx.font = 'bold 30px monospace';
        ctx.fillStyle = '#e6e6fa';
        let textStr = '';
        for(let i=0; i<lT; i++) {
            textStr += T[i] + ' ';
        }
        ctx.fillText(textStr, 10, CANVAS_HEIGHT / 2 + 10);

        // Draw Pattern (Above Text)
        ctx.font = 'bold 35px monospace';
        let patternDisplay = '';
        for(let i=0; i<lP; i++) {
            patternDisplay += P[i] + ' ';
        }
        ctx.fillText('Pattern:', 10, CANVAS_HEIGHT / 2 - 40);
        ctx.fillText(patternDisplay, 150, CANVAS_HEIGHT / 2 - 40);

        // Highlight current comparison characters
        if (k > 0 && j < lP) {
            // Text highlight at k
             ctx.fillStyle = '#ffeb3b'; // Yellow
             ctx.fillRect(k * textCharSize + (k > 0 ? textCharSize / 2 : 10), CANVAS_HEIGHT/2 - 45, textCharSize*0.8, 40);

            // Pattern highlight at j
            ctx.fillStyle = '#ffeb3b';
            let patternX = 150 + (j * (CANVAS_WIDTH / lP) * 1.2) - ((CANVAS_WIDTH/lP) * 0.6); // Approximate spacing
            if (patternX > 0) {
                ctx.fillRect(patternX, CANVAS_HEIGHT/2 - 45, (CANVAS_WIDTH / lP) * 1.2 * 0.8, 40);
            }

        } else if (matches > 0 && k < lT) {
             // Draw match indicator below text
             ctx.fillStyle = 'rgba(34, 157, 34, 0.8)'; // Green overlay
             ctx.fillRect(k * textCharSize + 5, CANVAS_HEIGHT / 2 - 10, (lT - k) * textCharSize, 20);
        }

         // Draw Prefix Table Label
        ctx.font = '30px monospace';
        ctx.fillStyle = '#9d86ff';
        let prefixLabel = "Prefix Table (pi): ";
        for(let i=0; i<lP; i++) {
            prefixLabel += pi[i] + ' ';
        }
        ctx.fillText(prefixLabel, 150, CANVAS_HEIGHT / 2 - 40);

    }

    // KMP Core Logic Step
    function kmpStep() {
        if (!animationRunning) return;

        draw(); // Redraw state before computation

        comparisons++;

        const charT = T[k];
        const charP = P[j];

        let next_j, next_k;

        if (charT === charP) {
            // Match found
            next_j = j + 1;
            next_k = k + 1;
        } else {
            // Mismatch: Use failure function jump
            next_j = pi[j - 1] !== undefined ? pi[j - 1] : 0;
            next_k = k + 1; // Text index always advances unless text is exhausted

            if (j === 0) {
                // If mismatch at the start of the pattern, just move to next text character
                next_j = 0;
                next_k = k + 1;
            }
        }

        // Update state variables
        if (next_j === lP) {
            // Full match!
            matches++;
            console.log(`Match found at index ${k - lP + 1}`);
            // After a full match, we jump using the failure function again
            j = pi[lP - 1];
            next_j = j; // The pattern effectively shifts by this amount

        } else {
             j = next_j;
        }

        k++;

        if (k > lT) {
            // Text exhausted, restart simulation
            initialize();
        }
    }

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

        kmpStep();
        requestAnimationFrame(animate);
    }

    // Start the process
    initialize();
    animate();
</script>
</body>
</html>
```
llama3.1:8bOKLIMFORMAT
Show code
```html
<!DOCTYPE html>
<html>
<head>
<style>
  canvas { border: 1px solid black; }
</style>
</head>
<body>
<script>
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = 800;
canvas.height = 100;

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

function drawText(text, x, y) {
  ctx.font = '24px Arial';
  ctx.textAlign = 'left';
  ctx.textBaseline = 'top';
  ctx.fillStyle = 'black';
  ctx.fillText(text, x, y);
}

function drawLine(x1, y1, x2, y2) {
  ctx.beginPath();
  ctx.moveTo(x1 + canvas.width / 2, y1);
  ctx.lineTo(x2 + canvas.width / 2, y2);
  ctx.stroke();
}

var pattern = 'abba';
var text = 'abcabbbdabbada';

function prefixFunction(pattern) {
  var p = new Array(pattern.length).fill(0);
  var i = 1;
  var j = 0;
  while (i < pattern.length) {
    if (pattern[i] == pattern[j]) {
      p[i++] = ++j;
    } else {
      if (j != 0)
        j = p[j - 1];
      else
        i++;
    }
  }
  return p;
}

function kmpSearch(text, pattern, prefixTable) {
  var i = 0;
  var j = 0;
  while (i < text.length && j < pattern.length) {
    if (text[i] == pattern[j]) {
      i++;
      j++;
    } else if (j != 0)
      j = prefixTable[j - 1];
    else
      i++;
  }
  return [i, j];
}

var prefixTable = prefixFunction(pattern);
ctx.fillStyle = 'black';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
drawText('Prefix table for "' + pattern + '"', canvas.width / 2, 20);

for (var i = 0; i < prefixTable.length; i++) {
  ctx.fillStyle = 'black';
  ctx.fillText(prefixTable[i], i * 50 + 10, 60);
}

function animate() {
  var result = kmpSearch(text, pattern, prefixTable);
  if (result[1] == pattern.length) {
    document.body.style.cursor = 'auto';
    console.log('Found a match at index', result[0]);
    ctx.fillStyle = 'black';
    ctx.textAlign = 'left';
    ctx.fillText(pattern + ' found at index ' + result[0], 10, canvas.height - 30);
  } else {
    document.body.style.cursor = 'crosshair';
  }

  var i = 0;
  while (i < text.length && i < canvas.width) {
    if (text[i] == pattern[0]) {
      ctx.fillStyle = 'red';
      ctx.font = '24px Arial';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      ctx.fillText(text.substring(0, i + 1), i - 3 + canvas.width / 2, 40);
    } else if (i >= result[1] && i < Math.min(result[1], prefixTable[result[1] - 1])) {
      ctx.fillStyle = 'blue';
      ctx.font = '24px Arial';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      drawLine(i + canvas.width / 2, 50, i + canvas.width / 2, 60);
    }

    if (i < prefixTable[result[1] - 1]) {
      ctx.fillStyle = 'green';
      ctx.font = '24px Arial';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'top';
      ctx.fillText(prefixTable.slice(0, result[1]), i + canvas.width / 2 - 20, 80);
    }

    ctx.fillStyle = 'black';
    ctx.font = '18px Arial';
    ctx.textAlign = 'right';
    ctx.textBaseline = 'bottom';
    ctx.fillText(i, canvas.width - 10, 90);

    i++;
  }
  requestAnimationFrame(animate);
}
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.
deepseek-r1:14bERRBLANKFORMAT
Show code
```html
<!DOCTYPE html>
<html><body style="background:#f0f0f0; margin:0; display:flex; justify-content:center; align-items:center;height:100vh">
<canvas id=c width=800 height=200></canvas>
<div id=o style="position:absolute;top:5px;left:5px;color:black"></div>
<script>
const c=document.getElementById('c'), ctx=c.getContext('2d');
let p,t,i,j,k,matchCount=1,charCompares=0;
function getP() {return String.fromCharCode(97+Math.random()*26).repeat(4)}
p=getP();t=getP().repeat(5);let pf=[];for(let i=0;i<=p.length-1;pf[i]=i==0?0:i+1 while pf[i]<=i&&(p[pf[i]]==p[i]?pf[i]++:pf[i--])) 
function animate(){c.width=c.width;o.textContent=`Matches: ${matchCount} | Comparisons: ${charCompares}`;
const cw=80,pw=p.length,tx=t.length;
ctx.fillStyle='#f0f'; ctx.fillRect(0,0,c.width,20);
ctx.fillStyle='black'; ctx.font='14px monospace';
for(j in p) {if(pf[j]==j-1)ctx.fillText(`${pf[j]}`,Number(j)*cw+30,25); else ctx.fillText(pf[j],Number(j)*cw+30,25)}
let x=i% tx; j=i?i-(k=pf[k])||0:k;
while(x<tx){if(i<j)p[i++]=''; if(!t[x]==p[j])break;if(p[j]){charCompares++;j++;x++;if(j==pw)matchCount++,i--}
else break;x++} 
ctx.fillStyle='#0f0'; ctx.fillRect(cw*i, 35, cw*pw,14);
if(i<tx)for(let m=0;m<pw&&t[i+m]===p[m];m++)ctx.fillStyle='#fff', ctx.fillRect(cw*(i+m),35,cw,14)}
setInterval(animate,16)
</script>
</body></html>
```
mistral-small:24bHANGFORMAT
Show code
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>KMP Animation</title>
<style>
  body {margin:0;display:flex;justify-content:center;align-items:center;height:100vh;background:#f0f0f0;}
  canvas {border:1px solid #000;}
</style>
</head>
<body>
<canvas id="kmpCanvas" width="800" height="400"></canvas>
<script>
function kmpSearch(text, pattern) {
    let lps = new Array(pattern.length);
    let i = 0, j = -1;
    lps[0] = -1;
    while (i < pattern.length) {
        if ((j === -1 || pattern[i] === pattern[j]) && i + 1 < pattern.length) {
            i++;
            j++;
            lps[i] = j;
        } else {
            j = lps[j];
        }
    }

    let matches = [];
    i = 0; j = 0;
    while (i < text.length) {
        if (pattern[j] === text[i]) { i++; j++; }
        if (j === pattern.length) { matches.push(i - j); j = lps[j]; }
        else if ((i < text.length) && (pattern[j] !== text[i])) {
            if (j != 0){ j = lps[j]; }else{ i++;}
        }
    }
    return {lps, matches};
}

const canvas = document.getElementById('kmpCanvas');
const ctx = canvas.getContext('2d');

function drawText(text, y) {
    ctx.fillStyle = '#000';
    text.split('').forEach((char, index) => {
        ctx.fillText(char, 10 + index * 20, y);
    });
}

function animateKMP() {
    const pattern = 'ababc';
    const text = 'ababcababcababcab';

    let { lps, matches } = kmpSearch(text, pattern);

    let frame = 0;
    let i = 0, j = 0;

    function drawFrame() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawText(pattern, 50);
        drawText(text, 100);

        if (frame == 0) {drawText(lps.join(' '), 20);}

        let textY = 100;

        ctx.fillStyle = '#f00';
        ctx.fillRect(10 + i * 20 - 5, textY - 5, 30, 30);

        if (i >= j) ctx.fillText(pattern[j], 870, 60);

        let matchesCount = 0;

        if (pattern[j] === text[i]) { i++; j++; }
        else {
            if (j != 0) {
                i--;
                j = lps[j];
                while(j != 0 && pattern[j] !== text[i]){
                    j = lps[j];
                    matchesCount += 1;
                }
            }else{
                i++;
            }
            frame++;
        }

        ctx.fillStyle = '#ff0';
        matches.forEach(match => {
            ctx.fillRect(10 + match * 20 - 5, textY - 5, 30, 30);
        });
        ctx.fillText('Comparisons: ' + (i + j), 10, 40);

        if (i >= text.length) {
            setTimeout(() => {animateKMP();}, 2000);
            return;
        }
        requestAnimationFrame(drawFrame);
    }

    drawFrame();
}

animateKMP();

</script>
</body>
</html>
```
qwq:32bERRBLANKLIM
Show code
<think>

Okay, I need to create a self-contained HTML file that animates the Knuth-Morris-Pratt (KMP) algorithm. Let me start by recalling how KMP works. The key part is building the prefix table (also called the failure function) for the pattern, which tells us how much to shift the pattern when a mismatch occurs. The animation should first show the prefix table, then slide the pattern over the text, highlighting comparisons, and jumping when there's a mismatch. Also, it has to count comparisons and restart after finishing.

First, the HTML structure. The file must have a canvas, and all the JavaScript in the same file. Since the user wants it self-contained, I'll use a script tag inside the body. The strict line limit is 120 lines, so I need to keep everything concise.

Let me outline the steps:

1. **Generate Random Text and Pattern**: The text should be longer, maybe around 20 characters, and the pattern 4-5 characters. Random each time when restarting.

2. **Compute Prefix Table**: The prefix array for the pattern. For each position i, compute the longest proper prefix which is also a suffix.

3. **Animation Setup**:
   - Draw the prefix table as a labeled row at the top of the canvas.
   - Draw the text and pattern below it.
   - Use requestAnimationFrame for smooth animation.

4. **Animation Steps**:
   - Animate building the prefix table (maybe step through each position).
   - Then shift the pattern along the text, highlighting compared characters.
   - When a mismatch occurs, jump the pattern by the prefix value instead of just 1.
   - Mark matches and count comparisons.

5. **Restart Mechanism**: After finishing, restart with new text and pattern. Since there's no user input, after finishing, schedule another run.

Potential challenges:

- Drawing text and pattern on canvas with clear positions.
- Implementing the prefix table correctly.
- Handling the animation frames for each step (building the prefix, then sliding).
- Keeping track of the animation state (e.g., building prefix, searching, done).
- Ensuring the code stays under 120 lines.

Starting with variables. Maybe use a global object or variables in the script. Let's see:

Variables needed:

- canvas and context
- text and pattern strings
- prefix array
- animation state (phases: building prefix, searching, done)
- current positions (i in text, j in pattern)
- comparison count
- animation timing (like step for animation frames)

First, the HTML:

```html
<canvas></canvas>
<script>...</script>
```

Now, the script. Let's structure the code with an animation loop using requestAnimationFrame. The main function will handle different phases.

Initialize canvas dimensions. Maybe 800x400 pixels.

In setup:

function init() {
  // generate random text and pattern
  const text = generateRandomString(20); // 20 characters
  const pattern = generateRandomString(4); // maybe 4 letters
  // compute prefix table
  const prefix = computePrefix(pattern);
  // reset animation states
  // ...
}

But since it's self-contained, all has to be inside the script. Need functions for generating random strings and computing prefix.

Wait, in the code, all functions must be inline or part of the script.

Implement computePrefix:

function computePrefix(pattern) {
  let prefix = Array(pattern.length).fill(0);
  let len = 0;
  for (let i = 1; i < pattern.length; ) {
    if (pattern[i] === pattern[len]) {
      len++;
      prefix[i] = len;
      i++;
    } else {
      if (len !== 0) len = prefix[len-1];
      else prefix[i++] = 0;
    }
  }
  return prefix;
}

Generate random string:

function randomString(length) {
  const chars = 'abcdefghijklmnopqrstuvwxyz';
  return Array.from({length}, () => chars[Math.floor(Math.random()*chars.length)]).join('');
}

Now, the animation phases. Let's have a variable indicating current phase:

let phase = 'prefix'; // or 'search' or 'done'

Each frame, check the phase and do the corresponding animation step.

Drawing the prefix table might need to step through each index, showing the value over time.

The steps:

1. Phase is 'prefix', so draw the pattern, its indices, and build the prefix array step by step. Each frame steps the prefix computation.

Wait, but how to animate building the prefix table? Maybe we can step through each step of the prefix array computation, showing each value as it's calculated. Alternatively, just draw it all once when ready. Hmm, the user said "first build the prefix table for a short pattern and draw it as a labelled row". So probably, the animation first shows the process of building the prefix array, perhaps sequentially. Maybe not necessary, but the problem says "build the prefix table" as part of the animation, so we have to animate that.

Alternatively, maybe first display the pattern and then draw the prefix table step by step. Maybe each step of the prefix array's calculation is shown frame by frame.

Hmm, that might be complex. Maybe simplify by just drawing the prefix array once it's computed, but labeled. Since the problem says "build the prefix table for a short pattern and draw it as a labelled row", perhaps just show the array values along with the pattern indices.

Wait, maybe the first phase is to draw the pattern, then show the prefix table values. So during the first phase, we draw the pattern and the prefix array. So perhaps first draw the pattern as a row, then below it, the prefix array's numbers.

But how to show the building? Maybe the prefix array is displayed step by step, with each number appearing in order, but given that the user wants smooth animation, maybe just draw it all at once once computed.

Alternatively, maybe the prefix array is shown by highlighting each entry as it's computed. Let me think of the steps:

- Phase 1: display the pattern and start building the prefix array step by step (each frame shows the next step's calculation). That might require tracking the current step in the prefix array's computation.

Alternatively, maybe the problem allows skipping the step-by-step building and just showing the final computed prefix array once it's ready, since the user might not mind as long as the prefix is displayed and used correctly. That could save lines.

Assuming that, then the first step is to draw the pattern and its prefix table.

Now, structuring the drawing functions.

The canvas needs to be cleared each frame, then redraw everything.

Probably, the text is displayed in a horizontal line (like a row), then the pattern is shown moving over it. The prefix table is a row above, with numbers under each character of the pattern.

The drawing functions:

function draw() {
  ctx.clearRect(0,0,canvas.width,canvas.height);
  // draw prefix table row
  drawPrefix();
  // draw text and pattern
  drawTextAndPattern();
  // show stats
  drawStats();
}

drawPrefix would:

- Draw the pattern's characters as the top row.
- Below them, the prefix array values as numbers.

drawTextAndPattern would draw the main text, and the pattern as a box moving along it, highlighting the current comparison.

Now, handling the search phase:

Variables needed:

- let i = current position in text (starting at 0)
- let j = current position in pattern (starting at 0)
- let phase is 'search'

Each frame, we need to move through the KMP algorithm steps.

Wait, but
deepseek-r1:32bSTATFORMAT
Show code
```html
<!DOCTYPE html>
<html><head><title>KMP Animation</title></head><body style="background:#1a1a1a;color:white;font-family:monospace;">
<canvas id=c width=800 height=400></canvas>
<script>
let c=document.getElementById('c'),ctx=c.getContext('2d'),w=800,h=400;
let pattern,text,table,state,anim,x,y,scale,timer=0,count=0,matches=[],patternX=0;

function randomString(l){return [...Array(l)].map(_=>String.fromCharCode(97+Math.random()*26)).join('')}

function buildKMPTable(p){
  let table=[...p].map(() => 0);
  for(let i=1,j=0;i<p.length;){
    if(p[i]==p[j]){table[i++]=j++;}else{
      if(j) j=table[j-1]; else{table[i]=0;i++;}
    }
  }return table;
}

function init(){
  pattern=randomString(4);text=randomString(20);
  patternX=50; anim=0;x=50;y=200;s=30;scale=20;
  state='build';count=0;matches=[];table=buildKMPTable(pattern);
}

function drawText(s,y){
  ctx.fillStyle='#404040';
  s.split('').forEach((c,i)=>ctx.fillRect(i*scale+x,y,scale,scale));
  ctx.strokeStyle='white';ctx.strokeRect(x,y,text.length*scale,scale);
}

function drawPattern(y,j=-1){
  ctx.fillStyle=j<0?'#209020':'#902020';
  pattern.split('').forEach((c,i)=>ctx.fillRect(i*scale+patternX,y,scale,scale));
  if(j>=0) ctx.strokeStyle='white',ctx.strokeRect(patternX+j*scale,y,scale,scale);
}

function animate(t){
  if(state=='build'){
    drawText(pattern,150);drawPattern(270);
    state='search'; anim=1;
  }else if(state=='search'){
    let i=anim,j=0,c1=text[timer],c2=pattern[j];
    ctx.fillStyle='#404040';ctx.fillRect(x+i*scale,200,scale,scale);
    drawText(text,200);drawPattern(270,j);
    if(c1==c2){count++;j++;i++}else{
      count++;
      let jump=table[j-1]||0;
      i-=jump;patternX=i*scale;
      ctx.fillStyle='#904040';ctx.fillRect(patternX+j*scale,270,scale,scale);
    }
    if(j==pattern.length){matches.push(i-j);j=table[j-1];}
    if(i>=text.length){
      state='done'; anim=0;timer=0;
      setTimeout(init,1500);
    }else{
      drawText(text,200,i*scale+x,'white');
      drawPattern(270,j,'white');
      timer++;
    }
  }
  ctx.fillStyle='black';ctx.font='20px monospace';
  ctx.fillText(`Comparisons: ${count} | Matches: ${matches.length}`,50,380);
}

init();
function step(t){
  if(state!='done')animate(t);requestAnimationFrame(step);
}
step(0);
</script></body></html>
```
command-r:35bHANGLIMFORMAT
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>Knuth-Morris-Pratt Animation</title>
    <style>
        body {
            margin: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f4f4f4;
        }

        canvas {
            border: 2px solid black;
            box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
        }
    </style>
</head>
<body>

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

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

    function generateRandomString(length) {
        const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        let randomStr = '';
        for (let i = 0; i < length; i++) {
            randomStr += characters.charAt(Math.floor(Math.random() * characters.length));
        }
        return randomStr;
    }

    function buildPrefixTable(pattern) {
        const n = pattern.length;
        let prefixTable = [0];

        for (let i = 1, j = 0; i < n;) {
            if (pattern[i] === pattern[j]) {
                prefixTable[i++] = ++j;
            } else {
                j = prefixTable[j - 1];
            }
        }

        return prefixTable;
    }

    function drawPrefixTable(prefixTable) {
        const textHeight = canvas.height / (2 + prefixTable.length);
        for (let i = 0, y = textHeight * 2; i < prefixTable.length; i++, y += textHeight) {
            ctx.fillText(`[${i}] ${prefixTable[i]}`, 30, y);
        }
    }

    function drawPatternMatch(pattern, index) {
        const textWidth = canvas.width / pattern.length;
        for (let i = 0; i < pattern.length; i++) {
            ctx.fillStyle = i === index ? 'green' : '#888';
            ctx.fillText(pattern[i], i * textWidth + 40, canvas.height - 20);
        }
    }

    function drawStringComparison(text, pattern) {
        const textHeight = canvas.height / (1 + text.length);
        for (let i = 0; i < text.length && i < pattern.length; i++) {
            ctx.fillStyle = text[i] === pattern[i] ? '#4CAF50' : 'red';
            ctx.fillText(text[i], i * textWidth + 20, Math.floor((i + 1) * textHeight));
        }
    }

    function animateKMP() {

        const pattern = generateRandomString(Math.floor(Math.random() * 5) + 3); // Random pattern length
        let text = generateRandomString(canvas.width / (pattern.length + 2)) + ' ' + generateRandomString(100); // Longer text with a space in the middle

        const prefixTable = buildPrefixTable(pattern);
        drawPrefixTable(prefixTable);

        ctx.font = '24px Arial';
        ctx.textAlign = 'left';
        ctx.fillStyle = '#333';
        let patternX = 20; // Start drawing pattern at this X position
        
        const textWidth = canvas.width / (1 + text.length); // Calculate the width of each character in the text

        for (let i = 0, matchCount = 0; ; i++) {
            if (i >= prefixTable[i] && i < pattern.length) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                drawPatternMatch(pattern, i % pattern.length);
                drawStringComparison(text, pattern);

                matchCount++;
                if (matchCount > text.length - pattern.length + 1) break; // Break when all matches found

                const jumpAmount = prefixTable[i] || pattern.length; // Jump by the prefix table value or pattern length
                const nextIndex = i < text.length ? i : undefined;
                requestAnimationFrame(() => animateKMP(text, pattern, patternX + textWidth * (jumpAmount - 1), matchCount));

                return;
            } else if (i === text.length || i === pattern.length) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);

                drawPatternMatch(pattern, nextIndex);
                drawStringComparison(text, pattern);

                break; // Break when either the pattern or text is fully compared
            }
        }
    }

    animateKMP();
</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">
<title>KMP Canvas</title>
<style>
html,body{margin:0;height:100%;background:#10141b;color:#e9eef7;font:16px system-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");
let W,H,run,last=0;
function fit(){W=c.width=innerWidth*devicePixelRatio;H=c.height=innerHeight*devicePixelRatio;x.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);W=innerWidth;H=innerHeight}
addEventListener("resize",fit);fit();
const pats=["ABA","ABAB","AABA","ABCA","CAB"],alph="ABC";
function make(){
 let p=pats[Math.random()*pats.length|0], t="", need=14+Math.random()*8|0;
 for(let i=0;i<need;i++)t+=alph[Math.random()*alph.length|0];
 let pos=Math.random()*(need-p.length)|0; t=t.slice(0,pos)+p+t.slice(pos+p.length);
 let pi=Array(p.length).fill(0),j=0,steps=[];
 for(let i=1;i<p.length;i++){while(j>0&&p[i]!=p[j]){steps.push({i,j,kind:"pfail"});j=pi[j-1]}steps.push({i,j,kind:p[i]==p[j]?"phit":"pmiss"});if(p[i]==p[j])j++;pi[i]=j}
 let ev=[],i=0,q=0,cmp=0,matches=[];
 while(i<t.length){cmp++;ev.push({i,q,shift:i-q,cmp,match:false,matches:[...matches]});
  if(t[i]==p[q]){i++;q++;if(q==p.length){matches.push(i-q);ev.push({i:i-1,q:q-1,shift:i-q,cmp,match:true,matches:[...matches]});q=pi[q-1]}}
  else if(q>0){let old=i-q;q=pi[q-1];ev.push({i,q,shift:i-q,old,cmp,jump:true,matches:[...matches]})}else i++}
 return{p,t,pi,steps,ev,phase:"pi",k:0,start:performance.now(),done:false}
}
run=make();
function box(s,px,py,w,h,fill,stroke="#354154"){
 x.fillStyle=fill;x.strokeStyle=stroke;x.lineWidth=2;x.beginPath();x.roundRect(px,py,w,h,6);x.fill();x.stroke();
 x.fillStyle="#e9eef7";x.textAlign="center";x.textBaseline="middle";x.font="700 22px ui-monospace,monospace";x.fillText(s,px+w/2,py+h/2)
}
function drawTable(r,hi){
 let cw=Math.min(58,(W-90)/r.p.length),sx=(W-cw*r.p.length)/2,py=120;
 x.fillStyle="#9aa7ba";x.font="14px system-ui";x.textAlign="left";x.fillText("Prefix table for pattern "+r.p, sx, py-36);
 for(let i=0;i<r.p.length;i++){box(r.p[i],sx+i*cw,py,cw,38,i==hi?"#2b6a7a":"#17202b");box(r.pi[i],sx+i*cw,py+42,cw,34,i==hi?"#4b713b":"#151b25")}
 x.fillStyle="#9aa7ba";x.textAlign="right";x.font="13px system-ui";x.fillText("char",sx-8,py+19);x.fillText("pi",sx-8,py+59)
}
function drawSearch(r,e,a){
 let cw=Math.min(54,(W-80)/r.t.length),sx=(W-cw*r.t.length)/2,ty=220,py=300;
 x.fillStyle="#9aa7ba";x.font="14px system-ui";x.textAlign="left";x.fillText("Text",sx,ty-30);x.fillText("Pattern slides by prefix-table jump on mismatch",sx,py-30);
 for(let i=0;i<r.t.length;i++)box(r.t[i],sx+i*cw,ty,cw,42,i==e.i?"#7a4d2b":"#182332");
 let sh=e.old!=null?e.old+(e.shift-e.old)*a:e.shift;
 for(let j=0;j<r.p.length;j++){let idx=sh+j,fill=j==e.q&&idx==e.i?"#b8323e":(idx==e.i?"#6d5722":"#20334a");box(r.p[j],sx+(sh+j)*cw,py,cw,42,fill)}
 if(e.jump){x.strokeStyle="#f0c65b";x.lineWidth=4;x.beginPath();x.moveTo(sx+e.old*cw+cw/2,py+64);x.lineTo(sx+e.shift*cw+cw/2,py+64);x.stroke();x.fillStyle="#f0c65b";x.textAlign="center";x.font="700 15px system-ui";x.fillText("jump "+(e.shift-e.old),sx+(e.old+e.shift)*cw/2+cw/2,py+88)}
 for(const m of e.matches){x.strokeStyle="#51c878";x.lineWidth=4;x.strokeRect(sx+m*cw,ty-6,cw*r.p.length,54)}
 if(e.match){x.fillStyle="#51c878";x.textAlign="center";x.font="700 24px system-ui";x.fillText("MATCH",W/2,390)}
 x.fillStyle="#e9eef7";x.textAlign="center";x.font="700 22px system-ui";x.fillText("Comparisons: "+e.cmp+"   Matches: "+e.matches.length,W/2,70)
}
function frame(t){
 let r=run; x.clearRect(0,0,W,H); x.fillStyle="#10141b"; x.fillRect(0,0,W,H);
 x.fillStyle="#e9eef7";x.textAlign="center";x.font="800 28px system-ui";x.fillText("Knuth-Morris-Pratt string search",W/2,38);
 if(r.phase=="pi"){let d=900,idx=Math.min(r.pi.length-1,Math.floor((t-r.start)/d));drawTable(r,idx);if(t-r.start>d*(r.pi.length+1)){r.phase="search";r.start=t}}
 else{drawTable(r,-1);let d=850,n=r.ev.length,raw=(t-r.start)/d,k=Math.floor(raw),a=raw-k;if(k<n)drawSearch(r,r.ev[k],Math.min(1,a));else{drawSearch(r,r.ev[n-1],1);if(raw>n+2)run=make()}}
 requestAnimationFrame(frame)
}
requestAnimationFrame(frame);
</script>
</body>
</html>
Claude Sonnet 5frontier referenceOK
Show code
<!doctype html>
<html><head><meta charset="utf-8"><title>KMP</title>
<style>
html,body{margin:0;background:#0b1020;overflow:hidden;height:100%}
canvas{display:block}
</style></head>
<body>
<canvas id="c"></canvas>
<script>
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
function resize(){cv.width=innerWidth;cv.height=innerHeight;}
resize();addEventListener('resize',resize);
const ALPH='ABC',CS=18;
let pat,text,lps,matches,i,j,bi,bj,comparisons,phase,patXcur,patXtarget,flash,doneTimer,frameCount;
function rnd(n){return Math.floor(Math.random()*n)}
function randStr(n){let r='';for(let k=0;k<n;k++)r+=ALPH[rnd(ALPH.length)];return r}
function init(){
  const pl=5+rnd(3);
  pat=randStr(pl);
  const tl=46;
  const arr=[];
  for(let k=0;k<tl;k++)arr.push(ALPH[rnd(ALPH.length)]);
  for(let k=0;k<2;k++){const pos=rnd(tl-pl+1);for(let x=0;x<pl;x++)arr[pos+x]=pat[x];}
  text=arr.join('');
  lps=new Array(pl).fill(0);
  matches=[];phase='build';bi=1;bj=0;
  i=0;j=0;comparisons=0;
  patXcur=0;patXtarget=0;flash=null;doneTimer=0;frameCount=0;
}
init();
function stepBuild(){
  const pl=pat.length;
  if(bi>=pl){phase='search';return;}
  if(pat[bi]===pat[bj]){bj++;lps[bi]=bj;bi++;}
  else if(bj!==0){bj=lps[bj-1];}
  else{lps[bi]=0;bi++;}
}
function stepSearch(){
  const pl=pat.length,tl=text.length;
  if(i>=tl){phase='done';return;}
  comparisons++;
  const eq=text[i]===pat[j];
  flash={idx:i,bad:!eq};
  if(eq){
    i++;j++;
    if(j===pl){matches.push(i-j);j=lps[j-1];}
  }else if(j!==0){j=lps[j-1];}
  else{i++;}
  patXtarget=(i-j)*CS;
}
function loop(){
  frameCount++;
  const stepDur=16;
  if(phase==='build'){if(frameCount%stepDur===0)stepBuild();}
  else if(phase==='search'){
    if(frameCount%stepDur===0)stepSearch();
    patXcur+=(patXtarget-patXcur)*0.25;
  }else if(phase==='done'){
    if(frameCount%stepDur===0){doneTimer++;if(doneTimer>18)init();}
  }
  draw();requestAnimationFrame(loop);
}
function box(x,y,ch,col,sub){
  ctx.fillStyle=col;ctx.fillRect(x,y,CS-2,CS-2);
  ctx.fillStyle='#0b1020';ctx.font='13px monospace';ctx.textAlign='center';
  ctx.fillText(ch,x+(CS-2)/2,y+15);
  if(sub!==undefined){ctx.fillStyle='#9fe';ctx.font='10px monospace';ctx.fillText(sub,x+(CS-2)/2,y+CS+11);}
}
function draw(){
  ctx.fillStyle='#0b1020';ctx.fillRect(0,0,cv.width,cv.height);
  ctx.fillStyle='#eef';ctx.font='16px monospace';ctx.textAlign='left';
  ctx.fillText('KMP String Search',20,26);
  ctx.font='12px monospace';ctx.fillStyle='#8ac';
  ctx.fillText('pattern="'+pat+'"  prefix-function (LPS table):',20,50);
  const x0=20,yT=70;
  for(let k=0;k<pat.length;k++){
    let col='#334',s;
    if(phase==='build'){
      if(k===bi)col='#e94';else if(k===bj)col='#4be';else if(k<bi)col='#4a7';
      s=k<bi?lps[k]:'';
    }else{col=(k<matches.length||true)?'#4a7':'#334';s=lps[k];}
    box(x0+k*CS,yT,pat[k],col,s);
  }
  ctx.fillStyle='#8ac';ctx.font='12px monospace';
  ctx.fillText('text:',20,140);
  const tx0=20,ty=150;
  for(let k=0;k<text.length;k++){
    let col='#223a55';
    if(matches.some(m=>k>=m&&k<m+pat.length))col='#2a6';
    if(flash&&flash.idx===k)col=flash.bad?'#e33':'#4be';
    box(tx0+k*CS,ty,text[k],col);
  }
  if(phase==='search'||phase==='done'){
    ctx.fillStyle='#8ac';ctx.font='12px monospace';
    ctx.fillText('pattern (sliding):',20,205);
    const py=215,px=tx0+patXcur;
    for(let k=0;k<pat.length;k++){
      let col='#556';
      if(phase==='search'&&k<j)col='#2a6';
      if(phase==='search'&&k===j&&flash&&flash.idx===i-1+((j===0)?1:0))col='#4be';
      box(px+k*CS,py,pat[k],col);
    }
    for(let m of matches){
      ctx.strokeStyle='#4f8';ctx.lineWidth=2;
      ctx.strokeRect(tx0+m*CS,ty-2,pat.length*CS-2,CS+2);
    }
  }
  ctx.fillStyle='#fff';ctx.font='14px monospace';
  ctx.fillText('Comparisons: '+comparisons+'   Matches found: '+matches.length,20,270);
  ctx.fillStyle='#789';ctx.font='11px monospace';
  ctx.fillText(phase==='build'?'Phase: building prefix table':phase==='search'?'Phase: searching (mismatch jumps by LPS)':'Done - restarting...',20,290);
}
requestAnimationFrame(loop);
</script>
</body></html>
Claude Haiku 4.5frontier referenceOK
Show code
<!DOCTYPE html><html><head><title>r73</title></head><body><canvas id="c"></canvas><script>
let c=document.getElementById('c'),x=c.getContext('2d');c.width=900;c.height=600;
let p="ABABD",t="ABABDABABAC",m=[0];
// Prefix table
for(let i=1;i<p.length;i++){let j=m[i-1];
  while(j>0&&p[i]!==p[j])j=m[j-1];m.push(p[i]===p[j]?j+1:0);
}
let i=0,j=0,cmp=0,found=[],phase=0,t_=0;
function draw(){
  x.fillStyle="#fff";x.fillRect(0,0,900,600);
  x.fillStyle="#000";x.font="14px mono";
  // Prefix table
  x.fillText("Pattern: "+p,50,50);
  x.fillText("Text:    "+t,50,80);
  x.fillText("Prefix:  "+m.join(" "),50,110);
  // Text comparison
  x.fillText("Text:",50,200);for(let k=0;k<t.length;k++){
    x.fillStyle=k===i?"#f00":(found.some(f=>f.includes(k))?"#0f0":"#000");
    x.fillText(t[k],100+k*20,200);
  }
  x.fillText("Pattern:",50,250);for(let k=0;k<p.length;k++){
    x.fillStyle=k===j?"#00f":"#000";
    x.fillText(p[k],100+k*20+(i-j)*20,250);
  }
  // Search step
  if(phase===0){
    if(i<t.length){
      if(t[i]===p[j]){i++;j++;}
      else{if(j>0)j=m[j-1];else i++;}
      cmp++;
      if(j===p.length){found.push([i-p.length,i-1]);j=m[j-1];phase=1;t_=0;}
    }else{phase=2;}
  }else if(phase===1){if(t_++>20)phase=0;}else{if(t_++>100){i=0;j=0;cmp=0;found=[];phase=0;}}
  x.fillStyle="#000";x.fillText("Comparisons: "+cmp+" Matches: "+found.length,50,350);
  requestAnimationFrame(draw);
}draw();
</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 —51.2 sOK
qwen3-coder:30b —28.2 sOK
gemma4:26b ——harness
qwen3:14b —52.7 sOK
llama3.1:8b —102.4 sOK
qwen3:8b ——harness
deepseek-r1:14b —113.3 sOK
mistral-small:24b —185.0 sOK
qwq:32b —134.9 sOK
deepseek-r1:32b —100.9 sOK
command-r:35b —127.8 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>
<meta charset="utf-8">
<title>Knuth-Morris-Pratt string search</title>
<style>
html,body{margin:0;height:100%;background:#070a12;display:grid;place-items:center;font-family:system-ui,sans-serif}
canvas{background:#0b1020;border-radius:16px;box-shadow:0 24px 70px #000a}
</style>
<canvas id=c width=960 height=540></canvas>
<script>
const c=document.getElementById('c'),x=c.getContext('2d'),W=960,H=540;
const R=a=>a[Math.floor(Math.random()*a.length)];let S;
function init(){
 const A=Math.random()<.5?['A','B']:['A','B','C'];
 const m=3+Math.floor(Math.random()*3),n=25+Math.floor(Math.random()*6);
 let p='',t='';
 for(let k=0;k<m;k++)p+=R(A);
 for(let k=0;k<n;k++)t+=R(A);
 for(let k=0;k<2;k++){const a=Math.floor(Math.random()*(n-m+1));t=t.slice(0,a)+p+t.slice(a+m)}
 S={p,t,m,n,lps:Array(m).fill(0),ph:0,tk:1,tl:0,tcmp:null,tok:1,i:0,j:0,cmp:null,ok:1,
    comps:0,mat:[],off:0,doff:0,tmr:0,fl:0,msg:'Phase 1 - building the prefix table (LPS)',col:'#9fb4d6'};
}
function step(){
 if(S.ph==0){
  if(S.tk>=S.m){S.ph=1;S.tcmp=null;S.msg='Phase 2 - sliding the pattern along the text';S.col='#9fb4d6';return}
  S.tcmp=[S.tk,S.tl];S.comps++;
  if(S.p[S.tk]==S.p[S.tl]){S.tok=1;S.tl++;S.lps[S.tk]=S.tl;S.msg='p['+S.tk+'] = p['+S.tl+"-1] -> lps["+S.tk+'] = '+S.tl;S.tk++;S.col='#3ddc84'}
  else if(S.tl>0){S.tok=0;S.msg='mismatch -> fall back to lps['+(S.tl-1)+'] = '+S.lps[S.tl-1];S.tl=S.lps[S.tl-1];S.col='#ff6b7a'}
  else{S.tok=0;S.lps[S.tk]=0;S.msg='no border -> lps['+S.tk+'] = 0';S.tk++;S.col='#ff6b7a'}
  return;
 }
 if(S.ph==2){init();return}
 if(S.i>=S.n){S.ph=2;S.cmp=null;S.msg='Scan complete - '+S.mat.length+' match(es) in '+S.comps+' comparisons. Restarting...';S.col='#f5c542';return}
 S.cmp=[S.i,S.j];S.comps++;
 if(S.t[S.i]==S.p[S.j]){
  S.ok=1;S.i++;S.j++;S.col='#3ddc84';S.msg='t['+S.cmp[0]+'] = p['+S.cmp[1]+"] = '"+S.p[S.cmp[1]]+"' -> advance both";
  if(S.j==S.m){S.mat.push(S.i-S.m);S.fl=34;S.msg='MATCH at index '+(S.i-S.m)+' -> continue with lps = '+S.lps[S.m-1];S.j=S.lps[S.m-1];S.col='#f5c542'}
 }else{
  S.ok=0;S.col='#ff6b7a';
  if(S.j>0){const b=S.lps[S.j-1];S.msg="mismatch '"+S.t[S.i]+"' vs '"+S.p[S.j]+"' -> jump "+(S.j-b)+' (lps['+(S.j-1)+'] = '+b+'), no backtrack in text';S.j=b}
  else{S.msg="mismatch '"+S.t[S.i]+"' vs '"+S.p[0]+"' -> shift by 1";S.i++}
 }
 S.off=S.i-S.j;
}
function row(s,x0,y,cw,f,h){
 for(let k=0;k<s.length;k++){const cx=x0+k*cw,st=f(k);
  x.fillStyle=st[0];x.fillRect(cx+1.5,y,cw-3,h);
  x.strokeStyle=st[1];x.lineWidth=2;x.strokeRect(cx+1.5,y,cw-3,h);
  x.fillStyle=st[2];x.font='bold 19px ui-monospace,monospace';x.textAlign='center';
  x.fillText(s[k],cx+cw/2,y+h/2+1)}
}
function box(bx,by,lab,val,col){
 x.fillStyle='#101830';x.fillRect(bx,by,272,60);x.strokeStyle='#22314f';x.lineWidth=1;x.strokeRect(bx+.5,by+.5,271,59);
 x.textAlign='left';x.fillStyle='#6d7f9e';x.font='12px system-ui';x.fillText(lab,bx+16,by+19);
 x.fillStyle=col;x.font='bold 24px ui-monospace,monospace';x.fillText(val,bx+16,by+41);
}
function draw(){
 x.fillStyle='#0b1020';x.fillRect(0,0,W,H);x.textBaseline='middle';x.textAlign='left';
 x.fillStyle='#eaf1ff';x.font='bold 25px system-ui';x.fillText('Knuth-Morris-Pratt string search',40,34);
 x.fillStyle='#6d7f9e';x.font='13px ui-monospace,monospace';x.fillText('pattern "'+S.p+'"   ·   text of '+S.n+' chars   ·   alphabet-driven random instance',40,60);
 const pw=46,px=110,py=100;
 x.fillStyle='#8aa2c8';x.font='12px system-ui';x.textAlign='right';x.fillText('PREFIX',100,py+17);x.fillText('TABLE',100,py+50);
 row(S.p,px,py,pw,k=>{
  if(S.ph==0&&S.tcmp&&k==S.tcmp[0])return[S.tok?'#153a28':'#3b141d',S.tok?'#3ddc84':'#ff6b7a','#fff'];
  if(S.ph==0&&S.tcmp&&k==S.tcmp[1])return['#152a4c','#5aa9ff','#dbe9ff'];
  return['#141d33','#25324f','#cfe0ff']},34);
 for(let k=0;k<S.m;k++){const d=S.ph>0||k<S.tk,cx=px+k*pw;
  x.fillStyle='#0e1526';x.fillRect(cx+1.5,py+36,pw-3,28);x.strokeStyle='#25324f';x.lineWidth=1;x.strokeRect(cx+1.5,py+36,pw-3,28);
  x.textAlign='center';x.font='bold 16px ui-monospace,monospace';x.fillStyle=d?'#f5c542':'#2c3a58';x.fillText(d?S.lps[k]:'?',cx+pw/2,py+51)}
 x.strokeStyle='#1a2440';x.lineWidth=1;x.beginPath();x.moveTo(40,196);x.lineTo(920,196);x.stroke();
 const cw=Math.min(30,(W-190)/S.n),tx=110,ty=232,py2=300;
 x.textAlign='right';x.fillStyle='#8aa2c8';x.font='12px system-ui';x.fillText('TEXT',100,ty+17);x.fillText('PATTERN',100,py2+17);
 row(S.t,tx,ty,cw,k=>{
  if(S.ph==1&&S.cmp&&k==S.cmp[0])return[S.ok?'#153a28':'#3b141d',S.ok?'#3ddc84':'#ff6b7a','#fff'];
  if(S.mat.some(a=>k>=a&&k<a+S.m))return['#0f2c1d','#2c7a4b','#a9efc6'];
  return['#141d33','#25324f','#cfe0ff']},34);
 S.mat.forEach(a=>{x.fillStyle=S.fl>0&&a==S.mat[S.mat.length-1]?'#f5c542':'#3ddc84';x.fillRect(tx+a*cw+2,ty+38,S.m*cw-4,4)});
 const ox=tx+S.doff*cw;
 row(S.p,ox,py2,cw,k=>{
  if(S.ph==1&&S.cmp&&k==S.cmp[1])return[S.ok?'#153a28':'#3b141d',S.ok?'#3ddc84':'#ff6b7a','#fff'];
  if(S.ph==1&&k<S.j)return['#152a4c','#5aa9ff','#dbe9ff'];
  return['#161f38','#2b3a5c','#9fb4d6']},34);
 if(S.ph==1&&S.cmp){const a=tx+S.cmp[0]*cw+cw/2,b=ox+S.cmp[1]*cw+cw/2;
  x.strokeStyle=S.ok?'#3ddc84':'#ff6b7a';x.setLineDash([4,4]);x.lineWidth=2;x.beginPath();
  x.moveTo(a,ty+36);x.lineTo(a,ty+48);x.lineTo(b,py2-12);x.lineTo(b,py2-2);x.stroke();x.setLineDash([])}
 x.textAlign='left';x.fillStyle=S.col;x.font='bold 16px ui-monospace,monospace';x.fillText(S.msg,40,378);
 box(40,410,'CHARACTER COMPARISONS',''+S.comps,'#eaf1ff');
 box(344,410,'MATCHES FOUND',S.mat.length+(S.mat.length?'  @ '+S.mat.join(', '):''),'#3ddc84');
 box(648,410,'POINTERS','i = '+Math.min(S.i,S.n)+'   j = '+S.j,'#5aa9ff');
 x.fillStyle='#4a5a78';x.font='12px system-ui';x.fillText('the text pointer i never moves backwards - on a mismatch the pattern jumps forward by j - lps[j-1]',40,502);
}
function loop(){
 S.doff+=(S.off-S.doff)*.22;if(Math.abs(S.off-S.doff)<.012)S.doff=S.off;
 if(S.fl>0)S.fl--;
 if(++S.tmr>=(S.ph==0?20:S.ph==1?27:110)&&S.doff==S.off){S.tmr=0;step()}
 draw();requestAnimationFrame(loop);
}
init();loop();
</script>