Files
mayatnikovandClaude Opus 4.7 28f5d9e483 fix(perception): use numeric block ids — callback matchers silently fail under ViaBackwards
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>
2026-05-26 13:45:29 +03:00

52 lines
2.1 KiB
JavaScript

// World perception primitives.
//
// PROBLEM: under mineflayer 1.21.4 + ViaBackwards (this server runs
// Paper 26.1.x with VV/VB translating 1.21.5+ protocol down to 1.21.4),
// bot.findBlock and bot.findBlocks with a CALLBACK matcher silently
// return null/empty — the Block objects passed into the callback have a
// wrong `.name` field because the protocol-side block-id mapping has
// drifted from minecraft-data 1.21.4. Confirmed live 2026-05-26 via
// diag.scan: oak_log count in radius 16 = 46 with `matching: numericId`
// but 0 with `matching: (b) => b.name === "oak_log"`.
//
// Mineflayer issue #2347 (findBlocks fails under ViaBackwards) is the
// upstream bug; the workaround is to feed numeric block IDs from the
// bot's registry directly. This module centralises that workaround so
// every skill stops growing its own subtly-broken matcher closure.
export function nameToId(bot, name) {
return bot.registry?.blocksByName?.[name]?.id ?? null;
}
export function namesToIds(bot, names) {
const out = [];
for (const n of names) {
const id = nameToId(bot, n);
if (typeof id === "number") out.push(id);
}
return out;
}
// Find up to `count` blocks matching any of the given names. Returns an
// array of Vec3 positions, sorted by mineflayer (typically by Manhattan
// distance from the bot). Empty when no name resolves to a registry id.
export function findBlocksByName(bot, names, { maxDistance = 64, count = 32 } = {}) {
const ids = namesToIds(bot, names);
if (ids.length === 0) return [];
return bot.findBlocks({ matching: ids, maxDistance, count }) || [];
}
// Same as findBlocksByName but returns the nearest matching Block object
// (not a Vec3). Skips positions for which a custom predicate returns
// false — used by callers that need post-filter logic like blacklists.
export function findNearestBlockByName(bot, names, { maxDistance = 64, predicate } = {}) {
const positions = findBlocksByName(bot, names, { maxDistance, count: 32 });
for (const pos of positions) {
const blk = bot.blockAt(pos);
if (!blk) continue;
if (predicate && !predicate(blk)) continue;
return blk;
}
return null;
}