Prioritize survival readiness over bot work and model goals
Core tests / core (push) Successful in 36s

This commit is contained in:
Codex
2026-09-24 04:05:48 +12:00
parent 1552e4c165
commit 0731774457
6 changed files with 135 additions and 16 deletions
+20
View File
@@ -40,3 +40,23 @@ test('foreground residents use configured local model',()=>{
assert.equal(route.provider,'ollama');
assert.equal(route.model,state.settings.brains.residentModel);
});
const {DeterministicProvider,LocalLLMProvider}=require('../.test-build/llm/index.js');
test('fresh injury overrides aggressive goals in both brain providers without a model call',async()=>{
const bot=seed().bots[0];
bot.personality.activities=['quest','rest'];bot.personality.aggression=100;
bot.world={at:Date.now()/1000,healthPct:70};
const observation={availableActions:['quest','rest']};
for(const provider of [new DeterministicProvider(),new LocalLLMProvider('ollama','unused',{})]){
const decision=await provider.decide(bot,observation);
assert.equal(decision.action,'rest');assert.match(decision.reason,/Staying alive/);
}
});
test('stale injury and unavailable rest do not override permitted decisions',async()=>{
const bot=seed().bots[0];bot.personality.activities=['quest','rest'];bot.personality.aggression=100;
bot.world={at:Date.now()/1000-120,healthPct:70};
const provider=new DeterministicProvider();
assert.equal((await provider.decide(bot,{availableActions:['quest','rest']})).action,'quest');
bot.world.at=Date.now()/1000;
assert.equal((await provider.decide(bot,{availableActions:['quest']})).action,'quest');
});
+13 -4
View File
@@ -2,8 +2,9 @@ from pathlib import Path
from lupa import LuaRuntime
setup='''
now=1000;os.time=function()return now end;calls={};people={}
function AuthDBQuery(sql)local i=1;return {GetUInt32=function(_,n)return n==0 and i or 10+i end,GetString=function()return i==1 and 'A' or 'B'end,NextRow=function()i=i+1;return i<=2 end} end
function AuthDBQuery(sql)if string.find(sql,'survival_holds',1,true) then return nil end;local i=1;return {GetUInt32=function(_,n)return n==0 and i or 10+i end,GetString=function()return i==1 and 'A' or 'B'end,NextRow=function()i=i+1;return i<=2 end} end
function GetPlayersInWorld()return people end
function GetPlayerByName(n)for _,p in pairs(people)do if p.name==n then return p end end end
function CreateLuaEvent(fn,ms)if ms==2000 then tick=fn end end
function RegisterPlayerEvent()end
auditRows={};function AuthDBExecute(sql)table.insert(auditRows,sql)end
@@ -13,7 +14,7 @@ function make(id,name)
local p={id=id,name=name,hp=100,distance=25,ai={}}
function p:GetGUIDLow()return self.id end;function p:GetName()return self.name end;function p:GetAccountId()return 10+self.id end
function p:IsPlayerbot()return not self.human end;function p:IsDead()return self.dead end;function p:GetBotAI()return self.ai end
function p:GetHealthPct()return self.hp end;function p:IsInCombat()return self.combat end
function p:GetPowerType()return self.powerType or 1 end;function p:GetPowerPct()return self.mana or 100 end;function p:GetAttackers()return self.attackers or {} end;function p:GetHealthPct()return self.hp end;function p:IsInCombat()return self.combat end
function p:IsTaxi()return self.taxi end;function p:IsCasting()return self.cast end;function p:IsRooted()return false end
function p:GetX()return self.x or 0 end;function p:GetY()return 0 end
function p:GetUnfriendlyUnitsInRange()return self.friendly and {} or self.selected and {self.selected} or {} end;function p:GetSelection()return self.selected end;function p:StopSpellCast()self.cast=false;self.interrupted=true end
@@ -22,7 +23,8 @@ function make(id,name)
function p:GetDistance()return self.distance end;function p:GetMapId()return 0 end;function p:GetPhaseMask()return 1 end
function p.ai:GetMaster()return p.master end;function p.ai:SetMaster(m)p.master=m end
function p.ai:HasGameClientMaster()return p.controlled end;function p.ai:IsMovementImpaired()return false end
function p.ai:GetGroupLeader()return leader end;function p.ai:ChangeStrategy(s,state)end
function p.ai:HasStrategy(s)return p.strategies and p.strategies[s] or false end
function p.ai:GetGroupLeader()return leader end;function p.ai:ChangeStrategy(s,state)p.strategies=p.strategies or {};p.strategies[string.sub(s,2)]=string.sub(s,1,1)=='+' end
function p.ai:DoSpecificAction(s)table.insert(calls,s);return true end
return p
end
@@ -45,8 +47,15 @@ l=new();l.execute("A.combat=true;A.hp=45;A.cast=true;step(0);assert(A.interrupte
l=new();l.execute("A.combat=true;A.hp=100;A.enemy={GetLevel=function()return 8 end,ToCreature=function()return nil end,IsHostileTo=function()return true end};step(0);assert(calls[1]=='flee')")
l=new();l.execute("A.selected={GetGUID=function()return 99 end,GetLevel=function()return 5 end,ToCreature=function(self)return self end,GetRank=function()return 1 end,IsHostileTo=function()return true end};step(0);assert(A.attackStopped and calls[1]=='flee')")
l=new();l.execute("A.friendly=true;A.selected={GetLevel=function()return 80 end,ToCreature=function()return nil end};step(0);assert(#calls==0)")
l=new();l.execute("A.combat=true;A.hp=40;step(0);A.x=10;step(2);A.combat=false;step(2);assert(string.find(auditRows[#auditRows], '6F75742D6F662D636F6D626174'))")
l=new();l.execute("A.combat=true;A.hp=40;step(0);A.x=10;step(2);A.combat=false;step(2);local found=false;for _,q in pairs(auditRows)do if string.find(q,'6F75742D6F662D636F6D626174') then found=true end end;assert(found)")
l=new();l.execute("A.combat=true;A.hp=40;step(0);A.hp=70;step(30);assert(string.find(auditRows[#auditRows], '6E6F2D6D6F76656D656E74'))")
l=new();l.execute("group();B.distance=150;step(0);assert(B.master==A)")
l=new();l.execute("group();A.distance=40;step(0);assert(A.waited);A.waited=false;step(22);assert(not A.waited)")
print('Earlier retreat, casting interruption, hostile/elite avoidance, bounded leader waiting, longer regroup and observed retreat outcomes passed')
l=new();l.execute("A.hp=80;A.strategies={rpg=true,grind=true,gather=true};step(0);assert(calls[1]=='eat' and A.strategies.rpg==false and A.strategies.grind==false);assert(AzerothAISurvivalOverride(A));A.hp=90;step(2);assert(AzerothAISurvivalOverride(A))")
l=new();l.execute("A.powerType=0;A.mana=20;step(0);assert(calls[1]=='eat' and calls[2]=='drink' and AzerothAISurvivalOverride(A))")
l=new();l.execute("A.combat=true;A.hp=75;A.attackers={1,2};step(0);assert(calls[1]=='flee')")
l=new();l.execute("A.combat=true;step(0);A.hp=75;step(2);assert(calls[1]=='flee')")
l=new();l.execute("A.hp=80;step(0);A.hp=100;local query=AuthDBQuery;function AuthDBQuery(sql)if string.find(sql,'survival_holds WHERE guid=1',1,true) then return {GetUInt32=function(_,i)return ({[0]=1,[2]=11,[3]=1,[4]=0,[5]=0,[6]=0,[7]=0})[i] end,GetString=function()return 'A' end} end;return query(sql)end;step(30);assert(A.strategies.rpg==true);assert(not AzerothAISurvivalOverride(A))")
print('Readiness holds, mana recovery, outnumbered/rapid-loss retreat, and original-strategy restoration passed')