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,全部通过。
这个提交包含在:
@@ -273,9 +273,12 @@ test('寻路超时会重新寻路,重试耗尽后走两步', async () => {
|
||||
assert.strictEqual(controller.sideStepCount, 1);
|
||||
});
|
||||
|
||||
test('连续超时累计达到阈值时回城', async () => {
|
||||
test('连续超时累计达到阈值时回城(需显式开启 returnOnTimeout)', async () => {
|
||||
const { controller, botManager, config } = setup({
|
||||
config: { waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0, returnThresholdSeconds: 1 }
|
||||
config: {
|
||||
waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0,
|
||||
returnThresholdSeconds: 1, returnOnTimeout: true, returnGraceSeconds: 0
|
||||
}
|
||||
});
|
||||
await controller.start(config);
|
||||
controller.stageStartedMillis = Date.now() - 10000;
|
||||
@@ -284,9 +287,54 @@ test('连续超时累计达到阈值时回城', async () => {
|
||||
assert.ok(botManager.commands.includes('u return'), '应发送回城指令');
|
||||
});
|
||||
|
||||
test('默认不回城:超时只跳过目标,不把机器人拉走', async () => {
|
||||
const { controller, botManager, config } = setup({
|
||||
config: { waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0, returnThresholdSeconds: 1 }
|
||||
});
|
||||
assert.strictEqual(config.returnOnTimeout, false, '默认应关闭自动回城');
|
||||
await controller.start(config);
|
||||
controller.stageStartedMillis = Date.now() - 10000;
|
||||
controller.tick();
|
||||
assert.notStrictEqual(controller.state, State.RETURNING, '默认不应触发回城');
|
||||
assert.ok(!botManager.commands.includes('u return'), '不应发送回城指令');
|
||||
});
|
||||
|
||||
test('回城保护期内即便开启回城也不执行,避免清掉刚摆好的位置', async () => {
|
||||
const { controller, botManager, config } = setup({
|
||||
config: {
|
||||
waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0,
|
||||
returnThresholdSeconds: 1, returnOnTimeout: true, returnGraceSeconds: 600
|
||||
}
|
||||
});
|
||||
await controller.start(config);
|
||||
controller.stageStartedMillis = Date.now() - 10000;
|
||||
controller.tick();
|
||||
assert.notStrictEqual(controller.state, State.RETURNING, '保护期内不应回城');
|
||||
assert.ok(!botManager.commands.includes('u return'));
|
||||
});
|
||||
|
||||
test('回城指令可通过 returnCommand 自定义', async () => {
|
||||
const { controller, botManager, config } = setup({
|
||||
config: {
|
||||
waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0,
|
||||
returnThresholdSeconds: 1, returnOnTimeout: true, returnGraceSeconds: 0,
|
||||
returnCommand: '/spawn'
|
||||
}
|
||||
});
|
||||
await controller.start(config);
|
||||
controller.stageStartedMillis = Date.now() - 10000;
|
||||
controller.tick();
|
||||
// 玩家可能顺手带上前导斜杠,发送时应被去掉
|
||||
assert.ok(botManager.commands.includes('spawn'), '应发送自定义指令:' + botManager.commands.join(', '));
|
||||
assert.ok(!botManager.commands.includes('u return'));
|
||||
});
|
||||
|
||||
test('回城等待结束后重新规划(非耐久保护触发)', async () => {
|
||||
const { controller, config } = setup({
|
||||
config: { waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0, returnThresholdSeconds: 1, returnWaitSeconds: 1 }
|
||||
config: {
|
||||
waitTimeoutSeconds: 5, retries: 1, sideStepMaxRounds: 0,
|
||||
returnThresholdSeconds: 1, returnOnTimeout: true, returnGraceSeconds: 0, returnWaitSeconds: 1
|
||||
}
|
||||
});
|
||||
await controller.start(config);
|
||||
controller.stageStartedMillis = Date.now() - 10000;
|
||||
|
||||
在新工单中引用
屏蔽一个用户