矮人要塞编年史 · Dwarf Fortress Annals

把《矮人要塞》(Dwarf Fortress) 世界生成的历史数据,写成中文战报体连载故事,自动推送到这个仓库。

本仓库是成品仓:放小说章节、迷你年表、人物索引,以及生成它们的代码。 原始 legends 导出数据(数百 MB)与游戏存档留在本地,不入库。


一、它是怎么工作的

Windows 侧:Dwarf Fortress 53.16 + DFHack 53.16-r2
   │  ① 生成世界(Medium 129×129 / 250 年历史)
   │  ② 进入 legends 模式,导出 legends.xml + legends_plus.xml
   ▼  ③ 经 /mnt/c 文件交换(不需要 WSL↔Windows 网络互访)
WSL2 Ubuntu 侧管道
   │  ④ 预处理:剔除 DF 塞进名字里的非法控制字符,CP437 → UTF-8
   │  ⑤ 解析史料:40 万条事件 / 4 万位人物 → 事件流 + 年表 + 人物索引
   │  ⑥ 切章:10 年一窗,每窗按显著度取前 12 条,同类事件≤4(避免整章讣告)
   │  ⑦ 写作:要塞编年史官口吻 / 战报体 / 允许自由演绎
   │  ⑧ 校验:正文专名必须能在史料里找到,可疑处标注但不阻断
   ▼  ⑨ git commit & push
Gitea:gitadmin/dwarf-fortress-annals(本仓库)

二、目录

路径 内容 入库
output/volumes/v<N>/chapters/ 连载正文 ✅
output/volumes/v<N>/timeline.md 迷你年表 ✅
output/volumes/v<N>/figures.md 人物索引 ✅
dfannals/ 管道源码 ✅
scripts/ 安装、部署、驱动脚本 ✅
prompts/chronicler.md 写作规范(决定文风与人设) ✅
tests/ 事实校验回归测试(含合成史料样例) ✅
data/exports/ 原始 legends 数据与预处理缓存 ❌
data/state.json 进度状态(当前世界 / 卷号 / 已写章节) ❌
data/GRILL-ME.md 立项访谈记录 ❌

三、依赖

依赖 版本 / 说明
Dwarf Fortress 53.16 免费版(Bay 12)。必须与 DFHack 严格配对
DFHack 53.16-r2。Windows 版经 dfhooks.dll 注入,不需要替换启动器
Python 3.12(用到 X | None 语法)
defusedxml sudo apt install python3-defusedxml(解析不可信 XML 用)
写作模型 经 https://wb2api.hajim1.art/v1(OpenAI 兼容)调用 cn:deepseek-v4-pro
发布 现有 ~/.ssh/id_ed25519(已注册到 Gitea 账号 gitadmin)

无独显也能跑:DF 本体不吃 GPU;写故事走云端 API,不做本地推理。

四、安装

# 1) Windows 侧装游戏与 DFHack(经 WSL 的 /mnt/c 操作,免管理员权限)
scripts/install-df.sh --download      # Bay12 与 GitHub 在国内较慢,会断点续传
                                      # GitHub 直连不通时改用镜像:https://gh-proxy.com/<原地址>

# 2) 装依赖
sudo apt install -y python3-defusedxml

# 3) 把驱动脚本部署进游戏目录
scripts/deploy-dfhack.sh /mnt/c/Users/$USER_WIN/DwarfFortress

五、产出一卷(生成新世界 → 导出 → 连载)

# 1) 启动游戏,停在标题界面
"/mnt/c/Users/23518/DwarfFortress/Dwarf Fortress.exe" &

# 2) 自动生成世界(Medium / 250 年)并导出 legends 数据到 data/exports/
scripts/make-world.sh 3 3

# 3) 一条命令产出下一章并推送
python3 -m dfannals.cli run

make-world.sh 全程从外部驱动游戏,不需要人工点击:

  • 用 dfannals/screentext 把画面读成字符网格,scripts/df-screen.py 按文字定位按钮
  • 用 dfannals/click 模拟鼠标、dfannals/setparams 直接设定世界参数
  • 用 dfannals/legends 进入 legends 模式(做法取自 DFHack 官方 open-legends.lua)
  • 用 dfannals/act / devel/send-key 发按键

备选:DF 自带命令行世界生成器 "Dwarf Fortress.exe" -gen 1 RANDOM "MEDIUM ISLAND", 可静默生成并导出,但不留存档,因此无法继续进 legends 导出,只适合快速验证参数。

六、子命令

python3 -m dfannals.cli status    # 当前世界 / 卷号 / 已写章节
python3 -m dfannals.cli plan      # 只看切章方案,不调模型
python3 -m dfannals.cli index     # 解析 exports → 年表 + 人物索引
python3 -m dfannals.cli next      # 生成下一章(--dry-run 只生成不推送)
python3 -m dfannals.cli run       # index + next,一条命令走完
python3 tests/test_factcheck.py   # 校验器回归测试

实测:约 2.5 分钟 / 章(解析 33 秒 + 模型 1–2 分钟),单章 1000–1400 字。

七、配置在哪

想改什么 改哪
世界大小 / 历史长度 scripts/make-world.sh 的两个档位参数
切章粒度(几年一章、每章几条、同类上限) dfannals/slice.py 顶部常量
哪些事件值得写(显著度评分) dfannals/score.py 的 NARRATIVE 表
文风、人设、演绎边界 prompts/chronicler.md
章节字数区间 dfannals/config.py 的 CHAPTER_MIN/MAX_CHARS
模型与网关 dfannals/config.py 的 PROVIDER / MODEL
发布目标、仓库名 dfannals/config.py 的 GITEA_*

API key 不入库:脚本运行时从 ~/.pi/agent/auth.json 读取,只在内存中,不打印、不落盘、不进 git。

八、踩过的坑(失败排查参考)

症状 原因与解法
not well-formed (invalid token) DF 在名字里嵌了 \x10/\x11 等 CP437 控制字符,XML 1.0 禁止。管道开头的预处理会剔除并转 UTF-8,缓存到 data/exports/clean/
事件数莫名翻倍 预处理缓存若与原始文件同目录,会被 *.xml 扫描当成第二份史料。缓存因此放在子目录
素材里出现 HF#-1、Site#-1 DF 用负数占位表示"无此项"。查询接口对负 id 返回空串,渲染时跳过
校验器把真名报成可疑 史料里是全小写程序名(yemi deermoths),正文做了首字母大写。已知名字集同时收录两种形式(有回归测试)
dfhack-run "cmd a b" 报 not recognized 参数要分开传:dfhack-run dfannals/click 10 2,不要整体加引号
切出几千章 40 万条事件里绝大多数是流水账(change hf state 7.3 万条)。必须先按显著度筛选,再按时间窗分桶
Bay12 下载只有 ~10KB/s 官网无国内镜像。放后台断点续传即可(16MB 约 27 分钟)
GitHub release 下载 0 字节 直连被断。用 https://gh-proxy.com/ 前缀
观测 / init 脚本不生效 53.16 只执行固定几个 init 文件。改用 dfhack-run 从外部驱动,更可靠

九、写作设定

  • 叙述者:世界档案库里的编年史官——少数亲历,多数事后依据档案与口述追述,因此对陈年旧账有看法
  • 口吻:战报体,严肃的骨架 + 诙谐的血肉,允许吐槽
  • 语言:中文;专有名词保留英文原文(首字母大写以便阅读)
  • 演绎边界:允许虚构对白、心理、场景;不许发明史料里没有的人名地名,不许改写史实骨架(年份、参与者、胜负)
  • 连载:同一世界长期连载;一部史写完则用 make-world.sh 生成新世界开新卷

十、当前状态与限制

  • 已完成:3 卷框架 + 第 1、2 章上线(Mon Sagus 世界,1–250 年,26 章规划)
  • 定时任务未启用:cron / 开机自启等稳定运行一段时间后再落地;目前按需手动执行
  • 原始数据不入库:data/ 已在 .gitignore 中
  • 诚实说明:世界创建各步骤均已逐步实测通过,并已整合为 make-world.sh;但该脚本尚未作为单次完整调用重跑验证(单次约 15 分钟),首次使用时请留意各阶段日志

十一、声明

《Dwarf Fortress》版权归 Bay 12 Games 所有。本仓库只包含由其生成数据的衍生文本, 不含游戏本体、美术资源或存档。

S
Description
矮人要塞世界史编年:由 legends 数据生成的中文战报体连载
Readme
403 KiB
Languages
Python 92.3%
Lua 4%
Shell 3.7%