Persist fatal encounters and audit resident readiness with party outcome metrics
Core tests / core (push) Successful in 42s

This commit is contained in:
Codex
2026-09-24 08:05:16 +12:00
parent 900b926b8e
commit d57d793a84
16 changed files with 254 additions and 15 deletions
+29
View File
@@ -0,0 +1,29 @@
from pathlib import Path
from lupa import LuaRuntime
source=(Path(__file__).resolve().parents[1]/'ale/azerothai_experience.lua').read_text()
l=LuaRuntime();l.execute('''
now=100000;os.time=function()return now end;events={};writes={};ddl=false
function CreateLuaEvent()end
function RegisterPlayerEvent(n,f)events[n]=f end
function AuthDBExecute(s)table.insert(writes,s)end
function AuthDBQuery(s)
if s:find('CREATE TABLE',1,true) then ddl=true;return nil end
if s:find('azerothai_lifecycle',1,true) then return {GetString=function()return 'A' end,GetUInt32=function()return 12 end} end
assert(ddl)
return {GetUInt32=function(_,i)return ({[0]=7,[1]=99990,[3]=50,[4]=0,[7]=5})[i] end,GetString=function()return 'Wolf' end,GetFloat=function()return 10 end,NextRow=function()return false end}
end
p={level=5,account=12};function p:GetGUIDLow()return 1 end;function p:GetAccountId()return self.account end;function p:GetName()return 'A' end
function p:IsPlayerbot()return true end;function p:GetBotAI()return {HasGameClientMaster=function()return p.controlled end} end
function p:GetMapId()return 0 end;function p:GetLevel()return self.level end;function p:GetX()return 10 end;function p:GetY()return 10 end
enemy={x=10};function enemy:ToCreature()return self end;function enemy:GetEntry()return 50 end;function enemy:GetX()return self.x end;function enemy:GetY()return 10 end;function enemy:GetName()return 'Wolf' end
''');l.execute(source)
l.execute('''
assert(AzerothAILearnedDanger(p,enemy));enemy.x=500;assert(not AzerothAILearnedDanger(p,enemy));enemy.x=10
p.level=8;assert(not AzerothAILearnedDanger(p,enemy));p.level=5
p.account=99;events[8](8,enemy,p);assert(#writes==0);p.account=12
p.controlled=true;events[8](8,enemy,p);assert(#writes==0);p.controlled=false
events[8](8,enemy,p);assert(#writes==1);events[8](8,enemy,p);assert(#writes==1)
''')
# Reload the whole module: durable SQL memory must still affect target selection.
l.execute(source);l.execute("assert(AzerothAILearnedDanger(p,enemy));now=400000;assert(not AzerothAILearnedDanger(p,enemy))")
print('Death memory: identity, human control, duplicate event, radius, level reassessment, expiry and reload persistence passed')
+27
View File
@@ -0,0 +1,27 @@
from pathlib import Path
from lupa import LuaRuntime
source=(Path(__file__).resolve().parents[1]/'ale/azerothai_readiness.lua').read_text()
setup='''
now=1000;os.time=function()return now end;calls={}
function WorldDBQuery()return {GetUInt32=function(_,i)return i==0 and 10 or 11 end,NextRow=function()return false end}end
function AuthDBQuery()return {GetUInt32=function(_,i)return i==0 and 1 or 12 end,GetString=function()return 'A' end,NextRow=function()return false end}end
function CreateLuaEvent(fn)tick=fn end;function PrintError(s)error(s)end
p={ai={}};function GetPlayersInWorld()return {p}end
function p:GetGUIDLow()return 1 end;function p:GetAccountId()return 12 end;function p:GetName()return 'A' end
function p:IsPlayerbot()return true end;function p:GetBotAI()return self.ai end;function p.ai:HasGameClientMaster()return p.human end
function p:IsDead()return p.dead end;function p:IsInCombat()return p.combat end;function p:IsCasting()return false end;function p:IsTaxi()return false end
function p:GetGroup()return p.group end;function p:GetFreeTalentPoints()return 1 end
function p:GetItemByPos(_,slot)if slot==15 then return {IsBroken=function()return true end,GetUInt32Value=function(_,i)return i==0x003D and 50 or 0 end}end end
function p.ai:GetInventoryItems()return {{GetEntry=function()return 10 end,GetCount=function()return 3 end}}end
function p:CanUseItem()return true end;function p:GetPowerType()return 0 end
function p.ai:DoSpecificAction(s)table.insert(calls,s)end;function p.ai:ChangeStrategy(s)table.insert(calls,s)end;function p.ai:HasCheat()return p.goldCheat end
function AzerothAISurvivalOverride()return p.recovering end
'''
def new():
l=LuaRuntime();l.execute(setup);l.execute(source);return l
l=new();l.execute("tick();local r=AzerothAIReadinessSnapshot(p);assert(r.foodCount==3 and r.drinkCount==0 and r.brokenItems==1 and r.freeTalents==1);assert(#calls==4);tick();assert(#calls==4)")
for condition in ['human','combat','dead','recovering']:
l=new();l.execute(f'p.{condition}=true;tick();assert(#calls==0)')
l=new();l.execute("p.group={IsRaidGroup=function()return false end,GetMembersCount=function()return 2 end,GetMembers=function()return {p} end};tick();assert(#calls==0)")
l=new();l.execute("p.goldCheat=true;tick();assert(#calls==2,'No repair or purchase under gold cheat')")
print('Readiness: inventory counts, broken gear, cooldown, combat/death/recovery/human/group guards and normal-cost actions passed')
+18
View File
@@ -10,3 +10,21 @@ test('activity distinguishes stale data and guarded actions from inferred stalls
test('pending social events count once after native confirmation',()=>{const s=state();s.community.feed=[{id:'social-1:reply',kind:'conversation',status:'pending',at:new Date(now*1000).toISOString(),names:['A','B']}];const a=observeInsights(s,undefined,now);s.community.feed[0].status='confirmed';const b=observeInsights(s,a.cursor,now+60);assert.equal(b.events.length,1);assert.equal(observeInsights(s,b.cursor,now+120).events.length,0);});
test('enabling failure tracking does not backfill old failed party actions',()=>{const s=state();const a=observeInsights(s,undefined,now);s.community.feed=[{id:'party-old',kind:'party',status:'failed',at:new Date((now-600)*1000).toISOString(),names:['A','B']},{id:'party-new',kind:'party',status:'failed',at:new Date((now+20)*1000).toISOString(),names:['A','B']}];const b=observeInsights(s,a.cursor,now+60);assert.deepEqual(b.events.map(e=>e.id),['social:party-new']);});
test('party time requires fresh consecutive matching living-party observations',()=>{
const s=state();for(const b of s.bots)Object.assign(b.world,{groupId:'g',groupNames:['A','B'],nearby:['A','B']});
let a=observeInsights(s,undefined,now);assert.equal(a.events.length,0);
for(const b of s.bots)b.world.at=now+60;
let c=observeInsights(s,a.cursor,now+60);assert.equal(c.events.filter(e=>e.kind==='nearPartySeconds').reduce((n,e)=>n+e.amount,0),120);
assert.equal(observeInsights(s,c.cursor,now+90).events.length,0,'same snapshot is not more evidence');
for(const b of s.bots)b.world.at=now+500;
assert.equal(observeInsights(s,c.cursor,now+500).events.length,0,'long collection gap excluded');
s.bots[1].world.isDead=true;s.bots[0].world.at=now+90;
assert.equal(observeInsights(s,c.cursor,now+90).events.filter(e=>e.kind==='nearPartySeconds').length,0);
});
test('monitor distinguishes mana recovery, named leader and ghost state',()=>{
const b=bot();Object.assign(b.world,{manaPct:20,survivalAction:'recovering readiness',survivalAt:now,survivalSince:now-70});
assert.equal(activityReason(b,now).label,'Recovering mana');assert.match(activityReason(b,now).detail,/1m 10s/);
b.world.survivalAction='regroup requested';b.world.leaderName='Zaku';assert.equal(activityReason(b,now).label,'Catching up to Zaku');
b.world.isGhost=true;assert.equal(activityReason(b,now).label,'Recovering as a ghost');
});