文件
lyizai 6e420e13f0 docs: 记录 yaw 修复结果与剩下的两个拦路虎
⑤ 已定位并修复(1.11.0):卡住不是地形问题,是朝向 —— bot.entity.yaw 无界增长,
抓包证据 yaw° 在 -1373 与 19254 之间跳(相邻两包折合差 114°),
坐标在 0.9 格内橡皮筋;修复后朝向变平滑(0,-9,-17,-26,-34…),速度 0.35 → 0.56 格/秒。

⑥ 剩下的拦路虎:服务端把机器人钉在固定坐标上。手动移动测试报告位移 2.40~4.81 格
(客户端确实走了),但一查状态位置又回到同一点;被服务器传送另有 16~194 次/30 秒。
客户端走、服务端拉回,净位移为 0 —— 这一层已不在代码控制范围内。

⑦ 服务端提示过「邦国已激活科技: 疆土开拓者 I 剩余时间: 0时13分3秒」——
开拓可能要求该邦国科技处于激活状态且有时间限制,尚未验证。
2026-10-01 18:16:07 +08:00

32 KiB

TODO:留给后续开发者的待办与已知问题

本文档记录尚未解决的大工程、已经排查过的线索、以及推荐的验证方法。 小事已在代码里做完并提交,这里只写需要集中攻关的部分。

最后更新:2026-09-29


0. 先读这里:项目现状与部署

项 值
技术栈 Node.js ≥ 22 + mineflayer 4.39.0 + mineflayer-pathfinder 2.4.5
测试 npm test(436 个用例全绿)
语法与前端 id 自检 npm run check
本地启动 npm start(控制台 http://127.0.0.1:3080/)
服务器 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,已停)
私有 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.7.0)已把本文档里的待办全部落地为代码 —— 详见 CHANGELOG 的 [1.7.0]。 下面每节都标注了「已实现 / 待实测」,未实测的部分需要部署到服务器后按验证方法确认。


0.1 🚀 部署方法(完整步骤)

方式一:一键部署(推荐)

# 推荐:把 SSH_HOST / SSH_PORT / SSH_USER / SSH_PASS 写进 .env(已被 .gitignore 忽略),
# 然后直接跑 —— 命令行传密码会留在 shell 历史与进程列表里
npm run deploy
# 也可以临时用环境变量覆盖(.env 里的值仍作为兜底)
# Windows PowerShell
$env:SSH_HOST='103.236.99.177'; $env:SSH_PORT='33851'
$env:SSH_USER='root';          $env:SSH_PASS='<服务器密码>'
npm run deploy

# Linux / macOS
SSH_HOST=103.236.99.177 SSH_PORT=33851 SSH_USER=root SSH_PASS='<密码>' npm run deploy

scripts/deploy.js 会依次做:打包 → 上传 → 远程执行 update.sh → 轮询确认控制台返回 200。任一步失败都会明确报错。

可选环境变量:DEPLOY_APP(默认 /opt/autostore-bot)、DEPLOY_PORT(默认 78)。 SSH_* 与 DEPLOY_* 只在本机生效,服务器上的 .env 不需要填。

方式二:手动部署

# 1. 打包(务必排除 data / .env / node_modules,否则会覆盖服务器上的真实数据)
tar -czf update.tar.gz \
  --exclude=node_modules --exclude=.git --exclude=data --exclude='*.log' --exclude=.env \
  -C . .

# 2. 上传
scp -P 33851 update.tar.gz root@103.236.99.177:/opt/autostore-bot/update.tar.gz

# 3. 远程应用(update.sh 会保留 .env / data / node_modules / runtime)
ssh -p 33851 root@103.236.99.177 'bash /opt/autostore-bot/update.sh'

辅助脚本

脚本 用途
scripts/deploy.js 一键部署(推荐)
scripts/ssh-helper.js 单条远程命令 / 上传:node scripts/ssh-helper.js exec "<命令>"、... put <本地> <远程>

Windows 自带 OpenSSH 但没有 sshpass / plink,无法非交互传密码, 所以这两个脚本用 ssh2 直接实现协议层(ssh2 是 devDependencies, 未安装时脚本会提示 npm install --save-dev ssh2 而不是裸崩)。

凭据一律走环境变量,脚本里不写死任何密码。

update.sh 做了什么

/opt/autostore-bot/update.sh 的行为:

  1. 解压 update.tar.gz 到 /opt/autostore-bot(保留 .env、data/、node_modules/、runtime/)
  2. 用私有 Node 重新安装/校验依赖
  3. pm2 restart autostore-bot --update-env
  4. 打印进程状态与端口监听情况

常用运维命令

# 进程状态
pm2 status
pm2 list

# 重启 / 停止
pm2 restart autostore-bot --update-env
pm2 stop autostore-bot

# 日志(实时)
tail -f /opt/autostore-bot/data/pm2-out.log     # 运行日志
tail -f /opt/autostore-bot/data/pm2-error.log   # 告警与错误
pm2 logs autostore-bot

# 端口
ss -lntp | grep ':78 '

# 回滚:把上一份备份目录改回来(update.sh 会先备份旧版本目录)
ls -1d /opt/autostore-bot/backup-* 2>/dev/null | tail -3

服务器上的关键文件

路径 说明
/opt/autostore-bot/.env 真实凭据(MC_USERNAME、MC_PASSWORD、WEB_PASSWORD、SIMP_API_KEY、SIMP_API_V2_KEY、可选 NOTIFY_WEBHOOK_URL),权限 0600,绝不进仓库
/opt/autostore-bot/data/autostore.json 运行时配置(控制台保存的),会被 update.sh 保留
/opt/autostore-bot/data/bots.json 机器人非敏感设置
/opt/autostore-bot/data/bots.credentials.json 界面添加的账号密码,权限 0600
/opt/autostore-bot/data/chat/<玩家名>.jsonl 公屏完整日志
/opt/autostore-bot/data/expansion-log.jsonl 开拓日志
/opt/autostore-bot/data/mapcache/ 大地图瓦片缓存
/opt/autostore-bot/ecosystem.config.js pm2 配置
/opt/autostore-bot/runtime/current 私有 Node(服务器自带 Node 太旧时用)

新增环境变量时

  1. 在 .env 里加真实值(只在服务器上加,不要提交);
  2. 在 .env.example 里加占位符(如 <your-xxx-key>),绝不含真实密钥;
  3. 确认 .gitignore 已包含 .env(本项目已含,并有 !.env.example 例外)。

1. 🟢 「机器人走不动」:已解决(根因是自定义高度偏好,见下)

🚧 2026-10-01 当前进展与剩下的拦路虎

修复高度偏好后,机器人确实能朝目标走了(实测 20 分钟前进 95 格), 但要真正开垦出第一块地,还要跨过这几关:

① 几何约束:目标天然就在 ~770 格外。 规划器 generateHolePriority 是按到玩家的曼哈顿距离排序取最近的合法候选 (expansionPlanner.js:109-130),而机器人出生在王城——它在「养老院」这个 33037 区块(约 180×180 区块) 的大邦国正中间。实测最近的候选在 53 区块 (≈768 格)外,且 targetCount: 1 只返回一个候选。所以长途跋涉是邦国几何决定的,绕不开。

② 服务器会踢它,三种原因都实测到过:

  • You were kicked from main: An internal error occurred in your connection.(最常见,约 1~6 分钟一次)
  • You were kicked from main: You are sending too many packets!
  • You were kicked from main: Flying is not enabled on this server
  • 未完成验证时还有 验证超时!您需要在 120 秒内完成身份验证(120 秒内要人工发码)

实测出站包速率 57.9 个/秒,其中 pong 30.6(服务端在疯狂 ping)、 协议补包(player_input + tick_end)合计 23.9、位置包 1.35。 注意:协议补包当初的立论依据(「走不动」的根因)已经被证伪(真因是高度偏好), 它们是否仍然必要、是否就是「发包太多」的原因,尚未验证——这是下一步要测的。

③ 半路会死:实测死因是 fell from a high place 与 was slain by Zombie。 摔死那次白走了 100 多格。

  • 摔死已处理:服务器配置里 pathing.maxDropDown 被改成了 64(钳制上限), 而原版摔落伤害超过 3 格后每格 1 点、23 格必死。已改回 4(代码默认本来就是 3)。
  • 被僵尸杀还没处理:autoAttack 是开的,但夜里野外仍会被围死。

④ 推进速度只有物理速度的 1/5:实测机器人几乎每 tick 都按着 forward+sprint、 velocity 约 0.2~3 格/秒,但 2 秒粒度的净位移只有 0.405 格/秒 (48.6 格 / 120 秒)——差额是被服务端拉回的部分。为此已补上 flags.hasHorizontalCollision(mineflayer 恒写 undefined,见 [1.10.0]), 实测该标志确实一直在被漏报(补写计数很快涨到 1 万+),但提速效果尚未量化验证。

⑤ 会卡在崎岖地形前「原地起跳」 —— ✅ 已定位并修复([1.11.0])。

真正的根因不是地形,是朝向:bot.entity.yaw 无界增长(mineflayer 收到 position 包时 直接赋值、不取模,GrimCompat 又在它基础上累加),实测涨到 19254°(≈336 弧度)。 抓包证据:

yaw°:  -1373 → 19254 → -1650 → -1407 → -1373 → 19254 → …     相邻两包折合差约 114°
坐标:  (6756.57, 11953.6) ⇄ (6757.45, 11953.8)                 0.9 格内「橡皮筋」

机器人一直按着前进键,但朝向每隔几个 tick 就被甩到另一个方向,于是原地走之字形、 净位移恒为 0。物理录制里的 jump 74/240、水平碰撞 195/240、onGround 79/240 都是这个 bug 的表象。 修复后实测朝向变成平滑转向(0, -9, -17, -26, -34, -43 …),速度从 0.35 提到 0.56 格/秒。

⑥ 剩下的拦路虎:服务端在把它「钉」在固定坐标上。 修复 yaw 之后,手动移动测试报告位移 2.40~4.81 格(说明客户端确实走了), 但一查状态,位置又回到同一个点(6756.57 / 6750.3)。被服务器传送另有 持续在 16~194 次/30 秒。也就是说客户端走、服务端拉回,净位移为 0 —— 这一层已经不在代码控制范围内,需要确认服务器侧的原因。

⑦ 服务端提示过「邦国已激活科技: 疆土开拓者 I 剩余时间: 0时13分3秒」 —— 开拓可能需要该邦国科技处于激活状态,且有时间限制。 这可能是「到了目标区块也未必能开垦成功」的一个前提条件,尚未验证。

✅ 2026-10-01 突破:修掉高度偏好后扩地真的跑起来了

根因:AltitudeGoal 把高度偏置加在启发值上 (heuristic = max(0, 基础距离 - 权重 × y)),而 mineflayer-pathfinder 每次 compute() 返回 partial 时用 h 最小的节点当 partial 路径的终点 (astar.js: if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode)。 于是「又高又偏」的节点 h 反而更小,partial 路径被引向高处而不是目标附近 —— A* 一直在算、永不收敛,机器人一次移动指令都发不出来。

生产对照实测(王城平台上,其余配置完全相同):

altitudePreference inputTicks 结果
true(修复前) 0 永远停在「寻路超时,重新寻路」,一步不动
false(修复后) 237 每 tick 都在走,blockedTicks = 0、frozenTicks = 0

已把默认值改为 false([1.9.1]),并在 AltitudeGoal 的注释里写清推导与实测数据。 想重新启用,必须把偏置改成「低处加移动代价」(改 getNeighbors 的 cost), 不能改启发值。

顺带修掉的第二个拦路虎([1.9.3]):服务器每 1~6 分钟把机器人踢下线一次 (An internal error occurred in your connection),原来一掉线任务就永久停住。 现在 autoResumeAfterReconnect(默认开)会在世界退出时记下配置、重连后自动接上; 手动停止 / 时段外 / 耐久保护停机不会被误恢复。

实测结果(修复后连续 20 分钟):

起点 6753.6, 143, 11953.7   距目标 763 格
终点 6768.5, 116, 12045.7   距目标 668 格      ← 前进 95 格,全程未掉线

「原点自救」表现完美(4/4 成功):每次被踢到登录服大厅都能自己回到真实世界。

仍需观察:推进速度约 5 格/分钟(正常步行 4.3 格/秒), 因为每段路径都会走到「寻路重试耗尽 → 往边上走两步 → 跳过目标 → 重新规划同一目标」 的循环里。待优化项:waitTimeoutSeconds(60)偏长、以及「跳过目标后立刻重选同一个目标」。

历史记录:三种「走不动」的区分(结论仍然有效)

🔴 2026-10-01 实测:三种「走不动」要分开看(先读这段)

用新增的物理 tick 录制(POST/GET /api/bots/:id/physics,见 CHANGELOG [1.9.0]) 逐 tick 抓了「输入 → 输出」,把三种完全不同的现象区分开了:

场景 物理有没有动 服务端在干什么 结论
登录服大厅 ✅ 每 tick 走 0.116 格(正常速度) 每 2 tick 推回一次(被服务器传送另有 240~260 次/30 秒) 真回弹,但这是未完成验证的惩罚,不是 bug
真实世界、建筑内 ✅ 加速到 0.21 格/tick(≈4.3 格/秒,正常步行) 入站位置包 0~1 个,没插手 撞墙:collidedHorizontally=true,blockedTicks 42~59/60
真实世界、卡在低处 ❌ 一次都没动 — 寻路器 60 秒规划超时,inputTicks=0,从未产生可执行路径

关键结论:物理引擎、协议补包、Grim 合规层全都正常(能加速到正常步行速度), frozenTicks 在所有场景都是 0 —— 「输入没进物理」这种情况从未出现。 searchRadius 从 -1 改成 128 对结果没有影响,所以也不是搜索空间无界导致的。

当前扩地的真正拦路虎(按优先级):

  1. 机器人被地形困在王城建筑里:实测 exits 四向只有西面可走,东面头顶是 torch(火把) —— 它被 /u return 传送进建筑后就出不来了。而此时 mineflayer-pathfinder 的「徒劳检测」会把每一段路径都清掉(见下),于是永远在「规划 → 卡住 → 清空 → 规划」里循环: blockedTicks 230/237、净位移 ~0。
  2. 装备耐久:lastReturn.reason = "装备耐久过低(主手 剩余耐久 15% ≤ 15%)" → 一启动扩地就 /u return 回城并停机,inputTicks=0。需要人工修装备。
  3. ⚠️ /u return 会造成摔落伤害:实测 health 3.79 → 1.24、7.8 → 5.6, 更早一次直接 fell from a high place 摔死。回城前应确认落点高度; 而且它把机器人送进建筑内部(见第 1 条),等于「回城即被困」。

🎯 死循环的确切机制(2026-10-01,读 mineflayer-pathfinder 源码定位)

mineflayer-pathfinder/index.js 末尾的「徒劳检测」:

// check for futility
if (performance.now() - lastNodeTime > 3500) {
  resetPath('stuck')   // 3.5 秒内到不了下一个路径点 → 清空路径
}

机器人被墙挡住时,3.5 秒必然到不了下一个路径点 → resetPath('stuck') → 重新规划 → 又卡住 → 又清空。每 3.5 秒一个循环,日志表现为 寻路事件:路径已生成(代价 63.07) 每 3 秒重复一次;resetPath 每次都会补一对 diggingAborted/diggingCompleted 监听器, 所以日志里还会出现 MaxListenersExceededWarning: 11 diggingAborted listeners —— 这条警告就是「路径被反复清空」的可靠信号。

另外两个也会清空路径的触发点(排查时注意):

  • blockUpdate:路径附近的方块一变就 resetPath('block_updated');
  • chunkColumnLoad:新加载的区块与已访问区块相邻就 resetPath('chunk_loaded') —— 服务器频繁传送机器人时,这个会被反复触发,A* 的 20 秒 thinkTimeout 因为每次都新建 AStar 而永远不会到期。

结论:只要机器人被墙挡住,它就会无限循环「规划 → 卡住 → 清空」, 既不会放弃目标也不会自己脱困。 需要先把它挪到开阔地;长期看应处理 path_reset('stuck') 的累计次数(连续 N 次就判定「此处出不去」并上报 / 换目标)。

⚠️ 线上实测补充(2026-09-29,它推翻了下面原有的框架)

用新增的出站包抓取(POST/GET /api/bots/:id/capture,见 CHANGELOG [1.8.0])做了对照, 结论是:「走不动」不是服务端回弹造成的,是客户端物理引擎没有把按键积分成位移。

手动按住一个方向键 3 秒的逐包实测(任务未启动、机器人空闲):

项 实测
入站位置包(服务端回弹) 0 个 —— 服务端全程没插手
出站位置包 5 个,全是 1 秒一次的空闲包(不是每 tick 一个)
客户端自己的坐标 完全没变(6752.3000, 146, 11922.3000 → 一字不差)
朝向 完全没变(-0.20876)
moveDiag.velocity 恒为 (0, -0.078, 0),speed = 0.078
player_input 位标志 backward: true ✅ —— 控制键确实按下去了

四方向各按 3 秒的位移:forward 0.02 / back 0.00 / left 1.22 / right 1.40(正常应约 12 格)。

推论:

  1. player_input 如实上报了输入、bot.getControlState() 也是 true, 但 prismarine-physics 的 applyHeading 没有产生任何水平速度 —— 要么物理引擎拿到的 control 快照里 forward/back 是 0, 要么 moveEntity 把位移全吃掉了。下一步要加 physicsDiag 直接看: bot.physicsEnabled、每 tick 的 physicsTick 计数、simulate 前后 onGround、 world.getBlock(脚下) 是否命中、以及 PlayerState 里读到的 control 快照。
  2. 「服务端回弹」只在寻路任务运行时出现(实测任务中入站 6/秒、出站 position_look 25/秒), 手动移动时入站为 0 —— 回弹是「任务驱动移动」的伴生现象,不是走不动的原因。 这也解释了为什么下面两套针对回弹的修正(方案 A/B)开关对位移毫无影响 (实测:修正开 0.46.4 格 / 修正关 0.42.4 格,同量级)。
  3. 原先记的「手动按键移动完全正常(forward 2 秒 4~8 格)」已不成立, 该基准需要重建 —— 基于它的推理(「规划问题 vs 协议问题」二分)也就全部偏了。

顺带确认(1.21.4 协议):

  • flags.hasHorizontalCollision 恒为 null(= 0):mineflayer 在 physics.js 里 把它写成 undefined,而原版客户端会在水平碰撞时置位;实测 62/62 个位置包都是 0。
  • teleport_confirm.teleportId 是大负数(如 -1884468723):该服务端用的是随机大整数, 不是原版的自增小整数,属于它自己的实现,不是 bug,但排查时别按原版预期去读。
  • 包类型分布对照(5 秒):空闲 player_input/tick_end/pong ≈ 100 且 position=5; 手动按键与空闲同构(没有 position_look);寻路中才有 position_look=126。

现象回顾(历史记录;其中「手动移动正常」已被上面的实测推翻)

  • 手动按键移动完全正常:通过控制台「移动测试」按 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 标志(出站包) ✅ 已按世界数据复核 packetStats.onGroundFixed
世界数据可读性 ✅ world.getBlock 正常,四向可见 115~125 格 pathingDiag.visibleDistance
寻路器本身 ✅ 在开阔地能找到路径 实测「已探索 3633 节点 → success,代价 113.8」
服务端回弹是否持续 ❌ 只在机器人尝试移动时出现 12 秒采样:空闲 +0,寻路中 +105
关闭疾跑 试过,无改善 allowSprint=false 后仍是 4.8 次/秒回弹
关闭合规层(grim.enabled=false) 试过,无改善 A/B 实测 6.0 vs 5.5 次/秒,位移都是 0
世界数据是否错误 ❌ 不是原因 实测脚下 stone,blockBelow 正确

🎯 关键线索(未变)

用 /api/status 的 moveDiag 采集到的实测数据:

  • 寻路时 forward/sprint 都按下、isMoving=true,但水平速度恒为 0;
  • velocity.y = -0.078 恰是自由落体第一 tick 的重力值 —— 说明速度每 tick 被清零;
  • bot.entity.onGround 被服务端位置包反复置为 false(mineflayer 的 physics.js 在处理 position 包时无条件置 false),物理引擎因此一直走「空中」分支。

✅ 本轮已实现的代码(1.7.0)

手段 开关 位置
方案 A:物理引擎 onGround 对齐 grim.syncGroundToPhysics(默认开) botManager._installGroundStateFix
方案 B:例行同步不采纳 onGround=false grim.trustSetbackOnGround + grim.setbackSyncEpsilon(默认 0.5 格) 同上(prependListener 抢在 mineflayer 之前快照)
出站包类型分布(假设 4 的对照依据) 常开 packetStats.outboundTypes
备选执行器:只借 A* 算路径、自己按键执行 pathing.executor: 'manual' src/bot/manualMover.js

请按下面的顺序实测:

  1. 部署后先在控制台确认「Grim 合规」卡片里的 地面修正 一行出现非零计数 (物理对齐 N · 保住 M · 最近同步差 X 格);
  2. 用「移动测试」确认手动移动仍然正常;
  3. 开始扩地,观察 moveDiag.speed 是否 > 0.1、moveDiag.onGround 是否稳定为 true、 「被服务器传送另有」的计数是否下降、以及是否真的能走完一段路;
  4. 若仍有回弹 → 在配置「寻路 → 寻路执行器」切到 手动执行(备选方案), 再观察一次(它只借用 A* 规划,执行层完全是我们自己的按键时序);
  5. 若两者都不行 → 回到下面「未完成的排查方向」,用 packetStats.outboundTypes + 旧模组抓包做逐项 diff(假设 5)。

未完成的排查方向(按优先级)

假设 3:跳跃 / 跑酷的物理预测不匹配

Grim 的 Prediction 检查对跳跃最严格(起跳时机、跳跃高度、疾跑跳加成)。 mineflayer 的 physics.js 在这些细节上与原版有已知偏差。

验证方法:把路径限制成纯平地,禁用一切跳跃:

{"allowSprint": false, "pathing": {"allowParkour": false, "allow1by1towers": false, "maxDropDown": 1}}

若回弹消失 → 需要在 GrimCompat 里拦下 jump 控制状态,改用更保守的移动策略。

假设 5:直接抓包对照(最彻底)

用 scripts/check-protocol.js 的思路扩展一个抓包脚本, 分别在「旧模组移动」和「mineflayer 寻路移动」时记录 5 秒的完整出站包序列,diff 出差异。 packetStats.outboundTypes 已经给出包类型分布作为起点。

提示:scripts/check-protocol.js 里读取 node_modules 的路径用了 path.join(__dirname, 'node_modules', ...),在 scripts/ 下是拿不到的 —— 顺手修一下(应为 path.join(__dirname, '..', 'node_modules', ...))。


2. ✅ 原点自救(已实现判定与统计,实测成功率待观察)

服务器把玩家丢到世界原点附近有两种完全不同的原因,以前在日志里长得一样:

原因 判断依据 处理方式 现状
死亡后被卡住 服务器没有要求验证码 左右 / 前后移动即可触发服务器重新传送 ✅ 两轮自救 + 成败判定
被切到登录服 服务器要求输入验证码 必须重新输入验证码,只能人工完成 ✅ 已识别并提示,支持 Webhook 提醒

⚠️ 不要用「脚下是不是基岩」来区分这两种情况 —— 实测两种情况下脚下都是基岩。 唯一可靠的信号是服务器有没有发「请直接输入验证码进行验证」这类提示。

本轮改进:分两轮(左右 → 前后 + 跳),每轮结束后等 800ms 让传送落地再判定「位置是否恢复」; 统计 attempts / successes / lastResult(/api/status 与控制台「连接」卡片可见); 两轮都失败时升级为明确提示,并支持 Webhook 提醒。 待观察:线上实际成功率(看 originRescue.successes / attempts), 若长期为 0,说明该服务器对移动输入不再响应,应把重心放在验证码流程上。


3. ✅ 自动回城(已加「原地是否更差」评估与前后坐标)

回城是破坏性操作:它把机器人拉回安全点,会清掉玩家在游戏客户端里手动摆好的位置。

现状(1.7.0 之后):

  • returnOnTimeout 默认关闭;即便打开,returnGraceSeconds(默认 300 秒)内不执行;
  • 回城前先评估:pathing.diagnostics().neighborCount 为 0(确实无路可走)才回城, 否则记为「原地还能走」并跳过目标 —— 回城只会白白丢掉位置;
  • 回城后会记录前后坐标与直线距离,日志写「回城结果:机器人从 (A) 挪到了 (B)(直线 X 格)」, 控制台「任务」卡片显示 最近回城;位置几乎没变时明确提示回城没有帮助。

4. ✅ 其它已知问题(本轮都已落地)

4.1 大地图 ✅

  • 层级 1~3 现在有后台预热:机器人移动超过 bigMap.prewarmMinMoveBlocks(默认 512 格) 或距上次超过 prewarmIntervalMs(默认 10 分钟)时,用一半并发把周边瓦片补下来;
  • 领土多边形按视野裁剪绘制,全世界视角不再全量绘制。

4.2 控制台 ✅

  • 日志改为按帧批量写入(一次布局、一次滚动),并用 content-visibility: auto 让浏览器跳过视口外行的渲染(等价于浏览器自带的虚拟列表,不改变内容与滚动位置);
  • 配置面板新增搜索框:按名称 / 字段名 / 说明过滤,隐藏不删除(保存时仍能读到隐藏字段的值)。

4.3 多机器人 ✅

  • 新增目标区块租约表(src/core/targetLease.js):谁先抢到谁去,其余机器人跳过;
  • 持有者每个 tick 续租;停止任务 / 完成任务时归还;掉线 / 卡死后租约到期自动失效;
  • 候选区块全被其他机器人预定时,等 15 秒再规划(而不是提前判「没有可用区块」)。
  • 待观察:多机器人同时扩地的实际撞车率(日志里出现「已被其他机器人预定,跳过」的次数)。

4.4 定时消息 ✅

  • 支持 cron 表达式(5 段,本地时间,含步长 / 区间 / 列表 / 星期范围); 非法表达式不会被静默吞掉,列表里会显示原因并按间隔发送;
  • 支持发送结果确认:填「期望回复」(正则)后,命中回复记「已确认」, 超时记「未确认」并写警告 —— 不再只靠人翻日志判断 /u restore confirm 是否生效。

4.5 登录验证 ✅

  • 验证码仍只能人工输入(服务器设计如此),但等待验证码与原点自救失败时会通过 Webhook 通知(默认按飞书 / Lark 自定义机器人的文本格式)提醒,避免错过 120 秒时限;
  • 配置位置:控制台「服务器 → 通知提醒(Webhook)」,或环境变量 NOTIFY_WEBHOOK_URL。

5. 常用排查命令

# 机器人完整状态(含寻路诊断 / 地面修正计数 / 自查统计)
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'

# 地面状态修正与出站包类型分布
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0].packetStats'

# 原点自救成功率 / 最近回城
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0].originRescue'
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status | jq '.data.bots[0].task.lastReturn'

# 实时日志
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

# 多机器人协调:谁在占哪块(日志)
grep '已被其他机器人预定' /opt/autostore-bot/data/pm2-out.log | tail -10

# 抓出站包(诊断「走不动」的主力工具,见问题 1 · 假设 5):
# 先在「能走」时抓 5 秒,再在「走不动」时抓 5 秒,然后逐包 diff
BOT=<机器人id>
curl -s -X POST -H "X-Auth-Token: $PW" -H 'Content-Type: application/json' \
  -d '{"seconds":5}' http://103.236.99.177:78/api/bots/$BOT/capture/start
# 取包类型分布(先看「发了哪几种、各多少次」)
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/bots/$BOT/capture | jq '.data.byType'
# 只取移动相关的原始包内容(看 onGround / hasHorizontalCollision / player_input 位标志 / teleportId)
curl -s -H "X-Auth-Token: $PW" \
  'http://103.236.99.177:78/api/bots/$BOT/capture?types=position_look,look,position,flying,player_input,teleport_confirm&limit=60' \
  | jq '.data.packets'
# 手动停止(给 seconds 时可省略)
curl -s -X POST -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/bots/$BOT/capture/stop

# 协议字段定义自检(核对 mineflayer 写出的字段名与 1.21.4 是否一致)
node scripts/check-protocol.js

# 录物理 tick(诊断「走不动」的另一半:抓包看「发了什么」,这里看「物理读到了什么、动没动」)
# 一边录一边按方向键,然后看三个计数:
#   inputTicks>0 且 inputMovedTicks=0 且 frozenTicks>0  → 输入根本没进物理
#   inputTicks>0 且 inputMovedTicks=0 且 blockedTicks>0 → 被方块挡住(地形)
#   inputMovedTicks>0 但服务端把位置拉回            → 才是服务端回弹
curl -s -X POST -H "X-Auth-Token: $PW" -H 'Content-Type: application/json' \
  -d '{"seconds":10}' http://103.236.99.177:78/api/bots/$BOT/physics/start
curl -s -X POST -H "X-Auth-Token: $PW" -H 'Content-Type: application/json' \
  -d '{"direction":"forward","seconds":3}' http://103.236.99.177:78/api/bots/$BOT/move
curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/bots/$BOT/physics \
  | jq '.data | {inputTicks, inputMovedTicks, blockedTicks, frozenTicks, movedTicks, maxHorizDelta, last}'
# 逐 tick 明细(看速度、onGround、碰撞、脚下方块)
curl -s -H "X-Auth-Token: $PW" 'http://103.236.99.177:78/api/bots/$BOT/physics?limit=40' \
  | jq '.data.ticks[] | {t, dx, dz, vx, vz, onGround, collidedHorizontally, blockBelow, control}'

判断「机器人是不是卡在登录服」

curl -s -H "X-Auth-Token: $PW" http://103.236.99.177:78/api/status \
  | jq '.data.bots[0] | {position, chunk, needsVerification}'
  • needsVerification == true → 服务器正在等待验证码,控制台会显示醒目提示(这是可靠信号)
  • 位置靠近原点且 needsVerification == false → 多半是死亡后卡住,机器人会自动自救(并记录成败)
  • ⚠️ world.blockBelow == "bedrock" 不能用来判断 —— 实测两种情况脚下都是基岩

6. 代码约定(改动前请遵守)

  • 提交信息用约定式提交(feat: / fix: / docs: / refactor: / test: / chore:),主题行说清做了什么。
  • 所有密钥只走 .env(已在 .gitignore 里),代码里绝不硬编码; 新增配置项要同时更新 .env.example 的占位符。
  • 注释与文档用中文,解释「为什么」而不是「做了什么」。
  • 每个 fix 都应补一个能复现原问题的测试;本项目测试在 test/,用 node:test。
  • 改前端记得跑 npm run check,它会校验 app.js 引用的元素 id 在 index.html 里都存在。
  • ⚠️ 写块注释时注意:中文解释里不要出现 */ 这两个字符(例如 cron 步长写法), 它会提前结束注释并导致语法错误(本项目已踩过一次)。