v0.3.1: survival behaviour overhaul — storyline, biome-aware scout, wedge-relocate, food/perf fixes, monitor TUI #28
+23
-10
@@ -540,16 +540,29 @@ function curriculumReflex(ctx) {
|
|||||||
return { action: "dispatched", kind: "curriculum-wander", label: "wander" };
|
return { action: "dispatched", kind: "curriculum-wander", label: "wander" };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick what to dispatch. Order: manifesto > storyline > curriculum.
|
// Pick what to dispatch. Order:
|
||||||
// Manifesto is highest because L0 alive emergencies (lava, low-HP +
|
// 1. manifesto L0 (alive emergencies: low HP near hostile, lava
|
||||||
// hostile) must override any narrative aspiration. Storyline beats
|
// under foot, food=0) — absolute priority; do NOT let
|
||||||
// curriculum because it expresses a concrete operational subgoal,
|
// storyline overrule a "you're dying" signal.
|
||||||
// not just "next milestone".
|
// 2. storyline — concrete narrative subgoal ("collect 8 logs",
|
||||||
let skillId = manifestoSkillId ?? storySkillId ?? plan.skillId;
|
// "craft wooden pickaxe"). Beats manifesto L1+ because the
|
||||||
let skillSource = manifestoSkillId
|
// ladder needs operational direction, not just "you need food
|
||||||
? `manifesto:${activeNeed.need.id}`
|
// → dispatch acquire-food forever".
|
||||||
: storySkillId ? `storyline:${storyStep.step.id}`
|
// 3. manifesto L1+ — fallback when storyline has no concrete
|
||||||
: "curriculum";
|
// pursue (e.g. armor levels with pursue=null).
|
||||||
|
// 4. curriculum plan — legacy fallback.
|
||||||
|
const manifestoEmergency = activeNeed?.need?.level === 0;
|
||||||
|
let skillId, skillSource;
|
||||||
|
if (manifestoEmergency) {
|
||||||
|
skillId = manifestoSkillId ?? storySkillId ?? plan.skillId;
|
||||||
|
skillSource = `manifesto:${activeNeed.need.id}`;
|
||||||
|
} else if (storySkillId) {
|
||||||
|
skillId = storySkillId;
|
||||||
|
skillSource = `storyline:${storyStep.step.id}`;
|
||||||
|
} else {
|
||||||
|
skillId = manifestoSkillId ?? plan.skillId;
|
||||||
|
skillSource = manifestoSkillId ? `manifesto:${activeNeed.need.id}` : "curriculum";
|
||||||
|
}
|
||||||
|
|
||||||
// v0.3.0 fast-advisor: if a fresh recommendation is sitting on ctx
|
// v0.3.0 fast-advisor: if a fresh recommendation is sitting on ctx
|
||||||
// (the result of a previous tick's async advise() call), use it.
|
// (the result of a previous tick's async advise() call), use it.
|
||||||
|
|||||||
+58
-1
@@ -242,9 +242,10 @@ test("curriculum dispatches suggested skill by id", () => {
|
|||||||
assert.ok(typeof dispatches[0].opts.onComplete === "function");
|
assert.ok(typeof dispatches[0].opts.onComplete === "function");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("manifesto: hungry bot with no food drives survive.acquire-food (overrides curriculum plan)", () => {
|
test("manifesto: hungry bot with no food drives survive.acquire-food (manifesto fallback when storyline disabled)", () => {
|
||||||
const { ctx, dispatches } = makeCtx({
|
const { ctx, dispatches } = makeCtx({
|
||||||
disableManifesto: false,
|
disableManifesto: false,
|
||||||
|
disableStoryline: true,
|
||||||
snapshot: {
|
snapshot: {
|
||||||
connected: true,
|
connected: true,
|
||||||
health: 20,
|
health: 20,
|
||||||
@@ -264,6 +265,62 @@ test("manifesto: hungry bot with no food drives survive.acquire-food (overrides
|
|||||||
assert.equal(ctx.activeNeed?.need?.id, "food");
|
assert.equal(ctx.activeNeed?.need?.id, "food");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("L0 manifesto emergency: defend/modes layer catches the threat BEFORE curriculum", () => {
|
||||||
|
// HP=4 + creeper@3m fires `modes` (self_preservation) or defendReflex
|
||||||
|
// before curriculum even runs — which is the right outcome: alive
|
||||||
|
// emergencies don't reach the storyline/manifesto branch at all.
|
||||||
|
const { ctx, dispatches } = makeCtx({
|
||||||
|
disableManifesto: false,
|
||||||
|
disableStoryline: false,
|
||||||
|
bot: { entities: { z1: { name: "creeper", height: 1.7, position: { x: 3, y: 64, z: 0, distanceTo: () => 3 } } }, entity: { position: { x: 0, y: 64, z: 0 } } },
|
||||||
|
snapshot: {
|
||||||
|
connected: true,
|
||||||
|
health: 4,
|
||||||
|
food: 12,
|
||||||
|
hasFood: false,
|
||||||
|
inventory: { oak_log: 10 },
|
||||||
|
equipment: {},
|
||||||
|
nearbyBlocks: { logs: 4 },
|
||||||
|
hazards: { footBlock: "grass_block", belowBlock: "dirt", headBlock: "air" },
|
||||||
|
closestHostile: { name: "creeper", distance: 3 },
|
||||||
|
threats: [{ name: "creeper", distance: 3, position: { x: 3, y: 64, z: 0 } }],
|
||||||
|
isDay: true,
|
||||||
|
curriculum: { plan: { skillId: "gather.logs" } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const out = runTick(ctx);
|
||||||
|
assert.notEqual(out?.kind, "curriculum-skill", "an upstream reflex caught the emergency before curriculum");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("storyline beats manifesto L1+ when both have suggestions", () => {
|
||||||
|
// Snapshot: well-fed (food=20 + 8 bread → manifesto L1 satisfied, L2
|
||||||
|
// tools_wood unmet) AND storyline orient_self complete (HP=20, blocks
|
||||||
|
// visible). Storyline should drive a wood-tier crafting step, not
|
||||||
|
// manifesto's gather.logs (which would also be valid but less concrete).
|
||||||
|
const { ctx, dispatches } = makeCtx({
|
||||||
|
disableManifesto: false,
|
||||||
|
disableStoryline: false,
|
||||||
|
snapshot: {
|
||||||
|
connected: true,
|
||||||
|
health: 20,
|
||||||
|
food: 20,
|
||||||
|
hasFood: true,
|
||||||
|
inventory: { bread: 8, oak_log: 10 }, // logs done, no planks
|
||||||
|
equipment: {},
|
||||||
|
nearbyBlocks: { logs: 3 },
|
||||||
|
hazards: { footBlock: "grass_block", belowBlock: "dirt", headBlock: "air" },
|
||||||
|
isDay: true,
|
||||||
|
_sessionMs: 60_000,
|
||||||
|
curriculum: { plan: { skillId: "explore.far" } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const out = runTick(ctx);
|
||||||
|
assert.equal(out.reflex, "curriculum");
|
||||||
|
// Storyline step crafting_basics suggests craft.planks (10 logs, no planks yet).
|
||||||
|
assert.equal(dispatches[0].label, "craft.planks");
|
||||||
|
assert.equal(ctx.storyStep?.step?.id, "crafting_basics");
|
||||||
|
});
|
||||||
|
|
||||||
test("manifesto: well-fed bot with all wood tools defers to curriculum plan", () => {
|
test("manifesto: well-fed bot with all wood tools defers to curriculum plan", () => {
|
||||||
const { ctx, dispatches } = makeCtx({
|
const { ctx, dispatches } = makeCtx({
|
||||||
disableManifesto: false,
|
disableManifesto: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user