<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Harish Kotra (he/him)</title>
    <description>The latest articles on DEV Community by Harish Kotra (he/him) (@harishkotra).</description>
    <link>https://dev.to/harishkotra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F101279%2F516b4cd2-cc6d-451c-a8c8-a7d9ab5ec41a.png</url>
      <title>DEV Community: Harish Kotra (he/him)</title>
      <link>https://dev.to/harishkotra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harishkotra"/>
    <language>en</language>
    <item>
      <title>Building "The One-Sentence Torture Test": making LLM comparisons falsifiable</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sun, 20 Sep 2026 15:03:39 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-the-one-sentence-torture-test-making-llm-comparisons-falsifiable-43kl</link>
      <guid>https://dev.to/harishkotra/building-the-one-sentence-torture-test-making-llm-comparisons-falsifiable-43kl</guid>
      <description>&lt;p&gt;&lt;em&gt;How a deliberately useless constraint ladder became a real measurement tool — and the six bugs that taught me the most.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "which model is better?"
&lt;/h2&gt;

&lt;p&gt;Every model comparison I've read lately ends the same way: two paragraphs side by side, and a judgement call. The longer answer "feels" smarter. The one with bullet points "feels" more organised. Nobody can point at a number and say &lt;em&gt;this is why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The trouble is that most interesting LLM tasks don't have a correct answer. Summarise this article. Explain this concept. Write a function. You can grade them, but the grader is another model, and now you're measuring your judge.&lt;/p&gt;

&lt;p&gt;So I built something with a different property: &lt;strong&gt;a task where the answer is either right or wrong, and a regex can prove which.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The premise
&lt;/h2&gt;

&lt;p&gt;Give the app a concept — &lt;code&gt;recursion&lt;/code&gt;, &lt;code&gt;love&lt;/code&gt;, &lt;code&gt;a for loop&lt;/code&gt;. It asks two models to explain it under seven escalating formatting constraints:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Constraint&lt;/th&gt;
&lt;th&gt;Checkable by&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Exactly one sentence&lt;/td&gt;
&lt;td&gt;counting terminators&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Exactly 10 words&lt;/td&gt;
&lt;td&gt;&lt;code&gt;split(/\s+/)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Exactly 5 words&lt;/td&gt;
&lt;td&gt;&lt;code&gt;split(/\s+/)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;As a pirate&lt;/td&gt;
&lt;td&gt;lexicon membership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Only the characters &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/^[01\s]+$/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;As a haiku (5-7-5)&lt;/td&gt;
&lt;td&gt;syllable estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;One sentence, no letter &lt;code&gt;e&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;case-insensitive scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These constraints are useless. That's the point. Nobody needs recursion explained in pirate speak. But &lt;strong&gt;"exactly 5 words" is not a matter of opinion&lt;/strong&gt;, and that makes the whole thing falsifiable.&lt;/p&gt;

&lt;p&gt;The failure messages are the product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 2 ten-words   A PASS  10 / 10 words
 2 ten-words   B FAIL  11 / 10 words
 5 binary      A FAIL  empty output      rt=3200
 7 no-e        B FAIL  3 / 0 'e' characters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can argue with &lt;code&gt;11 / 10 words&lt;/code&gt;. You can't argue with a vibe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: one module, two consumers
&lt;/h2&gt;

&lt;p&gt;The single most important decision in this codebase is that the ladder lives in &lt;code&gt;shared/&lt;/code&gt; and is imported by &lt;strong&gt;both&lt;/strong&gt; the server and the browser.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxa6lvottrydu0fb6wna0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxa6lvottrydu0fb6wna0.png" alt="Architecture" width="797" height="128"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The client imports server-side code over a relative &lt;code&gt;.ts&lt;/code&gt; path, which TypeScript allows with &lt;code&gt;allowImportingTsExtensions: true&lt;/code&gt;. That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The UI renders the rungs &lt;strong&gt;from the same definitions the server enforces&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The word count shown in the browser is computed by the same function that produced the verdict.&lt;/li&gt;
&lt;li&gt;It is structurally impossible to ship a UI that promises a constraint the server doesn't check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The alternative — duplicating the constraint list in the client — is the kind of bug that doesn't show up until someone changes one side.&lt;/p&gt;

&lt;p&gt;Each rung is data plus a pure function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Rung&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RungId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;instruction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// the exact text sent to the model&lt;/span&gt;
  &lt;span class="nl"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RungSpec&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// declarative rule&lt;/span&gt;
  &lt;span class="nl"&gt;heuristic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// does the verdict rely on an estimate?&lt;/span&gt;
  &lt;span class="nl"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;RungCheck&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RungCheck&lt;/code&gt; carries a machine-readable &lt;code&gt;actual&lt;/code&gt; / &lt;code&gt;expected&lt;/code&gt; / &lt;code&gt;unit&lt;/code&gt; triple, which is what lets the UI render "11 / 10 words" without the UI knowing anything about word counting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Design decisions that mattered
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Never trust the model to report on itself
&lt;/h3&gt;

&lt;p&gt;The model is asked to &lt;em&gt;produce&lt;/em&gt; text, never to &lt;em&gt;describe&lt;/em&gt; it. Every number is recomputed from the raw string. If a model says "here is exactly 10 words" and emits 11, the verdict is &lt;code&gt;11 / 10 words&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This sounds obvious. It's the reason the tool is worth building.&lt;/p&gt;

&lt;h3&gt;
  
  
  Empty output is a failure everywhere
&lt;/h3&gt;

&lt;p&gt;An empty string trivially satisfies "contains no letter &lt;code&gt;e&lt;/code&gt;". It also satisfies "no forbidden characters". Every rung explicitly rejects it, and the verification harness asserts that &lt;strong&gt;all seven&lt;/strong&gt; do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;every rung rejects empty output&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LADDER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LADDER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;LADDER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; rungs reject ""`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, the cheapest strategy for a struggling model is to say nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reasoning content is never read
&lt;/h3&gt;

&lt;p&gt;Reasoning models return a &lt;code&gt;reasoning_content&lt;/code&gt; field. I made a rule: read the &lt;strong&gt;token count&lt;/strong&gt;,&lt;br&gt;
never the &lt;strong&gt;text&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scrubReasoningContent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrubReasoningContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/reasoning_content|reasoning_text|thinking/i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scrubReasoningContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scrubber runs before every write to disk, so hidden chain-of-thought can't leak into the log even if a provider returns it. The verification harness then re-reads the raw file and scans every line to confirm — 280 lines, no &lt;code&gt;reasoning_content&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I added this partly on principle and partly because reasoning traces are the most likely place for something sensitive to end up in a JSONL file you forgot about.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSE over POST
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EventSource&lt;/code&gt; only does GET, and the concept plus the full provider config (including API keys) must not go in a query string. So the client uses plain &lt;code&gt;fetch&lt;/code&gt;, reads &lt;code&gt;response.body&lt;/code&gt; as a stream, and hand-parses the frames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/torture&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;concept&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// decode chunks → split on "\n\n" → parse each `data:` line as JSON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keys stay out of URLs and out of access logs, and verdicts still arrive as they land.&lt;/p&gt;

&lt;h3&gt;
  
  
  Per-provider reasoning toggle
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;chat_template_kwargs: { enable_thinking: false }&lt;/code&gt; is a vLLM/SGLang extension. It is &lt;strong&gt;not&lt;/strong&gt; in the OpenAI spec. A strict server may reject unknown fields outright.&lt;/p&gt;

&lt;p&gt;I originally had it as a global checkbox. When I added multi-provider support, I made it per-endpoint, because a global toggle would break a user's custom server the moment someone flipped it for the hosted one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bugs that taught me the most
&lt;/h2&gt;

&lt;p&gt;This is the part worth reading.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The SSE stream produced nothing at all
&lt;/h3&gt;

&lt;p&gt;The endpoint looked correct and returned HTTP 200 with the right headers. No events arrived.&lt;/p&gt;

&lt;p&gt;The cause: I had wired the abort handler to &lt;code&gt;req.on('close')&lt;/code&gt;. With &lt;code&gt;express.json()&lt;/code&gt; in the middleware chain, the request stream is fully consumed by the body parser, and &lt;code&gt;'close'&lt;/code&gt; fires on the request almost immediately — so my handler tore down the response before the&lt;br&gt;
first frame was written.&lt;/p&gt;

&lt;p&gt;The fix was to listen on the &lt;strong&gt;response&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* client actually went away */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One word. Two hours. If you build SSE on top of a body parser, this is the bug you will hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;verify.ts&lt;/code&gt; silently booted a second server
&lt;/h3&gt;

&lt;p&gt;The verification harness imported &lt;code&gt;./index&lt;/code&gt; to reuse &lt;code&gt;scoreRun()&lt;/code&gt;, which executed the module's top-level &lt;code&gt;app.listen()&lt;/code&gt;. The harness was then talking to a second server instance while I watched logs from the first, and the evidence didn't match what I thought I was testing.&lt;/p&gt;

&lt;p&gt;The fix was to extract the scoring logic into &lt;code&gt;scoring.ts&lt;/code&gt; so the harness could import pure functions without side effects. &lt;strong&gt;Importing a module should not start a server.&lt;/strong&gt; If your module has top-level side effects, your tests will eventually lie to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rung 7 only enforced half its instruction
&lt;/h3&gt;

&lt;p&gt;The instruction says: &lt;em&gt;"exactly one sentence that contains no letter &lt;code&gt;e&lt;/code&gt;."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My validator only checked the &lt;code&gt;e&lt;/code&gt;. A model could emit five sentences with no &lt;code&gt;e&lt;/code&gt; and pass.&lt;br&gt;
The verification harness caught it because the prompt and the check disagreed — a good reason to keep the instruction and the validator in the same object, where you can see the mismatch.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Empty output reported as a charset violation
&lt;/h3&gt;

&lt;p&gt;When a model returned nothing, rung 5 reported &lt;code&gt;charset violation:&lt;/code&gt; followed by an empty list of offenders. Technically true, completely useless. Empty output is now detected first and reported as &lt;code&gt;empty output&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Good failure messages are a feature. &lt;code&gt;charset violation:&lt;/code&gt; with nothing after it is a bug report about your own error handling.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Duplicate React keys
&lt;/h3&gt;

&lt;p&gt;Two model slots pointing at the same provider produced identical validation strings, so React logged duplicate-key warnings. The fix was deduping the problem list — which also improved the UX, since showing the same error twice is noise.&lt;/p&gt;

&lt;p&gt;Small, but it's the kind of thing that only surfaces when you actually look at console output.&lt;/p&gt;
&lt;h3&gt;
  
  
  6. The one that matters: 49/49 green while everything was broken
&lt;/h3&gt;

&lt;p&gt;This is the bug I'd frame.&lt;/p&gt;

&lt;p&gt;My browser test suite reported &lt;strong&gt;49/49 passing&lt;/strong&gt;. In the same run, the live ladder had failed &lt;strong&gt;all 14 rungs&lt;/strong&gt;. The scoreboard read &lt;code&gt;0 PASS, 14 FAIL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The cause was in my test, not the app: a selector meant to fill the API key field was filling the &lt;strong&gt;Base URL&lt;/strong&gt; field. The app dutifully tried to POST to an endpoint named after an API key, every call failed, and the UI correctly rendered 14 failures.&lt;/p&gt;

&lt;p&gt;The suite passed because it was checking the wrong things. It verified that the settings panel opened, that fields persisted, that the heuristic badge appeared — and it never once asserted that a rung &lt;em&gt;passed&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Two lessons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A green test suite is a claim, not evidence.&lt;/strong&gt; Assert on outcomes, not on the absence of exceptions. If your integration test can pass while the core feature fails completely, it is measuring the wrong thing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the output, not just the exit code.&lt;/strong&gt; The &lt;code&gt;0 PASS, 14 FAIL&lt;/code&gt; line was right there in the scrollback, three lines above &lt;code&gt;49/49 browser checks passed&lt;/code&gt;. The signal was present; I had to notice it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I now have the harness print the actual verdict distribution, and I check it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Verification in three layers
&lt;/h2&gt;

&lt;p&gt;Because the whole project is a claim about correctness, I built the evidence to match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1 — unit tests (49).&lt;/strong&gt; Validator tests are written to prove validators &lt;em&gt;can fail&lt;/em&gt;: known-bad inputs asserted to produce specific verdicts. A validator with no failing test isn't done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2 — live verification (15 checks, 16 cross-provider).&lt;/strong&gt; Runs the real ladder against real providers and asserts things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PASS] known 12-word string vs 10-word limit
       counted 12 words -&amp;gt; verdict "12 / 10 words" (ok=false)
[PASS] AC3 — both models received byte-identical prompts on every rung
       7 rungs x 2 models: all prompt strings identical
[PASS] AC5 — reasoning_content never stored (scanned the raw JSONL on disk)
       280 JSONL lines scanned, no reasoning_content field
[PASS] AC1 — word-count verdicts equal an independent recount of the raw output
       ten-words/B: validator=11, recount=11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the shape: each check asserts a &lt;strong&gt;property&lt;/strong&gt;, not a snapshot. "The validator can fail" is a property. "Byte-identical prompts" is a property. These don't go stale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 — real browser (49 checks).&lt;/strong&gt; Drives Chrome against the running UI, and includes a dependency-free PNG decoder so it can &lt;strong&gt;read the rendered pixels&lt;/strong&gt; and confirm the three.js scene actually drew green and red rungs — not just that the DOM contains the right text.&lt;/p&gt;

&lt;p&gt;It also runs a &lt;strong&gt;product-surface leak guard&lt;/strong&gt; that fails the build if an absolute filesystem path, &lt;code&gt;.jsonl&lt;/code&gt;, a &lt;code&gt;host:port&lt;/code&gt; string, or a raw API field name ever appears in the UI. Users should never see my &lt;code&gt;data/runs.jsonl&lt;/code&gt; path; they should see a download button.&lt;/p&gt;




&lt;h2&gt;
  
  
  Proving multi-provider actually works
&lt;/h2&gt;

&lt;p&gt;Supporting "any OpenAI-compatible endpoint" is easy to claim and easy to get subtly wrong — particularly key handling. The failure mode I cared about: leaking provider A's key to provider B.&lt;/p&gt;

&lt;p&gt;So I wrote a mock provider that &lt;strong&gt;rejects any bearer token except its own&lt;/strong&gt;, pointed Model A at Particle and Model B at the mock, and ran the ladder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[PASS] CROSS-PROVIDER: Model A and Model B hit different endpoints
       A -&amp;gt; https://api.particle.ai/v1 | B -&amp;gt; http://127.0.0.1:8099/v1
[PASS] every result records which provider served it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Had the app sent the wrong key, every call to B would have 401'd. The latency in the results makes the split unmistakable — 8ms from the local mock, ~1,400ms from Particle — while the validators ran identically on both outputs.&lt;/p&gt;

&lt;p&gt;This is the cheapest possible way to test a routing claim: build the hostile counterparty first.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the torture test actually reveals
&lt;/h2&gt;

&lt;p&gt;Running the default pair on &lt;code&gt;recursion&lt;/code&gt; produces a real, reproducible finding: &lt;strong&gt;the binary rung is the killer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Both models tend to spend their entire token budget on hidden reasoning and emit &lt;em&gt;nothing at all&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 5 binary   A FAIL  empty output   rt=3200  9515ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rt=3200&lt;/code&gt; is the reasoning token count. The model thought for 3,200 tokens and produced zero characters. My retry-with-doubled-budget path fired and also came back empty.&lt;/p&gt;

&lt;p&gt;Turn off reasoning and the failure mode flips completely. Binary passes easily, but the tight word-count rungs start failing on genuine over/under-shoots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; 2 ten-words  B FAIL  11 / 10 words
 3 five-words B FAIL  4 / 5 words
 7 no-e       B FAIL  3 / 0 'e' characters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same models, same prompts, entirely different failure surface. That's the interesting result:&lt;br&gt;
&lt;strong&gt;the constraint that breaks a model tells you something about how it fails, not just whether it's "better."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An honest caveat:&lt;/strong&gt; the lead changes hands between runs. I report what the verdicts say and don't tune for a preferred winner. One run is an anecdote. The value is the mechanism, not one scoreboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you want to build something like this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Make the answer checkable before you make it interesting.&lt;/strong&gt; I spent the first hour on the ladder, not the UI. If you can't write a pure function that returns &lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt; plus a readable reason, you don't have a measurement yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Put the rules where both sides can see them.&lt;/strong&gt; One module, imported by server and client.&lt;br&gt;
The class of bug it eliminates is large.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write validators that fail on purpose.&lt;/strong&gt; Every constraint in this repo has a test proving it rejects a known-bad input. Otherwise you're testing that your code runs, not that it works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assert properties, not snapshots.&lt;/strong&gt; "Both models got identical prompts" survives a refactor. A golden-file comparison does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Report empty output as its own thing.&lt;/strong&gt; It's the most common real failure with reasoning models, and it looks like a dozen different bugs if you don't name it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read your test output.&lt;/strong&gt; 49/49 green while the app failed every single rung.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run &lt;span class="nb"&gt;install&lt;/span&gt;:all
npm run dev          &lt;span class="c"&gt;# API :3001, UI :5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste any OpenAI-compatible key in Settings, pick a concept, run the ladder. Or point Model B at your own local server and race a hosted model against it.&lt;/p&gt;

&lt;p&gt;The repository includes the full verification suite, the mock provider, and the raw JSONL evidence behind every number in this post.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/259-one-sentence-torture-test" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/259-one-sentence-torture-test&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Building The Confidence Curve</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sat, 19 Sep 2026 10:01:49 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-the-confidence-curve-99i</link>
      <guid>https://dev.to/harishkotra/building-the-confidence-curve-99i</guid>
      <description>&lt;p&gt;There is a question that every team shipping on top of reasoning models has to answer, and almost nobody answers it with a measurement:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At what point does the extra thinking stop being worth it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A newer model ships. It reasons harder. It costs more per call and it is slower. The vendor's&lt;br&gt;
benchmark table shows it winning on average. But you do not serve averages — you serve a&lt;br&gt;
distribution of requests, and a single "it's better" number tells you nothing about the easy&lt;br&gt;
ones. If the newer model burns 2,900 reasoning tokens to answer a question the older one nails in&lt;br&gt;
175, the average is hiding a bill.&lt;/p&gt;

&lt;p&gt;So I built an instrument to answer it: &lt;strong&gt;The Confidence Curve&lt;/strong&gt;. A fixed bank of 50 questions,&lt;br&gt;
two models, every answer graded in code, rendered as a 3D surface where you can &lt;em&gt;see&lt;/em&gt; the&lt;br&gt;
crossover and &lt;em&gt;see&lt;/em&gt; the waste.&lt;/p&gt;

&lt;p&gt;Here is what it found, and here is how it is built.&lt;/p&gt;


&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diff   A acc   B acc    gap    A tok    B tok
   1    100%     60%    -40       52      175
   2    100%     60%    -40       64      230
   3     80%     40%    -40       76      285
   4    100%     60%    -40       88      340
   5     80%     80%      0      100      395
   6     40%     60%    +20      112      540   &amp;lt;- crossover
   7     60%    100%    +40      124      865
   8     40%    100%    +60      136     1370   &amp;lt;- hot but flat
   9     20%     80%    +60      148     2055   &amp;lt;- hot but flat
  10     40%     80%    +40      160     2920   &amp;lt;- hot but flat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Three things fall out of that table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Below difficulty 6, the newer model is 40 points &lt;em&gt;behind&lt;/em&gt;.&lt;/strong&gt; Not level. Behind — while spending&lt;br&gt;
three to four times the reasoning tokens. If your traffic skews easy, the newer model is a&lt;br&gt;
straight downgrade, and no aggregate benchmark will tell you that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The crossover is at difficulty 6&lt;/strong&gt;, and from there up the advantage holds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The top of the curve is where the money burns.&lt;/strong&gt; Model B's reasoning tokens grow about 17× from&lt;br&gt;
difficulty 1 to 10 (175 → 2,920) while its accuracy goes from 60% to 80%. From difficulty 7 to&lt;br&gt;
10, tokens grow 3.4× (865 → 2,920) and accuracy &lt;em&gt;falls&lt;/em&gt; from 100% to 80%.&lt;/p&gt;

&lt;p&gt;Then the experiment that makes the point sharpest. Run the whole thing again with reasoning&lt;br&gt;
switched off:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;reasoning on&lt;/th&gt;
&lt;th&gt;reasoning off&lt;/th&gt;
&lt;th&gt;cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model A&lt;/td&gt;
&lt;td&gt;66.0%&lt;/td&gt;
&lt;td&gt;62.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−4 pts&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model B&lt;/td&gt;
&lt;td&gt;72.0%&lt;/td&gt;
&lt;td&gt;54.0%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−18 pts&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Switching deliberation off drops the newer model &lt;em&gt;below&lt;/em&gt; the older one — 54% against 62%. Its&lt;br&gt;
entire 6-point advantage was bought with thinking tokens.&lt;/p&gt;

&lt;p&gt;There is a detail in that second run worth being precise about, because it complicates the story&lt;br&gt;
rather than tidying it. Per difficulty, the crossover with reasoning off still lands at 6:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;diff   A acc   B acc    gap
   1    100%     40%    -60
   4    100%     40%    -60
   5     80%     60%    -20
   6     40%     60%    +20   &amp;lt;- crossover, still 6
  10     40%     80%    +40
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So B still overtakes A at the hard end without reasoning — it is &lt;em&gt;better&lt;/em&gt; there, not merely&lt;br&gt;
better-resourced. What reasoning buys is the easy and middle range: across difficulties 1–5, B's&lt;br&gt;
average deficit widens from −32 points with reasoning to −44 without it, and that is where the&lt;br&gt;
18-point total collapse comes from. The honest summary is not "B's advantage is fake". It is that&lt;br&gt;
deliberation is load-bearing across most of the difficulty range, and the aggregate number hides&lt;br&gt;
which part of the curve it is holding up.&lt;/p&gt;


&lt;h2&gt;
  
  
  The design constraints that shaped everything
&lt;/h2&gt;

&lt;p&gt;Four decisions early on determined the whole architecture.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Grading happens in code, or it does not happen
&lt;/h3&gt;

&lt;p&gt;The temptation with an LLM comparison is to have a model judge the answers. It is the wrong&lt;br&gt;
tool. An LLM judge introduces a second model's biases into a measurement &lt;em&gt;about&lt;/em&gt; models, is not&lt;br&gt;
reproducible, and costs money on every run.&lt;/p&gt;

&lt;p&gt;So the bank ships with stored answers, and grading is a normalised string comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Prefer the last ANSWER: line — the system prompt asks for it to be final,&lt;/span&gt;
  &lt;span class="c1"&gt;// and a model that restates the format earlier should be judged on its last word.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*ANSWER&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;.+&lt;/span&gt;&lt;span class="se"&gt;?)\s&lt;/span&gt;&lt;span class="sr"&gt;*$/gim&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system prompt forces a machine-readable last line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are a precise assistant. Answer the question directly. End your answer with a final line of&lt;br&gt;
exactly: &lt;code&gt;ANSWER: &amp;lt;your answer&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then &lt;code&gt;normalise()&lt;/code&gt; strips everything that is presentation rather than knowledge — case,&lt;br&gt;
punctuation, thousands separators, currency symbols, a leading "the", a trailing full stop — and&lt;br&gt;
&lt;code&gt;grade()&lt;/code&gt; compares against the stored answer or any declared alias, with numeric equivalence so&lt;br&gt;
&lt;code&gt;5.0&lt;/code&gt; matches &lt;code&gt;5&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The verifier unit-tests the grader against 15 hand-written cases, and five of them &lt;strong&gt;must be&lt;br&gt;
rejected&lt;/strong&gt;: a wrong number (&lt;code&gt;16&lt;/code&gt; vs &lt;code&gt;15&lt;/code&gt;), an off-by-one (&lt;code&gt;14&lt;/code&gt; vs &lt;code&gt;15&lt;/code&gt;), a wrong multiple-choice&lt;br&gt;
letter, an empty answer, and a response with no &lt;code&gt;ANSWER:&lt;/code&gt; line at all. A grader that only ever&lt;br&gt;
returned &lt;code&gt;true&lt;/code&gt; would pass a naive test suite and destroy the entire measurement.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;code&gt;reasoning_content&lt;/code&gt; never leaves the provider boundary
&lt;/h3&gt;

&lt;p&gt;The brief said never log or display it. The reliable way to honour that is not discipline — it is&lt;br&gt;
to make the leak structurally impossible. The transport parses the provider response into a&lt;br&gt;
brand-new object containing only the fields the app needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// reasoning_content is deliberately dropped here and never referenced again.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;latencyMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;promptTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;readNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prompt_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;completionTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;readNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completion_tokens&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;reasoningTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;readReasoningTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;retried&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw response object goes out of scope and is never returned. Only the token count survives —&lt;br&gt;
read from &lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;, with a top-level fallback because&lt;br&gt;
not every OpenAI-compatible provider nests it the same way.&lt;/p&gt;

&lt;p&gt;The verifier then asserts against the committed JSONL that no reasoning text appears anywhere,&lt;br&gt;
and the browser suite asserts it never reaches the DOM.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The crossover must be computed, never declared
&lt;/h3&gt;

&lt;p&gt;The whole value of the app is that it finds the crossover. A hardcoded constant would make the&lt;br&gt;
app a very elaborate way of drawing a line I already decided on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findCrossover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Aggregate&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Crossover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DIFFICULTIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;agg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;difficulty&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;aAccuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;bAccuracy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;crossover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;here&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// The advantage must hold at every higher difficulty, not just here.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;staysPositive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;staysPositive&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;crossover&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crossover&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gaps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;none&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crossover&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;staysPositive&lt;/code&gt; clause is doing real work. With five samples per difficulty, one cell is&lt;br&gt;
worth 20 percentage points. A single positive gap at difficulty 7 followed by a negative gap at&lt;br&gt;
difficulty 8 is noise, not a crossover. Requiring the advantage to hold &lt;em&gt;all the way up&lt;/em&gt; is what&lt;br&gt;
makes the answer stable.&lt;/p&gt;

&lt;p&gt;Note the shape of the return value: &lt;code&gt;difficulty: number | null&lt;/code&gt; plus &lt;code&gt;none: boolean&lt;/code&gt;. The UI has&lt;br&gt;
to handle "no crossover found" as a first-class outcome, because a real run can produce one.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Every call is written to disk before it is shown
&lt;/h3&gt;

&lt;p&gt;The JSONL audit trail is appended per call, not written at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;appendRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;onProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ordering matters for two reasons. A sweep that dies at call 60 still leaves 60 graded&lt;br&gt;
records on disk, so the partial result is recoverable rather than lost. And the committed&lt;br&gt;
&lt;code&gt;data/runs/*.jsonl&lt;/code&gt; files are the evidence behind every number in the README — one line per&lt;br&gt;
graded call carrying the raw answer, the stored answer and the verdict, so anyone can check the&lt;br&gt;
claim instead of trusting it.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart TB
    subgraph Browser["Browser — Vite + React + TypeScript"]
        UI[App.tsx&amp;lt;br/&amp;gt;idle / running / complete / error]
        SP[Settings&amp;lt;br/&amp;gt;localStorage]
        SV[SurfaceView&amp;lt;br/&amp;gt;tooltip + overlay]
        SC[surface.ts&amp;lt;br/&amp;gt;three.js scene]
        CA[aggregate.ts&amp;lt;br/&amp;gt;live partial aggregation]
        UI --&amp;gt; SP
        UI --&amp;gt; SV
        SV --&amp;gt; SC
        UI --&amp;gt; CA
    end

    subgraph Node["Node — Hono on :3001"]
        API[app.ts&amp;lt;br/&amp;gt;routes + SSE]
        SW[sweep.ts&amp;lt;br/&amp;gt;orchestration]
        PV[provider.ts&amp;lt;br/&amp;gt;fetch, drops reasoning_content]
        GR[grade.ts&amp;lt;br/&amp;gt;normalised comparison]
        AG[aggregate.ts&amp;lt;br/&amp;gt;crossover + wasted zone]
        ST[store.ts&amp;lt;br/&amp;gt;JSONL append]
        API --&amp;gt; SW
        SW --&amp;gt; PV
        SW --&amp;gt; GR
        SW --&amp;gt; ST
        SW --&amp;gt; AG
    end

    subgraph Disk["Disk"]
        JSONL[(data/runs/runId.jsonl)]
    end

    subgraph External["External"]
        LLM[OpenAI-compatible&amp;lt;br/&amp;gt;/chat/completions]
    end

    UI --&amp;gt;|POST /api/curve| API
    API -.-&amp;gt;|SSE: start, progress, done, error| UI
    PV --&amp;gt;|plain fetch| LLM
    ST --&amp;gt; JSONL&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;A 100-call sweep against a real reasoning model takes minutes. That single fact dictates the&lt;br&gt;
transport: the run cannot be a request that returns a result, because nothing would be on screen&lt;br&gt;
for minutes.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant B as Browser
    participant A as API (Hono)
    participant S as sweep.ts
    participant P as Provider
    participant G as grade.ts
    participant D as JSONL

    B-&amp;gt;&amp;gt;A: POST /api/curve {questionIds, apiKey, config, condition}
    A--&amp;gt;&amp;gt;B: 200 text/event-stream
    A-&amp;gt;&amp;gt;S: runSweep(...)
    S--&amp;gt;&amp;gt;B: event: start {runId, total: 100}
    loop 50 questions x 2 models
        S-&amp;gt;&amp;gt;P: POST /chat/completions
        P--&amp;gt;&amp;gt;S: content + usage.completion_tokens_details
        S-&amp;gt;&amp;gt;G: extractAnswer(content), grade(...)
        S-&amp;gt;&amp;gt;D: append record
        S--&amp;gt;&amp;gt;B: event: progress {done, total, record}
    end
    S-&amp;gt;&amp;gt;S: aggregate, findCrossover, findWastedZone
    S-&amp;gt;&amp;gt;D: append summary
    S--&amp;gt;&amp;gt;B: event: done {result}&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Server-sent events, four frame types, one per graded call. Hono's &lt;code&gt;streamSSE&lt;/code&gt; makes the server&lt;br&gt;
side trivial; the client side needs a little care, because &lt;code&gt;EventSource&lt;/code&gt; cannot issue a POST and&lt;br&gt;
the sweep parameters (including the API key) belong in a body, not a query string. So the client&lt;br&gt;
reads the response body directly and parses frames by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// SSE frames are separated by a blank line.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;boundary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;boundary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trimStart&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;SweepEvent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buffer-and-boundary loop is the part that bites people. Chunks do not respect frame&lt;br&gt;
boundaries — a single &lt;code&gt;read()&lt;/code&gt; can deliver half a JSON payload, and the next one delivers the&lt;br&gt;
rest. Accumulating into a buffer and only parsing complete frames is what makes it correct.&lt;/p&gt;


&lt;h2&gt;
  
  
  Rendering: making a 20-bar table legible
&lt;/h2&gt;

&lt;p&gt;The data is two models × ten difficulties × two numbers (accuracy and reasoning tokens). A table&lt;br&gt;
communicates that; it just does not communicate &lt;em&gt;shape&lt;/em&gt;. The crossover is a place in space, and&lt;br&gt;
the wasted zone is a region, and both read better as terrain.&lt;/p&gt;

&lt;p&gt;three.js, two ridges of columns — Model A at the back, Model B at the front:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROW_A_Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROW_B_Z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;COLUMN_WIDTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ACCURACY_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// world units for 100%&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.06&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ACCURACY_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;normaliseTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meanReasoningTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;colour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rampColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The colour ramp is the load-bearing encoding. It is defined &lt;strong&gt;once&lt;/strong&gt; and consumed by both the&lt;br&gt;
three.js scene and the HTML legend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RAMP_STOPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#1d3f5c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// deep cool blue — barely thinking&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#2f7fa8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#57a86f&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// neutral green — the middle of the range&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#e0a92c&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f2701d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ff3d1f&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;// hot — burning tokens&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the scene and the legend each carried their own copy of the ramp, they would drift the first&lt;br&gt;
time someone tuned a colour, and the app would quietly start lying about its own scale. One&lt;br&gt;
definition, two consumers.&lt;/p&gt;

&lt;p&gt;Two other details worth stealing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normalise the colour range to the measured data, not to an absolute scale.&lt;/strong&gt; A run where Model&lt;br&gt;
A peaks at 160 reasoning tokens and Model B at 2,920 would render every A column as the same&lt;br&gt;
cold blue under a fixed 0–3,000 scale. Normalising to &lt;code&gt;reasoningRange(aggregate)&lt;/code&gt; makes the&lt;br&gt;
&lt;em&gt;relative&lt;/em&gt; burn visible, which is the actual question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Export PNG by temporarily resizing the renderer&lt;/strong&gt;, not by upscaling a screenshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;exportPng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientWidth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientHeight&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPixelRatio&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aspect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateProjectionMatrix&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toDataURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Restore the interactive size.&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousRatio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives a true 2x render (2520×1740 from a 1260×870 viewport) with crisp label text, rather&lt;br&gt;
than a blurry upscale. It requires &lt;code&gt;preserveDrawingBuffer: true&lt;/code&gt; on the renderer, which costs a&lt;br&gt;
little performance and is worth it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The bug that taught me the most
&lt;/h2&gt;

&lt;p&gt;The wasted-compute detector was originally a comparison between the two models: flag any&lt;br&gt;
difficulty where Model B spent many more tokens than Model A without being much further ahead.&lt;/p&gt;

&lt;p&gt;It flagged difficulty 6 — the crossover itself — as wasted compute. But at difficulty 6, Model B&lt;br&gt;
is &lt;strong&gt;20 points ahead&lt;/strong&gt;. Being ahead is not waste.&lt;/p&gt;

&lt;p&gt;The bug was a definition error, not a coding error. I had written down "wasted" as a &lt;em&gt;relative&lt;/em&gt;&lt;br&gt;
concept when the actual question is about &lt;em&gt;marginal return&lt;/em&gt;: is this model, at this difficulty,&lt;br&gt;
spending meaningfully more than it did one level down for no accuracy gain? That is a question&lt;br&gt;
about one model's own curve.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WASTED_TOKEN_GROWTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// &amp;gt;= 25% more reasoning tokens than the level below&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WASTED_ACCURACY_GAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// ...for &amp;lt;= 5 points of accuracy&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenGrowth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meanReasoningTokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meanReasoningTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meanReasoningTokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accuracyGain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;here&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accuracy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenGrowth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;WASTED_TOKEN_GROWTH&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;accuracyGain&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;WASTED_ACCURACY_GAIN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;difficulty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the reference run this correctly flags difficulties &lt;strong&gt;2, 6, 8, 9 and 10&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;difficulty 2: token growth +31.4%, accuracy gain  +0.0 pts
difficulty 6: token growth +36.7%, accuracy gain -20.0 pts
difficulty 8: token growth +58.4%, accuracy gain  +0.0 pts
difficulty 9: token growth +50.0%, accuracy gain -20.0 pts
difficulty 10: token growth +42.1%, accuracy gain +0.0 pts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Difficulty 2 is flagged, and it is worth sitting with that. Both models score 100% and 60% there&lt;br&gt;
— exactly as they did at difficulty 1 — but B's reasoning tokens rose 31% to reach the same&lt;br&gt;
place. That is the shape of waste at the easy end: not a catastrophe, just a model thinking&lt;br&gt;
harder to arrive at the same answer.&lt;/p&gt;

&lt;p&gt;And note that difficulty 6 is flagged on a &lt;em&gt;negative&lt;/em&gt; accuracy gain. The crossover and the start&lt;br&gt;
of the waste zone coincide here, which is a genuinely interesting reading: the level where B&lt;br&gt;
first pulls ahead is also the level where its extra thinking stops converting into accuracy. The&lt;br&gt;
two rules measure different things and are allowed to disagree; on this run they happen to point&lt;br&gt;
at the same difficulty.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building the surface live
&lt;/h2&gt;

&lt;p&gt;The first version had a genuine flaw: a blank canvas for the entire duration of a sweep. The&lt;br&gt;
surface only mounted once a run &lt;em&gt;completed&lt;/em&gt;, so with a real provider you would stare at an empty&lt;br&gt;
viewport for minutes behind a thin progress bar.&lt;/p&gt;

&lt;p&gt;The fix was not a spinner. It was to build the surface from the records as they stream in, which&lt;br&gt;
meant the client needed its own aggregator — the server only sends the aggregate at the end.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;liveAggregate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;liveRecords&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;liveRecords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelNames&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;liveRecords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelNames&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The surface then distinguishes three states per column:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unmeasured&lt;/strong&gt; → faint outline plus a floor plinth, so the empty shape of the run is visible
from the first frame&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partially measured&lt;/strong&gt; → translucent with a bright outline, because a cell holds five samples
and a column built from two of them must not look like a settled number&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finished&lt;/strong&gt; → solid, heat-coloured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One subtlety this exposed: mid-sweep the crossover tag read &lt;strong&gt;"None found"&lt;/strong&gt;. Unmeasured cells&lt;br&gt;
have a zero gap, so the rule correctly found no crossover and the UI confidently reported that&lt;br&gt;
Model B never wins — while the sweep was still running. It now reads &lt;strong&gt;"Measuring…"&lt;/strong&gt; and&lt;br&gt;
explains that the crossover needs every higher difficulty measured before it can be named.&lt;/p&gt;

&lt;p&gt;That is the same class of bug as the wasted-compute one: the arithmetic was right and the claim&lt;br&gt;
was wrong. A confident wrong answer is worse than an honest "I don't know yet", and a&lt;br&gt;
measurement tool has to be held to that standard harder than a normal app.&lt;/p&gt;

&lt;p&gt;Verified with a browser harness that samples the DOM 26 times during a sweep: the canvas is&lt;br&gt;
present in every sample, the call count climbs monotonically, and pixel analysis of the&lt;br&gt;
screenshots confirms the surface genuinely fills in (8.8% lit pixels early → 22.7% mid-sweep).&lt;br&gt;
A full 100-call sweep with live rendering completes in under a second against a fast provider,&lt;br&gt;
with no dropped state.&lt;/p&gt;


&lt;h2&gt;
  
  
  The port collision that nearly produced a false result
&lt;/h2&gt;

&lt;p&gt;This one is worth writing down because the failure mode was silent and the data looked fine.&lt;/p&gt;

&lt;p&gt;The dev setup is the standard shape: Vite on 5173 proxying &lt;code&gt;/api&lt;/code&gt; to the Hono server on 3001.&lt;br&gt;
During development I restarted the API, and another project on the machine took over 3001. Vite&lt;br&gt;
kept proxying faithfully. The web app called &lt;code&gt;/api/questions&lt;/code&gt;, got a 404, and rendered an empty&lt;br&gt;
bank. It called &lt;code&gt;/api/curve&lt;/code&gt;, and got whatever the other project returned for that path.&lt;/p&gt;

&lt;p&gt;Nothing crashed. The app just quietly talked to the wrong server.&lt;/p&gt;

&lt;p&gt;Two fixes, both cheap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The proxy target is configurable&lt;/strong&gt;, and the server refuses to start on a port held by a&lt;br&gt;
different server. It probes the port first and identifies what is there by shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Probe the port. 'ours' means a healthy instance of this app; 'foreign' means&lt;/span&gt;
&lt;span class="c1"&gt;// something else is listening and the proxy would send /api to it.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;foreign&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`Port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is already in use by a different server.\n\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`The web app proxies /api to 127.0.0.1:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, so it would talk to that\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`other server instead of this one — and quietly show the wrong data.\n\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`  PORT=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; pnpm -F server dev\n`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="s2"&gt;`  PORT=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; API_PORT=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; pnpm -F web dev`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general lesson: &lt;strong&gt;a dev proxy turns a port conflict into a data-integrity bug.&lt;/strong&gt; If your app&lt;br&gt;
fetches from a localhost port, a port collision does not produce an error — it produces plausible&lt;br&gt;
wrong data.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is verified, and what is not
&lt;/h2&gt;

&lt;p&gt;I want to be precise about this, because it is the part of the project most likely to be&lt;br&gt;
overstated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verified offline, automatically — 26 checks.&lt;/strong&gt; The full 50-question sweep runs twice (reasoning&lt;br&gt;
on, then off) through the real sweep, grader, JSONL store, aggregation and crossover code,&lt;br&gt;
against a deterministic offline transport whose competence curve is known in advance. The&lt;br&gt;
crossover it produces is exactly the declared ground truth, the grader's 15 cases pass, the&lt;br&gt;
reasoning-disabled run reports zero reasoning tokens across all 100 calls, and the JSONL contains&lt;br&gt;
no reasoning text and no API key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verified in a browser, automatically — 12 + 11 checks.&lt;/strong&gt; Settings persist across a reload, SSE&lt;br&gt;
progress streams, the surface fills in live, tooltips report both models' numbers, PNG export&lt;br&gt;
produces a real 2520×1740 download, &lt;code&gt;reasoning_content&lt;/code&gt; never appears in the DOM, and there are&lt;br&gt;
no page errors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not verified: real model behaviour.&lt;/strong&gt; The offline transport is not a model. It proves the&lt;br&gt;
plumbing, the grading, the crossover arithmetic and the visual encoding. Every number in the&lt;br&gt;
table at the top of this post comes from that transport, whose crossover is &lt;em&gt;scripted&lt;/em&gt; rather&lt;br&gt;
than observed. Running it against real models needs an API key, and I have deliberately not&lt;br&gt;
claimed results I did not measure.&lt;/p&gt;

&lt;p&gt;That distinction is the honest core of the project. The instrument is verified. The reading it&lt;br&gt;
gives on real models is the user's to take.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would add next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Error bars, before anything else.&lt;/strong&gt; Five samples per cell means every cell moves in 20-point&lt;br&gt;
steps. A crossover computed from 5-sample accuracies is a point estimate with no interval around&lt;br&gt;
it, and presenting it without one is the weakest thing about the current design. A Wilson&lt;br&gt;
interval per cell, rendered as a whisker on each column, would fix the visualisation; bootstrapping&lt;br&gt;
the crossover across resampled records would tell you how often it lands on the same difficulty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repeat runs and variance.&lt;/strong&gt; Run the same sweep N times and report the spread. Temperature 0 does not make a provider deterministic, and the variance between runs is probably comparable to the effects being measured — which is itself the most important thing a user could learn from&lt;br&gt;
this tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrency.&lt;/strong&gt; Calls are strictly sequential, which is correct but slow. A bounded pool of 4 would cut a real sweep from minutes to seconds. The care needed is that latency measurements get noisier under concurrency and some providers rate-limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost in currency.&lt;/strong&gt; Multiply reasoning tokens by the provider's per-token price and render a second surface in dollars. "Wasted compute" becomes a number a person can put in a budget.&lt;/p&gt;

&lt;p&gt;The code is in this repository — fork it, point it at your own two models, and find out where your crossover is. It will not be at 6. It will be wherever your workload puts it, which is exactly why it is worth measuring.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/258-the-confidence-curve" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/258-the-confidence-curve&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Two Models, One Board: Building "Model vs Model"</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Fri, 18 Sep 2026 18:23:34 +0000</pubDate>
      <link>https://dev.to/harishkotra/two-models-one-board-building-model-vs-model-2h6a</link>
      <guid>https://dev.to/harishkotra/two-models-one-board-building-model-vs-model-2h6a</guid>
      <description>&lt;p&gt;I wanted a way to show that a newer model is better than an older one that wasn't a table of numbers.&lt;/p&gt;

&lt;p&gt;Benchmark tables are boring and, worse, they're unconvincing. Nobody feels anything reading &lt;code&gt;MMLU: 78.3 → 81.1&lt;/code&gt;. But put two models on opposite sides of a tic-tac-toe board, enforce the rules with a real engine, and let them play a best-of-five, and you get something else entirely: an outcome you genuinely don't know in advance, a scoreboard you can argue with, and a format where the audience picks a side in the comments. The progression from an older model to a newer one stops being a delta and becomes a &lt;em&gt;record&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Model vs Model&lt;/strong&gt;. Two OpenAI-compatible endpoints, three games — tic-tac-toe, Connect 4, and a five-round structured debate — one three.js board that renders every move live, and a persistent scoreboard across the whole session.&lt;/p&gt;

&lt;p&gt;The interesting engineering isn't the games. It's that models are unreliable narrators of their own actions, and the whole thing only works if you refuse to trust them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The shape is deliberately boring: a browser, a dev proxy, an Express server, and a provider. All the interesting decisions live inside the server box.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────┐
│  BROWSER   Vite + React + TS  :5173                                      │
│                                                                          │
│    state machine:  idle ──► playing ──► finished / error                 │
│    three.js: one persistent scene, swaps board contents per game         │
└───────────────┬───────────────────────────────────────────▲──────────────┘
                │  POST /api/game                           │  SSE frames
                │  { game, rounds, settings }               │   event: start
                │                                           │   event: move
                ▼                                           │   event: done
┌───────────────────────────────────────────────────────────┴──────────────┐
│  VITE DEV PROXY   :5173 ──► :3001     /api/*   (unbuffered for SSE)      │
└───────────────┬───────────────────────────────────────────▲──────────────┘
                │                                           │
                ▼                                           │
┌───────────────────────────────────────────────────────────┴──────────────┐
│  EXPRESS SERVER   :3001                                                  │
│                                                                          │
│   ┌──────────────────────────────────────────────────────────────┐       │
│   │  MATCH RUNNER   match.ts                                     │       │
│   │                                                              │       │
│   │    for each game:                                            │       │
│   │      for each turn:                                          │       │
│   │        playTurn() ──► call model ──► parse ──► apply()       │       │
│   │                          ▲                    │              │       │
│   │                          │  rejected?         │              │       │
│   │                          └─── ONE retry ◄─────┘              │       │
│   │                               2nd failure = forfeit,         │       │
│   │                               turn passes, board untouched   │       │
│   └──────┬───────────────────────┬──────────────────────┬────────┘       │
│          │                       │                      │                │
│          ▼                       ▼                      ▼                │
│   ┌──────────────┐    ┌────────────────────┐   ┌───────────────────┐     │
│   │ RULES ENGINE │    │  LLM CLIENT        │   │  JSONL LOG        │     │
│   │ games/*.ts   │    │  llm.ts            │   │  log.ts           │     │
│   │              │    │                    │   │                   │     │
│   │ render()     │    │  plain fetch to    │   │  append-only      │     │
│   │ parse()      │    │  /chat/completions │   │  one record/move  │     │
│   │ apply()      │    │  no SDK            │   │  logs/matches     │     │
│   │ terminal()   │    │                    │   │  .jsonl           │     │
│   └──────────────┘    └─────────┬──────────┘   └───────────────────┘     │
└─────────────────────────────────┼────────────────────────────────────────┘
                                  ▼
                       ┌────────────────────────┐
                       │  MODEL PROVIDER        │
                       │  OpenAI-compatible     │
                       │  POST /chat/completions│
                       └────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The core decision: the server owns the board
&lt;/h2&gt;

&lt;p&gt;A model never sees game state. It sees a &lt;strong&gt;string&lt;/strong&gt; — a rendered text board — plus a move history, and it returns a move. That move is validated before it touches anything.&lt;/p&gt;

&lt;p&gt;That constraint is what makes the whole project tractable, and it's expressed as one generic interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;GameEngine&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GameId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Roles in play order, e.g. ['X','O']. */&lt;/span&gt;
  &lt;span class="nl"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="cm"&gt;/** Max moves before the game is declared drawn (safety valve). */&lt;/span&gt;
  &lt;span class="nl"&gt;maxMoves&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Plain-text board for the prompt. */&lt;/span&gt;
  &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Compact board for the wire (client renders 3D from this). */&lt;/span&gt;
  &lt;span class="nf"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TurnContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Strict-ish extraction of the move from raw model output. */&lt;/span&gt;
  &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;MoveParse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Rules validation. Never mutates; returns a new state on success. */&lt;/span&gt;
  &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;move&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ApplyResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;S&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;TerminalResult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** Line shown in the move log, e.g. "X plays cell 4". */&lt;/span&gt;
  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;move&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generic parameter &lt;code&gt;S&lt;/code&gt; is the whole trick. &lt;code&gt;runMatch()&lt;/code&gt; in &lt;code&gt;match.ts&lt;/code&gt; never knows whether it's playing tic-tac-toe or running a debate. It knows how to call a model, how to retry, how to forfeit, how to log, and how to stream — and it delegates every game-specific question to the engine. Tic-tac-toe's state is &lt;code&gt;(string|null)[]&lt;/code&gt;; Connect 4's is a 6×7 grid with a gravity rule; the debate's is a list of &lt;code&gt;{role, round, text}&lt;/code&gt; turns. The runner doesn't care.&lt;/p&gt;

&lt;p&gt;Two details make this hold up. First, &lt;code&gt;apply()&lt;/code&gt; &lt;strong&gt;never mutates&lt;/strong&gt;: it returns a brand-new state on success and an error on failure. So a rejected move cannot leave a half-written board behind, because there's no board to write to. Second, &lt;code&gt;terminal()&lt;/code&gt; is a pure query, so the runner can ask "is this over?" as often as it likes without side effects.&lt;/p&gt;

&lt;p&gt;Adding a fourth game means writing one file. Nothing in the runner changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The retry/forfeit state machine
&lt;/h2&gt;

&lt;p&gt;This is the part I'm happiest with, and it's the part that took the most thought.&lt;/p&gt;

&lt;p&gt;Models produce malformed output constantly. They wrap JSON in prose, they return &lt;code&gt;{"move": "four"}&lt;/code&gt;, they play in an occupied cell, they ignore the gravity rule. A naive implementation drops the bad move, or crashes, or — worst — applies something half-parsed and corrupts the board.&lt;/p&gt;

&lt;p&gt;The rule I settled on: &lt;strong&gt;one correction retry, then a forfeit. Never silently drop a move, never corrupt state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The retry is not just "try again". It's a &lt;em&gt;constrained&lt;/em&gt; retry that hands the model its own rejected output, the exact reason for rejection, and the list of legal moves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;system&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The correction retry explains exactly what was wrong and what is allowed.&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;(no output)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s2"&gt;`Your previous answer was REJECTED by the rules engine: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lastError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;`Legal moves right now: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;legalMoves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;legalMoves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Reply again with ONLY the corrected JSON object. No prose, no explanation.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// ... call the model, parse, then apply()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeding the rejected output back as an &lt;code&gt;assistant&lt;/code&gt; turn matters. It puts the model in a conversation with its own mistake rather than asking it to try again blind, and it reliably fixes the "I wrapped the JSON in prose" class of failure.&lt;/p&gt;

&lt;p&gt;If the second attempt also fails, the turn is a &lt;strong&gt;forfeit&lt;/strong&gt;. And here's the semantic that took me a rewrite to get right: a forfeit &lt;strong&gt;passes the turn&lt;/strong&gt;. It does not end the game. The board is untouched, the opponent plays next, and play continues. If a game reaches the move cap without a terminal state, the side that forfeited more turns loses — but a single forfeit is a penalty, not a loss.&lt;/p&gt;

&lt;p&gt;Every attempt is preserved. The JSONL record for a turn keeps both attempts, so a rejected move is visible forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"moveNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"player"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"move"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"legal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"illegal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"forfeit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correctionAttempted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"correctionSucceeded"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attempts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"raw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"legal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"column 3 is full — gravity means a disc cannot be placed there. Legal columns: 0, 1, 2, 4, 5, 6"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"attempt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"raw"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;move&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;4&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"legal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One record per move, with the full retry trail inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gravity as a testable rule
&lt;/h2&gt;

&lt;p&gt;Connect 4's rule is the best demonstration because it's a rule a model can &lt;em&gt;see&lt;/em&gt; and still violate. The board renders as text, with a legend and a full-column marker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    0   1   2   3   4   5   6
  | . | . | . | . | . | . | . |
  | . | . | . | . | . | . | . |
  | . | . | . | . | . | . | . |
  | . | . | . | . | . | . | . |
  | . | . | . | . | . | . | . |
  | R | Y | R | Y | R | Y | R |
  full?  F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;apply()&lt;/code&gt; enforces it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;landingRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;COLS&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;columnFull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`column &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is full — gravity means a disc cannot be placed there. Legal columns: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;
      &lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;moveNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;moveNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That error string is not decorative. It's the &lt;em&gt;exact&lt;/em&gt; text the correction retry hands back to the model, and it's what appears in the move log with a red badge. Because it names the rule and lists the legal alternatives, the retry almost always succeeds — which is the point. The retry exists to fix recoverable mistakes, not to punish.&lt;/p&gt;

&lt;p&gt;To prove the path is real, the mock provider I use for testing &lt;strong&gt;deliberately breaks the rule&lt;/strong&gt;: on a schedule, when a column is full, it plays into that column anyway. The retry then succeeds and the whole thing shows up in the log as a two-attempt record. I didn't want a retry path that only existed in theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reasoning-token trap
&lt;/h2&gt;

&lt;p&gt;Two things bit me here, and both are worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;max_tokens&lt;/code&gt; has a floor of 900, and it is not negotiable.&lt;/strong&gt; A move is a few tokens — &lt;code&gt;{"move": 4}&lt;/code&gt; is about five. So setting &lt;code&gt;max_tokens: 20&lt;/code&gt; seems obviously right. It isn't. If the model does any hidden reasoning, the reasoning consumes the budget, the model is cut off mid-thought, and &lt;code&gt;message.content&lt;/code&gt; comes back &lt;strong&gt;empty&lt;/strong&gt;. You get a perfectly successful HTTP 200 with nothing in it. The floor is enforced in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;effectiveMaxTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reasoning tokens live in a nested usage field.&lt;/strong&gt; Not &lt;code&gt;usage.reasoning_tokens&lt;/code&gt; on every provider, and definitely not in the message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reasoningTokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
  &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_tokens&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;
  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the rule I enforced throughout: &lt;strong&gt;&lt;code&gt;reasoning_content&lt;/code&gt; is counted, never stored.&lt;/strong&gt; The provider sends it; the client reads the usage field and discards the text. It is never logged, never put in an SSE event, never rendered. Only the token count survives, because that's the number the audience actually cares about — it's the visible cost of thinking.&lt;/p&gt;

&lt;p&gt;I test this adversarially. The mock provider returns a long &lt;code&gt;reasoning_content&lt;/code&gt; string on &lt;em&gt;every&lt;/em&gt; call, and the harness asserts that the string never appears in the JSONL log or in the DOM. If someone later "helpfully" adds reasoning to the UI, the test fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSE over POST
&lt;/h2&gt;

&lt;p&gt;I needed the server to stream each move as it's decided. &lt;code&gt;EventSource&lt;/code&gt; is the obvious tool and it's the wrong one: it's &lt;strong&gt;GET-only&lt;/strong&gt;, and a match request carries a settings object including an API key. Putting a key in a query string is not something I'm willing to do.&lt;/p&gt;

&lt;p&gt;So the client uses &lt;code&gt;fetch&lt;/code&gt; with a &lt;code&gt;POST&lt;/code&gt; body and reads the response body as a stream, buffering on the SSE frame delimiter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(;;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;streamDone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamDone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// SSE frames are separated by a blank line.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The buffering matters: a chunk boundary can land mid-frame, so you accumulate and only dispatch on a complete &lt;code&gt;\n\n&lt;/code&gt;-terminated frame. The frame parser also skips comment lines, which is how the 15-second heartbeat (&lt;code&gt;: ping&lt;/code&gt;) keeps intermediaries from timing the connection out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// heartbeat&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;dataLines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One extra thing on the server: the Vite dev proxy will happily buffer an event stream unless you tell it not to, so the proxy config forces &lt;code&gt;cache-control: no-cache, no-transform&lt;/code&gt; on &lt;code&gt;text/event-stream&lt;/code&gt; responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three.js rendering
&lt;/h2&gt;

&lt;p&gt;One persistent &lt;code&gt;BoardScene&lt;/code&gt; owns the renderer, camera, &lt;code&gt;OrbitControls&lt;/code&gt;, lights, and a render loop. Swapping games calls &lt;code&gt;setGame()&lt;/code&gt;, which disposes the previous board's meshes and rebuilds — the scene itself never gets torn down. The React wrapper pushes board snapshots in; it doesn't manage three.js objects.&lt;/p&gt;

&lt;p&gt;Tic-tac-toe pieces are extruded: the X is two crossed &lt;code&gt;BoxGeometry&lt;/code&gt; bars, the O is a torus laid flat, both with real shadow casting and a slight emissive lift so they read against the dark plate. Winning lines glow by walking the piece's materials and pushing &lt;code&gt;emissive&lt;/code&gt; to gold.&lt;/p&gt;

&lt;p&gt;Connect 4 gets the animation I cared most about. Discs don't fade in — they fall, under real integration, and bounce once with damping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Falling discs with a single damped bounce on landing.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gravity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;falling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bounced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;gravity&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bounced&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.28&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// bounce&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each move also nudges the camera toward the cell that was just played, then eases back to the resting framing. It's a small offset applied to the base camera position, so it composes with whatever the user has done with &lt;code&gt;OrbitControls&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;nudgeToward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;worldPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;THREE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vector3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;strength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.22&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;worldPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseTarget&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lengthSq&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;multiplyScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nudge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;camera&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nudge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basePos&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basePos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;strength&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nudge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing without an API key
&lt;/h2&gt;

&lt;p&gt;I didn't have a key while building this, and honestly that constraint improved the code. I wrote a small OpenAI-compatible provider (&lt;code&gt;scripts/mock-provider.mjs&lt;/code&gt;) that &lt;strong&gt;reads the board out of the prompt&lt;/strong&gt; and plays legal moves — it parses the "empty cells you may play" list, does win/block checks, and picks sensibly. It also breaks the gravity rule on a schedule, and returns &lt;code&gt;reasoning_content&lt;/code&gt; on every call.&lt;/p&gt;

&lt;p&gt;That gives me two harnesses that run with zero credentials:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm run verify&lt;/code&gt; — 76 checks on the rules engine, retry/forfeit paths, and the JSONL log.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run ui-check&lt;/code&gt; — 65 checks driving the real UI in headless Chrome over the DevTools Protocol.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle I held to: &lt;strong&gt;assert against independently re-derived state, not the server's own output.&lt;/strong&gt; When the harness checks that a tic-tac-toe game ended legally, it doesn't trust the server's &lt;code&gt;outcome&lt;/code&gt; field. It re-reads the final board, counts X's and O's, confirms &lt;code&gt;|X − O| ≤ 1&lt;/code&gt;, scans all eight lines for a win, and only then checks that the server's claim matches. For Connect 4 it scans every column bottom-up to prove no disc is floating. For the log it re-reads the file and counts records per game.&lt;/p&gt;

&lt;p&gt;This caught real bugs. A server that reports "legal terminal state" while its board is subtly wrong would pass a naive test and fail this one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bugs I actually hit
&lt;/h2&gt;

&lt;p&gt;These are the ones worth your time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;req.on('close')&lt;/code&gt; killed the SSE stream after one event.&lt;/strong&gt; The first end-to-end run hung forever: the browser got &lt;code&gt;event: start&lt;/code&gt; and then silence. The match was running fine — records were being written to the JSONL log the whole time — but every subsequent write was suppressed. The cause: I guarded writes with a &lt;code&gt;closed&lt;/code&gt; flag set by &lt;code&gt;req.on('close')&lt;/code&gt;. In Node, the &lt;strong&gt;request&lt;/strong&gt; stream emits &lt;code&gt;close&lt;/code&gt; as soon as its body has been fully consumed. &lt;code&gt;express.json()&lt;/code&gt; consumes the POST body immediately, so &lt;code&gt;close&lt;/code&gt; fired before the first move, and every write after that was silently dropped. The fix is one word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Listen on the RESPONSE, not the request: `req` emits 'close' as soon as the POST body&lt;/span&gt;
&lt;span class="c1"&gt;// has been consumed, which would suppress every subsequent write.&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Forfeit semantics: it must pass the turn, not end the game.&lt;/strong&gt; My first implementation treated a forfeit as an immediate game loss. That's a defensible reading of "forfeit" and it's wrong for this format — it makes a single malformed response decide a match, and it makes a best-of-five meaningless when a model has one bad turn. A forfeit now passes the turn; the game continues; the move cap plus a per-seat forfeit count decides the result if no terminal state is ever reached. The harness explicitly asserts the game &lt;em&gt;continued&lt;/em&gt; after a forced forfeit rather than aborting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;moveNumber&lt;/code&gt; collisions.&lt;/strong&gt; Because a forfeited turn doesn't advance the board's own move counter, the next player's move reused the same number — two JSONL records for "move 1" in the same game. The fix was to separate the two concepts: &lt;code&gt;moveNumber&lt;/code&gt; in the log and on the wire is a &lt;strong&gt;monotonic turn number&lt;/strong&gt;, incremented on every turn including forfeits, while the board's internal counter tracks applied moves. Uniqueness is now a property the harness checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Records appended before the result was stamped.&lt;/strong&gt; I wrote each record to disk as the move happened, then stamped the game's &lt;code&gt;result&lt;/code&gt; field onto the in-memory array afterward. Append-only JSONL can't be edited, so on disk every record was missing &lt;code&gt;result&lt;/code&gt;. The fix was to buffer a game's records in memory and append them once the outcome is known — still append-only, still one record per move, now with the result present. The tradeoff: a hard crash mid-game loses that game's records. For a game bounded at 42 moves I'll take it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;gl.readPixels&lt;/code&gt; returned black.&lt;/strong&gt; My browser check read pixels straight out of the WebGL context and got zeros, which looked exactly like "the board isn't rendering." It was a false alarm: the browser &lt;strong&gt;clears the drawing buffer after compositing&lt;/strong&gt;, so a read-back outside a draw call always returns black even when the frame rendered perfectly. I replaced it with a screenshot analysed pixel-by-pixel (via a small zlib-based PNG decoder), which measures what the user actually sees. I nearly "fixed" a working renderer because of this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An unrelated process on port 3001 silently swallowed requests.&lt;/strong&gt; Another project on my machine was listening on 3001. My Vite proxy pointed there, so requests went to &lt;em&gt;that&lt;/em&gt; server, which returned its own health payload and 404'd the game endpoint. My first instinct was that my code was broken. Now the API port is configurable (&lt;code&gt;API_PORT=3210 PORT=3210 npm run dev&lt;/code&gt;) and both harnesses &lt;strong&gt;refuse to start against an occupied port&lt;/strong&gt; rather than reporting false passes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An unreachable endpoint produced a slow forfeit cascade.&lt;/strong&gt; Point the Base URL at a dead host and every call fails. My retry logic dutifully retried, forfeited, passed the turn, and did the same for the next twenty turns — eventually "completing" a match where nothing happened. A transport failure is a &lt;em&gt;configuration&lt;/em&gt; problem, not a gameplay one, so it now aborts the match after two consecutive unreachable calls and surfaces the provider's real error text in the UI.&lt;/p&gt;

&lt;p&gt;That last one has a general lesson: retry-and-forfeit is the right policy for &lt;em&gt;bad output&lt;/em&gt;, and the wrong policy for &lt;em&gt;broken infrastructure&lt;/em&gt;. Conflating them hides your own misconfiguration behind a plausible-looking game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playing any two models: the player library
&lt;/h2&gt;

&lt;p&gt;The first version hardcoded exactly two slots: one Base URL, one key, Model A and Model B. That&lt;br&gt;
is the right shape for a single comparison and the wrong shape for everything after it. You&lt;br&gt;
cannot compare a hosted model against a local one, and you cannot put five models from the same&lt;br&gt;
provider in a rotation without re-typing the key five times.&lt;/p&gt;

&lt;p&gt;So the config became a two-level library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Endpoint  ── owns the base URL and the API key
   ▲
   │ endpointId
   │
ModelEntry ── owns a model name
   ▲
   │ seatA / seatB
   │
  Seat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for two levels rather than one flat list of "players" is the second use case in the&lt;br&gt;
title of this section. If each player carried its own key, "one endpoint with five models" would&lt;br&gt;
mean pasting the same secret five times, and rotating that key would mean editing five rows. Put&lt;br&gt;
the key on the endpoint and the model list becomes free.&lt;/p&gt;

&lt;p&gt;On the wire this stays simple. The client resolves a seat down to a single flat object before&lt;br&gt;
starting a match, so the server never learns that a library exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web/src/lib/settings.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveCompetitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Competitor&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;modelId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoints&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpointId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means the match runner did not have to change at all. It already took a seat and a model&lt;br&gt;
name; now it takes a seat and a small object that happens to include the endpoint. The generic&lt;br&gt;
&lt;code&gt;GameEngine&amp;lt;S&amp;gt;&lt;/code&gt; boundary paid for itself a second time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two bugs this feature introduced, both worth knowing about.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;the API key leaked into the &lt;code&gt;done&lt;/code&gt; event.&lt;/strong&gt; Seats used to hold a bare model name, so&lt;br&gt;
echoing &lt;code&gt;seats&lt;/code&gt; back to the client was harmless. Once a seat held the whole competitor — key&lt;br&gt;
included — that echo started shipping secrets back over the wire and into the browser. The fix&lt;br&gt;
is a redaction pass, and the harness now asserts the key is absent from the payload &lt;em&gt;and&lt;/em&gt; from&lt;br&gt;
the DOM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** The `done` event carries the seat assignment; it must not carry the keys with it. */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;redactSeats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Competitor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Competitor&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;A&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;redactCompetitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;redactCompetitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, &lt;strong&gt;a misconfigured endpoint quietly forfeited an entire match.&lt;/strong&gt; The old transport-failure&lt;br&gt;
guard only fired if &lt;em&gt;no&lt;/em&gt; call had ever succeeded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transportSuccesses&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;transportFailures&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With one seat working and one seat broken, &lt;code&gt;transportSuccesses&lt;/code&gt; was never zero, so the broken&lt;br&gt;
seat forfeited every single turn and the match "completed" with a lopsided score. That is a&lt;br&gt;
config error wearing a gameplay costume. The fix is to count consecutive failures &lt;strong&gt;per seat&lt;/strong&gt;,&lt;br&gt;
because a seat that cannot be reached twice in a row is not a model playing badly — it is a&lt;br&gt;
wrong URL or a bad key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;turn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;transportError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transportFailures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transportFailures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;seats&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Could not reach &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; at &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="s2"&gt;`two consecutive calls failed. Check that endpoint's base URL and API key in Players.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transportFailures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;seat&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general lesson, and it is the same one as before: &lt;strong&gt;retry-and-forfeit is the right policy for&lt;br&gt;
bad output and the wrong policy for broken infrastructure.&lt;/strong&gt; Adding a second endpoint made the&lt;br&gt;
distinction load-bearing, because now one seat can be perfectly healthy while the other is&lt;br&gt;
unreachable.&lt;/p&gt;

&lt;p&gt;The library also has to keep the app always playable. Deleting an endpoint cascades to its&lt;br&gt;
models, and a seat pointing at a deleted model is repaired rather than left dangling; the final&lt;br&gt;
endpoint and the last two models cannot be deleted at all. The browser harness walks that whole&lt;br&gt;
path — add a second endpoint, add a third model, attach it, delete the endpoint, confirm both the&lt;br&gt;
cascade and the seat repair — because "the user can break their own config" is exactly the kind&lt;br&gt;
of thing that only shows up in a real browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd build next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fourth game&lt;/strong&gt; to prove the engine abstraction. Nim or Reversi would each be one file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELO across sessions&lt;/strong&gt;, persisted outside the JSONL log so the scoreboard survives a restart and accumulates across days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A judge model for the debate.&lt;/strong&gt; Right now the debate is scored by a deterministic published rubric (cap compliance, engagement, concreteness, concision) so results are reproducible. A judge model would be more interesting and less honest — worth building as an explicit alternative with the bias caveats visible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay from JSONL.&lt;/strong&gt; Every record has the seed, the move, and the board. A scrubbable replay of a past match is nearly free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost and token accounting&lt;/strong&gt; per match, since I'm already logging &lt;code&gt;prompt_tokens&lt;/code&gt; and &lt;code&gt;completion_tokens&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel matches&lt;/strong&gt; on different seeds, to turn a single game into a distribution — which is what you actually need before claiming a model is better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A head-to-head ladder&lt;/strong&gt; where models play every game in rotation, so the first-move advantage averages out across a schedule instead of being neutralised one game at a time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveat
&lt;/h2&gt;

&lt;p&gt;I built and verified this without an API key. Everything you can test locally is real: the rules engines, the retry and forfeit paths, the SSE streaming, the JSONL log, the three.js board, the two harnesses — 76 rules checks and 65 browser checks, all passing against a local OpenAI-compatible mock.&lt;/p&gt;

&lt;p&gt;What is &lt;strong&gt;not&lt;/strong&gt; verified is the request shape against the specific provider it defaults to. The defaults point at &lt;code&gt;https://api.particle.ai/v1&lt;/code&gt; with models &lt;code&gt;deepseek-v4-flash-0731&lt;/code&gt; and &lt;code&gt;deepseek-v4.1-flash&lt;/code&gt;, but I never sent a live request to that endpoint. The client uses &lt;code&gt;response_format: { type: "json_object" }&lt;/code&gt;, a &lt;code&gt;max_tokens&lt;/code&gt; floor of 900, and optionally &lt;code&gt;chat_template_kwargs: { "enable_thinking": false }&lt;/code&gt; when the "Disable reasoning" toggle is on — and whether that last field is accepted, ignored, or rejected by a given provider is exactly the kind of thing you only learn by trying it. If the provider refuses the request, the real error text appears in the UI banner rather than a generic message, which is the next best thing to having tested it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5z37b1kpn68rwqcnj3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo5z37b1kpn68rwqcnj3y.png" alt="How it works 1" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovzi02pt3sxfo3pv0cty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fovzi02pt3sxfo3pv0cty.png" alt="How it works 2" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhg8fto0a25w8v3c5kn2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhg8fto0a25w8v3c5kn2s.png" alt="How it works 3" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgr1uj9h90bs4rgdob01w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgr1uj9h90bs4rgdob01w.png" alt="How it works 4" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/257-head-to-head-games" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/257-head-to-head-games&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Automating LLM A/B Testing: How to Build a Cross-Model Evaluation Harness Programmatically</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Fri, 18 Sep 2026 13:45:28 +0000</pubDate>
      <link>https://dev.to/harishkotra/automating-llm-ab-testing-how-to-build-a-cross-model-evaluation-harness-programmatically-5g5h</link>
      <guid>https://dev.to/harishkotra/automating-llm-ab-testing-how-to-build-a-cross-model-evaluation-harness-programmatically-5g5h</guid>
      <description>&lt;p&gt;If you are building LLM-powered products, you already know that benchmark leaderboards are not enough. What matters is how a model behaves on your prompts, your tone, your domain data, and your budget.&lt;/p&gt;

&lt;p&gt;This guide is for engineers and product teams who want to build a repeatable evaluation harness that can compare several open-weight models on the same dataset, score them automatically, and choose a winner using accuracy, latency, and token cost. By the end, you will have a working mental model for building a cross-model evaluation system that runs asynchronously, stores its results locally, and uses Featherless.ai as the model execution layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with ad hoc model testing
&lt;/h2&gt;

&lt;p&gt;Most teams evaluate models in the least reliable way possible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;copy a prompt into a chat UI&lt;/li&gt;
&lt;li&gt;compare a couple of responses by eye&lt;/li&gt;
&lt;li&gt;maybe keep a spreadsheet&lt;/li&gt;
&lt;li&gt;repeat the process later with a different model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That approach does not scale. It breaks down when you need to compare many rows, many models, or multiple scoring criteria at once. It also makes it hard to answer the real question:&lt;/p&gt;

&lt;p&gt;Which model performs best on this specific dataset, under this specific cost and latency profile?&lt;/p&gt;

&lt;p&gt;That is the problem this harness solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Featherless contributes to the system
&lt;/h2&gt;

&lt;p&gt;Featherless is the model execution layer for this project. Instead of provisioning GPUs, managing serving stacks, or wiring together one-off model hosts, the harness sends requests directly to Featherless’s OpenAI-compatible API and lets the platform handle model availability, concurrency limits, and serverless inference.&lt;/p&gt;

&lt;p&gt;That matters because the evaluation harness can focus on orchestration, scoring, and reporting rather than infrastructure. The app uses four Featherless surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/models&lt;/code&gt; for live model discovery and filtering&lt;/li&gt;
&lt;li&gt;OpenAI-compatible chat completions for candidate generation and judging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /account/concurrency&lt;/code&gt; for concurrency snapshots before a run&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /account/concurrency/stream&lt;/code&gt; for live concurrency telemetry during a run&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;The project is a full-stack LLM evaluation dashboard built with FastAPI on the backend and Next.js on the frontend.&lt;/p&gt;

&lt;p&gt;The workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;upload a CSV dataset with prompts and expected answers&lt;/li&gt;
&lt;li&gt;select 2 to 10 Featherless models&lt;/li&gt;
&lt;li&gt;fan out requests concurrently across the selected models&lt;/li&gt;
&lt;li&gt;score each candidate against ground truth with an LLM judge&lt;/li&gt;
&lt;li&gt;store logs and results in SQLite&lt;/li&gt;
&lt;li&gt;compare models in a dashboard ranked by weighted score&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How the async fan-out works
&lt;/h2&gt;

&lt;p&gt;The core performance trick is concurrent model execution. A naive loop would query one model at a time, which is slow and wastes the fact that the evaluation of each row is independent.&lt;/p&gt;

&lt;p&gt;Instead, the harness evaluates a row by dispatching one task per selected model and waiting for them together with &lt;code&gt;asyncio.gather&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nf"&gt;evaluate_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;selected_models&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the system safe against plan limits, the app wraps model calls in a semaphore. That lets users choose how many requests can run in parallel without exceeding their Featherless concurrency budget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;parallel_model_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_parallel_models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parallel_model_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parallel_model_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where Featherless’s cost-based concurrency model matters. A large model can consume more concurrency units than a smaller one, so “parallel” does not simply mean “unlimited.”&lt;/p&gt;

&lt;h2&gt;
  
  
  How candidate outputs are collected and scored
&lt;/h2&gt;

&lt;p&gt;Each candidate model receives the same prompt and system instruction. The goal is consistency, not creativity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s prompt directly. Be concise and factual.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each candidate response, the harness records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;output text&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;prompt tokens&lt;/li&gt;
&lt;li&gt;completion tokens&lt;/li&gt;
&lt;li&gt;total tokens&lt;/li&gt;
&lt;li&gt;any provider error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the judge model compares the candidate answer to the ground truth and returns a score from &lt;code&gt;0.0&lt;/code&gt; to &lt;code&gt;1.0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a strict LLM evaluation judge. Compare a candidate answer against &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the expected ground truth for semantic correctness. Return JSON only with &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;shape {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 0.0, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rationale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;short reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prompt:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected Ground Truth:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Candidate Output:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candidate_output&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Return only the JSON score object.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser is deliberately tolerant because real models sometimes return fenced JSON, not bare JSON.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rationale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The answer matches the ground truth.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is important in production. If your parser assumes perfect formatting, your judge becomes a brittle point of failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the dataset is structured
&lt;/h2&gt;

&lt;p&gt;The harness expects a CSV with three columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Prompt Input&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Expected Ground Truth&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID,Prompt Input,Expected Ground Truth
ticket-001,"Summarize this customer complaint: The user cannot reset their password after three attempts.","The user cannot reset their password because repeated attempts triggered a lockout."
ticket-002,"What is 18% of 250?","45"
ticket-003,"Rewrite this answer to sound more professional: 'I can't help with that right now.'","I’m unable to assist with that request at the moment."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This format is intentionally simple. It works for math, summarization, rewriting, extraction, and support workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real example run
&lt;/h2&gt;

&lt;p&gt;Here is the kind of comparison this harness is meant to produce.&lt;/p&gt;

&lt;p&gt;Scenario: a support team wants to compare two models on ticket summarization quality.&lt;/p&gt;

&lt;p&gt;Dataset: 25 rows of internal support tickets&lt;br&gt;
Candidates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;deepseek-ai/DeepSeek-V4-Flash&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google/gemma-4-E4B-it&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Judge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a separate judge model optimized for rubric-based evaluation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Observed result:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Semantic Accuracy&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;th&gt;Total Tokens&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Errors&lt;/th&gt;
&lt;th&gt;Weighted Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deepseek-ai/DeepSeek-V4-Flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.560&lt;/td&gt;
&lt;td&gt;3.60s&lt;/td&gt;
&lt;td&gt;12,749&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0.442&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;google/gemma-4-E4B-it&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.393&lt;/td&gt;
&lt;td&gt;4.63s&lt;/td&gt;
&lt;td&gt;17,987&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0.151&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What this tells you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;deepseek-ai/DeepSeek-V4-Flash&lt;/code&gt; was the better fit overall for this dataset&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google/gemma-4-E4B-it&lt;/code&gt; used more tokens and still scored lower&lt;/li&gt;
&lt;li&gt;the winner was selected by a custom weighting of accuracy, latency, and token use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the point of the harness. A leaderboard can tell you what is popular. This tells you what works for your task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the judge model is separate
&lt;/h2&gt;

&lt;p&gt;A strong candidate model is not automatically a strong judge. Those are different jobs.&lt;/p&gt;

&lt;p&gt;A good candidate model should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate useful task outputs&lt;/li&gt;
&lt;li&gt;follow the prompt closely&lt;/li&gt;
&lt;li&gt;be fast enough for your workflow&lt;/li&gt;
&lt;li&gt;fit your cost constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good judge model should:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apply a rubric consistently&lt;/li&gt;
&lt;li&gt;compare output to reference data&lt;/li&gt;
&lt;li&gt;be stable at low temperature&lt;/li&gt;
&lt;li&gt;minimize variance across evaluations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why the harness uses a dedicated judge model rather than reusing one of the candidates. If you let the same model generate and judge, you mix two different roles and make the evaluation less trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the backend handles failures
&lt;/h2&gt;

&lt;p&gt;The backend is designed to keep a run alive even when individual requests fail.&lt;/p&gt;

&lt;p&gt;It catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection timeouts&lt;/li&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;HTTP status errors&lt;/li&gt;
&lt;li&gt;schema validation failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters because distributed model inference fails in different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a model can be temporarily at capacity&lt;/li&gt;
&lt;li&gt;a model can return invalid structured output&lt;/li&gt;
&lt;li&gt;a model can be unavailable on the current plan&lt;/li&gt;
&lt;li&gt;the provider can change response shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The right behavior is not to crash the entire run. It is to record the failure, continue the other model calls, and expose the issue in the final report.&lt;/p&gt;

&lt;h2&gt;
  
  
  How concurrency visibility helps
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/account/concurrency/stream&lt;/code&gt; endpoint is useful because model limits are not static. A model may be available in the catalogue but temporarily unavailable under current load.&lt;/p&gt;

&lt;p&gt;The dashboard uses the stream to show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;current concurrency limit&lt;/li&gt;
&lt;li&gt;used concurrency cost&lt;/li&gt;
&lt;li&gt;number of active requests&lt;/li&gt;
&lt;li&gt;the model currently consuming capacity&lt;/li&gt;
&lt;li&gt;how long each in-flight request has been running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That gives the user a live view of whether the run is healthy or headed toward 429s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the storage layer is local
&lt;/h2&gt;

&lt;p&gt;The harness stores datasets, run metadata, and events in SQLite. That keeps the system reproducible and easy to inspect without adding a separate database dependency.&lt;/p&gt;

&lt;p&gt;This is enough for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dataset uploads&lt;/li&gt;
&lt;li&gt;saved run history&lt;/li&gt;
&lt;li&gt;per-row event logs&lt;/li&gt;
&lt;li&gt;summary calculations&lt;/li&gt;
&lt;li&gt;debugging failed runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a team workflow, that is usually the right tradeoff until you need shared multi-user access.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to extend next
&lt;/h2&gt;

&lt;p&gt;The most useful next features are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;row sampling controls, so you can test on 25 or 50 rows before spending tokens on the full dataset&lt;/li&gt;
&lt;li&gt;run export to CSV or JSON, so results can move into spreadsheets or BI tools&lt;/li&gt;
&lt;li&gt;historical comparisons, so teams can see whether a model is improving or regressing over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are practical extensions because they improve decision quality without complicating the core harness.&lt;/p&gt;

&lt;p&gt;The useful way to think about model selection is not “which model is best?” It is:&lt;/p&gt;

&lt;p&gt;Which model performs best on this dataset, under this latency budget, with this concurrency budget, using this scoring policy?&lt;/p&gt;

&lt;p&gt;Featherless handles the model serving side. FastAPI handles orchestration. SQLite handles auditability. The async harness handles throughput. The judge model handles scoring. Together, they turn model evaluation from an ad hoc exercise into a repeatable engineering workflow.&lt;/p&gt;

&lt;p&gt;Checkout Featherless.ai: &lt;a href="https://featherless.ai/" rel="noopener noreferrer"&gt;https://featherless.ai/?ref=harishkotra&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I made two LLMs draw the same cat. The constraint is the whole point.</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Thu, 17 Sep 2026 16:14:03 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-made-two-llms-draw-the-same-cat-the-constraint-is-the-whole-point-34ed</link>
      <guid>https://dev.to/harishkotra/i-made-two-llms-draw-the-same-cat-the-constraint-is-the-whole-point-34ed</guid>
      <description>&lt;p&gt;&lt;em&gt;Building Blindfolded Pictionary Duel: SVG path data, three.js, and four bugs that only showed up against a real provider.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;There is a specific kind of dishonesty that language models are very good at, and it is easiest to see in prose. Ask a model to describe a cat and you will get a fluent paragraph. Ask it to draw one and it has to commit to coordinates. There is no hedging in a &lt;code&gt;d&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;That is the entire idea behind this project. Two models get one byte-identical prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Draw &lt;strong&gt;a cat&lt;/strong&gt; as SVG path data. Use a &lt;code&gt;0 0 400 400&lt;/code&gt; viewBox. Output ONLY JSON matching this schema: &lt;code&gt;{"paths": [{"d": "&amp;lt;svg path data&amp;gt;", "stroke": "&amp;lt;hex colour&amp;gt;", "fill": "none|hex", "width": &amp;lt;number&amp;gt;}], "label": "&amp;lt;short label&amp;gt;"}&lt;/code&gt;. Use at least 3 and at most 40 paths. Coordinates must stay inside the viewBox. Do not include any text in the drawing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both drawings render live, side by side, on tilted three.js planes. The prompt escalates across rounds — &lt;em&gt;a cat&lt;/em&gt; → &lt;em&gt;a cat on a bicycle&lt;/em&gt; → &lt;em&gt;a cat riding a bicycle through a city at night&lt;/em&gt; — and each drawing reveals itself stroke by stroke, with a glowing pen head riding the tip of the current line.&lt;/p&gt;

&lt;p&gt;No image model. No diffusion. No retouching. A chat model is asked for vector coordinates, and whatever comes back gets parsed and drawn.&lt;/p&gt;

&lt;p&gt;Here is what I learned building it, including the four bugs that a mock would never have caught.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Three pnpm workspaces. &lt;code&gt;shared&lt;/code&gt; is the single source of truth for the wire contract, the prompt, and the round ladder, so the client and server cannot drift apart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslblht7kv8xokn0g1lid.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fslblht7kv8xokn0g1lid.png" alt="Architecture" width="800" height="1127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server is stateless and holds no credentials. The client sends its config — base URL, key, both model names — with every request. &lt;code&gt;Promise.all&lt;/code&gt; fires both calls concurrently, so a duel takes as long as the slower model rather than the sum.&lt;/p&gt;

&lt;p&gt;The reason there is no &lt;code&gt;.env&lt;/code&gt; file anywhere in this project is deliberate: configuration lives in the app's Settings panel, in &lt;code&gt;localStorage&lt;/code&gt;, and is sent per request. There is nothing to leak from the repo and nothing to configure before the first run except pasting a key.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 1: models typo the key, and I was deleting their drawings
&lt;/h2&gt;

&lt;p&gt;The first version of the validator looked for &lt;code&gt;path.d&lt;/code&gt;. If it was a string, keep it. Otherwise drop the path.&lt;/p&gt;

&lt;p&gt;Then I read the raw log and found this, from both models, independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"d: "&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M140 320 L152 296 L168 284 …"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value is perfect. The key is &lt;code&gt;"d: "&lt;/code&gt; — a colon and a space instead of a closing quote. My validator dropped it. Silently. A whole building disappeared from a cityscape and nothing in the UI said so.&lt;/p&gt;

&lt;p&gt;This is the class of bug that mocks hide, because when you write the mock you write the well-formed version. So the fix recovers aggressively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/src/validate.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PATH_KEY_CANDIDATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pathData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;points&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;recoverPathData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;normalised&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^&lt;/span&gt;&lt;span class="sr"&gt;a-z&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PATH_KEY_CANDIDATES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;normalised&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Last resort: any string value that looks like path data.&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;Mm&lt;/span&gt;&lt;span class="se"&gt;][\s\d&lt;/span&gt;&lt;span class="sr"&gt;.,-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replaying the fix over 82 already-logged outputs recovered real strokes in four of them. Repairs are surfaced in the UI as a &lt;strong&gt;repaired&lt;/strong&gt; badge with the reason attached, because a validator that silently fixes things is only marginally better than one that silently breaks them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 2: the reveal stalled at 38% and never moved
&lt;/h2&gt;

&lt;p&gt;The hook of this app is watching a drawing appear stroke by stroke. So this was fatal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;progress frozen at 38%, state.strokeIndex stuck at 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reveal works by advancing a distance each frame (&lt;code&gt;totalLength / duration&lt;/code&gt;) and painting only the newly travelled part. The partial-segment branch looked like this in spirit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paint from segment start → up to (start + remaining)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is correct for one frame. But if a segment is longer than one frame's travel, the next frame redraws &lt;strong&gt;from the segment start again&lt;/strong&gt;, never records how far it got, and the segment never completes. Progress freezes forever on the first long line.&lt;/p&gt;

&lt;p&gt;The fix is one field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;DrawState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;strokeIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;segIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;segT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/**
   * Distance already travelled INTO the current segment.
   * Without this, a segment longer than one frame's travel would restart from
   * its beginning every frame and the reveal would stall forever.
   */&lt;/span&gt;
  &lt;span class="nl"&gt;segOffset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;segOffset&lt;/code&gt; accumulates in the partial branch, resets on segment completion, and feeds both the progress counter and the pen head position. This bug is invisible in code review — it looks fine — and invisible in a unit test unless you happen to assert on a segment longer than one frame's travel. It took reading canvas pixels in a headless browser to find it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 3: reasoning models return &lt;em&gt;nothing at all&lt;/em&gt;, and &lt;code&gt;max_tokens&lt;/code&gt; is why
&lt;/h2&gt;

&lt;p&gt;This one cost an entire model its output and took the longest to understand.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;max_tokens&lt;/code&gt; pays for the model's &lt;strong&gt;thinking as well as its answer&lt;/strong&gt;. A thinking model that runs out mid-thought does not return a truncated drawing. It returns &lt;strong&gt;empty content&lt;/strong&gt;. Measured against a real provider with &lt;code&gt;deepseek-v4.1-flash&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;max_tokens&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;td&gt;all 1,600 spent on reasoning, &lt;strong&gt;0 paths&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4,000&lt;/td&gt;
&lt;td&gt;all 4,000 spent on reasoning, &lt;strong&gt;0 paths&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;all 8,192 spent on reasoning, &lt;strong&gt;0 paths&lt;/strong&gt; (city scene)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16,384&lt;/td&gt;
&lt;td&gt;✅ 8,350 reasoning tokens, then 23 paths&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The symptom the user sees is a seat that sits there showing nothing, then a forfeit. The default budget of 1,600 was &lt;em&gt;below the threshold at which the model can succeed at all&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The original retry policy doubled the budget: 1,600 → 3,200. Still nowhere near enough. So with reasoning on, the server now treats the configured budget as a &lt;strong&gt;floor&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared/src/index.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REASONING_MIN_BUDGET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16384&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// server/src/modelClient.ts&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;startingBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DuelConfig&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;disableReasoning&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTokens&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REASONING_MIN_BUDGET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;escalateBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;REASONING_MIN_BUDGET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below that floor a reasoning model cannot produce a drawing &lt;em&gt;by construction&lt;/em&gt;. Honouring a small number literally would only guarantee a wasted call, so the value is raised — and the value actually sent is recorded in every logged request body, so it is auditable rather than assumed. The verification suite asserts on it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reasoning_content&lt;/code&gt; is typed as &lt;code&gt;unknown&lt;/code&gt; in the response interface and never read, logged, stored, or rendered. Only the token count is reported, from &lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;. The code that would touch the field does not exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug 4: the loading state said "nothing is happening"
&lt;/h2&gt;

&lt;p&gt;With reasoning on, a single call can take two minutes. During that time the seat rendered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Blank sheet&lt;/strong&gt;&lt;br&gt;
Pick a rung or type a subject to start the duel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because &lt;code&gt;result&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt; while the request is in flight, and the component fell through to its idle overlay. The app was telling the user to start a duel they had already started.&lt;/p&gt;

&lt;p&gt;The fix is a real phase rather than a null check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;seatPhase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SeatPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;duel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drawing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;duel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shown&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a working state that admits what it is doing — spinner, ticking clock, indeterminate sweep, and a line that changes once the wait gets long:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__spinner"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading-title"&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;reasoningOn&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Thinking, then drawing…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Drawing…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading-elapsed"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;s&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading-track"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading-sweep"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"seat__loading-hint"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loadingHint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also made the reveal duration scale with density, because a 40-path cityscape flashing past in the same 3.4 seconds as a 12-path cat wastes the payoff after a two-minute wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rendering: from &lt;code&gt;d&lt;/code&gt; attribute to tilted plane
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;web/src/lib/svgPath.ts&lt;/code&gt; is a hand-written SVG path parser supporting &lt;code&gt;M m L l H h V v C c S s Q q T t A a Z z&lt;/code&gt;, flattening curves into polylines. It deliberately does &lt;strong&gt;not&lt;/strong&gt; use the DOM's &lt;code&gt;getPointAtLength&lt;/code&gt;, because the reveal animation needs per-point positions to drive the pen head, and the pen head needs to be at an arbitrary fractional position along the current segment.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;duelRenderer.ts&lt;/code&gt; paints those polylines into a 1024² canvas, maps it to a &lt;code&gt;CanvasTexture&lt;/code&gt; on a subtly tilted &lt;code&gt;PlaneGeometry&lt;/code&gt;, and adds a blurred offset copy as a soft shadow. Each seat tilts the opposite way; &lt;code&gt;OrbitControls&lt;/code&gt; is enabled with a straight-on default camera. The drawing is the star — the 3D is there to make it feel like a physical sheet, not to compete with it.&lt;/p&gt;

&lt;p&gt;The reveal paints &lt;strong&gt;append-only&lt;/strong&gt;: each frame draws only the newly travelled distance, so 40 paths cost about as much as 3.&lt;/p&gt;

&lt;p&gt;There is also a plain SVG fallback view that renders the same path data without WebGL. It is useful on machines without a GPU, and it is the honest way to show that the canvas contains nothing but the model's own coordinates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Verification: no mocks, by design
&lt;/h2&gt;

&lt;p&gt;The rule I set was: if it does not hit a real model, it is a unit test.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pnpm test&lt;/code&gt; — 17 unit tests over the pure functions: fence stripping, brace matching, command validation, coordinate clamping, malformed-key recovery, the 40-path cap, truncated JSON.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pnpm verify&lt;/code&gt; — drives the &lt;strong&gt;real app in a real browser&lt;/strong&gt; and reads the same &lt;code&gt;localStorage&lt;/code&gt; config the Settings panel writes. It has no API key of its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The harness configures itself through the app's own UI, and it exercises the failure paths the same way — by editing the model name and the token budget in the Settings panel, then pressing a rung. That is how it caught the budget problem: it asserted that a deliberately broken model forfeits &lt;em&gt;while the other seat survives&lt;/em&gt;, and that the provider's real error text appears verbatim.&lt;/p&gt;

&lt;p&gt;It measures the two drawings by reading pixels back out of the WebGL canvas and comparing 32×32 ink masks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webglCanvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getImageData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// paper is #f5f3ec; anything meaningfully darker is ink&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the two models ever produced the same drawing, that check fails. It never has — but the assertion is there precisely because "the app renders the same image twice" is the failure mode that would make the whole project a lie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two things the suite taught me about my own claims
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A single duel is a noisy sample.&lt;/strong&gt; My first suite asserted on one duel per rung and flaked roughly one run in three, because two models drawing "a cat" sometimes land on a similar layout. It now samples several duels and asserts on the aggregate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some of my README was wrong.&lt;/strong&gt; I had claimed that cross-model divergence grows with difficulty, because one run measured 18.4% → 42.8%. A later run measured 51.1% → 32.8%. The trend was noise. I removed the claim and documented both runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The finding I did not expect: reasoning kills the escalation
&lt;/h2&gt;

&lt;p&gt;The product promise is that the filmstrip shows drawings getting more complex as the prompt escalates. That holds — but only with reasoning &lt;strong&gt;off&lt;/strong&gt;. Across every duel in the log:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;mode&lt;/th&gt;
&lt;th&gt;Round 1 paths (A / B)&lt;/th&gt;
&lt;th&gt;Round 3 paths (A / B)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;reasoning off&lt;/td&gt;
&lt;td&gt;19.0 / 19.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38.5 / 39.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reasoning on&lt;/td&gt;
&lt;td&gt;20.6 / 24.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;23.0 / 24.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With reasoning off, path counts roughly double: models pad toward the 40-path cap. With reasoning on, they barely move, and individual pairs invert.&lt;/p&gt;

&lt;p&gt;Thinking models plan the scene and then draw it &lt;em&gt;economically&lt;/em&gt; — fewer, more deliberate paths. The escalation is real but it is a property of greedy, non-thinking generation, not of the model's understanding.&lt;/p&gt;

&lt;p&gt;I changed the suite to assert escalation only in reasoning-off runs and to print a note otherwise. A test that passes by being weakened is worse than no test; a claim that is quietly false is worse than both.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would tell you before you build something like this
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Constrain the output format, not the content.&lt;/strong&gt; The prompt is almost entirely about &lt;em&gt;shape&lt;/em&gt; — the viewBox, the schema, the path count, the coordinates. The model decides what a cat looks like. That division is why the output is legible without explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log the raw response, always.&lt;/strong&gt; Every duel appends the raw assistant content, the exact prompt, and the full request body to a JSONL file. Both of the parsing bugs I found were found by reading that file, not by reasoning about the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distrust your own verification.&lt;/strong&gt; Two of the four bugs above were in my test suite, not the app: a false "identical drawings" failure caused by empty double-forfeits comparing equal, and a harness that corrupted the user's settings when it crashed mid-run. Both are fixed — the second now restores settings in a &lt;code&gt;finally&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; on SIGINT.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Provider behaviour is not in the docs.&lt;/strong&gt; "A reasoning model returns empty content when it exhausts its budget" is not a documented contract anywhere I could find. It is the kind of thing you only learn by measuring, and it silently cost a whole model its output.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/blindfolded-pictionary-duel.git
&lt;span class="nb"&gt;cd &lt;/span&gt;blindfolded-pictionary-duel
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:5173" rel="noopener noreferrer"&gt;http://localhost:5173&lt;/a&gt;, paste a base URL and an API key into Settings, and press a rung.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd99gafxj21bh7qbjs1sb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd99gafxj21bh7qbjs1sb.png" alt="How this looks" width="800" height="842"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/256-blindfolded-pictionary-duel" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/256-blindfolded-pictionary-duel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I measured how deterministic LLMs actually are, prompt by prompt. The results broke my hypothesis.</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Wed, 16 Sep 2026 05:13:43 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-measured-how-deterministic-llms-actually-are-prompt-by-prompt-the-results-broke-my-hypothesis-1p7j</link>
      <guid>https://dev.to/harishkotra/i-measured-how-deterministic-llms-actually-are-prompt-by-prompt-the-results-broke-my-hypothesis-1p7j</guid>
      <description>&lt;p&gt;&lt;em&gt;A 3D heatmap of 320 model calls, and the four bugs that almost made it lie.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Temperature 0 is supposed to mean deterministic. Same prompt in, same bytes out, every time.&lt;/p&gt;

&lt;p&gt;It's the setting people reach for when they need reproducible output, and it's the setting most of us assume makes an LLM behave like a function.&lt;/p&gt;

&lt;p&gt;I wanted to know: &lt;strong&gt;is that actually true, and if not, which prompts break it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built a small experiment. Eight prompts, two models, ten repetitions each — 160 calls per run, 320 total. Then I rendered the whole thing as a 3D heatmap where colour is determinism and height is how many reasoning tokens the model burned getting there.&lt;/p&gt;

&lt;p&gt;Here's what came out, and what it cost me to trust it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The design
&lt;/h2&gt;

&lt;p&gt;Each bar is one &lt;strong&gt;prompt × one model&lt;/strong&gt;. Colour is the determinism score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;determinismScore = count(most common sha256) / reps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a deliberately brutal metric. I hash the raw response text and count how many of the ten repetitions produced &lt;em&gt;byte-identical&lt;/em&gt; output. Two responses differing by a single trailing space are different responses. If a model produces the same answer with different formatting, it scores low — and that's the point, because "the same answer" and "the same bytes" are very different guarantees for anything downstream that caches, diffs, or replays output.&lt;/p&gt;

&lt;p&gt;Height is the mean reasoning tokens. That pairing is the whole visual argument: a bar that is &lt;strong&gt;tall and red&lt;/strong&gt; is a prompt that is expensive &lt;em&gt;and&lt;/em&gt; unstable — the worst quadrant, and the one you want to find before it finds you in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  The prompt spectrum
&lt;/h3&gt;

&lt;p&gt;Eight prompts chosen to span the range from "there is exactly one right answer" to "there is no right answer":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Intent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;List the first 12 primes, comma-separated, nothing else&lt;/td&gt;
&lt;td&gt;Exact recall&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;What is 847 × 293? Show steps, final number on its own line&lt;/td&gt;
&lt;td&gt;Maths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Output a 5×5 multiplication table as a Markdown table&lt;/td&gt;
&lt;td&gt;Spatial / formatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Explain what a hash function is in one sentence&lt;/td&gt;
&lt;td&gt;Constrained prose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Draw an ASCII cat, 5 lines&lt;/td&gt;
&lt;td&gt;Structured art&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Write a haiku about the ocean&lt;/td&gt;
&lt;td&gt;Creative, fixed form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Tell me a joke about programmers&lt;/td&gt;
&lt;td&gt;Creative, conventional form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Describe a city that exists between sleeping and waking&lt;/td&gt;
&lt;td&gt;Open creative&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My hypothesis, stated before running anything: prompts 1–4 would be green (deterministic), 6 and 8 would be red, and the newer model's column would be greener than the older one's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two of those three predictions were wrong.&lt;/strong&gt; More on that below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The thing that makes or breaks the experiment: nonces
&lt;/h2&gt;

&lt;p&gt;Before any of the visuals, there's a subtlety that invalidates the entire experiment if you get it wrong.&lt;/p&gt;

&lt;p&gt;If you send the &lt;em&gt;identical&lt;/em&gt; request ten times, a provider-side response cache will happily return the identical bytes ten times. Your heatmap then measures &lt;strong&gt;the cache&lt;/strong&gt;, not the model. You'd get a beautiful, entirely green grid and conclude that LLMs are perfectly deterministic - a completely false result produced by a completely reasonable methodology.&lt;/p&gt;

&lt;p&gt;The fix is to make every request genuinely unique while keeping the &lt;em&gt;semantic&lt;/em&gt; content identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/src/sweep.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n&amp;lt;!--nonce:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;--&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each call appends an HTML comment containing 8 bytes of fresh randomness. The model sees a different string every time; the task is unchanged. If a cache is in play, it can't match.&lt;/p&gt;

&lt;p&gt;And because "unique nonce" is the load-bearing assumption, it's &lt;strong&gt;asserted&lt;/strong&gt;, not assumed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/src/nonce.ts&lt;/span&gt;
&lt;span class="nf"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;randomHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issuedCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duplicates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nonce generation failed: 1000 consecutive collisions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registry is seeded from the persisted log on startup, so uniqueness holds across restarts -  not just within one process. And the audit re-checks it from the raw file afterwards. If any nonce is reused, &lt;strong&gt;the numbers are discarded&lt;/strong&gt;, not caveated. I verified the check actually&lt;br&gt;
fires by injecting a duplicate into a copy of the log: the audit prints &lt;code&gt;FAIL — determinism numbers are invalid and must be discarded&lt;/code&gt; and exits 1. A check that has never failed is not a check.&lt;/p&gt;


&lt;h2&gt;
  
  
  What the data actually showed
&lt;/h2&gt;

&lt;p&gt;Two independent runs, 160 calls each. Model A = &lt;code&gt;deepseek-v4-flash-0731&lt;/code&gt;, Model B =&lt;br&gt;
&lt;code&gt;deepseek-v4.1-flash&lt;/code&gt;, temperature 0.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Predicted&lt;/th&gt;
&lt;th&gt;A det&lt;/th&gt;
&lt;th&gt;B det&lt;/th&gt;
&lt;th&gt;A reasoning&lt;/th&gt;
&lt;th&gt;B reasoning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Exact recall&lt;/td&gt;
&lt;td&gt;green&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maths&lt;/td&gt;
&lt;td&gt;green&lt;/td&gt;
&lt;td&gt;0.60&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;168&lt;/td&gt;
&lt;td&gt;195&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spatial / formatting&lt;/td&gt;
&lt;td&gt;green&lt;/td&gt;
&lt;td&gt;0.70&lt;/td&gt;
&lt;td&gt;0.90&lt;/td&gt;
&lt;td&gt;343&lt;/td&gt;
&lt;td&gt;328&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-sentence explanation&lt;/td&gt;
&lt;td&gt;green&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASCII drawing&lt;/td&gt;
&lt;td&gt;amber&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;0.40&lt;/td&gt;
&lt;td&gt;216&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Haiku&lt;/td&gt;
&lt;td&gt;red&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;373&lt;/td&gt;
&lt;td&gt;321&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Joke&lt;/td&gt;
&lt;td&gt;red&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.90&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open creative&lt;/td&gt;
&lt;td&gt;red&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1390&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1598&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Prediction 1: the creative prompts are red and tall — TRUE
&lt;/h3&gt;

&lt;p&gt;The haiku scored 0.10 on both models. The open-creative prompt scored 0.10 on both. And the open-creative cell is the tallest bar in the grid: ~1,400–1,600 mean reasoning tokens, against ~50 for the joke.&lt;/p&gt;

&lt;p&gt;That's the expensive-and-unstable quadrant, confirmed. The model thinks hardest exactly where it has the least reason to converge.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prediction 2: exact recall and maths are green — FALSE
&lt;/h3&gt;

&lt;p&gt;This is the interesting one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"List the first 12 prime numbers" scored 0.60.&lt;/strong&gt; The model never got the primes wrong. Not once. What varied was the comma spacing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2,3,5,7,11,13,17,19,23,29,31,37
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coin flip between two typographic conventions, on a question with exactly one correct answer. The &lt;em&gt;content&lt;/em&gt; was perfectly deterministic. The &lt;em&gt;bytes&lt;/em&gt; were not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maths scored 0.60–0.70&lt;/strong&gt; for the same reason. The answer was always correct — 248,171, every single time. But the working was formatted three different ways: &lt;code&gt;(847 × 200)&lt;/code&gt;, &lt;code&gt;847 × 200&lt;/code&gt;, and &lt;code&gt;847×200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my mental model was wrong. The green cells aren't "closed-form questions". They're &lt;strong&gt;questions with exactly one natural rendering&lt;/strong&gt;. The Markdown table (0.70–0.90) and the joke (0.90–1.00) both have a single strongly conventional form, and the model reproduces it. Primes and arithmetic have a correct &lt;em&gt;answer&lt;/em&gt; but no canonical &lt;em&gt;format&lt;/em&gt;, and the model free-varies across the space of acceptable renderings.&lt;/p&gt;

&lt;p&gt;That's a more useful finding than the one I set out to confirm, and it has a direct practical consequence: &lt;strong&gt;if you're caching or diffing LLM output, correctness is not the property you should be relying on.&lt;/strong&gt; A model that is right every time can still produce different bytes every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prediction 3: the newer model is more deterministic — FALSE
&lt;/h3&gt;

&lt;p&gt;Across both runs: &lt;strong&gt;Model A mean 0.494, Model B mean 0.476.&lt;/strong&gt; A 0.018 gap. Model B was ahead in only 5 of 16 cells, with 7 ties.&lt;/p&gt;

&lt;p&gt;There is no clear older-to-newer determinism progression in this data. I'd have liked a clean&lt;br&gt;
"newer model = more reliable" story, but the honest read is that &lt;strong&gt;the interesting axis here is&lt;br&gt;
the prompt, not the model.&lt;/strong&gt; The spread between the best and worst prompt (1.00 vs 0.10) is&lt;br&gt;
fifty times larger than the spread between the two models.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reproducibility
&lt;/h3&gt;

&lt;p&gt;I ran the whole thing twice. Pearson &lt;strong&gt;r = 0.93&lt;/strong&gt; on per-prompt determinism. Haiku and&lt;br&gt;
open-creative pinned at 0.10 both times. Max per-cell drift was 0.30, concentrated in the&lt;br&gt;
mid-range prompts. The &lt;em&gt;shape&lt;/em&gt; of the landscape is stable even where individual cells move —&lt;br&gt;
which is what you want from a measurement you're going to make claims from.&lt;/p&gt;


&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────┐
│  BROWSER  Vite + React + TypeScript        :5173             │
│                                                              │
│   App.tsx ──► Settings / Prompts / Overlay / Tooltip / Table │
│      ├──► heatmap.ts   InstancedMesh + OrbitControls + PNG   │
│      └──► color.ts     Oklab perceptual ramp                 │
└───────────────────────┬──────────────────────────────────────┘
                        │ fetch /api/*      SSE /stream, /replay
                        ▼
┌──────────────────────────────────────────────────────────────┐
│  SERVER  Hono + TypeScript                 :3001             │
│                                                              │
│   index.ts    routes, SSE, key resolution                    │
│   manager.ts  sequential engine, events, cancel, replay      │
│   provider.ts fetch → /chat/completions, retry, accounting   │
│   nonce.ts    nonce registry (asserted unique)               │
│   sweep.ts    aggregation + nonce audit                      │
│   store.ts    append-only JSONL, crash-tolerant reader       │
│   audit.ts    independent re-derivation from raw JSONL       │
└───────────────────────┬──────────────────────────────────────┘
                        │ one call at a time, never parallel
                        ▼
        POST {baseUrl}/chat/completions
        user: &amp;lt;prompt&amp;gt;

              &amp;lt;!--nonce:9f3ac41b7e0d2c85--&amp;gt;
                        │
                        ▼
        server/data/sweeps.jsonl  ──►  exports/ (PNG + JSONL + manifest)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Calls are strictly sequential
&lt;/h3&gt;

&lt;p&gt;Every call runs one at a time. Parallelising would be faster and would ruin the data: latency&lt;br&gt;
and token accounting get distorted by contention, and the whole point is that the numbers are&lt;br&gt;
trustworthy. A 160-call sweep takes 6–10 minutes, and that's the correct trade.&lt;/p&gt;
&lt;h3&gt;
  
  
  Storage is append-only JSONL
&lt;/h3&gt;

&lt;p&gt;Every record is appended the moment it completes. Nothing is ever rewritten. A crash loses at&lt;br&gt;
most the in-flight call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/src/store.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;appendLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JsonlLine&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ensureDataDir&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;appendFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSONL_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reader skips a torn final line rather than corrupting the file, so a hard kill mid-write is&lt;br&gt;
survivable. The JSONL is the source of truth — the UI is a view over it, and a page reload&lt;br&gt;
re-attaches to a sweep in progress.&lt;/p&gt;
&lt;h3&gt;
  
  
  Progress streams over SSE
&lt;/h3&gt;

&lt;p&gt;A long sweep must never look frozen. The server emits &lt;code&gt;snapshot&lt;/code&gt; on connect (so a late&lt;br&gt;
subscriber is never behind), then &lt;code&gt;progress&lt;/code&gt; and &lt;code&gt;cell&lt;/code&gt; events as each call lands, plus a 5s&lt;br&gt;
heartbeat.&lt;/p&gt;
&lt;h3&gt;
  
  
  Reasoning text is never stored
&lt;/h3&gt;

&lt;p&gt;The provider returns &lt;code&gt;reasoning_content&lt;/code&gt; alongside the answer. I record &lt;strong&gt;only its token&lt;br&gt;
count&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractReasoningTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isFinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CallRecord&lt;/code&gt; type has no field capable of holding the reasoning text. Not "we don't log it" —&lt;br&gt;
&lt;em&gt;there is nowhere to put it&lt;/em&gt;. That's a structural guarantee rather than a discipline, and the&lt;br&gt;
audit scans the raw file to confirm it holds.&lt;/p&gt;


&lt;h2&gt;
  
  
  Four bugs that would have made this lie
&lt;/h2&gt;

&lt;p&gt;The interesting part of this project wasn't writing it. It was the four defects that only&lt;br&gt;
surfaced by actually running the thing and checking the output, rather than assuming it worked.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. The route that silently swallowed page reloads
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/api/sweep/latest&lt;/code&gt; was registered &lt;em&gt;after&lt;/em&gt; &lt;code&gt;/api/sweep/:id&lt;/code&gt;. Hono matches in registration order,&lt;br&gt;
so &lt;code&gt;latest&lt;/code&gt; was captured as an id. Every page reload silently failed to re-attach to the running&lt;br&gt;
sweep. No error, no warning — it just quietly did nothing. Fix: register the literal route first.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Tone mapping made the legend a liar
&lt;/h3&gt;

&lt;p&gt;I had ACES filmic tone mapping enabled, which is a sensible default for pretty 3D. But this is a&lt;br&gt;
&lt;em&gt;data visualisation&lt;/em&gt;: a cell's rendered colour has to equal the colour shown in the legend. Tone&lt;br&gt;
mapping was desaturating everything, so the green cells weren't the green in the ramp.&lt;/p&gt;

&lt;p&gt;I only caught it because I wrote a pixel-level assertion — decode the exported PNG, count green&lt;br&gt;
pixels, fail if there are none. After switching to &lt;code&gt;NoToneMapping&lt;/code&gt;, green pixels went from&lt;br&gt;
&lt;strong&gt;237 to 6,551&lt;/strong&gt;. The bug was invisible to the eye and obvious to the assertion.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The CLI reported a number that was always 1.00
&lt;/h3&gt;

&lt;p&gt;The headless runner printed each cell's determinism as soon as the first repetition landed. With&lt;br&gt;
one sample, the most common hash is trivially the only hash — every cell reads &lt;code&gt;1.00&lt;/code&gt;. It looked&lt;br&gt;
like perfect determinism everywhere. Fix: wait for all reps before reporting a cell.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. A dropped event caused an infinite loop
&lt;/h3&gt;

&lt;p&gt;The terminal &lt;code&gt;done&lt;/code&gt; event was written like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeSSE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The write isn't awaited. The stream closes the instant the handler returns, so &lt;code&gt;done&lt;/code&gt; never&lt;br&gt;
flushed. The browser's &lt;code&gt;EventSource&lt;/code&gt; saw the connection drop and — correctly, per spec —&lt;br&gt;
auto-reconnected, which &lt;strong&gt;restarted the replay from the beginning&lt;/strong&gt;. Forever.&lt;/p&gt;

&lt;p&gt;It presented as a hung UI. The fix is to await the write before resolving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;void &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeSSE&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* client already gone */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nf"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four bugs, none of which threw an error. Every one of them produced plausible-looking output&lt;br&gt;
that was wrong.&lt;/p&gt;


&lt;h2&gt;
  
  
  Verification, so you don't have to trust me
&lt;/h2&gt;

&lt;p&gt;The repo ships the raw evidence and an independent audit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm audit        &lt;span class="c"&gt;# re-derives every claim from the raw JSONL&lt;/span&gt;
pnpm verify:ui    &lt;span class="c"&gt;# drives the real UI in a browser, hovers a cell, exports the PNG&lt;/span&gt;
pnpm verify:png   &lt;span class="c"&gt;# decodes the exported bitmap and asserts it's readable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pnpm audit&lt;/code&gt; re-reads the JSONL and checks four things independently:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Claim&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every call carries a unique nonce&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PASS&lt;/strong&gt; — 320 calls, 320 distinct nonces, 0 reused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Determinism derives from real sha256 of responses&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PASS&lt;/strong&gt; — 320/320 hashes match recomputed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reasoning tokens from &lt;code&gt;usage.completion_tokens_details&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PASS&lt;/strong&gt; — 320/320 well-formed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;reasoning_content&lt;/code&gt; never stored&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;PASS&lt;/strong&gt; — absent from every record and the raw file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exported PNG and the JSONL live in &lt;code&gt;exports/&lt;/code&gt; alongside a manifest, so the claim is&lt;br&gt;
auditable rather than asserted.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd tell you to take away
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Determinism is a property of the prompt, not the model.&lt;/strong&gt; The spread across prompts (1.00 to&lt;br&gt;
0.10) dwarfs the spread across models (0.494 vs 0.476). If you care about reproducibility,&lt;br&gt;
choose your prompts accordingly — and measure, because intuition is a poor guide here. I&lt;br&gt;
predicted two of my eight prompts wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correctness and reproducibility are different guarantees.&lt;/strong&gt; A model can be right every single&lt;br&gt;
time and still emit different bytes every single time. If you're caching, diffing, or replaying&lt;br&gt;
output, you need the second property, and you don't get it for free at temperature 0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creative prompts are where the compute goes.&lt;/strong&gt; The open-creative prompt burned ~30× the&lt;br&gt;
reasoning tokens of the joke, at the same determinism score. Both are red; only one is&lt;br&gt;
expensive. The heatmap makes that visible in a way a table doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the meta-lesson:&lt;/strong&gt; every one of the four bugs above produced output that looked fine. The&lt;br&gt;
nonce assertion, the pixel assertion, and the independent audit weren't ceremony — they were the&lt;br&gt;
only reason I caught them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/determinism-heatmap.git
&lt;span class="nb"&gt;cd &lt;/span&gt;determinism-heatmap
pnpm &lt;span class="nb"&gt;install
&lt;/span&gt;pnpm start        &lt;span class="c"&gt;# → http://localhost:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at any OpenAI-compatible endpoint via Settings — base URL, key, and both model names&lt;br&gt;
are editable. The heatmap on load is replayed from disk at zero token cost; press &lt;strong&gt;▶ Run sweep&lt;/strong&gt; to take fresh measurements, or &lt;strong&gt;↻ Replay&lt;/strong&gt; to watch a finished run fill in cell by&lt;br&gt;
cell without spending anything.&lt;/p&gt;

&lt;p&gt;Change the prompts. That's where the interesting variance is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15zgxlnj10mks9zpf8c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15zgxlnj10mks9zpf8c8.png" alt="Determinism Heatmap 1" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmctlaue4uxtbgnd0gpr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmctlaue4uxtbgnd0gpr.png" alt="Determinism Heatmap 2" width="800" height="944"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwydjq51tl0zzbck1sx99.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwydjq51tl0zzbck1sx99.png" alt="Determinism Heatmap 3" width="799" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/255-determinism-heatmap" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/255-determinism-heatmap&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I built a particle simulator to find out if two AI models are secretly the same model</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Tue, 15 Sep 2026 13:29:50 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-built-a-particle-simulator-to-find-out-if-two-ai-models-are-secretly-the-same-model-1dp4</link>
      <guid>https://dev.to/harishkotra/i-built-a-particle-simulator-to-find-out-if-two-ai-models-are-secretly-the-same-model-1dp4</guid>
      <description>&lt;p&gt;&lt;em&gt;How a frozen particle cloud became the most convincing argument I could make about model sameness and the three bugs that nearly made it lie.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The question nobody answers with a picture
&lt;/h2&gt;

&lt;p&gt;Every few weeks a new model ships with a new name, a new version number, and a benchmark table showing it is 3% better at something. The table is usually true and almost never useful. What I actually want to know is simpler and harder:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much of this model is new?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "is it better" — benchmarks cover that. I want to know whether the thing I'm paying more for is a genuinely different model or the same weights wearing a new hat. That is a question about &lt;em&gt;sameness&lt;/em&gt;, and the standard tools for answering it are all textual: benchmark deltas, eval scores, vibes.&lt;/p&gt;

&lt;p&gt;So I built something that answers it visually. The idea is almost embarrassingly simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Send the same prompt to two models. Turn each answer into a cloud of particles — one particle per sampled point. Then try to morph cloud A into cloud B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the two answers are byte-identical, the particles do not move at all.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That frozen frame is the whole product. A cloud that locks in place, desaturates, and gets a big &lt;code&gt;IDENTICAL&lt;/code&gt; stamp with a counter reading &lt;code&gt;0 particles moved&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's called &lt;a href="https://github.com/harishkotra/token-morph" rel="noopener noreferrer"&gt;The Identical Twins Test&lt;/a&gt;, and this post is about how it works, why the visual framing matters more than I expected, and the three bugs that nearly made it lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why particles, and why "nothing moving" is the point
&lt;/h2&gt;

&lt;p&gt;Most data visualisations are built to show activity. A dashboard that animates is a dashboard that looks like it's working. That instinct is exactly backwards for this problem.&lt;/p&gt;

&lt;p&gt;The finding here is an &lt;strong&gt;absence&lt;/strong&gt;. Two models producing the same bytes is a non-event — it's the thing you'd skip past in a diff. If I rendered it as, say, a green checkmark, nobody would feel anything. A checkmark is a claim; a frozen cloud is evidence.&lt;/p&gt;

&lt;p&gt;So the entire design goal was to make the absence of motion the loudest thing on screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cloud &lt;strong&gt;locks&lt;/strong&gt; — not slows down, locks.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;desaturates&lt;/strong&gt;, going grey, like something powered off.&lt;/li&gt;
&lt;li&gt;The camera &lt;strong&gt;stops orbiting&lt;/strong&gt; (more on this below — it was a bug at first).&lt;/li&gt;
&lt;li&gt;A large &lt;code&gt;IDENTICAL&lt;/code&gt; badge stamps over the frame.&lt;/li&gt;
&lt;li&gt;A counter reads &lt;code&gt;0 particles moved&lt;/code&gt;, and that zero is &lt;em&gt;measured&lt;/em&gt;, not hardcoded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the signature UI element is a per-character sha256 diff strip: one tick per character position, lit where the two answers differ. On an identical run, the strip is &lt;strong&gt;completely dark&lt;/strong&gt;. A second, independent confirmation that doesn't require reading a number.&lt;/p&gt;

&lt;p&gt;The contrast case matters too. When the answers genuinely differ, the cloud explodes outward and reforms into the new shape. Because you've just watched a cloud refuse to move, watching one tear itself apart reads as &lt;em&gt;violent&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Two processes, and the split is deliberate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────┐
│  Browser — localhost:5173                                                │
│                                                                          │
│  ┌────────────────────────┐   ┌───────────────────────────────────────┐  │
│  │  React shell           │   │  WebGL stage                          │  │
│  │  App.tsx               │   │  particles.ts                         │  │
│  │   • phase state machine│   │   • 220,000-particle pool             │  │
│  │   • run history        │   │   • ShaderMaterial + morph            │  │
│  │   • JSON export        │   │   • lock() / morphTo()                │  │
│  └───────────┬────────────┘   └───────────────▲───────────────────────┘  │
│              │                                │ SampleResult             │
│              │                    ┌───────────┴───────────┐              │
│              │                    │  sampler.ts           │              │
│              │                    │  text → canvas → pts  │              │
│              │                    └───────────────────────┘              │
│              │  fetch('/api/compare')      ← Vite dev proxy              │
└──────────────┼───────────────────────────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────────────────────────┐
│  Node server — localhost:3001                                            │
│                                                                          │
│  index.ts            POST /api/compare   POST /api/test-connection       │
│  model-client.ts     callModel(), testModel()                            │
│  hash.ts             sha256(), newRunId(), newNonce()                    │
└──────────────┬───────────────────────────────────────────────────────────┘
               │  fetch POST {baseUrl}/chat/completions
               ▼
     ┌──────────────────────────────────────────────┐
     │  Any OpenAI-compatible provider              │
     │  default: https://api.particle.ai/v1         │
     └──────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why is there a server at all?&lt;/strong&gt; Three reasons, and I want to be explicit because "why isn't this a static site" is the first question anyone asks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The verdict is computed server-side.&lt;/strong&gt; &lt;code&gt;hashEqual&lt;/code&gt; is decided from the response bytes on the server, so the client cannot accidentally — or conveniently — misreport it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The API key is never bundled.&lt;/strong&gt; It lives in &lt;code&gt;localStorage&lt;/code&gt;, goes to &lt;code&gt;localhost:3001&lt;/code&gt;, and gets forwarded from there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CORS.&lt;/strong&gt; Most providers don't send permissive CORS headers, so a browser-only version dies on the first request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stack: &lt;strong&gt;Vite 8 + React 19 + TypeScript 5.9&lt;/strong&gt; on the front, &lt;strong&gt;Express 5 on Node&lt;/strong&gt; on the back, &lt;strong&gt;three.js&lt;/strong&gt; for the particles. No component library, no state manager, no CSS framework, &lt;strong&gt;no OpenAI SDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That last omission is deliberate. The provider call is a plain &lt;code&gt;fetch&lt;/code&gt; to &lt;code&gt;/chat/completions&lt;/code&gt;, which is what makes the app work against &lt;em&gt;any&lt;/em&gt; OpenAI-compatible endpoint — particle.ai, OpenAI, Groq, Together, a local llama.cpp server — by changing one text field. An SDK would have bought me convenience and cost me the entire "works with anything" property.&lt;/p&gt;




&lt;h2&gt;
  
  
  The verdict is a byte comparison
&lt;/h2&gt;

&lt;p&gt;Here's the core of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/index.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nf"&gt;callModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelA&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nf"&gt;callModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;B&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelB&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// The verdict is a byte comparison, not a similarity score.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashEqual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha256&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both models get the same system prompt, the same user message, and temperature 0. Temperature 0 matters: the app is asking a question about determinism, so a divergence should be a real difference between the models rather than sampling noise.&lt;/p&gt;

&lt;p&gt;Note what this is &lt;em&gt;not&lt;/em&gt;: there's no embedding distance, no fuzzy matching, no similarity threshold. Identical bytes or nothing. That's a much stronger claim, and it's the one worth making visually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #1: the nonce, or how a cache could fake the entire result
&lt;/h2&gt;

&lt;p&gt;This is the bug that would have destroyed the project's credibility, and it's the one I'm most glad I caught before shipping.&lt;/p&gt;

&lt;p&gt;If the provider caches responses, sending the same prompt twice can return the same bytes twice — &lt;strong&gt;even when the two models would have answered differently&lt;/strong&gt;. That manufactures an &lt;code&gt;IDENTICAL&lt;/code&gt; verdict out of nothing.&lt;/p&gt;

&lt;p&gt;Sit with how bad that is. The app's entire purpose is to detect sameness. A cache makes it detect sameness &lt;em&gt;falsely&lt;/em&gt;. Every "finding" it produced would be an artifact of the infrastructure, and the more convincing the visual, the more effectively it would be lying.&lt;/p&gt;

&lt;p&gt;The fix is a fresh random nonce on every call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// server/hash.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;newNonce&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// server/index.ts — the nonce is part of the user message&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\n[nonce &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;]`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two calls can never share a cache key. The nonce is shown in the UI and included in the JSON export, so any run can be audited. I verified this by running the same prompt five times: five distinct nonces, five identical verdicts, one distinct digest. The stability is real, and it isn't a cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #2: the camera was moving
&lt;/h2&gt;

&lt;p&gt;The first version froze the particles. It looked right. I was happy with it.&lt;/p&gt;

&lt;p&gt;Then I measured it. The locked cloud drifted &lt;strong&gt;0.77 pixels over 2.5 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The particles weren't moving — but the &lt;em&gt;camera&lt;/em&gt; was still orbiting. So the frame was changing, which means a viewer watching the "frozen" cloud sees motion, and a viewer who sees motion concludes something happened. Which is precisely the opposite of the claim.&lt;/p&gt;

&lt;p&gt;"Nothing moves" has to be literally true, so &lt;code&gt;lock()&lt;/code&gt; now stops the camera too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;morphing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;identical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uFreeze&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uSaturation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uBurst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetPositions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basePositions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// Stop the camera too. "The particles do not move" has to be literally true,&lt;/span&gt;
  &lt;span class="c1"&gt;// so nothing in the frame is allowed to drift — including the viewpoint.&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orbitSpeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;movedCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;peakDisplacement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drift after the fix: &lt;strong&gt;0.064 px over 2.5 seconds&lt;/strong&gt;. Under a tenth of a pixel. No measurable motion.&lt;/p&gt;

&lt;p&gt;There's a subtlety in the shader worth calling out. I wanted a gentle breathing motion so a frozen cloud doesn't look like a dead render — but the breathing must not change any particle's &lt;em&gt;position&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A little breathing motion so a frozen cloud still reads as alive, but the&lt;/span&gt;
&lt;span class="c1"&gt;// particles' *positions* never change when frozen.&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;breathe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uTime&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;aSeed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2831&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;06&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;uFreeze&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;displaced&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;breathe&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;(1.0 - uFreeze)&lt;/code&gt; term zeroes the breathing the instant the cloud locks. The cloud still shimmers very slightly via point size, but no particle moves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug #3: measuring the wrong thing
&lt;/h2&gt;

&lt;p&gt;Here's a trap worth knowing if you ever test WebGL.&lt;/p&gt;

&lt;p&gt;The obvious way to verify the cloud rendered is to call &lt;code&gt;gl.readPixels&lt;/code&gt; on the canvas and count lit pixels. It returns &lt;strong&gt;all zeros&lt;/strong&gt;. The drawing buffer isn't preserved after compositing, so by the time your test code looks, there's nothing there.&lt;/p&gt;

&lt;p&gt;My first test suite confidently reported "0 lit sample points" on a canvas that was visibly full of particles. I spent a while debugging a renderer that was working perfectly.&lt;/p&gt;

&lt;p&gt;The fix: measure from actual screenshots. That's how I got the drift numbers above — capture frames, diff them, compute the cloud's centroid movement. It's slower and uglier than reading a buffer, and it's the only thing that measures what the user actually sees.&lt;/p&gt;




&lt;h2&gt;
  
  
  The particle system
&lt;/h2&gt;

&lt;p&gt;A fixed pool of &lt;strong&gt;220,000 particles&lt;/strong&gt; in a single &lt;code&gt;THREE.Points&lt;/code&gt; with a custom &lt;code&gt;ShaderMaterial&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why a fixed pool
&lt;/h3&gt;

&lt;p&gt;Reallocating geometry per run means a GPU buffer upload on every comparison and GC hitches during the morph — exactly when the animation has to be smooth. A fixed pool means the only per-run work is writing into two &lt;code&gt;Float32Array&lt;/code&gt;s.&lt;/p&gt;

&lt;p&gt;Unused particles aren't hidden with a draw-range trick. They're pushed 90 world units out of frame, which keeps the shader branch-free:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Particles that do not exist in the incoming layout are pushed out of frame&lt;/span&gt;
&lt;span class="c1"&gt;// rather than left sitting on top of the new text.&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;inactive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;aActive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="kt"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inactive&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The morph
&lt;/h3&gt;

&lt;p&gt;Every particle interpolates from its A position to its B position, with a radial burst peaking at the midpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aTarget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uProgress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Burst impulse: radial kick, peaked at the midpoint of the morph.&lt;/span&gt;
&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="kt"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;001&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;wobble&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;vec3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aSeed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aSeed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uTime&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;impulse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uBurst&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;uBurstRadius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;55&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;wobble&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;vec3&lt;/span&gt; &lt;span class="n"&gt;displaced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;impulse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CPU drives two envelopes — &lt;code&gt;easeInOutCubic&lt;/code&gt; for position, &lt;code&gt;sin²&lt;/code&gt; for the burst:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;morphStart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;morphDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// easeInOutCubic: slow out of A, fast through the middle, settle into B.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eased&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;eased&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Burst envelope peaks at the midpoint of the morph.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;burst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;material&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uniforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uBurst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;burst&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;wobble&lt;/code&gt; term is what stops the burst looking like a perfect sphere inflating. Each particle gets a deterministic per-seed multiplier between 0.45 and 1.0, so the cloud &lt;strong&gt;frays&lt;/strong&gt; instead of expanding uniformly. That one line is most of the difference between "impressive" and "cheap".&lt;/p&gt;

&lt;h3&gt;
  
  
  The counter is a measurement
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;0 particles moved&lt;/code&gt; is the headline number, so it can't be a hardcoded zero. It's computed by comparing every particle's destination against its origin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;POOL_SIZE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dz&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.0004&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;moved&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;movedCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;moved&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The epsilon absorbs float noise from normalisation. If the layouts are genuinely identical, every particle lands exactly where it started and the count is genuinely zero. If I ever break the morph, the counter will tell on me.&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning text into particles
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;sampler.ts&lt;/code&gt; does the boring-but-critical work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Draw the text into an offscreen 1400×760 canvas, wrapped and centred.&lt;/li&gt;
&lt;li&gt;Read pixels back, sample on a 4px grid; every point where red &amp;gt; 110 becomes one particle target.&lt;/li&gt;
&lt;li&gt;Normalise into centred world space of height 24 units, with ±0.8 of z-jitter for depth.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The bug that measurement caught and review never would
&lt;/h3&gt;

&lt;p&gt;The first version used a fixed font size. It looked fine in code. Then I measured particle counts per preset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;th&gt;Particles (before)&lt;/th&gt;
&lt;th&gt;Particles (after)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"Paris"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;48&lt;/strong&gt; — an empty frame&lt;/td&gt;
&lt;td&gt;4,044&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A tall column of 20 digits&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;145&lt;/strong&gt; — a faint streak&lt;/td&gt;
&lt;td&gt;2,206&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A five-character answer produced 48 particles. The cloud was effectively invisible, and the app looked broken for exactly the short, punchy prompts a user is most likely to try first.&lt;/p&gt;

&lt;p&gt;Fixed by fitting the text to the frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Largest font that fits, tried from biggest to smallest.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FONT_CANDIDATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;76&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// Then: if the first pass came back thin, resample on a finer grid.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_PARTICLES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finerStep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;MIN_PARTICLES&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every preset now lands between roughly 2,200 and 7,400 particles.&lt;/p&gt;

&lt;p&gt;One subtlety I nearly got wrong: the text block is centred &lt;strong&gt;as a whole&lt;/strong&gt;, not line by line. Per-line centring is the obvious implementation and it destroys the internal alignment ASCII art depends on — a cat drawn in ASCII comes out with its ears in the wrong place. Since ASCII art is one of the most visually satisfying prompt types here, that mattered.&lt;/p&gt;




&lt;h2&gt;
  
  
  Not leaking chain-of-thought
&lt;/h2&gt;

&lt;p&gt;Reasoning models can return a &lt;code&gt;reasoning_content&lt;/code&gt; field alongside &lt;code&gt;content&lt;/code&gt;. That's chain-of-thought, and it has no business in a log, an HTTP response, or the DOM.&lt;/p&gt;

&lt;p&gt;The defence is structural, not a filter. &lt;strong&gt;The field doesn't exist in any type in the codebase&lt;/strong&gt;, and the one function that reads a completion never looks at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ChatCompletionResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;model&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// reasoning_content is intentionally NOT read. See extractContent().&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nl"&gt;finish_reason&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the token &lt;em&gt;count&lt;/em&gt; crosses the wire, from &lt;code&gt;usage.completion_tokens_details.reasoning_tokens&lt;/code&gt;. There's nothing to leak because nothing ever reads it. I verified this with a canary string in a test provider: it never appeared in any response, log, or DOM node.&lt;/p&gt;

&lt;p&gt;A filter would have been the obvious approach and the wrong one — filters fail open when someone adds a new code path. A field that doesn't exist can't be accidentally serialised.&lt;/p&gt;




&lt;h2&gt;
  
  
  "Test connection" — the button that saves the first five minutes
&lt;/h2&gt;

&lt;p&gt;The app requires your own API key. That means the very first thing a new user does is type a key and hit Run, and if anything is wrong — typo'd key, missing &lt;code&gt;/v1&lt;/code&gt;, wrong model name — they get a failure and no idea which of three things caused it.&lt;/p&gt;

&lt;p&gt;So there's a &lt;strong&gt;Test connection&lt;/strong&gt; button that sends one tiny request (16 max tokens, costs almost nothing) to each model and classifies the failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The API key was rejected. Check that the key is complete and belongs to this base URL.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;model&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`The provider does not recognise the model "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;". Check the model name.`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The endpoint was not found. Check the base URL — it should end in /v1 for most providers.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bad key and a bad model name are different problems with different fixes. Conflating them into "request failed" wastes the user's time at the exact moment they're deciding whether your app works.&lt;/p&gt;

&lt;p&gt;Two details I'm happy with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The result clears the moment you edit any connection setting.&lt;/strong&gt; A stale "OK" can never describe a config you've since changed — a small thing that prevents a genuinely confusing failure mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Probes run concurrently&lt;/strong&gt;, so testing takes as long as the slower model, not both.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I'd tell someone building something similar
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Make the absence loud.&lt;/strong&gt; The instinct to visualise activity is strong and often wrong. If your finding is a non-event, the design problem is making that non-event feel like a verdict, and that's a harder and more interesting problem than adding more motion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure the thing you're claiming.&lt;/strong&gt; "The particles don't move" is a claim, and 0.77px of camera drift made it false. Every headline number in this app — the moved count, the drift, the particle counts — is measured rather than asserted. It caught three bugs that looked fine in review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structural safety beats filters.&lt;/strong&gt; Not having &lt;code&gt;reasoning_content&lt;/code&gt; in a type is stronger than scrubbing it before logging. Filters fail open; absent fields can't be serialised by accident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Beware the bug that fakes your own finding.&lt;/strong&gt; The nonce exists because a cache could have manufactured &lt;code&gt;IDENTICAL&lt;/code&gt; verdicts. When your app's purpose is detecting a specific condition, ask what could produce that condition falsely — that's where the credibility risk lives.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The app is open source, runs locally in two commands, and works against any OpenAI-compatible provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/token-morph
&lt;span class="nb"&gt;cd &lt;/span&gt;token-morph
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;localhost:5173&lt;/code&gt;, add your key in Settings, hit &lt;strong&gt;Test connection&lt;/strong&gt;, then pick the "Recursion, one sentence" preset and watch a cloud refuse to move.&lt;/p&gt;

&lt;p&gt;The most dramatic preset is &lt;strong&gt;"Count to 20, one per line"&lt;/strong&gt; — the two models lay the numbers out so differently that the cloud's silhouette barely survives the morph. The most subtle is &lt;strong&gt;"Ocean in 200 words"&lt;/strong&gt;, where the shape holds and only the density changes.&lt;/p&gt;

&lt;p&gt;If you find a model pair that's byte-identical on something it really shouldn't be, I'd love to see it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbj1mazbrgq3gvyc98p9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbj1mazbrgq3gvyc98p9u.png" alt="How the output looks" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/254-token-morph" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/254-token-morph&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I Built a Drag Race for LLMs, and the Loser Is Usually the Better Model</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Mon, 14 Sep 2026 14:35:17 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-built-a-drag-race-for-llms-and-the-loser-is-usually-the-better-model-17o1</link>
      <guid>https://dev.to/harishkotra/i-built-a-drag-race-for-llms-and-the-loser-is-usually-the-better-model-17o1</guid>
      <description>&lt;h3&gt;
  
  
  A technical deep-dive into The Reasoning Tax Race — streaming SSE, GPU particle exhaust, and making an invisible cost visible
&lt;/h3&gt;




&lt;p&gt;There's a chart that every team shipping LLM features has looked at. It has two bars: one for the old model, one for the new one. The new bar is taller. The caption says something like "improved reasoning."&lt;/p&gt;

&lt;p&gt;What the chart doesn't show you is the invoice.&lt;/p&gt;

&lt;p&gt;Newer reasoning models think before they answer. They emit hundreds of tokens you never see, you never read, and you absolutely do pay for. On a benchmark table that's an abstraction. In production it's your latency budget and your margin.&lt;/p&gt;

&lt;p&gt;I wanted to make that cost &lt;strong&gt;visible as a physical object&lt;/strong&gt; — something you could watch and feel rather than read in a table. So I built a drag race.&lt;/p&gt;




&lt;h2&gt;
  
  
  The premise
&lt;/h2&gt;

&lt;p&gt;Two models. One prompt. Identical bytes to both, fired at the same instant. Each gets a&lt;br&gt;
lane and a rocket.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rocket position&lt;/strong&gt; = tokens emitted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exhaust plume&lt;/strong&gt; = every token, as a particle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First across the finish line&lt;/strong&gt; = winner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the twist: the slower model is usually the &lt;em&gt;better&lt;/em&gt; one, and its exhaust trail is&lt;br&gt;
visibly, dramatically longer. That trail is the reasoning tax.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/screenshots%2F01-racing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/screenshots%2F01-racing.png" alt="Two rockets racing, with the loser trailing a much longer plume"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rule I set for myself was simple and non-negotiable: &lt;strong&gt;everything must be genuinely&lt;br&gt;
measured live.&lt;/strong&gt; No fake timers, no pre-baked token counts, no simulated latency. If a&lt;br&gt;
model stalls, its rocket stops and its plume stops growing. If I couldn't measure it, it&lt;br&gt;
didn't go on screen.&lt;/p&gt;

&lt;p&gt;That constraint turned out to be the entire engineering problem, and it produced the&lt;br&gt;
most interesting decisions in the project.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────────────────────────┐
│  BROWSER  ·  :5173                                                       │
│                                                                          │
│   React 18 + TypeScript                                                  │
│   ┌────────────────┐  ┌─────────────────┐  ┌──────────────────────────┐  │
│   │  Control deck  │  │   Lane HUDs     │  │   Reveal panel           │  │
│   │  prompt+presets│  │ tokens/reasoning│  │  answers · latency ·     │  │
│   │  Start / Reset │  │ stopwatch · TTFT│  │  reasoning tok · sha256  │  │
│   └────────────────┘  └─────────────────┘  └──────────────────────────┘  │
│           │                    ▲                        ▲                │
│           │ useRace()          │ live telemetry         │ outcome        │
│           ▼                    │                        │                │
│   ┌──────────────────────────────────────────┐          │                │
│   │  arrivalsRef  ──drainArrivals()──┐       │          │                │
│   └──────────────────────────────────┼───────┘          │                │
│                                      ▼                  │                │
│   ┌──────────────────────────────────────────────────┐  │                │
│   │  three.js RaceScene   (own rAF loop)             │  │                │
│   │   • 2 rockets      • GPU particle exhaust        │  │                │
│   │   • starfield      • finish gate + camera rig    │  │                │
│   └──────────────────────────────────────────────────┘  │                │
└──────────────────────────────┬───────────────────────────────────────────┘
                               │
              POST /api/race   │   SSE: start · first-token · tick ·
              (proxied by Vite)│        reasoning · retry · done · finish
                               ▼
┌──────────────────────────────────────────────────────────────────────────┐
│  NODE SERVER  ·  :3001   (Express + TypeScript)                          │
│                                                                          │
│   POST /api/race ──► runRace(prompt, config, emit)                       │
│                        │                                                 │
│                        ├──► runModel('A', modelA, …) ─┐  Promise.all     │
│                        └──► runModel('B', modelB, …) ─┘  (concurrent)    │
│                                    │                                     │
│                        streamOnce() parses provider SSE,                 │
│                        counts tokens, never stores reasoning text        │
└────────────────────────────────────┬─────────────────────────────────────┘
                                     │  fetch(…, { stream: true })
                                     ▼
              ┌────────────────────────────────────────────┐
              │  OpenAI-compatible provider                │
              │  POST {baseUrl}/chat/completions           │
              └────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The stack is deliberately boring: &lt;strong&gt;Vite + React + TypeScript + three.js&lt;/strong&gt; on the&lt;br&gt;
front, &lt;strong&gt;Node + Express + TypeScript&lt;/strong&gt; on the back. No database, no auth, no state&lt;br&gt;
library, no model SDK. Model calls are plain &lt;code&gt;fetch&lt;/code&gt; against the OpenAI-compatible&lt;br&gt;
&lt;code&gt;/chat/completions&lt;/code&gt; endpoint — which means pointing the whole thing at Ollama, vLLM, or&lt;br&gt;
OpenRouter takes seconds and zero code changes.&lt;/p&gt;


&lt;h2&gt;
  
  
  Making the visual &lt;em&gt;be&lt;/em&gt; the data
&lt;/h2&gt;

&lt;p&gt;This is the decision the project lives or dies on.&lt;/p&gt;

&lt;p&gt;It would have been easy to draw two rockets moving at speeds proportional to&lt;br&gt;
tokens/second, or to give each lane its own scale so both look dramatic. Both would&lt;br&gt;
have been lies. The gap between the rockets has to &lt;em&gt;mean&lt;/em&gt; something.&lt;/p&gt;

&lt;p&gt;So both lanes share &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;unitsPerToken&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web/src/three/RaceScene.ts&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;tokenToWorldX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;START_X&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitsPerToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One shared scale means the horizontal distance between the rockets is always exactly&lt;br&gt;
proportional to the difference in tokens emitted. It's not decoration. It's the data,&lt;br&gt;
rendered.&lt;/p&gt;

&lt;p&gt;But a fixed scale has a problem: you don't know in advance how many tokens a model will&lt;br&gt;
emit. Too small and the rockets fly off screen; too large and they never leave the&lt;br&gt;
start line. So the scale &lt;strong&gt;eases&lt;/strong&gt; while racing, keeping the leader framed.&lt;/p&gt;

&lt;p&gt;Then comes the moment that makes the whole thing work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/** Plant the finish line at the first finisher's token count and freeze scale. */&lt;/span&gt;
&lt;span class="nf"&gt;plantFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tokenCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetUnitsPerToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FINISH_X&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unitsPerToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetUnitsPerToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frozen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finishGate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;visible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finishGate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;FINISH_X&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finishGate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instant the first model finishes, two things happen: the scale &lt;strong&gt;freezes&lt;/strong&gt;, and the&lt;br&gt;
finish line is planted at &lt;em&gt;that model's&lt;/em&gt; token count.&lt;/p&gt;

&lt;p&gt;The slower model is still emitting. It keeps driving forward — straight past the finish&lt;br&gt;
line, trailing a plume that keeps growing. The overshoot is drawn to scale. &lt;strong&gt;The&lt;br&gt;
overshoot is the tax.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why the visual argument works where a bar chart doesn't. You don't read that&lt;br&gt;
model B cost more. You watch it lose the race and then keep going anyway.&lt;/p&gt;


&lt;h2&gt;
  
  
  The exhaust is not a particle effect
&lt;/h2&gt;

&lt;p&gt;Here's where "genuinely measured" got hard.&lt;/p&gt;

&lt;p&gt;My first implementation spawned exhaust on a timer, with some randomness for texture. It&lt;br&gt;
looked great. It was also completely fake — a model that stalled mid-generation would&lt;br&gt;
keep puffing smoke like nothing was wrong. That's a lie told by a visual, which is worse&lt;br&gt;
than a lie told by a number, because you feel it instead of reading it.&lt;/p&gt;

&lt;p&gt;The fix was to make the render loop consume real token arrivals. The hook buffers them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web/src/hooks/useRace.ts&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Real token arrivals buffered between frames. The scene drains this each
 * frame and spawns exactly that many exhaust particles — so if the model
 * stalls, the plume stops growing.
 */&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrivalsRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Arrival&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drainArrivals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrivalsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drained&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;arrivalsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;arrivalsRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;drained&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and the scene spawns only from what it drained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// web/src/three/RaceScene.ts&lt;/span&gt;

&lt;span class="c1"&gt;// --- exhaust: spawn ONLY from real token arrivals ---&lt;/span&gt;
&lt;span class="c1"&gt;// Each arrival carries the number of tokens that landed since the last&lt;/span&gt;
&lt;span class="c1"&gt;// frame. A stalled model produces no arrivals, so its plume stops growing.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrivals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arrival&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrivals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lanes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;arrival&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="c1"&gt;// One particle per token, plus a little extra while the rate is high so&lt;/span&gt;
    &lt;span class="c1"&gt;// a fast model reads as a denser plume rather than a dotted line.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;density&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrival&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrival&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;density&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spawnExhaust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrival&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lane&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrival&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arrivals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the plume is a &lt;strong&gt;readout&lt;/strong&gt;. Token arrival rate literally becomes particle spawn&lt;br&gt;
rate. A fast model reads as a dense, hot plume; a stalling model goes quiet.&lt;/p&gt;

&lt;p&gt;Plume size also scales with cumulative tokens, so the loser's trail is both longer &lt;em&gt;and&lt;/em&gt;&lt;br&gt;
fatter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Plume grows with cumulative tokens: more tokens -&amp;gt; fatter, longer-lived.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;plumeScale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cumulativeTokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateBoost&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Doing it at 60fps
&lt;/h3&gt;

&lt;p&gt;Thousands of particles per lane, spawned continuously, with zero per-frame allocation.&lt;/p&gt;

&lt;p&gt;One &lt;code&gt;THREE.Points&lt;/code&gt; per lane with a &lt;strong&gt;fixed 4000-particle pool&lt;/strong&gt; and a ring-buffer&lt;br&gt;
cursor. Dead particles aren't removed — they're pushed outside the clip volume in the&lt;br&gt;
vertex shader and collapsed to zero size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uTime&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;aBirth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aLife&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;aLife&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Dead particle: push it outside the clip volume and collapse it.&lt;/span&gt;
  &lt;span class="nb"&gt;gl_Position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;vec4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nb"&gt;gl_PointSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;vAlpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recycling is a modulo on an integer cursor. The GPU does the ageing, the drift, the&lt;br&gt;
expansion, and the fade — the CPU only writes new particles into the ring.&lt;/p&gt;


&lt;h2&gt;
  
  
  Four bugs that taught me something
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. The SSE stream that died after one event
&lt;/h3&gt;

&lt;p&gt;This one cost me an embarrassing amount of time, and the fix is a single word.&lt;/p&gt;

&lt;p&gt;The server was doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Node, &lt;code&gt;req&lt;/code&gt; emits &lt;code&gt;'close'&lt;/code&gt; when the &lt;strong&gt;request body has been fully consumed&lt;/strong&gt; — not&lt;br&gt;
when the client disconnects. Since the POST body is tiny and arrives instantly, &lt;code&gt;closed&lt;/code&gt;&lt;br&gt;
flipped to &lt;code&gt;true&lt;/code&gt; almost immediately, and my &lt;code&gt;emit()&lt;/code&gt; guard silently swallowed every&lt;br&gt;
event after &lt;code&gt;start&lt;/code&gt;. The race looked like it hung. Nothing errored. Nothing logged.&lt;/p&gt;

&lt;p&gt;The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NOTE: listen on the RESPONSE, not the request. `req` emits 'close' as soon&lt;/span&gt;
&lt;span class="c1"&gt;// as the POST body has been fully consumed, which would mark the stream dead&lt;/span&gt;
&lt;span class="c1"&gt;// before a single race event was written.&lt;/span&gt;
&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;closed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heartbeat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're building SSE on Express, this is the bug you will hit. Two more things worth&lt;br&gt;
copying: a heartbeat comment every 15 seconds so proxies don't idle the connection out&lt;br&gt;
during a long think, and headers that stop anything from buffering the stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/event-stream; charset=utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Cache-Control&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no-cache, no-transform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keep-alive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-Accel-Buffering&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;no&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;code&gt;EventSource&lt;/code&gt; can't POST
&lt;/h3&gt;

&lt;p&gt;The browser's &lt;code&gt;EventSource&lt;/code&gt; API only does &lt;code&gt;GET&lt;/code&gt;. I need to send a prompt body. So the&lt;br&gt;
client uses &lt;code&gt;fetch&lt;/code&gt; plus a &lt;code&gt;ReadableStream&lt;/code&gt; reader and parses SSE frames by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextDecoder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;decoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// SSE frames are separated by a blank line.&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r?\n\r?\n&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="c1"&gt;// …parse `event:` / `data:` lines, dispatch&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the buffer-and-split: chunks arrive at arbitrary boundaries, so a frame can be&lt;br&gt;
split mid-JSON. You must accumulate and only parse on the blank-line terminator. And&lt;br&gt;
flush any trailing frame after the loop, because the last one may lack a terminator.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. An empty response is a budget problem, not a refusal
&lt;/h3&gt;

&lt;p&gt;A reasoning model can spend its entire &lt;code&gt;max_tokens&lt;/code&gt; thinking and return &lt;strong&gt;empty&lt;br&gt;
content&lt;/strong&gt;. To a naive client that looks like a broken model. It's actually a&lt;br&gt;
misconfigured budget.&lt;/p&gt;

&lt;p&gt;So: retry once with double the budget.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Attempt 1, then (only if content came back empty) one retry with a&lt;/span&gt;
&lt;span class="c1"&gt;// doubled budget. An empty response is a budget problem, not a refusal.&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;maxTokensUsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTokens&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxTokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// …&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The subtle part is drift. If the retry builds its own request, it will eventually&lt;br&gt;
diverge from the first call — someone adds a field to one path and not the other. So&lt;br&gt;
both attempts build from one shared factory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Shared callbacks so the retry path can never drift from the first call.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attemptOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;AttemptOptions&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Counting reasoning without ever reading it
&lt;/h3&gt;

&lt;p&gt;This one is an ethics decision as much as a technical one.&lt;/p&gt;

&lt;p&gt;Reasoning traces are the model's private scratchpad. They can contain the user's data,&lt;br&gt;
half-formed (and wrong) intermediate conclusions, and content that was never meant to be&lt;br&gt;
shown. I need the &lt;em&gt;cost&lt;/em&gt; of reasoning. I do not need, want, or store its content.&lt;/p&gt;

&lt;p&gt;So the server tests for presence, increments a counter, and throws the text away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// COUNT reasoning, never capture it.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_content&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;reasoningChunks&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onReasoning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reasoningChunks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are exactly &lt;strong&gt;two&lt;/strong&gt; references to &lt;code&gt;reasoning_content&lt;/code&gt; in the entire server: that&lt;br&gt;
line and the doc comment above it. The authoritative number comes from usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;completion_tokens_details&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;reasoning_tokens&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasoningTokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a provider omits usage, it falls back to the live chunk count — so the HUD never&lt;br&gt;
shows a lie. And &lt;code&gt;npm run ui-check&lt;/code&gt; asserts that &lt;code&gt;reasoning_content&lt;/code&gt; never appears in&lt;br&gt;
the DOM.&lt;/p&gt;


&lt;h2&gt;
  
  
  Proving it isn't a nice animation
&lt;/h2&gt;

&lt;p&gt;The whole value of this project rests on one claim: it measures something real. So the&lt;br&gt;
claim has to be falsifiable. I wrote three scripts, no test framework.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;npm run verify&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Drives the live endpoint and asserts the two presets produce &lt;strong&gt;visibly different&lt;/strong&gt;&lt;br&gt;
races:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;━━━ DIVERGENCE ━━━
  ✓ lanes visibly separate (token gap &amp;gt; 20)          (gap = 38)
  ✓ slower model has the fatter plume                (loser 34 vs winner 21)
  ✓ outputs differ                                   (identical = false)
  ✓ reasoning token counts read from usage           (A=0 B=50)

━━━ TIE ━━━
  ✓ finish near-simultaneously (&amp;lt; 1500ms)            (gap = 0ms)
  ✓ IDENTICAL OUTPUT                                 (identical = true)

  ✓ the two prompts produced VISIBLY DIFFERENT races
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If both prompts ever produce identical-looking races, the app has stopped measuring&lt;br&gt;
anything and this script says so.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npm run ui-check&lt;/code&gt; — and a measurement bug worth reading
&lt;/h3&gt;

&lt;p&gt;Headless Playwright. It samples the HUD mid-race to prove counters advance live, and&lt;br&gt;
analyses screenshot pixels to confirm the slower lane really renders the fatter plume.&lt;/p&gt;

&lt;p&gt;My first version counted cyan and magenta pixels in the screenshot. It failed&lt;br&gt;
constantly, and the reason is a good lesson in measuring things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The glowing rails are the same colours as the rockets.&lt;/strong&gt; Worse, lane A's rail sits&lt;br&gt;
nearer the camera, so it's always bigger. The measurement was dominated by a static&lt;br&gt;
piece of scenery, not the exhaust.&lt;/p&gt;

&lt;p&gt;The fix: locate each lane's rail row (the densest run of that colour in its half of the&lt;br&gt;
screen), then count only pixels &lt;strong&gt;above&lt;/strong&gt; it — because exhaust drifts upward and the&lt;br&gt;
rail does not.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;run&lt;/th&gt;
&lt;th&gt;winner plume&lt;/th&gt;
&lt;th&gt;loser plume&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1,868 px / 69 px tall&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48,163 px / 172 px tall&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4,039 px / 69 px tall&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;29,673 px / 143 px tall&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2,264 px / 21 px tall&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;48,282 px / 135 px tall&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three consecutive runs, consistently ~10× difference. Now the test measures the thing it&lt;br&gt;
claims to measure.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;npm run mock&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A fake OpenAI-compatible provider so the whole pipeline is testable offline: a "slow&lt;br&gt;
thinker" that streams &lt;code&gt;reasoning_content&lt;/code&gt; before content, a model that returns &lt;strong&gt;empty&lt;br&gt;
content on its first call&lt;/strong&gt; so the retry path is observable, and a tie mode where both&lt;br&gt;
models converge on identical text and finish together.&lt;/p&gt;

&lt;p&gt;This is how I verified the retry, the reasoning counter, and the IDENTICAL OUTPUT&lt;br&gt;
verdict without spending a cent.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd tell you to build next
&lt;/h2&gt;

&lt;p&gt;The single highest-impact addition would be a &lt;strong&gt;cost ticker&lt;/strong&gt;. Reasoning tokens are&lt;br&gt;
billed. Multiply by the model's real price and put "the tax cost $0.0043" next to the&lt;br&gt;
plume. That converts a metaphor into a number a team can act on.&lt;/p&gt;

&lt;p&gt;After that: run each prompt 5× and show the median and spread. &lt;strong&gt;One race is an&lt;br&gt;
anecdote; five is a measurement.&lt;/strong&gt; A single race can mislead — sampling variance is&lt;br&gt;
real, and the app should be honest about that.&lt;/p&gt;

&lt;p&gt;And the most interesting experiment the architecture already supports: race the &lt;em&gt;same&lt;/em&gt;&lt;br&gt;
model with reasoning on versus off. The &lt;code&gt;Disable reasoning&lt;/code&gt; flag is already there. The&lt;br&gt;
quality delta is the honest value of the tax.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/harishkotra/reasoning-tax-race.git
&lt;span class="nb"&gt;cd &lt;/span&gt;reasoning-tax-race
npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example server/.env     &lt;span class="c"&gt;# paste your key&lt;/span&gt;
npm run dev                     &lt;span class="c"&gt;# → http://localhost:5173&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run &lt;strong&gt;Haiku about rain&lt;/strong&gt;, and watch the magenta rocket lose the race and keep going.&lt;/p&gt;

&lt;p&gt;Everything is measured live. Nothing is mocked. And if you find a race that surprises you, open an issue with the exported JSON — that's the most useful bug report this project can receive.&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/253-reasoning-tax-race" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/253-reasoning-tax-race&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Why Your AI Agents Keep Calling the Wrong Tools (And How We Built Toolsmith to Fix Them)</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sun, 13 Sep 2026 18:22:14 +0000</pubDate>
      <link>https://dev.to/harishkotra/why-your-ai-agents-keep-calling-the-wrong-tools-and-how-we-built-toolsmith-to-fix-them-2lle</link>
      <guid>https://dev.to/harishkotra/why-your-ai-agents-keep-calling-the-wrong-tools-and-how-we-built-toolsmith-to-fix-them-2lle</guid>
      <description>&lt;p&gt;If you have built an autonomous AI agent using OpenAI, Claude, LangChain, or CrewAI, you have inevitably encountered this scenario:&lt;/p&gt;

&lt;p&gt;Your agent needs to look up a customer order. Instead of calling &lt;code&gt;fetch_customer_order(order_id)&lt;/code&gt;, it calls &lt;code&gt;search_database(query="order")&lt;/code&gt;. Or worse, it hallucinates an argument like &lt;code&gt;status="completed_order_v2"&lt;/code&gt; because the schema simply said &lt;code&gt;"status": { "type": "string" }&lt;/code&gt; without defining permitted enum values. Or it enters an infinite loop, toggling back and forth between two sibling tools that do almost the same thing.&lt;/p&gt;

&lt;p&gt;When this happens, developers usually react by tweaking the system prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"IMPORTANT: You MUST always call fetch_customer_order when the user mentions an order! Do NOT call search_database!"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;Prompt Engineering Trap&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The root cause of tool-calling failures is almost never the model’s reasoning capacity. It is &lt;strong&gt;broken, sloppy tool contracts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, I will explain why agent tool schemas fail in production, break down the architecture of &lt;strong&gt;Toolsmith&lt;/strong&gt;—a developer tool we built to lint, score, and harden AI tool definitions—and share the algorithms and engineering decisions behind it.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Anatomizing the Broken Tool Contract
&lt;/h2&gt;

&lt;p&gt;Let’s look at a schema typical of many agent tutorials:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lookup_user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Used to find information about people in the company or app."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"parameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"properties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The user"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optional search parameters"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does an LLM fail when presented with this?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Passive, Vague Description&lt;/strong&gt;: &lt;em&gt;"Used to find information..."&lt;/em&gt; does not give the model an imperative trigger. When should it call this instead of a general search tool? Under what constraints?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing Imperative Verb&lt;/strong&gt;: High-performing tool descriptions start with active verbs (&lt;code&gt;Fetch&lt;/code&gt;, &lt;code&gt;Query&lt;/code&gt;, &lt;code&gt;Search&lt;/code&gt;, &lt;code&gt;Validate&lt;/code&gt;). This matches the instruction fine-tuning patterns of modern frontier models.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Untyped Parameter&lt;/strong&gt;: &lt;code&gt;"filter"&lt;/code&gt; has no &lt;code&gt;"type"&lt;/code&gt;. Is it a JSON object? A SQL string? A date range?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Enum Sets&lt;/strong&gt;: If &lt;code&gt;"status"&lt;/code&gt; or &lt;code&gt;"mode"&lt;/code&gt; only supports three valid states, failing to declare an &lt;code&gt;enum: ["active", "suspended", "archived"]&lt;/code&gt; guarantees model hallucinations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing &lt;code&gt;required&lt;/code&gt; Array&lt;/strong&gt;: The model has to guess whether &lt;code&gt;"user"&lt;/code&gt; is mandatory or optional.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multiply this by 15 or 20 tools in an enterprise agent runtime, and the model's tool-selection surface degrades into chaotic ambiguity.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Toolsmith System Architecture
&lt;/h2&gt;

&lt;p&gt;To solve this, we designed &lt;strong&gt;Toolsmith&lt;/strong&gt;: a tool that performs &lt;strong&gt;deterministic 0–100 linting&lt;/strong&gt; of JSON tool schemas and provides an automated &lt;strong&gt;hardening loop&lt;/strong&gt; powered by any OpenAI-compatible LLM endpoint (Ollama, Groq, OpenRouter, OpenAI) or Gemini.&lt;/p&gt;

&lt;p&gt;Here is the high-level architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────────────┐
│                        USER INPUT                              │
│  - Raw Tool Schemas (tools wrapper, bare array, single tool)   │
└───────────────────────────────┬────────────────────────────────┘
                                │
                                ▼
┌────────────────────────────────────────────────────────────────┐
│                     INPUT NORMALIZER                           │
│  - Detects shape &amp;amp; standardizes into NormalizedTool[]          │
└───────────────────────────────┬────────────────────────────────┘
                                │
                                ▼
┌────────────────────────────────────────────────────────────────┐
│               DETERMINISTIC LINTING ENGINE                     │
│  (Pure TypeScript, Zero-Latency, No API calls required)        │
│                                                                │
│  ├── Description Clarity (Imperative verbs, min/max length)    │
│  ├── Parameter Hygiene (Types, enums, required arrays)         │
│  ├── Naming Conventions (snake_case, length, prefixing)        │
│  ├── Sibling Tool Overlap (Jaccard distance, lexical matches)  │
│  └── Token Economics (Byte size, token density, context cost)  │
└───────┬───────────────────────┬────────────────────────┬───────┘
        │                       │                        │
        ▼                       ▼                        ▼
┌──────────────┐        ┌───────────────┐        ┌───────────────┐
│ 0-100 Score  │        │ Tool Category │        │ Tool          │
│ &amp;amp; Grade Dial │        │ Breakdown     │        │ Complexity    │
│ (SVG arc)    │        │ (Progress)    │        │ (L / M / H)   │
└───────┬──────┘        └───────┬───────┘        └───────┬───────┘
        │                       │                        │
        └───────────────────────┼────────────────────────┘
                                │
                                ▼
┌────────────────────────────────────────────────────────────────┐
│                SESSION HISTORY ENGINE (Recharts)               │
│  - Tracks score improvement trajectory across analysis runs    │
└───────────────────────────────┬────────────────────────────────┘
                                │
                                ▼
┌────────────────────────────────────────────────────────────────┐
│               AI CONTRACT HARDENING ENGINE                     │
│  - Injects typed schemas, imperative verbs, enum constraints   │
│  - Connects to: Local Ollama, Groq, OpenRouter, or OpenAI API  │
│  - Side-by-Side Diff Inspector with field-by-field reasoning   │
└────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. The Deterministic Rule Engine
&lt;/h2&gt;

&lt;p&gt;A critical design requirement for Toolsmith was &lt;strong&gt;determinism&lt;/strong&gt;. Developers do not want to wait 8 seconds or spend $0.05 on API calls just to check if their JSON schema has typos or missing types.&lt;/p&gt;

&lt;p&gt;The core linter runs synchronously in milliseconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runLinter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NormalizedTool&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;LintReport&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Description audits&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;checkDescriptionQuality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;checkParameterHygiene&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;checkNamingConventions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Cross-tool sibling overlap detection&lt;/span&gt;
  &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nf"&gt;checkToolOverlap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Score calculation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalScoreDeductions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scoreImpact&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;overallScore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;totalScoreDeductions&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;overallScore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computeGrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;overallScore&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;computeCategoryScores&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;toolCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking for Imperative Verbs
&lt;/h3&gt;

&lt;p&gt;Models trigger tools most reliably when descriptions start with active, unambiguous command verbs. The linter enforces this check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;IMPERATIVE_VERBS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;get&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fetch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;search&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;retrieve&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;read&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;create&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;insert&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;generate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;make&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;modify&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;set&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;patch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;delete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remove&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;send&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;execute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;calculate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkDescriptionQuality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NormalizedTool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstWord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;IMPERATIVE_VERBS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstWord&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-desc-verb`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Description should begin with an active imperative verb (e.g., 'Fetch...', 'Search...'). Currently starts with: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;firstWord&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;".`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Rewrite the first sentence to start with an active imperative command verb.`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;scoreImpact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Measuring Tool Complexity
&lt;/h2&gt;

&lt;p&gt;Not all tools carry equal cognitive load for an LLM. A tool with 8 top-level parameters and 3 layers of nested objects is significantly more likely to trigger execution errors than a simple key-value tool.&lt;/p&gt;

&lt;p&gt;To expose this risk, we implemented a recursive tree walker that computes tool complexity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeToolComplexity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;NormalizedTool&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ToolComplexityInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;paramCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nestedCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0 params&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;paramCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;nestedCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;maxDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;maxDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="nx"&gt;paramCount&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;nestedCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Low&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paramCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nestedCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;maxDepth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;High&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paramCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;nestedCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;maxDepth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;paramCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;nestedCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paramCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; param&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;paramCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;s&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;nestedCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; nested`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI visualizes this with high-contrast badges (&lt;code&gt;Low&lt;/code&gt; in emerald, &lt;code&gt;Medium&lt;/code&gt; in amber, &lt;code&gt;High&lt;/code&gt; in rose) right alongside each audited tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Visualizing Iterative Improvement with Recharts
&lt;/h2&gt;

&lt;p&gt;When developers iterate on their schemas—or run our AI hardening engine—they need to see if their contracts are getting measurably better.&lt;/p&gt;

&lt;p&gt;We integrated &lt;strong&gt;Recharts&lt;/strong&gt; to render a lightweight, zero-gradient trend line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ResponsiveContainer&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"100%"&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;140&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LineChart&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;history&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CartesianGrid&lt;/span&gt; &lt;span class="na"&gt;strokeDasharray&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"3 3"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#1e293b"&lt;/span&gt; &lt;span class="na"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;XAxis&lt;/span&gt; &lt;span class="na"&gt;dataKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"label"&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#64748b"&lt;/span&gt; &lt;span class="na"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;YAxis&lt;/span&gt; &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#64748b"&lt;/span&gt; &lt;span class="na"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Tooltip&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CustomTooltip&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Line&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"monotone"&lt;/span&gt;
      &lt;span class="na"&gt;dataKey&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"score"&lt;/span&gt;
      &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#818cf8"&lt;/span&gt;
      &lt;span class="na"&gt;strokeWidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;dot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#6366f1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;strokeWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#1e1b4b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;activeDot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#a5b4fc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;LineChart&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ResponsiveContainer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately surfaces the delta between an unoptimized schema (e.g. 42/100, Grade F) and its hardened variant (e.g. 96/100, Grade A).&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Flexible AI Hardening Without &lt;code&gt;.env&lt;/code&gt; Hassles
&lt;/h2&gt;

&lt;p&gt;Most developer tools force you to clone a repo, configure a &lt;code&gt;.env&lt;/code&gt; file, and restart containers just to test an AI model.&lt;/p&gt;

&lt;p&gt;In Toolsmith, the OpenAI-compatible client configuration lives entirely in browser state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Local Ollama&lt;/strong&gt;: Works out of the box (&lt;code&gt;http://localhost:11434/v1&lt;/code&gt;, model &lt;code&gt;llama3.1&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Groq Cloud&lt;/strong&gt;: Blazing-fast hardening via &lt;code&gt;llama-3.3-70b-versatile&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenRouter / OpenAI&lt;/strong&gt;: Plug in your personal API key and model of choice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the target endpoint is &lt;code&gt;localhost&lt;/code&gt; or &lt;code&gt;127.0.0.1&lt;/code&gt;, Toolsmith calls it &lt;strong&gt;directly from the user's browser&lt;/strong&gt;, avoiding server container networking bridges. For remote endpoints, it uses a lightweight server proxy to bypass CORS restrictions safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Key Takeaways for Production AI Agent Developers
&lt;/h2&gt;

&lt;p&gt;Building robust agents requires treating tool schemas with the same rigor we apply to OpenAPI specs and gRPC protobufs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write imperative, trigger-focused descriptions&lt;/strong&gt;: Never say &lt;code&gt;"Used for search"&lt;/code&gt;. Say: &lt;code&gt;"Search customer orders by date range or customer ID. Call when the user explicitly requests order status."&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always define explicit &lt;code&gt;enum&lt;/code&gt; values&lt;/strong&gt;: If a status is &lt;code&gt;["pending", "shipped", "delivered"]&lt;/code&gt;, write it out. Never leave it as an unconstrained &lt;code&gt;"string"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard against overlapping tool scopes&lt;/strong&gt;: If two tools have similar parameter names and verbs, merge them into a single parameterized tool or sharply distinguish their triggers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit schema token weight&lt;/strong&gt;: Tool definitions are injected into &lt;em&gt;every single prompt turn&lt;/em&gt;. Bloated tool schemas silently eat away your context window and inflate latency.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F78e3atnkb3hob5ophrx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F78e3atnkb3hob5ophrx0.png" alt="How this works - 1" width="799" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftju3dfr15d33utkm01qk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftju3dfr15d33utkm01qk.png" alt="How this works - 2" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/252-toolsmith" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/252-toolsmith&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>Voice Arena: I made two AI voice agents fight live on Agora and let the internet judge</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Sat, 12 Sep 2026 18:45:31 +0000</pubDate>
      <link>https://dev.to/harishkotra/voice-arena-i-made-two-ai-voice-agents-fight-live-on-agora-and-let-the-internet-judge-5mi</link>
      <guid>https://dev.to/harishkotra/voice-arena-i-made-two-ai-voice-agents-fight-live-on-agora-and-let-the-internet-judge-5mi</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Voice Arena puts two real Agora Conversational AI Engine agents in one RTC channel, gives them the same prompt, and lets you vote the winner. Red (VAD + barge-in) vs Blue (semantic end-of-speech + keyword-only interruption). Real &lt;code&gt;get_turns()&lt;/code&gt; latency receipts, ELO leaderboard, shareable knockout cards. Built with &lt;code&gt;agora-agents&lt;/code&gt; 2.8.1, FastAPI, and a neon zero-build frontend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Text LMArenas are solved. Voice is where the pain lives: Who &lt;em&gt;feels&lt;/em&gt; faster? Does VAD or semantic end-of-speech win a roast battle? Does generated filler actually hide LLM lag? I wanted a toy that answers those questions by &lt;em&gt;showing&lt;/em&gt; them — same prompt, two tunings, live audio, crowd vote. If you've ever argued about interruption settings in a PR, this is your boxing ring.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prompt ─┬─► Red session ──► RTC channel ─┐
        │     (VAD EoS, SoS interrupt)    ├─► your ears + your vote
        └─► Blue session ─► RTC channel ─┘
              (semantic EoS, keyword-only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Server&lt;/strong&gt; (&lt;code&gt;app.py&lt;/code&gt;, FastAPI):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/battle&lt;/code&gt; — text undercard. Two OpenAI-Compatible completions
(&lt;code&gt;MODEL_A&lt;/code&gt;/&lt;code&gt;MODEL_B&lt;/code&gt;), blind-shuffled left/right, latency timed server-side:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /api/arena/start&lt;/code&gt; — the main event. Real SDK sessions via &lt;code&gt;arena_agent.py&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;area&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GLOBAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;app_certificate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt;
                     &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;customer_secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;
&lt;span class="n"&gt;red&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_fighter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remote_uids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;think&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_speaking_action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;append&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# inject without killing the greeting
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/arena/turns&lt;/code&gt; — post-fight receipts: &lt;code&gt;session.get_turns()&lt;/code&gt; returns real
per-turn ASR/LLM/TTS timings. No vibes, just milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/vote&lt;/code&gt; — ELO (&lt;code&gt;k=24&lt;/code&gt;) persisted to &lt;code&gt;data/leaderboard.json&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fighters&lt;/strong&gt; (&lt;code&gt;arena_agent.build_fighter&lt;/code&gt;): the tuning &lt;em&gt;is&lt;/em&gt; the content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt;
       &lt;span class="n"&gt;system_messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SYSTEM_RED&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_stt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AresSTT&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_tts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MiniMaxTTS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speech-2.8-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;voice_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...))&lt;/span&gt;  &lt;span class="c1"&gt;# managed creds, no keys
&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_turn_detection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end_of_speech&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}}})&lt;/span&gt;  &lt;span class="c1"&gt;# red: twitchy
&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_interruption&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_of_speech&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_filler_words&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;enabled&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated_config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fallback_strategy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static_config&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;phrases&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me a sec.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...]}}})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with_labels&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arena&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voice-arena&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fighter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fighter&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap one block and the whole fight changes character. That's the demo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt; (static, zero build step): prompt roulette, live typing with EQ bars,&lt;br&gt;
latency badges (&lt;code&gt;⚡ms&lt;/code&gt;), SAY-IT-LIVE stage speech via session.say(), voice voting via Web Speech API&lt;br&gt;
(&lt;em&gt;"left wins"&lt;/em&gt;), confetti, canvas-rendered 1080×600 knockout card, 𝕏 intent link,&lt;br&gt;
and &lt;code&gt;?battle=id&lt;/code&gt; deep links so every fight is shareable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things I learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;think(append)&lt;/code&gt; is the showrunner.&lt;/strong&gt; Injecting the prompt with
&lt;code&gt;on_speaking_action="append"&lt;/code&gt; lets greetings land instead of getting clobbered. &lt;code&gt;interrupt&lt;/code&gt; feels chaotic on stage; &lt;code&gt;append&lt;/code&gt; feels produced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generated filler words earn their keep.&lt;/strong&gt; With &lt;code&gt;filler_words.mode=generated&lt;/code&gt;, the 1–2s LLM gap stops reading as "dead bot" and starts reading as stage presence. A/B it in the arena and you'll hear it instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EoS mode is a personality slider.&lt;/strong&gt; VAD Red interrupts, jokes, steps on lines. Semantic Blue waits, lands cleaner closers. Same models, different souls — and the crowd splits exactly the way you'd predict. That's the viral argument baked in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;voice-arena
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt   &lt;span class="c"&gt;# agora-agents==2.8.1 pinned&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env              &lt;span class="c"&gt;# add Agora + LLM creds&lt;/span&gt;
uvicorn app:app &lt;span class="nt"&gt;--port&lt;/span&gt; 8000       &lt;span class="c"&gt;# open http://localhost:8000&lt;/span&gt;
python arena_agent.py &lt;span class="nt"&gt;--channel&lt;/span&gt; arena-demo &lt;span class="nt"&gt;--prompt&lt;/span&gt; &lt;span class="s2"&gt;"Dump me like a pirate."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Try: same model both sides, different temps → then different EoS modes → then different voices. Each change is one env var. Each fight is one tweet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Heckle button (&lt;code&gt;session.interrupt()&lt;/code&gt; live), clip exporter from turn timestamps, third fighter round-robin, avatar video knockouts. PRs welcome — keep SDK pinned, keep &lt;code&gt;.env&lt;/code&gt; out of git, keep the fights blind.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd5r12vmw6behwxmgkyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcd5r12vmw6behwxmgkyw.png" alt="How this works 1" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv5ucxxnadbi5mvamc6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frv5ucxxnadbi5mvamc6j.png" alt="How this works 2" width="800" height="843"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/251-voice-arena" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/251-voice-arena&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>agora</category>
      <category>dailybuild2026</category>
    </item>
    <item>
      <title>I Scraped All 234 YC Summer 2026 Startups and Found 31 That Are Just a SKILL.md</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Fri, 11 Sep 2026 19:40:30 +0000</pubDate>
      <link>https://dev.to/harishkotra/i-scraped-all-234-yc-summer-2026-startups-and-found-31-that-are-just-a-skillmd-2n51</link>
      <guid>https://dev.to/harishkotra/i-scraped-all-234-yc-summer-2026-startups-and-found-31-that-are-just-a-skillmd-2n51</guid>
      <description>&lt;p&gt;&lt;strong&gt;A repeatable agent workflow is not a company. It's a skill. Here's the open-source proof.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every few months a new YC batch drops, and with it a wave of "AI startups" that are really just an agent doing a repeatable task. So I did the obvious thing: I scraped all &lt;strong&gt;234 companies&lt;/strong&gt; in the Summer 2026 batch, read every one-liner, and asked one question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many of these could have been a SKILL.md?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A SKILL.md is a markdown file that tells an AI agent how to do something. It's a procedure: the steps, the pitfalls, the verification. If a startup's core value is a repeatable workflow an agent can follow, with no hardware, no regulation, and no capital moat underneath, then it is, functionally, a skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The methodology
&lt;/h2&gt;

&lt;p&gt;I pulled the batch from the YC API (12 pages, 234 unique companies), then classified each one into three buckets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SKILL&lt;/strong&gt; (31, ~13%): the core is a documented procedure an agent executes. No heavy moat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PARTIAL&lt;/strong&gt; (130, ~56%): an agent plus a real moat (data, vertical integration, infrastructure).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NO&lt;/strong&gt; (73, ~31%): hardware, robotics, chips, pharma, insurance, defense, space, physical infrastructure, biology.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full classification is in &lt;code&gt;yc-s26-skill-analysis.csv&lt;/code&gt; in this repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;What separates the 31 from the rest is striking. Their one-liners read like procedure descriptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Least cost routing system to reduce 70%+ token spend"&lt;/li&gt;
&lt;li&gt;"AI engineer that debugs and fixes production incidents in realtime"&lt;/li&gt;
&lt;li&gt;"AI agents for compliance at institutional trading firms"&lt;/li&gt;
&lt;li&gt;"Autoresearch as a service"&lt;/li&gt;
&lt;li&gt;"AI-native debt collection company"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no proprietary hardware, no regulated license, no physical presence, no capital moat. The product is the workflow, the prompt, and the tooling around it. That is exactly what a SKILL.md is.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I rebuilt all 31 as real skills
&lt;/h2&gt;

&lt;p&gt;Each one is now a self-contained, open-source &lt;code&gt;SKILL.md&lt;/code&gt; with a &lt;strong&gt;Procedure&lt;/strong&gt;, the&lt;br&gt;
&lt;strong&gt;Pitfalls&lt;/strong&gt;, and a &lt;strong&gt;Verification&lt;/strong&gt; step. No company names, no trademarks, just the&lt;br&gt;
capability. A few examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;token-cost-router&lt;/code&gt; — route every LLM request to the cheapest model that can handle it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;production-incident-debugger&lt;/code&gt; — debug and fix a live incident fast and safely&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;government-contract-bidder&lt;/code&gt; — find, qualify for, and respond to gov contracts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;collections-agent&lt;/code&gt; — polite-but-firm collections on overdue accounts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hipaa-internal-tool-builder&lt;/code&gt; — vibe-code and deploy HIPAA-compliant internal tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How to use them
&lt;/h2&gt;

&lt;p&gt;Skills are portable. Copy the folder into whatever harness reads &lt;code&gt;SKILL.md&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; &lt;code&gt;~/.claude/skills/&amp;lt;skill&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor:&lt;/strong&gt; &lt;code&gt;~/.cursor/skills/&amp;lt;skill&amp;gt;/&lt;/code&gt; or &lt;code&gt;.cursor/skills/&lt;/code&gt; in a project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenCode:&lt;/strong&gt; &lt;code&gt;~/.config/opencode/skill/&amp;lt;skill&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hermes:&lt;/strong&gt; &lt;code&gt;~/.hermes/skills/&amp;lt;skill&amp;gt;/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then just ask your agent to do the thing. The frontmatter description makes it auto-discoverable.&lt;/p&gt;
&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The next time you see a funding announcement for an "AI agent that does X," ask whether X is a repeatable procedure. If it is, you don't need a subscription. You need a skill.&lt;/p&gt;

&lt;p&gt;And if you're building one of these, the moat is never the workflow. It's the data, the distribution, the integration, or the trust you earn. The workflow is the table stakes.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/EZwWi2-WH6o" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/250-yc-s26-skills" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/250-yc-s26-skills&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ycombinator</category>
      <category>dailybuild2026</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building Prism: a 100%-offline 3D visualizer for your AI coding history</title>
      <dc:creator>Harish Kotra (he/him)</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:35:11 +0000</pubDate>
      <link>https://dev.to/harishkotra/building-prism-a-100-offline-3d-visualizer-for-your-ai-coding-history-3ocl</link>
      <guid>https://dev.to/harishkotra/building-prism-a-100-offline-3d-visualizer-for-your-ai-coding-history-3ocl</guid>
      <description>&lt;p&gt;&lt;em&gt;How a stdlib-only Python server, a vendored three.js snapshot, and a strict "metadata-only" contract turned scattered agent session stores into an explorable city — with no network, no database, and no build step.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: my agents work while I sleep, and I have no idea what they did
&lt;/h2&gt;

&lt;p&gt;I run half a dozen AI coding harnesses — Claude Code, Codex, OpenCode,&lt;br&gt;
Antigravity, Pi, Goose… Each one keeps its own session store, in its own&lt;br&gt;
format, in its own corner of my home directory: JSONL files here, SQLite&lt;br&gt;
databases there, proprietary blobs somewhere else. I had hundreds of sessions and zero answers to simple questions: &lt;em&gt;which project eats my tokens? When do I actually work? What does each model cost me? What tools get used?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The existing "solutions" were dashboards that wanted my API keys, my prompts, my code — uploaded to somebody's cloud. That felt backwards. The data was already on my disk. What I needed was a &lt;strong&gt;reader&lt;/strong&gt;, not another service.&lt;/p&gt;

&lt;p&gt;So I built Prism: open a localhost page, fly through your coding life in 3D.&lt;/p&gt;

&lt;p&gt;Every design decision follows from three constraints: &lt;strong&gt;offline&lt;/strong&gt; (zero runtime&lt;br&gt;
network), &lt;strong&gt;read-only&lt;/strong&gt; (never touch the harness stores), &lt;strong&gt;metadata-only&lt;/strong&gt;&lt;br&gt;
(never read a prompt, reply, or file).&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture: boring on purpose
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foap04jdurgdg9gzc9nx6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foap04jdurgdg9gzc9nx6.png" alt="Architecture: boring on purpose" width="799" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The whole backend is &lt;strong&gt;Python stdlib only&lt;/strong&gt; — &lt;code&gt;http.server&lt;/code&gt;, &lt;code&gt;sqlite3&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;argparse&lt;/code&gt;, &lt;code&gt;unittest&lt;/code&gt;. No &lt;code&gt;pip install&lt;/code&gt; step exists, which means the install script is basically "make a venv, download one JS file, run":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./install.sh          &lt;span class="c"&gt;# venv + vendor three.js (SHA256-pinned) + serve&lt;/span&gt;
python3 server.py &lt;span class="nt"&gt;--export&lt;/span&gt; prism-data.json &lt;span class="nt"&gt;--no-browser&lt;/span&gt;   &lt;span class="c"&gt;# headless dump&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one network call in the project's life happens at install time&lt;br&gt;
(&lt;code&gt;curl&lt;/code&gt; three.js r128, hash-verified), and CI enforces the invariant forever:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make offline-check    &lt;span class="c"&gt;# grep web/ for remote URLs → fail the build if any&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frontend is vanilla JS with no bundler. Each lens is a single file exporting &lt;code&gt;{ create }&lt;/code&gt;, and &lt;code&gt;create(payload)&lt;/code&gt; returns &lt;code&gt;{ group, desc, legend, camera, tick, raycast, dispose }&lt;/code&gt;. That tiny interface is why nine lenses were cheap to build: Cityscape, Galaxy, Constellation, Timeline, Energy, Calendar, Tools, Hours, Models.&lt;/p&gt;

&lt;h2&gt;
  
  
  The heart: a schema that says "no"
&lt;/h2&gt;

&lt;p&gt;The most important file is &lt;code&gt;core/schema.py&lt;/code&gt;. Every reader normalizes into one document, and the schema gate rejects it before serving if anything is off — &lt;code&gt;NaN&lt;/code&gt;/&lt;code&gt;Infinity&lt;/code&gt;, negatives, boolean-as-number, malformed nested lists. A drifting upstream store format degrades to &lt;code&gt;status: unavailable&lt;/code&gt; in the UI, never a 500.&lt;/p&gt;

&lt;p&gt;A session (trimmed from a real export):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"harness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude_code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flybench"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-sonnet-4-6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"startedAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-07-09T14:02:11+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"durationSec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;412&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"fresh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12011&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3402&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15413&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"costUsd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"costKnown"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's absent: no prompts, no replies, no code. That absence is load-bearing — it's what makes "read my agent history" feel safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Readers: the extension surface
&lt;/h2&gt;

&lt;p&gt;Each harness is one ~100-line file. The OpenCode reader shows the whole&lt;br&gt;
pattern — including the discipline that matters most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;con&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqlite_ro&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# file:path?mode=ro + 500ms busy_timeout, ALWAYS
&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SELECT id, name, worktree FROM project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# metadata tables
&lt;/span&gt;    &lt;span class="c1"&gt;# …never the message/part tables. Those hold conversation content.
&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;con&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three helpers do the heavy lifting across all 28 readers: &lt;code&gt;iter_jsonl&lt;/code&gt;&lt;br&gt;
(skips malformed lines), &lt;code&gt;sqlite_ro&lt;/code&gt; (read-only open), and&lt;br&gt;
&lt;code&gt;finalize_reader_payload&lt;/code&gt; (newest-first sort, 500-session cap, totals, meta).&lt;/p&gt;

&lt;p&gt;Adding a harness is a 30-line file plus one fixture test — several Tier-2&lt;br&gt;
readers (Cursor, Zed, Ollama…) sit stubbed in &lt;code&gt;readers/&lt;/code&gt; waiting for someone with the app installed to map the real layout.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rendering 500+ sessions at 60fps with 2017-era three.js
&lt;/h2&gt;

&lt;p&gt;The trick is old and unglamorous: &lt;strong&gt;&lt;code&gt;InstancedMesh&lt;/code&gt;&lt;/strong&gt;. Cityscape draws every session building in a single draw call; per-frame updates only rewrite instance matrices and colors for the time-scrub reveal. Same for the timeline river (markers + spikes), the calendar wall, the treemap. r128 is pinned because it's the last version that loads as one global script with zero modules — no bundler, no import maps, works from &lt;code&gt;file://&lt;/code&gt; if needed.&lt;/p&gt;

&lt;p&gt;The fun engineering was anti-blob work. Real histories clump: same-day&lt;br&gt;
sessions stack exactly, daily-driver projects form complete graphs, one tool (&lt;code&gt;exec_command&lt;/code&gt;, 12k calls) eats the scene. Each lens got a declutter rule: timeline fans collisions into per-harness lanes, constellation keeps only ≥2-shared-day edges capped at top-2 per star, tools are log-scaled, treemap areas stay linear so "what you see is what you pay."&lt;/p&gt;

&lt;p&gt;My favorite lens technically is Cityscape's true-shape maps: each of the 10 cities is hand-charted polygon data — Manhattan's diagonal island, the Seine S-curve with Île de la Cité, Sydney's branching harbour — with districts rejection-sampled onto real land, clustered downtown. No downloaded tiles, no textures; water is a plane, streets are line segments, landmarks are primitives. A subway map, not a satellite photo — and instantly readable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Lessons worth stealing
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Constraints are features.&lt;/strong&gt; "No network, no writes, no content" killed entire categories of bugs (auth, sync, PII leaks) before they were born — and it's the whole pitch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A validation gate beats defensive rendering.&lt;/strong&gt; One strict schema check means nine lenses never think about malformed data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stdlib-only is a superpower for tools like this.&lt;/strong&gt; The install is 30 seconds because there's nothing to install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful degradation &amp;gt; coverage.&lt;/strong&gt; 8 verified readers + 20 honest "unavailable" stubs beats 28 half-broken parsers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prism is MIT-licensed. If you run coding agents, your history is already on your disk — go look at it: &lt;code&gt;./install.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ZojWDi7rYGw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Code &amp;amp; more: &lt;a href="https://www.dailybuild.xyz/project/249-prism" rel="noopener noreferrer"&gt;https://www.dailybuild.xyz/project/249-prism&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  How this looks
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23egpjtt4asa0a3epktj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23egpjtt4asa0a3epktj.png" alt="How this looks 1" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lhiientrpgfs7hcdxnn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lhiientrpgfs7hcdxnn.png" alt="How this looks 2" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkslodiu0ihoq4kwlmpv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhkslodiu0ihoq4kwlmpv.png" alt="How this looks 3" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg672dy7qlhtghphdgede.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg672dy7qlhtghphdgede.png" alt="How this looks 4" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9yv85awiv81j1g9z8agv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9yv85awiv81j1g9z8agv.png" alt="How this looks 5" width="799" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flz68d8x54wch1jymtdho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flz68d8x54wch1jymtdho.png" alt="How this looks 6" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>dailybuild2026</category>
    </item>
  </channel>
</rss>
