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,全部通过。
这个提交包含在:
@@ -99,6 +99,8 @@ class ExpansionController {
|
||||
this.retryCount = 0;
|
||||
/** 当前阶段(寻路 / 等待)的开始时间戳 */
|
||||
this.stageStartedMillis = 0;
|
||||
/** 本次连接/本次任务的开始时间戳,用于「刚连上时不许回城」的保护期 */
|
||||
this.sessionStartedMillis = Date.now();
|
||||
/** 上一次记录的「与目标的曼哈顿距离」,用于判断寻路是否在稳步推进 */
|
||||
this._lastTargetDistance = null;
|
||||
/** 最近一次开垦进度(0-100) */
|
||||
@@ -241,6 +243,9 @@ class ExpansionController {
|
||||
this.completedClaims = 0;
|
||||
this.retryCount = 0;
|
||||
this.consecutiveTimeoutMillis = 0;
|
||||
// 保护期从「本次任务开始」算起:玩家常趁机器人掉线时在客户端把它摆到开阔地,
|
||||
// 刚启动就被回城拉走等于白摆
|
||||
this.sessionStartedMillis = Date.now();
|
||||
this.sideStepCount = 0;
|
||||
this.sideStepping = false;
|
||||
this.returnThenStop = false;
|
||||
@@ -480,16 +485,20 @@ class ExpansionController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送回城指令:取消当前寻路,向服务器发送 `/u return`(约 10 秒后自动传送回安全位置),
|
||||
* 随后进入 RETURNING 状态等待传送完成。
|
||||
* 发送回城指令:取消当前寻路,向服务器发送回城指令(默认 `/u return`,
|
||||
* 约 10 秒后自动传送回安全位置),随后进入 RETURNING 状态等待传送完成。
|
||||
*
|
||||
* 指令内容取自 `returnCommand`,方便不同服务器替换成自己的回城命令。
|
||||
*
|
||||
* @param {string} reason 触发回城的原因
|
||||
*/
|
||||
_sendReturnCommand(reason) {
|
||||
const bm = this.botManager;
|
||||
const command = (this.config.returnCommand || 'u return').replace(/^\/+/, '');
|
||||
bm.pathing.cancel();
|
||||
this._log(reason + ',发送 /u return 回城,10 秒后自动传送');
|
||||
bm.sendCommand('u return');
|
||||
this._log(`${reason},发送 /${command} 回城,约 10 秒后自动传送`
|
||||
+ '(注意:这会把机器人拉回安全点,当前位置与玩家手动摆好的位置都会丢失)');
|
||||
bm.sendCommand(command);
|
||||
this.returnStartMillis = Date.now();
|
||||
this.setState(State.RETURNING, '等待回城传送(10 秒后自动传送)');
|
||||
}
|
||||
@@ -591,12 +600,28 @@ class ExpansionController {
|
||||
/**
|
||||
* 放弃当前目标(重试与走两步均已尝试)的统一出口:
|
||||
* 连续寻路超时累计达到阈值则回城传送并重新规划;否则跳过当前目标。
|
||||
*
|
||||
* 回城是**破坏性**操作,因此默认关闭(`returnOnTimeout`),且即便打开也有两道护栏:
|
||||
* - 刚建立连接的一段时间内不回城:玩家常常是趁机器人掉线时在客户端把它摆到开阔地,
|
||||
* 一连上就被拉回安全点等于白摆;
|
||||
* - 每次真正回城都明确记日志,避免事后只看到位置莫名其妙变了。
|
||||
*/
|
||||
_giveUpTarget(reason) {
|
||||
if (this.consecutiveTimeoutMillis >= this.config.returnThresholdSeconds * 1000) {
|
||||
this._sendReturnCommand(`连续寻路超时超过 ${this.config.returnThresholdSeconds} 秒`);
|
||||
const config = this.config;
|
||||
if (config.returnOnTimeout && this.consecutiveTimeoutMillis >= config.returnThresholdSeconds * 1000) {
|
||||
const sessionSeconds = Math.floor((Date.now() - this.sessionStartedMillis) / 1000);
|
||||
if (sessionSeconds < config.returnGraceSeconds) {
|
||||
this._log(`连续超时达阈值,但本次连接仅 ${sessionSeconds} 秒(保护期 ${config.returnGraceSeconds} 秒),`
|
||||
+ '不回城以免清掉刚摆好的位置;改为跳过当前目标');
|
||||
this.setState(State.FAIL, reason + '(保护期内不回城)');
|
||||
return;
|
||||
}
|
||||
this._sendReturnCommand(`连续寻路超时超过 ${config.returnThresholdSeconds} 秒`);
|
||||
return;
|
||||
}
|
||||
if (this.consecutiveTimeoutMillis >= config.returnThresholdSeconds * 1000 && !config.returnOnTimeout) {
|
||||
this._log('连续寻路超时已达回城阈值,但「超时自动回城」未启用,跳过当前目标(如需自动逃离卡死状态请在配置里打开)');
|
||||
}
|
||||
this._log(this.target ? `${reason},跳过 ${this.target.x},${this.target.z}` : reason);
|
||||
this.setState(State.FAIL, reason);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户