Files
dwarf-fortress-annals/README.md
T
Chen Yi 8630dcad55 文笔层:AI 味机检、标点规范化、分块大量扩写(第 1 集成稿 10805 字)
- 修复上次提交把 dfannals/cli.py 写成 0 字节的问题(它是唯一入口,导致管道不可运行)
- 性别:解析 <caste>,人物表与写作素材带性别(Ral Fastenhatchets 实为女性)
- 新增 dfannals/deslop.py:AI 味机械诊断(硬伤词/句式/标点,按千字密度报告)
- 新增 dfannals/normalize.py:标点与结构清理(引号配对、重复段落与句子、模型自加的小节标记)
- 新增 dfannals/expand.py 与 prompts/literary-expander.md:按年份场景分块大量扩写
- 专名防幻觉:每块附史料专名白名单,事后按段自动修复可疑专名
- episode 命令并进文笔层(骨架稿另存 .skeleton.md),新增 expand 命令做 A/B 对照
- 新增 notes/switched-threads.md 与 test_deslop / test_normalize 回归测试
- 提交前拦截「已跟踪文件为空」,防止上述事故复发
2026-10-05 22:27:02 +08:00

185 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 矮人要塞人物连载 · Dwarf Fortress Annals
把《矮人要塞》(Dwarf Fortress) 世界生成的历史数据,变成**跟着人走的中文连载故事**,自动推送到这个仓库。
不是编年史。编年史的主语是年代,读起来必然像流水账(本项目第一版就是这样被否掉的)。
这里的主语是**人**:先从史料里自动挖出一组关系密切、反复交锋的角色,再按因果链切集,
以"这些人物的传记作者"的视角贴着他们写。
---
## 一、它怎么工作
```text
① 线索挖掘 从 40 万条事件里建人物关系图,找出关系密切、有戏的 3–6 人小圈子
(只用有叙事含义的互动:对战/结缘/反目/绑架/构陷/迫害……)
② 切集 沿这条线按因果链切集:相邻事件间隔 >2 年断开,短链合并,过长拆上下集
③ 人物表 表格 + 每人一两句小传;"身份"由事件字段反推,不是猜的
④ 写作 主角的传记作者视角:他们的目标、算计、得失做主语;
世界大事只在影响到主角时提及
⑤ 自评闸门 5 个维度各 0–5 分,总分 <12 判"流水账" → 先重写一次 →
仍不合格就换下一条线索,并把放弃原因写进 notes/
⑥ 发布 git commit & push 到本仓库(原始史料与存档不入库)
```
## 二、这一卷的主角
| | |
|---|---|
| 世界 | Mon Sagus(The Plane of Dawn),Medium 129×129 / 250 年历史 |
| 主角组 | Guspu Frillyknots(鹰人掮客)、Ral Fastenhatchets(矮人)、Alath Blottedmine(矮人) |
| 线索 | 46 年间一个掮客反复收买、构陷一位管货栈的矮人,屡屡失手 |
| 选线依据 | 挖掘器排序第一名:有效互动 11 次、转折 11 次、阴谋×8 + 构陷×3 |
详见 [`output/volumes/v1/cast.md`](output/volumes/v1/cast.md)。
## 三、目录
| 路径 | 内容 | 入库 |
|---|---|---|
| `output/volumes/v<N>/chapters/` | 连载正文 | ✅ |
| `output/volumes/v<N>/cast.md` | 人物表(表格 + 小传) | ✅ |
| `output/volumes/v<N>/timeline.md` | 迷你年表 | ✅ |
| `output/volumes/v<N>/figures.md` | 人物索引(按史料出场次数排序) | ✅ |
| `notes/switched-threads.md` | 被放弃的线索及原因 | ✅ |
| `dfannals/` | 管道源码 | ✅ |
| `prompts/biographer.md` | 写作规范(决定视角、边界、篇幅) | ✅ |
| `scripts/` | 安装、部署、驱动、启动器 | ✅ |
| `tests/` | 回归测试(解析、切集、闸门、事实校验) | ✅ |
| `data/exports/` | 原始 legends 数据与预处理缓存 | ❌ |
| `data/state.json`、`data/threads.json`、`data/selection.json` | 进度、候选、选线依据 | ❌ |
## 四、依赖
| 依赖 | 版本 / 说明 |
|---|---|
| Dwarf Fortress | **53.16** 免费版(Bay 12),必须与 DFHack 严格配对 |
| DFHack | **53.16-r2**,Windows 版经 `dfhooks.dll` 注入,不必替换启动器 |
| Python | 3.12 |
| defusedxml | `sudo apt install -y python3-defusedxml` |
| 写作/自评模型 | 经 `https://wb2api.hajim1.art/v1`(OpenAI 兼容)调用 `cn:deepseek-v4-pro` |
| 发布 | 现有 `~/.ssh/id_ed25519`(已注册到 Gitea 账号 gitadmin) |
**启动器**:一律用 `scripts/dfannals ...`,不要直接 `python3 -m dfannals.cli`。
会话 PATH 上的 `python3` 可能是编辑器工具链自建的 venv(没有 defusedxml,
且关闭了 user site),启动器会自动挑一个能 `import defusedxml` 的解释器。
## 五、安装
```bash
scripts/install-df.sh --download # 装 DF + DFHack(Bay12 慢,会断点续传)
sudo apt install -y python3-defusedxml
scripts/deploy-dfhack.sh /mnt/c/Users/$USER_WIN/DwarfFortress # 部署观测/驱动脚本
```
## 六、产出一卷
```bash
# 1) 生成新世界并导出 legends(详见第九节)
scripts/make-world.sh 3 3 # 3 = Medium 大小,3 = 250 年
# 2) 建索引:年表 + 人物索引
scripts/dfannals index
# 3) 看候选线索(含信号明细),挑一条
scripts/dfannals threads --top 8 --samples 2
# 4) 看这条线切出的剧集与预算
scripts/dfannals episodes --thread 1
# 5) 生成人物表
scripts/dfannals cast --thread 1
# 6) 逐集产出(自动过闸门;不合格自动重写,再不合格自动换线索)
scripts/dfannals episode --index 1
scripts/dfannals episode --index 2
scripts/dfannals episode --index 3
```
实测:**单集约 2–4 分钟**(写作 1 次 + 自评 1 次,若判平淡会多一次重写),篇幅 800–1500 字。
## 七、配置在哪
| 想改什么 | 改哪 |
|---|---|
| 哪些互动算"关系"、权重、转折标记 | `dfannals/threads.py` 的 `SIGNALS` |
| 主线口径(≥5 次互动、3–6 人、跨度 ≥30 年) | `dfannals/threads.py` 顶部常量 |
| 切集参数(间隔 >2 年断开、每集预算) | `dfannals/episodes.py` 顶部常量 |
| 自评维度、阈值 12、重写/换线上限 | `dfannals/gate.py` 的 `DIMENSIONS` / `THRESHOLD` / `MAX_REWRITES` / `MAX_CANDIDATES` |
| 视角、文风、演绎边界、篇幅 | `prompts/biographer.md` |
| 同人同类事件的重复上限 | `dfannals/volume.py` 的 `PER_KEY_CAP` |
| 模型与网关 | `dfannals/config.py` 的 `PROVIDER` / `MODEL` |
| 发布目标、仓库名 | `dfannals/config.py` 的 `GITEA_*` |
**API key 不入库**:运行时从 `~/.pi/agent/auth.json` 读取,只在内存中,不打印、不落盘、不进 git。
## 八、子命令
```bash
scripts/dfannals status 当前世界 / 卷号 / 已完成集数
scripts/dfannals index 解析 exports → 年表 + 人物索引
scripts/dfannals threads 候选线索与信号明细
scripts/dfannals episodes --thread N 某条线索的剧集规划与预算
scripts/dfannals cast --thread N 人物表(--no-bios 只出表格)
scripts/dfannals episode --index N 产出第 N 集(--dry-run 不落盘)
```
测试:`/usr/bin/python3 tests/test_factcheck.py`、`test_episodes.py`、`test_gate.py`。
## 九、世界是怎么造出来的
`scripts/make-world.sh` 全程从外部驱动游戏,不需要人工点击:
- `dfannals/screentext` 把画面读成字符网格,`scripts/df-screen.py` 按文字定位按钮
- `dfannals/click` 模拟鼠标,`dfannals/setparams` 直接改内存里的世界生成参数
- `dfannals/legends` 进入 legends 模式(做法取自 DFHack 官方 `open-legends.lua`)
- 导出 `Export XML`(原生 legends.xml)与 `exportlegends`(扩展数据)
> 备选:`"Dwarf Fortress.exe" -gen 1 RANDOM "MEDIUM ISLAND"` 可静默生成并导出,
> 但**不留存档**,无法继续进 legends,只适合快速验证参数。
## 十、踩过的坑(失败排查参考)
| 症状 | 原因与解法 |
|---|---|
| `not well-formed (invalid token)` | DF 在名字里嵌了 `\x10`/`\x11` 等 CP437 控制字符,XML 1.0 禁止。预处理会剔除并转 UTF-8,缓存到 `data/exports/clean/` |
| 事件数莫名翻倍 | 预处理缓存若与原始文件同目录,会被 `*.xml` 扫描当成第二份史料。缓存因此放在子目录 |
| 素材里出现 `HF#-1`、`Site#-1` | DF 用负数占位表示"无此项"。查询接口对负 id 返回空串,渲染时跳过 |
| 人物关系在素材里丢失 | 真实史料用 `hfid_target`/`group_1_hfid`/`snatcher_hfid`/`seeker_hfid`/`hfid1`… 共 40+ 个字段表达关系;只认 `hfid` 会把"谁对谁做了什么"丢掉。现按"字段名含 hfid 即为人物"分类 |
| 校验器把真名报成可疑 | 史料里是全小写程序名(`yemi deermoths`),正文做了首字母大写。已知名字集同时收录两种形式(有回归测试) |
| 候选线索全是"同一对人反复对战" | 裸互动次数会被机械重复刷满(第一名 49 次里有 35 次是"反复请求拜师被拒")。现剔除重复性请求,并对同人同类事件封顶、要求 ≥2 种信号 |
| 主角组里混进泰坦巨兽 | 用"文明种族白名单(五族 + `*_MAN`)",实测覆盖 96.5% 的历史人物 |
| 一集被均分成两个 770 字的碎片 | 均分只看条数、不看预算。改为先取最小段数,再确保每段不低于下限 |
| 自评/小传返回空正文 | 推理模型的思考与正文共用 max_tokens,喂长材料时会烧光预算。结构化输出统一用 `MAX_TOKENS_STRUCTURED` |
| `ModuleNotFoundError: defusedxml` | PATH 上的 `python3` 被编辑器工具链的 venv 抢走了。用 `scripts/dfannals` 启动器 |
| `dfhack-run "cmd a b"` 报 not recognized | 参数要分开传:`dfhack-run dfannals/click 10 2`,不要整体加引号 |
| Bay12 下载只有 ~10KB/s;GitHub 直连 0 字节 | 官网无国内镜像,放后台断点续传;GitHub 用 `https://gh-proxy.com/` 前缀 |
| 自定义 init 脚本不生效 | 53.16 只执行固定几个 init 文件。改用 `dfhack-run` 从外部驱动更可靠 |
## 十一、闸门到底能不能判出流水账
`scripts/gate-selfcheck.py` 是这个闸门的校准证据(跑两次真实自评):
| 样本 | 总分 | 判定 |
|---|---|---|
| 故意构造的平淡稿(纯事件罗列) | 0/25 | 判平淡 ✓ |
| 场景级有戏剧本 | 16/25 | 通过 ✓ |
阈值 12 在两者之间,区分有效。注意:阈值是校准出来的,不是拍出来的——
第一版用"梗概体"当好稿样本时只得 11 分,换了场景级样本才验证出尺度没偏严。
## 十二、当前状态与限制
- **已完成**:第 1 卷 3 集上线 + 人物表 + 年表;旧的两章流水账已用普通提交删除(历史保留)
- **定时任务未启用**:cron / 开机自启等稳定运行一段时间后再落地,目前按需手动执行
- **原始数据不入库**:`data/` 已在 `.gitignore` 中
- **已知局限**:
- 关系图的边来自成对事件,独狼型人物(独自创作、被自然杀死)进不了线索挖掘
- 多参与者事件(比赛、集体迫害)会把同场者两两连边,圈子的"关系"偏共处而非互动
- 事实校验只拦得住凭空专名;动机、心理、对白全是允许的演绎,没有自动防线
## 十三、声明
《Dwarf Fortress》版权归 Bay 12 Games 所有。本仓库只包含由其生成数据的**衍生文本**,
不含游戏本体、美术资源或存档。