fix(runtime): unstick wander loop + chop radius + explore.far skill
Follow-up to the iteration-1 fixes. Live smoke on play.xmatic.team revealed the bot was spawning into a tree-less plain (no log within 32 blocks of spawn), looping wander→gather→no_target→wander forever inside a 16-block box. - runtime/actions.js: chopNearestTree search radius 32 → 64 (still no trees on this spawn, but a normal biome will be served well by it). wander now has a blind-walk fallback when pathfinder times out (look+forward+jump for 3 s) so the bot at least unsticks from leaves or pillars. Pathfinder timeout reduced 30 s → 15 s. - runtime/skills/explore-far.js: new explore.far skill — walks ~48 blocks in a quadrant (NE/SE/SW/NW, rotating per call) so successive hints actually circle the spawn instead of bouncing in place. Blind walk fallback included. - runtime/reflex.js: when the scheduler is told to wander twice in a row by gather.* recover hints, it now dispatches explore.far instead so the bot actually leaves the patch it's stuck in. Resets the consecutiveWanderHints counter on any success. - runtime/reflex.js (sleep): no longer dispatches when the bot has neither a bed in inventory NOR a known shelter/base location — saved one dispatch + 5-min cooldown per restart at night. - runtime/reflex.js (eat): inventory check + lastEatAt always updated fix the eat-spam loop observed live (every tick fired "eat" → "no food in inventory" → again). - runtime/skills/chop-logs.js: recognise "no log within ..." as no_target so the recover hint switches the bot to wander/explore. npm test 124/124. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+39
-5
@@ -358,15 +358,19 @@ export async function chopNearestTree(bot) {
|
||||
// findBlock invokes the matcher for blocks that pass the maxDistance
|
||||
// pre-filter; in dense areas some have a synthetic shape with no
|
||||
// `.position`. Guard or we crash before we even start pathfinding.
|
||||
// Search radius widened to 64 (2026-05-26): live spawn at this server
|
||||
// had no trees in 32-block radius and the bot looped wander→gather→
|
||||
// fail forever. 64 ≈ one chunk in either direction.
|
||||
const SEARCH_RADIUS = 64;
|
||||
const log = bot.findBlock({
|
||||
matching: (b) => {
|
||||
if (!b || !b.position || !LOG_NAMES.includes(b.name)) return false;
|
||||
const key = `${b.position.x},${b.position.y},${b.position.z}`;
|
||||
return !blacklist.has(key);
|
||||
},
|
||||
maxDistance: 32,
|
||||
maxDistance: SEARCH_RADIUS,
|
||||
});
|
||||
if (!log) return { ok: false, detail: "no reachable log within 32 blocks" };
|
||||
if (!log) return { ok: false, detail: `no reachable log within ${SEARCH_RADIUS} blocks` };
|
||||
|
||||
ensureCollectBlock(bot);
|
||||
setMovementsForGather(bot);
|
||||
@@ -403,13 +407,43 @@ export async function wander(bot, radius = 12) {
|
||||
try {
|
||||
await withTimeout(
|
||||
bot.pathfinder.goto(new goals.GoalNear(tx, ty, tz, 2)),
|
||||
30_000,
|
||||
15_000,
|
||||
`wander(${tx},${tz})`,
|
||||
);
|
||||
return { ok: true, detail: { to: { x: tx, y: ty, z: tz } } };
|
||||
} catch (e) {
|
||||
warn("action", `wander failed: ${e.message}`);
|
||||
return { ok: false, detail: e.message };
|
||||
warn("action", `wander pathfinder failed: ${e.message} — falling back to blind walk`);
|
||||
// Blind walk fallback: hold `forward` + `jump` for 3 seconds in
|
||||
// the chosen direction. Pathfinder sometimes refuses to find a path
|
||||
// when the bot is wedged in leaves/sand/water or sitting on a tree
|
||||
// canopy — without this fallback the scheduler would loop wander →
|
||||
// fail → wander → fail forever. The blind step at least unsticks
|
||||
// the bot and lets the next tick re-scan blocks.
|
||||
try {
|
||||
await blindStepToward(bot, tx, tz, 3_000);
|
||||
return { ok: true, detail: { to: { x: tx, y: ty, z: tz }, mode: "blind" } };
|
||||
} catch (e2) {
|
||||
warn("action", `wander blind walk also failed: ${e2.message}`);
|
||||
return { ok: false, detail: `pathfinder: ${e.message}; blind: ${e2.message}` };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function blindStepToward(bot, targetX, targetZ, durationMs) {
|
||||
try {
|
||||
const here = bot.entity.position;
|
||||
const dx = targetX - here.x;
|
||||
const dz = targetZ - here.z;
|
||||
// Yaw such that +Z is south (0) and angles go clockwise looking down.
|
||||
// Mineflayer uses radians.
|
||||
const yaw = Math.atan2(-dx, -dz);
|
||||
await bot.look(yaw, 0, true);
|
||||
bot.setControlState("forward", true);
|
||||
bot.setControlState("jump", true);
|
||||
await new Promise((r) => setTimeout(r, durationMs));
|
||||
} finally {
|
||||
bot.setControlState("forward", false);
|
||||
bot.setControlState("jump", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user