# 矮人要塞编年史 · 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/chapters/` | 连载正文 | ✅ | | `output/volumes/v/timeline.md` | 迷你年表 | ✅ | | `output/volumes/v/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,不做本地推理。 ## 四、安装 ```bash # 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 ``` ## 五、产出一卷(生成新世界 → 导出 → 连载) ```bash # 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 导出,只适合快速验证参数。 ## 六、子命令 ```bash 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 所有。本仓库只包含由其生成数据的**衍生文本**, 不含游戏本体、美术资源或存档。