chore: snapshot pre-v0.2.0 WIP (pathfinder/reflex/metrics/skills improvements)
Baseline for the v0.2.0 self-learning iteration. All 205 tests pass on this state. Subsequent commits in this branch layer the knowledge base, post-mortem coach, and persona narration on top. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+124
-4
@@ -1,6 +1,9 @@
|
||||
// Build a compact, JSON-safe snapshot of the world around the bot. Used both
|
||||
// for reflex decisions and for periodic IPC STATUS events.
|
||||
|
||||
import { foods } from "./skills/groups.js";
|
||||
import { findBlocksByName } from "./perception.js";
|
||||
|
||||
function vec3ToObj(v) {
|
||||
if (!v) return null;
|
||||
return { x: Math.round(v.x * 100) / 100, y: Math.round(v.y * 100) / 100, z: Math.round(v.z * 100) / 100 };
|
||||
@@ -29,6 +32,95 @@ const HOSTILE = new Set([
|
||||
"bogged",
|
||||
]);
|
||||
|
||||
const PASSIVE = new Set([
|
||||
"cow",
|
||||
"pig",
|
||||
"chicken",
|
||||
"sheep",
|
||||
"rabbit",
|
||||
"mooshroom",
|
||||
"cod",
|
||||
"salmon",
|
||||
]);
|
||||
|
||||
const INTERESTING_BLOCK_GROUPS = Object.freeze({
|
||||
logs: ["oak_log", "dark_oak_log", "spruce_log", "birch_log", "jungle_log", "acacia_log", "mangrove_log", "cherry_log", "pale_oak_log"],
|
||||
stone: ["stone", "cobblestone", "deepslate", "cobbled_deepslate", "andesite", "diorite", "granite"],
|
||||
water: ["water"],
|
||||
lava: ["lava", "fire", "soul_fire"],
|
||||
beds: ["white_bed", "orange_bed", "magenta_bed", "light_blue_bed", "yellow_bed", "lime_bed", "pink_bed", "gray_bed", "light_gray_bed", "cyan_bed", "purple_bed", "blue_bed", "brown_bed", "green_bed", "red_bed", "black_bed"],
|
||||
storage: ["chest", "trapped_chest", "barrel"],
|
||||
wool: ["white_wool", "orange_wool", "magenta_wool", "light_blue_wool", "yellow_wool", "lime_wool", "pink_wool", "gray_wool", "light_gray_wool", "cyan_wool", "purple_wool", "blue_wool", "brown_wool", "green_wool", "red_wool", "black_wool"],
|
||||
crops: ["wheat", "carrots", "potatoes", "beetroots", "sweet_berry_bush"],
|
||||
coal: ["coal_ore", "deepslate_coal_ore"],
|
||||
});
|
||||
|
||||
function inventoryCounts(bot) {
|
||||
return (bot.inventory?.items?.() ?? []).reduce((acc, item) => {
|
||||
acc[item.name] = (acc[item.name] ?? 0) + item.count;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function countInterestingBlocks(bot, pos, radius = 16) {
|
||||
const out = {};
|
||||
for (const [kind, names] of Object.entries(INTERESTING_BLOCK_GROUPS)) {
|
||||
let positions = [];
|
||||
try {
|
||||
positions = findBlocksByName(bot, names, { maxDistance: radius, count: 16 });
|
||||
} catch {
|
||||
positions = [];
|
||||
}
|
||||
if (positions.length === 0) continue;
|
||||
const nearest = positions
|
||||
.map((p) => ({ position: vec3ToObj(p), distance: Math.round(Math.hypot(p.x - pos.x, p.z - pos.z) * 10) / 10 }))
|
||||
.sort((a, b) => a.distance - b.distance)[0];
|
||||
out[kind] = { count: positions.length, nearest };
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function carriedEquipment(bot) {
|
||||
const slots = bot.inventory?.slots ?? [];
|
||||
return {
|
||||
hand: bot.heldItem?.name ?? null,
|
||||
head: slots[5]?.name ?? null,
|
||||
torso: slots[6]?.name ?? null,
|
||||
legs: slots[7]?.name ?? null,
|
||||
feet: slots[8]?.name ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function hasEdibleFood(bot, inventory) {
|
||||
const allowed = foods(bot);
|
||||
return Object.keys(inventory ?? {}).some((name) => allowed.has(name));
|
||||
}
|
||||
|
||||
function entityDistance(e, pos) {
|
||||
try {
|
||||
return Math.round(e.position.distanceTo(pos) * 10) / 10;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function entitySnapshot(e, pos) {
|
||||
return {
|
||||
name: e.username ?? e.name ?? e.displayName ?? "?",
|
||||
type: e.type ?? null,
|
||||
distance: entityDistance(e, pos),
|
||||
position: vec3ToObj(e.position),
|
||||
};
|
||||
}
|
||||
|
||||
function blockNameAt(bot, pos) {
|
||||
try {
|
||||
return bot.blockAt(pos)?.name ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function snapshot(bot) {
|
||||
if (!bot || !bot.entity) {
|
||||
return { connected: false };
|
||||
@@ -42,10 +134,29 @@ export function snapshot(bot) {
|
||||
return !best || d < best.d ? { d, e } : best;
|
||||
}, null);
|
||||
|
||||
const inventory = (bot.inventory?.items?.() ?? []).reduce((acc, item) => {
|
||||
acc[item.name] = (acc[item.name] ?? 0) + item.count;
|
||||
return acc;
|
||||
}, {});
|
||||
const inventory = inventoryCounts(bot);
|
||||
const nearbyBlocks = countInterestingBlocks(bot, pos);
|
||||
const droppedItems = entities
|
||||
.filter((e) => e.type === "object" || e.name === "item")
|
||||
.filter((e) => e.position && e.position.distanceTo(pos) <= 24)
|
||||
.map((e) => entitySnapshot(e, pos))
|
||||
.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity))
|
||||
.slice(0, 12);
|
||||
const passives = entities
|
||||
.filter((e) => PASSIVE.has((e.name || "").toLowerCase()) && e.position)
|
||||
.map((e) => entitySnapshot(e, pos))
|
||||
.sort((a, b) => (a.distance ?? Infinity) - (b.distance ?? Infinity))
|
||||
.slice(0, 12);
|
||||
const footBlock = blockNameAt(bot, pos);
|
||||
const belowBlock = blockNameAt(bot, pos.offset(0, -1, 0));
|
||||
const headBlock = blockNameAt(bot, pos.offset(0, 1, 0));
|
||||
const hazards = {
|
||||
lavaNearby: !!nearbyBlocks.lava,
|
||||
inFluid: footBlock === "water" || footBlock === "lava",
|
||||
footBlock,
|
||||
belowBlock,
|
||||
headBlock,
|
||||
};
|
||||
|
||||
return {
|
||||
connected: true,
|
||||
@@ -60,6 +171,15 @@ export function snapshot(bot) {
|
||||
weather: { rain: bot.isRaining, thunder: bot.thundering },
|
||||
dimension: bot.game?.dimension,
|
||||
inventory,
|
||||
hasFood: hasEdibleFood(bot, inventory),
|
||||
equipment: carriedEquipment(bot),
|
||||
nearbyBlocks,
|
||||
nearbyEntities: {
|
||||
passives,
|
||||
droppedItems,
|
||||
},
|
||||
hazards,
|
||||
biome: bot.blockAt?.(pos)?.biome?.name ?? bot.blockAt?.(pos)?.biome ?? null,
|
||||
players: players.map((p) => ({ name: p.username, distance: Math.round(p.position.distanceTo(pos)) })),
|
||||
hostileCount: hostiles.length,
|
||||
closestHostile: closestHostile
|
||||
|
||||
Reference in New Issue
Block a user