"""Run with python3 after installing lupa; no live server or database involved.""" from pathlib import Path from lupa import LuaRuntime lua = LuaRuntime() setup=''' rows={{10,"Dead",1,0,"dead",2},{11,"Zaku",1,1,"active",5}, {12,"Fresh",2,0,"reserve",2},{13,"Human",3,0,"active",1}, {14,"Wronglevel",2,0,"reserve",2}} players={}; writes={}; removed={}; values={}; events={}; deaths=0 function player(guid,name,account,level,dead) local p={guid=guid,name=name,account=account,level=level,dead=dead} function p:GetGUIDLow() return self.guid end function p:GetName() return self.name end function p:GetAccountId() return self.account end function p:GetLevel() return self.level end function p:GetClass() return self.class or 1 end function p:GetRace() return self.race or 2 end function p:IsDead() return self.dead end players[name]=p; return p end function AuthDBQuery(sql) if string.find(sql,"CREATE TABLE IF NOT EXISTS azerothai_realm_rules",1,true) then rulesReady=true;return nil end if string.find(sql,"INSERT IGNORE INTO azerothai_realm_rules",1,true) then assert(rulesReady);return nil end if string.find(sql,"SELECT hardcore FROM azerothai_realm_rules",1,true) then assert(rulesReady,'Schema must exist before synchronous mode reads');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 function q:NextRow() self.i=self.i+1;return self.i<=#rows end return q end function CharDBQuery(sql) return {GetUInt32=function() return 0 end} end function AuthDBExecute(sql) table.insert(writes,sql) end mgr={} function mgr:Remove(p) table.insert(removed,p.guid);players[p.name]=nil end function mgr:SetValue(guid,key,value) values[guid..key]=value end function mgr:GetValue(guid,key) return values[guid..key] or 0 end function GetRandomPlayerbotMgr() return mgr end function GetPlayerByName(name) return players[name] end function GetPlayersInWorld() return players end function RegisterPlayerEvent(id,fn) events[id]=fn end function CreateLuaEvent(fn) tick=fn end function PrintError(text) end function PrintInfo(text) end function AzerothAIEmitPermanentDeath(p) deaths=deaths+1 end function AzerothAIEmitPlayerSnapshot(name) end player(10,"Dead",1,80,false) player(11,"Zaku",1,7,true) player(12,"Fresh",2,1,false) -- A mismatched account must never be managed, even if the GUID/name matches. player(13,"Human",99,20,true) player(14,"Wronglevel",2,55,false) ''' lua.execute(setup) lua.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text()) lua.execute(''' assert(#removed==1 and removed[1]==10) assert(values['10logout']==1 and values['10add']==0) assert(players.Zaku and players.Human) assert(deaths==0) tick();assert(not players.Wronglevel) local fresh=players.Fresh events[8](8,nil,fresh);events[8](8,nil,fresh);events[6](6,nil,fresh) assert(deaths==1,'Repeated death events must be idempotent') tick();assert(not players.Fresh and values['12logout']==1) events[8](8,nil,players.Zaku);events[8](8,nil,players.Human) assert(deaths==1 and players.Zaku and players.Human) local before=#writes;tick();assert(#writes==before,'No per-second database churn') -- A retired identity accidentally logged in again must be evicted. player(10,'Dead',1,80,false);tick();assert(not players.Dead) player(15,'Factorynew',2,1,false);tick();assert(players.Factorynew) player(16,'Realhuman',99,1,true);tick();assert(players.Realhuman) assert(deaths==1) events[8](8,nil,players.Factorynew);tick();assert(not players.Factorynew) assert(deaths==2) local fast=player(17,'Fastlearner',2,1,false) events[3](3,fast);fast.level=2;tick();assert(players.Fastlearner) ''') balanced=LuaRuntime() balanced.execute(setup) balanced.execute(''' players={};rows={} for i=1,25 do rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2} player(100+i,'Horde'..i,2,5,false) end rows[#rows+1]={201,'Alliancefresh',2,0,'reserve',1} rows[#rows+1]={202,'Hordefresh',2,0,'reserve',2} player(201,'Alliancefresh',2,1,false) player(202,'Hordefresh',2,1,false) ''') balanced.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text()) balanced.execute(''' assert(players.Alliancefresh,'Underrepresented faction must fill its vacancy') assert(not players.Hordefresh,'Do not exceed 25 members of a faction') assert(values['202logout']==1) assert(deaths==0,'Faction deferral is not a death') ''') returning=LuaRuntime() returning.execute(setup) returning.execute(""" players={};rows={} for i=1,26 do rows[#rows+1]={100+i,'Horde'..i,2,0,'active',2} player(100+i,'Horde'..i,2,5,false) end rows[#rows+1]={999,'Zaku',2,1,'active',2} player(999,'Zaku',2,7,false) """) returning.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text()) returning.execute(""" local n=0;for _ in pairs(players) do n=n+1 end assert(n==25 and players.Zaku,'Returning population must obey faction cap and protect Zaku') assert(deaths==0,'Holding returning residents offline must not retire them') assert(values['125logout']==1 and values['126logout']==1) """) dk=LuaRuntime() dk.execute(setup) dk.execute(""" players={};rows={{201,'AllianceDK',2,0,'reserve',1},{202,'HordeDK',2,0,'reserve',2}} local a=player(201,'AllianceDK',2,55,false);a.class=6;a.race=1 local h=player(202,'HordeDK',2,55,false);h.class=6 """) dk.execute((Path(__file__).parents[1] / 'ale/azerothai_lifecycle.lua').read_text()) dk.execute(""" assert(players.AllianceDK and players.HordeDK) local entries=0 for _,q in pairs(writes) do if string.find(q,'entry_level=55',1,true) then entries=entries+1 end end 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')