diff --git a/CHANGELOG.md b/CHANGELOG.md index 87267ab..0ecfa45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/Modules/Libs/QuestieLoader.lua b/Modules/Libs/QuestieLoader.lua index 484e0b7..a34e59e 100644 --- a/Modules/Libs/QuestieLoader.lua +++ b/Modules/Libs/QuestieLoader.lua @@ -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 - diff --git a/Modules/Libs/QuestiePluginAPI.lua b/Modules/Libs/QuestiePluginAPI.lua index 9ef4e45..a152996 100644 --- a/Modules/Libs/QuestiePluginAPI.lua +++ b/Modules/Libs/QuestiePluginAPI.lua @@ -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 diff --git a/Modules/Options/CreditsTab/QuestieOptionsCredits.lua b/Modules/Options/CreditsTab/QuestieOptionsCredits.lua index bff5e61..b247f14 100644 --- a/Modules/Options/CreditsTab/QuestieOptionsCredits.lua +++ b/Modules/Options/CreditsTab/QuestieOptionsCredits.lua @@ -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", }, }, diff --git a/Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua b/Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua index e34efcb..a27132f 100644 --- a/Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua +++ b/Modules/Options/DatabaseTab/QuestieOptionsDatabase.lua @@ -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 diff --git a/Modules/QuestieCompat.lua b/Modules/QuestieCompat.lua index 1b4ab20..a1d6b98 100644 --- a/Modules/QuestieCompat.lua +++ b/Modules/QuestieCompat.lua @@ -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 - diff --git a/Modules/QuestieInit.lua b/Modules/QuestieInit.lua index 588c2b6..b1b8071 100644 --- a/Modules/QuestieInit.lua +++ b/Modules/QuestieInit.lua @@ -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 diff --git a/Modules/QuestieLearner.lua b/Modules/QuestieLearner.lua index 16a1a27..019fcef 100644 --- a/Modules/QuestieLearner.lua +++ b/Modules/QuestieLearner.lua @@ -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 diff --git a/Modules/QuestieLearnerExport.lua b/Modules/QuestieLearnerExport.lua index 1296670..81d4d5d 100644 --- a/Modules/QuestieLearnerExport.lua +++ b/Modules/QuestieLearnerExport.lua @@ -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 diff --git a/Modules/QuestieServer.lua b/Modules/QuestieServer.lua index 55a12cd..8ea5c3e 100644 --- a/Modules/QuestieServer.lua +++ b/Modules/QuestieServer.lua @@ -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", diff --git a/docs/changelog.html b/docs/changelog.html index e1da012..eff8d96 100644 --- a/docs/changelog.html +++ b/docs/changelog.html @@ -278,9 +278,9 @@
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.l10n module to prevent GetAreaIdByLocalName nil errors (Fixes Project Ebonhold runtime crash).session_export skill for standardized documentation and session artifact exports.QuestiePluginAPI:InjectZoneTables(). This caused zone lookups to fail, resulting in "No UiMapID or fitting parentAreaId" errors for custom zone IDs.QuestiePluginAPI:FinishLoading() and a registration handshake to resolve race conditions during addon initialization. By ensuring that database plugins report their data-injection status before QuestieInit Stage 3 completes, we eliminated "ghost maps" where pins and statistics would fail to render until a manual /reload.pairs() and ipairs() to next() 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+.pairs() and ipairs() to next() 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.MapIconTooltip.lua at line 239. A malformed if statement was trapping execution, preventing tooltips from updating when hovering over Quest objectives on the World Map.AscensionLoader.lua. The loader now utilizes an pattern-matching check against GetRealmName() to correctly identify and apply custom database overrides for all Project Ascension realms, including seasonal and specialized rule-set servers.QuestieInit. 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.attempt to call local 'l10n' (a table value) in GameVersionError.lua and implemented a tocVersion guard to prevent the "unsupported client" error on Classic-era private servers (e.g., Turtle WoW).attempt to call local 'l10n' (a table value) in GameVersionError.lua and implemented a tocVersion guard to prevent the "unsupported client" error on Classic-era private servers.GameVersionError.lua strings to correctly identify Questie-X as supporting Classic and private servers.Questie-X-WotLKDB. No direct taint vectors or secure function overrides were found.string.match and string.gmatch using string.find and string.gfind to ensure universal compatibility with legacy WoW clients (e.g., Turtle WoW).string.match and string.gmatch using string.find and string.gfind to ensure universal compatibility with legacy WoW clients.AceTimer-3.0 instances in ElvUI and OG-RaidHelper to resolve math.mod errors on Lua 5.0 clients.CreateColor polyfill with SetRGB, SetRGBA, SetColor, and GetColor methods.