feat: 1.7.0 落地 TODO 全部待办(地面修正 / 手动执行器 / 自救 / 回城 / 租约 / 通知)
按 TODO.md 的优先级把待办全部落地为代码,详见 CHANGELOG 的 [1.7.0]。 - 「走不动」攻关:地面状态修正(syncGroundToPhysics / trustSetbackOnGround)、 出站包类型分布、备选执行器 manualMover(只借 A* 规划、自己按键执行) - 原点自救分两轮并统计成败;回城前评估「原地是否更差」并记录前后坐标 - 新增目标区块租约表(targetLease)避免多机器人撞同一块 - 定时消息支持 cron 与「期望回复」确认;新增人工介入 Webhook 通知(notifier) - 大地图后台预热与视野裁剪;控制台日志按帧批量写入、配置面板支持搜索 顺带修掉两个影响部署的细节: - 部署脚本(deploy.js / ssh-helper.js)改为也能从 .env 读 SSH 凭据 —— 在命令行传密码会留在 shell 历史与进程列表里,写进已被忽略的 .env 更安全; 显式环境变量仍然优先。.env.example 补上占位符并说明这些只在本机用。 - package.json / package-lock.json 版本对齐到 1.7.0(此前停留在 1.1.0, 与 CHANGELOG 的语义化版本声明脱节)。 测试:368 → 436 个用例,`npm test` 与 `npm run check` 全绿。
这个提交包含在:
@@ -218,3 +218,65 @@ test('配置里 zoom 大于 maxZoom 时被钳制(避免爬到源站不存在
|
||||
assert.strictEqual(normalized.bigMap.zoom, 3);
|
||||
assert.strictEqual(service.maxZoom, 3);
|
||||
});
|
||||
|
||||
// ==================== 后台预热(机器人移动后顺带补高层级瓦片)====================
|
||||
|
||||
test('prewarmAround 只预热比「启动预爬取层级」更高的层级', async () => {
|
||||
const { service, calls } = setup({ bigMap: { zoom: 0, prewarmLevels: 2, prewarmRangeBlocks: 512 } });
|
||||
const result = await service.prewarmAround(0, 0);
|
||||
assert.strictEqual(result.skipped, undefined, '首次调用应真正预热');
|
||||
assert.strictEqual(result.minZoom, 1);
|
||||
assert.strictEqual(result.maxZoom, 2, '默认只预热上面两层');
|
||||
assert.ok(result.done > 0);
|
||||
for (const url of calls) {
|
||||
assert.ok(/\/tiles\/minecraft_overworld\/[12]\//.test(url), '不应预热层级 0(批量爬取负责):' + url);
|
||||
}
|
||||
});
|
||||
|
||||
test('prewarmAround 在机器人没怎么移动时跳过(避免频繁请求源站)', async () => {
|
||||
const { service, calls } = setup({ bigMap: { prewarmMinMoveBlocks: 512, prewarmIntervalMs: 600000 } });
|
||||
await service.prewarmAround(1000, 1000);
|
||||
const firstCalls = calls.length;
|
||||
const again = await service.prewarmAround(1000, 1000);
|
||||
assert.strictEqual(again.skipped, 'recent');
|
||||
assert.strictEqual(calls.length, firstCalls, '跳过时不应再发起任何下载');
|
||||
|
||||
// 移动够远之后应该重新预热
|
||||
const moved = await service.prewarmAround(2000, 2000);
|
||||
assert.strictEqual(moved.skipped, undefined);
|
||||
assert.ok(calls.length > firstCalls);
|
||||
});
|
||||
|
||||
test('prewarmAround 可关闭,且批量爬取在途时让路', async () => {
|
||||
const { service } = setup({ bigMap: { prewarm: false } });
|
||||
assert.strictEqual((await service.prewarmAround(0, 0)).skipped, 'disabled');
|
||||
|
||||
const second = setup().service;
|
||||
second.crawl.running = true;
|
||||
assert.strictEqual((await second.prewarmAround(0, 0)).skipped, 'busy');
|
||||
});
|
||||
|
||||
test('prewarmAround 覆盖半径越大、层级越多,需要的瓦片越多', async () => {
|
||||
const { service: small } = setup({ bigMap: { prewarmLevels: 1, prewarmRangeBlocks: 256 } });
|
||||
const smallResult = await small.prewarmAround(0, 0);
|
||||
const { service: big } = setup({ bigMap: { prewarmLevels: 2, prewarmRangeBlocks: 2048 } });
|
||||
const bigResult = await big.prewarmAround(0, 0);
|
||||
assert.ok(bigResult.total > smallResult.total, `大图幅应预热更多瓦片:${bigResult.total} vs ${smallResult.total}`);
|
||||
});
|
||||
|
||||
test('snapshot 暴露预热状态供控制台展示', async () => {
|
||||
const { service } = setup();
|
||||
await service.prewarmAround(0, 0);
|
||||
const snapshot = service.snapshot();
|
||||
assert.ok(snapshot.prewarm);
|
||||
assert.strictEqual(snapshot.prewarm.running, false);
|
||||
assert.ok(snapshot.prewarm.done > 0);
|
||||
assert.strictEqual(snapshot.prewarm.lastCenterX, 0);
|
||||
});
|
||||
|
||||
test('prewarmLevels 为 0 时不预热(只做启动批量爬取)', async () => {
|
||||
const { service, calls } = setup({ bigMap: { prewarmLevels: 0 } });
|
||||
const result = await service.prewarmAround(0, 0);
|
||||
assert.strictEqual(result.skipped, 'no-levels');
|
||||
assert.strictEqual(calls.length, 0);
|
||||
});
|
||||
|
||||
在新工单中引用
屏蔽一个用户