- Add CopySpawnTable and MergeSpawnTables helpers to deep-merge spawn
coordinate lists (zoneId -> {{x,y}, ...}) instead of replacing them
wholesale. Previously an override containing spawns would clobber the
base list, reducing visible pins for any NPC with partial overrides
(Sunstrider Isle calibration, Ascension area-specific fixes, etc.).
- _MergeOverride now special-cases the 'spawns' key to call the merger
with the base spawn table as a fallback when override is empty.
- GetSuppressedNPCs/Objects now check ld.settings.enabled alongside
prioritizeMyData. When the learner is fully disabled, the suppression
pass is skipped entirely (additive and AND-only).
- Use 3.3.5a-native Minimap:GetViewRadius() (instance method) instead of
C_Minimap.GetViewRadius (nil on 3.3.5a) with C_Minimap fallback. Returns
116.67 at zoom 5 instead of the hardcoded 125 from the broken lookup table.
- Replace minimapWidth = (GetWidth()/2) * (mapRadius/155.52) with
minimapWidth = GetWidth() * GetScale() / 2. The new formula yields the
actual half-width of the visible minimap in screen pixels, the correct
multiplier for diffX*minimapWidth when diffX is normalized by mapRadius.
- Comment out (do not delete) all QDMATH / UPDATE entering / ICON debug
blocks as --[[ DEBUG: ... --]] for future regression investigation.
- Remove loaded debug scripts from Questie-X.toc and add to .gitignore
(drift_test, radius_debug, spawn_calibration, etc.). Scripts preserved
in tests/ for reference.
- Add .gitignore entries for tests/ lowercase variant (case-sensitive
on Linux/macOS) and additional debug script patterns.
- Update CHANGELOG.md and docs/changelog.html with fix entry.
- Refresh handoff.md to reflect RESOLVED status and iteration 4 history.
Verified: pins now stay anchored to world positions across all zoom levels
on both Stock UI and ElvUI. Per-frame diffX ~0.35 yards during walking
translates to ~0.21 pixels of pin movement (correct for 1 yard of world
movement).
Previously DrawWorldIcon and DrawManualIcon both set floatOnEdge=false
for uiMapId 1241, forcing icons to always show regardless of distance.
This caused pins to render at minimap center when near a spawn, making
them appear to orbit/follow the player as they rotated.
The zone->world coordinate pipeline was verified correct: zoneW matches
dataW exactly for 1241 icons, and playerW is in the same coordinate
space. The problem was purely the floatOnEdge=false behavior combined
with the icon being essentially at the minimap center (1% of radius).
Enabling floatOnEdge=true lets the standard edge-floating path handle
1241 pins like every other zone: icons clamp to the edge when far,
slide along the rim when approaching, and occupy their correct angular
bearing from the player rather than sitting at dead center.
The uiMapID==1241 branch in drawMinimapPin was an experimental overlay
that ran BEFORE the standard minimap projection and rotation transforms.
It duplicated the projection math while bypassing the rotation matrix,
used a different edge-clamp path, and is the primary suspect for why
pins visually follow the player on Sunstrider Isle.
The standard minimap path (handle rotation -> adapt delta to mapRadius
-> apply shape mask -> float-on-edge or clamp) already handles all
pins uniformly regardless of zone. Removing this experimental branch
lets 1241 pins use the same proven path as every other zone.
Also cleaned up:
- stale lastInstanceId reference in throttled PIN debug trace
- unnecessary data.uiMapID guard in QuestieDebugMinimapPin gate
- Sunstrider-specific minimap debug block in UpdateMinimapPins
- /qdrift: one-shot diagnostic that traces the full player-position chain
(GetCurrentPlayerPosition -> GetWorldCoordinatesFromZone), checks
mapIdToUiMapId entries, shows pin world coords vs player world coords,
and verifies zone center bounds
- Throttled [QD] PIN debug in drawMinimapPin (1/sec) showing pinWorld,
playerWorld, distance, mapRadius, and diff values
- Enhanced [QD] CONV/CONV2 debug in GetCurrentPlayerPosition showing
raw coords, world coords after conversion, and zone coords after
back-conversion
Purpose: diagnose why minimap pins follow the player after the areaId fix
Root cause of minimap pin drift on Sunstrider Isle: on Ascension,
GetCurrentMapAreaID() returns 3430 for Eversong, but mapIdToUiMapId only
had the WotLK mapping (463→1941). This meant actualUiMapId was nil in
GetCurrentPlayerPosition(), causing the parent→child zone conversion to
be skipped entirely. Eversong-zone coords passed through tagged as
uiMapID 1241, producing wrong world positions and pin drift.
Fix: add Ascension areaId→uiMapId mappings (3430→1941, 3431→1241) to
mapIdToUiMapId after UiMapData init. Now the conversion path triggers
correctly: Eversong coords → world coords (via 1941 bounds) →
Sunstrider zone coords (via 1241 bounds).
One-line fix in Compat.lua. No changes to HBD.lua or projection logic.
Error: attempt to index field '?' (a number value) at line 1932.
waypoints[1] was a bare number instead of a coordinate pair {x,y}.
Added type(waypoints[1]) ~= 'table' guard alongside existing type(waypoints)
check. Also simplified firstWaypoint access since the guard ensures it's a table.
The underlying data issue (flat numbers vs coordinate pairs) is in the DB
and should be tracked separately — this fix prevents the crash only.
Adds _ComputeZoneSpaceDiff to HBD.lua that computes minimap pin
projection in the parent zone's coordinate space (Eversong 1941) instead
of using the world-coordinate delta. On Ascension, GetPlayerMapPosition
returns Eversong-relative coords for players on Sunstrider, but
GetWorldCoordinatesFromZone(1241) treats them as Sunstrider-relative,
producing a wrong player world position and causing pins to drift or
move opposite to the player.
Fix: (1) Store original zone coords on minimap pins at registration
(AddMinimapIconMap). (2) In drawMinimapPin, recover player's actual
Eversong zone coords by reversing the 1241-bounds conversion. (3)
Convert pin's 1241 zone coord to Eversong zone coord via 1241-world.
(4) Delta in Eversong zone-space, scale to pixels using parent-width/mapRadius.
Falls back to existing world-space math for pins without zone coords.
- Fix: isSunstrider block in _MergeSpawnEvidence now checks IsAscensionProtected
before writing learner data to npcDataOverrides. Without this guard, each Mana
Wyrm kill overwrote AscensionDB's z1241=5 data with learner z3431 coords.
- Fix: Clustering disabled for zone 1241 (Sunstrider Isle) in _DrawObjectiveIcons
so all 5 AscensionDB spawn pins display individually instead of collapsing to 2.
- Fix: Ghost pin loop in AvailableQuests.lua -- 'while frames[i]' was iterating a
string-keyed table with a numeric index (never iterated). Changed to pairs().
- Fix: NormalizeSpawnZoneKey now uses ZoneDB.areaIdToUiMapId for all zones so
learner evidence is stored under map IDs (e.g. 1241) not area IDs (e.g. 3431),
matching AscensionDB's key space. Applied in LearnNPC and _StoreGuidSpawnEvidence.
- Fix: isSunstrider detection in _MergeSpawnEvidence updated from hardcoded
zoneId==3431 check to IsSunstriderNativeZone() since zone IDs are now normalized
to map IDs at storage time.
- Phase 2: marked ⚠️ with known spec test failures (quarantined)
- Phase 3-5: marked ⚠️ as implemented, not in-game verified
- Removed ✅ COMPLETE claims that overstate test coverage
- PrintTargetCoords: add world coords computed from zone coord + uiMapId
- _CollectObjective debug: show spawnList[id] zone, firstCoord, isLearned per entry
- _ResolveArrowUiMapId: stop redirecting 1241→1941. With zoneDB now
mapping areaId 3431→uiMapId 1241, and player on uiMapId 1241, keeping
1241 as 1241 ensures both player and target world coords are computed
through the same ASCENSION_ZONE_BOUNDS[1241] calibration (1600x1066.67,
-2721, 8433.94). This gives correct ~169yd distance and bearing.
- zoneDB: remap areaId 3431 → uiMapId 1241 (Sunstrider Isle) so quest 8335
pins render on correct map instead of Eversong Woods (1941)
- compat/HBD: remove 1241→1941 redirect (Sunstrider pins now handled by zoneDB)
- compiler: QuerySingle override check skips empty tables (next() guard) so
spawn data overrides don't fall through to nil base DB values
- MapIconTooltip: nil guards for minLevel/maxLevel to prevent crashes
- Map/QuestieMap: remove stale 1241→1941 redirect comment
AlreadySpawned kept stale entries after UnloadQuestFrames ran.
The draw path for incomplete objectives never cleared it — only
completed objectives called _UnloadAlreadySpawnedIcons. Added
clear at the start of the spawnList draw path so _DetermineIconsToDraw
sees an empty AlreadySpawned and recreates icons each update cycle.
The monster handler replaced spawns with learned data when
prioritizeMyData was enabled but didn't set isLearned=true on
monsterData. The prioritizeMyData filter in PopulateObjective
checked 'if not spawnData.isLearned' and suppressed the spawn,
causing pins to not render after quest abandon+re-accept cycle.
QuestieDB:GetNPC and GetObject cache results in _QuestieDB.npcCache /
_QuestieDB.objectCache. When LearnNPC / LearnObject injects new spawn
coordinates into npcDataOverrides / objectDataOverrides, the cached
objects still hold stale (empty) spawn data. PopulateObjective rebuilds
spawnList by calling GetNPC/GetObject, but gets the cached empty data.
Fix: clear the relevant cache entry immediately after live injection so
the next GetNPC/GetObject call rebuilds with fresh override data.
This is the root cause of 'pins not showing on kill' — the spawnList
was rebuilt, but from a stale cache that didn't include the new learned
coordinates.
1. _InvalidateSpawnListsForNPC was setting objective.AlreadySpawned = nil,
causing _DetermineIconsToDraw to crash on nil-index when trying to draw
updated pins. The xpcall wrapper in UpdateObjectiveNotes swallowed the
error silently, so no icons appeared.
Fix: unload existing map/minimap icons explicitly, then set
AlreadySpawned = {} (empty table) instead of nil. Also clear
hasRegisteredTooltips so tooltips refresh.
2. Added GetAchievementNumCriteria defensive shim for Ascension servers
with incomplete achievement databases. Returns 0 for invalid IDs
instead of hard-erroring in Blizzard's WorldMapFrame.
Two fixes for QuestieLearner real-time pin rendering:
1. _MergeOverride now skips empty tables (IsEmptyTable guard).
QuestieLearner stores spawns={} before any coords are captured.
Previously _MergeOverride treated {} as valid data and replaced the
base DB's real spawn coordinates with an empty table, breaking pins
for all NPCs that had been seen but not killed (mouseovers, etc.).
2. _InvalidateSpawnListsForNPC triggers after LearnNPC adds new spawn data.
The quest objective system caches spawnList once per objective.
When a kill adds new coordinates, the cached list is stale.
The new helper:
- Scans all active quest objectives for references to the NPC
- Clears objective.spawnList and objective.AlreadySpawned
- Calls QuestieQuest:UpdateQuest to rebuild pins with fresh data
This makes learned pins appear in real-time without /reload.
7 macros for manual in-game testing: stats, target/mouseover dump,
zone info, debug toggle, force learn, and data re-injection.
Each fits within WoW's 255-char macro limit.
When UpdateNearestTargets uses ZoneDB:GetAreaIdByUiMapId(946) to verify
player zone, it returns 668 (Northshire) instead of 3430 (Sunstrider).
UiMapIdOverrides[946] was set to 668 for Northshire, so 946 always
resolves to Northshire's areaId even on Sunstrider Isle.
Added direct cache entries for both 1241→3430 and 946→3430 in the
uiMapIdToAreaIdCache (the fast-path lookup table) so GetAreaIdByUiMapId
returns the correct Sunstrider Isle areaId regardless of which map ID
the game returns.
Root cause: creatureLevels entry for 'Uneasy Citizen' was {} (empty table)
instead of {minLevel, maxLevel, rank}, so creatureLevels[name][1] was nil.
Added early-return guard in _GetLevelString: if creatureLevels[name] is
falsy or has no numeric level at index [1], return name unchanged.
Also updated CHANGELOG.md [Unreleased] section with this fix.