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>
runtime/actions.js — Mineflayer wrappers with hard timeouts and structured
{ok, detail} returns:
- attackNearest: equip best melee, lookAt, single swing per call
- fleeFrom: lazy-load pathfinder, walk N blocks away (canDig=false to
avoid burrowing through walls under panic)
- eatBestFood: scan inventory by FOOD_PRIORITY, equip + consume
- sleepInBed: find nearest placed bed within 16 blocks, path to it, sleep
- goTo: pathfinder.goto for operator come/follow
runtime/reflex.js — bodies now dispatch real actions via ctx.dispatch:
- operator-goal (highest): satisfy come/follow command
- defend: ≤4m attack, ≤12m + low HP/many hostiles flee
- eat: food < 16 + 5s cooldown
- sleep: night + safe + 30s retry cooldown
- idle: heartbeat every 20th tick
Reflex returns "skipped" when ctx.busy so we don't count busy ticks as
either productive or noop in the escalation counter.
runtime/bot.js:
- ctx.dispatch fire-and-forget wrapper with busy gate, onComplete hook
- consecutiveNoops counter; after ESCALATE_AFTER_NOOPS (=20, ~1 min at
tick=3s), askPi with the current snapshot. 10-min cooldown.
- operator chat handler: parses `<botname> <verb>` messages from
OPERATOR_USERNAMES. Verbs: status, pause, resume, stop, come.
- Death drops any pending operator goal.
Smoke-tested live against play.xmatic.team:25565: bot connected, logged
in via AuthMe, reflex chain dispatched flee/sleep, hard timeout fired
when pathfinder couldn't reach the flee target (expected — no usable
ground path in dark_forest at this spawn).
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>