perf: batch learner updates and remove Turtle support

This commit is contained in:
Xurkon
2026-06-04 06:47:45 -05:00
parent 581634d199
commit 51561b97d8
11 changed files with 157 additions and 78 deletions
+3 -3
View File
@@ -109,7 +109,7 @@
### Maintenance
- **Removed Turtle WoW references** — Deleted Turtle WoW row from the server compatibility table and removed Turtle WoW load tip from installation instructions in README.md. Turtle WoW is shutting down and no longer has a supported plugin.
- **Removed unsupported legacy server references** — Deleted an unsupported legacy server row from the server compatibility table and removed its load tip from installation instructions in README.md.
### Documentation
@@ -282,7 +282,7 @@ Addressed micro-stutters and FPS drops (190 → sub-100) reported during high-fr
- **[Feature — Enhanced Logging]** Improved Stage 3 initialization logging to provide detailed reporting on custom data injection. Developers can now verify the exact number of NPCs, Objects, and Items injected by plugins directly from the `DEVELOP` log.
- **[Fix — Ascension Zone Mapping]** Fixed a regression in `QuestieCompat` where `uiMapData` for Ascension-specific zones was not correctly propagating to the global mapping table, restoring map pin functionality for seasonal and custom zones.
- **[Fix — QuestieLearner]** Centralized zone/area ID lookup in `l10n` module to prevent `GetAreaIdByLocalName` nil errors (Fixes Project Ebonhold runtime crash).
- **[Fix — Custom Server Compilation]** Fixed database compilation not running on custom servers (Ascension, Ebonhold, Turtle WoW, etc.) where plugins inject data after initial load.
- **[Fix — Custom Server Compilation]** Fixed database compilation not running on custom servers (Ascension, Ebonhold, etc.) where plugins inject data after initial load.
- Modified `Modules/QuestieInit.lua` Stage1 to defer compilation to Stage3 for custom servers, ensuring plugins finish injecting data before compilation runs.
- Added `l10n:Initialize()` and `QuestieCorrections:MinimalInit()` calls when deferring to Stage3, as Stage2 (`QuestieJourney:Initialize()`) requires `hiddenQuests` to be populated.
- Added "Bronzebeard" and "Warcraft Reborn" to Ascension realm detection patterns in `Modules/QuestieServer.lua`.
@@ -593,7 +593,7 @@ Addressed micro-stutters and FPS drops (190 → sub-100) reported during high-fr
### Core & Stability (v1.3.5)
- **[Lua 5.0]** Globally polyfilled `string.match` and `string.gmatch` using `string.find` and `string.gfind` to ensure universal compatibility with legacy WoW clients (e.g., Turtle WoW).
- **[Lua 5.0]** Globally polyfilled `string.match` and `string.gmatch` using `string.find` and `string.gfind` to ensure universal compatibility with legacy WoW clients.
- **[AceTimer]** Patched embedded `AceTimer-3.0` instances in ElvUI and OG-RaidHelper to resolve `math.mod` errors on Lua 5.0 clients.
- **[Colors]** Updated `CreateColor` polyfill with `SetRGB`, `SetRGBA`, `SetColor`, and `GetColor` methods.
- **[Comm]** Improved cross-client data sharing stability.
+3 -4
View File
@@ -1,5 +1,5 @@
-- Shim for Lua 5.2+ (Retail) where table.getn was removed.
-- We cannot use the '#' length operator directly because Lua 5.0 (Turtle WoW)
-- We cannot use the '#' length operator directly because Lua 5.0
-- will trigger a compile-time syntax error parsing the file.
-- Using loadstring bypasses the 5.0 compiler and safely injects the # operator in 5.2+.
if not table.getn then
@@ -19,7 +19,7 @@ if not math.mod then
end
end
-- Shim for Lua 5.0 (Turtle WoW) where string.match is missing.
-- Shim for Lua 5.0 where string.match is missing.
-- Supports up to 5 captures (sufficient for all Questie uses).
if not string.match then
string.match = function(str, pattern, init)
@@ -41,7 +41,7 @@ if not string.gmatch then
string.gmatch = string.gfind
end
-- Shim for Lua 5.0 (Turtle WoW) where select() was not yet implemented.
-- Shim for Lua 5.0 where select() was not yet implemented.
-- Fix #7: The original used a `while n > 0` loop that never decremented n,
-- making the loop body run exactly once before returning. Use a plain
-- sequential block instead so the intent is obvious.
@@ -136,4 +136,3 @@ function Questie:Colorize(str, color)
-- Initial stub: just return the string without color or with basic color
return str
end
+2 -2
View File
@@ -21,7 +21,7 @@ function QuestiePluginAPI:HasPendingPlugins()
return self.pendingPluginsCount > 0
end
--- Returns the flavor key of the loaded DB plugin ("WotLK", "Classic", "TBC", "Turtle", "Ascension", etc.)
--- Returns the flavor key of the loaded DB plugin ("WotLK", "Classic", "TBC", "Ascension", etc.)
---@return string|nil
function QuestiePluginAPI:GetLoadedFlavor()
return self.loadedDBFlavor
@@ -276,7 +276,7 @@ function QuestiePlugin:InjectUiMapData(customUiMapData)
end
--- Signals that the plugin has finished loading. This automatically cleans up necessary caches.
---@param flavorKey string|nil Optional flavor label e.g. "WotLK", "Classic", "Turtle"
---@param flavorKey string|nil Optional flavor label e.g. "WotLK", "Classic", "Ascension"
function QuestiePlugin:FinishLoading(flavorKey)
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
if not QuestieDB then
@@ -95,7 +95,7 @@ function QuestieOptions.tabs.credits:Initialize()
thanks = {
type = "description",
order = 1,
name = function() return 'The Turtle WoW, Project Ascension, and Project Ebonhold communities for their ongoing support and feedback.'; end,
name = function() return 'The Project Ascension, Project Ebonhold, and custom server communities for their ongoing support and feedback.'; end,
fontSize = "medium",
},
},
@@ -20,7 +20,6 @@ end
local function GetServer()
if Questie.IsAscension then return "Ascension" end
if Questie.IsTurtle then return "Turtle" end
if Questie.IsEbonhold then return "Ebonhold" end
if Questie.IsEra then return "Era" end
if Questie.Is335 then return "WotLK" end
+2 -3
View File
@@ -241,7 +241,7 @@ end
-- old Lua 5.0 host with no secure-call protection), addon code can still call it
-- but it will simply be a no-op that prints a warning rather than injecting taint.
if not hooksecurefunc then
-- Lua 5.0 hosts (Turtle WoW pre-2.0 or custom servers) have no secure-call model,
-- Lua 5.0 hosts and older custom servers have no secure-call model,
-- so raw-hooking is equivalent to what Blizzard would do internally anyway.
-- Use a local to avoid polluting _G unnecessarily.
local function _rawHook(arg1, arg2, arg3)
@@ -278,7 +278,7 @@ end
if C_Timer then
QuestieCompat.C_Timer = C_Timer
else
-- C_Timer polyfill for Lua 5.0/5.1 clients that don't have it (e.g. Turtle WoW pre-2.0).
-- C_Timer polyfill for Lua 5.0/5.1 clients that don't have it.
-- Only stored in QuestieCompat namespace, NOT in bare _G.
local TickerFrame = CreateFrame("Frame")
local tickers = {}
@@ -626,4 +626,3 @@ end
QuestieCompat.LibUIDropDownMenu = QuestieCompat.LibUIDropDownMenu or {}
QuestieCompat.LibUIDropDownMenu.UIDropDownMenu_Menu_NewSize = function()
end
+2 -2
View File
@@ -279,7 +279,7 @@ QuestieInit.Stages[1] = function() -- run as a coroutine
needsCompilation = (not dbIsCompiled) or (QuestieLib:GetAddonVersionString() ~= dbCompiledOnVersion) or (l10n:GetUILocale() ~= dbCompiledLang) or (Questie.db.global.dbCompiledExpansion ~= WOW_PROJECT_ID)
-- Custom servers or presence of DB plugins: always defer to Stage3 to wait for plugin data injection
if Questie.IsAscension or Questie.IsEbonhold or Questie.IsValanior or Questie.IsTurtle or QuestieServer:IsAnyDBPluginEnabled() then
if Questie.IsAscension or Questie.IsEbonhold or Questie.IsValanior or QuestieServer:IsAnyDBPluginEnabled() then
compilationDeferred = true
l10n:Initialize()
coYield()
@@ -390,7 +390,7 @@ QuestieInit.Stages[3] = function() -- run as a coroutine
-- Always re-compile on custom servers to pick up QuestieLearner changes from SavedVariables,
-- or if compilation was explicitly deferred/needed.
local isCustomServer = Questie.IsAscension or Questie.IsEbonhold or Questie.IsValanior or Questie.IsTurtle or QuestieServer:IsAnyDBPluginEnabled()
local isCustomServer = Questie.IsAscension or Questie.IsEbonhold or Questie.IsValanior or QuestieServer:IsAnyDBPluginEnabled()
if isCustomServer or needsCompilation or (not Questie.db.global.dbIsCompiled) then
Questie:Debug(Questie.DEBUG_CRITICAL, "[QuestieInit:Stage3] Starting compilation (Server=" .. tostring(isCustomServer) .. ", Needed=" .. tostring(needsCompilation) .. ")")
if not QuestieDB.questData then
+137 -47
View File
@@ -210,6 +210,16 @@ local function CopyWithoutField(data, skippedKey)
return copy
end
local function DeepCopy(value)
if type(value) ~= "table" then return value end
local copy = {}
for key, child in pairs(value) do
copy[key] = DeepCopy(child)
end
return copy
end
-- Returns the grid-bucket key for a coordinate so nearby points share the same slot
local function CoordBucket(x, y)
return floor(x / COORD_GRID) * COORD_GRID, floor(y / COORD_GRID) * COORD_GRID
@@ -565,6 +575,89 @@ local function _InvalidateSpawnListsForNPC(npcId)
end
end
------------------------------------------------------------------------
-- Live learner update batching
--
-- Kill/loot bursts can call LearnNPC multiple times for the same NPC:
-- combat-log kill, quest-log objective progress, and loot correlation may all
-- arrive within a few frames. Saved learner evidence is updated immediately,
-- but live QuestieDB override/cache invalidation is batched so the large DB
-- layer is not churned on every single kill.
local LIVE_NPC_UPDATE_DELAY = 0.5
local function _ApplyNpcLiveUpdate(npcId)
local existing = Questie.dbLearner
and Questie.dbLearner.global
and Questie.dbLearner.global.npcs
and Questie.dbLearner.global.npcs[npcId]
if not existing then return false end
local threshold = (Questie.dbLearner.global.settings and Questie.dbLearner.global.settings.minConfidencePins) or MIN_CONFIDENCE_PINS
if existing.mc < threshold then return false end
if not (QuestieDB and QuestieDB.npcDataOverrides and existing[7] and next(existing[7])) then return false end
local ovr = QuestieDB.npcDataOverrides[npcId]
if not ovr then
if IsAscensionProtected("NPC", npcId, 7) then
QuestieDB.npcDataOverrides[npcId] = DeepCopy(CopyWithoutField(existing, 7))
else
QuestieDB.npcDataOverrides[npcId] = DeepCopy(existing)
end
else
-- Merge: fill missing fields; also overwrite empty-string names.
for k, v in pairs(existing) do
if k ~= 7 and not IsAscensionProtected("NPC", npcId, k) and (ovr[k] == nil or (k == 1 and ovr[k] == "")) then
ovr[k] = DeepCopy(v)
end
end
-- Always merge spawn coords.
if existing[7] and not IsAscensionProtected("NPC", npcId, 7) then
ovr[7] = ovr[7] or {}
for zid, coords in pairs(existing[7]) do
ovr[7][zid] = ovr[7][zid] or {}
for _, coord in ipairs(coords) do
InsertIfNewBucket(ovr[7][zid], coord[1], coord[2], GetCoordGridForZone(zid))
end
end
end
end
-- Clear the compiled DB cache once per flush so GetNPC rebuilds with the
-- latest coalesced override data instead of once per kill.
if QuestieDB.private and QuestieDB.private.npcCache then
QuestieDB.private.npcCache[npcId] = nil
end
return true
end
local function _FlushNpcLiveUpdates()
local pending = _Learner.pendingNpcLiveUpdates
_Learner.pendingNpcLiveUpdates = {}
_Learner.pendingNpcLiveUpdateTimer = nil
for npcId in pairs(pending) do
if _ApplyNpcLiveUpdate(npcId) then
_InvalidateSpawnListsForNPC(npcId)
end
end
end
local function _QueueNpcLiveUpdate(npcId)
_Learner.pendingNpcLiveUpdates = _Learner.pendingNpcLiveUpdates or {}
_Learner.pendingNpcLiveUpdates[npcId] = true
if _Learner.pendingNpcLiveUpdateTimer then return end
local timer = QuestieCompat and QuestieCompat.C_Timer
if timer and timer.After then
_Learner.pendingNpcLiveUpdateTimer = true
timer.After(LIVE_NPC_UPDATE_DELAY, _FlushNpcLiveUpdates)
else
_FlushNpcLiveUpdates()
end
end
------------------------------------------------------------------------
-- CrossLinkAfterNPC: called when a new NPC is first learned.
-- Scans all learned quests for any reference to this npcId and stitches
@@ -887,48 +980,14 @@ function QuestieLearner:LearnNPC(npcId, name, level, subName, npcFlags, factionS
existing.ls = time() -- Update last seen
existing.mc = (existing.mc or 0) + 1
local threshold = (Questie.dbLearner.global.settings and Questie.dbLearner.global.settings.minConfidencePins) or MIN_CONFIDENCE_PINS
-- Live injection: update npcDataOverrides only if confidence threshold is met
-- and only if the NPC has actual spawn data from kills (not just player-position fallback)
if existing.mc >= threshold and QuestieDB and QuestieDB.npcDataOverrides and existing[7] and next(existing[7]) then
local ovr = QuestieDB.npcDataOverrides[npcId]
if not ovr then
if IsAscensionProtected("NPC", npcId, 7) then
QuestieDB.npcDataOverrides[npcId] = CopyWithoutField(existing, 7)
else
QuestieDB.npcDataOverrides[npcId] = existing
end
else
-- Merge: fill missing fields; also overwrite empty-string names
for k, v in pairs(existing) do
if not IsAscensionProtected("NPC", npcId, k) and (ovr[k] == nil or (k == 1 and ovr[k] == "")) then ovr[k] = v end
end
-- Always merge spawn coords
if existing[7] and not IsAscensionProtected("NPC", npcId, 7) then
ovr[7] = ovr[7] or {}
for zid, coords in pairs(existing[7]) do
ovr[7][zid] = ovr[7][zid] or {}
for _, coord in ipairs(coords) do
InsertIfNewBucket(ovr[7][zid], coord[1], coord[2], GetCoordGridForZone(zid))
end
end
end
end
-- Clear the compiled DB cache so GetNPC rebuilds with the new override data.
if QuestieDB.private and QuestieDB.private.npcCache then
QuestieDB.private.npcCache[npcId] = nil
end
end
-- Live injection is intentionally batched: repeated kill/log/loot events for
-- the same NPC update saved evidence immediately, then flush QuestieDB once.
_QueueNpcLiveUpdate(npcId)
if isNew then
Questie:Debug(Questie.DEBUG_LEARNER, "[QuestieLearner] New NPC learned:", npcId, name or "?")
CrossLinkAfterNPC(npcId)
end
-- Existing NPC got new spawn data: invalidate cached spawnLists
-- for any active quest objective that references this NPC so the
-- map system rebuilds them with fresh data on next update.
_InvalidateSpawnListsForNPC(npcId)
_Learner:BroadcastIfCommsAvailable("NPC", npcId, existing)
end
@@ -1196,9 +1255,7 @@ local function _MergeSpawnEvidence(npcId)
"duplicates", duplicates,
"zone", tostring(topEvidence.zoneId))
if QuestieDB.private and QuestieDB.private.npcCache then
QuestieDB.private.npcCache[npcId] = nil
end
_QueueNpcLiveUpdate(npcId)
return promoted > 0 or duplicates > 0
end
@@ -1287,10 +1344,8 @@ local function _MergeSpawnEvidence(npcId)
"zone " .. topEvidence.zoneId .. " at " .. floor(topPct + 0.5) .. "% confidence",
spawned and "SPAM" or "IGNORED_DUPLICATE")
-- Clear npcCache so GetNPC returns fresh data
if QuestieDB.private and QuestieDB.private.npcCache then
QuestieDB.private.npcCache[npcId] = nil
end
-- Clear/rebuild cache through the same coalesced path used by kill learning.
_QueueNpcLiveUpdate(npcId)
return true
end
@@ -3568,9 +3623,44 @@ end
function _Learner:BroadcastIfCommsAvailable(typ, id, data)
local QuestieLearnerComms = QuestieLoader:ImportModule("QuestieLearnerComms")
if QuestieLearnerComms and QuestieLearnerComms.BroadcastLearnedData then
local op = (data.mc and data.mc > 1) and "UPDATE" or "NEW"
QuestieLearnerComms:BroadcastLearnedData(op, typ, id, data)
if not (QuestieLearnerComms and QuestieLearnerComms.BroadcastLearnedData) then
return
end
_Learner.pendingBroadcasts = _Learner.pendingBroadcasts or {}
local key = typ .. ":" .. tostring(id)
local op = (data.mc and data.mc > 1) and "UPDATE" or "NEW"
local existingPending = _Learner.pendingBroadcasts[key]
_Learner.pendingBroadcasts[key] = {
typ = typ,
id = id,
data = data,
-- Preserve the first-discovery signal while still sending the latest
-- coalesced payload for the entity.
op = (existingPending and existingPending.op == "NEW") and "NEW" or op,
}
if _Learner.pendingBroadcastTimer then return end
local timer = QuestieCompat and QuestieCompat.C_Timer
local function FlushBroadcasts()
local pending = _Learner.pendingBroadcasts
_Learner.pendingBroadcasts = {}
_Learner.pendingBroadcastTimer = nil
local comms = QuestieLoader:ImportModule("QuestieLearnerComms")
if not (comms and comms.BroadcastLearnedData) then return end
for _, entry in pairs(pending) do
comms:BroadcastLearnedData(entry.op, entry.typ, entry.id, entry.data)
end
end
if timer and timer.After then
_Learner.pendingBroadcastTimer = true
timer.After(2, FlushBroadcasts)
else
FlushBroadcasts()
end
end
-1
View File
@@ -39,7 +39,6 @@ end
local function GetServerKey()
if QuestieServer then
if Questie.IsAscension then return "Ascension" end
if Questie.IsTurtle then return "Turtle" end
if Questie.IsEbonhold then return "Ebonhold" end
if Questie.IsEra then return "Era" end
if Questie.Is335 then return "WotLK" end
+1 -8
View File
@@ -17,11 +17,6 @@ Questie.IsWotlk = (WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC)
Questie.IsTBC = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC)
Questie.IsClassicEra = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC)
-- Try GetBuildInfo for Turtle WoW (Interface: 11200, no WOW_PROJECT globals)
local _, _, _, tocVersion = GetBuildInfo()
Questie.IsTurtle = (not Questie.IsRetail and not Questie.IsWotlk and not Questie.IsTBC and
not Questie.IsClassicEra and tocVersion and tocVersion < 20000)
-- Custom server detection
Questie.IsEbonhold = false
Questie.IsAscension = false
@@ -41,7 +36,6 @@ local function GetExpectedPluginFlavor()
if Questie.IsAscension then return "AscensionDB", "Questie-X-AscensionDB" end
if Questie.IsEbonhold then return "EbonholdDB", "Questie-X-EbonholdDB" end
if Questie.IsValanior then return "ValaniorDB", "Questie-X-ValaniorDB" end
if Questie.IsTurtle then return "TurtleDB", "Questie-X-TurtleDB" end
if Questie.IsWotlk then return "WotLKDB", "Questie-X-WotLKDB" end
if Questie.IsTBC then return "TBCDB", "Questie-X-TBCDB" end
if Questie.IsClassicEra then return "ClassicDB", "Questie-X-ClassicDB" end
@@ -53,7 +47,7 @@ function QuestieServer:Init()
Questie:Debug(Questie.DEBUG_INFO, "[QuestieServer] Realm:", realmName)
Questie:Debug(Questie.DEBUG_INFO, "[QuestieServer] WOW_PROJECT_ID:", tostring(WOW_PROJECT_ID))
Questie:Debug(Questie.DEBUG_INFO, "[QuestieServer] IsWotlk:", tostring(Questie.IsWotlk), "IsTBC:", tostring(Questie.IsTBC),
"IsClassicEra:", tostring(Questie.IsClassicEra), "IsTurtle:", tostring(Questie.IsTurtle),
"IsClassicEra:", tostring(Questie.IsClassicEra),
"IsAscension:", tostring(Questie.IsAscension), "IsEbonhold:", tostring(Questie.IsEbonhold))
end
@@ -64,7 +58,6 @@ function QuestieServer:IsAnyDBPluginEnabled()
"Questie-X-WotLKDB",
"Questie-X-ClassicDB",
"Questie-X-TBCDB",
"Questie-X-TurtleDB",
"Questie-X-AscensionDB",
"Questie-X-EbonholdDB",
"Questie-X-ValaniorDB",
+6 -6
View File
@@ -278,9 +278,9 @@
<hr>
<h2 id="session-34">Session 34 &mdash; Turtle WoW Cleanup (2026-05-04)</h2>
<h2 id="session-34">Session 34 &mdash; Unsupported Legacy Server Cleanup (2026-05-04)</h2>
<ul>
<li><strong>[Maintenance]</strong> Removed Turtle WoW references &mdash; Deleted Turtle WoW row from the server compatibility table and removed Turtle WoW load tip from installation instructions in README.md. Turtle WoW is shutting down and no longer has a supported plugin.</li>
<li><strong>[Maintenance]</strong> Removed unsupported legacy server references &mdash; Deleted an unsupported legacy server row from the server compatibility table and removed its load tip from installation instructions in README.md.</li>
<li><strong>[Documentation]</strong> Clarified two-addon installation requirement &mdash; Updated README.md installation instructions to explicitly state that Questie-X requires BOTH the core addon AND a server-specific database plugin to function.</li>
</ul>
@@ -426,7 +426,7 @@
<li><strong>[Fix &mdash; Ascension Zone Mapping]</strong> Fixed a regression in <code>QuestieCompat</code> where <code>uiMapData</code> for Ascension-specific zones was not correctly propagating to the global mapping table, restoring map pin functionality for seasonal and custom zones.</li>
<li><strong>[Fix &mdash; QuestieLearner]</strong> Centralized zone/area ID lookup in <code>l10n</code> module to prevent <code>GetAreaIdByLocalName</code> nil errors (Fixes Project Ebonhold runtime crash).</li>
<li><strong>[Feature &mdash; Session Export]</strong> Added <code>session_export</code> skill for standardized documentation and session artifact exports.</li>
<li><strong>[Fix &mdash; Custom Server Compilation]</strong> Fixed database compilation not running on custom servers (Ascension, Ebonhold, Turtle WoW, etc.) where plugins inject data after initial load.</li>
<li><strong>[Fix &mdash; Custom Server Compilation]</strong> Fixed database compilation not running on custom servers (Ascension, Ebonhold, etc.) where plugins inject data after initial load.</li>
<li><strong>[Fix &mdash; Zone Mapping Bug]</strong> Fixed incorrect key assignment in <code>QuestiePluginAPI:InjectZoneTables()</code>. This caused zone lookups to fail, resulting in "No UiMapID or fitting parentAreaId" errors for custom zone IDs.</li>
</ul>
@@ -450,7 +450,7 @@
<h2 id="v148">v1.4.8 &mdash; Plugin Synchronization &amp; Lua 5.0 Refactor</h2>
<ul>
<li><strong>[Plugin Synchronization]</strong> Overhauled the Questie-X plugin loading architecture. Introduced <code>QuestiePluginAPI:FinishLoading()</code> and a registration handshake to resolve race conditions during addon initialization. By ensuring that database plugins report their data-injection status before <code>QuestieInit</code> Stage 3 completes, we eliminated "ghost maps" where pins and statistics would fail to render until a manual <code>/reload</code>.</li>
<li><strong>[Universal Lua 5.0 Refactor]</strong> Executed a codebase-wide transition from <code>pairs()</code> and <code>ipairs()</code> to <code>next()</code> and numeric loops. This refactor targets the Lua 5.0 engine used by legacy clients (e.g., Turtle WoW), which can exhibit inconsistent behavior or performance degradation when using standard iterators in high-frequency database sweeps. This change guarantees stable, universal performance across all WoW versions from 1.12 to 3.3.5+.</li>
<li><strong>[Universal Lua 5.0 Refactor]</strong> Executed a codebase-wide transition from <code>pairs()</code> and <code>ipairs()</code> to <code>next()</code> and numeric loops. This refactor targets the Lua 5.0 engine used by legacy clients, which can exhibit inconsistent behavior or performance degradation when using standard iterators in high-frequency database sweeps. This change guarantees stable, universal performance across supported WoW versions.</li>
<li><strong>[Fix &mdash; MapIconTooltip]</strong> Fixed a critical syntax error in <code>MapIconTooltip.lua</code> at line 239. A malformed <code>if</code> statement was trapping execution, preventing tooltips from updating when hovering over Quest objectives on the World Map.</li>
<li><strong>[Fix &mdash; AscensionDB]</strong> Enhanced realm-specific logic in <code>AscensionLoader.lua</code>. The loader now utilizes an pattern-matching check against <code>GetRealmName()</code> to correctly identify and apply custom database overrides for all Project Ascension realms, including seasonal and specialized rule-set servers.</li>
<li><strong>[Performance]</strong> Refined the background loading throttler in <code>QuestieInit</code>. The initialization sequence now yields more efficiently to the main UI thread during massive database injections, reducing "frame-stutter" during the initial login sequence while strictly maintaining loading priority for essential UI modules.</li>
@@ -594,7 +594,7 @@
<h3>Core &amp; Stability</h3>
<ul>
<li><strong>[Fix]</strong> Resolved <code>attempt to call local 'l10n' (a table value)</code> in <code>GameVersionError.lua</code> and implemented a <code>tocVersion</code> guard to prevent the "unsupported client" error on Classic-era private servers (e.g., Turtle WoW).</li>
<li><strong>[Fix]</strong> Resolved <code>attempt to call local 'l10n' (a table value)</code> in <code>GameVersionError.lua</code> and implemented a <code>tocVersion</code> guard to prevent the "unsupported client" error on Classic-era private servers.</li>
<li><strong>[Fix]</strong> Updated <code>GameVersionError.lua</code> strings to correctly identify Questie-X as supporting Classic and private servers.</li>
<li><strong>[Taint Analysis]</strong> Completed a full audit of <code>Questie-X-WotLKDB</code>. No direct taint vectors or secure function overrides were found.</li>
<li><strong>[Version Sync]</strong> Synchronized versions to v1.3.4 across all components.</li>
@@ -935,7 +935,7 @@
<h3>Core & Compatibility</h3>
<ul>
<li><strong>[Lua 5.0]</strong> Globally polyfilled <code>string.match</code> and <code>string.gmatch</code> using <code>string.find</code> and <code>string.gfind</code> to ensure universal compatibility with legacy WoW clients (e.g., Turtle WoW).</li>
<li><strong>[Lua 5.0]</strong> Globally polyfilled <code>string.match</code> and <code>string.gmatch</code> using <code>string.find</code> and <code>string.gfind</code> to ensure universal compatibility with legacy WoW clients.</li>
<li><strong>[AceTimer]</strong> Patched embedded <code>AceTimer-3.0</code> instances in ElvUI and OG-RaidHelper to resolve <code>math.mod</code> errors on Lua 5.0 clients.</li>
<li><strong>[Colors]</strong> Updated <code>CreateColor</code> polyfill with <code>SetRGB</code>, <code>SetRGBA</code>, <code>SetColor</code>, and <code>GetColor</code> methods.</li>
<li><strong>[Comm]</strong> Improved cross-client data sharing stability.</li>