Sku v43.7 - patch notes

Overview

Changes

Bugfixes

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 <delay> <hold>, /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".