From c35258e6153a856086a7a171504f34db746a1540 Mon Sep 17 00:00:00 2001 From: DuvelCorp Date: Thu, 17 Sep 2026 11:25:53 +0200 Subject: [PATCH] Reuse best-matching pet record on stable scan to stop shell duplication --- api/core-scan-stablemaster.lua | 51 ++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/api/core-scan-stablemaster.lua b/api/core-scan-stablemaster.lua index 766710e..b2bd6f0 100644 --- a/api/core-scan-stablemaster.lua +++ b/api/core-scan-stablemaster.lua @@ -2138,16 +2138,45 @@ local function MTH_PETS_SelectPetIdBySnapshot(store, snapshot, previousPetId) if snapshot.signature and snapshot.signature ~= "" then local list = store.signatureIndex[snapshot.signature] if type(list) == "table" then - local count = 0 - local candidateId = nil + local liveIds = {} for _, listedId in pairs(list) do - if listedId and store.activeById[listedId] then - count = count + 1 - candidateId = listedId + if listedId and store.activeById[tostring(listedId)] then + table.insert(liveIds, tostring(listedId)) end end - if count == 1 then - return candidateId + local liveCount = table.getn(liveIds) + if liveCount == 1 then + return liveIds[1] + elseif liveCount > 1 then + -- Several records share this name+family (earlier fragmentation, or two + -- pets with the same name). Reuse the best existing match instead of + -- minting yet another empty shell that only worsens the fragmentation: + -- require a level match, then prefer loyalty match and records that + -- already carry tame/spellbook history, then most recently seen. + local snapLevel = tonumber(snapshot.level) + local snapLoyalty = tonumber(snapshot.loyaltyLevel) + local bestId = nil + local bestScore = -1 + local bestSeen = -1 + for i = 1, liveCount do + local rowId = liveIds[i] + local row = store.activeById[rowId] + if type(row) == "table" and snapLevel and tonumber(row.level) == snapLevel then + local score = 0 + if snapLoyalty and tonumber(row.loyaltyLevel) == snapLoyalty then score = score + 2 end + if MTH_PETS_RowHasAnyTameMetadata(row) then score = score + 1 end + if type(row.petSpellbook) == "table" then score = score + 1 end + local seen = tonumber(row.lastSeen) or tonumber(row.lastUpdated) or 0 + if score > bestScore or (score == bestScore and seen > bestSeen) then + bestScore = score + bestSeen = seen + bestId = rowId + end + end + end + if bestId then + return bestId + end end end end @@ -2364,10 +2393,14 @@ local function MTH_PETS_RecordStableSlot(pets, slot, raw1, raw2, raw3, raw4, raw local snapshotGuid = snapshot and snapshot.guid or nil local signatureMatches = (existingSignature and snapshotSignature and existingSignature == snapshotSignature) and true or false local guidMatches = (existingGuid and snapshotGuid and existingGuid ~= "" and existingGuid == snapshotGuid) and true or false + -- An empty shell must not lock the slot: a richer same-signature record may + -- exist that we should re-adopt instead (self-heals earlier fragmentation). + local existingHasData = MTH_PETS_RowHasAnyTameMetadata(existingRow) + or type(existingRow.petSpellbook) == "table" or existingRow.petNumber ~= nil - if signatureMatches or guidMatches then + if guidMatches or (signatureMatches and existingHasData) then petId = existingId - else + elseif not signatureMatches then MTH_PETS_LogConsistency("RecordStableSlot remapping stale slot=" .. tostring(slotNumber) .. " existingId=" .. tostring(existingId) .. " existingSignature='" .. tostring(existingSignature or "") .. "'"