feat(runtime): autonomous reflex — chop wood + wander + proactive movement (#8)
Closes the "bot stands on a tree doing nothing" problem reported live when the operator launched the TUI after PR #6 landed. The reactive chain (operator > defend > eat > sleep > idle) was passive by design: day-time, full HP and food, no hostile within 4 m ⇒ every reflex returned noop. The bot perched in dark-oak canopy and never moved. Changes runtime/actions.js: - chopNearestTree: find any *_log within 32 blocks, equip best axe (falls back to fists), path to the block, dig. Per-bot 5-min blacklist of unreachable log positions so we don't grind on the same impossible target. - wander: pick a random offset 6-16 blocks away and path there. - setMovementsForGather / setMovementsForTravel: every action that uses pathfinder now sets its own Movements profile (canDig=true) instead of inheriting whatever the previous caller left. The old behaviour caused chop to inherit flee's canDig=false and get stuck in the canopy. - fleeFrom now uses canDig=true too — the user observed the bot permanently stuck on a leaf block because escape required digging. runtime/reflex.js: - new autonomousReflex between sleep and idle. Cooldown 10s. Picks chop when log count < 16, else wander. When chop reports "no reachable log within 32 blocks" we switch to wander for 60s so we don't re-fire chop against the same impossible position. - defendReflex tightened: only flee when closest is ≤8m (or ≤12m on low HP). Avoids the "82 distant hostiles ⇒ constant flee loop" pathology observed at this spawn. - flee cooldown: same mob name within 60s ⇒ noop, so we yield to other reflexes if flee keeps timing out. - sleepReflex retry cooldown raised 30s → 5min. Sleeping fails permanently if no bed is around; the short retry blocked autonomous behaviour every tick. - ctx.lastReflex now records {name, label, ts} after each dispatched/completed reflex so the TUI can show what the bot just decided. runtime/bot.js: - per-tick snapshot adds lastReflex and busy fields for the TUI. - maybeReplyToPlayer: light canned greetings (yo/hey/hi/привет) to non-operators when they address the bot. 30s cooldown so we don't spam. tui/tui.tsx: - status bar shows either "▸ busy: <label>" while an action is in flight, or "last reflex: <name> (<label>) Ns ago" when idle. Gives an at-a-glance answer to "what is the bot doing right now?" Smoke-tested live (play.xmatic.team, 2026-05-25T13:30-13:39): - bot did dispatch chop tree (oak_log at 617,82,95) - real bug surfaced and 3-fail rule filed a proposal automatically - after the runtime fix, chop returns "no reachable log" gracefully - bot switched to wander on the next tick - position moved from (623.31, 85, 95.12) to (623.41, 86.02, 96.7) — the first observable movement in this dark-oak spawn Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #8.
This commit is contained in:
+20
@@ -123,10 +123,30 @@ function StatusBar({ snapshot, paused, connectedToBot }: { snapshot: Snapshot; p
|
||||
{snapshot.closestHostile ? ` closest=${snapshot.closestHostile.name}@${snapshot.closestHostile.distance}m` : ""}
|
||||
{snapshot.pendingProposals ? <Text color="magenta" bold>{` [proposals ${snapshot.pendingProposals}, press y]`}</Text> : null}
|
||||
</Text>
|
||||
<Text>
|
||||
{snapshot.busy ? (
|
||||
<Text color="cyan">▸ busy: {snapshot.busy.label}</Text>
|
||||
) : snapshot.lastReflex ? (
|
||||
<Text dimColor>
|
||||
last reflex: {snapshot.lastReflex.name}
|
||||
{snapshot.lastReflex.label ? ` (${snapshot.lastReflex.label})` : ""}{" "}
|
||||
{snapshot.lastReflex.ts ? formatAge(snapshot.lastReflex.ts) : ""}
|
||||
</Text>
|
||||
) : (
|
||||
<Text dimColor>no reflex action yet</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function formatAge(tsMs: number): string {
|
||||
const ageMs = Date.now() - tsMs;
|
||||
if (ageMs < 60_000) return `${Math.floor(ageMs / 1000)}s ago`;
|
||||
if (ageMs < 3600_000) return `${Math.floor(ageMs / 60_000)}m ago`;
|
||||
return `${Math.floor(ageMs / 3600_000)}h ago`;
|
||||
}
|
||||
|
||||
function ProposalPanel({
|
||||
proposal,
|
||||
onClose,
|
||||
|
||||
Reference in New Issue
Block a user