上半 · 这九份自己漏着的
六个洞,按值钱程度排
前两条是真正重要的:①是正确性,②是这个项目最独特的资产没有入口——
都已补。后四条是工程卫生,④⑤ 也补了。六条全补完了,同一天。
①
闸门第二关是字符串匹配,不是数值核对
scripts/check.mjs + check-browser.mjs · 2026-09-07 两半都补完
已补
旧闸门里,页面 JS 改坏了、运行时算出别的数,它照样绿——只要正文那句话还写着原来那个数。
当时的毛病,两条
synced() 做的是 src.includes('109 倍'):验的是「正文里写着这个字符串」,
不是「页面运行时算出这个数」。第二条更阴——硬串很脆,
'0.45m ~ 0.60m'、'∛机器精度' 这类排版一改就失配,
而一条断言消失只是少一行绿,不会红。
做法
正文里的每个数改成槽位:<b data-fact="dh.gap">65536 倍</b>,
由页面自己那份实现在运行时填,同时挂到 window.__facts。
闸门按键 → 值核对,并检查没有孤儿槽位(加了 data-fact 却没人验)。
两个方向都做过反向测试:改错数字 → 红;加槽位不验 → 红。
结果
主闸门 75 → 137 条断言,零依赖,几秒钟跑完。
后半也做完了:npm run check:browser 真开一个浏览器,
读 window.__facts,把「运行时算出来的数」和「页面上写着的数」对上——
37 条断言、42 个键在真浏览器里闭环。⇒ lib 值 == 静态初值 == 运行时值,三者相等。
它单独一个 CI job,主闸门保持零依赖。
第五关开起来抓到的第一个东西
05 卡尔曼那页在浏览器里抛 getS is not defined——registerFacts() 整个异常,
两个 fact 从来没登记上,而静态闸门一直是绿的:它只看得见初值。
这正是这一关存在的理由,而它抓到的第一个受害者是我自己前一天写的代码。
顺带它还把「页面报错就红」变成了一条断言 —— 在这之前,九个页面里任何一个 JS 抛异常都只能靠肉眼发现。
补的过程中当场抓到六处,其中一处是页面上的数本身就错了。
| 抓到什么 | 性质 |
| 03 |
正文「差 47000 倍」是错的,真值 65536(= 2¹⁶)。
而旧断言 near(2¹⁶, 65536, 1) 恒真——标签挂着 47000,验的却是个恒等式,
synced 再确认页面上写着这个串 |
闸门空转新的一类 |
| 02 |
正文公式 255q + 2√(255q) < 16 解出来是 3.8%,页面写 3.6%。
3.6% 对应的是 Z ≈ 2.211(「74 个码字全成功」那个分位数);闸门里则手抄了 x = 9.2 当「解」 |
公式和它自己的数不自洽 |
| 09 |
正文那七个数(区间 + 六个值 + 0.15m)页面运行时根本不算,是一次性实验手抄的;
闸门只卡了 0.35 < r < 0.70 这条两边都能过的松带子 |
手抄 + 松带子 |
| 04 |
三处漂移:33.5→33.7、19.5→19.6、7.8→8.0。
旧断言是 near(ratio, 33.5, 2.5) 这种容差带,正文那个数从来没被真比过 |
实现漂了,正文没跟 |
| 05 |
67 米 → 68 米,同上 |
同上 |
| 08 |
1906 ms / 31.0 倍 / 2.0 倍 三个数之前没有任何东西在验,
只有字符串核对。现在真跑了——数是对的 |
无人验(结果无恙) |
最该记住的一条
设计文档里那 21 处订正分 A 类(结论错)和 B 类(测量脏)。03 这一处是第三类:闸门自己空转。
断言写得像在验,实际验的是一个恒等式。它比 B 类更隐蔽——B 类至少给出一个看起来正常的错数字,
而这一类连看都没看。
还留在弱一档的
这些 lib/ 没算,仍然只有字符串核对(闸门里都标了 ⚠):
04 摇镜三档(1.91 / 1.39 / 1.01)、05 隧道三个数(11.8 / 314 / 34)和四配置 σ 比、07 的 0.076、06 的「d = 4」。
②
那二十一处订正,应该是首页第一入口
design/corrections.html · 2026-09-07 已补 · 现在是二十七处
已补
九份演示网上有替代品(没这么较真,但有)。「我预期错了,这是被打回的记录」——没有。
做法
独立成页:每一处摊开成「我以为 / 实测是 / 为什么会错」,并标出是哪一道关抓到的。
首页顶部第一个链接,页脚也指过去。原来的两类之外多了一类,是补第 ① 条时抓出来的:
| | 靠什么抓 |
| A 类 13 处 | 结论错了拍的定性判断被真数打翻,错得显眼 |
先写下预期再实测——不写下来就没有「被打回」这回事 |
| B 类 8 处 | 测量本身不干净给出的是一个看起来完全正常的错数字 |
同一件事用两份独立实现各算一遍 |
| C 类 6 处 | 检查在空转断言写得像在验,实际什么都没看 |
把手抄的数换成槽位,由实现在运行时填 |
顺带收掉的一个洞
index.html 以前完全不在闸门视野里(check.mjs 只读 switches/),已经漂了四处:
闸门条数还写着 75、04 卡片 33.5×/19.5×、05 的 67 米、09 的 0.45m ~ 0.60m、页脚「二十一处」。
⇒ fact 机制改成全局键模型:一个键一个值,出现在哪个页面都必须一致,
覆盖 12 个页面、43 个键、67 处引用。最后一节验首页写着的断言条数
—— 加或删一条断言就得改 index.html,否则闸门红。这是故意的。
③
分享层是空的
13 个页面 · 2026-09-07 已补
已补
九份都做成了 Artifact 形状、明摆着是给人转发的,转出去却只有一条光秃秃的链接。
做法
13 个页面各写一句 <meta name="description">,build 从
<title> 和它派生出 og:title / og:description / og:type / og:site_name / og:locale
和 twitter 那三条。⚠ 首页以前是原样复制的(它自带骨架标签),那样它拿不到 og——
现在剥掉骨架走同一条路,13 个页面一视同仁。设了 SITE_URL 还会补上
og:url 和 canonical。
⚠ 顺带堵掉一个陷阱
<meta> 里挂不了 data-fact 槽位 ⇒ 描述里一旦写进实测值,
它就成了下一处会漂而且没人验的数。所以闸门加了一条:
描述里出现任何一个 fact 值就红。十三句描述因此都是定性的。
那张图也有了
npm run shots 用同一个浏览器给每页截一张 1200×630 存进 assets/og/,
build 拷进 dist/og/ 并挂上 og:image,卡片升成 summary_large_image。
截的就是页面顶部那一屏——eyebrow + 标题 + 一句 thesis + 总闸 + 活着的演示,
本来就是为「一眼看懂」设计的,页面改了重截一次就同步,不会变成又一处手工维护的东西。
⚠ 闸门只验它在不在、尺寸对不对,验不了内容新不新。
④
build.mjs 的切割点
scripts/build.mjs · 2026-09-07 已补
已补
⚠ 这一页原来把它写成「加第十份时会踩的哑雷」。说轻了——它已经炸了。
实况
body.replace(/^…(?=<div class="wrap">)/, '$1')
是把匹配到的前缀换成它自己,于是 head 等于整个 body,
body.slice(head.length) 切出来是空串。
dist/ 里 11 个页面的内容全在 <head> 里,<body> 是空的。
为什么没人发现
浏览器会自动纠错:在 <head> 里遇到 <div> 就隐式关掉 head、开 body,
把内容重新挂对地方,所以站点看着完全正常。
而 CI 唯一那条检查只看首行有没有 doctype,照样绿。
⇒ 它和第 ① 条抓到的 C 类是同一种病:一个看着在验的检查,验的不是它声称的那件事。
做法
显式匹配切割点,build 里当场断言三条:切出来的 <body> 不少于 500 字符、
<title> 必须落在 head 那一半、找不到容器直接抛错
(加第十份换个类名会立刻失败,而不是静默塞进 head)。CI 另加一条独立复查。
⑤
20 处 requestAnimationFrame,0 处 IntersectionObserver
七个页面 · 2026-09-07 已补
已补
画布滚出视口照跑,同页离屏的动画没有任何东西会停下它——读底下那四张卡的时候,画布全在屏幕外。
做法
七个带 rAF 循环的页面(02 和 09 本来就没有)各加一个 IntersectionObserver:
观察本页所有画布,一张都看不见就停循环,回到视口再接上。
只关动画,不碰任何数字——fact 都是加载时就算好的。
⑥
没有 LICENSE,index.html 有个没写下来的例外
仓库根 · 2026-09-07 已补
已补
许可证:拆开
代码(lib/ scripts/ .github/)走 MIT,
内容(九份页面、设计文档、被打回的记录、分享卡片)走 CC BY 4.0。
拆的理由写在 LICENSE 头一段:那九份的价值不在代码上,在那些被实测打回的结论上——
代码希望被随便拿去用,结论希望被引用时说一声出处。
末尾附了一句请求(不是许可条件):引用页面上的数,请连它的条件一起引,
单拎一个数字出来正是这个项目从头到尾在反对的事。
那个例外:直接消掉,不是记下来
index.html 原来是唯一带骨架标签的源文件,build 得特判它、CI 那条检查对它开口子。
现在它也是 Artifact 形状了,13 个源文件走同一条路,没有例外了——
build 里那段剥骨架的正则也一并删掉。
下半 · 第十份该是谁
缺口在类别上,不在题材上
九份在题材上覆盖得不错。但它们全是同一种论证形状:
同一个问题,换掉一行,计算代价爆炸。有三整类产业级算法,这个形状套不上——所以一个都没进来。
| 缺的类别 | 它的「关掉之后」是什么 | 现有九份为什么覆盖不到 |
| 激励 / 机制设计拍卖 · 匹配 |
不是算得慢,是参与者开始撒谎,市场散架 |
九份里没有一个有「多个各有自己利益的参与者」 |
| 分布式一致性 / 容错Paxos · Raft · 2PC✓ 第 12 份 |
不是慢,是两个人同时以为自己是对的 |
九份的尺子全是「多少次 / 多少字节」,没有「多少次分歧」—— 现在有了 |
| 索引 / 数据结构B 树 · 倒排 · FM-index✓ 第 13 份 |
不是算法变慢,是数据规模的天花板整个塌下来 |
尺子该是内存字节和磁盘页数,九份一把都没用过 |
候选池
创造了新产业的算法
按经济体量排。⚠ 量级一栏是公开口径的数量级,不是实测数字——
这一页上唯一一处不能用闸门验的数据,标出来。绿底三行是下面详写的三个。
| 算法 | 它凭空造出来的产业 | 量级 | 一行开关 |
| A |
广义二价拍卖 GSP / VCGVickrey 1961 → AdWords 2002 |
搜索与展示广告——把注意力变成了一个可以清算的市场 |
~3000 亿 美元/年 |
二价换一价 → 出价互相试探、震荡,收入和效率一起掉 |
| B |
PageRank / 幂迭代Page & Brin 1998✓ 第 14 份 |
网页搜索本身——是 A 的前提 |
同上 |
特征向量迭代换回词频排序 → 前十全是灌水页 |
| C ★ |
一致性哈希Karger 1997 |
CDN 与云缓存——Akamai 1998 就是照着这篇论文开的公司 |
云基础设施 的一层 |
hash%N 换成环上取后继 |
| D |
Paxos / Raft + 两阶段提交Lamport 1998 · Ongaro 2014 |
云数据库、几乎所有多副本存储 |
同上 |
关掉多数派确认 → 分区时两个 leader 同时写,钱花两遍 |
| E |
B 树 + 基于代价的查询优化器1970s |
关系数据库产业 |
千亿级/年 |
走索引换全表扫描:磁盘页 logBN → N/页 |
| F |
收缩层次 CH / A*Hart 1968 · Geisberger 2008 |
导航、网约车、即时配送、物流调度 |
千亿级/年 |
关掉 CH 退回 Dijkstra:洲际路网上 settle 的节点数从几百跳到千万 |
| G |
注意力 / Transformer2017 |
现在这一波 |
已经很大 且在长 |
换回 RNN 的顺序依赖:每样本串行步数 O(1) → O(n) |
| H ★ |
BWT + FM-index2000 → BWA / Bowtie 2009 |
基因组测序服务业——「千元基因组」里,比对这一半是算法给的,不是化学给的 |
数百亿/年 |
后向搜索换回逐位置扫描 |
| I ★ |
稳定匹配 Gale–Shapley1962 · 2012 诺贝尔经济学奖 |
住院医师分配、校位分配、器官交换 |
不以营收计 但是真人的一生 |
延迟接受换成先到先得 → 屏幕上当场冒出想互换的配对 |
| J |
概率数据结构Bloom 1970 · HyperLogLog 2007 |
大数据与实时分析这一层 |
中等 |
精确去重 10⁹ 个 id:几 GB → 12 KB,尺子是内存 |
| K |
SHA + Merkle 树Merkle 1979 |
加密资产(争议大) |
万亿级市值 |
去掉 Merkle 证明:验一笔交易要下整条链,log n → n |
如果只加一份
三个候选,照六个字段填一遍
规矩没变:关掉的必须是一行不是一个概念、永远两把尺子、每一个都要给反例。
填不满这六格的题材就不该做——下面三个都填得满。
C
一致性哈希
Karger et al. 1997 · 2026-09-07 已建成 · switches/10-chash.html
已建成
关掉它,每加一台缓存机器,整个互联网都要回源一次。
关掉哪一行
server = hash(key) % N 换成「把节点和键都散在一个环上,键顺时针找第一个节点」。一个表达式。
键的分布、哈希函数、节点数滑块全不动。
关掉
节点数从 8 拖到 9 的那一瞬间,左边网格里 87.5% 的格子同时变红——
这些键全部换了机器,缓存全失效,全部回源。
打开
同一个动作,右边只有 11.1% 变红,而且变红的全是相邻的一段弧——
新节点只从它的后继手里接走了自己那一段。
尺子 A机器无关
加一个节点时被重映射的键的比例 ⚠ 这一栏原来写的 87.5% 是错的(把 N 当成了旧的节点数),实测见下
取模N/(N+1) = 89.0%
一致性哈希11.8%
7.5 ×
尺子 B体感
扩容那一刻的回源带宽尖峰 · 这次扩容要不要停服
取模源站被打爆 · 得挑凌晨
一致性哈希照常滚动上线
啊哈瞬间
这条闭式解不需要跑就能证,而它是一家公司的全部立论:
Akamai 1998 年成立,招股书里那个技术就是前一年这篇论文。
一个取模换成一个环,撑起了今天整个 CDN 行业。
建成之后 · 三处预期被实测打回
① 取模「87.5%」→ 89.0%(闭式解是 N/(N+1),我把 N 当成了旧台数)。
② 「1/(N+1) 是理论下界」→ 它是期望,不是下界:N=16 实测 5.7% 低于 5.9%。
站得住的是一句更强的话——搬家的键恒等于新机器接管的键,一个都不多(逐键数过,搬去别处的是 0 个)。
③ 「取模负载几乎完美」→ 它是 √(N/K),N=64 时 3.65%。
外加一处测量脏:用 N=8 量 1/√V 律,8 个样本算变异系数量出的是噪声,换 N=64 才干净。
⇒ 页面上真正的头条不是 89% 对 11.8%,是 取模搬的里面有 77.8% 是白搬——
老机器之间互搬,没有任何理由。
它什么时候不赢
节点少的时候环上分布很不均,朴素一致性哈希的负载方差比取模还差——
得靠虚拟节点补回来。让用户把「每节点虚拟点数」从 1 拖到 200,看着负载柱状图从参差不齐变平。
这个反例最好,因为它说清了这个算法真正的前提:它买的是「变化时的稳定」,代价是「静态时的均匀」。
I
拍卖与匹配
Vickrey 1961 · Gale–Shapley 1962 · 2026-09-07 已建成 · switches/11-mechanism.html
已建成
关掉它,市场不是变慢,是所有人开始撒谎。
关掉哪一行
成交价从「第二高价」改成「你自己的出价」(拍卖那半),
或者把延迟接受的「暂时持有、可被更好的申请挤掉」改成「先到先得、一旦接受不再反悔」(匹配那半)。
参与者的真实估值、到达顺序全不动。
关掉
一价拍卖里出价开始互相试探、来回震荡,没人再报真话;
先到先得的匹配里,屏幕上当场冒出一堆互相想换的配对——两边都觉得跟对方换更好,但换不了。
打开
二价下照实报价成了每个人的最优策略,出价曲线一夜之间躺平;
延迟接受跑完之后,阻塞对为 0——这是可以逐对枚举验证的,不是宣称。
尺子 A机器无关
阻塞对的个数(枚举全部配对可验) · 出价偏离真实估值的幅度
先到先得阻塞对 > 0
延迟接受阻塞对 = 0
尺子 B体感
这套规则真的在管什么
全美住院医师分配 · 校位分配 · 肾脏交换链
啊哈瞬间
前九份都是「人和机器」,这一份是人和人。08 拥塞那份已经摸到了这条边
(「AIMD 是一份社会契约,不是一条物理定律」),但那份里作弊者只是个开关,没有真的均衡。
而且大多数人对二价的直觉是错的——「报低一点更划算」会被实测当场打脸,第 6 条规矩天然满足。
建成之后 · 两处预期被实测打回
① 这一页原来写「一价出价互相试探、震荡,收入和效率一起掉」——两样都没掉:
收入 0.6664 对 0.6667(收入等价定理),效率都是 100%(同比例压价保序)。
② 「出价会震荡」是学习过程的现象,不是机制的性质。
⇒ 论点因此换掉了:二价买的不是钱,是「不用猜」——
二价的最优出价永远是 1.000 × v,一价从 0.484 × v(2 人)爬到 0.947 × v(20 人),
对手多一个就得重算一次。和 08「关掉 AIMD 吞吐一点没变,塌的是公平」是同一种形状。
它什么时候不赢
⚠ 这一条必须说,否则整份演示就是在骗人:二价的「说真话最优」只在单物品下成立。
真实的搜索广告是多个广告位的 GSP,它不是 VCG,均衡里照样要压价。
另外延迟接受只对提出申请的那一方最优,另一方拿到的是它可能得到的最差稳定解——
「谁提亲」这个看似无关的选择,决定了利益归谁。
H
BWT 与 FM-index
Burrows–Wheeler 1994 · 2026-09-07 已建成 · switches/13-fmindex.html
已建成
关掉它,「千元基因组」里便宜下来的那一半根本没发生——测序仪读得再快,也没人比对得完。
关掉哪一行
把 FM-index 的后向搜索(每次一个字符,两次 rank 查询,把候选区间往里收)
换成在 3×10⁹ 个碱基上逐位置比对。同一条 read,同一个参考基因组。
关掉
一条 100bp 的 read 要扫过整条染色体。一次 30× 全基因组有十亿条 read,
进度条上的预计完成时间以周计。
打开
同一条 read 一百来步就定位完了——步数只和 read 长度有关,
和基因组多大完全无关。而且那个索引比原基因组还小。
尺子 A机器无关
定位一条 100bp read 所需的字符比较次数 · 以及索引占多少字节
逐位置扫描正比于 3×10⁹
后向搜索约 100 步 · 索引 < 原文
尺子 B体感
一次 30× 全基因组比对的机时
关掉以周计
打开以小时计
啊哈瞬间
一个为压缩而生的变换,顺手成了一个索引。BWT 1994 年被造出来是为了让文本更好压
(bzip2 就是它),六年后有人发现那个压缩过的东西本身可以直接被搜索,不用解开。
测序成本这些年掉了六个数量级,通常全算在化学头上——有一半在这里。
它什么时候不赢
后向搜索只认精确匹配。错一个碱基,区间当场空掉。
Illumina 那种 0.1% 错误率还能靠回溯硬撑,换成纳米孔那种 5%~10% 错误率的长读长,
这个策略直接失效——所以现代长读长比对器回到了 minimizer 加动态规划的种子延伸路线。
它买的是「精确匹配下的对数时间」,这个前提一旦不成立,优势归零。
建议的动手顺序
按「先补正确性,再补传播,最后才加内容」排。第十份是可选的——
六个洞补完,这九份就已经比现在结实一档。第一条已经划掉了。
①②③④⑤ ✓ 已完成
主闸门 75 → 137 条,键→值核对覆盖 13 个页面;二十七处订正独立成页并挂上首页;
build 的空 <body> 修好并加了断言;七个页面离屏停动画。
⇒ 这九份现在比一天前结实一档。
⑥ LICENSE ✓ 已完成
MIT(代码)+ CC BY 4.0(内容),拆开的理由写在 LICENSE 头一段。
顺带把 index.html 那个「唯一带骨架标签的源文件」例外直接消掉了——
消掉例外比记下例外好。
① 的后半:puppeteer 那一关 ✓ 已完成
npm run check:browser:37 条断言、42 个键在真浏览器里闭环,外加「页面报错就红」。
依赖只装在这一支,主闸门仍然零依赖、几秒钟跑完,CI 里两个 job 分开。
⚠ 它开起来抓到的第一个东西,是我前一天写的代码(见 ① 那条)。
同一个浏览器顺手把 ③ 差的那 13 张 og:image 也截了。
- ⇒ 六个洞全补完,只剩第十份
这一页原来列的六条,加上 ① 的后半,同一天全做完了。
下面这条是唯一还开着的,而它是加东西,不是补洞。
第十份:一致性哈希 ✓ 已建成
switches/10-chash.html:65 536 个键按哈希顺序铺成一张图,
加一台机器,取模那边 89.0% 变色、环那边 11.8%。
三处预期被实测打回,全部留在页面上。
⇒ 第十一份也建成了,见下。
第十一份:拍卖与匹配 ✓ 已建成
switches/11-mechanism.html:前十份关掉的都是「机器算得动」,这一份关掉的是「人肯说真话」。
⚠ 它把自己的论点也推翻了一次——关掉二价,收入一分没少(收入等价定理)、效率一点没掉,
塌的是「你必须知道多少东西才出得了价」。
⇒ 第十二份也建成了,见下。
第十二份:分布式一致性 ✓ 已建成
switches/12-consensus.html:一行开关 —— 提交前要不要等 q 台确认。
关掉之后 39.9% 的已提交条目是分叉的(同一个位置两个值);
分歧恒为 0 当且仅当 2q > N,枚举 55 对子集验过,一对不相交的都没有 ——
一致性靠数数保证,不靠通信。
⚠ 它又抓到一处 B 类:我第一次用「集群可用率」量代价,量出 100%,把代价整个盖住。
⇒ 上半那三个类别缺口全填了;再往下就该从产业表里挑(B PageRank、D 两阶段提交、E B 树、F 最短路、H FM-index…)。
⚠ 一个反面提醒:G 和 07 撞格子
注意力 / Transformer 虽然是十个候选里最热的,但它和 07 梯度那份撞位置——
两份都会落到「AI 为什么可能」这一句上,而 07 已经占了。
真要做,切入点必须换成并行性而不是算力:关掉的是「一次能看全序列」,
换回 RNN 的顺序依赖,尺子 A 是每个样本的串行步数(O(1) 对 O(n)),
体感是「GPU 喂不满」。这和 07 那份的「梯度要算多少次前向」是两件不同的事,
说不清这个区别就不该做。