------------------------------------------------------------------------------------------------------- - Sku Anniversary Addon Release Notes (English) ------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------- Changes in Sku v43.10 Overview New Auras: new base aura "Your target switches to a group member". When the enemy you have targeted turns to another member, a sound plays and the member is named by their key number, in party and raid. Changes Role detection: the health monitor now detects tank, healer and damage automatically for raid members 26 to 40 too. Before only up to slot 25. Focus: /focus1 to /focus8 now accept every raid slot (raid1 to raid40), before only up to raid25. Auras: new unit values "party or raid members" and "... excluding yourself". They cover the whole raid; the existing party values only cover your own subgroup in a raid. Bugfixes Monitor combat: since v43.9 the threat warnings raised a Lua error (aqCombat.lua:380, UnitGUID) and stayed silent, for example when you pulled aggro in a group. Fixed. Monitor combat: threat warnings audible again Simple: Pulling aggro in a group, or coming close to it, gave an error instead of the warning since v43.9. The "threat high" and "threat low" warnings play again. They do not name a player and never did; the v43.9 notes described that wrongly. Technically: the four outputs of the two warnings passed unit1 = tAllPartyRaidUnits[x] with no x in scope, so always nil; their templates contain no ${unit1}. v43.9 sent the value through tSpokenGroupUnit, which called UnitGUID(nil). The argument is gone, and tSpokenGroupUnit now returns nil for nil. Role detection: raid slots 26 to 40 Simple: Since v43.9 the raid health monitor speaks members 26 to 40 too, but their role was not detected automatically. Now it is. Technically: RoleCheckerIsUnitGUIDInPartyOrRaid only knew raid1 to raid25; the limit is gone. RoleCheckerGetUnitRole crashed when the member it was asked about got pruned as no longer in the group while cleaning up the role data, and its last line read a variable out of scope. Both fixed. Focus: all 40 raid slots Simple: "/focus3 raid30" used to store the text "raid30" as the name, and the focus key then found nobody. Now the member's name is stored, as for raid1 to raid25. Technically: tFocusUnitIds (skuFocus.lua) knew raid, raidpet and their target chains only for 1 to 25, now up to MAX_RAID_MEMBERS. Auras: base aura "Your target switches to a group member" Simple: For tanks. Under New aura, Base auras. When the enemy you have targeted switches to another party or raid member, a sound plays and Sku says who: "party 3" in a party (the numpad key), the dial number in a raid. When it comes back to you, it stays quiet. "Who" narrows down which members are watched. This is the aura from the old protection warrior set, which could not be loaded for a long time, with a tighter condition. Technically: event UNIT_TARGETCHANGE, source contains target and is attackable, dest = the chosen value (preset raidWoPlayer), outputs sound plus dest unit. The old set only checked "source is not a party member"; since Sku tracks the targets of all 40 raid slots, a healer in another subgroup selecting someone fired it too. Base auras can now ask for a value other than a spell (valueLabel, emptyValueMessage, defaultValues). Auras: values for the whole raid Simple: Source, target and "target of your target" have two new values: "party or raid members" (with you) and "party or raid members excluding yourself". In a party they behave like the party values, in a raid they cover every member. The old party values stay as they are: only your own subgroup in a raid. Technically: tEvaluateRaidValue (data.lua) checks the unit set for party1-4 and raid1-40. "Excluding yourself" means no "player" in the set, because in a raid GetBestUnitId also returns your own raidN slot for you. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.9 Overview New Raid subgroup keys: Alt+Numpad 1 to 8 target the first member of raid subgroup 1 to 8. With an empty group Sku says "Group N empty" and nothing happens; outside a raid "No raid". Own sub-menu "Raid subgroup keys" in the Sku key bindings, can be switched off under Features. Changes Auras: basic auras now offer the same output list under "Output" as custom auras, meaning every sound and every data output (for example spell name or target unit), several at once. Before, there was only a single sound. Profession and trainer windows: after crafting, learning or selecting, the menu refreshes quietly, the cursor stays on the same entry, and only a genuinely different entry is announced. Monitor combat: "Announce deaths" now names the numpad key of the dead member in a party: "party 5, dead" means numpad 5 is dead. This changes a very old standard; the fourth party member used to be announced as "party 4". Monitor combat: in a raid "Announce deaths" says the dead member's dial-targeting number, the key or digit pair that targets them. It used to say "party 2" for your own subgroup and "raid 17" for everyone else. Raid slots 26 to 40 are now announced too. Monitor combat: "Count deaths up" is removed. Monitor health: "Add Dead on 0 percent" is now off by default, for party and raid, for existing characters as well. Dial Targeting: raids only now. "Enabled" is an On/Off toggle; in a party numpad 1 to 5 stay the normal target keys (1 = self, 2 to 5 = the others). Dial Targeting: in raids of up to 10 players always one key per member (0 = member 10), two digits from 11 players on. The option "Single key action in raids up to 10 players" is gone, that is the rule now. Monitor health, raid: the numbers are the dial-targeting numbers, so in raids of up to 10 players 1 to 10 in subgroup order. They always used the two-digit scheme before, so in a 10-man with someone in group 3 key 7 was called "11". Monitor health, raid: members numbered 26 to 40 are now spoken (number, then health in tens) with the new setting "Voice for members above 25". Pitch files exist up to 25 only, those members used to be silent. Monitor debuffs, raid: the numbers are the dial-targeting numbers as well. Monitor combat: threat warnings, "target of target" and "out of range" name group members like the death announcement does, with the numpad key in a party ("party 2" for the first member) and the dial number in a raid. Before it was the raw raid slot and "party 1" for key 2; raid slots 26 to 40 were missing entirely. Auras: the outputs "source unit" and "target unit" name party members by key number and raid members by dial number as well. Bugfixes Quest menu: "Route" under a creature was always empty when its name contains a hyphen or a bracket (for example "Un'Goro-Gorilla" on German clients). Fixed. Close route: the search for linked waypoints near a target often returned fewer than the ten nearest. Most probably never affected anyone, a pure correctness fix. Auras: "Delete all auras" deleted every aura on a single key press without asking. Now it asks "Really delete?" first, like deleting a single aura does. Profession and trainer windows: after every craft or learn the window name was announced, and the cursor sometimes jumped to another entry. Fixed. Profession windows: Enter on a recipe or on "Create" announced the entry twice. Fixed. Mailbox: on opening, "Target" was announced before "New mail". Fixed. Key bindings: the second key ("Rebind secondary key") had no effect for a number of keys, among them "Trigger monitor party continuous output", target distance, panic mode, minimap scan, group range check, "Turn to unit" and the scan keys. Fixed. Monitor combat: "Ignore dead party pets" jumped back to "Yes" after every reload. Fixed. Monitor debuffs, raid: the continuous output said raid slot plus one, a number that belonged to nobody. Fixed. Monitor combat: "party 5, dead" (fourth party member, new in this version) beeped with "Numbers only" off because there is no voice word "party5". It now says "party" and "5". Quest menu: "Route" finds creatures with a hyphen in their name Simple: In the quest menu (Quest start, Quest target, Delivery) a creature offers "Route", "Close route" and "Waypoint". "Route" said "list empty" whenever the creature's name contained a hyphen, a bracket or a similar character, even when the creature is well connected to the route network. On German clients that was 249 creatures, among them all three ape kinds in Un'Goro Crater. "Close route" and "Waypoint" were not affected. Technically: SkuQuest/Options.lua, CreateRtWpSubmenu, "Route" branch: string.find(i, wpName) without the plain argument read the waypoint name as a Lua pattern. There "-" is a quantifier ("o-" = any number of o), so the name never found itself. Now string.find(i, wpName, 1, true) at both call sites. The route data in the crater was fine (checked: links, spawn indices and connection to the route network). Close route: the ten nearest linked waypoints are really found Simple: Most creatures are not linked to the route network themselves. For "Close route" Sku therefore looks for up to ten linked waypoints within 500 yards of the target and takes the best reachable one. That search often returned fewer than ten. The nearest waypoint was always among them, though, and the route network is connected almost everywhere. This most probably never affected anyone; the fix is there for correctness only. The bug could only have shown when the nearest waypoint lies on a detached piece of the network: then "list empty" or a small detour. Technically: SkuNav:GetNearestWpsWithLinksToWp (SkuNav/Core.lua). The sorted insert had no append case: a candidate farther away than every entry was dropped although the list still had room, so the first hit of pairs() set the ceiling. The trim above N entries always removed the farthest one and stays. Checked offline against "the N nearest" (20000 random cases): old 9059 too short, new all correct. All five callers evaluate the whole list and take the best reachable entry, so more candidates can only give equally good or better routes. Auras: basic auras get the full output list Simple: Under "New aura", "Basic auras" every template had a "Sound" entry where you could pick exactly one sound and nothing else. That entry is now called "Output", followed by the number of outputs switched on, and it opens the same list a custom aura has: every sound and every data output, for example spell name or target unit. Each entry is a switch, Enter turns it on or off, and several may be on at once. What the template used to set (its sound, plus spell name and target unit for "Your buff on a group member expired") is already switched on when the form opens, and can now be switched off too. Technically: SkuAuras/Options.lua. tBuildOutputsLevel takes an optional context (outputs, ownerLabel, tooltip); without it the list edits the aura builder's draft as before, with it the basic-aura form's list. The form (tBaseForm) holds a list outputs instead of a single sound value, seeded from defaultSound and defaultOutputs (formerly fixedOutputs, which were appended unconditionally on create). The summary and tBaseFormCommit read that list; with no output on it still says "No output set". Auras: "Delete all auras" asks first Simple: "Delete all auras" in the Auras menu had no level below it: one Enter or Right arrow and every aura of the character was gone. Now the entry leads to "Really delete?", like deleting a single aura. Only Enter there deletes, Left arrow backs out. Technically: SkuAuras/Options.lua, MenuBuilder: the entry is now dynamic and its BuildChildren injects the single child "Really delete?". On entering, selectTarget points at the entry itself, so Enter on the child calls its OnAction. Before it was dynamic = false with no children, so OnPostSelect went straight to the OnAction branch. Menu: paths are resolved instead of walked Simple: Whenever Sku itself puts the menu at a certain place (a window opens, a quick key like Shift-F10, or a window changed and its menu is rebuilt), it used to walk the path there as if Enter were pressed on every level, announcing every stop on the way. During the quiet rebuild of a profession or trainer window after a craft or a learn you therefore heard the window name ("Trade skill", "Class Trainer"), and the cursor was restored by row number into a list that had just changed. Now the target path is resolved directly, without announcing the stops, and the cursor returns to the same entry: the same recipe (even with a new count), the same skill, the same button. Only when that entry is gone is the place the cursor landed announced. Technically: SkuOptions:ResolveMenuPath and EnsureLevelBuilt (SkuZOptions/Core.lua) replace the loop in SlashFunc that called OnSelect(true) on every path segment. Levels on the way are only built (dynamic: RebuildNodeChildren, otherwise one BuildChildren when empty), never selected; only the destination gets its OnSelect as before (pre-build of an empty, non-dynamic level, never on isSkuToggle). The side effects of the walk are gone with it: actionOnEnter nodes along the path fired, every sibling before the match was pre-built. SlashFunc's aSilent parameter is effective now. The restore in SkuCore:CheckFrames makes one silent call instead of two speaking ones and sets the cursor directly: skuIdentity, then name, then the row at the old index (clamped to the new length). Window entries carry skuIdentity (copied by SkuIterateGossipList): recipes "recipe:Name (rank)", categories "cat:", trainer skills "skill:", otherwise "frame:" for every entry that is alone on its frame (SkuCore:PrepareWindowEntries at the end of the three builders). SkuCore:RefreshWindowMenuQuietly (LocalMenu.lua) is the shared quiet rebuild for TRADE_SKILL_UPDATE/CRAFT_UPDATE and the Train button; it speaks only when the identity (or the count-stripped name) is a different one afterwards. The bags keep their own path (tFindMenuNodeByPath, tBagAnnounceSuppress). Profession windows: Enter on a recipe or "Create" announced the entry twice Simple: After Enter on a recipe or a Create button the entry was spoken twice. Now once. Technically: The classic click path in SkuIterateGossipList called a speaking CheckFrames after func and OnUpdate 0.35 s later, on top of the key handler's own announce. The third announce was usually caught by the duplicate guard (1 s), the second one came 0.9 s later and was not. Entries with quietClick (set by PrepareWindowEntries for every click button without containerFrameName) now run func plus RefreshWindowMenuQuietly; buttons with a secure /click keep the old path. Mailbox: "Target" before "New mail" Simple: On opening a mailbox, "Target" was announced first and only then "New mail". Now only "New mail". Technically: Mail:MAIL_SHOW opened the path "Local, Mail" before the MailFrame was visible, but the mail entry under Local exists only while its frame is visible. The path found nothing, and the silent open announced the first root entry instead ("Target", the target menu); the bags pulled the menu into the mail a moment later via CheckFrames. The debug log showed "path walk found nothing ... short,lokal,post" on every mailbox since at least v43.6; it was only noticed once the intermediate announcements were gone. Now MAIL_SHOW opens one frame later (C_Timer.After(0)), only with a visible MailFrame and only if the cursor is not already in the mail branch. Key bindings: second key for monitor, scan, turn and friends Simple: Under "Sku key bindings" every key has a first and a second binding. For a number of keys the second one could be set and was announced as "Key 2", but did nothing. Affected were "Trigger monitor party continuous output", target distance, panic mode, minimap scan wide and narrow, group member range check, "Turn to unit" 1 to 6 and 180 degrees, "Scan continue", scan 1 to 8 and the resource notification. The second key now works everywhere. The monitor key's English name also loses the typo "continous". Technically: SkuCore/Core.lua, OnHide script of SkuCoreControlOption1: the bindings owned by this frame were armed with SetOverrideBindingClick for .key only (SKU_KEY_TAXICANCEL was the lone exception), while the click dispatcher checks both fields through SkuKeyBindsMatchKey. The binding menus in SkuZOptions/Core.lua have always armed key2, which is why the second key worked for other actions. A local tArm(aConst) helper now binds every key SkuKeyBindsGetKeys returns; the twenty hand-written single lines are gone. Monitor combat: the death announcement names the numpad key Simple: Under Monitor, Combat, Friendly, "Announce deaths" announces a dead group member. The number in it is now always the key that targets that member. In a party those are Sku's default keys: numpad 1 = self, 2 to 5 = the four others. The fourth party member is therefore now "party 5, dead", no longer "party 4". The health monitor and the overview have always counted this way. In a raid the dial-targeting number is spoken: the single key up to 10 players, the two-digit sequence from 11 on. It used to say "party 2" for members of your own subgroup and "raid 17" (the raw raid slot) for everyone else, two numberings in one fight, neither matching the numpad. Raid slots 26 to 40 were not announced at all. "Count deaths up" is removed: in "number only" mode its "3, dead" could not be told from "party 3, dead", and the counter never went down on a resurrection. Technically: SkuCore/aqCombat.lua, aqCombat_SKU_UNIT_DIED. New helper tDeathUnitToken(GUID) returns the spoken token and the real unit id separately, the UnitIsDeadOrGhost and pet checks need the raw slot. Party: token from aqCombatGroupGuidToUnitId, digit plus 1. Raid: all MAX_RAID_MEMBERS slots scanned by GUID (the 25 in tAllPartyRaidUnits were not enough), number read from the dial-targeting grid (SkuSecureTargetingFrame, unitNameSlotGG-SS, key = (GG-1)*5+SS; insecure code may read a secure frame's attributes), else tDTRaidRoster from aq.lua, else live (subgroup-1)*5+position. Grid and roster are rebuilt out of combat only, so they go stale together with the numpad. partyDeadCount with its counter, menu and locale key removed, the stored value is cleared at login. Monitor combat: "Ignore dead party pets" stays on No Simple: The setting could be set to "No" but was back on "Yes" after the next reload. Fixed. Technically: aqCombatOnLogin set the value with "x or true"; a stored false is falsy in Lua and was replaced by true at every load. Now only when nil. Monitor health: "Add Dead on 0 percent" off by default Simple: The pitch style of the party and raid health monitor appends a "dead" clip to the tone of a member at 0 percent. That was on for party and raid and is now off, for existing characters as well. Switch it back on under Monitor, Party or Raid, Health if you want it. The combat monitor's death announcement (above) is thereby the only death message that runs on its own. Technically: SkuCore/aq.lua, AqOnLogin: default false instead of true, one-time switch of existing characters via addDeadOn0PercentDefaultOff (same pattern as *DefaultOff in aqCombat). Dial Targeting: raids only, one key up to 10 players Simple: "Enabled" under Dial Targeting is now an On/Off toggle and only acts in a raid. The choices "Party" and "Party and Raid" are gone: in a party Sku's default keys numpad 1 to 5 reach everyone anyway, and the party mode laid a shifted numbering (0 = self, 1 to 4 = the others) over them and hid the default keys while it was active. A stored "Party" becomes Off, "Raid" and "Party and Raid" become On. In raids of up to 10 players one key per member is now always active (numpad 1 to 9, 0 = member 10, in subgroup order), from 11 players on the two-digit input. The option "Single key action in raids up to 10 players" is gone, that is the rule now. Technically: SkuCore/DialTargeting.lua. enabled is mapped once at login to Off/On, singleKeyinRaid10 cleared. DialTargetingRosterUpdate and DialTargeting_EndableDisable only check UnitInRaid and enabled == On; the party branch (unitNameSlot01-0x = partyx) and the dead "party" branch in the secure snippet are removed. Raid mode: tNumCurMembers > 10 means two digits, else raid10. Three locale keys removed. All of it untested in game. ------------------------------------------------------------------------------------------------------- Group numbers: one count for every monitor Simple: Every number Sku says for a group member should be the key that targets them. In a party that is numpad 1 for you and 2 to 5 for the others; in a raid the dial-targeting number, a single key 1 to 10 with up to 10 players and two digits from 11 on. The death announcement counts that way since this version. Now the raid health monitor and the raid debuff monitor, the threat warnings, "target of target" and "out of range" in the combat monitor, and the aura outputs "source unit" and "target unit" do too. The party health and debuff monitors keep the party count inside a raid, because whoever watches only their own subgroup there targets it with numpad 2 to 5. The raid health monitor has pitch files for numbers 1 to 25 only, so members 26 to 40 were silent. They are spoken now, the number first, then health in steps of ten, with the voice from the new setting "Voice for members above 25" (Monitor, Raid, Health; default Justin). Technically: SkuCore/aq.lua: SkuCore.Monitor.RaidMemberNumbersByName() builds the name-to- number table by the dial rule (up to 10 members flat 1-10 in subgroup order, from 11 (subgroup-1)*5+position); tDTRaidRoster and the dial-targeting grid (DialTargeting.lua) are filled from it, tDTRaidRoster always used the two-digit formula before. SkuCore.Monitor.GroupMemberNumber(unitId) returns the key number for party/raid tokens. The continuous raid debuff output said string.sub(unitId, 5)+1. MonitorOutputRaidPercent2 speaks numbers above 25 via MonitorOutputPlayerStatus (raid.health2.voice, default 1) and returns the duration to the queue; the queue entries carry the absolute health for that. SkuCore/aqCombat.lua: tAllPartyRaidUnits and tUnitsToTestOnGameRaidTargets had raid1-25 hard-wired; they are now refilled in place from the real raid size on every roster event. tSpokenGroupUnit(unitId) applies the death-announcement mapping to every friendly announcement. "party5"/"partypet5" do not exist as words in the combat voices, SkuCoreAqCombatGetVoiceString splits them into "party", "pet", "5". SkuAuras/data.lua: tUnitIdToSpokenName says party N as N+1 and raid N as the dial number. Raid subgroup keys: jump into a raid group with one key Simple: Whoever only heals their own group in a raid needs a quick way into a group without the two-digit dial entry. Alt+Numpad 1 to 8 target the first member of raid subgroup 1 to 8 (the member numbered 1 in that group, the same order dial targeting and the monitors use). The target is announced like any target. If the group is empty, Sku says "Group N empty" and nothing happens; outside a raid "No raid". The eight keys have their own sub-menu "Raid subgroup keys" in the Sku key bindings, the second key works too. The feature can be switched off under Features. As with every targeting key: group changes during a fight are picked up after the fight, until then the key jumps to the previous first member. Technically: New file SkuCore/subgroupTargeting.lua (submodule SubgroupTargeting, in the TOC after skuFocus.lua). Eight SecureActionButtons SkuSubgroupTarget1-8 with type1=macro, macrotext1="/tar ", bound via SetOverrideBindingClick WITHOUT a fifth argument (LeftButton, so type1/macrotext1 is read); the names come from GetRaidRosterInfo (first name per subgroup in roster order) on GROUP_ROSTER_UPDATE, GROUP_FORMED/JOINED/LEFT and PLAYER_ENTERING_WORLD out of combat, deferred to PLAYER_REGEN_ENABLED in combat. PreClick (insecure, runs in combat too) speaks the empty / no-raid line. Control frame SkuCoreSubgroupControl, script OnHide, arms both keys of every const via SkuOptions:SkuKeyBindsGetKeys. Consts SKU_KEY_SUBGROUPTARGET1-8, default ALT-NUMPAD1-8 (SkuZOptions/SkuKeyBinds.lua), group "Raid Untergruppen Tasten" in SkuCore/Options.lua. Changes in Sku v43.8 Overview New Game settings: checkboxes with a slider or a dropdown (for example "Max foreground FPS"), buttons, the "Graphics quality" block and the colorblind settings can now be operated; they used to read "not supported". Game settings: number values start with an "Enter value" entry (text box), digits filter the value list, and the values carry the game's own labels ("60 FPS", "150%"). Game settings: dropdowns say "(recommended)" and "(not available)" like the game, greyed-out rows say "(disabled)", and subcategories of addon settings are shown. Changes Game error messages (for example "You have no target") are read only once while you hammer a key. The same message is announced again only after you have left the key alone for about two and a half seconds, or when a different error message came in between. Turn to beacon, waypoint or unit: the turn now lands equally precisely on every machine and is much faster. At 20 frames per second a half turn took almost half a second and every fourth key press was lost; now a quarter of a second, and hardly any press is lost. On fast machines big turns land within about one degree instead of three to five. The self-calibration from v43.7 is gone. Bugfixes Speech: since v43.7 the same line queued up again on every key press and was read many times in a row, even after a more important announcement. Fixed. Menu: pressing a key (End, Home, arrow) right after "menu open" still got you the first main-menu entry ("Target menu") read out after the new entry. Fixed. Speech: a line no longer queues up behind itself Simple: Since v43.7 a message that was triggered several times in quick succession could enter the queue any number of times. If you hammered a key that raises an error, you then heard "You have no target" ten or twenty times, and after a target announcement cut in, the message came once more. Now: if the same text is already waiting or is being spoken right now, the new copy is discarded. Menu lines and other announcements you explicitly ask for again with a key are not affected. Technically: SkuVoice:OutputStringBTtts (Libs/SkuVoice-1.0). The "is exactly this being spoken" check used to run at the handover to the client. Since the v43.7 client gate a copy only reaches the handover once its predecessor has FINISHED - the check never matched any more (log: queue 35 x the same line). Lines without a queuereset are now checked when they are ENQUEUED, against mSkuVoiceQueueBTTS and mSkuVoiceQueueBTTS_Speaking; log tag "BTTS ENQUEUE-DUP". New harness scenario "errspam" (dev/rework-docs/voice-harness). Menu: the first entry was still spoken late after a quick key press Simple: When the menu opens, Sku says "menu open" and then the first entry. If you pressed a key while "menu open" was still speaking, you heard the new entry - and after it "Target menu", the first entry, which was no longer the current one. It depended only on how fast you pressed, not on the key or the menu. Now the old first entry is dropped as soon as another menu entry is announced. Technically: Two causes. (1) The v43.7 rule "keep a waiting line without a reset behind the new overwrite line" (meant for chat that a warning cuts into) also applied to menu lines. The keep loop in SkuVoice (queuereset scan, Libs/SkuVoice-1.0) now drops a waiting line whose scope equals the scope of the new overwrite line: a scope is one position in one UI, only its newest line is true. (2) The two lines spoken at open ("Menu;open" and SkuOptions.Menu[1].name, SkuZOptions/Core.lua) called OutputStringBTtts directly WITHOUT the "menu" scope that VocalizeCurrentMenuName gives every other menu line - so the scope comparison had nothing to compare. Both carry "menu" now; as a side effect EndScope("menu") drops both when the menu is closed again at once. Log before: "BTTS queuereset kept 1 waiting line(s) behind the overwrite". Error messages: announced only when they newly appear Simple: Sku now follows the rule the game applies for sighted players. There an error message stays on screen for about two and a half seconds; if the same message comes again during that time, it only flashes briefly and stays longer instead of appearing anew. Accordingly Sku reads an error message when it appears and stays quiet while it is still "on screen". Every further key press extends that time. The game's click sound still tells you on every press that it is not working yet. If you leave the key alone for about two and a half seconds and then press again, the message is announced again. A DIFFERENT error message is always read at once, and after it the first one counts as new again: error A, error B, error A are three announcements. This affects spoken errors only, not the sound and voice hints. Technically: UIErrors:OutputError (SkuCore/UIErrors.lua), TTS branch. Only the LAST spoken error is remembered: tSpokenKey plus tSpokenShownUntil = GetTime() + 2.5 (displayDuration 2 + fadeDuration 0.5 from UIErrorsFrame.xml). Every repeat sets the time again, like ResetMessageFadeByID; any other error, including a sound or voice hint, replaces or clears tSpokenKey. The first version kept one time stamp PER text - so error A stayed silent although error B had come in between. Deliberately a time stamp and not a read of UIErrorsFrame: Blizzard throttles only about 20 message types, for the rest a new line is added per press, and the order of the two UI_ERROR_MESSAGE handlers is not defined. Game settings: every control of the Blizzard settings Simple: In the menu Settings, Game settings many entries only read "(not supported)", for example "Max foreground FPS". They can be operated now. A checkbox with a slider becomes two entries: the switch and "... value". Number values are a value list with the game's own labels ("60 FPS", "150%"); digits filter the list, and the first entry "Enter value" opens the text box. Dropdowns mark the recommendation with "(recommended)" and values this machine cannot run with "(not available)". Buttons such as "Reset chat position" run their action. The "Graphics quality" block is a submenu with "Raid and battleground". Key bindings link to Sku's own menu. A setting the game currently greys out says "(disabled)". Also, graphics, display and UI scale settings used to be staged only and never applied - they take effect immediately now. Technically: SkuCore/gameOptions.lua now dispatches on the initializer's frame template (Blizzard_SettingControls.lua) instead of the value type: Checkbox, Slider, Dropdown, CheckboxSlider (cbSetting/sliderSetting), CheckboxDropdown, CheckboxWithButton, Button, AdvancedQualitySection (its option lists are closures inside the frame; Sku derives them from a table per cvar and asks IsGraphicsSettingValueSupported), ColorblindSelector, keybinding sections. setting:SetValue(v) without the second argument only stores a pendingValue on settings carrying CommitFlag.Apply, which only the panel's Apply button writes - Sku never opens the panel, hence SetValue(v, true) now, plus RestartGx / UpdateWindow / SaveBindings by commit flag. GetAllCategories returns top-level categories only; GetSubcategories is walked recursively. ShouldShow false = row skipped, GetModifyPredicates false = suffix "(disabled)". Value labels come from options.formatters[Label.Right]; typed input is mapped back linearly through the formatter (percent, quality slider 1..10). Every write logs "gameOptions: set". An offline harness against fake Setting objects passed; verified in game: the FPS switch and value (log: PROXY_FOREGROUND_FPS = 20, TurnCal fps 20). Turn to beacon: frame-exact instead of time-based Simple: Until now Sku rotated the camera for a computed time and then stopped it. The game only moves the camera once per frame, and a timer hits the frame boundary only roughly - at low frame rates it misses. On a slow machine (20 frames per second, reproduced in the test with the FPS limit) every turn over 15 degrees therefore took almost half a second, small turns fell a third short, every fourth key press hit the busy window and was lost, and mounted you circled close waypoints again. Now Sku counts the frames and uses their real duration: the turn speed is chosen so the angle is a whole number of frame steps, and if the last step would overshoot, it is shortened. Result at 20 frames per second: a half turn in a quarter of a second, every turn within two degrees, hardly any lost presses, no circling. At 80 to 120 frames: big turns with about one degree of error instead of three to five, small ones under half a degree. The self-calibration from v43.7 is gone - it learned values that are the same on every machine. /skuturn now only shows whether the fast gear works on this machine; /skuturn reset clears that check. Technically: GameWorldObjects:TurnToWorldPosition (SkuCore/gameWorldObjects.lua). Measured on about 400 turns at 20 and 77 to 125 fps (residual 0.5 ms): the camera steps once per frame by speed times the REAL frame duration; the frame whose OnUpdate issues the stop no longer steps; the frame duration measured in an OnUpdate is exactly the next step's. Planner: n = ceil(angle / (1440 * f)) steps, v = angle / (n * f), CVar at most 360, the rest as the MoveView factor. The stop comes from an OnUpdate counter that accumulates the real frame durations and stops as soon as the sum reaches the motion time angle/v; if the last step would overshoot, cameraYawMoveSpeed is scaled to the remainder for that one frame and the stop follows in the next - the CVar takes effect at once (54 trimmed turns: error 0.5 degrees instead of the 5.3 a dead CVar would have given). C_Timer was the cause of the old scatter: it only fires on frame boundaries and slips a whole frame at 2 percent frame jitter. The camera "glide" of v43.7 was this one idle frame, not momentum. The calibration (scale and latency per gear, store v4/v5) learned 1.00 and one frame with a spread of 0.01 and was removed; store turnCal v6 only keeps noGear2 (factor has no effect: median real/asked speed under 0.7 over 6+ fast turns). Busy window after the stop 2 frames or 50 ms instead of 3 or 100. Log "TurnCal" per press with n, m, mt, trim, dts (all frame durations), fest/fact/fmax, rest_land, lead_shift; analysis script pattern an4.py in the scratchpad. Lessons for WowVision: dev/rework-docs/TURN-TO-BEACON-LESSONS.md. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.7 Overview Changes Turn to beacon (T), turn to unit and turn to target: big turns now land with ONE press. Until now every press got stuck at about 88 degrees, so an about-face always took two. Turns are clearly faster: half a turn takes a good tenth of a second on a fast machine instead of a quarter second. If you already face the beacon (within 3 degrees), you are no longer turned. This ends the swinging back and forth around the beacon ping. The turn calibrates itself: after every turn Sku checks how far you really turned and adapts to your machine and your frame rate. The first few turns after the update may be imprecise. Keyboard echo with a real Windows voice (not the NVDA bridge): the long pause after every typed letter is gone, the letters follow each other closely. Pasting text into an input field (Ctrl+V): Sku now says "Pasted" once instead of reading the pasted text letter by letter. Bugfixes Swimming and flying: the pitch lock now engages in combat too. If you ran into water while fighting, the first turns used to make you dive. Items from an equipment set: the tooltip named the armor type (for example "Cloth") twice. Now only once. Speech through a real Windows voice (not the NVDA bridge): lines that had long been replaced were still spoken seconds or minutes later - typed filter letters after "Menu closed", skipped list entries after releasing the arrow key, guild chat a minute late. Fixed. Menu: when the menu opened straight into a window or a list (bag, vendor, Shift+F10), "Target menu" was spoken after the content. Fixed. Real Windows voice: if another addon spoke while you were typing, a letter could follow after "cancelled". Fixed. Turning: faster, more precise, self-calibrated Simple: "Turn to beacon" (T) and the two other turn commands were reworked based on measurements in the game. Three things change noticeably. First: big turns arrive with one press. Until now one press managed about 88 degrees at most, no matter how far it was meant to go - an unnoticed bug since v43.2. Second: the turn is faster, an about-face takes only a good tenth of a second. Third: if you are already aligned within 3 degrees, another press does nothing. Before, every press turned at least 5 degrees, so you swung around the beacon ping and never came to rest. In water this also saves needless dive nudges. Sku learns your machine along the way: after every turn it measures how far the character really turned and computes the next turn from that. At a high frame rate Sku turns faster by itself, at a low one slower and precise instead. After the update the first few turns are learning steps and may miss. Very big turns near 180 degrees land within about 5 degrees; now and then a second press refines. A press during a running turn is discarded as before. Technically: GameWorldObjects:TurnToWorldPosition (SkuCore/gameWorldObjects.lua). Measured 2026-09-21: cameraYawMoveSpeed has no effect above 360 - higher values do not turn faster (376, 453 and 731 all gave ~88 degrees in 0.25 s). The v43.2 rule max(360, angle/0.25) therefore silently capped every press at ~88 degrees. More speed only comes from the argument of MoveViewLeftStart/MoveViewRightStart, which multiplies the CVar (technique from WowVision). Two gears: gear 1 = CVar alone up to 360 deg/s, gear 2 = CVar 360 times a factor up to 1440 deg/s. Model per gear: turned = k * wanted speed * (timer duration + L); k from the median from the first sample on, from 8 samples with spread durations a line fit for k and L. Speed by frame rate: the stop scatter is half a frame time times speed, allowed are 3 degrees or 6 percent of the angle; never slower than angle/0.25 s; slowed down when the timer would be shorter than 2 frames. If the factor does not multiply on a client, Sku retires gear 2 for good. Samples account-wide in SkuOptions.db.global.SkuCore.turnCal. The guard against follow-up presses now also covers the measuring wait (a press in that gap computed from the old facing). Every turn writes a "TurnCal" line to the debug log. /skuturn shows the state, /skuturn reset discards the samples, /skuturn legacy switches to the old behaviour. Built, tested and removed again: a correction within the same press - the camera has momentum and glides on after the stop, a command into that glide cost up to 35 degrees. Swimming and flying: pitch lock engages in combat too Simple: The automatic pitch lock that keeps you level while swimming and flying used to wait for combat to end. If you entered water while fighting you were unprotected that long, and every beacon turn pushed you deeper. The lock now engages at once. The Ctrl+Shift+N key now works in combat as well. Technically: The InCombatLockdown checks in SkuCore:TogglePitchLock and in the automatic tick (SkuCore/Core.lua) rested on the untested assumption that pitchlimit is protected in combat. It is a console command and can be set in combat (tested 2026-09-21). In that day's log, 6 seconds with three unprotected turns lay between the start of swimming and the lock. Tooltip of set items: armor type was read twice Simple: If an item belongs to one of your saved equipment sets, Sku inserts the line "Belongs to set" into the tooltip. On exactly those items the armor type was then heard twice, for example "Soulbound, Cloth, Head, Cloth". The first "Cloth" was a leftover and did not belong there. It now comes only once, after the slot: "Soulbound, Head, Cloth". On weapons the same happened to the weapon type and the speed. Items that are in no set were never affected. Technically: tInsertSetLineAtTop (SkuCore/equipmentSets.lua) shifts every tooltip line from line 2 on down by one. The right column was only written when the source line had one. The line that "Head | Cloth" moved away from got "Soulbound" on the left and kept its old "Cloth" on the right. TooltipLines_helper reads every FontString region on its own, so the leftover was audible. The right column is now always written: set and shown, or cleared and hidden; the same for line 2. The bug had been in the code since v42.00 and only showed up once sets were saved. Speech: replaced lines were still spoken later (real Windows voices) Simple: This only affects players who let Sku speak through a real Windows voice, not through the NVDA bridge. There, a line that Sku had long replaced with a newer one could get stuck inside the game and be spoken much later after all: typing a filter quickly in a menu, you heard "g r" or "g r e i f" only after "Menu closed". Holding the arrow key, you heard two or three skipped entries after the last one. And chat lines that arrived while you were scrolling came up to 70 seconds late and out of order. In a log a player sent in, that was 24 of 524 announcements in half an hour. This is fixed: what Sku has replaced or cancelled stays silent. The rules about what may interrupt what do not change. Mixing stays too: when an important announcement cuts into a row of waiting lines (chat, status messages), it speaks first and the row then goes on, oldest line first. Only what has grown older than 30 seconds by then is dropped. Technically: Libs/SkuVoice-1.0. C_VoiceChat.StopSpeakingText only ends the utterance that is PLAYING. It cannot reach one that was handed over but has not started, and anything handed over while another utterance is still in the client is parked there and only starts at the next natural PLAYBACK_FINISHED - however many stops happen in between. New: one shared gate (mClientGate) for ALL handovers. Sku gives the client at most ONE utterance; the next line waits in Sku's own queue for FINISHED/FAILED with the matching id, where queuereset, EndScope and CancelBttsOutput can still reach it. Because the lines now wait inside Sku, the queuereset rule the client used to provide by accident had to be stated: a waiting overwrite line is superseded and dropped (mSkuVoiceQueueBTTS_Overwrite); a waiting line without a queuereset is moved behind the new overwrite line as long as it is younger than tBttsKeptMaxAge (30 s). All five stop sites go through BttsStop: if the utterance in the client has not started, the stop is noted and delivered 0.1 s after its STARTED, from OnUpdate - never from the STARTED handler itself: a StopSpeakingText from there wedged the client's whole speech output until a restart in testing (the old kill window did exactly that). This replaces the v43.5 kill window (the same idea with a guessed time span, wired to two sites only) and the typing echo lane's own gate. Safeguards: 0.5 s without STARTED opens the gate (and still sends a noted stop); after three handovers without any STARTED the gate stands down until one arrives again. On the NVDA bridge STARTED and FINISHED arrive in the handover frame, so the gate is never shut there - behaviour unchanged. Verified with an offline model of the client (dev/rework-docs/voice-harness): old code 27-33 late utterances in 3 minutes of random load, new code 0; bridge model identical old and new. In the log: "stop deferred (handover not started)", "deferred stop fires", "client gate ttl", "queuereset kept", "STALE-DROP". Follow-up bug, fixed along the way: the "stop audio output" key (Ctrl+V) only cancelled the line being spoken, the waiting ones carried on - one press per line. Before, the whole burst already sat in the client and the one stop hit all of it; since the gate the lines wait with Sku, and StopOutputEmptyQueue never emptied that queue. StopOutputEmptyQueue(true, true) now calls CancelBttsOutput: waiting lines and waiting typed characters dropped, then the stop. Callers with (true, nil) stay as they are - waiting chat lines are meant to survive those. Harness scenario "stopkey": old build four audible lines, new build one (cut short). Menu: "Target menu" was spoken after the content Simple: When the menu opens not at its root but straight at a certain place - opening a bag, a vendor or a mailbox, Shift+F9 and Shift+F10, the quick-select keys - you heard "Target menu", the first entry of the menu root, after the actual content. For example "Bag 1, Target menu". That is fixed: you only hear where you landed. "Menu open" is no longer spoken for these opens; it was cut off at once anyway, and the open sound stays. Opening the menu normally is unchanged: "Menu open", then "Target menu". Technically: SkuOptions:SlashFunc (SkuZOptions/Core.lua) opens a closed menu through the OnClick of OnSkuOptionsMain and then walks the path. The open branch spoke "Menu open" as an overwrite and queued Menu[1].name as a waiting line behind it. The path walk's announcement set a queuereset, and since this version's client gate a queuereset keeps waiting non-overwrite lines behind the new line - meant for chat, here it caught the root entry (log: "queuereset kept 1 waiting line(s)"). Fixed at the source instead of by suppression: with a path (at least two fields) SlashFunc sets SkuOptions.tOpeningForPathWalk around the open call, and the open branch then says nothing. The flag is cleared right after the call, also on an error (pcall, error re-raised). If the path matches nothing and the menu sits on the root, SlashFunc delivers the announcement afterwards and writes "menu: path walk found nothing" to the debug log. Intermediate stops of the path were silent before already. Keyboard echo with a real Windows voice: the long pause between letters is gone Simple: If you use Sku with a real Windows voice (not the NVDA bridge), typing in chat left a pause of half a second to a full second after every letter - about one letter per second, no matter how fast the voice was set. A slower voice only made the letter longer, the pause stayed the same. That pause is gone now, the letters follow each other closely. Every letter is still spoken, doubled ones included, and no letter follows after Escape. The same pause sat between lines waiting behind each other (parts of a quest text, several chat lines); it is hardly noticeable there, but it is shortened as well. Nothing changes on the NVDA bridge. Technically: The client reports VOICE_CHAT_TTS_PLAYBACK_FINISHED a fixed 0.5 to 1 s AFTER the audio ended ("End" bookmark) and starts nothing new before it. Handing over earlier does not help (v43.5e: the utterance is only parked and can then no longer be cancelled). What frees the client at once is a stop. New in Libs/SkuVoice-1.0: once "Start" and then "End" arrived for our utterance and something is waiting (echo lane or queue), OnUpdate sends a StopSpeakingText after tTailCutDelay (0.03 s) and hands over after tTailCutHold (0.06 s). Never from the bookmark handler. An "End" before "Start" (happens on the first utterance after a stop) does not count. If nothing waits, the line ends by itself as before. The cut does not set the bridge mark, blocks the "#queue > 1" bypass once and drains the duplicate-guard entry itself (a stopped utterance reports no FINISHED). Measured in the offline model: letter cycle 1.15 s -> 0.73 s. For trying, session only: /skudebug tts tail , /skudebug tts tail off. In the log: "tail cut -> StopSpeakingText". Speech from other addons confused the keyboard echo Simple: If another addon (or a /run command) spoke through the game's text-to-speech while you were typing, a letter could follow after "cancelled", that is after closing the chat box. Only with a real Windows voice. Fixed: while foreign speech is playing, Sku holds back its letters and chat lines; if you close the box, the waiting letters are dropped. Announcements that interrupt everything anyway (menu, "cancelled", priority lines) still interrupt the foreign speech at once, as before. Technically: The client gate only knew Sku's own handovers. A foreign utterance (capture: id 403, parked, started 11 s later under the first typed character) left the next character without a STARTED, the 0.5 s TTL gave up on it, a second one went into the client and took the first one's STARTED for its own. New (mForeign, tMaxSeenId, tClientGateFreedAt): a STARTED nobody is waiting for, or one with an id below the highest seen before our handover, is foreign - never adopted as our id, and nothing is handed over while it plays (silence ceiling 8 s; our own stops end it). On the bridge every id is 0, so "older" is strictly-less and never fires there. In the frame in which one of our utterances reports FINISHED nothing is handed over: a parked foreign one starts on exactly that FINISHED and has to be seen first. Harness scenario "foreign": old build four letters after "cancelled", new build none. In the log: "BTTS foreign utterance", "BTTS foreign cleared". Pasting into an input field: "Pasted" instead of a string of letters Simple: If you pasted text with Ctrl+V into the chat line or into one of Sku's input fields, the whole pasted text was read out letter by letter. Sku now says "Pasted" once. Read the content of the field as usual with arrow up or arrow down. Normal typing is unchanged, fast typing included. If you paste only one or two characters, they are still spoken as characters. Technically: A paste delivers the clipboard as individual OnChar calls, all in the same frame. tSpeakTypedChar (SkuZOptions/Core.lua) counts characters per frame (GetTime() is constant within a frame): from the third on the run counts as a paste - CancelEcho drops the characters already waiting (none has been handed over in that frame, the pump runs in OnUpdate), then one SpeakEcho("Pasted"), every further character of the frame is ignored. Threshold 3 because two keystrokes can fall into one frame at a low frame rate. Applies to AttachInputEcho (chat) and the Sku edit box. In the log: "inputEcho paste erkannt". ------------------------------------------------------------------------------------------------------- Changes in Sku v43.6 Overview Changes Profession windows: the recipe list now shows the whole profession with categories you can fold and unfold, instead of a scroll list of 8 entries. Profession windows: Page Up and Page Down jump from category to category. Profession windows: the "materials available" filter is a regular switch and no longer leads to empty pages. Bugfixes Chat: "whisper sender" and "send message to channel" in the line menu (Ctrl+Enter on a chat line) did nothing at all on some machines - the chat line never opened. Whispering from the target menu and from the dungeon browser did nothing on the same machines either. Keys: if you started v43 once, went back to v42 and re-bound Shift+F9 to F12 there, v43 left you without a key for the waypoint list, route destinations, action bars and "end route" - the keys are now carried over after the fact. Classic Era: the list of uninterruptible casts aborted with an error while loading; it now loads on every client. Profession windows: the recipe tooltip (Shift+Arrow-down) left out the name of some reagents, you only heard numbers like "0 2". Chat: "whisper sender" did not open a chat line Simple: If you opened the line menu in the chat with Ctrl+Enter and chose "whisper sender", on some machines nothing happened: no sound, no chat line with the sender's name. The same went for "send message to channel" and "send item link to channel". "Copy chat line" and "copy sender name" kept working because they take a different path. All three entries open the chat line again, and the target channel is announced as usual ("Tell Kai"). Technically: SkuChat:SetEditboxToCustom called the global ChatEdit_UpdateHeader and only showed the edit box AFTER it. On the 2.5.6 client that name is no longer a real function but an alias from Blizzard_DeprecatedChatInfo (Deprecated_ChatFrame.lua), and that whole file returns immediately when the CVar loadDeprecationFallbacks is off. The name is then nil, the call throws, and Show() and SetFocus() never run. New: SkuChat:UpdateEditboxHeader calls the real frame method ChatFrame1EditBox:UpdateHeader() and keeps the global name only as a fallback. SkuChat_CanAddChannel reads Constants.ChatFrameConstants.MaxChatChannels instead of the deprecated MAX_WOW_CHAT_CHANNELS. Whispering from the target menu and the dungeon browser did nothing Simple: "Whisper" in the menu for your current target, and for a group leader in the dungeon browser, opened no chat line on the same machines - without any message. Both work again. Technically: Both places called ChatFrame_OpenChat behind an "if it exists" check. That too is only an alias from Blizzard_DeprecatedChatInfo; when it is missing, nothing happened, silently. ChatFrameUtil.OpenChat is used now, the old name only as a fallback (SkuMob/Options.lua, SkuCore/dungeonBrowser.lua). Keys: Shift+F9 to F12 led nowhere after switching v43 - v42 - v43 Simple: This hit anyone who had started v43 once, then went back to v42 and put quick access 1 to 4 on Shift+F9 to F12 again there. On the next v43 start the fixed actions (waypoint list, route destinations, action bars, end route or waypoint) had no key any more, and the four keys called old quick-access paths instead. One of those pointed at a menu entry that no longer exists: the menu opened and the cursor stayed on "Target menu" - it looked exactly like the earlier "waypoints are still loading" hang. Sku now recognises this state at startup and moves the four keys onto the fixed actions by itself. Your own keys on the quick-access slots are left alone. Thanks to Kai for the precise analysis (issue 7). Technically: tMigrateQuickKeys (SkuZOptions/SkuKeyBinds.lua) took the mere presence of SKU_KEY_NAVWAYPOINTSQUICK as "already migrated". v43 had created the entry on its first start, v42 emptied it again when it assigned the keys to MENUQUICK1 to 4 - present, but empty. New is a rescue pass: if NAVWAYPOINTSQUICK, NAVROUTEDESTINATIONSQUICK and ACTIONBARSOPEN are all three empty AND at least one of MENUQUICK1 to 4 holds one of the v42 default keys SHIFT-F9 to SHIFT-F12, the carry-over runs once more - in that pass only for the slots that hold such a default key. Whoever cleared the new keys on purpose therefore keeps every quick-access key of their own. The pass runs once: afterwards the new entries are no longer empty. Classic Era: the list of uninterruptible casts did not load Simple: On Classic Era one of Sku's data files aborted with a Lua error at login. That left Sku without the list "Announce interruptible casts only" works from. The file now loads completely on every client. Nothing changes on TBC Anniversary. Technically: SkuDB/uninterruptibleCasts.lua builds its name keys with GetSpellInfo(id) right inside the table constructor. Era does not know the TBC spells 29121 (Shoot Bow) and 33808 (Shoot Gun), GetSpellInfo returns nil, and "[nil] = true" raises "table index is nil" - the constructor aborts, SkuDB.uninterruptibleCasts.spells and .npcSpells stay nil. The file now uses a local wrapper that returns a placeholder for an unknown id, one no cast name can ever equal. The entry is inert on that client, the constructors stay line-identical to the upstream source (ClassicCastbars), and the same guard covers the npcSpells lines, where a nil would have raised a concatenation error. Profession windows reworked: foldable categories instead of 8 entries per page Simple: The profession windows (all crafting professions, enchanting and beast training) now behave consistently. Until now the menu only ever showed the 8 rows the Blizzard window was displaying, plus "scroll up" and "scroll down". Now the whole profession is one list: each category as a heading with its recipes below it. Enter on a category folds or unfolds it, the new state is spoken, and Sku remembers it per character and profession. Page Down and Page Up jump to the next or previous category. "Selected" and the create buttons are still at the end of the window. If two categories share a name (tailoring has "Cloth" twice), the second one is called "Cloth 2". The "materials available" filter is now a regular switch: Enter flips it and the cursor stays where it is. With the filter on, recipes without materials disappear, and so do categories with nothing to make. You no longer end up on empty pages with no way out, and enchanting no longer has pages with only the headings left. The list updates itself after crafting and only speaks when the entry under the cursor is gone. Technically: Build_TradeSkillFrame and Build_CraftFrame (SkuCore/LocalMenu.lua) read the list from GetTradeSkillInfo / GetCraftInfo instead of scraping the rows TradeSkillSkill1-8 and Craft1-8; selection goes through TradeSkillFrame_SetSelection / CraftFrame_SetSelection. Blizzard's TradeSkillOnlyShowMakeable is no longer used: it only resets the scroll position when the SELECTION index falls off the shortened list - otherwise the offset stayed behind the end of the list, and all 8 rows and the scroll bar were gone. The filter now checks numAvailable in both windows; the old resFilterApplied cache (per profession, although Blizzard's switch is global) is gone. New: TRADE_SKILL_UPDATE and CRAFT_UPDATE rebuild the menu, debounced and only when the list shown has changed (SkuCore:RefreshProfessionMenu). The window list builder has two new fields: "toggle" (a MakeToggleNode switch inside a window) and "isSectionHeader" (jump target for Page Up/Down). The filter state is now stored under the profession name instead of the window title; an old value is carried over. Profession windows: the recipe tooltip named some reagents without a name Simple: In a recipe's tooltip (Shift+Arrow-down) a few reagents showed only the count, for example "0 2" instead of "Bleach 0 of 2". It hit items the game client has never seen - mostly pure vendor goods such as bleach, dyes or flux. Cloth and leather are almost always known to the client already, which is why it looked random. Sku now says "loading" in that place and fetches the name in the background. Close the tooltip and press Shift+Arrow-down again: the name is there. This happens once per item, after that the client knows it for good. Technically: GetTradeSkillReagentInfo / GetCraftReagentInfo return no name for an item that is not in the item cache, and the reagent link then carries empty brackets "[]". The fallback wrote a "?", which TTS does not speak. The finished text was also stored in the menu node's textFull and never rebuilt, so the fault stayed even after the item had loaded. New (SkuZOptions/Core.lua, SHIFT-DOWN): with no name, the item id is read from the link and GetItemInfo(id) is called, which also requests the item from the server. An incomplete tooltip is marked skuRecipeIncomplete and rebuilt on the next key press. Every case writes a "recipeTooltip" line to the debug log (recipe index, reagent, link). ------------------------------------------------------------------------------------------------------- Changes in Sku v43.5-tools - installer 5.2 and login tool 3.4, no new addon version Overview New Login tool 3.4: Blizzard's social contract is recognized, scrolled to its end and accepted - until now a new blind player was stuck in front of it. Login tool 3.4: the PvP warning shown when creating a character on a PvP realm is read out and answered with Enter or Escape. Changes Login tool 3.4: hardcore and PvP warnings are found at every screen resolution and window size, not only on large screens. Login tool 3.4: every voice is tried out inaudibly before it is accepted - a broken voice can no longer mute the tool. Installer 5.2: the log bundle now contains a list of all Windows voices (voices.txt). Installer 5.2: steps against false alarms from Windows Defender - the installer file names its product and author, and the bundled NVDA bridge file ships without a leftover developer signature. Bugfixes Login tool 3.4: "Select voice" was empty on some machines, and first-time setup could never get past it. Login tool 3.4: Blizzard's social contract is accepted automatically Simple: New accounts - and everyone else whenever Blizzard revises the text - are shown a "social contract" on character selection. The window swallows every key, "Accept" is locked until the text has been scrolled to the end with the mouse, and "Decline" quits the game. The tool stood silent in front of it, or at best said "unknown screen". It now says "Blizzard's social contract is open. Accepting it, please wait.", scrolls the text to the end, presses Accept, checks that the window is gone and then builds the character list as usual. The same happens when the contract comes back in the middle of a session. If it does not work, the tool says what a sighted person has to do once and tries again after a minute. Decline is never pressed. Technically: The dialog is SocialContractFrame (Blizzard_GlueXML\SocialContract.xml/.lua) and is triggered by the SERVER (C_SocialContractGlue.GetShouldShowSocialContract -> SOCIAL_CONTRACT_STATUS_UPDATE); no CVar or setting exists for it, so it can neither be provoked nor answered in advance. The old code could not work on the Anniversary client: the helper's check wants the dimmed yellow logo, which the BC glue screen does not have, and its third probe sits in the middle of the contract's text - check false, screen "unknown", the tool never entered login mode; the click half was a blind sweep of 34 clicks. New: eight Sc* probe points in data.ini (derived from the XML: frame 510x632, buttons at ui y 706..728, NOT a DefaultScaleFrame) plus IsSocialContract, AcceptContract and HandleSocialContract in flows.ahk and two CheckMode branches in menu.ahk - no rebuilt helper. Verified with /validate, zero false positives on all reference screenshots and hits on synthetic contract images at three resolutions. Still untested on the real dialog, because it cannot be produced on demand; every attempt writes its probe values to log.txt as "SocialContract:" lines. Login tool 3.4: hardcore and PvP warnings at every resolution, PvP creation warning new Simple: The hardcore warning on the realm list, the hardcore rules shown when creating a character, and the PvP warnings are windows Blizzard keeps the same size in real screen pixels. Their position therefore depends on the resolution, and the tool's fixed probe points were only right on large screens (1200 pixels high and up). On a common 1080p screen, or in a small window, the tool would have stood silent in front of the hardcore rules. It now searches for these windows itself and finds them at any resolution. New with it is the PvP warning shown when creating a character on a PvP realm: it is read out, Enter agrees, Escape declines. The PvP warning on the realm list looks exactly like the hardcore one and was therefore already being read out. Technically: HardcorePopUpFrame (240 and 580 high) and RealmWarningPopUpFrame (240 and 360 high) inherit DefaultScaleFrame and are scaled by GetDefaultScale() - a client function with no Lua source. Measured in game with /wdeval: 0.64 at 2880x1800, where 768/height would be 0.43; so there is a floor, and 1080p would presumably give 0.71. Instead of assuming the scale, GlueDialogScan (flows.ahk) finds it: in all four shapes both buttons lie on a straight line out of the screen centre and only the distance depends on the scale. One screen grab (new class ScreenGrab in ui.ahk, 1000 pixels in 16 ms), then scale 0.60..1.25 per height: red at three columns, no red in the gap and just outside, black at the backdrop point. The fixed probes remain the first check, the scan the second. Clicks (GlueDialogClick) and OCR text bands (GlueDialogBand) follow the found scale. Verified with a Python port on 66 synthetic dialogs (6 resolutions, 3 heights, 4 scales): height always right, scale within 0.012, zero false positives. Still untested on real dialogs; every find is logged to log.txt as a "GlueDialog:" line with its scale. Login tool 3.4: empty voice selection fixed, first-time setup no longer gets stuck Simple: On some machines Windows cannot deliver the list of installed voices although speech itself works - for instance after a third-party voice was removed uncleanly or only half installed. Since 3.3 the tool no longer crashes there, but "Select voice" was then empty: right arrow did nothing, and because first-time setup only moves on to the language after a voice was picked, there was no way through. The tool now reads the voices from the Windows registry itself in that case. If that finds none either, the menu holds the entry "No voices found, press Enter to keep the system voice" - Enter keeps the voice the tool is speaking with and setup continues. Technically: The user's log bundle showed "GetVoices FAILED, voice list empty: (0x80045039)" four times, then "SwitchToSetup". Not a single token was unreadable (the 3.3 case) - sap.GetVoices() or .Count itself throws SPERR_NO_MORE_ITEMS while sap.Voice.GetDescription() works. New: RegistryVoiceTokens() in sapi.ahk builds one SAPI.SpObjectToken per key under Speech\Voices\Tokens (HKLM and HKCU) via SetId - on the failure path only. Only entries with a name and a CLSID are listed, because a broken entry does not throw here, GetDescription() simply returns "". AddVoiceNodes() in menu.ahk replaces the three identical loops and adds the fallback entry when the list is empty. Checked with a harness that loads the real sapi.ahk and makes GetVoices throw exactly this error: same voices as the normal path, all settable, two deliberately broken entries skipped. The actual failure could not be reproduced; still untested on the user's machine. Login tool 3.4: a broken voice can no longer mute the tool Simple: A voice whose program was removed but whose entry stayed behind in Windows still shows up in the voice list. Picking it was accepted by Windows without any error - and the tool was mute afterwards, from the main menu even permanently, because the choice is saved. The tool now tries every voice inaudibly before accepting it. If it does not work, everything stays as it was and the tool says "This voice does not work, choose another one". At startup a saved voice that no longer speaks is not applied either; the Windows default voice stays in place. Technically: Measured with a registration that has a name and a CLSID but no engine: sap.Voice := token is accepted, only Speak throws 0x80040154 (class not registered). VoiceWorks() in sapi.ahk therefore speaks on a separate SpVoice into an SpMemoryStream, 16 to 31 ms, and never touches the live voice. SetSapiVoiceByName and SetToolVoiceByName now return success, VoiceSelectAction and ApplyToolVoice act on it. SAPI2SR is exempt: the bridge hands text to the screen reader whatever the output stream is, so the test would be heard. A voice that loads but produces only silence is not caught by the test. Installer 5.2: the log bundle lists all Windows voices Simple: "Collect logs" in the installer now also puts a file voices.txt into the bundle. It names every voice registered in Windows and whether the program behind it still exists. With voice problems in the login tool or in the game this shows at once which entry is broken. Ships with installer 5.2. Technically: LogCollector.WriteVoiceRegistrations reads Speech\Voices\Tokens, Speech\Voices\TokenEnums and Speech_OneCore\Voices\Tokens in the 64-bit and the 32-bit registry view (the login tool is 64-bit and cannot see 32-bit-only voices) and in HKCU, plus DefaultTokenId. Per entry: name, CLSID, the InprocServer32 DLL with a check whether it is registered and present on disk, VoicePath/LangDataPath and the attributes. Installer 5.2: steps against false alarms from Windows Defender Simple: Some players reported that Windows Defender removed the freshly downloaded installer as dangerous. The installer is not dangerous; our own Defender scan with current signatures finds nothing in it. Two things that made the file look more suspicious than it has to are fixed: its file properties now say what it is and who makes it (product "Sku Installer", company "Sku", author JeanStiletto, a link to the project) instead of being blank, and one file of the bundled NVDA bridge no longer carries a signature that came from the developer's own PC and meant nothing on anyone else's. To be honest about the limit: the installer is still not signed with a bought certificate, so every new build starts with no download reputation and Defender can still hold it back in its first days. If that happens, open Windows Security, Protection history, choose the entry and allow or restore the file - and please send us the name of the detection. Technically: SkuInstaller.exe was and is not Authenticode-signed; release.ps1 has no signing step, only the bridge DLLs are signed, on the user's machine, by sku-nvda-voice-sign.ps1. An unsigned exe has only per-hash cloud reputation, and the v43.5 exe was one day old with nine downloads when the reports came in; the local scan (engine 1.1.26080.3, signatures 1.459.278.0) is clean for the exe, the bridge payload and the bundled AutoHotkey, so the verdict is cloud/ML or behavioural, not a static signature. Changed: Company, Product, AssemblyTitle, Description, Authors and Copyright in SkuInstaller.csproj (every field used to read "SkuInstaller", copyright empty). And x86\sapi2sr_engine.dll inside payload\sapi2sr-payload.zip had been copied back out of the developer's Program Files and still carried that machine's throwaway signature ("signed by an untrusted root" everywhere else); it is now the bare DLL, 123320 -> 115712 bytes. The Authenticode PE hash excludes the signature, so both pins in the signing script still match (5A76A572... x64, 1281E2B9... x86) and the on-machine signing is unchanged. dev\rework-docs\nvda-voice-signing\strip_payload_signature.py does the strip for the next payload refresh. Not changed, and still what a behaviour engine sees: elevated run, embedded executables, regsvr32 /s, a hidden PowerShell that adds a code-signing certificate to the machine's root store, self-replacement. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.5 Overview Bugfixes Quest database: opening "All" (and "Starts in zone", "Nearby quests") froze the game for several seconds. Chat: after opening the chat line with "/" the old channel was announced (e.g. "Tell Kai"), although only the command typed next decides the channel. Speech: single lines randomly stayed silent over the NVDA/SAPI bridge (e.g. one item in the equipment list) while their neighbours were spoken. Quest: "Raene's Cleansing" (Iron Pommel, Ashenvale) had no quest target; it now points at the Rusty Chest the Rotting Slimes leave behind. Speech: typed characters and cancelled announcements were spoken late - letters from a long-closed chat line turned up seconds later in a different menu. Login tool 3.3: a damaged Windows voice on the PC made the tool quit with an error window (GetVoices, 0x80045039) right after announcing its version. Speech: scrolling quickly through long entries over the NVDA/SAPI bridge read the previous entry to the end before the one you stopped on. Quest database: opening the quest lists froze the game Simple: Opening the "All" list under Quest > Quest database had caused a freeze of several seconds since v43.2. The list opens instantly again; a quest's entries (accept, target, turn-in ...) are built only when you enter that quest. The same applies to "Starts in zone" and "Nearby quests". Technically: "All" built the complete submenu (CreateQuestSubmenu) for every one of the several thousand quests in the database up front. Since v43.2 that submenu's target branch resolves the nearest route waypoint for quests with a trigger location (triggerEnd) geometrically (GetTriggerEndWps) - a full scan of one continent's waypoint cache - and since v43.3 for every such quest instead of only those without other objectives. One open therefore cost around 170 continent scans in a single frame. The quest entries are now dynamic nodes with their own BuildChildren, as "Current Quests" always was. Chat: wrong channel announcement after opening with the slash key Simple: If you had last whispered someone and opened the chat line with "/", you heard "Tell Kai" - and right after "/g " then "Guild". The first announcement was wrong: as long as the line starts with a slash nothing goes to that channel, the command decides. The announcement now stays silent while a command is in the line, and speaks the channel once it is settled ("/g " becomes "Guild") or when the slash is deleted again. A deliberate switch to "Say" ("/s ") is now announced as well, unless Say was the default channel anyway. When recalling a sent line (arrow up), the channel prefix is omitted if the line is a command. Technically: Blizzard's header always shows the chatType attribute, and on open that is the sticky channel of the last sent message (whisper is sticky too, tellTarget included). The slash key calls ChatFrameUtil.OpenChat("/"): the same header plus a "/" in the box, set only in the next OnUpdate (editBox.setText), so the UpdateHeader calls of the open still run with an empty box. Text starting with "/" never goes to the header's channel: only when ParseText resolves the command ("/g " at the space, "/w Name " after the name) does Blizzard set chatType, SetText(rest) and then UpdateHeader. Sku now checks the effective text, pending setText included (SkuChat:EditboxTextIsCommand), at every announce site (UpdateHeader hooks, focus, deferred speak); a new OnTextChanged edge covers deleting the slash, for which Blizzard calls no UpdateHeader. Speech: single lines randomly stayed silent Simple: Walking quickly through a list, now and then one single line was not spoken - e.g. "Naga combat gloves" in the equipment list while its neighbours were - and it stayed silent on repeated passes. This affected the NVDA/SAPI bridge and could happen anywhere in Sku, mostly after a /reload. Every line is spoken reliably again. Technically: The WoW client caches rendered speech per text and replays it on a repeat without invoking the voice again - on the bridge that replay is silence. Sku therefore appends a per-text counted run of no-break spaces (1..64) to every text so that each repeat is a new text. But the counter lived in Lua only and restarted at every /reload, while the client's cache survives a reload (the utterance IDs run straight across it). A line seen again after the reload could thus land back inside an already rendered range and stayed silent until the counter had walked out of it - six presses in a row in the log, each with a clean SpeakText + STARTED + FINISHED. The counters now live in SkuOptionsDB.global (bttsCacheBust) and continue across the reload; in addition the per-text space is 512 variants instead of 64 (leading no-break spaces 1..8 carry the high part of the counter). Quest: "Raene's Cleansing" (Iron Pommel) had no quest target Simple: Quest 1027 "Raene's Cleansing" in Ashenvale asks for the last piece of Dartol's Rod, the Iron Pommel. Sku knew no place for it, so "Quest target" stayed empty. The quest target now leads into the slime area south-east of Raynewood Retreat: the Rotting Slimes there leave a Rusty Chest behind when killed, and the pommel is inside. Technically: Item 5519 (Iron Pommel) correctly points at object 19021 (Rusty Chest) in the database, but that record carried no spawn coordinates, neither in objects.lua nor in objects_fixes.lua - the item branch of GetResultingWps found nothing. objects_fixes.lua (TBC and Era) now adds Wowhead's 44 chest positions in Ashenvale (zone 331, roughly 68-78 / 68-80); that is the same patch as the spawns of the Rotting Slime (3928) that leaves the chest. Speech: typed characters and cancelled announcements were spoken late Simple: If you typed a longer message and then closed the chat line, you kept hearing the letters afterwards - one by one, while you had long since moved on to another menu; in one capture for almost a minute. The same applied to a cancelled announcement: after "Cancelled" a remnant of the old line followed, for instance a distance readout from the waypoint menu you had just closed. Whatever is still pending when you close or cancel is now discarded. Typing echo stays complete - no letter is lost. Technically: The client accepts every SpeakText without limit but plays them strictly one at a time, and C_VoiceChat.StopSpeakingText only cancels the utterance that is PLAYING - there is no queue purge like SAPI's PurgeBeforeSpeak. Anything already handed over is therefore unrecallable. The typing lane used to hand over one utterance per keystroke, three to four a second, while the client manages about one: a typed line of 104 characters parked around 80 utterances inside the client, which then dribbled out one per second. For the same reason CancelBttsOutput and EndScope had no effect - they empty Sku's own queue, which the characters were never in (85 of 93 EndScope calls in one capture dropped nothing). The characters now wait inside Sku, only one goes to the client at a time, and the next only on its PLAYBACK_FINISHED - the moment the client will accept another utterance at all (every STARTED in the capture coincides with a FINISHED). Nothing is ever handed over that is not yet playing, so a cancel reaches everything. A short kill window covers the remainder: after a hard cancel nothing is handed over and any utterance that starts regardless is stopped at once. This mirrors NVDA's structure, which likewise keeps the queue itself and gives the voice one utterance at a time. Login tool 3.3: crash at startup caused by a damaged Windows voice Simple: After a fresh install the tool still announced "version 3.2" and then quit with an error window ("Error: (0x80045039) Specifically: GetVoices"). The trigger was a damaged voice in Windows - for example an uninstalled voice that left remnants behind. The tool now skips such a voice, notes it in log.txt and starts normally; the voice is just missing from the "select voice" menu. Technically: 0x80045039 is SPERR_NO_MORE_ITEMS: SAPI reports N voices but one of them cannot be read (a broken entry under HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens). The tool died building the voice menu, in the for-loop over sap.GetVoices(). GetVoices() and SetSapiVoiceByName() now go through the new ReadableVoiceTokens(), which reads each voice by Item(i) in its own try; an unreadable one is skipped and logged, and if even the list fails it stays empty. SapiInit() reads the default voice under the same protection. Not yet tested on a PC with a broken voice. Speech: fast scrolling over the NVDA bridge read the previous long entry to the end Simple: With the NVDA/SAPI bridge voice, moving quickly through a list with long entries (the waypoint list, quest texts, chat history) first read the previous entry to the end and only then the one the cursor was on. Now the old entry is cut off after a moment and the entry you stopped on is read, as with a Windows voice. Longer texts that come in several parts are still read completely, and typing echo is unchanged. The fix needs the updated NVDA bridge that the Sku installer 5.1 installs: update through the installer (not only the addon zip) and restart WoW completely afterwards. Technically: The game's StopSpeakingText only stops the client's own playback. On the bridge the text is already in NVDA's queue at that point, and SAPI never passes "purge before speak" on to an engine, so nothing on the path could interrupt NVDA. The only thing that reaches the engine is the utterance itself, markup included - so the intent now travels inside it, as with Prism's speak(text, interrupt): every stop sets a flag, and the next line carries . The engine shipped with the installer (SAPI2SR with Sku's patch C) calls nvdaController_cancelSpeech when it sees that bookmark and then forwards the text. Lines handed over without a stop in front carry no mark and keep queueing. Real SAPI voices consume the bookmark silently; an older engine skips it. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.4 Overview New Pawn: Addons > Pawn holds Pawn's tooltip switches, and Sku tooltips name the upgrade in percent, the difference per attribute and missing attributes. By Yennesta. New key "open damage meter" jumps straight to the Details reports; the key bindings gain their own "Addons" group. Changes Chat: on send or cancel a still-running typing echo is reliably stopped, while the readback of the sent message is never cut. A menu's or window's announcements end with the window: whatever is still waiting when it closes is dropped, whatever is playing is stopped. Bugfixes Mac: arrow, function and page keys arrived twice - empty bag and bar slots were skipped, and the tooltip reader restarted from the top on every Shift+Down. Raid health monitor: a manually assigned role landed on the wrong player, and without role detection the monitor stayed silent. Merchant: the buyback tab read and bought the item with the same number from the normal stock. The equipped-item comparison in tooltips was sometimes missing - the scanning tooltip had lost its owner. Tooltip reader: at the end of a text, for instance in the friends list, Shift+Down alternated between silence and the last line. Announcements requested by key (target health, distance, target ...) stayed silent on a second press within a second. Mac: arrow and function keys arrived twice Simple: On the Mac, fast scrolling skipped empty bag and action-bar slots, and the tooltip reader started over on every Shift+Down. Both are fixed; Windows was never affected. Technically: For keys without a character - arrows, F keys, page up and down, home, end, insert, delete - the Mac client passes the operating system's stand-in characters from the private-use Unicode area (U+F700 upwards) through OnChar; Windows fires no OnChar for those keys. Every arrow press therefore reached the menu twice: as the bound key and as a phantom character forwarded unchecked into the key handler. There the double-tap counter saw two presses per press (every "Empty" entry skipped), and the rule "any other key closes the reader" closed the tooltip reader after each Shift+Down. The menu now forwards from OnChar only characters that type-ahead knows (a positive list), the two typing-echo hooks drop the whole private-use area U+E000 to U+F8FF, and a double tap now requires the same key twice. Dropped characters go to the debug ring with their bytes so a tester can prove the phantom on their client. Diagnosed and first guarded by Yennesta (PR 19). Raid health monitor: roles on the right player Simple: Assigning a role to a raid member by hand often hit a different player as soon as the subgroup order differed from the join order. And without automatic role detection the monitor stayed completely silent for that player. Technically: The assignment menu numbers members by their audible, subgroup-sorted number and stored the role under that number; the health evaluation read it via the real raidN index. The menu keeps its numbering and retains the real index per entry, under which it stores and reads. Without a role (SkuAuras off, or none detected) the role id stayed nil, and UNIT_HEALTH indexed the filter table with it - aborting the whole evaluation. Role resolution is now one function: assignment, then SkuAuras, otherwise the "No role" bucket. By Yennesta. Merchant: buyback buys back what was sold Simple: On the buyback tab Sku named the wrong item and, on confirmation, bought the merchant's offer with the same number instead of the sold piece. Technically: The buyback tab reuses the buttons and numbers of the normal stock. Sku read them with SetMerchantItem and bought with BuyMerchantItem, both referring to the stock. With the buyback tab active the query now goes through SetBuybackItem and the purchase through BuybackItem; a buyback is one stack, so there is no quantity submenu there. By Yennesta. Pawn: upgrade and stat differences in the tooltip Simple: With Pawn installed, an item's Sku tooltip names the upgrade in percent and the scale right below the name, appends to each attribute line the difference to the equipped item (spoken "plus" or "minus", damage per second with one decimal) and lists attributes the equipped item has and the new one lacks. Addons > Pawn holds the switch "Pawn in Sku tooltips" plus Pawn's own tooltip settings; without Pawn it only says that Pawn is missing. By Yennesta. Technically: New file SkuCore/pawnIntegration.lua; every Pawn call is guarded and the tooltip pass runs in pcall, without Pawn nothing is added. Compared with the contribution: the switch is a default in the SkuCore settings schema (on), the line match ignores case (on English clients the stat is "Damage Per Second", the tooltip line "damage per second" - the DPS difference was silently dropped there), and a failing pass logs its error to the debug ring. Key "open damage meter" and key group "Addons" Simple: A new key, unbound by default, jumps straight to Addons > Damage Meter > Reports. The key bindings now have an "Addons" group holding the keys for AtlasLoot and the damage meter. Technically: Bound and dispatched like the dungeon browser key; the menu path goes by node ids (Addons, DamageMeter, Reports) and thus works in every language. The report entries deliver their text when read (a function instead of a table, like the auction house entries): fresh Details data, no cost for unread fights, and it works when the cursor lands on an entry by key without passing through OnEnter. Key and ids by Yennesta. Equipped-item comparison sometimes missing Simple: On Shift+Down over an item the section "currently equipped item" was sometimes missing, seemingly at random. It is always there now. Technically: Sku reads tooltips through one shared, invisible GameTooltip. A GameTooltip only fills its lines while it has an owner, and loses it by itself: a call without content (an empty bar, bag or equipment slot, an item not yet loaded) hides it and clears the owner. From then on every query silently returned nothing until another path happened to call SetOwner. Three places had already fixed this for themselves, which made the fault look random. All 18 query sites now re-own before every read through one helper. Tooltip reader: the end of a text Simple: In the friends list (and everywhere else) the reader did not stay on the last line with Shift+Down but alternated between silence and the last line - like an endless loop. It now reads the last line on every press. Technically: At the end of a text the reader hands the same line to the voice layer again. Its v43.2 duplicate guard drops a line identical within one second unless it is marked as key-triggered - and the reader never marked. Every reader line, links included, carries the mark now. Announcements requested by key no longer stay silent Simple: Target health, distance, target announcement, roll info, turn to beacon, waypoint keys and the like: a second press within a second with the same answer stayed silent. The answer now comes on every press. Technically: The same duplicate guard; only the menu's key handler had marked so far. Rather than marking every announcement individually, the three key dispatchers (main frame, SkuCore control frame, SkuNav) remember the frame of their call, and the voice layer treats every handover within 0.1 seconds of it as key-triggered. A menu rebuild's re-announce comes from a timer in later frames and falls outside that window; the menu's key handler keeps marking explicitly. Speech scopes: announcements end with their window Simple: Two cases with slow SAPI voices: after send or cancel in chat the voice finished reading a line recalled with arrow-up, and after learning three skills at a trainer and pressing Escape the trainer was announced three more times. Both are gone: the chat echo is cut on close - but never the readback of the sent message - and whatever a menu or window still had to say when it closed is dropped or stopped. Combat, chat and navigation announcements are not affected. Technically: A keypress alone still cancels nothing (a v43.2 rule), and on the screen reader bridge the end of an utterance cannot be determined because FINISHED arrives in the handover frame - the "something is speaking" bookkeeping is always empty there, which is why even the stop on menu close was suppressed as pointless. What is known exactly is what was handed last and for whom. A line can now carry a scope ("menu" for menu announcements, "echo" for the typing lane), and SkuVoice:EndScope drops that scope's waiting lines when it ends and stops the voice only if the running utterance belongs to it. It is called on menu close and in the Hide hook of every tracked window, so also on the game's own Escape. The chat box's soft stop now asks SkuVoice:IsEchoInFlight instead of the one-second rule: echo is cut however long it has been running; if anything else has been handed since - above all the server's readback of the sent message - nothing is stopped. Yennesta proposed the unconditional cancel in PR 19; this is the targeted form of it. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.3 Overview New The installer now exists for the Mac too - install, update and self-update just like on Windows. Thanks to Yennesta. Installer 5.0: seven proven companion addons such as Questie and Deadly Boss Mods can be installed alongside and are kept up to date - Windows and Mac, Anniversary and Classic Era. Mac: the installer and the login tool speak German, English and French; the login tool recognizes French clients too. Changes Turn to unit and Turn to target now turn like the beacon turn: shortest way around, at most a quarter second. Bugfixes Speech: individual announcements stayed silent even though the sound played - mostly the short lines you hear over and over. Interact key: after a kill it sometimes grabs a far-off mob and runs at it instead of looting - Sku now warns you with a sound. Mac: the voice read technical control markup along with every line. Stable master: the menu showed and swapped the wrong pet slots, and the swap announcement reported failure too early. Chat: a combat log tab stayed completely silent. Dial Targeting was completely dead on the anniversary client - it works again, tested in a 25-player raid. French: four chat settings texts still spoke English. French: four chat texts still spoke English Simple: On a French client, the arrow-keys setting for the chat input, the channel announcement setting with its explanation, and the message for an unknown slash command could still be heard in English. They are translated now. Technically: The four v43.1 texts were missing from the translation store the French locale file is generated from. While fixing that, 29 texts hand-written into the generated file for v43.2 turned out to exist only there - the next generation run would have silently reverted them to English. All of them are now in the store, and the generation tool only writes the file on an explicit command instead of on any unrecognized argument. Speech: repeated announcements no longer go silent Simple: Navigating menus and tooltips, you would now and then get only the sound, with the announcement itself missing. It mostly hit the short lines you hear over and over all evening - "menu closed", "accept", "buffs", or a single typed character. Technically: The client caches synthesized speech by text and, on a repeat, replays it from that cache without invoking the voice again. Across a bridge to a screen reader that replay is silent. Sku therefore appends an inaudible marker to every line so each call is generated fresh - until now from a counter that restarted every 64 announcements. A line that came up again exactly 64 announcements later was therefore character-identical and hit the cache. The counter now runs per text instead of shared across all announcements; a newly seen line still takes its starting value from the shared counter, so that clearing the table (at 512 distinct lines) cannot throw a line back onto its own first value. Replayed against a recording of 1307 announcements: 27 character-identical repeats before, 80 with a per-text counter lacking that seeded start - worse - and 0 as shipped. Mac: the voice no longer reads control markup Simple: On the Mac, the voice audibly spoke control characters along with lines that are meant only for the machinery. They are gone there now. Technically: For the handshake with the screen reader, Sku appends a marker to every line; the Mac client does not filter such markup out of the speech and read it verbatim. On the Mac the marker is now withheld; the inaudible characters that defeat the repeat cache stay, so every repeat is still generated fresh there too. Idea by Yennesta. Turn to unit: faster and more precise Simple: "Turn to unit" and "Turn to target" now turn exactly like the beacon turn: the shortest way around, in at most a quarter second, instead of searching clockwise for up to a second and a half. Technically: The turn core built and extensively tested for v43.2 (direction snap, speed cap, leading while moving, leveling with the pitch lock) has been separated from the waypoint resolution, so the unit turn now calls it whenever the client provides coordinates for the unit. The old search across nameplates remains as the fallback for instances, where there are no coordinates. Resolving the target to a unit follows an idea by Yennesta. Stable master: the pet swap hits the right slots again Simple: In the hunter's stable master menu the slots were shifted - the wrong pet was shown and swapped. On top of that, on a slow connection the swap wrongly announced that the pet could not be swapped even though it had worked. Both are fixed, and the announcement now names the new pet. Technically: Blizzard's stable window uses action indices 1 to 3 (active pet plus two stable slots); Sku queried one too low. Fix by Yennesta. The success announcement also no longer decides on the first stable event (which already fires for picking a pet up) but waits until the active pet's name matches or two seconds have passed - and it is translated in all three languages now. New: the installer exists for the Mac too Simple: Sku can now be installed and kept up to date on macOS just as comfortably as on Windows: an installer with speech output, self-update and the same components. The download page offers one link per platform. Contributed by Yennesta - thank you! Technically: The Mac branch hangs off the same release flow as Windows: version detection follows GitHub's releases/latest redirect instead of reading a web page, and the files with their sha256 checksums are verified locally at publish time and attached to the same release. Interact key: a warning sound when it grabs the wrong target Simple: Right after something dies, the interact key (G by default) sometimes targets a completely different attackable mob - even a far-off one - instead of the corpse in front of you, and auto-walks you there instead of looting. Sku now plays a warning sound at exactly that moment, so you hear the wrong grab immediately and can cancel the auto-walk by hand. Technically: The key calls Blizzard's InteractUnit("anyinteract") with loose targeting; when the just-looted corpse vanishes under the keypress, the client loosely reaches to the nearest attackable unit in tab range, makes it your hard target and walks there. The key itself cannot be replaced (InteractUnit is protected), and the target cannot be cleared from an addon either (ClearTarget is protected too, in combat and out). What remains is detection: immediately after a corpse leaves, a live, attackable target that is NOT in combat with you - then the warning sound. A target you chose deliberately with a key (Alt+H / Ctrl+H) does not trigger it. Installer 5.0: install proven companion addons alongside Simple: On the components page you can now check seven proven addons - Questie, Deadly Boss Mods, AtlasLoot, Details, GTFO, BugSack with BugGrabber, and Pawn. Whatever is checked, the installer installs alongside Sku and keeps up to date on every run - on Windows and on the Mac, for Anniversary and Classic Era, each client getting the build that matches it. Everything is on by default except Pawn. Technically: Both platforms read the same catalog. Addons with GitHub releases come straight from there (resolved via the releases/latest redirect, no API key, no rate limit); the rest is pinned to verified CurseForge files with sha256 checksums. Deadly Boss Mods is one entry made of four packages, and BugSack and BugGrabber are deliberately one shared checkbox. Each client receives its matching build - Pawn for Classic Era, for example, comes from the Classic package. Mac: installer and login tool in three languages Simple: Both now speak German, English and French. The language can be changed in the respective menu and otherwise follows the system language. The login tool also recognizes French clients and reads level lines verbatim - so a German client says "Stufe 36" again instead of "Level 36". Technically: The app, the install script and the login tool share one language choice (stored setting before system language before English); stored values such as the voice-pack names stay German internally so existing preferences keep working. The login tool's screen detection is independent of the interface language. Chat: the combat log gets its events again Simple: A "Combat Log" chat tab stayed completely silent. It fills up again. Thanks to the community tester for the report including the solution. Technically: The tab had subscribed to the COMBAT_LOG_EVENT event, which does not fire on this client the way the code expects; it now subscribes to COMBAT_LOG_EVENT_UNFILTERED - the same event the other Sku modules have long used. Dial Targeting works again Simple: Targeting via the number pad (the "Dial Targeting" entry in the Sku menu) was completely dead on the anniversary client - no keypress arrived. It works again, tested in a 25-player raid. On top of that: in a regular party (outside a raid) it had never found anyone, members 30 to 40 could not be dialed, and a half-entered number could corrupt the next input - all fixed. Technically: The modern client only delivers keyboard clicks a button has registered for via RegisterForClicks - that was missing, which muted the whole feature. The action now also fires exactly once per keypress regardless of the user's click setting. The party layout comes from party1 through party4 instead of the raid-only query, the first-digit gate lets subgroups 6 to 8 through, and the filled key grid is written to the debug log on every change. Note: the numbers match the raid section of the overview page; the group section there always counts you as 1 and shifts the others - that is intentional and not a bug in the targeting. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.2 Overview New Quests can be tracked and untracked from the quest menu - a new "Track quest" entry at the bottom of every quest menu, and it can be switched off. New "Nearby quests" list in the quest menu: your whole quest log in one list, sorted by how far away the next thing is - with distance and compass direction. Off by default. New keybind "Target nearest quest mob" (Alt+H): targets a creature belonging to an open quest objective - including one that merely drops an item you need. The "Next enemy in combat" keybind (now bound to Ctrl+H by default) finds something far more often: it no longer depends on the view cone. New keybind "Output full tooltip of current target" (Ctrl+Shift+V): reads the current target's complete tooltip aloud, including what the Hunter ability Beast Lore reveals. Bags menu: a new "all bank" entry right after "all bags" while the bank is open - everything in the bank in one flat list, exactly like "all bags" is for your bags. Ctrl+Enter moves an item from there into your bags, the same way it already did inside the individual bank bags. Chat: the input line now speaks like every other Sku input field - every typed character, the character or word under the cursor, and with arrow up the messages you sent last, to send again or to change. Chat: the target channel is announced whenever it is not "Say" - on opening and on every change; can be turned off in the chat settings ("announce the chat input channel"), on by default. Chat: an unknown slash command is now announced instead of being silently discarded. Swimming and flying: Sku keeps you level. The new pitch lock ends the creeping dive during automatic turns, and after an interact approach to an NPC or a press of ascend/descend you are actively straightened out again. On by default; can be switched off under Settings -> General. New keybind "Toggle pitch lock while swimming and flying" (Ctrl+Shift+N): locks and releases the pitch manually - switching it off and on again re-levels you at any time. Experimental: gather routes for ore, herbs, gas clouds and chests - Sku walks you through the zone node by node, nearest first. Under Settings -> Scan -> resource scanning -> Gather routes. New and barely tested in game; feedback is explicitly wanted. New "Nameplates" menu under Settings -> Visual aids: nameplate size, target emphasis, class colors and dimming of the remaining plates - settings the game supports but never offers anywhere in its own options window. The reading window for quest text, tooltips, chat history and wiki is now configurable: font size, font, color pair, opacity and outline under Settings -> Visual aids -> Text window. Changes Turn towards beacon (T): the turn now lands far more accurately. It used to run so fast that merely stopping it frame-exactly could cost 20 degrees and more of overshoot depending on frame rate; the speed now scales with the size of the turn, and no turn takes longer than a quarter of a second. Turn towards beacon: while moving, the turn leads the target - it aims where the waypoint will be when the turn finishes. This ends the circling around close waypoints, known above all from riding and flying. Turn towards beacon: a press during a running turn is discarded instead of cutting the turn short - pressing repeatedly keeps refining as before. Navigation: under "Direct waypoint", "Current map by distance" now comes first and "Recent" second. Quest database: "Starts in zone" shows the distance-sorted list directly - the intermediate "By distance" level is gone. Quest database: "Starts in zone" now names the compass direction of every quest as well, not just the distance. Quest database: brought up to Questie's current data - 33 quests that had no target now have one, five missing quests added, plus about 2000 smaller corrections to names, levels, quest givers and prequests. Bags: after right-clicking an item the cursor stays on that item instead of jumping to the top of the list. Bags: actions with a cast time (enchant, fishing lure, weapon oil, armor kit, disenchant) announce the item again once the cast has finished and leave the cursor on it. Bags: a confirmation dialog such as "Replace existing enchantment?" no longer costs the position - after "Yes" as after "No" you land back on the item. Chat: "Add Link to chat" inserts the item at the cursor instead of replacing the line, and leaves the cursor in front of it - so a channel such as "/p " still fits before it. Chat: an item link counts as a single character when reading - arrow left and right jump across the whole link and speak its name. Chat: "Add Link to chat" no longer closes the bags. Chat: the arrow keys move the text cursor in the chat input; can be turned off under chat settings. Auras: a weapon enchant (fishing lure, poison, oil) can now be addressed like a spell - the event "Weapon enchant expired" together with "spell name". Auras: new condition and new output "weapon enchant hand" (main hand or off hand). Menu: closing a window now rebuilds the menu data once instead of up to six times. Menu: the type-ahead filter ignores accents - on a French client "general" now finds "Fournitures generales", and "o" finds an entry starting with "O". On a French AZERTY keyboard the u key now types into the filter too. French: the waypoint comments are now available in French - 207 texts across 546 waypoints, translated from the German original. Installer: a game folder off the beaten track only has to be pointed out once - the installer now remembers where each WoW client lives and finds it again on every later update. The reading window now starts in a sans serif font at 18 point instead of Playfair Display at 12 - the previous default was the smallest and thinnest text surface in the whole addon. The reading bar can now be set to a font and color pair as well, with its own values kept separate from the text window. Auction house: "Level descending" now sorts the whole result list instead of only its first page; equal levels are ordered by the price per item. Auction house: "level" means the required level everywhere - and is only read out where it actually tells the entries apart. Bugfixes Menu: while you were walking, an open menu was as good as dead - every key except Escape was swallowed until you stood still. Escape did not close the menu while standing right on a quest object - the quest window reopened endlessly. Menu: Enter on an on/off entry in a filtered list read the first entry of the list back instead of the toggled state. Menu: a line you ask for again with a keypress is read out again instead of being swallowed. Menu: the type-ahead filter treated what you typed as a search pattern - an input like "50%" or "(long" found nothing or broke off. Keyboard echo: typed characters are spoken instantly; swallowed and late letters are gone. Speech: announcements repeatedly went silent for a while while navigating - most reliably on a single hit after filtering. Voice output: item links were read out as a run of colour codes instead of as their name - everywhere in Sku, not only in chat. Turn towards beacon: after turns triggered in quick succession, the camera keys' turn speed could stay permanently quadrupled. Deleting a custom waypoint removed the wrong internal id, and "/skucheck wp" reported four errors on the quick waypoints that were none. Quest menu: for quests with mixed objectives ("kill X and collect Y") the "Quest target" entry only showed the first objective kind - enemies, item sources and the exploration point now sit side by side. Quest menu: for 174 quests whose objective is a PLACE, the place was in the data but "Quest target" still answered only "Empty" - it looked for a waypoint under a name nobody ever assigns. The nearest route waypoint is now computed from the coordinates. For 77 FURTHER quests the objective data was missing entirely - objects, creatures and 26 place coordinates have been entered for them, some of them from web sources: reports on whether they are right are welcome. Quest menu: about 300 quests whose quest giver, target or turn-in sits inside a dungeon showed empty entries - they now lead to the dungeon entrance. Thirteen talk-to quests (among them the old priest spell quests) shipped without a turn-in in the data - the Delivery entry now leads to the NPC the quest text names. Quest database: right after a /reload, quests of inactive world events (such as Midsummer quests) could appear in "Starts in zone". Quest database: the "Starts in zone, By distance" list broke off after the first quest. Bags: the type-ahead filter stuck after using an item and could only be cleared by closing the bags. After an action with a cast time a wrong item was announced right after the correct one. The bag list held for combat could go stale when a quest window was closed while the bags were open. Bags menu: with a bag-replacement addon such as Bagnon, which hides Blizzard's bank window, every bank container vanished from the menu. Auras: the Well Fed buff appeared twice in the spell picker - both entries sounded the same and only one could ever fire. Auras: entries that only ever exist as a weapon enchant (such as "Fishing Skill +75") are now marked as such in the pickers. Auras: "weapon enchant main hand contains X" together with "Weapon enchant expired" could never fire - /skucheck auras now reports that combination. Auras: the end of a weapon enchant is detected frame-accurately instead of up to a quarter second later. Action bars: Shift-Down no longer read the spell tooltip in the assignment menu. Dungeon browser: accepting a group invitation no longer drops you into the dungeon browser by itself. French: the waypoint comments had been completely silent since v42.11 - including warnings such as "careful, ravine" or "wait here for the zeppelin". French: 22 of the 94 minimap scanner resource names were wrong, so those nodes were never announced. French: inside the Cave of the Mists the waypoint list stayed empty. Nameplate coloring was dead after every login and every reload: the switch read as on, but nothing was colored until you flipped it off and on again by hand. The reading bar stayed invisible in combat - exactly when it is needed most. The reading bar showed only the name of a menu entry, not the value spoken after it - switch states and counts were audible but not readable. A window that had been closed could be announced again later - typically the trainer, long after you had opened your profession window. The menu sometimes opened by itself at its top level instead of where you had asked to go. With two windows open at once, closing the second one went unnoticed. After a window was closed, its entries could linger under "Local". Trainer: learning several spells in quick succession and closing the window right after left it still talking. Auction house: "usable only" hid exactly the high-level items you are shopping for from the full-scan list. Auction house: the full-scan list filtered by level and quality although no filter was set - trade goods, materials and everything grey were missing. Auction house: a category without subcategories showed nothing but "All". Auction house: a cancelled purchase could hijack the next search and arm a buy nobody asked for. Auction house: a search the watchdog cuts short says so now - a half-filled list used to look like a finished result. Swimming and flying: the pitch lock ends the unwanted diving Simple: While swimming or flying, every automatic turn towards the beacon pulled you a little bit downwards - over a longer stretch that added up until you were under water or on the ground without noticing. Now Sku locks your pitch as soon as you swim or fly: you move exactly level, and Space and X still carry you up and down. And if something does tilt you - such as the automatic approach when interacting with an NPC - Sku actively straightens you out again: when the NPC window opens, shortly after every turn, and whenever you press ascend or descend. On by default; anyone who wants to dive by pitching with the mouse while swimming (residual sight) switches it off under Settings -> General. On top of that, the new keybind "pitch lock" (Ctrl+Shift+N) locks and releases manually. Technical: Three building blocks. First, the console variable pitchlimit 0 caps the character's movement pitch (the technique of the LevelFlight and FlightHUD addons) - set persistently rather than around each turn impulse, because exactly that toggle race is what made earlier attempts fail. pitchlimit is writable but not readable; every login therefore blindly restores the default of 88 so a lock can never get stuck. Second, the lock only holds what is there: engine-driven movement such as the interact approach still tilts the character, and afterwards no keyboard input exists that raises the pitch again. The active re-leveling uses the very transfer that originally caused the bug: ResetView/SetView snaps the camera to the known, almost level default preset, a mouselook impulse transfers it onto the character, and the lock caps the small remainder to zero. Triggers are the NPC window, every turn (delayed by 0.5 s, because the engine applies the yaw transfer a frame later - an immediate second impulse would undo the turn) and, via hooksecurefunc, the ascend/descend functions JumpOrAscendStart, AscendStop, SitStandOrDescendStart and DescendStop - independent of which keys they are bound to. Third, a pitch gauge for the log: measured horizontal speed divided by GetUnitSpeed is the cosine of the trajectory angle - the only measurement this client offers (GetUnitPitch was removed in patch 7.1 and is missing here too). It measures the trajectory, not the body pitch, and serves diagnosis only. The cursor stays on the item in the bag Simple: Right-clicking an item in the bag list - use it, equip it, sell it to a vendor, apply an enchantment - dropped you onto the first entry of the whole list. For items that vanish straight away that barely showed, because the list re-sorts anyway; for everything else it was simply a lost position. Now the cursor stays on the item you worked on, and when the item leaves the bag you land on its slot - that is, on whatever moved up into it. Technically: The cause was never shifting indices but the third line of the right-click macro, "/script SkuCore:CheckFrames()". It dated from before the event-driven confirm existed and ran AT THE KEYPRESS - before the action had changed anything at all, and for an action with a cast time seconds before. It therefore rebuilt stale data and cost the cursor for nothing. Removed; the refresh belongs to the BAG_UPDATE-driven confirm alone, which rebuilds only once the bags have really settled and then re-pins the cursor by bag slot and item id. In addition, CheckFrames no longer remembers only the row number of the focused entry across a rebuild but its name as well, and looks that up in the rebuilt list - the row number alone stops being right as soon as the list had a different shape in between. Enchants, lures and oils: the list refreshes after the cast Simple: An enchantment, a fishing lure, a weapon oil, an armor kit or disenchanting only change the bags when the cast finishes - often five seconds after the click. Sku had long given up by then: the list stayed stale and the cursor sat somewhere. Now Sku waits the cast out, announces the item once afterwards and leaves the cursor on it. If you navigate on yourself in the meantime, you keep your own position - Sku then stays out of it. Technically: The confirm window opened by the right-click expired after 2.5 seconds. UNIT_SPELLCAST_START/-CHANNEL_START now push its deadline out to the end of the cast while keeping the anchor captured at the click, so the re-pin afterwards lands on exactly the item that was worked on. Deliberately without a spell list: an open window is itself the proof that this cast belongs to the action. Two hard guards, both learned from a real case: the cast has to follow the click within one second ("/use" starts it in the same frame), and the extension is a one-shot. Without them a 22 second fishing cast kept pushing the deadline ahead of itself and the window never died. The confirmation dialog no longer costs the position Simple: Applying an enchantment to an already enchanted item gets you Blizzard's "Replace existing enchantment?" question. After "Yes" or "No" you ended up at the very top of the bag hierarchy, on "Bag 1" instead of on the item. Now both answers lead back to the item, and it is announced once. Technically: The dialog is a modal window between the click and its effect, and it broke the position twice over: the 2.5 second deadline expired while it was up, and Sku anchors the menu INSIDE the dialog, so on closing there was nothing left to return to - the generic entry then took the only open window and landed at the top of the bag list. SkuBagPostActionPopupGate holds the window open for as long as the dialog stands (up to 30 seconds) and triggers the confirm when it closes. That is the right tool precisely because it covers both answers: "No" produces neither a cast nor a bag change, so nothing else could ever put the cursor back. While the dialog stands the confirm holds off entirely - otherwise an unrelated bag change (loot landing, a trade settling) would tear the cursor out of the dialog. The type-ahead filter in the bag list clears again Simple: Typing a name in the bag list filters it down to the matches. That filter used to stop clearing as soon as you had used the filtered item: even arrow left and right again did not bring the full list back, only closing the bags helped. Now every one of those keys clears the filter again, the way you are used to. When nothing matches the filter at all, the list stays empty as before and the arrow keys only repeat the typed filter - which is exactly how you hear that there is no hit. Technically: SkuOptions:ClearFilter only forgot the typed string; the filtered child list ApplyFilter had installed stayed in the tree until something replaced it wholesale - and a level built from game frames, such as the bags, cannot refresh in place, it needs a full rebuild. Masked for years by the forced CheckFrames in the macro (see above), it showed the moment an action does not change the bags at all. ClearFilter now genuinely restores the list, including the forward and backward links, and does so on the node it filtered rather than the currently focused one. Escape did not close the menu while standing right on a quest object Simple: Standing at roughly zero metres in front of a quest object and closing the menu with Escape pulled the quest window straight back up - endlessly. In that spot the menu could barely be closed at all. Now the open game windows are closed first and the menu itself afterwards. Technically: The menu hid itself first and worked through the open interact windows after that. The quest frame was therefore closed while the game still reported the object in range, and it reopened immediately. The order is reversed now and secured by a counter that "/skucheck menu" reads out: a violation of that order is noticed without anyone having to be standing on exactly that spot by chance. Fewer duplicate rebuilds when closing a window Simple: A single Escape used to trigger up to six complete rebuilds of the menu data, because several windows close at once and Blizzard's quest window alone hides four sub-panels. That cost noticeable time. Now exactly one rebuild runs per frame. Also, the bag list held for combat could go stale when a quest window was closed while the bags were open - a fault that would only have surfaced later, in combat. Technically: The hide paths run through SkuCore:CheckFramesCoalesced, whose gate opens once per frame; the actual pass is deferred by 0.01 s anyway and reads live state, so it sees exactly what the last call of the burst would have seen. The OnShow side is deliberately left out. The bookkeeping in GENERIC_OnClose has been given its own gate: it previously hung on the return value of that same shared gate, and since the quest sub-panels fire first they consumed it and the bookkeeping - the combat bag list refresh among it - was skipped. Speech: announcements no longer fall silent Simple: While navigating, speech kept going quiet for a while - an arrow key simply did not read the entry out, in a quiet moment with nothing else going on. It was most reliable when you filtered a list down to a single hit and then moved quickly between the filter and that one hit: after a few passes the hit stayed silent for good, even if you waited a second. Both are fixed. Technically: Two independent causes, both below the queue - Sku itself had handed over every announcement cleanly; in a 95-minute capture every keypress produced exactly one SpeakText with a STARTED. First, the same line was re-issued up to four times inside one second, because a redundant window check announces the focused menu entry again; 95 of 1512 utterances were such repeats. Each carries its own "queuereset", i.e. a StopSpeakingText, and that does reach the screen reader - so the line was cut mid-word and restarted, and whenever the resend was lost along the way nothing was left. The existing duplicate guard could not catch this: it hangs off VOICE_CHAT_TTS_PLAYBACK_FINISHED, and across the screen-reader bridge STARTED and FINISHED both come back in the same frame - so there the guard is always empty and can never fire. There is now a time-based guard that drops an immediately following identical line within one second, together with its queuereset; dropping only the text would still cancel the line being spoken and put nothing in its place. It compares against the line handed over immediately before and nothing else, so it cannot swallow a genuine re-read, and it only applies when the first copy really began to play. Second, the cache-buster had stopped working. The client stores synthesized speech by text and replays it on a repeat without invoking the voice again - across a bridge that replay is silent. The counter-measure so far was a cycling , but the client parses markup off and keys only on the spoken text: in the capture every single utterance carried its own marker and the hit still went silent. The original buster varied real characters and did work; switching it to a marker was meant to protect prosody and quietly brought back the very bug it had been written against. Real text is appended again: a cycling run of 1 to 64 no-break spaces after the last word, where it is neither audible nor able to colour the prosody. Well Fed: only one entry left in the spell picker Simple: Anyone building an aura on the Well Fed buff found two entries in the spell picker that were both spoken as "Well Fed". Only one of them could ever fire, the other was a dead record - which one you hit was pure luck and not audible. There is only one entry now, and it is the right one. An already saved aura pointing at the wrong one is moved over once at the next login and works afterwards. Technically: Since v43.0 auras are keyed on a spell's English identity, so the same aura fits in every language. In the shipped spell database, however, ten rows carry the English name IN QUOTES - 44097 to 44106 plus 69559 and 65365 carry the name with two quote characters wrapped around it, where the other 65 rows carry it without. That created a second identity behind one and the same localized name, and the menu disambiguation appends the English identity in such a case: one entry read "Well Fed (Well Fed)", the other the same but with two extra quote characters around Well Fed. Speech does not read quotes, so the two entries sounded identical. Well Fed is the only one of the 26,650 localized names in the data set whose two identities differ by nothing but those characters. The name is now unquoted when the identity is formed, and on BOTH sides - in the value list and in every resolution of a live event - because changing one side only would produce a stored value no event can match any more. Only names quoted at BOTH ends are unquoted; the 17 remaining rows with quotes inside the name (Goblin "Boom" Box, "VICTORY" Perfume) keep them, because there they are part of the name. A one-time migration moves saved auras over. Weapon enchants are recognisable in the spell picker Simple: A fishing lure, a sharpening stone or a weapon oil is not a buff but an enchant on the weapon. It appears in no buff list of the game and fires no aura event - an aura on "event = aura lost and spell name = Fishing Skill +75" could therefore never fire, even though the entry is in the picker and sounds perfectly normal. Such entries now carry the suffix "(weapon enchant)" in every picker, so it is audible what they are. To react to one, use either the event "Weapon enchant expired" or the condition "Your weapon enchant main hand". The suffix is not read out in the announcement of a fired aura. Technically: The value list of the "spell name" attribute is the entire spell database, including rows that only ever reach the player as a temporary weapon enchant - the lure is spell 8084 behind enchant id 265. The affected identities are now determined while the lists are built (every spell id of the identity is referenced by the enchant database) and get the suffix. Deliberately MARKED rather than removed: there are 395 of them and they are not all inert - Fiery Weapon, Executioner and Unholy Strength are enchant procs that do appear in the combat log under exactly that name. Removing them would have taken working auras their only way to name them. The suffix rides the same mechanism as the English disambiguation, so it drops out again in the speech of a fired aura. Weapon enchants are addressed like spells Simple: For a damage-over-time spell you write "event equals aura lost" and "spell name equals " - the event itself says what it is about. For a weapon enchant that was not possible: the fishing lure could only be named through "your weapon enchant main hand", and that is a question about state, not about the event. In the "contains" form such an aura could not fire at all - by the time the event arrives the enchant is already gone, so the list no longer contains it. Only the inverted "contains not" form worked. The event now carries the enchant's name itself, and the aura is written exactly like a spell one: "event equals Weapon enchant expired" and "spell name equals Fishing Skill +75". "contains not" keeps working. Technically: The two synthesised events WEAPON_ENCHANT_REMOVED and WEAPON_ENCHANT_UPDATE put the HAND name in field 13 - and field 13 is the spell name field. So a "spell name" condition compared against "Main Hand", and a "spell name" output announced the hand instead of the enchant. The enchant now sits there: field 12 gets its spell id, field 13 its name, both through the same resolvers the value list is built from - the stored value is the same one the weapon-enchant condition uses. A removal reports the enchant that WAS there, which is the identity meant. Existing auras cannot go silent from this: "Main Hand", "Off hand", "Haupthand" and "Nebenhand" do not occur once as a spell name across all 49,117 rows of the spell database, so a "spell name" condition could never have matched on this event anyway. The hand is not lost, it moves to its own field with its own condition and output ("weapon enchant hand"), stored as a language-neutral token so a shared aura means the same hand in every language. What was deliberately NOT done is the other obvious route - filling the state list on a removal with the outgoing enchant: the list is shared, so it would lie to every other aura on that pass, re-arm a "contains not" gate and thereby produce double announcements, and it would contradict the remaining duration, which is deliberately forced to 0 on a removal. In addition, /skucheck auras now reports the "removal event plus contains" combination as an aura that can never fire. Fixed along the way: when only the off hand changed, the event was still reported as the main hand. The end of a weapon enchant is detected at once Simple: The game does not announce that a fishing lure or a poison has run out - Sku looks every 0.25 seconds. The announcement was therefore up to a quarter second late. It now arrives at the moment it expires. Technically: How long an enchant still lasts is fixed the moment you see it - GetWeaponEnchantInfo returns the remaining milliseconds. The ticker records the next expiry from it, and the frame driver compares one number per frame and then calls exactly the ordinary player ticker. That is the same shape as the duration deadline scheduler introduced in v43.0 and costs no extra polling. Double announcements are impossible because the ticker only emits when its stored snapshot really changed. Deliberately NOT done: putting the ticker on every frame - that would be fifteen times the polling for the same gain. Toggling in a filtered list reads the new state back again Simple: Narrowing a list with the type-ahead filter and then pressing Enter on an on/off entry removes the filter - that part is intended. The entry was toggled correctly too, you just could not hear it: the cursor jumped to the top of the list, and what was announced was the first entry rather than the new state. In the aura outputs the first entry's sound preview played on top of that. You now stay on the entry you toggled and hear its new state. Technically: Enter removes the filter BEFORE it dispatches the action - the menu node calls ApplyFilter("") for that. Its cleanup ended in a blanket OnFirst() on the level, so the cursor moved to the top of the list in EVERY case. The toggle itself was never affected: it acts on the node (actionInPlace), not on the cursor. Only what got read back afterwards was wrong - and OnFirst additionally fired the first entry's OnEnter on landing, which for the aura sounds is the audition. ApplyFilter("") now follows the same rule ClearFilter already follows: the restored list holds the SAME node objects, so a cursor on a real entry stays valid; only the synthetic "Filter;" row has no home in it, and for that row alone the full OnFirst() including OnEnter still runs, so the audition on landing is not lost. With no filter active ApplyFilter("") always was a no-op - which is why toggling in an unfiltered list never misbehaved; both cases now behave the same. Side effect of the same rule: Backspace now leaves the cursor on the entry it was on instead of jumping to the top of the list. Dungeon browser: accepting an invitation no longer lands you in the browser Simple: If you were listed in the dungeon browser and then accepted a group invitation, you ended up in the dungeon browser without pressing a single key: the menu reopened by itself and the cursor sat on "Not listed". For sighted players nothing happens at that moment - Blizzard's group finder window never opens when an invitation is accepted. Now Sku stays quiet too: you stay where you were. Technically: Two paths led there, both are closed now. (1) The moment the server drops your listing - which is exactly what it does when you join a group - LFG_LIST_ACTIVE_ENTRY_UPDATE arrives, and the browser rebuilt its menu entry in response. That rebuild ended in a SlashFunc onto its own menu path, and that reopens a CLOSED menu and descends into it. It now only runs while the menu is open AND the cursor actually stands inside the dungeon browser - only then do the freshly built entries have to be put back under the cursor. Skipping it costs nothing: the menu entry is "dynamic" and rebuilds on the next descend anyway. As a result no listing change you did not cause yourself (expiry, an edit by the party leader) can yank you out of your bags into the browser either. (2) A third of a second after the invitation dialog has disappeared, the browser checks whether it should reopen. It only asked "is any group finder window open right now?" and turned that into an open. Now the state present when the invitation ARRIVES is remembered, and only exactly that is restored. If the invitation put you into a group, nothing happens at all - neither open nor close. New keybind: read the current target's full tooltip Simple: On a target change Sku announces a fixed, short set - name, level, elite or rare, plus the second tooltip line with the creature type. Everything below that in the tooltip has never been reachable: the faction line, the PvP marker, and above all what the Hunter ability Beast Lore reveals about a beast - its family, its diet and whether it can be tamed. The new keybind (default: Ctrl+Shift+V) reads the complete tooltip out loud on request. It acts only when pressed: the normal target announce stays exactly as short as it was, and anyone who does not want the extra detail never hears it. That is also why there is no setting for it - pressing the key IS the request. With no hard target the key describes the current soft target instead, so it also works on something you have not committed to yet. Rebindable under Sku key bindings, target and soft targeting. Technically: The idea, the proof and the reference implementation come from ZenqFR, in the companion addon SkuBeastLore (github.com/ZenqFR/Sku-BeastLore); the Beast Lore output was tested there on a real Hunter. Here the feature is rebuilt natively against Sku's own interfaces, so no extra addon is needed. The Beast Lore lines are produced by the client itself inside the unit tooltip; no Blizzard interface Lua builds them and no API returns them separately - reading the tooltip is the only way to reach them. That is also why a separate option for just the Hunter information is not possible: those lines could only be isolated by locale-dependent text matching. SkuMob:OutputTargetTooltip resolves target, softenemy, softfriend and softinteract in turn and reads through Sku's shared SkuScanningTooltip. Because that frame is shared - bags, auction house, chat links and quest text all read the same one - it is cleared before AND after, and its owner restored exactly as SkuCore sets it at login. Track and untrack quests Simple: Every quest menu now ends with a "Track quest" entry. ENTER flips it, the cursor stays where it is, and the new state is spoken straight back - "Track quest;On" or "Track quest;Off". Tracked means WoW's own marker on the minimap and the world map. Anyone with some remaining sight finds quest objectives faster that way; anyone who sees nothing gets nothing out of the markers. So the entry can be switched off entirely - under Sku settings, SkuQuest, "show track quest entry"; it is on by default. It only appears for quests in your own quest log, because a quest from the quest database is not one you have yet. Two limits come from the game itself: The Burning Crusade allows at most five tracked quests at a time, and a quest with no objectives at all cannot be tracked. In both cases Sku speaks the game's own message and the state stays as it was. Technically: The idea and the reference implementation come from ZenqFR, in the companion addon SkuQuestNearby (github.com/ZenqFR/Sku-QuestNearby); with ZenqFR's agreement feature is rebuilt here natively against Sku's own interfaces, so no extra addon is needed. The entry lives in the chunk-local CreateQuestSubmenu rather than on a hook over the public wrapper - which is why every quest view gets it: quest log, quest database and SkuChat's quest menus. There is no C_QuestLog on 2.5.6: AddQuestWatch, RemoveQuestWatch and IsQuestWatched take a quest LOG INDEX, not a quest ID, and that index shifts whenever a quest is accepted or handed in. Sku therefore keeps a quest ID to index map, re-verifies the remembered index on every read, and drops the map on the quest events; without that cache the "quest database, all" list would run a full quest-log scan per quest. Turning tracking on goes through AutoQuestWatch_Insert with QUEST_WATCH_NO_EXPIRE so the quest lands in QUEST_WATCH_LIST and no expiry timer can remove it again; turning it off removes the QUEST_WATCH_LIST row first and only then calls RemoveQuestWatch - exactly like Blizzard's own click path, or a long-untracked quest keeps counting against the limit of five. QuestWatch_Update only runs outside combat, because it ends up moving protected frames; the next QUEST_LOG_UPDATE runs it anyway. New keybind "Target nearest quest mob" (Alt+H) Simple: One press targets a creature that belongs to a quest objective you still have open - including one you do not have to kill at all, but which merely drops an item you need. Example: for "Essence for the Engines" the quest log only names the item; the key still targets the Mana Wraith that drops it. If nothing matches in range Sku says "no quest target in range"; if the quest log holds nothing suitable at all, "no quest target in your log". The key is bound to Alt+H out of the box and can be rebound under Sku keybindings, "Target and soft targeting". Technically: The idea and the reference implementation come from ZenqFR, in the companion addon SkuQuestTarget (github.com/ZenqFR/Sku-QuestTarget); here the feature was rebuilt natively with ZenqFR's agreement, so no extra addon is needed. Candidates come from your own quest log via SkuQuest:GetQuestTargetIds; item objectives are traced back through the drop source (npcDrops, and one level deep also itemDrops) to the creatures that drop them. The search looks in the zone you are standing in first - a creature with no spawn there cannot possibly be in targeting range and must not take a slot in the macro; only when nothing is recorded for your own zone does it sort across the whole continent. Distance uses the nearest recorded spawn point, not the first one in the list. The macro is multi-line, and one quirk of the game decides the outcome: every matching line fires and each match overwrites the previous one, so the LAST one wins. Candidates are therefore included near to far, so the length budget can only cut distant ones, but written out reversed, so the closest candidate is the final line. It is rebuilt on every press out of combat; in combat the last built text fires. "Next enemy in combat" now finds things you are not facing (Ctrl+H) Simple: The key could only ever find what you were already looking at - with the same enemy five metres behind you it reported nothing. That is not a setting, it is how the game's tab targeting works. So Sku now remembers the names of hostile creatures you come across and additionally tries them by name - and a name works in every direction. In practice: anything you have seen, fought or targeted once stays reachable from anywhere for two minutes, including with your back to it. The search now also restarts at the nearest enemy on every press instead of cycling onwards. The key is bound to Ctrl+H out of the box. Technically: The approach is borrowed from ZenqFR's SkuQuestTarget, which uses the same route for its secondary function. /targetenemy is tab and searches the view cone - nothing can change that. "/tar name" instead searches the client's name list and is direction-independent, but it needs a name, and there is no interface that enumerates units behind you. Sku therefore collects names continuously from four sources: hostile nameplates, your own target, the unit under the mouse, and the combat monitor's enemy list. Collection runs on a one-second tick while the key is bound, because a nameplate only exists while the creature is in view - which is exactly when you do not need the key. Only names are collected there; the expensive range measurement runs once per key press. Remembered names last two minutes. The macro is "/cleartarget", then "/targetenemy", then the remembered names far to near - the last matching line wins, so the nearest one. New "Nearby quests" list Simple: The quest menu has a third entry now, after "Current Quests" and "Quest database": "Nearby quests". It holds your whole quest log in ONE list - started and finished mixed together - sorted by how far away the thing that currently matters for each quest is. Every entry names the distance first, then the compass direction, then the kind, then the quest, so you hear "180 Meters; north-east; quest target; Thieving Kobolds" or "340 Meters; south; Turn in; The Wolf Pack Leader". For a quest with open objectives that is the distance to the nearest objective; for one reported complete, the distance to the turn-in. "Current Quests" groups by the quest log's own headers and therefore sorts by zone - this list answers the other question: what is nearest from where I stand. Enter opens the same quest menu as everywhere else, waypoints and the "Track quest" entry included. The list is OFF out of the box and is switched on under Sku settings, SkuQuest, "show nearby quests entry": it recomputes the whole quest log against Sku's spawn database every time it opens, and only someone who uses it should pay for that. Where Sku's data yields no location the entry reads "Location unknown" instead of a distance and sorts to the end - it never disappears. Technically: Idea and reference implementation by ZenqFR, from the companion addon SkuQuestNearby (github.com/ZenqFR/Sku-QuestNearby); rebuilt natively here with ZenqFR's permission so no extra addon is needed - in particular without Questie and without GatherMate2. The maths lives in the new, stateless file SkuQuest/Proximity.lua. Two tiers, and the order is the point: first the nearest recorded spawn point IN the zone you are standing in - the selection runs cheaply in map coordinates and only the winner is converted to world coordinates once; only if that yields nothing, the minimum across all zones of the same continent. A different continent is not an imprecise distance, it is no distance, and stays out. Item objectives are traced back to the creatures and world objects that drop them, explore objectives to their recorded coordinates. Unlike the waypoint path, ALL of a quest's objective lists are read, not just the first non-empty one - a quest with "kill eight wolves AND collect five pelts" would otherwise only ever show the wolves. Filtering is by objective KIND only: which kinds are still open comes from Blizzard's own progress display ("monster", "item", "object"), and that maps one-to-one onto the three lists; matching a progress line against an individual database entry would be guesswork, because neither order is bound to the other. At most twelve ids are evaluated per quest and kind - the limit here is the hardcore realms' script budget, not the last decimal. The compass direction comes from the same world coordinates through the same helper the waypoint lists and the minimap scanner use. Deliberately a compass bearing and not a clock position: a clock position is relative to where you are facing and would be wrong the moment you turn - but the list is built once and then read through. Quest database: "Starts in zone" without the extra level Simple: "Quest database, Starts in zone" held exactly one entry, "By distance", and only inside it the quests. A level with nothing to choose is just a keypress. The list now sits directly under "Starts in zone". That list was also broken: it broke off after the first quest, so you got exactly one entry instead of all the zone's quests. Technically: The extra level existed because a second sort ("by difficulty") was once meant to sit beside it; that construction site sat commented out next to it for years and was never built. The break came from a line "tcount = tcount + 1" in the list builder: tcount was not a local there but a global that is never set, so incrementing it raised on nil. An error inside a menu's child build is swallowed - the build therefore ended silently after the first entry. Nothing ever read the counter; it is removed rather than initialised. Everything in the bank in one list, and the bank is recognised under Bagnon too Simple: The bags menu had "all bags" - one flat list across every bag, sorted by name. The bank had no such thing: at an open bank the menu did show "Bank" and "Bank Bag 1" through "Bank Bag 7", but looking for something meant walking into each compartment in turn. Now, while the bank is open, a new "all bank" entry sits directly after "all bags": everything lying in the bank in one list, with the same type-ahead filter. Ctrl+Enter moves an item from there into your bags - exactly as it already did inside the individual bank bags. On top of that, Sku now works out whether the bank is open from the game's own messages rather than from Blizzard's window. Anyone using a bag-replacement addon such as Bagnon, which hides that window, had no bank in the menu at all until now - Sku considered it closed and let every bank container disappear. No bridge addon is needed for that any more. Technically: The suggestion comes from ZenqFR's companion addon SkuBagnonBridge (github.com/ZenqFR/Sku-BagnonBridge), which closes the same gap in its own way and likewise recognises the bank from the game's events; with ZenqFR's agreement the part that needs no further addon is built natively here. The flat list was limited to bags 0 to 4; the bank containers (-1, -3, 5 to 11) were never copied into it. They now collect in a list of their own, deliberately kept separate from the bag list: SkuCore.combatBagOrder derives from that one - the mirror used in combat - and it can only ever use bag slots. The entries in the new list are copies of the same per-slot entries and carry bag and slot with them, so every existing action works unchanged. The bank's state now hangs on BANKFRAME_OPENED and BANKFRAME_CLOSED: both come from the interaction with the banker itself and are independent of who draws the window. It is reset on load and after /reload, and the old window check is kept alongside - so the detection can only ever get more cases right than before, never fewer. Deleting a custom waypoint cleared the wrong entry Simple: Deleting a waypoint you had created yourself did not remove its own id from an internal lookup table but that of a different waypoint. And "/skucheck wp" reported four errors on the four quick waypoints although nothing was wrong with them. Technically: A waypoint's wpId is its identity - the link table is keyed by it - not a description of where it is. The area id packed into it dates from the moment the waypoint cache was built and stays put when a custom waypoint moves; that is exactly what happens to the four quick waypoints at every loading screen, because they are re-set to the player position then. The check rebuilt the id from the record's current areaId and compared - it now uses the area packed into the id itself. The delete path built the id from ".areaid" (the field is areaId), so it computed with area 1 instead of the real one and cleared the entry of whichever record really owns that key; it now uses the record's stored id. The chat input has become usable Simple: The chat line was the only input field in Sku you typed into blind. There was a sound on opening and one on closing, and nothing else - no character echo, no reading under the cursor, and no word about where the message would actually go. It now behaves like every other Sku input field. Every typed and deleted character is spoken, arrow left and right read the character under the cursor and with Ctrl the whole word, arrow up recalls the messages you sent last - to send again or to change - and says the channel along with them, because a recalled line does not show you where it would go. The channel is also announced on opening and on every change, but deliberately not for "Say": that is the harmless case, and saying it every time would be noise. If the announcement does not suit your workflow, turn it off in the chat settings under "announce the chat input channel"; it is on by default. Turning it off also drops the channel in front of a line recalled with arrow up. An item in the chat counts as a single character when reading instead of sixty-five characters of markup. And a mistyped command such as "/tets" now says "unknown command" instead of simply doing nothing. Technical: The keyboard echo of Sku's own fields now attaches to foreign ones through SkuOptions:AttachInputEcho. Three things differ there: it attaches with HookScript rather than SetScript, because Blizzard's field carries its own handlers for history and name completion; the template sets ignoreArrows, which is why plain arrows went to the game and the text cursor needed ALT - that is turned back; and it arms on focus, because a foreign field has no open-and-close pair for the echo to hang off. The channel is read from the input's header rather than rebuilt from chatType - the header is already translated and covers whisper targets, numbered channels and Blizzard's own switches mid-call. Its announcement runs deferred and in a single slot, because every channel change is triggered by a keystroke, and that keystroke's character echo used to throw the announcement out of the queue every time before it was ever spoken. Item links are read as their name, not as colour codes Simple: An item - in chat, in the mail, in loot - was read out as a long run of colour codes, identifiers and colons instead of simply as its name. Technical: The voice output first replaced every vertical bar with a space and THEN called the function that strips colour codes, links and textures. All of its patterns look for a vertical bar, which no longer existed by that point - so they had been matching nothing all along. Order swapped; the blanket replacement is now only a safety net behind it. Quest database: "Starts in zone" also names the compass direction Simple: The list of quests that start in this zone used to say only how far away the quest giver is. Now, like the "Nearby quests" list, it also says which direction that is - distance first, then the compass direction. Technically: The quest giver's world coordinates were already in this list; the direction comes from the same function the waypoint lists use. Deliberately the compass direction and not a clock direction: a clock bearing is relative to where you are facing and would be wrong the moment you turn, while this list is built once and then read through. An entry's quest id now comes straight off the row instead of a lookup table whose key was the old label text without the direction. ------------------------------------------------------------------------------------------------------- Keyboard echo: characters are spoken instantly Simple: While typing into an input field the letters did not keep up with your fingers: some were swallowed, others arrived noticeably late, and doubled letters as in "letter" or "all" were regularly heard only once. Every typed character now arrives immediately, and a key pressed twice is spoken twice. Technically: The echo used to run through the same queue as every menu announcement, and that queue is built for announcements, not for keystrokes. It collected characters for a tenth of a second and spoke them with overwrite - and overwrite here means: first StopSpeakingText, then wait another tenth of a second so that stop does not wipe out the very line it was meant to make room for. Those two in series are about 0.2 seconds per keystroke, and one keystroke's stop regularly landed on the next one. On top of that came this version's new back-to-back duplicate guard, for which a single character is an utterance identical to itself - hence the swallowed doubled letters. Typed characters now take their own fast lane: one slot in which the newest keystroke wins, handover on the same frame, the stop issued once at the start of a typing burst instead of once per keystroke, and the duplicate guard no longer sees typed characters at all. Background: a screen reader never needs any of this, because its own interface offers cancelling as part of the speak call - one single operation. The game's interface cannot do that, so Sku has to rebuild it from two calls that can overtake each other. What is new is doing that only once per typing burst. A line asked for again by keypress is spoken again Simple: Asking to hear a menu line again with a keypress sometimes produced silence - the speech output considered the repeat redundant. Technically: The duplicate guard discards a line that was already spoken identically within one second. That is right for the redundant echo of a menu rebuild and wrong for the same text the user just asked for with a key. The two cannot be told apart by their text, and no time window separates them - only what triggered them. The menu's key handler therefore now marks its announcement as key-triggered, and the guard always lets marked lines through. Redundant repeats are still discarded, now regardless of how fast they arrive. Quests with mixed objectives show every objective kind Simple: For a quest like "kill 8 wolves AND collect 5 pelts", the "Quest target" entry in the quest menu only showed the first objective kind - usually the enemies; the places to find the collectible item, or an exploration point, were silently missing. 95 quests were affected. All objective kinds now sit side by side in one list, and the "Target nearest quest mob" keybind also knows both halves of such quests. Fixed along the way: a quest that has only an exploration point and no other objective data now gets a "Quest target" entry at all, and an unresolvable exploration point no longer produces an entry that just announces "empty". Technically: SkuQuest:GetQuestTargetIds was an elseif chain over the quest database's objective lists (creatures, else objects, else items, ...) and could only ever return ONE list with ONE type by its very signature. Replaced by GetQuestTargetGroups: an array of {targets, type} groups - creatures merged with deduplicated kill credits, objects, items and, for the objectives callers only, the quest's triggerEnd point. All seven call sites are converted (quest menu, waypoint cache, quest markers, quest-target key); the old name remains as a first-group wrapper for the WowVision port. The item branch of GetResultingWps is also guarded against unknown item ids now - it indexed unchecked so far and let the swallowed menu build die as a silent "list empty". Quest database brought up to Questie's current data Simple: Sku's built-in quest database comes from Questie and was about two years old. After the sync, 33 quests whose "Quest target" used to stay empty have real targets (the Darkmoon card decks, the repeatable Ogri'la and Violet Eye quests, "Finding the Keymaster"), the six "Investigate the Scourge" quests now have exploration points, five missing quests were added, and about 2000 records carry smaller corrections to names, levels, quest givers and prequest chains. Verified: the corrections are identical for Era and TBC, the update is safe on both. Technically: A semantic merge, not a byte copy (new tool dev/rework-docs/_refresh_questdata_from_questie.py, then _db_convert.py): Questie's schema diverges from field 27 on, so only fields 1-26 are adopted; Sku's own extraObjectives and skuData fields are preserved per record. The requiredRaces convention (old all-races mask 2047, now 0 - identical for Sku's logic) counts as equal in the comparison, otherwise 2117 records with no content change would have sat in the diff. Every referenced creature, object and item id was validated against the shipped databases (base + WotLK); one record was kept at its old state because of that. Event quests no longer show up right after loading Simple: In the first seconds after a /reload, quests of inactive world events could appear in "Starts in zone" - Midsummer quests in August, say. Once Questie had finished loading they vanished again by themselves; they are now hidden in that window too. Technically: The two availability gates only consulted Questie from Questie.started on, and that flag flips at the very END of Questie's initialization. Questie's static blacklist QuestieCorrections.hiddenQuests - removed quests plus ALL event quests; the active ones are only unhidden after the server's calendar answer - exists much earlier though. IsQuestieUnavailable now reads it directly before started: a plain table read; the memoize-poisoning hazard behind the started gate is specific to IsDoable. The value "HIDE_ON_MAP" is skipped just like Questie itself does, and every pre-start hide writes a dprint line to the log. Quests in dungeons lead to the entrance instead of nowhere Simple: For about 300 quests the quest giver, the target or the turn-in sits inside a dungeon - "Vorrel's Revenge" starts in the Scarlet Monastery, "Willix the Importer" ends in Razorfen Kraul. Quest start, Quest target and Delivery used to be empty there, because instances have no world map. Those entries now read "NPC, entrance, dungeon name, zone" with a route to the dungeon entrance - battleground quests included (Alterac Valley picks the right entrance per faction). Drop sources of an ITEM inside the same dungeon collapse into a single "dungeon name, entrance, zone" entry - "The Fate of Ramaladni" otherwise named 29 Naxxramas trash mobs one by one, all leading to the same gate; kill objectives keep their individual mob names. Technically: New generated table SkuDB.InstanceEntrances (from Questie's dungeon table, tool dev/rework-docs/_gen_instance_entrances.py): instance areaId -> entrances {parent zone, x, y, optional faction}, including the wing sub-ids and a GetBuildInfo gate for the WotLK entrance moves. The spawn resolvers (CreatureIdHelper, the object and item branches of GetResultingWps) no longer skip map-less areas; they answer like the v43.2 triggerEnd detour: convert the entrance coordinates to world coordinates, take the nearest route waypoint. Memoised per waypoint-cache generation; the faction filter reads UnitFactionGroup. 77 field-objective quests now have a real quest target Simple: 77 quests whose objective is a place or object out in the field (the Mulgore water wells, the druids' Moonkin Stone, the Crystal Pylons of Un'Goro Crater, Rolf's corpse, the four draenei totems, the hunters' Taming the Beast chain and many more) had no target data at all - the "Quest target" entry was simply missing. All of them now have a real, routable quest target. Important: the objects and creatures come from Sku's own spawn data, but 26 locations come from web sources (warcraft.wiki.gg, Wowhead) and could not all be checked in game. They were chosen with care but may be off in individual cases - a report on whether such a target is right or wrong helps a lot. Even an imprecise point beats none at all: the route leads into the right area. Technically: Result of the review list dev/rework-docs/QUEST-TARGET-PROPOSALS.txt (sections 1-3, reviewed by hand): 27 object and 24 creature targets as objectives entries in quests_fixes.lua (ids validated against the own databases, spawn zones verified, no -1,-1 positions in the open world) plus 26 triggerEnd coordinates from the web research. Two quests (9410, 10166) already carried the right object id in extraObjectives, which the target menus do not read - the objectives field was added there. Thirteen talk-to quests have a turn-in again Simple: Thirteen quests - above all the old priest spell quests (Stars of Elune, Elune's Grace, A Lack of Fear, Hex of Weakness), plus among others "The Tome of Nobility" and "Ally of the Netherwing" - shipped without a turn-in in the data: neither a target nor a delivery point, nothing was routable. The Delivery entry now leads to the NPC the quest text itself names. Technically: finishedBy entries in quests_fixes.lua; the creature ids were resolved from the quest text against the creature database (every name is unique there, and the spawns lie in the zone the text names). The candidates come from the new tool dev/rework-docs/_quest_target_candidates.py, which classifies every target-less quest and writes the review list QUEST-TARGET-CANDIDATES.txt for the remaining ones (real field objectives without a coordinate source). Experimental: gather routes Simply: You pick a resource - an ore, a herb, a gas cloud or a kind of chest - and Sku walks you through the zone node by node, always the nearest one first. Where the path data covers the node it takes a real route; where it does not you get a straight beacon and Sku says so - a beacon can point through a wall, a route cannot. While flying it never routes: up there the straight line is the right answer, not the fallback. At the node Sku waits briefly while you gather, then moves on. The keybind "next route waypoint" (Ctrl+Shift+W by default) skips the current node while a gather route is running, so no new key is needed. With the matching tracking active (Find Minerals, Find Herbs) the minimap scanner checks on approach whether the node is actually still there, and skips mined-out ones with an announcement of its own. Without tracking the route still runs, just without that check. Found under Settings -> Scan -> resource scanning -> Gather routes, together with the check radius, the number of nodes per route and the switch for the check itself. For ore, herbs and chests "Show herb and mining node waypoints" has to be on in the navigation settings - otherwise those nodes do not exist as waypoints at all, and Sku says exactly that on start rather than wrongly declaring the zone empty. Gas clouds do not need the setting. Important: This feature is explicitly experimental. It has only been tried in game in outline, and the "is the node still there" check could not be exercised under real conditions at all, because no character here has a gathering profession - without Find Minerals or Find Herbs there are no blips on the minimap for the scanner to read. If you play Mining or Herbalism: please try it and report what goes wrong. Technically: The idea and the reference implementation come from ZenqFR, from the companion addon SkuGatherRoute (github.com/ZenqFR/Sku-GatherRoute); the route mechanics were devised and demonstrated there - a waypoint family under one shared base name, every node reached over a close route, a distinct announcement for every transition, and the existing keybind as the skip key. Here the feature is rebuilt natively with ZenqFR's agreement, so that no additional addon is required. One difference is deliberate: the original requires GatherMate2, this version does not. We see no advantage in that dependency - the nodes have long been in SkuNav's waypoint cache complete with world coordinates, and named so that the resource IS the waypoint's base name; a second data source and a second addon would add nothing to that while costing every user another install. Should GatherMate2 do something we have overlooked - self-recording of nodes actually gathered would be the candidate - then please tell us what for, and we will look at it. The presence check hooks MinimapScanner:MinimapScanFastStop, the one point every scan funnels through. With "notify on resources" on, the scan that is running anyway is enough; otherwise the route scans by itself, throttled and only within range of the target, and those scans stay silent. A hit confirms, a miss proves nothing - a scan only ever reports one name and may have caught a different blip. Only after several unsuccessful scans in range does a node count as gone. When in doubt it never skips: a node wrongly kept costs a few steps, a node wrongly skipped is never noticed at all. The menu stays usable while you walk Simple: If you opened the menu while walking, it came up and said "menu opened", but no key did anything any more - no arrow, no letter, no Enter. Only Escape got through. It only became usable once you stood still. That is fixed: an open menu now accepts keys whether you are walking, running or auto-running. Technically: There were two movement gates, not one. The first prevents the menu from OPENING while a movement key is held down - that one stays, and it is needed: the menu's bindings would swallow the key-up and you would run on forever. Auto-run has been exempt from it since v42.00 (its own AutoRun flag, which IsPlayerMoving deliberately does not read). The second sat in the menu's own key handler and, as long as a movement latch was set, let every keypress except Escape expire. That second gate buys nothing: once the menu is open the bindings already own the keys, so discarding the press stops no movement - it only deadens the menu, and on top of that stamps "open later" on a menu that is already open, which the reopen loop then has to clear again. It now applies only when the menu is NOT open. The four remaining open-gates also write a line to the debug log naming the movement key that is holding them - before, they were silent, and a report of "the menu would not open while walking" left no trace at all. Navigation: "Current map by distance" now comes first Simple: Under "Direct waypoint", "Recent" was first and "Current map by distance" second. The two are swapped - the list of waypoints in the current zone is the more common way in, and it now sits directly under the cursor. Technically: Purely the order of the injections in SkuNav:MenuBuilder. "Recent" remains tied to a non-empty list of recently used waypoints; when that list is empty, "Current map by distance" is first anyway, as it was before. The type-ahead filter ignores accents Simple: In every list you can narrow down by typing, accented letters were treated as characters of a different language. On a French client "general" found nothing even though "Fournitures generales" was right there, and "ingenieur" did not find "Ingenieur" - while "ing" did. German was hit too: an entry starting with a capital umlaut could never be reached through the filter. Now the letter counts, not the accent. On top of that, the u key on a French AZERTY keyboard now types into the filter as well - it is the only accented letter that keyboard delivers as a real key event. Technically: string.lower in Lua is byte-wise and maps A-Z and nothing else, so it never folds an accented character on either side of the comparison. SkuMenuFilterMatch (used by both ApplyFilter and JumpToFilterMatch) and SettingsSearchMatch now fold the entry name and the typed query to unaccented ASCII before comparing; only 2-byte Latin-1 Supplement and Latin Extended-A sequences are touched, and pure ASCII takes a fast path. Strictly more permissive than before: nothing that matched stops matching. The two string.find calls also pass plain=true now - the typed query was being read as a Lua pattern, so an input like "50%" or "(long" found nothing or threw. The accent folding comes from PR #9 (ZenqFR), the u key from PR #13 (Naxedim). Action bars: the spell tooltip is read out again Simple: In the action bar menu (Shift-F11), Shift-Down on a spell in the assignment list read out nothing at all. The tooltip is back - as it is for a key's current content and for the two pet ability reads next to it, which had the same weakness. Technically: Two independent causes, both addressed because neither could be ruled out from the code alone. First, SkuScanningTooltip is created once at login with no template and shared by every module; whatever used it last can leave it without a usable owner, after which every Set* call populates nothing - silently, with no error. It is now re-owned immediately before being populated. Second, SetSpellByID was handed the third return of GetSpellBookItemName, which on this client follows the Classic signature and returns only (name, rank) - the id is a retail addition and was nil. The id now comes from GetSpellBookItemInfo, and the tooltip is populated with SetSpellBookItem, which needs no id at all. The read is also pcall'd, since it runs on every cursor move. From PR #10 (ZenqFR). Quest target: the place was in the data, but never found Simple: 174 quests that ask you to explore a spot or use something there answered nothing but "Empty" under "Quest target". The entry now leads to the nearest route waypoint at that place. Technically: GetTriggerEndWps composed a waypoint NAME (";;;;") and resolved it through the exact-name lookup in the waypoint cache. But nothing generates a waypoint under that name - unlike creature and object spawns - so it only ever hit if someone had authored one by hand into the route data: exactly one exists (quest 10211, Shattrath). The composed name is still used whenever it resolves; otherwise geometry takes over - map coordinates to world coordinates, then the nearest real ROUTE waypoint. GetNearestWpToCoords2 gained an optional typeId filter for that, so the fallback does not land in the ~145,000 generated spawn points, plus a guard for a missing continent bucket where pairs(nil) threw. Every coordinate pair of every zone is read now instead of only the first, and the result is memoised per waypoint-cache generation. French: the waypoint comments speak again - and now in French Simple: The comments on waypoints - among them the safety-critical ones such as "careful, from here the route runs along the edge of a gorge", "wait here for the zeppelin" or "this NPC moves" - had been completely silent on a French client since v42.11. They speak again, and they now speak French: 207 texts sitting on 546 waypoints. Technically: The route data carries lComments for enUS and deDE only (~296 waypoints). SkuNav:PlayWpComments read comments[Sku.Loc] with no fallback. Until v42.11 Sku.Loc was "enUS" on a French client because no frFR locale existed, and you heard the English texts; since locales/frFR.lua ships, Sku.Loc is "frFR", the lookup is nil, and the function returns without a word. New Sku.locList (SkuUtil.lua), the list counterpart of Sku.locStr, returns the first NON-EMPTY list among Sku.Loc / enUS / deDE. Non-empty matters, because SkuMM.lua materialises an empty comments[Sku.Loc] on every waypoint it draws - a plain or chain would latch onto that. The translation is made from the GERMAN original, not via the English: the English lost detail on exactly these safety texts (the gorge warning without "you walk on the edge" and without the direction of travel, "extremely narrow bridge with a ravine" reduced to "extremely narrow bridge"), and several English entries are empty, including the lethal-elevator warning that sits on eight waypoints. French: resource names and the Cave of the Mists Simple: Two faults that hit a French client only. First, 22 of the 94 minimap scanner resource names were wrong - the scanner matches them against the game's tooltip, so a wrong name means that node is never announced, with no error. Second, inside the Cave of the Mists the waypoint list stayed empty, while a route started outside kept running. Technically: Each of the 22 names was adjudicated against a source independent of both addons: 19 through SkuDB's own frFR tables (imported from Questie's Blizzard strings), three through Wowhead's tooltip API. For the cave, Geo.lua compares GetMinimapZoneText() verbatim against L["Cave of the Mists"]; the generated frFR locale held "Grotte des Brumes", a free translation - the client says "Caverne des Brumes". The compare was therefore dead, uiMap stayed on the continent, GetAreaIdFromUiMapId answered -1 and GetCurrentAreaId nil, so ListWaypoints2 bailed out. The value was read with /szp in the zone, not translated, and now sits both in the generated file and in the TSV store it is assembled from - together with the note that it must not be translated freely. From PRs #14 and #16 (Naxedim). Turn to beacon: more accurate, and with lead Simple: The automatic turn to the beacon (T) now lands much more accurately. It used to run so fast that stopping it on a frame boundary alone could cost twenty degrees of overshoot or more, depending on your frame rate. The speed now follows the size of the turn, and no turn takes longer than a quarter second. While you are moving it also leads: it aims at where the waypoint will be when the turn ends - which ends the circling around nearby waypoints that showed up mostly when mounted and in flight. And a press during a turn that is already running is discarded instead of aborting that turn halfway; pressing repeatedly still refines as it always did. Technically: Four parts, tested together. The snap to the default camera before the turn stays - it is load-bearing for the yaw, without it the angle is wrong. The turn speed is capped from the remaining angle instead of running flat out. The lead computes from the current speed and the expected duration of the turn where the waypoint will be at the end. Plus a guard against the fault where, after turns triggered in quick succession, the camera keys' turn speed could stay quadrupled for good. The installer remembers where your WoW is Simple: The installer looks for the game in the usual places - Program Files, plus a handful of likely folders on C, D and E. Anyone whose WoW sits anywhere else had to browse to the folder by hand, and not once but on EVERY update: the installer forgot the answer the moment it closed. It now writes the folder down after an install has gone through, and starts there the next time. Showing it one client also finds the other one beside it, so a Classic Era next to an Anniversary on the same unusual drive is picked up by itself. If the game later moves or is uninstalled, the note is dropped and the search runs as it did before. The installer is version 4.5 with this. Technical: New PathMemory.cs. The install manifest could not serve for this - SkuInstall.json lives INSIDE the AddOns folder, so reading it presupposes having already found the folder we are trying to remember. Each client's folder is therefore written as a plain "product=path" line into SkuPaths.txt, in two places: %LOCALAPPDATA%\SkuUpdater, beside the persistent updater copy the "Sku Updater" shortcut launches, and %ProgramData%\SkuUpdater as a machine-wide mirror. The mirror is not redundancy for its own sake: the installer requests elevation, so a standard user who answers the UAC prompt with someone else's admin credentials runs the whole program under that other profile and would otherwise write the note into a LOCALAPPDATA they never see again. On the next run a remembered folder outranks detection - it is a decision, while detection is a guess over a short list of drives - and its base directory is prepended to the probe list, which is what makes remembering one client pay off for the other. Guards against a stale note: the folder must still exist, and its .flavor.info must not now report a different client; either way the note is ignored rather than sending an install somewhere dead. Written only after a client has installed cleanly, so a cancelled or failed run leaves nothing on record. Plain text on purpose - it can be read out and corrected in Notepad, and deleting a line restores auto-detection for that client. New headless check "SkuSelfTest pathmemory": it prints what the machine has on record and round-trips the write, read and stale cases against a throwaway store, never touching the real one. The reading window can be configured Simple: Quest text, tooltips, the chat history and the wiki pages all end up in the same reading window. How that window looked was fixed in the code: Playfair Display at 12 point, a thin serif face at the smallest size the addon uses anywhere. For someone with residual vision that was the most important surface in the addon and at the same time the hardest to read. Settings -> Visual aids -> Text window now offers font sizes from 14 to 44 point, four fonts, four color pairs (white on black, yellow on black, black on white, white on blue), the background opacity and an outline. Out of the box the window now runs in a sans serif face at 18 point, so even without changing anything it is considerably easier to read than before. Technically: The surface itself belongs to Libs/SkuTTS-1.0 (frame SkuTTSMainFrame with its FontString .FS), where the appearance was a single hardcoded SetFont call. The settings now live in SkuCore VisualAids under visualAids.textWindow, and SkuTTS:Output calls VisualAids:TextWindowLayout before each show - pcall-guarded and in the same direction in which SkuZOptions already feeds the reading bar. That way the lib needs to know nothing about the options, and a disabled Visual aids module simply leaves the window looking as it did. Every font assignment is checked and falls back to the client's standard font rather than leaving a blank surface - the two bundled families Raleway and Playfair live under Libs/SkuTTS-1.0/fonts and ship only in the release ZIP. The size steps are deliberately smaller than the reading bar's: a whole quest text goes here, and the window has no scrollbar, so it clips at the bottom sooner the larger the font gets. Everything is still spoken in full; the display is the second track. The reading bar shows what Sku says - and it works in combat Simple: Two things at once. First, the bar stayed empty in combat, which is exactly when reading along matters most. Second, it showed only the name of a menu entry: on an on/off switch you got the name but not the state, on a bag line the name but not the count - everything Sku additionally reads out was missing from the bar. Both are fixed; the bar now shows the same line Sku speaks, and it does so in the middle of combat too. On top of that its font and color pair are now selectable as well, with values of its own alongside the text window's. Technically: The combat lockout was a confusion of visible with open: the check hung off SkuOptions:IsMenuOpen(), which tests OnSkuOptionsMain:IsVisible(). That frame is an ancestor of the secure ENTER buttons and therefore protected, so its Show() is blocked in combat - the combat menu deliberately runs headless and reports through SkuOptions.combatMenuActive instead. templates.lua and SkuZOptions already check exactly that pair; the reading bar simply did not know about it. The bar itself is an ordinary frame with no secure children, so its Show() was never the problem in combat. Because the combat path only closes the menu logically (the frame Hide is protected there) and does so at six different sites, the bar now hides itself: an OnUpdate checks every 0.25 s whether the menu is still open - WoW runs OnUpdate only on visible frames, so it costs nothing while hidden. The content was simply a discarded parameter: the caller always passed the complete spoken string, yet currentMenuPosition.name was displayed instead. The passed string now takes precedence, and the semicolons of the speech separator become dashes for display. Nameplates: the settings the game hides Simple: The client has long been able to draw its nameplates bigger, more colorful and with more contrast - it just never offers it. Blizzard's own options window shows not one of these settings in Classic; the checkbox for larger nameplates that current WoW has is explicitly left empty in Classic. Without Sku you could only reach them through console commands. The new menu under Settings -> Visual aids -> Nameplates makes them available: nameplate size, target emphasis, dimming of all other plates, class colors for enemies and allies, and names only for friendly players. Unlike the older nameplate coloring this does not paint something behind the plate but changes the real plate itself. Out of the box everything sits exactly where the client ships it - choose nothing and nothing changes. Technically: Pure CVar settings, no frame of our own, no taint. Nameplate size sets nameplateMinScale and nameplateMaxScale together (setting them apart would produce distance-dependent scaling, which nobody wants here), emphasis sets nameplateSelectedScale, dimming nameplateMinAlpha, plus nameplateShowClassColor, nameplateShowFriendlyClassColor and nameplateShowOnlyNameForFriendlyPlayerUnits. Every value and the existence of each individual CVar was measured against the live client 2.5.6.69546, and the 2.0 ceiling checked against the client's clamping. Important for future work: the extracted BlizzardInterfaceCode in the client folder is a March/April 2026 dump and therefore older than the July nameplate change; it names retail CVars such as NamePlateVerticalScale or ShowClassColorInNameplate that do not exist in this exe at all. The exe strings are what counts. CVar writes are blocked in combat and fail silently, so a change made there is remembered and applied at PLAYER_REGEN_ENABLED. Sku re-applies the values at login, but only once something has actually been chosen in the menu - anyone who set their nameplates by hand through the console keeps them untouched. Nameplate coloring survives a restart Simple: If you switched nameplate coloring on, you had it exactly until you logged in again or reloaded the interface. After that the switch still read as on, but nothing was colored any more. The only way to get it going again was to flip the switch off and on by hand - once per session, every session. That is fixed; the setting now simply works. Technically: Arming the event driver (NAME_PLATE_UNIT_ADDED, _REMOVED and PLAYER_TARGET_CHANGED) hung exclusively off the menu switch's OnAction. The stored value was read at load but applied nowhere, so after every login not a single event was registered. Arming now sits in VisualAids:OnEnable, where the follow warning and the next-enemy button have always been armed, with a delayed second attempt in case the profile is not up yet on the first pass. A closed window is no longer announced after the fact Simple: Learning several spells at a trainer, closing the window quickly and then opening your profession window brought the trainer window back after a while - sometimes only after you had read a tooltip. Sku remembered where in the menu it still meant to jump and did it later, without checking whether that window still existed. Technically: The deferred menu jump is three fields (openMenuAfterCombat, openMenuAfterMoving, openMenuAfterPath) that used to be written independently. SlashFunc arms flag AND path together; other sites armed only the flag - and so inherited whatever path was still lying around. The path was also only cleared in the deferred part of CheckFrames, which bails out while you are moving and retries half a second later, whereas the release runs per frame and fires the instant you stop - a race the release won. The three fields are one unit now (SkuCore:ClearDeferredMenuOpen), arming a flag drops the path, and the clearing happens in the window's Hide hook (SkuCore:GENERIC_OnClose) - exactly when the jump becomes invalid. Deliberately without a timeout and without a visibility probe: the state is cleared where it goes wrong, rather than outrun. The menu no longer opens its top level by itself Simple: Now and then the menu opened on its own - and at its very top, instead of where you wanted to go. Technically: SkuCoreControlOption1 holds keybinds (target distance, panic mode, minimap scan, cancel flight, turn to unit) and has nothing to do with the menu. Its OnShow bails while you are moving or in combat because SetOverrideBindingClick is not allowed there and would swallow the key-up of a held movement key - but it bailed by setting openMenuAfterMoving, and the release, finding no path, turned that into a menu opening at its root. The frame has its own flag now (rebindControlKeysAfterBlock) and only gets its keys back. Closing a second open window is noticed again Simple: With two windows open at once - a vendor and the bags, say - only the first close was noticed. On the second the menu neither rebuilt nor closed. Technically: One of the two hook installers wrapped Show and Hide around a single shared switch (SkuDropdownlistGenericFlag): any Show set it, and only the first Hide after that consumed it. Which installer got a window was pure timing - this one takes everything that exists one second after entering the world, while windows such as the trainer, the tradeskill and the auction house are created later and fall to the other. The switch is gone; GENERIC_OnClose coalesces on its own anyway. No contents of a closed window under "Local" any more Simple: After a window was closed its entries could stay under "Local" and turn up again the next time the menu was opened. Technically: SkuCore.GossipList is the Local menu's data source, but it was only emptied when the menu happened to be open at that moment. On the Escape path it never is: the menu frame deliberately goes down before the windows, and the code responsible runs a frame later. It is now emptied as soon as no window is open. Trainer: quiet once it is closed Simple: Learning several spells in quick succession and closing the window right after left the trainer's announcements still coming. Technically: Every click on "Train" queued a rescan after half a second and an announcement after 0.85 seconds, with no way to cancel them. Both steps now check whether ClassTrainerFrame is still visible at all. The v43.1 guard against speaking into a closed menu did not help here: as soon as the next window reopened the menu, it was open. The retry for silent rescans while moving also lost its silent flag and was scheduled once per click - it now carries its arguments along and runs only once. Auction house: the result list really is sorted Simple: Sorting by "Level descending" still gave you a list that started with low-level junk. The sort only ever worked inside one page - and one page is the 50 cheapest auctions. What stood at the top was the highest level among the cheapest grey goods, while the level 70 items sat hundreds of entries further down. Even after the finished sound, because nothing was ever re-sorted. Now the server sorts the result list by level itself, and equal levels are ordered by the price per item. Technically: Every query went out with the server sort hard-wired to "unitprice", and the result list is built page by page, append-only (new items go to the end so the user's cursor never jumps). The order of the list is therefore always the server's, and our own sort only ever ordered one page. For "Level ascending" and "descending" the server answer is now sorted by the "level" column - the same column Blizzard's own level sort button sets. Buy and strategy buy stay on "unitprice": they depend on the cheapest offer being on page 0. So does a name search, where every hit is the same item: the server would have sorted by a field in which nothing differs and handed back its pages in its own order instead of cheapest first. On top of that, the level comparators now use the price per item as their second key; bid-only auctions count with their bid per item instead of a buyout of 0. Auction house: "level" means the required level everywhere Simple: The level in the results was sometimes the required level and sometimes the item level, depending on which list it came from and whether the item has a requirement at all. A stack of Netherweave Cloth (requirement 0, item level 55) therefore outranked a level 40 sword. It is now always the required level, the column the auction house itself calls "level". And it is only read out where it tells the entries apart: in a category or in the full-scan list. A search for an item name returns auctions of one and the same item - there it was the same number 800 times over. If you filter by level, you still get it announced everywhere. Technically: One shared reader for both result lists: field 6 of the server answer, else the value from SkuDB. Field 7 is honoured - for recipes field 6 holds the required profession skill, for containers the number of bag slots, neither of which is a level. The item level is never substituted again. 0 now means "no level requirement" instead of "unknown", which is why no "Level 0" is announced any more (0 is true in Lua, which is how it got spoken in the first place). Auction house: "usable only" no longer hides anything Simple: In the full-scan list this filter left nothing but low-level goods in some categories - exactly the high-level items you were shopping for were gone. The normal search never behaved that way. Technically: In the live list the server filters. The full scan fetches everything unfiltered and Sku decides for itself - until now strictly by the canUse field from the scan answer. The client can only fill that field when it already had the item data for the row; during a full scan it does not have it for the majority of rows. They are the very same rows for which name, level and quality have to be repaired - only canUse had no repair. So what disappeared was what the player has never seen, and that is mostly the high-level things. A "not usable" now only counts on a complete row; otherwise the required level from SkuDB decides. The database knows no class or race restrictions, so in case of doubt one item too many is shown rather than the wanted one hidden. Auction house: no filter means no filtering Simple: Even with no filter set, the full-scan list dropped everything without a level requirement - trade goods, materials, many recipes - and all grey items on top of that, although the quality menu kept displaying "Poor", i.e. no restriction. Technically: The defaults of the unset filters were a 1 each instead of a 0, and the level was read raw from the scan answer. A row whose level the client did not know during the scan came back as 0 and thus fell below the minimum level of 1; the repair from the database only fired on a missing value, not on a 0. Both defaults are 0 now, the level comes from the shared reader, and the repair during ingest treats 0 as "missing". The user's own minimum level as a last-resort fallback is gone: a row stamped with it always passed the user's own filter and was then announced with that level as well. Auction house: categories without subcategories list their items again Simple: Under "Auctions by item" there were categories that contained nothing but "All" - quest items, for instance. Technically: The item list compared the subclass even where there is none. A category without subcategories is built without a subclass, and the comparison is then false for every item. The full-scan twin has carried the guard all along; the item list does now too. Auction house: a cancelled purchase stays cancelled Simple: Cancel a buy, do something else, and out of nowhere the buy dialog could come back - and the next Enter, meant to open the search field, bought. At least it bought the right thing, but nobody had asked for it. Technically: Cancelling dropped the timers and the key bindings, but not the buy target. The query function reads its mode off exactly that - there is a buy target, so this is a buy query - and so the user's next, completely unrelated query ran into the buy path: it found the old target in the fresh list, built the dialog and armed Enter. In the log of 2026-09-01 the cancel (18:45:40), the new search (18:46:12, immediately "MATCH FOUND, showing popup") and the purchase (18:46:16, "bid accepted") are four menu keypresses apart. The buy target is now cleared at the three points where the intent to buy ends: cancelling the dialog (including through the 30 second safety net), starting a new search or list, and closing the auction house. The buy path itself is untouched - it does not go through the search entry point, and the retry after a lost race keeps its target. Auction house: an aborted search says so Simple: When a search is cut short because the server stops answering, the half-filled result list stayed on screen and looked like a finished result. Now you hear "Search aborted, list incomplete". Technically: The watchdog ends a paged search after 60 seconds without a server answer, or after 180 seconds in total - silently, until now. The finished sound is the signal for "complete", but its absence is not something you hear. It is only announced when the user is actually inside that result list; a search running in the background stays quiet. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.1 Overview Changes Buying multiple pieces at the auction house now runs through as long as more offers at the same audible price exist. Every successful purchase is confirmed right away with "Bought, n of m". When a price is sold out, Sku says "No more offers at this price" with the tally and stays in the results list. New setting "Speak keyboard echo" (Settings -> General, on by default): the character-by-character read-back while typing in input fields can be turned off. Auction house: the full scan confirms itself for the first time after 5 seconds, then every 10 seconds as before. Quest menu: the "Group members" entry now sits in third place instead of in the middle, so "Current Quests" and "Quest database" always keep their position. Bugfixes Auction house: buying multiple pieces wrongly aborted after almost every successful purchase with "Auction sold out" and threw you out of the search. After closing the menu, deferred announcements kept speaking ("New letter plus", "Subject: ", "Sent", "All opened" from a mailbox that was long closed). Auction house: an incomplete server response no longer wrongly ends the search with "empty" - a genuine search with no hits still answers immediately. Auction house: a search that cannot start at all now names the reason ("not possible, scan in progess" or "Auction house busy") instead of saying "empty". Auction house: a search with no hits now says "no results" instead of "empty" - and the search field stays in the list, with the cursor landing right on it. Auction house: a full scan cancelled by closing the window afterwards blocked every search with "not possible, scan in progess", although no scan was running any more. Auction house: the full scan sometimes died silently right after the start announcement; the 16-minute lock was still consumed. French clients: the waypoint list was empty in several zones (fix by Naxedim). New setting: Speak keyboard echo Simple: In input fields (writing a letter, search, profile name...) Sku reads every typed character out loud, as well as backspace and the cursor readings with the arrow keys. If you dislike that or hear it twice, you can now turn it off under Settings -> General, "Speak keyboard echo" (right next to "Speak menu numbers" and "Announce submenus"). The default is ON, so nothing changes unless you change it. Status announcements like "Cancelled" or the field confirmation after ENTER always remain. No more announcements into a closed menu Simple: Confirming an input field with ENTER and closing the menu right after still produced "Subject: " - as if the keyboard echo trailed the close. In the same way, "New letter plus", "Sent" or "All opened" still spoke from a mailbox that was long closed. Now anything that wants to announce a menu entry first checks whether the menu is even open - and the mailbox receipts check whether the mailbox is still open. The server system line "Mail sent." in chat is unaffected. Technical: The stragglers were never the speech queue (closing already clears it via queuereset + StopSpeakingText) but producers that only enqueue AFTER the close: C_Timer confirmations (0.3 s after attaching/field input/sending, 0.5 s after "Open all") and server events (MAIL_SEND_SUCCESS). Another blanket queue purge on window close would therefore not catch them, but WOULD cut legitimate announcements (chat, combat, navigation) off mid-word. Instead: a central gate in SkuOptions:VocalizeCurrentMenuName (no open menu, no headless combat menu and no SkuChat line menu -> silent; the string mode still returns text), plus three producer gates in mail (MailboxOpenFlag for "Sent", IsMenuOpen for the field confirmation, MailFrame visibility for "All opened"). MAIL_FAILED deliberately stays unfiltered - a failure must be audible even after closing. Auction house: buying multiple pieces wrongly aborted after almost every purchase with "Auction sold out" Simple: Picking a results entry like "14 times Void Sphere" and buying five of them meant hearing "Auction sold out" after almost every purchase and landing back outside the search - even though the purchase had succeeded and the money was deducted correctly. Since the success itself was never announced, it sounded like a string of failures while the items were already in the bag. The cause: sellers undercut each other by 1 copper. The results list groups all auctions whose price SOUNDS the same - above 1 gold the copper part is not spoken - but the buy continuation looked for the next piece at the exact copper amount, which usually no longer existed once the cheapest one was bought. The continuation now works through such a group piece by piece: the next offer may only differ in copper, the inaudible part of the price. An audibly more expensive offer - whether 1 silver or 100 gold more - is NEVER offered automatically, and every single piece still requires its own confirmation. Technically: Proven in the debug log of 2026-08-25: five purchases at 52g99s81c through 52g99s84c, each run bought exactly one piece and then aborted. The continuation (_ABReQueryBuy → match scan) compared item ID + stack size + EXACT buyout; the results list however groups by the formatted label (SkuGetCoinText very-short drops copper above 1 gold), so a group spans a 1-silver bucket. The scan now tracks the cheapest buyable offer with the same item ID and stack size in the same bucket (floor(buyout/100) equal, only from 10000 copper up) and adopts its price fields (8/9/10/11) into QueryBuyData; failCount restarts at 0 for the new price group. Below 1 gold the label speaks the copper - there the old exact behaviour remains. Bid purchases keep the strict 17-field comparison. Auction house: every purchase is confirmed, the end of a price group reports the tally and stays in the list Simple: After every successful piece Sku now immediately says "Bought, n of m" - before, the only success signal was the next buy prompt. Once all offers at the same audible price are gone, it says "No more offers at this price" with the tally, for example "2 of 5 bought", and the cursor stays on the entry in the results list instead of jumping several levels up to "Auctions" as before. From there you can navigate straight to the next, more expensive entry and buy again. Technically: The success announcement sits in _ABContinueOrFinish, right after the server's "Bid accepted.". The sold-out path positions first (AuctionStayOnResultsEntry; if pruning fails because the record was already removed after the purchase, a fallback via AuctionResultsItemEntryFromCursor keeps the cursor on the item entry) and speaks the message 0.4 s later without interrupting - the same pattern as the existing "Done. All bought". Auction house: an incomplete server response no longer counts as "no hits" Simple: When the server answers contradictorily - it reports hits but delivers not a single row - Sku used to end the search right away with "empty". It now waits for the complete response in that case and requests the page again. A search that genuinely finds nothing (a typo in the search field, an empty category) still answers immediately - with a hit count of 0 and "no results". Technically: In AUCTION_ITEM_LIST_UPDATE_LIST an empty page only counts as premature when the server contradicts itself: tBatch == 0 (no row on this page) while tCount > 0 (total hit count greater than 0). QueryListEmptyWaits then counts up, the scan stays in "waiting" and the ticker re-requests the same page; after 10 such responses the result is accepted anyway (emergency exit). tCount == 0, by contrast, is the honest "no hits" answer and has to pass through without delay: the buy path counts EVERY empty response at this point (QueryBuyEmptyWaits) because it wants to re-find an auction that may still be alive - for a search the same rule would be wrong. In the debug log of 2026-08-26 the server answers the search "feuerpartiekel" with 0 of 0 in the same second; that is the real response, not a phantom one. Auction house: a search that cannot start at all now names the reason Simple: There are moments when the auction house does not accept a search: while a full scan runs in the background, while a purchase is armed, or when the server is throttling. Sku did not notice that so far - it built the list anyway and said "empty", as if the search had found nothing. On the first attempt in a session that sounded like an empty category; later the list even showed the hits of the PREVIOUS search. Sku now names the reason. If a full scan is running, "not possible, scan in progess" is in the list the moment you open it - the same message the selling menu has always given in this situation. Waiting makes no sense there: a full scan occupies the auction house for minutes. For the short obstacles (armed purchase, throttle) Sku stays on "Waiting" and keeps trying by itself; if the search gets through within 5 seconds everything continues as usual, otherwise it says "Auction house busy, search did not start". If a full scan begins during those attempts, Sku aborts them at once and says so. Arrow left and right again starts a fresh attempt at any time. Technically: AuctionHouseStartQuery returns false in three places and, as of this version, the REASON as a second return value ("secureBuy", "getAllRunning", "getAllCooldown", "queryThrew"). The three browse callers - search field, "All" and single item in AuctionHouseBuildItemDBMenu - had ignored that return value and set QueryResultsHost, cleared children and rebuilt anyway; the results builder found state == "idle", skipped its "Waiting" branch and fell through to the empty check. They now go through AuctionBrowseStart, which decides on the reason: getAll reasons report immediately, everything else is queued as a retry (QueryStartPending). AuctionBrowseRetryTick hangs off the existing AH OnUpdate ticker - deliberately frame-driven rather than a C_Timer chain, because an aborted chain never reaches its re-arm line - sits at the very top ahead of its early returns (or it froze for the duration of a getAll ingest), attempts at most every 0.25 s and only when CanSendAuctionQuery is true, re-checks the reason, and re-queues itself explicitly after a failed attempt, since StartQuery calls ResetQuery internally and clears the marker in doing so even when it then returns false. After success QueryResultsHost is set again, because the fresh query setup nils it. QueryStartFailed carries the TEXT to display (AH_QueryBusy or the selling menu's scan message) and is checked BEFORE the "Waiting" condition: the blocking scan itself holds AuctionScan.state at "waiting"/"paging", so otherwise "Waiting" would always win and the reason would be unreachable. The immediate path deliberately does NOT speak - the message is the only list entry and is read out on opening anyway, while a separate announcement would be cut off by the following vocalize and fired again on every pass (OnEnter runs on arrival already). ResetQuery clears both markers. Auction house: "no results" instead of "empty", and the search field stays reachable Simple: A search with no hits - a typo, say - used to say "empty". That sounds like an empty list, while what is meant is "found nothing": it now says "no results". The search field had also disappeared from the list after a search; a second search meant navigating out and back in. It now stays in the list as "enter search string", and with zero hits the cursor jumps straight to it - one ENTER and you type the next term. When there are hits, the cursor lands on the first hit as before. Technically: The incremental build called the results builder directly and thereby bypassed the search entry's BuildChildren - which is where "enter search string" is injected, so it was gone after the first page. It now calls aHost:BuildChildren(aHost) (for "All"/single item that IS the results builder). So the search entry does not fall back to "Waiting" in the process, its gate also checks QueryResultsPartialReady. AuctionFocusAfterResults picks the cursor: the first entry that is neither the input field nor "no results" nor "Waiting"; if there is none, the input field. The zero-hit announcement goes out as ONE utterance ("no results;enter search string") - a second OutputStringBTtts would have cut off the first. In the full-scan category list the zero-hit entry is called "no results" too; the branch with no scan data at all stays "empty", because that is a different statement. Auction house: a cancelled full scan blocked every later search Simple: Cancelling a running full scan by closing the auction house meant that EVERY search and every category afterwards said "not possible, scan in progess" - although no scan was running any more. It stayed that way until you started another full scan and let it finish. Closing now tears the scan down properly and the next search runs normally. (The server's 16 minute lock on the next full scan is unaffected - that is server side and applies after a cancel too.) Technically: AuctionHouseResetQuery refuses to reset while a getAll scan runs unless the caller insists with force - that protects a running full scan from being killed by an incidental reset (selling menu, browse paths). AUCTION_HOUSE_CLOSED called AuctionScanFinish WITHOUT force, so closing mid-scan left state = "waiting" with QueryData.getAll = true standing; that is exactly what the search's refusal check tests, so every query from then on was rejected as "getAllRunning". Closing now forces - it IS the end of the scan, and no events arrive afterwards anyway. Two logging gaps that hid the bug are fixed with it: the refusal in AuctionHouseResetQuery now writes its own line (previously you only saw the call above it and assumed the state had been cleared), and AuctionScanFinish reports "finished" only AFTER the reset, with reset=true/false - the log used to claim a scan end that never happened. Auction house: first full-scan announcement after 5 seconds Simple: While the full scan waits for the server's answer, Sku reports "full scan, n seconds" every 10 seconds. That meant the very first of those announcements only arrived after 10 seconds - ten seconds in which you had no way of knowing whether the scan was running at all. The first confirmation now comes after 5 seconds, the second 10 seconds later, and from then on in the 10 second rhythm (so at 5, 15, 25 ... seconds). Nothing else changes: the 25 percent messages of the ingest phase and the completion announcement stay as they were. Technically: The interval in the AH OnUpdate ticker now lives in tFullScanSpeakNext, which starts at tFullScanSpeakFirst (5) and switches to tFullScanSpeakEvery (10) after the first announcement. The counter is reduced by whichever threshold currently applies rather than by a fixed 10, so the phase stays correct. When the work phase ends the threshold drops back to 5 - otherwise the next scan would have given its first confirmation only after 10 seconds again. Auction house: the full scan sometimes died silently right after the start announcement Simple: Sometimes "Full scan started" was followed by nothing at all: no 10-second progress announcement, no completion message, and the menus that should be locked during a scan were not - yet the 16-minute lock was consumed. This mostly hit after waiting out the cooldown right at the open auction house. Internally the scan was killed immediately after starting; it now always starts with a freshly zeroed clock. Technically: Proven in the debug log of 2026-08-26: QueryAuctionItems and "watchdog: getAll timeout 600s" in the SAME second. The scan ticker's watchdog counters were already full before the start: tTime collected the entire idle time at the open AH (the idle branch never reset it - the 16-minute wait > 600 s was enough on its own), and tFullScanElapsed survived every scan end because its reset sits behind the 0.2 s tick gate of the serialize branch, which a fast serialization never passes. The first watchdog tick after the start added both and killed the fresh scan; the watchdog zeroed itself in doing so, which is why the next attempts worked again - hence the "mostly it works". SkuCore.AuctionScanWatchdogReset now zeroes both counters after every actually sent QueryAuctionItems, and the idle branch resets tTime itself (which also protects the paged searches' stall watchdog). Verified 2026-08-26: a scan after ~28 min of standing at the AH ran through, reading 173841 auctions. French clients: the waypoint list was empty in several zones (fix by Naxedim) Simple: In the Wailing Caverns, in the Deeprun Tram, in Timbermaw Hold and in Onyxia's Lair, Sku reported an empty waypoint list although the zone is fully mapped. Two observations narrowed it down: a route that was already running kept working, and the same waypoints stayed selectable from outside the zone. So no data was missing - Sku simply did not know which zone the character was standing in. German and English clients were never affected. Technical: Two independent causes with the same symptom, both found, fixed and verified in game by Naxedim on a French client (PR #8). First, SkuNav/Geo.lua compares a handful of zone names VERBATIM against GetMinimapZoneText(); four of them had been translated freely in locales/frFR.lua, so no comparison matched any more and GetBestMapForUnit handed back the continent map instead of the zone map. The new values were read from C_Map.GetAreaInfo() on a French client, not translated. Second, instances whose real area row is commented out of SkuDB/assets/maps.lua hang on a synthetic row (id 100000 and up) that C_Map can resolve nothing for; it stayed English while GetCurrentAreaId's last fallback compares it against the localized MAP name - no match, no areaId, no continent, empty list. Those rows now take their name from the uiMap that carries the same English name; in French that is exactly two, Onyxia's Lair and the Wailing Caverns. Added afterwards: a guard so that no synthetic row can be given a name a real row already carries (Wintergrasp), the "do not translate freely" marker on every verbatim-compared zone name in locales/enUS.lua, and a French translation store keyed by the locale key instead of by line number - it had drifted by one line and would have shifted almost the whole table on the next regeneration. In passing, for English clients: L["Die Hoehlen des Wehklagens"] read "The Wailing Caverns", which no client says. The same special case was dead there too and is corrected now. Quest menu: "Group members" now comes last Simple: With Questie loaded, Sku shows your party members' quests as an extra entry while you are in a group. Until now that entry sat between "Current Quests" and "Quest database" - and since it appears and disappears with the group, the quest database kept moving between second and third place depending on whether you were grouped. Anyone navigating there from muscle memory ended up somewhere else solo than in a group. "Group members" is now always last, in third place; the first two entries stay exactly where they are, alone or in a group. Technical: In SkuQuest:MenuBuilder the conditional tSpecs block (showGroupQuests + QuestieReady + IsInGroup) moves behind the "Quest database" block. SkuMenu:Build renders the specs in array order without any sorting, so source order is menu order. The entry itself, its condition and its submenus are unchanged. ------------------------------------------------------------------------------------------------------- Changes in Sku v43.0 Overview New Shift-F9 to Shift-F12 have their own keybinding entries now, and quick-access slots 1 to 4 are free again. The taxi flight announcements can be switched off, and they never name your take-off or arrival point as a landing option. New aura condition "Buff Debuff own casts only" filters this aura's buff and debuff lists to effects you cast yourself. New command /skucheck verifies fixed rules against the live game state; it has also taken over the database tools as sub-commands (wp, db, mem). The installer collects every log file for a bug report at one button press. The login screen is usable with the login tool now. Waypoint lists announce how far the loading has got. Changes Aura expiry warnings arrive on time instead of at the next combat event. Aura evaluation per combat event is considerably cheaper. Cancelling navigation is one key again instead of two. The power monitor can follow the resource bar the game shows, so a druid gets mana, rage or energy according to form. The login tool's log survives a restart of the tool. Route waypoint data becomes usable much earlier after login, your own continent first, and needs about 22 MB less memory; the part of the route data your game version does not read is no longer built at all. Auras are internationally interchangeable now: an aura works on every language client. Shared aura sets arrive usable at a group member on another language. Every rank of a spell counts as one entry for an aura. Spell names the game hands out more than once can be told apart in the selection lists now. The bundled aura sets are no longer German clients only. The menu you build an aura in has been rebuilt: attribute list sorted by name, two-value conditions as switches, spells typed as well as picked, and every value list opens faster. Every menu entry in the whole addon is about 13 times cheaper to create, which makes the big spell lists open about three times faster. A list whose build was cut off by the server is continued on the next frame instead of quietly staying incomplete. Settings with two values read their value out with the name and switch with Enter; the RIGHT arrow and the submenu are gone. The volume sliders in the Sku menu set the game's own setting directly; Sku no longer keeps a copy of its own. Bugfixes French clients: all aura sounds and pre-recorded speech clips were silent (fix by Naxedim). French clients: the range check announced nothing and its menu threw an error (fix by Naxedim). French clients: two follow-ups from the same report fixed. Nine translated labels meant something different depending on the language (fix by ZenqFR). Auras: some group slots were announced as a beep instead of a word. Auras: an aura set to "once" could fire several times in a row in dense combat. Menu: on large maps the Shift-F9 waypoint list only opened on the second try. The keys in the keyring were announced as "Empty". On hardcore realms Sku now finishes building its waypoint data instead of stopping silently. Navigation: the route list could come up empty and silent when the waypoint data was rebuilt while a menu was open. Missing route data is reported now instead of showing up as an empty list. A popup that opened during combat (group invite, summon, release) could be read but not answered. A route would not start if its list finished loading while you were in it. The login tool mistook the login screen for character selection. The login tool: on realms with ten or more characters the character list was incomplete, wrongly numbered or would not scroll back. Power monitor: the resource setting "nothing" threw a stream of Lua errors. Power monitor: a monitor switched off in the features menu still announced resource changes. French clients: the list of weapon enchants for aura conditions was empty. The ready check did not land straight on "Ready" the way every other popup does. A game volume set in the Sku menu could be back at an old value on another character. The four standard profiles are created without switching profile in between - on hardcore realms that could leave you in a profile that is not yours. With thanks to Naxedim and ZenqFR The next four fixes are the first direct code contributions to the now-public Sku repository. Naxedim found, diagnosed and fixed the two bugs that had muted large parts of Sku's audio on French game clients; ZenqFR audited the translation files, resolved every conflicting duplicate on the English and French side and improved the tool that guards against new ones. Full credit for these fixes goes to them. French clients: all aura sounds and pre-recorded speech were silent (fix by Naxedim) Simple: On a French game client, every aura whose output is a sound played nothing at all since v42.11 - no error, no log line, just silence - and every pre-recorded speech clip quietly fell back to plain TTS. The cause: since Sku speaks French it also looks for a FRENCH voice pack, and no such pack exists, so the search matched nothing and everything the voice pack provides went dead. Spoken output merely degraded to TTS, but a sound effect has nothing to degrade to - which is why this felt like "my auras stopped working" rather than "the audio changed". The pack search now uses the same English fallback the rest of the audio system has used since v42.09. German and English clients are completely unaffected. Technical: The voice-pack resolver in Core.lua matched installed pack locales against Sku.Loc; once v42.11 shipped locales/frFR.lua, Sku.Loc became "frFR" on French clients, no pack could declare it, Sku.AudiodataPath stayed "" and VoicePackAudioDir() returned nil - silencing all 64 SkuCore.outputSoundFiles entries (aura sounds, SkuMob combat sound, SkuChat line sound) plus every pack speech clip. It now matches Sku.LocAudio or Sku.Loc, the exact "which recorded language do I fall back to" rule IntegratedAudioDir() already uses; Sku.LocAudio is "enUS" on every non-deDE client, so deDE/enUS behaviour is byte-identical. Found on a live French client, fixed and verified in game by Naxedim (PR #5). French clients: the range check announced no distances and its menu threw an error (fix by Naxedim) Simple: On a French client the range check never announced a distance any more, no matter the target, and opening the range configuration menu produced an error instead of the menu. Two causes, both French-only: first, the setting "speak the distance" is stored in the saved configuration as the translated WORD for "Spoken" - a config written while Sku still ran as English (before v42.11) holds the English word, which the now French client no longer recognises. Second, the number clips only exist recorded in German and English, and the code listed exactly those two languages, so a French client got silence even with a freshly written French config. The stored marker is now accepted in all three languages, an unknown value can no longer break the menu, and the clips fall back to the English recordings like the rest of the audio system. German and English clients are unchanged: same marker, same clips, same audio. Technical: A new RangeCheck:IsSpokenSound() accepts "Gesprochen"/"Spoken"/"Parlé" (plus the live L["vocalized"]), used by both DoRangeCheck and the menu builder in Options.lua - the latter previously fell into the else branch for an unrecognised value and concatenated nil from SkuCore.RangeCheckSounds, which is what threw. The clip folder is chosen via Sku.LocAudio (hans_de-de for deDE, hans_en-us otherwise) instead of a Sku.Loc chain that matched neither branch on frFR. New entries still write L["vocalized"]; no data migration. Found and verified on a live French client by Naxedim (PR #6). Translations: nine labels meant something different depending on the language (fix by ZenqFR) Simple: Nine texts existed twice in the translation files with two DIFFERENT meanings, and each language could end up picking a different one of the two. Most audible on English clients, where the wrong twin won several times: the stance action bar was announced as "Special Action bar" (a different bar entirely), the debug keybind as "debug mode" instead of "debug output", a menu category as "Features" instead of "Values", distances as "Meter" instead of "Meters". For each pair the actually correct meaning was determined from where the text is used in the code, and the wrong twin was deleted in all three languages - English, German and French now agree on all nine. Six German-only duplicates remain deliberately open for a German speaker to decide. Technical: AceLocale resolves duplicate keys first-wins for the default locale (enUS) and last-wins everywhere else, so a duplicate with two values silently means a different thing per language. ZenqFR traced each key's live call sites to pick the surviving value (PR #3) and taught dev/rework-docs/_locale_dupes.py to compare only the parsed Lua string literal instead of the raw line, so trailing "--" comments no longer count as conflicts (PR #4) - of 64 reported conflicts only 30 were real; enUS and frFR now lint at zero, deDE at the six German wording pairs left for a native speaker. French clients: two follow-ups from Naxedim's voice-pack report Simple: The sound called "bring" - a bell, named after the noise it makes - was listed in French menus as "apporter": the translation had read it as the English verb "to bring". It is a name, not a word, and now stays "bring" like its untranslated siblings "brang", "dang" and "drmm". And the internal diagnostic that records which voice pack was found now names the language actually searched instead of the client language - before, a French client without the English pack installed would have claimed "no voice pack found for frFR", pointing at a pack that cannot exist. Technical: locales/frFR.lua reverts L["bring"] to the onomatopoeia. Core.lua now sets Sku.AudiodataPathInfo in an else branch of the resolver from tWantedLoc (the Sku.LocAudio-based search key) plus the client locale; read it with /wdeval Sku.AudiodataPathInfo. The third item from the PR #5 report - frFR translating the output-label prefix to "aura;son" - was reviewed and deliberately KEPT: every comparison site rebuilds the prefix from the live locale table and saved settings/auras store identifier keys ("sound-brass1"), never the label, so the translation is safe; French TTS for a French label beats an English clip. Bags: the keys in the keyring were announced as "Empty" Simple: Since v42.13 every keyring slot spoke as "Empty" even though the keys were there and still worked - it only surfaced once someone actually needed the keyring. The trigger was the v42.13 bank fix: in the game client the keyring has the same quirk as the main bank container and needed the same special handling, which back then only the bank received. Keys are announced with their names again. The keyring also now shows only as many slots as it really uses instead of a flat 32 - previously dozens of empty slots followed the keys. Technical: GameTooltip:SetBagItem(-2, slot) populates nothing on this client, exactly like bank container -1. Key names had only ever worked through the generic SetHyperlink fallback from 67f2132; when v42.13 deliberately removed that fallback and switched only the bank (-1) to SetInventoryItem, the keyring silently fell back to the blank SetBagItem. tSetTooltipContainerItem now reads -2 via SetInventoryItem("player", KeyRingButtonIDToInvSlotID(slot)) - exactly what Blizzard's own ContainerFrameItemButton_OnEnter does for the keyring. Build_BagsFrame additionally clamps the slot count of -2 with GetKeyRingSize() (filled slots rounded up to full rows of four), the way Blizzard's keyring frame does, instead of taking the raw 32 from GetContainerNumSlots. Diagnostics: new command /skucheck verifies fixed rules against the live game state Simple: /skucheck walks all bag data and checks that every occupied slot resolves to a speakable name; the result is one spoken line, such as "Bag check: 64 checked, no problems", with a pointer to the log when there are problems. The command checks the data itself, not the menus - so it would have reported the keyring bug even if you never open the keyring. The rule list grows only with fixed bugs: from now on every fix ships its own tripwire rule, so a later change cannot quietly reintroduce the same bug. As of this version /skucheck is also the only check command: the three database tools have become sub-commands - /skucheck wp verifies the waypoint data and runs as part of a bare /skucheck, while /skucheck db and /skucheck mem are measurements and only run when asked for. The old names /skudbwpcheck, /skudbcheck and /skudbmem still work. Technical: At the bottom of LocalMenu.lua; iterates tBagSlotListSorted with the same container API and the same name resolution the bag menu uses. Rule 1: slot with an item id but blank tooltip text = violation; each violation is written to the SkuDebugLog ring as a "skucheck" line with bag, slot, item id and link. The closed bank is skipped with a log line (not silently), the keyring is deliberately swept at all raw 32 slots, and items not yet sent by the server count as "pending" rather than as failures. The wp/db/mem domains call SkuDBTools.RunWpCheck/RunDbCheck/RunMem (SkuDBTools.lua); they run as a background job and announce their own result, but also write it into the ring as "skucheck" lines - the waypoint violations individually, which used to be readable only in SavedVariables. /skucheck db also reports how many datasets changed against the previous capture. The reason for the merge: with three separate commands it was possible to run "the check" and miss the waypoint rules entirely. Auras: expiry warnings arrive on time, not at the next combat event Simple: Two long-standing complaints shared one cause: an aura like "debuff on the target below 3 seconds" or "my debuff fell off the target" was only re-checked when some OTHER combat event happened to arrive. In a melee-only fight the warning could therefore be a whole swing late, out of combat minutes late - or never fire before the expiry itself. Sku now notes, while checking the effect, WHEN the threshold will be reached and wakes the check at exactly that moment; an effect appearing or vanishing also triggers the check directly. In addition, an aura's spoken words may now move past waiting announcements the way the alert sounds already do - they interrupt nothing and overlap nothing, they just no longer queue at the back. Weapon enchant warnings benefit too: instead of up to a second of slack they now fire frame-exact. Technical: Three mechanisms: (1) a deadline scheduler in SkuAuras/Core.lua - every evaluation pass records in tNextDurationDeadline the earliest threshold moment (expirationTime minus threshold) over all active duration conditions (smaller operator); the frame driver compares one number per frame and then fires ONE synthetic DURATION_DEADLINE pass. Replaces the per-second weapon-enchant near-expiry refire. (2) UNIT_AURA triggers an evaluation on a genuine membership change of the effects (added/removed, not stack or duration updates) - a name diff against a snapshot, debounced against combat-log passes in the same frame and against target changes. (3) the word outputs use OutputString's existing but never connected aInstant path (front insert instead of append); a same-frame cursor keeps multi-part outputs in spoken order. Auras: evaluation per combat event made considerably cheaper Simple: In a raid Sku evaluates auras hundreds of times per second. Several hidden cost drivers are gone: conditions were checked twice, remaining durations were re-queried from the client on every event, and the whole group was searched one query at a time for every event. Two silent bugs fixed along the way: an aura without a "spell on cooldown" condition could announce ANOTHER aura's cooldown name, and item cooldowns were re-stamped on every bag change. /skucheck gains an "auras" domain for this, verifying the evaluation writes no globals anymore. Technical: Evaluate once instead of twice (the single-value branch of the attributes loop accidentally ran twice; plus two leaked globals - tSpellNameOnCdValue injected stale values across auras). Remaining durations come from an expirationTime extension of the tier-2 list cache instead of UnitAura rescans (/skuauracache verify now also checks the exp values, /skuauracache off disables both). Group GUIDs resolve through a map invalidated on roster events instead of raid1..40 loops per event (result order and the RoleChecker's historical raid1..25 horizon are preserved exactly). targetUnitDistance and targetTargetUnitId are lazy, LogRecorder does one settings walk instead of four, and the UNIT_INVENTORY_CHANGED guard tests the real itemID instead of a nil global. Installer: one button collects every log file for a bug report Simple: The installer's opening screen has a new button, "Collect all logs". It writes ONE zip file to your Downloads folder holding everything needed to diagnose a problem - and it does so for ALL detected game versions at once, not just one: Sku's debug and error log, the captures from BugGrabber/BugSack and WVDebug, the game's own logs including its Lua errors, the CVar file Config.wtf, a list of every installed addon with its version, the login tool's log and the installer's own. Until now these files had to be dug out one by one from three separate directory trees, and forgetting one of them earned you a follow-up question instead of an answer. The file is called Sku-Logs-.zip; the installer reads out its full path and opens the folder with the file already selected, so it can go straight onto a bug report. A typical collection is around 7 MB. Technically: New file LogCollector.cs in the installer, driven by a fourth button in UpdatePromptForm (label and spoken text in all three languages). Collection goes into one subfolder per client: SavedVariables from the account AND the character scope (Sku writes both) including the .bak second copies - those are the PREVIOUS session's state and therefore often the only surviving copy of the very debug ring being reported on - plus Logs, Errors, Config.wtf, Sku.toc, .build.info and an addon list with TOC versions and symlink flags. Alongside them go system-info.txt (OS, language, one block per client, and a list of what did NOT come along and why - an omission should read as an omission rather than as an absence) and the installer log, which is forced out of Logger's in-memory buffer onto disk for the purpose. The Errors folder is deliberately exempt from the 30-day age limit - it only holds anything when something actually broke, and the first test run otherwise shipped zero of the 21 crash reports present - and is capped at the newest 25 instead. This puts the installer at version 4.2. Login tool: its log now survives a restart of the tool Simple: The login tool deleted its log on EVERY start. So anyone who hit a bug, closed the tool and opened it again before collecting the logs had destroyed the very evidence they were about to report - and that is the single most common sequence a bug report goes through. The last five sessions now stay beside the live log.txt as log-1.txt through log-5.txt, and the installer's new collect button picks them up automatically. Technically: log.ahk now writes to an ABSOLUTE path derived from the script's own location instead of the bare name "log.txt", which resolves against the process working directory - START.ahk does set that correctly, but it is process-wide state anything later can change, and a log that lands where nobody looks is as good as no log at all. ClearLogFile() rotates instead of deleting. Packaging and .gitignore exclude log-*.txt exactly as they exclude log.txt, and an existing installation's own logs are never touched by an upgrade. Login tool: the login screen announced itself as character selection Simple: When the game cannot reach the server at start you do not land in character selection - you land on the login screen with account name, password and a Login button. The tool led into the main menu there, whose first entry is "select character", so the one screen that proves there is no character list right now announced itself as character selection. Anyone who cannot see it heard nothing that told it apart from the real thing. The tool now says once on arrival: not logged in, either there is no connection to the server or the account name and password still have to be entered in the game. It also notices the screen changing now: log in by hand and the character list is built straight away, instead of the tool sitting silently on the old menu. Technically: "no connection" is NOT a screen of its own - verified on the live 2.5.6 client, it is pixel- and OCR-identical to a normal login screen (same widget probes, checks.login = true, no popup). There is nothing to mark, so the tool says only what is always true there: this client is not logged in. The menu is its own tree now rather than the main menu, and InitLogin only ran on the MODE transition, which is why a screen change went unnoticed; the watcher now triggers a full re-initialization whenever the login screen is entered or left. Login tool: the login screen is usable now Simple: The menu on the login screen now leads with the screen's own controls: account name, password, log in, save account name - followed, unchanged, by voice, language, game type, region and version, so the settings stay reachable even when the login fails. The tool stores NOTHING and types nothing: it only puts the game's caret in the field and hands the keyboard to the client, and you type. There is no auto-login and there will not be one. The account name is read back, "empty" when it is empty; the password is NEVER read back. While the keyboard belongs to a field the arrow keys go to the game, so you can actually reach a typo; Enter ends the entry, Escape leaves it, and neither is passed to the game - logging in is its own entry so nothing gets submitted by accident. Closing and reopening Battle.net is usually the quicker way back; the manual route is there for when you want it. Technically: four new widgets in data.ini ([Classic] and [BurningCrusade]): LoginAccountField 10000,403, LoginPasswordField 10000,478, LoginSubmitButton 10000,531 and LoginSaveAccountCheckbox 26,662 - measured on the live client at 2880x1800 and converted into the 768-high UI space, so they are resolution independent, and cross-checked against entries already in the file ("Account erstellen" measured 549 against the stored 550). The field content is OCR'd from the BOX alone, so a label elsewhere can never be read out as the content. "Log in" follows the attempt: one-button progress dialogs are read aloud and NOT clicked, because their single button is Cancel and it disconnects the login in flight - the same trap as the realm join; two-button dialogs are read and answered. Where the widgets are absent (Cata, Retail) the entries say so and do nothing. Login tool: the character list on realms with ten or more characters Simple: The character panel shows exactly nine characters at a time. From the tenth on the list scrolls, and everything about the list went wrong at once: characters were missing, the numbering did not match the actual list, and getting back to the top of the list did not always work. All three are the same cause. The tool walks the list with the arrow keys and watches how the highlight moves - and once the list scrolls, the highlight stops moving, so "nothing changed" was read as "the list ends here". A single keypress the game swallows looks exactly the same, and that is what cut lists short. With nine characters or fewer the list never scrolls, which is why none of this was ever visible. The tool now asks twice before believing the list ended, and it reads the character name printed under the character model to settle it - that name is the selected character no matter where the list is scrolled. It also says the running count every ten characters, because a long list is otherwise half a minute of silence, and the menu stays usable while it is being rebuilt instead of going dead. NOT TESTED in the game: there is no realm here with more than nine characters. If the list is still wrong, the tool's log now names the exact decision it got wrong. Technically: Verified against the client's own interface source. Blizzard_GlueXML_TBC.toc loads Vanilla\CharacterSelectConstants.lua with CHARACTER_SELECT_MAX_CHARACTERS = 9, and CharacterSelect_OnShow assigns that to MAX_CHARACTERS_DISPLAYED. Every stop condition in the walk that rested on ONE observation now needs two, with a longer settle in between, and presses again only after a look has proved nothing moved - pressing again on a step that already registered skips a character silently. The wrap-around detection now demands slot 1 exactly, because CharacterSelectScrollDown_OnClick past the last character does CHARACTER_LIST_OFFSET = 0 followed by SelectCharacter(1); it also waits for the re-scroll to draw before confirming. The fallback that reads only the visible section now scrolls to the top first, because UpdateCharacterSelection sets the offset to selectedIndex - MAX_CHARACTERS_DISPLAYED whenever the last played character sits below the fold - the nine visible rows were then characters 6 to 14 announced as 1 to 9. The pixel probes for the selection bar were moved left: at ten characters UpdateCharacterList widens the panel from 260 to 280 and shows a scrollbar whose backdrop is solid black over the strip the bar vacates. The menu is now built detached and published in one assignment like the realm list. Hardcore realms: Sku now finishes loading its waypoint data instead of stopping silently Simple: On hardcore realms the game client allows addons noticeably less computing time per frame than on normal realms. Sku builds its waypoint and route data right after login, and that build ran straight into the limit there: the client simply aborted whatever was running, and Sku never noticed. The result was that every waypoint list answered "waypoints still loading" for the entire session - no error, no speech, no log entry - and reloading the interface did not help either. Sku now notices when the client throttles it, makes its work packets smaller and carries on instead of stopping; if a packet is killed anyway, the build restarts by itself. The price: on such realms it takes a few seconds longer after login until routes and waypoints are fully there. Everything else in Sku is usable immediately, and the waypoint lists now tell you how far the build has got (next entry). Normal realms are unchanged. Technically: the client reports the abort as the LUA_WARNING "insecure scripts exceeded execution limit for addon Sku", or as "script ran too long" raised inside the running coroutine slice. On the hardcore realm this happened at every single login (52/25/25 warnings per login) and in eight recorded normal Era and Anniversary sessions not once - for identical work (1936 ms on Era against 1955 ms on hardcore for the same build). Sku:BuildFrameBudgetMs is adaptive now: every such warning halves the per-frame ceiling (150 -> 75 -> 37.5 -> 20 ms), which benefits the whole post-login build sequence, not just the waypoint cache. The cache build is no longer paced by a self-re-arming C_Timer chain either - that re-arming was exactly what the abort swallowed, leaving the coroutine suspended forever with nobody to resume it - but by an OnUpdate driver the client itself calls every frame, so a killed packet costs one frame instead of the whole build. If the coroutine dies regardless, the build restarts up to three times, and the build step puts its request back instead of losing it inside a pcall. Navigation: a route list could come up empty without saying why Simple: a tester on a hardcore realm reported that the Shift-F10 route list only said "list empty" in a place that has routes. Behind that were two different ways for a list to end up empty with nothing at all to indicate it. First: now that Sku restarts its waypoint build on hardcore realms when the client kills it (see above), the waypoints can be swapped out while a menu is open. A list still built from the previous run then pointed at a waypoint that no longer existed. That threw an error in the middle of building the sub-entries, and because the error was caught, the level was simply left empty: no entries, no hint, no error message, nothing to hear. Second: if the route data itself was missing, Sku still reported "loaded" and every route list was empty - again with no indication whatsoever. Both are visible now: the list answers properly instead of staying mute, and missing route data is announced like any other database error. Technical: three places, all the same shape - a failure must not stay invisible. 1. SkuNav:GetAllMetaTargetsFromWp5 dereferenced the start waypoint (.worldX) with no check. After a waypoint cache rebuild - new in v43.0, because the build restarts itself on hardcore realms and because CleanupWaypoints deletes waypoints that have no links - that name can be gone. It returns an empty answer now and logs a line with the cache generation. 2. SkuOptions:VocalizeCurrentMenuName and the path walker call BuildChildren inside a pcall so that a broken submenu builder can never swallow the announcement of the entry's NAME. That stays - but the error is now logged with the node name and the node is marked (buildChildrenFailed). Without it, a level with zero children was indistinguishable from a level that is genuinely empty. 3. Sku:EnsureData skipped a builder global that is not a function without a word and then reported the dataset ready anyway. That is the worst possible shape: every guard in the rest of the code asks "is it ready", not "is it there". For the routes it ends in empty link data, deleted route waypoints, and menus calmly announcing "list empty". Each missing builder is logged individually now; if they are ALL missing the dataset counts as failed and is spoken. Plus one log line in SkuNav:GetAllLinkedWPsInRangeToCoords: when Sku cannot map the current area to a continent (caves, the Deeprun Tram, unmapped sub-areas) that query bails out empty - and the list then said "list empty", which sounds like a statement about the data and is not one. /skuzoneprobe reports the same case in full. Login: the route waypoint data is ready much earlier Simple: After login Sku rebuilds every waypoint and every connection between them; until that is done the waypoint lists answer "waypoints are still loading". More than half of that wait went into the connections, and the same data was walked four times over. It is one walk now, and it starts with the continent you are standing on: that one is finished first, and from that moment your lists are usable while the rest of the world is still being built. If you are sitting on the waiting hint at that moment, the first entry is read out to you automatically, without pressing a key. Measured on the same machine: the connection part now costs about half, and the lists for your own continent are there roughly 0.7 seconds earlier. Nothing about the routes themselves changes - the same waypoints, the same connections, just sooner. On top of that the waypoint store needs about 22 MB less memory, because every connection is no longer kept twice - on weak machines that is the more noticeable half. And another slice of the wait disappears entirely: Sku ships two large route files, and the TBC build only needs the connections out of the second one. Its waypoints were built at every login anyway and thrown away unread a moment later; they are simply not built now. That saves roughly a quarter of a second and about 21 MB of peak memory per login - on Era realms, where the whole file is unused, roughly 0.4 seconds. Both files keep shipping complete: when we move to Wrath of the Lich King later, that is exactly the data that becomes the right one, and the selection flips over. Technical: The link part of the waypoint cache build ran as FOUR full passes over ~197,000 directed edges: prune and symmetrise (CheckAndUpdateProfileLinkData), materialise byId/byName, re-derive the whole table back out of the cache (SaveLinkDataToProfile), and a final pass over all ~145,000 cache records (CleanupWaypoints). It is one pass now: the pruning rides along, the reverse edge is written straight into the target record - which is why the order of continents no longer matters - and the re-derive is gone, because it rewrote the table that is already there. The one case where it could still change something (an endpoint whose name belongs to a different cache record) is counted and triggers the old path; it does not occur in the shipped route data. The final pass gets the list of records that can be deleted at all from the custom pass, bucketed by continent, and runs per continent as soon as that continent is done. Fixed along the way: the old prune pass inserted new KEYS into the very table it was iterating with pairs() - undefined behaviour in Lua; the reverse edges are collected and applied afterwards now. And the "own continent first" split added in July was never active at login: it resolves the zone through GetMinimapZoneText, which is still empty when the build starts - the continent is resolved again before the link part now. Backed by a reimplementation of both variants over the real route data (dev/rework-docs/simulate_link_build.py: identical cache, identical link table, for every continent) and in game by /skucheck wp, which now also verifies that every link has its reverse edge. Technical, part two: every link was held twice - once under the target waypoint's cache index (byId) and once under its name (byName). byName is gone and all 14 call sites use byId; the new WaypointCacheGetIdForIndex is the counterpart to WaypointCacheGetIdForName. The writer into the link table got cheaper rather than just smaller: it used to translate every target name back into an id, and now it already has the record. Measured: about 70,000 tables and 224,000 strings less, 22 MB off the memory report (/skucheck mem), and another ~40 ms off the link part of the build. Why it was doubled: an unfinished migration - Sku 32.31 already carries the commented-out call that was meant to write the byName table straight into the stored link table; byId arrived later as the fast path for route finding, and both stayed. Three latent bugs went with it: a loop in waypoint deletion that only cleaned the deleted waypoint against itself, ids built from ".areaid" instead of ".areaId" (so always with area 1), and an access to Links[nil] for temp waypoints. Technical, part three: each route file was ONE builder that constructed the whole file. Measured in game: 375 ms for the WotLK file and 355 ms for the base file, every login. On TBC the navigation uses the base file's waypoints and only the WotLK file's LINKS (the union of both graphs, since July) - LoadDefaultMapData nil'ed the WotLK waypoints, levels and sequence numbers again right after the merge. That is 71% of that file's bytes, built and discarded unread. The files are now wrapped per SECTION (dev/rework-docs/_wrap_deferred.py, mode "sections"): one builder per top-level section of routedata.global, each holding a byte-exact slice of the original data - the tool asserts the slices reassemble into the source file, nothing is re-serialised. SkuDeferredData.lua picks the builders by game version (TBC: every base-file section plus the WotLK links; Era: the base file only) and nils the builder globals it skipped, or their source-string constants would stay pinned in memory. New: /skucheck routes. It is deliberately shaped so that a wrong selection is loud instead of silently costing half the graph: after EnsureData not a single SkuDBBuildRoute* global may survive - a survivor means a section the selection does not know about - the tables the navigation reads must be present and non-empty, and per game version exactly the right half must be absent. Waypoint lists now tell you how far the loading has got Simple: the message "waypoints still loading" was written for a wait of a second or two. When the client throttles Sku (see the previous entry) it can stand for ten seconds and more, and at that point it is indistinguishable from a hang. It now carries a percentage, for example "waypoints still loading, 47%". The number moves from the start: the first steps are the database parts the build has to wait for at all (0, 20, 40 percent), after which the build itself counts up to 99. Every keypress on the entry announces the current value, and once everything is there the first real waypoint is announced automatically as before. Technically: the yardstick is the work time of a COMPLETE build, which every successful run stores in the global SkuNav settings (wpcTotalWorkMs). What is measured is work, not wall clock: the work is stable, the wall clock now varies with the budget backoff. That keeps the percentage honest on any machine; only the very first build after a fresh install uses the default. Cost per frame: one integer comparison - only a CHANGED percentage rebuilds the string, and only for an entry the focus is actually on. The entry carries a tag for that, because its name is no longer a fixed string: the two places that used to recognise it by name - the not-selectable gate and the automatic refresh at the end of the build - test the tag now. Shift-F9 to Shift-F12: named keybindings, and four quick-access slots back Simple: Four Sku actions - the two navigation quick lists (Shift-F9 and Shift-F10), the action bars menu (Shift-F11) and cancelling route navigation (Shift-F12) - were wired straight onto the keys of audio menu quick access 1 to 4. That had two consequences. Those four quick-access slots were dead: their set keys (Ctrl-Shift-F9 to Ctrl-Shift-F12) still stored a menu path and still confirmed it out loud, but no key could ever call it back. And the four actions could only be moved to a different key by rebinding an entry named "audio menu quick access 1" to "4" - which is not where anyone looks for "cancel navigation". Each action now has its own entry in the keybinding menu, under its own name, still on Shift-F9 to Shift-F12. Quick access 1 to 4 are free and unassigned again, exactly like slots 5 to 10. Your existing settings are carried over on first login: whatever key you have on quick access 1 to 4 today is the key that keeps performing that same action - it simply moves to the new entry, so nothing changes for your fingers. A slot you had deliberately cleared stays cleared. Technical: SkuZOptions/Core.lua's OnClick intercepted SKU_KEY_MENUQUICK1..4 and returned before the generic quick-select loop further down the same handler. The three moved actions are now SKU_KEY_NAVWAYPOINTSQUICK, SKU_KEY_NAVROUTEDESTINATIONSQUICK and SKU_KEY_ACTIONBARSOPEN, each dispatched by the module that owns it; the two nav lists joined the "Navigation and waypoints" keybinding group. A run-once migration in SkuKeyBindsUpdate moves key and key2 from the old slot to the new const, detected by the absence of the new const in the profile, so it runs exactly once and before the defaults are filled. Stored quick-select paths are kept, so binding a key to slot 1 to 4 restores the old shortcut. New tripwire: /skucheck keys reports any key held by two Sku bindings at once (transient override keys excepted, they share by design). Cancelling navigation was two different keys behind one action Simple: there were two ways to stop following a route or waypoint, and they behaved differently. The properly named binding "stop following route or waypoint" shipped without a key at all, and when you did assign one it announced "following stopped" - the same words the automatic arrival uses, so you could not tell the two apart - and it said nothing whatsoever when what you cancelled was a route rather than a single waypoint. The other was the hardcoded Shift-F12, which said "navigation canceled" but existed only as quick-access slot 4. They are one key now: "stop following route or waypoint", on Shift-F12 by default and freely rebindable, with the confirmation sound and the distinct "navigation canceled". It stays completely silent when there was nothing to cancel, and it still works while you are moving and in combat. Technical: the SkuNav OnClick branch for SKU_KEY_STOPROUTEORWAYPOINT now calls SkuNav:CancelNavigationSilent() and only speaks and plays sound 835 on a true return. That drops the old unguarded path, which fired SKU_NAVIGATION_STOPPED and the sound even with nothing running, and whose announce came from EndFollowingWpOrRt and was conditional on a selected waypoint. EndFollowingWpOrRt itself is unchanged - its fifteen other call sites use it as the teardown before selecting a new target, where clearing temporary waypoints would be wrong. Navigation: a route would not start if its list finished loading while you were in it Simple: If you opened the route list (Shift-F10) while Sku was still building its waypoint data, the list refreshed itself the moment that data was ready - and from then on Enter on a route point no longer started the navigation. Instead the menu jumped back up to the entry-point level, as if the key had done nothing at all. Closing the menu and opening it again fixed it, which made the whole thing look random. The refreshed list now behaves exactly like a freshly opened one: Enter starts the navigation. The same applies to the nearby-waypoints list (Shift-F9) and to every waypoint list that refreshes itself when the data arrives. Technical: the push-refresh at the end of the asynchronous waypoint-cache build rebuilt the level with a bare children = {} plus BuildChildren. That skips what OnPostSelect does for a select level: seed selectTarget on the level and copy it onto every fresh child. Without it the entire sublevel below inherits selectTarget = nil, so the leaf's Enter falls into the generic branch of OnPostSelect - no OnAction runs (RouteFollowOnAction never fired) and the cursor is set to self.parent, which is the observed jump. That rebuild now lives in exactly one place, SkuOptions:RebuildNodeChildren, used by OnPostSelect itself, by the waypoint push-refresh and by the volatile-list refresh - the last one in keep mode, so a live refresh cannot discard a target the user has already selected. Two tripwires ship with the fix: OnPostSelect logs and counts every Enter that lands on a leaf with no selectTarget below a select level, and /skucheck menu rebuilds a throwaway select level to verify the contract and reports that session tally. Auras: group slots were announced as a beep instead of a word Simple: When an aura announces the unit that triggered the event, some group slots produced only a short beep instead of a word - the sound Sku plays when it has no recording for a word. Slot 3 and every "target of" slot were affected. These are now split into words the voice pack reliably ships: "party 3", "target party 2". All other units keep saying their translated name. Technical: sourceUnitId and destUnitId handed the raw unit token to the audio voice as ONE word. The packs ship party0/1/2/4 but no party3 and no partyNtarget file at all, so the rest degraded to the missingAudio beep (party3 alone counted 129). tUnitIdToSpokenName in SkuAuras/data.lua splits the party tokens; other tokens fall back to their localised friendlyName from the value lists and only then to the raw token. Fixed in passing: notifyChat printed the table reference instead of the unit. Auras: an aura set to "once" could fire several times in a row in dense combat Simple: An aura meant to report only ONCE per application - typically "Shadow Word: Pain expires in one second" - could produce four sounds in one second during a boss fight. It happened when several players had the same effect on the target, or when the remaining duration could not be read at all in one pass: Sku took that as "the effect is fresh again" and released the once-lock. With no reading, the lock now stays closed. A genuine refresh or a new application still releases it immediately. Technical: The reset formula of the count condition re-armed used on every pass where the other conditions held and the smaller-duration condition read false - and a pass with NO reading (name not in the list, no exp entry, or the exp map answering with another caster's same-name aura) satisfies exactly that. tSmallerDurationNoRead in SkuAuras/Core.lua blocks the reset when there was no reading; the new log line "aura gate re-armed" records every release. Observed as four firings within 987 ms (DURATION_DEADLINE, then UNIT_POWER twice and SPELL_PERIODIC_DAMAGE). Menu: on large maps the Shift-F9 waypoint list only opened on the second try Simple: On maps with very many waypoints Shift-F9 stayed on the top entry instead of opening the list; one more arrow press then opened it. The reason was work that ran three times instead of once: the jump built the same list - about 1900 entries on some maps - three times in a single frame, and the game client aborted that with its script time limit. The abort is silent, which is why nothing appeared to happen. On top of that, every keypress on an entry rebuilt its child list and duplicated the entries. Both are gone, the list is built once. Shift-F10 was never affected because that list is capped at ten destinations. Technical: SlashFunc built the children three times: before the name match, in the loop's OnSelect, and again in the tail. The match is now decided BEFORE the build, and the tail skips its re-select when the loop already selected that same node (actionOnEnter nodes excluded - there the two calls genuinely differ). VocalizeCurrentMenuName only builds when there is nothing to count - it needs the children solely for the ";plus" marker, and most builders APPEND through InjectMenuItems rather than replace (measured 1859 -> 3718 -> 5577 entries while arrowing). Follow-up from testing: window levels under "Local" (Dialog, Merchant, Quest, Flight master) build their children lazily and carry no dynamic flag; with the pre-build skipped they looked like childless leaves, and the leaf branch calls CloseMenu - but closing clicks the close button of every window in interactFramesList, so the jump to a flight master closed its window again at once. The build therefore still runs when the list is empty, unless the node is dynamic. Two tripwires in /skucheck menu count both cases. Taxi flight: the landing announcements can be switched off - and they no longer name the flight point you took off from or the one you are flying to Simple: During a taxi flight Sku says "next flight point X" and, about 800 yards out, "early landing available at X", so that you can get off early with the keybind. For anybody who flies a lot that is chatter, so there is a switch now: Settings > General > "Announce flight points you can land at". It is on by default and it silences the SPEECH only - the flight is still tracked, and the early-landing key works exactly as before and still confirms out loud what it did, because that is the answer to a key you just pressed. Second, several reports said Sku named the flight point the flight had STARTED at, or the one it was heading to, as a place you could land early. Neither is an early landing: one is where you already were, the other is simply arriving. Both are impossible now. Third, once you have actually requested the early landing, Sku stops commenting on the route. It used to name the stop AFTER the one you were being set down at - ten seconds before touching down at Ratchet it would say "next flight point Astranaar", which reads as if the flight were carrying on. Technical: The announcement had one blind spot, the fallback to the nearest flight point. The route is captured at takeoff from the flight map (the only moment the route interface answers at all), and every announcement hangs off that route. If it is missing, the code falls back to "the nearest usable flight point" - and that knows nothing about the flight: shortly after takeoff the takeoff point is the closest one for a while, and shortly before landing the destination is. The one everyday way to lose the route is a /reload in mid-flight: the capture hook fired before the reload and cannot fire a second time. That is exactly why this was reproducible for the people affected and never visible here. Three changes: (1) the captured route survives a /reload - it is written to the character's saved variables together with the current leg and read back while still airborne, so a reload no longer drops you into the fallback at all; (2) the flight's own start point is now read from the route capture as well (the source slot of the first hop), so start and destination are both known by name and neither can be announced in any mode; (3) in fallback mode a flight point has to be getting CLOSER before it is offered as a landing option - a point you are flying away from is behind you. (4) a granted request now ends the route as far as the cursor is concerned: the requested stop is where the flight ends, so passing it is arriving, not a hop. The escape from that is geometric rather than a timer - still airborne a thousand yards beyond it means the server ignored the request, and the route resumes on the spot. New tripwire "/skucheck taxi" pins the rule: an early landing is never the flight's own start or end point. The switch is stored per profile as taxiAnnounceLandingPoints. Power monitor: "nothing" is now "current resource", and it follows the bar you see Simple: The health & power monitor watches exactly one resource, and it was a fixed choice: mana, rage, energy - or "nothing". But "nothing" was not an off switch, it was a hole: it threw a stream of Lua errors, and what the monitor watched instead was never anybody's choice. The entry is now called "current resource" and it is first in the list. It follows the bar the game is showing you: a druid gets mana in caster form, rage in bear and energy in cat without ever going back into the menu. Every other class has only one bar, so for them it is simply their resource. Picking a fixed resource still works exactly as before. To switch the monitor off, use "Enabled: No" - that is what the switch is for. If you had "nothing" selected, you will find the monitor switched off and the resource set to "current resource"; switch it back on if you want to hear it. Technically: "NOTHING" passed power index -1 (Enum.PowerType.None) straight into UnitPower and UnitPowerMax - a value the monitor has no business asking for. In its place there is GetMonitoredPowerType(), which resolves the stored value at read time: a fixed choice to its index, "current resource" through UnitPowerType("player") to the bar being displayed. Both read paths go through it, including the token comparison in UNIT_POWER_UPDATE - with the automatic type the token to react to is not the stored one. Both paths now also check for nil and for a zero maximum instead of dividing by it; that was the actual error source, and because one of the two sits inside the OnUpdate driver it produced not one error but one per tick. Since the watched resource can now change under the monitor, the previous percentage is reset whenever the token changes - otherwise the first announcement after a form shift would either be swallowed or given the wrong rising/falling direction. On top of that the handler now bails when the module is disabled: it hangs off two registrations (its own AceEvent object and the dispatcher in Core.lua) but OnDisable dropped only the first, so a monitor switched off in the features menu kept announcing. New check "/skucheck power" pins the rule: the configured resource must always resolve to something the client can read; a deliberately chosen resource the class does not have counts as pending, not as a violation. The stored value "NOTHING" is migrated to "ACTIVE" once at login, with the monitor switched off. Auras identify spells by identity, no longer by the translated name Simple: An aura you built used to be tied to your game language. The stored value was the translated spell name - "Frostblitz" - and an English client only knows "Frostbolt", so the same aura never fired there. For the same reason an aura set shared in a group arrived dead at a group member on another language, and the bundled aura sets existed on German clients only. Sku now remembers the language-independent identity of the spell. What you see and hear is still exactly the name your game uses - in the menu, in the aura's own name, and in the announcement when it fires. Your existing auras are converted once at the first login, with nothing for you to do; their names do not change. A second win comes with it: every rank of a spell, and the variants enemies cast, belong to one entry. "Tell me when my target casts Frostbolt" now covers all 103 Frostbolt variants instead of the single one that happened to be in the list. Whether YOU or an enemy cast it is still decided by the separate "Event source" condition, not by the spell. Where one translated spell name stands for several different English ones - German "Verblassen" is both "Fade" and "Fade Out" - the list adds the English name in brackets so the entries stay distinguishable. Only the list; the announcement when the aura fires stays short. Pick such an entry and the oldest variant wins - for "Verblassen" that is the priest spell, not "Fade Out". Auras you had already saved under such a name are left untouched and keep reacting to every variant, as before. Technically: The identity is the enUS spell name out of SkuDB.SpellDataTBC, which already ships and is already maintained - no new file, no generated id table (one would have to be kept in sync with spells.lua, and a regeneration would silently orphan saved auras). Stored values carry the "spellgroup:" tag; the display still comes from SkuAuras.values[key].friendlyName. The live UnitAura list is mapped to the group name via return value 10 (spellId); on a non-English client the localized name sits beside it as a compatibility alias so an unconverted old value keeps matching. On an English client group name and live name are the same string, the alias is never written, and the cost is exactly zero. An id SpellDataTBC does not know - the population most likely to drift as the Anniversary timeline cycles - falls back to the localized name, i.e. to the previous behaviour. 838 of the 26,603 German names (3.15%) cover more than one English name. At runtime the group with the lowest spell id wins - the colliding variants are without exception later additions; checked against the cases where the answer is known (Verblassen 586 vs 5543, Geschwaechte Seele 6788 vs 36788, Schattenwort: Schmerz 589 vs 37603, Erneuerung 139 vs 37563, Gedankenkontrolle 605 vs 7645). SAVED values under such a name are still NOT converted, because today they match every variant and converting would silently drop the others; their menu entries get the English name appended. A group's display name always comes from the lowest id in it, so it cannot change between sessions. New checks under "/skucheck auras": group entries exist at all, every id of a spell resolves to ONE group, and the run-once conversion left no value behind. Sharing aura sets: new format without a language tag Simple: A shared aura set no longer carries a language tag, and the "set is for another language" warning is gone - the conditions are language-free now. On accepting, each aura is also named in YOUR language instead of keeping the sender's sentence. Auras you named yourself keep their name. Technically: SkuAuraSetV2 (payload "AURASET2", no loc field) is what gets sent; V1 is still received, warning included, because such packets still carry localized values. Only V2 is sent on purpose: an additional V1 packet would leave older clients holding auras with group values they cannot resolve. The name is re-derived through BuildAuraName on import (SkuAuras:RelocalizedAuraName); customName auras are excluded, and those are exactly the ones other auras can reference, so cross-aura references survive intact. Building auras: the menu you build an aura in has been rebuilt Simply: An aura is still made of conditions, and everything you have already built keeps evaluating unchanged - but the way to a condition is shorter in a lot of places now, and the lists you walk through on the way are sorted, short and fast. In the order you meet them: The attribute list is sorted by name throughout - including the group entries, which used to sit on top no matter what they were called. An entry is where its name puts it, and typing its first letters gets you there. The 35 events sit behind two entries, "General events" and "Spell events"; health, resource, mana, rage, energy, runic power and combo points sit together in "Your health or resource"; and every aura you have named yourself is a condition under "Your own auras" instead of loose among the real attributes - the top list used to grow by one entry every time you named something, and those entries sorted into every letter of the alphabet. Each group entry says what is selected inside it as you read past, so you do not have to enter it to find out. A condition with only two values - "true" and "false", or "buff" and "debuff" - is a switch now instead of three levels. The value sits on the entry and Enter flips it: "In combat; true", Enter, "In combat; false". While you are BUILDING, Enter goes one step further to "not set" and back again, so a switch you set by accident can be taken off. Worth understanding: "not set" is not a third value, it means the condition is not there at all - that is the neutral state, not "false". "false" is a comparison like any other: "in combat equals false" fires ONLY out of combat. Spells still come from a list, but you can also type them: "Enter spell" is the first entry of every spell value list - Enter, name or number, Enter, and Sku tells you which spell it made of it. The two conditions "spell ID" and "item ID" are gone from the attribute list in exchange: they meant a SINGLE rank of a spell, and so, sitting right next to "spell name", they meant the opposite of what the same typed value does there. New in the list is "Buff Debuff own casts only" - set to true, this aura's buff and debuff lists only see effects YOU cast, so "my Shadow Word: Pain is running out" no longer reports another priest's on the same target. And because "Source (L)" and "target (L)" were regularly taken for your own target, they are called "Event source (L)" and "Event target (L)" now: they filter the triggering EVENT. Finally the lists themselves: every value list opens noticeably faster, "spell usable" no longer freezes the game when you open it, switching the spell of a duration condition has no stall and no longer reads the previous spell as still selected, and "Delete" asks before an aura is gone - it was the one entry in that list that did not, right next to "Duplicate", which always asked. Technically: An attribute becomes a switch when its type is BINARY, it has exactly two values, and its type allows exactly one operator (tBinaryValuesForAttribute in SkuAuras/Options.lua); what gets written is exactly what the old value list wrote, so an aura built through the switch is indistinguishable from one built the old way, and an empty values list drops out of the draft - that is "not set". The condition row pairs actionInPlace with actionOnEnter, which is what tells Enter and RIGHT apart on the same node. The attribute list collects groups and attributes with their display name and injects both in name order. The two event families are ONE attribute and ONE stored condition: each family list offers the other as a single entry, and both re-point at the condition the draft already holds - without that, walking into both would leave two conditions on `event`, which the save merges into one always-true OR group. listsOwnOnly is a binary modifier: getAuraList picks up the caster (7th return of UnitAura) in the same scan and fills parallel own-sets that EvaluateAllAuras swaps in per marked aura. The value lists sort with one sort key per entry instead of two per comparison (27,057 instead of about 800,000 new strings per spell list), "spell usable" asks the 132 action slots instead of all 49,000 spells in the database, and the duration condition lets each entry say its own state as it is read (tValueToggleRefreshLiveName, ONE function shared by reference) instead of resetting 27,000 entries on every Enter. "spell ID"/"item ID" are still evaluated and only hidden from the builder behind a retired flag, so an imported or old aura cannot run onto a nil; with them go their value lists, about 49,000 spell and 25,000 item IDs that were built on every load. The locale KEYS of the renamed conditions are unchanged, so no stored aura moves. Deleting also meant removing the manage menu's aName branch: selectTarget is only re-pointed once the level is entered, so until then the confirmation would have been cosmetic. Delete now refreshes the aura pseudo-attributes too - a deleted aura used to stay offerable as a condition. French clients: the list of weapon enchants for aura conditions was empty Simple: On a French client the aura condition for weapon enchants offered not a single entry, so such a condition could not be built at all. Technically: SkuAuras:ResolveWeaponEnchantName read column 1 of the enchant database for enUS and column 2 for deDE and otherwise left the name nil - for any other language the function therefore returned nil for EVERY enchant and the value list came out empty. It takes the column of your own language now, else the English one, the way the rest of the naming system has since v42.09. Found while porting to group identity, not from a report. Menu: every entry is now about 13 times cheaper to create Simple: Long lists build noticeably faster. It shows most on the spell lists of an aura condition: those hold 27,057 entries, and on a hardcore realm they had stopped opening at all - that server kills addon scripts that run too long, and the build only got about halfway. This is not only the aura lists: every menu in Sku is made of the same entries. Technically: A menu entry used to be a deep copy of the SkuGenericMenuItem template - per entry a helper table, a pairs() walk over all 28 fields with a type() call and three key comparisons each, 28 stores, and a recursive copy of the children table. An entry is now an almost empty table whose metatable points at the template through __index: two tables and one store. Measured over 27,057 entries: creating the nodes drops from 78 to 6 ms (13x), and the whole list build from 100 to 31 ms (3.2x, and 2.7x faster than before the aura rework). Writing a field on an entry still shadows the template as it always did; children stays a table of its own per entry on purpose, or every menu item in the addon would share ONE child list. The single place that copies an entry (the filter entry in SkuOptions:ApplyFilter) re-attaches the metatable. /skucheck menu verifies both on throwaway entries. Measured for comparison: the aura rework itself barely slowed these lists down. Moving to groups grew the spell list by 460 entries out of 26,597 (+1.7%), and toggles instead of plain entries cost about 19% more build time. The build was expensive long before that; on a hardcore realm, though, a script limit is a cliff and not a slope, and 19% was enough to go over it. Menu: a cut-off list stayed silently incomplete Simple: On a hardcore realm the server kills an addon script that runs too long. When that hit the build of a long list, the list did open on the SECOND arrow-right - but that was not a fresh attempt, it was the half-finished remains of the first. On the spell lists that meant about 6,900 of 27,000 spells with nothing to say so: anyone who could not find their spell had to conclude it did not exist. The build is now continued on the next frame from where it stopped, and the list completes within a few frames without you pressing anything. Technically: The script budget is per execution, so the next frame gets a fresh one - exactly why the route data build is sliced across frames. SkuOptions:ContinueInterruptedBuild calls BuildChildren again on a zero-delay timer for as long as the level is incomplete. Only a builder that declares itself with `resumableBuild` and keeps its own cursor is re-entered: an ordinary BuildChildren APPENDS, so a second call would create every entry twice - which is exactly why the old guard existed. The cursor is set AFTER an entry, never before, or an entry the interrupt prevented would count as built. A pass that makes no progress gives up rather than repeating forever. /skucheck menu verifies on a throwaway level that a cut-off build continues instead of restarting. Settings with two values are switches now, not submenus Simple: A setting that only knows two states - "On" and "Off", "Yes" and "No" - now reads its value out together with its name: you hear "Sku menu in combat; on", for example. Enter flips it, the focus stays where it is, and you hear the entry again in its new state straight away. You no longer need the RIGHT arrow for it, because the submenu holding the two values is gone. It used to take three keys - right, arrow, Enter - and you had to go in before you could find out what the setting was even set to. This covers the whole settings tree, the health and combat monitor, the chat and combat log filters, the features menu, the camera menu and other addons' settings: about 150 entries. Settings with MORE than two values keep their list, because there is genuinely something to pick there. Technically: A new shared menu element, SkuOptions:MakeToggleNode in SkuZOptions/templates.lua; SkuOptions:MakeInPlaceToggle converts an existing isSelect site in ONE line and keeps using that site's own GetCurrentValue and OnAction, so every per-setting quirk stays exactly as it was. Which of the two values is handed over as "on" does not matter, and that is what made converting the ~50 existing Yes/No sites mechanical rather than a judgement call each time. A toggle is a leaf (actionInPlace, no dynamic, no children), which is why RIGHT does nothing on one, and a RefreshLiveName hook re-reads the state immediately before every announcement - the old submenu got that for free by calling GetCurrentValue on each descend. Three traps came with it: SkuOptions:SlashFunc's path walk and the settings search both select with aEnterFlag = true and would have FLIPPED a toggle named in a path instead of navigating to it - both now only park the cursor, and the search matches against toggleLabel because the name carries the state. RefreshLiveName is not applied to a list's filter header: SkuOptions:ApplyFilter builds it by copying the first child, and the copy would otherwise overwrite the string you are typing. And a toggle whose reader throws is counted in /skucheck menu - it would otherwise sound like a setting with no value rather than like a defect. Ready check: the window did not land on the answer the way every other popup does Simply: When someone in the group starts a ready check, Sku opens the menu and jumps into it, exactly as it does for any popup. For this one window the cursor did not arrive on "Ready" but one level above it, which left two more keypresses between you and the answer. You now land on "Ready" straight away, "Not ready" is below it, and LEFT takes you back up to "ready check". Who started the check is on both entries as full text. Technically: ReadyCheckFrame is only a wrapper around ReadyCheckListenerFrame, which carries the message and the two buttons; walked generically, that wrapper became a menu level of its own and the auto-descend in SkuCore:CheckFrames landed on it. There is a builder for it now (SkuCore:Build_ReadyCheckFrame in SkuCore/LocalMenu.lua, registered in interactFramesListManual) that builds the two answers flat as directAction entries - the same pattern Build_RolePollPopup uses for the role poll. The captions come from the buttons themselves, which Blizzard localises in ReadyCheckFrame_OnLoad, so no new locale entry is needed. Only visible buttons are listed: when you started the check yourself, Blizzard hides the answer side and there is nothing to answer. Game volumes: a value you set came back loud again on another character Simply: If you turned the master volume down in the Sku menu, you found it back at the old value on another character. Sku had been keeping its own copy of the five volumes and the five sound switches on top of the game's, and writing that copy back into the game at every login. The copy hangs off the Sku profile, the game setting off the account: two characters in different profiles therefore heard two different volumes, and a change made in Blizzard's own sound options was silently gone again at the next login. Sku only sets the values now; remembering them is the game's job, exactly as it is for every other player. The sliders in the menu show the game's real value from now on, including when you changed it somewhere else. What you give up: volumes no longer follow a Sku profile. Technically: SkuOptions:OnEnable wrote soundChannels and soundSettings from the profile back into the CVars at every login, preceded by a special rule that read a stored value of 0 or -1 as a corrupted profile and then re-adopted all five channels from the CVars. That whole block is gone. The menu nodes in SkuZOptions/Options.lua read and write the CVars directly (tGetSoundCVarPercent, tSetSoundCVarPercent, tGetSoundCVarBool); the ten keys are out of SkuOptions.defaults and out of the schema, and a one-time pass in OnInitialize clears them from every stored profile - not just the active one, because without a defaults entry AceDB would never strip them again. soundChannels itself stays: SkuChannel, the channel Sku plays its own output on, is a real Sku setting. Reading now rounds instead of truncating - tonumber("0.35") * 100 is 34.999... and would have come back as 34. New profiles ship no volume defaults at all; the game's own defaults apply. The four standard profiles are created without switching profile in between Simply: At your first login Sku creates the four standard profiles if they are missing. It used to do that by switching into each missing profile in turn and then back into yours. On a hardcore realm the server aborts long addon scripts, and if that caught this sequence the character was left sitting in someone else's profile - with all of that profile's settings instead of yours. But a profile exists as soon as it has a name; nothing needs to be switched for it. Ten heavy steps have become four short ones that cannot go wrong halfway through. Technically: The four SetProfile calls plus the switch back in SkuCore/Core.lua are replaced by four assignments into SkuOptions.db.sv.profiles. AceDB's GetProfiles enumerates exactly that table, and a profile is only populated by copyDefaults the first time someone switches into it anyway - the stored end state is the same as before, four empty tables. Each SetProfile call by contrast fired OnProfileShutdown and ran removeDefaults and copyDefaults over the complete defaults tree of all eight modules, about ten walks in a single frame. Dropping the switching also drops the SkuCore.AutoChange flag, which muted OnProfileChanged meanwhile and stayed at true when the sequence was cut off - after which no profile change got through for the rest of the session. The dispatcher pcalls this handler, so the abort was invisible. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.13 Combat: "Announce interrupts on your target" is now off by default Simple: The second of the two interrupt switches now starts switched off as well. Both halves of the split - your current target and all enemies - are opt-in from now on, because an interrupt announcement cuts into everything else Sku says and most players do not want it running by itself. Nothing about the announcement changed, only whether it is on when you have never touched the setting. Turn it back on under Combat, Enemies, "Announce interrupts on your target". As with the all-enemies switch this is applied once to existing profiles too, so a value inherited from the old single switch does not silently keep it on. Technical: In aqCombat.lua the migration block no longer seeds yourTargetInterrupts from the former outputInterrupts flag; it defaults to false like allEnemiesInterrupts, and a run-once yourTargetInterruptsDefaultOff marker clears profiles that earlier 42.13 builds had already filled from the old value. AceDB defaults only fill nil, which is why the marker is needed rather than a plain default change. tOldOutputInterrupts is gone with its last reader. Auction house: a full scan announced itself as "auction started" Simple: Starting a full auction house scan said "auction started, please wait", which does not tell you which of the two kinds of scan is running - a search and a full scan sound far too similar for something that then occupies the auction house for minutes. It now says "full auction house scan started, please wait", matching the completion line, which has always said "auction house full scan completed". Technical: The locale string "Full scan started" was reworded in deDE, enUS and frFR. No code path changed; the announcement site in auctionHouse.lua is untouched. Sku focus: calling a focus arrived late and stuttered Simple: Pressing a focus key to take the stored focus as your target spoke the name with a noticeable delay - often started, cut off and started again. A key press delivers two events, one when the key goes down and one when it comes back up, and Sku ran the stored targeting macro on both. The second run took the same unit again, and because every target announcement clears the speech queue first, the line that had just begun was stopped and restarted - late by exactly as long as the key was held. The macro now runs once per press. This surfaced now because the focus keys sit on the numeric keypad by default since v42.13, so they are being used at all. Technical: The eight focus buttons in skuFocus.lua registered RegisterForClicks("AnyUp", "AnyDown") and therefore fired the macro on both edges. The control frame behind the SET keys had the same bug and had already been moved to a single edge; the GET buttons were explicitly left out at the time. They now register "AnyDown" as well. What remains in the log is two PLAYER_TARGET_CHANGED and exactly one spoken line per press; those two events come from /target itself, which clears the old target before setting the new one - visible in the soft-target lock writing two different values (0, then 2) rather than the same one twice. Target change: a duplicated range check and roster loops that ran on nothing Simple: On every target change Sku walked the whole party and the whole raid to find out whether the target is already fighting someone in your group - including when you were alone and neither existed. That was 88 lookups into nothing, and with "Announce player controlled units with generic descriptions" enabled it multiplied to over two thousand. On top of that the range check ran twice per target change: the second call sat in a piece of code that could never receive its result and therefore never announced anything. Both are gone. Nothing Sku says changes, and the combat-status detection stays complete - in a party or raid every member is still checked, plus your pet and yourself, and the "who is my target attacking" question is asked exactly as before. Technical: In SkuMob PLAYER_TARGET_CHANGED the party1-4 and raid1-40 loops are now gated on IsInGroup() / IsInRaid(), and the raid walks only to GetNumGroupMembers() - the same idiom already used in SkuAuras and SkuQuest. The order is preserved (party before raid) so a member of your own subgroup still answers "party N". In SkuNav the aForTarget branch of GetNonAutoLevel is gone: it opened with DoRangeCheck, which returns no value at all, so its first condition never held - all that was left was the side effect, a second forced range check and a possible second distance sound per target change. Its caller in SkuMob and the helper GetCreatureIdFromCreatureGUID, used nowhere else, went with it. Target in combat: the indicator was permanently on for some users Simple: This only affected anyone with "Announce player controlled units with generic descriptions" switched on (off by default). For them every target was reported as being in combat - beep or spoken - including a completely uninvolved animal standing in a field. For a unit Sku cannot classify, and that includes a unit that does not exist at all, the name function returns an empty string. That empty string was stored as an entry in the list of your own group's names. The test that follows - "is my target attacking someone in my group?" - also got an empty string for "my target is attacking nobody", and it matched that very entry. So the answer was always yes. Empty names are now neither stored nor compared. For affected users the combat sound will come far less often, namely only when the target really is fighting you, your pet or your group. The same cause hit "Auto set private Sku raid targets on in combat targets": there, practically every target got a marker. Technical: With vocalizePlayerNamePlaceholdersSkuTts = true, GetTtsAwareUnitName returns "" for any unit that is neither you nor your pet nor a group member. tRosterNames picked up that empty key, and the targettarget lookup hit it again. Both sides now also test name ~= "". Auras: a verification aid from the rebuild shipped switched on Simple: The aura-list cache also rebuilt every list a second time from scratch in order to compare the two results. That was a correctness check from the rebuild phase, shipped enabled by mistake - and it cost exactly the work the cache exists to save. It is off now; /skuauracache verify on turns it back on for a hunt. Atlas Loot: stutter on login and on opening Simple: Recently the game froze completely for four to five seconds a few seconds after the loading screen, and then took about twenty seconds instead of eight to calm down. The cause was the Atlas Loot integration: on every login and every reload Sku worked through the entire Atlas Loot database - roughly 40,000 entries in one go - even in a session where you never opened Atlas Loot at all. That no longer happens at login. It happens when you actually open Atlas Loot, and it runs in small slices in the background instead of holding the frame. If you do not use Atlas Loot you now pay nothing for it, and its data addons are no longer loaded needlessly either. Second half: opening the Search list inside Atlas Loot also hung noticeably. That list has around 20,000 entries, and building it looked up the name again and asked the server for item data for every single one - for 20,000 entries you have not even looked at. The name is already known at that point, and only the entry your cursor is on needs the data. The list opens correspondingly faster now. Technical: alIntegrationQueryAll builds two reverse indexes over the Atlas Loot data: "which boss drops this item", and the name list behind Search. The run hung off a PLAYER_LOGIN retry ladder (5/10/20/35/60 s) plus a PLAYER_ENTERING_WORLD/ZONE_CHANGED_NEW_AREA safety net, so it fired about three seconds after entering the world - unsliced, in a single frame. Its justification had expired: it was meant to spare the Ctrl+Shift+L jump a cold start, but that jump lost its context detection long ago and now only opens the menu node, which starts the build itself. Three of the four tables the run filled had no reader at all: alLookupBosses and alLookupInstances were written and never read, serving only as the "has this run yet?" flag for the very ladder that ran it; alDropsByBoss and alDropsByInstance were read only behind a condition that can never be true, since the context flag is only ever set to nil. All four are gone, together with that flag, its 120 s timer and BuildContextualWishlistEntry. What remains runs as a coroutine on a 30 ms per frame budget: the menu starts it in the background (QueryAllStart), and whoever needs a complete index - Search, wishlist by dungeon, "Dropped by" - waits for it at its own call site (alIntegrationQueryAll, a no-op once it is built). Each row also got cheaper: name resolution now asks Sku's own item table before GetItemInfo, which queued a server query for every id the client had not cached, and skips the six substitution passes for names coming from that table, which carry no markup to strip in the first place. Building the Search menu was the second hotspot: alIntegrationItemMenuBuilder now optionally takes the name the caller already has (the Search index is keyed BY that name), which drops the per-entry name resolution, the Unescape pass and above all SkuCore:RequestItemData - an ItemMixin plus a registered load callback per entry, and a server query for every id the client had not cached, some 20,000 times in one frame. A boss's loot list (a few dozen rows) still requests eagerly, so an uncached name there keeps filling itself in, and the focused entry requests its data in OnEnter anyway - so the spoken text is unchanged. Collision warning: beeps with nothing in the way Simple: The "you are stuck" beeps could turn up while you were simply walking from one NPC to the next, with nothing blocking you at all. The counter behind the warning was never reset, so harmless single measurements piled up over minutes and every sixth one set off a beep. Technical: The warning compares the distance covered in one 0.15 s tick against what your current speed should have covered, and beeps after six slow ticks. Only the counter had no reset except the one when it fired - not on a good tick, not when the warning was disarmed - so it was a lifetime accumulator rather than a run of six in a row. The first tick after every PLAYER_STARTED_MOVING samples a partial window, because the previous sample was taken while you were still standing, and therefore always scores as too slow: roughly every sixth start of any movement produced a beep, minutes apart and unrelated to anything. That has been in there for a long time but only counted while you held a movement key yourself; since engine-driven walks were added to the warning in this version, every automatic walk-to-target counts too, which is why it became noticeable while working through NPCs. The counter now resets on any good tick and whenever the warning is disarmed, exactly as the follow variant of the warning already did. That variant also stays as it is: it is disarmed by AUTOFOLLOW_END, and the unfollow sound is emitted by that same path, so hearing it is the proof the state was reset. The warning now leaves a line in the debug log when it fires, so a report of "beeps out of nowhere" can be pinned to the path it came from. Taxi flight: two flight announcements that were not true Simple: Two things were announced that did not hold. First, shortly before arriving, Sku said "early landing available at X" - and X was the destination you were already flying to; landing early there is just arriving. Second, "flight ended" (or "flight started") sometimes came long after the flight, typically the moment you next mounted or dismounted, occasionally minutes later and nowhere near a flight point. The first line is gone; the second now comes exactly when the flight really starts or ends, and never otherwise. Technical, the destination as a landing point: The last-point exclusion was already there; the route cursor simply slid out from under it at the decisive moment. The route is captured at takeoff and a stop counts as passed once it is closer than 250 yards. But the taxi flies straight into its final node, so that one got "passed" too: the cursor ran off the end of the route, the next stop was nil, and the target lookup quietly degraded to the nearest-flight-point fallback. That fallback knows nothing about the route, so the source flipped from "route" to "nearest" - and that is exactly what the rule silencing the final leg keys off. The destination was well inside the 800-yard threshold by then, so it announced. The cursor now stops on the destination instead of walking past it: the destination is not a stop you pass, it is where the flight ends. The rule therefore holds through touchdown, and the keybind can still name the destination on demand. Technical, the late announcement: It hung on two flags that PLAYER_CONTROL_LOST and PLAYER_CONTROL_GAINED set to 1 and that ONLY the next PLAYER_MOUNT_DISPLAY_CHANGED ever cleared. If that event did not follow - or arrived before the control event at a landing - the 1 simply waited, and the next mount or dismount consumed it. PLAYER_CONTROL_GAINED also fires for plenty of things that are not a landing (mind control ending, a cinematic, a knockback, a vehicle), so the flag could be set with no flight involved at all. The state is now DERIVED from UnitOnTaxi instead of latched: it speaks only on a real false->true / true->false transition, every relevant event re-runs the same idempotent check (control events also re-check after a short delay, because the client flag lags the event), and a login or /reload adopts the current state silently. Technical, belt and braces: The taxi module ("early landing available at X") could not fail this way - that line sits behind UnitOnTaxi AND the visible cancel button. Hardened anyway, since both are client state: regaining player control now means "the flight is over, full stop" - the watch is torn down and no event may re-arm it until a new flight is actually taken. On top of that, a momentarily invisible cancel button (a zone hand-off mid-flight is enough) no longer wipes the announcement latches, which could otherwise name the same flight point a second time. "/skutaxi" reports the new state as "landed=". Group invite and summon: the decline button is back Simple: A group invitation or a summon ("port") offered only "Accept" in the menu - the button to decline it was missing, and it stayed missing for as long as the dialog was open. Both buttons are listed again. Technical: Blizzard locks the decline button on exactly these prompts for a moment after the dialog appears (SetupLockOnDeclineButtonAndEscape, the same helper that turns Escape off for them) as mouse-misclick protection against invite and summon spam. Sku's generic frame reader skips any greyed-out widget, and it scrapes a popup exactly ONCE, 0.1 s after it shows (GENERIC_OnOpen) - right inside that lock window. So the decline button was dropped, and because nothing rebuilds the menu while the dialog just sits there, it never came back. SkuCore:IterateChildren now exempts StaticPopup buttons from the "never list a disabled widget" rule: navigating to a menu entry and pressing Enter is a deliberate act, not a misclick. Those buttons also keep their OnClick handler even when the disabled widget reports no mouse-click input, so the entry cannot end up listed but dead. Combat: a popup could be read but not answered Simple: when a popup opened during a fight - a group invite, a summon, the release prompt - Sku could read it out and you could navigate it, but the activate key did nothing; the entry was simply announced again. You had to wait for the fight to end, and an invite expired in the meantime. Popup buttons can be pressed in combat now, all four of them, not just Accept and Decline. Technical: in combat the activate key never reaches the secure menu button at all: PLAYER_REGEN_DISABLED hides OnSkuOptionsMain, whose OnHide clears that button's override bindings while the grace window is still open, and the key is driven instead by SkuCombatMenuKey's snippet, which treats ENTER as "route only" and hands it straight to the insecure dispatcher (SkuCore/combatMenuKeys.lua). Readable, navigable, dead on ENTER. Unlike the bag and trade paths this needs no mirror and no secure arming: a StaticPopup button is NOT a protected frame and its OnClick is plain Lua (StaticPopup_OnClick calls the dialog's own OnAccept/OnCancel: AcceptGroup, DeclineGroup, ResurrectAccept, RepopMe, ConfirmSummon). None of those is hardware-gated, so calling it directly works in combat exactly as out of it. Deliberately limited to the popup buttons - every other click payload in this builder does need the genuine hardware event and belongs in the mirror. Guards: only in combat, only while the button is really there, and only while Blizzard has it ENABLED (the decline button is locked for its first second, where "/click" is equally inert). Also new: all four buttons are handled this way; buttons 3 and 4 used to fall through to the generic click path with no insecure action at all. /skucheck menu counts the cases where an activation in combat found no button left to click. Typing in input fields: no more letters trailing after the field is gone Simple: While typing into a Sku input field (writing mail, a search box, the auction house) the spoken characters lagged behind the typing, tripped over each other, and letters kept arriving long after the field had closed - most noticeably with SAPI voices, which a keystroke did not interrupt. Sku now types the way a screen reader does: the next keystroke cancels the previous announcement instead of queueing up behind it. Whatever was typed while an announcement was still running is merged into ONE announcement, so pasting with Ctrl+V or holding an arrow key can no longer produce a flood. And when the field closes - via Enter, via OK, via Escape or by any other route - Sku no longer just drops what is waiting, it also stops what is currently being spoken. Fixed alongside: the characters ( and % were never announced at all while typing. Technical: The echo in SkuOptions:EditBoxShow appended one utterance per typed character to the BTTS queue. The v42.11 backlog cap (GetBttsQueueDepth / TrimBttsQueue) could never bite: the SkuVoice pump skips its 0.1 s pacing as soon as more than one entry is pending (`#mSkuVoiceQueueBTTS > 1`) and hands the whole burst to C_VoiceChat.SpeakText within a few frames, i.e. into the CLIENT's queue. Sku's own queue is empty afterwards, the measured depth is practically always 0, the cap never triggers, and on close Trim finds nothing left to drop - by then the backlog sits in the client/SAPI queue, which only a real StopSpeakingText can reach. The echo now runs through exactly one slot: characters are collected until the next flush (at most 6, newest win), spoken as ONE overwriting utterance, and rate-limited to a 0.1 s minimum gap; a generation counter discards flush timers and deferred arrow reads the moment the field is gone. New: SkuVoice:CancelBttsOutput - clears the BTTS queue, calls StopSpeakingText and arms the pump's post-stop hold at 0.15 s so the confirmation issued right afterwards is not killed by the asynchronous stop; it is wired to ENTER, OK, ESCAPE and the frame's OnHide (so a foreign :Hide() is covered too). Also: SkuVoice:CheckIgnore now uses string.find(..., plain=true) - typed punctuation such as ( or % was treated as a Lua pattern and threw "unfinished capture" / "malformed pattern", which the caller's pcall swallowed; and the echo passes ignoreLinks, because every keystroke used to run through the full wiki link search whose result is discarded here anyway. Performance improvements and fixes when loading the spell database Simple: At login some players got a spoken "Sku database error, data set spells". It was reported from Era, but the common factor is processing power rather than the client: on a fast machine it never happened, on a weaker one it happened every time, and it could just as well have hit TBC. The database itself was never broken: the only thing that failed was the selection list for auras derived from it, which was built in ONE pass over roughly 90,000 rows. On a slower machine the client aborted that pass with "script ran too long" - and because the list was written in place while it was built, it stayed half filled afterwards: part of the spells were missing from the aura settings, and auras pointing at a missing entry either announced nothing or left the menu standing on an error. The build now runs in small slices spread over several frames, exactly like the rest of the post-login database build, and is published in one piece only at the very end. If it does abort, the previous lists stay as they were instead of half filled, no database error is spoken, and the rest of the spell data still counts as sound. On top of that the list is now built once per session instead of on every loading screen. Technical: SkuAuras:BuildAttributeValueLists walks itemLookup AND SpellDataTBC and allocates a table plus one or two concatenated strings per row. It used to be called synchronously from two places: the "auraValueLists" build step (after items+spells) and PLAYER_ENTERING_WORLD. The function now takes an optional yield callback, polled every 1000 rows, builds into local tables and publishes them at the end with one assignment each (atomic - an abort changes nothing at all). It is driven by its own coroutine with an OnUpdate pump that shares the post-login frame budget (Sku:BuildFrameBudgetMs, registered as build worker "skuAuraLists"), so three live workers still cost 150 ms per frame in total. The build step only starts that driver and no longer calls ctx.fail: a failure of the derived list used to mark the whole 'spells' data family failed and trigger the error announcement, although the spell data was complete. Failures now go to SkuErrorLog ("skuAuraLists") and the debug log. PLAYER_ENTERING_WORLD starts the same driver, which is a no-op once a build has succeeded - so the full rebuild on every zone change and instance entry is gone. The build also skips a spell row with no name for the active locale instead of dying on it, and the three unguarded friendlyName lookups in the aura menu fall back to the raw key. An enemy that poisoned you before it died is no longer counted again Simple: The enemy count has been reliable in ordinary fights since v42.10, but one case was left: if an enemy hit you with a poison - or any other lingering effect - shortly before it died, the number dropped correctly when it died, climbed back up by one a moment later, and only fell for good once the poison ran out, long after the enemy was dead. That is fixed, and the detection itself was not touched to do it: nothing new is counted, one thing simply stops being counted wrongly. Technical: A dead enemy is remembered by GUID so a trailing combat-log line cannot revive it. That mark used to be cleared wholesale in PLAYER_REGEN_ENABLED - which is exactly the moment the last enemy's death takes you out of combat, and so the instant before the first poison tick arrives. Every tick is a combat-log line with the dead enemy as its source and you as its target; the add path reads that as "an enemy is fighting us" and re-admitted the corpse by its GUID. The second zero then only came from the 6 second idle sweep after the last tick. The mark now carries a timestamp and is no longer dropped wholesale at the end of a fight, only once it is older than 60 seconds. Nothing changes inside a fight - there the mark already lived until combat ended, which is why "one of two enemies dies poisoned while the second one fights on" was counted correctly before. No new state and no new special case are added, only a lifetime for a mark that already existed. The existing escape hatch stays: if the same GUID shows up demonstrably alive on a unit token, the mark is lifted at once, so a real enemy can never disappear from the count for good. A trade that completed successfully was announced as a withdrawn confirmation Simple: When a trade actually went through, it still ended on " withdrew their confirmation". Nothing had gone wrong: on completion the server clears both confirmation ticks first and only then closes the window, and that clearing looks exactly like a real withdrawal. The false line is gone now. There is deliberately no "trade complete" announcement in its place: client 2.5.6 offers no signal that tells completion apart from cancellation, and a guessed message would be worse than none. Technical: Captured for a successful enchant inside a trade, all within one second: TRADE_ACCEPT_UPDATE(1,1) -> (0,1) -> (0,0) -> TRADE_CLOSED. TRADE_ACCEPT_UPDATE read those 1->0 transitions as a real withdrawal. There is no way to tell them apart: TRADE_CLOSED carries no payload, and ERR_TRADE_COMPLETE / ERR_TRADE_CANCELLED do not exist on 2.5.6 (checked against the shipped TradeInfoDocumentation.lua). The two un-accept announcements now wait 0.5s behind a generation counter that ResetTradeAcceptState() bumps at every trade boundary, and are dropped if the trade has ended, the frame is gone, or that side is back to accepted. "Trade confirmed" was swallowed every single time Simple: If you confirmed a trade from the menu, you never heard your own confirmation. And out of several trade announcements in quick succession only the last one was ever spoken - that is, the wrong one. They all come through now, in the right order. Technical: OutputStringBTtts with aOverwrite=true appends a "queuereset", and the pump deletes everything queued BEFORE that marker, so in a burst each trade announcement ate its predecessor. All trade announcements now pass false. That alone does not save your own confirmation: when you accept from the menu, the server reply is already queued before the menu appends its own line for the key press, whose reset then deletes it. The accept announcements therefore go through tSayAccept with a flat 0.35s delay that puts them behind the menu line. Queue ordering only, no state inference - and deliberately without a generation check, because a trade closing right afterwards is exactly when that confirmation matters most. Enter applies a disenchant or an enchant to a bag item again, in both orders Simple: Cast Disenchant (or take an enchant, an armor kit, a weapon oil) while you are already standing on the target item in the bag list, then press Enter: until now nothing at all happened, and only Ctrl+Enter worked. The other order was fine - cast first, then walk to the item. Enter now applies in both orders. Left click really is the game's own way to do this: in Blizzard's bag code a left click on an item, while a spell is waiting for an item target, uses the item for that spell. In v41 this worked through the old "left click" entry; the menu rework in v42.00 lost it and v42.04 brought it back only for the cast-first order. Technical: The secure left-click button is staged when an entry is FOCUSED, and a bag item's apply payload ("/use ") is only staged while SpellIsTargeting() is true at that moment. Cast afterwards and the button still held the pre-targeting payload - for a bag item there is none at all - while the targeting snapshot taken in PreClick additionally makes the insecure PickupContainerItem fallback skip itself, so the key press did literally nothing. The staging moved out of the generic OnEnter into SkuOptions:StageClickMacros (both secure buttons) and is re-run from two places: CURRENT_SPELL_CAST_CHANGED, whose handler was empty and whose dispatcher signature was one slot off, and the left button's own PreClick, which runs before the secure handler reads the attributes, so the swap still counts for that very key press. Re-staging touches only entries that carry an apply payload, only while the menu is open, and only out of combat. Ctrl+Enter on a vendor item opened the dressing room instead of buying it Simple: At a vendor, Ctrl+Enter (right click) on an item is supposed to buy one of it. Instead the dressing room opened. Buying through the item's submenu always worked and is unchanged; the key now buys as well, on the buyback tab too. Technical: The right-click key carries CTRL by default, a "/click RightButton" macro reads the LIVE keyboard, and every native item button's XML routes ANY modified click to *_OnModifiedClick -> HandleModifiedItemClick -> DressUpLink - the plain right-click branch is never reached. This is the same trap that broke oils on equipped items back then, fixed there with "/use ". Vendor items now call Blizzard's unwrapped MerchantItemButton_OnClick directly, which keeps the extended-cost and high-price confirmation dialogs. Bags, bank and equipment slots were already immune because they go through "/use" or the container API. Everywhere else that Sku clicks a native button (loot, trade, mail, spellbook, guild bank) the right-click key has no separate action to lose - there Ctrl+Enter does nothing or shows the dressing room, while Enter does the real thing. The left click may now be bound to a key with Shift, Control or Alt Simple: The activate key can be rebound to a combination such as Ctrl+Enter and still works. Before, such a key would have opened the dressing room on a vendor item or on an equipped item instead of doing the thing, for the same reason the vendor right click did. Technical: Entries whose click would be swallowed by a modifier carry a second, modifier-proof payload (plainMacrotext) that calls the action directly instead of clicking the native button: vendor items via Blizzard's unwrapped MerchantItemButton_OnClick, equipment slots via PickupInventoryItem, trade slots via ClickTradeButton / ClickTargetTradeButton. It is staged only while a modifier is physically held - the secure button's PreClick decides, since that is the only moment the keyboard state can be read, and it runs before the secure handler reads the attributes. With the default modifier-free Enter, staging is unchanged down to the byte, so nothing can regress there. Bags, the bank, quest rewards, the guild bank, popups and tabs needed no entry: they either go through the container API or have no modified-click branch at all. Loot buttons, mail attachments and spellbook buttons do carry that branch, but Sku never clicks them - loot has its own handling, attachments go through TakeInboxItem and the spellbook is not among the windows Sku opens. Should the spellbook ever be added, it needs "/cast " rather than this mechanism, because casting is a protected function that no direct call may perform. The menu's click keys could not be assigned at all Simple: In the key bindings, "Menu; left click/activate" and "Menu; right click" answered "Invalid. Press another key." to every Enter you pressed - with or without a modifier - so neither could be set, not even back to its own default. Both can be assigned now. Technical: The reserved-key list is matched as a SUBSTRING, so "ENTER" also catches NUMPADENTER and CTRL-ENTER. It exists to protect the menu's navigation keys - but these two bindings live on exactly that family: Enter is the default of the one, Ctrl+Enter of the other. They are now exempt from the block for Enter keys only; arrows, backspace and tab stay blocked there as well, because they drive navigation, which is not part of this binding. The in-combat menu keys already had such an exemption. Applied in both places that capture a key: the key binding menu and the single-entry helper. The menu click key no longer deletes the game binding on the same key Simple: Putting "Menu; left click/activate" back on Enter warned that the key was "already bound to Open chat" and, once confirmed, did exactly that: Enter no longer opened the chat. There was no way back either, because the key bindings refused every Enter for game commands as invalid. Yet these two never got in each other's way - they shared Enter for years: while the menu is open Enter activates the entry, otherwise it opens the chat. That is how it works again. When a key is shared like this, Sku now says so ("Note! That key is also used by ... Both bindings are kept.") instead of removing the other binding. If you already lost chat: simply assign "Open chat" to Enter again in the key binding menu, which is possible now. Nothing else about conflicts changes - two Sku keys on the same key are still reported and the older one released. Technical: The menu's click keys are never a real binding but an override binding on the secure menu button, armed by its OnShow and cleared by its OnHide - it exists only while the menu is open and outranks the game command exactly for that time. The same holds for the in-combat menu keys, armed only during a fight. The conflict check did not know that difference: it compares bare key names, so it reported a conflict and, on confirmation, called SetBinding(key) plus SaveBindings - which permanently stripped OPENCHAT off Enter. It only became reachable because the fix above made those bindings assignable to Enter in the first place. The affected entries now carry a transientOverride marker in skuDefaultKeyBindings, read through SkuOptions:SkuKeyBindsIsTransientOverride; for them the conflict handling against GAME commands is dropped (no confirmation, no unbind) and replaced by the spoken note. Against other Sku bindings it stays fully in place, because those are armed at the same time and really do evict each other. The same rule applies in the other direction so a game command does not release the menu key, and the Enter family is no longer blocked when assigning game commands - arrows, backspace and tab still are. The note rides on the same announcement as the new key, because a separate call with aOverwrite resets the queue and would have swallowed itself. Items in the bank are called what they are called again Simple: In the bank - and occasionally elsewhere - some items read as "Retrieving item information" instead of their name. That is not an addon error message but a placeholder from the game: the client shows it while it is still fetching an item's data from the server. It mostly hit items with a large tooltip - recipes, set pieces, socketed gear - because their display is only complete once ALL the data referenced in it has arrived. Sku used to take that placeholder as the item's NAME, and then kept it: it never went away, not after navigating onto the entry again, not after leaving and re-entering the menu. Doubly annoying, because the sentence is identical for every affected slot - several waiting items sounded the same, could not be told apart or aimed at, and it was also what sort-by-name and the jump-by-first-letter matched on. Now Sku takes the name from its own shipped item list when the client does not have it yet, appends only a short "(loading)", actively requests the data from the server, and re-reads the entry as soon as it arrives. Opening the bank also requests the data for every bank slot up front, so the first pass usually does not have to wait at all. Technically: The sentence is the game constant RETRIEVING_ITEM_INFO. There was not a single check against it in the addon - the only mention in the source sat in a developer tool and compared a hardcoded German string. It stayed permanent for three reasons: there was no GET_ITEM_INFO_RECEIVED handler anywhere, no load request was ever made (writing to a tooltip is not a request you can follow up on), and there was no second route to a name. New: SkuUtil:IsRetrievingItemInfo (detects the placeholder via the game constant rather than German text), SkuCore:ResolveItemName (client cache, then SkuDB.itemLookup, then the name in the link), SkuCore:PendingItemLabel, SkuCore:RequestItemData and SkuCore:ContinueOnItemData via Item:CreateFromItemID():ContinueOnItemLoad. A dedicated event frame collects GET_ITEM_INFO_RECEIVED and triggers one coalesced quiet rebuild from it; BANKFRAME_OPENED pre-requests container -1 and bank bags 5 to 11. There is a specific reason the bank was the hotspot: SetBagItem(-1, slot) returns nothing on 2.5.6, so the bags rework fell back to SetHyperlink(GetContainerItemLink(...)) there - precisely the path that depends on the item cache. Bank slots are real inventory slots (slot n = inventory slot n + 39), so SetInventoryItem reads them just as locally as SetBagItem reads a bag; this is not a step back to the rendered widgets the rework retired - no frame, no OnEnter, no force-opening the bags. No fallback was deliberately left behind it: it would only hide whether the correct path works. Items no longer disappear from the AtlasLoot loot lists Simple: Entries were missing from the loot lists - no gap, no notice. A list with twelve possible drops showed seven and read like a complete list of seven. The cause is the same as in the item above: items whose data the client had not yet received from the server were not recognised as items and were left out entirely. That hit exactly the entries this feature is for, because a loot list consists almost entirely of items you have never looted or inspected - the less familiar the boss, the more went missing. It was not repeatable either: opening the same list a second time could give a longer one, with the entry numbers shifted. Now every entry is there, and searching by name also finds items you have never held. The lists become longer than you are used to, and the entry numbers shift once accordingly. Technically: At four places a router decided from C_Item.GetItemNameByID(id) whether a loot row is an item or a spell - a test that conflates "not an item" with "not sent by the server yet". An item that had not loaded fell into the spell branch, GetSpellInfo returned nothing for it, and the row was dropped. The intent of the test is right (keep junk ids out of the menu), the question was just wrong: GetItemInfoInstant answers it locally from the client's own item database. There is now a tIsItemId helper for that; at one of the four places the problem had already been worked around individually with a SkuDB check, which this replaces. Branch precedence is unchanged. The early return in the "item" branch of the menu builder is also gone; the entry is named via SkuCore:ResolveItemName or - stably - "item ", deliberately not the "(loading)" form, which would move the entry out from under the cursor once the data lands. tItemNameTable behind the search by name is filled via ResolveItemName as well, and the GetItemNameByID call on the favourites at login, whose result was thrown away, is now a real load request. The dungeon browser offered every dungeon instead of only the ones you can join Simple: Under "Create entry" you got the complete list of the category's dungeons - the same one at every level. A level twenty character was offered the heroic Outland instances, a level seventy character was offered the Deadmines, and both could be ticked and posted even though you cannot get in there at all. The visible window never behaved that way: there the game narrows the list down to what suits your own level, and that narrowing was missing on our side - we simply read the unfiltered list. Sku now shows the same selection the window does. If you do need the whole list anyway, for instance to offer yourself for a dungeon far below your level, switch off the new "Only matching dungeons" entry; it sits directly above the dungeon groups and, while on, says how many entries it is currently hiding. Dungeons you have already ticked always stay visible even when the narrowing would otherwise hide them - otherwise you would post something you can no longer find in the menu. Technical: tGetCategoryActivities called C_LFGList.GetAvailableActivities(categoryID) without the filters argument and therefore got the entire category; the levels from GetActivityInfoTable were only read for the label, never used to filter. Filtering now happens in two AND-ed layers: once through the game's own set (GetAvailableActivities(categoryID, nil, Enum.LFGListFilter.Recommended)) and once locally against UnitLevel("player") using minLevelSuggestion/maxLevelSuggestion - on 2.5.x those are the only fields carrying real levels. The game's set is only trusted when it comes back non-empty AND as a true subset of the unfiltered list, so a build with a different signature can never blank the menu; missing level data lets an entry through. New: the dungeonBrowser.showAllActivities setting (char), the SkuCoreDungeonToggleShowAll toggle (rebuilds the tab, since the set of children changes), the counters in DungeonBrowser.tFilterStats, and an "activity filter" dprint line carrying the individual counts of both layers. Quick menu: Lua errors on some settings Simple: In the quick menu several settings threw a Lua error the moment you opened them with right arrow - most noticeably under "Speech output", but also the beacon volume under "Volumes" and the yes/no toggles under "Other". The very same settings reached through their normal place in the settings menu worked fine. The quick menu does not own any settings of its own: it shows the same entries a second time in a second place, and that second listing was missing one piece of information, without which Sku could not find the stored value. Both routes now show the same value and write to the same setting. On top of that, "Other" lists all four intended entries again: "Do not hide tooltip" and "Play NPC greetings" had quietly disappeared from it when they were given a new home elsewhere in the settings menu. Technical: SkuOptions:IterateOptionsArgs decides from its fourth argument (aModule) whether a node is schema-managed: skuManaged = (v.get == nil and v.set == nil and aModule ~= nil). When it is not, GetCurrentValue reads through self.optionsPath[self.profileIndex]:get() - and those get/set closures are exactly what the schema-managed nodes do not have, since they live off aModule. The quick menu's six mirror calls in SkuZOptions/Core.lua passed no aModule, so opening any such node ran straight into "attempt to call method 'get' (a nil value)". Affected were WowTtsVoice/-Speed/-Volume (SkuChat), beaconVolume (SkuNav), readAllTooltips/interactMove (SkuCore) and vocalizeMenuNumbers/ vocalizeSubmenus (SkuOptions); the audio channels and sound settings stayed intact because they carry their own get/set. All six calls now pass module and keyPrefix exactly as the original menu location does ("SkuOptions"/ "soundChannels.", "SkuNav"/"", "SkuOptions"/"soundSettings.", "SkuChat"/"", "SkuCore"/"", "SkuOptions"/""), so the stored values are unchanged. The "Other" call additionally gets aIncludeHidden = true, because doNotHideTooltip and playNPCGreetings now carry forAudioMenu = false and were being filtered out without a word. In addition, every node produced by IterateOptionsArgs now reads and writes through two central helpers (tOptGet/tOptSet) with three tiers - SkuSettings, own get/set closure, direct table access - so a future mirror site without aModule at worst reads the value directly instead of throwing. Two menu moves: the beacon sound settings and "Other" Simple: The beacon settings (beacon volume, click on beacons with its angle and sound, and the sound sets for narrow, wide and last beacons) used to sit under Monitor. They now live where the rest of the navigation settings live: Settings, Navigation, Beacon sound settings. The entry is no longer called just "Beacon" but says what it is about, and Monitor no longer has it at all. On top of that, "Other" is the last entry of the quick menu again - "Dial Targeting" and "Soft Targeting" now come before it instead of after. Technical: The beacon block moves unchanged out of Aq:MonitorMenuBuilder (SkuCore/aq.lua) into the Navigation branch of SkuCore:MenuBuilder (SkuCore/Options.lua), where it hangs as a "Beacon sound settings" subentry (German "Beacon Soundeinstellungen", French "Réglages de son des balises") below the navigation settings. These are the same option nodes from SkuNav.options.args, passed through IterateOptionsArgs against SkuSettings:Sub("SkuNav") with the same module and keyPrefix - so the stored values are unchanged, and the sound set nodes keep their OnAction that plays a sample beacon. As before the list is an explicit whitelist, not the whole args table: a new beacon node has to be added there too. In the quick menu (SkuZOptions/Core.lua) Dial Targeting and Soft Targeting were pulled in front of the 7.4 block; that child list carries no sorting flag, so insertion order is display order. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.12 The character sheet reads out the description of every stat again Simple: Under "Stats" each value - spell damage, spell hit rating, stamina, weapon speed and all the others - had only its name and its number. The description the game shows next to it was empty: what the stat does, the breakdown per spell school, main hand and off hand. The full-text key said nothing on those entries. Only the resistances and your equipment had a description. Now every stat has one, and it is read fresh each time you land on the entry, so it is current after a buff or a gear change too. Technical: The tooltip read was commented out at that spot and every entry was built with an empty textFull - unchanged all the way back to v41.06, so this is not something the rework broke, it never worked. Two reasons it could not have worked as written: GetButtonTooltipLines was called with GameTooltip as its second argument, which skips exactly the step that fills the tooltip in the first place; and without that argument the helper would still have bailed, because it only fires an OnEnter for objects carrying a .type marker - the stat frames do not have one, which is precisely why the resistance loop sets it before its own read. On top of that came a third reason, the one that would have made a naive fix wrong: TBC drives all 29 stats through ONE shared widget (PlayerStatFrameLeft1), and the Blizzard setters leave their state on it - .tooltip/.tooltip2 plus .bonusDamage/.spellCrit/.damage for the four stats that install their own OnEnter. None of them clears what the previous one left, four of them swap OnEnter out and never back; Blizzard's own UpdatePaperdollStats re-points it before every group for exactly that reason. Without the same reset, stat N would have read out stat N-1's description. The read now resets the widget before each setter and walks the tooltip via NumLines/GameTooltipTextLeft|Right rather than GetRegions - the latter hands back the left and the right column as two separate runs, so the spell school breakdown would have been read as six school names followed by six numbers. The live getter that re-reads the value on every landing now returns the description as a second value alongside it, and afterwards the widget is handed back to the state Blizzard expects via PaperDollFrame_UpdateStats. Sku no longer depends on internal tokens being left untranslated Simple: On a French client the Quest Log key did nothing at all - no focus, no sound, no error. A single translated word in the French locale file was to blame. Some entries in there are not a label but an internal token that Sku uses to build and recognise its own menu paths; translate one and the addon can no longer find its own path. That is fixed, and fixed in a way that cannot recur: the token is not translatable text any more. If you have got used to typing a French path it still gets you there. A second find of the same kind would have silently switched every French client over to English names for mobs, objects and quests the next time the locale file was sorted - that is gone too. Technical: Found, root-caused and fixed by ZenqFR (Games-Access) on a live French client, as PR #2 in this repo. The follow-up changes build on that. L["short"] carried two unrelated meanings: in about 25 places it was the first field of a SkuOptions:SlashFunc path, i.e. a protocol token the code compares against itself, and in exactly one place (SkuCore/aq.lua) it is genuine user-facing text. A translator only ever sees the second meaning. The two are now split: Sku.MENU_ROOT is the token and is used by all 23 path-building sites plus the six that hardcoded the literal "short," - one of which was the Quest Log key. The comparison still accepts L["short"] as well, so a localized path anyone has learned keeps working. Fixed in passing: the fallback in SkuCore/dungeonBrowser.lua was "sku", not a valid token at all. Second, frFR.lua defined L["locale"] twice - "enUS" in the sorted body and "frFR" appended at the end. It resolved correctly only by AceLocale's ordering: a non-default locale is registered through a proxy that rawsets unconditionally, so the LAST assignment wins. Sku.Loc is read straight from that key, and it selects which SkuDB name tables the entire client uses - regenerating or sorting the file would have flipped every French client to English DATA silently: no error, no log line, just the wrong names everywhere. The losing line is deleted, the surviving one documented, and dev/rework-docs/_locale_dupes.py now reports duplicate keys per locale file. The lint also makes visible that enUS, as the AceLocale default locale, is FIRST-wins instead: the same duplicated key can resolve to different entries per language. 64 such conflicts exist today, nearly all old translator noise; they were deliberately left alone and are at least visible now. Third, SkuDBEnsureActiveLocaleTables actually keeps its promise now - it also runs at ADDON_LOADED (previously only one OnUpdate after PLAYER_LOGIN, so anything touching [Sku.Loc] in its own PLAYER_LOGIN handler saw nil for a moment) and no longer returns silently on an unusable Sku.Loc: it validates, logs the likely culprit, and builds the tables regardless. Speech no longer goes silent when you move through a list quickly Simple: Scrolling fast through a menu, a list of nearby objects or your bags could stop the speech entirely - only the click and beacon sounds were left - until you waited a few seconds or moved to a different part of the interface. Every entry you passed was swallowed, and often the one you finally stopped on as well. Now every entry you move over is handed to the voice, and the entry you stop on is spoken. Nothing was changed about which announcement wins: a new one still replaces the one before it, so scrolling fast still reads only what you land on, and cancelling speech in combat is unchanged. Technical: The Blizzard-TTS pump paced itself with a countdown that subtracted a single frame delta per run, but its body only runs every 0.01 s - so above roughly 100 fps it subtracted far less than the time that had really passed and the intended 0.1 s hold before the next utterance stretched to 0.2-0.3 s real. Since a queue reset deletes whatever line is still waiting, a longer hold meant more lost announcements: the bug got worse the higher the framerate. The hold is now a GetTime deadline, identical at 60 fps and correct above it. Measured on a capture at 6-7 keypresses per second: before, 7 resets produced 2 spoken lines; after, 7 resets produce 7. A second guard suppresses a StopSpeakingText when nothing is in flight and one was already sent within 0.15 s - the client processes stops asynchronously, so a trailing one could cancel the next utterance before it started. That guard never fires while navigating now; it remains for event-driven bursts (mail, chat) which produced 17 stops in one second with no speech at all. Less work per spoken line Simple: Sku does noticeably less work for every line it speaks and every sound it plays, which shows up as smoother scrolling. Technical: Two temporary [SOUND-PROBE] diagnostics were removed. Both passed debugstack(...) as an argument to dprint, and dprint only tests the debug flag inside itself - so a full stack walk ran for every sound even with logging off, and the one in Sku/Core.lua hooked the global PlaySound, so Blizzard's own calls paid it too. They accounted for a quarter of all captured log lines. The queue pump now skips its sweeps entirely while both queues are empty instead of running them about a hundred times a second, and the wiki link search bails out before normalising the string rather than after: it is called for every spoken line from OutputStringBTtts, where its result is discarded anyway, and without a wiki index it was running about 19 string.gsub passes to reach a guard that did nothing. The installer and the download page speak French now too Simple: Sku itself has spoken French since v42.11, but everything around it did not. The Sku Installer now runs in French - it takes its language from Windows and you can change it at any time in the "Language of this installer" list - and the download page has a French version, reachable from the language links at the top of either page. These patch notes exist in French as well, starting at v42.11. There is still no French voice pack: on a French client Sku speaks through your screen reader, which is why the installer offers the English pack there. The installer is version 4.1 with this. Technical: A French table in the installer's Loc.cs (Lang.Fr, the same key set as English and German, "fr" taken from the OS UI culture; the language combo is built from ComponentsForm._langOrder so no index literal has to be kept in step), docs/index-fr.html, and "Patch Notes Sku FR.txt" mirrored onto the website by release.ps1. The docs rewrites in release.ps1 now run over a LIST of pages and match on language-neutral fragments, so a translated page cannot keep a stale version number - the same failure the v42.11 heading fix was about, one page further; it also picked up the SkuMapper heading, which had been drifting unnoticed. The Discord announcement is per channel now: a webhook line may name the languages that channel wants, so a German-only server gets one German line with German links rather than three languages it did not ask for. Two menus that were nothing but a detour are gone Simple: Under "Settings" there was a "Combat" entry holding exactly one item: "Sku menu in combat". That item now sits directly in "Settings, General", and the empty "Combat" level above it is gone. Same story in the main menu: "Tools" only ever held "Dial Targeting" and "Soft Targeting"; both now live in the "Quick menu", and "Tools" has left the main menu. Nothing about the settings themselves changed, and every value you had saved is kept. Technical: All three entries keep their existing builders, their db and their keyPrefix - only where they are attached changed, so stored values are untouched. "Werkzeuge" is removed as a SkuMenu module and from SkuMenu:SetRootLayout (SkuZOptions/SkuMenu.lua); DialTargetingMenuBuilder and the softTargeting group (still via IterateOptionsArgs with aIncludeHidden=true, since it is flagged forAudioMenu=false) now hang in the Quick menu's BuildChildren (SkuZOptions/Core.lua). The "Kampf" spec drops out of tSpecs in SkuCore/Options.lua; CombatMenuOpenMenuBuilder is now called at the end of the General builder, against the same combatMenuOpen setting. No SlashFunc path pointed at either menu, so nothing else had to follow. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.11 Taxi: Sku names the next flight point and can land you there Simple: On a flightmaster taxi Sku now announces which flight point is coming up next, and you can get off there instead of riding all the way to the destination. The key for that is CTRL-SHIFT-E by default; like every other Sku key it is rebindable, listed under "Navigation and waypoints". There are two announcements per leg because they answer different questions: the next stop at takeoff and after each hop, and "early landing possible" about 800 yards before it. Before the final stop Sku stays quiet - landing early there is simply arriving. The whole thing can be switched off under "Taxi flight". Flightmaster taxis only, never vehicles. Technical: New module SkuCore/taxi.lua. The route is captured, not guessed: hooksecurefunc on TakeTaxiNode fires while the taxi map is still open, the one window in which GetNumRoutes, TaxiGetNodeSlot and TaxiNodeName still answer at all. That makes the stop list exact, and every stop on it is genuinely reachable by this character. A proximity cursor advances it as the taxi flies over each node; the nearest-node search survives only as a labelled fallback for a /reload mid-flight, filtered by faction and discovery. Three client facts are recorded in the file header so they do not have to be derived again: the request always lands you at the NEXT flight point (as TAXI_CANCEL_DESCRIPTION says too), which is why the route cursor is the correct answer; IsPossessBarVisible reports false while PossessButton2 is shown - the reliable affordance is MainMenuBarVehicleLeaveButton, read with IsVisible, and it is up for the whole flight; and PLAYER_CONTROL_LOST fires while UnitOnTaxi is still false, so any teardown wired to it would wipe freshly captured state. Every entry point re-checks UnitOnTaxi - the same test Blizzard uses to pick TaxiRequestEarlyLanding over VehicleExit. Diagnostics: "taxi:" lines in SkuDebugLog and /skutaxi. Group invites are no longer declined silently Simple: Closing the Sku menu - for any reason at all: Escape out of the bags, opening another window, a handover into combat - declined a pending group invite, and the person inviting you saw a decline. For a sighted player nothing at all happens at that point; the dialog simply sits there for its 60 seconds. So this was a loss that only Sku users had. The invite is no longer answered when the menu closes; it just leaves the screen and is reachable again under "Local". Technical: StaticPopupDialogs["PARTY_INVITE"].OnHide calls DeclineGroup unless dialog.inviteAccepted is set - and OnHide does fire on a plain frame:Hide() (XML OnHide -> GameDialogMixin:OnHide -> StaticPopup_OnHide -> dialogInfo.OnHide). The Sku menu's OnHide runs exactly that StaticPopup1:Hide(). New is SkuCore:NeutralizePopupHide(frame), called immediately before it: for PARTY_INVITE it sets inviteAccepted so Blizzard's OnHide skips the decline, and marks the frame so our own hide hook can tell a neutralised hide from a real answer - plain fields on an insecure frame, no taint; OnShow resets inviteAccepted, so a later genuine show is unaffected. There is no "invite pending" query API on this client, so the cache is ours: seeded from PARTY_INVITE_REQUEST, dropped on PARTY_INVITE_CANCEL, on any non-neutralised hide of the dialog (a real accept, decline or timeout) and by its own deadline. For the record, every dialog with a destructive OnHide was audited: PARTY_INVITE (fixed here), LEVEL_GRANT_PROPOSED (DeclineLevelGrant), EQUIP_BIND and relatives (CancelPendingEquip - no skip flag, needs a different fix) and QUIT (CancelLogout, benign). Pending prompts stay reachable in the Local menu Simple: When you leave the Sku menu with Escape, an open dialog disappears from the screen - a summon, the release-spirit prompt, a resurrect offer, a ready check. Until now it was gone until the next /reload, even though it stayed valid on the server the whole time. Such prompts now appear as their own entry directly under "Local", named after the prompt; RIGHT or ENTER shows the real window again, and Sku descends into it so you operate the usual buttons. Technical: New module SkuCore/pendingPrompts.lua. The prompts are re-offered from STATE, not from the frame: summon via C_SummonInfo, resurrect via ResurrectGetOfferer, death release via GetReleaseTimeRemaining plus the self-res options from C_DeathInfo, ready check via GetReadyCheckTimeLeft/Status. A raw :Hide() only reaches StaticPopup_OnHide and never OnCancel, so nothing was ever declined or released - PLAYER_ENTERING_WORLD is the only place Blizzard re-shows DEATH and CONFIRM_SUMMON, hence "gone until /reload". While its own dialog is on screen the entry is suppressed, so it never doubles up with the popup-scrape node. Importantly these entries are PASSIVE: the "is anything open" predicate in CheckFrames does double duty - it keeps an open menu alive and it force-opens the menu via SlashFunc. Pending prompts feed only the first (tPendingOnly), otherwise a permanently reachable prompt would yank the menu open on every bag update; the same reasoning keeps them out of combatMenuHasWindow. The first form was nested, with its own action leaves - functional, but too much navigation. It is now a single flat node per prompt: kind="action" with dynamic=true, only so the key handler routes RIGHT into OnSelect at all, plus an OnSelect override that bypasses the generic descend machinery; the real buttons arrive through the normal scrape of the re-shown window. Trade: what is in the trade window is now announced live Simple: Right-clicking an item into the trade window was completely silent, and its entry in the bag menu stayed unchanged. Sku now says " in trade slot N" or "trade slot N cleared", including for your trade partner's side, which was previously neither visible nor audible. The lists "Your items" and "Partner's items" refresh by themselves; the "Refresh" entry is no longer needed for that. While an item sits in the trade, its bag entry carries an "in trade" prefix, spoken before the item name, and the moment the marker appears on the entry you are standing on Sku speaks just "in trade" - it does not read the whole item name at you again. And after a completed trade the item you gave away now reliably disappears from the bag list instead of only sometimes. Technical: The existing sell/bank machinery could not apply here: putting an item into the trade window does not remove it from the bag. The client only sets isLocked on the slot (Blizzard's own ContainerFrame reacts to ITEM_LOCK_CHANGED by merely desaturating the icon), so no BAG_UPDATE fires and there was nothing for the gated handlers to react to. Items leave the bags only when the trade COMPLETES - and that BAG_UPDATE races the CheckFrames in TRADE_CLOSED, while Sku.tBagPostAction had already expired 2.5s after the right-click. Losing that race was the "sometimes it stays in the list" symptom. New is Sku.tBagSettleWindow, a second, narrower gate in SkuCore:BAG_UPDATE_DELAYED, armed for 3s by TRADE_CLOSED, driving the SILENT SkuBagIdleRefresh - not the speaking confirm, since no cursor is anchored on the traded item - plus a 1s fallback for the reverse race. TRADE_PLAYER_ITEM_CHANGED and TRADE_TARGET_ITEM_CHANGED are registered for the first time, deduped per slot by item and count and gated on TradeFrame:IsVisible so the volleys at trade end stay quiet. Both share SkuCore:TradeMenuRefresh, a debounced quiet rebuild. The "in trade" marker is a prefix: in a bag list it follows the position number, in the "all items" list it is prepended after the sort (like the "new" prefix), so the transient marker never moves an item out of its alphabetical place. The announce hangs off the silent re-pin in SkuBagIdleRefresh, which compares the marker on the focused entry before and after the rebuild and speaks only when it appeared - self-deduping, since the follow-up refreshes see it on both sides. SkuCore:TradePartnerName is now the single source for the partner name, including for the existing trade-money announcement. Combat: Sku announces when a spell has been interrupted Simple: Under Hostile -> Casting, "Announce interrupts" now exists separately for your current target and for all enemies - exactly like the cast announcements themselves. That lets you hear kicks on your own target without hearing every kick in the raid. When you or your pet interrupt, you hear "You interrupted the spell" with the spell name; when someone in your group interrupts, "spell interrupted". Only the announcement for your current target is on by default, all enemies is yours to switch on; anyone who had switched the announcement off stays silent. Technical: SPELL_INTERRUPT from the combat log via the new dispatcher event SKU_SPELL_INTERRUPT. The former single outputInterrupts switch is replaced by yourTargetInterrupts and allEnemiesInterrupts; only yourTargetInterrupts is nil-filled from the old value, allEnemiesInterrupts starts off. With the global switch off, an interrupt is only announced when the destGUID of the interrupted cast is your current target; with both on it still announces once. "You" means a source affiliation of MINE, which covers your own pet (felhunter Spell Lock and the like). Combat: cast announcements can be limited to interruptible spells Simple: New setting "Only announce interruptible spells" (off by default) under Hostile -> Casting. With it on, Sku no longer reports casts that interrupting would not affect anyway. Technical: The notInterruptible return of UnitCastingInfo and UnitChannelInfo is dead data on the Anniversary 2.5.6 client - it came back nil on every cast while return 9 delivered the correct spellId; Blizzard's own castbar force-falses the flag for BC and older. Interruptibility therefore comes from a static list, new under SkuDB/uninterruptibleCasts.lua, imported from ClassicCastbars (MIT licence). It covers the old world; empty SkuTbcAdditions merge tables are ready for TBC findings of our own. Matching is by spellID, localized name, or npcID plus name. Soft targeting: "if no hard target is locked" now actually reaches the client Simple: The setting controlling whether soft targeting is used for interacting never did anything. The interact key did not care whether you had a target hard-locked - a corpse or a chair next to it grabbed the keypress anyway. That is fixed, and fixed in the client rather than in an emulation. Please note: these client settings are locked during combat. Sku therefore keeps them at rest in the state where the suppression is already in place if something jumps you and you only target it once the fight is on. Two corners remain: if you kill your target, the state persists until your next target change - during that moment nearby objects are not addressable (the corpse you have targeted still loots normally). And if you enter combat holding a non-attackable target, soft targeting stays on for that fight. That would need a write during combat and cannot be closed from an addon. Technical: All 32 SoftTarget CVars are still registered in 2.5.6 (scanned in WowClassic.exe) - so the client is not the reason. Both branches of the old if/else wrote SoftTargetMatchLocked = 0, identically dead code in v32.31 and in v41.04; the setting never left Lua. 41.05 then wrote 1, but collapsed option 2 into option 1, and SoftTargetWithLocked - the CVar that actually governs this - was never written by any Sku version. Measured on 2.5.6.68575, out of combat, corpse and live mob in range, interact key pressed with the mob hard-targeted: WithLocked 0 acts on the mob and ignores the corpse, 1 and 2 both loot the corpse. So only 0 suppresses soft targeting while a target is locked, and there is no "only when attackable" middle value. The option now maps onto that directly: 0 (always) -> WithLocked 2, MatchLocked 0; 1 (if no hard target) -> WithLocked 0, MatchLocked 1; 2 (no attackable target) -> MatchLocked 2, with WithLocked flipped between 0 and 2 on PLAYER_TARGET_CHANGED. The resting state is 0, because WithLocked only acts while a target is locked - resting there costs nothing and covers the one case the CVars could otherwise never reach. 2 is now written only for the one state that genuinely needs soft targeting WITH a target locked: a target you cannot attack - a corpse you are looting, an NPC you are talking to, a player you are following. Removed: the SkuMob ticker that rewrote SoftTargetInteract four times a second, its duplicate in PLAYER_TARGET_CHANGED and the interactTempDisabled shadow state - that emulation could not work at all, because the CVars are protected in combat. tSetSoftTargetCVar now verifies every write by reading the CVar back (numeric compare, so a normalised "15.000000" is not a false failure) and re-arms the PLAYER_REGEN_ENABLED replay when one is refused, instead of caching a value that never landed. An in-combat write logs "softTarget refused ... {want=3, got=0}" and raises ADDON_ACTION_BLOCKED. Menu: ENTER kept working into the invisible menu after it closed Simple: After selecting a waypoint - and in similar cases - the menu closed correctly, but every further press of ENTER still clicked around in the invisible menu: the navigation sound played, the selection ran again ("new navigation"), and ENTER no longer opened the chat window. On top of that the menu could get stuck open if a movement key went down in the same instant: the waypoint was set, but the menu stayed open and kept the keys captured. Both are fixed. Technical: Two separate causes. First, OnPostSelect continues after the action that closed the menu: it re-pointed currentMenuPosition at the selectTarget - overwriting the close's root reset - and fired its OnEnter; the generic OnEnter then unconditionally re-armed the SKU_KEY_MENULEFTCLICK override bindings on the secure button. Override bindings on a HIDDEN frame are fully active, so ENTER was resurrected right after OnHide had cleared it. Now the generic OnEnter only arms the click keys while the secure button is shown, and OnPostSelect stops before the trailing re-focus when the menu is no longer open (the headless combat menu is exempt via combatMenuActive). Second, CloseMenu routes through the same toggle handler as opening, whose two moving-defer gates silently aborted the close - one reads the live movement flags, the other the cached SkuCore.isMoving, which is why one path passed and the other aborted. Close requests now bypass those gates; Hide and ClearOverrideBindings are safe while moving, and a successful close reaches the openMenuAfter resets again. Belt and braces, the reopen ticker disarms flags that can never fire anyway and re-runs the navigation setup only when the menu is visibly open but key-dead (new: SkuOptions.menuNavKeysBound). Collision warning during automatic walk-to-target Simple: The warning that you are walking into something only armed while you held a movement key yourself. With the game's own "Interact With Target", the engine walks you there without any key being held - and there you could wedge on an obstacle without noticing. Those walks are now covered too. Technical: A new EngineMoving flag from PLAYER_STARTED_MOVING and PLAYER_STOPPED_MOVING arms the coordinate-based warning for engine-driven walks as well. PLAYER_STOPPED_MOVING does not fire while you push against a wall - precisely the case the warning is for (proven by the stale-AutoRun clear). Pure turning, falling and autofollow are excluded; follow has its own collision warning. Fewer errors in instances and raids Simple: Two sources of errors that produced constant Lua errors inside instances are fixed - noticeable as error messages that no longer appear and calmer boss fights. Technical: First, UnitPosition("player") returns nil inside instances, raids and battlegrounds, so SkuNav:Distance returned nil and GetUnsortedAvailableQuestsTable crashed on concatenation on every QUEST_LOG_UPDATE (24 times in a single raid session). The existing 99999-metre convention now applies: the quest stays listed and sorts last. Second, ClearOverrideBindings and SetOverrideBindingClick are combat-protected, and the surrounding pcall does not suppress the ADDON_ACTION_BLOCKED event (84 times in one fight, plus BugGrabber execution-limit warnings). Applying the AtlasLoot key binding is now skipped during combat and retried once via PLAYER_REGEN_ENABLED. "Temporarily stop following while casting" is active again for a test Simple: This setting was in the menu, but its function had been switched off since the upstream v41.06 state. It is enabled again, off by default, so it can be established whether this client permits resuming follow automatically at all. If you switch it on, expect that following may not come back by itself after a cast. Technical: The bodies of UnfollowOnCast and FollowOnCast were commented out, for a reason that was never recorded. They now answer one question: does this client execute FollowUnit from a server-event context, i.e. without a hardware event, or is it silently gated? The proof channel is the AUTOFOLLOW_END/BEGIN voice output and "followcast" lines in SkuDebugLog, including a state check 0.5s after the call. Fixed along the way: in the CHANNEL_START/STOP handlers unitTarget and aUnitTarget did not match up, so the unfollow and refollow never fired for channelled spells. Mail: after sending a letter Sku read the typed text back later Simple: A while after a letter had been sent, Sku started repeating the characters that had been typed - sometimes long after the mailbox was closed. Two independent bugs were behind it. First, once you had opened a mailbox it counted as open internally until the next /reload, so every piece of mail arriving later rebuilt the menu and announced the current entry again - wherever in the menu you happened to be. If you were standing on a letter field, that entry was the text you had just typed. Second, the per-character readout while typing appended every character to the end of the speech queue: if you type faster than the voice speaks, that builds a backlog, and closing the input field did not end it - it simply kept playing. Now the readout follows your typing: if it falls too far behind, what has not been spoken yet is dropped instead of trailing along. "Sent" and a field confirmation come immediately, no longer behind everything you typed. And an inbox update only has an effect while the mailbox is genuinely open and you are inside the Mail branch of the menu. Two small things in the same place: the paste field ran its OK action n times on the nth open, and both Sku input fields now reliably release the keyboard when they close. Technical: MailboxOpenFlag was set in MAIL_SHOW but never cleared anywhere; MAIL_CLOSED now clears it. MAIL_INBOX_UPDATE does not only arrive at an open mailbox - the server also sends it when mail arrives later - and it ran unchecked into the generic OnUpdate (SkuZOptions/templates.lua). That is not a quiet refresh: it discards the current level's children, rebuilds them and calls VocalizeCurrentMenuName. On top of the flag we now check MailFrame:IsVisible() and walk the parent chain up to the Local contributor node L["Mail"] - only there is a rebuild a refresh rather than an intrusion. The forced SlashFunc call without a menu position survives only for the genuine open-mailbox case; a background event must never force the menu open. The typing readout from v42.08 appends rather than overwrites on purpose, otherwise fast typing swallows characters - but it had no ceiling. At roughly 0.4s per character against four to five keystrokes per second the queue grows without bound, and the dequeue pushes a backlog into C_VoiceChat.SpeakText at about 100 entries per second, where Sku can no longer catch up with it. Hence two new methods in SkuVoice-1.0, GetBttsQueueDepth and TrimBttsQueue(aKeep): only what is still WAITING in Sku's own queue is dropped, never anything already handed over - which is why it cannot cut a word in half, and why it is legitimate even with "never reset the queues", a setting that is about cutting off speech in progress. The cap is three waiting characters. The backlog is dropped before the OK callback runs, and both MAIL_SEND_SUCCESS and the field confirmation in MailEditor now speak overwriting instead of appending. EditBoxPasteShow attached its OnEnterPressed via HookScript on EVERY call - HookScript does not replace, it chains - so the OK callback ran n times on the nth open; it now lives in the create-once branch. Both input fields call ClearFocus when they hide, because the field is a grandchild of the hidden frame and the keyboard focus would otherwise stay on an invisible edit box. Item level: every item was announced with the same number Simple: The item level in item descriptions was wrong for as long as it has existed. It did not belong to the item you were reading at all - it belonged to whatever item had last been looked up somewhere in Sku, usually hours earlier, and it stayed that way for the whole session. That is why it read the same number on every item, and why it was so often a 1: a key or a quest item, the kind of thing that gets looked up early and really is item level 1. A /reload did not help, because the wrong number was not stale data that gets refreshed - it was simply the wrong item being asked. The same wrong item also decided the quality that is appended to item names. Both are correct now. Item level is also only announced for gear you can actually wear: keys, quest items, reagents and consumables are all item level 1 by definition, so that line said nothing about them and is gone. It has also disappeared from places it never belonged, such as buff descriptions and the resistances on the character screen. Technical: A tooltip's GetItem() is a sticky cache, not a description of what the frame renders: ClearLines() does not reset it, and neither does a SetX() that fails to populate - an uncached item, bank container -1, or any spell, aura or stat tooltip that has no item at all. SkuScanningTooltip is created once at PLAYER_LOGIN and never re-owned, so its GetItem() keeps answering with the last item that ever resolved through it. On top of that, TooltipLines_helper read the link from SkuScanningTooltip no matter whose regions it had been handed, and most callers hand it GameTooltip's. Two smaller faults compounded it: GetSpell()'s second return, which is not a link, was assigned into ItemLink and passed to GetDetailedItemLevelInfo; and an uncached item yields an EMPTY link, which is truthy in Lua and sailed straight through the "if ItemLink then" guard. Resolution now lives in SkuUtil - TooltipFirstLine, TooltipItemLink, ItemQualityString, ItemLevel - and trusts GetItem() only when the name it reports matches the tooltip's rendered first line, which is the one piece of ground truth available. The three duplicated blocks in LocalMenu and utilities collapse into it; tScanTooltipRegions resolves link, quality and item level itself and returns the validated link, so callers downstream get the one the text describes. The equippable gate reads equipLoc from GetItemInfo and falls back to C_Item.GetItemInventoryTypeByID while the item cache is still cold. French: Sku is now available in French Simple: Sku can now be used in French - tested on a French client. Around 95 percent of the menu and announcement texts are translated (3063 of 3220 entries), and everything else falls back to English automatically, so there is never a gap or an empty entry. Two things that were still missing have been added: the route and waypoint names of the translation pipeline have their own French table with 153 entries, whose place names are imported from a real French client capture rather than guessed, and all 158 places where text is held bilingually in the code now carry a French version as well. The GAME names - creatures, quests, items, objects - are imported wholesale from Questie, so they are Blizzard's own French strings rather than machine translations and match what a French client displays, which is what keeps route and waypoint names searchable. Zone names come straight from the client. Voice output still exists in German and English only; every other client language uses the English audio files. For German and English players nothing changes in the text - but memory use does: Sku now builds only its own language's name tables, plus English as a fallback, instead of always building all of them. Technical: Sku.Locs is now an ORDERED three-locale list, and the packed route names in SkuNav:LoadDefaultMapData are split positionally against it instead of by a string.match with a greedy "(.+)" on either side of the separator. That old parse was not merely two-locale-only: ".+" is greedy, so with three fields "a", "b" and "c" the first two would have arrived as a single English field and only the last as the German one. Files with fewer fields than locales stay valid and fall back to enUS. Sku.LocAudio splits the AUDIO locale from the data locale, and two pre-existing bugs surfaced and were fixed on the way: SkuAudioFileIndexIntegrated[Sku.Loc] was unguarded and nil-indexed on any client language without its own sub-table, and GetAudiodata had no case fallback while the enUS index stores its identifiers lowercased and callers pass them capitalised - which is why the inside/outside zone announcement has been silently dead on English clients. ChunkLoader now has a locale gate: it previously built EVERY registered chunk regardless of client language, so a German client also materialised the full English name tables and vice versa. The rule is now "active locale plus enUS", with objectLookup.deDE structurally exempt because the English objectLookup is DERIVED from its key set - gating it naively would have left English clients with no object names at all. The four hardcoded .deDE merges became SkuDBMergeLocales, which fixes a latent bug along with it: a French client would have got base TBC names but silently no WotLK names. After merging, gaps in the active locale are filled from enUS (Questie has no French name for around 2700 ids Sku knows); deliberately a build step rather than an __index metatable, because pairs() does not traverse __index and several consumers ENUMERATE these tables rather than index them - a metatable would have fixed the lookups and quietly left the menus short. deDE and enUS are excluded from the backfill so the German build stays byte-identical (verified, 37 of 37 datasets). The locale file itself is Sku/locales/frFR.lua, generated from part files: untranslated entries keep their English value, so the table is always complete and valid Lua, and AceLocale returns nil for de/en anyway - there the file costs a parse and nothing else. Leading and trailing spaces are load-bearing in that table but do not survive a TSV round-trip, so they are re-applied from each English original during assembly, and the validator checks the FINAL string for placeholder count, edge whitespace and semicolon count. For testing without a second client there is /skudebug locale , which forces the DATA locale and persists; it applies on ADDON_LOADED rather than PLAYER_LOGIN, because the deferred route build (measured at 1.87s against 2.01s for PLAYER_LOGIN) already decides which name fields get materialised. Three hard errors on a forced-French client are fixed: maps.lua is a plain, unchunked table and carried AreaName_lang/Name_lang for deDE and enUS only - the missing locale is now filled once at ADDON_LOADED from C_Map (GetAreaInfo and GetMapInfo; 2819 entries, 1672 of them from the client), giving real Blizzard names with no zone-name data to ship; SkuDB.SpellDataTBC nests its locales INSIDE each entry, so nothing ever localized them and eleven sites threw on frFR - the active locale now points at the English sub-table by reference (no copy, no extra strings), which is enough for names used mostly as aura-matching keys; and SkuDB.objectResourceNames is keyed by object name per locale and was read unguarded during the waypoint cache build - an English fallback would have been wrong there (it would never match and would silently turn every ore and herb into an ordinary waypoint), so the set is derived through the object ids instead. The whole French branch has since been played through on a French client and works: menus, names and routes come up in French, and where no translation exists the English fallback steps in without a gap. The route table routeOverrides_frFR.lua and the third argument on every Sku.deEn site are part of that. Since zhCN and ruRU run with Sku.Loc = enUS, the same pass covers them too. Download page: the heading named a stale version Simple: On the download page the main addon's heading still read "Version 42.06" while the link underneath correctly pointed at 42.10. Since screen reader users navigate by heading, the first thing announced in that section was the wrong version - it read as if the page were serving an outdated Sku. Heading and link now agree. Technical: release.ps1 rewrote the href and the link text on every release but never the heading, so it had been stuck since 42.06 (2026-07-17) across four releases. The heading is now part of Set-DocsSkuLink and cannot drift again. The linked zip was correct the whole time (verified: Sku-42.10.zip contains "## Version: 42.10"). Beacons: a separate sound for the last waypoint of a route Simple: There is now a third beacon sound next to the ones for small and large beacons: the sound for the last beacon of a route. It plays for the final waypoint of a meta route only, so you can hear whether the point you are walking towards is the destination or just another leg. The setting sits under Monitor -> Beacon, directly below the small and large beacon sounds, and it defaults to Beacon 3. Following a single waypoint is unchanged - that still uses the small or large sound according to the waypoint's size. Technical: New profile setting SkuNav beaconSoundSetLast (default "Beacon 3"), declared in the SkuNav schema and added to the Beacon submenu's explicit node whitelist in SkuCore/aq.lua - that list is not the whole args table, so a new option node has to be named there as well or it never appears in the menu. GetBeaconSoundSetName takes a second argument and returns the last-beacon set when it is true, leaving the narrow/wide behaviour and the one-argument callers (the panic beacon, the TomTom hook) untouched. The new SkuNav:IsLastMetaRouteWp compares the waypoint NAME against the last entry of metapathFollowingMetapaths[target].pathWps rather than reading metapathFollowingCurrentWp, because SelectWP runs before that index is advanced - an index test would be off by one. Going by name also covers skipping ahead with MoveToWp and the reverse-route start, which all set the metapath state before calling SelectWP. Better defaults for new players Simple: Setting Sku up fresh now starts from a usable configuration instead of leaving you to find the important bits first. A few keys come pre-assigned, the chat is split up sensibly, and some announcements are on from the start - broadly the way experienced players set it up anyway. Every one of these defaults still sits in its usual place in the menu and can be changed as always. Existing profiles and characters are untouched: a default only applies where nothing was ever saved. To pick up the new keys, use Sku key bindings -> reset everything - note that this really does reset ALL keys, including the ones you assigned yourself. Technical: Pure default changes in SkuZOptions/SkuKeyBinds.lua (skuDefaultKeyBindings), SkuChat/Core.lua and Options.lua (ChatFrameDefaultTabs plus the default tab set), SkuCore/Options.lua (SkuCore.defaults) and SkuCore/aqCombat.lua (aqCombatOnLogin). Stored values survive, because every site only writes on nil and AceDB fills defaults into gaps only. Two real behaviour changes come with it: the binder in SkuNav:CreateSkuNavMain only ever applied .key, so a SECOND key assigned by the user did nothing at all for every navigation action (SkuKeyBindsMatchKey had understood it all along) - it now goes through SkuKeyBindsGetKeys and binds both. And because SkuChat emits a message once per tab that has the type enabled, a message type with a tab of its own has to be muted in every other tab or it gets read out twice. Auction house: buying several of the same item works again Simple: Buying more than one auction of the same item bought the first one and then stopped with "internal auction error"; the second purchase was never offered again. Both are fixed - the follow-up purchases run through, and the server's spurious error message is no longer read out while a search is running. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.10 Navigation: waypoints work again in areas without a map of their own Simple: In the Deeprun Tram, Shift+F9 and Shift+F10 reported no waypoints at all, even though the area is fully mapped and linked. The same family of causes hit places on the edge of instances - the Uldaman entrance, the Karazhan forecourt and similar spots in caves and tunnels: there too both lists simply came up empty, without any error. That is fixed; the waypoint list and route guidance work in those places again. Please note: in such spots the list can also contain waypoints of the area above or beside you. If you are standing in a cave underneath another zone, check before selecting whether the waypoint really belongs to your surroundings - otherwise the guidance runs into the ceiling. Inside the instances themselves (Uldaman, Karazhan) there are still no waypoints. Nothing is mapped in there, and that stays as it is. Technical: Two separate causes, both in SkuNav/Geo.lua. First, areaId 2257 (Deeprun Tram) has no ExternalMapID row, so GetUiMapIdFromAreaId returns nil for it and the strict name-plus-uiMap loop in GetCurrentAreaId can never match such areas. Up to v41 that loop compared against a never-declared global and matched them by accident; once the variable was declared properly, GetCurrentAreaId returned nil there, and with a nil continent both ListWaypoints2 and GetAllLinkedWPsInRangeToCoords bail out silently. The name match is now redone deliberately as a second pass, restricted to areas without a uiMap - precisely the set the strict loop could never have matched, so no normal zone can be reinterpreted by it. Second, in caves, tunnels and in front of instance doors the client often returns the continent uiMap (1415/1414). The recovery for that in GetBestMapForUnit matched the SUBZONE name against InternalAreaTable - but all 285 instance rows are commented out there, so a subzone name like "Uldaman" matches nothing, the continent id survives, GetAreaIdFromUiMapId turns it into AreaId -1, and GetCurrentAreaId has nothing left to try (there is no row named "Eastern Kingdoms"). There is now a fallback to GetRealZoneText, the zone name, which always has a row; it only runs when the existing paths produced nothing usable. Also, /skuzoneprobe (/szp) now reports both area resolvers side by side, the cache split by waypoint type, the size of the per-continent bucket and what the two quick lists return on the spot. A bug in it was fixed on the way: the probe read WaypointCache as a global although it is a file-local of SkuNav/Core.lua, so the "survived in cache" number was always 0 and falsely reported that everything had been pruned. Combat: counting the enemies in combat is much steadier and more accurate Simple: The enemy-count announcement kept going up and down although nothing about the situation had changed - a boss fight against three enemies produced fourteen announcements in barely a minute, most of them wrong. Four causes are fixed: First, an enemy that did nothing for six seconds - during a fear, in a cast phase, while nobody happened to be attacking it - was dropped from the count and added again on its next swing. Sku now recognises from many more signs that an enemy is still in the fight. Enemies that really do leash or vanish still drop out of the number as before. Second, after a kill you correctly heard "0", then wrongly "1" a moment later, then "0" again. Dead enemies can no longer come back. Third, the number fell to 0 in the middle of a fight and climbed back up when you personally were briefly out of combat - because a fear ran you off, or your last attacker died. In the group modes ("enemies attacking the party or you") it now depends on whether anyone in the group is still fighting. Fourth, friendly NPCs were counted as enemies as soon as a group member healed or buffed them - escort NPCs in instances, for instance. They no longer count. Technical: All in SkuCore/aqCombat.lua. (1) lastUpdate is now refreshed by any combat-log line naming a tracked enemy as source OR destination, and in addition by mere presence in a polled unit slot - the latter only while UnitAffectingCombat is true for that slot. That condition is what preserves the purpose of the 6-second sweep: a leashing enemy stays visible for a while but loses its combat flag, so it is not refreshed and still drops out. (2) A death is recorded in tDeadGuids, the entry is removed from the tPendingAdds coalescing window, and aqCombat_CREATURE_ADDED_TO_COMBAT refuses dead GUIDs - previously "false or {}" turned the death marker back into a live entry. The mark is only lifted if a unit token reports the enemy alive again. (3) PLAYER_REGEN_ENABLED (= "YOU left combat") wiped the whole threatTable; in modes 2 and 3 that is now deferred while any group member is UnitAffectingCombat, with a 2-second re-check. Mode 4 ("attacking you") still cleans up immediately. (4) Hostility is decided from the combat-log reaction flags (COMBATLOG_OBJECT_REACTION_*) and from UnitReaction <= 4, asked once per enemy and never re-judged - deliberately NOT via UnitCanAttack, because encounters flag bosses non-attackable or immune mid-fight and they would drop out of the count exactly then. Auction house: auctions can be posted again without opening your bags first Simple: Posting an auction sometimes did nothing at all - the sell slot stayed empty and the auction silently never went up. It worked for anyone who had visited a mailbox, a vendor or the bank beforehand, because those windows open every bag automatically. That is why the fault looked random and only hit some sessions. Posting no longer needs your bags to have been opened. While at it, the item is now looked up in your bags at the moment you post: if the stack has moved since the menu was built, the right item still goes up. Locked and soulbound copies of the same item ID are skipped instead of letting the post fail silently. Technical: SkuCore/auctionHouse.lua. The post path dragged the item into the slot through the bag FRAME's script: _G["ContainerFrame(bag+1)Item(numSlots-slot+1)"]:GetScript("OnDragStart"). Those buttons only exist once a bag has actually been rendered, and the ContainerFrame[N] <-> bag ID mapping is assigned at OPEN time - the hardcoded N = bag + 1 only holds when every bag was opened in order. Without that the drag grabbed nothing and PostAuction silently no-op'd. This was a regression of the 2026-06-25 fix: the v42 sell rework restored "the original v41.06 post path" and brought the frame drag back with it. Reading and picking up now go through the container API - the same route bag sorting and socketing already take: new _ASBagNumSlots, _ASBagSlotInfo, _ASFindBagSlot and _ASPickupBagItem helpers, each dual-path over C_Container and the classic globals. Bag and slot are resolved fresh by itemId on click (the slot noted at menu build is only a hint now and gets re-verified), then PickupContainerItem followed by ClickAuctionSellItemButton; ClearCursor in the abort branch so failed staging cannot leave the item on the cursor. The _G[containerFrameName].info.id refinement in the price preset is gone too: it returned nothing without rendered bags, and the itemId from the container scan is authoritative anyway. Auction house: the lowest price is no longer reported as 0 copper by mistake Simple: For some items Sku announced a lowest buyout of "0 copper" even though several auctions with perfectly ordinary prices were up - searching for the same item by hand found the correct prices straight away. From the same cause the announced average was too low, the number of data points too high, and the price suggested when selling could sit at 0. For other items you got the opposite, "no buyout data available", although buyouts existed. Both are fixed. Current price data is correct again after your next full scan; stored history heals per item as each is scanned again, and until then old zero prices are treated as "no data" rather than read out. Technical: SkuCore/auctionHouse.lua. Two faults working together. First, in AuctionBuildPriceData every auction row always contributed TWO data points, a bid price and a buyout price. Auctions without a buyout (buyoutPrice == 0, the normal case in the TBC auction house) were written into the buyout list as a plain 0. Second, the condition in AuctionUpdateAuctionDBHistory - "not tLow or (tPrice > 0 and tPrice < tLow)" - bound the tPrice > 0 guard to the SECOND branch only. So the first price the loop saw took the low whatever its value, including 0; after that no price was ever smaller than 0, so the low stayed 0. That is why it only struck sometimes - namely when the first auction of that item in the scan happened to have no buyout. The zeros also dragged the median down, and if more than half the auctions had no buyout the median became 0, at which point the outlier filter tPrice < median * 10 rejected every real price as well - that was the "no data despite existing buyouts" case. Now only real prices > 0 become data points at all (an auction without a buyout simply yields no buyout data point), tPrice > 0 gates both branches, and a median <= 0 bypasses the outlier filter instead of discarding everything. When reading in AuctionHouseGetAuctionPriceHistoryData a stored low <= 0 counts as "no data" - needed because already saved AuctionDBHistory can still hold zero lows from the old calculation until that item is scanned again. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.09 Classic Era: game key bindings can now be set again Simple: On the Classic Era client it was possible to clear a game key binding, but never to assign a new one - the menu simply reported the key as "nothing" afterwards. Assigning keys now works exactly as it does on the Anniversary client, for both the first and the second key of a command. Technical: SkuCore's binding helpers called SetBinding(key, command, 1) on the non-TBC branch. The third bindingMode argument is rejected by Era 1.15, so the call returned false and bound nothing, while the one-argument unbind form kept working - hence "unbinding works, binding does not". Sku.isTBC is true for every client from interface 20505 upwards, so that branch was only ever reached on Era. All five call sites (SetBinding, SetBinding2, DeleteBinding2 and both keys in ResetBindings) now use the two-argument form, which is correct on both clients. Classic Era: joining the Sku chat channel no longer fails Simple: Turning on "Join Sku Chat Channel" on Classic Era did not work properly - the channel was never added to your chat window. It now joins correctly. Technical: SkuChat:JoinOrLeaveSkuChatChannel branched on Sku.isTBC and called a global ChatFrame_AddChannel, which does not exist in the Anniversary interface code at all. The real API is ChatFrameMixin:AddChannel, which loads on every flavour including Era, so the branch was removed. "Join Sku Chat Channel" is findable again Simple: The setting had been moved to Settings -> Speech output, where it sat next to the text-to-speech options. Because a second menu of the same name (under Audio) shows only the three TTS sliders, the setting appeared to be missing entirely. It is back where it belongs, under Sku Chat -> Options. Technical: Rendered with keyPrefix "" - the same prefix the speech-output menu used - so existing saved values carry over untouched. The remaining relocated SkuChat options (TTS voice, speed and volume, never reset queues, all voice output via Blizzard TTS, do not read out emojis) stay under Speech output. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.08 With thanks to Naxedim Almost every improvement in this version was inspired by the companion add-ons Naxedim built and shared at https://naxedim.github.io/sku-addons/ - SkuMoneyReplacement, SkuMailboxReplacement, SkuAuctionHouseReplacement and SkuMinimapScannerFR. They have been ported natively into Sku here, using Sku's own menu system. Full credit for the ideas and the working solutions goes to Naxedim. Mail: attachments, sending and large amounts Simple: Attaching an item to a letter is now reliable - the item really lands on the mail and never gets stuck on your cursor. A new "Attached items" entry lets you review what is attached and press Enter to send an item back to your bags. When a mail cannot be sent (for example an unknown recipient) you now hear "Send failed" with the reason, and your recipient, subject and text are kept so you can just fix the name and send again. You can now also attach more than 999 gold: each coin list has a new "Enter amount" entry at the top where you type any value. Technical: The attach path uses an explicit free send-slot plus GetSendMailItem verification with ClearCursor on failure (the old OnDropAny path is a no-op on Anniversary). MAIL_FAILED is now handled (announce + keep draft); the draft is cleared only on MAIL_SEND_SUCCESS. Coin lists gained an EditBox entry; the attachment-review submenu lists GetSendMailItem and un-attaches via ClickSendMailItemButton. Text fields: every character is spoken as you type and edit Simple: When you type into any Sku text field (mail, auction search, amounts, equipment-set names and so on) you now hear each character as you type it and each one you delete. The arrow keys read the character at the cursor (Ctrl + arrow reads the whole word), Up/Down read the whole text, and Escape says "Cancelled". Technical: SkuOptions:EditBoxShow gained OnChar/OnKeyDown reading (UTF-8 safe). Typed characters queue so fast typing on slow voices speaks every character; cursor reads use overwrite. Plain arrows are free (menu navigation is on Shift-arrows), so no binding suspension is needed. Auction house: add-on tooltip info, no more stuck keys, item names in any language Simple: Item tooltips in the auction house now also show information added by other add-ons (like Pawn upgrade scores) as an extra "Add-on info" section at the end, without changing Sku's own tooltip. Closing the auction house no longer leaves Enter and Escape stuck (Escape opens the game menu again as it should). In the strategy-buy item picker you can now type the item name in your own language. Technical: AuctionBuildItemTooltip drives the real GameTooltip once (invisibly) to capture third-party OnTooltipSetItem lines and appends the non-base lines as a section (SkuScanningTooltip is not re-owned). On menu close, override bindings on the buy binder and both menu buttons are cleared in several passes, guarded to menu-closed / out-of-combat. Strategy-buy gained a free-text item entry (Sku only ships German/English name tables). Full-scan ingest raised 250 -> 400 rows/frame. Guild bank: read and move gold Simple: The guild bank menu has a new "Guild bank money" entry: read how much gold is in the bank, how much you can still withdraw today, and how much you carry, and deposit or withdraw gold by typing an amount. Deposits and withdrawals are checked first (enough gold, daily limit) and confirmed by voice. Technical: Build_GuildBankFrame's money TODO is filled with raw gossip entries; deposit/withdraw run GetGuildBankWithdrawMoney-limited checks and announce the before/after money difference. Trade: the trade window money is read out Simple: When you trade, Sku now tells you when the other player puts gold into the trade, changes it or takes it back, and reads your own offer as you set it. (Adding gold to a trade from an add-on is blocked by Blizzard, so this is the reading side only.) Technical: TRADE_MONEY_CHANGED is handled via the dispatcher; own and partner money are compared against last-known values and announced with the partner name. Resource scanner: native French node names Simple: The minimap and ground resource scanner now knows the French names of ore veins, herbs, gas clouds and chests, so it works natively on a French game client. Technical: frFR resource names were added to SkuCore.RessourceTypes, and frFR to Sku.LocsPartly so Sku.LocP stays frFR on a French client, with an enUS safety fallback. Navigation: a flightmaster flight cancels an active route Simple: When you take a flight from a flightmaster while a route is being followed, the route is now cancelled automatically - it makes no sense to keep it while the taxi flies you elsewhere. Flying yourself on a mount or riding a flying vehicle does not cancel the route. Technical: The taxi-started branch (PLAYER_CONTROL_LOST followed by PLAYER_MOUNT_DISPLAY_CHANGED) now calls SkuNav:CancelNavigationSilent, gated on UnitOnTaxi("player") so it fires only for flightmaster flights - self-flying never fires PLAYER_CONTROL_LOST, and in a flying vehicle UnitOnTaxi stays false. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.07 AddOn settings: read and change other addons' options Simple: A new "AddOn settings" entry in the Addons menu (and the "AddOns" button in the Escape game menu) lets you read and change other addons' settings with the screen reader - Questie, Deadly Boss Mods, AtlasLoot, Extended Character Stats and more. Their options show up as normal Sku menus: on/off toggles, choice lists, sliders and text fields. Can be turned off in the Features menu. Technical: SkuCore/addonOptions.lua walks any addon's AceConfig options table via the process-global AceConfigRegistry (IterateOptionsTables / GetOptionsTable) with AceConfig inheritance semantics, rendered with the same menu shapes as gameOptions.lua. Curated top-level list; load-on- demand config addons are loaded when opened. Deadly Boss Mods has its own adapters: per-boss-mod options from DBM.Mods and the core options via a DBM-GUI widget walk. Distance checks: bands stay put and are ready quickly after login Simple: The distance announcements (the "Entfernung" range checks) had a bug where range bands you had configured could silently switch themselves off and the list could come up empty. Fixed: your bands stay, the list fills in reliably, and newly available bands are announced. After a fresh login the bands are also ready much faster than before, and the 100-yard band works again on TBC. Heads-up: you will now hear more distance callouts than you are used to, because every band that becomes available is announced - silence any you do not want under Monitor -> Entfernung. Technical: RangeCheck no longer prunes saved bands against transient LibRangeCheck availability; the menu populates synchronously on enable with timed silent retries and refreshes from the live library. LibRangeCheck-3.0 was updated v31 -> v35 (spell bands refresh while leveling via LEARNED_SPELL_IN_TAB). A parallel item pre-warm at activate fills item bands in a burst on a cold client cache instead of a serial per-item crawl. Five DM-tribute permanent elixirs were added to the TBC 100-yard tables so the 100 band forms. A combat guard skips non-attackable targets whose interact-distance checkers return wrong values in combat. Focus: game focus changes announced by voice Simple: Setting or clearing the game's focus target is now announced by voice ("Focus set to " / "Focus removed"). The eight Sku focus keybinds and their menu group are renamed "Sku Focus" so they read as clearly distinct from the game's own focus. Setting a Sku focus no longer speaks its confirmation twice. Technical: PLAYER_FOCUS_CHANGED is routed through Blizzard TTS (engine 2, the user's global voice) and registered with the SkuFocus module toggle. The "set" keybind now registers AnyDown only (was AnyUp+AnyDown, which fired on both key edges). Menu: stray sound on open/close removed Simple: Opening or closing the menu sometimes played an extra "off" ping left over from an internal cleanup. Now only the menu swoosh plays; every other action keeps its usual sound. Technical: SkuTTS:Hide gained a silent flag that the menu open/close toggle uses to suppress the sound-off2 ping from the defensive hide of a leftover reading frame. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.06 Enchanting: crash when opening the window fixed Simple: Opening the enchanting window could throw an error. Fixed. Technical: The craft menu items added in 42.05 (filter and tooltip) are now fully pcall-guarded, so unexpected API behavior on this backport no longer crashes the menu. Auction house: clearer announcement on scan start Simple: Starting the auction house scan now says "Auction scan started, please wait". ------------------------------------------------------------------------------------------------------- Changes in Sku v42.05 Professions: recipe tooltip via Shift+Arrow-down (all professions) Simple: With the focus on a recipe in the list, Shift+Arrow-down now reads a tooltip: the result name, the reagents per line as "have slash required", and the finished item's tooltip. It shows the FOCUSED recipe, even if a different one is selected. Works for all normal professions and for Enchanting. The existing tooltip on the selected recipe stays. Technical: Each recipe row carries its real index (button GetID); the tooltip is built from GetTradeSkillReagentInfo / GetCraftReagentInfo and the result item link by that index, without changing the selection. Professions: "materials available" filter (all professions) Simple: A toggle at the top of the profession window. Enter switches it on/off and says the state. When on, only makeable recipes stay in the list. Default off, remembered per profession and character. Beast training gets no filter (no materials there). Technical: Normal professions via Blizzard's TradeSkillOnlyShowMakeable; Enchanting filters itself via GetCraftInfo.numAvailable. Aura sets: available account-wide Simple: Created and received aura sets are now available on all characters of the account, no longer just one. Technical: Set storage moved from char to global scope; the login reset of the global SkuAuras table now clears only the debug log and keeps the sets. Aura sets: rename and activate Simple: A set's submenu now also has Rename and Activate. Activate replaces the current auras with the set's and asks first (Enter confirms, Escape cancels). Aura menu cleaned up Simple: The dead "import aura sets" entry is gone. The old set management with the three test sets is removed. The share entry is now called "Set management". Auras: crash when creating a new aura fixed Simple: Creating a new aura threw an error. Fixed. Dual talent specialization: switching specs without reload Simple: After switching the active spec the talent tree is shown correctly right away; a /reload is no longer needed. Technical: On ACTIVE_TALENT_GROUP_CHANGED the Blizzard talent frame is pulled to the new active group and redrawn before Sku builds its menu. Talents: correct tab names in the inactive specialization Simple: In the inactive specialization the talent trees showed as "tab 201", "281" and similar for some classes instead of their names. The real tree names are shown now. Technical: Name taken from the Blizzard tab button PlayerTalentFrameTab instead of the group-parametrized API value, with a numeric fallback. Trainer: price in the confirmation dialog Simple: When resetting talent points, buying dual talent specialization, and on other gold confirmations, a "Cost" line with the gold amount now shows at the top. Technical: The StaticPopup reader now includes StaticPopup1MoneyFrame.staticMoney as the first child entry (via SkuGetCoinText). Questie: remote-control chat announcements Simple: Under Addons there is now Questie (when Questie is loaded). There you can set the chat-announcement channel (Off / Group / Raid / Both) and toggle each individually: quest accepted, abandoned, progress, completed, quest item. Technical: Sets Questie.db.profile.questAnnounce* directly; Questie persists itself. Pet: unspent training points are shown Simple: The overview page always showed 0 for the pet's training points. It now shows the real number. Technical: GetPetTrainingPoints instead of UnitCharacterPoints("pet") (TBC has no pet talent points). Friends list: "Remove" moved to the bottom Simple: In a friend's submenu, "Remove" is now the last entry instead of near the top. Monitor: "notice on pet starving" placed correctly Simple: The entry sat one level too deep. It is now under Monitor, Health and status, Pet, next to Health. Mail: keyring items can be attached Simple: When composing mail, "add item" now also shows keyring items (e.g. a non-soulbound silver skeleton key) and lets you send them. Soulbound keys stay hidden. Keyring entries are at the end of the list, bag contents first. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.04 Bags: applying enchantments and armor kits with the left click works again Simple: With an enchantment or an armor kit waiting for a target item, pressing Enter (left click) on the target item in the bags did nothing - only Ctrl+Enter (right click) applied it. Enter now applies it, like the standard UI does. On equipped items both keys already worked. Technical: The bag item's Enter action was a plain insecure PickupContainerItem; the native left click routes through the hardware-event-gated UseContainerItem when SpellCanTargetItem(). Bag items and equipment slots now carry an applyMacrotext ("/use bag slot" resp. "/use slot") that the menu stages on the secure left-click button while SpellIsTargeting() is live at focus time; a targeting-only PreClick snapshot lets the insecure fallback skip its pickup after the macro applied, without breaking drop/swap of a held cursor item. The /use form is also modifier-independent - a synthesized "/click ... LeftButton" reads the live keyboard state, so a rebound left-click key with a modifier would count as a modified click (dress-up/chat link) and never apply. Starting zones: Ctrl+Shift+Tab cycles protected NPCs again Simple: Since the WoW client update of July 9 the special Ctrl+Shift+Tab key no longer cycled through the protected (otherwise untargetable) NPCs in the starting zones - pressing it just cleared your target. It works again. Technical: Client build 2.5.6.68575 removed the CVar nameplateShowFriends (split into nameplateShowFriendlyPlayers + nameplateShowFriendlyNpcs). GetCVar on the gone name returns nil without an error, so Sku's force-on silently stopped, friendly nameplates never appeared and the name repo behind SkuCoreSecureTabButton stayed empty. A new cached resolver SkuCore.FriendlyNameplateCVars() picks the CVar set per client (the Era client keeps the old umbrella name) and is used by the cycler's force-on, the login CVar baseline and the camera menu (the friendly nameplates toggle now sets a CVar list). Navigation menu: flattened, Shift+F9/F10 quick lists reworked Simple: The Navigation menu entries now sit directly under Navigation (Units route on top, then Direct waypoint - renamed from "Select" -, Clear visited, Set quick waypoint); the old "Waypoint" / "Follow route" wrappers and the redundant "Deselect all" / "Deselect" entries are gone (Shift+F12 already cancels navigation silently). The navigation settings moved out of the Nav menu: general settings to Settings -> Navigation, beacon settings to Monitor -> Beacon. Shift+F9 / Shift+F10 open the nearby-waypoints / nearby-route-destinations lists directly, and LEFT from such a quick list switches into the fuller menu it shortcuts into. Technical: The quick keys use a dedicated handler plus a transient root entry (like the Shift+F11 action-bars handler) instead of the fragile localized SlashFunc label-walk; shared list builders hoisted to SkuNav module scope; the stale MenuQuickSelect1-4 label-path defaults are retired and the inert "quick select" settings group is hidden. Same args/db, saved values kept. AtlasLoot professions: Classic and TBC recipes in one list Simple: In the AtlasLoot profession menu each profession appeared twice (e.g. "Enchanting" for Classic and again for TBC) and a category such as bracer enchants only showed one era's recipes. Each profession is now a single entry and every category lists both eras' recipes, sorted by required skill. Technical: The profession branch merges at two levels: crafting modules are grouped by localized profession name (Enchanting + EnchantingBC -> one entry), then same-named categories are merged across modules and the leaf renders both modules' recipes sorted by skill. WotLK modules self-gate to the WotLK client, so the TBC phase yields exactly Classic + BC. Mailbox: typed field contents are read back reliably Simple: After typing recipient, subject or text into a mail field, the confirmation (e.g. "Recipient: Playername") was spoken with the Sku audio voice, which cannot speak free text like player names - the line stayed silent or incomplete. The confirmation now always uses the Blizzard TTS voice. Technical: The MailEditor confirmation calls pass engine 2 (Blizzard TTS) to OutputStringBTtts instead of the default audio-databank path. Class trainer: focus stays on "Train" Simple: Pressing "Train" at a class trainer jumped the focus to the top of the window. It now stays on the Train button after the skill list has settled, so training several skills in a row is one keypress each. Technical: The post-train re-pin searched the focused entry's CHILDREN for the Train button instead of its siblings; the window rebuild re-anchors by index, so it never matched. It now searches the level the cursor is on (parent.children, then children), and the intermediate CheckFrames runs quiet so only the final position is announced. Flightmaster: "Close" no longer listed Simple: The flightmaster window listed a "Close" entry. Sku never lists close buttons (Escape closes every window), so it is gone. Technical: The "Close" label was auto-attached to the nameless TaxiCloseButton by the buttons-without-fontstring fallback. "Close" is now in blockedWidgetStrings, so generically mirrored windows drop close buttons like the hand-built window mirrors always did. Menus reorganized Simple: "Sku menu in combat" moved from Monitor -> Combat to a re-added Settings -> Combat submenu. "Notice on pet starving" moved to Monitor -> Pet -> Health. The Auras menu lost its duplicated level: one RIGHT from the Auras entry lands directly on "New aura" (the functionless "Options" entry is gone). Technical: Same settings and storage (combatMenuOpen, classes.hunter.petHappyness) - only the menu placement changed. SkuAuras:MenuBuilder builds the list directly into the module root entry; the SlashFunc anchor paths dropped the aurenList segment. Documentation: standard key binding hints corrected Simple: The "Standard Key Bindings for Blind Players" help files (English, German, French) named several outdated defaults. Corrected: herb/ore scan is Ctrl+Shift+F / Ctrl+Shift+R, advance/back one beacon is Ctrl+Shift+W / Ctrl+Shift+S, quick waypoints are Shift+F5 to Shift+F8, turn to beacon is I. The README now points at the current download page, which also links these help files and the patch notes directly. Technical: Verified against skuDefaultKeyBindings in SkuZOptions/SkuKeyBinds.lua; the website copies under docs/ carry the same corrections. ------------------------------------------------------------------------------------------------------- Changes in Sku v42.03 Bank window: item names and right-click restored Simple: Bank slots showed "Empty" even when they held an item, and right- clicking a bank slot did nothing. Bank slots now read out their item name again, and right-click (Ctrl+Enter) moves the item into your bags. Technical: The container-API bag rework read names via GameTooltip:SetBagItem, which returns nothing for the bank main container (id -1) on 2.5.6; a hyperlink fallback (SetHyperlink via GetContainerItemLink) restores the name. The bank right-click now runs an insecure UseContainerItem (bank moves are not hardware-gated) with an event-driven refresh on BAG_UPDATE_DELAYED. Bag sorting and cleanup: rebuilt, no more error loops Simple: Sorting or cleaning up bags could spam internal bag errors, misplace items or get stuck in an endless loop. Sorting is rewritten to be reliable, no longer needs the bags to be open, and "sort all bags" works. Technical: Replaced the click-simulation bubble sort (coroutine on a 0.01s ticker) with a C_Container engine: PickupContainerItem swaps, a selection sort (guaranteed termination), one swap per BAG_UPDATE_DELAYED settle with a watchdog and per-bag step cap, all pcall-guarded and traced. Also guards a talent-frame "table index is nil" crash. Error feedback: choose how each error is signalled Simple: Each error type (out of range, stunned, wrong direction, interrupted, no line of sight, ...) now has its own submenu where you pick how it is announced: a TTS voice, a spoken voice hint (Marlene or Hans, German or English), a short sound, or silence. The old spoken hint clips are selectable again. Technical: SkuCore\UIErrors.lua gains a nested menu builder (mirrors the chat per-channel voice pattern); the value stays one persisted string ("voice" / "voice#" / clip path / beep path / silent) so existing settings carry over. OutputError decodes "voice#" and passes the voice index to OutputStringBTtts; focus-preview auditions the choice. Menus reorganized Simple: New "Werkzeuge" (Tools) menu holds Dial Targeting and Soft Targeting. Monitor now also holds Distance, Fall detection, Error feedback, Target options and Quest notifications. The Audio menu is flattened. The old "Barrierefreiheit" menu is now called "Schnellmenue" (Quick menu). Technical: Moves in SkuZOptions/SkuMenu and SkuCore\aq.lua MonitorMenuBuilder; RangecheckMenuBuilder exposed for reuse; the now-empty Kampf submenu removed; same db/keyPrefix so saved values are preserved. Group-member quests: no longer crash on progress Simple: Opening a group member's quest that had objective progress sometimes errored and aborted the read. It now opens reliably. Technical: The group-member OnEnter concatenated onto the sections TABLE from GetQuestDataStringFromDB; it now builds a proper sections table (live progress first) like the own-quest-log path. Installer: works behind shared / VPN connections Simple: The installer could fail with "rate limit exceeded" before any download started, on shared or VPN internet connections. It no longer does. Technical: Dropped the api.github.com release enumeration (60 req/h/IP cap); asset (tag, filename) pairs are pinned in Config and direct github.com/.../releases/download URLs are built instead. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.06 Friends list: "Invite" works again Simple: In the Friends window a friend could not be invited (error tone). The invite works again, including Battle.net friends. Technical: InviteUnit is no longer a global in 2.5.5; the normal friend branch (SkuCore\friends.lua) now uses C_PartyInfo.InviteUnit with a fallback, like the Battle.net branch. Aura set sharing: easier to use Simple: "Sets (share)" now sits in 3rd place (under New aura and Manage auras). "Create set from current auras" is now "Create new set from current auras". When creating a set you can now type the name directly (previously only pasting worked). The announcement is simplified to "Set created" because a freely chosen name cannot be voiced. Technical: SkuAuras\sharing.lua now uses SkuOptions:EditBoxShow (with keyboard focus) instead of EditBoxPasteShow; menu order adjusted in SkuAuras\Options.lua; announcement only once. Aura set sharing: "Delete set" now works reliably Simple: When deleting a set the entry sometimes stayed and the next Enter only played an error tone. Now the set is deleted, the menu jumps back to the set list and rebuilds it. Technical: After SetsDelete the menu navigates back to "Sets (share)" and calls OnSelect / VocalizeCurrentMenuName (SkuAuras\sharing.lua); announcement simplified to "Set deleted". ------------------------------------------------------------------------------------------------------- Changes in Sku v41.05 Enemy combat status: in-combat/passive on target (Off / Beep / Announce) Simple: New item under Accessibility, Misc: "Enemy combat status". "Beep" (default) is the previous behavior - a tone before the enemy name when the enemy is fighting you or your group. "Announce" keeps the tone and also says "in combat" or "passive" before the name. "Off" disables tone and status announcement. The cycle key is unchanged. Technical: In SkuMob\Core.lua (PLAYER_TARGET_CHANGED) the existing InCombatSound now only plays when the mode is not "off"; in "announce" mode "in combat" is prepended to the name like the existing "passive". New DB value profile.SkuMob.enemyCombatStatusMode (off/beep/announce, default beep -> existing characters unchanged). Menu in SkuZOptions\Core.lua, locale "im kampf" in deDE/enUS. Pure announcement code, no taint. Stability: crash in the friendly health/heal monitor fixed Simple: The "fatality" error that could occur when switching to yourself or a pet during combat (with the "factor in incoming heals" option enabled) is fixed. Health announcements keep working normally. Technical: In SkuCore\aq.lua, UNIT_HEALTH read UnitGetIncomingHeals(unit, "player") without a nil guard; with no incoming player heal (the normal case) the subtraction "number minus nil" threw a Lua error. Added "or 0" in four places. Stability: rare menu error (Core.lua:4021) fixed Simple: A rare error tone when navigating in certain menu states (e.g. an open chat line via Ctrl+Enter, then arrow) is gone. Technical: In SkuOptions:VocalizeMultipartString a debug breadcrumb loop "while tTable.parent.name do" had no nil guard; at a root node without parent -> "attempt to index field 'parent'". Guard added. Debug path only, no behavior change. Follow warning: no false "follow ended" when re-pressing follow Simple: When you press the follow key again while following (to check it), WoW briefly stops and immediately re-establishes the follow. Previously this falsely said "follow ended". Now it stays with the normal bling in that case; "follow ended" only fires on a REAL break. Technical: The AUTOFOLLOW_END announcement is delayed by 0.4 s and suppressed if an AUTOFOLLOW_BEGIN arrives in between (re-follow). Generation counter against stale timers. In SkuCore\visualAids.lua. Fix: "click sound at beacons" menu was sometimes empty (Navigation, Options) Simple: The click-sound choice (beep/click) was sometimes empty and not changeable (the on/off toggle still worked). Cause was a load-order race: the click sounds come from the add-on SkuBeaconSoundsets and are only registered on entering the world - but Sku sometimes built the list earlier (empty). Now the list is reliably rebuilt shortly after loading. Technical: Additive deferred rebuild in SkuNav\Options.lua (NOT in the protected SkuNav\Core.lua): on PLAYER_ENTERING_WORLD + C_Timer.After it rebuilds SkuNav.ClickClackSoundsets from BeaconLib:GetClickClackSoundSets() and re-points clickClackSoundset.values. pcall-guarded, no-op if SkuBeaconSoundsets is missing. IMPORTANT: if SkuBeaconSoundsets is disabled/missing the list stays empty (beacon sounds also missing) - a common cause of user "audio problems". Mouse: alternative control ("walk to target") cleanly toggleable + switch in the menu Simple: Accessibility menu, under Other, now has the switch "Walk to target on interact". Sighted players can turn off the auto-walk-on-click control; it no longer disturbs the normal mouse. Technical: The WorldFrame mouse handler is no longer overwritten via SetScript but installed additively via HookScript EXACTLY ONCE (guard flag, not in the 100ms path) and does nothing while interactMove is off. The switch reuses the existing options node, exposed in Accessibility, Other. Nameplates tied to the camera Simple: Friendly nameplates are only forced as long as the camera SkuStandard is active. When the camera menu is released you can change or disable the nameplates yourself. Technical: The nameplateShowFriends=1 enforcement in the OnUpdate loop now only applies when cameraOptions.skuStandard ~= false (plain read with full and-chain, combat gate preserved). New: black reading bar (accessibility, for low-vision players) Simple: A black bar at the top or bottom of the screen shows ONLY the current menu item (not the whole path) being spoken, in large high-contrast text. Only visible while the menu is open. Default OFF. In the new menu item Accessibility, Visual aids, you can toggle it and set font size (up to very large; larger also gets bolder), position (top/bottom) and opacity. Long lines run off to the right - this is intended. Fix: with very large font the bar now grows DOWNWARD (text was cut off at the top); it is no longer clipped at the top screen edge. Technical: New non-secure FontString frame on UIParent (EnableMouse(false), no taint), fed by SkuOptions.currentMenuPosition.name (current line only), event-driven (no OnUpdate, not in the 100ms loop), text set AFTER speech. Font via SetFont with THICKOUTLINE from medium size up (boldness). FontString now anchored TOPLEFT (JustifyV TOP), bar height px+40%+12, so large text grows downward instead of clipping at the top. Logic isolated in SkuCore\visualAids.lua; values per profile under db.profile.SkuCore.visualAids.lineBar. New "Visual aids" item. New: nameplate colors (accessibility, for low-vision players) Simple: In Accessibility, Visual aids, Nameplate colors you can color nameplates by reaction (enemy, neutral, friend), choose each color yourself, plus mode (only target or all nameplates), size and opacity. Default OFF. A warning explains that the colors are display only. Technical: Own function in SkuCore\visualAids.lua, own opt-in flag (not testMode), event-driven (NAME_PLATE_UNIT_ADDED/REMOVED, PLAYER_TARGET_CHANGED), no C_Timer fan-out, not in the 100ms loop, no TOOLTIP strata. Full nil guards (UnitReaction, 4-level frame check). In the OFF state the events are unregistered. New: mouse finder (accessibility, for low-vision players) Simple: In Accessibility, Visual aids, Mouse finder, enable it. You now bind the key DIRECTLY in the Mouse finder menu (third item "find mouse", go right, Rebind) - just like in the Sku key bindings, no separate search needed. The plain text hint was removed. On key press a high-contrast marker briefly lights up around the mouse (pulse ring or compass strokes, selectable). Default OFF. Technical: Well-formed key entry SKU_KEY_MOUSEFINDER (no blank entry, hence no login crash), the handler shows a non-interactive overlay (EnableMouse(false)) centered on GetCursorPosition, about a quarter of the screen, auto-hidden via C_Timer.After. No secure button, no taint. The key-binding entry inside the Mouse finder menu is fully encapsulated in SkuCore\visualAids.lua (own capture helper, writes to the same SkuKeyBinds DB as the main menu); SkuCore\Options.lua is left untouched. New: announce number of enemies (accessibility, Other) Simple: Accessibility, Other now has "Announce number of enemies" with three settings: "elite enemies only", "all enemies" and "off". One choice sets the right combination, so you do not have to find the two separate switches in the hostile monitor. Technical: Convenience selector that couples two existing values (in db.char.SkuCore.aq[talentSet].combat.hostile): relativeNumberUnitsInCombat.value and ignoreNonElite. Mapping: elite only -> ignoreNonElite=true, value=3 (enemies attacking you/party); all enemies -> ignoreNonElite=false, value=3; off -> ignoreNonElite=true, value=1 (off). Fix: "all enemies" and "elite only" now ALSO enable the combat monitor master (db.char.SkuCore.aq[talentSet].combat.enabled= true) - previously, on a character whose master was off, "all enemies" produced no announcement. "off" deliberately leaves the master untouched (only the two single options are set), so other combat monitoring is not affected. New: warning when follow breaks (accessibility, Other) Simple: Accessibility, Other now has "Warning when follow breaks" (on/off, default off). When you follow someone (autofollow) and the follow ends - whether the target moves too far, stops, or you move - you hear "Follow ended". No distance tracking or picking a person is required. Technical: Lean listener on the public events AUTOFOLLOW_BEGIN/END in SkuCore\visualAids.lua; announcement only if a follow had begun and the switch (db.profile.SkuCore.followBreakWarn) is on. No taint, no distance tracking, no OnUpdate. Note: warning when SOMEONE ELSE stops following YOU is not possible (no event for it). New: key "next enemy in combat" (Sku key bindings) Simple: The Sku key bindings (Core menu) have the new action "next enemy in combat". Bind it to a key and you cycle through nearby enemies on key press - even in the middle of combat. Sku announces the new target as usual. Default: no key set. Technical: SECURE implementation via a SecureActionButton (macro /targetenemy) to which the chosen key is bound with SetOverrideBindingClick. This makes the in- combat target switch allowed (no ADDON_ACTION_BLOCKED, no taint). SKU_KEY_NEXTCOMBATENEMY -> SkuCore:UpdateNextCombatEnemyBinding (in SkuCore\visualAids.lua); the binding is deferred during combat and reapplied via PLAYER_REGEN_ENABLED. Note: /targetenemy cycles nearby attackable enemies, not exactly filtered to those fighting you - an exactly filtered variant would be a larger, later addition. New: create aura sets and share them in the group (stage 1+2) Simple: The Auras menu now has "Sets (share)". You can create a named set from your current auras (same name exists -> a number is appended), list and delete sets. A set can be "share with group": your group sees " shares aura set " and at the end "sharing complete". Anyone with Sku in the group receives it under "received sets" and can accept it there (with duplicate-name check) or discard it. Note: in this stage only between the same client language; the language translator comes later. Technical: Isolated in NEW file SkuAuras\sharing.lua, pcall-guarded, does NOT change the existing aura system (auras stay name-based). Sets: db.char.SkuAuras.Sets (snapshot). Transport via AceComm-3.0 (embedded into SkuAuras, prefix SkuAuraSetV1, automatic chunking) plus AceSerializer (SkuOptions:Serialize/ Deserialize). Receive into db.char.SkuAuras.PendingSets; accept imports with duplicate check and calls UpdateAttributesListWithCurrentAuras. Visible group announce via SendChatMessage, data hidden via addon message. No taint. Later: stage 3 (account-wide library), stage 4 (language translator via SkuDB.SpellDataTBC). Details in the plan in the Heute folder. Default value: continuous volume of health monitors is now 100 Simple: On a NEW installation the volume for the continuous output of the health monitors (player, party, raid, pet) is now 100 instead of 50 - matching the event volume (which was already 100). Existing profiles stay unchanged. Technical: Default continouslyVolume in SkuCore\aq.lua changed from 50 to 100 (4 places: player/party/raid/pet health). Only applies while the value is still nil (fresh setup). Soft targeting: "hard target over soft target" now works + no more combat error Simple: The setting "hard target counts before soft target" had NO effect before - a spell went to the soft target despite having a hard target. Fixed: with a hard target, the hard target now counts. Also the "ADDON_ACTION_BLOCKED" error that appeared after login on the first friendly target in combat is gone. Technical: In UpdateSoftTargetingSettings (SkuZOptions/Core.lua) BOTH branches used to set SoftTargetMatchLocked = 0 (bug) -> now 1 when matchLocked>0. Also the SoftTarget CVars are protected in combat: the function now skips all SetCVar calls in combat (InCombatLockdown guard) and re-applies once after combat (PLAYER_REGEN_ENABLED). No loss of function (CVars are frozen in combat anyway), no more ADDON_ACTION_BLOCKED. Version number raised to 41.05. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.04 Auction house: purchase messages now in the correct language Simple: If something goes wrong during a purchase (e.g. you press Enter too early), the message now appears in English when you play the English client - before it was always in German. Technical: Three hard-coded German strings in the buy path (auctionHouse.lua) are now localized via L[...]; keys added to deDE.lua and enUS.lua. Auction house: full scan starts reliably, no false 16-minute lockout Simple: Previously the full scan could fail silently and then be locked for 16 minutes without ever scanning. Now it either really starts, or you hear "Scan not possible yet, please wait a moment" - and the lockout is only set when a scan actually ran. Focus stays on the scan entry. Technical: AuctionHouseStartQuery now returns true only when QueryAuctionItems was actually issued; the getAll scan is checked against CanSendAuctionQuery first. The timestamp (AuctionLastFullScanTime) is only set on a real start. noStepUpAfterSelect keeps the focus. Auction house: focus stays when filtering/sorting Simple: After choosing e.g. "rare" or "level descending", the focus no longer jumps one level too far back but stays on the filter. Technical: The five filter/sort entries (level min/max, quality, usable only, sort by) now have noStepUpAfterSelect = true. AtlasLoot/Wishlist: no more raid lag Simple: The wishlist slowed things down during raid combat with lots of chat messages. Fixed - the wishlist-item sound on loot/roll/link still works. Technical: Instead of scanning the whole favorites list with GetItemInfoInstant per chat line/item link, there is now an itemID cache (O(1) lookup) rebuilt only on change. With an empty wishlist the chat handlers return immediately. AtlasLoot: Ctrl+Shift+L simplified Simple: Ctrl+Shift+L now simply opens Atlas Loot. The automatic jump to the current instance or the boss in your target was removed - it was unreliable and could cause errors. Technical: The context detection (instance/boss/difficulty) in AtlasLootShortcut was removed; it always opens the default path and clears the context flag. Friends list: invite Battle.net friends Simple: You can now invite a Battle.net friend who is currently playing WoW to your group via the menu. Before, nothing happened. Technical: The invite OnAction (friends.lua) now has a BNet branch: character name + realm from gameAccountInfo, invite via C_PartyInfo.InviteUnit (fallback InviteUnit/BNInviteFriend). If the friend is not playing WoW, an announcement is made. Auras: display name no longer crashes Simple: When showing or browsing auras, a Lua error could occur if an aura used a value or action the addon could not resolve at that moment. Fixed. Technical: BuildAuraName (SkuAuras/Options.lua) now does all dynamic lookups (types, attributes, operators, values, actions, outputs) nil-safe; unknown keys show the raw key instead of erroring. Not in this version (intentionally deferred) - Bags: focus/refresh after "will be bound to you" - touches the sensitive bag frame area and will be handled separately. - Soft/hard target (hard target should override soft target; less target spam when zoomed out) - lives in the protected targeting system; analyzed only, not yet implemented. Version - Sku.toc and SkuDispatcher/Sku.toc updated to v41.04. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.03 Accessibility menu: 7.4 Other - New sub-item 7.4 "Other" under menu item 7. Quick access to: do not hide game tooltip, read all tooltips, play NPC greetings, announce menu numbers, announce submenus. - Shortcuts to the existing options - changes apply both ways. The cursor stays on the entry after toggling. AtlasLoot/Wishlist: boss source in tooltip ("By slot" view) - In the wishlist under "By slot", the drop source (instance and boss) is now shown reliably in the tooltip - even right after login, without opening the "By dungeon" view first. Auras: weapon buff conditions (new) - Four new conditions: "Your weapon enchant main hand" and "... off hand" (contains / contains not) plus their remaining duration in seconds. - Lets you build alerts, e.g. when a poison/oil/sharpening stone is on the main hand or the remaining duration drops below a value. - Purely additive; existing auras are unchanged. Version - Sku.toc and SkuDispatcher/Sku.toc updated to v41.03. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.08 Menu 7 "Accessibility" with quick access - Menu item 7 is now "Accessibility": 7.1 Volume, 7.2 Speech output, 7.3 Camera. - 7.1 Volume: master, sound effects, music, ambience and dialog volume, beacon volume, plus reverb, positional low-pass filter, DSP effects, sound in background and zone music no delay. These are shortcuts to the existing options and stay in sync both ways. - 7.2 Speech output: TTS voice, speed and volume. - 7.3 Camera: the former Video options menu (follow, zoom, etc.). - The original menus (Options/Options, Navigation/Options, Chat/Options) remain unchanged. - After changing a value the cursor stays on that control (menu 7 only), so several values can be set quickly. Mailbox: focus stays on the letter - After taking gold or an item, the cursor stays on the letter instead of jumping up to the inbox list. Press right-arrow to reopen the (updated) attachments and take more. When deleting, a neighbour letter is focused. Navigation: "Data" menu item hidden - The "Data" item under Navigation is no longer shown. Version - Sku.toc and SkuDispatcher/Sku.toc updated to v41.02.08. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.07 Camera: free camera after leaving Sku standard - When Sku standard is released, your own camera settings are now kept - even when turning with T or toward a waypoint. The forced camera snap (SetView) only applies in Sku standard. Turning itself still works. - Personal camera profile: changes made in free mode are saved and automatically restored after login. Safe Sku standard remains the default for everyone else. - Menu 7 "Video options" as the basis for the new structure. Version - Sku.toc and SkuDispatcher/Sku.toc updated to v41.02.07. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.06 Camera Options (Menu item 7) - New menu "Camera options" replaces the former "Reserved" placeholder. - Sku standard lock/unlock: Locks all camera settings to the Sku standard (required for waypoint navigation). Locking triggers a confirmation dialog and performs a full camera reset including SaveView(2). Unlocking restores saved user settings. - Camera distance: 6 levels from Close (factor 1) to Maximum (factor 4). Zoom is physically applied. - Camera height: 4 levels from flat to bird eye view. Note: only works when tracking style is not set to Always. - Camera pitch speed: 4 levels from Slow to Very fast. - Camera tracking style: 4 options matching WoW originals (Smart, Only when moving, Always, Never). "Always" is the Sku standard for waypoint navigation. - Show/hide interface: Toggles the entire WoW UI on or off. - More camera functions: Camera transition (instant/smooth), over shoulder view, nameplates (enemy/friendly/all on/off). - All settings display their current value in the menu name. Active option marked with "(active)". - When Sku standard is locked, all sub-menus are blocked with an announcement. - Settings persist across logout/login (stored in SavedVariables). Auction House: Crash on English client fixed - Opening the Auction House could cause an error (ipairs on nil) when AuctionCategories was not yet initialized by Blizzard. Fix: nil-guard before both ipairs loops and 0.3s delay on menu build. AtlasLoot: Localization corrected - The groups "Instances", "Raids" and "World Bosses" under AtlasLoot Lists were hardcoded in German. They now use L[] entries and display correctly on English clients. Also fixed: "Heroic", "Normal" and the difficulty fallback label. Character Frame: Item in slot - CheckFrames is no longer called immediately after picking up an item from an equipment slot. This allows placing the item into a bag without the menu corrupting the slot state. Soulbind confirmation: Focus improvement - After confirming the "This item will be bound to you" dialog, the focus no longer jumps back to the Bags level. Instead, CheckFrames is called with a delay and the cursor is properly repositioned. World Map: Visual flickering reduced - The addon opened and closed the World Map in the background (every 0.15s) to read position data. When the map was already open, this caused visual flickering. Fix: Show/Hide is only executed when the map is not visible. Version display - Sku.toc and SkuDispatcher/Sku.toc updated to v41.02.06. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.05 Pet Action Bar: Autocast fixed - Autocast toggle for pet abilities now works. Uses macrotext with /click RightButton in hardware event context. Menu shows "Autocast (activated/deactivated)" with sub-entries to toggle on and off. Role Poll window made accessible - The group leader's role check now opens a Sku menu with options "Tank", "Healer" and "Damage". Enter on a role selects it and confirms immediately. Already selected roles are not reset. Equipment Slot double-click fixed - Left-clicking equipped items in the character window picked up the item and immediately dropped it back. Cause: both macrotext and OnAction fired. Fix: OnAction checks via GetCursorInfo whether the item is already on the cursor. Key binding: Conflict resolution fixed - When reassigning a key to a different action, previously ALL bindings of the old action were deleted (primary and secondary). Now only the conflicting key is removed, the other binding is preserved. Socketing window: Duplicate gems fixed - Identical gems are now grouped and displayed with count (e.g. "Solid Star of Elune 2x"). After inserting into a socket, the used bag slot is marked as occupied so the next socket correctly shows and can use the remaining gem. Wishlist: Category "Other" added - Items without an equipment slot (tokens, recipes, mounts) can now be added to the wishlist. They appear under "By Slot" in the new category "Other". Key binding reference included - Three readme files in the Sku folder: Standard key bindings for blind players in German, English and French. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.04 PageDown/PageUp in Sku menu corrected - PageDown/Up no longer jumps 10 menu entries in the Sku menu. The keys now only trigger scrolling in profession windows (recipe lists) and update the menu accordingly. In all other menus (bags, auction house, etc.) the keys have no effect. Localization: Equipment sets - The menu entry "Ausruestungssets" in the character window was a hardcoded German string. It now uses L["Equipment sets"] and displays correctly as "Equipment sets" on English clients. Localization: Socketing window - The frame name "Sockeln" on the first menu level was a hardcoded string. It now uses L["Sockeln"] and displays correctly as "Socket" on English clients. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.03 Mailbox: New combined input - New Letter now has three menu entries: "Attach items", "Attach gold" and "Start sending". The order is logical: optional attachments first, then send. - "Start sending" opens an input field. Usage: type name, press Tab, type subject, press Tab, optionally type body text, press Enter. The mail is sent immediately. - After attaching items or gold, focus automatically returns to the respective menu entry. After sending, focus returns to "New Letter". - Technical: New function SkuCore:MailEditorCombined() in mail.lua. Uses OnKeyDown handler on EditBox for Tab and Enter detection. Tab switches fields (name/subject/body), Enter finalizes and calls SendMail() directly. Menu stays open (MailFrame must be visible for SendMail()). Warlock Pet: Separate target menu - The target menu for warlock pets (demons) now shows the correct actions: Raid Marker, Set Focus, Interact and Dismiss. Previously, hunter pet actions were incorrectly displayed. - Technical: SkuMob/Options.lua, tIsPet block: Class check via UnitClass("player"). HUNTER shows hunter actions, all other classes show the generic pet menu. Missing audio words fixed - "Tabulatortaste" and "absenden" replaced with "Tab" and "Senden", as the original words had no audio files in SkuAudioData and triggered a beep. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.02 (Working version) Insights from mailbox repair: - CloseMenu() also closes the Blizzard MailFrame. SendMail() requires an open MailFrame. Therefore CloseMenu() must NOT be called before mail input. - EditBox OnEnterPressed does not fire when SetScript("OnKeyDown") is set. Enter must be intercepted manually in the OnKeyDown handler. - SkuOptionsEditBoxOkScript is a local variable in SkuZOptions/Core.lua and cannot be called from other files (e.g. mail.lua). Callbacks must be defined as local functions in the calling scope. - RegisterForClicks("AnyDown") prevents double-enter but blocks EditBox OnEnterPressed (fires on key-up). Solution: OnKeyDown handler on EditBox intercepts Enter directly — independent of SecureActionButton override binding. - Debounce approach (100ms lock in PostClick) works for bags and wishlist but breaks mailbox and other EditBox inputs. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02.01 (Working version) Double Enter trigger fixed - The SecureActionButton responded to both key-down AND key-up, causing every Enter press to trigger two actions. Affected all menus: bags, auction house, wishlist. - Technical: SkuZOptions/Core.lua line 2738. "AnyUp, AnyDown" -> "AnyDown". AnyDown provides hardware event context (important for left-click/items). AnyUp alone does not provide hardware context. Stable Master restored - Stable master functions from version 40.x adopted. The stable master is usable again. - Technical: SkuCore/LocalMenu.lua, Build_PetStableFrame copied 1:1 from template 2.x. Uses tFrame:GetScript("OnDragStart")(...) and OnReceiveDrag — works in the Gossip/LocalMenu system via the click=true path. Pet Rename fixed - Fix: Menu closed before input field (CloseMenu), EditBoxShow instead of EditBoxPasteShow, with delay (C_Timer.After 0.3s). - Technical: EditBoxPasteShow was for paste input (OnChar, min 10 chars). EditBoxShow is for keyboard input (OnEnterPressed). CloseMenu() clears the Enter override binding so the EditBox can receive Enter. secureMacro system for protected functions - The SecureActionButton attribute was set with invalid mouse button parameter "ENTER". TBC 2.5.5 does not find type/macrotext attributes for unknown button names (no fallback to default attributes). - Fix: New secureMacro flag on menu entries. When set, additionally sets typeENTER/macrotextENTER attributes. Entries without secureMacro clear these attributes (prevents conflicts with left-click/OnAction). - Technical: SkuZOptions/templates.lua OnEnter handler. SkuMob/Options.lua: secureMacro=true on PetDismiss and PetRename confirmation entry. - Key insight: macrotext does NOT fire in the Gossip/LocalMenu system (tested with /say, /run, /click — no output). macrotext ONLY fires in the SkuMob/SkuGenericMenuItem system (proven by PetRename/PetDismiss). Pet Dismiss / Pet Release localization - Labels matched to in-game terms. - secureMacro flag for PetDismiss (/petdismiss in hardware event context). ------------------------------------------------------------------------------------------------------- Changes in Sku v41.02 Version Jump 40.x to 41.x - Performance improved, wiki section removed. - Localization of new content: socketing, AtlasLoot wishlist, dropdown menus, dungeon finder. Bugfix: Letter and Number Keys Blocked on English Clients - When logging in on an English WoW client, an automatic profile switch caused all letter, number and space key inputs to be blocked by orphaned override bindings. This affected e.g. the 7 key (and therefore / on QWERTZ keyboards). Root cause: SkuKeyBindsUpdate() directly called the OnShow handler of the menu frame, which set all menu navigation bindings without OnHide ever clearing them. The affected keybinding entry (SKU_KEY_QUESTABANDON) has been corrected. - Additionally, all module OnEnable calls in the profile handlers (OnProfileChanged, OnProfileCopied, OnProfileReset) are now wrapped in pcall, so SkuKeyBindsUpdate() is always reached even if individual modules error. - The deduplication in SkuKeyBindsUpdate has been fixed: functions like CreateMainFrame are now called only once instead of approximately 30 times. Localization: Socketing Window - All approximately 20 hardcoded German strings in Build_SocketingFrame.lua replaced with L[] calls. English clients now display correct English text (socket colors, menu items, status messages). - Critical menu comparison (child.name == "Sockeln") localized so the socketing menu is correctly found on English clients. Missing Locale Key - L["Warlock"] added to enUS.lua. The key existed in deDE.lua but was missing from the English locale file. API Compatibility - SetMinResize/SetMaxResize in SkuNav/SkuMM.lua replaced with capability detection (SetResizeBounds preferred, fallback to SetMinResize). Prevents possible errors on TBC Anniversary (Interface 11508). Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to v41.01.04 (without beta). ------------------------------------------------------------------------------------------------------- Changes in Sku v41.01.03 beta Pet Rename - ADDON_ACTION_BLOCKED fixed: The SecureActionButton was tainted by a HookScript handler. WoW therefore blocked the protected PetRename() call. The HookScript has been replaced with PostClick, which does not affect secure macro execution. - Menu rebuild after name input restored: After typing the new name, the menu is now automatically updated so the confirmation entry is immediately visible. Auction House - Crash on "New Auction" fixed: When an inventory item was not yet fully loaded in the client cache, a nil value on the name query caused a crash. Items without a cached name are now skipped. Navigation - Crash on special tasks fixed: When the task database was not loaded, accessing SkuDB.Tasks caused a crash. The data is now checked for existence first. - Dangerous menu item removed: The menu item "Delete all routes and waypoints" under Navigation/Data has been removed. It would have irreversibly deleted all user-defined navigation data. - Export function enabled: Route mapping data can now be exported as text under Navigation/Data for import into the SkuMapper tool. Wishlist (Atlasloot) - Audio feedback on Enter fixed: The feedback when adding/removing wishlist items was immediately cleared by an internal cleanup. The voice output is now slightly delayed so it is reliably audible. Stability - Nil guard for menu position name: Prevents the follow-up error "bad argument #1 to find (string expected, got nil)" when the menu is in an invalid state. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to v41.01.03 beta. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.01.02 beta Bug Fixes (Sound) - Sound loss fixed: If the saved profile contained a master volume of 0 (e.g. due to corrupted SavedVariables), all WoW sound channels were set to mute on login. There is now a safeguard: if a master volume of 0 is detected in the profile, the addon re-reads the current Blizzard settings instead of applying 0. - CVar reads secured: All volume queries during first-time profile initialization now use tonumber() with a fallback to 100 percent. If WoW returns an invalid value, full volume is used instead of mute. - Pre-existing CVar bug fixed: Three sound settings (DSP effects, sound in background, zone music without delay) were previously not passed correctly to WoW because the same CVar name was used due to a copy-paste error. The three settings are now applied correctly. Installation Note - After copying the new addon, it is recommended to delete the Sku SavedVariables once. The file is located at WTF/Account/ACCOUNTNAME/SavedVariables/Sku.lua in the WoW folder. This creates a fresh profile that picks up the correct Blizzard volume settings. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v41.01.02 beta. ------------------------------------------------------------------------------------------------------- Changes in Sku v41.01.01 beta Performance - Wiki/Tutorial module removed: The "Tutorials and Wiki" module (menu item 7) has been completely removed. This saves approximately 30 MB of memory (28 MB wiki database plus lookup index) and noticeably speeds up login, as 39,600 fewer lines need to be parsed. - No more permanent OnUpdate handler: The frame handler that ran every time the wiki menu was opened (~60 times per second) no longer exists. Menu - Menu item 7 is now a placeholder named "Reserved". It displays the message "This section is currently being reworked". The section will be filled with new content in a future version. Key Bindings - The default key binding SHIFT-F4 (open adventure guide) has been removed, as the associated module no longer exists. Players who had manually rebound the key do not need to take any action — the entry is ignored and causes no errors. Safety - Nil guards for wiki references: All locations in the TTS system (SkuTTS), the options system (SkuZOptions), and the voice output (SkuVoice) that access wiki data are now protected with nil checks. This ensures that no error messages occur even though the wiki data is no longer loaded. Bug Fixes - tasks.lua (navigation tasks for DK quests) was incorrectly removed along with the wiki module. The file has been restored. Fixes the error "bad argument #1 to 'pairs' (table expected, got nil)" in specialNavigationTasks.lua. Companion Addons - SkuAudioData_fast_de: Interface version updated to 11508, dependency on Sku added. Fixes "attempt to index global 'Sku' (a nil value)". - SkuBeaconSoundsets: Interface version updated to 11508. - SkuCustomBeaconsAdditional: Interface version updated to 11508, dependencies added. - SkuCustomBeaconsEssential: Interface version updated to 11508, dependencies added. - The corrected TOC files are located next to the Sku folder in the output directory. Simply copy them into the existing addon folders in the game. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v41.01.01 beta. ------------------------------------------------------------------------------------------------------- Changes in Sku v40.06 Wishlist - Add/remove items fixed: The wishlist now reliably responds when adding and removing items. Previously, the action could silently fail if the item was not yet cached or if the menu rebuild triggered an error. - Audio feedback: When adding an item, "Added to wishlist" is now announced; when removing, "Removed from wishlist". Previously there was no feedback at all. - Cache fallback: If an item is not yet in the client cache on the first attempt, it is automatically requested and retried after a short delay. Pet Rename - Confirmation fixed: The two-step process (enter name, then select "Confirm rename") now works reliably again. The faulty menu rebuild after name entry, which triggered the crash "attempt to call method 'BuildChildren' (a nil value)", has been removed. Stable Master - Drag/drop fixed: Pet swapping between active companion and stable slots now uses macrotext in hardware event context. Previously the click ran in addon context and could be blocked by Blizzard. - Purchase price: The "Buy another slot" button now displays the price in gold/silver/copper. - Automatic refresh: After a pet swap or stable slot purchase, the stable master window is automatically refreshed (PET_STABLE_UPDATE event). - Event registration: PET_STABLE_SHOW, PET_STABLE_CLOSED and PET_STABLE_UPDATE are now correctly registered in the SkuDispatcher. The event handlers existed before but were never called. Bug Fixes - VocalizeCurrentMenuName: Nil guard for BuildChildren added. Prevents the generic crash "attempt to call method 'BuildChildren' (a nil value)" which could occur in multiple menu areas (wishlist, pet rename, stable master). Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v40.06. ------------------------------------------------------------------------------------------------------- Changes in Sku v40.05 Wishlist - Sound on chat link: When a wishlist item is linked in party, raid, say or yell chat, the same notification sound now plays as with the loot roll window. - Color codes removed: Dungeon names and boss names from AtlasLoot are now displayed without WoW color codes (|cff...). Affects both the "By dungeon" view and the tooltip with boss drop sources. Bug Fixes - Missing locale entries: L["Up"] and L["Down"] for wishlist item reordering added. Fixes "AceLocale-3.0: Missing entry for 'Down'" error. - Combat log: CombatLog_OnEvent is now checked for availability before calling. Fixes the crash "attempt to call global 'CombatLog_OnEvent' (a nil value)" on TBC Anniversary. - Item list: C_Item.GetItemNameByID is now guarded with a fallback. Fixes the crash "attempt to concatenate a nil value" when an item is not yet cached. - Interact distance: CheckInteractDistance is now called via pcall. Fixes "ADDON_ACTION_BLOCKED CheckInteractDistance()" during combat. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v40.05. ------------------------------------------------------------------------------------------------------- Changes in Sku v40.04.01 Atlas Loot - Atlas Loot is now accessible via Ctrl+Shift+L (when installed). The keybinding can be freely reassigned under Shift+F1, Core, Sku key bindings. - Wishlist with sound: When a wishlist item appears in loot, a sound plays. When the item is received, it is automatically removed from the wishlist. - Wishlist: New menu structure with two submenus "By dungeon" and "By slot". "By dungeon" shows only dungeons that have wishlist items (format: "Count Dungeonname"). "By slot" contains the previous slot-based list. Equipment Manager - The equipment manager is accessible in the character frame (key C). Equipment sets can be saved, equipped, overwritten, renamed and deleted. - Create macro: New menu entry for each equipment set. Creates a character macro that equips the set when executed. Existing macros are updated. - Localization: All menu entries and status messages of the equipment manager are now localized. On English clients all texts appear in English. Target Action Menu (CTRL+T) / Pet Actions - Dismiss fixed: "Dismiss" now uses macrotext (/petdismiss) instead of an addon context call. This avoids ADDON_ACTION_FORBIDDEN and reliably sends the pet away temporarily. - Rename fixed: Pet renaming now works via a two-step process. Step 1: Enter name. Step 2: Select "Confirm rename" (macrotext in hardware context). This avoids ADDON_ACTION_FORBIDDEN when saving the name. - Release with safety check: "Release pet" is now called "Permanently release pet" and requires confirmation through a safety dialog. The previous code called PetAbandon directly, which permanently released the pet without warning the user. PopUp Detection - Delayed second call: StaticPopup_Show now additionally calls CheckFrames with a 0.15 second delay. This ensures popups (e.g. overwrite enchantment, confirm pet rename) are reliably shown in the Sku menu, even when the popup content is not yet fully set on the first call. Error Log (ErrorLog) - Combat throttling: During combat, a maximum of 5 errors per second are logged. Excess errors are discarded. The BugGrabber bridge is fully paused during combat. This prevents the "insecure scripts exceeded execution limit" error in raids (e.g. Karazhan, AQ40). - Localization: All slash command help and status messages are now localized. Localization - Approximately 70 new L[] entries in both locale files (AL_*, EQ_*, ERRLOG_*, MOB_*). - Hardcoded German strings in alIntegration.lua, equipmentSets.lua and ErrorLog.lua replaced with L[] calls. - equipmentSets.lua: Broken locale system (SkuLocale) replaced with Sku.L. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v40.04.01. ------------------------------------------------------------------------------------------------------- Changes in Sku v40.04 Dungeon Browser - Localization: All dungeon browser menu entries (roles, status, dungeon list, player list, actions) now use the localization system. On English clients all texts appear correctly in English. - Heroic dungeons: The dungeon browser now also shows heroic dungeons. These appear in a separate section below normal dungeons with the prefix "Heroic: " before the name. Heroic dungeons can be selected and listed just like normal dungeons. - Keybinding: The dungeon browser now has its own keybinding under Shift+F1, Core, Sku key bindings, Assign key, "Open dungeon browser". No default key is assigned; the user can freely assign one. - Deselect all: New "Deselect all" button in the dungeon browser. Resets all dungeon selections at once. - Display: The "Selected" indicator is now placed before the dungeon name (instead of after), so blind players immediately hear whether a dungeon is already marked when scrolling through the list. - Technical: ~39 new L[] entries (DB_*) in both locale files. Hardcoded strings in dungeonBrowser.lua replaced with L[] calls. Two "not info.isHeroic" filters removed (tGetEligibleDungeons and tBuildPhaseB). Heroic dungeons displayed as separate section with L["DB_HeroicSection"] in Phase A. SKU_KEY_OPENDUNGEONBROWSER defined in SkuKeyBinds.lua, handler and override binding in SkuZOptions/Core.lua. DungeonBrowserDeselectAll() sets db.selection = {} and triggers menu rebuild. Navigation / Nearby Route - Bugfix: "Nearby Route" no longer aborts after the first waypoint. Navigation via "Nearby Route" (in the quest book and in the SkuNav menu) was incorrectly terminated with "Route following ended" after reaching the first waypoint instead of continuing to the destination. This error occurred across all zones and affected all nearby route navigations. The problem is fixed -- nearby routes now reliably guide to the destination. - Technical: The BuildChildren of the "Closest route" handlers in SkuQuest/Options.lua (lines 1101ff) and SkuNav/Options.lua (lines 447ff) used a flat structure that set metapathFollowingStart and metapathFollowingMetapaths once for the first entry point. The already-corrected "Route" handler set metapathFollowingTarget = nil in its per-entry-point submenus, which destroyed the nearby route data when navigating between siblings. Fix: Same restructuring to per-entry-point submenus as the "Route" handler -- each entry point computes its own metapaths in its own BuildChildren function. Chat Invite - Bugfix: The invite function in the chat context menu (CTRL+Enter) works again. Previously an error could occur when InviteUnit was not available as a global function. - Technical: SkuChat/Core.lua line 2688: InviteUnit() replaced with C_PartyInfo.InviteUnit() fallback (same pattern as SkuMob/Options.lua). Target Action Menu (CTRL+T) - Pet actions expanded: Two new entries in the pet submenu. "Dismiss" sends the pet away temporarily (it can be called back at any time). "Rename" opens an input field where a new name can be entered and confirmed with Enter. - Localization: All entries of the target action menu (Inspect, Follow, Trade, Duel, Whisper, Invite, Add friend, Ignore, Promote leader, Remove, Pet actions, Pet mode, Raid markers, Report player, Loot method, Reset instances, Leave group) are now localized. On English clients all texts appear in English. - Technical: SkuMob/Options.lua: ~78 new L[] entries (MOB_*) in both locale files. All hardcoded strings in tRaidMarkers, tReportReasons, tLootMethods, tBuildPetModeSubmenu and tBuildTargetMenu replaced with L[] calls. PetDismiss (temporary only) and PetRename (via EditBoxPasteShow) added as new tAddAction entries in the tIsPet branch. Character Frame (Equipment Slots) - Bugfix: Wizard oil, poisons and sharpening stones can now be applied to equipped weapons through the Sku menu without ADDON_ACTION_FORBIDDEN errors. Previously the click on equipment slots was executed from addon context which was blocked by Blizzard. - Technical: SkuZOptions/Core.lua line 3958: Equipment slot left-click now uses macrotext ("/click SlotName LeftButton") instead of pure OnAction with PickupInventoryItem. This runs the click in hardware event context so Blizzard allows the interaction. The previous OnAction code remains as fallback. Role Poll - Bugfix: The manual role poll (Tank/Healer/Damage) no longer causes a crash. Previously an error could occur when the role buttons had no .role property, leading to a nil error that required a reload to play again. - Technical: SkuCore/LocalMenu.lua line 2026: Nil guard and fallback role names (TANK/HEALER/DAMAGER) added. tButtons[x] is additionally checked for existence. Aura Export - Bugfix: "Export all auras" works again and no longer throws an error. The GetAddOnMetadata function was not available in newer WoW versions and caused a crash during export. - Technical: SkuAuras/Options.lua line 812: GetAddOnMetadata replaced with safe fallback pattern (GetAddOnMetadata or C_AddOns.GetAddOnMetadata, with "unknown" as final fallback). Same fix also in SkuZOptions/Core.lua line 5252 (waypoint export). /sku version Command - Bugfix: The chat command "/sku version" now reliably outputs the addon version. Previously the output could be empty when GetAddOnInfo was not available. - Technical: SkuZOptions/Core.lua line 128: GetAddOnInfo supplemented with safe GetAddOnMetadata fallback (same pattern as ErrorLog.lua). Output now shows title and version number. Minimap Resource Scan - Bugfix: Passive minimap monitoring (ore, herbs, etc.) no longer intercepts mouse clicks. Previously, clicking at various points in the addon could produce a "ping" sound and the click would not be executed because the invisible 1-pixel minimap under the cursor intercepted the click. - Technical: SkuCore/minimapScanner.lua: In MinimapScanFast(), all visible minimap children (especially MiniMapTrackingFrame) are now hidden before positioning the minimap under the cursor. Protection ported from version 1.x (lines 479-484). RestoreMinimap restores children via MMA_VISIBLE. - Bugfix: Herb finding, mining and other tracking functions now work correctly with passive minimap scanning. Previously, tracking was disabled on every scan cycle and could not be restored because C_Minimap.GetTrackingInfo return values (table instead of individual values) were unpacked incorrectly. Herbs and ores stopped appearing on the minimap even though the buff bar showed tracking as active. - Technical: GetTrackingInfo calls in tCaptureMinimapState() and the tracking disable loop of MinimapScanFast() now use the same type(result)=="table" check as SlashActiveSeekings and SkuZOptions/Core.lua. tCaptureMinimapState/tApplyMinimapState correctly save and restore tracking state. - Bugfix: The passive minimap scan now announces herbs, ores and other resources via speech while moving again. Previously no announcement was made because the tooltip matching in the passive scan path could never correctly identify resource names -- the matching logic used exact equality instead of substring search and failed on special characters (apostrophes in "Khadgar's Whisker" etc.) or missing dependencies. - Technical: The C_Timer.After callback in MinimapScanFast() now uses the same string.find substring match as the active scan (MinimapScanFindActiveRessource). SkuChat:Unescape is used optionally but no longer blocks processing. gmatch fragmentation and exact equality check removed. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v40.04. ------------------------------------------------------------------------------------------------------- Changes in Sku v40.03y Mailbox - Multiple attachments: after taking a single attachment, the remaining attachments in the same mail are reliably read out by Sapi again. Custom auto-jump path removed -- the mailbox now uses the standard refresh again (MAIL_INBOX_UPDATE, currentMenuPosition:OnUpdate), exactly like a fresh mailbox open. Atlas Loot - Open keyboard shortcut is now freely rebindable: under Shift+F1, Core, Sku key bindings, Assign key the entry "Open Atlas Loot" appears. The default key remains Ctrl+Shift+L; the user can rebind it like any other Sku shortcut. The new binding takes effect immediately. - Context-aware jump into boss/instance submenu significantly improved: Path order now matches the actual menu hierarchy (Lists, Plugin, Expansion, Group, Instance/Raid, Boss, Difficulty, Items). The difficulty was previously placed before the instance -- the path could never resolve. Difficulty detection has three fallback layers: primary GetInstanceInfo, secondary the character portrait setting (GetDungeonDifficulty / GetRaidDifficulty -- locked in by the server when entering the instance and therefore reliable), tertiary default "Normal" for world bosses. Lookup tables are now eagerly populated already at login (with staggered retries 5/10/20/35/60 s) and on zone change into an instance. The shortcut therefore works without a cold-start delay on the first key press. - Wishlist auto-remove: as soon as the "I feel Good" sound plays (a wishlist item has entered the inventory), the item is automatically removed from all wishlist slots. Comparison is primarily by itemID, with a hyperlink-pattern fallback. Items like rings or trinkets that can sit in two slots simultaneously are fully cleaned up. Character Frame (equipment slots) - Left-click / right-click on equipped items works again. On TBC Anniversary 2.5.5 a /click LeftButton without a modifier only triggers UseInventoryItem -- useless for armor, rings and trinkets. Sku now uses a direct API path for equipment slots: Left-click: PickupInventoryItem(slotID) -- the item attaches to the cursor (for swapping, selling or socketing). Right-click: unequip -- PickupInventoryItem + iteration through the bags, first empty slot, PickupContainerItem. If all bags are full the cursor is cleared and a Sapi notice "No free bag space" is announced. On an empty slot an "Empty" notice is announced. Dungeon Browser - Auto-refresh removed: the player list is no longer automatically refreshed every 10 seconds. The previous ticker incorrectly affected all Sku menus (currentMenuPosition:OnUpdate independent of the current menu path) and interrupted announcements in completely unrelated submenus. - Manual "Refresh list" button: triggers a /who search and rebuilds the dungeon browser menu specifically after 0.6 s. Cross-menu protection: if the user navigated away in the meantime nothing happens. - Sapi feedback after a successful refresh: "Updated. N players found" (or "No players found" / "1 player found"). Issued with a delay after the OnUpdate vocalization so the status announcement is not suppressed. - Phase transition A to B (sign-up): two staggered passes -- the first pass announces the new status via Sapi, the second pass runs silently in the background (DungeonBrowserRebuildSilent) to integrate the players received via /who. The status announcement now plays through completely while the player list is populated regardless. - Phase transition B to A (sign-off): double rebuild reduced to one; Sapi announcement is no longer cut off. - Behavior on group invite: the Sku menu stays open while an invite popup is open. Sku's standard path focuses the popup so the user can interact with Yes/No directly. On accept, Blizzard automatically closes the container frame -- the Sku menu follows and closes too. On decline the container frame stays open and so does the Sku menu. Stability / quest tooltip - Crash on quest tooltip request (Shift+Arrow Down) in Shadowmoon Valley fixed. Cause: tInternalChannelName could become nil in CHAT_MSG_CHANNEL_NOTICE/YOU_LEFT (zone-specific channel without a mapping), string.lower(nil) crashed. The follow-up Lua error wiped out the currentMenuPosition pointer, causing all subsequent arrow/Enter key presses to cascade with "attempt to index nil" -- the Sku menu was unusable until reload. - Three combined safety layers: 1. Direct nil-guard in SkuChat/Core.lua before the string.lower call. 2. Defensive nil-protection in the SkuGenericMenuItem OnUpdate (templates.lua) for currentMenuPosition:OnEnter / VocalizeCurrentMenuName. 3. Recovery guard in the OnClick key dispatcher: on a nil currentMenuPosition Menu[1] is set as a fallback, otherwise the key press is silently discarded -- no more error spam. Version Display - Sku.toc and SkuDispatcher/Sku.toc updated to title/version v40.03y. -------------------------------------------------------------------------------------------------------