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" };
|
||||
}
|
||||
|
||||
// Pick what to dispatch. Order: manifesto > storyline > curriculum.
|
||||
// Manifesto is highest because L0 alive emergencies (lava, low-HP +
|
||||
// hostile) must override any narrative aspiration. Storyline beats
|
||||
// curriculum because it expresses a concrete operational subgoal,
|
||||
// not just "next milestone".
|
||||
let skillId = manifestoSkillId ?? storySkillId ?? plan.skillId;
|
||||
let skillSource = manifestoSkillId
|
||||
? `manifesto:${activeNeed.need.id}`
|
||||
: storySkillId ? `storyline:${storyStep.step.id}`
|
||||
: "curriculum";
|
||||
// Pick what to dispatch. Order:
|
||||
// 1. manifesto L0 (alive emergencies: low HP near hostile, lava
|
||||
// under foot, food=0) — absolute priority; do NOT let
|
||||
// storyline overrule a "you're dying" signal.
|
||||
// 2. storyline — concrete narrative subgoal ("collect 8 logs",
|
||||
// "craft wooden pickaxe"). Beats manifesto L1+ because the
|
||||
// ladder needs operational direction, not just "you need food
|
||||
// → dispatch acquire-food forever".
|
||||
// 3. manifesto L1+ — fallback when storyline has no concrete
|
||||
// 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
|
||||
// (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");
|
||||
});
|
||||
|
||||
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({
|
||||
disableManifesto: false,
|
||||
disableStoryline: true,
|
||||
snapshot: {
|
||||
connected: true,
|
||||
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");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const { ctx, dispatches } = makeCtx({
|
||||
disableManifesto: false,
|
||||
|
||||
Reference in New Issue
Block a user