diff --git a/CHANGELOG.md b/CHANGELOG.md index 040842a..1f6f65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 +## [1.9.1] - 2026-10-01 + +**修复「寻路器完全规划不出路径」** —— 这是自动扩地跑不起来的直接原因。 + +### 修复 + +- **高度偏好(`altitudePreference`)默认关闭**:它的启发值实现与 + mineflayer-pathfinder 的内部约定冲突。该库每次 `compute()` 返回 `partial` 时, + 用 **`h` 最小**的节点当作 partial 路径的终点 + (`astar.js`: `if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode`), + 而 `AltitudeGoal` 把 `h` 减去了 `权重 × y`,于是「又高又偏」的节点胜出, + partial 路径被引向高处而不是目标附近 —— 表现为 A* 一直在算、永不收敛, + **机器人一次移动指令都发不出来**。 + - 实测(王城平台上,其余配置完全相同):开启 `inputTicks = 0`、任务永远停在 + 「寻路超时,重新寻路」;关闭 `inputTicks = 237`、立刻开始移动。 + - `searchRadius` 从 -1 改到 128 对此**没有影响**,所以不是搜索空间无界导致的。 + - 想重新启用,应把偏置改成**低处加移动代价**(改 `getNeighbors` 的 `cost`), + 保持启发值只反映到目标的真实距离。`AltitudeGoal` 的注释里记了完整推导。 + +### 测试 + +新增 1 个回归用例(468 → 469):断言高度偏好默认关闭,并用启发值差值 +把「为什么它必然出问题」固化下来。 + ## [1.9.0] - 2026-09-29 补上诊断「走不动」的最后一块拼图:抓包只能看到「客户端发了什么」, diff --git a/package-lock.json b/package-lock.json index 44b177c..4c89384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "autostore-bot", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autostore-bot", - "version": "1.9.0", + "version": "1.9.1", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index f37973d..288e15f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autostore-bot", - "version": "1.9.0", + "version": "1.9.1", "description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)", "main": "src/index.js", "scripts": { diff --git a/src/bot/pathingService.js b/src/bot/pathingService.js index 9c0a720..f6da70e 100644 --- a/src/bot/pathingService.js +++ b/src/bot/pathingService.js @@ -515,8 +515,22 @@ class PathingService { * 对应原 Java 版的 GoalXZAltitude。 * * heuristic = max(0, 基础启发值 - 权重 × 节点高度) - * 即 y 越大启发成本越低(A* 更倾向走高处),y 越小成本越高(对走低处施惩罚), - * clamp 到非负以免破坏 A* 的排序。到达判定与内层目标完全一致(不约束高度)。 + * + * ⚠️ **这个实现会让寻路器完全规划不出路径,默认已关闭(见 `altitudePreference`)。** + * + * 原因是它与 mineflayer-pathfinder 的内部约定冲突:该库在每次 `compute()` 返回 + * `partial` 时,用 **`h` 最小** 的节点当作 partial 路径的终点 + * (`astar.js`: `if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode`)。 + * 而这里把 `h` 减去了 `权重 × y`,于是「又高又偏」的节点 h 反而更小, + * partial 路径被引向高处而不是目标附近 —— 表现就是 A* 一直在算、永远不收敛, + * 机器人一次移动指令都发不出来。 + * + * 实测(2026-10-01,王城平台上,其余配置完全相同): + * - 开启:`inputTicks = 0`,任务永远停在「寻路超时,重新寻路」; + * - 关闭:`inputTicks = 237`,立刻开始移动。 + * + * 若要重新实现高度偏好,正确做法是给**低处邻居加移动代价**(改 `getNeighbors` + * 返回的 `cost`),保持启发值只反映到目标的真实距离。 */ class AltitudeGoal extends Goal { /** diff --git a/src/config/expansionConfig.js b/src/config/expansionConfig.js index be08a9b..9b3499b 100644 --- a/src/config/expansionConfig.js +++ b/src/config/expansionConfig.js @@ -97,9 +97,20 @@ function defaultConfig() { allowJump: true, allowBreak: false, allowPlace: false, - /** 寻路高度偏好:优先往高处走 */ - altitudePreference: true, - /** 高度偏好权重:每上升 1 格折算的启发成本减少量 */ + /** + * 寻路高度偏好:优先往高处走。 + * + * ⚠️ **默认关闭,因为当前实现会让寻路器完全规划不出路径。** + * 实测(2026-10-01,王城平台上):打开时 `inputTicks = 0`(一次移动指令都不发)、 + * 任务永远停在「寻路超时,重新寻路」;关掉后 `inputTicks = 237`,立刻开始移动。 + * + * 原因见 `AltitudeGoal` 的注释:它把高度偏置加在启发值上, + * 而 mineflayer-pathfinder 用「h 最小」挑 partial 路径的终点(`bestNode`), + * 于是偏置会把 partial 路径引向「又高又偏」的节点,永远到不了目标附近。 + * 想重新启用,应把偏置改成**移动代价**(低处加 cost),而不是改启发值。 + */ + altitudePreference: false, + /** 高度偏好权重:每上升 1 格折算的启发成本减少量(当前实现下不生效) */ altitudeWeight: 0.1, // ==================== 生存辅助(与 Java 版同名) ==================== diff --git a/test/pathingService.test.js b/test/pathingService.test.js index a1af7cd..f0ff31d 100644 --- a/test/pathingService.test.js +++ b/test/pathingService.test.js @@ -35,6 +35,21 @@ test('AltitudeGoal 使用 GoalNearXZ 时保留其到达范围', () => { assert.strictEqual(goal.isEnd({ x: 100, y: 70, z: 201 }), true, '范围内算到达'); }); +test('高度偏好默认关闭:它的启发值会破坏 partial 路径的终点选择', () => { + // 回归:开启时实测 inputTicks = 0(寻路器一次移动指令都发不出), + // 关掉后 inputTicks = 237(立刻开始移动)。详见 AltitudeGoal 的注释。 + const config = defaultConfig(); + assert.strictEqual(config.altitudePreference, false, '默认必须是关闭的'); + // 上面的差值断言同时说明了「为什么」:h 被减去了 weight × y, + // 而 mineflayer-pathfinder 用 h 最小来挑 partial 路径终点, + // 于是又高又偏的节点会胜出,路径永远到不了目标附近。 + const goal = new AltitudeGoal(new GoalXZ(0, 0), 0.1, 64); + const farButHigh = goal.heuristic({ x: 0, y: 300, z: 0 }); // 就在目标正上方 + const nearButLow = goal.heuristic({ x: 40, y: 64, z: 0 }); // 离目标 40 格 + assert.ok(farButHigh <= nearButLow, + `高处节点的启发值 ${farButHigh} 不大于低处近节点的 ${nearButLow},正是问题所在`); +}); + // ==================== 长距离分段寻路 ==================== // // mineflayer-pathfinder 只能规划已加载区块范围(实测线上约 8 区块 ≈ 130 格),