Files
Questie-X/docs/changelog.html
T
Xurkon 72bff01724 docs(changelog): correct POI entry to match duplicate-suppression approach
The 1.6.4 changelog described an earlier SyncBlizzardObjectivePOIs helper that
was superseded by the per-quest duplicate-POI suppression in QuestieCompat
(commit 70a9934). Update both CHANGELOG.md and the HTML changelog to describe
the actual shipped behavior.
2026-06-12 22:41:13 -05:00

1292 lines
151 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Questie-X - Developer Documentation</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-tertiary: #21262d;
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--accent-green: #3fb950;
--accent-blue: #58a6ff;
--accent-purple: #a371f7;
--accent-orange: #d29922;
--border-color: #30363d;
--code-bg: #1b1f23;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
}
.hero {
background: linear-gradient(135deg, #1c2128 0%, #0d1117 100%);
padding: 80px 20px;
text-align: center;
border-bottom: 1px solid var(--border-color);
position: relative;
}
.hero h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 15px;
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero .subtitle {
font-size: 1.2rem;
color: var(--text-secondary);
max-width: 800px;
margin: 0 auto 30px;
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 60px 20px;
}
section {
margin-bottom: 80px;
}
h2 {
font-size: 1.8rem;
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
color: var(--accent-blue);
}
h3 {
font-size: 1.3rem;
margin: 30px 0 15px;
color: var(--text-primary);
}
p {
margin-bottom: 15px;
color: var(--text-secondary);
}
code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
background: var(--bg-tertiary);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background: var(--code-bg);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--border-color);
overflow-x: auto;
margin: 20px 0;
}
pre code {
background: none;
padding: 0;
color: #e6edf3;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin: 30px 0;
}
.card {
background: var(--bg-secondary);
padding: 30px;
border-radius: 12px;
border: 1px solid var(--border-color);
}
.card h4 {
color: var(--accent-green);
margin-bottom: 10px;
font-size: 1.1rem;
}
.important {
background: rgba(210, 153, 34, 0.1);
border-left: 4px solid var(--accent-orange);
padding: 15px 20px;
margin: 20px 0;
border-radius: 0 4px 4px 0;
}
footer {
text-align: center;
padding: 40px;
border-top: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 0.9rem;
}
.file-path {
color: var(--accent-purple);
font-weight: 600;
}
@media (max-width: 768px) {
.grid {
grid-template-columns: 1fr;
}
.hero h1 {
font-size: 2.2rem;
}
}
</style>
</head>
<body>
<div class="hero">
<h1>Questie-X Documentation</h1>
<p class="subtitle">Complete history of changes, fixes, and additions.</p>
<div style="display: flex; justify-content: center; gap: 10px;">
<code>Version: v1.6.4</code>
<a href="index.html"
style="background: var(--bg-tertiary); color: var(--accent-blue); text-decoration: none; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; border: 1px solid var(--border-color);">&larr; Back to Documentation</a>
</div>
</div>
<div class="container">
<h2 id="unreleased-performance-refactor">[Unreleased] &mdash; Performance Refactor Branches</h2>
<ul>
<li><strong>[QuestieDB &mdash; Required Source Item Guard]</strong> Fixed a fresh-load crash in <code>QuestieDB.GetQuest()</code> where quests that had <code>requiredSourceItems</code> but no <code>objectives</code> table would dereference <code>objectives[3]</code> while building <code>SpecialObjectives</code>. This was a code-side nil guard bug on our end, not a saved-variable problem, and it is now covered by a regression test.</li>
<li><strong>[Questie Debug &mdash; Message Throttle]</strong> Added a live debug-message throttle so non-fatal debug output cannot spam chat faster than it can be read. The throttle is configurable in the Advanced tab and keeps fatal output separate.</li>
<li><strong>[Questie Learner &mdash; Tooltip Controls]</strong> Added learner-specific tooltip controls so players can toggle the learned spawn line, total learned spawn count, learner confidence lines, automatic tooltip resizing, and a secondary learner tooltip from the Tooltip options. The learner tooltip paths now respect those toggles before adding extra lines.</li>
<li><strong>[Questie Options &mdash; Faster Live Slider Refresh]</strong> Reduced the redraw debounce on the icon, clustering, and learner spawn-dedup sliders so changes refresh pins much sooner after the user releases the slider instead of waiting half a second before <code>SmoothReset()</code> even begins.</li>
<li><strong>[QuestieLearner &mdash; Learner Sunstrider Clustering]</strong> Relaxed the Sunstrider Isle clustering override in learner mode so the clustering/deduplication knobs can still consolidate newly learned pins there. Static/auto keeps the original Sunstrider visibility behavior, but learner mode now preserves clustering so nearby learner spawns do not render as an unbounded fan-out of separate icons.</li>
<li><strong>[QuestieLearner &mdash; Turn-In Arrow Spawn Promotion]</strong> Allowed quest-related NPC and object spawns to keep their live learner coordinates even when the static database marks those spawn fields as protected. Learner mode now merges the learner's live override tables back into the getter path too, so quest giver and turn-in locations can point from learner-discovered hand-in targets without relaxing protection for unrelated world spawns.</li>
<li><strong>[QuestieLearner &mdash; Quest-Only Item Learning]</strong> Hardened item learning so plain junk loot no longer gets recorded as learner state. Only quest-relevant items are accepted now, including items that already have quest references in learned quest data. Quest-item drops still learn and still build source pins, but non-quest loot like generic trade goods and junk no longer pollutes the item learner or source cache.</li>
<li><strong>[QuestieLearner &mdash; Quest-Only Object Learning]</strong> Hardened object learning so non-quest world objects are ignored unless they are explicitly tied to a quest flow. Quest giver, turn-in, objective, and <code>GAMEOBJECT_USED</code> promotions now mark the object as quest-relevant before it is stored, and learned object coordinates are only promoted into live object overrides when the object has quest references. This keeps source pins for real quest objects while preventing unrelated world objects from being recorded.</li>
<li><strong>[QuestieLearner &mdash; Quest Object Coordinate Promotion]</strong> When a quest-related object is learned, the learner now preserves the actual interaction coordinates when available instead of relying only on fallback player position. Object objective mapping now reuses the recent object evidence cache so the stored spawn point matches the real interaction location and can render immediately as a map pin.</li>
<li><strong>[QuestieLearner &mdash; Source Merge Hardening]</strong> Incoming ITEM and OBJECT network merges now reject non-quest data unless the payload is already marked quest-relevant or the local quest graph proves a quest reference exists. This prevents comms-fed junk loot or unrelated objects from reappearing after the local learner gate has filtered them out.</li>
<li><strong>[QuestieLearner &mdash; Source Pin Cache Hygiene]</strong> Updated learner cache invalidation and live override injection so quest-item and quest-object updates clear stale caches and rebuild the source pin data immediately instead of waiting for a reload.</li>
<li><strong>[QuestieLearner &mdash; Kill/Pin Refresh Throttling]</strong> Debounced learner-triggered map-pin refreshes so heavy kill streaks do not redraw pins on every event. Added a maximum wait cap so batched updates still flush predictably instead of being pushed out forever by constant activity.</li>
<li><strong>[QuestieLearner &mdash; Bystander Kill Suppression]</strong> Changed visible nearby <code>UNIT_DIED</code> handling so kills from other players can update short-lived correlation evidence without immediately running full learner injection or pin refresh work.</li>
<li><strong>[QuestieLearner &mdash; PARTY_KILL Event-Order Fix]</strong> Fixed an edge case where a <code>UNIT_DIED</code> debounce entry could suppress a later authoritative <code>PARTY_KILL</code> for the same GUID. The debounce now tracks event type and allows the player's/group's kill event through while still suppressing true duplicates.</li>
<li><strong>[QuestieLearner &mdash; Immediate Spawn Pin Rendering]</strong> Learner kill evidence now becomes spawn coordinates immediately in learner mode so learned NPC kills can spawn map pins without waiting for the later confidence merge path.</li>
<li><strong>[QuestieLearner &mdash; Quest Accept Resolver Hardening]</strong> Removed the learner <code>QUEST_ACCEPTED</code> last-resort quest-ID fallback that could invent impossible quest IDs from raw event arguments. Accepted quests now only learn when the ID resolves back to a real quest-log entry, which prevents bogus keys from being persisted in learner SavedVariables or live overrides.</li>
<li><strong>[QuestieLearner &mdash; Live Performance Options]</strong> Added Advanced-tab controls for learner intensity, pin refresh delay, maximum pin refresh wait, minimum kills before learned pins, and live NPC update delay so users can tune the system for low-end PCs or heavy-activity zones.</li>
<li><strong>[QuestieComms &mdash; User-Controlled Intensity]</strong> Added Advanced-tab QuestieComms controls, including a full disable switch and live throttles for queue processing, quest-state broadcasts, and bulk sync pacing.</li>
<li><strong>[QuestieComms &mdash; Disable Gate Fix]</strong> Scoped the comms enable helper so the disable switch no longer calls a nil global and every send/process entry point consistently respects the setting.</li>
<li><strong>[Arrow &mdash; Low-End Performance Controls]</strong> Added live Arrow update throttles to reduce repeated nearest-target and coordinate work while preserving existing arrow behavior.</li>
<li><strong>[Measured Hot Paths &mdash; Phase 3]</strong> Landed measured optimizations on the phase 3 branch for literal localization caching, available quest redraw batching, <code>QuestieDB.IsDoable</code> batch reads, hot profile aliases, <code>GetTime()</code> hoists, NPC fallback lookup caching, and validate-cache allocation cleanup.</li>
<li><strong>[QuestieQuest &mdash; Unavailable Quest Guard]</strong> Guarded the available-quest draw thread so unresolved quest IDs are skipped safely instead of crashing the thread, and deduped the skip log so the same missing quest does not spam every redraw.</li>
<li><strong>[QuestieLearner &mdash; Data Source Mode Cohesion]</strong> Reworked the Auto / Learner / Static / Neither data-source modes so switching between them applies live and consistently. A single missing static sub-table (npc/object/quest/item) no longer locks the whole addon into learner mode &mdash; only the genuinely-missing store falls back. Static and Neither no longer silently fall back to learner records, switching modes now clears the per-zone quest cache, and the mode switch drives a full pin/tracker redraw through <code>QuestieQuest:SmoothReset()</code> (the previous redraw call imported a mis-named module and silently did nothing).</li>
<li><strong>[QuestieLearner &mdash; Pin Refresh Latency]</strong> Collapsed a redundant second debounce stage on the live-learn pin-refresh path. Newly learned spawns now redraw within a single debounce window instead of waiting out both the NPC live-update delay and a separate pin-refresh delay, roughly halving perceived pin-update latency in the Balanced and Low presets. Also fixed a latent infinite timer re-arm that could occur once the pin flush was triggered directly.</li>
<li><strong>[Map &mdash; Dense Pin Clustering Aggressiveness Knob]</strong> Re-implemented density-adaptive clustering for crowded kill objectives, now controlled by a new <em>Dense pin clustering aggressiveness</em> slider on the Advanced tab (0 = show every pin, higher = tighter consolidation where many pins share a zone). Coincident pins are always deduplicated regardless of the clustering settings, and the intentional per-zone (Sunstrider Isle) and object-icon range overrides are preserved.</li>
<li><strong>[QuestieLearner &mdash; One Pin Per Spawn, Not Per Kill]</strong> Fixed learner kill evidence rendering a separate pin for every kill. Because kill coordinates are the player's position at kill time (and respawns carry fresh GUIDs), repeated kills at the same spot drifted just enough to dodge the exact-match dedup. The immediate learner-mode spawn builder now merges evidence within a small radius into one pin per physical spawn, and the weighted spawn-evidence merge groups kills by coordinate bucket so a single spot can actually accumulate enough evidence to clear the confidence threshold. The merge distance is exposed as a new <em>Spawn Pin Dedup Radius</em> slider on the Advanced tab (0 = show every distinct position, higher = fewer/tighter pins per spawn) which redraws live.</li>
<li><strong>[Error Suppression &mdash; Debug Modes]</strong> Moved missing quest and other non-fatal database/error spam out of normal chat output and into Questie debug-critical/developer output. Fatal startup failures remain loud.</li>
<li><strong>[Tooltip Data Precedence]</strong> Updated tooltip handling so QuestieLearner defers to AscensionDB-owned tooltip/objective data instead of hiding or replacing server-plugin data for active quests.</li>
</ul>
<div class="important">
<strong>Release status:</strong> The bulk of this work shipped in <code>v1.6.4</code> (below). The remaining measured performance optimizations are staged on the <code>phase3-measured-perf</code> branch and are being cherry-picked and in-game tested one at a time on top of <code>v1.6.4</code>.
</div>
<h2 id="v164">[v1.6.4] &mdash; Universal Lua 5.0 Compatibility (Stage 1), Native Objective POI Sync, Cache &amp; Prestige Fixes</h2>
<p><strong>&#9888; Performance Refactor In Progress</strong> &mdash; This release ships bug fixes, new features, and the first stage of universal Lua 5.0 (Vanilla 1.12) &rarr; Retail compatibility. Several measured performance optimizations remain staged on the <code>phase3-measured-perf</code> branch and are being cherry-picked and in-game tested one at a time on top of this release.</p>
<h3>Compatibility</h3>
<ul>
<li><strong>[Lua 5.0 &mdash; Modulo Operator]</strong> Routed the nine arithmetic <code>%</code> modulo sites (daily/weekly quest flag tests, Darkmoon Faire cycle math, learner GUID/flag math, race/class flag tests) through the existing <code>math.mod</code> shim. Lua 5.0 has no <code>%</code> operator outside string formatting, so this was a parse error on Vanilla 1.12 clients; the addon now parses on 5.0 through Retail.</li>
<li><strong>[Lua 5.0 &mdash; Options Tab Tables]</strong> Fixed <code>QuestieOptions.tabs.{auto,dbm,icons,nameplate}</code> being initialized with <code>{...}</code> instead of <code>{}</code>. On Lua 5.1 this silently captured the addon varargs into the table; on Lua 5.0 it is a parse error. All sibling tabs already used <code>{}</code>, and <code>Initialize</code> repopulates the table, so there is no behavioral change.</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>[Map &mdash; Suppress Duplicate Native Quest POIs]</strong> Rather than globally disabling the server/Blizzard objective POIs, Questie now keeps them enabled and hides only the individual Blizzard POI buttons for quests that already have a visible Questie POI (per-quest duplicate-POI suppression in <code>QuestieCompat</code>, hooked at init). Blizzard POIs still appear for quests Questie does not cover, but no longer stack on top of Questie's own objective icons.</li>
<li><strong>[Prestige &mdash; Player Cache Reset]</strong> The cached player level/race/class flags are now reset on prestige so available-quest eligibility recomputes against the new character state instead of stale pre-prestige values.</li>
<li><strong>[Quest &mdash; Completion Cache Clear On Refresh]</strong> Quest completion lookups are cleared on refresh so re-accepted and re-evaluated quests no longer read stale completion state.</li>
<li><strong>[Map &mdash; Sunstrider Quest Prereq Gate]</strong> (#8) Added the missing prereq link for <code>Felendren the Banished</code> (8335), so the quest no longer appears as available before <code>Aggression</code> (8334) is completed.</li>
<li><strong>[Map &mdash; Minimap Fade No Longer Hides Icons]</strong> (#17) Distance-based minimap fading now clamps to the configured faded-icon opacity floor instead of reaching 0 alpha. The fade distance can visually de-emphasize distant icons, but only the range cutoff can remove them from the minimap.</li>
<li><strong>[Map Tooltip &mdash; Objective Progress Refresh]</strong> (#18) Minimap and world-map objective pin tooltips now refresh the underlying objective before rendering progress text, so kill counters update on hover instead of staying at the count captured when the pin was drawn.</li>
<li><strong>[Learner &mdash; Stop Recording Bystander Kills As Your Own]</strong> Spawn recording is now gated on a captured position, so kills of mobs you never engaged no longer store a spawn at your position or inflate the kill count. The NPC's name/quest data is still learned via mouseover/target.</li>
</ul>
<h2 id="v163">[v1.6.3] &mdash; Arrow Redesign, Minimap Pin Drift Fix (Live API + Corrected Pixel Math), Sunstrider Isle Arrow Distance, Map Pins, Tooltip Schema Fixes, QuestData String Safety</h2>
<ul>
<li><strong>[Docs &mdash; Release Metadata Sync]</strong> Updated the README badge, documentation version badges, and in-game addon version to <code>v1.6.3</code>, then tightened the release filters so handoff notes, local dev settings, and other workspace-only artifacts stay out of the exported release archive.</li>
<li><strong>[Docs &mdash; Minimap Range Cutoff]</strong> Documented the new minimap icon range cutoff slider and its real-time refresh behavior so the published notes match the current UI and runtime behavior.</li>
<li><strong>[Fix &mdash; Debug-Only Error Policy]</strong> Routed non-fatal addon errors through <code>Questie:Debug(Questie.DEBUG_CRITICAL, ...)</code> so normal play no longer gets spammed by red chat errors, while the small set of startup-breaking conditions still uses a separate fatal path.</li>
<li><strong>[Fix &mdash; Arrow Asset Regression &amp; Redesign]</strong> Rebuilt the bundled arrow set so the live dropdown is backed by four image arrows (<code>Arrow1</code> through <code>Arrow4</code>) plus one legacy sheet arrow (<code>arrowold</code>). The old default <code>arrow.tga</code> cache path was retired in favor of the new image-specific asset names, and the generator now keeps sheet-mode detection isolated so only <code>arrowold</code> or an explicitly custom sheet can enter the sprite-sheet path.
<ul>
<li><strong>Bundled asset layout</strong>: The new arrow images live under <code>Icons\\Arrows</code>, with matching generated preview TGAs so the dropdown can show a visible swatch for each style.</li>
<li><strong>Runtime split</strong>: Image styles rotate as single textures; sprite-sheet logic is reserved for <code>arrowold</code> and explicitly custom sheet uploads only.</li>
<li><strong>Regression proofing</strong>: The asset generator now classifies styles by alpha bounds and texture format before writing the manifest, so a future asset rename or format change cannot silently reintroduce sheet mode for every style.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Arrow UI/Attachment Redesign]</strong> The Arrow tab now exposes independent arrow/objective positioning, individual lock toggles, attach/detach behavior, a gap slider, objective transparency, distance-unit selection, and larger font sizing. The objective text can be attached to the arrow again with a one-click reset, but it can also be moved and locked independently when detached.
<ul>
<li><strong>Attached mode</strong>: When attached, the arrow and objective frame move together as a pair, and the gap slider controls the distance between the visible arrow and the objective block.</li>
<li><strong>Detached mode</strong>: When detached, the arrow and objective text each remember their own saved position instead of snapping back to a shared default anchor.</li>
<li><strong>Preview handling</strong>: The dropdown uses preview swatches from the generated <code>Icons\\Arrows\\*_preview.tga</code> files so users can see the artwork at a glance instead of guessing from the name.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Arrow Runtime Texture Fallback]</strong> Bundled image arrows now use the generated runtime TGA as the live render source, while the preview TGA stays dedicated to the dropdown swatch. This keeps image arrows stable even when the client behaves oddly with a specific source file, while the sheet arrow stays isolated to <code>arrowold</code>.</li>
<li><strong>[Fix &mdash; Arrow2 Orientation]</strong> Rotated <code>Arrow2</code> 180 degrees so the pointed tip is the top-facing tip of the asset instead of the base. The runtime TGA and its preview stay in sync so the dropdown thumbnail and in-game arrow match exactly.</li>
<li><strong>[Fix &mdash; Minimap Pin Alignment At Non-1 UI Scales]</strong> Reverted the attempted pixel-snapping fix and corrected the minimap coordinate math instead. Quest pins now stay anchored to their world positions across all minimap zoom levels (0-5+) and across both Stock UI and ElvUI.
<ul>
<li><strong>Time-to-fix note</strong>: This took roughly three weeks of iteration. The first three attempts either crashed, partially fixed only some zoom levels, or regressed zoom 0. The final patch was the first one that held across the full zoom range without the pin math drifting again.</li>
<li><strong>Iteration 1 &mdash; mapRadius reuse (broken)</strong>: The first attempt reused <code>mapRadius</code> for both the radius and the pixel multiplier, which crashed on 3.3.5a because the live API fallback was not wired in yet.</li>
<li><strong>Iteration 2 &mdash; direct half-width (partial)</strong>: The second attempt used the unscaled minimap half-width directly and fixed low zoom levels, but higher zooms still drifted because the map radius source was still wrong.</li>
<li><strong>Iteration 3 &mdash; scaleFactor normalization (regressed)</strong>: The third attempt added a normalization factor based on a reference radius and inverted the ratio, which made zoom 0 worse and had to be backed out.</li>
<li><strong>Iteration 4 &mdash; final fix</strong>: The final patch combined the native 3.3.5a <code>Minimap:GetViewRadius()</code> API with the unscaled minimap half-width formula, which kept the world-yard math aligned with the minimap frame coordinate space.</li>
<li><strong>Root Cause 1 &mdash; Hardcoded lookup table used on 3.3.5a</strong>: <code>Compat/HBD.lua</code> was reading <code>mapRadius</code> from a hardcoded <code>minimap_size</code> lookup table calibrated for stock WoW zoom levels. The API check <code>C_Minimap and C_Minimap.GetViewRadius</code> evaluated to <code>nil</code> on 3.3.5a (and Ascension), so the broken lookup table was always used. The lookup value <code>minimap_size.outdoor[5] = 250</code> produced <code>mapRadius = 125</code>, but the actual live minimap view radius at zoom 5 is <code>116.67</code> yards (from <code>Minimap:GetViewRadius()</code>). This 6.7% error compounded across all pin offsets.</li>
<li><strong>Root Cause 2 &mdash; Factor-of-2 in pixel math</strong>: <code>minimapWidth</code> was computed as <code>(GetWidth() * mapRadius / 155.52) / 2</code>, mixing pixel-half-width with a yards-based scale factor. The <code>/ 155.52</code> constant was a hardcoded normalization that did not match the live API value. The math was self-inconsistent: ratio <code>minimapWidth / mapRadius</code> was <code>0.56</code> (off by ~7% from the correct <code>0.6</code>).</li>
<li><strong>Root Cause 3 &mdash; Scale applied to the wrong layer</strong>: <code>SetPoint</code> offsets are expressed in the minimap parent frame's coordinate space, not physical pixels. Multiplying the offset by <code>GetScale()</code> pushed Questie pins away from that coordinate space and made them diverge from the native minimap quest blips whenever UI/minimap scale was not 1.0.</li>
<li><strong>Fix &mdash; <code>Compat/HBD.lua</code> lines 337-339</strong>: Changed the radius API detection to use the 3.3.5a-native <code>Minimap:GetViewRadius()</code> (instance method), with <code>C_Minimap.GetViewRadius</code> as secondary fallback. The new line:<pre><code>local MinimapRadiusAPI = (C_Minimap and C_Minimap.GetViewRadius) or Minimap.GetViewRadius</code></pre></li>
<li><strong>Fix &mdash; <code>Compat/HBD.lua</code> line 528 and line 639</strong>: Replaced <code>C_Minimap.GetViewRadius()</code> with <code>MinimapRadiusAPI(Minimap)</code> in both call sites, so the live API is invoked through the resolved function reference.</li>
<li><strong>Fix &mdash; <code>Compat/HBD.lua</code> lines 536-540 and lines 653-656</strong>: Replaced the <code>minimapWidth = (GetWidth() / 2) * (mapRadius / 155.52)</code> formula with <code>minimapWidth = GetWidth() / 2</code>. The offset math now stays in the minimap frame's own coordinate space, which is the correct unit for <code>SetPoint</code>.</li>
<li><strong>Fix (debug code preserved as comments)</strong>: The temporary debug prints and <code>_G.QuestieDebugPinMath</code> global state in <code>Compat/HBD.lua</code> remain wrapped as comments so production chat stays clean. The commented-out blocks are preserved for future regression investigation &mdash; to re-enable, remove the comment markers and reload.</li>
<li><strong>Verification</strong>: Live <code>Minimap:GetViewRadius()</code> still returns the correct world-space radius at zoom 5. The pin offsets now use the same unscaled coordinate space as the minimap frame, so quest pins stay aligned with the native markers across fractional UI scales and window scaling combinations.</li>
</ul>
</li>
<li><strong>[Fix &mdash; MapIconTooltip _GetLevelString Guard]</strong> Resolved <code>attempt to concatenate local 'minLevel' (a nil value)</code> crash in <code>MapIconTooltip.lua:494</code> (<code>_GetLevelString</code> function). The creature name &quot;Uneasy Citizen&quot; existed in <code>creatureLevels</code> as an empty table <code>{}</code> rather than the expected <code>[1]=minLevel, [2]=maxLevel, [3]=rank</code> tuple. Added an early-return guard at the top of <code>_GetLevelString</code>: if <code>creatureLevels[name]</code> is falsy or not a table with a numeric level at index <code>[1]</code>, return the name unmodified.</li>
<li><strong>[Fix &mdash; Tooltip Learned Data Schema Mismatch]</strong> Reworked learned NPC/object tooltip registration in <code>Modules/Tooltips/Tooltip.lua</code> to match the actual <code>QuestieLearner</code> storage format.
<ul>
<li><strong>Player-Facing Symptom</strong>: Fixed the Stormwind mouseover crash reported on Bronzebeard while hovering city guards and other learned tooltip targets: <code>Questie-X\\Modules\\Tooltips\\Tooltip.lua:240: attempt to index local 'objList' (a number value)</code>.</li>
<li><strong>Root Cause</strong>: <code>QuestieLearner:_AddToArray</code> stores flat arrays of quest IDs (<code>learnedNpc[10] = { questId1, questId2, ... }</code>, <code>learnedObj[2] = { questId1, questId2, ... }</code>), but the pushed tooltip code still iterated them as <code>{ questId -&gt; objList }</code> maps. That caused crashes like <code>attempt to index local 'objList' (a number value)</code> when a quest ID number was treated like an objective-text array.</li>
<li><strong>Fix</strong>: Replaced the legacy <code>for questId, objList in next, ...</code> / <code>objList[oIndex]</code> traversal with schema-correct lookup: iterate learned quest IDs via <code>ipairs</code>, fetch <code>QuestieLearner.data.quests[questId]</code>, then walk <code>qData[10]</code> objective slots and entries (<code>objEntry[2]</code>) to reconstruct tooltip text safely.</li>
<li><strong>Scope</strong>: Applied to both learned NPC tooltips (<code>m_&lt;id&gt;</code>) and learned object tooltips (<code>o_&lt;id&gt;</code>). Object lookup now reads quest IDs from <code>learnedObj[2]</code> (quest starts) instead of the old <code>learnedObj[10]</code> path.</li>
</ul>
</li>
<li><strong>[Fix &mdash; InsertMissingQuestIds String Guard]</strong> Added <code>if type(QuestieDB.questData) ~= "table" then return end</code> guard at the start of <code>InsertMissingQuestIds()</code> in both <code>tbcQuestFixes.lua</code> and <code>wotlkQuestFixes.lua</code>. Prevents the function from writing to <code>questData</code> while it is still an uncompiled string during early loader initialization.</li>
<li><strong>[Fix &mdash; Sunstrider Isle Arrow Distance (Map Closed)]</strong> Resolved arrow distance showing ~1118 yards instead of ~37 yards on Sunstrider Isle when the world map is NOT open. Map open and zoomed out showed correct distance.
<ul>
<li><strong>Root Cause (Player Position)</strong>: <code>HBD:GetPlayerWorldPosition()</code> returns a non-nil value on Sunstrider Isle, but those coords are Eastern Kingdoms world position (wrong), not Sunstrider's actual position. Because a non-nil value is returned, the fallback chain never fires. The arrow then calculates distance using wrong player coords vs correct target coords, giving a wildly incorrect distance.</li>
<li><strong>Root Cause (Ghost Map Player Position)</strong>: <code>C_Map.GetPlayerMapPosition(946, "player")</code> where <code>946</code> is Ascension's ghost/cosmic map for Sunstrider Isle returns <code>(0, 0)</code> because the ghost map has no valid coordinate data. The correct map for player position on Sunstrider is <code>1941</code> (Eversong Woods parent), which shares the same world coordinate space and returns valid zone coords.</li>
<li><strong>Root Cause (OnUpdate same-map check)</strong>: <code>OnUpdate</code> was using <code>_ResolveArrowUiMapId(_arrow_playerUiMapId)</code> (which resolves 1241&rarr;1941) for the player-side uiMapId, making it equal to <code>target.uiMapId</code> (also 1941). This caused the same-map branch to fire, which then called <code>C_Map.GetPlayerMapPosition(_arrow_playerUiMapId)</code> with 946, getting <code>(0, 0)</code> and computing wrong distance.</li>
<li><strong>Fix &mdash; QuestieArrow.lua UpdateNearestTargets</strong>: Added Sunstrider detection (<code>zoneId == 3430 OR 3431 OR uiMapId == 1241</code>) <em>before</em> calling <code>HBD:GetPlayerWorldPosition()</code> to force the C_Map fallback path. All 4 Sunstrider detection checks in QuestieArrow.lua now accept zoneId 3430 OR 3431 OR uiMapId 1241 (lines 354, 395, 747, 782), since <code>GetCurrentZoneId()</code> can return either 3430 or 3431 when the player is physically on uiMap 1241. The fallback now calls <code>C_Map.GetPlayerMapPosition(1241, "player")</code> to get Sunstrider map-space coords, then converts through <code>HBD:GetWorldCoordinatesFromZone(..., 1941)</code> using Eversong Woods bounds &mdash; which share Sunstrider's world coordinate space.</li>
<li><strong>Fix &mdash; QuestieArrow.lua OnUpdate same-map branch</strong>: Changed <code>C_Map.GetPlayerMapPosition(1941, "player")</code> for player zone coords (not <code>_arrow_playerUiMapId</code> which is 946). Normalizes both <code>playerUiMapId</code> and <code>targetUiMapId</code> through <code>_ResolveArrowUiMapId()</code> before same-map comparison. Uses <code>zoneScale = 13.53</code> yards/zone-unit for distance. Declares <code>worldPlayerX/Y</code> before the if-else to prevent nil in debug prints.</li>
<li><strong>Debug Output</strong>: Added debug prints showing UnitPosition vs HBD player coords comparison, raw vs resolved uiMapIds, branch selection, and per-frame distance calculation inputs.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Sunstrider Isle Map Pins Not Appearing]</strong> Resolved quest objective icons (map pins) not appearing on the world map when zoomed into Sunstrider Isle (uiMapId 1241), even though they appeared correctly when zoomed out to Eversong Woods (uiMapId 1941).
<ul>
<li><strong>Root Cause</strong>: <code>HBDPins:HandlePin</code> (<code>HereBeDragons-Pins-2.0:424</code>) has an early-return guard: <code>if not HBD.mapData[uiMapID] then return end</code>. When the player zooms into Sunstrider Isle, <code>uiMapID</code> is 1241, but <code>HBD.mapData[1241]</code> is nil &mdash; no mapData entry existed for Sunstrider's custom child map. The icon was silently dropped before any coordinate conversion occurred.</li>
<li><strong>Fix &mdash; Compat/HBD.lua</strong>: Added <code>mapData[1241] = mapData[1941]</code> alias and <code>mapData[946] = mapData[1941]</code> alias. Sunstrider Isle (1241) and its ghost map (946) share Eversong Woods' (1941) world coordinate space for these conversions.</li>
<li><strong>Fix &mdash; HBD fallback loading</strong>: Added a lazy fallback to the real HBD library's <code>mapData</code> for maps not present in Questie's compat table, so custom/private-server maps can still resolve world and zone coordinates when <code>QuestieCompat</code> lacks a local entry.</li>
<li><strong>Fix &mdash; Modules/Map/QuestieMap.lua</strong>: Originally added <code>_ResolveMapUiMapId()</code> helper (1241&rarr;1941) to redirect Sunstrider pins to Eversong's coordinate space. This redirect was later <strong>removed</strong> so pins on 1241 render natively using Ascension-calibrated bounds. Added <code>areaIdToUiMapId[1241] = 1241</code> mapping in zoneDB.lua so <code>DrawWorldIcon</code> can place pins directly on the Sunstrider sub-map. HBD's ZONE_REDIRECT visibility (<code>ResolveZone(1241)=1941</code>) ensures pins on 1241 are also visible on the Eversong map.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Sunstrider zoneId 3431 Detection]</strong> <code>GetCurrentZoneId()</code> returns 3431 (Eversong Woods) when the player is physically on uiMap 1241 (Sunstrider Isle), not 3430 as previously assumed. All 4 Sunstrider detection checks in QuestieArrow.lua now accept zoneId 3430 OR 3431 OR uiMapId 1241 (lines 354, 395, 747, 782). Without this fix, NONE of the Sunstrider coordinate overrides triggered, causing the arrow to compute player and target positions in different coordinate spaces (858 yard offset).</li>
<li><strong>[Fix &mdash; Arrow Rotation Direction]</strong> WoW's <code>Texture:SetRotation(r)</code> rotates CW for positive r, NOT CCW as the code comment claimed. Changed <code>rotAngle = -relative</code> to <code>rotAngle = relative</code> (line 441). The arrow was rotating in the opposite direction of the target, pointing away instead of toward it.</li>
<li><strong>[Fix &mdash; Collection Function Distance Mismatch]</strong> <code>_CollectFinisherSpawns</code> and <code>_CollectObjective</code> converted targets through 1941 (Eversong) bounds while player coordinates were in 1241 (Sunstrider) bounds. Added <code>sunOverride = (pMap == 1241)</code> variable and forced target conversion through 1241 bounds at all 4 conversion sites in both functions. Without this fix, sortedTargets showed dist=1261 instead of the correct ~48 yards.</li>
<li><strong>[Fix &mdash; NPC Spawn Zone for Sunstrider]</strong> NPC 15281 (Lanthan Perilon) spawn data changed from zone 3430 to zone 1241. Coordinates gathered via <code>GetPlayerMapPosition</code> on uiMap 1241 are in 1241's normalized space, NOT 1941's. Using zone 3430 (&rarr;1941) produced world coordinates outside 1241's 0-1 range, making map pins invisible on the Sunstrider sub-map.</li>
<li><strong>[Fix &mdash; Sunstrider Map Pin System]</strong> Removed the 1241&rarr;1941 redirect in <code>_ResolveMapUiMapId()</code>. Pins on 1241 now render natively using Ascension-calibrated bounds instead of being forced to 1941's coordinate space. Added <code>areaIdToUiMapId[1241] = 1241</code> mapping in zoneDB.lua so <code>DrawWorldIcon</code> can place pins directly on the Sunstrider sub-map.</li>
<li><strong>[Fix &mdash; Northshire Valley UiMapData Registration]</strong> Added explicit <code>QuestieCompat.UiMapData[1238]</code> for Northshire Valley so custom/private-server zone lookups have concrete geometry for the child map instead of relying on incomplete parent fallbacks.</li>
<li><strong>[Fix &mdash; QuestieLearner Icon Preservation]</strong> Preserved the learned objective icon when registering with the tooltip system so nameplates can render the correct learned slay/loot/talk marker. Previously the icon was always nil on fresh registration.</li>
<li><strong>[Notes &mdash; Failed Approaches Documented]</strong> The release notes now explicitly capture the Sunstrider approaches that did <em>not</em> work:
<ul>
<li>Relying on <code>HBD:GetPlayerWorldPosition()</code> while the world map is closed on Sunstrider, which returned the wrong world space.</li>
<li>Using ghost map <code>946</code> for <code>C_Map.GetPlayerMapPosition</code>, which returned invalid or misleading local coordinates.</li>
<li>Treating <code>QuestieLearner</code> tooltip data as legacy <code>{ questId -&gt; objList }</code> maps instead of the actual flat quest-id arrays.</li>
</ul>
</li>
<li><strong>[Note]</strong> This section documents the published v1.6.3 release state. Future development work will appear in the next versioned section.</li>
</ul>
<hr>
<h2 id="v161">v1.6.1 &mdash; Map Icon Completion Fix</h2>
<ul>
<li><strong>[Fix &mdash; Map Icon Completion]</strong> Resolved a bug where quest objective icons (map pins and minimap markers) persisted on the world map and minimap after objectives were fulfilled, only disappearing after speaking to the quest giver to complete the quest.
<ul>
<li><strong>SpecialObjectives Dirty Flag</strong>: Added a missing loop in <code>SetObjectivesDirty</code> to reset <code>isUpdated = false</code> on <code>quest.SpecialObjectives</code> alongside the existing <code>quest.Objectives</code> loop. Previously, special objectives (e.g. demonic runestones, portal-closing mechanics) would skip the <code>ObjectiveUpdate</code> early-exit guard because their <code>isUpdated</code> flag was never cleared, preventing <code>objective.Completed</code> from being set to <code>true</code> and leaving map icons on-screen indefinitely.</li>
<li><strong>Completion Guard in PopulateObjective</strong>: Added a defensive check in <code>PopulateObjective</code> so that objectives without an <code>Update</code> function still unload their spawned icons if <code>objective.Completed</code> or <code>quest.isComplete</code> is already <code>true</code> from a prior update cycle.</li>
</ul>
</li>
<li><strong>[Fix &mdash; QuestLogCache Error Spam]</strong> Silenced repetitive <code>[ERROR] Please report this error. GetQuest/GetQuestObjectives: The quest doesn't exist in QuestLogCache</code> chat messages that fired for quests not present in the cache (e.g. quest IDs 595, 959, 254048).
<ul>
<li><strong>Root Cause</strong>: <code>QuestLogCache.GetQuest</code> and <code>QuestLogCache.GetQuestObjectives</code> called <code>Questie:Error(...)</code> unconditionally whenever a quest ID was not found in the cache, flooding chat on every objective update cycle.</li>
<li><strong>Fix</strong>: Demoted both calls (and the accompanying <code>debugstack</code> print) from <code>Questie:Error</code> to <code>Questie:Debug(Questie.DEBUG_DEVELOP, ...)</code>. The messages are now silent during normal play and only visible when developer debug mode is active.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="session-34">Session 34 &mdash; Unsupported Legacy Server Cleanup (2026-05-04)</h2>
<ul>
<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>
<hr>
<h2 id="v160">v1.6.0 &mdash; Map Search &amp; Data Integrity</h2>
<ul>
<li><strong>[Fix &mdash; Map Search DB]</strong> Resolved a critical database issue where objects and NPCs searched via Advanced Search failed to display all available spawn locations.
<ul>
<li><strong>Override Prioritization</strong>: Refactored <code>QuestieDB:GetObject</code>, <code>GetNPC</code>, and <code>GetItem</code> to prioritize manual corrections (overrides) over the compiled database. This ensures human-verified data is always used when available.</li>
<li><strong>Correction Loader Fix</strong>: Resolved a fundamental flaw in the correction system where WotLK-specific object fixes were being written to the binary database companion table (<code>objectData</code>) instead of the active override table (<code>objectDataOverrides</code>), causing overrides to be ignored by the rendering engine.</li>
<li><strong>Data Compatibility</strong>: Hardened the database engine to support both integer and string-based indexing, preventing <code>nil</code> errors on legacy clients while maintaining performance.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Object Spawn Data]</strong> Restored missing "Blood of Heroes" (ID 176213) spawn locations.
<ul>
<li><strong>Override Migration</strong>: Restructured the 150+ verified spawn locations to use the direct override path, ensuring they correctly render for all factions in Plaguelands on WotLK servers.</li>
<li><strong>Data Merge</strong>: Integrated 150+ verified spawn locations from both the Classic database and Wowhead scraping into the WotLK module.</li>
<li><strong>Deduplication</strong>: Sanitized coordinate data to remove duplicate pins, providing a cleaner map interface for high-density spawns in Plaguelands.</li>
</ul>
</li>
<li><strong>[UI &mdash; Search Experience]</strong> Improved the Advanced Search results interface.
<ul>
<li><strong>Dynamic Button States</strong>: Fixed the "Show on Map" button state management to correctly toggle to "Remove from Map" when pins are active.</li>
<li><strong>Stability</strong>: Removed experimental rendering logic that caused regressions with standard quest objective icons.</li>
</ul>
</li>
<li><strong>[Fix &mdash; ChatFilter Mythic Keystone Parsing]</strong> Resolved a bug where mythic keystone IDs posted in chat were incorrectly parsed as quest links and converted to broken clickable quests.
<ul>
<li><strong>Pattern Guard</strong>: Added an early skip in <code>ChatFilter.Filter</code> to detect and skip entries where the extracted name starts with <code>"Keystone"</code>, preventing keystones from entering the quest-link conversion path.</li>
<li><strong>Root Cause</strong>: The regex <code>\[(.+) %((%d+)%)]</code> matched chat messages like <code>[Keystone 12345]</code> because the name portion "Keystone 12345" matched <code>.+</code> and the ID happened to correspond to a valid quest ID in <code>QuestPointers</code>, causing a false-positive hyperlink replacement.</li>
</ul>
</li>
<li><strong>[Fix &mdash; Ebonhold Call Board Repeatable Quests]</strong> Resolved a bug where repeatable quests from the Ebonhold Call Board showed as permanently complete in the tracker after re-accepting them.
<ul>
<li><strong>State Leak</strong>: On Ebonhold, Call Board quests auto-complete and vanish from the quest log without firing a <code>QUEST_REMOVED</code> event. Questie was marking them as <code>QUEST_TURNED_IN</code> in the internal <code>questLog</code> table but never cleaning that state on re-accept, causing the tracker to render them as already-complete.</li>
<li><strong>Re-Accept Guard</strong>: Added a pre-check in the <code>QUEST_ACCEPTED</code> handler that detects quests already in <code>QUEST_TURNED_IN</code> state and performs full cleanup (<code>QuestLogCache.RemoveQuest</code>, <code>CompleteQuest</code>, <code>Journey:CompleteQuest</code>, <code>Announce:CompletedQuest</code>, <code>Tracker:RemoveQuest</code>, <code>questLog[questId] = nil</code>) before processing the fresh accept.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="v159">v1.5.9 &mdash; Combat Hardening &amp; NPC Mapping</h2>
<ul>
<li><strong>[Fix &mdash; QuestieQuest Crash]</strong> Resolved a high-frequency crash involving "Water Revenant" (30877) and other entities handled via the <code>QuestieQuest:killcredit</code> path.</li>
<li><strong>[Fix &mdash; Error Handling]</strong> Replaced legacy <code>debugstack()</code> calls in <code>ERR_FUNCTION</code> with <code>Questie:Error</code>. This prevents the error handler from causing secondary crashes when generating stacks on older WoW clients.</li>
<li><strong>[Fix &mdash; NPC Mapping]</strong> Fixed a variable shadowing issue in the combat tracker that caused NPC mapping logic to fail during rapid kill-credit updates.</li>
</ul>
<hr>
<h2 id="v158">v1.5.8 &mdash; Expanded Font Customization</h2>
<ul>
<li><strong>[Feature &mdash; Arrow Font Selection]</strong> Added dedicated font family and size customization for the Waypoint Arrow. Supports all <code>LibSharedMedia-3.0</code> registered fonts.</li>
<li><strong>[Feature &mdash; Tracker Font Controls]</strong> Added "Apply to All" buttons for Quest Tracker headers and objectives, allowing users to synchronize font styles across the entire tracker interface instantly.</li>
<li><strong>[Fix &mdash; LibSharedMedia Iteration]</strong> Fixed a bug where font lists appeared empty on certain locales by switching from <code>ipairs</code> to <code>pairs</code> for <code>SharedMedia</code> hash-table iteration.</li>
<li><strong>[Fix &mdash; Real-time Options]</strong> Refactored arrow position, scale, and alpha sliders to apply changes in real-time without requiring a UI reload.</li>
</ul>
<hr>
<h2 id="v157">v1.5.7 &mdash; Ace3 Shim &amp; Load Prioritization</h2>
<ul>
<li><strong>[Fix &mdash; Options Crash]</strong> Resolved a <code>nil</code> value error in <code>AddToBlizOptions</code> affecting 3.3.5a clients.
<ul>
<li><strong>Compatibility Shim</strong>: Implemented an <code>AceConfigDialog-3.0</code> shim in <code>!X-Libs</code> to correctly bridge modern Ace3 options calls to the legacy 3.3.5a <code>InterfaceOptions_AddCategory</code> API.</li>
</ul>
</li>
<li><strong>[Stability &mdash; Load Order]</strong> Updated <code>Questie-X.toc</code> and <code>!X-Libs</code> to ensure the compatibility suite initializes before <code>BugSack</code> and other diagnostic tools, preventing interleaved error reporting.</li>
</ul>
<hr>
<h2 id="v155">v1.5.5 &mdash; Performance &amp; Stability</h2>
<ul>
<li><strong>[Performance &mdash; Position Caching]</strong> Implemented a 50ms cache for <code>GetPlayerWorldPosition</code> and <code>GetPlayerZonePosition</code> in <code>Compat/HBD.lua</code>. This drastically reduces C API overhead which was the #1 hotspot, with automatic cache invalidation on zone transitions.</li>
<li><strong>[Performance &mdash; Arrow DB Optimization]</strong> Overhauled <code>ZoneDB:GetAreaIdByUiMapId</code> to use an O(1) reverse-mapping cache instead of an O(n) linear scan, eliminating a significant CPU bottleneck triggered hundreds of times per minute by the directional arrow.</li>
<li><strong>[Performance &mdash; Tooltip Throttling]</strong> Throttled the <code>GameTooltip</code> <code>OnUpdate</code> hook to run at 10Hz (100ms) and added a text-change cache, preventing redundant and expensive tooltip recalculations every single frame.</li>
<li><strong>[Performance &mdash; Network Refactor]</strong> Refactored <code>QuestieLearnerComms</code>: reduced compression overhead (negligible size difference but massive CPU savings), slowed processing ticker to match output rate limits, and optimized duplicate detection to O(1).</li>
<li><strong>[Performance &mdash; Arrow Closures]</strong> Hoisted heavy inner closures out of the hot <code>QuestieArrow:UpdateNearestTargets</code> loop to module-level functions, eliminating persistent memory allocation spikes and Garbage Collection (GC) pressure.</li>
<li><strong>[Fix &mdash; Profiler Display]</strong> Fixed textual overlap and vertical spacing issues in the <code>QuestieProfiler</code> UI. Function names are now properly left-aligned, and horizontal spacing ensures metrics remain legible regardless of function name length.</li>
<li><strong>[Performance &mdash; Math Inlining]</strong> Removed <code>QuestieLib:Euclid</code> function call overhead from the minimap icon <code>FadeLogic</code> hot loop in favor of an inline Pythagorean distance check, reducing micro-stutters during movement.</li>
<li><strong>[Performance &mdash; Quest Tracker Bag Scans]</strong> Removed O(N) nested loops iterating over the entire character inventory in <code>TrackerLinePool:SetItem</code> and <code>QuestieQuest:CheckQuestSourceItem</code>, replacing them with native O(1) <code>GetItemCount(itemId)</code> queries. Completely eliminated thousands of <code>GetContainerItemInfo</code> calls.</li>
<li><strong>[Performance &mdash; Tracker Cooldown Throttling]</strong> Added a 5Hz (0.2s) execution throttle to the tracker quest item button <code>btn.OnUpdate</code> frame handler, dropping baseline <code>GetItemCooldown</code> API polls from over 6000 checks every few minutes down to a fraction of that load.</li>
<li><strong>[Fix &mdash; Tooltip Duplicates]</strong> Added aggressive regex filtering to automatically strip Ascension's duplicate native custom quest text lines from appearing twice in unit and item tooltips (Before Questie seamlessly injects its own higher-fidelity formatted data).</li>
</ul>
<hr>
<h2 id="v153">v1.5.4 &mdash; Dedicated Keybinds Tab</h2>
<ul>
<li><strong>[Feature &mdash; Dedicated Keybinds Tab]</strong> Added a new Keybinds options tab with configurable keybinds for:
<ul>
<li><strong>Use Nearest Quest Item</strong> &mdash; Press to automatically use the quest item for the nearest incomplete quest objective</li>
<li><strong>Toggle Options</strong> &mdash; Open/close the Questie Options window</li>
<li><strong>Toggle Tracker</strong> &mdash; Show/hide the Questie Tracker</li>
<li><strong>Toggle My Journey</strong> &mdash; Open/close the Journey window</li>
</ul>
</li>
<li><strong>[Feature &mdash; Use Quest Item Keybind]</strong> Added configurable keyboard hotkey to automatically use the quest item for the nearest incomplete quest objective. When pressed, Questie scans active quests for usable quest items (items with spells via <code>GetItemSpell</code>), checks which ones are in the player's bags using <code>QuestieCompat.GetContainerItemInfo</code>, calculates proximity to quest objectives using <code>HBD:GetWorldDistance</code>, and uses the nearest one via <code>UseItemByName</code>. Configurable via Tracker options tab under "Use Quest Item (Nearest)".</li>
<li><strong>[Fix &mdash; Custom Zone Map Pins]</strong> Fixed map icons not appearing in Ascension custom zones (e.g., Valley of Trials, Northshire Valley). The issue was that custom zone UiMapData was not properly injected into <code>QuestieCompat.UiMapData</code> before HBD initialized its map cache. Added <code>ZoneDB:ApplyCustomZones()</code> function that hooks <code>ZoneDB.Initialize</code> to inject custom zones BEFORE the original initialization runs. This ensures HBD's <code>mapData</code> table (which references <code>QuestieCompat.UiMapData</code>) contains custom zone entries like 1244 (Valley of Trials). Also modified <code>QuestiePluginAPI:InjectUiMapData()</code> to call <code>ZoneDB:ApplyCustomZones()</code> after injecting custom zone data.</li>
<li><strong>[Fix &mdash; Zone Name Fallback]</strong> Fixed "Unknown Zone" display for custom zones in the tracker. When <code>GetZoneNameByID</code> fails for custom zone IDs (like 1244), the system now falls back to <code>GetQuestLogZoneName</code> which reads the zone header directly from the quest log where custom zone names are properly displayed.</li>
<li><strong>[Fix &mdash; GetCurrentUiMapID]</strong> Updated <code>QuestieCompat.GetCurrentUiMapID()</code> to check <code>QuestieCompat.UiMapData</code> directly for custom zone IDs. Previously, only <code>mapIdToUiMapId</code> was checked, which doesn't contain custom zones. Added fallback: <code>if QuestieCompat.UiMapData and QuestieCompat.UiMapData[mapID] then return mapID end</code></li>
<li><strong>[Fix &mdash; Arrow Waypoint Zone Filter]</strong> Fixed waypoint arrow not showing targets in custom zones. The arrow's auto-tracking logic was filtering out objectives by zone comparison (<code>zone ~= playerZoneId</code>), but custom zones use different IDs (e.g., 1244 for Valley of Trials) than their parent zones (e.g., 14 for Durotar). Added <code>QuestiePlayer:GetCurrentUiMapId()</code> function and updated arrow zone filtering to compare both <code>playerZoneId</code> AND <code>playerUiMapId</code> against objective zone IDs in all 4 zone filter locations within <code>_CollectObjective</code>, <code>_CollectFinisherSpawns</code>, and finisher waypoint loops.</li>
<li><strong>[Fix &mdash; Tracker Objective Nil Check]</strong> Added defensive nil check for <code>objective.Description</code> when rendering quest objectives in <code>QuestieTracker.lua</code>. Custom server quests may have objectives without a Description field, which would previously cause "attempt to index field 'Description' (a nil value)" crash.</li>
<li><strong>[Fix &mdash; InjectUiMapData Registration]</strong> Fixed <code>QuestiePluginAPI:InjectUiMapData()</code> to call <code>ZoneDB:ApplyCustomZones()</code> after injecting custom zone data, ensuring the zone mappings are properly registered with both <code>ZoneDB</code> and <code>QuestieCompat.UiMapData</code>.</li>
<li><strong>[Fix &mdash; Realm Name Matching]</strong> Fixed Ascension realm detection in <code>AscensionUiMapData.lua</code> (Questie-X-AscensionDB) to use <code>string.find()</code> instead of exact string comparison. Realms like "Bronzebeard - Warcraft Reborn" now properly match the "Bronzebeard" pattern, allowing custom zone data to load on all Ascension server variants.</li>
<li><strong>[Fix &mdash; PrintDifficultyColor Nil Text]</strong> Fixed crash in <code>PrintDifficultyColor</code> when quest name is nil. Added defensive nil check in <code>GetColoredQuestName</code> to return early if <code>QuestieDB.QueryQuestSingle</code> returns nil for a quest, preventing "attempt to concatenate local 'text' (a nil value)" errors.</li>
<li><strong>[Fix &mdash; Townsfolk POI Map IDs]</strong> Fixed some Townsfolk POIs not appearing in custom zones. Removed the <code>uiMapId > 1000</code> restriction in <code>ZoneDB:ApplyCustomZones()</code> that was preventing custom zone IDs below 1000 from being registered as self-mappings. This caused some Townsfolk NPCs with zone IDs < 1000 to fail UiMapId lookups and not display on the map.</li>
<li><strong>[Fix &mdash; Townsfolk DataOverrides Lookup]</strong> Fixed Townsfolk initialization to properly check both <code>QuestieDB.npcData</code> and <code>QuestieDB.npcDataOverrides</code> / <code>QuestieDB.objectDataOverrides</code>. On custom servers (Ebonhold, Ascension), the database is loaded via plugins into overrides tables, but <code>Townsfolk.Initialize()</code> was only checking the base database tables which are empty until Stage 3 compilation. This affected Flight Masters, Auctioneers, Innkeepers, Repair Vendors, Class Trainers, and Mailboxes.</li>
</ul>
<hr>
<h2 id="v152">v1.5.2 &mdash; Ascension Custom Zone Support</h2>
<ul>
<li><strong>[Fix &mdash; Custom Zone Map Pins]</strong> Fixed map icons not appearing in Ascension custom zones (e.g., Valley of Trials, Northshire Valley). The issue was that custom zone UiMapData was not properly injected into <code>QuestieCompat.UiMapData</code> before HBD initialized its map cache. Added <code>ZoneDB:ApplyCustomZones()</code> function that hooks <code>ZoneDB.Initialize</code> to inject custom zones BEFORE the original initialization runs. This ensures HBD's <code>mapData</code> table (which references <code>QuestieCompat.UiMapData</code>) contains custom zone entries like 1244 (Valley of Trials). Also modified <code>QuestiePluginAPI:InjectUiMapData()</code> to call <code>ZoneDB:ApplyCustomZones()</code> after injecting custom zone data.</li>
<li><strong>[Fix &mdash; Zone Name Fallback]</strong> Fixed "Unknown Zone" display for custom zones in the tracker. When <code>GetZoneNameByID</code> fails for custom zone IDs (like 1244), the system now falls back to <code>GetQuestLogZoneName</code> which reads the zone header directly from the quest log where custom zone names are properly displayed.</li>
<li><strong>[Fix &mdash; GetCurrentUiMapID]</strong> Updated <code>QuestieCompat.GetCurrentUiMapID()</code> to check <code>QuestieCompat.UiMapData</code> directly for custom zone IDs. Previously, only <code>mapIdToUiMapId</code> was checked, which doesn't contain custom zones. Added fallback: <code>if QuestieCompat.UiMapData and QuestieCompat.UiMapData[mapID] then return mapID end</code></li>
<li><strong>[Fix &mdash; Arrow Waypoint Zone Filter]</strong> Fixed waypoint arrow not showing targets in custom zones. The arrow's auto-tracking logic was filtering out objectives by zone comparison (<code>zone ~= playerZoneId</code>), but custom zones use different IDs (e.g., 1244 for Valley of Trials) than their parent zones (e.g., 14 for Durotar). Added <code>QuestiePlayer:GetCurrentUiMapId()</code> function and updated arrow zone filtering to compare both <code>playerZoneId</code> AND <code>playerUiMapId</code> against objective zone IDs in all 4 zone filter locations within <code>_CollectObjective</code>, <code>_CollectFinisherSpawns</code>, and finisher waypoint loops.</li>
<li><strong>[Fix &mdash; Tracker Objective Nil Check]</strong> Added defensive nil check for <code>objective.Description</code> when rendering quest objectives in <code>QuestieTracker.lua</code>. Custom server quests may have objectives without a Description field, which would previously cause "attempt to index field 'Description' (a nil value)" crash.</li>
<li><strong>[Fix &mdash; InjectUiMapData Registration]</strong> Fixed <code>QuestiePluginAPI:InjectUiMapData()</code> to call <code>ZoneDB:ApplyCustomZones()</code> after injecting custom zone data, ensuring the zone mappings are properly registered with both <code>ZoneDB</code> and <code>QuestieCompat.UiMapData</code>.</li>
<li><strong>[Fix &mdash; Realm Name Matching]</strong> Fixed Ascension realm detection in <code>AscensionUiMapData.lua</code> (Questie-X-AscensionDB) to use <code>string.find()</code> instead of exact string comparison. Realms like "Bronzebeard - Warcraft Reborn" now properly match the "Bronzebeard" pattern, allowing custom zone data to load on all Ascension server variants.</li>
<li><strong>[Feature &mdash; Use Quest Item Keybind]</strong> Added configurable keyboard hotkey to automatically use the quest item for the nearest incomplete quest objective. When pressed, Questie scans active quests for usable quest items (items with spells via <code>GetItemSpell</code>), checks which ones are in the player's bags using <code>QuestieCompat.GetContainerItemInfo</code>, calculates proximity to quest objectives using <code>HBD:GetWorldDistance</code>, and uses the nearest one via <code>UseItemByName</code>. Configurable via Tracker options tab under "Use Quest Item (Nearest)".</li>
</ul>
<hr>
<h2 id="v151">v1.5.1 &mdash; Minimap Icon Visibility Fix</h2>
<ul>
<li><strong>[Fix &mdash; Minimap Icons]</strong> Prevented external "minimap button grabber" addons (e.g., Leatrix Plus, MBB) from incorrectly hiding Questie-X quest pins. Minimap icons now use a protected <code>QuestieFrameGroup</code> container that is ignored by these addons while maintaining correct spatial anchoring via <code>HereBeDragons</code>.</li>
</ul>
<hr>
<h2 id="v150">v1.5.0 &mdash; Universal Stability &amp; Zone Mapping</h2>
<ul>
<li><strong>[Fix &mdash; Database Robustness]</strong> Added strict guards against invalid or zero IDs in <code>QuestieDB</code> lookup functions (<code>GetNPC</code>, <code>GetObject</code>, <code>GetItem</code>). This prevents the "rawdata is nil" debug spam that occurred when custom plugins attempted to access uninitialized or malformed entity data.</li>
<li><strong>[Refactor &mdash; Lookup Logic]</strong> Refactored <code>QuestieDB</code> override handling to support both numeric and string keys simultaneously. This ensures that custom server data (e.g., Ascension, Ebonhold) is correctly resolved regardless of how the third-party plugin formats its internal IDs.</li>
<li><strong>[Refactor &mdash; Data Injection]</strong> Updated <code>QuestieLearner:InjectLearnedData</code> to enforce numeric key normalization. All dynamically learned spawn data is now strictly indexed by numeric IDs, preventing type-mismatch collisions during database merging.</li>
<li><strong>[Feature &mdash; Enhanced Logging]</strong> 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 <code>DEVELOP</code> log.</li>
<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, 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>
<hr>
<h2 id="v149">v1.4.9 &mdash; Validation, Profiler &amp; Quest Link Fixes</h2>
<ul>
<li><strong>[Fix &mdash; Quest Cache]</strong> Resolved the "GetQuest: The quest doesn't exist in QuestLogCache" fatal error occurring during initialization on the Ascension WoW client. Implemented a robust retry mechanism in <code>_QuestEventHandler:InitQuestLog</code> and refactored <code>QuestLogCache.GetQuest</code> and <code>GetQuestObjectives</code> to fail gracefully.</li>
<li><strong>[Fix &mdash; Validation Crash]</strong> Fixed crash in <code>DecodePointerMap</code> when compiled database pointer map was empty or corrupted. Added defensive check to return empty table instead of crashing.</li>
<li><strong>[Fix &mdash; Database Compiler]</strong> Added skip logic in <code>ValidateObjects</code> and <code>ValidateQuests</code> when compiled binary data is missing, preventing validation failures on cached databases from incomplete previous sessions.</li>
<li><strong>[Fix &mdash; Initialization]</strong> Modified <code>QuestieInit</code> to skip validation in Stage 1 when plugins are pending, since plugins inject data after compilation and validation would compare stale pre-plugin data against post-plugin data.</li>
<li><strong>[Fix &mdash; Quest Links]</strong> Fixed duplicate quest links when shift-clicking quests in the quest log (e.g., "[A Boar's Vitality] [A Boar's Vitality]"). Removed redundant <code>ChatEdit_InsertLink</code> call from <code>QuestLogTitleButton_OnClick</code> hook.</li>
<li><strong>[Fix &mdash; Profiler]</strong> Fixed "memory allocation error: block too big" crash in <code>QuestieProfiler</code>. Added early-exit logic in <code>HookTable</code> to skip large pure-data tables that don't benefit from profiling.</li>
<li><strong>[Fix &mdash; Zone Mapping]</strong> Added UiMapId overrides for 1415 (Eastern Kingdoms) and 947 (Azeroth) to resolve "No AreaId found" warnings on Ascension servers.</li>
<li><strong>[Fix &mdash; Quest Validation]</strong> Fixed <code>QuestieValidateGameCache</code> to silently skip "ghost quests" instead of failing validation, resolving infinite retry loops on servers with custom quest content.</li>
<li><strong>[Fix &mdash; AscensionDB]</strong> Fixed syntax error in <code>AscensionNpcDB_2.lua</code> (missing closing <code>}</code> at end of NPC entry for ID 3287).</li>
</ul>
<hr>
<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, 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>
</ul>
<hr>
<h2 id="v147">v1.4.7 &mdash; Taint Resolution &amp; WotLKDB Restructure</h2>
<ul>
<li><strong>[Quest Cache]</strong> Resolved requested "Quest cache validation timed out!" errors during initialization. Increased validation timeout to 10 seconds and relaxed internal criteria to support slower server response times.</li>
<li><strong>[Architecture]</strong> Refactored <code>Questie-X-WotLKDB</code> database files from monolithic global definitions into smaller, manageable chunks using <code>addonTable</code> to prevent memory allocation failures during <code>LoadAddOn()</code> and eliminate global namespace pollution.</li>
<li><strong>[Taint Fix]</strong> <code>Loader.lua</code> now explicitly exports WotLKDB data to <code>_G.QuestieX_WotLKDB_*</code> with proper capitalization to match <code>QuestieInit</code> expectations, fully resolving database loading failures and taint vectors.</li>
<li><strong>[Version]</strong> Bumped version to 1.4.7 across the codebase.</li>
</ul>
<hr>
<h2 id="v146">v1.4.6 &mdash; Saved Variables &amp; Texture Fixes</h2>
<ul>
<li><strong>[Fix]</strong> Resolved issue where Questie would not save options or show the Welcome screen repeatedly.</li>
<li><strong>[Fix]</strong> Synchronized AceAddon registration name to "Questie-X" to match folder structure.</li>
<li><strong>[Fix]</strong> Patched AceGUI-3.0 widgets to use string texture paths instead of FileDataIDs for WotLK 3.3.5a.</li>
<li><strong>[Cleanup]</strong> Removed all QX debug print statements.</li>
<li><strong>[Version]</strong> Bumped version to 1.4.6 across the codebase.</li>
</ul>
<hr>
<h2 id="v145">v1.4.5 &mdash; Network &amp; Taint Stability Update</h2>
<ul>
<li><strong>[Network Fix]</strong> Resolved a critical crash ("<code>Usage: AceSerializer:Deserialize(str): str must be a string, got table</code>") occurring in QuestieLearnerComms and Export functions. This was caused by a lightweight, customized <code>AceSerializer-3.0.lua</code> implementation in Questie-X that lacked proper <code>self</code> parameter handling for standard colon-syntax method calls (<code>:</code>). As a result, method calls were serializing/deserializing the library table itself instead of the intended payload string. We patched <code>AceSerializer</code> natively to dynamically support both dot (<code>.</code>) and colon (<code>:</code>) syntax seamlessly without dropping arguments, while ensuring <code>Deserialize</code> correctly yields <code>(success, result)</code> tuples expected by the calling functions.</li>
<li><strong>[Network Fix]</strong> Expanded the previous LibDeflate network crash hotfix: completely purged all 71 instances of unsafe <code>#</code> -&gt; <code>table.getn</code> replacements injected by earlier vanilla compatibility automation throughout <code>LibDeflate.lua</code>. These have been wrapped with a custom dual-typed safe access function that flawlessly determines whether to compute properties natively via <code>string.len(x)</code> for strings or the standard <code>table.getn(x)</code> for structured tables&mdash;guaranteeing 100% stable networking cross-client from 1.12 to 3.3.5 and upwards.</li>
<li><strong>[Network Fix]</strong> Fixed a critical Lua error (<code>bad argument #1 to 'getn' (table expected, got string)</code>) occurring during data-sharing via the hidden <code>questiecomm</code> addon channel. A legacy string length method (<code>table.getn</code>) was mistakenly used in <code>LibDeflate</code> string decoding; this has been restored to the universally compatible <code>string.len</code>.</li>
<li><strong>[Init Fix]</strong> Fixed an issue where the database loader would silently abort on custom 3.3.5 / legacy client setups due to false-positive modern client detection. The <code>isModernClient</code> safeguard now uses a bulletproof <code>tocversion</code> range check to flawlessly differentiate between original legacy engines and modern Classic counterparts.</li>
<li><strong>[QuestieLearner Fix]</strong> Resolved an <code>attempt to index global 'l10n' (a nil value)</code> error that triggered when accepting a new quest, caused by a missing module import at the top of <code>QuestieLearner.lua</code>.</li>
<li><strong>[Taint Fix]</strong> Resolved lingering <code>ADDON_ACTION_BLOCKED</code> taint on <code>ActionButton</code> and <code>StaticPopup</code> that occurred when Questie loaded data from its cache rather than manually recompiling. The <code>_G</code> namespace cleanup for <code>Questie-X-WotLKDB</code> globals now runs accurately during cached loads (via <code>UpdateWotLKDBStats</code>), ensuring the taint vector is closed and additionally freeing ~20MB of redundant cached memory.</li>
<li><strong>[Taint Fix]</strong> Resolved <code>ADDON_ACTION_BLOCKED</code> errors caused by <code>Questie-X-WotLKDB</code> leaving tainted global variables in <code>_G</code> after initialization. <code>QuestieInit._pullGlobal</code> now sets <code>_G[globalName] = nil</code> immediately after copying each WotLKDB table reference into <code>QuestieDB</code>, removing the taint vector while keeping all data fully accessible through <code>QuestieDB</code>. <code>QuestieLearner</code> real-time updates are unaffected &mdash; they write exclusively to <code>QuestieDB.*Overrides</code> and <code>learnedData</code>.</li>
</ul>
<hr>
<h2 id="v144">v1.4.4 — AceGUI Pool &amp; Event Handling Fixes</h2>
<ul>
<li><strong>[AceGUI Fix]</strong> Fixed <code>Compat/embeds.xml</code> to load Wrath-compatible Ace library versions from <code>Libs/</code> (AceGUI-3.0 v34, AceConfigDialog-3.0 v66) instead of newer versions from <code>..\Libs/</code> that caused widget pool corruption.</li>
<li><strong>[AceGUI Fix]</strong> Added nil checks throughout AceGUI-3.0 (<code>Create</code>, <code>Release</code>, <code>WidgetBase.Fire</code>, <code>WidgetContainerBase</code> methods) to prevent crashes when pooled widgets have corrupted/nil properties.</li>
<li><strong>[AceGUI Fix]</strong> Added content nil checks to layout functions (List, Flow, Fill, Table) to prevent crashes when <code>content</code> is nil during layout.</li>
<li><strong>[AceGUI Fix]</strong> Applied same nil check fixes to <code>Compat/Libs/AceGUI-3.0/AceGUI-3.0.lua</code> for consistency.</li>
<li><strong>[Event Fix]</strong> Added nil check for <code>message</code> parameter in <code>QuestieEventHandler:ChatMsgSystem</code> to prevent "bad argument #1 to 'find'" errors.</li>
<li><strong>[Event Fix]</strong> Added nil check for <code>level</code> parameter in <code>QuestiePlayer:SetPlayerLevel</code> to prevent "number expected, got nil" errors.</li>
<li><strong>[QuestieLearner Fix]</strong> Added <code>SanitizeData</code> function with depth limiting and proper key/value filtering to remove functions, userdata, and thread values from learned data before network serialization.</li>
<li><strong>[QuestieLearner Fix]</strong> Added pcall wrapper around <code>AceSerializer:Serialize</code> to catch and log any remaining serialization errors instead of crashing.</li>
<li><strong>[QuestieLearner Fix]</strong> Added early return checks in <code>BroadcastLearnedData</code> when data is nil or sanitization produces empty results.</li>
<li><strong>[Journey Fix]</strong> Added nil check for <code>container</code> in <code>HandleTabChange</code> to prevent "attempt to index local 'container'" errors.</li>
<li><strong>[l10n Fix]</strong> Added type check for <code>translationValue</code> in l10n:translate to prevent "bad argument #2 to 'format'" errors when translation is not a string or when format arguments are missing.</li>
<li><strong>[Tracker Fix]</strong> Removed redundant shift-click tracking toggle logic in <code>Hooks.lua</code> that was instantly reverting tracker states when shift-clicking a quest in the Quest Log.</li>
</ul>
<hr>
<h2 id="v143">v1.4.3 — Taint & API Compatibility Fixes</h2>
<ul>
<li><strong>[Taint Fix]</strong> Deferred SetItemRef hook execution using C_Timer.After to avoid taining protected execution contexts.</li>
<li><strong>[Taint Fix]</strong> Removed redundant <code>_G = _G or {}</code> from WotLKDB data file that could contribute to namespace pollution.</li>
<li><strong>[Taint Fix]</strong> Moved xpcall polyfill from bare <code>_G.xpcall</code> to <code>QuestieCompat.xpcall</code> namespace to prevent polluting the global table.</li>
<li><strong>[API Fix]</strong> Added polyfills for <code>GetCurrentRegion</code> and <code>GetCurrentRegionName</code> for AceDB-3.0 compatibility on WotLK/Classic.</li>
<li><strong>[API Fix]</strong> Added polyfills for <code>Ambiguate</code> and <code>RegisterAddonMessagePrefix</code> for AceComm-3.0 compatibility on WotLK/Classic.</li>
<li><strong>[API Fix]</strong> Added conditional check for <code>DialogBorderOpaqueTemplate</code> and <code>SetFixedFrameStrata</code> in AceConfigDialog for WotLK/Classic.</li>
<li><strong>[AceGUI Fix]</strong> Added WotLK-compatible fallback for <code>SetColorTexture</code> using <code>SetTexture</code> + <code>SetVertexColor</code>.</li>
</ul>
<hr>
<h2 id="v142">v1.4.2 — Quest Route Optimization</h2>
<ul>
<li><strong>[Feature]</strong> Added Quest Route Optimization with three modes: Single Quest, All Tracked Quests, and TSP Approximation.</li>
<li><strong>[Feature]</strong> Route mode can be selected in Tracker options under "Route Mode".</li>
<li><strong>[Feature]</strong> Nearest-neighbor TSP algorithm for calculating optimal quest routes.</li>
<li><strong>[Feature]</strong> Visual route lines drawn on map connecting objectives in optimized order.</li>
</ul>
<hr>
<h2 id="v141">v1.4.1 — Cleanup &amp; Repository Maintenance</h2>
<ul>
<li><strong>[Cleanup]</strong> Removed <code>.history/</code> and <code>Research/</code> folders from repository tracking.</li>
<li><strong>[Cleanup]</strong> Removed <code>Tests/</code> folder from repository tracking.</li>
<li><strong>[Badge]</strong> Updated downloads badge format in README.</li>
<li><strong>[Gitignore]</strong> Added <code>tests/</code> to <code>.gitignore</code>.</li>
</ul>
<hr>
<h2 id="v140">v1.4.0 — Taint Resolution &amp; Compatibility Fixes</h2>
<ul>
<li><strong>[C_Timer Fix]</strong> Fixed <code>C_Timer</code> OnUpdate to use precise <code>(self, elapsed)</code> parameter instead of <code>1/GetFramerate()</code>.</li>
<li><strong>[Achievement Fix]</strong> Fixed <code>IsAchievementCompleted</code> to properly check completion boolean via <code>select(4, GetAchievementInfo(...))</code>.</li>
<li><strong>[Map Fix]</strong> Fixed <code>C_Map.GetPlayerMapPosition</code> to use correct legacy API.</li>
<li><strong>[QuestieLearner Fix]</strong> Fixed <code>GetNpcIdFromGUID</code> and <code>GetObjectIdFromGUID</code> being called before definition.</li>
<li><strong>[Taint Fix]</strong> Added <code>InCombatLockdown()</code> guards and <code>pcall</code> wrappers to secure hooks to prevent protected function access errors.</li>
</ul>
<hr>
<h2 id="v137">v1.3.7 — Quest Tracking &amp; Robustness</h2>
<ul>
<li><strong>[Quest Tracking]</strong> Resolved inconsistent quest tracking/untracking by making the tracking state idempotent. This eliminates "doing nothing" results while toggling quests in the Quest Log and prevents tracking loops caused by Blizzard's auto-track feature.</li>
<li><strong>[Robustness]</strong> Improved <code>QuestieTracker</code> to safely handle <code>nil</code> returns from the WoW API (<code>GetQuestLogTitle</code>), preventing potential "attempt to compare number with nil" errors during rapid quest log updates.</li>
<li><strong>[Internal Logic]</strong> Refined the detection of internal Blizzard objective updates to ensure they don't accidentally untrack quests that the user intended to watch.</li>
<li><strong>[Linting]</strong> Suppressed diagnostic warnings related to global namespace shimming in <code>QuestieCompat.lua</code>.</li>
</ul>
<hr>
<h2 id="v136">v1.3.6 — Tooltip Fixes &amp; Enhanced Taint Workaround</h2>
<ul>
<li><strong>[Quest Progress]</strong> Resolved an issue where quest tooltips would not reliably update their progress counts when dynamically learning AI spawns or during rapid kill credit updates.</li>
<li><strong>[Taint Workaround]</strong> Reinforced the <code>WorldMapTaintWorkaround</code> for older WoW clients (3.3.5) by correcting the <code>IsAddOnLoaded</code> detection timing and adding explicit empty-function stubs to blocked dropdowns.</li>
<li><strong>[Diagnostics]</strong> Improved error reporting for global variable collisions during module initialization.</li>
</ul>
<h2 id="v135">v1.3.5 — Comprehensive Taint &amp; Architecture Fixes</h2>
<p><em>A comprehensive update resolving 13 distinct issues related to global environment taint, unsafe polyfills, and module stability across all supported Lua environments (5.0, 5.1, 5.2).</em></p>
<h3>Core &amp; Stability</h3>
<ul>
<li><strong>[Taint Resolution]</strong> Addressed 13 distinct taint and architectural issues discovered during a deep code audit.</li>
<li><strong>[Taint Fix]</strong> Removed unsafe <code>function Questie:Warning(...)</code> global monkey-patching in <code>QuestieTracker.lua</code> (<code>_InstallMissingQuestLogWarningFilter</code> deleted). Ghost quest iterations now use a safe, two-phase collection and deletion pattern to prevent warning generation upfront.</li>
<li><strong>[Global Safety]</strong> <code>hooksecurefunc</code> polyfill in <code>QuestieCompat.lua</code> no longer pollutes the global <code>_G</code> namespace. It runs strictly as a local fallback when the native function is absent.</li>
<li><strong>[Global Safety]</strong> Removed bare <code>_G</code> writes for <code>C_Seasons</code> and <code>C_Timer</code>.</li>
<li><strong>[Global Safety]</strong> Prevented <code>_G.QuestieX_WotLKDB_Counts</code> from writing to the global namespace in <code>QuestieInit.lua</code>. Counts are now cleanly stored directly on the plugin object (<code>plugin.stats</code>).</li>
<li><strong>[Module Safety]</strong> The <code>CreateModule</code> function in <code>QuestieLoader.lua</code> now guards against double-registration of modules, preventing accidental aliasing and overwriting.</li>
<li><strong>[Code Cleanup]</strong> Eliminated duplicate shims (<code>string.match</code>, <code>select</code>) from <code>QuestieCompat.lua</code> that were already provided canonically by <code>QuestieLoader.lua</code>.</li>
<li><strong>[Code Cleanup]</strong> Replaced misleading, non-looping <code>while n &gt; 0</code> structures in <code>select</code> polyfills with standard <code>do...end</code> blocks.</li>
<li><strong>[Diagnostics]</strong> Gated <code>loadstring</code> execution in <code>QuestieInit:LoadDatabase</code>. On modern clients, it now safely blocks and logs an error instead of executing potentially tainted legacy string DBs at runtime.</li>
</ul>
<hr>
<h2 id="v134">v1.3.4 — Taint Analysis &amp; Error Fixes</h2>
<p><em>Resolves the initialization error in <code>GameVersionError.lua</code> and implements a version guard to support Classic-era private servers. Also confirms the integrity of the WotLKDB module after a deep taint analysis.</em></p>
<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.</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>
</ul>
<hr>
<h2 id="v133">v1.3.3 — Critical Taint Fix &amp; Module Loading</h2>
<p><em>Addresses the missing <code>WorldMapTaintWorkaround</code> module that was excluded from previous releases, finally enabling the intended seat-of-pants taint mitigation strategy.</em></p>
<h3>Core &amp; Stability</h3>
<ul>
<li><strong>[Taint Fix]</strong> Officially included <code>Modules\WorldMapTaintWorkaround.lua</code> in all TOC files to ensure it's loaded and executed.</li>
<li><strong>[Error Handling]</strong> Included <code>Modules\GameVersionError.lua</code> in all TOC files for better unsupported client detection.</li>
<li><strong>[Version Sync]</strong> Synchronized version numbers across all 4 TOC flavors.</li>
</ul>
<hr>
<h2 id="v132">v1.3.2 — Taint Resolution &amp; Stability</h2>
<p><em>Finalizes the Taint Resolution project, eliminating <code>ADDON_ACTION_BLOCKED: UseAction()</code> errors by refactoring internal hooks to use secure alternatives and hardening the global namespace against collisions.</em></p>
<h3>Core &amp; Stability</h3>
<ul>
<li><strong>[Taint Resolution]</strong> Refactored <code>Hooks.lua</code> to use <code>hooksecurefunc</code> instead of raw hooks for all secure functions.</li>
<li><strong>[Global Safety]</strong> Enhanced <code>QuestieLoader.lua</code> with a new collision-aware <code>PopulateGlobals</code> engine that prevents overwriting existing global variables and provides diagnostic warnings.</li>
<li><strong>[Security]</strong> Eliminated global namespace modifications in <code>QuestieInit.lua</code>.</li>
<li><strong>[Workaround Hardening]</strong> Refactored <code>WorldMapTaintWorkaround.lua</code> to remove legacy global function reassignments that were causing secondary taint.</li>
</ul>
<h2 id="v131">v1.3.1 — QuestieComms Streamlining</h2>
<p><em>Refines the data-sharing mechanism to use exclusively hidden global channels, removing guild-channel broadcasts to minimize chat traffic while maintaining real-time data synchronization.</em></p>
<h3>Network &amp; Comms</h3>
<ul>
<li>Removed <code>AceComm:SendCommMessage</code> targeting the <code>GUILD</code> channel in <code>QuestieLearnerComms.lua</code>. All background data synchronization now travels exclusively via the <code>questiecomm</code> hidden global channel.</li>
<li>Refined the communication "heartbeat" to ensure zero impact on player guild chat logs while maintaining real-time confidence updates for learned NPCs.</li>
</ul>
<h2 id="v130">v1.3.0 — QuestieLearner Confidence &amp; Tiered Pruning Engine</h2>
<p><em>Implements a robust verification model for learned data, introducing a tiered confidence system (Verified vs Unconfirmed), automated stale data cleanup with protection for high-confidence entries, and a critical correction to coordinate scaling logic for 3.3.5a combat log events. Refactors global data sharing to utilize hidden chat channels for community-wide confidence calculation.</em></p>
<h3>QuestieLearner.lua — Confidence &amp; Coordinate Scaling</h3>
<ul>
<li><strong>[Coordinate Scaling Fix — 0-100 normalization]</strong> <code>OnCombatLogEvent</code> now explicitly scales player position coordinates by 100 before recording. Previously, <code>C_Map.GetPlayerMapPosition</code> returned values on a 0-1 range, while Questie's internal modules and map pins expect 0-100. This fix resolves the "pins in the top-left corner" bug for all learned data.</li>
<li><strong>[Tiered Data Model — Verified vs Unconfirmed]</strong> Introduced the <code>mc</code> (Match Count) field as a primary confidence metric. Data is promoted to "Verified" status once <code>mc >= minConfidencePins</code> (default: 2). Verified data is exempt from automatic time-based pruning.</li>
<li><strong>[Last Seen (ls) Timestamping]</strong> Added a <code>ls</code> key to the learned data schema for NPCs, Objects, Quests, and Items. Updated on every kill, interaction, or network confirmation. This provides the temporal baseline for the new stale data cleanup engine.</li>
<li><strong>[Confidence-based Map Pin Gating]</strong> Modified the map pin generation logic to honor the <code>minConfidencePins</code> setting. Pins only appear on the map/minimap if the learned data has been confirmed by multiple kills (either local or received via network).</li>
<li><strong>[Global Data Sharing — QuestieComms Refactor]</strong> Switched from <code>PARTY</code>/<code>RAID</code> channels to a hidden global channel for learned data broadcast. This allows kills by any Questie-X user in the vicinity to contribute to local data confidence, effectively crowd-sourcing verification in real-time.</li>
<li><strong>[Tooltip Confidence Display]</strong> The tooltip handler now pulls the <code>mc</code> count for learned entries and displays it alongside the "Learned" label (e.g., <code>(Learned - Confidence: 3)</code>), providing immediate visual feedback on data reliability.</li>
</ul>
<h3>QuestieLearnerExport.lua — Tiered Pruning Engine</h3>
<ul>
<li><strong>[RunPrune — Stale threshold logic]</strong> Implemented automated time-based pruning in the <code>Cleanup</code> function. Only entries marked as "Unconfirmed" (low confidence) are subject to expiration. The expiration logic uses <code>(time() - entry.ls) > staleThreshold</code> (default: 90 days).</li>
<li><strong>[Verified Protection]</strong> Verified entries are explicitly protected from the time-based cleanup loop unless the <code>pruneVerified</code> setting is manually enabled by the user.</li>
</ul>
<h3>QuestieOptionsDatabase.lua — Verification Controls</h3>
<ul>
<li><strong>[Stale Threshold Slider]</strong> Added a configuration slider in the Database tab allowing users to set the cleanup window (1 to 180 days).</li>
<li><strong>[Prune Verified Toggle]</strong> Added a checkbox to allow manual purging of verified data if desired.</li>
<li><strong>[Prioritize My Data]</strong> Implemented a "Prioritize My Data" toggle that dynamically hides static database pins when high-confidence learned data exists for the same NPC in a zone.</li>
</ul>
<h2 id="v129">v1.2.9 — QuestieLearner Cross-Link Engine + Tracker Zone Fix + Untrack Fix</h2>
<p><em>Introduces a universal bidirectional cross-link engine in QuestieLearner that automatically stitches relationships between all four entity types (NPCs, Quests, Objects, Items) as data is learned — no manual wiring needed. Fixes the tracker's persistent "Unknown Zone" header for custom/unknown quests by replacing unreliable map API calls with the canonical 3.3.5a quest log header walk. Fixes a logic inversion in UntrackQuestId that prevented shift-click untacking from working.</em></p>
<h3>QuestieLearner.lua — Universal Cross-Link Engine</h3>
<ul>
<li><strong>[Cross-link engine — full rewrite]</strong> Replaced the narrow NPC↔Quest cross-link stubs with a comprehensive bidirectional relationship engine covering all four entity types. Three shared primitives underpin the whole system: <code>_AddToArray(tbl, key, value, ovrTable, ovrId)</code> adds a value to an array field and mirrors it to the live <code>*DataOverrides</code> table in the same call; <code>_AddToNestedArray</code> does the same for two-level nested arrays (used for quest starters/finishers sub-slots <code>quest[2][1]</code>, <code>quest[2][2]</code>, etc.); <code>_AddToQuestObjective</code> inserts <code>{entityId, text}</code> pairs into <code>quest[10][slot]</code> with the same dual-write pattern. Every write is idempotent — a no-op if the value is already present. All three primitives write to both <code>learnedData</code> (SavedVariables, persistent) and the live <code>*DataOverrides</code> tables (in-memory, immediate effect on map pins and tooltips).</li>
<li><strong>[CrossLinkAfterNPC]</strong> Called whenever a new NPC ID is first committed to <code>learnedData.npcs</code>. Performs three scans across all learned quests: (a) if <code>quest[2][1]</code> contains this npcId → adds questId to <code>npc[10]</code> (questStarts); (b) if <code>quest[3][1]</code> contains this npcId → adds questId to <code>npc[11]</code> (questEnds); (c) walks <code>quest[10][3]</code> (item objectives) — for each item whose <code>item[2]</code> (npcDrops) already references this NPC, the NPC is injected into <code>quest[10][1]</code> as a creature objective so it receives map pins and tooltip kill-count text.</li>
<li><strong>[CrossLinkAfterQuest]</strong> Called when a new quest is first committed to <code>learnedData.quests</code>. Links all referenced entities in one pass: starter NPCs (<code>quest[2][1]</code>) → each known NPC's <code>npc[10]</code>; starter objects (<code>quest[2][2]</code>) → each known object's <code>obj[2]</code>; finisher NPCs (<code>quest[3][1]</code>) → <code>npc[11]</code>; finisher objects (<code>quest[3][2]</code>) → <code>obj[3]</code>; source item (<code>quest[11]</code>) → <code>item[5]</code> (startQuest key per itemDB schema); item drop chain: for each item in <code>quest[10][3]</code> whose <code>item[2]</code> lists known NPCs, those NPCs are injected into <code>quest[10][1]</code> as creature objectives.</li>
<li><strong>[CrossLinkAfterObject]</strong> Called when a new object is first committed to <code>learnedData.objects</code>. Scans all learned quests: if <code>quest[2][2]</code> references this objectId → adds questId to <code>obj[2]</code> (questStarts); if <code>quest[3][2]</code> references it → adds questId to <code>obj[3]</code> (questEnds).</li>
<li><strong>[CrossLinkAfterItem]</strong> Called when a new item is first committed to <code>learnedData.items</code> AND whenever a new drop-NPC relationship is added via <code>LearnItemDrop</code>. Two passes: (1) scan all learned quests for <code>quest[11] == itemId</code> → set <code>item[5] = questId</code> (this item starts that quest); (2) scan all learned quests for <code>quest[10][3]</code> entries matching itemId — for each such quest, every NPC in <code>item[2]</code> (drop sources) is injected into <code>quest[10][1]</code> as a creature objective. Practical example: accept a quest needing "Wolf Pelt" → wolf is killed and the pelt drops → <code>LearnItemDrop</code> fires → <code>CrossLinkAfterItem</code> runs → the wolf NPC is immediately added as a creature objective for the pelt quest with active map pins, no reload.</li>
<li><strong>[CrossLinkAfterQuestGiver]</strong> Now handles all three entity type slots (1=NPC, 2=Object, 3=Item) instead of only NPCs. For typeSlot=1: stitches <code>npc[10/11]</code><code>quest[2/3][1]</code> bidirectionally. For typeSlot=2: stitches <code>obj[2/3]</code><code>quest[2/3][2]</code>. For typeSlot=3: adds itemId to <code>quest[2][3]</code> (item starters sub-slot). Called by <code>LearnQuestGiver</code> for all typeSlots, replacing the previous NPC-only check.</li>
<li><strong>[LearnItemDrop hook]</strong> Now calls <code>CrossLinkAfterItem(itemId)</code> every time a new NPC is added to an item's drop list, not only on initial item creation. This retroactively propagates the drop→quest objective chain for previously learned items.</li>
<li><strong>[LearnQuest / LearnObject / LearnItem hooks]</strong> Each now calls its respective <code>CrossLinkAfterX</code> function on first creation of the entity record. Subsequent updates to an existing record skip the cross-link scan (already linked on creation).</li>
<li><strong>[Item schema correction]</strong> Fixed an incorrect use of key <code>[9]</code> (which maps to <code>itemLevel</code> in <code>QuestieDB.itemKeys</code>) for quest-source storage. The correct key is <code>[5]</code> (<code>startQuest</code>). Affects <code>CrossLinkAfterQuest</code> and <code>CrossLinkAfterItem</code>.</li>
<li><strong>[Mouseover — all NPCs now learned]</strong> Removed the <code>UnitReaction &lt; 4</code> early-return guard that blocked learning hostile or neutral NPCs via mouseover. The existing <code>QUESTGIVER</code> NPC-flag bitmask check (<code>0x02</code>) remains as the primary filter, ensuring only NPCs with a quest-giver relationship are stored. Hostile quest objective NPCs are now captured on any hover, target, or kill and are correctly cross-linked to their quests.</li>
</ul>
<h3>TrackerUtils.lua — Zone Resolution Rewrite</h3>
<ul>
<li><strong>[GetQuestLogZoneName — canonical 3.3.5a zone lookup]</strong> Added new local function <code>GetQuestLogZoneName(questId)</code>. Implements the only fully reliable zone resolution method in 3.3.5a: iterates the quest log from index 1 to <code>GetNumQuestLogEntries()</code> to locate the target quest's log index, then walks backwards from that index until an entry where <code>isHeader=true</code> is found. That header's <code>title</code> string is the zone name (e.g. <code>"Shadowmoon Valley"</code>). <code>GetRealZoneText()</code> and <code>GetCurrentMapAreaID()</code> were previously used but are unreliable because they reflect the currently viewed map, not the quest's zone. The 3.3.5a client places zone header entries directly above their child quests in the flat quest log list — this is guaranteed by the client and is the same method Blizzard's own UI uses to render the zone groups.</li>
<li><strong>[BuildFallbackQuest — zone via log walk]</strong> Replaced the <code>GetRealZoneText()</code> call with the inline header walk. The loop that already iterates the quest log to find the fallback quest's entry now continues backwards from that same index to find the header. <code>GetAreaIdByZoneName()</code> converts the string to an area ID for <code>zoneOrSort</code>; the raw string is stored as <code>quest.zoneName</code> for display. If no header is found (e.g. "Special" category with no zone header), <code>zoneName</code> remains nil and the tracker falls back to the <code>_GetZoneName(zoneOrSort)</code> path.</li>
<li><strong>[_isLogFallback patch block — zone fix]</strong> The block that patches <code>QuestLogCache</code> fallback objects (identified by <code>_isLogFallback=true</code> and absence of an <code>IsComplete</code> method) now calls <code>GetQuestLogZoneName(capturedId)</code> immediately after patching <code>Objectives</code>, <code>SpecialObjectives</code>, and <code>ExtraObjectives</code>. If a zone name is returned, both <code>quest.zoneName</code> and <code>quest.zoneOrSort</code> are set on the object in-place. Previously this block left both values at their <code>QuestLogCache</code> defaults (<code>zoneOrSort=0</code>, <code>zoneName=nil</code>), which caused every fallback quest to group under "Unknown Zone" in the tracker regardless of where the quest was accepted.</li>
<li><strong>[Fallback cache invalidation]</strong> <code>_fallbackQuests[qid]</code> is now evicted and unconditionally rebuilt if the cached entry has no <code>zoneName</code>. This handles the race condition where <code>BuildFallbackQuest</code> was called during addon initialization before the quest log entries were populated, caching a zoneless entry that would persist for the entire session.</li>
</ul>
<h3>QuestieTracker.lua — Untrack Fix</h3>
<ul>
<li><strong>[UntrackQuestId — inverted logic fixed]</strong> <code>UntrackQuestId</code> previously set <code>AutoUntrackedQuests[questId] = nil</code> when the player untracks a quest. The tracker filter expression is <code>not Questie.db.char.AutoUntrackedQuests[questId]</code> — when the value is <code>nil</code>, <code>not nil</code> is <code>true</code>, meaning "show this quest". Clearing the entry on untrack therefore immediately re-showed the quest on the very next tracker update tick, making untacking appear completely broken. Fix: in <code>autoTrackQuests</code> mode, <code>UntrackQuestId</code> now sets <code>AutoUntrackedQuests[questId] = true</code> (evaluated as "hide"). In manual-track mode, it clears <code>TrackedQuests[questId]</code> as before, and <code>AutoUntrackedQuests</code> is cleared instead of being incorrectly toggled. The two tracking modes are now cleanly separated within the function.</li>
<li><strong>[AQW_Insert — QuestLogFrame gate removed]</strong> The condition <code>IsShiftKeyDown() and QuestLogFrame:IsShown()</code> inside <code>AQW_Insert</code> was blocking the re-tracking path (clearing <code>AutoUntrackedQuests[questId]</code>) whenever the quest log frame was not open. Since untacking fires <code>RemoveQuestWatch</code><code>UntrackQuestId</code> directly, this <code>AQW_Insert</code> branch was only ever reached for re-tracking — yet the gate required the quest log to be open. The gate has been removed; re-tracking now works from any UI context.</li>
</ul>
</div>
<div class="container">
<h2 id="v126">v1.2.6 — QuestieLearner Comprehensive Overhaul</h2>
<p><em>Rewrote QuestieLearner from scratch to fix all known data-capture deficiencies. Adds full quest field coverage, grid-based coordinate clustering, quest-giver-only mouseover filtering, async item-info retry, object loot detection, and live inject-on-import so imported data takes effect without a reload.</em></p>
<h3>QuestieLearner.lua — Full Rewrite</h3>
<ul>
<li><strong>[Quest capture completeness]</strong> <code>LearnQuest</code> now accepts a generic <code>data</code> table keyed by Questie wiki array indices rather than individual positional arguments. <code>OnQuestDetail</code> captures title, objectives text block, quest description body, and current zone as <code>zoneOrSort[8]</code>. <code>OnQuestAccepted</code> fills quest level from <code>GetQuestLogTitle</code>, per-objective text from <code>GetQuestLogLeaderBoard</code>, and required money from <code>GetQuestLogRequiredMoney</code>. <code>OnQuestComplete</code> captures finish/reward text via <code>GetRewardText</code>.</li>
<li><strong>[Mouseover filter]</strong> <code>OnMouseoverUnit</code> now only learns an NPC if its <code>UnitNPCFlags</code> bitmask includes the <code>QUESTGIVER</code> bit (<code>0x02</code>), OR if the NPC already exists in <code>QuestieDB</code> as a known quest starter/finisher. All other NPCs are silently ignored — this eliminates the flood of irrelevant NPC entries the learner previously accumulated.</li>
<li><strong>[Target changed]</strong> <code>OnTargetChanged</code> no longer calls <code>LearnNPC</code> on every target switch. It now only populates the <code>guidNpcCache</code> for kill-tracking purposes, avoiding recording non-quest NPCs.</li>
<li><strong>[Coordinate clustering — grid bucketing]</strong> Replaced the naive "within 1 unit radius" deduplication with a 2×2 grid bucket scheme (<code>COORD_GRID = 2.0</code>). A new point is inserted only when no existing point shares the same grid cell. This prevents coordinate scatter across a kill area while still preserving distinct spawn clusters. The <code>InsertIfNewBucket</code> helper is shared across NPC, Object, and all merge paths (including <code>InjectLearnedData</code> and <code>HandleNetworkData</code>).</li>
<li><strong>[Object recording]</strong> <code>OnLootOpened</code> now checks whether the loot source GUID is a <code>GameObject</code>. If so, <code>LearnObject</code> is called with the object's ID and name. <code>OnGossipShow</code> records both objects and NPCs via <code>GetIdAndTypeFromGUID</code>. <code>OnQuestDetail</code> and <code>OnQuestComplete</code> also record the interacting object when the quest giver/finisher is a <code>GameObject</code>.</li>
<li><strong>[Item loot — async GetItemInfo retry]</strong> <code>OnLootOpened</code> queues unresolved item links (where <code>GetItemInfo</code> returns nil because the item is not yet in the client cache) into <code>_Learner.pendingItemLinks</code>. A new <code>OnGetItemInfoReceived(itemId)</code> handler fires on the <code>GET_ITEM_INFO_RECEIVED</code> event, resolves queued links for that item ID, and calls <code>LearnItem</code>/<code>LearnItemDrop</code> once the data is available. This fixes silent data loss on first-encounter loots.</li>
<li><strong>[Quest giver entity type]</strong> <code>LearnQuestGiver</code> now accepts an <code>entityType</code> argument (1=NPC, 2=GameObject, 3=item) and stores starters/finishers in the correct sub-array slot matching the Questie wiki spec <code>{ [1]={npcIds}, [2]={objIds}, [3]={itemIds} }</code>.</li>
<li><strong>[Kill tracking]</strong> <code>OnCombatLogEvent</code> retains all three GUID-resolution paths (dash-split, GUID cache, hex-prefix) with a 10-minute TTL cache cleanup. Uses <code>CombatLogGetCurrentEventInfo()</code> with fallback to varargs for cross-client compatibility.</li>
<li><strong>[InjectLearnedData — grid clustering]</strong> All coordinate merge loops now use <code>InsertIfNewBucket</code> instead of the old radius check.</li>
</ul>
<h3>QuestieLearnerExport.lua — Import Live-Inject Fix</h3>
<ul>
<li><strong>[MergeImport → InjectLearnedData]</strong> After merging all four data categories, <code>MergeImport</code> immediately calls <code>QuestieLearner:InjectLearnedData()</code>. Imported data is pushed into <code>QuestieDB.*DataOverrides</code> in the same frame — override-driven map pins update without a <code>/reload</code>. A full reload is still needed to pick up newly imported quest starters/finishers for quests already in the player's quest log.</li>
</ul>
<h3>README — Import Clarification</h3>
<ul>
<li>Corrected the "no reload required" claim. The import flow now accurately describes when an immediate effect is visible versus when a <code>/reload</code> is beneficial.</li>
</ul>
</div>
<div class="container">
<h2 id="v125">v1.2.5 — Ebonhold &amp; Ascension DB Plugin Load Fix</h2>
<p><em>Fixed a fatal load-time crash in all four Ebonhold DB files (and identically in the Ascension DB files) caused by calling <code>GetRealmName()</code> and <code>QuestieLoader:CreateModule()</code> at file scope — before WoW's API is fully available. Switched to plain global table population; realm-gating and injection remain safely deferred to the loader's <code>PLAYER_LOGIN</code> handler.</em></p>
<h3>Root Cause</h3>
<ul>
<li><strong>[File-scope execution]</strong> <code>EbonholdNpcDB.lua</code>, <code>EbonholdObjectDB.lua</code>, <code>EbonholdItemDB.lua</code>, and <code>EbonholdQuestDB.lua</code> each began with <code>if GetRealmName() ~= "Rogue-Lite (Live)" then return end</code> followed immediately by <code>QuestieLoader:CreateModule("EbonholdDB")</code>. Both calls execute at file-load time (during <code>LoadAddOn()</code>), before <code>PLAYER_LOGIN</code> has fired and before <code>QuestieLoader</code> is guaranteed to be populated. This produced <code>attempt to index global 'QuestieLoader' (a nil value)</code> on every load.</li>
<li><strong>[Same issue in Ascension DB]</strong> All four <code>Bronzebeard/AscensionXxxDB.lua</code> files had the same pattern with a different realm guard. Same crash, same fix applied.</li>
</ul>
<h3>Questie-X-EbonholdDB</h3>
<ul>
<li><strong>[Ebonhold/EbonholdNpcDB.lua]</strong> Removed file-level realm guard and <code>QuestieLoader:CreateModule</code> call. Replaced with <code>_G.EbonholdDB = _G.EbonholdDB or {}</code> and a local alias. Data is now populated unconditionally into the global at load time.</li>
<li><strong>[Ebonhold/EbonholdObjectDB.lua]</strong> Same fix.</li>
<li><strong>[Ebonhold/EbonholdItemDB.lua]</strong> Same fix.</li>
<li><strong>[Ebonhold/EbonholdQuestDB.lua]</strong> Same fix.</li>
<li><strong>[EbonholdLoader.lua]</strong> Replaced <code>QuestieLoader:ImportModule("EbonholdDB")</code> with <code>_G.EbonholdDB or {}</code>. The loader's existing <code>PLAYER_LOGIN</code> handler with realm check remains intact as the sole gate for injection into Questie-X.</li>
</ul>
<h3>Questie-X-AscensionDB</h3>
<ul>
<li><strong>[Bronzebeard/AscensionNpcDB.lua]</strong> Removed file-level realm guard and <code>QuestieLoader:CreateModule</code> call. Replaced with <code>_G.AscensionDB = _G.AscensionDB or {}</code>.</li>
<li><strong>[Bronzebeard/AscensionObjectDB.lua]</strong> Same fix.</li>
<li><strong>[Bronzebeard/AscensionItemDB.lua]</strong> Same fix.</li>
<li><strong>[Bronzebeard/AscensionQuestDB.lua]</strong> Same fix.</li>
<li><strong>[AscensionLoader.lua]</strong> Replaced <code>QuestieLoader:ImportModule("AscensionDB")</code> with <code>_G.AscensionDB or {}</code>.</li>
<li><strong>[Questie-X-AscensionDB.toc]</strong> TOC title corrected from <code>Questie-Ascension</code> to <code>Questie-X-AscensionDB</code> to match the folder name and plugin naming convention.</li>
</ul>
<h3>XXH_Lua_Lib Universal Compatibility Fix</h3>
<ul>
<li><strong>[Libs/XXH_Lua_Lib/XXH_Lua_Lib.lua]</strong> All string-length calls were incorrectly using <code>table.getn(str)</code>, which expects a table — passing a string (e.g. a player name like <code>"MooeshaLC@Asc.BB25"</code>) caused a crash: <code>bad argument #1 to 'getn' (table expected, got string)</code>. Fixed by replacing all string-length calls with <code>string.len(str)</code> (universal across Lua 5.0 and 5.1). Table-length calls restored to <code>table.getn(t)</code> for the same universal coverage — the <code>#</code> length operator is only available in Lua 5.1+ and would break on WoW 1.12 vanilla clients.</li>
</ul>
<h3>Repository & Releases</h3>
<ul>
<li>Published initial GitHub releases for all four database plugins: <a href="https://github.com/Xurkon/Questie-X-WotLKDB" style="color: var(--accent-blue)">Questie-X-WotLKDB</a>, <a href="https://github.com/Xurkon/Questie-X-TBCDB" style="color: var(--accent-blue)">Questie-X-TBCDB</a>, <a href="https://github.com/Xurkon/Questie-X-EbonholdDB" style="color: var(--accent-blue)">Questie-X-EbonholdDB</a>, <a href="https://github.com/Xurkon/Questie-X-AscensionDB" style="color: var(--accent-blue)">Questie-X-AscensionDB</a>.</li>
<li>Added <code>.gitattributes</code> to all five repos with <code>export-ignore</code> for dev-only files (<code>.gitattributes</code>, <code>.gitignore</code>, <code>Tools/</code>, <code>docs/</code>, <code>.git_disabled/</code>). Release archives are now generated from tags — named <code>RepoName-vX.Y.Z.zip</code> rather than <code>RepoName-main.zip</code>.</li>
<li>README plugin table updated to link all four available repos.</li>
</ul>
<hr>
<h2 id="v124">v1.2.4 — Retail/SoD Corrections Removed</h2>
<p><em>Deleted all Season of Discovery, Season of Mastery, and Hardcore correction files. These were retail-specific and never referenced by the TOC. Cleaned up all dead imports, constants, and code branches they left behind in <code>QuestieCorrections.lua</code>.</em></p>
<h3>Files Deleted</h3>
<ul>
<li><strong><code>Database/Corrections/SeasonOfDiscovery.lua</code></strong> — SoD base quest/NPC/object/item overrides</li>
<li><strong><code>Database/Corrections/sodQuestFixes.lua</code></strong> — SoD quest corrections</li>
<li><strong><code>Database/Corrections/sodNPCFixes.lua</code></strong> — SoD NPC corrections</li>
<li><strong><code>Database/Corrections/sodItemFixes.lua</code></strong> — SoD item corrections</li>
<li><strong><code>Database/Corrections/sodObjectFixes.lua</code></strong> — SoD object corrections</li>
<li><strong><code>Database/Corrections/Automatic/sodBaseQuests.lua</code></strong> — SoD auto-generated base quests</li>
<li><strong><code>Database/Corrections/Automatic/sodBaseNPCs.lua</code></strong> — SoD auto-generated base NPCs</li>
<li><strong><code>Database/Corrections/Automatic/sodBaseItems.lua</code></strong> — SoD auto-generated base items</li>
<li><strong><code>Database/Corrections/Automatic/sodBaseObjects.lua</code></strong> — SoD auto-generated base objects</li>
<li><strong><code>Database/Corrections/HardcoreBlacklist.lua</code></strong> — Hardcore mode quest blacklist</li>
<li><strong><code>Database/Corrections/SoMPhases.lua</code></strong> — Season of Mastery phase data (was already commented out in TOC)</li>
</ul>
<h3>QuestieCorrections.lua Cleanup</h3>
<ul>
<li><strong>Imports removed:</strong> <code>HardcoreBlacklist</code> and <code>SeasonOfDiscovery</code> <code>ImportModule</code> calls deleted.</li>
<li><strong>Constants removed:</strong> <code>SOD_ONLY = 5</code> and <code>HIDE_SOD = 6</code> deleted from the expansion filter enum. Remaining constants (<code>TBC_ONLY</code>, <code>CLASSIC_ONLY</code>, <code>WOTLK_ONLY</code>, <code>TBC_AND_WOTLK</code>, <code>CLASSIC_AND_TBC</code>) are unchanged.</li>
<li><strong><code>filterExpansion</code>:</strong> Removed the <code>isSoD</code> local and the two <code>SOD_ONLY</code> / <code>HIDE_SOD</code> branches.</li>
<li><strong><code>MinimalInit</code>:</strong> Removed the <code>if Questie.IsSoD then addOverride(...SeasonOfDiscovery...) end</code> block and the <code>if Questie.IsHardcore then HardcoreBlacklist:Load() end</code> block.</li>
<li><strong><code>Initialize</code>:</strong> Removed the 8-call <code>if Questie.IsSoD then SeasonOfDiscovery:LoadBase*/Load*() end</code> block covering quest/NPC/item/object base data and fixes.</li>
<li><strong>TOC:</strong> Removed the commented-out <code>#Database\Corrections\SoMPhases.lua</code> line.</li>
</ul>
<hr>
<h2 id="v123">v1.2.3 — Diagnostics Refactor &amp; Monolithic DB Removal</h2>
<p><em>Moved all DB init diagnostics out of chat and into the Develop debug level. Replaced hardcoded quest-ID spot-checks with generic per-table stats. Removed the stale monolithic database folders fully superseded by the DB plugin architecture (~30 MB removed).</em></p>
<h3>DB Init Diagnostics — DEVELOP Level</h3>
<ul>
<li><strong>[QuestieInit — <code>_dbStats</code> helper]</strong> Added a local <code>_dbStats(t)</code> function returning <code>count=N minID=X maxID=Y</code> for any table. Used at every checkpoint so diagnostic output is meaningful for any server's dataset without hardcoding expansion-specific IDs.</li>
<li><strong>[QuestieInit — <code>_dbDiag</code> removed]</strong> Eliminated the <code>local _dbDiag = {}</code> accumulator and the deferred <code>C_Timer.After(6, ...)</code> print block. Diagnostics are now emitted inline as <code>Questie:Debug(Questie.DEBUG_DEVELOP, "[DBDiag] ...")</code> calls at the exact point each stage completes — visible in real time when Develop logging is enabled, not as a delayed flood 6 seconds post-load.</li>
<li><strong>[QuestieInit:LoadBaseDB]</strong> Pull result line converted to <code>DEBUG_DEVELOP</code>.</li>
<li><strong>[QuestieInit:loadFullDatabase]</strong> Four diagnostic checkpoints converted to <code>DEBUG_DEVELOP</code> with generic <code>_dbStats</code> output: After LoadBaseDB, After Corrections, Before Compile, After Compile.</li>
<li><strong>[QuestieInit:LoadDatabase — error paths]</strong> <code>loadstring</code> parse errors and <code>pcall</code> execution errors converted to <code>DEBUG_DEVELOP</code>.</li>
<li><strong>[QuestieInit.Stages[1] — cached path]</strong> <code>DB was CACHED (no recompile)</code> line converted to <code>DEBUG_DEVELOP</code>.</li>
</ul>
<h3>DB Plugin — Loader.lua Premature Count Check Removed</h3>
<ul>
<li><strong>[Questie-X-WotLKDB/Loader.lua]</strong> Removed the <code>countTable</code> / quest+npc+obj+item count block and the <code>DEBUG_CRITICAL</code> "questData is empty" warning that fired at <code>PLAYER_LOGIN</code>. This check always reported zero counts because it ran before <code>QuestieInit</code>'s loading coroutine had started. Loader.lua is now a minimal registration stub.</li>
</ul>
<h3>Monolithic Database Folders Removed</h3>
<ul>
<li><strong>[Database/Classic/]</strong> Deleted <code>classicQuestDB.lua</code> (1.0 MB), <code>classicNpcDB.lua</code> (2.0 MB), <code>classicObjectDB.lua</code> (1.0 MB), <code>classicItemDB.lua</code> (2.1 MB). Never listed in <code>Questie-X.toc</code>. Classic data is now provided exclusively by the ClassicDB plugin.</li>
<li><strong>[Database/TBC/]</strong> Deleted <code>tbcQuestDB.lua</code> (1.6 MB), <code>tbcNpcDB.lua</code> (3.5 MB), <code>tbcObjectDB.lua</code> (1.6 MB), <code>tbcItemDB.lua</code> (3.3 MB). TBC data is now provided exclusively by the TBCDB plugin.</li>
<li><strong>[Database/Wotlk/]</strong> Deleted <code>wotlkQuestDB.lua</code> (2.3 MB), <code>wotlkNpcDB.lua</code> (5.2 MB), <code>wotlkObjectDB.lua</code> (2.0 MB), <code>wotlkItemDB.lua</code> (4.4 MB). WotLK data is now provided exclusively by the WotLKDB plugin. <strong>Combined removal: ~30 MB of dead weight from the core repository.</strong></li>
</ul>
<hr>
<h2 id="v122">v1.2.2 — DB Plugin Pull Architecture &amp; Compiler Hardening</h2>
<p><em>Overhauled the DB plugin loading pipeline from a fragile push/bridge pattern to a direct pull at init time. Fixed a long-standing compiler bug that silently dropped <code>extraobjective</code> condition data. Removed all <code>DevTools_Dump</code> calls that were printing raw Lua table syntax to chat during validation.</em></p>
<h3>DB Plugin Architecture — Pull Pattern</h3>
<ul>
<li><strong>[QuestieInit:LoadBaseDB]</strong> Replaced the broken <code>QuestieX_CoreDB</code> bridge mechanism with a direct pull of DB plugin globals at init time. <code>LoadBaseDB()</code> now checks for <code>QuestieX_WotLKDB_quest</code>, <code>QuestieX_WotLKDB_npc</code>, <code>QuestieX_WotLKDB_object</code>, and <code>QuestieX_WotLKDB_item</code> globals and assigns them directly to <code>QuestieDB.*Data</code>. Globals are cleared (<code>= nil</code>) after transfer to free memory. This runs inside Questie-X's own init coroutine at a known point in the load sequence, with no dependency on cross-addon module references.</li>
<li><strong>[QuestieDB.lua]</strong> Removed <code>_G.QuestieX_CoreDB = QuestieDB</code> export. The CoreDB bridge global is no longer needed and was never reliably accessible from DB plugin Loader.lua files due to module-registry timing.</li>
</ul>
<h3>DB Plugin — Loader.lua Simplification</h3>
<ul>
<li><strong>[Questie-X-WotLKDB/Loader.lua]</strong> Stripped the entire data-transfer block (the <code>if not QuestieX_CoreDB then return end</code> guard and subsequent <code>QuestieX_CoreDB.*Data = ...</code> assignments). The file is now a <code>PLAYER_LOGIN</code> handler that registers the plugin with <code>QuestiePluginAPI</code>, logs absorbed table sizes, and emits a <code>DEBUG_CRITICAL</code> warning if <code>questData</code> is still empty after init.</li>
</ul>
<h3>Compiler — extraobjectives Conditions Field</h3>
<ul>
<li><strong>[compiler.lua — writer]</strong> Added serialization of <code>data[6]</code> (conditions table). After writing the 5 existing fields per entry, the writer now writes a <code>WriteByte(n)</code> count followed by <code>WriteShortString(key)</code> + <code>WriteInt24(value)</code> for each condition entry. If <code>data[6]</code> is nil, writes <code>0</code>.</li>
<li><strong>[compiler.lua — reader]</strong> Added deserialization of the conditions field. Reads a <code>ReadByte()</code> condition count; if non-zero, reconstructs the conditions table as <code>{[key]=value}</code> and assigns it to <code>entry[6]</code>.</li>
<li><strong>[compiler.lua — skipper]</strong> Updated to skip condition bytes: reads condition count, then skips a <code>ShortString</code> + <code>Int24</code> per condition.</li>
<li><strong>[Root cause]</strong> <code>QuestieDB.lua:1443</code> reads <code>HideCondition = o[6]</code> from each extraobjective. Before this fix, compiled data always produced <code>o[6] = nil</code>, so <code>ShouldHideObjective()</code> never activated. The <code>hideIfQuestActive</code> / <code>hideIfQuestComplete</code> conditions were silently ignored post-compilation. Validation also flagged the mismatch every load, which triggered <code>DevTools_Dump</code> to flood chat with raw Lua table syntax.</li>
</ul>
<h3>Compiler — DevTools_Dump Removal</h3>
<ul>
<li><strong>[compiler.lua — ValidateNPCs / ValidateObjects / ValidateItems / ValidateQuests]</strong> Removed all four <code>DevTools_Dump({["Compiled Table:"]=a, ["Base Table:"]=b})</code> calls from table-mismatch branches. These calls serialized full Lua tables to the chat frame as raw source code, appearing to users as a syntax error or data corruption. The preceding <code>Questie:Warning(...)</code> line already captures the mismatch identity. <code>DevTools_Dump</code> is a retail WoW debugging API unavailable or unreliable on private/custom servers and should never be called in production validation paths.</li>
</ul>
<hr>
<h2 id="v117">v1.1.7 v1.2.1 — DB Loading Diagnostics &amp; Fallback Tracker</h2>
<p><em>Ongoing stability pass. Hardened the entire database loading pipeline, wired up the live quest-log fallback for quests missing from the DB, and fixed a chain of nil-guard crashes across corrections, map, and tracker modules.</em></p>
<h3>Database Loading</h3>
<ul>
<li><strong>[LoadDatabase]</strong> Replaced bare <code>loadstring()</code> / <code>fn()</code> calls with proper error capture (<code>local fn, err = loadstring(...)</code> + <code>pcall(fn)</code>). Errors now print to chat with the failing key and string length instead of silently falling back to <code>{}</code>.</li>
<li><strong>[Compiler]</strong> Fixed <code>hasData</code> guard in <code>QuestieDBCompiler:Compile()</code> — now accepts <code>type == "table"</code> in addition to <code>"string"</code>, preventing the compiler from silently aborting when <code>LoadDatabase</code> has already decoded the string to a table before compilation.</li>
<li><strong>[DB Architecture]</strong> Identified and documented that loading multiple DB plugins simultaneously (e.g. WotLKDB + ClassicDB) causes <code>questData</code> overwrites. On WotLK servers only <code>Questie-X-WotLKDB</code> should be enabled alongside the server-specific plugin.</li>
</ul>
<h3>Live Fallback for Missing Quests</h3>
<ul>
<li><strong>[QuestieDB.GetQuest]</strong> When <code>rawdata</code> is nil, a minimal quest object is now built from <code>QuestLogCache</code> instead of returning nil. The fallback populates <code>name</code>, <code>level</code>, <code>isComplete</code>, and an empty <code>Objectives</code> table with <code>_isLogFallback = true</code>.</li>
<li><strong>[QuestieQuest.PopulateQuestLogInfo]</strong> Fallback quests now seed their <code>Objectives</code> table from <code>QuestLogCache.GetQuestObjectives</code> on first call, then call <code>obj:Update()</code> on each to refresh progress from the live quest log.</li>
<li><strong>[QuestieQuest.UpdateObjectiveNotes]</strong> Fallback quests now early-return to skip the static DB spawn-list path (<code>objectiveSpawnListCallTable</code>), preventing "Corrupted objective data" errors caused by nil NPC IDs.</li>
</ul>
<h3>Crash Fixes</h3>
<ul>
<li><strong>[QuestieLib.GetQuestString]</strong> Guard against nil <code>name</code> — returns quest ID string as fallback.</li>
<li><strong>[QuestieDB spawn loop]</strong> Nil-guard on <code>objectData[id]</code> before clearing spawn keys in prune loop (<code>attempt to index nil</code> at QuestieDB:1715).</li>
<li><strong>[QuestieDB.GetSpawnList]</strong> Wrapped <code>objectiveSpawnListCallTable</code> result in nil-guard before iterating — prevents <code>pairs(nil)</code> when a referenced NPC/object is missing from the loaded DB.</li>
<li><strong>[QuestieQuestPrivates killcredit]</strong> <code>monster(killCreditNpcId)</code> result nil-guarded before indexing — prevents crash for kill-credit NPCs absent from <code>npcData</code>.</li>
<li><strong>[Townsfolk]</strong> <code>flags</code> nil-guard added before <code>bitband(flags, VENDOR)</code> call.</li>
<li><strong>[QuestieQuest.UpdateObjectiveNotes]</strong> <code>quest.SpecialObjectives</code> nil-guard before <code>next()</code> call.</li>
<li><strong>[QuestieQuest.PopulateQuestLogInfo]</strong> <code>quest.SpecialObjectives</code> nil-guard before <code>next()</code> call.</li>
</ul>
<hr>
<h2 id="v116">v1.1.6 — Minimap Icon &amp; P2 Stability</h2>
<p><em>Minimap button overhaul and second pass of P2 bug fixes.</em></p>
<h3>Minimap</h3>
<ul>
<li><strong>[MinimapIcon]</strong> Replaced default minimap icon with custom <code>mmapIcon.tga</code>. Applied <code>SetMask</code> for circular clip on WotLK+ clients with <code>SetTexCoord</code> fallback for 1.12 vanilla clients.</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>[QuestieServer]</strong> Restored plugin status UI, <code>C_QuestLog</code>/<code>C_Map</code> shims, and <code>QuestieServer</code> init sequence.</li>
<li><strong>[QuestieLoader]</strong> Corrected <code>select()</code> polyfill to not use <code>arg</code> table.</li>
<li><strong>[TOC]</strong> Added XXH load to TBC toc and guarded <code>plugin.stats</code> nil access.</li>
</ul>
<hr>
<h2 id="v115">v1.1.5 — QuestieLearner Expansion &amp; Database Options</h2>
<p><em>Major expansion of the data-learning system and a new Database options tab.</em></p>
<h3>QuestieLearner</h3>
<ul>
<li><strong>[QuestieLearner]</strong> Expanded hook coverage: quest accept, objective kill, object interaction, and item loot all feed learned data back to the appropriate DB table.</li>
<li><strong>[QuestieLearnerComms]</strong> Broadcast/receive learned entries to nearby Questie-X users via addon messages.</li>
<li><strong>[Custom Server Detection]</strong> Learned data for unrecognised quest IDs is stored under a per-realm key in <code>QuestieLearnerDB</code> to separate retail/private/custom content.</li>
<li><strong>[DEVELOP logging]</strong> Debug messages emitted on every successful learn and every failed-to-learn event.</li>
</ul>
<h3>Options — Database Tab</h3>
<ul>
<li><strong>[QuestieOptionsDatabase]</strong> New "Database" tab with Import / Export (LibDeflate base64 encoded strings) and Cleanup (prune stale learned entries) functionality.</li>
</ul>
<hr>
<h2 id="v114">v1.1.4 — Questie-X: Plugin Architecture & Maintenance Update</h2>
<p><em>This release marks the official rebranding from <strong>Questie-335</strong> / <strong>PE-Questie</strong> to <strong>Questie-X</strong> and introduces the new plugin architecture. Additionally, this version includes significant UI enhancements, core compatibility refinements for legacy clients, and critical database corrections.</em></p>
<h3>Architecture Changes</h3>
<ul>
<li><strong>[Repo]</strong> Repository renamed and re-homed to <code>Xurkon/Questie-X</code>. Remote updated from <code>PE-Questie</code> to <code>Questie-X</code>.</li>
<li><strong>[Plugin API]</strong> Introduced <code>QuestiePluginAPI</code> (<code>Modules/Libs/QuestiePluginAPI.lua</code>). Plugins register themselves and inject quest, NPC, object, item, and zone data without modifying core files.</li>
<li><strong>[Server Detection]</strong> Added <code>QuestieServer</code> module (<code>Modules/QuestieServer.lua</code>) for improved runtime server environment detection.</li>
<li><strong>[Network]</strong> Added <code>QuestieLearnerComms</code> module (<code>Modules/Network/QuestieLearnerComms.lua</code>) for cross-client quest data sharing.</li>
<li><strong>[Database]</strong> Removed embedded <code>Database/Ascension/</code> and <code>Database/Ebonhold/</code> folders. All custom server data is now distributed via separate plugin addons.</li>
</ul>
<h3>UI Enhancements</h3>
<ul>
<li><strong>[Options]</strong> Resizable Options window! The Questie options UI can now be resized with corner drag functionality. Size and position persist between sessions.</li>
<li><strong>[Options]</strong> New <strong>Credits Tab</strong>! A dedicated tab in the options menu to acknowledge contributors and community partners.</li>
<li><strong>[Tutorial]</strong> Improved tutorial flows for objective type selection.</li>
</ul>
<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.</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>
</ul>
<h3>Map & Tooltips</h3>
<ul>
<li><strong>[Tooltips]</strong> NPC names and objective text now populate reliably on map pins and world unit tooltips.</li>
<li><strong>[Tooltips]</strong> Resolved <code>[QuestieTooltips:GetTooltip] m_20509</code> debug log spam.</li>
</ul>
<h3>Quest Data</h3>
<ul>
<li><strong>[Database]</strong> Scraped and injected missing spawn coordinates for Bonechewer Mutant, Raider, Evoker, and Scavenger (NPC IDs 16876, 16925, 19701, 18952) from Wowhead.</li>
<li><strong>[Quest 10482]</strong> Correctly mapped Bonechewer NPCs to quest objectives in both WotLK and TBC database correction files.</li>
</ul>
<h3>New Plugins</h3>
<ul>
<li><strong>Questie-Ascension</strong> &mdash; Project Ascension server database.</li>
<li><strong>Questie-Ebonhold</strong> &mdash; Ebonhold server database.</li>
</ul>
<h3>TOC / Addon Identity</h3>
<ul>
<li><strong>[TOC]</strong> Core addon <code>.toc</code> files updated to <code>Questie-X</code> title and <code>v1.1.4</code> version.</li>
<li><strong>[Libs]</strong> Added <code>LibDeflate</code>, <code>XXH_Lua_Lib</code>, <code>LibDBIcon-1.0</code>, and <code>LibDataBroker-1.1</code> to the Libs directory.</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>[QuestieDB]</strong> Overhauled <code>QuestieDB.IsComplete</code> to accurately verify all objectives are finished using <code>numFulfilled == numRequired</code> instead of the unreliable server-side <code>finished</code> flag.</li>
<li><strong>[QuestieQuest]</strong> Implemented <code>HideCondition</code> mechanism for objectives, allowing specific spawns to be hidden based on quest log status (<code>hideIfQuestActive</code> / <code>hideIfQuestComplete</code>).</li>
<li><strong>[Cache]</strong> Demoted Cache Validation "0/15 Error" to Debug level to reduce user confusion during login.</li>
</ul>
<hr>
<h2 id="v992">v9.9.2 — Final Pre-Refactor Release</h2>
<p><em>⚠️ This is the final stable release before a major architectural refactoring. All active features and quest data from previous releases are preserved.</em></p>
<h3>Fixes</h3>
<ul>
<li><strong>[Map]</strong> Resolved an issue where NPC names and objective text were inconsistently missing from map tooltips.</li>
</ul>
<h3>New Ebonhold Quests</h3>
<ul>
<li><strong>Western Plaguelands Trophy</strong> (ID 50187) - Kill 1 Rare in Western Plaguelands.</li>
</ul>
<hr>
<h2 id="v991">v9.9.1</h2>
<h3>New Ebonhold Quests</h3>
<ul>
<li><strong>Brood of the Black Flight</strong> (ID 50057) - Kill 30 Dragonkin in Burning Steppes.</li>
</ul>
<hr>
<h2 id="v990">v9.9.0</h2>
<h3>New Ebonhold Quests</h3>
<ul>
<li><strong>Azeroth</strong>: Southern Jungle (Complete 6 quests in Stranglethorn Vale).</li>
<li><strong>Kalimdor</strong>: Sandstone Giants (Kill Sandstone Giants in Tanaris). Felwood Restoration
(Complete 6 quests in Felwood).</li>
</ul>
<h3>Fixes</h3>
<ul>
<li><strong>[Quest]</strong> Fixed QuestieDB initialization error caused by missing table depth for custom
creature objectives in EbonholdQuestDB.</li>
<li><strong>[System]</strong> Adjusted Questie:Error output logic so that critical addon-breaking errors
always print regardless of the Enable Debug-PRINT setting.</li>
<li><strong>[Map]</strong> Fixed missing map pins for the "Sandstone Giants" quest by switching from
<code>creatureObjective</code> to <code>killCreditObjective</code>, allowing Questie to correctly
resolve Dune Smasher spawn locations from the NPC database.
</li>
<li><strong>[Tracking]</strong> Fixed collection quest counters showing stale counts when the Ebonhold scav
bot loots items — implemented a 3-stage <code>BAG_UPDATE_DELAYED</code> strategy: immediate scan, 0.3s
debounce scan, and a 2s follow-up scan to allow the server's quest-objective cache to flush batch loot
counts.</li>
</ul>
<h2 id="v975">v9.7.5</h2>
<h3>Fixes</h3>
<ul>
<li><strong>[Quest]</strong> Fixed an issue where re-accepted quests (e.g., repeatable custom quests) would
not show objective pins/icons on the map.</li>
</ul>
<h3>New Quests</h3>
<ul>
<li><strong>[Database]</strong> Added <strong>Storm Peak Orders</strong> (ID 50150) - <em>The Storm
Peaks</em></li>
<ul>
<li>Objective: Complete any 6 quests in The Storm Peaks.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Wild Basin</strong> (ID 50094) - <em>Sholazar Basin</em></li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 29 Beast NPC types with full spawn coordinates.</li>
</ul>
<ul>
<li>NPCs: King Krush, Shardhorn Rhino, Aotona, Pitch, Serfex the Reaver, Dreadsaber, Hardknuckle
Matriarch, Shango, Venomtip, Bushwhacker, Hardknuckle Charger, Ravenous Mangal Crocolisk, Farunn,
Zeptek the Destroyer, Goretalon Matriarch, Sapphire Hive Wasp, Emperor Cobra, Sapphire Hive Drone,
Shattertusk Bull, Siltslither Eel, Spirit of Atha, Stranded Thresher, Mangal Crocolisk, Spirit of
Koosu, Longneck Grazer, Goretalon Roc, Sapphire Hive Queen, Spirit of Ha-Khalan, Bittertide Hydra
</li>
</ul>
</ul>
<h2 id="v974">v9.7.4</h2>
<h3>Fixes</h3>
<ul>
<li><strong>[Tooltips]</strong> Fixed "attempt to concatenate local 'name' (a nil value)" error when quest
starters/finishers have missing names in the database.</li>
<li><strong>[Database]</strong> Added missing spawn coordinates for Quest 50031 "Stormbound" elementals in
Storm Peaks (zone 67).</li>
<li><strong>[Database]</strong> Fixed "Unknown Zone" issue for custom quests by correcting Zone ID index
usage (swapped `[6]` RequiredRaces for `[17]` ZoneID).</li>
<li><strong>[Database]</strong> Corrected Dragonblight Zone ID in custom quest definitions.</li>
</ul>
<h3>New Quests</h3>
<ul>
<li><strong>[Database]</strong> Added <strong>Morogh Missions</strong> (ID 50098) - <em>Dun Morogh</em></li>
<ul>
<li>Objective: Complete any 6 quests in Dun Morogh. Auto-completes upon reaching the objective.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Azuremyst Aid</strong> (ID 50100) - <em>Azuremyst Isle</em>
</li>
<ul>
<li>Objective: Complete any 6 quests in Azuremyst Isle. Auto-completes upon reaching the objective.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Stormforged Scales</strong> (ID 50066) - <em>The Storm
Peaks</em></li>
<ul>
<li>Objective: Kill 30 Dragonkin. Includes 8 Dragonkin NPC types with full spawn coordinates.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Peak Predators</strong> (ID 50095) - <em>The Storm Peaks</em>
</li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 24 Beast NPC types with full spawn coordinates.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Peak Predators</strong> (ID 50096) - <em>Icecrown</em></li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 12 Beast NPC types with full spawn coordinates.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Icecrown Advance</strong> (ID 50151) - <em>Icecrown</em></li>
<ul>
<li>Objective: Complete any 6 quests in Icecrown. Auto-completes upon reaching the objective.</li>
</ul>
<li><strong>[Database]</strong> Added <strong>Storm Peaks Trophy</strong> (ID 50205) - <em>The Storm
Peaks</em></li>
<ul>
<li>Objective: Kill 1 Rare in The Storm Peaks. Includes 4 Rare NPC types (Skoll, Time-Lost Proto-Drake,
Vyragosa, Dirkee) with full spawn coordinates.</li>
</ul>
</ul>
<h2 id="v973">v9.7.3</h2>
<h3>New Features</h3>
<ul>
<li><strong>[Database]</strong> Implemented <strong>Ebonhold Database Module</strong>.</li>
<ul>
<li>Created dedicated `Database/Ebonhold/` structure for custom server data.</li>
</ul>
<ul>
<li>Added `EbonholdLoader` to inject custom Quests, NPCs, Objects, and Items as overrides.</li>
</ul>
<ul>
<li>**Note:** This structure preserves custom data during upstream Questie updates.</li>
</ul>
<li><strong>[Objectives]</strong> Implemented <strong>Automated Text Retrieval</strong>.</li>
<ul>
<li>Questie now attempts to fetch quest text from the server at runtime for custom quests that are
missing from the database.</li>
</ul>
<ul>
<li>Added "Objectives Board" (ID 600600) as a global quest starter.</li>
</ul>
</ul>
<h3>Quests (Custom Content)</h3>
<ul>
<li><strong>[New]</strong> Added <strong>Heart of the Dragonflights</strong> (ID 50064) -
<em>Dragonblight</em>
</li>
<ul>
<li>Objective: Kill 30 Dragonkin. Includes 49 Dragonkin NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Skies of Blade's Edge</strong> (ID 50060) - <em>Blade's Edge
Mountains</em></li>
<ul>
<li>Objective: Kill 75 Dragonkin. Includes 17 Dragonkin NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Shadowed Beasts</strong> (ID 50087) - <em>Shadowmoon Valley</em>
</li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 25 Beast NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Forest Stalkers</strong> (ID 50083) - <em>Terokkar Forest</em></li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 53 Beast NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Savage Heights</strong> (ID 50085) - <em>Blade's Edge
Mountains</em></li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 47 Beast NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Unstable Fauna</strong> (ID 50086) - <em>Netherstorm</em></li>
<ul>
<li>Objective: Kill 75 Beasts. Includes 18 Beast NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Elemental Balance</strong> (ID 50026) - <em>Nagrand</em></li>
<ul>
<li>Objective: Kill 30 Elementals. Includes 12 Elemental NPC types.</li>
</ul>
<li><strong>[New]</strong> Added <strong>Redridge Trophy</strong> (ID 50160) and <strong>Zangarmarsh
Trophy</strong> (ID 50192).</li>
</ul>
<h3>New Ascension Quests</h3>
<ul>
<li><strong>Westfall</strong>: Agria's Medicine, Seven Years of Bad Luck, Worm-Eaten Apple, Goldshire's
Generosity, Bookworm, Knowledge Corrupts, The Ruins of Northshire, Accursed Sisterhood, Words That
Shepherd Madness, Oracular Idol, A Betrayal Within, The Maid I Left Behind, The Saddest Among Us, The
Threat Swept Downstream, Stay a While, Defias Disruption.</li>
<li><strong>Dun Morogh</strong>: A Small Mistake, We Found Her!, The Scout's Favor, Old Mirsinth, Smoke on
the Wind, A Promising Path, A Fitting Disguise, His Radiant Majesty, Deciphering Radiation, Soaking the
Masses, Sever the Right Hand, A Growing Business, Thunderbrew's Hop, Stay a While, Live-Fire Demo, Bots
on Strike, A Brother's Betrayal, The True Story, Timber for the Coldhewn, Icehide the Unbroken.</li>
<li><strong>Teldrassil</strong>: The Carrion Road, The Sister Who Never Returned, Finding the Good Meat,
Transsubstantiating the Flesh, Communion Banquet, A Trail of Petals, Restless entrails, A Dark Warning,
The Aid of Theren-Dion, No Place for Scavengers, Termites in Teldrassil, Stay a While, Elydna's
Heirloom.</li>
<li><strong>Durotar</strong>: To Find a Cure, A Dangerous Sample, Knowledge of the Centaurs, Those Who Fell,
The Way is Shut, The Sinister Triad, So That He May Hear Again, A Sinister Ritual, Innocents for
Sinners, Unease Makes Tongues Wag, A Door Left Ajar, Esgramor's Master, Shinies!, Echoes of Hirsutta,
Auction the Past, Stay a While, Durotar's Dire Drought, The Queen's Decree, Avianna's Rose, The Last
Piece, Reversion.</li>
<li><strong>Tirisfal Glades</strong>: Rude Awakening, Marla's Last Wish, Monsters With Noble Intentions,
Restless Family Members, An Unspeakable Secret, A Noble Heritage, The True Heir of the Cains, The
Friends We Make Along the Way, I'm Home, Apothecary Flemer, The Nature of Freedom, Spotless Standing,
Stay a While, More Than the Sum of its Parts, Scarlet Correspondence, A Humble Duty, The Balnirs' Rest,
Brewing Disarray, This Is Justice.</li>
<li><strong>Mulgore</strong>: Death and Tribute, Death and Exile, Death and Dishonor, Death and Justice,
Death by Laughter, To Whom I Devote, Fighting Over Carrion, Smoke on the Horizon, Amphora of Sacred
Water, Stay a While, Exile of Embers, The Smoke that Remembers, The Circles Rite.</li>
</ul>
<h3>New Ebonhold Quests</h3>
<ul>
<li><strong>Outland</strong>: Elemental Balance, Savage Heights, Unstable Fauna, Forest Stalkers, Marsh
Predators, Skies of Blade's Edge, Shadowed Beasts, Zangarmarsh Trophy.</li>
<li><strong>Northrend</strong>: Tundra Turbulence, Stormbound, Dragonblight Trophy, Heart of the
Dragonflights, Peak Predators, Icecrown Advance, Storm Peaks Trophy, Stormforged Scales, Fjord Front,
Wild Basin, Grizzly Trophy, Zul'Drak Trophy, Basin Expeditions.</li>
<li><strong>Azeroth</strong>: Redridge Trophy, Morogh Missions, Azuremyst Aid, Shadow of Teldrassil, Trials
of Durotar, Song of the Woods, Morogh Trophy, Tirisfal Trophy.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li><strong>[Tracker]</strong> <strong>Combat Update Fix</strong>: Tracker now updates objectives
immediately during combat without causing Lua errors or taint.</li>
<li><strong>[Tracker]</strong> <strong>Bag Update Fix</strong>: Quest progress now updates immediately when
looting items (fixes delay with loot bots).</li>
<li><strong>[Arrow]</strong> <strong>Refined Visibility Logic</strong>:</li>
<ul>
<li>**Auto Nearby**: Arrow correctly defaults to showing the nearest quest when no quests are tracked.
</li>
</ul>
<ul>
<li>**Zone Filter**: In "Auto Mode", the arrow hides if the nearest quest is in a different zone.</li>
</ul>
<ul>
<li>**Instance Filter**: Arrow explicitly hides if the target is in a different instance.</li>
</ul>
<li><strong>[Map]</strong> Fixed an issue where completed quest icons would persist on the map
(`RequestMapUpdate` logic).</li>
<li><strong>[Database]</strong> Updated `wotlkNpcDB.lua` with scraped spawn data for 12 key beast NPCs in
Terokkar Forest to ensure accuracy.</li>
<li><strong>[Arrow]</strong> Fixed a nil function error for `_CollectObjective` when processing incomplete
quests.</li>
<li><strong>[Arrow]</strong> Fixed syntax issues that prevented `QuestieArrow` module from initializing
correctly.</li>
<li><strong>[Database]</strong> Fixed a runtime crash in `ZoneDB` when encountering maps with no AreaId
mapping (e.g., Kalimdor).</li>
</ul>
</div>
<footer>
<p>Maintained by Xurkon</p>
<div style="margin-top: 15px; display: flex; justify-content: center; gap: 10px;">
<a href="https://www.patreon.com/Xurkon" target="_blank">
<img src="https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white" alt="Patreon" height="28">
</a>
<a href="https://www.paypal.me/Xurkon" target="_blank">
<img src="https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white" alt="PayPal" height="28">
</a>
</div>
</footer>
</body>
</html>