# 更新日志 本项目遵循 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 与 [语义化版本](https://semver.org/lang/zh-CN/)。 ## [1.11.1] - 2026-10-01 给「寻路状态」进度日志加节流 —— 它一度是日志体积的主要来源。 ### 修复 - **「寻路状态」进度日志刷屏**:mineflayer-pathfinder 每用满一个 tick 的计算预算 (`tickTimeout`,默认 40ms)就发一次 `path_update`,而原来的处理函数**每次都记一条**。 实测 4 分钟写了 **3893 条**(约 16 条/秒)—— 既淹没了真正有用的日志 (按 120 字节/行算一天能写上百 MB,而本服磁盘只剩 2.4G), 也让「寻路到底卡在哪」更难看清。 现在只在三种情况下记:**状态发生变化**(partial → success / noPath / timeout, 最要紧的信号)、**距上一条超过 2 秒**、**已探索节点数比上一条多出 1000 以上** (长规划里仍能看出推进速度,不丢定位卡死所需的信息)。 ### 测试 新增 1 个用例(480 → 481):300 次 `partial` 被节流到十几条以内且不为零, 状态变化时立刻记录。 ## [1.11.0] - 2026-10-01 **修掉「机器人原地走之字形、净位移恒为 0」的朝向 bug** —— 这是扩地跑不完 那 763 格长途的直接原因。 ### 修复 - **`bot.entity.yaw` 无界增长,导致发包朝向在两个方向间来回跳**。 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)。 现在 `_onPhysicsTick` 每 tick 都调 `wrapAngle` 把 yaw 归一化到 `(-π, π]`, `setLook` 也会归一化传入的目标角度。归一化只改变表示、不改变朝向 (`shortestAngleDelta(huge, wrapped) === 0`),所以对旋转逻辑完全透明。 ### 测试 新增 2 个用例(478 → 480):`wrapAngle` 的归一化与边界(含 19254° 这个实测值、 NaN / Infinity),以及「从未归一化的巨大 yaw 出发、推进 40 tick 后仍归一化且朝向正确」。 ## [1.10.1] - 2026-10-01 修掉 1.9.3 自动恢复里的一个会让恢复永久丢失的缺陷。 ### 修复 - **`resumeConfig` 在尝试恢复之前就被清掉了**:`tick()` 里先取走待恢复的配置再调 `start()`,而 `start()` 在「已进入世界但还没完成登录」时会直接返回 false (`isReady()` 要求 `loggedIn`)。本服掉线频繁、重连后常常短暂处于这个状态, 于是**一次失败的尝试就把这次恢复永久丢掉了**,任务再也不会自己接上。 现在:① 等 `bm.isReady()` 才尝试;② 只有 `start()` 真的返回 true 才清掉登记, 失败时保留并在下一 tick 继续试(日志打「自动恢复未成功(…),稍后重试」)。 ### 测试 新增 1 个用例(477 → 478):未就绪时不启动也不丢登记,就绪后恢复并清登记。 ## [1.10.0] - 2026-10-01 补上出站包一直漏报的 `hasHorizontalCollision`,针对「机器人一直在走、却只走出 物理速度的 1/5」这个现象。 ### 新增 - **`grim.reportHorizontalCollision`(默认开启)**:把物理引擎算出的 `bot.entity.isCollidedHorizontally` 如实写进出站位置包的 `flags.hasHorizontalCollision`。 - mineflayer 在 `physics.js` 里把这个位标志写成 `undefined`(源码注释还标着 「1.21.3+」),序列化后**恒为 0** —— 而原版客户端在水平碰到方块时会上报 1; - 机器人在崎岖地形上几乎一直在水平碰撞,却始终上报「没碰撞」, 正是反作弊 Prediction 最容易判错的形态:服务端按「该碰撞」预测、 客户端说「没碰撞」,于是被判异常并拉回; - 实测(2026-10-01,王城外山区):机器人 `controls` 几乎每 tick 都是 `forward+sprint`,`velocity` 约 0.2~3 格/秒,但 2 秒粒度采样的**净位移只有 0.405 格/秒**(48.6 格 / 120 秒)—— 差额正是被服务端拉回的部分。 - 物理引擎早已算出真实值(`moveEntity` 里赋值),只是从没被发出去。 - 控制台「Grim 合规」配置面板新增对应开关;`packetStats.horizontalCollisionFixed` 暴露补写次数。 ### 测试 新增 4 个用例(473 → 477):按真实值补写、已一致时不重复计数、开关可关闭、 物理引擎没给出状态时不瞎猜。 ## [1.9.3] - 2026-10-01 **掉线重连后自动恢复扩地任务** —— 这是让扩地能真正连续跑起来的最后一环。 ### 新增 - **`autoResumeAfterReconnect`(默认开启)**:本服会周期性把机器人踢下线 (实测约 1~6 分钟一次,踢出原因是 `An internal error occurred in your connection`), 每次都会走到「世界已退出,任务停止」。原来的行为是就此停住 —— 机器人虽然能被**原点自救**送回真实世界(实测 4/4 成功),却再也不会继续扩地, 于是永远停在原地。 现在世界退出时会把本次任务的配置记下来,重新进入世界后自动 `start()` 接上。 手动停止 / 到开垦时段外 / 耐久保护停机**不会**被自动恢复(只有因世界退出而停的那次会登记)。 - 状态文案改为「世界已退出,等待重连后自动恢复」,日志会打出 「已重新进入世界,自动恢复扩地任务」。 ### 测试 新增 3 个用例(470 → 473):世界退出后暂停并在重连时恢复、开关可关闭、 手动停止不会被重连重新拉起。 ## [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 **修复「寻路器完全规划不出路径」** —— 这是自动扩地跑不起来的直接原因。 ### 修复 - **高度偏好(`altitudePreference`)默认关闭**:它的启发值实现与 mineflayer-pathfinder 的内部约定冲突。该库每次 `compute()` 返回 `partial` 时, 用 **`h` 最小**的节点当作 partial 路径的终点 (`astar.js`: `if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode`), 而 `AltitudeGoal` 把 `h` 减去了 `权重 × y`,于是「又高又偏」的节点胜出, partial 路径被引向高处而不是目标附近 —— 表现为 A* 一直在算、永不收敛, **机器人一次移动指令都发不出来**。 - 实测(王城平台上,其余配置完全相同):开启 `inputTicks = 0`、任务永远停在 「寻路超时,重新寻路」;关闭 `inputTicks = 237`、立刻开始移动。 - `searchRadius` 从 -1 改到 128 对此**没有影响**,所以不是搜索空间无界导致的。 - 想重新启用,应把偏置改成**低处加移动代价**(改 `getNeighbors` 的 `cost`), 保持启发值只反映到目标的真实距离。`AltitudeGoal` 的注释里记了完整推导。 ### 测试 新增 1 个回归用例(468 → 469):断言高度偏好默认关闭,并用启发值差值 把「为什么它必然出问题」固化下来。 ## [1.9.0] - 2026-09-29 补上诊断「走不动」的最后一块拼图:抓包只能看到「客户端发了什么」, 看不到物理引擎内部读到了什么输入、算出了什么、有没有真的移动。 新增**物理 tick 录制器**,与抓包配合就能把三种完全不同的原因一刀切开。 ### 新增 - **物理 tick 录制**(`src/core/physicsRecorder.js` + 控制台接口): - `POST /api/bots/:id/physics/start`(可带 `seconds`)、`/physics/stop`、 `GET /api/bots/:id/physics`(支持 `?limit=`); - 逐 tick 记录:位置增量、速度、`onGround`、**`isCollidedHorizontally`**、 控制键快照、脚下 / 脚部 / 头部方块名、`physicsEnabled`; - 汇总给出三个判定用的计数:`inputTicks`(有移动输入的 tick 数)、 `inputMovedTicks`(其中真的产生了水平位移的)、 `blockedTicks`(有输入、没位移、物理报告碰撞 → **被方块挡住**)、 `frozenTicks`(有输入、没位移、物理没报告碰撞 → **输入根本没进物理**); - 录制开始时先取一次当前位置作基线,避免第一个 tick 因「没有上一 tick 可比」 被误判成「有输入但没动」; - 滚动缓冲只保留最近 240 个 tick(约 12 秒),只放内存、不写盘。 ### 变更 - `FakeBot` 补上与 mineflayer 一致的 `controlState`(带 getter 的实时控制键代理)—— 那正是 mineflayer 传给物理引擎的那个对象。 ### 测试 新增 17 个用例(451 → 468):物理录制器的逐 tick 记录、三种故障判定 (被挡住 / 输入没生效 / 正常移动)、滚动缓冲、方块读不到时的容错, 以及控制台物理录制接口的 HTTP 用例。 ## [1.8.0] - 2026-09-29 把「走不动」的排查手段补成可复用的诊断能力:能按需录下机器人**真正发出的协议包内容**, 与「能走」时的序列逐包对照(TODO.md 问题 1 · 假设 5)。 ### 新增 - **出站包抓取**(`src/core/packetCapture.js` + 控制台接口): - `POST /api/bots/:id/capture/start`(可带 `seconds` 到点自动停止)、 `POST /api/bots/:id/capture/stop`、`GET /api/bots/:id/capture`(支持 `?types=` 与 `?limit=`); - **只在录制期间挂钩 `client.write`**,其余时间零开销 —— 这个钩子每个出站包都要过一遍; - 录到的内容做降深 + 截断的 JSON 化处理,向量对象只留坐标(`move_minecart` 这类包 能带上万个浮点数,不处理会把缓冲区撑爆); - 只放内存、有上限(默认 4000 包),不写盘。 ### 修复 - **`scripts/check-protocol.js` 其实一直跑不通**:`minecraft-data` 的 `types.packet[1][1].type[1].fields[包名]` 给出的是**类型名字符串**(如 `packet_position_look`), 必须再去 `types[名字]` 取 `['container', [字段...]]` 才能展开字段;原实现直接对它 `.map()`, 必然抛 `def.map is not a function`。顺带修掉读 `node_modules` 时少了一层 `..` 的路径。 - 修完即可确认:1.21.4 上 `position` / `position_look` / `look` 的地面标志是 `flags: MovementFlags`(`u8` 位标志 `[onGround, hasHorizontalCollision]`), 没有独立的 `onGround` 字段;`_fixOutboundOnGround` 改的正是正确的 `flags.onGround`。 - **两条租约用例的时序余量过窄,会偶发失败**:`ttlMs: 40` 配两次 `setTimeout(25)`, 只剩 15ms 调度余量,全量并行跑测试时会被压垮。改为 `ttlMs: 400` 配 280ms 睡眠, 两侧各留 120ms 余量;断言内容不变(仍是「总耗时 > 原始 TTL、距续租 < TTL」)。 ### 测试 新增 15 个用例(436 → 451):抓包器的录制 / 摘钩子 / 上限丢弃 / 按包名过滤 / 序列化边界,其中包含一条回归用例 —— `position_look` 的参数本身就是 `{x, y, z, yaw, pitch, onGround, flags}`,**不能**被「有 x/y/z 就当向量」的规则吞掉; 另有控制台抓包接口的 HTTP 用例。 ## [1.7.0] - 2026-09-29 按 `TODO.md` 的优先级把待办全部落地:先是「走不动」的攻关与备选方案, 然后是自救 / 回城 / 大地图 / 控制台 / 多机器人 / 定时消息 / 登录提醒。 ### 新增 - **地面状态修正(TODO 问题 1 · 假设 2 的方案 A / B)**: - 方案 A(`grim.syncGroundToPhysics`):每个物理 tick 在「物理引擎算完、位置包发出之前」 用世界数据复核脚下是否有实体方块,是则强制把 `bot.entity.onGround` 同步为 true, 让移动回到地面分支(此前一直被服务端位置包置为 false,物理引擎长期按空中处理); - 方案 B(`grim.trustSetbackOnGround` + `grim.setbackSyncEpsilon`):服务端下发的目标位置 与本地几乎一致(例行同步)时只采纳坐标、保留我们自己的 `onGround`,不采纳它的 false; - 两者都与 `grim.enabled` 无关;控制台「Grim 合规」卡片显示 `物理对齐 / 保住 / 最近同步差` 三个计数,便于判断修正是否真的生效。 - **备选移动执行器 `manualMover.js`**(`pathing.executor: 'manual'`): 只借用 mineflayer-pathfinder 的 A* **算路径**,执行改为自己按键 —— 朝路径点转向、朝向对上才按前进、遇一格坎跳一下、长时间无位移重新规划。 控制键时序与「移动测试」的手动按键完全一致,便于逐 tick 对照排查。 - **出站包类型分布**(`packetStats.outboundTypes`):用于与旧模组抓包逐项对照(假设 4)。 - **原点自救升级**(TODO 问题 2):分两轮(左右 → 前后 + 跳),每轮结束后等传送落地再判定 「位置是否恢复」;统计 `attempts / successes / lastResult`(暴露在 `/api/status` 与控制台), 两轮都失败时升级为明确提示(点明可能是登录服,需要人工发验证码)。 - **回城改进**(TODO 问题 3): - 回城前用 `pathing.diagnostics().neighborCount` 评估「原地是否真的更差」: 还能走就放弃回城、改为跳过目标(回城只会白白丢掉玩家摆好的位置); - 回城完成后记录前后坐标与直线距离,日志与控制台展示「回城把机器人从 A 挪到了 B」, 位置几乎没变时明确提示回城没有帮助。 - **大地图后台预热 + 视野裁剪**(TODO 4.1):机器人移动超过阈值(或距上次超过 10 分钟)时, 用低并发把周边高层级瓦片补下来(已缓存只做一次 `statSync`,批量爬取在途时让路); 领土多边形按包围盒裁剪,全世界视角不再全量绘制。 - **控制台**(TODO 4.2):日志按帧批量写入(一次布局、一次滚动), 并用 `content-visibility` 让浏览器跳过视口外行的渲染;配置面板新增搜索框 (按名称 / 字段名 / 说明过滤,隐藏不删除,保存不受影响)。 - **目标区块租约表**(TODO 4.3,`src/core/targetLease.js`): 多机器人抢同一区块时谁先抢到谁去,其余机器人跳过;持有者持续续租, 掉线 / 卡死后租约到期自动失效;候选区块全被预定时等 15 秒再规划,而不是提前判「没有可用区块」。 - **定时消息支持 cron 与发送结果确认**(TODO 4.4): 5 段 cron 表达式(支持步长 / 区间 / 列表 / 星期范围,非法表达式会说明原因并按间隔发送); 可填「期望回复」(正则)确认发送是否真的生效,超时未回复记 `⚠ 未确认` 并写警告。 - **人工介入提醒(Webhook)**(TODO 4.5,`src/core/notifier.js`): 等待验证码(120 秒时限)与原点自救失败时推送到飞书 / Lark 自定义机器人; 支持 `NOTIFY_WEBHOOK_URL` 兜底、按问题去重(默认 60 秒)、快照只暴露域名不泄露完整地址。 ### 变更 - `ExpansionController` 挑选目标、跳过目标、停止任务、完成任务时统一维护租约; - `WebConsole` 增加低频(1 分钟)大地图预热检查,停止时一并清理定时器; - 定时消息列表与配置面板同步支持 cron / 期望回复字段。 ### 测试 新增 68 个用例(368 → 436),覆盖地面状态修正、手动执行器、原点自救判定、 回城评估与前后坐标、目标租约、cron 解析与确认、通知器、大地图预热。 ## [1.6.0] - 2026-09-29 围绕「机器人为什么走不动」做了一轮系统排查,并给破坏性操作加上护栏。 ### 新增 - **回城护栏**:`/u return` 是破坏性操作 —— 它把机器人拉回安全点, 会**清掉玩家在游戏客户端里手动摆好的位置**。项目里曾因此反复把机器人 从玩家摆好的位置拉走,排查了很久才定位。现在: - `returnOnTimeout` **默认关闭**,不再自动回城 - 即便打开,`returnGraceSeconds`(默认 300 秒)内也不执行, 避免把刚摆好的位置清掉 - `returnCommand` 可配置(默认 `u return`),适配不同服务器 - 每次回城都明确记日志,说明「会丢失当前位置与玩家手动摆好的位置」 - 因未启用而跳过时也会记一条说明,避免用户以为「怎么不回城了」 - **区分「被传送到原点」的两种原因**(以前在日志里长得一样,常被误判成机器人坏了): - **服务器要求输入验证码** → 被切到登录服,必须人工发验证码,给出明确提示 - **服务器没要求验证码** → 死亡后被卡住,**自动左右移动触发服务器重新传送**(`_escapeFromOrigin()`) - ⚠️ 这两种情况**脚下都是基岩**,所以不能用「脚下是不是基岩」区分; 唯一可靠信号是服务器有没有发验证码提示。自救在两种情况下都无害, 因此即使判断错了也不会让情况变糟 - **移动执行诊断 `moveDiag`**:暴露当前按下的控制键、速度、寻路器内部状态 (`isMoving` / `isMining` / `goal` / 剩余路径点数)。这是排查「走不动」的关键手段 —— 能一刀切开「寻路器没执行」与「执行了但服务端不认」。 - **寻路诊断 `pathingDiag`**:起点可走邻居数、寻路器能否通过 `world.getBlock` 读到方块、 四向可见距离。 ### 变更 - `_isStandingOnGround()` 增加「服务端是否正把我们按在原地」的判断 (`_serverPinsUs()`):短时间内反复收到几乎相同位置的位置包时, 说明在服务端眼里机器人是站着的,此时以服务端为准更安全 ### 排查结论(已排除的方向,详见 `TODO.md`) - ❌ **`GrimCompat` 的朝向限速不是原因**:A/B 实测关闭合规层后回弹频率没有变化 (6.0 vs 5.5 次/秒,位移都是 0) - ❌ **疾跑不是原因**:关闭后仍是 4.8 次/秒 - ❌ **世界数据不是原因**:实测脚下 `stone`,`blockBelow` 正确 - 🎯 **关键线索**:寻路时 `forward`/`sprint` 确实按下、`isMoving=true`, 但**水平速度恒为 0**(`velocity.y = -0.078` 恰是自由落体第一 tick 的值), 说明速度每 tick 被清零;同时 `bot.entity.onGround` 被服务端位置包反复置为 false, 物理引擎一直按「空中」处理。下一步应从「让 `entity.onGround` 与出站包一致」入手。 ## [1.5.1] - 2026-09-28 接入 API-V2 密钥后按实测响应修正字段名(之前是按文档猜的,跑起来全不对)。 ### 修复 - **API-V2 `/players` 返回的是对象不是数组**:`{"online":8,"max":5000,"players":[...]}`, 原解析按裸数组处理会拿到空列表;且里面**没有坐标与延迟**,只有所在世界 - **API-V2 `/dragon` 的字段是 `dragonAlive`**,不是 `alive`;也没有 health / phase - **API-V2 `/time` 直接给算好的时刻**:`{world, tick, phase, time12, time24}`, 原实现还在用 `world_time` / `day_time` / `is_day` 自己换算,全部取不到 - **API-V2 `/players/{name}/equipment` 的物品结构**: `{type, key, amount, damage, maxDurability, unbreakable}`,装备在 `equipment` 子对象里。 `damage` 是**已损耗值**,原来会把「损耗 157」当成「剩余 157」显示; 现在换算成 `remainingDurability` 并在界面显示「剩余 / 上限」+ 百分比 - **限流提示**:V2 触发 429 时服务端带 `retryAfterSeconds`,现在直接告诉用户还要等多少秒 (末影龙接口只有 1 次/分钟,实测超限时提示「还需等待 16 秒」) ### 新增 - `SimpApiV2Client` 支持注入 `fetchImpl`,便于测试替换网络层 ## [1.5.0] - 2026-09-28 ### 新增 - **SimpCore 开放 API 数据浏览**:以前只用了「邦国领土」这一类接口,控制台也只能看到一行 「已启用 / 未启用」。现在把官方文档里的公开接口都接进来,并做成「查看 API 数据」弹层: - **概览**:两套密钥的配置状态、请求数、缓存命中、最近 HTTP 状态与错误、健康检查按钮 - **邦国**:27 个邦国的君主 / 成员数 / 区块数 / 领土面积 / 特产 / 王城区块; 点开看成员(职位、职业)、已激活科技、捐献收支、领土统计(忠诚度、最早/最近占领) - **玩家**:1600+ 名玩家可搜索(邦国 / 职位 / 职业 / 称号),点开看详情与所属邦国 - **全服**:生效加成 / 外交关系 / 封禁记录 / 上下线日志 / 公告历史 - **实时 V2**:在线玩家、游戏时间、末影龙、指定玩家的装备与手持物品 - **`SimpApiClient` 补齐到官方文档的全量接口**(原来只有 7 个,现在 16 个): `/info/players`、`/info/unions/{key}/members`、`/tech`、`/contributions`、 `/info/buffs`、`/info/diplomacy`、`/info/bans`、`/info/logs`、`/info/announcements` - **`SimpApiV2Client`**(新):`/players`、`/players/{name}/equipment`、`/time`、`/dragon`。 文档明确 **API 与 API-V2 是两套独立、互不通用的密钥体系**,因此单独一个实例与配置; V2 配额很小(末影龙仅 1 次/分钟),一律按需查询、不做轮询 ### 变更 - 快捷发送去掉 `/spawn` 与 `/list`,只留 `/u return` `/u claimmode` `/u restore confirm` ### 修复(解析字段名以实测响应为准) - 捐献记录:响应里**只有 `player_uuid` 没有玩家名**,现在先用邦国成员表、查不到再退到全服 玩家表补全;时间字段是 `contributed_at` 不是 `created_at`;补充 `reason`(用途)列 - 公告:`message` 是 **Minecraft 聊天组件的 JSON 字符串**,新增 `componentToText()` 压平成纯文本 - 封禁:实际字段是 `banned_player_name` / `banned_by_player_name` / `banned_at` / `unbanned` - 日志:实际字段是 `username` / `action` / `timestamp` - 玩家表:上下线时间是 `last_join_time` / `last_quit_time` - 王城坐标:服务端可能返回 `null`,原来会被 `Number(null)` 算成 0, 伪装成「王城在世界原点 (0,0)」;现在显式判空返回 null ## [1.4.1] - 2026-09-27 ### 变更 - **控制台输入框不再区分聊天与指令**:两者本来就是同一个入口 —— 以 `/` 开头按游戏指令发送,否则按公屏消息发送。因此删掉了「公屏聊天 / 游戏指令」下拉框 和重复的「快捷公屏消息」输入框,只保留**一个输入框 + 快捷发送 + 移动测试**。 接口返回新增 `kind` 字段说明实际走的是哪条路,Toast 也会区分「消息已发送 / 指令已发送」 - 快捷发送按钮补上 `/u restore confirm`(配合定时消息常用) ## [1.4.0] - 2026-09-27 大地图改为跟随机器人、支持可调图幅;新增定时重复发送消息;控制台界面重做并适配移动端。 ### 新增 - **定时重复发送消息 / 指令**(`src/bot/scheduledMessenger.js`) - 每个机器人可配置多条定时任务,按固定间隔重复发送同一条内容, 例如每 300 秒发一次 `/u restore confirm` 维持疆土民心 - 以 `/` 开头的内容自动按游戏指令发送,否则按公屏消息发送 - 只在「已连接 + 已通过登录验证」时才发;未就绪的周期直接顺延、**不补发**, 避免重连后一次性连发多条 - 与指令节流队列共用,不会绕过 `server.commandCooldownMs` 刷屏 - 控制台新增「定时消息」卡片:新增 / 删除 / 启用停用,并显示距下次发送的剩余秒数 - 接口:`GET|POST /api/bots/:id/schedules` - **大地图跟随机器人 + 可调图幅** - 爬取与显示区域改为**以机器人当前位置为中心**(`bigMap.followBot`,默认开启), 不再傻傻地爬世界原点周边 - 界面新增「图幅」下拉框(±512 / ±1024 / ±2048 / ±4096 / ±8192 / 全世界), 切换后即时重新对焦并拉取对应范围 - 新增「爬取周边」按钮:只爬当前图幅内的瓦片。 层级 0 每张瓦片覆盖 4096 格,所以 ±4096 只需 **9 张**,而全世界要 100 张 - `POST /api/bigmap/crawl` 支持 `centerX` / `centerZ` / `rangeBlocks`; `GET /api/bigmap` 支持 `rangeBlocks` 与 `bot` 查询参数 - 启动预爬取会先等机器人进入世界,以它的位置为中心;等不到才退回世界原点 - **领土地图以机器人所在邦国为中心** - 之前初始视图用的是控制器的 `playerChunk`,而任务启动前它是 (0,0), 导致地图永远停在世界原点附近 —— 完全不是用户关心的位置 - 现在按「机器人所在区块 → 包含它的邦国 → 该邦国王城 / 外接矩形」定位 - **控制台界面重做 + 移动端适配** - 重新设计的深色主题:统一圆角、阴影、焦点态与悬停态,卡片式分层 - 响应式三档:≥1200px 三栏;≤1200px 两栏(配置栏落到下方并自动分列);≤820px 单栏 - 移动端:底部导航(状态 / 地图 / 日志 / 配置)一键定位、触控目标放大到 ≥36px、 地图与日志高度按视口自适应、避让底部安全区、Toast 全宽 - 横屏手机压矮地图高度;尊重系统「减少动效」偏好 - 「添加玩家」折叠进 details,减少左栏长度 - 顶栏在窄屏下自动换行,按钮等分排布 ### 变更 - 大地图默认图幅从「全世界 ±20480」改为「±4096 格」,默认预爬取瓦片数从 100 张降到 9 张 - 「回到机器人」保留并修正:没有坐标时给出明确提示,而不是静默无反应 ### 修复 - `POST /api/bigmap/crawl` 的响应把中心与图幅字段传错(实际爬取是对的,但返回值显示成世界原点) - 定时任务保存后接口返回的是原始列表而非归一化结果,导致前端拿不到「剩余秒数」 ## [1.3.0] - 2026-09-27 按线上实测结果修复「机器人走不动」,并移植旧版机器人已验证的关键补包。 ### 修复 - **控制台登录按钮点不动(致命)**:`bindEvents()` 只在 `startApp()` 里执行,而 `startApp()` 只在登录成功后才跑 —— 未登录时登录按钮根本没绑上事件,形成死锁。 现在登录相关按钮在 `main()` 里无条件绑定 - **机器人走不动(致命,根因已定位并修复)**:mineflayer **不发 `player_input` 包**。 1.21.2+ 服务端的移动预测依赖它,原版客户端每 tick 都发,而 mineflayer 只在 骑乘 / 跳下 / 潜行时发,地面移动的 forward / jump / sprint 完全不发。 带反作弊的服务器因此判定「没有任何输入却发生了位移」,**每 tick 把玩家拉回原位置**。 实测:修复前入站强制位置同步约 **17 次/秒**、按方向键 2 秒位移 0.00 格; 修复后入站降到 **0 次**、位移 0.53~1.40 格。 同时补发原版每 tick 末尾的 **`tick_end`** 包(反作弊用它判定 tick 边界,mineflayer 完全不发) - **寻路插件从未加载**:项目从没调用 `bot.loadPlugin(pathfinder)`,`bot.pathfinder` 恒为 `undefined`、`isReady()` 恒为 false,扩地任务连启动都启动不了 - **寻路事件监听重复注册**:`configure()` 每次配置变更都会重跑并再挂一遍监听, 导致同一事件被处理多次(日志里同一秒出现多条相同事件) - **资源包未自动接受**:服务器要求资源包时不接会卡在加载界面进不了世界 ### 新增 - **长距离分段寻路**:mineflayer-pathfinder 只能规划**已加载区块**范围(实测约 8 区块 ≈ 130 格), 而扩地目标常在几十个区块之外,A* 会瞬间返回 noPath。现在会把旅程拆段: 走到已加载范围边缘附近 → 新区块加载 → 再规划下一段。 段长用 `bot.blockAt` 对未加载区块返回 null 的特性**二分探测**得到,不依赖世界内部结构 - **寻路预算可配置**:`pathing.thinkTimeoutMs` / `tickTimeoutMs` / `searchRadius` (`searchRadius` 为负表示不限制剪枝) - **位置同步诊断**:位置包收发计数、`player_input` / `tick_end` 计数、`onGround`、 游戏模式、维度、脚下方块、六向「脚部 / 头部 / 地面」与可通行判定 - **手动移动测试**:控制台按钮绕开寻路器直接按方向键并回报真实位移 - 配置界面新增协议补包与寻路预算相关字段 ### 已知限制(重要) - **当前所在位置无法寻路**:实测机器人站在 (6759, 72, 11926) 时,A* **恒定只展开 47 个节点** 就返回 noPath。已逐项排除:挖方块 / 放方块 / 跑酷 / 搭塔 / 开门 / 最大下落(试到 64 格)/ 高度偏好 / 水域代价 / 搜索半径(试到不限制)/ 时间预算 / 依赖版本 (与能正常移动的旧机器人完全一致:mineflayer 4.39.0 + pathfinder 2.4.5 + prismarine-world 3.7.0)。 旧机器人日志证明寻路本身可用(2 次 `goal_reached`、2 秒走过 8.7 格), 但它当时在 **y=-59**,而本机器人被 `/spawn` 放到了 **y=72**,怀疑是新位置的地形让移动生成器 找不到任何可行走分支。**下一步需要把机器人挪到开阔地再试** ## [1.2.0] - 2026-09-27 首次真正连上线上服务器(Debian 12 + pm2 + 端口 78)后,暴露并修掉了一批 **只有实机运行才会发现**的缺陷。这些问题的共同点是:单元测试全绿,但机器人根本干不了活。 ### 修复 - **寻路插件从未加载(致命)**:项目从没调用过 `bot.loadPlugin(pathfinder)`, 导致 `bot.pathfinder` 恒为 `undefined`、`isReady()` 恒为 false, 扩地任务连启动都启动不了,而界面上只显示一句含糊的「机器人尚未就绪」。 现在在 `createBot` 之后立刻加载,失败时明确报错 - **登录门禁死锁**:`/login`、`/register`、简幻通的 `/auth` 本身就是登录流程的一部分, 原先的门禁把它们一并拦下,形成「要先登录才能发登录指令」,账号永远卡在未验证状态。 新增 `server.preLoginCommands` 白名单(默认 `login,l,register,reg,auth`) - **验证码发不出去**:部分服务器要求把验证码**直接当聊天内容**发出去(不是指令), 而 `sendChat` 同样被登录门禁挡住。现在服务器明确要求验证时会自动放行聊天, 另有 `server.allowChatBeforeLogin` 给无提示的服务器兜底 - **配置了登录指令却没密码时永久卡死**:`.env` 里没有密码时程序无法发送任何登录指令, 却把 `loggedIn` 永久置为 false,控制台连手动指令都发不出去。 现在走与「服务器不回提示」一致的兜底逻辑(超时后按无需登录放行),并在日志里说明 - **离线时指令被静默丢弃却回报「已发送」**:`bot.chat()` 在未连接时无处可发。 现在 `canSendCommand()` 要求已连接并进入世界,失败信息区分 「未连接」「登录被服务器拒绝」「未通过登录验证」三种情况 - **「被服务器传送」日志洪水**:账号未完成服务器验证时服务端每 tick 强推一次位置, 实测 3 分钟刷出 748 行日志,几十分钟就会冲光日志环形缓冲并把 WebSocket 打满。 现在首次立即记录、其余按 30 秒窗口聚合,断开连接时刷出未汇总计数 - **外站账号绑定流程无法识别**:原先只认 `/login` 与 `/register`, 遇到要求 `/auth <验证码>` 或扫码绑定的服务器时,表现只是静默的死循环。 现在命中相关提示会输出可操作的中文告警(每个连接只提示一次), 并新增 `server.authCommand` 模板供有固定验证码的服务器自动过验证 - **重连无退避**:连续被踢时每 10 秒硬撞服务器。现在按指数退避 (`server.reconnectMaxDelayMs`,默认上限 120 秒),成功进入世界后计数清零 - **控制台瓦片 URL 与后端路由不一致**:前端请求 `/api/bigmap/tile/:z/:x_y.png`, 后端路由为 `/api/bigmap/tile/:z/:x/:y.png` ### 新增 - **控制台添加 / 移除玩家**:不必改 `.env` 重启,直接在界面上填玩家名 + 密码即可挂新账号。 密码只写入服务器本地凭据文件 `data/bots.credentials.json`(权限强制 0600), 不进仓库、不出现在任何接口响应里;`.env` 与凭据文件同名时以 `.env` 为准。 `.env` 里配置的账号不允许从控制台移除(否则重启后会「复活」) - **手动移动测试**:控制台可按前 / 后 / 左 / 右 / 跳 2 秒并显示真实位移, 用于区分「寻路器算不出路径」与「客户端根本走不动」这两种完全不同的故障 (绕开寻路器直接走控制键) - **位置同步诊断**:`snapshot` 暴露位置包收发计数、onGround、游戏模式、维度、 脚下方块与六向实心情况,`/api/bots/:id/status` 可直接看到 - **按世界数据修正出站 onGround**(`grim.reportOnGroundFromWorld`,默认开启): mineflayer 收到服务端 `position` 包时会把 `entity.onGround` 置为 false, 在频繁下发位置包的服务器上会导致客户端持续上报「站在地上却在空中」。 现在发包前用脚下方块复核,只有确实不一致时才改写 - `scripts/check-protocol.js`:核对 mineflayer 写出的包名 / 字段名与目标版本协议是否一致 ### 已知限制(重要) - **本服上 mineflayer 客户端无法移动**:实测(`DiLiDaLaDaLa`,已绑定简幻通、登录正常) 按方向键 2 秒位移 0.00~0.02 格;服务端每 tick 回弹一次(入站 ~17/s、出站 ~19/s)。 已系统性排除:区块数据缺失、onGround 不一致、Grim 合规层、游戏模式(survival)、 物理引擎未启用、寻路插件缺失、`teleport_confirm` 协议字段不匹配。 连接、登录、收发指令、收发聊天、公屏日志、大地图爬取均正常,**唯独移动被服务端拒绝**。 彻底定位需要把客户端的移动包与原生客户端逐字节对比,属于独立的排查工作 - 同机上原有的 `minecraft-bot`(端口 91)与该机器人不能同时使用同一账号,会互相踢下线 ## [1.1.0] - 2026-09-27 新增多机器人挂机、开拓日志、游戏公屏完整日志与大地图。 ### 新增 - **多机器人同时挂机**(`src/bot/botPool.js`、`src/config/botsConfig.js`) - `.env` 里用 `MC_BOTS` 一次配置多个账号(支持 JSON 数组与 `用户名:密码` 逗号分隔两种写法), 未配置时回退到 `MC_USERNAME` / `MC_PASSWORD` 单机器人模式 - 每个机器人独立连接、独立登录、独立跑扩地状态机;共享同一份领土数据服务与运行日志 - **密码只从 `.env` 读取**:`data/bots.json` 只存启用状态、备注名、是否自动开始与各自的配置覆盖 - 每个机器人可有自己的扩地配置覆盖(策略、目标邦国、目标数量等),未覆盖字段回退全局默认 - 控制台新增机器人列表(连接状态 / 坐标 / 任务 / 已开拓数)、机器人切换、全部开始 / 全部停止 - **开拓日志**(`src/core/expansionLog.js`) - 逐条记录**开拓玩家、邦国、区块、方块坐标、时间、判定来源**,追加写入 `data/expansion-log.jsonl` - 支持按机器人 / 邦国 / 时间筛选、按机器人统计与今日计数、导出带 BOM 的 CSV - 文件超过 16MB 自动轮转为 `.1`;进程被杀留下的半行在重启时被安全跳过 - 控制台新增「开拓日志」表格页签,支持筛选、清屏、导出 - **游戏公屏完整日志 + 聊天框**(`src/core/chatLog.js`) - 每个机器人收到的**全部**公屏 / 系统 / 动作栏消息按机器人分文件落盘(`data/chat/<机器人>.jsonl`) - 本机器人发出的聊天与指令同样留档,可对照「指令发出后服务器回了什么」 - 控制台新增「公屏聊天」页签(可按机器人或全部机器人查看、关键字筛选、导出)与公屏消息发送框 - **登录验证门禁** - 游戏侧:未完成服务器登录验证时拒绝发送指令 / 聊天,且 `isReady()` 不放行扩地任务; 新增登录结果确认兜底(`server.loginConfirmTimeoutMs`),避免服务器不回提示导致机器人永远卡在未登录 - 控制台侧:新增 `WEB_PASSWORD` 与 `/api/login`、`/api/session`,页面弹出登录框, 验证通过后才允许操作各机器人的控制台、聊天框与扩地任务 - **大地图**(`src/core/bigMapService.js`) - 爬取服务器地图站点(squaremap)的瓦片图片与领土标记,拼在一张图上查看**所有机器人的坐标** - 坐标系已对源站实测标定:瓦片 512×512,层级 0 每张覆盖 4096 方块,层级 3 为 1 方块 1 像素 (用 27 个王城坐标交叉验证:正确公式下王城所在瓦片平均海洋占比 28.7%, 错误假设为 41.6% / 47.3% / 76.0%) - 瓦片磁盘缓存 `data/mapcache/`,默认启动时预爬取层级 0(全世界 10×10=100 张), 更高层级按需穿透拉取并永久缓存;`/api/bigmap/tile/:z/:x/:y` 代理避免跨域 - 领土以 markers.json 的原始多边形绘制(预编译为 `Path2D`),叠加本次会话新占领区块、 王城与全部机器人坐标;支持滚轮缩放、拖拽平移、全世界 / 回到机器人一键定位 - **测试从 154 个增加到 242 个**:新增开拓日志、公屏日志、大地图坐标系与缓存、 机器人池与配置隔离、凭据解析、机器人登录门禁、多机器人 Web 接口与登录鉴权等用例 ### 变更 - `TerritoryModel` 新增 `flyingChunksOf()` / `ownChunksOf()`,邦国归属改为**按名称显式传入**, 不再依赖全局 `selfStateName`(多机器人会互相覆盖);同时保留 markers.json 的原始多边形 - `ExpansionPlanner` 的己方邦国解析顺序改为「配置里的 `stateName` → 模型全局 `selfStateName`」 - 运行日志统一加机器人名前缀(`[游戏名] …`),多机器人共用一份日志也能分清来源 - Web 接口改为以 `/api/bots/:id/...` 为主,原有单机器人接口(`/api/task/*`、`/api/command`、 `/api/bot/reconnect`)保留为默认机器人的等价别名 - `/api/map` 支持 `?bot=` 按机器人视角返回其邦国、目标与玩家区块 ### 修复 - 修正控制台地图瓦片 URL 与后端路由不一致的问题(前端请求 `/api/bigmap/tile/:z/:x_y.png`, 后端路由为 `/api/bigmap/tile/:z/:x/:y.png`) - **「被服务器传送」日志洪水**:账号未完成服务器验证时,服务端会每 tick 强推一次位置, 实测 3 分钟刷出 748 行日志,几十分钟就会把 2000 条的日志环形缓冲冲光、并把 WebSocket 打满。 现改为首次立即记录、其余按 30 秒窗口聚合成一条(`被服务器传送另有 N 次`), 并在连接断开时把未汇总的计数刷出去 - **外站账号绑定流程无法识别**:本项目原先只认 `/login` 与 `/register`, 遇到「简幻通」这类要求 `/auth <验证码>` 或扫码绑定的服务器时, 表现只是「连上 → 被传送 → 120 秒后被踢 → 重连」的静默死循环,失败原因对用户完全不可见。 现新增 `NEED_AUTH_PATTERN` 识别,命中后输出可操作的中文告警(每个连接只提示一次), 并新增 `server.authCommand` 模板供配置固定验证码的服务器自动过验证 - **重连无退避**:连续被踢时每 10 秒硬撞一次服务器。现按指数退避拉长间隔 (`server.reconnectMaxDelayMs`,默认上限 120 秒),成功进入世界后计数清零 - **登录门禁死锁**:有些服务器的登录流程本身就是指令(`/login`、`/register`, 简幻通体系还要先 `/auth`),原先的门禁把这些指令也一并拦下,导致 「要先登录才能发登录指令」,账号永远卡在未验证状态。 现新增 `server.preLoginCommands`(默认 `login,l,register,reg,auth`)白名单, 这些前缀在未验证时也放行;其余指令仍要求已完成验证 - **离线时指令被静默丢弃却回报「已发送」**:`bot.chat()` 在未连接时无处可发, 原先调用方只会收到「已发送」。现在 `canSendCommand()` 要求已连接并进入世界, 且失败信息会区分「未连接」「登录被拒」「未通过验证」三种情况并给出对应处理建议 ### 已知限制 - **多个机器人共享一份领土模型**:`markers.json` 是全服数据,没必要每个机器人各拉一遍; 因此「会话新占领」区块是全局共享的,控制台地图的己方邦国显示的是最近启动的那个机器人的邦国 - **大地图层级 3 不预爬取**:全图 80×80=6400 张瓦片约 200MB,改为按需拉取 + 永久缓存 - **扫码类账号绑定无法自动化**:`/auth <验证码>` 的验证码是动态的、扫码更是需要人工, 程序只能明确报错提示;绑定是一次性的,绑定成功后同一账号即可自动登录 ## [1.0.0] - 2026-02-14 由 Fabric 客户端模组 AutoStore v1.3.1 重构为 Node.js + mineflayer 机器人,UI 改为网页控制台, 并新增 Grim 反作弊合规层。 ### 新增 - **Grim 反作弊合规层**(`src/bot/grimCompat.js`) - 旋转不超前:接管 `bot.look`,把朝向按 ≤ 8.6°/tick 逐步推进,保证本地朝向与服务端已收到的朝向一致, 消除 mineflayer「本地朝向瞬间跳变、数据包滞后补发」导致的 Prediction 分歧 - 攻击三重校验:朝向已同步 + 距离 ≤ 3.0 格 + 视线可达 + 攻击冷却 + 非骑乘/进食状态 - 动作互斥:拦下疾跑+潜行、疾跑+使用物品等原版不可能出现的组合(MultiActions) - 物理参数只读自检:核对 `bot.physics` 关键常量、`physicsEnabled`、游戏模式、鞘翅状态 - 寻路约束:默认禁止挖方块 / 放方块 / 跑酷 / 1×1 搭塔,限制最大下落高度,推高液体代价优先绕开水域 - **网页控制台**(`src/web/`) - 实时状态面板(连接 / 任务状态机 / 领土数据源 / Grim 合规) - Canvas 领土地图:滚轮缩放、拖拽平移、图层开关、回到玩家、全图适配 - 7 个分组的配置表单,保存即生效 - 实时日志流、日志导出、手动指令发送 - REST + WebSocket 接口,支持 `WEB_TOKEN` 令牌鉴权(定时安全比较) - **SimpCore 开放 API 第四数据源**:低频轮询己方邦国领土明细 + 王城兜底,支持手动拉取全服领土 - **开垦时段可配置**:`enforceClaimTime` / `claimStartHour` / `claimEndHour`(原版硬编码 11:00-22:00) - **战斗筛选可配置**:`combat` 分组(索敌半径、末影人 / 蜘蛛 / 猪灵开关、高危生物跳过) - **断线自动重连**与登录 / 注册指令模板(密码从 `.env` 读取,支持 `{password}` 占位符) - **154 个测试用例**,覆盖坐标编解码、领土模型、规划器、数据解析、合规层、状态机、Web 接口 ### 变更 - 寻路引擎从 Baritone 换成 mineflayer-pathfinder;高度偏好(`GoalXZAltitude`)用包装 Goal 的 `heuristic = max(0, base - weight × y)` 等价移植 - 区块坐标内部表示从对象改为整数键(`x × 2^26 + z`),避免数万区块产生海量临时对象 - 扩地状态机、规划策略、超时重试与回城逻辑与原版逐行对照移植,行为保持一致 ### 修复 - 修正 `containsFuzzy` 的模糊匹配:原实现只取固定长度前缀的末行编辑距离, 导致 `containsFuzzy('开垦中 50%', '开垦中', 0)` 返回 `false`(完全相同的子串都匹配不上)。 现按注释语义改为「存在编辑距离 ≤ maxEdits 的子串」 - 修正占领去重:原实现依赖 `Set.add` 的返回值判断重复,而 JS 的 `Set.add` 恒返回 Set 对象, 会导致同一区块被重复计数 - 修正非法枚举值的处理:策略等枚举字段不再「钳制到最近边界」(把非法的 99 静默变成策略 4), 而是回退到默认值 ### 已知限制 - **自动用船默认关闭**:mineflayer 使用的 prismarine-physics 没有载具物理,上船后本地物理预测被关闭, 位置完全由服务端下发的船实体位置决定,Grim 的 Vehicle 检查风险高。 用船逻辑已完整实现,需显式开启 `waterMode: 'boat'` + `autoBoat: true` - **末影人与蜘蛛默认不打**:mineflayer 拿不到可靠的「是否被激怒」与「环境亮度」信息, 宁可漏打也不误打;可用 `combat.attackEnderman` / `combat.attackSpiders` 放开 - **监守者 / 末影龙 / 凋灵默认跳过**:原版会攻击它们,但那基本等于自杀