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 轮全量运行全绿。
这个提交包含在:
lyizai
2026-09-29 22:01:02 +08:00
父节点 51664c55b6
当前提交 8ab2d59eb0
共修改 14 个文件,包含 867 行新增和 62 行删除
+10 -4
查看文件
@@ -399,15 +399,21 @@ test('停止任务时归还租约,其他机器人立刻可以接手', async ()
});
test('运行中持续续租:租约不会在长距离寻路中途过期', async () => {
const table = new TargetLeaseTable({ ttlMs: 40 });
// 时序余量刻意放宽:这条用例要证明的是「续租把到期时间往后推了」,
// 即「总耗时 > 原始 TTL」但「距续租的耗时 < TTL」。
// 睡眠时长取 0.7 × TTL,两侧各留 0.3 × TTL(120ms)的调度余量 ——
// 早先用 TTL=40ms / 睡眠 25ms 只剩 15ms 余量,全量并行跑测试时必然偶发失败。
const ttlMs = 400;
const sleepMs = 280;
const table = new TargetLeaseTable({ ttlMs });
const { controller, config } = setup({ targetLeases: table, botId: 'BotA' });
await controller.start(config);
const target = controller.target;
await new Promise((resolve) => setTimeout(resolve, 25));
await new Promise((resolve) => setTimeout(resolve, sleepMs));
controller.tick(); // 续租
await new Promise((resolve) => setTimeout(resolve, 25));
// 已经超过原始 TTL(40ms),但续租过,仍应归 A
await new Promise((resolve) => setTimeout(resolve, sleepMs));
// 总耗时已超过原始 TTL(400ms),但续租过,仍应归 A
assert.strictEqual(table.tryAcquire(target, 'BotB'), false, '续租后不应被抢走');
});