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>
98 lines
3.3 KiB
JavaScript
98 lines
3.3 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import { getSkill } from "./index.js";
|
|
import { digEscapeTunnel, _internal } from "./recovery-tunnel-out.js";
|
|
|
|
function makePos(x, y, z) {
|
|
return {
|
|
x, y, z,
|
|
offset(dx, dy, dz) { return makePos(x + dx, y + dy, z + dz); },
|
|
clone() { return makePos(x, y, z); },
|
|
};
|
|
}
|
|
|
|
function makeBlock(name, x, y, z) {
|
|
return {
|
|
name,
|
|
boundingBox: name === "air" ? "empty" : "block",
|
|
position: makePos(x, y, z),
|
|
};
|
|
}
|
|
|
|
function makeBot(blocks = {}) {
|
|
return {
|
|
entity: { position: makePos(0, 64, 0) },
|
|
blockAt(pos) {
|
|
const x = Math.floor(pos.x);
|
|
const y = Math.floor(pos.y);
|
|
const z = Math.floor(pos.z);
|
|
const name = blocks[`${x},${y},${z}`] ?? "stone";
|
|
return makeBlock(name, x, y, z);
|
|
},
|
|
canDigBlock(block) { return block.name !== "bedrock"; },
|
|
};
|
|
}
|
|
|
|
test("recovery.tunnel-out is registered", () => {
|
|
const skill = getSkill("recovery.tunnel-out");
|
|
assert.ok(skill);
|
|
assert.equal(skill.preconditions({}).ok, false);
|
|
assert.equal(skill.preconditions({ bot: makeBot() }).ok, true);
|
|
});
|
|
|
|
test("tunnel ranking prefers the most-free safe cardinal", () => {
|
|
const blocks = {};
|
|
// North is already a two-high open tunnel with solid floor.
|
|
for (let step = 1; step <= 3; step++) {
|
|
blocks[`0,64,${-step}`] = "air";
|
|
blocks[`0,65,${-step}`] = "air";
|
|
blocks[`0,63,${-step}`] = "dirt";
|
|
}
|
|
// West is blocked by a player/build-looking block and must be unusable.
|
|
blocks["-1,64,0"] = "oak_planks";
|
|
|
|
const ranked = _internal.rankTunnelDirections(makeBot(blocks), 3);
|
|
assert.equal(ranked[0].name, "N");
|
|
assert.equal(ranked[0].usable, true);
|
|
const west = ranked.find((d) => d.name === "W");
|
|
assert.equal(west.usable, false);
|
|
assert.deepEqual(west.blockers.map((b) => b.name), ["oak_planks"]);
|
|
});
|
|
|
|
test("safe dig guard allows natural blocks and rejects build/storage blocks", () => {
|
|
const bot = makeBot();
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("dirt", 1, 64, 0)), true);
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("oak_leaves", 1, 64, 0)), true);
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("oak_log", 1, 64, 0)), true);
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("oak_planks", 1, 64, 0)), false);
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("chest", 1, 64, 0)), false);
|
|
assert.equal(_internal.isSafeTunnelDigTarget(bot, makeBlock("bedrock", 1, 64, 0)), false);
|
|
});
|
|
|
|
test("tunnel-out does not count jumping in place as escape", async () => {
|
|
const blocks = {};
|
|
for (let step = 1; step <= 3; step++) {
|
|
blocks[`0,64,${-step}`] = "air";
|
|
blocks[`0,65,${-step}`] = "air";
|
|
blocks[`0,63,${-step}`] = "dirt";
|
|
}
|
|
// Make the other cardinals unusable so the test exercises one clean
|
|
// tunnel candidate and then verifies vertical-only movement is rejected.
|
|
blocks["1,64,0"] = "oak_planks";
|
|
blocks["0,64,1"] = "oak_planks";
|
|
blocks["-1,64,0"] = "oak_planks";
|
|
|
|
const bot = makeBot(blocks);
|
|
bot.setControlState = (control, on) => {
|
|
if (control === "jump" && on) {
|
|
bot.entity.position = makePos(bot.entity.position.x, bot.entity.position.y + 1, bot.entity.position.z);
|
|
}
|
|
};
|
|
|
|
const res = await digEscapeTunnel(bot, { maxSteps: 3, minMove: 0.75, pushMs: 0 });
|
|
assert.equal(res.ok, false);
|
|
assert.equal(res.code, "wedged");
|
|
assert.match(res.detail.error, /moved only 0\.00 horizontally/);
|
|
});
|