Recover non-progressing cast loops and bound host model prompt cache
Core tests / core (push) Successful in 57s

This commit is contained in:
Codex
2026-09-24 03:42:18 +12:00
parent 8e01cdee22
commit 1552e4c165
4 changed files with 113 additions and 18 deletions
+64 -15
View File
@@ -1,6 +1,11 @@
-- Conservative recovery for managed residents with fresh quest/explore intent.
-- Native position/progress is sampled independently of dashboard summaries.
local intents, observations, pendingResults = {}, {}, {}
local progress={}
-- Successful gameplay, rather than merely starting a cast, resets the idle clock.
for _,event in ipairs({32,37,54}) do
RegisterPlayerEvent(event,function(_,p) if p:IsPlayerbot() then progress[p:GetGUIDLow()]=os.time() end end)
end
local LIMIT = 600
local COOLDOWN = 1800
local function sqltext(s)
@@ -13,6 +18,22 @@ AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_recovery_log (
AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_recovery_results (
attempt_id BIGINT UNSIGNED PRIMARY KEY,status VARCHAR(24) NOT NULL,
checked_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,detail VARCHAR(255) NOT NULL)]])
AuthDBExecute([[CREATE TABLE IF NOT EXISTS azerothai_recovery_leases (
guid INT UNSIGNED PRIMARY KEY,name VARCHAR(64),account_id INT UNSIGNED,
restore_at BIGINT UNSIGNED,gathering TINYINT,looting TINYINT)]])
local function restoreStrategies(now)
local q=AuthDBQuery('SELECT guid,name,account_id,gathering,looting FROM azerothai_recovery_leases WHERE restore_at<='..now)
if not q then return end
repeat
local guid,name,account=q:GetUInt32(0),q:GetString(1),q:GetUInt32(2)
local p=GetPlayerByName(name)
if p and p:IsPlayerbot() and p:GetGUIDLow()==guid and p:GetAccountId()==account and p:GetBotAI() and not p:GetBotAI():HasGameClientMaster() then
if q:GetUInt32(3)==1 then p:GetBotAI():ChangeStrategy('+gather',1) end
if q:GetUInt32(4)==1 then p:GetBotAI():ChangeStrategy('+loot',1) end
AuthDBExecute('DELETE FROM azerothai_recovery_leases WHERE guid='..guid)
end
until not q:NextRow()
end
local function checkResults(now)
for guid,r in pairs(pendingResults) do
local p=GetPlayerByName(r.name);local status,detail
@@ -39,17 +60,55 @@ function AzerothAISetRecoveryIntents(rows)
return 'Recovery intentions refreshed'
end
local function safe(p,a)
if p:IsDead() or p:IsInCombat() or p:IsCasting() or p:IsTaxi() or a:HasGameClientMaster()
if p:IsDead() or p:IsInCombat() or p:IsTaxi() or a:HasGameClientMaster()
or p:IsRooted() or a:IsMovementImpaired() then return false end
local g=p:GetGroup()
if g then
if #g:GetMembers()~=g:GetMembersCount() then return false end
for _,m in pairs(g:GetMembers()) do if not m:IsPlayerbot() then return false end end
for _,m in pairs(g:GetMembers()) do if not m:IsPlayerbot() or not m:GetBotAI() or m:GetBotAI():HasGameClientMaster() then return false end end
end
return true
end
local function recover(p,a,r,guid,now,followup)
local x,y,z,map,level=p:GetX(),p:GetY(),p:GetZ(),p:GetMapId(),p:GetLevel()
local q=AuthDBQuery('SELECT guid FROM azerothai_lifecycle WHERE guid='..guid..' AND account_id='..r.account..' AND BINARY name='..sqltext(r.name).." AND state='active'")
local recent=AuthDBQuery('SELECT id FROM azerothai_recovery_log WHERE guid='..guid..' AND at>NOW()-INTERVAL '..COOLDOWN..' SECOND LIMIT 1')
if recent and followup then
local failed=AuthDBQuery("SELECT l.id FROM azerothai_recovery_log l JOIN azerothai_recovery_results r ON r.attempt_id=l.id WHERE l.guid="..guid.." AND r.status='no_progress' AND l.at>NOW()-INTERVAL 1800 SECOND AND (SELECT COUNT(*) FROM azerothai_recovery_log x WHERE x.guid=l.guid AND x.at>NOW()-INTERVAL 1800 SECOND)=1 LIMIT 1")
if failed then recent=nil end
end
if q and not recent then
local interrupted=p:IsCasting()
if interrupted then p:StopSpellCast() end
local staleFlight=p:GetMovementType()==7 and not p:IsTaxi()
if staleFlight then p:MoveExpire();p:MoveClear() end
a:Reset(true)
local gathering,looting=a:HasStrategy('gather',1),a:HasStrategy('loot',1)
if gathering or looting then
AuthDBExecute('INSERT INTO azerothai_recovery_leases VALUES ('..guid..','..sqltext(r.name)..','..r.account..','..(now+120)..','..(gathering and 1 or 0)..','..(looting and 1 or 0)..') ON DUPLICATE KEY UPDATE restore_at=VALUES(restore_at),gathering=GREATEST(gathering,VALUES(gathering)),looting=GREATEST(looting,VALUES(looting))')
a:ChangeStrategy('-gather',1);a:ChangeStrategy('-loot',1)
a:DoSpecificAction('release loot',true)
end
local moved=a:DoSpecificAction('move random',true)
local detail=(interrupted and 'Interrupted non-progressing cast; ' or '')..(staleFlight and 'Cleared stale flight; ' or '')..'AI replanned; gathering paused 120s; walking request='..tostring(moved)
AuthDBExecute('INSERT INTO azerothai_recovery_log(guid,name,detail) VALUES ('..guid..','..sqltext(r.name)..','..sqltext(detail)..')')
pendingResults[guid]={name=r.name,account=r.account,at=now,x=x,y=y,z=z,map=map,level=level}
PrintInfo('[AzerothAI recovery] '..r.name..': '..detail)
return true
end
return false
end
-- Operator-only entry point for a stall already verified through repeated observations.
function AzerothAIRecoverObservedStall(name,guid,account)
local p=GetPlayerByName(name)
if not p or not p:IsPlayerbot() or p:GetGUIDLow()~=guid or p:GetAccountId()~=account then return 'Identity unavailable' end
local a=p:GetBotAI()
if not a or not safe(p,a) then return 'Unsafe to recover now' end
return recover(p,a,{name=name,account=account},guid,os.time(),true) and 'Recovery requested; awaiting observed progress' or 'Inactive identity or cooldown'
end
local function sample()
local now=os.time()
restoreStrategies(now)
checkResults(now)
for guid,r in pairs(intents) do
local p=GetPlayerByName(r.name)
@@ -62,22 +121,11 @@ local function sample()
local x,y,z,map=p:GetX(),p:GetY(),p:GetZ(),p:GetMapId()
local level,hp=p:GetLevel(),p:GetHealth()
local old=observations[guid]
if not old or old.map~=map or old.level~=level or old.hp~=hp or (x-old.x)^2+(y-old.y)^2+(z-old.z)^2>25 then
if not old or old.map~=map or old.level~=level or old.hp~=hp or (progress[guid] or 0)>old.since or (x-old.x)^2+(y-old.y)^2+(z-old.z)^2>25 then
observations[guid]={x=x,y=y,z=z,map=map,level=level,hp=hp,since=now}
elseif now-old.since>=LIMIT then
-- Persistent cooldown survives ALE reloads; exact identity must still be active.
local q=AuthDBQuery('SELECT guid FROM azerothai_lifecycle WHERE guid='..guid..' AND account_id='..r.account..' AND BINARY name='..sqltext(r.name).." AND state='active'")
local recent=AuthDBQuery('SELECT id FROM azerothai_recovery_log WHERE guid='..guid..' AND at>NOW()-INTERVAL '..COOLDOWN..' SECOND LIMIT 1')
if q and not recent then
local staleFlight=p:GetMovementType()==7 and not p:IsTaxi()
if staleFlight then p:MoveExpire();p:MoveClear() end
a:Reset(true)
local moved=a:DoSpecificAction('move random',true)
local detail=(staleFlight and 'Cleared stale flight; ' or '')..'AI replanned; walking request='..tostring(moved)
AuthDBExecute('INSERT INTO azerothai_recovery_log(guid,name,detail) VALUES ('..guid..','..sqltext(r.name)..','..sqltext(detail)..')')
pendingResults[guid]={name=r.name,account=r.account,at=now,x=x,y=y,z=z,map=map,level=level}
PrintInfo('[AzerothAI recovery] '..r.name..': '..detail)
end
recover(p,a,r,guid,now)
old.since=now
end
end
@@ -85,6 +133,7 @@ local function sample()
end
-- Forget residents whose intent changed to resting/socializing or who went offline.
for guid in pairs(observations) do if not intents[guid] then observations[guid]=nil end end
for guid,at in pairs(progress) do if now-at>3600 then progress[guid]=nil end end
end
CreateLuaEvent(function()local ok,err=pcall(sample);if not ok then PrintError('[AzerothAI recovery] '..tostring(err)) end end,30000,0)
PrintInfo('[AzerothAI] Conservative stuck recovery enabled: 10 minutes idle, 30 minute cooldown')