feat: 新增出站包抓取,供「走不动」逐包对照(TODO 假设 5)
线上实测确认手动执行器(1.7.0 备选方案)不能解决「走不动」:前 20 秒走 13 格后 即卡在「执行中断(stuck)→重规划→缩短段长→跳过目标→重新规划同一目标」的死循环。 只靠 packetStats.outboundTypes 的计数看不出「发出去的内容对不对」,所以补一个 能录原始出站序列、并能在「能走」与「走不动」两种状态下 diff 的诊断能力。 新增 src/core/packetCapture.js: - 只在录制期间挂钩 client.write,其余时间零开销(该钩子每个出站包都要过一遍); - 录到的内容做降深 + 截断的 JSON 化处理,向量对象只留坐标 (move_minecart 这类包能带上万个浮点数,不处理会把缓冲区撑爆); - 只放内存、有上限(默认 4000 包),不写盘。 接口(控制台): - POST /api/bots/:id/capture/start(可带 seconds 到点自动停止) - POST /api/bots/:id/capture/stop - GET /api/bots/:id/capture(支持 ?types= 与 ?limit=) 顺带修掉两个先前就存在的问题: - scripts/check-protocol.js 其实一直跑不通:minecraft-data 的 types.packet[1][1].type[1].fields[包名] 给的是类型名字符串, 要再去 types[名字] 取 ['container', [字段...]] 才能展开,原实现直接 .map() 必然抛错; 另有读 node_modules 少一层 '..' 的路径问题。 - 两条租约用例(expansionController / targetLease)的时序余量只有 15ms, 全量并行跑测试时偶发失败;改为 TTL 400ms 配 280ms 睡眠,各留 120ms 余量, 断言内容不变。 测试:436 → 451 个用例,连续 16 轮全量运行全绿。
这个提交包含在:
@@ -11,6 +11,7 @@ const { AutoEater } = require('./autoEat');
|
||||
const { AutoBoatController } = require('./autoBoat');
|
||||
const { DurabilityGuard } = require('./durabilityGuard');
|
||||
const { ChatKind } = require('../core/chatLog');
|
||||
const { PacketCapture } = require('../core/packetCapture');
|
||||
|
||||
/** 服务器要求登录 / 注册时的提示关键词 */
|
||||
const LOGIN_PROMPT_PATTERN = /请(先)?(输入)?登录|请登录|请输入密码|login|登入/i;
|
||||
@@ -198,6 +199,14 @@ class BotManager extends EventEmitter {
|
||||
/** 每 tick 补发的 tick_end 次数 */
|
||||
tickEnd: 0
|
||||
};
|
||||
/**
|
||||
* 出站包抓取器(诊断用,默认不录制)。
|
||||
*
|
||||
* `packetStats.outboundTypes` 只统计「发了多少」,而反作弊拒绝移动时
|
||||
* 真正要看的是「发出去的内容对不对」,所以需要能录原始序列的工具,
|
||||
* 与「能走」时的序列逐包 diff(TODO.md 假设 5)。
|
||||
*/
|
||||
this.packetCapture = new PacketCapture();
|
||||
}
|
||||
|
||||
// ==================== 连接生命周期 ====================
|
||||
@@ -510,6 +519,8 @@ class BotManager extends EventEmitter {
|
||||
_wireEvents(bot) {
|
||||
this._installGroundStateFix(bot);
|
||||
this._wirePacketStats(bot);
|
||||
// 重连会换新的 client,这里每次都重新登记(只在录制期间才会真正挂钩 write)
|
||||
this.packetCapture.setClient(bot._client);
|
||||
|
||||
bot.on('login', () => {
|
||||
this.state = 'online';
|
||||
@@ -1193,6 +1204,43 @@ class BotManager extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始抓出站包(诊断用)。
|
||||
*
|
||||
* 典型用法:先在「机器人能走」时抓 5 秒,再在「走不动」时抓 5 秒,两次逐包 diff ——
|
||||
* 出站包内容上的差异(onGround / hasHorizontalCollision / player_input 位标志、
|
||||
* 位置与朝向是否合并成一次更新)比任何猜测都更能定位反作弊为什么拒绝移动。
|
||||
*
|
||||
* @param {number} [seconds] 录制时长(秒),省略则需手动停止
|
||||
* @returns {{ok: boolean, message: string}}
|
||||
*/
|
||||
startPacketCapture(seconds) {
|
||||
const result = this.packetCapture.start(seconds);
|
||||
if (result.ok) this._log('抓包:' + result.message);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止抓包(已录到的内容保留,可继续用 `packetCaptureSnapshot()` 取)。
|
||||
*
|
||||
* @returns {{ok: boolean, message: string}}
|
||||
*/
|
||||
stopPacketCapture() {
|
||||
const result = this.packetCapture.stop();
|
||||
this._log('抓包:' + result.message);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出抓包结果。
|
||||
*
|
||||
* @param {{types?: string|string[], limit?: number}} [options]
|
||||
* @returns {object}
|
||||
*/
|
||||
packetCaptureSnapshot(options) {
|
||||
return this.packetCapture.snapshot(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动移动测试:按住某个方向键若干秒,然后松开。
|
||||
*
|
||||
|
||||
@@ -596,6 +596,35 @@ class BotPool extends EventEmitter {
|
||||
return entry.manager.walkTest(direction, seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始抓出站包(诊断用)。
|
||||
* @param {string} id
|
||||
* @param {number} [seconds] 录制时长(秒),省略则需手动停止
|
||||
* @returns {{ok: boolean, message: string}}
|
||||
*/
|
||||
startCapture(id, seconds) {
|
||||
return this.require(id).manager.startPacketCapture(seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止抓包。
|
||||
* @param {string} id
|
||||
* @returns {{ok: boolean, message: string}}
|
||||
*/
|
||||
stopCapture(id) {
|
||||
return this.require(id).manager.stopPacketCapture();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出抓包结果。
|
||||
* @param {string} id
|
||||
* @param {{types?: string|string[], limit?: number}} [options]
|
||||
* @returns {object}
|
||||
*/
|
||||
captureSnapshot(id, options) {
|
||||
return this.require(id).manager.packetCaptureSnapshot(options);
|
||||
}
|
||||
|
||||
// ==================== 状态快照 ====================
|
||||
|
||||
/**
|
||||
|
||||
在新工单中引用
屏蔽一个用户