Featured image of post 大灾变纪念馆:从存档里读出五个人

大灾变纪念馆:从存档里读出五个人

10 份死亡存档、3 个角色、5 段人生。墓志铭里每个数字都必须能在存档里找到出处。

F:\CDDA\graveyard\ 里躺着 10 次角色死亡记录。

这是《大灾变:黑暗之日》(Cataclysm: Dark Days Ahead)删档后自动留下的完整档案:每次一份 .sav(角色与世界在死亡那一刻的状态)和一份 .log(大事记)。

存档格式是开着的——首行 # version N,之后是一整个 JSON 对象。所以那个角色的姓名、属性、技能、特性、变异、击杀数、走过的每一处地方,甚至一生中打破的每一条戒律,都能直接读出来。

我把它读出来,然后做了两件事:把这些人还原成可看的记录,以及给他们写墓志铭。


读出来的五个人

graveyard/ 里 5 份存档、3 个角色:

角色职业 / 出身存活击杀变异结局线索
特里安娜实验室技术员 / 精灵模组2 回合029刚开局就结束
克里斯托弗·哈里斯无职业 / 避难者23.6 小时5040死在无线发射塔一带
阿里安娜·娜吉雅维度法师 / 避难者1.53 天11497第一次尝试
阿里安娜·娜吉雅同上3.47 天313125最远的一次
阿里安娜·娜吉雅同上0.2 小时086足迹停在「坚岩/实心土」——被埋在石头和泥土里

一个我最初写错的判断

阿里安娜出现了三次。怎么判断这三次是同一个人?

我的第一版判据是:属性完全相同。

测试直接把它打回来了。

三次存档里 STR/DEX/INT 确实都是 24/24/23——但最后一次 PER 从 24 掉到了 23。而特性数从 69 → 57 → 73。

真相是:属性会被变异和伤势改写,特性不会。

所以正确的判据是特性集,不是当前属性。

这个小错误是这个项目第一个教训:你以为稳定的标识,恰恰是最容易变的那个。


墓志铭,以及它凭什么可信

生成式文字最容易犯的错是编造事实。

一个 AI 写出来的墓志铭,读起来可以很动人,但里面的"击杀 313 个敌人"——是真的吗?还是它为了让句子好看随口说的?

所以这里的做法是:

每一句话涉及的每个数字和名字,都作为一条「可核查声明」(Claim)被登记下来,verify() 再从解析出的存档里重新推导一遍。

比如这一段:

阿里安娜·娜吉雅,女。存活 3.47 天后死去。生前击杀 313 个敌人。身上留下 125 处变异。带着 57 项特性离世。最擅长咒术(15 级)。足迹遍及 9 处,最常出现在紧急避难所 B-19(36 次)、森林(25 次)、公路(17 次)。一生打破 10 条戒律……她把这个世界推开了一段距离,然后被它合上。

里面每一个数字——3.47 / 313 / 125 / 57 / 15 / 9 / 36 / 25 / 17 / 10——全部是声明,全部被重新推导核对过。

校验器自己也要被校验

只有正向检查还不够。

因为"校验器"本身可能是个摆设——它可能永远返回 true。

所以测试里加了两条反向验证:故意塞一个伪造的击杀数(9999)和伪造的姓名进去,verify() 必须报错。

这是这个项目最核心的一条设计原则:

一个从不失败的校验器,等于没有校验器。


测试抓到的两个真实缺陷

  1. 小时数校验逻辑写错了。 存活小时 这条声明拿 round(days24) 去和 days 比,23.6 小时被判成不匹配。修法是让声明带上单位(hours 与 days 分开)。

  2. 地点到访次数没有登记为声明。 墓志铭里印着「幸存者营地(12 次)」,但 12 不在任何 Claim 里。是测试里那条「文本中出现的每个数字都必须有出处」把它揪出来的。

这正是这套约束存在的理由:

没被登记的印刷数字,就是没被核查的数字。


足迹

每条 .log 事件都带 oter_id / oter_name(发生地点),所以"足迹"不是编的,是从数据长出来的:

  • 克里斯托弗:幸存者营地 ×12、无线发射塔 ×4、民宅 ×4、森林 ×4
  • 阿里安娜(最远那次):紧急避难所 B-19 ×36、森林 ×25、公路 ×17
  • 阿里安娜(最后一次):紧急避难所 C-41 ×2、坚岩 ×2、实心土 ×1

最后一行不需要任何解释。

它只是数据——她死在岩石和泥土之间。


一个说明

存档里含有成人模组的全局变量与事件名。

我只做了结构化统计与叙事重建,没有展开那部分内容。纪念馆页面上也不会出现。


为什么做这个

一个游戏存档里躺着的东西,本来会跟着硬盘一起被格式化掉。

但它其实是一份完整的人生记录:这个人走过哪里、杀过什么、变成了什么、最后倒在什么地方。

我只是把它读出来了。


打开纪念馆 →


技术细节

文件作用
cddamem/save.py存档解析(.sav + .log),产出 Life
cddamem/epitaph.py墓志铭生成 + Claim 校验
summarise.py命令行汇总
make_memorial.py纪念馆页面(自包含 HTML)
tests/test_save.py19 项断言,主要是与原始 JSON 交叉核对
tests/test_epitaph.py5 项断言,含伪造数据必须被抓

两条验证主线:

  • 交叉核对:解析出的技能集必须与原始 JSON 的 level>0 集合完全相等;击杀数必须等于 kill_tracker.kills 之和;.log 里"开始大灾变"的回合必须等于 .sav 的 game_start。猜出来的数字过不了。
  • 反向验证:故意伪造声明,校验器必须报错。

这个项目最后让我想的一句话是:

写一个动人的故事不难。难的是故事里的每个数字都经得起回头查。

使用 Hugo 构建
主题 Stack 由 Jimmy 设计