Files
pepa-pi-bot/runtime/manifesto/state.test.js
T
mayatnikovandClaude Opus 4.7 502d7ebae4 fix(v0.3.1): sated bot stops chasing food + perf-leak + fuzzy improvement dedup
Third day of "bot just walks back and forth burning tokens". Root
causes were mechanical, not the manifesto:

1. SATED BOT CHASING FOOD (the big one)
   Bot had food=17 (nearly full) but storyline first_food + manifesto
   L1 required 2+ food ITEMS in inventory, so it looped scout-food /
   acquire-food for hours instead of working. Now both treat a hunger
   bar >= 14 (SATED_FOOD) as satisfied even with empty food inventory —
   a full bot chops wood / makes tools and grabs food opportunistically,
   only hard-pursuing food when actually hungry (< 14).
   manifesto/needs.js foodDetect + goal/storyline.js first_food.completed.

2. perf_hooks MEMORY LEAK (overnight OOM suspect)
   "MaxPerformanceEntryBufferExceededWarning: 1,000,001 measure entries".
   mineflayer/pathfinder emit perf marks we never consume. Added a
   60s reaper in bot.js (performance.clearMeasures/clearMarks). unref'd.

3. IMPROVEMENT QUEUE SELF-DUPLICATING
   The LLM re-filed closed gaps with reworded titles (#5/#8/#9 were
   dupes of implemented #1/#2/#3). Exact-title dedup missed them.
   Replaced with token-set fuzzy match (isDuplicateTitle): jaccard>=0.75
   OR >=3 shared meaningful tokens with jaccard>=0.5. Also: a re-filed
   gap that's already implemented/rejected is NOT resurrected as a new
   open row. Cleared all 5 open requests (now genuinely implemented).

Also confirmed (no change needed):
- canDig=true is a DELIBERATE codebase-wide choice ("without it the bot
  gets permanently stuck", actions.js). The stale memory recommending
  canDig=false is updated. ViaBackwards dig works partially (dug:1
  moved:1.8 observed); false would trap the bot in every pit.
- scout-food already has blind/tunnel fallback + 12s step timeout
  (operator's earlier edits) so trapped-pathfinder degrades instead of
  hanging 30s.

Tests: 407 green (was 404). Updated needs/state/storyline tests for the
SATED_FOOD threshold; added fuzzy-dedup + tokenize/jaccard tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:34:42 +03:00

123 lines
3.6 KiB
JavaScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { pickActiveNeed, describeActiveNeed, _resetForTest } from "./state.js";
function snap(overrides = {}) {
return {
connected: true,
health: 20,
food: 20,
hasFood: false,
inventory: {},
equipment: { hand: null, head: null, torso: null, legs: null, feet: null },
nearbyBlocks: {},
hazards: { footBlock: "grass_block", belowBlock: "dirt", headBlock: "air" },
isDay: true,
hostileCount: 0,
closestHostile: null,
...overrides,
};
}
test("pickActiveNeed: disconnected → null", () => {
_resetForTest();
assert.equal(pickActiveNeed({ connected: false }), null);
assert.equal(pickActiveNeed(null), null);
});
test("pickActiveNeed: fresh spawn → L0 alive if zero food", () => {
_resetForTest();
const a = pickActiveNeed(snap({ food: 0 }));
assert.equal(a.need.id, "alive");
assert.equal(a.skillId, "survive.scout-food");
});
test("pickActiveNeed: hungry (food<14), no food in inventory → L1 food (scout)", () => {
_resetForTest();
const a = pickActiveNeed(snap({ food: 8 }));
assert.equal(a.need.id, "food");
assert.equal(a.skillId, "survive.scout-food");
});
test("pickActiveNeed: sated (food=20) + empty inventory → skips L1, goes to L2 tools_wood", () => {
_resetForTest();
const a = pickActiveNeed(snap());
assert.equal(a.need.id, "tools_wood", "sated bot works toward tools, not chasing food");
});
test("pickActiveNeed: food covered → L2 tools_wood (gather logs)", () => {
_resetForTest();
const a = pickActiveNeed(snap({ inventory: { bread: 8 } }));
assert.equal(a.need.id, "tools_wood");
assert.equal(a.skillId, "gather.logs");
});
test("pickActiveNeed: tools wood done → L3 shelter (gather wool)", () => {
_resetForTest();
const a = pickActiveNeed(snap({
inventory: {
bread: 8,
wooden_pickaxe: 1, wooden_axe: 1, wooden_sword: 1,
},
}));
assert.equal(a.need.id, "shelter_basic");
// no wool, no bed → gather.wool
assert.equal(a.skillId, "gather.wool");
});
test("pickActiveNeed: shelter done → L4 tools_stone", () => {
_resetForTest();
const a = pickActiveNeed(snap({
nearbyBlocks: { beds: 1 },
inventory: {
bread: 8,
wooden_pickaxe: 1, wooden_axe: 1, wooden_sword: 1,
},
}));
assert.equal(a.need.id, "tools_stone");
assert.equal(a.skillId, "gather.stone");
});
test("pickActiveNeed: armor pursue=null → ladder skips to food_security", () => {
_resetForTest();
// Everything up through tools_stone satisfied, no armor (pursue=null).
// Should advance to food_security, not stall.
const a = pickActiveNeed(snap({
nearbyBlocks: { beds: 1 },
inventory: {
bread: 8,
wooden_pickaxe: 1, wooden_axe: 1, wooden_sword: 1,
stone_pickaxe: 1, stone_axe: 1, stone_sword: 1,
},
}));
assert.equal(a.need.id, "food_security");
assert.ok(a.blockedNeeds.some((b) => b.id === "armor_basic"), "armor_basic recorded as blocked");
});
test("pickActiveNeed: hostile imminent + low HP → L0 takes over", () => {
_resetForTest();
const a = pickActiveNeed(snap({
health: 6,
closestHostile: { name: "creeper", distance: 3 },
inventory: { bread: 8, wooden_pickaxe: 1, wooden_axe: 1, wooden_sword: 1 },
nearbyBlocks: { beds: 1 },
}));
assert.equal(a.need.id, "alive");
assert.equal(a.skillId, "survive.flee");
});
test("describeActiveNeed: returns 'L<n> <id> → <skill>'", () => {
_resetForTest();
const s = describeActiveNeed(snap({ food: 8 }));
assert.match(s, /^L1 food → /);
});
test("pickActiveNeed: caches within TTL — same snapshot ref returns same result", () => {
_resetForTest();
const s = snap();
const a = pickActiveNeed(s);
const b = pickActiveNeed(s);
assert.equal(a, b, "second call returns cached object");
});