fix: 给「寻路状态」进度日志加节流(1.11.1)

mineflayer-pathfinder 每用满一个 tick 的计算预算(tickTimeout,默认 40ms)就发一次
path_update,而原来的处理函数每次都记一条 —— 实测 4 分钟写了 3893 条(约 16 条/秒)。
既淹没了真正有用的日志(按 120 字节/行算一天能写上百 MB,而本服磁盘只剩 2.4G),
也让「寻路到底卡在哪」更难看清。

现在只在三种情况下记:
1. 状态发生变化(partial → success / noPath / timeout)—— 最要紧的信号;
2. 距上一条超过 2 秒;
3. 已探索节点数比上一条多出 1000 以上 —— 长规划里仍能看出推进速度,
   不丢定位卡死所需的信息。

测试 480 → 481。
这个提交包含在:
lyizai
2026-10-02 01:28:03 +08:00
父节点 6e420e13f0
当前提交 6ff45a48ae
共修改 5 个文件,包含 105 行新增和 11 行删除
+20
查看文件
@@ -2,6 +2,26 @@
本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。
## [1.11.1] - 2026-10-01
给「寻路状态」进度日志加节流 —— 它一度是日志体积的主要来源。
### 修复
- **「寻路状态」进度日志刷屏**:mineflayer-pathfinder 每用满一个 tick 的计算预算
(`tickTimeout`,默认 40ms)就发一次 `path_update`,而原来的处理函数**每次都记一条**。
实测 4 分钟写了 **3893 条**(约 16 条/秒)—— 既淹没了真正有用的日志
(按 120 字节/行算一天能写上百 MB,而本服磁盘只剩 2.4G),
也让「寻路到底卡在哪」更难看清。
现在只在三种情况下记:**状态发生变化**(partial → success / noPath / timeout,
最要紧的信号)、**距上一条超过 2 秒**、**已探索节点数比上一条多出 1000 以上**
(长规划里仍能看出推进速度,不丢定位卡死所需的信息)。
### 测试
新增 1 个用例(480 → 481):300 次 `partial` 被节流到十几条以内且不为零,
状态变化时立刻记录。
## [1.11.0] - 2026-10-01 ## [1.11.0] - 2026-10-01
**修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完 **修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完
自动生成
+2 -2
查看文件
@@ -1,12 +1,12 @@
{ {
"name": "autostore-bot", "name": "autostore-bot",
"version": "1.11.0", "version": "1.11.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "autostore-bot", "name": "autostore-bot",
"version": "1.11.0", "version": "1.11.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
+1 -1
查看文件
@@ -1,6 +1,6 @@
{ {
"name": "autostore-bot", "name": "autostore-bot",
"version": "1.11.0", "version": "1.11.1",
"description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)", "description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
+50 -8
查看文件
@@ -25,9 +25,18 @@ const MAX_SEGMENT_SHRINK = 3;
* - 大幅提高进入液体格的代价,优先绕开水域(mineflayer 液体物理与原版差异最大) * - 大幅提高进入液体格的代价,优先绕开水域(mineflayer 液体物理与原版差异最大)
*/ */
/**
* 「寻路状态」日志的最小间隔(毫秒):进度日志最多这么频繁地记一条。
*
* mineflayer-pathfinder 每用满一个 tick 的计算预算就发一次 `path_update`,
* 不节流的话实测 4 分钟能写 3893 条,把真正有用的日志全淹了。
*/
const PATH_LOG_MIN_INTERVAL_MS = 2000;
/** 「寻路状态」日志的节点数增量阈值:探索推进这么多节点才值得再记一条 */
const PATH_LOG_NODE_STEP = 1000;
/** 路径事件枚举,语义对齐 Baritone 的 PathEvent */ /** 路径事件枚举,语义对齐 Baritone 的 PathEvent */
const PathEvent = { const PathEvent = { CALC_STARTED: 'CALC_STARTED',
CALC_STARTED: 'CALC_STARTED',
CALC_FINISHED_NOW_EXECUTING: 'CALC_FINISHED_NOW_EXECUTING', CALC_FINISHED_NOW_EXECUTING: 'CALC_FINISHED_NOW_EXECUTING',
CALC_FAILED: 'CALC_FAILED', CALC_FAILED: 'CALC_FAILED',
AT_GOAL: 'AT_GOAL', AT_GOAL: 'AT_GOAL',
@@ -57,6 +66,10 @@ class PathingService {
this._pathing = false; this._pathing = false;
this._lastStatus = null; this._lastStatus = null;
this._configured = false; this._configured = false;
/** 「寻路状态」日志的节流状态:上次记的状态 / 时间 / 节点数 */
this._lastLoggedStatus = null;
this._lastPathLogAt = 0;
this._lastLoggedNodes = 0;
/** 事件监听是否已注册(configure 会被反复调用,不能重复注册) */ /** 事件监听是否已注册(configure 会被反复调用,不能重复注册) */
this._listenersBound = false; this._listenersBound = false;
/** /**
@@ -134,12 +147,7 @@ class PathingService {
bot.on('goal_reached', () => this._onSegmentReached()); bot.on('goal_reached', () => this._onSegmentReached());
bot.on('path_update', (r) => { bot.on('path_update', (r) => {
this._lastStatus = r.status; this._lastStatus = r.status;
// 完整记录寻路器状态:长距离寻路卡住时,只看成功 / 失败是查不出原因的 this._logPathProgress(r);
this._log(`寻路状态:${r.status}`
+ (r.visitedNodes !== undefined ? `(已探索 ${r.visitedNodes} 节点)` : '')
+ (r.cost !== undefined ? `(代价 ${r.cost})` : '')
+ `,本段目标 ${this.currentWaypoint ? Math.round(this.currentWaypoint.x) + ',' + Math.round(this.currentWaypoint.z) : '无'}`
+ `,最终目标 ${this.finalTarget ? Math.round(this.finalTarget.x) + ',' + Math.round(this.finalTarget.z) : '无'}`);
if (r.status === 'success') { if (r.status === 'success') {
this.calcFailed = false; this.calcFailed = false;
this._emit(PathEvent.CALC_FINISHED_NOW_EXECUTING, { cost: r.cost, nodes: r.visitedNodes }); this._emit(PathEvent.CALC_FINISHED_NOW_EXECUTING, { cost: r.cost, nodes: r.visitedNodes });
@@ -183,6 +191,40 @@ class PathingService {
return true; return true;
} }
/**
* 记录寻路进度(**带节流**)。
*
* mineflayer-pathfinder 每用满一个 tick 的计算预算(`tickTimeout`,默认 40ms)
* 就发一次 `path_update`。长距离规划时这会以**每秒十几到几十条**的频率刷屏 ——
* 实测 4 分钟写了 **3893 条**「寻路状态」,既淹没了真正有用的日志
* (按 120 字节/行算,一天能写上百 MB,本服磁盘本就只剩 2.4G),
* 也让「寻路到底卡在哪」更难看清。
*
* 所以只在下面三种情况下记一条:
* 1. 状态发生变化(partial → success / noPath / timeout)—— 这是最要紧的;
* 2. 距上一条超过 {@link PATH_LOG_MIN_INTERVAL_MS};
* 3. 已探索节点数比上一条多出 {@link PATH_LOG_NODE_STEP} 以上
* —— 长规划里仍能看出「探索推进得有多快」,不会丢掉定位卡死所需的信息。
*
* @param {{status: string, visitedNodes?: number, cost?: number}} r path_update 事件
*/
_logPathProgress(r) {
const now = Date.now();
const nodes = Number.isFinite(r.visitedNodes) ? r.visitedNodes : 0;
const statusChanged = r.status !== this._lastLoggedStatus;
const stale = now - this._lastPathLogAt >= PATH_LOG_MIN_INTERVAL_MS;
const progressed = nodes - this._lastLoggedNodes >= PATH_LOG_NODE_STEP;
if (!statusChanged && !stale && !progressed) return;
this._lastLoggedStatus = r.status;
this._lastPathLogAt = now;
this._lastLoggedNodes = nodes;
this._log(`寻路状态:${r.status}`
+ (r.visitedNodes !== undefined ? `(已探索 ${r.visitedNodes} 节点)` : '')
+ (r.cost !== undefined ? `(代价 ${r.cost})` : '')
+ `,本段目标 ${this.currentWaypoint ? Math.round(this.currentWaypoint.x) + ',' + Math.round(this.currentWaypoint.z) : '无'}`
+ `,最终目标 ${this.finalTarget ? Math.round(this.finalTarget.x) + ',' + Math.round(this.finalTarget.z) : '无'}`);
}
/** /**
* 配置变更后重新应用(不重建事件监听)。 * 配置变更后重新应用(不重建事件监听)。
* @param {object} config 新配置 * @param {object} config 新配置
+32
查看文件
@@ -50,6 +50,38 @@ test('高度偏好默认关闭:它的启发值会破坏 partial 路径的终
`高处节点的启发值 ${farButHigh} 不大于低处近节点的 ${nearButLow},正是问题所在`); `高处节点的启发值 ${farButHigh} 不大于低处近节点的 ${nearButLow},正是问题所在`);
}); });
test('寻路状态日志被节流:进度刷屏不会把日志写爆', () => {
// mineflayer-pathfinder 每用满一个 tick 的计算预算(tickTimeout,默认 40ms)
// 就发一次 path_update。不节流时实测 4 分钟写了 3893 条「寻路状态」,
// 既淹没真正有用的日志(一天上百 MB,本服磁盘本就只剩 2.4G),
// 也让「寻路卡在哪」更难看清。
const bot = new FakeBot({ x: 0.5, y: 64, z: 0.5 });
bot.pathfinder = {
setMovements: () => {},
setGoal: () => {},
thinkTimeout: 0,
tickTimeout: 0,
searchRadius: 0
};
const logs = [];
const logger = { log: (msg) => logs.push(String(msg)), error: () => {}, list: () => [], clear: () => {} };
const service = new PathingService(bot, defaultConfig(), logger);
service.configure();
// 模拟一次长规划:状态一直是 partial、节点数快速增长
for (let i = 0; i < 300; i++) {
bot.emit('path_update', { status: 'partial', visitedNodes: i * 20, cost: 90 });
}
const progressLogs = logs.filter((m) => m.includes('寻路状态'));
assert.ok(progressLogs.length >= 2, `不应把进度信息完全丢掉,实际 ${progressLogs.length} 条`);
assert.ok(progressLogs.length <= 12, `300 次 partial 应被节流到十几条以内,实际 ${progressLogs.length} 条`);
// 状态变化是最要紧的信号,必须立刻记一条
logs.length = 0;
bot.emit('path_update', { status: 'success', visitedNodes: 6100, cost: 90 });
assert.strictEqual(logs.filter((m) => m.includes('寻路状态')).length, 1, '状态变化要立刻记录');
});
// ==================== 长距离分段寻路 ==================== // ==================== 长距离分段寻路 ====================
// //
// mineflayer-pathfinder 只能规划已加载区块范围(实测线上约 8 区块 ≈ 130 格), // mineflayer-pathfinder 只能规划已加载区块范围(实测线上约 8 区块 ≈ 130 格),