v1.6.3-unreleased: Sunstrider zone fixes, QuestieLearner icon preservation, migration, and Busted test suite

Code changes (13 files, +916/-171):
- ZoneDB integration: GetZoneId() converts uiMapId→areaId via ZoneDB reverse lookup
- MIN_CONFIDENCE_PINS reduced to 1 for Ascension (incomplete NPC DBs)
- QuestieLearner icon preservation: objective Icon passed to RegisterObjectiveTooltip
- InjectLearnedData zone migration: converts old uiMapId spawn keys to areaId
- areaId passed through all LearnNPC call sites (OnMouseoverUnit, OnQuestDetail,
  OnQuestComplete, OnQuestAccepted, OnQuestTurnedIn, OnGossipShow)
- Compat/Compat.lua: C_Map.GetPlayerMapPosition UiMapData support
- Compat/HBD.lua: Sunstrider mapData aliases and fallback loading
- Sunstrider arrow fix (QuestieArrow.lua), resolved pin rendering (QuestieMap.lua)
- _MergeOverride helper for string/numeric key compatibility
- Northshire Valley UiMapData registration

Testing infrastructure:
- .busted config pointing to tests/ directory
- Tests/wow_api_mock.lua: WoW API mocks (ZoneDB, C_Map, QuestLogCache, etc.)
- Tests/QuestieLearner_spec.lua: 12 tests covering coordinate scaling, spell cast
  learning, zone migration (NPC/objects), icon preservation, settings defaults,
  and LearnNPC spawn zone tracking
- selene.toml + wow_classic.yml: linter configuration

Documentation:
- Makefile with test/lint/ci targets
- sunstrider-coordinate-collection.md: coordinate data reference
- sunstrider-pin-fix.md: root cause analysis and fix documentation
This commit is contained in:
Xurkon
2026-05-16 07:32:47 -05:00
parent 306fb2ac89
commit bf6ecaedbe
22 changed files with 2218 additions and 179 deletions
+33 -12
View File
@@ -169,32 +169,53 @@
<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.1</code>
<code>Version: v1.6.2 + Unreleased</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">[Unreleased] &mdash; Sunstrider Isle Arrow, Tooltip Guard, QuestData String Safety</h2>
<h2 id="unreleased">[Unreleased] &mdash; Sunstrider Isle Arrow Distance, Map Pins, Tooltip Schema Fixes, QuestData String Safety</h2>
<ul>
<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, return the name unmodified.
</li>
<li><strong>[Fix &mdash; Tooltip NPC/Object Type Guard]</strong> Resolved a crash in <code>QuestieTooltips</code> when hovering over NPC or object tooltip keys (<code>m_&lt;id&gt;</code>, <code>o_&lt;id&gt;</code>) where <code>learnedNpc[10]</code> or <code>learnedObj[10]</code> was unexpectedly a string instead of a table.
<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>Root Cause</strong>: <code>InsertMissingQuestIds</code> in the WotLKDB corrections files writes directly to <code>QuestieDB.questData[questId]</code> but <code>questData</code> is stored as a loadable Lua string on Ascension. When code later tried to index into that string as a table, it threw <code>attempt to index field 'questData' (a string value)</code>.</li>
<li><strong>Fix</strong>: Added <code>if type(objList) ~= "table" then break end</code> guard in both <code>m_/NPC</code> and <code>o_/object</code> iteration paths in <code>Tooltip.lua</code> before iterating <code>learnedNpc[10]</code> / <code>learnedObj[10]</code>.</li>
<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 / Zone Override]</strong> Resolved the quest arrow not appearing on Sunstrider Isle (Ascension's starting zone) when the world map is closed.
<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</strong>: <code>C_Map.GetBestMapForUnit("player")</code> returns <code>946</code> (ghost/loading map uiMapId) instead of <code>1241</code> (Sunstrider Isle's real uiMapId) when the world map is closed. <code>ZoneDB:GetAreaIdByUiMapId(946)</code> had no override, causing <code>GetCurrentZoneId()</code> to return <code>946</code> instead of <code>3430</code> (Sunstrider Isle's areaId). This broke target zone filtering in <code>_CollectObjective</code> and caused <code>HBD:GetWorldCoordinatesFromZone</code> to return <code>0,0</code> (no world coord data for map 946).</li>
<li><strong>Fix &mdash; zoneDB.lua</strong>: Added <code>[946] = 3430</code> and <code>[1241] = 3430</code> to <code>UiMapIdOverrides</code> so <code>GetAreaIdByUiMapId</code> always resolves to the real Sunstrider Isle areaId regardless of which ghost or real uiMapId the game returns.</li>
<li><strong>Fix &mdash; QuestieArrow.lua</strong>: Updated <code>UpdateNearestTargets</code> fallback chain to use <code>QuestiePlayer:GetCurrentUiMapId()</code> for player position. When that returns an invalid/ghost map (946/947/0), it falls back to a <code>ZoneDB</code> lookup via the actual zoneId. Ensures the arrow gets real world coordinates via <code>C_Map.GetPlayerMapPosition</code> + <code>HBD:GetWorldCoordinatesFromZone</code> regardless of map open/closed state.</li>
<li><strong>Debug Output</strong>: Added per-frame debug output (respecting <code>debugArrow</code> profile setting) showing <code>frameShown</code>, <code>target.title</code>, player coordinates, and uiMapId values.</li>
<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</code>) <em>before</em> calling <code>HBD:GetPlayerWorldPosition()</code> to force the C_Map fallback path. 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>: Added <code>_ResolveMapUiMapId()</code> helper (1241&rarr;1941) applied across <code>FadeLogic</code>, <code>FindClosestStarter</code>, <code>GetNearestSpawn</code>, and <code>GetNearestQuestSpawn</code>. <code>DrawWorldIcon</code> and <code>DrawManualIcon</code> now also store and render Sunstrider map icons against the resolved parent map coordinate space.</li>
</ul>
</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 unreleased 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> The Unreleased section reflects the current working tree, including documentation for local in-progress fixes that are not yet part of a pushed release.</li>
</ul>
<hr>