提交图
21 次代码提交
作者 SHA1 备注 提交日期
lyizai ab598a7101 fix: 修掉 yaw 无界增长 —— 它让机器人原地走之字形、净位移恒为 0(1.11.0)
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)。

这是扩地跑不完那 763 格长途的直接原因,也解释了出站包里 look 包(38490)
比 position_look(23804)还多。

修复:新增 wrapAngle,_onPhysicsTick 每 tick 归一化 entity.yaw 到 (-π, π],
setLook 也归一化传入的目标角度。归一化只改表示不改朝向
(shortestAngleDelta(huge, wrapped) === 0),对旋转逻辑完全透明。

测试 478 → 480。
2026-10-01 18:08:41 +08:00
lyizai 1b958cb804 fix: 自动恢复不再因一次失败的尝试而永久丢失
1.9.3 的 tick() 里先取走 resumeConfig 再调 start(),而 start() 在
「已进入世界但还没完成登录」时会直接返回 false(isReady() 要求 loggedIn)。
本服掉线频繁、重连后常常短暂处于这个状态,于是第一次失败的尝试就把这次恢复
永久丢掉了,任务再也不会自己接上 —— 实测监控里任务长时间停在
「机器人尚未就绪(未连接 / 未进入世界)」就是这个原因。

现在:① 等 bm.isReady() 才尝试;② 只有 start() 真的返回 true 才清掉登记,
失败时保留并在下一 tick 继续试,日志打「自动恢复未成功(…),稍后重试」。

顺带:服务器上保存的 pathing.maxDropDown 被改成了 64(钳制上限),
而原版摔落伤害超过 3 格后每格 1 点、23 格必死 —— 实测机器人正是
「fell from a high place」摔死并重生回王城,白走了 100 多格。
已在服务器配置上改回 4(代码默认本来就是 3)。

测试 477 → 478。
2026-10-01 17:22:11 +08:00
lyizai 2ae2833568 feat: 补上出站包一直漏报的 hasHorizontalCollision(1.10.0)
针对「机器人一直在走、净位移却只有物理速度的 1/5」这个现象。

mineflayer 在 physics.js 里把 flags.hasHorizontalCollision 写成 undefined
(源码注释还标着「1.21.3+」),位标志序列化后恒为 0 —— 而原版客户端在水平碰到
方块时会上报 1。机器人在崎岖地形上几乎一直在水平碰撞,却始终上报「没碰撞」,
正是反作弊 Prediction 最容易判错的形态:服务端按「该碰撞」预测、客户端说
「没碰撞」,于是被判异常并拉回。

物理引擎其实早已算出真实值(moveEntity 里给 entity.isCollidedHorizontally 赋值),
只是从没被发出去。现在在 _fixOutboundOnGround 里按真实值补上,由
grim.reportHorizontalCollision(默认开)控制。

实测依据(王城外山区,2 秒粒度采样 120 秒):
- controls 几乎每 tick 都是 forward+sprint,velocity 约 0.2~3 格/秒;
- 净位移只有 48.6 格 / 120 秒 = 0.405 格/秒,差额就是被服务端拉回的部分。

顺带把 _fixOutboundOnGround 拆成 _fixOnGroundFlag / _fixHorizontalCollisionFlag
两个职责单一的方法;控制台配置面板新增对应开关,
packetStats.horizontalCollisionFixed 暴露补写次数。

测试 473 → 477。
2026-10-01 17:04:25 +08:00
lyizai 0bed12e189 feat: 掉线重连后自动恢复扩地任务(autoResumeAfterReconnect)
本服会周期性把机器人踢下线(实测约 1~6 分钟一次,踢出原因是
"An internal error occurred in your connection"),每次都会走到「世界已退出,任务停止」。
原来的行为是就此停住 —— 机器人虽然能被原点自救送回真实世界(实测 4/4 成功),
却再也不会继续扩地,于是永远停在原地。

现在世界退出时把本次任务的配置记下来(resumeConfig),
重新进入世界后自动 start() 接上;状态文案改为「世界已退出,等待重连后自动恢复」,
并打印「已重新进入世界,自动恢复扩地任务」。

只有因世界退出而停的那次会登记:手动停止 / 到开垦时段外 / 耐久保护停机
都会在 stop() 里清空 resumeConfig,不会被重连重新拉起。
开关 autoResumeAfterReconnect 默认开启,可关。

测试 470 → 473。
2026-10-01 16:34:56 +08:00
lyizai 5513ef9f5f fix: 物理录制器区分「被服务端推回」与「输入没进物理」
原来 frozenTicks 只看「有输入 + tick 级无位移 + 物理没报告碰撞」,
但服务端推回发生在 tick 之间,tick 级位移同样是 0 —— 实测大厅里就是这个形态:
collidedHorizontally = false、frozenTicks = 194,而物理其实每 tick 都在动。

现在再按水平速度是否非零细分:
- pushedBackTicks:速度非零 → 物理确实动了,位移被服务端位置包抹掉;
- frozenTicks:速度也是 0 → 这才是「输入根本没进物理」。

类注释里的判定表也同步改成三分法(被挡住 / 被推回 / 输入没生效)。

测试 469 → 470。
2026-10-01 16:28:14 +08:00
lyizai 3a508f6818 fix: 高度偏好默认关闭 —— 它让寻路器完全规划不出路径(自动扩地的直接拦路虎)
实测定位:高度偏好的启发值实现与 mineflayer-pathfinder 的内部约定冲突。
该库每次 compute() 返回 partial 时,用 h 最小的节点当 partial 路径的终点
(astar.js: if (neighborNode.h < this.bestNode.h) this.bestNode = neighborNode),
而 AltitudeGoal 把 h 减去了 权重 × y,于是「又高又偏」的节点 h 反而更小,
partial 路径被引向高处而不是目标附近 —— A* 一直在算、永不收敛,
机器人一次移动指令都发不出来。

王城平台上的对照实测(其余配置完全相同):
- altitudePreference = true  → inputTicks = 0,任务永远停在「寻路超时,重新寻路」
- altitudePreference = false → inputTicks = 237,立刻开始移动

顺带排除:searchRadius 从 -1 改到 128 对结果没有任何影响,
所以不是「搜索空间无界」导致的;pathfinder 与 manual 两种执行器表现一致。

改动:
- expansionConfig 默认 altitudePreference 改为 false,并在注释里写清原因与
  「想重新启用应该怎么做」(把偏置改成低处加移动代价,保持启发值只反映真实距离);
- AltitudeGoal 的类注释补上完整推导与实测数据,避免以后有人又把它打开;
- 新增回归用例:断言默认关闭,并用启发值差值把「为什么必然出问题」固化下来。

测试 468 → 469,全绿。
2026-10-01 00:34:51 +08:00
lyizai b2b0317af7 feat: 新增物理 tick 录制,把「走不动」的三种原因切开(1.9.0)
抓包已经证明「手动按键时客户端坐标一字未变、服务端入站位置包为 0」,
问题在 prismarine-physics 这一层;但抓包只能看到「发出去的包」,
看不到物理引擎内部读到了什么输入、算出了什么、有没有真的移动。
本提交补上这最后一块拼图。

新增 src/core/physicsRecorder.js + 控制台接口
(POST /api/bots/:id/physics/start|stop、GET /api/bots/:id/physics):

逐 tick 记录位置增量、速度、onGround、isCollidedHorizontally、控制键快照、
脚下/脚部/头部方块名、physicsEnabled;汇总给出四个判定计数:
- inputTicks        有移动输入的 tick 数
- inputMovedTicks   其中真的产生了水平位移的
- blockedTicks      有输入、没位移、物理报告碰撞  → 被方块挡住(地形)
- frozenTicks       有输入、没位移、物理没报告碰撞 → 输入根本没进物理

即:抓包看「发了什么」,本录制器看「物理读到了什么、动没动」,两者配合即可定位。

细节:
- 录制开始时先取一次当前位置作基线,避免第一个 tick 因「没有上一 tick 可比」
  被误判成「有输入但没动」;
- 滚动缓冲只保留最近 240 个 tick(约 12 秒),只放内存、不写盘;
- 录制全程 try 包裹,诊断自身出异常绝不影响物理循环;
- FakeBot 补上与 mineflayer 一致的 controlState(带 getter 的实时控制键代理)。

测试:451 → 468 个用例(含三种故障判定的回归用例与控制台接口用例),全绿。
2026-09-30 21:05:38 +08:00
lyizai 8ab2d59eb0 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 轮全量运行全绿。
2026-09-29 22:01:02 +08:00
lyizai 51664c55b6 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` 全绿。
2026-09-28 22:46:39 +08:00
lyizai 0cd65cf9be fix: 修正原点(00)两种原因的判断依据 —— 两种情况下脚下都是基岩
用户指出:死亡被卡到 00 时脚下**也是基岩**,所以原来用
lockBelow === 'bedrock' 区分「登录服」和「死亡卡住」是错的,
会把死亡卡住误判成登录服,于是不做自救、只让用户去输验证码。

改成以「服务器有没有要求输入验证码」为唯一可靠信号:
- 要求验证码 -> 判定为登录服,提示人工发验证码(不做无意义的左右移动)
- 没要求验证码 -> 判定为死亡卡住,自动左右移动触发服务器重新传送

自救本身在两种情况下都无害(在登录服里按左右键不会有后果),
所以即使判断错了也不会把事情弄得更糟。

另外:验证码提示往往比 spawn 晚到,所以 _handleAuthPrompt 里也补了一次判断 ——
若提示到达时机器人正好在原点附近,会明确点出「被反复传送刷屏」正是登录服造成的,
把这个因果关系讲清楚,避免用户只看到传送刷屏而不知原因。

同步修正 TODO.md 与 CHANGELOG 里那条错误结论,并标注「不要用基岩做判断依据」。

测试 363 -> 368,全部通过。
2026-09-28 20:32:49 +08:00
lyizai 9ca80d2f6e feat: 回城护栏 + 原点自救 + 移动诊断,并留下 TODO 交接文档
先纠正上一轮的误判:那个「寻路恒定 47 节点」是机器人卡在百米深坑里造成的,
我把它归因成配置问题并反复触发 /u return 回城,把玩家手动摆好的位置又拉走了 —— 抱歉。

回城护栏(破坏性操作不再自动执行)
- returnOnTimeout 默认关闭;即便打开,returnGraceSeconds(默认 300 秒)内也不执行,
  避免把玩家刚在客户端摆好的位置清掉
- returnCommand 可配置(默认 u return);每次回城都明确记日志说明会丢失当前位置
- 因未启用而跳过时也记一条说明,避免用户以为「怎么不回城了」

区分「被传送到原点」的两种原因(以前日志里长得一样,常被误判成机器人坏了)
- 脚下是 bedrock -> 在登录服,必须人工输入验证码,给出明确提示
- 脚下不是基岩 -> 死亡后被卡住,自动左右移动触发服务器重新传送

新增两个诊断字段(排查「走不动」的关键手段)
- moveDiag:当前按下的控制键、速度、寻路器 isMoving/goal/剩余路径点数。
  能一刀切开「寻路器没执行」与「执行了但服务端不认」
- pathingDiag:起点可走邻居数、寻路器能否读到方块、四向可见距离

_isStandingOnGround() 增加「服务端是否正把我们按在原地」的判断

排查结论(详见 TODO.md)
- 排除:GrimCompat 朝向限速(A/B 实测关闭后回弹 6.0 -> 5.5 次/秒,无变化)
- 排除:疾跑(关闭后仍 4.8 次/秒)
- 排除:世界数据错误(实测脚下 stone)
- 关键线索:寻路时 forward/sprint 确实按下、isMoving=true,但水平速度恒为 0
  (velocity.y=-0.078 恰是自由落体第一 tick 的值,说明速度每 tick 被清零),
  且 bot.entity.onGround 被服务端位置包反复置为 false,物理引擎一直按空中处理。
  下一步应从「让 entity.onGround 与出站包一致」入手。

TODO.md 面向下一位开发者:包含项目现状与部署、完整证据链、按优先级排好的假设与
验证命令、备选方案(绕开 pathfinder 自己写转向控制器)、以及其它待办。

测试 360 -> 363,全部通过。
2026-09-28 20:21:41 +08:00
lyizai 71e78f06dc fix: 按实测响应修正 SimpCore API-V2 的字段名
接入 V2 密钥后实跑发现字段名与文档描述对不上,全部按真实响应修正:

- /players 返回的是对象 {online,max,players:[...]} 而不是裸数组,
  原解析会拿到空列表;且里面没有坐标与延迟,只有所在世界
- /dragon 的字段是 dragonAlive,不是 alive,也没有 health / phase
- /time 直接给算好的时刻 {world,tick,phase,time12,time24},
  原实现还在用 world_time/day_time/is_day 自己换算,全部取不到
- /players/{name}/equipment 的物品是 {type,key,amount,damage,maxDurability,unbreakable}
  且挂在 equipment 子对象下;damage 是已损耗值,原来会把「损耗 157」当成
  「剩余 157」显示,现在换算成 remainingDurability 并显示「剩余/上限」+ 百分比
- 429 限流时服务端带 retryAfterSeconds,现在提示里直接写出还要等多少秒
  (末影龙 1 次/分钟,实测提示「还需等待 16 秒」)

另外给 SimpApiV2Client 加了可注入的 fetchImpl,便于测试替换网络层。

实测(DiLiDaLaDaLa):在线 8 人、游戏时间 19:45 NIGHT、
下界合金剑 剩余 1992/2031、头盔 剩余 198/407。

测试 336 -> 343,全部通过。
2026-09-28 13:56:41 +08:00
lyizai b9cae1aac2 feat: 接齐 SimpCore 开放 API 全量接口并新增 API 数据浏览弹层
以前只用了「邦国领土」这一类接口,控制台也只能看到一行「已启用/未启用」,
大量服务端公开信息拿得到却看不到。现在按官方文档 https://open.bgjq.top/api-docs.html 补齐。

新增
- 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 是两套独立、互不通用的密钥体系,因此单独实例与配置
  (apiV2Enabled / apiV2Key,或环境变量 SIMP_API_V2_KEY);V2 配额很小
  (末影龙 1 次/分钟),一律按需查询、不做轮询
- 控制台「查看 API 数据」弹层,五个页签:
  概览(两套密钥状态/请求数/缓存/健康检查)· 邦国(27 个,点开看成员/科技/捐献/领土统计)
  · 玩家(1600+ 可搜索,点开看详情与所属邦国)· 全服(加成/外交/封禁/日志/公告)
  · 实时 V2(在线玩家/游戏时间/末影龙/装备)
- /api/simp/* 一整套只读代理接口,统一处理「未配 Key / 上游错误」并给出可读提示

修复(解析字段名以实测响应为准)
- 捐献:响应只有 player_uuid 没有玩家名 -> 先用成员表补全、查不到再退到全服玩家表;
  时间字段是 contributed_at;补充 reason(用途)列;amount 可为负(邦国支出)
- 公告: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

变更
- 快捷发送去掉 /spawn 与 /list,只留 /u return、/u claimmode、/u restore confirm

测试 326 -> 336,全部通过。
2026-09-28 13:45:23 +08:00
lyizai f1316bf58c refactor: 控制台输入框不再区分聊天与指令,只留一个入口
聊天和指令本来就是同一个东西:以 / 开头按游戏指令发送,否则按公屏消息发送。
服务端 /api/bots/:id/chat 一直就是这么分流的,前端多一个模式下拉框纯属多余。

- 删掉「公屏聊天 / 游戏指令」下拉框与重复的「快捷公屏消息」输入框
- 只保留:一个输入框 + 快捷发送按钮 + 移动测试按钮
- 接口返回新增 kind 字段(chat / command)说明实际走的是哪条路
- Toast 区分「消息已发送」与「指令已发送:/xxx」
- 快捷发送补上 /u restore confirm(配合定时消息常用)
- 空内容提示从「消息为空」改为「内容为空」(不再只针对聊天)

测试 325 -> 326,全部通过。
2026-09-28 01:00:05 +08:00
lyizai a83dea9677 feat: 大地图跟随机器人并支持可调图幅,新增定时消息,控制台界面重做并适配移动端
大地图跟随机器人 + 可调图幅
- 爬取与显示区域改为以机器人当前位置为中心(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),
  导致地图永远停在世界原点附近。现在按「机器人所在区块 → 包含它的邦国 →
  该邦国王城/外接矩形」定位

定时重复发送消息(src/bot/scheduledMessenger.js)
- 每个机器人可配多条定时任务,按固定间隔重复发送同一内容,如每 300 秒 /u restore confirm
- 以 / 开头按游戏指令发送,否则按公屏消息发送
- 只在已连接且通过登录验证时发送;未就绪的周期顺延且不补发,避免重连后一次性连发
- 与指令节流队列共用,间隔下限 5 秒、单机器人最多 20 条
- 控制台新增「定时消息」卡片(新增/删除/启停 + 剩余秒数倒计时)

控制台界面重做 + 移动端适配
- 重新设计的深色主题:统一圆角、阴影、焦点态与悬停态,卡片式分层
- 响应式三档:≥1200px 三栏;≤1200px 两栏;≤820px 单栏 + 底部导航
- 移动端:触控目标 ≥36px、地图/日志高度按视口自适应、避让底部安全区、
  横屏压矮地图、尊重「减少动效」偏好、Toast 全宽
- 「添加玩家」折叠进 details;顶栏窄屏自动换行且按钮等分

修复
- POST /api/bigmap/crawl 响应把中心与图幅字段传错(实际爬取正确,返回值显示成原点)
- 定时任务保存后接口返回原始列表而非归一化结果,前端拿不到剩余秒数

测试 308 -> 325,全部通过。
2026-09-28 00:27:58 +08:00
lyizai fcd5fb2bd9 fix: 修复登录按钮死锁,移植旧版 player_input 补包解决「机器人走不动」
控制台登录按钮点不动(致命)
- 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_end(原版每 tick 末尾发送,反作弊用它判定 tick 边界,mineflayer 完全不发)
- 资源包未自动接受会卡在加载界面进不了世界,现自动接受
- configure() 每次配置变更都重复注册寻路事件监听,导致同一事件被处理多次

长距离分段寻路
- mineflayer-pathfinder 只能规划已加载区块范围(实测约 8 区块 ≈ 130 格),
  扩地目标常在几十个区块外,A* 瞬间 noPath。现在自动拆段:走到已加载范围边缘 → 新区块加载 → 再规划。
  段长用 bot.blockAt 对未加载区块返回 null 的特性二分探测,不依赖世界内部结构。

新增
- pathing.thinkTimeoutMs / tickTimeoutMs / searchRadius(负值表示不限制剪枝)
- 位置包收发计数、player_input/tick_end 计数、游戏模式、维度、脚下方块、
  六向「脚部/头部/地面」与可通行判定等诊断字段
- 手动移动测试按钮(绕开寻路器直接按方向键并回报真实位移)

已知限制
- 机器人当前所在位置 (6759,72,11926) 无法寻路:A* 恒定只展开 47 个节点就 noPath。
  已逐项排除挖方块/放方块/跑酷/搭塔/开门/最大下落(试到 64 格)/高度偏好/水域代价/
  搜索半径(试到不限制)/时间预算/依赖版本(与能正常移动的旧机器人完全一致)。
  旧机器人日志证明寻路可用(2 次 goal_reached、2 秒走 8.7 格),但它当时在 y=-59,
  本机器人被 /spawn 放到了 y=72。下一步需把机器人挪到开阔地再试。

测试 290 -> 308,全部通过。
2026-09-27 23:52:29 +08:00
lyizai abecdd2648 fix: 修复实机运行才暴露的 8 个缺陷,新增控制台添加玩家与移动测试
首次真正连上线上服务器(Debian 12 + pm2 + 端口 78)后暴露的问题,单元测试全绿但机器人干不了活:

致命
- 寻路插件从未加载:项目从没调用 bot.loadPlugin(pathfinder),bot.pathfinder 恒为
  undefined、isReady() 恒为 false,扩地任务连启动都启动不了,界面只显示含糊的
  「机器人尚未就绪」。现在 createBot 后立刻加载,失败时明确报错。

登录门禁相关(三处死锁)
- preLoginCommands 白名单:/login、/register、简幻通的 /auth 本身就是登录流程的一部分,
  原先被门禁一并拦下,形成「要先登录才能发登录指令」
- 验证码常被要求直接当聊天内容发出(不是指令),原先被 sendChat 的门禁挡住;
  现在服务器明确要求验证时自动放行,另有 server.allowChatBeforeLogin 兜底
- 配置了登录指令却没有密码时 loggedIn 永久为 false,控制台连手动指令都发不出;
  现在走与其他「服务器不回提示」一致的超时兜底并给出说明

可用性
- 离线时指令被静默丢弃却回报「已发送」;现在要求已连接并进入世界,失败信息区分
  「未连接」「登录被拒」「未通过验证」
- 「被服务器传送」日志洪水:实测 3 分钟 748 行,会冲光日志缓冲并打满 WebSocket;
  现在按 30 秒窗口聚合,断开时刷出未汇总计数
- 识别外站账号绑定流程并给出可操作告警,新增 server.authCommand 模板
- 重连指数退避(server.reconnectMaxDelayMs)
- 控制台瓦片 URL 与后端路由不一致

新增
- 控制台添加 / 移除玩家:不必改 .env 重启。密码只写入 data/bots.credentials.json
  (强制 0600),不进仓库、不出现在任何响应里;.env 与凭据文件同名时以 .env 为准,
  .env 里的账号不允许从控制台移除
- 手动移动测试接口与按钮:绕开寻路器直接按方向键,返回真实位移,
  用于区分「算不出路径」与「根本走不动」
- 位置同步诊断:位置包收发计数、onGround、游戏模式、维度、脚下方块与六向实心
- grim.reportOnGroundFromWorld:按世界数据修正出站 onGround(默认开启)
- scripts/check-protocol.js:核对包名与字段名同目标版本协议是否一致

文档
- README 新增「部署到服务器」与「机器人走不动的排查方法」两章

已知限制:实测本服上 mineflayer 客户端无法移动(按方向键 2 秒位移 0.00~0.02 格,
服务端每 tick 回弹一次),已排除区块数据、onGround、Grim 层、游戏模式、物理引擎、
寻路插件、teleport_confirm 协议字段等因素,彻底定位需与原生客户端逐字节对比移动包。
连接 / 登录 / 指令 / 聊天 / 公屏日志 / 大地图均正常。

测试从 263 增至 290,全部通过。
2026-09-27 23:06:39 +08:00
lyizai 2671da95d5 fix: 打通登录门禁死锁,失败信息区分未连接/登录被拒/未通过验证
继续修复部署时暴露的门禁设计问题:

- 登录门禁死锁:/login、/register、简幻通的 /auth 本身就是登录流程的一部分,
  原先门禁把它们一并拦下,形成「要先登录才能发登录指令」,账号永远卡在未验证状态。
  新增 server.preLoginCommands 白名单(默认 login,l,register,reg,auth),
  这些前缀在未验证时也放行,其余指令仍要求已完成验证。

- 离线时指令被静默丢弃却回报「已发送」:bot.chat() 在未连接时无处可发。
  canSendCommand() 现在要求已连接并进入世界,失败信息区分
  「未连接」「登录被服务器拒绝」「未通过登录验证」三种情况并给出对应建议。

- 前端配置表单支持 list 类型字段(逗号分隔的字符串数组)。

测试从 252 增至 263,全部通过。
2026-09-27 22:11:52 +08:00
lyizai d6b1339867 fix: 修正服务器反复传送刷屏日志、识别外站账号绑定流程、重连指数退避
部署到 Debian 服务器时暴露的三个真实缺陷:

1. 「被服务器传送」日志洪水
   账号未完成服务器验证时服务端每 tick 强推一次位置,实测 3 分钟刷出 748 行日志,
   几十分钟就会冲光 2000 条的日志环形缓冲并把 WebSocket 打满。
   现改为首次立即记录、其余按 30 秒窗口聚合成一条,断开连接时刷出未汇总计数。

2. 外站账号绑定流程无法识别
   原先只认 /login 与 /register,遇到简幻通这类要求 /auth <UID> <验证码> 或扫码绑定的
   服务器时,表现只是「连上→被传送→120 秒后被踢→重连」的静默死循环。
   现新增 NEED_AUTH_PATTERN 识别并输出可操作的中文告警(每连接一次),
   新增 server.authCommand 模板供有固定验证码的服务器自动过验证。

3. 重连无退避
   连续被踢时每 10 秒硬撞服务器。现按指数退避拉长(server.reconnectMaxDelayMs,
   默认上限 120 秒),成功进入世界后清零;抽出 computeReconnectDelay 便于测试。

顺带补充 README 的「部署到服务器」章节(Node ≥ 22 要求、私有运行时、pm2、日志分文件、排障表)。

测试从 242 增至 252,全部通过。
2026-09-27 22:02:24 +08:00
lyizai 39624da2d5 feat: 支持多机器人挂机、开拓日志、公屏完整日志与大地图
多机器人(src/bot/botPool.js、src/config/botsConfig.js)
- .env 用 MC_BOTS 一次配置多个账号(JSON 数组或 用户名:密码 逗号分隔),未配置时回退单机器人
- 每个机器人独立连接 / 登录 / 扩地状态机,共享一份领土数据服务与运行日志
- 密码只从 .env 读取;data/bots.json 只存启用状态、备注名、自动开始与各自的配置覆盖
- 每个机器人可覆盖全局配置(策略 / 目标邦国 / 目标数量等),未覆盖字段回退全局默认

开拓日志(src/core/expansionLog.js)
- 逐条记录开拓玩家、邦国、区块、方块坐标、时间与判定来源,追加写入 data/expansion-log.jsonl
- 支持按机器人 / 邦国 / 时间筛选、统计、导出带 BOM 的 CSV,超过 16MB 自动轮转

游戏公屏完整日志与聊天框(src/core/chatLog.js)
- 每个机器人的全部公屏 / 系统 / 动作栏消息按机器人分文件留档,自身发出的聊天与指令同样留档
- 控制台新增公屏聊天页签与消息发送框

登录验证门禁
- 游戏侧:未完成登录验证时拒绝发送指令 / 聊天,isReady() 也不放行扩地任务;
  新增 server.loginConfirmTimeoutMs 兜底,避免服务器不回提示导致永远卡在未登录
- 控制台侧:新增 WEB_PASSWORD 与 /api/login、/api/session,页面登录框验证通过才允许操作

大地图(src/core/bigMapService.js)
- 爬取服务器地图站点(squaremap)的瓦片图片与领土标记,一张图上看全部机器人坐标
- 坐标系对源站实测标定并用 27 个王城交叉验证;瓦片磁盘缓存,层级 0 启动预爬取、更高层级按需穿透
- 领土按 markers.json 原始多边形绘制(预编译 Path2D,even-odd 保留孔洞),叠加王城与机器人标签

配套改造
- TerritoryModel 新增 flyingChunksOf / ownChunksOf,邦国归属改为按名称显式传入,
  不再依赖全局 selfStateName(多机器人会互相覆盖)
- Web 接口以 /api/bots/:id/... 为主,原单机器人接口保留为默认机器人的等价别名
- 新增 scripts/check-web-ids.js 校验前端元素引用一致性

测试从 154 增至 242,全部通过。
2026-09-27 21:22:57 +08:00
lyizai a12a24c716 feat: 用 Node.js + mineflayer 重构 AutoStore,UI 改为网页控制台并适配 Grim 反作弊
由 Fabric 客户端模组 AutoStore v1.3.1 重构为独立 Node.js 机器人,无需游戏客户端。

核心实现:
- 领土数据:Dynmap markers.json 拉取解析(奇偶填充识别国中飞地)、SimpCore 开放 API 第四数据源、
  带缓存击穿的时间戳请求与内容未变化跳过解析
- 扩地规划:四种策略(方形外扩 / 区域 S 形扫描 / 玩家周围 / 绕国一圈)逐行对照移植,
  接壤判定只算己方领土,飞地优先填补
- 扩地状态机:规划→寻路→等待开垦→成功/失败→下一目标→完成,含超时重试、
  往边上走两步自救、连续超时回城、耐久保护回城
- 生存辅助:自动进食、自动攻击(豁免中立生物)、自动游泳防溺水、自动用船(默认关闭)
- 寻路:Baritone → mineflayer-pathfinder,高度偏好用包装 Goal 等价移植

Grim 反作弊合规层(新增,核心差异点):
- 旋转不超前:接管 bot.look 按 ≤8.6°/tick 推进朝向,消除 mineflayer
  「本地朝向瞬间跳变、数据包滞后补发」导致的 Prediction 分歧
- 攻击三重校验:朝向已同步 + 距离 ≤3.0 格 + 视线可达 + 攻击冷却 + 非骑乘/进食
- 动作互斥:拦下疾跑+潜行、疾跑+使用物品等原版不可能出现的组合
- 物理参数只读自检 + 寻路约束(禁挖/禁放/禁跑酷/禁搭塔/限下落/绕开水域)

网页控制台(新增):
- 状态面板、Canvas 领土地图(缩放/平移/图层)、分组配置表单、实时日志流
- REST + WebSocket 接口,支持 WEB_TOKEN 令牌鉴权

修复:
- containsFuzzy 只取固定长度前缀的末行编辑距离,导致完全相同的子串也匹配不上
- 占领去重依赖 Set.add 返回值(JS 中恒为真),导致同一区块被重复计数
- 非法枚举值不再钳制到最近边界,改为回退默认值

测试:154 个用例,npm test 全绿;npm run check 语法自检通过。
2026-09-27 17:17:41 +08:00