<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>实验 on CloudLake</title><link>https://cloudlake.eu.org/categories/%E5%AE%9E%E9%AA%8C/</link><description>Recent content in 实验 on CloudLake</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Tue, 20 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://cloudlake.eu.org/categories/%E5%AE%9E%E9%AA%8C/index.xml" rel="self" type="application/rss+xml"/><item><title>让 AI 给自己写决策函数</title><link>https://cloudlake.eu.org/p/ai-evolution/</link><pubDate>Tue, 20 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/ai-evolution/</guid><description>&lt;img src="https://cloudlake.eu.org/p/ai-evolution/cover.jpg" alt="Featured image of post 让 AI 给自己写决策函数" /&gt;&lt;p&gt;表面上看，这是个网页小游戏。&lt;/p&gt;&#10;&lt;p&gt;一个机器人在地图上走，收集正分地块，躲开负分地块，在限定步数内拿最高分。&lt;/p&gt;&#10;&lt;p&gt;但真正被测试的不是机器人。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;是 AI 会不会给自己写一个能赢的函数。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="第一轮ai-亲自玩"&gt;&lt;a href="#%e7%ac%ac%e4%b8%80%e8%bd%aeai-%e4%ba%b2%e8%87%aa%e7%8e%a9" class="header-anchor"&gt;&lt;/a&gt;第一轮：AI 亲自玩&#10;&lt;/h2&gt;&lt;p&gt;Round 1，机器人每一步都由 API 驱动：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;观察 → 调 DeepSeek → 返回「思考 + 行动」→ 移动 → 下一步&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;每步的响应是两行：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;思考: 右下角有正分格，但中间隔着墙，先向右绕&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;行动: RIGHT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;「思考」显示在日志面板里，「行动」驱动移动。&lt;/p&gt;&#10;&lt;p&gt;这一轮很慢，也很贵——&lt;strong&gt;每一步都是一次 API 调用&lt;/strong&gt;。一局 60 步，就是 60 次请求。&lt;/p&gt;&#10;&lt;p&gt;但它产出了一样极有价值的东西：&lt;strong&gt;一份完整的决策日志。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="反思把日志变成代码"&gt;&lt;a href="#%e5%8f%8d%e6%80%9d%e6%8a%8a%e6%97%a5%e5%bf%97%e5%8f%98%e6%88%90%e4%bb%a3%e7%a0%81" class="header-anchor"&gt;&lt;/a&gt;反思：把日志变成代码&#10;&lt;/h2&gt;&lt;p&gt;一局结束后，我把整局记录发回给 AI：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;每一步它看到了什么&lt;/li&gt;&#10;&lt;li&gt;每一步它决定了什么&lt;/li&gt;&#10;&lt;li&gt;最终的得分与路径&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;然后要求它：&lt;strong&gt;不要再一步一步决定。写一个函数，让函数来替你做决定。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;产出长这样：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;span class="lnt"&gt;12&#10;&lt;/span&gt;&lt;span class="lnt"&gt;13&#10;&lt;/span&gt;&lt;span class="lnt"&gt;14&#10;&lt;/span&gt;&lt;span class="lnt"&gt;15&#10;&lt;/span&gt;&lt;span class="lnt"&gt;16&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;decide&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// observation: 周围 8 格（NW/N/NE/W/E/SW/S/SE）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// state: 已走步数、剩余步数、已收集分数&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// config: 网格配置&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// memory: 跨轮记忆&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;moves&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UP&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DOWN&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;LEFT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;RIGHT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 优先走向正分且未收集的格子&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;observation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collected&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;m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 没有正分就随机&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;moves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这就是 &lt;strong&gt;v1&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="关键转折成本从-o步数-变成-o1"&gt;&lt;a href="#%e5%85%b3%e9%94%ae%e8%bd%ac%e6%8a%98%e6%88%90%e6%9c%ac%e4%bb%8e-o%e6%ad%a5%e6%95%b0-%e5%8f%98%e6%88%90-o1" class="header-anchor"&gt;&lt;/a&gt;关键转折：成本从 O(步数) 变成 O(1)&#10;&lt;/h2&gt;&lt;p&gt;&lt;img alt="AI 决策函数进化循环" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="evolution-loop.jpg"&gt;&lt;/p&gt;&#10;&lt;p&gt;对比一下两种驱动方式：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;API 模式&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;函数模式&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;驱动&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每步一次 API 调用&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;本地执行一个函数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;成本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;O(步数)&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;O(1)&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;速度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每秒 1~2 步（受网络限制）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每秒上万步&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;可复现&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;依赖模型版本&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;完全确定&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;同一个 AI，在第二轮里变成了一个 100 行的函数。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;然后这个函数可以跑几千局——不需要再花任何 API 费用。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="进化让函数一代代变强"&gt;&lt;a href="#%e8%bf%9b%e5%8c%96%e8%ae%a9%e5%87%bd%e6%95%b0%e4%b8%80%e4%bb%a3%e4%bb%a3%e5%8f%98%e5%bc%ba" class="header-anchor"&gt;&lt;/a&gt;进化：让函数一代代变强&#10;&lt;/h2&gt;&lt;p&gt;第二轮结束后，把「函数表现」和「上一轮表现」一起发回给 AI，让它改进。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Round 1 API 亲自玩 → 收集日志&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;反思 日志 → v1 函数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Round 2 v1 函数跑 → 记录得分&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;反思 对比 → v2 函数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Round 3 v2 函数跑 → 记录得分&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;……&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;最终 最优函数 + 完整进化轨迹&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;默认 5 轮。每一轮的函数版本都存进 localStorage，可以：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;查看任意版本的代码&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;手动编辑&lt;/strong&gt;再测&lt;/li&gt;&#10;&lt;li&gt;导出/导入&lt;/li&gt;&#10;&lt;li&gt;对比各代得分&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="一个反直觉的地方"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e5%8f%8d%e7%9b%b4%e8%a7%89%e7%9a%84%e5%9c%b0%e6%96%b9" class="header-anchor"&gt;&lt;/a&gt;一个反直觉的地方&#10;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;函数变得更长，不一定变得更强。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;我观察到的情况是：前两代函数通常一路膨胀（加启发式、加规则、加特判），但从第三代开始往往会&lt;strong&gt;缩短&lt;/strong&gt;——AI 会主动删掉那些在实测里没帮助的分支。&lt;/p&gt;&#10;&lt;p&gt;这其实很像人类写代码的过程：先什么都想加，再靠数据砍掉。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="沙箱ai-写的代码不能直接信任"&gt;&lt;a href="#%e6%b2%99%e7%ae%b1ai-%e5%86%99%e7%9a%84%e4%bb%a3%e7%a0%81%e4%b8%8d%e8%83%bd%e7%9b%b4%e6%8e%a5%e4%bf%a1%e4%bb%bb" class="header-anchor"&gt;&lt;/a&gt;沙箱：AI 写的代码不能直接信任&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;decide&lt;/code&gt; 是 AI 生成的任意 JavaScript。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;直接 &lt;code&gt;eval&lt;/code&gt; 等于把浏览器交出去。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;所以：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;代码在 &lt;strong&gt;Web Worker&lt;/strong&gt; 里执行，拿不到 DOM 和网络&lt;/li&gt;&#10;&lt;li&gt;执行前做语法校验（&lt;code&gt;new Function&lt;/code&gt; 能构造出来才算合法）&lt;/li&gt;&#10;&lt;li&gt;必须有名为 &lt;code&gt;decide&lt;/code&gt; 的函数，否则拒绝装载&lt;/li&gt;&#10;&lt;li&gt;抛异常就捕获、记录、&lt;strong&gt;不回退到默认策略&lt;/strong&gt;（否则你永远不知道它坏了）&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;span class="lnt"&gt;6&#10;&lt;/span&gt;&lt;span class="lnt"&gt;7&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;runDecide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;observation&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;state&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;config&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;memory&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; return decide(observation, state, config, memory);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sb"&gt; `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fn&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;一个 AI 写的函数，要么在受限环境里跑，要么别跑。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="顺带测的第二件事长上下文记忆"&gt;&lt;a href="#%e9%a1%ba%e5%b8%a6%e6%b5%8b%e7%9a%84%e7%ac%ac%e4%ba%8c%e4%bb%b6%e4%ba%8b%e9%95%bf%e4%b8%8a%e4%b8%8b%e6%96%87%e8%ae%b0%e5%bf%86" class="header-anchor"&gt;&lt;/a&gt;顺带测的第二件事：长上下文记忆&#10;&lt;/h2&gt;&lt;p&gt;这个游戏还测了另一条线——&lt;strong&gt;AI 在不同信息密度下的决策质量&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;历史信息可以切换成两种模式：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;模式&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;每步传给 API 的内容&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;上下文大小&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;完整历史&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每步观察 + 决策 + 完整路径&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;线性增长&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;压缩摘要&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;已收集分数列表 + 已访问坐标集合&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;基本恒定&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;这就是一个很干净的对照实验：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;同一个模型，给它线性增长的上下文，和给它固定大小的摘要，决策质量会不会下降？&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;而且这个问题在当前特别实际——所有做长上下文应用的人都要面对。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="网格本身也是变量"&gt;&lt;a href="#%e7%bd%91%e6%a0%bc%e6%9c%ac%e8%ba%ab%e4%b9%9f%e6%98%af%e5%8f%98%e9%87%8f" class="header-anchor"&gt;&lt;/a&gt;网格本身也是变量&#10;&lt;/h2&gt;&lt;p&gt;为了不让实验退化成「一张随机图上的运气」，网格可以精细配置：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;分数分布&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;均匀分布 / 正态分布（可调均值、标准差）&lt;/li&gt;&#10;&lt;li&gt;正分格占比（默认 60%）&lt;/li&gt;&#10;&lt;li&gt;数值取整，正态钳制到 &lt;code&gt;[min, max]&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;墙壁结构&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;corridor&lt;/code&gt; — 曲折走廊，终点高奖励&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;room&lt;/code&gt; — 封闭房间，1~2 个入口，内部高奖励&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;maze&lt;/code&gt; — 递归分割小迷宫，死胡同里藏高奖励&lt;/li&gt;&#10;&lt;li&gt;随机递归分割（可设递归深度）&lt;/li&gt;&#10;&lt;li&gt;结构深处的格子奖励 &lt;strong&gt;2x~3x&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;同一份决策函数，在开放地图上可能很聪明，在迷宫里可能直接走进死胡同。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;所以只测一种地图是不够的。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一个必须处理的边界情况"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e5%bf%85%e9%a1%bb%e5%a4%84%e7%90%86%e7%9a%84%e8%be%b9%e7%95%8c%e6%83%85%e5%86%b5" class="header-anchor"&gt;&lt;/a&gt;一个必须处理的边界情况&#10;&lt;/h2&gt;&lt;p&gt;设计文档里我列了一张边缘情况表，其中一条特别重要：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;场景&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;行为&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;函数代码执行异常&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;捕获错误，日志提示，&lt;strong&gt;不回退&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;为什么不回退到「随机走」？&lt;/p&gt;&#10;&lt;p&gt;因为&lt;strong&gt;静默降级会让统计数据失真&lt;/strong&gt;。如果函数坏了却自动换成随机策略，你会看到「这一代得分下降了」，但不知道为什么——你会以为是策略变差了，实际上是崩溃了。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;让失败暴露出来，比让它看起来能跑更重要。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="这个项目真正在问什么"&gt;&lt;a href="#%e8%bf%99%e4%b8%aa%e9%a1%b9%e7%9b%ae%e7%9c%9f%e6%ad%a3%e5%9c%a8%e9%97%ae%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;这个项目真正在问什么&#10;&lt;/h2&gt;&lt;p&gt;表面上它在问：AI 能不能在网格游戏里拿高分？&lt;/p&gt;&#10;&lt;p&gt;实际上它在问三个问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;AI 能不能从「做决策」上升到「写决策程序」？&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;写出来的程序，能不能在一代代反馈中变好？&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;上下文从线性增长换成固定摘要，它的判断会退步多少？&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;第一个是能力边界，第二个是进化机制，第三个是工程现实。&lt;/p&gt;&#10;&lt;p&gt;而这三件事，恰好也是今天做 AI 应用的人每天在纠结的。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="技术栈"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e6%a0%88" class="header-anchor"&gt;&lt;/a&gt;技术栈&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;原生 JavaScript&lt;/code&gt; · &lt;code&gt;Canvas&lt;/code&gt; · &lt;code&gt;DeepSeek API&lt;/code&gt; · &lt;code&gt;Web Worker 沙箱&lt;/code&gt; · &lt;code&gt;localStorage&lt;/code&gt; · 零构建步骤&lt;/p&gt;&#10;&lt;hr&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个项目让我改了一个看法。&lt;/p&gt;&#10;&lt;p&gt;以前我觉得「AI 会不会写代码」是个演示性问题。&lt;/p&gt;&#10;&lt;p&gt;现在我觉得它是&lt;strong&gt;成本问题&lt;/strong&gt;——&lt;strong&gt;当 AI 学会写一个函数来代替自己反复推理时，它就从「一次服务」变成了「一份资产」。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>615 张蓝图，6.6 秒：我把《异星工厂》的蓝图拆开了</title><link>https://cloudlake.eu.org/p/factorio-lab/</link><pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/factorio-lab/</guid><description>&lt;img src="https://cloudlake.eu.org/p/factorio-lab/cover.jpg" alt="Featured image of post 615 张蓝图，6.6 秒：我把《异星工厂》的蓝图拆开了" /&gt;&lt;p&gt;我有一个文件夹，里面躺着 615 个 &lt;code&gt;.txt&lt;/code&gt; 文件。每个文件里是一串以 &lt;code&gt;0eJz...&lt;/code&gt; 开头的字符——这是《异星工厂》（Factorio）的蓝图字符串。&lt;/p&gt;&#10;&lt;p&gt;蓝图是玩家花了几个月搭出来的工厂。它现在只是一串文本，你看不出里面有什么，算不出它能产多少东西，也没法验证它是不是真的能跑。&lt;/p&gt;&#10;&lt;p&gt;我想让它变成&lt;strong&gt;能看、能算、能验证&lt;/strong&gt;的东西。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一字符串里到底装了什么"&gt;&lt;a href="#%e4%b8%80%e5%ad%97%e7%ac%a6%e4%b8%b2%e9%87%8c%e5%88%b0%e5%ba%95%e8%a3%85%e4%ba%86%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;一、字符串里到底装了什么&#10;&lt;/h2&gt;&lt;p&gt;官方没有公开蓝图格式。资料里的说法是：Base64 编码的 zlib 流，解出来是 JSON。&lt;/p&gt;&#10;&lt;p&gt;照着做，第一步就失败了：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zlib.error: Error -3 while decompressing data: incorrect header check&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;我换了好几种字母表、试了 URL-safe 变体、补了不同数量的填充，全都不对。最后不猜了，&lt;strong&gt;直接读原始字节&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0e 4a 7a 5f ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;code&gt;0e&lt;/code&gt; 是 Base64 解码的第一个字节？不是。真相是——&lt;strong&gt;第一个字符 &lt;code&gt;0&lt;/code&gt; 根本不属于负载&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;0&lt;/code&gt; 是版本号，本身就是一个 Base64 字符（数值 52）。真正的负载从&lt;strong&gt;下标 1&lt;/strong&gt; 开始。从第二个字符往后解，第一个字节正好是 &lt;code&gt;78&lt;/code&gt;，那是 zlib 的合法头。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;坑一：开头那个字符不是负载。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;坑二：填充没被去掉。&lt;/strong&gt; 好几处资料说 Factorio 会去掉 &lt;code&gt;=&lt;/code&gt;，但三个文件&lt;strong&gt;全都以 &lt;code&gt;=&lt;/code&gt; 或 &lt;code&gt;==&lt;/code&gt; 结尾&lt;/strong&gt;。去掉就刚好差两个字符，解出来是残的。&lt;/p&gt;&#10;&lt;p&gt;结论写在项目里的一句话：&lt;strong&gt;以文件为准，不以文档为准。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;验证方式是往返：&lt;code&gt;decode&lt;/code&gt; 之后再 &lt;code&gt;encode&lt;/code&gt;，必须&lt;strong&gt;逐字符完全相同&lt;/strong&gt;。三个原始文件、以及内部嵌的&lt;strong&gt;全部 615 个蓝图&lt;/strong&gt;，逐字符都通过了。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二数值不能抄得读"&gt;&lt;a href="#%e4%ba%8c%e6%95%b0%e5%80%bc%e4%b8%8d%e8%83%bd%e6%8a%84%e5%be%97%e8%af%bb" class="header-anchor"&gt;&lt;/a&gt;二、数值不能抄，得读&#10;&lt;/h2&gt;&lt;p&gt;蓝图里只有&amp;quot;这台机器在这里、朝这个方向&amp;quot;，没有&amp;quot;它有多快&amp;quot;。速度表在游戏安装目录里。&lt;/p&gt;&#10;&lt;p&gt;所以我写了一个&lt;strong&gt;受限的 Lua 求值器&lt;/strong&gt;（&lt;code&gt;fablue/lua.py&lt;/code&gt;）：它能处理 &lt;code&gt;data:extend&lt;/code&gt; 语句、表达式、内置库和辅助函数，但&lt;strong&gt;绝不执行宿主代码&lt;/strong&gt;——没有 &lt;code&gt;require&lt;/code&gt;、没有文件系统、没有外部依赖。&lt;/p&gt;&#10;&lt;p&gt;第一版解出 1,367 个原型，还有 149 个 &lt;code&gt;data:extend&lt;/code&gt; 解不出来。加了对跨文件、局部变量、函数返回值的支持之后：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;修复前&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;现在&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;解出的原型&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,367&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;4,633&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;解不出的 &lt;code&gt;data:extend&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;149&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;传送带速度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;机器速度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;剩下 25 个全是跨文件 &lt;code&gt;data.raw&lt;/code&gt; 派生的瓦片/装饰类，&lt;strong&gt;不含速度表&lt;/strong&gt;——这个上限是我报出来的，不是藏起来的。&lt;/p&gt;&#10;&lt;p&gt;读出来的真值（全部来自 &lt;code&gt;data.raw&lt;/code&gt;）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;项目&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;值&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;传送带速度&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.03125 / 0.0625 / 0.09375 / 0.125 tile/tick&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;双 lane 吞吐&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;15 / 30 / 45 / 60 items/s&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;单 lane 吞吐&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;7.5 / 15 / 22.5 / 30 items/s&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;组装机 1/2/3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;crafting_speed &lt;strong&gt;0.5 / 0.75 / 1.25&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;电炉 / 石炉 / 钢炉&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;2 / 1 / 2&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="一个犯了两次的-2-倍错误"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e7%8a%af%e4%ba%86%e4%b8%a4%e6%ac%a1%e7%9a%84-2-%e5%80%8d%e9%94%99%e8%af%af" class="header-anchor"&gt;&lt;/a&gt;一个犯了两次的 2 倍错误&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;×480&lt;/code&gt; 是&lt;strong&gt;双 lane 合计&lt;/strong&gt;。推导是 &lt;code&gt;0.03125 × 60 / 0.25 = 7.5&lt;/code&gt;（单 lane），乘 2 才得 15。&lt;/p&gt;&#10;&lt;p&gt;早期版本把这个常数标成 &amp;ldquo;per lane&amp;rdquo;，同时 &lt;code&gt;GameDB.belt_items_per_second&lt;/code&gt; 又乘了一次 lanes——&lt;strong&gt;同一个 2 倍错误，在两个地方各犯了一次&lt;/strong&gt;。因为是&amp;quot;自洽的错&amp;quot;，测试抓不到。&lt;/p&gt;&#10;&lt;p&gt;现在常量改名为&amp;quot;双 lane 因子&amp;quot;，新增 &lt;code&gt;belt_items_per_second_lane&lt;/code&gt; 属性，并把推导写进注释。&lt;strong&gt;单位写清楚，就不会再被隐含。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="三615-个蓝图一个-canvas"&gt;&lt;a href="#%e4%b8%89615-%e4%b8%aa%e8%93%9d%e5%9b%be%e4%b8%80%e4%b8%aa-canvas" class="header-anchor"&gt;&lt;/a&gt;三、615 个蓝图，一个 canvas&#10;&lt;/h2&gt;&lt;p&gt;查看器的约束很硬：&lt;strong&gt;单文件、离线、无外链&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;615 个蓝图、334,856 个实体，如果每个实体画一个 SVG 矩形，是数十 MB 的 DOM。所以：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;一个 canvas&lt;/strong&gt;，不是每蓝图一个&lt;/li&gt;&#10;&lt;li&gt;每实体只存 4 个整数：&lt;code&gt;x2, y2, 类别, 朝向&lt;/code&gt;。坐标翻倍是因为 Factorio 有大量实体放在&lt;strong&gt;半格&lt;/strong&gt;上（整数存不下 0.5）&lt;/li&gt;&#10;&lt;li&gt;全部数据一次性嵌进页面 → &lt;strong&gt;6.04 MB&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;性能靠&lt;strong&gt;视口裁剪&lt;/strong&gt;：懒建空间哈希 + &lt;code&gt;Int32Array&lt;/code&gt;。一个 34,650 实体的蓝图，在深度缩放下&lt;strong&gt;一帧只画 13 个实体&lt;/strong&gt;；全可见时 p95 是 &lt;strong&gt;8.8 ms&lt;/strong&gt;，中位 0.6 ms。&lt;/p&gt;&#10;&lt;p&gt;实体还&lt;strong&gt;按真实尺寸绘制&lt;/strong&gt;——尺寸取自每个原型的 &lt;code&gt;collision_box&lt;/code&gt;（3×3 的机器是 2.4，补齐到 3 格；火箭发射井 8.4 → 9 格；传送带 0.8 → 1 格），690 个尺寸表项随页面发出。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="四让它算"&gt;&lt;a href="#%e5%9b%9b%e8%ae%a9%e5%ae%83%e7%ae%97" class="header-anchor"&gt;&lt;/a&gt;四、让它算&#10;&lt;/h2&gt;&lt;p&gt;看图不够，我关心的是：&lt;strong&gt;这个工厂到底产多少、卡在哪&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;计算内核输出四类东西：净产出速率、外部输入、配比失衡、传送带饱和。&lt;/p&gt;&#10;&lt;p&gt;一个真实例子（34,650 个实体）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;传送带饱和 copper-cable 151.1/s，是 express-transport-belt 容量的 3.36 倍&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;净产出 copper-cable 151.13/s、sulfuric-acid 97.00/s、petroleum-gas 34.00/s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;外部输入 water 1587.50/s、crude-oil 480.00/s、iron-plate 282.15/s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;瓶颈 未知配方：assembling-machine-3 ×18 台无法确定配方，未计入产出速率&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;核心公式全部写在代码注释里，不从别处抄：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;每秒次数 = crafting_speed ÷ recipe.energy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;每秒产出 = 次数 × 单次产出量&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;每 lane = belt_speed × 240，双 lane = belt_speed × 480&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;模块效应也计入了&lt;/strong&gt;：同一个机器内的模块加成&lt;strong&gt;相加&lt;/strong&gt;（三个 +50% 速度模块 = ×2.5，不是 1.5³）。有一条专门的断言守着：组装机 3 做铜缆，无模块 &lt;strong&gt;5.0/s&lt;/strong&gt;，装三个速度模块 &lt;strong&gt;12.5/s&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="五几处刻意做对的判断"&gt;&lt;a href="#%e4%ba%94%e5%87%a0%e5%a4%84%e5%88%bb%e6%84%8f%e5%81%9a%e5%af%b9%e7%9a%84%e5%88%a4%e6%96%ad" class="header-anchor"&gt;&lt;/a&gt;五、几处&amp;quot;刻意做对&amp;quot;的判断&#10;&lt;/h2&gt;&lt;p&gt;这些地方很容易写成&amp;quot;看起来对、其实错&amp;quot;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;stone-furnace&lt;/code&gt; 这类实体在蓝图里&lt;strong&gt;没有 &lt;code&gt;recipe&lt;/code&gt; 字段&lt;/strong&gt;，但 Factorio 也不把默认配方写进蓝图。所以必须报成&amp;quot;&lt;strong&gt;未知配方（可用 overrides 指定）&lt;/strong&gt;&amp;quot;，而不是当成&amp;quot;这台机器闲置&amp;quot;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;信标&lt;/strong&gt;（59 个）持有模块但不执行配方，如果当机器算，每个信标都会变成一条假&amp;quot;未知配方&amp;quot;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;实验室和采矿机&lt;/strong&gt;的产出取决于科技/矿脉，单独报，不归入&amp;quot;未知配方&amp;quot;。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;&amp;ldquo;蓝图没给配方&amp;quot;和&amp;quot;蓝图给了但游戏数据里查不到&amp;quot;分开报&lt;/strong&gt;。后者我核实过 &lt;code&gt;mods&lt;/code&gt; 目录确实没装 mod，所以那批 mod 蓝图算不出来是&lt;strong&gt;如实缺口&lt;/strong&gt;，不是静默失败。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="六验证334-项断言--三个浏览器终检"&gt;&lt;a href="#%e5%85%ad%e9%aa%8c%e8%af%81334-%e9%a1%b9%e6%96%ad%e8%a8%80--%e4%b8%89%e4%b8%aa%e6%b5%8f%e8%a7%88%e5%99%a8%e7%bb%88%e6%a3%80" class="header-anchor"&gt;&lt;/a&gt;六、验证：334 项断言 + 三个浏览器终检&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;测试文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;断言&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;在验证什么&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_blueprint&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;14&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;编码往返、格式事实、拒绝畸形输入&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_all_blueprints&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;全部 615 个蓝图&lt;/strong&gt;逐字符往返&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_gamedata&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;79&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;合成数据证明数值真来自文件&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_gamedata_independent&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;45&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;正则直接从 Lua 抠数值&lt;/strong&gt; + 物理独立推导&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_calc&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;90&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;精确数值、未知配方不静默丢弃、循环保护&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_integration&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;22&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;跨模块接线&lt;/strong&gt;：模块效应→计算、尺寸→渲染&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;test_view&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;76&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;页面无外链、嵌入数据条数等于输入&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;code&gt;test_gamedata_independent.py&lt;/code&gt; 是关键一条：它&lt;strong&gt;不经过提取器的 API&lt;/strong&gt;，而是用正则直接从原始 Lua 文件里抠数值比对——提取器的 bug 无法靠&amp;quot;自洽&amp;quot;藏住。&lt;/p&gt;&#10;&lt;p&gt;三个&lt;strong&gt;真浏览器终检&lt;/strong&gt;不是检查 HTML 文本，而是真的操作页面：缩放/平移真的改变渲染、点到饱和蓝图真的显示警告、放大后每实体像素数真的从 3.1 涨到 50,181.6。&lt;/p&gt;&#10;&lt;p&gt;这一层抓到了 &lt;strong&gt;5 个纯 Python 测试看不见的 bug&lt;/strong&gt;：相机 NaN、缺边界、图例按数字索引查却按名字存、&lt;code&gt;renderLegend()&lt;/code&gt; 重建时销毁了正在处理点击的节点、&lt;code&gt;data-i=&amp;quot;0&amp;quot;&lt;/code&gt; 的 falsy 边界。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这就是为什么要用真浏览器驱动页面。DOM 里的字符串说&amp;quot;我很正常&amp;rdquo;，不算数。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;最终：&lt;strong&gt;334/334 通过，7/7 浏览器终检全过&lt;/strong&gt;，端到端 6.6 秒。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="七还没做到的"&gt;&lt;a href="#%e4%b8%83%e8%bf%98%e6%b2%a1%e5%81%9a%e5%88%b0%e7%9a%84" class="header-anchor"&gt;&lt;/a&gt;七、还没做到的&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;信标效应没建模&lt;/strong&gt;——需要空间分析（每台机器周围有几个信标），目前只在 notes 里计数。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;传送带饱和是按物品总量、不是按段判定&lt;/strong&gt;——蓝图 JSON 不记录机器和传送带的连接关系，要按段判就得从坐标和朝向重建拓扑，这是独立的一大块。&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;K2SE 的 mod 配方读不到&lt;/strong&gt;，因为这台机器没装 mod。装上、把 &lt;code&gt;data_root&lt;/code&gt; 指过去就能算。&lt;/li&gt;&#10;&lt;li&gt;品质加成、插入机吞吐、电力网络都还没建模。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;这些限制我都写在页面的结论里，&lt;strong&gt;不含糊过去&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/factorio/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;打开蓝图平台（6 MB，含 615 张蓝图）→&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;</description></item><item><title>没有 nasm 也没有 qemu，我从零写了汇编器和模拟器</title><link>https://cloudlake.eu.org/p/kiln/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/kiln/</guid><description>&lt;img src="https://cloudlake.eu.org/p/kiln/cover.jpg" alt="Featured image of post 没有 nasm 也没有 qemu，我从零写了汇编器和模拟器" /&gt;&lt;p&gt;我想在这台机器上跑一段能在裸机上启动的代码。&lt;/p&gt;&#10;&lt;p&gt;需要两样东西：汇编器（把汇编变成字节）和模拟器（把字节执行起来）。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;nasm&lt;/code&gt; 装不上，&lt;code&gt;qemu&lt;/code&gt; 装不上——&lt;code&gt;sudo&lt;/code&gt; 要密码、Docker 没启动、直接下载超时。于是这个项目把两样都自己实现了。&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;模块&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;说明&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;模拟器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;kiln/cpu.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8086 寄存器组、段寄存器、ModRM 全部寻址方式、8/16 位运算与标志位、中断、VGA 文本显存、键盘控制器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;汇编器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;kiln/asm.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;迭代到收敛的多遍汇编器，输出字节 + 清单&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;反汇编器&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;kiln/decode.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;独立实现&lt;/strong&gt;，不参考汇编器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="为什么反汇编器要独立写"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e5%8f%8d%e6%b1%87%e7%bc%96%e5%99%a8%e8%a6%81%e7%8b%ac%e7%ab%8b%e5%86%99" class="header-anchor"&gt;&lt;/a&gt;为什么反汇编器要独立写&#10;&lt;/h2&gt;&lt;p&gt;汇编器决定&amp;quot;一条指令变成哪些字节&amp;quot;，反汇编器决定&amp;quot;这些字节是哪条指令&amp;quot;。&lt;/p&gt;&#10;&lt;p&gt;如果反汇编器照着汇编器的逻辑写，那它只能证明&amp;quot;我按我的想法解释我自己的输出&amp;quot;——这是自证。&lt;/p&gt;&#10;&lt;p&gt;所以两者&lt;strong&gt;互不参照&lt;/strong&gt;。然后测试断言它们在 &lt;code&gt;boot.asm&lt;/code&gt; 的&lt;strong&gt;每一条语句&lt;/strong&gt;上都一致。&lt;/p&gt;&#10;&lt;p&gt;这样，&amp;ldquo;两边以完全相同的方式出错&amp;quot;才会漏过去——比&amp;quot;汇编器没崩溃&amp;quot;强得多。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;但仍然不够。&lt;/strong&gt; 因为两边&lt;strong&gt;可以同时错得一致&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;所以还有第二重证据：把程序&lt;strong&gt;真的执行&lt;/strong&gt;一遍，用&lt;strong&gt;手算的期望值&lt;/strong&gt;对最终寄存器和内存下断言。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;tests/test_core.py&lt;/code&gt; 里的每个期望值都是手写推导的，不是从模拟器输出里抄的。否则又成了自证。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="测试抓到的-12-个真实缺陷"&gt;&lt;a href="#%e6%b5%8b%e8%af%95%e6%8a%93%e5%88%b0%e7%9a%84-12-%e4%b8%aa%e7%9c%9f%e5%ae%9e%e7%bc%ba%e9%99%b7" class="header-anchor"&gt;&lt;/a&gt;测试抓到的 12 个真实缺陷&#10;&lt;/h2&gt;&lt;p&gt;这些不是假设，是测试真的报错、我逐条定位出来的：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;#&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;缺陷&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;症状&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;解码器对 &lt;code&gt;0x83 /r&lt;/code&gt; 的立即数宽度判错&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;add ax,1&lt;/code&gt; 编码 3 字节、解码成 4 字节，之后全部错位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;_cond&lt;/code&gt; 里局部变量名与标志位常量同名&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;CF&lt;/code&gt; 变成未绑定局部变量，所有条件跳转崩溃&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;_apply_alu&lt;/code&gt; 回调签名不一致&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;运算结果写进&lt;strong&gt;错误的操作数&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ALU 的 form 分支反了&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;add ax,[mem]&lt;/code&gt; 变成写内存，把内存砸掉&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ModRM 构造把 src/dst 写反&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;add ax,bx&lt;/code&gt; 编成 &lt;code&gt;03 D8&lt;/code&gt;（实为 &lt;code&gt;add bx,ax&lt;/code&gt;）——&lt;strong&gt;能跑、但做反了&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;解码器方向同样反了&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;汇编器改对后，清单仍显示相反结果，&lt;strong&gt;误导出 3 次错误&amp;quot;修复&amp;rdquo;&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;_shift&lt;/code&gt; 对 mod=3 的寄存器操作数走了内存写入&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;shl ax,8&lt;/code&gt; 把结果写进代码段，&lt;strong&gt;自我破坏引导扇区&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;0F&lt;/code&gt; 两字节转义前缀未拦截&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;被当成别的指令&lt;strong&gt;静默执行&lt;/strong&gt;——最危险的一类&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;C1 /4 ib&lt;/code&gt; 立即数取在 ModRM &lt;strong&gt;之前&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把 ModRM 字节当移位位数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;段寄存器 push/pop 未实现，位判断用错&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;1E&lt;/code&gt;（&lt;code&gt;push ds&lt;/code&gt;）被当成 4 字节 NOP，指令流错位&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;两遍汇编 + 前向引用猜测 → 标签地址漂移&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;call blit&lt;/code&gt; 跳到标签&lt;strong&gt;后 3 字节&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;test reg,reg&lt;/code&gt; 用位测试而非 &lt;code&gt;84/85&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;test al,al&lt;/code&gt; 把寄存器当立即数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;第 5 和第 6 条最值得说：&lt;strong&gt;它们是对称的&lt;/strong&gt;。汇编器把方向搞反，解码器也把方向搞反，于是&amp;quot;汇编→解码&amp;quot;的往返测试&lt;strong&gt;完全通过&lt;/strong&gt;——两个错误互相抵消了。只有拿手算字节去对，才看得见。&lt;/p&gt;&#10;&lt;p&gt;第 11 条促成了根本性重新设计：改成&lt;strong&gt;迭代到收敛&lt;/strong&gt;，并且&lt;strong&gt;加宽单调&lt;/strong&gt;（一旦加宽就不回缩）。否则跳转长度和标签地址会互相影响，永远不收敛。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="引导扇区在裸机上画-mandelbrot"&gt;&lt;a href="#%e5%bc%95%e5%af%bc%e6%89%87%e5%8c%ba%e5%9c%a8%e8%a3%b8%e6%9c%ba%e4%b8%8a%e7%94%bb-mandelbrot" class="header-anchor"&gt;&lt;/a&gt;引导扇区：在裸机上画 Mandelbrot&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;examples/boot.asm&lt;/code&gt; 是一个 512 字节的引导扇区，末尾带 &lt;code&gt;55 AA&lt;/code&gt; 签名。BIOS 把它载入 &lt;code&gt;0000:7C00&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;流程：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;建立 &lt;code&gt;DS/SS&lt;/code&gt; 与栈&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;int 10h&lt;/code&gt; 切到 80×25 彩色文本&lt;/li&gt;&#10;&lt;li&gt;打印横幅&lt;/li&gt;&#10;&lt;li&gt;用 &lt;strong&gt;8.8 定点整数&lt;/strong&gt;渲染 Mandelbrot 到 &lt;code&gt;0800:0000&lt;/code&gt; 的缓冲区（40×24 字符）&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;rep movsw&lt;/code&gt; 整块搬到 &lt;code&gt;B800:0000&lt;/code&gt; 显存&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;hlt&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;缓冲区放在扇区&lt;strong&gt;之外&lt;/strong&gt;有原因：800 个字符塞不进 512 字节。而且放在已知地址后，验证器可以直接从 RAM 里读渲染结果，和&lt;strong&gt;独立的浮点实现&lt;/strong&gt;逐格比对。&lt;/p&gt;&#10;&lt;h3 id="定点乘法两个看着都对的错版本"&gt;&lt;a href="#%e5%ae%9a%e7%82%b9%e4%b9%98%e6%b3%95%e4%b8%a4%e4%b8%aa%e7%9c%8b%e7%9d%80%e9%83%bd%e5%af%b9%e7%9a%84%e9%94%99%e7%89%88%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;定点乘法，两个看着都对的错版本&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;imul&lt;/code&gt; 把有符号 32 位积放在 &lt;code&gt;DX:AX&lt;/code&gt;。8.8 定点结果就是该积的 bit 8..23，由两半拼成：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-asm" data-lang="asm"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;imul&lt;/span&gt; &lt;span class="no"&gt;bx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="no"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;dx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;shl&lt;/span&gt; &lt;span class="no"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="c1"&gt;; bit 16..23 -&amp;gt; bit 8..15&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;shr&lt;/span&gt; &lt;span class="no"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="c1"&gt;; bit 8..15 -&amp;gt; bit 0..7&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;or&lt;/span&gt; &lt;span class="no"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;cx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;两个诱人的简写都是错的，而且&lt;strong&gt;都表现为&amp;quot;逻辑 bug&amp;quot;而不是&amp;quot;算术 bug&amp;quot;&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;只做 &lt;code&gt;shl ax,8&lt;/code&gt;：保留 bit 16..31，&lt;strong&gt;丢掉低字的小数部分&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;对低字做 &lt;code&gt;shr cx,8 / shl ax,8 / or&lt;/code&gt;：&lt;strong&gt;丢掉整数部分&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;这类错误的可怕之处在于：结果总是&amp;quot;差不多但不是&amp;quot;，而&amp;quot;差不多&amp;quot;在图形上表现为轻微的错位，很容易被误认为&amp;quot;渲染问题&amp;quot;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="现在的状态如实说"&gt;&lt;a href="#%e7%8e%b0%e5%9c%a8%e7%9a%84%e7%8a%b6%e6%80%81%e5%a6%82%e5%ae%9e%e8%af%b4" class="header-anchor"&gt;&lt;/a&gt;现在的状态（如实说）&#10;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;已经跑通的：&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;python tests/test_core.py&lt;/code&gt; → &lt;strong&gt;62/62&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;汇编器对 &lt;code&gt;boot.asm&lt;/code&gt; 收敛输出&lt;strong&gt;正好 512 字节&lt;/strong&gt;，签名 &lt;code&gt;55 AA&lt;/code&gt; 正确&lt;/li&gt;&#10;&lt;li&gt;反汇编器与汇编器在冒烟测试的每一条指令上长度一致，逐条核对助记符&lt;/li&gt;&#10;&lt;li&gt;模拟器通过全部条件码、栈/call/ret、段 wraparound、除零故障、BIOS 显存、键盘阻塞/轮询断言&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;尚未完成的：&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;引导扇区的 &lt;strong&gt;Mandelbrot 渲染循环在末尾仍有一处控制流缺陷&lt;/strong&gt;，会跑到扇区外执行。&lt;/p&gt;&#10;&lt;p&gt;定点乘法本身已经被单独验证正确（&lt;code&gt;imul 0xFE00×0xFE00 → 0x0400&lt;/code&gt;），渲染循环也能正确遍历 960 个像素、正确推进缓冲区指针。&lt;strong&gt;出问题的是渲染之后的返回路径&lt;/strong&gt;，&lt;code&gt;tests/test_boot.py&lt;/code&gt; 的 14 项断言目前 10 项通过。&lt;/p&gt;&#10;&lt;p&gt;这一条我没有修完就停手了。&lt;/p&gt;&#10;&lt;p&gt;它属于&amp;quot;能自己走完的收尾&amp;quot;，但它前面连续暴露了 12 个更深的机制性缺陷。我判断继续在同一处反复试错的收益，已经低于把已有成果讲清楚。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="已知的模拟器边界"&gt;&lt;a href="#%e5%b7%b2%e7%9f%a5%e7%9a%84%e6%a8%a1%e6%8b%9f%e5%99%a8%e8%be%b9%e7%95%8c" class="header-anchor"&gt;&lt;/a&gt;已知的模拟器边界&#10;&lt;/h2&gt;&lt;p&gt;这些会&lt;strong&gt;明确报错&lt;/strong&gt;，而不是猜着执行：&lt;/p&gt;&#10;&lt;p&gt;32 位操作数/地址、保护模式、FPU、多数字符串指令、&lt;code&gt;0F&lt;/code&gt; 扩展表、本项目未用到的 BIOS 服务。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="如果继续"&gt;&lt;a href="#%e5%a6%82%e6%9e%9c%e7%bb%a7%e7%bb%ad" class="header-anchor"&gt;&lt;/a&gt;如果继续&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;先用一个 &lt;code&gt;trace&lt;/code&gt; 工具把渲染后的返回路径逐条打出来——这是定位最后那个缺陷最短的路。&lt;/li&gt;&#10;&lt;li&gt;给模拟器加&lt;strong&gt;&amp;ldquo;指令边界断言&amp;quot;开关&lt;/strong&gt;：任何一次 &lt;code&gt;step()&lt;/code&gt; 落在清单记录之外就立即报错。本次 12 个缺陷里有 5 个都是靠等价的观察才找到的，做成常驻检查会省很多时间。&lt;/li&gt;&#10;&lt;li&gt;加&lt;strong&gt;收敛性检查&lt;/strong&gt;：断言任意源码都在 N 遍内收敛，并把实际遍数记进清单。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个项目最后教会我的不是 x86。&lt;/p&gt;&#10;&lt;p&gt;是&lt;strong&gt;&amp;ldquo;能跑&amp;quot;和&amp;quot;对&amp;quot;之间隔着多远&lt;/strong&gt;——中间那 12 个缺陷，每一个都穿着&amp;quot;能跑&amp;quot;的外衣。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>大灾变纪念馆：从存档里读出五个人</title><link>https://cloudlake.eu.org/p/cdda-memorial/</link><pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/cdda-memorial/</guid><description>&lt;img src="https://cloudlake.eu.org/p/cdda-memorial/cover.jpg" alt="Featured image of post 大灾变纪念馆：从存档里读出五个人" /&gt;&lt;p&gt;&lt;code&gt;F:\CDDA\graveyard\&lt;/code&gt; 里躺着 10 次角色死亡记录。&lt;/p&gt;&#10;&lt;p&gt;这是《大灾变：黑暗之日》（Cataclysm: Dark Days Ahead）删档后自动留下的完整档案：每次一份 &lt;code&gt;.sav&lt;/code&gt;（角色与世界在死亡那一刻的状态）和一份 &lt;code&gt;.log&lt;/code&gt;（大事记）。&lt;/p&gt;&#10;&lt;p&gt;存档格式是&lt;strong&gt;开着的&lt;/strong&gt;——首行 &lt;code&gt;# version N&lt;/code&gt;，之后是一整个 JSON 对象。所以那个角色的姓名、属性、技能、特性、变异、击杀数、走过的每一处地方，甚至&lt;strong&gt;一生中打破的每一条戒律&lt;/strong&gt;，都能直接读出来。&lt;/p&gt;&#10;&lt;p&gt;我把它读出来，然后做了两件事：&lt;strong&gt;把这些人还原成可看的记录&lt;/strong&gt;，以及&lt;strong&gt;给他们写墓志铭&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="读出来的五个人"&gt;&lt;a href="#%e8%af%bb%e5%87%ba%e6%9d%a5%e7%9a%84%e4%ba%94%e4%b8%aa%e4%ba%ba" class="header-anchor"&gt;&lt;/a&gt;读出来的五个人&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;graveyard/&lt;/code&gt; 里 5 份存档、3 个角色：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;角色&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;职业 / 出身&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;存活&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;击杀&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;变异&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;结局线索&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;特里安娜&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;实验室技术员 / 精灵模组&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2 回合&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;29&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;刚开局就结束&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;克里斯托弗·哈里斯&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;无职业 / 避难者&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;23.6 小时&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;50&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;死在无线发射塔一带&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;阿里安娜·娜吉雅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;维度法师 / 避难者&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.53 天&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;114&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;97&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;第一次尝试&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;阿里安娜·娜吉雅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;3.47 天&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;313&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;125&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;最远的一次&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;阿里安娜·娜吉雅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.2 小时&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;86&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;足迹停在「坚岩／实心土」——被埋在石头和泥土里&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一个我最初写错的判断"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e6%88%91%e6%9c%80%e5%88%9d%e5%86%99%e9%94%99%e7%9a%84%e5%88%a4%e6%96%ad" class="header-anchor"&gt;&lt;/a&gt;一个我最初写错的判断&#10;&lt;/h2&gt;&lt;p&gt;阿里安娜出现了三次。怎么判断这三次是同一个人？&lt;/p&gt;&#10;&lt;p&gt;我的第一版判据是：&lt;strong&gt;属性完全相同&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;测试直接把它打回来了。&lt;/p&gt;&#10;&lt;p&gt;三次存档里 STR/DEX/INT 确实都是 24/24/23——但&lt;strong&gt;最后一次 PER 从 24 掉到了 23&lt;/strong&gt;。而特性数从 69 → 57 → &lt;strong&gt;73&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;真相是：&lt;strong&gt;属性会被变异和伤势改写，特性不会。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;所以正确的判据是&lt;strong&gt;特性集&lt;/strong&gt;，不是当前属性。&lt;/p&gt;&#10;&lt;p&gt;这个小错误是这个项目第一个教训：&lt;strong&gt;你以为稳定的标识，恰恰是最容易变的那个。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="墓志铭以及它凭什么可信"&gt;&lt;a href="#%e5%a2%93%e5%bf%97%e9%93%ad%e4%bb%a5%e5%8f%8a%e5%ae%83%e5%87%ad%e4%bb%80%e4%b9%88%e5%8f%af%e4%bf%a1" class="header-anchor"&gt;&lt;/a&gt;墓志铭，以及它凭什么可信&#10;&lt;/h2&gt;&lt;p&gt;生成式文字最容易犯的错是&lt;strong&gt;编造事实&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;一个 AI 写出来的墓志铭，读起来可以很动人，但里面的&amp;quot;击杀 313 个敌人&amp;quot;——是真的吗？还是它为了让句子好看随口说的？&lt;/p&gt;&#10;&lt;p&gt;所以这里的做法是：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;每一句话涉及的每个数字和名字，都作为一条「可核查声明」（Claim）被登记下来，&lt;code&gt;verify()&lt;/code&gt; 再从解析出的存档里重新推导一遍。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;比如这一段：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;阿里安娜·娜吉雅，女。存活 3.47 天后死去。生前击杀 313 个敌人。身上留下 125 处变异。带着 57 项特性离世。最擅长咒术（15 级）。足迹遍及 9 处，最常出现在紧急避难所 B-19（36 次）、森林（25 次）、公路（17 次）。一生打破 10 条戒律……她把这个世界推开了一段距离，然后被它合上。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;里面每一个数字——&lt;code&gt;3.47 / 313 / 125 / 57 / 15 / 9 / 36 / 25 / 17 / 10&lt;/code&gt;——&lt;strong&gt;全部是声明，全部被重新推导核对过&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="校验器自己也要被校验"&gt;&lt;a href="#%e6%a0%a1%e9%aa%8c%e5%99%a8%e8%87%aa%e5%b7%b1%e4%b9%9f%e8%a6%81%e8%a2%ab%e6%a0%a1%e9%aa%8c" class="header-anchor"&gt;&lt;/a&gt;校验器自己也要被校验&#10;&lt;/h3&gt;&lt;p&gt;只有正向检查还不够。&lt;/p&gt;&#10;&lt;p&gt;因为&amp;quot;校验器&amp;quot;本身可能是个摆设——它可能永远返回 true。&lt;/p&gt;&#10;&lt;p&gt;所以测试里加了两条&lt;strong&gt;反向验证&lt;/strong&gt;：故意塞一个伪造的击杀数（9999）和伪造的姓名进去，&lt;code&gt;verify()&lt;/code&gt; &lt;strong&gt;必须报错&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;这是这个项目最核心的一条设计原则：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;一个从不失败的校验器，等于没有校验器。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="测试抓到的两个真实缺陷"&gt;&lt;a href="#%e6%b5%8b%e8%af%95%e6%8a%93%e5%88%b0%e7%9a%84%e4%b8%a4%e4%b8%aa%e7%9c%9f%e5%ae%9e%e7%bc%ba%e9%99%b7" class="header-anchor"&gt;&lt;/a&gt;测试抓到的两个真实缺陷&#10;&lt;/h2&gt;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;小时数校验逻辑写错了。&lt;/strong&gt; &lt;code&gt;存活小时&lt;/code&gt; 这条声明拿 &lt;code&gt;round(days24)&lt;/code&gt; 去和 &lt;code&gt;days&lt;/code&gt; 比，23.6 小时被判成不匹配。修法是让声明带上单位（&lt;code&gt;hours&lt;/code&gt; 与 &lt;code&gt;days&lt;/code&gt; 分开）。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;地点到访次数没有登记为声明。&lt;/strong&gt; 墓志铭里印着「幸存者营地（12 次）」，但 &lt;code&gt;12&lt;/code&gt; 不在任何 Claim 里。是测试里那条「&lt;strong&gt;文本中出现的每个数字都必须有出处&lt;/strong&gt;」把它揪出来的。&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;这正是这套约束存在的理由：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;没被登记的印刷数字，就是没被核查的数字。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="足迹"&gt;&lt;a href="#%e8%b6%b3%e8%bf%b9" class="header-anchor"&gt;&lt;/a&gt;足迹&#10;&lt;/h2&gt;&lt;p&gt;每条 &lt;code&gt;.log&lt;/code&gt; 事件都带 &lt;code&gt;oter_id&lt;/code&gt; / &lt;code&gt;oter_name&lt;/code&gt;（发生地点），所以&amp;quot;足迹&amp;quot;不是编的，是从数据长出来的：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;克里斯托弗&lt;/strong&gt;：幸存者营地 ×12、无线发射塔 ×4、民宅 ×4、森林 ×4&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;阿里安娜（最远那次）&lt;/strong&gt;：紧急避难所 B-19 ×&lt;strong&gt;36&lt;/strong&gt;、森林 ×25、公路 ×17&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;阿里安娜（最后一次）&lt;/strong&gt;：紧急避难所 C-41 ×2、&lt;strong&gt;坚岩 ×2、实心土 ×1&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;最后一行不需要任何解释。&lt;/p&gt;&#10;&lt;p&gt;它只是数据——她死在岩石和泥土之间。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一个说明"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e8%af%b4%e6%98%8e" class="header-anchor"&gt;&lt;/a&gt;一个说明&#10;&lt;/h2&gt;&lt;p&gt;存档里含有成人模组的全局变量与事件名。&lt;/p&gt;&#10;&lt;p&gt;我只做了&lt;strong&gt;结构化统计与叙事重建&lt;/strong&gt;，没有展开那部分内容。纪念馆页面上也不会出现。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="为什么做这个"&gt;&lt;a href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e5%81%9a%e8%bf%99%e4%b8%aa" class="header-anchor"&gt;&lt;/a&gt;为什么做这个&#10;&lt;/h2&gt;&lt;p&gt;一个游戏存档里躺着的东西，本来会跟着硬盘一起被格式化掉。&lt;/p&gt;&#10;&lt;p&gt;但它其实是一份&lt;strong&gt;完整的人生记录&lt;/strong&gt;：这个人走过哪里、杀过什么、变成了什么、最后倒在什么地方。&lt;/p&gt;&#10;&lt;p&gt;我只是把它读出来了。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/memorial/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;打开纪念馆 →&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;&lt;hr&gt;&#10;&lt;h2 id="技术细节"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e7%bb%86%e8%8a%82" class="header-anchor"&gt;&lt;/a&gt;技术细节&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cddamem/save.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;存档解析（&lt;code&gt;.sav&lt;/code&gt; + &lt;code&gt;.log&lt;/code&gt;），产出 &lt;code&gt;Life&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cddamem/epitaph.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;墓志铭生成 + Claim 校验&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;summarise.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;命令行汇总&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;make_memorial.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;纪念馆页面（自包含 HTML）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;tests/test_save.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;19 项断言，主要是&lt;strong&gt;与原始 JSON 交叉核对&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;tests/test_epitaph.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5 项断言，含伪造数据必须被抓&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;两条验证主线：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;交叉核对&lt;/strong&gt;：解析出的技能集必须与原始 JSON 的 &lt;code&gt;level&amp;gt;0&lt;/code&gt; 集合&lt;strong&gt;完全相等&lt;/strong&gt;；击杀数必须等于 &lt;code&gt;kill_tracker.kills&lt;/code&gt; 之和；&lt;code&gt;.log&lt;/code&gt; 里&amp;quot;开始大灾变&amp;quot;的回合必须等于 &lt;code&gt;.sav&lt;/code&gt; 的 &lt;code&gt;game_start&lt;/code&gt;。&lt;strong&gt;猜出来的数字过不了。&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;反向验证&lt;/strong&gt;：故意伪造声明，校验器必须报错。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个项目最后让我想的一句话是：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;写一个动人的故事不难。难的是故事里的每个数字都经得起回头查。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>拆开 TI-Nspire 的 .tns 看里面</title><link>https://cloudlake.eu.org/p/zehnscope/</link><pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/zehnscope/</guid><description>&lt;img src="https://cloudlake.eu.org/p/zehnscope/cover.jpg" alt="Featured image of post 拆开 TI-Nspire 的 .tns 看里面" /&gt;&lt;p&gt;这台机器上有一个真实的 TI-Nspire 开发环境：OS 3.9.0.463 classic Touchpad、Ndless、从源码构建的 ARM 工具链。&lt;/p&gt;&#10;&lt;p&gt;目录里躺着 &lt;strong&gt;1055 个 &lt;code&gt;.tns&lt;/code&gt; 文件&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;.tns&lt;/code&gt; 不是一个格式。它是一层 &lt;code&gt;PRG\0&lt;/code&gt; 外壳，里面是 &lt;strong&gt;Zehn 容器&lt;/strong&gt;——Ndless 自己的可执行容器格式。而这个格式&lt;strong&gt;没有公开文档&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;这个项目把它读懂了，并且用「&lt;strong&gt;能不能逐字节重建&lt;/strong&gt;」来证明。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="自己解出来的格式"&gt;&lt;a href="#%e8%87%aa%e5%b7%b1%e8%a7%a3%e5%87%ba%e6%9d%a5%e7%9a%84%e6%a0%bc%e5%bc%8f" class="header-anchor"&gt;&lt;/a&gt;自己解出来的格式&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 2&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 3&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 4&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 5&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 6&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 7&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 8&#10;&lt;/span&gt;&lt;span class="lnt"&gt; 9&#10;&lt;/span&gt;&lt;span class="lnt"&gt;10&#10;&lt;/span&gt;&lt;span class="lnt"&gt;11&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.tns 文件&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── 装载器桩 0 ~ 0x1EC 字节（常见；也有 0/480/604/616/632 字节的变体）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── Zehn 容器&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 头 32 字节 8 个小端 u32：&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ &amp;#34;Zehn&amp;#34; / version=1 / file_size / reloc_count /&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ flag_count / extra_size / alloc_size / entry_offset&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 重定位表 reloc_count × 4 字节&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ 低 8 位 = 类型，高 24 位 = 偏移&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 标志表 flag_count × 4 字节（同样的 8/24 位切分）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── 额外数据 extra_size 字节（名字/作者/声明等字符串住在这里）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 代码 剩下的；若首个重定位是 FILE_COMPRESSED 则为 zlib 流&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="两个坑我都先踩了"&gt;&lt;a href="#%e4%b8%a4%e4%b8%aa%e5%9d%91%e6%88%91%e9%83%bd%e5%85%88%e8%b8%a9%e4%ba%86" class="header-anchor"&gt;&lt;/a&gt;两个坑，我都先踩了&#10;&lt;/h2&gt;&lt;h3 id="坑一zehn这个字符串会骗你"&gt;&lt;a href="#%e5%9d%91%e4%b8%80zehn%e8%bf%99%e4%b8%aa%e5%ad%97%e7%ac%a6%e4%b8%b2%e4%bc%9a%e9%aa%97%e4%bd%a0" class="header-anchor"&gt;&lt;/a&gt;坑一：「Zehn」这个字符串会骗你&#10;&lt;/h3&gt;&lt;p&gt;最直接的思路是——搜文件里 &lt;code&gt;Zehn&lt;/code&gt; 这四个字节，从那里开始解。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;但装载器桩里也出现了这个字符串。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;只搜这 4 个字节会找到诱饵。真正的头必须同时满足三个条件：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;version == 1&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;各表长度放得下（不越界）&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;file_size&lt;/code&gt; 确实落在文件范围内&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;光靠一个魔数是不够的。&lt;/strong&gt; 得用那些字段之间的一致性来互相验证。&lt;/p&gt;&#10;&lt;h3 id="坑二能力不等于要求"&gt;&lt;a href="#%e5%9d%91%e4%ba%8c%e8%83%bd%e5%8a%9b%e4%b8%8d%e7%ad%89%e4%ba%8e%e8%a6%81%e6%b1%82" class="header-anchor"&gt;&lt;/a&gt;坑二：「能力」不等于「要求」&#10;&lt;/h3&gt;&lt;p&gt;genzehn 的 &lt;code&gt;--color-support&lt;/code&gt; &lt;strong&gt;默认是 true&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;它的含义是「&lt;strong&gt;支持&lt;/strong&gt; CX/CM 彩屏」，不是「&lt;strong&gt;需要&lt;/strong&gt;彩屏」。&lt;/p&gt;&#10;&lt;p&gt;我一开始理解反了，于是&lt;strong&gt;每一个正常程序都被判成「需要彩屏，所以在这台黑白机上跑不了」&lt;/strong&gt;——一个完全反向的结论，而且看起来非常合理。&lt;/p&gt;&#10;&lt;p&gt;正确判据必须看值为 &lt;strong&gt;0&lt;/strong&gt;：0 才表示&amp;quot;不支持这类机器&amp;quot;。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个坑值得单独说：&lt;strong&gt;当一个字段名叫 &lt;code&gt;supports&lt;/code&gt;，人们会下意识把它读成 &lt;code&gt;requires&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="验证逐字节重建"&gt;&lt;a href="#%e9%aa%8c%e8%af%81%e9%80%90%e5%ad%97%e8%8a%82%e9%87%8d%e5%bb%ba" class="header-anchor"&gt;&lt;/a&gt;验证：逐字节重建&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;rebuild()&lt;/code&gt; 从解析出来的字段把容器&lt;strong&gt;重新拼回去&lt;/strong&gt;，然后和原文件比字节。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;span class="lnt"&gt;5&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python -m zehnscope --rebuild &amp;#34;E:\QA\nspire-dev&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;11/11 containers rebuilt byte for byte&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python tests/test_zehn.py&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ok all 100 containers rebuild identically&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;这比&amp;quot;解析没崩溃&amp;quot;强得多。&lt;/p&gt;&#10;&lt;p&gt;因为偏移、计数、位域切分、字节序、字符串在额外数据里的定位——&lt;strong&gt;任何一处理解错，字节就对不上&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;它一次同时检验所有假设。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h3 id="语料是-1055-个真实文件不是精选样本"&gt;&lt;a href="#%e8%af%ad%e6%96%99%e6%98%af-1055-%e4%b8%aa%e7%9c%9f%e5%ae%9e%e6%96%87%e4%bb%b6%e4%b8%8d%e6%98%af%e7%b2%be%e9%80%89%e6%a0%b7%e6%9c%ac" class="header-anchor"&gt;&lt;/a&gt;语料是 1055 个真实文件，不是精选样本&#10;&lt;/h3&gt;&lt;p&gt;其中 100 个是真正的 Ndless 程序，955 个只是沿用了 &lt;code&gt;.tns&lt;/code&gt; 扩展名的文档/资源（&lt;code&gt;boot1.img.tns&lt;/code&gt;、Hitori 关卡、Liero 素材……）。&lt;/p&gt;&#10;&lt;p&gt;一个只见过&amp;quot;干净样本&amp;quot;的解析器，证明力弱得多。&lt;strong&gt;跨十年、多个工具链、8 种不同桩的 100 个真实容器&lt;/strong&gt;，才是像样的考验。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="手写一个-deflate-解码器"&gt;&lt;a href="#%e6%89%8b%e5%86%99%e4%b8%80%e4%b8%aa-deflate-%e8%a7%a3%e7%a0%81%e5%99%a8" class="header-anchor"&gt;&lt;/a&gt;手写一个 DEFLATE 解码器&#10;&lt;/h2&gt;&lt;p&gt;Zehn 的压缩用 zlib。&lt;/p&gt;&#10;&lt;p&gt;但语料里&lt;strong&gt;没有一个压缩容器&lt;/strong&gt;——光靠真实文件测不到这条路。&lt;/p&gt;&#10;&lt;p&gt;所以自己按格式&lt;strong&gt;造一个&lt;/strong&gt;再读回来。&lt;/p&gt;&#10;&lt;p&gt;解码器是手写的 RFC 1951 + 1950：stored / fixed / dynamic 三种块、LZ77 回引用、Adler-32 校验。&lt;/p&gt;&#10;&lt;p&gt;然后对照系统 zlib，在 &lt;strong&gt;40 组&lt;/strong&gt;样本上比对输出（10 种载荷 × 4 个压缩级别）。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;一个实现自己和自己一致，说明不了什么。两个独立实现一致，才算证据。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一个被窄样本骗过的断言"&gt;&lt;a href="#%e4%b8%80%e4%b8%aa%e8%a2%ab%e7%aa%84%e6%a0%b7%e6%9c%ac%e9%aa%97%e8%bf%87%e7%9a%84%e6%96%ad%e8%a8%80" class="header-anchor"&gt;&lt;/a&gt;一个被窄样本骗过的断言&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;tests/test_zehn.py&lt;/code&gt; 打印出来的实测结果：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;桩大小&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;容器数&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0x1EC (492)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;53&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;17&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;632&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;11&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;480&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;616&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;604&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&lt;strong&gt;17 个容器完全没有桩&lt;/strong&gt;——容器就在文件偏移 0。&lt;/p&gt;&#10;&lt;p&gt;我最初写了一条「所有程序共用一个桩」的断言，在 14 个 &lt;code&gt;samples-tns&lt;/code&gt; 上&lt;strong&gt;全部通过&lt;/strong&gt;，在全语料上&lt;strong&gt;直接失败&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;这正是把断言写在窄样本上会骗人的例子：&lt;strong&gt;样本越小，错误越自信。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="兼容性判定一个被推翻的直觉"&gt;&lt;a href="#%e5%85%bc%e5%ae%b9%e6%80%a7%e5%88%a4%e5%ae%9a%e4%b8%80%e4%b8%aa%e8%a2%ab%e6%8e%a8%e7%bf%bb%e7%9a%84%e7%9b%b4%e8%a7%89" class="header-anchor"&gt;&lt;/a&gt;兼容性判定：一个被推翻的直觉&#10;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;compat.py&lt;/code&gt; 拿程序&lt;strong&gt;自己声明的能力&lt;/strong&gt;和&lt;strong&gt;具体机器&lt;/strong&gt;对判。&lt;/p&gt;&#10;&lt;p&gt;这台机器的约束很紧：classic 非 CX、320×240 黑白、Touchpad、Ndless r2016、64MB。&lt;/p&gt;&#10;&lt;p&gt;原本的直觉解释是：「SDK 是 r2022、设备是 r2016，所以自写的程序被拒载重启」。&lt;/p&gt;&#10;&lt;p&gt;但实测结论是：&lt;strong&gt;这 100 个程序里，没有一个声明 Ndless 版本窗口&lt;/strong&gt;（&lt;code&gt;NDLESS_VERSION_MIN&lt;/code&gt; / &lt;code&gt;REVISION_MIN&lt;/code&gt; 全缺）。&lt;/p&gt;&#10;&lt;p&gt;也就是说 &lt;strong&gt;版本不匹配不会导致装载被拒&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;那条&amp;quot;合理&amp;quot;的解释，直接被数据否掉了。真正的原因得往别处找——&lt;code&gt;ut_read_os_version_index()&lt;/code&gt; 的地址哈希、&lt;code&gt;sc_addrs_ptr&lt;/code&gt; 查表、或者 classic 上看门狗没关。&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这是这个项目最值钱的一条：&lt;strong&gt;一个解释听起来越合理，越要拿数据去撞它。&lt;/strong&gt;&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="还能往哪走"&gt;&lt;a href="#%e8%bf%98%e8%83%bd%e5%be%80%e5%93%aa%e8%b5%b0" class="header-anchor"&gt;&lt;/a&gt;还能往哪走&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;容器写入器&lt;/strong&gt;：现在只会重建&lt;strong&gt;已有&lt;/strong&gt;容器。接上 ELF 解析就能把任意 ARM ELF 打包成合法 &lt;code&gt;.tns&lt;/code&gt;——包括 genzehn 那部分（扫 &lt;code&gt;.rel.text&lt;/code&gt;、发 &lt;code&gt;ADD_BASE&lt;/code&gt;/&lt;code&gt;ADD_BASE_GOT&lt;/code&gt;、处理 GOT 回滚）&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;OS 镜像&lt;/strong&gt;：&lt;code&gt;boot1.img.tns&lt;/code&gt; / &lt;code&gt;boot2.img.tns&lt;/code&gt; / &lt;code&gt;diags.img.tns&lt;/code&gt; 不是 Zehn 容器，是 TI 的 OS/引导镜像，格式完全不同&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;syscall 面&lt;/strong&gt;：把容器里的重定位和 &lt;code&gt;syscall-list.h&lt;/code&gt; 的 ID 表关联，就能静态列出每个程序用到的 syscall，再和 r2016 的表做差集——&lt;strong&gt;那才是&amp;quot;为什么重启&amp;quot;的正确问法&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/zehn/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;看 100 个程序的容器报告 →&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;&lt;hr&gt;&#10;&lt;h2 id="技术细节"&gt;&lt;a href="#%e6%8a%80%e6%9c%af%e7%bb%86%e8%8a%82" class="header-anchor"&gt;&lt;/a&gt;技术细节&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;文件&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;zehnscope/core.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;容器解析 + &lt;strong&gt;逐字节重建&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;zehnscope/inflate.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;手写 DEFLATE/zlib 解码器&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;zehnscope/__main__.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CLI：inspect / &lt;code&gt;--rebuild&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;compat.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;能力 vs 机型判定（含&amp;quot;能力≠要求&amp;quot;的说明）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;tests/test_zehn.py&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;18 项断言&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;逆向工程真正的方法论，不是&amp;quot;猜对格式&amp;quot;。&lt;/p&gt;&#10;&lt;p&gt;是&lt;strong&gt;找到一种只有完全理解它才能通过的检验&lt;/strong&gt;，然后不断撞上去。&lt;/p&gt;&#10;&lt;p&gt;逐字节重建就是这样一种检验。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>17 个词画出一朵玫瑰</title><link>https://cloudlake.eu.org/p/loom/</link><pubDate>Sun, 11 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/loom/</guid><description>&lt;img src="https://cloudlake.eu.org/p/loom/cover.jpg" alt="Featured image of post 17 个词画出一朵玫瑰" /&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 每轮固定转 1/47 圈，半径按 1+0.45·sin(3θ) 起伏 —— 走满一圈正好闭合&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0.021277 turn&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0.021277 i * 3 * sin 0.45 * 1 + 55 * fwd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0.0008 i * color&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;七行，画出一朵三瓣玫瑰。&lt;/p&gt;&#10;&lt;p&gt;这是一门语言的完整程序。语料只有 &lt;strong&gt;17 个词&lt;/strong&gt;，但它&lt;strong&gt;图灵完备&lt;/strong&gt;——&lt;code&gt;jz&lt;/code&gt; 加回跳足以表达任意控制流。&lt;/p&gt;&#10;&lt;p&gt;打开一个 html 文件就能用：左边写程序，按 &lt;code&gt;Ctrl+Enter&lt;/code&gt; 重跑，画布上出现图案。&lt;strong&gt;单文件、离线、零依赖。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="17-个词"&gt;&lt;a href="#17-%e4%b8%aa%e8%af%8d" class="header-anchor"&gt;&lt;/a&gt;17 个词&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;词&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;作用&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;数字&lt;/code&gt; / &lt;code&gt;p0&lt;/code&gt;…&lt;code&gt;p5&lt;/code&gt; / &lt;code&gt;i&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;压入常量、参数滑杆、当前轮数&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;+ - / mod neg&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;二元算术；&lt;code&gt;neg&lt;/code&gt; 取负&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;&amp;gt; &amp;lt; sign&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;比较（返回 0/1）；&lt;code&gt;sign&lt;/code&gt; 取符号&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;sin cos&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;输入是&lt;strong&gt;圈&lt;/strong&gt;不是弧度：&lt;code&gt;0.25&lt;/code&gt; → 90°&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;rand&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;伪随机 0…1，种子固定 ⇒ 可复现&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;fwd back&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;沿朝向前进/后退（画线）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;turn right&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;转向：&lt;code&gt;turn 0.25&lt;/code&gt; = 左转 90°&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;jump&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;画笔瞬移到 (x, y)，不画线&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;N st&lt;/code&gt; / &lt;code&gt;N ld&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;存入 / 取出 N 号寄存器（N = 0…7）&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;pen&lt;/code&gt; &lt;code&gt;color&lt;/code&gt; &lt;code&gt;width&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;抬落笔、色相 0…1、笔宽&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;jmp N&lt;/code&gt; / &lt;code&gt;jz N&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;跳到第 N 行；&lt;code&gt;jz&lt;/code&gt; 在栈顶为 0 时跳&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;pause&lt;/code&gt; / &lt;code&gt;halt&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;交还控制权（动画靠它）/ 停机&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;栈是唯一的数据通道，所以约定是&lt;strong&gt;后缀式&lt;/strong&gt;：写 &lt;code&gt;0.25 turn&lt;/code&gt;，不写 &lt;code&gt;turn 0.25&lt;/code&gt;。&lt;/p&gt;&#10;&lt;p&gt;跳转用&lt;strong&gt;代码行号&lt;/strong&gt;。跳到注释行或空行时会自动顺延到后面第一条真指令。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;程序会被反复执行&lt;/strong&gt;——这就是动画的来源，&lt;code&gt;i&lt;/code&gt; 是已经跑过的轮数。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="设计上真正需要决定的三件事"&gt;&lt;a href="#%e8%ae%be%e8%ae%a1%e4%b8%8a%e7%9c%9f%e6%ad%a3%e9%9c%80%e8%a6%81%e5%86%b3%e5%ae%9a%e7%9a%84%e4%b8%89%e4%bb%b6%e4%ba%8b" class="header-anchor"&gt;&lt;/a&gt;设计上真正需要决定的三件事&#10;&lt;/h2&gt;&lt;p&gt;写这门语言的过程中，有三处不做决定就写不下去。&lt;/p&gt;&#10;&lt;h3 id="1-循环的进度谁提供"&gt;&lt;a href="#1-%e5%be%aa%e7%8e%af%e7%9a%84%e8%bf%9b%e5%ba%a6%e8%b0%81%e6%8f%90%e4%be%9b" class="header-anchor"&gt;&lt;/a&gt;1. 循环的进度谁提供？&#10;&lt;/h3&gt;&lt;p&gt;不是 &lt;code&gt;while&lt;/code&gt;，而是「程序反复执行 + &lt;code&gt;i&lt;/code&gt; 计数」。&lt;/p&gt;&#10;&lt;p&gt;好处是写参数方程时不用自己维护计数器。代价很实在：&lt;strong&gt;每轮的步长必须一样&lt;/strong&gt;。用 &lt;code&gt;i&lt;/code&gt; 递增角度画不出闭合曲线——每轮走的角不同，永远回不到起点。&lt;/p&gt;&#10;&lt;p&gt;这个坑是我自己写示例时踩到的，注释留在了「三瓣」示例里。&lt;/p&gt;&#10;&lt;h3 id="2-循环里的状态放哪"&gt;&lt;a href="#2-%e5%be%aa%e7%8e%af%e9%87%8c%e7%9a%84%e7%8a%b6%e6%80%81%e6%94%be%e5%93%aa" class="header-anchor"&gt;&lt;/a&gt;2. 循环里的状态放哪？&#10;&lt;/h3&gt;&lt;p&gt;最初只有栈。而栈每轮必须清空，否则运行时会一圈圈涨。&lt;/p&gt;&#10;&lt;p&gt;结果：&lt;strong&gt;累加器根本写不出来&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;所以加了&lt;strong&gt;寄存器&lt;/strong&gt;：&lt;code&gt;5 0 st&lt;/code&gt; 存、&lt;code&gt;0 ld&lt;/code&gt; 取。&lt;/p&gt;&#10;&lt;p&gt;栈保持&amp;quot;每轮归零&amp;quot;的干净语义，跨轮状态一律走寄存器。分析器因此能给出很强的保证：&lt;strong&gt;程序停止时栈必须是空的&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="3-怎么保证这些约定不被写坏"&gt;&lt;a href="#3-%e6%80%8e%e4%b9%88%e4%bf%9d%e8%af%81%e8%bf%99%e4%ba%9b%e7%ba%a6%e5%ae%9a%e4%b8%8d%e8%a2%ab%e5%86%99%e5%9d%8f" class="header-anchor"&gt;&lt;/a&gt;3. 怎么保证这些约定不被写坏？&#10;&lt;/h3&gt;&lt;p&gt;靠 &lt;code&gt;analyze()&lt;/code&gt;：对编译出的指令做前向数据流分析。&lt;/p&gt;&#10;&lt;p&gt;一条指令被不同路径以不同栈深到达，就重新入队（上限 6 个栈深，再多就是无限增长）。&lt;/p&gt;&#10;&lt;p&gt;判据是&lt;strong&gt;程序停止时栈深为 0&lt;/strong&gt;。这一条覆盖三种坑：栈被弹穿、分支不均衡、循环里偷偷堆值。&lt;/p&gt;&#10;&lt;p&gt;被拒绝时给的是可读的诊断：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✗ 栈不平衡：第 1 行（栈在循环里无限增长（同一条指令被越来越深的栈反复到达））。&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 每个词都要自己把栈收干净，跳转的两个分支不能一个多一个少。&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;编译期还有一道 peephole：&lt;code&gt;N fwd&lt;/code&gt; 折成一条带参指令（&lt;code&gt;fwd#N&lt;/code&gt;），既让指令数少一半，也消掉了&amp;quot;两个字面量在分支里叠在栈上&amp;quot;的歧义。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="验证"&gt;&lt;a href="#%e9%aa%8c%e8%af%81" class="header-anchor"&gt;&lt;/a&gt;验证&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mjs&lt;/span&gt; &lt;span class="c1"&gt;# 63 项断言&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mjs&lt;/span&gt; &lt;span class="c1"&gt;# 27 项断言（真机 Chromium）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;make&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;samples&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mjs&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="c1"&gt;# 逐个示例：编译 → 栈分析 → 真跑一遍&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt;：同一种子逐位一致，不同种子不同图 ⇒ 任何观察都可复现&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;敌意输入&lt;/strong&gt;：空程序、只有注释、除零、取模零、寄存器槽越界、&lt;code&gt;1e9&lt;/code&gt; 大数 —— 都不崩&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：约 &lt;strong&gt;2400 万条指令/秒&lt;/strong&gt;（单线程）；界面默认每秒执行 600 条&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;示例是机械生成的&lt;/strong&gt;：生成器先编译、做栈分析、真跑 8000 步，全部通过才写回 &lt;code&gt;loom.html&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;最后这条是被逼出来的做法——&lt;strong&gt;在此之前我手工改示例错了五次&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;h3 id="验证抓到的-8-个真实缺陷"&gt;&lt;a href="#%e9%aa%8c%e8%af%81%e6%8a%93%e5%88%b0%e7%9a%84-8-%e4%b8%aa%e7%9c%9f%e5%ae%9e%e7%bc%ba%e9%99%b7" class="header-anchor"&gt;&lt;/a&gt;验证抓到的 8 个真实缺陷&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;#&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;缺陷&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;症状&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;跳转目标被折成 &lt;code&gt;[&amp;quot;lit&amp;quot;, idx]&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;jz&lt;/code&gt; 拿到 0 而不是目标行，条件跳转全部失效&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;跳转占位指令多占一个槽位&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;目标整体错位，&lt;code&gt;i&lt;/code&gt; 永不递增&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;跳转参数是&lt;strong&gt;字符串&lt;/strong&gt; &lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;Number.isFinite(&amp;quot;0&amp;quot;)&lt;/code&gt; 为 false ⇒ 回边从未被分析&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;末端判定读的是执行&lt;strong&gt;前&lt;/strong&gt;的栈深&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;1 fwd&lt;/code&gt; 这类正常程序被误判不平衡&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;分析器只记&amp;quot;首达&amp;quot;栈深&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每轮偷偷堆值的程序被放行&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;缺&amp;quot;每轮入口栈深一致&amp;quot;不变量&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同上——运行 1500 步后栈涨到 1500&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;示例里 &lt;code&gt;i 0.1 color&lt;/code&gt; 的栈残留&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每轮留一个数，栈无限增长&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;自动取景用&amp;quot;全部历史外接框&amp;quot;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;随机游走把视野撑到无穷，画面全黑&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;第 8 条不是语言问题，是&lt;strong&gt;可视化策略&lt;/strong&gt;问题。用历史包络去取景，对无界图形必然是错的；改成跟随画笔当前位置之后才正常。&lt;/p&gt;&#10;&lt;p&gt;第 3 条尤其典型：&lt;code&gt;&amp;quot;0&amp;quot;&lt;/code&gt; 和 &lt;code&gt;0&lt;/code&gt; 在 JavaScript 里看起来都像 0，但 &lt;code&gt;Number.isFinite(&amp;quot;0&amp;quot;)&lt;/code&gt; 是 &lt;code&gt;false&lt;/code&gt;。于是回边从来没被分析过，&lt;strong&gt;静态检查等于不存在&lt;/strong&gt;——而界面一切正常。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="它现在能画什么"&gt;&lt;a href="#%e5%ae%83%e7%8e%b0%e5%9c%a8%e8%83%bd%e7%94%bb%e4%bb%80%e4%b9%88" class="header-anchor"&gt;&lt;/a&gt;它现在能画什么&#10;&lt;/h2&gt;&lt;p&gt;它只画线。但 17 个词加上反复执行，能出来的东西比想象的多：蕨类、格栅、漂移的涡、以及那朵玫瑰。&lt;/p&gt;&#10;&lt;p&gt;参数滑杆 &lt;code&gt;p0&lt;/code&gt;…&lt;code&gt;p5&lt;/code&gt; 让你不改代码就能调形状——这也是它作为一个&amp;quot;玩的东西&amp;quot;成立的原因。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/loom/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;打开 Loom 玩一下 →&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;&lt;hr&gt;&#10;&lt;h2 id="还能往哪走"&gt;&lt;a href="#%e8%bf%98%e8%83%bd%e5%be%80%e5%93%aa%e8%b5%b0" class="header-anchor"&gt;&lt;/a&gt;还能往哪走&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;图形输出&lt;/strong&gt;：现在只画线。加 &lt;code&gt;fill&lt;/code&gt; / &lt;code&gt;stroke&lt;/code&gt; 路径模式就能做面&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;多画笔&lt;/strong&gt;：&lt;code&gt;pen&lt;/code&gt; 已经是状态机，加若干&amp;quot;画笔槽&amp;quot;就能一次画多层&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;编辑器&lt;/strong&gt;：行号已经在了，差行内报错高亮；&lt;code&gt;analyze()&lt;/code&gt; 已经能给出准确行号，接口是现成的&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;17 个词听起来很少。&lt;/p&gt;&#10;&lt;p&gt;但语言的表达能力从来不是词表决定的，是&lt;strong&gt;组合规则&lt;/strong&gt;决定的。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item><item><title>摩擦越大，原始汤越活</title><link>https://cloudlake.eu.org/p/primordium/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate><guid>https://cloudlake.eu.org/p/primordium/</guid><description>&lt;img src="https://cloudlake.eu.org/p/primordium/cover.jpg" alt="Featured image of post 摩擦越大，原始汤越活" /&gt;&lt;p&gt;一个极简的人工生命沙盒。规则只有三条，作用在每一对上：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1) 径向力 f = w_ij · (1 − d/R) 正吸引 / 负排斥&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2) 对齐力矩 速度朝邻居速度方向旋转 （群游、漩涡的来源）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3) 摩擦 每步速度 × fric&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;物种数量任意，&lt;strong&gt;相互作用矩阵 &lt;code&gt;w_ij&lt;/code&gt; 可以编辑&lt;/strong&gt;——所以它不是&amp;quot;一个模拟&amp;quot;，而是一个&lt;strong&gt;可以在上面找行为的实验台&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;拖动画布可以抓起粒子搅拌，滚轮缩放。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="我在这台实验台上发现的两件事"&gt;&lt;a href="#%e6%88%91%e5%9c%a8%e8%bf%99%e5%8f%b0%e5%ae%9e%e9%aa%8c%e5%8f%b0%e4%b8%8a%e5%8f%91%e7%8e%b0%e7%9a%84%e4%b8%a4%e4%bb%b6%e4%ba%8b" class="header-anchor"&gt;&lt;/a&gt;我在这台实验台上发现的两件事&#10;&lt;/h2&gt;&lt;p&gt;这些预设不是我手调出来的，是扫描脚本在规则空间里跑出来的。&lt;/p&gt;&#10;&lt;p&gt;而扫描本身给了两个&lt;strong&gt;反直觉&lt;/strong&gt;的结果。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="发现一摩擦越大体系反而越活"&gt;&lt;a href="#%e5%8f%91%e7%8e%b0%e4%b8%80%e6%91%a9%e6%93%a6%e8%b6%8a%e5%a4%a7%e4%bd%93%e7%b3%bb%e5%8f%8d%e8%80%8c%e8%b6%8a%e6%b4%bb" class="header-anchor"&gt;&lt;/a&gt;发现一：摩擦越大，体系反而越&amp;quot;活&amp;quot;&#10;&lt;/h3&gt;&lt;p&gt;第一轮扫描 &lt;strong&gt;486 组对称规则&lt;/strong&gt;（同类相吸 + 异类相斥），跑 900 步后统计&amp;quot;是否冻结&amp;quot;：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;摩擦&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;冻结比例&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.900&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;44%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.950&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;31%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;0.975&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;11%&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;直觉会说&amp;quot;摩擦越大越容易静止&amp;quot;。实测正相反。&lt;/p&gt;&#10;&lt;p&gt;原因在于：&lt;strong&gt;成对吸引作用于彼此，会把动能互相抽干&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;粒子在吸引下加速、越过对方、被反向拉回——每一次都在消耗能量。摩擦&lt;strong&gt;弱&lt;/strong&gt;时，这套&amp;quot;摆&amp;quot;很快把能量耗散干净，然后卡死在力平衡的位置，变成一动不动的静态点阵。&lt;/p&gt;&#10;&lt;p&gt;摩擦&lt;strong&gt;大&lt;/strong&gt;反而阻止了这个过程。能量来不及被抽干，体系保持缓慢滑动。&lt;/p&gt;&#10;&lt;p&gt;顺带两条结论：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;高摩擦下所有活动规则都&lt;strong&gt;碎成 100+ 个孤立小团&lt;/strong&gt;（最大团占比 5–11%），聚不成大结构&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;对齐&lt;/strong&gt;这一项在对称规则里几乎不影响存活率，只改变形状&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="发现二持续活动来自力的不对称不是来自加能量"&gt;&lt;a href="#%e5%8f%91%e7%8e%b0%e4%ba%8c%e6%8c%81%e7%bb%ad%e6%b4%bb%e5%8a%a8%e6%9d%a5%e8%87%aa%e5%8a%9b%e7%9a%84%e4%b8%8d%e5%af%b9%e7%a7%b0%e4%b8%8d%e6%98%af%e6%9d%a5%e8%87%aa%e5%8a%a0%e8%83%bd%e9%87%8f" class="header-anchor"&gt;&lt;/a&gt;发现二：持续活动来自&amp;quot;力的不对称&amp;quot;，不是来自加能量&#10;&lt;/h3&gt;&lt;p&gt;对称规则要么冻结、要么碎成小团。改一处结构就能解决：&lt;strong&gt;让力矩阵不对称&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;A 追 B、B 躲 A——即&lt;strong&gt;非互易作用&lt;/strong&gt;。这样&amp;quot;作用—反作用&amp;quot;不再成对抵消，体系可以自己驱动自己。&lt;/p&gt;&#10;&lt;p&gt;三物种循环（A 追 B，B 追 C，C 追 A）扫描 108 组：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;仍在移动的粒子&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;非互易矩阵&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;100%&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;同一矩阵对称化后&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;59–69%&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;对照实验很干净——&lt;strong&gt;同一组参数，只把矩阵对称化&lt;/strong&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;span class="lnt"&gt;4&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chase flee fric align core | 非互易 moved/phi/团数 | 对称化 moved/phi/团数&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0.80 -0.70 0.985 0.00 2.0 | 100% 0.694 186 | 59% 0.440 134&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1.20 -1.20 0.985 0.00 2.0 | 100% 0.622 161 | 63% 0.182 126&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 0.40 -0.20 0.960 0.30 4.0 | 98% 0.724 75 | 69% 0.437 73&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;最好的规则（「翻涌」预设）：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;span class="lnt"&gt;3&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="mf"&gt;1.2&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.7&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="c1"&gt;// 摩擦 0.96，对齐 0.3，斥核 4.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;p&gt;100% 粒子持续移动，混合度 Φ=0.48，最大团占比 16%，而&amp;quot;结构还在重组&amp;quot;的指标（末段熵的波动）是全扫描最高——&lt;strong&gt;0.0245&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="把看起来有意思变成可测量的数"&gt;&lt;a href="#%e6%8a%8a%e7%9c%8b%e8%b5%b7%e6%9d%a5%e6%9c%89%e6%84%8f%e6%80%9d%e5%8f%98%e6%88%90%e5%8f%af%e6%b5%8b%e9%87%8f%e7%9a%84%e6%95%b0" class="header-anchor"&gt;&lt;/a&gt;把&amp;quot;看起来有意思&amp;quot;变成可测量的数&#10;&lt;/h2&gt;&lt;p&gt;界面右侧的指标都是&lt;strong&gt;可证伪&lt;/strong&gt;的，不是装饰：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;指标&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;定义&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;判读&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;聚类系数 C&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;你邻居的邻居也是你邻居的概率&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;随机撒点≈0，成团后上升&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;最大团占比&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;按并查集连通的最大团 / 总数&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;晶体≈100%，气体≈0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;混合度 Φ&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;每个粒子&amp;quot;最近异类距离 ≤ 最近同类距离&amp;quot;的比例&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;相分离→0，追逐/共生→高&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;空间熵 H&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;网格计数的归一化熵&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;均匀→1，塌缩成团→下降&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;不可预测性&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;与&amp;quot;同初值 + 1.5px 微扰&amp;quot;副本的平均位置发散&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;从 0.02 起步，1200 步后可读&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;实测对照（1500 步，800–1100 粒子）：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;预设&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;C&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;Φ&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;H&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;最大团&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;动能&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;翻涌（非互易）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.85&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.48&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.74&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;16%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;5.61&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;生命边缘（对称）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.00&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.13&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.79&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;98%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;~0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;结晶（对称、低摩擦）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.77&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.95&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.79&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;33%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;0.004&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;相分离&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.00&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;0.02&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.75&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;61%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.64&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;原始汤（无作用）&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.57&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.49&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.77&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;98%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;~0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;「相分离」的 Φ=0.02 和「翻涌」的 Φ=0.48——&lt;strong&gt;同一个指标把两种完全不同的结构分开了&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;不可预测性上，「翻涌」的最大发散是冻结体系的数十倍。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;同一个 1.5px 的扰动，在自我驱动的体系里会长成完全不同的世界，在冻结体系里什么都不会发生。&lt;/strong&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="验证不靠肉眼"&gt;&lt;a href="#%e9%aa%8c%e8%af%81%e4%b8%8d%e9%9d%a0%e8%82%89%e7%9c%bc" class="header-anchor"&gt;&lt;/a&gt;验证：不靠肉眼&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;&#10;&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1&#10;&lt;/span&gt;&lt;span class="lnt"&gt;2&#10;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#10;&lt;td class="lntd"&gt;&#10;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node verify.mjs # 71 项断言&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node verify-browser.mjs # 34 项断言（真机 Chromium）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#10;&lt;/div&gt;&#10;&lt;/div&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;确定性&lt;/strong&gt;：同一种子两次运行逐位一致；不同种子不同 ⇒ 任何观察可复现&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;数值健全&lt;/strong&gt;：7 个预设各跑 1500 步，无 NaN / Infinity，坐标不出界，速度有界&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;指标区分力&lt;/strong&gt;：三个结构指标在预设之间跨度都在 0.05 以上&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;物理直觉断言&lt;/strong&gt;：相分离的 Φ 必须低于追逐体系；晶体的最大团必须大于原始汤&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：单线程 900 粒子 0.19 ms/步、3000 粒子 1.4 ms/步；浏览器里 3000 粒子仍 45–60 fps&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;关键的一条：&lt;code&gt;verify.mjs&lt;/code&gt; &lt;strong&gt;直接从 HTML 里抽出 UMD 核心在 Node 里跑&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;所以&amp;quot;这个模拟是什么行为&amp;quot;可以&lt;strong&gt;用脚本回答&lt;/strong&gt;，不需要打开浏览器用眼睛猜。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p style="text-align:center;margin:2.5em 0"&gt;&#13;&#10;&lt;a href="https://cloudlake.eu.org/lab/primordium/" style="display:inline-block;padding:12px 26px;border-radius:8px;background:var(--accent-color);color:var(--accent-color-text);text-decoration:none;font-weight:600"&gt;打开原始汤实验室 →&lt;/a&gt;&#13;&#10;&lt;/p&gt;&#13;&#10;&lt;hr&gt;&#10;&lt;h2 id="还能往哪走"&gt;&lt;a href="#%e8%bf%98%e8%83%bd%e5%be%80%e5%93%aa%e8%b5%b0" class="header-anchor"&gt;&lt;/a&gt;还能往哪走&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;能量守恒版本&lt;/strong&gt;：现在是耗散系统，加入速度相关的能量注入，看它是否进入稳态湍流&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;自适应规则&lt;/strong&gt;：让 &lt;code&gt;w_ij&lt;/code&gt; 本身随接触历史变化，看是否自发出现分工或领地&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;三维&lt;/strong&gt;：核心已经是纯数组合集，换成三个坐标即可，代价只是渲染要改写&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;这个项目最让我意外的地方，不是某个预设好看。&lt;/p&gt;&#10;&lt;p&gt;是&lt;strong&gt;&amp;ldquo;加能量&amp;quot;并不是让系统活起来的唯一办法&lt;/strong&gt;——把对称性打破，它自己就会动。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;</description></item></channel></rss>