fix(runtime/skills): guard stationary blind fallback
This commit is contained in:
+14
-1
@@ -454,6 +454,7 @@ export async function wander(bot, radius = 12) {
|
|||||||
return { ok: true, detail: { to: { x: tx, y: ty, z: tz }, via: best.name } };
|
return { ok: true, detail: { to: { x: tx, y: ty, z: tz }, via: best.name } };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn("action", `wander pathfinder failed: ${e.message} — continuing blind in ${best.name}`);
|
warn("action", `wander pathfinder failed: ${e.message} — continuing blind in ${best.name}`);
|
||||||
|
const beforeBlind = clonePos(bot.entity.position);
|
||||||
try { await bot.look(best.yaw, 0, true); } catch {}
|
try { await bot.look(best.yaw, 0, true); } catch {}
|
||||||
bot.setControlState("forward", true);
|
bot.setControlState("forward", true);
|
||||||
bot.setControlState("jump", true);
|
bot.setControlState("jump", true);
|
||||||
@@ -463,7 +464,19 @@ export async function wander(bot, radius = 12) {
|
|||||||
bot.setControlState("forward", false);
|
bot.setControlState("forward", false);
|
||||||
bot.setControlState("jump", false);
|
bot.setControlState("jump", false);
|
||||||
}
|
}
|
||||||
return { ok: true, detail: { to: { x: tx, y: ty, z: tz }, mode: "blind", via: best.name } };
|
const moved = horizontalDistance(beforeBlind, bot.entity.position);
|
||||||
|
if (moved < 0.75) {
|
||||||
|
info("action", `wander: blind ${best.name} moved only ${moved.toFixed(2)} horizontally → tunnel-out`);
|
||||||
|
const tunnel = await digEscapeTunnel(bot, { maxSteps: 3, reason: `wander blind ${best.name}` });
|
||||||
|
const detail = { to: { x: tx, y: ty, z: tz }, mode: "blind", via: best.name, moved, recovery: tunnel.detail ?? null };
|
||||||
|
if (!tunnel.ok) return { ok: false, code: tunnel.code ?? "wedged", detail, worldDelta: tunnel.worldDelta ?? null };
|
||||||
|
return { ok: true, detail: { ...(tunnel.detail ?? {}), previousMode: "blind", recovered: true }, worldDelta: tunnel.worldDelta ?? null };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
detail: { to: { x: tx, y: ty, z: tz }, mode: "blind-moved", previousMode: "blind", via: best.name, moved },
|
||||||
|
worldDelta: { movedTo: clonePos(bot.entity.position) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,24 +122,62 @@ export const skill = Object.freeze({
|
|||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn("action", `explore.far pathfinder failed: ${e.message} — continuing blind`);
|
warn("action", `explore.far pathfinder failed: ${e.message} — continuing blind`);
|
||||||
try { await bot.look(best.yaw, 0, true); } catch {}
|
return blindWalkOrTunnelOut(bot, {
|
||||||
bot.setControlState("forward", true);
|
yaw: best.yaw,
|
||||||
bot.setControlState("jump", true);
|
dirName: best.name,
|
||||||
try {
|
blindMs: args.blindMs ?? 7_000,
|
||||||
await new Promise((r) => setTimeout(r, 7_000));
|
tunnelPushMs: args.tunnelPushMs,
|
||||||
} finally {
|
reason: `explore.far blind ${best.name}`,
|
||||||
bot.setControlState("forward", false);
|
});
|
||||||
bot.setControlState("jump", false);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true, code: "done",
|
|
||||||
detail: { mode: "blind", dir: best.name },
|
|
||||||
worldDelta: { movedTo: null },
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function blindWalkOrTunnelOut(bot, { yaw, dirName, blindMs = 7_000, minMove = 0.75, tunnelPushMs, reason = "blind fallback" } = {}) {
|
||||||
|
const before = clonePos(bot.entity.position);
|
||||||
|
try { await bot.look(yaw, 0, true); } catch {}
|
||||||
|
bot.setControlState("forward", true);
|
||||||
|
bot.setControlState("jump", true);
|
||||||
|
try {
|
||||||
|
await new Promise((r) => setTimeout(r, Math.max(0, blindMs)));
|
||||||
|
} finally {
|
||||||
|
bot.setControlState("forward", false);
|
||||||
|
bot.setControlState("jump", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const moved = horizontalDistance(before, bot.entity.position);
|
||||||
|
if (moved >= minMove) {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
code: "done",
|
||||||
|
detail: { mode: "blind-moved", previousMode: "blind", dir: dirName, moved },
|
||||||
|
worldDelta: { movedTo: clonePos(bot.entity.position) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
info("action", `explore.far: blind ${dirName} moved only ${moved.toFixed(2)} horizontally → tunnel-out`);
|
||||||
|
const tunnelArgs = { maxSteps: 3, reason };
|
||||||
|
if (tunnelPushMs !== undefined) tunnelArgs.pushMs = tunnelPushMs;
|
||||||
|
const tunnel = await digEscapeTunnel(bot, tunnelArgs);
|
||||||
|
const detail = { mode: "blind", dir: dirName, moved, recovery: tunnel.detail ?? null };
|
||||||
|
if (!tunnel.ok) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
code: tunnel.code ?? "wedged",
|
||||||
|
detail,
|
||||||
|
worldDelta: tunnel.worldDelta ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
code: "done",
|
||||||
|
detail: { ...(tunnel.detail ?? {}), previousMode: "blind", recovered: true },
|
||||||
|
worldDelta: tunnel.worldDelta ?? { movedTo: clonePos(bot.entity.position) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _internal = { blindWalkOrTunnelOut };
|
||||||
|
|
||||||
const CARDINAL_YAWS = [
|
const CARDINAL_YAWS = [
|
||||||
{ name: "N", yaw: Math.PI },
|
{ name: "N", yaw: Math.PI },
|
||||||
{ name: "E", yaw: -Math.PI / 2 },
|
{ name: "E", yaw: -Math.PI / 2 },
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { test } from "node:test";
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
import { getSkill } from "./index.js";
|
import { getSkill } from "./index.js";
|
||||||
|
import { _internal as exploreFarInternal } from "./explore-far.js";
|
||||||
import { digEscapeTunnel, _internal } from "./recovery-tunnel-out.js";
|
import { digEscapeTunnel, _internal } from "./recovery-tunnel-out.js";
|
||||||
|
|
||||||
function makePos(x, y, z) {
|
function makePos(x, y, z) {
|
||||||
@@ -98,3 +99,25 @@ test("tunnel-out does not count jumping in place as escape", async () => {
|
|||||||
assert.equal(res.code, "wedged");
|
assert.equal(res.code, "wedged");
|
||||||
assert.match(res.detail.error, /moved only 0\.00 horizontally/);
|
assert.match(res.detail.error, /moved only 0\.00 horizontally/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("explore.far blind fallback does not report done when position is unchanged", async () => {
|
||||||
|
const blocks = {};
|
||||||
|
const bot = makeBot(blocks);
|
||||||
|
bot.look = async () => {};
|
||||||
|
bot.lookAt = async () => {};
|
||||||
|
bot.dig = async (block) => {
|
||||||
|
blocks[`${block.position.x},${block.position.y},${block.position.z}`] = "air";
|
||||||
|
};
|
||||||
|
bot.setControlState = () => {}; // no physics movement in this wedged simulation
|
||||||
|
|
||||||
|
const res = await exploreFarInternal.blindWalkOrTunnelOut(bot, {
|
||||||
|
yaw: 0,
|
||||||
|
dirName: "E",
|
||||||
|
blindMs: 0,
|
||||||
|
tunnelPushMs: 0,
|
||||||
|
});
|
||||||
|
assert.equal(res.ok, false);
|
||||||
|
assert.equal(res.code, "wedged");
|
||||||
|
assert.equal(res.detail.mode, "blind");
|
||||||
|
assert.equal(res.detail.dir, "E");
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user