- 移除 output/volumes/v1/chapters/001-1-9.md 与 002-10-19.md(仅普通提交,git 历史保留) - 移除被取代的写作规范 prompts/chronicler.md 与旧的按年代切章模块 - README 全面改写为人物主线流程、参数位置与踩坑记录 - scripts/dfannals 启动器:自动挑一个装了 defusedxml 的解释器
185 lines
10 KiB
Markdown
185 lines
10 KiB
Markdown
# 矮人要塞人物连载 · Dwarf Fortress Annals
|
||
|
||
把《矮人要塞》(Dwarf Fortress) 世界生成的历史数据,变成**跟着人走的中文连载故事**,自动推送到这个仓库。
|
||
|
||
不是编年史。编年史的主语是年代,读起来必然像流水账(本项目第一版就是这样被否掉的)。
|
||
这里的主语是**人**:先从史料里自动挖出一组关系密切、反复交锋的角色,再按因果链切集,
|
||
以"这些人物的传记作者"的视角贴着他们写。
|
||
|
||
---
|
||
|
||
## 一、它怎么工作
|
||
|
||
```
|
||
① 线索挖掘 从 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 所有。本仓库只包含由其生成数据的**衍生文本**,
|
||
不含游戏本体、美术资源或存档。
|