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:
2026-05-26 11:18:36 +03:00
co-authored by Claude Opus 4.7
parent 29542f0559
commit 19dc8e12c6
6 changed files with 206 additions and 9 deletions
+34 -2
View File
@@ -26,6 +26,13 @@ import {
} from "./actions.js";
import { runSkill, getSkill } from "./skills/index.js";
// Each "wander hint" triggered by a skill returning no_target should take
// the bot meaningfully further than 16 blocks — otherwise the curriculum
// re-fires the same skill, gets no_target again, and the bot loops in
// place. We escalate every other wander hint into explore.far (~48
// blocks, quadrant-rotating).
let consecutiveWanderHints = 0;
const REFLEX_LOG = "reflex";
// A reflex returns one of:
@@ -117,11 +124,28 @@ function eatReflex(ctx) {
// ---- sleep -----------------------------------------------------------------
// Inventory check so the sleep reflex doesn't waste a dispatch when we
// have no bed AND no bed nearby — let the curriculum (survive.bed) drive
// bed acquisition instead. The action itself still re-checks, but pre-
// filtering here saves a dispatch + 5-min cooldown on impossible states.
const ANY_BED_NAME_RE = /(?:^|_)bed$/;
function hasAnyBedItem(inv) {
return Object.keys(inv ?? {}).some((n) => ANY_BED_NAME_RE.test(n));
}
function sleepReflex(ctx) {
const s = ctx.snapshot;
if (!s.connected) return { action: "noop" };
if (s.isDay) return { action: "noop" };
if (s.closestHostile && s.closestHostile.distance < 8) return { action: "noop" }; // not safe
// Skip dispatch entirely when there is no bed in inventory AND no
// placed bed location we know about. Otherwise every restart at night
// burns a "sleep → no bed" dispatch+5-min cooldown for nothing — saw
// this live 2026-05-26 where the bot would dispatch sleep right after
// every spawn before doing anything productive.
const bedItem = hasAnyBedItem(s.inventory);
const bedLoc = s.locations?.shelter ?? s.locations?.base ?? null;
if (!bedItem && !bedLoc) return { action: "noop" };
// Longer cooldown after a failure — if there's no bed nearby, retrying
// every 30s blocks autonomous behaviour without ever succeeding.
const since = Date.now() - (ctx.lastSleepAttemptAt ?? 0);
@@ -171,10 +195,16 @@ function curriculumReflex(ctx) {
const wanderHintUntil = ctx.skillBackoff?.["__wander_hint__"] ?? 0;
const wantWander = wanderHintUntil && Date.now() < wanderHintUntil;
// No skill plan from curriculum OR a recent skill asked us to wander
// dispatch a wander fallback so we keep moving.
// No skill plan from curriculum OR a recent skill asked us to wander.
// First hint → small wander (might just be 32-block reach issue).
// Every subsequent hint while still inside the backoff window → use
// explore.far so the bot actually leaves the patch it's stuck in.
if (!plan?.skillId || wantWander) {
ctx.lastCurriculumAt = Date.now();
if (wantWander && consecutiveWanderHints >= 1) {
ctx.dispatch(() => runSkill("explore.far", ctx), "explore.far", {});
return { action: "dispatched", kind: "curriculum-explore-far", label: "explore.far" };
}
ctx.dispatch(() => wander(ctx.bot, 16), "wander", {});
return { action: "dispatched", kind: "curriculum-wander", label: "wander" };
}
@@ -204,6 +234,7 @@ function curriculumReflex(ctx) {
// Same fix the old autonomous reflex applied for "no reachable
// log" — switch to exploration for a minute.
ctx.skillBackoff["__wander_hint__"] = Date.now() + SKILL_BACKOFF_MS;
consecutiveWanderHints++;
}
if (!res?.ok) {
// missing_tool / missing_material / no_target shouldn't be
@@ -215,6 +246,7 @@ function curriculumReflex(ctx) {
} else {
// Success clears the wander hint immediately.
ctx.skillBackoff["__wander_hint__"] = 0;
consecutiveWanderHints = 0;
}
},
});