v0.3.0-rc.2: manifesto / needs ladder L0-L10
Adds an explicit hierarchical needs catalogue that the reflex consults
on every tick. The bot now pursues tangible intermediate goals (food,
wood tools, shelter, stone tools, ...) instead of inheriting whatever
the curriculum thought was "next".
Ladder:
L0 alive HP>5, food>0, not in lava, not panic-near hostile
L1 food ≥6 food items in inventory (or sated + any food)
L2 tools_wood wooden_pickaxe + wooden_axe + wooden_sword
L3 shelter_basic bed placed nearby or in inventory
L4 tools_stone stone-tier triplet
L5 armor_basic any chestplate (pursue=null until craft.leather-*
lands; ladder gracefully skips)
L6 food_security ≥16 food items
L7 tools_iron iron-tier triplet (pursue=gather.stone for now)
L8 armor_iron iron chestplate (pursue=null for now)
L9 village_seed bed + chest in nearby blocks
L10 village_full never detected, falls through to curriculum
Each need has detect(snapshot) → bool and pursue(snapshot) →
{skillId, args} | null. The ladder picks the LOWEST unsatisfied
pursuable need. Needs whose pursue is null get recorded as
blockedNeeds and the walk continues — no stalling on missing skills.
Wired into curriculumReflex: manifesto takes precedence over
curriculum.plan when it has a concrete suggestion. Tests can pass
ctx.disableManifesto=true to exercise the curriculum branch
in isolation (existing reflex tests keep passing this way).
Pi self-reflection prompt now includes
"activeNeed (Maslow ladder L0-L10): L2 tools_wood → gather.logs"
so Pi advises at the right level instead of giving generic guidance.
skillId returned by pursue() is validated against the live registry
(rc.1 plumbing) — manifesto cannot accidentally dispatch a
hallucinated skill name.
Tests: 315 green (was 279 on rc.1, +36 new):
- runtime/manifesto/needs.test.js — 24 tests (per-need detect/pursue,
helper sums)
- runtime/manifesto/state.test.js — 10 tests (ladder walk, hostile
takeover at L0, armor skipping, caching)
- runtime/reflex.test.js — 2 integration tests (manifesto overrides
curriculum plan; well-fed bot pursues tools_stone)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+29
-6
@@ -26,6 +26,7 @@ import {
|
||||
} from "./actions.js";
|
||||
import { runSkill, getSkill } from "./skills/index.js";
|
||||
import { consult as consultAdvice, reportOutcome as reportAdviceOutcome } from "./coach/advice.js";
|
||||
import { pickActiveNeed } from "./manifesto/state.js";
|
||||
import { situationHash } from "./scenario-memory.js";
|
||||
import { tickModes } from "./modes.js";
|
||||
|
||||
@@ -438,6 +439,22 @@ function curriculumReflex(ctx) {
|
||||
const plan = s.curriculum?.plan;
|
||||
const wanderHintUntil = ctx.skillBackoff?.["__wander_hint__"] ?? 0;
|
||||
const wantWander = wanderHintUntil && Date.now() < wanderHintUntil;
|
||||
|
||||
// v0.3.0-rc.2 — manifesto layer. Walk the L0-L10 needs ladder; the
|
||||
// lowest unsatisfied need dictates the planned skill. The curriculum
|
||||
// plan is used as a fallback when the manifesto has nothing concrete
|
||||
// (e.g. armour pursue=null, or village_full with no specific next
|
||||
// step). This is what makes the bot pursue tangible intermediate
|
||||
// goals (tools_wood → shelter → tools_stone → ...) instead of
|
||||
// wandering in the same quadrant.
|
||||
//
|
||||
// Tests can pass ctx.disableManifesto=true to exercise the curriculum
|
||||
// branch in isolation without having to construct a full snapshot.
|
||||
const activeNeed = ctx.disableManifesto ? null : pickActiveNeed(s);
|
||||
if (activeNeed) {
|
||||
ctx.activeNeed = activeNeed;
|
||||
}
|
||||
const manifestoSkillId = activeNeed?.skillId ?? null;
|
||||
const metricRecovery = metricRecoverySkill(ctx, plan?.skillId);
|
||||
if (metricRecovery) {
|
||||
ctx.lastCurriculumAt = Date.now();
|
||||
@@ -476,7 +493,7 @@ function curriculumReflex(ctx) {
|
||||
// First hint → small wander (might just be 32-block reach issue).
|
||||
// Every subsequent hint while still inside the backoff window → use
|
||||
// explore.far so the bot actually leaves the patch it's stuck in.
|
||||
if (!plan?.skillId || wantWander) {
|
||||
if ((!plan?.skillId && !manifestoSkillId) || wantWander) {
|
||||
ctx.lastCurriculumAt = Date.now();
|
||||
const fallbackId = wantWander && consecutiveWanderHints >= 1 ? "explore.far" : "wander";
|
||||
// v0.2.0-rc.3 — consult advice on the FALLBACK dispatch too. Without
|
||||
@@ -511,15 +528,18 @@ function curriculumReflex(ctx) {
|
||||
return { action: "dispatched", kind: "curriculum-wander", label: "wander" };
|
||||
}
|
||||
|
||||
const skillId = plan.skillId;
|
||||
// Pick what to dispatch: manifesto wins over curriculum plan because
|
||||
// it expresses concrete needs rather than abstract "next milestone".
|
||||
const skillId = manifestoSkillId ?? plan.skillId;
|
||||
const skillSource = manifestoSkillId ? `manifesto:${activeNeed.need.id}` : "curriculum";
|
||||
const skill = getSkill(skillId);
|
||||
if (!skill) {
|
||||
// Curriculum suggested a skill that isn't registered yet — fall back
|
||||
// Suggested a skill that isn't registered yet — fall back
|
||||
// to wander rather than spinning. This is the right behaviour for
|
||||
// future milestones we haven't wired (e.g. shelter blueprints).
|
||||
ctx.lastCurriculumAt = Date.now();
|
||||
ctx.dispatch(() => wander(ctx.bot, 16), "wander", {});
|
||||
return { action: "dispatched", kind: "curriculum-wander", label: `wander (no skill ${skillId})` };
|
||||
return { action: "dispatched", kind: "curriculum-wander", label: `wander (no skill ${skillId}; source ${skillSource})` };
|
||||
}
|
||||
|
||||
// Per-skill backoff: if this exact skill failed with a non-recoverable
|
||||
@@ -558,7 +578,10 @@ function curriculumReflex(ctx) {
|
||||
}
|
||||
|
||||
ctx.lastCurriculumAt = Date.now();
|
||||
ctx.dispatch(() => runSkill(dispatchSkillId, ctx), dispatchSkillId, {
|
||||
const dispatchArgs = (manifestoSkillId && manifestoSkillId === dispatchSkillId)
|
||||
? (activeNeed.args ?? {})
|
||||
: {};
|
||||
ctx.dispatch(() => runSkill(dispatchSkillId, ctx, dispatchArgs), dispatchSkillId, {
|
||||
onComplete: (res) => {
|
||||
ctx.skillBackoff = ctx.skillBackoff ?? {};
|
||||
if (advice.lessonId) reportAdviceOutcome({ lessonId: advice.lessonId, succeeded: !!res?.ok });
|
||||
@@ -582,7 +605,7 @@ function curriculumReflex(ctx) {
|
||||
}
|
||||
},
|
||||
});
|
||||
return { action: "dispatched", kind: "curriculum-skill", label: dispatchSkillId };
|
||||
return { action: "dispatched", kind: "curriculum-skill", label: dispatchSkillId, source: skillSource };
|
||||
}
|
||||
|
||||
// ---- idle ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user