文件
BotStore/TODO.md
T
lyizai 9ca80d2f6e feat: 回城护栏 + 原点自救 + 移动诊断,并留下 TODO 交接文档
先纠正上一轮的误判:那个「寻路恒定 47 节点」是机器人卡在百米深坑里造成的,
我把它归因成配置问题并反复触发 /u return 回城,把玩家手动摆好的位置又拉走了 —— 抱歉。

回城护栏(破坏性操作不再自动执行)
- returnOnTimeout 默认关闭;即便打开,returnGraceSeconds(默认 300 秒)内也不执行,
  避免把玩家刚在客户端摆好的位置清掉
- returnCommand 可配置(默认 u return);每次回城都明确记日志说明会丢失当前位置
- 因未启用而跳过时也记一条说明,避免用户以为「怎么不回城了」

区分「被传送到原点」的两种原因(以前日志里长得一样,常被误判成机器人坏了)
- 脚下是 bedrock -> 在登录服,必须人工输入验证码,给出明确提示
- 脚下不是基岩 -> 死亡后被卡住,自动左右移动触发服务器重新传送

新增两个诊断字段(排查「走不动」的关键手段)
- moveDiag:当前按下的控制键、速度、寻路器 isMoving/goal/剩余路径点数。
  能一刀切开「寻路器没执行」与「执行了但服务端不认」
- pathingDiag:起点可走邻居数、寻路器能否读到方块、四向可见距离

_isStandingOnGround() 增加「服务端是否正把我们按在原地」的判断

排查结论(详见 TODO.md)
- 排除:GrimCompat 朝向限速(A/B 实测关闭后回弹 6.0 -> 5.5 次/秒,无变化)
- 排除:疾跑(关闭后仍 4.8 次/秒)
- 排除:世界数据错误(实测脚下 stone)
- 关键线索:寻路时 forward/sprint 确实按下、isMoving=true,但水平速度恒为 0
  (velocity.y=-0.078 恰是自由落体第一 tick 的值,说明速度每 tick 被清零),
  且 bot.entity.onGround 被服务端位置包反复置为 false,物理引擎一直按空中处理。
  下一步应从「让 entity.onGround 与出站包一致」入手。

TODO.md 面向下一位开发者:包含项目现状与部署、完整证据链、按优先级排好的假设与
验证命令、备选方案(绕开 pathfinder 自己写转向控制器)、以及其它待办。

测试 360 -> 363,全部通过。
2026-09-28 20:21:41 +08:00

252 行
13 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# TODO:留给后续开发者的待办与已知问题
> 本文档记录**尚未解决的大工程**、已经排查过的线索、以及推荐的验证方法。
> 小事已在代码里做完并提交,这里只写需要集中攻关的部分。
>
> 最后更新:2026-09-29
---
## 0. 先读这里:项目现状与部署
| 项 | 值 |
| --- | --- |
| 技术栈 | Node.js ≥ 22 + mineflayer 4.39.0 + mineflayer-pathfinder 2.4.5 |
| 测试 | `npm test`(363 个用例全绿) |
| 语法与前端 id 自检 | `npm run check` |
| 服务器 | `root@103.236.99.177:33851`,部署目录 `/opt/autostore-bot` |
| 控制台 | http://103.236.99.177:78/ ,密码在服务器 `.env` 的 `WEB_PASSWORD` |
| 进程管理 | pm2 应用 `autostore-bot`(端口 78);旧版 Fabric 机器人为 `minecraft-bot`(端口 91,已停) |
| 更新方式 | 打包上传 → `bash /opt/autostore-bot/update.sh`(保留 `.env` / `data/` / `node_modules/` / `runtime/`) |
| 私有 Node | `/opt/autostore-bot/runtime/current/bin/node`(v24.16.0) |
**旧版 Fabric 模组源码**(重要参考,实机可用的实现):
`C:\Users\luoyi\AppData\Local\Temp\autostore-deploy\oldbot.js`(1356 行)。
它在这台服务器上是**真的能走路、能寻路、能扩地**的,所以遇到 mineflayer 行为诡异时,
第一件事应该是拿它的实现逐项对照,而不是自己猜。
---
## 1. 🔴 最大问题:寻路器驱动的移动会被服务端持续回弹
### 现象
- **手动按键移动完全正常**:通过控制台「移动测试」按 forward 2 秒,实测位移 4~8 格,位置保持。
- **寻路器驱动的移动被回弹**:一旦 `bot.pathfinder.setGoal()` 开始执行,
服务端以 **5~9 次/秒** 的频率强制同步位置(`forcedMove`),机器人几乎原地不动。
- 偶尔能挪动一点,但速度只有 **0.3 格/秒**(正常步行 4.3 格/秒),
所以几百格外的目标在超时前根本走不到。
### 已确认正常的部分(不要再怀疑这些)
| 项 | 状态 | 证据 |
| --- | --- | --- |
| `player_input` 补包 | ✅ 每 physicsTick 都发(~20/s),bitflags 正确 | `packetStats.playerInput` 持续增长 |
| `tick_end` 补包 | ✅ 每帧发送 | `grim.protocolPackets.tickEndSent` |
| `onGround` 标志 | ✅ 出站包已按世界数据复核 | 实测空闲时 `onGround=true`,世界脚下确实是 stone |
| 世界数据可读性 | ✅ `world.getBlock` 正常,四向可见 115~125 格 | `pathingDiag.visibleDistance` |
| 寻路器本身 | ✅ 在开阔地能找到路径 | 实测「已探索 3633 节点 → success,代价 113.8」 |
| 服务端回弹是否持续 | ❌ **只在机器人尝试移动时出现**;空闲时入站位置包为 0 | 12 秒采样:空闲 +0,寻路中 +105 |
| 关闭疾跑 | 试过,**无改善** | `allowSprint=false` 后仍是 4.8 次/秒回弹 |
| **关闭合规层(grim.enabled=false)** | 试过,**无改善** | A/B 实测:开启 6.0 次/秒 vs 关闭 5.5 次/秒,机器人位移都是 0 格 |
| 世界数据是否错误 | ❌ **不是原因** | 实测脚下 `stone`,`blockBelow` 正确 |
### 🎯 最关键的一条线索:水平速度恒为 0
用 `/api/status` 的 `moveDiag` 字段采集到的实测数据:
**空闲时**
```json
{ "controls": { "forward": false, "sprint": false, ... }, "anyPressed": false,
"velocity": { "x": 0, "y": -0.078, "z": 0 }, "speed": 0.078, "onGround": true,
"pathfinder": { "isMoving": false, "goal": null } }
```
**寻路中**
```json
{ "controls": { "forward": true, "sprint": true, ... }, "anyPressed": true,
"velocity": { "x": 0, "y": -0.078, "z": 0 }, "speed": 0.078, "onGround": false,
"pathfinder": { "isMoving": true, "goal": {...} } }
```
**解读**(这是下一步攻关的起点):
1. **寻路器确实在执行** —— `forward`/`sprint` 都按下去了,`isMoving=true`。
所以不是「寻路器没跑」,问题在更底层。
2. **水平速度恒为 0**,而 `velocity.y = -0.078` 恰好是**自由落体第一 tick** 的重力值。
这说明速度**每 tick 都被清零**,机器人永远积累不起速度。
3. **`onGround` 被服务端的 position 包反复置为 false**:
mineflayer 的 `physics.js` 在处理服务端 `position` 包时会把 `entity.onGround` 置为 false,
而本服下发位置包的频率(5~6 次/秒)远高于物理引擎把它改回来的速度。
结果是:**机器人站在石头上,但物理引擎一直认为自己在空中**,
于是水平加速走的是「空中控制」分支,而位置包又把速度清零,两者叠加就是「按着前进键却纹丝不动」。
4. 已尝试的缓解:`_isStandingOnGround()` 里加了「服务端是否正把我们按在原地」的判断
(`_serverPinsUs()`),出站包的 `onGround` 已经能正确上报。
**但 `bot.entity.onGround`(物理引擎内部值)仍然是 false**,所以物理计算依旧按空中处理 ——
这可能就是还差的那一步。
### 排查建议(按优先级,已按实测结果更新)
#### 假设 1:`GrimCompat` 对 `bot.look` 的拦截 ❌ **已排除**
A/B 实测 grim 开/关对回弹频率没有影响(6.0 vs 5.5 次/秒),机器人位移都是 0。不要再往这个方向查。
#### 假设 2:`bot.entity.onGround` 被位置包置 false(**最可疑,优先测**)
这是目前唯一能解释「按键有、速度恒 0」的机制。方向:
- **方案 A**:在 `physicsTick` 里,当出站包已判定「脚下有方块」时,
把 `bot.entity.onGround` 也强制同步为 true,让物理引擎走地面分支。
(注意:物理引擎每 tick 会自己重算这个值,所以需要在它之后、发送位置包之前介入,
或者直接监听并覆盖。需要读 mineflayer `lib/plugins/physics.js` 确认执行顺序。)
- **方案 B**:降低服务端下发位置包的频率对我们的影响 ——
例如在收到 `position` 包时,如果位置与我们自己的预测几乎一致(只差浮点误差),
**不采纳它的 onGround=false**,只同步坐标。
这比方案 A 更贴近「我们是对的、服务端只是话多」的实际情况。
- **验证**:改完后看 `moveDiag.speed` 是否变成 0.1 以上、`moveDiag.onGround` 是否稳定为 true,
以及 `被服务器传送另有` 的计数是否下降。
#### 假设 3:跳跃 / 跑酷的物理预测不匹配
Grim 的 Prediction 检查对**跳跃**最严格(起跳时机、跳跃高度、疾跑跳加成)。
mineflayer 的 `physics.js` 在这些细节上与原版有已知偏差。
**验证方法**:把路径限制成纯平地,禁用一切跳跃:
```json
{"allowSprint": false, "pathing": {"allowParkour": false, "allow1by1towers": false, "maxDropDown": 1}}
```
若回弹消失 → 需要在 `GrimCompat` 里拦下 `jump` 控制状态,改用更保守的移动策略。
#### 假设 4:发包顺序 / 包类型与原版不一致
旧模组的调试指令 `!tp` 额外发的是 **`position_look`**(同时带位置与朝向)。
需要实测确认:mineflayer 在朝向变化时到底发的是 `position` 还是 `position_look`,
以及服务端是否因为「朝向变了却只发 position」而判定异常。
**验证方法**:在 `GrimCompat` 里统计出站包类型分布,
与旧模组移动时的包序列逐项对照(旧模组源码见上文路径)。
#### 假设 5:直接抓包对照
用 `scripts/check-protocol.js` 的思路扩展一个抓包脚本,
分别在「旧模组移动」和「mineflayer 寻路移动」时记录 5 秒的完整出站包序列,
diff 出差异。这是最彻底也最费时的办法,但能一次性定位问题。
### 💡 备选方案:绕开 mineflayer-pathfinder
既然**手动控制状态是有效的**,可以考虑不依赖 pathfinder 的执行层,自己写一个简单的转向控制器:
1. 用 `bot.pathfinder` **只算路径**(`bot.pathfinder.getPathTo()` 拿到路径点数组,不执行);
2. 自己实现执行:朝下一个路径点 `bot.look` → 按住 `forward` → 到位后切下一个点;
3. 遇到 1 格高的坎就按 `jump`,遇到障碍就重新算路径。
这样能完全控制发包与朝向的时序,也便于在 `GrimCompat` 里做逐 tick 审计。
工作量中等,但比继续调 pathfinder 的参数更可能出结果。
---
## 2. 🟡 「被传送到原点」的两种原因(已在代码里区分,但可继续完善)
服务器把玩家丢到世界原点附近有两种完全不同的原因,**以前在日志里长得一样**,
经常被误判成「机器人坏了」:
| 原因 | 特征 | 处理方式 | 现状 |
| --- | --- | --- | --- |
| 死亡后被卡住 | 脚下不是基岩,坐标靠近原点但地形正常 | **左右移动即可触发服务器重新传送**,可自动自救 | ✅ 已实现 `BotManager._escapeFromOrigin()` |
| 被切到登录服 | 脚下是 **bedrock**、四周虚空、坐标极小(如 `(-2,-2,-4)`) | **必须重新输入验证码**,只能人工完成 | ✅ 已识别并提示,控制台有醒目横幅 |
**待完善**:自救的成功率没有统计。可以记录「自救后位置是否恢复」,
失败时再升级为提示用户;也可以尝试更多方向组合(前/后/跳)。
---
## 3. 🟡 自动回城(`/u return`)已加护栏,但仍可改进
回城是**破坏性**操作:它把机器人拉回安全点,会**清掉玩家在游戏客户端里手动摆好的位置**。
项目里曾因此反复把机器人从玩家摆好的位置拉走,排查了很久。
现状(已修):
- `returnOnTimeout` **默认关闭**;
- 即便打开,`returnGraceSeconds`(默认 300 秒)内不执行回城;
- 回城指令可配置(`returnCommand`,默认 `u return`);
- 每次回城都明确记日志,说明「会丢失当前位置」。
**待改进**:
- 回城前先判断「原地是否真的更差」——例如用 `pathingDiag.neighborCount` 判断
当前位置是否还有可走方向;如果邻居数正常,回城毫无意义。
- 记录回城前后的坐标,在控制台上展示「回城把机器人从 A 挪到了 B」,
让用户能判断这次回城是否值得。
---
## 4. 🟢 其它已知但优先级较低的问题
### 4.1 大地图
- 目前只有层级 0 会批量预爬取,层级 1~3 靠用户缩放时按需拉取,首次浏览较慢。
- 可以考虑后台低优先级预热:机器人移动到新区域时,顺带爬取周边高层级瓦片。
- 领土多边形在缩放较大时仍是全量绘制,超大地图(全世界视角 + 全部邦国)可能掉帧,
可以按视野做裁剪。
### 4.2 控制台
- 移动端布局已适配,但**日志区在手机上滚动性能一般**(每行都是 DOM 节点)。
数据量大时建议改虚拟列表,或限制保留行数。
- 配置面板有 8 个标签页、字段很多,可以加搜索框。
### 4.3 多机器人
- 领土模型是全局共享的,多机器人同时扩地时对同一批候选区块的协调依赖
`_isClaimedByOthers`,但没有真正的分布式锁;高并发时可能撞车。
- 建议给「目标区块」加一个带租约的占用表。
### 4.4 定时消息
- 只支持固定间隔,没有「cron 式」或「每天某时刻」的调度。
- 没有发送结果确认:`/u restore confirm` 发出去后是否成功,目前只靠人看日志。
### 4.5 登录验证
- 验证码只能人工获取与输入(服务器设计如此)。
可以在「等待验证码」时通过通知渠道(如飞书机器人)提醒用户,避免错过 120 秒时限。
---
## 5. 常用排查命令
```bash
# 机器人完整状态(含寻路诊断)
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0]'
# 寻路诊断:邻居数、世界可读性、四向可见距离
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0].pathingDiag'
# 实时日志
tail -f /opt/autostore-bot/data/pm2-out.log
tail -f /opt/autostore-bot/data/pm2-error.log
# 公屏完整日志(含服务器消息,JSONL)
tail -f /opt/autostore-bot/data/chat/<玩家名>.jsonl
# 寻路状态(判断「探索了多少节点」是定位卡死的关键指标)
grep '寻路状态' /opt/autostore-bot/data/pm2-out.log | tail -20
# 被服务器回弹的频率(>100/30秒 说明移动被反作弊拒绝)
grep '被服务器传送另有' /opt/autostore-bot/data/pm2-error.log | tail -5
```
### 判断「机器人是不是卡在登录服」
```bash
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status \
| jq '.data.bots[0] | {position, chunk, below: .world.blockBelow, needsVerification}'
```
- `world.blockBelow == "bedrock"` 且坐标很小 → **在登录服**,需要人工发验证码
- `needsVerification == true` → 服务器正在等待验证码,控制台会显示醒目提示
---
## 6. 代码约定(改动前请遵守)
- 提交信息用约定式提交(`feat:` / `fix:` / `docs:` / `refactor:` / `test:` / `chore:`),主题行说清做了什么。
- **所有密钥只走 `.env`**(已在 `.gitignore` 里),代码里绝不硬编码;
新增配置项要同时更新 `.env.example` 的占位符。
- 注释与文档用中文,解释「为什么」而不是「做了什么」。
- 每个 `fix` 都应补一个能复现原问题的测试;本项目测试在 `test/`,用 `node:test`。
- 改前端记得跑 `npm run check`,它会校验 `app.js` 引用的元素 id 在 `index.html` 里都存在。