diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b46cd..3c20885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ 本项目遵循 [约定式提交](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 **修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完 diff --git a/package-lock.json b/package-lock.json index ac02467..2f37252 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "autostore-bot", - "version": "1.11.0", + "version": "1.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autostore-bot", - "version": "1.11.0", + "version": "1.11.1", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index d9bffe2..956e8f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autostore-bot", - "version": "1.11.0", + "version": "1.11.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 f6da70e..11c6efc 100644 --- a/src/bot/pathingService.js +++ b/src/bot/pathingService.js @@ -25,9 +25,18 @@ const MAX_SEGMENT_SHRINK = 3; * - 大幅提高进入液体格的代价,优先绕开水域(mineflayer 液体物理与原版差异最大) */ +/** + * 「寻路状态」日志的最小间隔(毫秒):进度日志最多这么频繁地记一条。 + * + * mineflayer-pathfinder 每用满一个 tick 的计算预算就发一次 `path_update`, + * 不节流的话实测 4 分钟能写 3893 条,把真正有用的日志全淹了。 + */ +const PATH_LOG_MIN_INTERVAL_MS = 2000; +/** 「寻路状态」日志的节点数增量阈值:探索推进这么多节点才值得再记一条 */ +const PATH_LOG_NODE_STEP = 1000; + /** 路径事件枚举,语义对齐 Baritone 的 PathEvent */ -const PathEvent = { - CALC_STARTED: 'CALC_STARTED', +const PathEvent = { CALC_STARTED: 'CALC_STARTED', CALC_FINISHED_NOW_EXECUTING: 'CALC_FINISHED_NOW_EXECUTING', CALC_FAILED: 'CALC_FAILED', AT_GOAL: 'AT_GOAL', @@ -57,6 +66,10 @@ class PathingService { this._pathing = false; this._lastStatus = null; this._configured = false; + /** 「寻路状态」日志的节流状态:上次记的状态 / 时间 / 节点数 */ + this._lastLoggedStatus = null; + this._lastPathLogAt = 0; + this._lastLoggedNodes = 0; /** 事件监听是否已注册(configure 会被反复调用,不能重复注册) */ this._listenersBound = false; /** @@ -134,12 +147,7 @@ class PathingService { bot.on('goal_reached', () => this._onSegmentReached()); bot.on('path_update', (r) => { this._lastStatus = r.status; - // 完整记录寻路器状态:长距离寻路卡住时,只看成功 / 失败是查不出原因的 - 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) : '无'}`); + this._logPathProgress(r); if (r.status === 'success') { this.calcFailed = false; this._emit(PathEvent.CALC_FINISHED_NOW_EXECUTING, { cost: r.cost, nodes: r.visitedNodes }); @@ -183,6 +191,40 @@ class PathingService { 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 新配置 diff --git a/test/pathingService.test.js b/test/pathingService.test.js index f0ff31d..a74827b 100644 --- a/test/pathingService.test.js +++ b/test/pathingService.test.js @@ -50,6 +50,38 @@ test('高度偏好默认关闭:它的启发值会破坏 partial 路径的终 `高处节点的启发值 ${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 格),