Reconcile delayed hardcore deaths with native population registry

This commit is contained in:
Codex
2026-09-22 18:15:35 -04:00
parent 46935c791e
commit 8e96c7ff47
3 changed files with 41 additions and 0 deletions
+12
View File
@@ -9,6 +9,7 @@ local TARGET_PER_FACTION = 25
local gates = {} local gates = {}
local gateAt = {} local gateAt = {}
local arrivals = {} local arrivals = {}
local reconciledAt = 0
local alliance = {[1]=true,[3]=true,[4]=true,[7]=true,[11]=true} local alliance = {[1]=true,[3]=true,[4]=true,[7]=true,[11]=true}
local horde = {[2]=true,[5]=true,[6]=true,[8]=true,[10]=true} local horde = {[2]=true,[5]=true,[6]=true,[8]=true,[10]=true}
local function faction(row) local function faction(row)
@@ -77,6 +78,17 @@ end
local function enforce() local function enforce()
if not ready then reload() end if not ready then reload() end
-- The dashboard may receive a verified death from an older transport backlog
-- after this process seeded its registry. Import terminal states only; never
-- overwrite a locally observed death with an older active database row.
if os.time()-reconciledAt>=10 then
local q=AuthDBQuery("SELECT guid,name,account_id FROM azerothai_lifecycle WHERE state='dead' AND protected=0 AND LOWER(name)<>'zaku'")
if q then repeat
local row=residents[q:GetUInt32(0)]
if row and not row.protected and row.name==q:GetString(1) and row.account==q:GetUInt32(2) then row.state="dead" end
until not q:NextRow() end
reconciledAt=os.time()
end
local mgr=GetRandomPlayerbotMgr() local mgr=GetRandomPlayerbotMgr()
-- Disallow every retired identity before the native manager fills vacancies. -- Disallow every retired identity before the native manager fills vacancies.
for guid,row in pairs(residents) do for guid,row in pairs(residents) do
+8
View File
@@ -41,6 +41,14 @@ already does. Archived snapshots and terminal application commands use the chose
state and memorials are preserved. Current application log files rotate at 10 MB state and memorials are preserved. Current application log files rotate at 10 MB
with three copies. Database files may retain freed SQLite pages for reuse. with three copies. Database files may retain freed SQLite pages for reuse.
The minute operations check also reconciles verified AzerothCore death records
with the native hardcore registry using character GUID, account and exact name.
Protected residents are excluded. Each correction keeps the old registry row
and supporting death in `lifecycle-reconciliation.jsonl`. The Lua guard imports
terminal registry states every ten seconds, removes the retired identity, and
allows its faction's replacement slot to fill under the existing 25/25 cap.
This handles delayed death events received after the registry was first seeded.
Install `deploy/playable/install-user-recovery.py` as the realm owner. It registers Install `deploy/playable/install-user-recovery.py` as the realm owner. It registers
`azerothai-runtime.service` with Restart=always and a minute operations timer in `azerothai-runtime.service` with Restart=always and a minute operations timer in
the user's systemd manager. Enable user lingering for reboot startup. Telemetry the user's systemd manager. Enable user lingering for reboot startup. Telemetry
+21
View File
@@ -44,6 +44,27 @@ try:
state=json.load(urllib.request.urlopen(base+'/api/state',timeout=10));days=max(3,min(30,int(state.get('opsRetentionDays',7)))) state=json.load(urllib.request.urlopen(base+'/api/state',timeout=10));days=max(3,min(30,int(state.get('opsRetentionDays',7))))
env=os.environ.copy();env['MYSQL_PWD']=cfg['DOCKER_DB_ROOT_PASSWORD'] env=os.environ.copy();env['MYSQL_PWD']=cfg['DOCKER_DB_ROOT_PASSWORD']
def sql(q):return subprocess.check_output(['docker','exec','-e','MYSQL_PWD','ac-database','mysql','-uroot','-N','-B','-e',q],env=env,text=True) def sql(q):return subprocess.check_output(['docker','exec','-e','MYSQL_PWD','ac-database','mysql','-uroot','-N','-B','-e',q],env=env,text=True)
# Reconcile delayed verified deaths by the full native identity. This never
# revives an identity, changes a protected resident, or counts a human as a bot.
if state.get('settings',{}).get('hardcore',{}).get('enabled'):
retired=0
registry={int(line.split('\t')[0]):line for line in sql("SELECT guid,name,account_id,state,died_at FROM acore_auth.azerothai_lifecycle WHERE state<>'dead' AND protected=0 AND LOWER(name)<>'zaku'").splitlines() if line}
for bot in state.get('bots',[]):
identity=bot.get('autonomy') or {};death=bot.get('death') or {}
if bot.get('status')!='dead' or not death.get('verified') or death.get('source')!='azerothcore' or not identity.get('managed'):continue
if not bot.get('mortal') or bot.get('plotArmorHpFloor',0)>0 or bot.get('name','').lower()=='zaku':continue
guid=identity.get('characterGuid');account=identity.get('accountId')
if type(guid) is not int or type(account) is not int or guid<=0 or account<=0:continue
before=registry.get(guid,'');fields=before.split('\t')
if len(fields)!=5 or fields[1]!=bot['name'] or int(fields[2])!=account:continue
name=bot['name'].encode().hex()
where=f"guid={guid} AND account_id={account} AND BINARY name=UNHEX('{name}') AND protected=0 AND LOWER(name)<>'zaku' AND state<>'dead'"
# Keep an audit of the old registry row and supporting death before changing it.
audit=lab/'lifecycle-reconciliation.jsonl'
with audit.open('a') as f:f.write(json.dumps({'at':time.time(),'before':before.strip(),'death':death})+'\n')
changed=sql("UPDATE acore_auth.azerothai_lifecycle SET state='dead',died_at=COALESCE(died_at,NOW()) WHERE "+where+'; SELECT ROW_COUNT()')
retired+=int(changed.strip())
maintenance.append(f'Hardcore registry checked; {retired} delayed deaths reconciled')
for table,column in [('azerothai_event_outbox','delivered_at'),('azerothai_result_outbox','delivered_at')]: for table,column in [('azerothai_event_outbox','delivered_at'),('azerothai_result_outbox','delivered_at')]:
sql(f'DELETE FROM acore_auth.{table} WHERE {column} IS NOT NULL AND {column}<NOW()-INTERVAL 1 DAY LIMIT 2000') sql(f'DELETE FROM acore_auth.{table} WHERE {column} IS NOT NULL AND {column}<NOW()-INTERVAL 1 DAY LIMIT 2000')
sql(f'DELETE FROM acore_auth.azerothai_snapshot_archive WHERE created_at<NOW()-INTERVAL {days} DAY LIMIT 2000') sql(f'DELETE FROM acore_auth.azerothai_snapshot_archive WHERE created_at<NOW()-INTERVAL {days} DAY LIMIT 2000')