Support persistent residents with normal resurrection and closer companions
Core tests / core (push) Successful in 2m54s

This commit is contained in:
Codex
2026-09-24 05:33:45 +12:00
parent 0731774457
commit 64e2796a74
11 changed files with 85 additions and 16 deletions
+6
View File
@@ -48,3 +48,9 @@ test('companion ranking prefers complementary native roles and shared active que
const {companionScore}=require('../.test-build/community.js');const a=bot('A'),b=bot('B'),c=bot('C');a.world.combatRole='tank';b.world.combatRole='healer';c.world.combatRole='damage';assert.ok(companionScore(a,b)>companionScore(a,c));
b.world.combatRole='damage';a.world.quests=[1,2];c.world.quests=[1,2];assert.ok(companionScore(a,c)>companionScore(a,b));
});
test('adventuring companions stay within five levels',()=>{
const a=bot('A'),b=bot('B');b.level=a.level+6;
assert.equal(partyDecision(a,b).accepted,false);
b.level=a.level+5;assert.equal(partyDecision(a,b).accepted,true);
});
+12
View File
@@ -92,3 +92,15 @@ test('unverified bridge death fails safe without erasing identity',async()=>{
await applyAuthoritativeAleEvent(store,'player.snapshot',{roster:true,observedAt:100,characterGuids:[123]});
assert.equal(store.state.bots.filter(b=>b.status==='online').length,0);
});
test('normal resurrection mode preserves memories and identity across death and later snapshot',async()=>{
const store=new Store();store.state.settings.hardcore.enabled=false;
const bot=store.state.bots.find(b=>b.id==='bot-6');
bot.memories.push({id:'keep',text:'My companion',importance:8,source:'observation',at:new Date().toISOString()});
const payload={name:bot.name,isBot:true,characterGuid:99,accountId:2,healthPct:0};
await applyAuthoritativeAleEvent(store,'player.killed-by-creature',payload);
assert.notEqual(store.state.bots.find(b=>b.id===bot.id).status,'dead');
await applyAuthoritativeAleEvent(store,'player.snapshot',{...payload,healthPct:100});
const revived=store.state.bots.find(b=>b.id===bot.id);
assert.equal(revived.death,undefined);assert.ok(revived.memories.some(m=>m.id==='keep'));
});
+8
View File
@@ -20,10 +20,12 @@ function player(guid,name,account,level,dead)
players[name]=p; return p
end
function AuthDBQuery(sql)
if string.find(sql,"azerothai_realm_rules",1,true) then return {GetUInt32=function()return mode or 1 end} end
if string.find(sql,'SELECT id FROM account',1,true) then
if string.find(sql,'id=99 ',1,true) then return nil end
return {GetUInt32=function() return 2 end}
end
if string.find(sql,"SELECT guid,name,account_id FROM azerothai_lifecycle WHERE state='dead'",1,true) then return nil end
local q={i=1}
function q:GetUInt32(n) return rows[self.i][n+1] end
function q:GetString(n) return rows[self.i][n+1] end
@@ -131,3 +133,9 @@ assert(entries==2,'Death Knights must be audited at level 55')
events[8](8,nil,players.HordeDK);tick();assert(not players.HordeDK and deaths==1)
""")
print('PASS: retirement, protection, level-1 entry, replay safety, and 25/25 faction balancing')
normal=LuaRuntime();normal.execute(setup)
normal.execute("mode=0;players={};rows={};for i=1,25 do rows[#rows+1]={100+i,'Resident'..i,2,0,'active',2};player(100+i,'Resident'..i,2,5,i==1) end;rows[#rows+1]={200,'Reserve',2,0,'reserve',2};player(200,'Reserve',2,1,false)")
normal.execute((Path(__file__).parents[1]/'ale/azerothai_lifecycle.lua').read_text())
normal.execute("events[8](8,nil,players.Resident1);tick();assert(players.Resident1 and deaths==0);assert(not players.Reserve,'Recoverable death must retain its population slot');local n=0;for _ in pairs(players)do n=n+1 end;assert(n==25)")
print('Normal resurrection mode preserves identity and reserves the dead resident population slot')
+4 -2
View File
@@ -44,9 +44,9 @@ l=new();l.execute("group();AzerothAISetSurvivalEnabled(false);step(0);assert(#ca
print('Native survival: retreat cooldown, recovery, regroup range, human-party protection, and disabled grouping passed')
l=new();l.execute("A.combat=true;A.hp=45;A.cast=true;step(0);assert(A.interrupted and calls[1]=='flee')")
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.combat=true;A.hp=100;A.enemy={GetGUID=function()return 999 end,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.friendly=true;A.selected={GetGUID=function()return 999 end,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);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)")
@@ -59,3 +59,5 @@ l=new();l.execute("A.combat=true;A.hp=75;A.attackers={1,2};step(0);assert(calls[
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')
l=new();l.execute("A.combat=true;A.hp=75;A.attackers={1,2};A.enemy={GetGUID=function()return 999 end,GetLevel=function()return 5 end,ToCreature=function()return nil end};step(0);A.selected=A.enemy;A.combat=false;A.hp=100;A.attackers={};step(20);assert(A.attackStopped and calls[#calls]=='flee');A.attackStopped=false;step(601);assert(not A.attackStopped)")
print('Dangerous encounter target avoidance expires after ten minutes')