feat(runtime/pathfinder): stuck-replan watchdog — react to mid-path obstacles
Problem (live 2026-05-26 screenshot): player drops a block in front of the bot mid-path. mineflayer-pathfinder computes the path once when goto() is called and never recomputes for world changes. Bot pushes forward against the new block until the 30–60 s goto timeout fires, visible as the bot just standing there pressing W. Fix — runtime/pathfinder-watchdog.js: per-bot poll loop (2 s tick) that runs while bot.pathfinder.goal is non-null. Tracks horizontal position. If movement < 0.5 blocks for > 6 s after an initial 1.5 s grace, forces a replan: setGoal(null) + setGoal(<same goal>) on a 250 ms delay. That makes the planner rebuild the path against the current world, so it routes around the new block — or, with canDig=true in our profiles, digs through it. Capped at 3 replans per goal so a genuinely unreachable target still bubbles up to the caller's timeout. Side benefit: catches mineflayer-pathfinder issue #222 ("path hangs on unreachable goal") much earlier than our 45 s goto wrappers. Wired into bot.js on the "spawn" event and stopped on gracefulExit. 7 new unit tests cover the polling math + replan cap. 197/197 green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,7 @@ import { startPlanner, isPlannerBusy, readNextMilestone, planExists } from "./pl
|
||||
import { computeState, STATES } from "./state.js";
|
||||
import { createNoProgressDetector } from "./no-progress.js";
|
||||
import { maybeStartViewer } from "./viewer.js";
|
||||
import { createPathfinderWatchdog } from "./pathfinder-watchdog.js";
|
||||
import { nextMilestone as nextCurriculumMilestone } from "./curriculum.js";
|
||||
import { listLocations } from "./locations.js";
|
||||
import { runSkill } from "./skills/index.js";
|
||||
@@ -66,6 +67,7 @@ const ESCALATE_AFTER_NOOPS = 20;
|
||||
const ESCALATION_COOLDOWN_MS = 10 * 60 * 1000;
|
||||
|
||||
let bot = null;
|
||||
let pathWatchdog = null;
|
||||
let reflexPaused = false;
|
||||
let tickTimer = null;
|
||||
let reconnectTimer = null;
|
||||
@@ -638,6 +640,12 @@ function connect() {
|
||||
appendDiary(`spawned at ${bot.entity.position.x.toFixed(0)},${bot.entity.position.y.toFixed(0)},${bot.entity.position.z.toFixed(0)}`);
|
||||
ipc?.broadcast(EVENT_TYPES.STATUS, buildSnapshot(bot));
|
||||
maybeStartViewer(bot).catch((e) => warn("viewer", `start threw: ${e?.message ?? e}`));
|
||||
// Pathfinder stuck-watchdog: replan when an obstacle appears mid-path
|
||||
// (mineflayer-pathfinder doesn't recompute on world changes).
|
||||
try {
|
||||
pathWatchdog?.stop();
|
||||
pathWatchdog = createPathfinderWatchdog(bot);
|
||||
} catch (e) { warn("pathfinder", `watchdog start failed: ${e?.message ?? e}`); }
|
||||
});
|
||||
|
||||
bot.on("messagestr", (text) => {
|
||||
@@ -1033,6 +1041,8 @@ function gracefulExit(code) {
|
||||
info("runtime", "shutting down");
|
||||
if (tickTimer) clearInterval(tickTimer);
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
try { pathWatchdog?.stop(); } catch {}
|
||||
pathWatchdog = null;
|
||||
try {
|
||||
bot?.quit("shutdown");
|
||||
} catch {}
|
||||
|
||||
Reference in New Issue
Block a user