<?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: stepinto.dev</title>
    <description>The latest articles on DEV Community by stepinto.dev (@stepinto).</description>
    <link>https://dev.to/stepinto</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%2F4186945%2F27bce636-dcde-4827-83aa-0f2f1f2a14e9.png</url>
      <title>DEV Community: stepinto.dev</title>
      <link>https://dev.to/stepinto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stepinto"/>
    <language>en</language>
    <item>
      <title>The EU Has an AI Act, the UK Has Regulators and No Bill</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Fri, 13 Nov 2026 07:20:59 +0000</pubDate>
      <link>https://dev.to/stepinto/the-eu-has-an-ai-act-the-uk-has-regulators-and-no-bill-2fdn</link>
      <guid>https://dev.to/stepinto/the-eu-has-an-ai-act-the-uk-has-regulators-and-no-bill-2fdn</guid>
      <description>&lt;p&gt;Say you are building an AI product on a model you did not train, calling it through another company's API, and you sell the product in the UK and the EU. Which legislation applies to it, and who is responsible under it, you or the company that trained the model?&lt;/p&gt;

&lt;p&gt;Both regimes apply, and for the product the responsible party is you. The EU AI Act calls you the provider of an AI system, and the company that trained the model has separate duties of its own. The UK has no AI act, so there the answer comes from the laws and regulators that already covered your sector. The rest of this post sets out both regimes and where your product sits in each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two approaches to the same product
&lt;/h2&gt;

&lt;p&gt;The EU has one regulation of 113 articles that applies in every member state: &lt;a href="https://eur-lex.europa.eu/eli/reg/2024/1689/oj" rel="noopener noreferrer"&gt;Regulation (EU) 2024/1689&lt;/a&gt;, amended in July by the Digital Omnibus, &lt;a href="https://eur-lex.europa.eu/eli/reg/2026/1744/oj" rel="noopener noreferrer"&gt;Regulation (EU) 2026/1744&lt;/a&gt;. The UK regulates AI through the laws and regulators it already had.&lt;/p&gt;

&lt;p&gt;The AI Act applies to UK companies with EU customers. Article 2(1)(a) covers providers who place a system on the EU market "irrespective of whether those providers are established or located within the Union or in a third country", and Article 2(1)(c) covers providers and deployers in a third country "where the output produced by the AI system is used in the Union". A product built in the UK with customers in Germany is covered, and software sold in both markets has to satisfy both regimes.&lt;/p&gt;

&lt;p&gt;The difference shows up in the work. The Act's duties are written as requirements on the system: what it logs, what it tells the user, how its output is marked, and what documentation exists before it goes on the market. The UK's duties arrive through whichever regulator covers the sector, and the one part written at that level of detail is the rules on automated decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the legislation as an engineer
&lt;/h2&gt;

&lt;p&gt;I am an engineer and not a lawyer, and I am reading this legislation to find out what it asks me to build. Since &lt;a href="https://stepinto.dev/posts/ai-law-on-fridays/" rel="noopener noreferrer"&gt;last week&lt;/a&gt;, Fridays on this blog are for that: one article of the AI Act, or one of the UK's rules, each week, verbatim, with every clause annotated for the engineers who have to build to it. A single article is hard to follow without knowing where it sits, so this week is an overview of both regimes, with a sample of the annotated format near the end.&lt;/p&gt;

&lt;p&gt;What the Act asks of you depends on who you are, what your system does, and when.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who you are
&lt;/h2&gt;

&lt;p&gt;Almost every duty in the Act is addressed to a provider or a deployer, so the two definitions in Article 3 decide most of what follows. A provider develops an AI system, or has one developed, and places it on the market or puts it into service under its own name or trademark. A deployer uses an AI system under its authority, outside personal non-professional activity. Both definitions are annotated in full further down.&lt;/p&gt;

&lt;p&gt;You can be a provider without training a model. If your product calls a model API and shows the result to people under your name, you developed an AI system and put it on the market. The company that trained the model is the provider of a general-purpose AI model, which is a separate role with separate duties.&lt;/p&gt;

&lt;p&gt;There are four more roles. An authorised representative is a person or company in the EU with a written mandate to act for a provider outside it. An importer brings a non-EU company's system onto the EU market. A distributor is anyone else in the supply chain who makes a system available. A product manufacturer puts an AI system on the market inside its own product. The Act calls all six "operators" (Article 3(8)).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the system does
&lt;/h2&gt;

&lt;p&gt;The Act sorts AI systems by what they are used for, and the duties rise with the risk.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prohibited practices&lt;/strong&gt; (Article 5). Banned outright since 2 February 2025: manipulative techniques that cause harm, social scoring, untargeted scraping of faces, emotion recognition at work and in schools, and several more. The Omnibus adds two more: systems that generate non-consensual intimate imagery, and systems that generate child sexual abuse material.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-risk systems&lt;/strong&gt; (Article 6). There are two ways a system becomes high-risk. The first is being a safety component of a product already regulated under the laws in Annex I, such as medical devices, machinery and toys. The second is a use listed in Annex III, which has eight areas, among them biometrics, employment, education, law enforcement and access to essential services, which is where credit scoring sits. Most of the Act's length is about these systems: risk management, data governance, logging, human oversight, technical documentation and a conformity assessment before the system goes on the market.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency duties&lt;/strong&gt; (Article 50). Chatbots have to say they are AI, generated content has to carry a machine-readable mark, and deep fakes have to be labelled. This applies whatever the risk tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General-purpose AI models&lt;/strong&gt; (Articles 51 to 55). Duties on the companies that train the models: technical documentation, a summary of the training content, a copyright policy, and more for models classified as carrying systemic risk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Everything else.&lt;/strong&gt; No obligations beyond AI literacy (Article 4), with voluntary codes of conduct encouraged under Article 95.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fines follow the same order (Article 99). A prohibited practice costs up to €35 million or 7% of worldwide annual turnover, whichever is higher. Most other breaches, Article 50 included, cost up to €15 million or 3%. Giving a regulator incorrect or misleading information costs up to €7.5 million or 1%. For SMEs, and since the Omnibus for small mid-caps too, it is whichever figure is lower.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it applies
&lt;/h2&gt;

&lt;p&gt;The Act arrives in stages, and the Omnibus moved the biggest one. The high-risk obligations for Annex III systems were due on 2 August 2026 and now apply from 2 December 2027.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;What applies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2 February 2025&lt;/td&gt;
&lt;td&gt;The prohibitions in Article 5, and AI literacy in Article 4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 August 2025&lt;/td&gt;
&lt;td&gt;Duties for general-purpose AI models, the governance bodies, and penalties&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27 July 2026&lt;/td&gt;
&lt;td&gt;The Omnibus in force. Article 4 now asks providers and deployers to "take measures to support the development of AI literacy" and no longer to ensure it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 August 2026&lt;/td&gt;
&lt;td&gt;The Act's general application date, which brings in Article 50, and the Commission's power to fine model providers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 December 2026&lt;/td&gt;
&lt;td&gt;The end of the grace period for marking content from generative systems already on the market on 2 August, and the two new prohibitions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 August 2027&lt;/td&gt;
&lt;td&gt;Each member state has at least one AI regulatory sandbox running. General-purpose models already on the market before August 2025 have to comply&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 December 2027&lt;/td&gt;
&lt;td&gt;High-risk obligations for Annex III systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 August 2028&lt;/td&gt;
&lt;td&gt;High-risk obligations for systems in Annex I products&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 August 2030&lt;/td&gt;
&lt;td&gt;Older high-risk systems intended for use by public authorities have to comply&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Commission fills in the detail with guidelines and codes of practice. For Article 50 there are &lt;a href="https://digital-strategy.ec.europa.eu/en/library/guidelines-transparency-obligations-providers-and-deployers-ai-systems" rel="noopener noreferrer"&gt;guidelines&lt;/a&gt; published on 20 July 2026 and a &lt;a href="https://digital-strategy.ec.europa.eu/en/policies/code-practice-ai-generated-content" rel="noopener noreferrer"&gt;Code of Practice&lt;/a&gt; finalised on 10 June. For general-purpose models there is a code of practice from July 2025. The guidelines on what counts as high-risk under Article 6 were published as &lt;a href="https://digital-strategy.ec.europa.eu/en/library/draft-commission-guidelines-classification-high-risk-ai-systems" rel="noopener noreferrer"&gt;a draft&lt;/a&gt; on 19 May 2026 and were still a draft when I checked on 12 November.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who enforces it
&lt;/h2&gt;

&lt;p&gt;Each member state names market surveillance authorities, and they enforce the Act for most systems. The AI Office, inside the Commission, has exclusive power over general-purpose AI models. The Omnibus gave it more: it is now also the only enforcer for AI systems built on a general-purpose model by the same provider, with exceptions that include products under Annex I and critical infrastructure, and for AI systems that are part of a very large online platform or search engine (Article 75(1)). A company that builds on its own model and sells across the EU is regulated by the AI Office rather than a national authority.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UK has no AI act
&lt;/h2&gt;

&lt;p&gt;The UK's approach comes from a white paper of March 2023, &lt;a href="https://www.gov.uk/government/publications/ai-regulation-a-pro-innovation-approach/white-paper" rel="noopener noreferrer"&gt;A pro-innovation approach to AI regulation&lt;/a&gt;. It gives existing regulators five principles to apply in their own sectors: safety, security and robustness; appropriate transparency and explainability; fairness; accountability and governance; contestability and redress. The principles are not law. The ICO applies them to personal data, the FCA to financial services, the CMA to competition, Ofcom to communications and the MHRA to medical devices, each using the powers it already had.&lt;/p&gt;

&lt;p&gt;A bill has been discussed for two years and has not appeared. The King's Speech of 13 May 2026 contained no AI bill. Parliament's Joint Committee on Human Rights recommended one in September, and this month the AI minister said the government would look at regulation for the most advanced models if it turned out to be needed. I could find no bill, consultation or timetable.&lt;/p&gt;

&lt;p&gt;Three things in the UK are more definite: the new rules on automated decisions, the AI Security Institute and copyright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automated decisions.&lt;/strong&gt; Section 80 of the &lt;a href="https://www.legislation.gov.uk/ukpga/2025/18/section/80" rel="noopener noreferrer"&gt;Data (Use and Access) Act 2025&lt;/a&gt; replaced Article 22 of the UK GDPR with Articles 22A to 22D, in force since 5 February 2026. They cover decisions "based solely on automated processing", meaning with "no meaningful human involvement", that have a legal or similarly significant effect on the person. Article 22B restricts such decisions when they use special category data. Article 22C sets the safeguards for the rest: tell the person, let them make representations, let them obtain human intervention, and let them contest the decision. The ICO's updated guidance went out for consultation on 31 March 2026 and has not been published in final form. For an engineer, 22C is the closest thing the UK has to an AI Act article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI Security Institute.&lt;/strong&gt; It tests frontier models before and after release and publishes research. It was renamed from the AI Safety Institute in February 2025 and is now part of the Cabinet Office. It is not set up in law and has no powers over anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copyright.&lt;/strong&gt; UK law has no exception for commercial text and data mining, so training on copyrighted work for commercial use needs a licence. The Data (Use and Access) Act required the government to report on copyright and AI, and the &lt;a href="https://www.gov.uk/government/publications/report-and-impact-assessment-on-copyright-and-artificial-intelligence" rel="noopener noreferrer"&gt;report and impact assessment&lt;/a&gt; came out on 18 March 2026. They changed nothing in the law, and the government no longer has a preferred option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Earlier posts
&lt;/h2&gt;

&lt;p&gt;I wrote about these two regimes in February, from the architecture side. &lt;a href="https://stepinto.dev/posts/compliance-as-code-two-europes/" rel="noopener noreferrer"&gt;Architecting for Two Europes with Compliance-as-Code&lt;/a&gt; builds one platform for both, with the jurisdiction split in middleware, and its logging and oversight patterns are the ones to build towards for the high-risk date. &lt;a href="https://stepinto.dev/posts/fine-tuning-under-european-law/" rel="noopener noreferrer"&gt;What It Actually Takes to Fine-Tune an LLM in Europe&lt;/a&gt; covers copyright and text and data mining on both sides, and when fine-tuning makes you the provider of a new general-purpose model. The March copyright report left UK law as it was, so that post's advice to license for the UK and treat that as the standard for both markets still holds. &lt;a href="https://stepinto.dev/posts/sovereign-inference-stack/" rel="noopener noreferrer"&gt;A Practical Guide to the European Sovereign Inference Stack&lt;/a&gt; routes requests between the EU GDPR and the UK GDPR, and &lt;a href="https://stepinto.dev/posts/securing-open-source-ai-supply-chain/" rel="noopener noreferrer"&gt;Securing the Open Source AI Supply Chain&lt;/a&gt; maps the technical documentation in Article 11 and Annex IV to MLOps tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How each article will be read
&lt;/h2&gt;

&lt;p&gt;From next Friday each post takes one article in the format below: the text verbatim, with a plain reading and notes for every clause. Select any part of the text and they open underneath. The two samples here show the format, and neither is a full treatment of its article.&lt;/p&gt;

&lt;p&gt;The first is the pair of definitions from Article 3 that answers the question at the top of this post.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; act-excerpt act="article-3" &amp;gt;}}&lt;/p&gt;

&lt;p&gt;The second is the first paragraph of Article 50, which has applied since August and is the part of the Act most products built on a model API meet first.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; act-excerpt act="article-50" paras="1" &amp;gt;}}&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If your product calls a model API and goes out under your name, you are the provider of an AI system under the Act. The company that trained the model has its own duties, and they do not replace yours.&lt;/li&gt;
&lt;li&gt;With customers in the EU, the Act applies to you wherever the company is based.&lt;/li&gt;
&lt;li&gt;Article 50 already applies. Generative systems that were on the market on 2 August have until 2 December 2026 to mark their content.&lt;/li&gt;
&lt;li&gt;Check your use case against Annex III before 2 December 2027, when the high-risk obligations start for those systems.&lt;/li&gt;
&lt;li&gt;In the UK, find out which regulator covers your sector. If the product makes decisions about people with no meaningful human involvement, Article 22C applies: tell the person, let them make representations, let them obtain human intervention, and let them contest the decision.&lt;/li&gt;
&lt;li&gt;Training on copyrighted work for commercial use in the UK needs a licence.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Friday
&lt;/h2&gt;

&lt;p&gt;Next Friday, 20 November, is Article 50 in full, with every clause selectable and the Commission's guidelines and the Code of Practice cited by paragraph. Article 6, which decides whether a system is high-risk, follows on 27 November. Each post takes one article and works out what it asks of the software.&lt;/p&gt;

&lt;p&gt;This is an engineer's reading and no substitute for legal advice.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>europe</category>
      <category>regulation</category>
      <category>compliance</category>
    </item>
    <item>
      <title>A Claude Client in One Rust File, and the First Dependency</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Thu, 12 Nov 2026 07:21:42 +0000</pubDate>
      <link>https://dev.to/stepinto/a-claude-client-in-one-rust-file-and-the-first-dependency-3b85</link>
      <guid>https://dev.to/stepinto/a-claude-client-in-one-rust-file-and-the-first-dependency-3b85</guid>
      <description>&lt;p&gt;Bash, C# and Go all had HTTP and TLS in their standard libraries, but unfortunately Rust doesn't. &lt;a href="https://stepinto.dev/claude-hot-path/#spec" rel="noopener noreferrer"&gt;The spec&lt;/a&gt; lets a language without them use one named library for the transport, and that library counts towards size, so this is the first time the leaderboard has to price a dependency. The transport is &lt;code&gt;ureq&lt;/code&gt; with rustls, the parsing is &lt;code&gt;serde_json&lt;/code&gt;, and together they bring in thirty-six crates. The result is still the smallest thing on the board so far.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/AndyCross/claude-hot-path/tree/main/clients/rust" rel="noopener noreferrer"&gt;github.com/AndyCross/claude-hot-path&lt;/a&gt;, and the leaderboard is at &lt;a href="https://stepinto.dev/claude-hot-path/#leaderboard" rel="noopener noreferrer"&gt;/claude-hot-path/&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the transport
&lt;/h2&gt;

&lt;p&gt;The usual answer for HTTP in Rust is &lt;code&gt;reqwest&lt;/code&gt;, which is async and brings tokio and hyper with it. For a client that makes two sequential requests and streams the reply to stdout, async buys nothing and costs a runtime. &lt;code&gt;ureq&lt;/code&gt; is the blocking alternative, it defaults to rustls so there is no OpenSSL to link, and version 3 exposes the response body as a &lt;code&gt;Read&lt;/code&gt;, which is all the SSE loop needs.&lt;/p&gt;

&lt;p&gt;One configuration line matters. By default &lt;code&gt;ureq&lt;/code&gt; turns a 4xx or 5xx response into an &lt;code&gt;Err&lt;/code&gt;, which would throw away the body that holds the API's error message. The spec wants that message on stderr, so the agent is built with status errors off and the client reads the status itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ureq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ureq&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;config_builder&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;http_status_as_error&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;span class="nf"&gt;build&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;into&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;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;The body reader goes into a &lt;code&gt;BufReader&lt;/code&gt; and &lt;code&gt;lines()&lt;/code&gt; does the rest. The &lt;code&gt;let else&lt;/code&gt; on the prefix strip is doing the same job as the &lt;code&gt;continue&lt;/code&gt; in Go, skipping &lt;code&gt;event:&lt;/code&gt; lines, pings and blank separators:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BufReader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body_mut&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;as_reader&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;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lines&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="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap_or_default&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="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"data: "&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="k"&gt;continue&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="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serde_json&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unwrap_or_else&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;eprintln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"error: bad event: {e}"&lt;/span&gt;&lt;span class="p"&gt;)&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;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="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_u64&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;unwrap_or&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;as&lt;/span&gt; &lt;span class="kt"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block_start"&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="n"&gt;content&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="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content_block"&lt;/span&gt;&lt;span class="p"&gt;]&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="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;partial&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="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&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="p"&gt;}&lt;/span&gt;
        &lt;span class="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block_delta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"text_delta"&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;let&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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;unwrap_or&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="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_bytes&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;unwrap&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;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&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;unwrap&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="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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;unwrap_or&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="nf"&gt;to_string&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;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Value&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&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="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input_json_delta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"partial_json"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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;unwrap_or&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="n"&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="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block_stop"&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"tool_use"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_empty&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;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serde_json&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&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;unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;json!&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="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="kc"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"message_delta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stop_reason"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;as_str&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&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="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;&lt;code&gt;serde_json::Value&lt;/code&gt; with index syntax is the Rust equivalent of C#'s &lt;code&gt;JsonNode&lt;/code&gt;, and I used it for the same reason: the shapes are dynamic and a typed struct per event would be longer than the client. Indexing a &lt;code&gt;Value&lt;/code&gt; that lacks the key returns &lt;code&gt;Null&lt;/code&gt; rather than panicking, so the &lt;code&gt;as_str().unwrap_or&lt;/code&gt; chains read a missing field as empty and move on.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;out&lt;/code&gt; is a locked &lt;code&gt;stdout&lt;/code&gt;, flushed after every delta. Rust's &lt;code&gt;Stdout&lt;/code&gt; is line-buffered, and a text delta rarely ends in a newline, so without the flush the words would sit in the buffer until the stream ended. The harness catches that: the runner records when the first visible byte reached stdout and the mock records when it wrote each chunk, and a client whose first text lands after the stream finished fails conformance. With the flush removed it failed by a millisecond, and with it there are eleven to spare.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing the library does not have
&lt;/h2&gt;

&lt;p&gt;Timezones are the gap again. Rust's standard library has no zoneinfo reader and &lt;code&gt;chrono-tz&lt;/code&gt; or &lt;code&gt;jiff&lt;/code&gt; would be a third dependency carrying a copy of the database. The platform has one at &lt;code&gt;/usr/share/zoneinfo&lt;/code&gt;, in the TZif format from RFC 8536, and reading it is sixty lines: skip the version 1 block, take the 64-bit transition times from the version 2 block, find the last transition before now, and apply its offset. The civil date comes out of Howard Hinnant's days-to-date algorithm, which is nine lines of integer arithmetic. It is more code than any other part of the client, and it is part of why the turnaround is 0.7 ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it measures as
&lt;/h2&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;Rust&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;C# Native AOT&lt;/th&gt;
&lt;th&gt;bash and curl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Artefact&lt;/td&gt;
&lt;td&gt;1.4 MB&lt;/td&gt;
&lt;td&gt;5.8 MB&lt;/td&gt;
&lt;td&gt;5.4 MB&lt;/td&gt;
&lt;td&gt;3.7 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;2.0 MB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2.4 MB&lt;/td&gt;
&lt;td&gt;23.7 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ship&lt;/td&gt;
&lt;td&gt;3.3 MB&lt;/td&gt;
&lt;td&gt;5.8 MB&lt;/td&gt;
&lt;td&gt;7.8 MB&lt;/td&gt;
&lt;td&gt;23.7 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start, median of 100&lt;/td&gt;
&lt;td&gt;1.1 ms&lt;/td&gt;
&lt;td&gt;1.7 ms&lt;/td&gt;
&lt;td&gt;3.0 ms&lt;/td&gt;
&lt;td&gt;7.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turnaround, median of 100&lt;/td&gt;
&lt;td&gt;0.7 ms&lt;/td&gt;
&lt;td&gt;1.2 ms&lt;/td&gt;
&lt;td&gt;1.4 ms&lt;/td&gt;
&lt;td&gt;19.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client total, median of 100&lt;/td&gt;
&lt;td&gt;3.2 ms&lt;/td&gt;
&lt;td&gt;4.8 ms&lt;/td&gt;
&lt;td&gt;7.1 ms&lt;/td&gt;
&lt;td&gt;35.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS&lt;/td&gt;
&lt;td&gt;3 MB&lt;/td&gt;
&lt;td&gt;6 MB&lt;/td&gt;
&lt;td&gt;7 MB&lt;/td&gt;
&lt;td&gt;11 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;184&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Third-party crates or packages&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;rustc 1.99.1, linux/arm64, Apple M4 Max under Docker. Release profile with &lt;code&gt;opt-level = "z"&lt;/code&gt;, fat LTO, one codegen unit, &lt;code&gt;panic = "abort"&lt;/code&gt; and symbols stripped.&lt;/p&gt;

&lt;p&gt;The binary is 1.4 MB with a complete TLS stack inside it, and rustls and its crypto provider are most of that. The 2.0 MB runtime is glibc, &lt;code&gt;libgcc_s&lt;/code&gt; and the loader, the same bill .NET paid, and a musl build would fold most of that into the binary. I haven't measured whether the total would come out smaller. I have left it dynamic because the Debian image is what someone would actually deploy, and because the leaderboard already has one static entry to compare against.&lt;/p&gt;

&lt;p&gt;Three megabytes of peak memory is half of Go's, the cold start is the fastest so far, and the turnaround is the fastest on the board by a clear margin, though it didn't start out that way: the first version shelled out to &lt;code&gt;date&lt;/code&gt; for the timezone and measured three milliseconds between turns, and when I replaced the process with the TZif reader above it only came down to about two. The rest was ureq: it resolves the hostname on every request, before it checks the connection pool, so each turn paid a DNS round trip to Docker's resolver for a socket it then reused. Pointing the client at the mock's IP instead of its name was how I found that, and the fix is the caching resolver, plugged in through &lt;code&gt;Agent::with_parts&lt;/code&gt;. Thirty-six crates is the cost, and it is the first non-zero number in that column. Go and .NET got their HTTP stacks from a standard library maintained by one organisation. Rust's comes from thirty-six crates with separate maintainers, which is more to audit and keep patched. The column is there so you can weigh that yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run 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/AndyCross/claude-hot-path
&lt;span class="nb"&gt;cd&lt;/span&gt; claude-hot-path
./bench/bench.sh rust
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker and Python 3, no key. The release build takes about fifteen seconds in the container. Next is Zig, which has HTTP and TLS in its standard library and a reputation for binaries measured in kilobytes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>rust</category>
      <category>sse</category>
    </item>
    <item>
      <title>Your Name on a Seville Street Sign, Painted by Rule</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Wed, 11 Nov 2026 12:01:13 +0000</pubDate>
      <link>https://dev.to/stepinto/type-a-word-get-the-wall-1kob</link>
      <guid>https://dev.to/stepinto/type-a-word-get-the-wall-1kob</guid>
      <description>&lt;p&gt;I have been building a series of pages where the computer generates the pattern: &lt;a href="https://stepinto.dev/posts/tiles-painted-by-rule/" rel="noopener noreferrer"&gt;tin-glazed tiles painted by rule&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/cut-tile-and-poured-cement/" rel="noopener noreferrer"&gt;cut tile and poured cement&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/heraldry-by-rule/" rel="noopener noreferrer"&gt;heraldry&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/knotwork-by-rule/" rel="noopener noreferrer"&gt;knotwork&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/a-piece-that-breaks-another-piece/" rel="noopener noreferrer"&gt;a piece that breaks another piece&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/light-on-brick-paint-on-cream/" rel="noopener noreferrer"&gt;brick in raking light&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/the-seventeen-ways-a-wall-can-repeat/" rel="noopener noreferrer"&gt;the seventeen ways a wall can repeat&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/iron-and-silk/" rel="noopener noreferrer"&gt;iron and silk&lt;/a&gt; and &lt;a href="https://stepinto.dev/posts/clay-in-clay/" rel="noopener noreferrer"&gt;clay in clay&lt;/a&gt;. Every one of those grows a repeat from a seed. This one starts from a word, which does not repeat: &lt;a href="https://stepinto.dev/art/letras.html" rel="noopener noreferrer"&gt;Azulejos de letras&lt;/a&gt; is the Seville street sign, with the street name typed in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx71c4esfduxslw0dfznv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx71c4esfduxslw0dfznv.webp" alt="A plaque of white glazed tiles reading CALLE BETIS in heavy blue serif capitals painted across the tile joints, inside a border of blue vine tiles with grape leaves, bunches and tendrils, mitred at the corners, on a whitewashed wall" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Calle Betis. The word painted across the joints, a vine of leaves and grapes round the edge, cut at forty-five degrees in the corners, all in the one cobalt.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every street in Seville is named on a plaque of tiles. They are cobalt capitals on a white tin glaze, painted across the grid as one word so the joints run through the letters, with a border of vines or scrolls round the lot that turns the corners. The potteries of Triana, across the river, made them by the thousand through the early twentieth century, and made the shop fronts too, so a bodega or a pharmacy announced itself in the same letters as the street it stood on. The letters are a nineteenth-century serif with a lot of contrast between the thick strokes and the thin ones, painted by hand onto the raw glaze, so no two are quite alike and the blue pools a little where the brush stopped.&lt;/p&gt;

&lt;h2&gt;
  
  
  A typeface made of strokes
&lt;/h2&gt;

&lt;p&gt;The page does not use a font. Each capital is a short list of strokes in a box one cap-height tall: a stem, which is thick; a hairline, which is thin; a thick diagonal; a bowl, which is an arc; a dot; and a tilde for the Ñ. An A is a hairline up the left, a thick stroke down the right and a hairline across. A B is a stem, two bowls and three hairlines joining them. An S is two bowls back to back. That is the whole alphabet, the digits, Ñ and the accented vowels a Spanish street needs, in about sixty lines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7n410zdd8n9dh8t3fep.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk7n410zdd8n9dh8t3fep.webp" alt="A wide plaque of white tiles showing every letter from A to Z, then Ñ and the accented vowels, the digits 0 to 9, a full stop, comma, hyphen, slash and the Nº sign, all in blue serif capitals with slab serifs" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The whole typeface. Every glyph is a list of strokes; the serifs, the contrast and the wobble come from the rules, not the glyph.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The contrast is a rule rather than a drawing. A bowl's width depends on the angle of the point along it: full stem width where the tangent is upright, hairline width at the top and bottom, which is how a Didone letter is cut and what makes an O look like an O rather than a ring. Every stem and every foot gets a bracketed slab serif, a short bar with a small curve into the stroke, drawn by the same rule whatever the letter. Change one number and every serif on the wall changes with it.&lt;/p&gt;

&lt;p&gt;The brush comes next. Each stroke becomes a polygon, the engine's wobble runs along its edges in proportion to the hand slider, and the colour goes on the way the tile pages' washes do: a dark pass slid a little down and right, where the glaze pools against the raised edge of a stroke, then the cobalt, then a thinner pass slid the other way. A heavier brush widens the stems and leaves the hairlines alone. The same letter on two tiles is painted twice from two seeds, so the plaque never has two identical Ls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Across the joints, or one to a tile
&lt;/h2&gt;

&lt;p&gt;The street signs paint the word across the tiles as if the grid were not there, and that is the default: the letters are set at their own widths, each line centred on a row of tiles, and the joints are drawn back over them afterwards so a stem can stop at a grout line and carry on the other side. Letters painted this way are set a little heavier, the way the real ones are. Some shop fronts did it the other way, one letter to a tile or two, and the page does that as well; when it pairs, it pairs within a word, so a space is a blank tile rather than a tile with half a word on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgr9jd5wfi41dakxhcyb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqgr9jd5wfi41dakxhcyb.webp" alt="A plaque reading CERÁMICA SANTA ANA with two letters on most tiles, in a border of blue acanthus scrolls with green fronds and yellow centres that bend round the corners" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Two letters to a tile, the shop-front way, in the santa ana colours with the acanthus border.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A drop cap is the first letter at nearly twice the height, with the rest of the first line hugging it and the second line beside it. In the one-to-a-tile lettering it takes a tile four times the size. It is the same glyph at a larger cap height, and the serifs scale with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7jvk6znu4ggk6qewq97g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7jvk6znu4ggk6qewq97g.webp" alt="An aged cream plaque reading TABERNA SOL Y SOMBRA in dark manganese capitals, the T a drop cap four tiles big, inside a border of linked ochre and black rings, with crazing across several tiles" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A drop cap, manganese on an old cream, with age on. The chain border's rings run through each other across every joint.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The border is a repeat after all
&lt;/h2&gt;

&lt;p&gt;The one thing on the plaque that does repeat is the border, and it repeats in one dimension, the way &lt;a href="https://stepinto.dev/art/rejeria.html" rel="noopener noreferrer"&gt;Rejería&lt;/a&gt;'s frieze runs along its rail. A border tile is drawn once, in a square with the outside of the plaque at the top and the words at the bottom, and then laid along every side turned a quarter each time, so the design always faces inward.&lt;/p&gt;

&lt;p&gt;The corner was the hard part. A rosette on a square tile is what I did first, and it stops the border dead at each corner, where the real plaques carry the vine round. So the corner tile is the border tile bent: the strip is warped into a quarter-annulus about the tile's inner corner, the distance from that corner becoming how far into the border a point sits and the angle round it becoming how far along, so the strip's right edge becomes the corner's right edge, its left edge becomes the corner's bottom edge, and the vine turns through a quarter between them and meets its neighbours on both sides. That is a general operation on any border tile, so it went into the engine as &lt;code&gt;bendCorner&lt;/code&gt;, and the outer corner the bend does not reach is filled with fresh glaze.&lt;/p&gt;

&lt;p&gt;Most of the real plaques are squarer than that. Their borders are cut the way a picture frame is, at forty-five degrees, with the strip running into the corner from both sides and meeting on the diagonal. That is the other corner, &lt;code&gt;mitreCorner&lt;/code&gt;, and it is the default: the strip clipped to the triangle above the diagonal, then turned three quarters and clipped to the triangle below it. The bent corner stays as an option for the plaques with a rounded frame.&lt;/p&gt;

&lt;p&gt;The two borders the real plaques wear are the vine and the acanthus. The vine is &lt;a href="https://stepinto.dev/art/jerez.html" rel="noopener noreferrer"&gt;Jerez&lt;/a&gt;'s: a stem waving across the tile, a five-lobed leaf on each crest with its veins left in the glaze, a bunch of grapes hanging in each trough, a smaller leaf and bunch the other way, and tendrils curling off the joints. The acanthus is the baroque cartouche: a thick C-scroll curling in on itself with leaf fronds off its back, one turning each way per tile, and a bud where they meet at the joint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzhjtn1crzicocy53cctr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzhjtn1crzicocy53cctr.webp" alt="A close view of a plaque reading PARRA with a border of blue vine tiles, each with a large five-lobed leaf, a hanging bunch of grapes, a small leaf and bunch, and tendrils" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The vine, close. Leaves, bunches and tendrils in one blue, the veins and the lights left in the glaze.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqfref7y44t0wc949axz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffqfref7y44t0wc949axz.webp" alt="A close view of a plaque reading ROLEO with a border of blue acanthus scrolls, each tile holding two C-scrolls turning toward each other with fronds along their backs" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The acanthus, close. Two scrolls to a tile, fronds along the outside of each sweep, a bud at the joint.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8j9mdnute4viigmpy5e0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8j9mdnute4viigmpy5e0.webp" alt="A plaque reading PLAZA DEL SALVADOR in heavy blue capitals painted across the joints, inside a border where two blue ribbons cross each other along every side, on an ochre rendered wall" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Plaza del Salvador on albero render, with the ribbon. Both ribbons meet their own ends at the joint, so the border is continuous round the plaque.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The rest are simpler. One is a flat yellow band with a sprig on every tile, a five-petal flower with a white heart, a leaf each side and dots round it, for the shop signs that put one letter to a tile inside a strip. Those signs use half-height border tiles, two to each letter tile, so the border has a size of its own, whole or half, and with the band on, every letter tile gets a small curl and dots in its corners so the letters and the band read as one set. Another is a chain of rings, where the ring on the joint passes through the ring in the middle of each neighbour. There is a ribbon in two blues, which is two sine waves half a turn apart, and the last is the ruled lines on their own. Each one has to meet itself at the tile's edge, so the design is built from the edge inward, the same constraint Fajalauza's branch had and the reason it looks right at any length.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3asjx5p9l0n09bic6ob.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh3asjx5p9l0n09bic6ob.webp" alt="A plaque reading CASA, one blue letter to a white tile with small blue curls in each corner, inside a narrow yellow band of half-height tiles each carrying a small blue flower with green leaves and white dots, mitred at the corners" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Casa. One letter to a tile with a curl in each corner, inside a band of half tiles with a sprig on each, in the santa ana colours, the corners mitred.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing not drawn by rule
&lt;/h2&gt;

&lt;p&gt;The Portuguese plaques go further than a border of tiles. The whole frame is one painting, a rococo cartouche of C-scrolls and shells that ignores the grid entirely and is cut into tiles afterwards. There is no repeat in it to write a rule for, so for that one border the page does what Fajalauza and Delft did for their birds and figures and uses a drawing: an image model was asked for a flat cobalt cartouche on white with an empty centre, and the page lays plain tiles, multiplies the painting over them so its white disappears into the glaze, draws the joints back through it and paints the word in the middle. It is the only border on the page that is a picture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn10d1fuvwb7j7pp2g3t9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn10d1fuvwb7j7pp2g3t9.webp" alt="A plaque of white tiles with a painted cobalt rococo frame of scrolls and shells round the edge, the word JEPPERS in heavy blue capitals across the middle, the tile joints running through the painting" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The cartouche: a painted frame cut into tiles, the one border on the page that is a picture rather than a rule.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The wall behind it is whitewash, an ochre render, a blue wash or a field of plain glazed tiles, and the plaque stands proud of the render with a shadow. Age crazes a few tiles, warms some and chips a corner, and stains the lime.&lt;/p&gt;

&lt;h2&gt;
  
  
  A banner tool
&lt;/h2&gt;

&lt;p&gt;I built this one partly because I wanted it. The text goes in the query string, so a link like &lt;a href="https://stepinto.dev/art/letras.html?text=STEP%20INTO&amp;amp;border=roleo&amp;amp;field=anil" rel="noopener noreferrer"&gt;letras.html?text=STEP%20INTO&amp;amp;border=roleo&amp;amp;field=anil&lt;/a&gt; is a banner, and the save button gives you the PNG.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4lzorkv436m14pdqzmj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi4lzorkv436m14pdqzmj.webp" alt="A plaque reading STEP INTO in heavy blue capitals with an acanthus scroll border on a blue-washed wall" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This site's name, as a plaque on añil. The ?text= query string makes it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The typeface stayed on the page, because nothing else has wanted letters painted by rule yet, and a thing moves into the engine the second time a page asks for it. Sutton Hoo is next, garnet cloisonné in gold cells, the cutter's third customer.&lt;/p&gt;

</description>
      <category>art</category>
      <category>generative</category>
      <category>canvas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>A Faster C# Claude Client, by Breaking the Rules</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Wed, 11 Nov 2026 07:21:39 +0000</pubDate>
      <link>https://dev.to/stepinto/a-faster-c-claude-client-by-breaking-the-rules-51aj</link>
      <guid>https://dev.to/stepinto/a-faster-c-claude-client-by-breaking-the-rules-51aj</guid>
      <description>&lt;p&gt;Last week I wrote the &lt;a href="https://stepinto.dev/posts/claude-hot-path-csharp/" rel="noopener noreferrer"&gt;C# client&lt;/a&gt; for the hot path the way Microsoft's templates say to write it: &lt;code&gt;HttpClient&lt;/code&gt;, &lt;code&gt;System.Text.Json&lt;/code&gt; nodes, a &lt;code&gt;StreamReader&lt;/code&gt;, &lt;code&gt;Console.Out&lt;/code&gt;, Native AOT with the size-oriented settings. That was the point of it, since the series measures what each language gives you out of the box, and out of the box C# landed at 7.1 ms on the path against Go's 4.4. I put the extra down to the loader resolving glibc and the AOT runtime starting up, and left it there, though it nagged at me all week.&lt;/p&gt;

&lt;p&gt;I wanted to know how much of that gap is C# and how much is the defaults. So this is the same spec, the same mock, the same harness, with the "out of the box" rule removed and one of the leaderboard's own rules bent: HTTP is no longer coming from the platform. It comes from a hundred lines of hand-written HTTP/1.1 over a socket that libc opened. The result is 3.35 ms, a millisecond under Go, and the fastest row on the board as it stands. It has its own row on the &lt;a href="https://stepinto.dev/claude-hot-path/" rel="noopener noreferrer"&gt;leaderboard&lt;/a&gt;, next to the stock C# row rather than replacing it, because the two answer different questions.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/AndyCross/claude-hot-path/tree/main/clients/csharp-tuned" rel="noopener noreferrer"&gt;github.com/AndyCross/claude-hot-path&lt;/a&gt;, 344 lines once blanks and comments are taken out, against 102 for the stock client.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the seven milliseconds went
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;strace -f -r&lt;/code&gt; on the stock client against the mock is the whole diagnosis. The run is 1,729 lines of syscalls. Before the first request leaves the process it has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loaded glibc and libm through the dynamic loader, the 2.5 MB the leaderboard's runtime column charges it for,&lt;/li&gt;
&lt;li&gt;started nine threads: a finalizer, a signal handler, a timer, a thread pool gate and three pool workers, and two &lt;code&gt;.NET Sockets&lt;/code&gt; threads with an epoll instance each, because &lt;code&gt;HttpClient&lt;/code&gt; is asynchronous all the way down even when nothing else in the program is,&lt;/li&gt;
&lt;li&gt;opened seven probe sockets to learn which address families and options the kernel supports,&lt;/li&gt;
&lt;li&gt;resolved the name and connected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the last byte of the second reply it writes a newline, returns from &lt;code&gt;Main&lt;/code&gt;, and the runtime shuts down: the &lt;code&gt;HttpClient&lt;/code&gt; is disposed, the connection pool is torn down, the finalizer runs, the threads are joined. The harness's tail column, from the mock finishing to the process exiting, was 2.9 ms for C# and 1.8 for Go.&lt;/p&gt;

&lt;p&gt;None of that is slow in absolute terms. It is a few hundred microseconds here and there, and a service that lives for a day would never see it. The leaderboard counts all of it, so this client removes what it can.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP by hand, over libc's socket
&lt;/h2&gt;

&lt;p&gt;The spec says HTTP and TLS come from the platform where the platform has them, and .NET has them. &lt;code&gt;HttpClient&lt;/code&gt; is a connection pool, a request pipeline, HTTP/2 and HTTP/3 negotiation, proxy detection, cookie handling and an async engine, which suits a service and is far more than a process needs when it will make two requests on one connection and exit. So this client does not use it. It calls &lt;code&gt;getaddrinfo&lt;/code&gt;, &lt;code&gt;socket&lt;/code&gt;, &lt;code&gt;connect&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; directly, through &lt;code&gt;LibraryImport&lt;/code&gt;, and the project file's &lt;code&gt;DirectPInvoke&lt;/code&gt; item makes those links resolve at build time instead of through &lt;code&gt;dlopen&lt;/code&gt; at run time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Native&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;StringMarshalling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StringMarshalling&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;getaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AddrInfo&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;AddrInfo&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;res&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="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;protocol&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="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;len&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="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nuint&lt;/span&gt; &lt;span class="n"&gt;len&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="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;nint&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nuint&lt;/span&gt; &lt;span class="n"&gt;len&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="nc"&gt;LibraryImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EntryPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"_exit"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nc"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hints&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;AddrInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="cm"&gt;/* AF_INET */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SockType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="cm"&gt;/* SOCK_STREAM */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;AddrInfo&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;res&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="nf"&gt;getaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToString&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;hints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;res&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cannot resolve "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&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;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;SockType&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mh"&gt;0x80000&lt;/span&gt; &lt;span class="cm"&gt;/* SOCK_CLOEXEC */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Protocol&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="n"&gt;fd&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;one&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="nf"&gt;setsockopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="cm"&gt;/* IPPROTO_TCP */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="cm"&gt;/* TCP_NODELAY */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;one&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="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="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nc"&gt;AddrLen&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fd&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;span class="n"&gt;fd&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;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;freeaddrinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cannot connect to "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;host&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hints ask for IPv4 only, so the resolver sends one query instead of an A and an AAAA. On top of the descriptor sits a &lt;code&gt;Stream&lt;/code&gt; subclass whose &lt;code&gt;Read&lt;/code&gt; and &lt;code&gt;Write&lt;/code&gt; are &lt;code&gt;read(2)&lt;/code&gt; and &lt;code&gt;write(2)&lt;/code&gt; and nothing else, so that &lt;code&gt;SslStream&lt;/code&gt; has something to wrap when the base URL is &lt;code&gt;https&lt;/code&gt;. Over plain &lt;code&gt;http&lt;/code&gt;, which is what the mock speaks, that class is the entire transport.&lt;/p&gt;

&lt;p&gt;The HTTP itself is the smallest thing that correctly reads what Go's &lt;code&gt;net/http&lt;/code&gt; sends back when a handler flushes: a status line, headers, and a chunked body. The reader finds &lt;code&gt;\r\n\r\n&lt;/code&gt;, reads three digits out of the status line, scans the headers for &lt;code&gt;transfer-encoding: chunked&lt;/code&gt; or &lt;code&gt;content-length&lt;/code&gt;, and then hands the body back in decoded pieces, one chunk at a time, straight out of its own buffer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Next&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&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;default&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;chunked&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;remaining&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;nl&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"\r\n"&lt;/span&gt;&lt;span class="n"&gt;u8&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;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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Fill&lt;/span&gt;&lt;span class="p"&gt;(&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;span class="n"&gt;done&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;return&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sizeLine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;semi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sizeLine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s1"&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;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="no"&gt;ASCII&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;semi&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="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;sizeLine&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sizeLine&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;semi&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="nc"&gt;NumberStyles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;HexNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;nl&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&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="n"&gt;done&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;return&lt;/span&gt; &lt;span class="k"&gt;default&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nc"&gt;Fill&lt;/span&gt;&lt;span class="p"&gt;(&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;span class="n"&gt;done&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;return&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remaining&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="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nc"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&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;remaining&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="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;n&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;remaining&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunked&lt;/span&gt;&lt;span class="p"&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="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Fill&lt;/span&gt;&lt;span class="p"&gt;(&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;start&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="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;done&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;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;piece&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 request goes out as one &lt;code&gt;write&lt;/code&gt;: the fixed header bytes, built once in the constructor, then the key, the content length and the body. The connection stays open for the second turn. There is no pool, no timeout, no redirect handling and no HTTP/2, and if the API ever sends something this reader does not understand it will throw an &lt;code&gt;IOException&lt;/code&gt; rather than cope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading events without building a tree
&lt;/h2&gt;

&lt;p&gt;The stock client parses each &lt;code&gt;data:&lt;/code&gt; line into a &lt;code&gt;JsonNode&lt;/code&gt; and walks it with indexers. That allocates a tree per event and a string per property, and then unescapes the text into another string before &lt;code&gt;Console.Out&lt;/code&gt; encodes it back to UTF-8. This client runs a &lt;code&gt;Utf8JsonReader&lt;/code&gt; over the line's bytes, matches property names with &lt;code&gt;ValueTextEquals&lt;/code&gt; against UTF-8 literals, and copies the one value it cares about into a scratch buffer with &lt;code&gt;CopyString&lt;/code&gt;, which unescapes straight into bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;r&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;Utf8JsonReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&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="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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;kind&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="n"&gt;index&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="n"&gt;valLen&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="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sub&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="n"&gt;id&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="n"&gt;name&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TokenType&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;JsonTokenType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EndObject&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block_start"&lt;/span&gt;&lt;span class="n"&gt;u8&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block_delta"&lt;/span&gt;&lt;span class="n"&gt;u8&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"message_delta"&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;3&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="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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"index"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetInt32&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"content_block"&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"delta"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TokenType&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nc"&gt;JsonTokenType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EndObject&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"type"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&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="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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&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="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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&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="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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"partial_json"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;valLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CopyString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scratch&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ValueTextEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"stop_reason"&lt;/span&gt;&lt;span class="n"&gt;u8&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TokenType&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;JsonTokenType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetString&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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Skip&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="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="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Read&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;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Skip&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A text delta then goes from the scratch buffer to file descriptor 1 in one &lt;code&gt;write(2)&lt;/code&gt;, and is appended to the block's own byte buffer for the echo. Tool input fragments are appended to a second buffer as they arrive and never parsed until the tool runs, when a second &lt;code&gt;Utf8JsonReader&lt;/code&gt; pulls out the one &lt;code&gt;timezone&lt;/code&gt; field. The request body on the way out is a &lt;code&gt;Utf8JsonWriter&lt;/code&gt; over an &lt;code&gt;ArrayBufferWriter&lt;/code&gt;, with the tool declaration as a &lt;code&gt;u8&lt;/code&gt; literal written through &lt;code&gt;WriteRawValue&lt;/code&gt; and the assembled tool input written the same way, so the fragments the model sent are the bytes the model gets back.&lt;/p&gt;

&lt;p&gt;The streaming check in the harness still passes, since there is no buffer between the socket and stdout to forget to flush.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Console, no Uri, no shutdown
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Console.Out&lt;/code&gt; on Unix sets up an encoder, a synchronised writer and a &lt;code&gt;StreamWriter&lt;/code&gt; with a buffer that has to be flushed after every delta. &lt;code&gt;Console.In&lt;/code&gt; does the same in the other direction. This client wraps descriptors 0 and 1 in the same &lt;code&gt;read&lt;/code&gt;/&lt;code&gt;write&lt;/code&gt; stream the socket uses, and the prompt is read as bytes and written back into the request body as bytes, never becoming a &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;System.Uri&lt;/code&gt; is a lot of machinery to split one string in three places. The base URL is parsed with &lt;code&gt;IndexOf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And when the last reply is in and the newline is written, the client calls &lt;code&gt;_exit(0)&lt;/code&gt; instead of returning from &lt;code&gt;Main&lt;/code&gt;, which skips the runtime's orderly shutdown. I expected that to show in the tail column, and it did not. The same static build with the &lt;code&gt;_exit&lt;/code&gt; line commented out measured 3.43 ms to 3.35, which is inside the run-to-run noise, and its tail was 0.97 ms to 1.0. With no sockets engine, no pool and no thread pool, there is nothing left for the shutdown to do, and the kernel's teardown of the process is the whole tail either way. The line stays because it says plainly what the program wants, and it costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The project file
&lt;/h2&gt;

&lt;p&gt;The stock client's &lt;code&gt;.csproj&lt;/code&gt; is tuned for size. This one is tuned for speed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;OptimizationPreference&amp;gt;&lt;/span&gt;Speed&lt;span class="nt"&gt;&amp;lt;/OptimizationPreference&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;InvariantGlobalization&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/InvariantGlobalization&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;UseSystemResourceKeys&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/UseSystemResourceKeys&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;EventSourceSupport&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/EventSourceSupport&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StackTraceSupport&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/StackTraceSupport&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ConcurrentGarbageCollection&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/ConcurrentGarbageCollection&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;IlcDisableReflection&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/IlcDisableReflection&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;IlcDehydrate&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/IlcDehydrate&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StaticExecutable&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/StaticExecutable&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;PositionIndependentExecutable&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/PositionIndependentExecutable&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StaticOpenSslLinking&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/StaticOpenSslLinking&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ConcurrentGarbageCollection&lt;/code&gt; off means the runtime does not start a background GC thread for a heap that will never be collected. &lt;code&gt;IlcDehydrate&lt;/code&gt; off stops the compiler packing the runtime's type data into a compact form that the process unpacks before &lt;code&gt;Main&lt;/code&gt;, at the price of a larger binary. &lt;code&gt;IlcDisableReflection&lt;/code&gt; drops the reflection stack, which this client never touches. I measured those three plus &lt;code&gt;Speed&lt;/code&gt; on the untouched stock code before changing a line of it, and they bought nothing: 6.31 ms became 6.37. Whatever the stock client is spending its time on, it is not the GC thread or the type data. They stay in the project file because they are the right settings for this program, even though they did not move the number.&lt;/p&gt;

&lt;p&gt;The last three are the ones that change what ships. &lt;code&gt;StaticExecutable&lt;/code&gt; on the &lt;code&gt;linux-musl-arm64&lt;/code&gt; runtime identifier links the whole thing against musl with no dynamic loader at all, so the image is &lt;code&gt;FROM scratch&lt;/code&gt; and the runtime column on the leaderboard reads zero. &lt;code&gt;StaticOpenSslLinking&lt;/code&gt; pulls OpenSSL into the binary too, because a static musl executable cannot &lt;code&gt;dlopen&lt;/code&gt; the &lt;code&gt;libssl&lt;/code&gt; the TLS stack would otherwise load, and that is where most of the 10.9 MB comes from. It rebuilds the crypto shim from source during publish, so the SDK image needs &lt;code&gt;cmake&lt;/code&gt;, &lt;code&gt;openssl-libs-static&lt;/code&gt; and &lt;code&gt;zlib-static&lt;/code&gt; installed before &lt;code&gt;dotnet publish&lt;/code&gt; will succeed. &lt;code&gt;PositionIndependentExecutable&lt;/code&gt; off is the one I would not have found without running &lt;code&gt;file&lt;/code&gt; on the output: the first static build was &lt;code&gt;static-pie&lt;/code&gt;, which means the process relocates itself on every start, and for a binary this size that is a measurable number of page faults before the first instruction of &lt;code&gt;Main&lt;/code&gt;. Turning it off gives a plain &lt;code&gt;statically linked&lt;/code&gt; ELF that the kernel maps and runs.&lt;/p&gt;

&lt;p&gt;The finished &lt;code&gt;strace&lt;/code&gt; of a run is 260 lines, down from 1,729. One thread is created, the finalizer, because the runtime insists. After that it is one DNS query, one socket, one &lt;code&gt;connect&lt;/code&gt;, one &lt;code&gt;write&lt;/code&gt; per request, one &lt;code&gt;write&lt;/code&gt; per text delta, and &lt;code&gt;exit_group&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each step bought
&lt;/h2&gt;

&lt;p&gt;Every row is the same spec, the same mock and 100 runs, measured one after another on the same evening. The Go and stock C# rows on the leaderboard were measured on earlier evenings and read a little slower (4.42 and 7.09); the machine was in a better mood tonight, and the comparison that matters is between rows in this table.&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;Cold start&lt;/th&gt;
&lt;th&gt;Turnaround&lt;/th&gt;
&lt;th&gt;Tail&lt;/th&gt;
&lt;th&gt;On the path&lt;/th&gt;
&lt;th&gt;Artefact&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Peak RSS&lt;/th&gt;
&lt;th&gt;Lines&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stock C#, as posted&lt;/td&gt;
&lt;td&gt;3.33 ms&lt;/td&gt;
&lt;td&gt;0.92 ms&lt;/td&gt;
&lt;td&gt;1.95 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.31 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;5.6 MB&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;7.2 MB&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock code, tuned project file&lt;/td&gt;
&lt;td&gt;3.19 ms&lt;/td&gt;
&lt;td&gt;0.94 ms&lt;/td&gt;
&lt;td&gt;2.02 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.37 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.5 MB&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;8.1 MB&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewritten, glibc, dynamic&lt;/td&gt;
&lt;td&gt;2.07 ms&lt;/td&gt;
&lt;td&gt;0.76 ms&lt;/td&gt;
&lt;td&gt;1.20 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.25 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;7.4 MB&lt;/td&gt;
&lt;td&gt;2.5 MB&lt;/td&gt;
&lt;td&gt;6.6 MB&lt;/td&gt;
&lt;td&gt;344&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewritten, static musl, orderly exit&lt;/td&gt;
&lt;td&gt;1.59 ms&lt;/td&gt;
&lt;td&gt;0.69 ms&lt;/td&gt;
&lt;td&gt;0.97 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.43 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.9 MB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4.3 MB&lt;/td&gt;
&lt;td&gt;343&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewritten, static musl, &lt;code&gt;_exit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1.67 ms&lt;/td&gt;
&lt;td&gt;0.73 ms&lt;/td&gt;
&lt;td&gt;1.00 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.35 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;10.9 MB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4.3 MB&lt;/td&gt;
&lt;td&gt;344&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go, for reference&lt;/td&gt;
&lt;td&gt;2.01 ms&lt;/td&gt;
&lt;td&gt;0.70 ms&lt;/td&gt;
&lt;td&gt;1.30 ms&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.11 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;6.1 MB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6.3 MB&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rewrite is worth two milliseconds, and most of that is cold start: no sockets engine, no thread pool, no probe sockets, no &lt;code&gt;Console&lt;/code&gt;, no &lt;code&gt;Uri&lt;/code&gt;. On glibc, still dynamically linked, it is within a fraction of a millisecond of Go. Going static is worth most of another millisecond, takes the loader out of the cold start and the shared libraries out of the ship size, and halves the memory. The project file knobs and &lt;code&gt;_exit&lt;/code&gt; are inside the noise.&lt;/p&gt;

&lt;p&gt;3.35 ms on the path puts this client at the top of the board as it stands, with Go at 4.42 and the stock C# client it started from at 7.09. The languages still to come may beat it, and the C# row that matters for the series is still the stock one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it shows
&lt;/h2&gt;

&lt;p&gt;I would not ship this client. I would ship the stock one: it is 102 lines, it uses the HTTP stack that handles proxies, HTTP/2, timeouts and certificate revocation, and nobody has to know what a chunk size line looks like to maintain it. This one has &lt;code&gt;AddrInfo&lt;/code&gt; laid out by hand to match a C struct, a handful of constants from &lt;code&gt;socket.h&lt;/code&gt; with comments saying which is which, and a reader that will throw on a trailer it has not seen. It is 10.9 MB because it carries its own OpenSSL, against 5.6 for the stock binary plus the 2.5 of libraries that binary borrows from the image.&lt;/p&gt;

&lt;p&gt;The gap between the stock C# client and Go came from what the defaults build on top of the runtime. The bare start of the static binary, &lt;code&gt;execve&lt;/code&gt; to &lt;code&gt;exit_group&lt;/code&gt; with nothing to do, is level with Go's, and every piece on top of it can be taken away in C# with no unsafe code outside the P/Invoke class and the two stream methods that call it, because the pieces underneath (&lt;code&gt;Utf8JsonReader&lt;/code&gt;, &lt;code&gt;LibraryImport&lt;/code&gt;, &lt;code&gt;Stream&lt;/code&gt;, Native AOT's static linking) are all in the box too. That is a different answer to the one the first C# post gave, and I think it is the more useful one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run 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/AndyCross/claude-hot-path
&lt;span class="nb"&gt;cd&lt;/span&gt; claude-hot-path
./bench/bench.sh csharp-tuned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker and Python 3, no key. The build runs in the Alpine SDK image and takes about a minute, most of it compiling the OpenSSL shim. The TLS path was checked against the real API from the image with an invalid key, which returns the 401 the spec's error case wants, over HTTPS, through the same hand-written reader.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Clay in Clay</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Mon, 09 Nov 2026 12:52:55 +0000</pubDate>
      <link>https://dev.to/stepinto/clay-in-clay-49j1</link>
      <guid>https://dev.to/stepinto/clay-in-clay-49j1</guid>
      <description>&lt;p&gt;I have been building a series of pages where the computer generates the pattern: &lt;a href="https://stepinto.dev/posts/tiles-painted-by-rule/" rel="noopener noreferrer"&gt;tin-glazed tiles painted by rule&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/cut-tile-and-poured-cement/" rel="noopener noreferrer"&gt;cut tile and poured cement&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/heraldry-by-rule/" rel="noopener noreferrer"&gt;heraldry&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/knotwork-by-rule/" rel="noopener noreferrer"&gt;knotwork&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/a-piece-that-breaks-another-piece/" rel="noopener noreferrer"&gt;a piece that breaks another piece&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/light-on-brick-paint-on-cream/" rel="noopener noreferrer"&gt;brick in raking light&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/the-seventeen-ways-a-wall-can-repeat/" rel="noopener noreferrer"&gt;the seventeen ways a wall can repeat&lt;/a&gt; and &lt;a href="https://stepinto.dev/posts/iron-and-silk/" rel="noopener noreferrer"&gt;iron and silk&lt;/a&gt;. We've been travelling round Europe, mostly round Spain, borrowing from the art and the history of each place. This one is from England, which has its own tile, made by pressing a pattern into one clay and filling it with another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmbsce6tf1h73evitqu26.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmbsce6tf1h73evitqu26.webp" alt="A floor of red clay tiles, each with a buff roundel lettered HONI SOIT QUI MAL Y PENSE round a buff lion passant guardant with a blue tongue, black rings and buff lilies where four tiles meet, and blue foils on the joints" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Encaustic, the town hall preset. Buff and black and blue clay pressed into a red body, the leopard of England traced from the drawing Leonor's shields use, and the Garter motto round it.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rosa y Granada
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/art/rosaygranada.html" rel="noopener noreferrer"&gt;Rosa y Granada&lt;/a&gt; came first, in October, and never had a post of its own. Catherine of Aragon arrived in England in 1501 with the pomegranate of Granada as her badge, and for twenty-odd years it was carved and painted beside the Tudor rose wherever the king's works went. The page puts the two on a floor in the English medieval way, which is the inlaid tile. A red earthenware tile was pressed with a wooden stamp, the recess filled with white pipeclay slip, the surplus scraped off level, and the whole glazed with lead and fired. The lead glaze turns the white yellow and the red a deep brown. There are two colours and no brush, so the only hand in it is the stamp, cut once and pressed a thousand times, and the slip, which goes on thick in one place and thin in another.&lt;/p&gt;

&lt;p&gt;On the page, an inlaid tile is two operations. A recess is filled with another clay, and a line of the body is left standing between two fills. Every painter on the page is one or the other. The rose is five heart-shaped petals inlaid in slip with a smaller rose of five more left standing in the red body inside it, which is how you get a red rose on a white one with only one colour to fill with. The pomegranate is the one traced for &lt;a href="https://stepinto.dev/art/leonor.html" rel="noopener noreferrer"&gt;Leonor&lt;/a&gt;: its silhouette goes in as slip and the drawing's black lines are reserved in red.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six hundred years later
&lt;/h2&gt;

&lt;p&gt;Inlaid tiles died out in the sixteenth century and came back in the nineteenth. Samuel Wright of Shelton patented a way of making them in 1830, Herbert Minton bought a share of the patent in 1835, and A. W. N. Pugin wanted floors for the Gothic churches he was building and, from the late 1840s, for the new Palace of Westminster. Minton made them to his designs. They were called encaustic, from the Greek for burnt in, and within a generation they were in every church porch, town hall and hallway in the country.&lt;/p&gt;

&lt;p&gt;The idea is the same and nearly everything around it changed. The body was pressed in a plaster mould and the hollows filled with liquid clay. There were four or five colours instead of two: red, buff, black, blue, sometimes white or green. The surface was left unglazed. The inlay went deep enough to outlast a century of boots, where the slip on a lot of medieval tiles was thin enough to walk off. And the tiles came out of the factory by the hundred thousand, all one size, to be set out with a string line by a contractor rather than bedded in lime by a mason.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6yg4btt89vcbmmkjoko.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb6yg4btt89vcbmmkjoko.webp" alt="Three floors side by side. Left, red-brown medieval tiles with yellow roses, pomegranates and roundels in wide grey lime joints. Middle, buff Victorian tiles with black quatrefoils filled with red lilies, blue and red rings and small foils, in fine dark joints. Right, cream cement tiles with burgundy octagonal frames and green and burgundy rings" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Westminster from Rosa y Granada, westminster from Encaustic, and zaguán from Hidráulico. A medieval floor, a Victorian one, and a Barcelona one from a generation later.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One file, two recipes
&lt;/h2&gt;

&lt;p&gt;The medieval material lived in Rosa y Granada's page, because nothing else needed it. Encaustic does, and a material that two pages share belongs in a file of its own rather than in the engine, which every tile page imports. The engine takes a material object with hooks for how a shape is filled, how the ground is laid, how far a tile strays from its cell, what the joint is and what age does, and it doesn't need to know that two of its pages are made of clay.&lt;/p&gt;

&lt;p&gt;So the stamp operations, the rose and the lily moved into &lt;code&gt;clay-assets/clay.js&lt;/code&gt;, and the material became two recipes. Each one says what the clay looks like up close (the colour functions are trimmed here):&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="no"&gt;RECIPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// white slip in red clay under lead, 1250 to 1550&lt;/span&gt;
  &lt;span class="n"&gt;medieval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mottle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mottleOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1501&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="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;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;speckOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1254&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;light&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 glaze pools against the edge of the inlay as a dark hairline&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;hexA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;poolW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.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="c1"&gt;// clays pressed into clay in a mould, 1840 to 1900&lt;/span&gt;
  &lt;span class="n"&gt;victorian&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mottle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;mottleOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1840&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="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="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;speck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;speckOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1851&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&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="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// two plastic clays pressed together bleed a hair into each other&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;hexA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;poolW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The medieval one is heavier everywhere: more mottle where the slip went on thick and thin, more iron coming up through the glaze, a dark line where the lead pooled against the inlay. The Victorian one is flatter and finer, and the line at the edge of an inlay is the inlay's own colour, a hair of one clay pressed into the next. Around those sit the two materials. The medieval one has a glossy surface, a wide lime joint and tiles turned and shifted by hand. The Victorian one is matte, with a thin cement joint and tiles that sit almost exactly in their cells.&lt;/p&gt;

&lt;p&gt;Moving the material is a refactor of a page that people can link to by seed, so it had to change nothing. I hashed every preset's canvas and the card thumbnails before the move and after it, and they came out byte for byte the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cousins of Hidráulico
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/art/hidraulico.html" rel="noopener noreferrer"&gt;Hidráulico&lt;/a&gt; was the first page to use the material hook: the Azulejería engine with the glaze swapped for pigmented cement, the way every hallway in Spain was floored between 1880 and 1960. Put the three side by side and Encaustic sits between them. Like the medieval tile it is clay inlaid in clay and fired. Like the cement tile it is pressed, matte, many-coloured, made in a factory and laid by the square metre, and it wears rather than crazes. A cement tile's colours meet with no outline at all, because the brass divider lifts out and leaves them butted together. A Victorian tile's colours meet with that faint line where two wet clays touched, and a medieval tile's meet with a dark one where the glaze pooled.&lt;/p&gt;

&lt;p&gt;The wear shows the difference best. Age on the medieval floor takes the glaze off along the walked line and the slip off the middle of each tile, so the pomegranates fade to brown ghosts. Age on the Victorian floor dulls it, works dirt into the joints, scratches it and cracks a few tiles straight across, but the pattern stays.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2keeydeomz62q3x10ei.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj2keeydeomz62q3x10ei.webp" alt="Two worn floors side by side. Left, medieval tiles where the yellow slip of the pomegranates has worn away to brown in the middle of each tile, with scuffs and cracks. Right, Victorian red tiles with buff lilies in lozenges and black foils, dulled and cracked in places, with dark dirty joints, the pattern still sharp" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nave from Rosa y Granada and porch from Encaustic, both with wear on. The medieval slip walks off; the Victorian inlay doesn't.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gothic nouns
&lt;/h2&gt;

&lt;p&gt;The motifs are the Victorian Gothic ones. Quatrefoil puts a four-lobed Gothic foil round the corner where four tiles meet, lobes on the diagonals, a lily in each lobe pointing out, and a circle round the lot. Each foil is a ring of circles whose outer arcs run from one cusp to the next, and the band is that outline at two radii filled even-odd, with a field of another clay inside when the seed wants one. Diaper is Pugin's lilies in a net of lozenges, the lozenges' points at the middle of each edge so the net runs on across the floor.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm24tblqfj5ug6q634ywn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm24tblqfj5ug6q634ywn.webp" alt="A buff floor of Victorian tiles: large black quatrefoils edged in blue holding four red lilies pointing out from the centre, ringed in red, with small blue-ringed foils between them and red foils on the joints" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The westminster preset. Four tiles make each quatrefoil; the small foils between them are where four far corners meet.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F047w8gd9wjt9vr3ha4k8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F047w8gd9wjt9vr3ha4k8.webp" alt="Red tiles with gold lilies set in a net of double gold lozenge lines, black foils edged in gold at the points where the lozenges meet, and gold-ringed foils with black centres in alternate lozenges" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The pugin preset: gold on red, lilies in lozenges.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A word has a right way up
&lt;/h2&gt;

&lt;p&gt;The other two motifs were the hard ones, because they have a right way up. The engine's module repeat mirrors every other tile, which is how a design that only fills a quarter becomes a pattern four tiles wide. Mirror a rose and it is still a rose. Mirror a lion and he faces the other way, and mirror a motto and it is mirror writing. The single repeat lays every tile the same way up, but it builds the tile itself from four mirrored quarters, so even there a word drawn in the design comes out backwards in two of them.&lt;/p&gt;

&lt;p&gt;So the lion and the motto are drawn once, whole, by a painter that works out where it is. The engine paints the four quarters in a fixed order, and the last of them is turned half round, so it is the only one that isn't mirrored. The painter reads the canvas transform, does nothing on the first three quarters, and on the last one throws the transform away and draws the roundel square to the tile in device pixels:&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="nf"&gt;medal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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="n"&gt;g&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTransform&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="no"&gt;W&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;quarter&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;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&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="no"&gt;W&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;2&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&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="no"&gt;W&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;2&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;quarter&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;d&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="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="n"&gt;cx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;quarter&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="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;k&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;hypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quarter&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r&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="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&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;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTransform&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="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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&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 band, the motto, the field and the device, upright&lt;/span&gt;
  &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing else can stop the wall itself mirroring or turning the tile, so those two motifs lay the single repeat whatever you choose, and the repeat menu changes to say so, which is also how a Victorian tiler would have laid a lettered tile.&lt;/p&gt;

&lt;p&gt;The lettering is MedievalSharp, which is near enough to the Lombardic capitals Pugin used. A motto runs clockwise round the band from a small lozenge at the bottom, so it reads up the left side and across the top. Each letter is measured, the gaps are spread so the motto fills the ring, and a short motto goes round twice rather than spacing its letters out to the width of a tile. My first try used an uncial face, which draws H like a d, and a town hall floor that said HONI SOIT read rather differently. Where a Victorian chancel floor had scripture, mine says LOVE IS ENOUGH, after William Morris's poem of 1872, and the other roundels carry NULLIUS IN VERBA, SAPERE AUDE, NO GODS NO MASTERS and E PUR SI MUOVE. The lion is Leonor's traced leopard of England in buff, with its claws and tongue in blue on the palettes that have a blue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwa9nr65f29yvlyfsgyf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxwa9nr65f29yvlyfsgyf.webp" alt="A checkerboard of plain black tiles and blue tiles, each blue tile carrying a buff roundel lettered LOVE IS ENOUGH twice round, with a buff quatrefoil holding four lilies inside it and quarter rings and red foils at the corners" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The chancel preset. Words round a foil on a blue ground, laid with plain black, the cheapest tile a Victorian church could buy.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/art/encaustic.html" rel="noopener noreferrer"&gt;Encaustic&lt;/a&gt; has six presets: westminster, pugin, town hall, chancel, a cream and chocolate Dorset church, and a porch with a hundred and fifty years of boots on it. The advanced tab has the four motifs, five palettes, the four repeats, and a mould slider that sets how loose the press was, which for a Victorian tile is not very. Double-tap for a new design. &lt;a href="https://stepinto.dev/art/rosaygranada.html" rel="noopener noreferrer"&gt;Rosa y Granada&lt;/a&gt; is unchanged apart from sharing its material file with Encaustic.&lt;/p&gt;

&lt;p&gt;Azulejos de letras is next: Seville's blue-on-white street-name tiles, where the lettering is the whole tile rather than a band round the edge.&lt;/p&gt;

</description>
      <category>art</category>
      <category>generative</category>
      <category>canvas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How a World Model Differs From an LLM, and Why You Want Both</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Mon, 09 Nov 2026 07:24:02 +0000</pubDate>
      <link>https://dev.to/stepinto/how-a-world-model-differs-from-an-llm-and-why-you-want-both-5768</link>
      <guid>https://dev.to/stepinto/how-a-world-model-differs-from-an-llm-and-why-you-want-both-5768</guid>
      <description>&lt;p&gt;The &lt;a href="https://stepinto.dev/posts/world-models-104/" rel="noopener noreferrer"&gt;last post&lt;/a&gt; ended by saying the next one would put the heater model next to an LLM. This series has drawn that line twice already. The &lt;a href="https://stepinto.dev/posts/world-models-001/" rel="noopener noreferrer"&gt;first post&lt;/a&gt; said that if you ask a chatbot what happens when you turn the heater on, "you'll get a fluent, plausible story about the room warming up", and the &lt;a href="https://stepinto.dev/posts/world-models-101/" rel="noopener noreferrer"&gt;101 post&lt;/a&gt; put it in a table: for "what if?", an LLM "narrates a plausible story" and a world model "rolls the trajectory forward".&lt;/p&gt;

&lt;p&gt;I meant to repeat that comparison with the room from 104 as the example. Before writing it down a third time I thought I had better check it, so I gave Claude Opus 5.5 the same room, the same readings and the same questions I had asked the thirteen-weight model, and scored the answers against the simulator. The table in 101 turned out to be wrong, and the way it was wrong shows where each kind of model belongs.&lt;/p&gt;

&lt;p&gt;Everything below ran on 27 October 2026 against &lt;code&gt;claude-opus-5-5&lt;/code&gt; at its default settings, three runs per question. Every answer, with its token count and cost, is in &lt;a href="https://stepinto.dev/data/heater-llm-runs.csv" rel="noopener noreferrer"&gt;heater-llm-runs.csv&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The door, asked of Claude
&lt;/h2&gt;

&lt;p&gt;The test room is the one from 104: 22 degrees inside, minus four outside, heater on throughout. At step 10 someone opens a door that no log records, and it stays open until step 20. In 104, at step 13, the latent model had watched the room fall three steps in a row, its private field read 0.81, and it forecast five steps ahead almost exactly.&lt;/p&gt;

&lt;p&gt;Claude got the same fourteen readings as a CSV and nothing else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;step,room_c,outside_c,heater_until_next_step
0,22.00,-4,on
1,21.39,-4,on
...
11,13.14,-4,on
12,10.10,-4,on
13,7.76,-4,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The question was to forecast steps 14 to 18 with the heater on, and to end with the five numbers as JSON. Here is what came back, next to the real room and the 104 model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;step&lt;/th&gt;
&lt;th&gt;real room&lt;/th&gt;
&lt;th&gt;104 model&lt;/th&gt;
&lt;th&gt;Claude, run 2&lt;/th&gt;
&lt;th&gt;Claude, run 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;6.03&lt;/td&gt;
&lt;td&gt;6.52&lt;/td&gt;
&lt;td&gt;6.00&lt;/td&gt;
&lt;td&gt;6.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;4.76&lt;/td&gt;
&lt;td&gt;5.41&lt;/td&gt;
&lt;td&gt;4.67&lt;/td&gt;
&lt;td&gt;4.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;3.67&lt;/td&gt;
&lt;td&gt;4.41&lt;/td&gt;
&lt;td&gt;3.66&lt;/td&gt;
&lt;td&gt;3.69&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;3.51&lt;/td&gt;
&lt;td&gt;2.90&lt;/td&gt;
&lt;td&gt;2.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;2.65&lt;/td&gt;
&lt;td&gt;2.71&lt;/td&gt;
&lt;td&gt;2.32&lt;/td&gt;
&lt;td&gt;2.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0.53&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two runs out of three beat my model. Claude did it by spotting that the drops after step 10 shrink by a constant ratio and fitting a decay curve to them:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The behaviour changed sharply, with drops of 4.05, then 3.04, then 2.34 °C. Each drop is about 0.75–0.77 times the previous one. That is classic exponential decay toward a new, much colder equilibrium.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It put the new equilibrium at about half a degree. The true figure, from the constants hidden in the simulator, is 0.8. Then it guessed at a cause the log never mentions: "something changed around step 10. Possibly the heater failed or weakened, or a large heat loss opened up, such as a window or door."&lt;/p&gt;

&lt;p&gt;The 104 model has a field that tracks the door but cannot name it, and Claude can name it but keeps no field, which matters again further down.&lt;/p&gt;

&lt;p&gt;Run 1 matched the others for four steps and then forecast 1.2 degrees for step 18. It had read my "the heater stays on from step 13 to step 17" as the heater going off for the last step, said so, and gave 2.25 as the answer if it stayed on. That one is my wording, and I have left it in the scores.&lt;/p&gt;

&lt;h2&gt;
  
  
  With the log pasted in
&lt;/h2&gt;

&lt;p&gt;The 104 model learned from 4,800 logged steps, so the fair comparison gives Claude the same 4,800 steps. As CSV that is 77,142 input tokens per question. Two of the three answers came back with the room's constants written out. Run 1 put the closed-room leak at 0.067 of the gap per step and the heater at 1.2 degrees, then found the door:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;During these events the loss rate rises to about 0.25 × (room − outside), while the heater still adds about 1.2 °C.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;realRoom&lt;/code&gt; uses 0.07, 1.2 and 0.25. Run 3 got the same three numbers and cited two episodes from the log where the door had opened ("episode 3 from 17.80 °C and episode 22 from 17.58 °C"). I checked both against the simulator's private trace, and the door was open in each.&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;run 1&lt;/th&gt;
&lt;th&gt;run 2&lt;/th&gt;
&lt;th&gt;run 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;forecast for steps 14 to 18&lt;/td&gt;
&lt;td&gt;6.0, 4.75, 3.85, 3.25, 2.9&lt;/td&gt;
&lt;td&gt;6.0, 5.0, 4.6, 4.6, 4.9&lt;/td&gt;
&lt;td&gt;6.0, 4.7, 3.8, 3.1, 2.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE against the real room&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;1.28&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run 3 is the best forecast anything has made of this room in the series. All three runs noticed that door events in the log end after somewhere between two and eighteen steps, and allowed for this one ending. Runs 1 and 3 nudged their last steps up a little. Run 2 leaned on it hard and blended the two cases into a forecast that levels off at 4.6 and turns back up. Its reasoning was sound, but the door stayed open. It was answering a question about a distribution with one number, which is a problem the heater model has too, and one this series comes back to.&lt;/p&gt;

&lt;p&gt;This is what system identification looks like when the system is a textbook one. Newton's law of cooling is in every physics course Claude read, so the form of the model came for free, and the log only had to supply three numbers. My thirteen weights had to discover the form as well as the numbers, from the same rows, with no prior at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same question as a decision
&lt;/h2&gt;

&lt;p&gt;The reason to have a world model is to choose what to do, so the second question was a small planning problem, posed at step 5 of the same room while the door was still shut and the room was at 19.15:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want the room at or above 12 °C at every reading from step 6 to step 25, with the heater on for as few steps as possible. Which of steps 5 to 24 should the heater be on for, and what is the lowest the room gets? And could the heater hold the room at 16 °C or above for those 20 steps if it ran the whole time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because I wrote the room, I know the answer. With the true constants the fewest is fifteen: coast for five steps while the room falls to 12.11, then heat for the rest. And no, the heater cannot hold 16, because at minus four outside a closed room with the heater on settles at 13.1.&lt;/p&gt;

&lt;p&gt;This time there were three versions of Claude. One got the six readings and no tools. One got the readings and the 4,800-row log. The third got the readings and the 104 model, wrapped as a tool.&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="n"&gt;simTool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"simulate_room"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Run the room's fitted world model forward from the current reading (step 5) under a heater schedule, without touching the real heater. Returns the predicted reading at each following step."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;input_schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;heater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"array"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"off"&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;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Heater setting from step 5 onwards, one entry per step. Entry i is the setting from step 5+i to step 6+i."&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="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"heater"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;additionalProperties&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;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;strict&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="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the tool is the 104 model. It observes the room up to step 5, so its private field is set, and then runs &lt;code&gt;act&lt;/code&gt; blind under whatever schedule Claude asks for, which is the rollout from 101 with learned weights.&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;simulate&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="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heater&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;boolean&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="kt"&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;m&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;WorldModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&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;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&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="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&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="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;truth&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="p"&gt;;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[&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="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="n"&gt;on&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;heater&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;"turnHeaterOn"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"turnHeaterOff"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&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="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roomTemp&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;2&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;return&lt;/span&gt; &lt;span class="n"&gt;out&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 loop around it is the standard tool-use loop: send the question, run any &lt;code&gt;simulate_room&lt;/code&gt; calls Claude makes, send the predictions back, and repeat until it answers. Each plan was then run in the real room 200 times, with fresh draughts each time and the door shut.&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;heater steps&lt;/th&gt;
&lt;th&gt;lowest, as Claude said&lt;/th&gt;
&lt;th&gt;real lowest, mean of 200&lt;/th&gt;
&lt;th&gt;stays at 12 or above&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;true optimum&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;12.08&lt;/td&gt;
&lt;td&gt;125 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude, readings only (all three runs)&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;12.9&lt;/td&gt;
&lt;td&gt;13.00&lt;/td&gt;
&lt;td&gt;200 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude with the log, run 1&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;12.11&lt;/td&gt;
&lt;td&gt;12.08&lt;/td&gt;
&lt;td&gt;125 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude with the log, runs 2 and 3&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;12.9, 12.7&lt;/td&gt;
&lt;td&gt;13.00&lt;/td&gt;
&lt;td&gt;200 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude with the 104 model, runs 2 and 3&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;12.04&lt;/td&gt;
&lt;td&gt;10.61&lt;/td&gt;
&lt;td&gt;0 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude with the 104 model, run 1&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;12.27&lt;/td&gt;
&lt;td&gt;10.95&lt;/td&gt;
&lt;td&gt;0 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every run answered the 16 degree question correctly. On the schedule, Claude with six readings fitted a leak of 0.076 and a heater of 1.30, worked out that the heater settles the room at about 13.2, and chose to coast for four steps and heat for sixteen, leaving a margin of about 0.9 degrees, "which allows for some error in the fitted model". Claude with the log found the true optimum once, to the hundredth of a degree, and noted that its margin was 0.1 and that switching on a step earlier would be safer. The other two log runs took that safer option.&lt;/p&gt;

&lt;p&gt;Claude with my world model as a tool did worst. It made seven to nine calls, reasoned carefully about each result, and settled on thirteen heater steps with a predicted low of 12.04. In the real room that plan drops to 10.6, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the tool made it worse
&lt;/h2&gt;

&lt;p&gt;Claude's search was fine. I tried all 1,048,576 schedules against the 104 model, which took about two seconds, and the fewest heater steps it allows is thirteen as well. Claude found a thirteen-step plan in seven to nine calls. The model's optimum is wrong.&lt;/p&gt;

&lt;p&gt;One run's second call was the heater off for all twenty steps. The model said the room would end at 5.25 degrees. The real room ends at about 1.4. Every outside temperature in the training log was between 2 and 12 degrees. At minus four, with the heater off for long stretches, the model is well outside anything it has seen, and its weights do not extrapolate the way physics does. The 104 post noted that the latent model's room and outside weights were no longer equal and opposite, and that the gradient had found "a different split that fits the log just as well". That split fits the log, and it does not fit minus four. The five-step forecasts in 104 were close because five steps is not long enough for the error to build up, and twenty steps 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%2Fm2me101um51j8z464ji0.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%2Fm2me101um51j8z464ji0.png" alt="Heater off for twenty steps at minus four outside, door shut" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The heater left off for twenty steps from the step 5 reading, door shut. The 104 model agrees with the room for about five steps and then cools too slowly, ending almost four degrees warm. The green line is the model from the next section.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/data/heater-llm-heater-off.csv" rel="noopener noreferrer"&gt;Download the data (CSV)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the world model gave confident, wrong numbers, and Claude believed them, as it should have done with nothing to say otherwise. The tool returned a list of temperatures with no sign that the last fifteen of them were guesswork. The plan that is cheapest on a flawed model is the one that runs closest to the model's flaws, because that is where the model is most generous. Any search does this, whether the searcher is an LLM or a loop over every schedule there is.&lt;/p&gt;

&lt;p&gt;Claude on its own did better because it brought a prior. A leak proportional to the temperature difference, plus a constant from the heater, is the right shape for this room, and Claude knew that shape before it saw a reading. My thirteen weights had no prior to bring, and away from their data that cost them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving the model Claude's shape
&lt;/h2&gt;

&lt;p&gt;The first thing I tried was more data. Refitted on logs that include days down to minus six, the thirteen weights swung the other way and predicted a heater-off room colder than the outside air, which no room can be. The problem was the shape of the model, which more data does not change.&lt;/p&gt;

&lt;p&gt;Claude had already described the right one. The runs that saw the log, between them, spelled it out: a leak proportional to the gap between inside and outside, a constant from the heater, and a second, bigger leak that comes and goes. The 104 model can have that shape without giving up anything that made it a world model. The private field stays, &lt;code&gt;observe&lt;/code&gt; stays, and the backwards walk through each episode stays. Only &lt;code&gt;act&lt;/code&gt; changes.&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;act&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heaterOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;heaterAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&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="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outsideTemp&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roomTemp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leak&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;doorLeak&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;#&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;gap&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heaterOn&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heat&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;roomTemp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;roomTemp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outsideTemp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outsideTemp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;heaterOn&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;That is three prediction weights instead of seven, nine weights in all, trained on the same 4,800 rows for the same three thousand epochs, in under half a second. It has no bias term. The physics has none, and with one in, the fit traded the bias off against the leak and ended up more than five degrees too cold after twenty steps with the heater off. I made that choice after seeing round one, so weigh the result knowing it was not decided in advance.&lt;/p&gt;

&lt;p&gt;What came out reads like the inside of &lt;code&gt;realRoom&lt;/code&gt;. With the field at its door-shut level the leak works out at 0.072, against a true 0.07. With the door open the field goes to 0.97 and the leak to 0.245, against a true 0.25. The heater is 1.16, against 1.2. The field became the door again, and this time the weights around it are the room's own constants.&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;door forecast RMSE&lt;/th&gt;
&lt;th&gt;heater off for twenty steps, step 25 (real 1.42)&lt;/th&gt;
&lt;th&gt;fewest heater steps on the model&lt;/th&gt;
&lt;th&gt;that plan in the real room&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;104 model&lt;/td&gt;
&lt;td&gt;0.53&lt;/td&gt;
&lt;td&gt;5.25&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;lowest 10.83, safe in 0 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;physics-shaped model&lt;/td&gt;
&lt;td&gt;0.10&lt;/td&gt;
&lt;td&gt;1.15&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;lowest 13.00, safe in 200 of 200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&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%2Fr9e10dzy17gg7zx0tmbi.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%2Fr9e10dzy17gg7zx0tmbi.png" alt="How far off each five-step forecast of the open-door room was" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Every forecast of steps 14 to 18 with the door open, as the root mean square error against the real room. Claude's runs spread from 0.07 to 1.28. Each model gives one answer, every time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/data/heater-llm-forecast-errors.csv" rel="noopener noreferrer"&gt;Download the data (CSV)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The door forecast of 0.10 beats five of Claude's six forecasts, and the model gives the same answer every time it is asked. Then I gave Claude this model as the tool instead, with the same question. All three runs settled on sixteen heater steps after three to five calls, and the plan held the room above 12 in all 200 draws. Claude also explained the plan better than I would have:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One step of heating is worth most at the end, because its warmth has less time to leak away before step 25. An off step is cheapest early on.&lt;/p&gt;
&lt;/blockquote&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%2F0h5uzgzlie9vo7484wn7.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%2F0h5uzgzlie9vo7484wn7.png" alt="Claude's plan to keep the room at or above 12 degrees, predicted against real" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The plan Claude chose with each model as its tool. Dashed is what the model said would happen, solid is the real room, averaged over 200 runs with the door shut. The strip underneath shows which steps had the heater on. The 104 model promised the room would stay above 12 and it fell to 10.6; the physics-shaped model was slightly pessimistic, and the room stayed above 13.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/data/heater-llm-plans.csv" rel="noopener noreferrer"&gt;Download the data (CSV)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sixteen is one more than the true minimum. The model is about six tenths of a degree pessimistic at the bottom of the plan, so it asks for an extra step. The fifteen-step plan leaves a tenth of a degree to spare and dips below 12 in 75 of the 200 draws, so I would take the extra step anyway, though the model did not choose it for that reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each answer cost
&lt;/h2&gt;

&lt;p&gt;With the right shape, the world model matches Claude on accuracy and is a long way ahead on cost.&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;input tokens&lt;/th&gt;
&lt;th&gt;output tokens&lt;/th&gt;
&lt;th&gt;seconds&lt;/th&gt;
&lt;th&gt;dollars at list price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;forecast, readings only&lt;/td&gt;
&lt;td&gt;270&lt;/td&gt;
&lt;td&gt;1,410 to 1,833&lt;/td&gt;
&lt;td&gt;14 to 19&lt;/td&gt;
&lt;td&gt;0.03 to 0.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;forecast, with the log&lt;/td&gt;
&lt;td&gt;77,142&lt;/td&gt;
&lt;td&gt;2,266 to 2,290&lt;/td&gt;
&lt;td&gt;25 to 26&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan, readings only&lt;/td&gt;
&lt;td&gt;297&lt;/td&gt;
&lt;td&gt;2,423 to 3,863&lt;/td&gt;
&lt;td&gt;26 to 42&lt;/td&gt;
&lt;td&gt;0.05 to 0.08&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan, with the log&lt;/td&gt;
&lt;td&gt;77,169&lt;/td&gt;
&lt;td&gt;5,235 to 8,713&lt;/td&gt;
&lt;td&gt;59 to 97&lt;/td&gt;
&lt;td&gt;0.41 to 0.48&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan, with the 104 model as a tool&lt;/td&gt;
&lt;td&gt;15,005 to 16,777&lt;/td&gt;
&lt;td&gt;3,164 to 3,995&lt;/td&gt;
&lt;td&gt;43 to 53&lt;/td&gt;
&lt;td&gt;0.13 to 0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;plan, with the physics-shaped model as a tool&lt;/td&gt;
&lt;td&gt;4,184 to 10,744&lt;/td&gt;
&lt;td&gt;2,148 to 2,912&lt;/td&gt;
&lt;td&gt;34 to 36&lt;/td&gt;
&lt;td&gt;0.06 to 0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;either model, all 1,048,576 schedules&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;about 2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A twenty-step rollout of either model takes about two microseconds. A forecast from Claude with the log takes 25 seconds and 35 cents. That is a factor of more than ten million in time. Planning properly means rolling the model forward thousands of times per decision, and a thermostat makes a decision every step. Monitoring means running the model alongside every device, all the time.&lt;/p&gt;

&lt;p&gt;Then there is the size of the log. The 4,800 rows of one simulated room were 77,142 tokens. A real device logging once a minute produces 525,600 rows a year, about 8.4 million tokens at the same rate. That is eight times Claude's context window for a year of one device, before anyone mentions a second device or a second sensor channel. Nine weights are a summary of the log that never needs re-reading. Claude re-reads the whole log on every question, which is the &lt;a href="https://stepinto.dev/posts/world-models-102/" rel="noopener noreferrer"&gt;102 post's&lt;/a&gt; point about the LLM equation made literal: the condition on the right of the bar is the entire history, and every call pays for all of it.&lt;/p&gt;

&lt;p&gt;The usual objection is that you cannot train an LLM on your telemetry. That turned out to be beside the point, because Claude did not need training to fit this room. It needed the log pasted in, and pasting is where the trouble is. Every question sends the customer's telemetry to someone else's servers, and the thing answering lives behind an API that, as &lt;a href="https://stepinto.dev/posts/when-the-api-gets-switched-off/" rel="noopener noreferrer"&gt;this June showed&lt;/a&gt;, can stop answering overnight. A heater controller that needs a call across the Atlantic to work out what to do next has a single point of failure in another jurisdiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each one belongs
&lt;/h2&gt;

&lt;p&gt;Round one went to Claude, and round two went to the world model with Claude's help. What the two have is different sets of properties, and the system you want puts each kind of model where its properties fit.&lt;/p&gt;

&lt;p&gt;The world model belongs in the loop. It is small, fixed, local and fast enough to be called a million times per decision, on the device, with nothing leaving the building. Its weakness is that it knows only its data and the shape it was given, and it does not know where either stops being true.&lt;/p&gt;

&lt;p&gt;The LLM belongs at the edges, and the experiment showed three edges where it was the better tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intent.&lt;/strong&gt; "At or above 12 at every reading, as few heater steps as possible" is how a person states a goal. Claude turned it into a constraint and an objective without being asked to, and its answer to the 16 degree question was correct every time. A planner needs that translation done before it can start, and a person should not have to write the objective function themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Names.&lt;/strong&gt; The latent model's field went to 0.9 when the door opened, and nothing in the model can say what 0.9 means. Claude, shown the same readings, said "a window or door". Nobody acts on a monitor that says "the field has risen", but "this looks like a window has been left open" gets the window closed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structure.&lt;/strong&gt; The form Claude proposed from the log (a leak proportional to the difference, a constant heater gain and a second, larger leak that comes and goes) turned out to be a better model of this room than my thirteen weights. Round two is the small version of letting an LLM propose the shape and then fitting and running that shape locally on the real data, which puts its prior and the world model's speed in the same system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7ergzkz0abz9d3ydozg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi7ergzkz0abz9d3ydozg.webp" alt="A diagram. On the left, an LLM box with speech bubbles for Goal and Explanation. An arrow leads into a dashed box labelled on the device, every step, which holds a Planner box feeding a World model box circled by an arrow labelled thousands of rollouts. The world model exchanges heater on/off and readings with a radiator on the right. A dashed purple arrow labelled proposes the shape runs from the LLM to the world model." width="800" height="297"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The LLM takes the goal in and the explanation out, and proposes the model's shape once. Everything inside the dashed box runs on the device, every step, with no network.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So the shape is an LLM on the outside, a world model on the inside, and a planner between them. A person states a goal. The LLM turns it into an objective. The planner rolls the world model forward as many times as it needs. The LLM explains the plan, says when the model's predictions and the room have parted company, and puts a name on the reason. Only the middle of that has to run every step, and none of the middle needs a network connection.&lt;/p&gt;

&lt;p&gt;Round one showed the condition the whole arrangement depends on. A world model handed to an LLM as a tool gets believed. If it does not say how sure it is, the LLM cannot tell its good predictions from its bad ones, and a better LLM will only exploit the bad ones more efficiently. The physics-shaped model was right this time, and it would have been believed just as readily if it had been wrong.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://stepinto.dev/posts/world-models-101/" rel="noopener noreferrer"&gt;101 post&lt;/a&gt; said that an LLM asked "what if?" narrates a plausible story. For a room whose physics is in the textbooks, that is no longer true. Given the readings, Claude fitted a model and rolled it forward in its reasoning, and did it well. The part of 101 that held up is the part this post kept running into: Claude keeps no state between calls, so every answer starts again from the readings and pays for the whole log.&lt;/p&gt;

&lt;p&gt;The next post builds the physics-shaped model properly, into the &lt;a href="https://stepinto.dev/heater/?scenario=door" rel="noopener noreferrer"&gt;playground&lt;/a&gt; next to the 104 one, and puts a planner on top of it. Claude's three to five tool calls were a search done by hand, and the planner runs thousands of rollouts per decision, with a plan button to go with it. After that comes the warning the tool here never gave, which is a model that can say where its own predictions stop being worth trusting. Every answer from this post, with its token count and cost, is in &lt;a href="https://stepinto.dev/data/heater-llm-runs.csv" rel="noopener noreferrer"&gt;heater-llm-runs.csv&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>worldmodels</category>
      <category>llm</category>
      <category>agents</category>
    </item>
    <item>
      <title>Iron and Silk</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 15:58:54 +0000</pubDate>
      <link>https://dev.to/stepinto/iron-and-silk-4b87</link>
      <guid>https://dev.to/stepinto/iron-and-silk-4b87</guid>
      <description>&lt;p&gt;I have been building a series of pages where the computer generates the pattern: &lt;a href="https://stepinto.dev/posts/tiles-painted-by-rule/" rel="noopener noreferrer"&gt;tin-glazed tiles painted by rule&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/cut-tile-and-poured-cement/" rel="noopener noreferrer"&gt;cut tile and poured cement&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/heraldry-by-rule/" rel="noopener noreferrer"&gt;heraldry&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/knotwork-by-rule/" rel="noopener noreferrer"&gt;knotwork&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/a-piece-that-breaks-another-piece/" rel="noopener noreferrer"&gt;a piece that breaks another piece&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/light-on-brick-paint-on-cream/" rel="noopener noreferrer"&gt;brick in raking light&lt;/a&gt; and, this morning, &lt;a href="https://stepinto.dev/posts/the-seventeen-ways-a-wall-can-repeat/" rel="noopener noreferrer"&gt;the seventeen ways a wall can repeat&lt;/a&gt;. The brick piece, Sebka, drew nothing in shadow. The wall had three heights, the sun was a direction, and every shadow was a level's own outline moved along the sun's slant. That rule turned out to have two more pieces in it, and they are about as different as two materials can be: wrought iron, which is all shadow, and embroidered silk, which is all shine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyivq95eqscy070ukvdzs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyivq95eqscy070ukvdzs.webp" alt="A black silk shawl hung from a rail against a dark wall, embroidered with roses and peonies in pink, purple, yellow and cream with green leaves and two blue birds, a vine with small flowers running round the border, and a knotted net and long fringe hanging below and down both sides" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mantón de Manila, the feria preset. Every petal is satin stitch with a direction, and the direction is what the light sees.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Rejería
&lt;/h2&gt;

&lt;p&gt;Every window in Seville and Córdoba has a reja, and the balconies have grilles. Bars, rails, a frieze of scrolls along the rail, rivets where the bars pass through, and a twist in each bar where the smith heated it and turned it. The repeat is one-dimensional. A frieze is one motif set out once per bay and going on for ever, with a rosette every few bays, and the bars are the other direction's repeat. The two together are the whole grille.&lt;/p&gt;

&lt;p&gt;The motifs are all one function. A scroll is a bar that leaves a root along a heading and winds in, tightening as it goes:&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;function&lt;/span&gt; &lt;span class="nf"&gt;scroll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heading&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="nx"&gt;turns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sense&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;cx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;R&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;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sense&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="nx"&gt;PI&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ry&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;R&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="nx"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sense&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="nx"&gt;PI&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a0&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;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ry&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rx&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;pts&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;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;40&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;t&lt;/span&gt; &lt;span class="o"&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;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;phi&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;turns&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;TAU&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;R&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="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.72&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="mf"&gt;0.1&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="nx"&gt;pts&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;cx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&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;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sense&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;cy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;r&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="nx"&gt;a0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;sense&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;phi&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;pts&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;Two of those from a root at the middle of a bay's edge, one heading up and one down, is a C. Two from the bay's centre heading opposite ways with the same sense is an S. Two from the bottom point heading up and outward and curling in at the top is a heart, and from the same point curling the other way, with a bar between, a lyre. The rosette is a ring with eight of them radiating, alternately turning.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs321az1h5msgh0q8idox.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs321az1h5msgh0q8idox.webp" alt="A wrought-iron grille of bars and rails on an ochre wall in late sun, a frieze of heart-shaped scrolls with a rosette every fifth bay, the grille throwing a soft shadow up and to the left" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The córdoba preset. Hearts along the rail, an ochre patio wall, the sun low at half past five.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The grille is one path
&lt;/h2&gt;

&lt;p&gt;The piece is the shadow. The grille stands off the wall by a distance you set, from a finger's width to half a bay, and the sun is Sebka's sun: along the wall from the left at six, in front at one, along it to the right at eight. Everything the grille is made of, the rectangles of bars and rails, the polylines of scrolls, the discs of rivets, is one path. It is drawn once in the sky's shade colour onto a layer, moved along the sun's slant by the stand-off, blurred in proportion to the stand-off, and multiplied onto the wall. Then it is drawn again in iron, with a highlight down the side toward the sun, which is the only lighting the iron gets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fytmw79nw78ysfr003sea.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fytmw79nw78ysfr003sea.webp" alt="Three panels of the same grille of C-scrolls on whitewash. In the first the shadow is a thin hard rim beside every bar. In the second the shadow has moved a quarter of a bay from the iron and softened. In the third it lies half a bay away, soft and faint, the scrolls' shadows reading as blurred rings" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The sevilla preset at three stand-offs. The geometry and the sun are the same; only the gap between iron and wall changes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That is the whole trick and it took one afternoon, which is what a rule you already have should cost. The second afternoon went on the iron being iron: the twist drawn as alternating lit and dark stripes clipped to the bar, the rivets as domes lit on the sun's side, gilt on the scroll tips for the cathedral preset, and rust running down the lime from under every rivet when age is on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4azfeb1brdzb66tahhwu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4azfeb1brdzb66tahhwu.webp" alt="A close view of black iron scrolls on pale lime, each scroll ending in a gilded tip that catches the light, the rivets lit on one side, the shadow of the grille falling behind it" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The catedral preset, up close. Black iron and gilt on the tips of the scrolls, after the big rejas of Seville cathedral.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Mantón de Manila
&lt;/h2&gt;

&lt;p&gt;The shawl of the feria is a square of silk, black or cream or red, embroidered edge to edge with roses, peonies, leaves and the odd bird, finished with a knotted net and a long fringe. It came from Canton by way of Manila on the galleon to Seville, and Seville made it its own.&lt;/p&gt;

&lt;p&gt;The material is thread. Satin stitch lays silk side by side in one direction across a shape, and silk shines where the light falls across the thread rather than along it. So every shape in the embroidery carries one number beyond its outline and colour: the angle its stitches run at. A petal is stitched from the centre outward, so its direction is its angle round the flower. A leaf is two halves, each stitched at an angle to the midrib, so the two faces never shine together. The swallow is traced from a flat drawing, the way Fajalauza's bird was, and its body, wings and tail are each stitched along their own line. The brightness of a shape is how far its stitch angle is from the light:&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;lit&lt;/span&gt; &lt;span class="o"&gt;=&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="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="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="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="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is shaded by hand. The fill is the thread's colour pulled toward its dark face by how little it is lit, and over it the threads themselves are drawn, lines along the stitch direction with a shine between them whose strength is that same number. Turn the light slider and the roses light up petal by petal as each one's angle comes round, and the leaves swap which half is bright.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgp0k24crekc6kyqt92a.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzgp0k24crekc6kyqt92a.webp" alt="Three close views of the same embroidered roses and leaves on black silk under light from the right, from above and from the upper left. In each, different petals of each rose are bright and different halves of each leaf, while the flowers themselves do not move" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The same bouquet under three lights. The petals that shine are the ones whose stitches run across the light.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Black on black
&lt;/h2&gt;

&lt;p&gt;A mourning shawl is black silk embroidered in black, and it is the best test of the idea, because colour cannot help. The luto preset turns the sheen all the way up and gives every thread the same near-black. The flowers are there only because their stitches run in different directions from the silk and from each other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxmagoi5vkdbpn3oyiunu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxmagoi5vkdbpn3oyiunu.webp" alt="A close view of a black silk shawl embroidered entirely in black thread, the roses, leaves and a bird visible only as regions of fine parallel lines catching the light at different angles" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The luto preset. No colour at all; every shape is told from the ground by the angle of its thread.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The silk itself gets a broad shine along the light's direction and a weave of fine lines across it. The shawl hangs from a rail along its top edge, with its shadow on the wall behind, so the knotted net and the fringe fall down the sides and below. Each thread of the fringe is its own line with a wave in it, lit by the same rule, which for a hanging thread means it shines when the light is from the side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmo5bilft2j2c5xdymiu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpmo5bilft2j2c5xdymiu.webp" alt="A close view of cream silk with a large dark peony with a yellow heart, pink and purple roses, green leaves and a blue bird, the stitching of each shape visible as fine parallel lines" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The tarde preset on cream silk with the light low from the left. The dark outer petals and the bright inner ones are the same colour pair at different angles.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the two share
&lt;/h2&gt;

&lt;p&gt;Rejería has no lighting model beyond a highlight; the sun's whole job is to move a shadow. The mantón has no shadows beyond the one the shawl throws on the wall; the light's whole job is to pick which threads shine. Both pages are the same shape underneath: a geometry built once from a seed, a light that is one angle, and a rule that turns the angle into what you see. Sebka had three heights and a slant. Rejería has one height and a slant. The mantón has no heights at all, just a direction per stitch. The sheen pass is the thing this one leaves behind, and it will come back the next time a page needs metal to catch the light.&lt;/p&gt;

</description>
      <category>art</category>
      <category>generative</category>
      <category>canvas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>The Seventeen Ways a Wall Can Repeat</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 14:09:10 +0000</pubDate>
      <link>https://dev.to/stepinto/the-seventeen-ways-a-wall-can-repeat-4pdb</link>
      <guid>https://dev.to/stepinto/the-seventeen-ways-a-wall-can-repeat-4pdb</guid>
      <description>&lt;p&gt;I have been building a series of pages where the computer generates the pattern: &lt;a href="https://stepinto.dev/posts/tiles-painted-by-rule/" rel="noopener noreferrer"&gt;tin-glazed tiles painted by rule&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/cut-tile-and-poured-cement/" rel="noopener noreferrer"&gt;cut tile and poured cement&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/heraldry-by-rule/" rel="noopener noreferrer"&gt;heraldry&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/knotwork-by-rule/" rel="noopener noreferrer"&gt;knotwork&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/a-piece-that-breaks-another-piece/" rel="noopener noreferrer"&gt;a piece that breaks another piece&lt;/a&gt;, and last time &lt;a href="https://stepinto.dev/posts/light-on-brick-paint-on-cream/" rel="noopener noreferrer"&gt;brick in raking light and Granada's green and blue&lt;/a&gt;. The tile walls in that run all repeat the same way. A square design is laid four times, mirrored or turned, and the wall is that block over and over. It worked for every wall so far, and it is one of seventeen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F19iytno7kwd5uzvwawm2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F19iytno7kwd5uzvwawm2.webp" alt="A grid of seventeen panels of pink roses on green sprigs on cream paper, each labelled with a wallpaper group name from p1 to p6m. The sprig is the same in every panel. In the first it simply repeats; in others it is mirrored, turned upside down, slid, or laid in fours around a point; in the last rows it is cut into wedges and folded into three-fold and six-fold rosettes" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One sprig, the same seed, and the seventeen groups. The sprig never changes. The group is what the wall does with it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every repeating pattern in the plane has one of &lt;a href="https://en.wikipedia.org/wiki/Wallpaper_group" rel="noopener noreferrer"&gt;seventeen symmetry groups&lt;/a&gt;. This was proved in 1891 by &lt;a href="https://en.wikipedia.org/wiki/Evgraf_Fedorov" rel="noopener noreferrer"&gt;Evgraf Fedorov&lt;/a&gt;, a crystallographer who was after the 230 groups of three dimensions and did the plane on the way, and the groups are called the wallpaper groups because wallpaper is where most people meet them. A wallpaper is a repeating pattern, so it has one of these seventeen symmetries whether its designer knew or not. William Morris did not know, as far as I can tell, and his papers still sort neatly into the groups: a mirror here, a glide there, a trellis with mirrors both ways. &lt;a href="https://stepinto.dev/art/papelpintado.html" rel="noopener noreferrer"&gt;Papel Pintado&lt;/a&gt; draws one sprig in a square and makes the group a control.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a group is, on a wall
&lt;/h2&gt;

&lt;p&gt;A symmetry of a pattern is a movement that leaves it looking the same. Slide a wallpaper by one repeat and it looks the same, so translation is a symmetry of every wallpaper. The seventeen groups are the seventeen ways you can add to that: a half turn, a quarter turn, a third of a turn, a mirror, a glide (reflect and slide along the mirror by half a repeat), and the combinations of those that are consistent with a lattice. There is no fifth of a turn because pentagons do not tile, which is also why &lt;a href="https://stepinto.dev/art/cini.html" rel="noopener noreferrer"&gt;Çini&lt;/a&gt; has to work so hard for its stars.&lt;/p&gt;

&lt;p&gt;The engine's old repeats are three of the seventeen in disguise. Lay a diagonal-symmetric tile four times mirrored and you have pmm; lay it four times turned and you have p4; and because the tile is symmetric across its own diagonal, the mirrored module is actually p4m, the kaleidoscope. The new page's sprigs are deliberately asymmetric, because a symmetric design hides the group. Give p1 a tile with a mirror in it and what you see is cm.&lt;/p&gt;

&lt;p&gt;Each group in the engine is three things:&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="nx"&gt;pgg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&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="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="mi"&gt;0&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;domain&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="nx"&gt;copies&lt;/span&gt;&lt;span class="p"&gt;:&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="nx"&gt;glide&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PI&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;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;translate&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="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;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;glide&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PI&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;1&lt;/span&gt;&lt;span class="p"&gt;)))],&lt;/span&gt;
  &lt;span class="nx"&gt;lattice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rectangular&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;glides both ways and never a mirror&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 cell is the lattice, two vectors in tiles. The domain is the part of the tile that is drawn: the whole square for twelve of the groups, half of it cut along a diagonal for cmm, p4m and p4g, and for the five hexagonal groups the 60° rhombus whose long diagonal runs from corner to corner, or a third, a sixth or a twelfth of it. The copies are the matrices that lay the domain out inside one cell. Lay the cell at every lattice point and the plane is covered exactly once. The wall's painter walks the cells a window touches, and for each copy sets the transform, clips to the domain, and draws the tile image into it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1w5gsxolscen4lto53s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh1w5gsxolscen4lto53s.webp" alt="Three panels of green sprigs on cream. In the first, pm, each sprig has a mirror image beside it and the rows line up. In the second, pg, the mirror images are slid half a row down, so the rows zigzag. In the third, cm, the sprigs are mirrored and every row is stepped by half a repeat" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;pm, pg and cm with the willow inks. A mirror, a glide, and a mirror on a lattice whose rows step by half.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every domain keeps the design's main corner. For the hexagonal groups that matters, because the twelfth of a rhombus that p6m draws is a thin wedge, and it is the wedge with the root of the sprig in it. Twelve copies of that wedge about a point is a rosette, and the cuts heal, because wherever the domain is cut by a mirror line the two halves of a cut bloom meet their own reflection and close. Only a cut across a translation leaves a seam, and that is the seam every tile wall has had from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checked before it was looked at
&lt;/h2&gt;

&lt;p&gt;I did not trust myself to get seventeen groups right by eye, and some of the pairs are easy to confuse. p3m1 and p31m both have thirds of a turn and mirrors; the difference is whether every centre of rotation sits on a mirror line. pmg and pgg both have half turns; one has a mirror and the other only glides. So before the page existed, two scripts checked the module.&lt;/p&gt;

&lt;p&gt;The first drops thousands of random points on the plane and counts how many copies of the domain each one falls in. The answer has to be one, for every group, every time, and it was. The second takes three asymmetric points in the domain, generates their orbit under the group, and then tries candidate symmetries against it: rotations of every order about a grid of centres, and reflections in lines at every multiple of fifteen degrees. For each group it reports the highest rotation order, how many mirror directions there are, and whether the rotation centres sit on mirrors. That table matched the textbook for all seventeen, once I had taught the checker to look at fifteen degrees and at the irrational centres the hexagonal groups use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fku41yfichey5hyd48top.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fku41yfichey5hyd48top.webp" alt="Two panels of brown sprigs with orange flowers on cream. Left, p3m1: six-pointed rosettes of stems with small buds, every centre on a line of mirror symmetry. Right, p31m: triangles of stems with orange blooms clustered at the corners, the blooms in threes about centres that are not on mirror lines" width="800" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;p3m1 and p31m, same seed, same inks. Both have thirds of a turn and mirrors. In the first every centre is on a mirror; in the second one kind of centre is not, and the blooms gather there in threes.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Block print
&lt;/h2&gt;

&lt;p&gt;The material is wallpaper, which was printed from blocks: one block per colour, pressed by hand down the length of the paper, and the blocks never land quite where the last one did. On the page each colour's block is given a small random offset per tile, and the key block, the dark outline, has its own. The register slider is how far they stray. At the far end of it the outline sits visibly beside the colour it was meant to enclose, which is how a Morris paper looks if you get close to it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbjre0y531ifyxvzzoy9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzbjre0y531ifyxvzzoy9.webp" alt="A close view of pink roses with dark outlines on cream paper, the rose colour sitting slightly to one side of its outline and the green leaves a hair off theirs, on paper covered in fine fibres" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The trellis preset with the register slider all the way up. Each colour is its own block and none of them agree.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The inks are flat, because block printing is flat. The paper has fibres lying every way, drawn once per tile, and clouds of tint laid over the whole sheet rather than per tile, since a sheet of paper is one sheet and nothing on it may show where a tile ends. The first version put the clouds on the tile, and the lattice showed through as hexagons behind the jasmine. Age foxes the paper with brown spots, yellows it, and fades it where the sun has been.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxahk1fa1y02ggrue8ec.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frxahk1fa1y02ggrue8ec.webp" alt="Red five-petalled flowers on green sprigs with yellow berries on a dark indigo ground, the sprigs laid in a zigzag so that each one is the glide reflection of its neighbour" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The strawberry preset, pgg: glides both ways and never a mirror. Red and cream on indigo, after the paper of the same name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The presets are named for Morris papers because the groups they use are the groups those papers use. Trellis is pmm. Willow Bough glides, so willow is pg. Daisy is p6m, which turns the sprig into a snowflake, and honeysuckle is cm, mirrors with every row stepped by half.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it leaves behind
&lt;/h2&gt;

&lt;p&gt;The groups went into the engine as a module of their own, &lt;code&gt;groups.js&lt;/code&gt; in &lt;a href="https://www.npmjs.com/package/azulejeria" rel="noopener noreferrer"&gt;azulejeria 2.4.0&lt;/a&gt;, and any wall built on the engine can now set its repeat to a group's name. The four classic repeats and the whole-tile picture are untouched: every preset on every existing page renders byte for byte as it did before, which was checked the way the engine's notes insist on, by hashing every canvas before and after.&lt;/p&gt;

&lt;p&gt;There is a second new piece this week, &lt;a href="https://stepinto.dev/art/rejeria.html" rel="noopener noreferrer"&gt;Rejería&lt;/a&gt;, wrought iron and its shadow on whitewash, where the repeat is one-dimensional, a frieze of scrolls along a rail, and the piece is the shadow the grille throws. It gets its post together with the embroidered silk it will share a light with.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzv2oecw5aoc9jllxsos2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzv2oecw5aoc9jllxsos2.webp" alt="A wrought-iron grille of vertical bars and horizontal rails in front of a whitewashed wall lit by low evening sun, a frieze of lyre-shaped scrolls along one rail with a rosette every few bays, and the whole grille cast as a soft shadow on the wall well to the left of itself" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Rejería, the úbeda preset. The grille stands half a bay off the wall and the sun is at seven in the evening.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>art</category>
      <category>generative</category>
      <category>canvas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Reading the AI Act One Article at a Time</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Fri, 06 Nov 2026 09:24:19 +0000</pubDate>
      <link>https://dev.to/stepinto/reading-the-ai-act-one-article-at-a-time-2452</link>
      <guid>https://dev.to/stepinto/reading-the-ai-act-one-article-at-a-time-2452</guid>
      <description>&lt;p&gt;The two posts I wrote in February about building AI platforms under European law, &lt;a href="https://stepinto.dev/posts/compliance-as-code-two-europes/" rel="noopener noreferrer"&gt;compliance-as-code&lt;/a&gt; and &lt;a href="https://stepinto.dev/posts/fine-tuning-under-european-law/" rel="noopener noreferrer"&gt;fine-tuning&lt;/a&gt;, stop at the architecture. They say where the compliance layer sits and not much about what goes in it. The AI Act is written in clauses, and each clause ends up as something specific in the code: a field in a log, a state in the UI, a test in CI. Most of what I read about the Act is written for lawyers or for the board, and very little of it gets as far as a pull request.&lt;/p&gt;

&lt;p&gt;So from today, Fridays on this blog are for AI law. Each week I take one article of the EU AI Act, or one of the UK's rules on automated decisions, put the text up verbatim, and annotate every clause for the engineers who have to build to it. The first is Article 50, on 20 November.&lt;/p&gt;

&lt;p&gt;The Digital Omnibus came into force on 27 July and moved the high-risk obligations for Annex III systems (recruitment, credit scoring, education, essential services) to 2 December 2027. Most teams now know the date and have not started building, which makes this a good year to read the text properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a page looks like
&lt;/h2&gt;

&lt;p&gt;Each post is one whole article, with the text front and centre. Click a clause, or tap it on a phone, and a panel opens with the clause quoted with its pinpoint, a plain reading of what it means, and the notes. Notes come in five kinds, each with its own colour, and you can filter by kind: software, contested, guidance, elsewhere in the law, and dates.&lt;/p&gt;

&lt;p&gt;This is one clause from the Article 50 page, as the panel shows it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Article 50(2), the standard editing exception&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"This obligation shall not apply to the extent the AI systems perform an assistive function for standard editing"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What it means.&lt;/strong&gt; Tools that only help prepare existing content for publication (grammar, spelling, formatting, compression, red-eye removal) need not mark their output as AI-generated. Once the change affects meaning, style or intent, it is no longer standard editing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software.&lt;/strong&gt; One system can be exempt for some outputs and not others. "Fix grammar" is standard editing, while "Rewrite in a formal tone" changes style and must be marked, and the Commission's guidelines confirm the exception applies to the altered content rather than to the system as a whole. So the code that applies the mark needs to know which feature produced each output. A single &lt;code&gt;marked: true&lt;/code&gt; at the API gateway either over-marks the spellchecker or under-marks the rewriter. &lt;em&gt;(Guidelines ¶92)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contested.&lt;/strong&gt; The guidelines' own list of standard editing includes "AI-generated translations of text", next to spellchecking. A translation can change meaning, and the same guidelines say editing stops being standard when it does. A translation that localises tone and idiom for a market is rather close to the rewriting they say must be marked. &lt;em&gt;(Guidelines ¶90, ¶92)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Article 50 page has 33 clauses and 38 notes like that. Every clause and note has its own link, which is handy when you want to send someone the exact sentence you are arguing about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes keep arriving after a post goes out
&lt;/h2&gt;

&lt;p&gt;Publishing a post doesn't freeze the page. When new guidance comes out, or someone tells me I have read a clause wrong, I add a note with the date it went in, and the log at the foot of the page lists every change by date. A few lawyers I know are reading the drafts, and when they correct me the correction goes in as a dated note with their name on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first three
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;20 November.&lt;/strong&gt; &lt;a href="https://stepinto.dev/series/article-by-article/" rel="noopener noreferrer"&gt;Article 50&lt;/a&gt;, the transparency duties: telling people they're talking to an AI, and marking generated content. It has applied since 2 August, and the grace period for marking output from systems already on the market ends on 2 December.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;27 November.&lt;/strong&gt; Article 6, which decides whether your system is high-risk, with its four exceptions at the centre.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;4 December.&lt;/strong&gt; Article 26, the duties of deployers, the companies that use a high-risk system rather than build it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am an engineer, not a lawyer, so treat all of this as an engineer's reading and not as legal advice.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>europe</category>
      <category>regulation</category>
      <category>compliance</category>
    </item>
    <item>
      <title>A Claude Client in One C# File, Compiled Ahead of Time</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Thu, 05 Nov 2026 07:21:51 +0000</pubDate>
      <link>https://dev.to/stepinto/a-claude-client-in-one-c-file-compiled-ahead-of-time-p1i</link>
      <guid>https://dev.to/stepinto/a-claude-client-in-one-c-file-compiled-ahead-of-time-p1i</guid>
      <description>&lt;p&gt;Last week's client was &lt;a href="https://stepinto.dev/posts/claude-hot-path-go/" rel="noopener noreferrer"&gt;Go&lt;/a&gt;: a 5.8 MB static binary that starts in under two milliseconds, which is a high bar for any language with HTTP in its standard library. This week it's C#. .NET has had everything the spec needs in the box for a decade, and since .NET 8 it has had Native AOT, which compiles the whole program to a single native executable with no runtime to install. I wanted to know whether that gets a C# client anywhere near Go on size and cold start. It gets closer than I expected, though the runtime column on the leaderboard shows what the binary size leaves out.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/AndyCross/claude-hot-path/tree/main/clients/csharp" rel="noopener noreferrer"&gt;github.com/AndyCross/claude-hot-path&lt;/a&gt;, and the spec every client passes is at &lt;a href="https://stepinto.dev/claude-hot-path/" rel="noopener noreferrer"&gt;/claude-hot-path/&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  JsonNode, not source generators
&lt;/h2&gt;

&lt;p&gt;The idiomatic way to do JSON in AOT .NET is &lt;code&gt;System.Text.Json&lt;/code&gt; with a source-generated &lt;code&gt;JsonSerializerContext&lt;/code&gt;, so the serialiser knows every type at compile time and nothing needs reflection. For a client this small I went the other way and used &lt;code&gt;JsonNode&lt;/code&gt;, the DOM API, because the request body and the event payloads are built and read dynamically anyway and a context class would have doubled the file.&lt;/p&gt;

&lt;p&gt;That choice runs into AOT's trimming analysis in one place. &lt;code&gt;JsonArray.Add&amp;lt;T&amp;gt;(T)&lt;/code&gt; is generic, and when &lt;code&gt;T&lt;/code&gt; is &lt;code&gt;JsonObject&lt;/code&gt; the compiler warns that creating a &lt;code&gt;JsonValue&lt;/code&gt; from a non-primitive type needs runtime code generation. It is a false alarm here, because a &lt;code&gt;JsonObject&lt;/code&gt; is a node and gets added as one, but the fix is to say so with a cast:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&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="n"&gt;JsonNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JsonObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"assistant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeepClone&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 casts and the publish is warning-free. The rest of the project file is the usual AOT size settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;PublishAot&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/PublishAot&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StripSymbols&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/StripSymbols&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;OptimizationPreference&amp;gt;&lt;/span&gt;Size&lt;span class="nt"&gt;&amp;lt;/OptimizationPreference&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;InvariantGlobalization&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/InvariantGlobalization&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;UseSystemResourceKeys&amp;gt;&lt;/span&gt;true&lt;span class="nt"&gt;&amp;lt;/UseSystemResourceKeys&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;EventSourceSupport&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/EventSourceSupport&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;StackTraceSupport&amp;gt;&lt;/span&gt;false&lt;span class="nt"&gt;&amp;lt;/StackTraceSupport&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;InvariantGlobalization&lt;/code&gt; is the big one. Without it the binary carries ICU for culture-aware string handling, and a client that compares a few ASCII event names does not need any of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headers first, then the stream
&lt;/h2&gt;

&lt;p&gt;The one line that makes streaming work in &lt;code&gt;HttpClient&lt;/code&gt; is the completion option. By default &lt;code&gt;SendAsync&lt;/code&gt; buffers the whole response body before returning, which for an SSE stream means waiting for the model to finish before printing anything, and the harness fails a client that does that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&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;HttpCompletionOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseHeadersRead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With headers-read completion the body is a live stream, and a &lt;code&gt;StreamReader&lt;/code&gt; over it gives the same line loop as Go's scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StreamReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStreamAsync&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLineAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;line&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="n"&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="s"&gt;"data: "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ordinal&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonNode&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="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;))!;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"content_block_start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;content&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="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"content_block"&lt;/span&gt;&lt;span class="p"&gt;]!.&lt;/span&gt;&lt;span class="nf"&gt;DeepClone&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="k"&gt;partial&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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&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="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"content_block_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"delta"&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"text_delta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;]!;&lt;/span&gt;
                &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flush&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]![&lt;/span&gt;&lt;span class="s"&gt;"text"&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]![&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"input_json_delta"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"partial_json"&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="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"content_block_stop"&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]![&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"tool_use"&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]![&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonNode&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="k"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&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="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s"&gt;"message_delta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"delta"&lt;/span&gt;&lt;span class="p"&gt;]?[&lt;/span&gt;&lt;span class="s"&gt;"stop_reason"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stdout.Flush()&lt;/code&gt; is there because &lt;code&gt;Console.Out&lt;/code&gt; is buffered when it is not a terminal, and under the harness it never is. Go's &lt;code&gt;os.Stdout&lt;/code&gt; is unbuffered and the bash client's &lt;code&gt;printf&lt;/code&gt; goes straight out, so this is the first client where streaming could have silently become batching.&lt;/p&gt;

&lt;p&gt;The second turn echoes the assistant content back with &lt;code&gt;DeepClone()&lt;/code&gt;, because a &lt;code&gt;JsonNode&lt;/code&gt; can only have one parent and the same array is about to be the &lt;code&gt;content&lt;/code&gt; of a message inside &lt;code&gt;messages&lt;/code&gt; and the source of the &lt;code&gt;tool_result&lt;/code&gt; blocks. &lt;code&gt;TimeZoneInfo.FindSystemTimeZoneById&lt;/code&gt; takes IANA names on Linux directly, so the tool is three lines and no conversion table.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it measures as
&lt;/h2&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;C# Native AOT&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;bash and curl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Artefact&lt;/td&gt;
&lt;td&gt;5.4 MB&lt;/td&gt;
&lt;td&gt;5.8 MB&lt;/td&gt;
&lt;td&gt;3.7 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;2.4 MB&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;23.7 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ship&lt;/td&gt;
&lt;td&gt;7.8 MB&lt;/td&gt;
&lt;td&gt;5.8 MB&lt;/td&gt;
&lt;td&gt;23.7 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start, median of 100&lt;/td&gt;
&lt;td&gt;3.0 ms&lt;/td&gt;
&lt;td&gt;1.7 ms&lt;/td&gt;
&lt;td&gt;7.0 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turnaround, median of 100&lt;/td&gt;
&lt;td&gt;1.4 ms&lt;/td&gt;
&lt;td&gt;1.2 ms&lt;/td&gt;
&lt;td&gt;19.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client total, median of 100&lt;/td&gt;
&lt;td&gt;7.1 ms&lt;/td&gt;
&lt;td&gt;4.8 ms&lt;/td&gt;
&lt;td&gt;35.3 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Peak RSS&lt;/td&gt;
&lt;td&gt;7 MB&lt;/td&gt;
&lt;td&gt;6 MB&lt;/td&gt;
&lt;td&gt;11 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What each row means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Artefact&lt;/strong&gt; is the file the build produces: the native executable for C# and Go, the script for bash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime&lt;/strong&gt; is everything else that has to be in the image for it to run. For C# that's glibc, &lt;code&gt;libgcc_s&lt;/code&gt; and the dynamic loader. Go needs nothing. Bash needs bash, curl and every library they link.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship&lt;/strong&gt; is the two added together, which is what you actually deploy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start&lt;/strong&gt; is from the process spawning to the first request reaching the mock API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turnaround&lt;/strong&gt; is from the first reply finishing to the second request arriving: parsing the stream, running the tool and sending the next request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client total&lt;/strong&gt; is every millisecond of the run when the mock wasn't streaming, including the tail after the last reply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Peak RSS&lt;/strong&gt; is the most memory the process held at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines of code&lt;/strong&gt; counts the non-blank, non-comment lines of the one source file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The timings are medians of 100 runs, taken from timestamps the mock recorded, with nothing subtracted.&lt;/p&gt;

&lt;p&gt;.NET SDK 10.0.402, linux/arm64, Apple M4 Max under Docker. Published with &lt;code&gt;-r linux-arm64&lt;/code&gt; and the project settings above, run from the &lt;code&gt;runtime-deps&lt;/code&gt; image.&lt;/p&gt;

&lt;p&gt;The binary is smaller than Go's, which surprised me and will surprise more people. It is also not the whole story, and this is the reason the leaderboard has a runtime column. The Go binary is static. The .NET one links glibc, &lt;code&gt;libgcc_s&lt;/code&gt; and the dynamic loader, and &lt;code&gt;ldd&lt;/code&gt; on it finds 2.4 MB of shared libraries that have to be in the image. Add them and .NET ships 7.8 MB to Go's 5.8. Leave them out and you have an image that does not start. The &lt;code&gt;runtime-deps&lt;/code&gt; base image exists for exactly this reason. Its chiseled variant is smaller still, and has no shell, which is why the harness measures from the exported image rather than by running commands inside it.&lt;/p&gt;

&lt;p&gt;Cold start is three milliseconds against Go's under-two. The extra millisecond is the loader resolving those libraries and the AOT runtime initialising its GC, and it is a long way short of what a JIT-compiled .NET console app spends before it reaches &lt;code&gt;Main&lt;/code&gt;. The turnaround between the two requests is within a fraction of a millisecond of Go's, and peak memory is about the same. For a language that spent twenty years needing a runtime installed on the box, that table is a remarkable place to have got to.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run 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/AndyCross/claude-hot-path
&lt;span class="nb"&gt;cd &lt;/span&gt;claude-hot-path
./bench/bench.sh csharp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker and Python 3, no key. The AOT publish needs &lt;code&gt;clang&lt;/code&gt; and &lt;code&gt;zlib1g-dev&lt;/code&gt; in the SDK image, which the Dockerfile installs, and takes about a minute. Next week is Rust, the first client in the series without HTTP in its standard library.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Light on Brick, Paint on Cream</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Wed, 04 Nov 2026 10:51:39 +0000</pubDate>
      <link>https://dev.to/stepinto/light-on-brick-paint-on-cream-db4</link>
      <guid>https://dev.to/stepinto/light-on-brick-paint-on-cream-db4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Then the light's gone over the fence to someone else's garden,&lt;br&gt;
And the flowers stand there in the blue, keeping the colour to themselves&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://stepinto.dev/words/last-light.html" rel="noopener noreferrer"&gt;Last Light&lt;/a&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've been building a series of pages where the computer generates the pattern: &lt;a href="https://stepinto.dev/posts/tiles-painted-by-rule/" rel="noopener noreferrer"&gt;tin-glazed tiles painted by rule&lt;/a&gt;, then &lt;a href="https://stepinto.dev/posts/cut-tile-and-poured-cement/" rel="noopener noreferrer"&gt;cut tile and poured cement&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/heraldry-by-rule/" rel="noopener noreferrer"&gt;heraldry&lt;/a&gt;, &lt;a href="https://stepinto.dev/posts/knotwork-by-rule/" rel="noopener noreferrer"&gt;knotwork&lt;/a&gt; and, last time, &lt;a href="https://stepinto.dev/posts/a-piece-that-breaks-another-piece/" rel="noopener noreferrer"&gt;a piece that breaks another piece&lt;/a&gt;. They've been travelling round Europe, borrowing from the art and the history of each place, and trying to capture something about how patterns repeat across the whole of the artistic world. These two are Andalusia again, Seville and Granada, and they sit at opposite ends of what a page can do with a pattern. &lt;a href="https://stepinto.dev/art/sebka.html" rel="noopener noreferrer"&gt;Sebka&lt;/a&gt; paints nothing and lights everything. &lt;a href="https://stepinto.dev/art/fajalauza.html" rel="noopener noreferrer"&gt;Fajalauza&lt;/a&gt; lights nothing and is all paint.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dn0ickcyqtxbth5kd0n.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dn0ickcyqtxbth5kd0n.webp" alt="A wall of orange brick carrying a lattice of lobed arches that cross each other to make rows of scalloped lozenges, the openings sunk and in deep brown shadow, standing on rows of pale marble colonnettes, divided by brick pilasters and string courses with a row of studs, every edge throwing a long shadow to the left" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The tarde preset. The Giralda's net at half past six in the evening, the sun low on the right and every lobe throwing a long tooth of shade.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One shape on a lozenge grid
&lt;/h2&gt;

&lt;p&gt;The net on the upper storeys of the Giralda is called sebka, and it turns up on the Mudéjar towers of Teruel and Calatayud, on the Hassan tower in Rabat and the Kutubiyya in Marrakech. Lobed arches spring from a row of columns and go on crossing each other up the wall, so the face becomes a lattice of rhombi with every side scalloped.&lt;/p&gt;

&lt;p&gt;It's the simplest rule I've written for any of these pages. Set out a lozenge grid. Take each lozenge, shrink it by the width of the band, scallop its sides inward with the number of lobes you've asked for, and cut it out of a brick face as an opening. The lattice is whatever brick is left between the holes. The bottom row are the same shape with their lower halves dropped down to the imposts, on jambs, so the net ends on a row of marble colonnettes the way the real ones do. Pilasters and string courses stand in front and divide the wall into panels. The geometry stops there, and everything else on the page is light.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every shadow is cast
&lt;/h2&gt;

&lt;p&gt;The face has three heights: the sunk openings and the fields of the arcade, the brick face itself, and the courses and pilasters standing proud of it. The sun is a direction. The wall faces south, the sun comes up on the left at six, stands highest at one and sets on the right at eight, and from its angle the page works out a slant: how far along the wall a point standing one unit proud throws its shadow.&lt;/p&gt;

&lt;p&gt;Nothing on the page is drawn in shadow. Each level's shadow on the level below is that level's own outline, moved along the slant by the drop between them, with the caster cut back out of it. On an offscreen canvas that comes to a handful of compositing operations:&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="c1"&gt;// the face onto the openings, one drop down&lt;/span&gt;
&lt;span class="nf"&gt;pathAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalCompositeOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;destination-out&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;pathAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sunk&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="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// courses and pilasters onto the openings, two drops down&lt;/span&gt;
&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;globalCompositeOperation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source-over&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nf"&gt;pathAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;pathAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;mv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proud&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;lg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Filling the opening and then punching out the same opening moved along the sun leaves a crescent on the side the light comes from, which is the shadow of the brick lip on the floor of the hole. A course standing two units proud throws its shadow twice as far into the openings below. The layer is laid over the wall with &lt;code&gt;multiply&lt;/code&gt;, softened by a small blur so the edge isn't a knife cut, and made deeper the more squarely the sun strikes, without ever reaching black.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2kq7zinkxd3hqosu9bn.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2kq7zinkxd3hqosu9bn.webp" alt="Three panels of the same brick lattice. On the left, morning light from the left throws long shadows to the right inside every opening. In the middle, at midday, each opening has only a thin rim of shade along its top. On the right, evening light from the right throws long shadows to the left" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The giralda preset at half past seven, half past twelve and half past six. The geometry is identical in all three; only the sun has moved.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A shadow on a sunny wall isn't a darker version of the wall. It's the wall lit by the sky instead of the sun, so it goes blue, and it goes bluer when the sun is low and orange. The sun on the page has a colour of its own, near white at height and gold at the horizon, and the shade it leaves is the sky's. Multiplied over orange brick, that cool shade comes out as a deep brown with no warmth in it, which is what the openings of a brick tower look like against the lit face around them. At noon there's only a thin rim of it under the top of each opening.&lt;/p&gt;

&lt;p&gt;Two smaller things do a lot of the work at a low sun. The mortar joints are struck back from the face, so each one fills with the shadow of the course above, and the brick suddenly reads as laid rather than printed. And the courses carry a frieze of studs, each one a low pyramid with four faces, so every stud has a face to the sun and a face away from it and the whole band glitters when the light rakes along it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F62btzm36y4760k1ke2ef.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F62btzm36y4760k1ke2ef.webp" alt="A brick wall with a wide lattice of two-lobed arches, each opening holding a glazed disc alternately green and white, the courses set with green and white glazed studs, morning light casting shadows to the right" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The teruel preset. Aragón's Mudéjar towers set glazed discs in the openings and glazed studs in the courses, and they catch the sun differently from the brick around them.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's an hour slider, and a time checkbox that runs the day. The presets are towers: giralda in the morning and again as tarde in the evening, teruel and calatayud in Aragón's brick with glazed discs or lime in the openings, hassan and kutubiyya in the stone of Rabat and Marrakech, and doncellas in Nasrid plaster after the Alcázar's courtyard. None of this went into the package. The shadow caster is three passes on offscreen canvases and it belongs to Sebka until another page wants it, which will probably be Rejería, wrought iron and its shadow on whitewash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fajalauza
&lt;/h2&gt;

&lt;p&gt;Fajalauza is the pottery of Granada's Albaicín, named after the gate its workshops stood by. It's a cream tin glaze with copper green and cobalt blue laid on it thick and fast, and the same few things painted over and over: a pomegranate, the one on the city's arms; a plump bird with a raised tail; and a branch, running round the rim of a plate or across a row of tiles.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/art/fajalauza.html" rel="noopener noreferrer"&gt;Fajalauza&lt;/a&gt; is a tile wall on the same engine as &lt;a href="https://stepinto.dev/art/jerez.html" rel="noopener noreferrer"&gt;Jerez&lt;/a&gt;, with Granada's nouns. Nothing on it is lit. It's flat colour on a glaze, painted by the drifting brush every page in the engine shares.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8icwvpedddwkv6bpob0e.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8icwvpedddwkv6bpob0e.webp" alt="A wall of cream tiles. At every other grout crossing four blue pomegranates point outward from a dark purple hub, with green leaves between them along the joints. At the crossings in between, a green ring of branch with small leaves surrounds a blue and green rosette" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The granada preset. Four pomegranates meet nose out at every other crossing, a leaf along each joint between them, and the branch makes a wreath round the crossings in between.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bird and the pomegranate are traced
&lt;/h2&gt;

&lt;p&gt;I learned three times on these pages that a figure drawn from parts looks like a toy. A traditional figure that painters copied by hand for centuries has a shape the hand knows and a rule doesn't. So the pomegranate and the bird were traced from flat drawings from the start, the same way Delft's ships and Jerez's horse were.&lt;/p&gt;

&lt;p&gt;The drawings come from an image model, on the terms the tracer wants: a pure white page, one solid gold fill for the body, a thick black line closing every part, red and blue for the parts that take another colour, and nothing else in the frame. The bird came back as a plump folk-art bird with a red wing scalloped in black, a blue fanned tail and a blue crest. The pomegranate came back split, with forty-five blue seeds packed in rows, a gold crown and two red leaves.&lt;/p&gt;

&lt;p&gt;The tracer from the heraldry post classes every pixel by its nearest colour, walks the edges of each class into closed loops, simplifies them and normalises them to the figure's box. The bird is one silhouette, six loops of wing, seven of tail and crest, and twenty-one of ink. The pomegranate's seeds come through as forty-five separate loops, one per seed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqw01p6fbgxv6q830ipd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flqw01p6fbgxv6q830ipd.webp" alt="Two rows. In the top row, a flat drawing of a gold bird with a red scalloped wing, blue tail and crest, beside a wall of cream tiles each holding that bird in blue with a green wing and tail standing on a green sprig, blue and green flowers at the corners. In the bottom row, a flat drawing of a gold pomegranate split to show blue seeds, with two red leaves, beside the pomegranate tile wall in blue and green" width="800" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The two drawings, and the walls traced from them. Gold became the main colour, red and blue the second, black the ink.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the wall, gold becomes the main colour, washed thin and then again slightly off so it pools at one edge. Red and blue become the other colour, laid over a knock of glaze so they don't mix with the wash underneath. Black becomes the ink, laid last. The palettes carry a &lt;code&gt;leaf&lt;/code&gt; and a &lt;code&gt;seed&lt;/code&gt; colour beside the &lt;code&gt;wash&lt;/code&gt;, so the two colours trade places from palette to palette: a blue bird with a green wing on the fajalauza palette, a green bird with a dark green wing on verde, and blue on white throughout on azul.&lt;/p&gt;

&lt;p&gt;The pomegranate stands on the tile's diagonal, its stem at the corner and its crown pointing into the tile, so where four tiles meet, four pomegranates point outward from one hub. The bird stands in the middle of the tile, and each of the eight paintings a wall draws from turns it left or right on its own random stream, so a wall laid all one way up is a flock rather than one bird copied.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farbn47r05zpptzrw1pv2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Farbn47r05zpptzrw1pv2.webp" alt="A wall of white tiles painted in cobalt only. Each tile holds a bird in a circle, standing on a sprig with a sprig either side, some facing left and some right, with a blue flower completed across every grout crossing" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The alacena preset, all cobalt, every bird in its circle. Some face left and some face right, depending on how that painting came out.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The branch has to meet itself
&lt;/h2&gt;

&lt;p&gt;The branch couldn't be traced, because it has to continue from one tile into the next however the wall is laid, mirrored, turned or every tile the same way up. A drawing can't promise that. A rule can.&lt;/p&gt;

&lt;p&gt;Each stem is a quarter circle of radius one half round a corner of the tile. It reaches each edge at its midpoint and meets it square on, and every repeat the engine has maps edge midpoints onto edge midpoints, so the stem always arrives where the neighbouring tile's stem leaves. With a quarter circle round every corner, each grout crossing gets a whole ring, each ring touches its four neighbours at the midpoints of the tiles' edges, and the wall becomes one chain.&lt;/p&gt;

&lt;p&gt;The first version put leaves on both sides of the stem, and the outward ones from four rings all crowded the middle of every tile into a dark knot. Now the leaves turn into the rings, each mirrored across the middle of its arc so the ring stays balanced, and the middle of the tile is left for three berries from each ring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85o0w1yyb0zfdi304zto.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F85o0w1yyb0zfdi304zto.webp" alt="A wall of cream tiles covered by a continuous green branch forming a chain of touching rings, one ring round every grout crossing, with leaves turned inward and a small green and blue rosette in the centre of alternate rings" width="799" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The rama preset on the verde palette. Every stem is a quarter circle of radius one half, so the rings close across the grout.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are seven presets: granada, pájaro, rama, albaicín (an old yellowed cream with four pomegranates on every tile), alacena, carmen (the branch whole on every tile) and sacromonte (green birds under a manganese line). Fajalauza needed nothing from the engine beyond the &lt;code&gt;variants&lt;/code&gt; option Delft added last time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple at the rule
&lt;/h2&gt;

&lt;p&gt;The two pieces have nothing in common in their code. Sebka's work is geometry and compositing, with no brush at all, and Fajalauza's is a palette, two traced figures and a quarter circle. What they share is that each one is simple at the rule and complicated where the material is. The lattice is a lozenge grid and the branch is a quarter circle, and nearly all the code on both pages goes on making brick sit in the sun and paint pool on a glaze.&lt;/p&gt;

&lt;p&gt;The seventeen wallpaper groups are next: every way a pattern can repeat on a plane, as a control on the wall, in block-printed paper with the registration drifting.&lt;/p&gt;

</description>
      <category>art</category>
      <category>generative</category>
      <category>canvas</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Latent State Is the Fields You Never Logged</title>
      <dc:creator>stepinto.dev</dc:creator>
      <pubDate>Mon, 02 Nov 2026 07:23:35 +0000</pubDate>
      <link>https://dev.to/stepinto/latent-state-is-the-fields-you-never-logged-23ip</link>
      <guid>https://dev.to/stepinto/latent-state-is-the-fields-you-never-logged-23ip</guid>
      <description>&lt;p&gt;The &lt;a href="https://stepinto.dev/posts/world-models-103/" rel="noopener noreferrer"&gt;last post&lt;/a&gt; ended with a challenge. Four weights fitted from logs can learn a room, but give that room a door that opens and closes on a schedule the logger never records, and the best a linear model can do is average over the door.&lt;/p&gt;

&lt;p&gt;The log has three columns: the room temperature, the outside temperature and whether the heater is on. When the door opens the room cools faster, and every row written while it is open looks like a row from a room that is simply worse at holding heat. The model sees the effect and never the cause, so fitting the same three columns harder gets it nowhere. What the log does still have is order. A room that was cooling steadily and then drops three degrees in one step has told you something, even if no column says what.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://stepinto.dev/posts/world-models-102/" rel="noopener noreferrer"&gt;102 post&lt;/a&gt; described how we might work around this: give the model a private field it keeps for itself, and let it decide what to put there. It also promised to explain how such a field can be learned when nobody ever sees it.&lt;/p&gt;

&lt;p&gt;This post builds the room with the door, watches the four-weight model fail on it, gives the model one private field, and watches the field turn into the door. The maths comes at the end, because by then it describes something you have watched run. The &lt;a href="https://stepinto.dev/heater/?scenario=door" rel="noopener noreferrer"&gt;playground&lt;/a&gt; has the door in it now, so everything below can be rerun in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  An invisible door (according to the data)
&lt;/h2&gt;

&lt;p&gt;I'm using the same simulated room from the last post, with one addition. Someone keeps opening the door, and while it is open the room leaks heat three and a half times faster.&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;LEAK_CLOSED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.07&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// the 103 room&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LEAK_OPEN&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;doorOpen&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="c1"&gt;// the room's secret; never written to the log&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;someone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// opens the door about once every 25 steps, leaves it open about 8&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;doorOpen&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="nf"&gt;rand&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="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="nx"&gt;doorOpen&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="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="nf"&gt;rand&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="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="nx"&gt;doorOpen&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;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;realRoom&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;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heaterOn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;heaterAfter&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;action&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;leak&lt;/span&gt; &lt;span class="o"&gt;=&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;outsideTemp&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;roomTemp&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;doorOpen&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;LEAK_OPEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LEAK_CLOSED&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;heat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;heaterOn&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;REAL_HEAT&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="na"&gt;roomTemp&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;roomTemp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;leak&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;heat&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;gauss&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;NOISE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;outsideTemp&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;outsideTemp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heaterOn&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 rule from the last post still holds: nothing below this line looks at &lt;code&gt;doorOpen&lt;/code&gt;. The logger calls &lt;code&gt;someone()&lt;/code&gt; before every step and writes down the same three fields it always did, so a row from the new log is indistinguishable from a row from the old one. Eighty episodes of sixty steps gives 4,800 transitions, and the door is open for 22 percent of them. Nothing in the log says which.&lt;/p&gt;

&lt;p&gt;If you run devices in the field, this is every column your telemetry does not have. The window someone left open, the second heater in the room that is not on your network, the pump that was switched to manual for an afternoon. The dynamics change and the log does not say why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What four weights do with a door
&lt;/h2&gt;

&lt;p&gt;First I refitted the four-weight model on the new log, with the same &lt;code&gt;features&lt;/code&gt;, the same twenty-line training loop and ten thousand epochs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;weight&lt;/th&gt;
&lt;th&gt;sealed room (103)&lt;/th&gt;
&lt;th&gt;with the door&lt;/th&gt;
&lt;th&gt;truth, door closed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;room&lt;/td&gt;
&lt;td&gt;-0.074&lt;/td&gt;
&lt;td&gt;-0.037&lt;/td&gt;
&lt;td&gt;-0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;outside&lt;/td&gt;
&lt;td&gt;0.069&lt;/td&gt;
&lt;td&gt;0.040&lt;/td&gt;
&lt;td&gt;0.07&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;heater&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;td&gt;0.97&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bias&lt;/td&gt;
&lt;td&gt;0.077&lt;/td&gt;
&lt;td&gt;-0.41&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I expected the loss to get worse, and it flattens at 0.29 against 0.01 in the sealed room. I didn't expect the shape of the damage. The model has not averaged over the door, which would have given a room weight somewhere between the closed and open leak rates. It has halved the room weight and gone looking for the lost heat in the bias.&lt;/p&gt;

&lt;p&gt;The reason is that the door is not independent of the things the model can see. An open door drags the room down, so in the log a cold room and a big drop arrive together, and the only lever the model has for "cold room" is the room weight. It pushes that weight towards zero to account for the drops, then pulls the bias negative to make the sums balance. A hidden variable that moves with the visible ones bends the fit, which is rather worse than adding noise to it.&lt;/p&gt;

&lt;p&gt;You can see the bend when the model is asked to forecast. Later in the post there is a room at minus four with the heater on, where the door opens at step 10. Ask this model at step 13, with the room at 7.8 degrees and falling fast, what the next five steps look like, and it says the room warms to 8.3. The real room goes to 2.7.&lt;/p&gt;

&lt;h2&gt;
  
  
  One private field
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://stepinto.dev/posts/world-models-102/" rel="noopener noreferrer"&gt;102 post&lt;/a&gt; sketched the shape of the fix as a class with a field nobody outside can read:&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;class&lt;/span&gt; &lt;span class="nc"&gt;WorldModel&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="nb"&gt;Float32Array&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// s_t lives here, and nowhere in your logs&lt;/span&gt;
  &lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Observation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* fold the new reading into #state */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;act&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;Action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Distribution&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Observation&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;/* predict, given #state and a */&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;Here is the smallest version of that I could make work. The private field is one number. &lt;code&gt;act&lt;/code&gt; is the four-weight model from 103, plus a second weighted sum that the field switches on. &lt;code&gt;observe&lt;/code&gt; folds what just happened into the field, using a weighted sum of its own, squashed through &lt;code&gt;tanh&lt;/code&gt; so the field stays between minus one and one.&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;type&lt;/span&gt; &lt;span class="nx"&gt;Weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// act: change in room temperature = base + field * extra&lt;/span&gt;
  &lt;span class="na"&gt;room&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;outside&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;heater&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;bias&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;fieldRoom&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;fieldOutside&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;fieldBias&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="c1"&gt;// observe: next field = tanh(a weighted sum of the field and what just happened)&lt;/span&gt;
  &lt;span class="nl"&gt;uField&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;uRoom&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;uOutside&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;uHeater&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;uChange&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;uBias&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;class&lt;/span&gt; &lt;span class="nc"&gt;WorldModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;#field&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="c1"&gt;// s_t lives here, and nowhere in the logs&lt;/span&gt;
  &lt;span class="nl"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Weights&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&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;Weights&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;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;reset&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;#field&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="nf"&gt;act&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;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputs&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;action&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;w&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;w&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;base&lt;/span&gt; &lt;span class="o"&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;room&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;room&lt;/span&gt; &lt;span class="o"&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;outside&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;outside&lt;/span&gt; &lt;span class="o"&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;heater&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;heater&lt;/span&gt; &lt;span class="o"&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;bias&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;extra&lt;/span&gt; &lt;span class="o"&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;fieldRoom&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;room&lt;/span&gt; &lt;span class="o"&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;fieldOutside&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;outside&lt;/span&gt; &lt;span class="o"&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;fieldBias&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;roomTemp&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;roomTemp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;base&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;#field&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;outsideTemp&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;outsideTemp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;heaterOn&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;heaterOn&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;observe&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;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nextState&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="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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputs&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;action&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;w&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;w&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;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roomTemp&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;roomTemp&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;#field&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;tanh&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;uField&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;#field&lt;/span&gt; &lt;span class="o"&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;uRoom&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;room&lt;/span&gt; &lt;span class="o"&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;uOutside&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;outside&lt;/span&gt; &lt;span class="o"&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;uHeater&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;heater&lt;/span&gt; &lt;span class="o"&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;uChange&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="o"&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;uBias&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;Thirteen numbers, against four. Seven of them say what the field does to the prediction; six say what the model should write into the field after each step. Nothing in there mentions a door. The field is allowed to be anything between minus one and one, and the model gets to decide what it means.&lt;/p&gt;

&lt;p&gt;Two small things changed alongside. &lt;code&gt;inputs&lt;/code&gt; is &lt;code&gt;features&lt;/code&gt; with the temperatures centred and scaled (room temperature minus 17, outside minus 7, both divided by five), and the change the model observes is divided by five too. In the last post I left the inputs raw and paid for it with ten thousand epochs down a long thin valley. This time the loop below is more expensive per epoch, so I took the shortcut the 103 post mentioned, and it converges in three thousand.&lt;/p&gt;

&lt;p&gt;The other change is to the &lt;code&gt;observe&lt;/code&gt; signature. The 102 sketch took an observation; this one takes the whole transition, because the thing the field most needs to know is how the room moved compared with what the model expected, and that needs both ends of the step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Training through the field
&lt;/h2&gt;

&lt;p&gt;The 102 post promised to explain how this gets trained. The field is never in the log, so there is no column to compare it against. The thirteen weights have to be judged by the only thing that is in the log, the next temperature, and six of those weights only affect the next temperature indirectly, by changing what gets written into the field, which changes the prediction a step later, and the step after that.&lt;/p&gt;

&lt;p&gt;So the gradient has to flow through the field. In 103 each row of the log was its own small problem, and the loop could visit rows in any order. Now a row's prediction depends on the field, the field depends on the previous row, and so on back to the start of the episode. The training loop walks each episode forwards, remembering what the field was at every step, and then walks it backwards, asking at each step how much that step's field mattered to everything that came after 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;function&lt;/span&gt; &lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transition&lt;/span&gt;&lt;span class="p"&gt;[][],&lt;/span&gt; &lt;span class="nx"&gt;epochs&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;lr&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="p"&gt;{}&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;Weights&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;k&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="nx"&gt;w&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="nx"&gt;k&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="s2"&gt;u&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="nf"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&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="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;losses&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="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;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flat&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;let&lt;/span&gt; &lt;span class="nx"&gt;epoch&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;epoch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;epochs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;epoch&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;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="kd"&gt;as &lt;/span&gt;&lt;span class="nx"&gt;Weights&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;k&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="nx"&gt;g&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="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;loss&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;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;episode&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// forwards through the episode, remembering what the field was at every step&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;steps&lt;/span&gt; &lt;span class="o"&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;field&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;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;t&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;episode&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;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputs&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;state&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;action&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;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roomTemp&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roomTemp&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;base&lt;/span&gt; &lt;span class="o"&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;room&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;room&lt;/span&gt; &lt;span class="o"&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;outside&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;outside&lt;/span&gt; &lt;span class="o"&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;heater&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;heater&lt;/span&gt; &lt;span class="o"&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;bias&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;extra&lt;/span&gt; &lt;span class="o"&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;fieldRoom&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;room&lt;/span&gt; &lt;span class="o"&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;fieldOutside&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;outside&lt;/span&gt; &lt;span class="o"&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;fieldBias&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;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt; &lt;span class="o"&gt;+&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;extra&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;loss&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;err&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;change&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;target&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;next&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;tanh&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;uField&lt;/span&gt; &lt;span class="o"&gt;*&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;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uRoom&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;room&lt;/span&gt; &lt;span class="o"&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;uOutside&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;outside&lt;/span&gt; &lt;span class="o"&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;uHeater&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;heater&lt;/span&gt; &lt;span class="o"&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;uChange&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;change&lt;/span&gt; &lt;span class="o"&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;uBias&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;steps&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;change&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;extra&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="p"&gt;});&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;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;// backwards through the episode: how much did each step's field matter to everything after it?&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dField&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="c1"&gt;// d loss / d (field after this step)&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="nx"&gt;steps&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;1&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;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;--&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;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;steps&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dPre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dField&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="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;next&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;next&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// through the tanh&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;uField&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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;field&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="nx"&gt;uRoom&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;room&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="nx"&gt;uOutside&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outside&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="nx"&gt;uHeater&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heater&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="nx"&gt;uChange&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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;change&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="nx"&gt;uBias&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;dPre&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="nx"&gt;room&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;err&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;room&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="nx"&gt;outside&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;err&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outside&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="nx"&gt;heater&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;err&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;heater&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="nx"&gt;bias&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;err&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="nx"&gt;fieldRoom&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;err&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;field&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;room&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="nx"&gt;fieldOutside&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;err&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;field&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;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outside&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="nx"&gt;fieldBias&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;err&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;field&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;dField&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;err&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;extra&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;dPre&lt;/span&gt; &lt;span class="o"&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;uField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// on to the step before&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;k&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="nx"&gt;w&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="nx"&gt;lr&lt;/span&gt; &lt;span class="o"&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;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;losses&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;loss&lt;/span&gt; &lt;span class="o"&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="k"&gt;return&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;losses&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;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;losses&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The forwards half is &lt;code&gt;act&lt;/code&gt; and &lt;code&gt;observe&lt;/code&gt; inlined. The backwards half is the chain rule, and two lines of it carry the whole idea. &lt;code&gt;dPre&lt;/code&gt; is how much the loss would change if the field written at this step were nudged, pulled back through the &lt;code&gt;tanh&lt;/code&gt;. The last line passes that on to the step before, because the step before chose the field this step started with. Everything else is the &lt;code&gt;err * input&lt;/code&gt; pattern from 103, applied to whichever weight the line is about.&lt;/p&gt;

&lt;p&gt;This is backpropagation through time, and it is the reason recurrent models are trained on episodes rather than on rows. Three thousand epochs over 4,800 rows takes about half a second in Node 23. The six &lt;code&gt;u&lt;/code&gt; weights start at small random values rather than zero, because with every &lt;code&gt;u&lt;/code&gt; at zero the field never moves, nothing depends on it, and the gradient for those weights is exactly zero forever. The seven prediction weights start at zero as before.&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%2F0jiyq22rcx83hni1qwum.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%2F0jiyq22rcx83hni1qwum.png" alt="Mean squared error while training the latent model, three thousand epochs" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mean squared error per epoch for the latent model, with three reference lines. The four-weight model stopped at 0.29. A model that could see the door would reach 0.01, the level of the draughts. The same model told the door one step late gets 0.17, and the latent model settles just under it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/data/heater-door-loss.csv" rel="noopener noreferrer"&gt;Download the data (CSV)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The two dashed lines show what the model is being asked to do. I fitted a seven-weight model that is told the door state, which is cheating, to find out what the best possible fit looks like. Scored normally it reaches the noise floor. Then I gave the same model the door state one step late, so it only learns the door has moved after seeing the step on which it moved, and it gets 0.174. The step the door moves on is a surprise by definition, and the 310 steps where it moved account for 0.165 of that on their own. The latent model gets 0.172.&lt;/p&gt;

&lt;p&gt;That second line is a reference rather than a floor, and the latent model sitting under it is the clue. The late-door model insists the door is wherever it was a step ago. A model that knows the door a step late and also knows how often it moves (about one step in eight while it is open, one in twenty-five while it is shut) can hedge, adding a little leak to every closed step in case the door has just opened, and that scores 0.163. Refitting the seven weights on the late door state lands in the same place. So the latent model is about a hundredth above what noticing the door can reach with this shape of model. None of these numbers is a true lower bound, either: all of them are scored on the 4,800 rows the models were fitted to, and a model with a better shape could go lower.&lt;/p&gt;

&lt;p&gt;Here is what it learned, with the temperature weights divided by five so they read per degree.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;weight&lt;/th&gt;
&lt;th&gt;learned&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;weight&lt;/th&gt;
&lt;th&gt;learned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;room&lt;/td&gt;
&lt;td&gt;-0.111&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uField&lt;/td&gt;
&lt;td&gt;0.007&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;outside&lt;/td&gt;
&lt;td&gt;0.078&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uRoom&lt;/td&gt;
&lt;td&gt;-0.049&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;heater&lt;/td&gt;
&lt;td&gt;1.21&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uOutside&lt;/td&gt;
&lt;td&gt;0.031&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bias&lt;/td&gt;
&lt;td&gt;-0.92&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uHeater&lt;/td&gt;
&lt;td&gt;0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fieldRoom&lt;/td&gt;
&lt;td&gt;0.010&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uChange&lt;/td&gt;
&lt;td&gt;-2.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fieldOutside&lt;/td&gt;
&lt;td&gt;0.015&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;uBias&lt;/td&gt;
&lt;td&gt;-0.39&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fieldBias&lt;/td&gt;
&lt;td&gt;-1.83&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the right-hand column as the recipe for the field. The weight on &lt;code&gt;uChange&lt;/code&gt; is by far the largest: the field is mostly a function of how much the room moved last step, with the heater and the temperatures subtracted off so that a drop the base model already expected does not count. The weight on &lt;code&gt;uField&lt;/code&gt;, the field's memory of itself, is almost nothing. For this room the model decided it did not need memory, because one step of an open door is loud enough to hear on its own. That changes when the door gets quieter, further down.&lt;/p&gt;

&lt;p&gt;The left-hand column is less tidy than in 103. The room and outside weights are no longer equal and opposite, and the field's effect is almost all in &lt;code&gt;fieldBias&lt;/code&gt;. The model is not obliged to factor the room into "closed door physics plus a door term" the way I would, and the gradient found a different split that fits the log just as well. A private field has no label on it. It holds whatever the model found useful, and you judge it by the predictions, as the 102 post said you would have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The field finds the door
&lt;/h2&gt;

&lt;p&gt;The test is the same unseen room as in the last post: 22 degrees inside, minus four outside, colder than anything in the log. The heater is on throughout. The door opens at step 10 and closes at step 20, and the models are not told.&lt;/p&gt;

&lt;p&gt;Two things are different about how the models are run. First, each one sees the real reading at every step and predicts the next one, the way a thermostat would, rather than running thirty steps blind from the start. With a door it cannot see, no model could run thirty steps blind, and the point of the field is that the model notices. Second, at three chosen steps, each model is asked to run five steps blind from where it is, which is the "what if" that world models are for.&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%2Fc5tfyl40y3b0q9u3d29u.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%2Fc5tfyl40y3b0q9u3d29u.png" alt="Thirty steps with the heater on at minus four outside, the door open from step 10 to 20" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Top: the real room, with five-step forecasts from both models branching off at steps 5, 13 and 23. Bottom: the private field after each step, with the door state it never saw dashed over it. The field is near zero for ten steps, 0.91 the step after the door opens, and 0.12 the step after it closes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stepinto.dev/data/heater-door-unseen.csv" rel="noopener noreferrer"&gt;Download the data (CSV)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The strip at the bottom is the result I was after. The field sits a little below zero while the door is closed, jumps to 0.9 on the first step after it opens, stays high while it is open, and drops back the step after it closes. The model was never told there was a door, and the field is a door anyway, because a door was the thing in the room that the log could not explain and a single number could.&lt;/p&gt;

&lt;p&gt;Step by step at the two moments that matter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;step&lt;/th&gt;
&lt;th&gt;door&lt;/th&gt;
&lt;th&gt;real room&lt;/th&gt;
&lt;th&gt;four-weight, one step ahead&lt;/th&gt;
&lt;th&gt;latent, one step ahead&lt;/th&gt;
&lt;th&gt;field after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;closed&lt;/td&gt;
&lt;td&gt;17.19&lt;/td&gt;
&lt;td&gt;17.16&lt;/td&gt;
&lt;td&gt;16.97&lt;/td&gt;
&lt;td&gt;-0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;13.14&lt;/td&gt;
&lt;td&gt;16.96&lt;/td&gt;
&lt;td&gt;16.90&lt;/td&gt;
&lt;td&gt;0.91&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;10.10&lt;/td&gt;
&lt;td&gt;13.06&lt;/td&gt;
&lt;td&gt;11.16&lt;/td&gt;
&lt;td&gt;0.86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;7.76&lt;/td&gt;
&lt;td&gt;10.13&lt;/td&gt;
&lt;td&gt;8.52&lt;/td&gt;
&lt;td&gt;0.81&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;1.94&lt;/td&gt;
&lt;td&gt;2.69&lt;/td&gt;
&lt;td&gt;2.27&lt;/td&gt;
&lt;td&gt;0.59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;closed&lt;/td&gt;
&lt;td&gt;2.83&lt;/td&gt;
&lt;td&gt;2.27&lt;/td&gt;
&lt;td&gt;1.77&lt;/td&gt;
&lt;td&gt;0.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;closed&lt;/td&gt;
&lt;td&gt;3.55&lt;/td&gt;
&lt;td&gt;3.13&lt;/td&gt;
&lt;td&gt;3.56&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Step 11 is wrong for both models and has to be. Nobody could have predicted the door. From step 12 the latent model is within a degree and closing, while the four-weight model is three degrees adrift and stays there. Step 21 is the same story in reverse. Over the thirty steps the one-step error is 1.16 degrees for the four-weight model and 0.78 for the latent one; leave out the two steps the door moved on and it is 0.95 against 0.32.&lt;/p&gt;

&lt;p&gt;At step 13 the latent model has had two steps to notice the door, and its field reads 0.81. Asked for five steps blind, it says the room will fall from 7.8 to 2.7, and the room falls to 2.7. The four-weight model, from the same reading, says 8.3. It has no way to represent "the room is currently leaking faster than usual", so it cannot condition on it, and its bent physics sends the forecast the wrong way.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;forecast from&lt;/th&gt;
&lt;th&gt;real, five steps on&lt;/th&gt;
&lt;th&gt;four-weight&lt;/th&gt;
&lt;th&gt;latent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;step 5, door closed&lt;/td&gt;
&lt;td&gt;17.2&lt;/td&gt;
&lt;td&gt;17.7&lt;/td&gt;
&lt;td&gt;17.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;step 13, door open&lt;/td&gt;
&lt;td&gt;2.7&lt;/td&gt;
&lt;td&gt;8.3&lt;/td&gt;
&lt;td&gt;2.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;step 23, door just closed&lt;/td&gt;
&lt;td&gt;6.8&lt;/td&gt;
&lt;td&gt;5.3&lt;/td&gt;
&lt;td&gt;6.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The strip also shows the field drifting down from 0.91 to about 0.55 over the ten open steps. It is reading the size of the drop, and the drop shrinks as the room gets closer to the temperature an open room would settle at. The model has no memory to hold the field up because, as the weights table said, it did not learn any. Make the door quieter and that changes. Rerunning the whole script with the open-door leak at 0.15 instead of 0.25 brings the memory weight from 0.007 to 0.08, and at 0.12 it is 0.26: when one step is no longer enough to tell, the model learns to add up several. At 0.10, where the door only adds a few hundredths to the leak, the field stops tracking the door at all, and the latent model's loss sits above the one-step-late line. There is a signal it cannot find in 4,800 rows, and the answer, as with most problems in this series, is more log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now the notation
&lt;/h2&gt;

&lt;p&gt;Everything above fits in two lines. The private field is (h), the readings the logger can see are (o), and the action is (a).&lt;/p&gt;

&lt;p&gt;$$h_t = f(h_{t-1},\ o_{t-1},\ a_{t-1},\ o_t)$$&lt;/p&gt;

&lt;p&gt;$$\hat{o}_{t+1} = g(h_t,\ o_t,\ a_t)$$&lt;/p&gt;

&lt;p&gt;The first line is &lt;code&gt;observe&lt;/code&gt;: the new field is a function of the old field and the step that just happened. The second is &lt;code&gt;act&lt;/code&gt;: the predicted next reading is a function of the field, the current reading and the action. In this post (f) is a &lt;code&gt;tanh&lt;/code&gt; of a weighted sum and (g) is two weighted sums, and training them was gradient descent with the chain rule run backwards through the first line.&lt;/p&gt;

&lt;p&gt;Put the field and the readings together and call the pair (s_t), and the second line is the world model equation from the &lt;a href="https://stepinto.dev/posts/world-models-102/" rel="noopener noreferrer"&gt;102 post&lt;/a&gt;, (P(s_{t+1} \mid s_t, a_t)), with the probability dropped because the model predicts a single number. The state the equation conditions on was always meant to include the fields you never logged. That is why it is called latent: the field is hidden from the logger, but the maths had a place for it all along.&lt;/p&gt;

&lt;h2&gt;
  
  
  How DreamerV3 does the same job
&lt;/h2&gt;

&lt;p&gt;The part of this post that carries over most directly to the research systems is the one it spent longest on. Nobody tells DreamerV3 what to keep in its recurrent state either. The state has to hold whatever a single observation cannot show, such as how fast something is moving or where an object went when it left the frame, and it learns to hold those things for the same reason the field learned the door: the predictions were wrong without them, and the gradient, walking back through each sequence, found which numbers would fix that.&lt;/p&gt;

&lt;p&gt;The architecture carries over less neatly. DreamerV3 calls its world model a recurrent state-space model, and it has more parts than my two methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a sequence model, a GRU, that carries a recurrent state (h_t) forward from (h_{t-1}), the last action and the last latent (z_{t-1})&lt;/li&gt;
&lt;li&gt;an encoder that reads the new observation alongside (h_t) and produces (z_t), a stochastic latent made of 32 categorical variables with 32 classes each&lt;/li&gt;
&lt;li&gt;a dynamics predictor that guesses (z_t) from (h_t) alone, before the observation arrives&lt;/li&gt;
&lt;li&gt;a decoder, with heads for reward and for whether the episode carries on, that reads (h_t) and (z_t) together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My &lt;code&gt;observe&lt;/code&gt; does the work of the sequence model and the encoder in one line, and my &lt;code&gt;act&lt;/code&gt; is closest to the decoder. Three of the differences matter, and each of them shows up somewhere in this post.&lt;/p&gt;

&lt;p&gt;The first is where the readings go. My &lt;code&gt;act&lt;/code&gt; is handed the current reading, so the field only has to carry what the reading leaves out, and one number covers the door. DreamerV3's decoder has nothing but the state to work from, so the state has to carry everything, the equivalent of the temperature included. That is why it is hundreds or thousands of numbers wide, and it is what lets the model imagine many steps ahead with no observations at all. My five-step forecasts get away with feeding the predicted temperature back in as the next reading, which works only because the temperature is the whole of what the logger sees.&lt;/p&gt;

&lt;p&gt;The second is the guess made before the reading. DreamerV3 keeps two versions of (z_t) at every step: the dynamics predictor's guess from (h_t), and the encoder's answer once the observation is in. Part of its training loss is the gap between the two. Step 11 in the table above is that gap in miniature, where the model expected a shut door and the reading said otherwise. My model only ever has the answer, so when it forecasts blind its field stays wherever it was. It cannot imagine the door shutting at step 20, or opening in the first place. DreamerV3's guess is a distribution, so an imagined rollout can open the door in some samples and leave it shut in others.&lt;/p&gt;

&lt;p&gt;The third is how the training is fed. The backward walk through each sequence is the same, but DreamerV3 cuts its experience into chunks of 64 steps drawn from a replay buffer that keeps filling while it acts, and its loss has more than one term: how well it reconstructs the observation, the reward and the continuation, plus two terms that pull the guess and the answer towards each other, weighted 0.5 and 0.1. Mine is one squared error over eighty fixed episodes.&lt;/p&gt;

&lt;p&gt;The 101 post joked about representation collapse, where a model scores perfectly by declaring every state identical. That failure needs the model's target to be something the model produces itself. JEPA-style models, from LeCun's line of work, predict the representation of the next observation rather than the observation, so a representation that never changes predicts itself perfectly, and they keep a separate, slowly updated target encoder behind a stop-gradient to prevent it. My model's target is a thermometer reading, which it has no way to change. A constant field earns it nothing, and the model falls back to the four-weight fit and its 0.29. DreamerV3 has the same protection, because it reconstructs its observations, and a quieter risk from the other side: the term that pulls the encoder's answer towards the guess would, pushed far enough, teach the encoder to ignore the observation. DreamerV3 stops penalising that gap once it is below one nat, which it calls free bits.&lt;/p&gt;

&lt;p&gt;The nearest thing this post has to collapse is a field that goes unused, and that is less final than it sounds. With every &lt;code&gt;u&lt;/code&gt; weight at exactly zero the gradient is exactly zero, which is why they start small and random, but near zero it is small and still there. When the open-door leak came down to 0.10, the field stopped tracking the door because the signal was too faint to find in 4,800 rows, which is a shortage of log and not a trap the training fell into.&lt;/p&gt;

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

&lt;p&gt;The &lt;a href="https://stepinto.dev/heater/?scenario=door" rel="noopener noreferrer"&gt;playground&lt;/a&gt; has a scenario switch at the top. In the door scenario, recording adds the door, training runs the four-weight model and then the latent one, and the right-hand pane runs the room at minus four with a slider for where the five-step forecast starts. Drag that slider across step 10 and watch the orange forecast turn the wrong way. The open-door leak has a slider too; bring it down towards the closed rate and watch the memory weight in the latent model's table climb, then watch the field stop finding the door altogether.&lt;/p&gt;

&lt;p&gt;The playground runs the same code with &lt;code&gt;Math.random&lt;/code&gt; where the script's seed is, so your numbers will differ from the ones above by a little and agree with them in shape. The data behind each chart is linked under it.&lt;/p&gt;

&lt;p&gt;The next post puts this model next to an LLM. One predicts the next token of text, the other predicts the next state of a room, and the interesting part is where an agent would want both.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>worldmodels</category>
      <category>typescript</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
