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 <name> 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 "<partner> 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 <bag> <slot>") 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 <frame> 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 <slot>". 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 <name>" 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 <id>", 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.