<?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/tags/%E5%8F%AF%E6%A0%B8%E6%9F%A5%E5%A3%B0%E6%98%8E/</link><description>Recent content in 可核查声明 on CloudLake</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Wed, 14 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://cloudlake.eu.org/tags/%E5%8F%AF%E6%A0%B8%E6%9F%A5%E5%A3%B0%E6%98%8E/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>