Phase 7 of plans/autonomous-survival-bot-prd.md. Five small modules
that close the recurring "shared state" and "version-pinned list"
failure modes the PRD flags in §7 and §5.4.
New:
- runtime/movement-profiles.js: named profiles (GATHER, TRAVEL, FLEE,
BUILD, RETURN_TO_BASE) as pure descriptors via PROFILE_DEFAULTS,
plus applyProfile(profile, bot) that hands a fresh Movements to
pathfinder. Avoids the "flee left canDig=false on the shared
Movements, next chop got stuck in canopy" regression.
- runtime/owned-blocks.js: JSONL ledger of blocks this bot placed/
removed (state/<host>/owned-blocks.jsonl); isOwned({x,y,z}) for
O(1) lookups; ensureDir() makes the parent dir lazily.
- runtime/claim-avoidance.js: classifyArea({blocks, isOwned}) returns
player_build / natural_or_owned / insufficient_data based on
man-made block density vs ownership ratio; shouldAvoid(area) helper.
Designed for gather/place skills to call before touching contested
area.
- runtime/skills/compat.test.js: runs runtime/skills/groups.js against
real minecraft-data registries for 1.18.2, 1.20.4, 1.21.5; spot-
checks that pale_oak_log only appears on 1.21+ etc.
- runtime/compat.test.js: 10 tests covering movement descriptors,
isManMadeBlockName, classifyArea, owned-blocks markPlaced/dedup/
isOwned/markRemoved.
npm test now 79/79.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
2.6 KiB
JavaScript
95 lines
2.6 KiB
JavaScript
// "Does this look like a player build?" heuristic. Pure function;
|
|
// caller passes a list of nearby blocks and an isOwned predicate.
|
|
//
|
|
// Approach (intentionally conservative — false positives are fine):
|
|
// * Count the man-made blocks within the test set. Man-made =
|
|
// processed wood, stone bricks, smooth stone, glass, wool, etc.
|
|
// Things that don't naturally generate without effort.
|
|
// * Count owned-by-this-bot blocks.
|
|
// * If man-made density is high AND the area is not predominantly
|
|
// owned by this bot → flag as player_build. The skill code
|
|
// should refuse to dig/place there.
|
|
//
|
|
// We don't try to detect ages, signs, or claim plugins — heuristics
|
|
// only. The cost of false positives is "bot wandered around a
|
|
// rectangular hut", which is fine; the cost of false negatives is
|
|
// "bot griefed a player base", which is unacceptable.
|
|
|
|
const MAN_MADE_PREFIXES = [
|
|
"_planks",
|
|
"stone_bricks",
|
|
"smooth_stone",
|
|
"polished_",
|
|
"chiseled_",
|
|
"glass",
|
|
"wool",
|
|
"terracotta",
|
|
"concrete",
|
|
"sandstone_stairs",
|
|
"_slab",
|
|
"_stairs",
|
|
"_fence",
|
|
"_door",
|
|
"_trapdoor",
|
|
"crafting_table",
|
|
"furnace",
|
|
"chest",
|
|
"barrel",
|
|
"shulker_box",
|
|
"anvil",
|
|
"enchanting_table",
|
|
"bookshelf",
|
|
"ladder",
|
|
];
|
|
|
|
const MAN_MADE_EXACT = new Set([
|
|
"crafting_table",
|
|
"furnace",
|
|
"chest",
|
|
"barrel",
|
|
"bookshelf",
|
|
"anvil",
|
|
"enchanting_table",
|
|
"ladder",
|
|
"bell",
|
|
"lectern",
|
|
"composter",
|
|
"smoker",
|
|
"blast_furnace",
|
|
"loom",
|
|
"cartography_table",
|
|
"fletching_table",
|
|
"stonecutter",
|
|
"grindstone",
|
|
]);
|
|
|
|
export function isManMadeBlockName(name) {
|
|
if (!name) return false;
|
|
if (MAN_MADE_EXACT.has(name)) return true;
|
|
return MAN_MADE_PREFIXES.some((p) => name.includes(p));
|
|
}
|
|
|
|
export function classifyArea({ blocks, isOwned, minSamples = 5, manMadeThreshold = 0.4 }) {
|
|
if (!Array.isArray(blocks) || blocks.length < minSamples) {
|
|
return { verdict: "insufficient_data", manMade: 0, owned: 0, total: blocks?.length ?? 0 };
|
|
}
|
|
let manMade = 0;
|
|
let owned = 0;
|
|
for (const b of blocks) {
|
|
if (!b || !b.name) continue;
|
|
if (isManMadeBlockName(b.name)) manMade++;
|
|
if (b.position && typeof isOwned === "function" && isOwned(b.position)) owned++;
|
|
}
|
|
const total = blocks.length;
|
|
const manMadeRatio = manMade / total;
|
|
const ownedRatio = owned / total;
|
|
if (manMadeRatio >= manMadeThreshold && ownedRatio < manMadeRatio * 0.6) {
|
|
return { verdict: "player_build", manMade, owned, total, manMadeRatio, ownedRatio };
|
|
}
|
|
return { verdict: "natural_or_owned", manMade, owned, total, manMadeRatio, ownedRatio };
|
|
}
|
|
|
|
export function shouldAvoid(area) {
|
|
return area?.verdict === "player_build";
|
|
}
|