Root cause of "bot just stands still": every gather.* skill was using
bot.findBlock({ matching: (b) => names.includes(b.name) }), and under
mineflayer 1.21.4 + ViaBackwards the Block objects fed into the
callback have a wrong .name field (Block.type / numeric id is still
correct — this is mineflayer issue #2347). Every search returned null,
every skill reported "no_target", reflex looped wander → tunnel-out
forever. The bot's logs said "dispatch ok" while the operator watched
it pace in circles.
Proven live with a new diag.match skill on play.xmatic.team:
findBlocks({matching: numericIds}) → 50 hits
findBlocks({matching: (b) => b.name === ...}) → 0 hits ← the bug
findBlock({matching: (b) => b.name === ...}) → null ← the bug
findBlock({matching: numericIds}) → dark_oak_log @ (606,62,110)
After this fix the same bot from the same spawn dispatches gather.logs
and reaches the chop loop ("chop: dark_oak_log at 606,62,110 (tool=fists)")
instead of returning "no reachable log within 64 blocks".
Changes:
- runtime/perception.js (new): findBlocksByName / findNearestBlockByName
centralise the numeric-id workaround for any future skill.
- runtime/actions.js: chopNearestTree, sleepInBed, placeCraftingTable now
use perception. Also load mineflayer-tool plugin alongside collectblock
(collectblock 1.6 hard-requires bot.tool to dispatch a dig).
- gather-stone, gather-wool, deposit-surplus rewritten to numeric-id
search. gather-wool also loads mineflayer-tool.
- diagnose-scan.js (new): two diagnostic skills — diag.scan reports
findBlocks counts per radius for common blocks; diag.match cross-tests
the four matcher styles so this regression can be re-proven on demand.
- runtime/skills/index.js: registers diag.scan + diag.match.
Memory: project_findblock_callback_broken_under_viabackwards.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two bugs the live self-improvement run exposed:
1) Auto-patch was spawned with detached:false, so when supervisor
restarted bot.js (file change after Pi's commit landed on the
auto branch), the auto-patch child was killed mid-way — Pi's
commit lived in the auto branch but never got cherry-picked.
Recovered manually this round via reflog + cherry-pick. Now
detached:true + child.unref() + a per-run log at
state/_auto-patch-last.log so the operator can read Pi's full
output later.
2) Pi's recovery-tunnel-out.test.js was created but not in npm test
script; tests would have stayed unrun forever. Added.
Also commits the Pi-authored skill (eb29591 cherry-picked):
- runtime/skills/recovery-tunnel-out.js (+ test)
- improvements to runtime/actions.js + runtime/skills/explore-far.js
- wired into runtime/skills/index.js
npm test 137/137.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When probe-cardinal shows all 4 directions blocked (the bot is in a
1×1 pit, surrounded by leaves, or in a corridor corner), don't just
hold forward+jump — actually dig the block above the bot's head,
jump into the new gap, repeat up to 3 times. Both wander and
explore.far now call escapePit() in this branch.
Observed live: bot fell into a pit at (623,71,106) after first
explore.far and looped wedged-jump→still-wedged→wedged-jump for 60s
before this fix. With escape-pit, the bot now actually breaks out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ground-truth finding (diag.physics):
forward N:0.03 E:3.38 S:0 W:3.26 → forward WORKS in unobstructed dirs
jump ΔY=1.25 → jump WORKS (vanilla height)
dig untested (no soft block within 6 of spawn)
So the bot CAN move and jump — the previous "stands still" symptom was
our wander/explore code picking blocked random angles and trusting a
pathfinder that times out on this server's terrain. Each retry just
picked another random direction, often the same blocked one.
- runtime/actions.js wander: probe 4 cardinal yaws for 800ms each,
measure actual Δ, commit to the best one for the remaining budget.
Falls back to "wedged-jump" (forward+jump 2.5s) only when ALL four
cardinals are <0.5 blocks.
- runtime/skills/explore-far.js: same probe-then-go shape, scaled to a
~48-block long walk in the best direction. Replaces the static
NE/SE/SW/NW quadrant rotation that ignored what was actually
walkable.
- runtime/movement-profiles.js: canDig back to true on gather/travel/
flee. The earlier "everything false" defensive default was based on
a wrong hypothesis (silent dig failure) — diag.physics + server-side
inspection (no anti-cheat plugin, spawn-protection=0) showed dig is
fine.
- runtime/compat.test.js: assertions follow profile defaults.
- runtime/skills/diagnose-physics.js: forward probe now tries 4
cardinals and returns trials + bestDir + bestDist so it can be used
to debug "wedged" reports later.
Verified live: bot now actually walks 47 blocks north after
probe.cardinal showed N:2.4 free. First end-to-end real movement on
play.xmatic.team since this session started.
npm test 124/124.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two-pronged response to user-confirmed "bot stands still, doesn't actually
chop" on play.xmatic.team:
1. Pin protocol — .env now sets MC_VERSION=1.21.4. minecraft-data has
wrong packet ID mappings for protocol 775 (server 26.1.2 via
ViaBackwards 5.9.1) — see mineflayer#3888 and #3717. 1.21.5 also has
an enchants decoder bug that breaks bot.dig. 1.21.4 is the last
protocol mineflayer 4.37.1 can speak cleanly through VIA.
2. Don't trust dig success — runtime/actions.js chopNearestTree and
runtime/skills/gather-stone.js now lookAt(face center)+forceLook,
await collectBlock, then re-read the target block. If the log/stone
is STILL there, return ok:false code:"silent_dig_failure" and
blacklist the position. Prevents the curriculum from reporting
"wood.16 in progress" while the world hasn't actually changed.
3. Defensive default — runtime/movement-profiles.js: canDig=false on
every profile until dig is confirmed working live. Otherwise
pathfinder schedules paths through must-dig blocks and the bot loops.
4. Ground-truth probe — runtime/skills/diagnose-physics.js dispatches
forward/jump/dig probes and writes the result to the diary. New
IPC command cmd:run-skill lets the operator (or a future curriculum
trigger) fire any skill on demand; it waits for the current action
to finish before dispatching. /tmp/pepa-runskill.mjs is a one-shot
client.
Live probe on play.xmatic.team confirmed: forward Δ=0.003 over 2s
(BROKEN — server rejects movement packets), jump ΔY=0.42 (likely
physics jitter, not a real jump). Strongly suggests an anti-cheat
plugin gating bot-style movements server-side — beyond protocol pin.
npm test 124/124.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Recovers the bot from the live-server symptoms reported 2026-05-26:
1) constant supervisor reconnects, 2) chop "clicks once and stops",
3) sleep does nothing without a bed and so blocks night-skipping for
other players, 4) curriculum reflex always fell through to wander.
Supervisor (#38):
- runtime/watch-filter.js: pure predicate excluding *.test.js + the
supervisor itself; recursive:true so skills/ + social/ edits also
restart. Burned a working main once when test files counted toward
the rollback threshold.
- runtime/supervisor.js: watch-triggered restarts no longer count
toward the crash-loop rollback path. Watcher is now recursive.
Chop / mine (#39):
- runtime/actions.js + runtime/skills/gather-stone.js: replaced raw
pathfinder.goto + bot.dig with mineflayer-collectblock's
bot.collectBlock.collect — handles approach, repositioning, LoS,
dig and pickup as one primitive. Old version "swung once" because
GoalGetToBlock often parked the bot in leaves above the log.
Sleep + bed (#40):
- runtime/actions.js: sleepInBed now ALSO places a carried bed on
solid ground next to the bot and sleeps on it. Critical so the bot
stops blocking player night-skipping the moment it owns a bed.
Bed pipeline (#41):
- runtime/skills/gather-wool.js: gather.wool skill — mines wool block
if any nearby, otherwise shears or attacks the nearest sheep.
- runtime/skills/craft.js: craftBedSkill (any colour the bot has ≥3
wool of, plus 3 planks, plus a table).
- runtime/curriculum.js: new milestone survive.bed sits between
wood.tools and stone.32 so the bot gets a bed BEFORE everything else.
Test fixture updated to include a red_bed in post-survive.bed stages.
Village / shelter / wheat (#42, #43):
- runtime/skills/build-shelter.js: village.build-shelter — real 3×3×3
resumable hut blueprint around the recorded base, places one block
per loop, idempotent so an interrupted build resumes correctly,
marks each placed block in the owned-blocks ledger.
- runtime/skills/deposit-surplus.js: village.deposit-surplus opens
the nearest chest and transfers surplus stacks while keeping a
reserve of tools/food/bed.
- runtime/skills/farm-wheat.js: farm.wheat does one step per call
(till adjacent-to-water grass, plant seeds, or harvest ripe wheat).
- runtime/curriculum.js: village.shelter milestone after base-site.
Scheduler glitch (root of "always wander"):
- runtime/bot.js: curriculum + locations are now computed BEFORE
runTick. Previously they were stamped AFTER, so reflex.js saw
snapshot.curriculum=undefined every tick and fell through to the
wander fallback. Verified live: scheduler now dispatches
gather.logs/gather.stone/craft.* by id via runSkill.
Eat-spam:
- runtime/reflex.js: eatReflex now checks inventory for actual food
and updates lastEatAt on EVERY dispatch (not only successes), so a
failed eat respects the 5 s cooldown instead of firing every tick.
npm test 123/123. Validated live on play.xmatic.team (curriculum
dispatched gather.logs via runSkill, recover hint switched to wander
when no log in range).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the loop "стой и кидай proposals" → "копит ресурсы, строит,
работает к глобальной цели". Three pieces:
1. Crafting primitives (runtime/actions.js).
craftPlanks (4 per log, any wood type), craftSticks (4 per 2 planks),
placeCraftingTable (crafts a table from planks if needed + places at
reference block + reuses an existing table within 4 m), craftWoodenAxe,
craftWoodenPickaxe, craftWoodenSword. Each uses bot.recipesFor()
+ bot.craft() with a 15s timeout. Returns the same {ok, detail}
contract as the other actions.
inv.{getItemCount, getAnyPlanksCount, getAnyLogCount} helpers
exported so the reflex layer can read inventory cheaply without
pulling mineflayer state through every reducer.
2. Tech-tree reflex (runtime/reflex.js).
New techTreeReflex between sleep and autonomous. Inventory-driven
progression: log+0 planks → planks; planks+0 sticks → sticks;
planks+sticks+no axe → wooden_axe; +no pickaxe → wooden_pickaxe;
+no sword → wooden_sword. 5 s cooldown so we don't fire on every
tick.
Pure script, no LLM. The progression is exactly what a player
does in the first 10 min on a new world; making it scripted means
the bot never burns tokens on it.
3. LLM planner (runtime/planner.js).
Background timer (every 15 min, with a 30 s warm-up after start).
Reads goal.md + plan.md + a slim snapshot, prompts Pi to output a
fresh plan.md to stdout. Stripped of code fences and written
verbatim to state/<host>/plan.md. Capped at 16 KB.
The plan is markdown the operator can read or edit by hand. Numbered
milestones, ✓ prefix for completed ones, kept short. The reflex
layer doesn't auto-execute LLM text — but the planner sets the
long-horizon shape that future reflexes (build house, plant farm)
can read.
5 min timeout on the pi subprocess. If it crashes or times out, the
next 15-min tick just retries — no propagation to the reflex loop.
The progression now looks like, roughly:
chop log (autonomous) →
craft planks → craft sticks → wooden_axe (tech-tree) →
chop faster (autonomous, has axe now) →
wooden_pickaxe + wooden_sword (tech-tree) →
mine stone … (next PR: stone tools, farm site selection,
house frame)
Smoke-tested: all three modules import cleanly, exports check out.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>