diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6f65d..4918cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 +## [1.9.2] - 2026-10-01 + +修正物理录制器里一个会误导排查的分类错误。 + +### 修复 + +- **`frozenTicks` 把「被服务端推回」误判成「输入没进物理」**:原来只看 + 「有输入 + tick 级无位移 + 物理没报告碰撞」。但服务端推回发生在 tick 之间, + tick 级位移同样是 0 —— 实测大厅里就是这个形态(`collidedHorizontally = false`、 + `frozenTicks = 194`,而物理其实每 tick 都在动)。 + 现在再按**水平速度是否非零**细分: + - `pushedBackTicks`:速度非零 → 物理确实动了,位移被服务端位置包抹掉; + - `frozenTicks`:速度也是 0 → 这才是「输入根本没进物理」。 + +### 测试 + +新增 1 个用例(469 → 470)覆盖「被推回」形态。 + ## [1.9.1] - 2026-10-01 **修复「寻路器完全规划不出路径」** —— 这是自动扩地跑不起来的直接原因。 diff --git a/package-lock.json b/package-lock.json index 4c89384..f483be2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "autostore-bot", - "version": "1.9.1", + "version": "1.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autostore-bot", - "version": "1.9.1", + "version": "1.9.2", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index 288e15f..942d5ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autostore-bot", - "version": "1.9.1", + "version": "1.9.2", "description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)", "main": "src/index.js", "scripts": { diff --git a/src/core/physicsRecorder.js b/src/core/physicsRecorder.js index add1169..201d7f0 100644 --- a/src/core/physicsRecorder.js +++ b/src/core/physicsRecorder.js @@ -14,9 +14,10 @@ * | --- | --- | * | `control.forward = true` 但速度恒为 0、位置不动、`collidedHorizontally = false` | **输入没进物理**(applyHeading 没被调用 / control 快照是空的) | * | 速度非 0 但位置不动、`collidedHorizontally = true` | **被方块挡住**(地形问题,或脚下/身旁方块读错) | - * | 速度与位置都在变,但服务端把位置拉回 | **服务端回弹**(此时才轮到反作弊那条线) | + * | 速度非 0、位置不动、`collidedHorizontally = false` | **被服务端推回**(推回发生在 tick 之间,所以 tick 级位移为 0) | * - * 所以 `isCollidedHorizontally` 是这里最关键的一个字段。 + * 所以 `isCollidedHorizontally` 与「速度是否非零」是这里最关键的两个字段: + * 前者把「被挡住」与其余情况分开,后者把「输入没生效」与「被推回」分开。 */ /** 默认保留多少个 tick(20 TPS 下约 12 秒) */ @@ -143,11 +144,16 @@ class PhysicsRecorder { const all = this.ticks; const hasInput = (t) => t.control.forward || t.control.back || t.control.left || t.control.right; const hasMoved = (t) => Math.abs(t.dx) > MOVE_EPSILON || Math.abs(t.dz) > MOVE_EPSILON; + const hasHorizVelocity = (t) => Math.hypot(t.vx, t.vz) > MOVE_EPSILON; const inputTicks = all.filter(hasInput); const inputMoved = inputTicks.filter(hasMoved); const blocked = inputTicks.filter((t) => !hasMoved(t) && t.collidedHorizontally); - const frozen = inputTicks.filter((t) => !hasMoved(t) && !t.collidedHorizontally); + // 有输入、无位移、物理也没报告碰撞时,还要看速度: + // 速度非零说明物理确实动了,只是位移被服务端的位置包抹掉了(推回发生在 tick 之间); + // 速度也是 0 才是「输入根本没进物理」。 + const pushedBack = inputTicks.filter((t) => !hasMoved(t) && !t.collidedHorizontally && hasHorizVelocity(t)); + const frozen = inputTicks.filter((t) => !hasMoved(t) && !t.collidedHorizontally && !hasHorizVelocity(t)); let ticks = all; const limit = Number.isFinite(opts.limit) && opts.limit > 0 ? Math.floor(opts.limit) : all.length; @@ -169,7 +175,15 @@ class PhysicsRecorder { inputMovedTicks: inputMoved.length, /** 有输入、没位移、且物理报告水平碰撞:被方块挡住 */ blockedTicks: blocked.length, - /** 有输入、没位移、且物理没报告碰撞:输入根本没生效 */ + /** + * 有输入、没位移、物理没报告碰撞、但**水平速度非零**: + * 物理确实在动,位移却被服务端的位置包抹掉了(实测大厅里就是这个形态)。 + */ + pushedBackTicks: pushedBack.length, + /** + * 有输入、没位移、物理没报告碰撞、**水平速度也是 0**: + * 这才是「输入根本没进物理」(applyHeading 没被调用 / control 快照为空)。 + */ frozenTicks: frozen.length, movedTicks: all.filter(hasMoved).length, maxHorizDelta: Number(maxHoriz.toFixed(ROUND_DIGITS)), diff --git a/test/physicsRecorder.test.js b/test/physicsRecorder.test.js index 59fe741..4e458db 100644 --- a/test/physicsRecorder.test.js +++ b/test/physicsRecorder.test.js @@ -91,6 +91,24 @@ test('有输入但没位移且物理没报告碰撞 → 记为「输入没进物 assert.strictEqual(snap.blockedTicks, 0, '没报告碰撞就不算「被挡住」'); }); +test('有输入、没位移、没碰撞,但水平速度非零 → 记为「被服务端推回」', () => { + // 实测形态:大厅里物理每 tick 都在动,位移却被服务端的位置包抹掉 + // (推回发生在 tick 之间,所以 tick 级位移为 0、也没有方块碰撞) + const { bot, recorder } = setup(); + recorder.start(); + bot.setControlState('forward', true); + bot.entity.velocity.set(0, -0.0784, 0.02319); + tick(bot, { isCollidedHorizontally: false }); + tick(bot, { isCollidedHorizontally: false }); + + const snap = recorder.snapshot(); + assert.strictEqual(snap.inputTicks, 2); + assert.strictEqual(snap.inputMovedTicks, 0); + assert.strictEqual(snap.pushedBackTicks, 2); + assert.strictEqual(snap.frozenTicks, 0, '速度非零就不该算「输入没进物理」'); + assert.strictEqual(snap.blockedTicks, 0); +}); + test('没有输入时不计入 inputTicks', () => { const { bot, recorder } = setup(); recorder.start();