const {test}=require('node:test'); const assert=require('node:assert/strict'); const ale=require('../.test-build/ale-store.js'); const llm=require('../.test-build/llm/index.js'); const {runLiveAutonomyTick}=require('../.test-build/autonomy.js'); test('inference preserves intervening observations and cancels after retirement',async()=>{ const now=Date.now(); const bot={id:'hero',name:'Zaku',status:'online',tier:'hero',decisions:0, personality:{activities:['explore']},autonomy:{managed:true},level:5,zone:'Old zone'}; let state={revision:1,bots:[bot],events:[],sequence:0,settings:{hardcore:{enabled:false}}}; const store={load:async()=>structuredClone(state),save:async(s,revision)=>{ assert.equal(revision,state.revision);state=s; }}; ale.recentAleEvents=async()=>[{at:new Date(now).toISOString(),kind:'player.snapshot',payload:{name:'Zaku',isBot:true,level:5,zoneName:'Old zone'}}]; let queued=0,retire=false; ale.queueAleCommand=async()=>{queued++;return {id:'intent'};}; llm.providerForBot=()=>({route:{provider:'ollama',model:'test'},provider:{decide:async()=>{ state.revision++; state.bots[0].level=6;state.bots[0].zone='New zone'; state.bots[0].autonomy.observedAt=new Date(now+1000).toISOString(); if(retire)state.bots[0].status='retired'; return {action:'explore',goal:'Explore nearby',reason:'Curious'}; }}}); assert.equal((await runLiveAutonomyTick(store,{}, {},{now})).ran,true); assert.equal(state.bots[0].level,6);assert.equal(state.bots[0].zone,'New zone'); assert.equal(queued,1); retire=true; assert.equal((await runLiveAutonomyTick(store,{}, {},{now,forceBotId:'hero'})).ran,false); assert.equal(queued,1); }); test('rest requests never enable indefinite stay',()=>{ const {compileLivePlayerbotIntent}=require('../.test-build/autonomy.js'); const source=compileLivePlayerbotIntent('A',{action:'rest',goal:'Recover',reason:'Tired'}); assert.doesNotMatch(source,/\+stay/);assert.match(source,/-stay/);assert.match(source,/GetHealthPct/);assert.match(source,/AzerothAITrainingOverride/); }); test('fresh full recovery removes unnecessary rest but stale or low mana does not',()=>{ const {availableIntentActions,decisionContext}=require('../.test-build/autonomy.js'); const bot={personality:{activities:['quest','rest']},world:{at:Date.now()/1000,healthPct:100,manaPct:100,questBrief:'A real quest',threatBrief:'Wolf level 7',readinessAt:Date.now()/1000,foodCount:0,drinkCount:0}}; assert.deepEqual(availableIntentActions(bot),['quest']);assert.match(decisionContext(bot),/A real quest/);assert.match(decisionContext(bot),/Wolf level 7/); bot.world.manaPct=20;assert.deepEqual(availableIntentActions(bot),['quest','rest']); bot.world.at-=200;assert.deepEqual(availableIntentActions(bot),['quest','rest']);assert.doesNotMatch(decisionContext(bot),/Wolf/); }); test('intent outcomes require fresh matching evidence, not just accepted commands',()=>{ const {startIntentCheck,observeIntentChecks}=require('../.test-build/intent-evidence.js'); const now=Date.now()/1000,b={level:10,world:{at:now,x:0,y:0,mapId:0,healthPct:50,manaPct:10,groupNames:[],xp:100},autonomy:{}}; const check=startIntentCheck(b,{action:'quest'},'q',now);b.autonomy.intentChecks=[check]; b.world.x=30;b.world.at=now+30;observeIntentChecks(b);assert.equal(check.status,'pending','Walking is not quest progress'); b.world.xp=110;observeIntentChecks(b);assert.equal(check.status,'observed'); const rest=startIntentCheck(b,{action:'rest'},'r',now+30);b.autonomy.intentChecks=[rest];b.world.healthPct=100;b.world.manaPct=100; observeIntentChecks(b);assert.equal(rest.status,'pending','Same snapshot cannot prove recovery'); b.world.at=now+60;b.world.inCombat=false;observeIntentChecks(b);assert.equal(rest.status,'observed'); const party=startIntentCheck(b,{action:'socialize'},'p',now+60,'Peer');b.autonomy.intentChecks=[party];b.world.at=now+90;b.world.nearby=['Peer'];observeIntentChecks(b);assert.equal(party.status,'pending'); b.world.groupId='g';b.world.groupNames=['Peer'];observeIntentChecks(b);assert.equal(party.status,'observed'); }); test('death interrupts intentions and unverified work times out honestly',()=>{ const {startIntentCheck,observeIntentChecks}=require('../.test-build/intent-evidence.js');const n=Date.now()/1000; const b={level:10,world:{at:n,x:0,y:0,mapId:0,groupNames:[],xp:100},autonomy:{}}; const c=startIntentCheck(b,{action:'trade'},'t',n);b.autonomy.intentChecks=[c];b.world.at=n+601;observeIntentChecks(b);assert.equal(c.status,'no_progress'); const d=startIntentCheck(b,{action:'quest'},'q',n+601);b.autonomy.intentChecks=[d];b.world.at=n+630;b.world.isGhost=true;observeIntentChecks(b);assert.equal(d.status,'interrupted'); });