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