From 2ae28335684c5275822b9e6d2883c45af8c297c2 Mon Sep 17 00:00:00 2001 From: lyizai <3945107889@qq.com> Date: Thu, 1 Oct 2026 17:04:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A1=A5=E4=B8=8A=E5=87=BA=E7=AB=99?= =?UTF-8?q?=E5=8C=85=E4=B8=80=E7=9B=B4=E6=BC=8F=E6=8A=A5=E7=9A=84=20hasHor?= =?UTF-8?q?izontalCollision=EF=BC=881.10.0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 针对「机器人一直在走、净位移却只有物理速度的 1/5」这个现象。 mineflayer 在 physics.js 里把 flags.hasHorizontalCollision 写成 undefined (源码注释还标着「1.21.3+」),位标志序列化后恒为 0 —— 而原版客户端在水平碰到 方块时会上报 1。机器人在崎岖地形上几乎一直在水平碰撞,却始终上报「没碰撞」, 正是反作弊 Prediction 最容易判错的形态:服务端按「该碰撞」预测、客户端说 「没碰撞」,于是被判异常并拉回。 物理引擎其实早已算出真实值(moveEntity 里给 entity.isCollidedHorizontally 赋值), 只是从没被发出去。现在在 _fixOutboundOnGround 里按真实值补上,由 grim.reportHorizontalCollision(默认开)控制。 实测依据(王城外山区,2 秒粒度采样 120 秒): - controls 几乎每 tick 都是 forward+sprint,velocity 约 0.2~3 格/秒; - 净位移只有 48.6 格 / 120 秒 = 0.405 格/秒,差额就是被服务端拉回的部分。 顺带把 _fixOutboundOnGround 拆成 _fixOnGroundFlag / _fixHorizontalCollisionFlag 两个职责单一的方法;控制台配置面板新增对应开关, packetStats.horizontalCollisionFixed 暴露补写次数。 测试 473 → 477。 --- CHANGELOG.md | 27 ++++++++++++++++++ package-lock.json | 4 +-- package.json | 2 +- src/bot/botManager.js | 44 +++++++++++++++++++++++++++-- src/config/expansionConfig.js | 14 ++++++++++ src/web/public/app.js | 1 + test/botManager.test.js | 52 +++++++++++++++++++++++++++++++++++ 7 files changed, 139 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e9532f..8f6f729 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 +## [1.10.0] - 2026-10-01 + +补上出站包一直漏报的 `hasHorizontalCollision`,针对「机器人一直在走、却只走出 +物理速度的 1/5」这个现象。 + +### 新增 + +- **`grim.reportHorizontalCollision`(默认开启)**:把物理引擎算出的 + `bot.entity.isCollidedHorizontally` 如实写进出站位置包的 + `flags.hasHorizontalCollision`。 + - mineflayer 在 `physics.js` 里把这个位标志写成 `undefined`(源码注释还标着 + 「1.21.3+」),序列化后**恒为 0** —— 而原版客户端在水平碰到方块时会上报 1; + - 机器人在崎岖地形上几乎一直在水平碰撞,却始终上报「没碰撞」, + 正是反作弊 Prediction 最容易判错的形态:服务端按「该碰撞」预测、 + 客户端说「没碰撞」,于是被判异常并拉回; + - 实测(2026-10-01,王城外山区):机器人 `controls` 几乎每 tick 都是 + `forward+sprint`,`velocity` 约 0.2~3 格/秒,但 2 秒粒度采样的**净位移只有 + 0.405 格/秒**(48.6 格 / 120 秒)—— 差额正是被服务端拉回的部分。 + - 物理引擎早已算出真实值(`moveEntity` 里赋值),只是从没被发出去。 + - 控制台「Grim 合规」配置面板新增对应开关;`packetStats.horizontalCollisionFixed` + 暴露补写次数。 + +### 测试 + +新增 4 个用例(473 → 477):按真实值补写、已一致时不重复计数、开关可关闭、 +物理引擎没给出状态时不瞎猜。 + ## [1.9.3] - 2026-10-01 **掉线重连后自动恢复扩地任务** —— 这是让扩地能真正连续跑起来的最后一环。 diff --git a/package-lock.json b/package-lock.json index 7fa7c2f..27257db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "autostore-bot", - "version": "1.9.3", + "version": "1.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autostore-bot", - "version": "1.9.3", + "version": "1.10.0", "license": "MIT", "dependencies": { "chalk": "^4.1.2", diff --git a/package.json b/package.json index fc70ed8..d360b98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "autostore-bot", - "version": "1.9.3", + "version": "1.10.0", "description": "AutoStore Bot —— Minecraft 自动扩地机器人(mineflayer 重写版,多机器人挂机,Web 控制台,开拓日志,大地图,Grim 反作弊适配)", "main": "src/index.js", "scripts": { diff --git a/src/bot/botManager.js b/src/bot/botManager.js index ce8c3d0..8b494e6 100644 --- a/src/bot/botManager.js +++ b/src/bot/botManager.js @@ -181,6 +181,8 @@ class BotManager extends EventEmitter { lastOutboundAt: 0, /** 出站包中 onGround 被世界数据纠正的次数 */ onGroundFixed: 0, + /** 出站包中 hasHorizontalCollision 被物理引擎真实值补上的次数 */ + horizontalCollisionFixed: 0, /** 【方案 A】物理引擎的 onGround 被世界数据强制同步为 true 的次数 */ physicsGroundSync: 0, /** 【方案 B】保住本地 onGround、不采纳例行同步的位置包次数 */ @@ -839,14 +841,52 @@ class BotManager extends EventEmitter { */ _fixOutboundOnGround(params) { const grim = this.config && this.config.grim; - if (!grim || grim.reportOnGroundFromWorld !== true) return; - if (!params || !params.flags || typeof params.flags.onGround !== 'boolean') return; + if (!grim) return; + if (!params || !params.flags) return; + this._fixOnGroundFlag(grim, params); + this._fixHorizontalCollisionFlag(grim, params); + } + + /** + * 用世界数据复核出站包的 `flags.onGround`。 + * + * @param {object} grim + * @param {object} params 出站包内容 + */ + _fixOnGroundFlag(grim, params) { + if (grim.reportOnGroundFromWorld !== true) return; + if (typeof params.flags.onGround !== 'boolean') return; const actual = this._isStandingOnGround(); if (actual === null || params.flags.onGround === actual) return; params.flags.onGround = actual; this.packetStats.onGroundFixed++; } + /** + * 补上出站包的 `flags.hasHorizontalCollision`。 + * + * mineflayer 在 `physics.js` 里把它写成 `undefined`(源码注释还写着「1.21.3+」), + * 位标志序列化后**恒为 0** —— 而原版客户端在水平碰到方块时会上报 1。 + * 机器人在崎岖地形上几乎一直在水平碰撞,却始终上报「没碰撞」, + * 正是 Grim 的 Prediction 最容易判错的形态:服务端按「该碰撞」预测, + * 客户端说「没碰撞」,于是被判定异常并拉回。 + * + * 物理引擎(prismarine-physics)其实已经算出了真实值 + * (`bot.entity.isCollidedHorizontally`,见 `moveEntity`),只是从没被发出去。 + * 这里把它如实补上。 + * + * @param {object} grim + * @param {object} params 出站包内容 + */ + _fixHorizontalCollisionFlag(grim, params) { + if (grim.reportHorizontalCollision !== true) return; + const entity = this.bot && this.bot.entity; + if (!entity || typeof entity.isCollidedHorizontally !== 'boolean') return; + if (params.flags.hasHorizontalCollision === entity.isCollidedHorizontally) return; + params.flags.hasHorizontalCollision = entity.isCollidedHorizontally; + this.packetStats.horizontalCollisionFixed++; + } + /** * 发送一条「需要人工介入」的通知(验证码 / 自救失败)。 * diff --git a/src/config/expansionConfig.js b/src/config/expansionConfig.js index cbe05de..a707935 100644 --- a/src/config/expansionConfig.js +++ b/src/config/expansionConfig.js @@ -256,6 +256,20 @@ function defaultConfig() { * 开启后发包前会用脚下方块复核,只有确实不一致时才改写。 */ reportOnGroundFromWorld: true, + /** + * 是否把物理引擎的 `isCollidedHorizontally` 如实写进出站包的 + * `flags.hasHorizontalCollision`(默认开启)。 + * + * mineflayer 在 `physics.js` 里把这个位标志写成 `undefined`,序列化后**恒为 0** + * —— 而原版客户端在水平碰到方块时会上报 1。机器人在崎岖地形上几乎一直在 + * 水平碰撞,却始终上报「没碰撞」,正是反作弊 Prediction 最容易判错的形态: + * 服务端按「该碰撞」预测,客户端说「没碰撞」,于是被判异常并拉回。 + * 实测(2026-10-01)机器人一直按着 forward+sprint,物理速度约 2 格/秒, + * 但净位移只有 0.4~0.5 格/秒,差额正是被服务端拉回的部分。 + * + * 物理引擎已经算出了真实值(`bot.entity.isCollidedHorizontally`),只是没发出去。 + */ + reportHorizontalCollision: true, /** * 【方案 A】是否把物理引擎的 onGround 与本地世界数据对齐(默认开启)。 * diff --git a/src/web/public/app.js b/src/web/public/app.js index dc253b4..6b702fc 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -323,6 +323,7 @@ const CONFIG_SCHEMA = { { path: 'grim.checkPhysicsIntegrity', label: '启动物理参数自检', type: 'checkbox' }, { path: 'grim.blockActionsWhileRiding', label: '骑乘时禁止攻击 / 进食', type: 'checkbox' }, { path: 'grim.reportOnGroundFromWorld', label: '按世界数据修正 onGround', type: 'checkbox', hint: '服务端频繁下发位置包时,mineflayer 会持续上报「站地上却在空中」而被反作弊每 tick 拉回;开启后在发包前用脚下方块复核(实测本服必需)' }, + { path: 'grim.reportHorizontalCollision', label: '补上 hasHorizontalCollision', type: 'checkbox', hint: 'mineflayer 把这个位标志写成 undefined,序列化后恒为 0,而原版客户端碰到方块时会上报 1;开启后按物理引擎的真实值补上(实测本服机器人一直按着前进键、物理速度约 2 格/秒,净位移却只有 0.4~0.5 格/秒,差额就是被服务端拉回的部分)' }, { path: 'grim.syncGroundToPhysics', label: '物理引擎 onGround 对齐(方案 A)', type: 'checkbox', hint: '每个物理 tick 用世界数据复核脚下是否有方块,是则把物理引擎的 onGround 同步为 true,让移动走地面分支(TODO 问题 1 的疑似根因)' }, { path: 'grim.trustSetbackOnGround', label: '不采纳例行同步的 onGround(方案 B)', type: 'checkbox', hint: '服务端下发的位置与本地几乎一致时,只同步坐标、不采纳它的「在空中」,避免物理引擎的地面状态被反复打断' }, { path: 'grim.setbackSyncEpsilon', label: '方案 B 判定阈值(格)', type: 'number', step: 0.05, min: 0, max: 5, hint: '位置差不超过它时视为例行同步;超过则按真正的传送处理。先用默认值实测,再看状态卡里的「最近同步差」调整' }, diff --git a/test/botManager.test.js b/test/botManager.test.js index 2a1f7f4..e15896e 100644 --- a/test/botManager.test.js +++ b/test/botManager.test.js @@ -476,6 +476,58 @@ test('出站包结构异常时不抛异常', () => { assert.doesNotThrow(() => manager._fixOutboundOnGround({ flags: {} })); }); +test('补上 hasHorizontalCollision:mineflayer 恒写 undefined(序列化后为 0)', () => { + // 回归:原版客户端碰到方块时上报 1,而 mineflayer 在 physics.js 里把它写成 + // undefined —— 位标志序列化后恒为 0。机器人在崎岖地形上几乎一直水平碰撞, + // 却始终上报「没碰撞」,反作弊的 Prediction 会按「该碰撞」预测后判定异常并拉回。 + const { manager } = setup(); + const bot = botStandingOnStone(); + bot.entity.isCollidedHorizontally = true; + manager.bot = bot; + + const params = { flags: { onGround: true, hasHorizontalCollision: undefined } }; + manager._fixOutboundOnGround(params); + assert.strictEqual(params.flags.hasHorizontalCollision, true, '应按物理引擎的真实值补上'); + assert.strictEqual(manager.packetStats.horizontalCollisionFixed, 1); + + // 已一致时不重复计数 + manager._fixOutboundOnGround(params); + assert.strictEqual(manager.packetStats.horizontalCollisionFixed, 1); +}); + +test('没有水平碰撞时如实上报 false', () => { + const { manager } = setup(); + const bot = botStandingOnStone(); + bot.entity.isCollidedHorizontally = false; + manager.bot = bot; + const params = { flags: { onGround: true, hasHorizontalCollision: undefined } }; + manager._fixOutboundOnGround(params); + assert.strictEqual(params.flags.hasHorizontalCollision, false); + assert.strictEqual(manager.packetStats.horizontalCollisionFixed, 1); +}); + +test('关闭 reportHorizontalCollision 后不碰这个标志', () => { + const { manager } = setup({ config: { grim: { reportHorizontalCollision: false } } }); + const bot = botStandingOnStone(); + bot.entity.isCollidedHorizontally = true; + manager.bot = bot; + const params = { flags: { onGround: true, hasHorizontalCollision: undefined } }; + manager._fixOutboundOnGround(params); + assert.strictEqual(params.flags.hasHorizontalCollision, undefined); + assert.strictEqual(manager.packetStats.horizontalCollisionFixed, 0); +}); + +test('物理引擎没给出碰撞状态时不瞎猜', () => { + const { manager } = setup(); + const bot = botStandingOnStone(); + delete bot.entity.isCollidedHorizontally; + manager.bot = bot; + const params = { flags: { onGround: true, hasHorizontalCollision: undefined } }; + manager._fixOutboundOnGround(params); + assert.strictEqual(params.flags.hasHorizontalCollision, undefined); + assert.strictEqual(manager.packetStats.horizontalCollisionFixed, 0); +}); + test('位置包统计暴露在 snapshot 中,便于排查走不动的问题', () => { const { manager } = setup(); const snap = manager.snapshot();