-------------------------------------------------------------------------------------------------------
- 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.
-------------------------------------------------------------------------------------------------------