fix: 修掉 yaw 无界增长 —— 它让机器人原地走之字形、净位移恒为 0(1.11.0)

mineflayer 收到服务端 position 包时会直接 bot.entity.yaw = conv.fromNotchianYaw(…)
不做取模,而 GrimCompat 又在它基础上逐 tick 累加,两者叠加让 yaw 一路涨到
19254°(≈336 弧度)。

抓包实测的后果:
  yaw°:  -1373 → 19254 → -1650 → -1407 → -1373 → 19254 → …
  坐标:  (6757.50, 11953.70) ⇄ (6757.89, 11954.12)   0.4 格内反复振荡
相邻两包的朝向折合相差约 114°,而机器人一直按着前进键 ——
于是它原地走之字形、净位移恒为 0,看起来像「卡在墙前反复起跳」
(物理录制里 jump 74/240、水平碰撞 195/240、onGround 只有 79/240)。

这是扩地跑不完那 763 格长途的直接原因,也解释了出站包里 look 包(38490)
比 position_look(23804)还多。

修复:新增 wrapAngle,_onPhysicsTick 每 tick 归一化 entity.yaw 到 (-π, π],
setLook 也归一化传入的目标角度。归一化只改表示不改朝向
(shortestAngleDelta(huge, wrapped) === 0),对旋转逻辑完全透明。

测试 478 → 480。
这个提交包含在:
lyizai
2026-10-01 18:08:41 +08:00
父节点 841d20afef
当前提交 ab598a7101
共修改 5 个文件,包含 94 行新增和 6 行删除
+26
查看文件
@@ -8,6 +8,7 @@ const { FakeBot } = require('./helpers/fakeBot');
const {
GrimCompat,
shortestAngleDelta,
wrapAngle,
eyeDistanceToEntity,
VANILLA_PHYSICS_BASELINE
} = require('../src/bot/grimCompat');
@@ -111,6 +112,31 @@ test('shortestAngleDelta 走最短路径并处理环绕', () => { assert.ok(Mat
assert.ok(Math.abs(shortestAngleDelta(0, Math.PI * 2)) < 1e-9);
});
test('wrapAngle 把任意角度归一化到 (-π, π]', () => {
assert.ok(Math.abs(wrapAngle(0.5) - 0.5) < 1e-9, '范围内不动');
assert.ok(Math.abs(wrapAngle(Math.PI * 2) - 0) < 1e-9, '整圈归零');
// 实测抓到过 19254°(≈336 弧度)的 yaw —— 必须被归一化
const huge = (19254 * Math.PI) / 180;
const wrapped = wrapAngle(huge);
assert.ok(wrapped > -Math.PI && wrapped <= Math.PI, `归一化后应在 (-π, π],实际 ${wrapped}`);
assert.ok(Math.abs(shortestAngleDelta(huge, wrapped)) < 1e-9, '归一化不应改变朝向');
assert.strictEqual(wrapAngle(NaN), 0);
assert.strictEqual(wrapAngle(Infinity), 0);
});
test('旋转推进后 yaw 始终保持归一化(回归:无界增长导致原地走之字形)', () => {
// mineflayer 收到 position 包时会直接给 entity.yaw 赋一个未取模的值,
// 本层又在其上累加;不归一化的话 yaw 会一路涨到上万度,
// 发包朝向在两个方向间来回跳,机器人原地走之字形、净位移恒为 0。
const { grim, bot } = setup();
bot.entity.yaw = (19254 * Math.PI) / 180; // 未归一化的巨大值
grim.setLook(0.5, 0);
for (let i = 0; i < 40; i++) grim._onPhysicsTick();
assert.ok(bot.entity.yaw > -Math.PI && bot.entity.yaw <= Math.PI,
`yaw 应被归一化,实际 ${bot.entity.yaw}`);
assert.ok(Math.abs(shortestAngleDelta(bot.entity.yaw, 0.5)) < 1e-4, '最终仍应朝向目标');
});
test('eyeDistanceToEntity 计算眼睛到碰撞箱的最近距离', () => {
const bot = new FakeBot({ x: 0, y: 64, z: 0 });
// 目标在正前方 2 格,碰撞箱中心高度与眼睛同高