fix: 高度偏好默认关闭 —— 它让寻路器完全规划不出路径(自动扩地的直接拦路虎)

实测定位:高度偏好的启发值实现与 mineflayer-pathfinder 的内部约定冲突。
该库每次 compute() 返回 partial 时,用 h 最小的节点当 partial 路径的终点
(astar.js: if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode),
而 AltitudeGoal 把 h 减去了 权重 × y,于是「又高又偏」的节点 h 反而更小,
partial 路径被引向高处而不是目标附近 —— A* 一直在算、永不收敛,
机器人一次移动指令都发不出来。

王城平台上的对照实测(其余配置完全相同):
- altitudePreference = true  → inputTicks = 0,任务永远停在「寻路超时,重新寻路」
- altitudePreference = false → inputTicks = 237,立刻开始移动

顺带排除:searchRadius 从 -1 改到 128 对结果没有任何影响,
所以不是「搜索空间无界」导致的;pathfinder 与 manual 两种执行器表现一致。

改动:
- expansionConfig 默认 altitudePreference 改为 false,并在注释里写清原因与
  「想重新启用应该怎么做」(把偏置改成低处加移动代价,保持启发值只反映真实距离);
- AltitudeGoal 的类注释补上完整推导与实测数据,避免以后有人又把它打开;
- 新增回归用例:断言默认关闭,并用启发值差值把「为什么必然出问题」固化下来。

测试 468 → 469,全绿。
这个提交包含在:
lyizai
2026-10-01 00:34:51 +08:00
父节点 c89f4f8f0f
当前提交 3a508f6818
共修改 6 个文件,包含 72 行新增和 8 行删除
+16 -2
查看文件
@@ -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 {
/**