feat: 补上出站包一直漏报的 hasHorizontalCollision(1.10.0)

针对「机器人一直在走、净位移却只有物理速度的 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。
这个提交包含在:
lyizai
2026-10-01 17:04:25 +08:00
父节点 ed55308885
当前提交 2ae2833568
共修改 7 个文件,包含 139 行新增和 5 行删除
+42 -2
查看文件
@@ -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++;
}
/**
* 发送一条「需要人工介入」的通知(验证码 / 自救失败)。
*
+14
查看文件
@@ -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 与本地世界数据对齐(默认开启)。
*
+1
查看文件
@@ -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: '位置差不超过它时视为例行同步;超过则按真正的传送处理。先用默认值实测,再看状态卡里的「最近同步差」调整' },