fix: 修复实机运行才暴露的 8 个缺陷,新增控制台添加玩家与移动测试

首次真正连上线上服务器(Debian 12 + pm2 + 端口 78)后暴露的问题,单元测试全绿但机器人干不了活:

致命
- 寻路插件从未加载:项目从没调用 bot.loadPlugin(pathfinder),bot.pathfinder 恒为
  undefined、isReady() 恒为 false,扩地任务连启动都启动不了,界面只显示含糊的
  「机器人尚未就绪」。现在 createBot 后立刻加载,失败时明确报错。

登录门禁相关(三处死锁)
- preLoginCommands 白名单:/login、/register、简幻通的 /auth 本身就是登录流程的一部分,
  原先被门禁一并拦下,形成「要先登录才能发登录指令」
- 验证码常被要求直接当聊天内容发出(不是指令),原先被 sendChat 的门禁挡住;
  现在服务器明确要求验证时自动放行,另有 server.allowChatBeforeLogin 兜底
- 配置了登录指令却没有密码时 loggedIn 永久为 false,控制台连手动指令都发不出;
  现在走与其他「服务器不回提示」一致的超时兜底并给出说明

可用性
- 离线时指令被静默丢弃却回报「已发送」;现在要求已连接并进入世界,失败信息区分
  「未连接」「登录被拒」「未通过验证」
- 「被服务器传送」日志洪水:实测 3 分钟 748 行,会冲光日志缓冲并打满 WebSocket;
  现在按 30 秒窗口聚合,断开时刷出未汇总计数
- 识别外站账号绑定流程并给出可操作告警,新增 server.authCommand 模板
- 重连指数退避(server.reconnectMaxDelayMs)
- 控制台瓦片 URL 与后端路由不一致

新增
- 控制台添加 / 移除玩家:不必改 .env 重启。密码只写入 data/bots.credentials.json
  (强制 0600),不进仓库、不出现在任何响应里;.env 与凭据文件同名时以 .env 为准,
  .env 里的账号不允许从控制台移除
- 手动移动测试接口与按钮:绕开寻路器直接按方向键,返回真实位移,
  用于区分「算不出路径」与「根本走不动」
- 位置同步诊断:位置包收发计数、onGround、游戏模式、维度、脚下方块与六向实心
- grim.reportOnGroundFromWorld:按世界数据修正出站 onGround(默认开启)
- scripts/check-protocol.js:核对包名与字段名同目标版本协议是否一致

文档
- README 新增「部署到服务器」与「机器人走不动的排查方法」两章

已知限制:实测本服上 mineflayer 客户端无法移动(按方向键 2 秒位移 0.00~0.02 格,
服务端每 tick 回弹一次),已排除区块数据、onGround、Grim 层、游戏模式、物理引擎、
寻路插件、teleport_confirm 协议字段等因素,彻底定位需与原生客户端逐字节对比移动包。
连接 / 登录 / 指令 / 聊天 / 公屏日志 / 大地图均正常。

测试从 263 增至 290,全部通过。
这个提交包含在:
lyizai
2026-09-27 23:06:39 +08:00
父节点 2671da95d5
当前提交 abecdd2648
共修改 15 个文件,包含 1243 行新增和 34 行删除
+31
查看文件
@@ -233,9 +233,37 @@ pm2 startup systemd -u root --hp /root # 开机自启
| 现象 | 原因 | 处理 |
| --- | --- | --- |
| 任务点了「开始扩地」却说「机器人尚未就绪」 | 寻路插件没加载,`bot.pathfinder` 恒为 undefined | 已在 `createBot` 后自动 `loadPlugin`;若仍出现看日志里的插件加载错误 |
| 日志被「被服务器传送」刷屏 | 账号未完成服务器验证,服务端每 tick 强推位置 | 已按 30 秒窗口聚合;看是否有「账号绑定」告警 |
| 连上 120 秒后被踢,反复循环 | 服务器要求外站账号绑定(如简幻通 `/auth`) | 人工完成一次绑定;绑定后同账号即可自动登录 |
| `mineflayer` 加载报错 / 语法不支持 | 系统 Node < 22 | 用项目私有运行时启动 |
| 能连上、能收指令,但**走不动** | 客户端移动包被服务端拒绝(回弹) | 用控制台的「移动测试」按钮确认位移;详见下方专章 |
### 6. 「机器人走不动」的排查方法
控制台右下角有**移动测试**按钮(前 / 后 / 左 / 右 / 跳),它绕开寻路器直接按控制键 2 秒并回报真实位移。
这能一眼区分两种完全不同的故障:
- **位移正常、但任务报「目标不可达」** → 是规划问题(距离太远、被地形挡住、挖/放方块被 Grim 约束禁用)
- **位移 ≈ 0** → 客户端移动被服务端拒绝,属于协议 / 反作弊交互问题,改规划参数没有意义
配套的诊断数据在 `GET /api/bots/:id/status`:
| 字段 | 含义 |
| --- | --- |
| `packetStats.inboundPosition` | 服务端下发的强制位置同步次数 |
| `packetStats.outboundPosition` | 客户端上报的位置包次数 |
| `packetStats.onGroundFixed` | 出站包中 onGround 被世界数据纠正的次数 |
| `onGround` | 客户端当前认为自己在不在空中 |
| `gameMode` / `dimension` | 游戏模式与维度(adventure / spectator 会限制移动) |
| `world.blockBelow` / `world.surroundings` | 脚下方块与六向实心情况(判断是否被困住) |
判读方式:
- 入站高、出站为 0 → 客户端根本没上报位置
- 出入都高但位移为 0 → 客户端在报,服务端不认(回弹)
- `onGround` 恒为 false 而 `world.blockBelowSolid` 为 true → 客户端上报「站在地上却在空中」,
开启 `grim.reportOnGroundFromWorld` 可修正
---
@@ -400,6 +428,9 @@ npm start
| POST | `/api/bots/:id/task/start` | 启动该机器人的扩地(body 为配置覆盖) |
| POST | `/api/bots/:id/task/stop` | 停止该机器人的扩地 |
| POST | `/api/bots/:id/reconnect` | 重连该机器人 |
| POST | `/api/bots/:id/move` | 手动移动测试(`direction` / `seconds`),返回真实位移 |
| POST | `/api/bots` | 控制台新增玩家(`username` / `password` / `label`) |
| DELETE | `/api/bots/:id` | 移除控制台新增的玩家(`.env` 里的会返回 400) |
| POST | `/api/bots/:id/command` | 向该机器人发送游戏指令(要求已登录验证) |
| POST | `/api/bots/:id/chat` | 向该机器人发送公屏消息(要求已登录验证) |
| GET | `/api/bots/:id/chat` | 该机器人的公屏日志(`limit` / `since` / `kind` / `keyword`) |