------------------------------------------------------------------------------------------------------- - Notes de version de l'addon Sku Anniversary (Français) ------------------------------------------------------------------------------------------------------- Sku parle français depuis la v42.11 ; ces notes commencent donc à cette version. Pour les versions antérieures, voir « Patch Notes Sku EN.txt » ou « Patch Notes Sku DE.txt ». ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.10 Aperçu Nouveautés Auras : nouvelle aura de base « Votre cible passe sur un membre du groupe ». Quand l'ennemi que vous ciblez se tourne vers un autre membre, un son est joué et le membre est nommé par son numéro de touche, en groupe comme en raid. Changements Détection des rôles : le moniteur de santé détecte maintenant automatiquement tank, soigneur et dégâts aussi pour les membres de raid 26 à 40. Avant, seulement jusqu'à la place 25. Focalisation : /focus1 à /focus8 acceptent maintenant toutes les places de raid (raid1 à raid40), avant seulement jusqu'à raid25. Auras : nouvelles valeurs d'unité « membres du groupe ou du raid » et « ... sauf vous ». Elles couvrent tout le raid ; les valeurs de groupe existantes ne couvrent en raid que votre propre sous-groupe. Corrections Moniteur de combat : depuis la v43.9, les alertes de menace déclenchaient une erreur Lua (aqCombat.lua:380, UnitGUID) et restaient muettes, par exemple quand on prenait l'aggro en groupe. Corrigé. Moniteur de combat : les alertes de menace s'entendent à nouveau Simple : Prendre l'aggro en groupe, ou en être proche, donnait une erreur au lieu de l'alerte depuis la v43.9. Les alertes « menace haute » et « menace basse » reviennent. Elles ne nomment aucun joueur et ne l'ont jamais fait ; les notes de la v43.9 le décrivaient à tort. Technique : les quatre sorties des deux alertes passaient unit1 = tAllPartyRaidUnits[x] sans x défini à cet endroit, donc toujours nil ; leurs modèles ne contiennent pas ${unit1}. La v43.9 faisait passer la valeur par tSpokenGroupUnit, qui appelait UnitGUID(nil). L'argument est retiré, et tSpokenGroupUnit renvoie nil pour nil. Détection des rôles : places de raid 26 à 40 Simple : Depuis la v43.9 le moniteur de santé du raid annonce aussi les membres 26 à 40, mais leur rôle n'était pas détecté automatiquement. C'est fait maintenant. Technique : RoleCheckerIsUnitGUIDInPartyOrRaid ne connaissait que raid1 à raid25 ; la limite est supprimée. RoleCheckerGetUnitRole plantait quand le membre demandé était retiré des données de rôle comme ne faisant plus partie du groupe, et sa dernière ligne lisait une variable hors de portée. Les deux sont corrigés. Focalisation : les 40 places de raid Simple : « /focus3 raid30 » enregistrait le texte « raid30 » comme nom, et la touche de focalisation ne trouvait alors personne. Maintenant c'est le nom du membre qui est enregistré, comme pour raid1 à raid25. Technique : tFocusUnitIds (skuFocus.lua) ne connaissait raid, raidpet et leurs chaînes target que de 1 à 25, maintenant jusqu'à MAX_RAID_MEMBERS. Auras : aura de base « Votre cible passe sur un membre du groupe » Simple : Pour les tanks. Sous Nouvelle aura, Auras de base. Quand l'ennemi que vous ciblez passe sur un autre membre du groupe ou du raid, un son est joué et Sku dit qui : « party 3 » en groupe (la touche du pavé numérique), le numéro de dial en raid. Quand il revient sur vous, rien n'est dit. « Qui » permet de restreindre les membres surveillés. C'est l'aura de l'ancien set du guerrier protection, qu'on ne pouvait plus charger depuis longtemps, avec une condition plus précise. Technique : événement UNIT_TARGETCHANGE, source contient target et est attaquable, destination = la valeur choisie (préréglée raidWoPlayer), sorties son plus unité cible. L'ancien set ne vérifiait que « la source n'est pas un membre du groupe » ; comme Sku suit les cibles des 40 places de raid, un soigneur d'un autre sous-groupe qui sélectionnait quelqu'un la déclenchait aussi. Les auras de base peuvent maintenant demander autre chose qu'un sort (valueLabel, emptyValueMessage, defaultValues). Auras : valeurs pour tout le raid Simple : Source, cible et « cible de votre cible » ont deux nouvelles valeurs : « membres du groupe ou du raid » (vous compris) et « membres du groupe ou du raid sauf vous ». En groupe elles se comportent comme les valeurs de groupe, en raid elles couvrent tous les membres. Les anciennes valeurs de groupe restent telles quelles : en raid, seulement votre sous-groupe. Technique : tEvaluateRaidValue (data.lua) cherche party1-4 et raid1-40 dans l'ensemble d'unités. « Sauf vous » veut dire : pas de « player » dans l'ensemble, car en raid GetBestUnitId renvoie aussi votre propre place raidN. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.9 Aperçu Nouveautés Touches de sous-groupe de raid : Alt+pavé 1 à 8 cible le premier membre du sous-groupe de raid 1 à 8. Si le groupe est vide, Sku dit « Groupe N vide » et rien ne se passe ; hors raid « Pas de raid ». Sous-menu propre « Touches de sous-groupe de raid » dans les raccourcis Sku, désactivable sous Fonctions. Changements Auras : les auras de base proposent désormais sous « Sortie » la même liste de sorties que les auras personnalisées, c'est-à-dire tous les sons et toutes les sorties de données (par exemple le nom du sort ou l'unité cible), plusieurs à la fois. Avant, il n'y avait qu'un seul son. Fenêtres de métier et de maître : après fabrication, apprentissage ou sélection, le menu se rafraîchit en silence, le curseur reste sur la même entrée, et seule une entrée vraiment différente est annoncée. Moniteur combat : « Annoncer les morts » nomme désormais, en groupe, la touche du pavé numérique du membre mort : « party 5, dead » signifie que le pavé 5 est mort. Cela change un très vieux standard ; le quatrième membre du groupe était annoncé « party 4 ». Moniteur combat : en raid, « Annoncer les morts » dit le numéro de ciblage au cadran du membre mort, c'est-à-dire la touche ou les deux chiffres qui le ciblent. Avant : « party 2 » pour son propre sous-groupe et « raid 17 » pour tous les autres. Les places de raid 26 à 40 sont désormais annoncées aussi. Moniteur combat : « Compter les morts » est supprimé. Moniteur santé : « Ajouter Mort à 0 pour cent » est désormais désactivé par défaut, pour le groupe et le raid, y compris pour les personnages existants. Ciblage au cadran : uniquement en raid désormais. « Activé » est un interrupteur Activé/Désactivé ; en groupe, les touches 1 à 5 du pavé numérique restent les touches de ciblage normales (1 = soi, 2 à 5 = les autres). Ciblage au cadran : dans les raids jusqu'à 10 joueurs, toujours une touche par membre (0 = membre 10), deux chiffres à partir de 11 joueurs. L'option « Action à touche unique dans les raids jusqu'à 10 joueurs » disparaît, c'est la règle désormais. Moniteur santé, raid : les numéros sont les numéros du ciblage au cadran, donc 1 à 10 dans l'ordre des sous-groupes pour les raids jusqu'à 10 joueurs. Avant, toujours le schéma à deux chiffres ; dans un raid de 10 avec quelqu'un dans le groupe 3, la touche 7 s'appelait « 11 ». Moniteur santé, raid : les membres numérotés 26 à 40 sont désormais prononcés (numéro, puis santé par dizaines) avec le nouveau réglage « Voix pour les membres au-delà de 25 ». Les fichiers de hauteur n'existent que jusqu'à 25, ces membres restaient muets. Moniteur debuffs, raid : les numéros sont également ceux du ciblage au cadran. Moniteur combat : les avertissements de menace, « cible de la cible » et « hors de portée » nomment les membres comme l'annonce de mort, avec la touche du pavé numérique en groupe (« party 2 » pour le premier membre) et le numéro du cadran en raid. Avant : la place de raid brute et « party 1 » pour la touche 2 ; les places de raid 26 à 40 manquaient entièrement. Auras : les sorties « unité source » et « unité cible » nomment aussi les membres du groupe par numéro de touche et les membres du raid par numéro du cadran. Corrections Menu des quêtes : « Itinéraire » sous une créature était toujours vide quand son nom contient un trait d'union ou une parenthèse (par exemple « Gorille Crins-de-ciel »). Corrigé. Fermer l'itinéraire : la recherche des points de passage reliés près d'une cible renvoyait souvent moins que les dix plus proches. N'a très probablement jamais touché personne, correction de pure exactitude. Auras : « Supprimer toutes les auras » supprimait toutes les auras d'une seule touche, sans demander. Désormais « Vraiment supprimer ? » est demandé d'abord, comme pour la suppression d'une seule aura. Fenêtres de métier et de maître : après chaque fabrication ou apprentissage, le nom de la fenêtre était annoncé et le curseur sautait parfois sur une autre entrée. Corrigé. Fenêtres de métier : Entrée sur une recette ou sur « Créer » annonçait l'entrée deux fois. Corrigé. Boîte aux lettres : à l'ouverture, « Cible » était annoncé avant « Nouveau courrier ». Corrigé. Raccourcis clavier : la seconde touche (« Réassigner la touche secondaire ») restait sans effet pour plusieurs touches, dont « Déclencher la sortie continue du moniteur de groupe », distance de la cible, mode panique, scan de la minicarte, vérification de portée du groupe, « Tourner vers l'unité » et les touches de scan. Corrigé. Moniteur combat : « Ignorer les familiers morts du groupe » revenait sur « Oui » après chaque rechargement. Corrigé. Moniteur debuffs, raid : la sortie continue disait la place de raid plus un, un numéro qui n'appartenait à personne. Corrigé. Moniteur combat : « party 5, dead » (quatrième membre du groupe, nouveau dans cette version) bipait avec « Numéros seulement » désactivé, car il n'existe pas de mot vocal « party5 ». Désormais « party » puis « 5 » sont dits. Menu des quêtes : « Itinéraire » trouve les créatures avec un trait d'union dans leur nom Simple : Dans le menu des quêtes (Début de quête, Cible de quête, Livraison), une créature propose « Itinéraire », « Fermer l'itinéraire » et « Point de passage ». « Itinéraire » disait « liste vide » dès que le nom de la créature contenait un trait d'union, une parenthèse ou un caractère semblable, même quand la créature est bien reliée au réseau de chemins. Sur un client allemand, cela touchait 249 créatures, dont les trois espèces de singes du cratère d'Un'Goro. « Fermer l'itinéraire » et « Point de passage » n'étaient pas concernés. Technique : SkuQuest/Options.lua, CreateRtWpSubmenu, branche « Itinéraire » : string.find(i, wpName) sans l'argument plain lisait le nom du point de passage comme un motif Lua. Là, « - » est un quantificateur (« o- » = un nombre quelconque de o), le nom ne se trouvait donc jamais lui-même. Désormais string.find(i, wpName, 1, true) aux deux endroits. Les données d'itinéraire du cratère étaient correctes (vérifié : liens, indices d'apparition et raccordement au réseau). Fermer l'itinéraire : les dix points de passage reliés les plus proches sont vraiment trouvés Simple : La plupart des créatures ne sont pas elles-mêmes reliées au réseau de chemins. Pour « Fermer l'itinéraire », Sku cherche donc jusqu'à dix points de passage reliés dans un rayon de 500 mètres autour de la cible et prend le meilleur qui soit accessible. Cette recherche en renvoyait souvent moins de dix. Le point le plus proche en faisait pourtant toujours partie, et le réseau de chemins est connexe presque partout. Cela n'a très probablement jamais touché personne ; la correction n'est là que pour l'exactitude. Le défaut n'aurait pu se voir que si le point le plus proche se trouve sur un morceau détaché du réseau : alors « liste vide » ou un petit détour. Technique : SkuNav:GetNearestWpsWithLinksToWp (SkuNav/Core.lua). L'insertion triée n'avait pas de cas d'ajout en fin de liste : un candidat plus éloigné que toutes les entrées était écarté alors que la liste avait encore de la place, et le premier résultat de pairs() fixait ainsi le plafond. La coupe au-delà de N entrées retirait déjà toujours la plus éloignée et reste en place. Vérifié hors jeu contre « les N plus proches » (20000 cas aléatoires) : ancien 9059 trop courts, nouveau tous corrects. Les cinq appelants parcourent toute la liste et prennent la meilleure entrée accessible ; davantage de candidats ne peut donc donner que des itinéraires aussi bons ou meilleurs. Auras : les auras de base ont la liste de sorties complète Simple : Sous « Nouvelle aura », « Auras de base », chaque modèle avait une entrée « Son » où l'on pouvait choisir exactement un son, rien d'autre. Cette entrée s'appelle désormais « Sortie », suivie du nombre de sorties activées, et ouvre la même liste qu'une aura personnalisée : tous les sons et toutes les sorties de données, par exemple le nom du sort ou l'unité cible. Chaque entrée est un interrupteur, Entrée l'active ou le désactive, et plusieurs peuvent être actives à la fois. Ce que le modèle imposait jusqu'ici (son son, plus le nom du sort et l'unité cible pour « Votre amélioration sur un membre du groupe a expiré ») est déjà activé à l'ouverture et peut maintenant aussi être désactivé. Technique : SkuAuras/Options.lua. tBuildOutputsLevel accepte un contexte optionnel (outputs, ownerLabel, tooltip) ; sans lui, la liste modifie comme avant le brouillon du constructeur d'auras, avec lui la liste du formulaire de base. Le formulaire (tBaseForm) tient une liste outputs au lieu d'une seule valeur sound, préremplie à partir de defaultSound et defaultOutputs (auparavant fixedOutputs, ajoutées d'office à la création). Le résumé et tBaseFormCommit lisent cette liste ; sans aucune sortie active, le message reste « Aucune sortie définie ». Auras : « Supprimer toutes les auras » demande confirmation Simple : « Supprimer toutes les auras » dans le menu Auras n'avait pas de niveau en dessous : une touche Entrée ou Flèche droite, et toutes les auras du personnage avaient disparu. Désormais l'entrée mène à « Vraiment supprimer ? », comme la suppression d'une seule aura. Seul Entrée à cet endroit supprime, Flèche gauche annule. Technique : SkuAuras/Options.lua, MenuBuilder : l'entrée est désormais dynamic et son BuildChildren crée l'unique enfant « Vraiment supprimer ? ». En y entrant, selectTarget pointe sur l'entrée elle-même, Entrée sur l'enfant appelle donc son OnAction. Avant, dynamic = false sans enfants, OnPostSelect passait directement dans la branche OnAction. Menu : les chemins sont résolus au lieu d'être parcourus Simple : Chaque fois que Sku place lui-même le menu à un endroit donné (une fenêtre s'ouvre, une touche rapide comme Maj-F10, ou une fenêtre a changé et son menu est reconstruit), il parcourait jusqu'ici le chemin comme si Entrée était pressée à chaque niveau, en annonçant chaque étape. Lors de la reconstruction silencieuse d'une fenêtre de métier ou de maître après une fabrication ou un apprentissage, on entendait donc le nom de la fenêtre (« Métier », « Maître »), et le curseur était remis par numéro de ligne dans une liste qui venait de changer. Désormais le chemin cible est résolu directement, sans annonce des étapes, et le curseur revient sur la même entrée : la même recette (même avec un nouveau nombre), la même compétence, le même bouton. Ce n'est que si cette entrée a disparu que l'endroit où le curseur a atterri est annoncé. Technique : SkuOptions:ResolveMenuPath et EnsureLevelBuilt (SkuZOptions/Core.lua) remplacent la boucle de SlashFunc qui appelait OnSelect(true) sur chaque segment du chemin. Les niveaux traversés sont seulement construits (dynamic : RebuildNodeChildren, sinon un BuildChildren s'ils sont vides), jamais sélectionnés ; seule la destination reçoit son OnSelect comme avant (préconstruction d'un niveau vide non dynamique, jamais sur isSkuToggle). Les effets de bord du parcours disparaissent avec lui : les nœuds actionOnEnter sur le chemin se déclenchaient, tous les frères avant la correspondance étaient préconstruits. Le paramètre aSilent de SlashFunc est désormais effectif. La restauration dans SkuCore:CheckFrames fait un appel silencieux au lieu de deux parlants et place le curseur directement : skuIdentity, puis nom, puis la ligne à l'ancien index (bornée à la nouvelle longueur). Les entrées de fenêtre portent skuIdentity (copié par SkuIterateGossipList) : recettes « recipe:Nom (rang) », catégories « cat: », compétences du maître « skill: », sinon « frame: » pour toute entrée seule sur son frame (SkuCore:PrepareWindowEntries à la fin des trois constructeurs). SkuCore:RefreshWindowMenuQuietly (LocalMenu.lua) est la reconstruction silencieuse commune pour TRADE_SKILL_UPDATE/CRAFT_UPDATE et le bouton Apprendre ; elle ne parle que si l'identité (ou le nom sans le nombre) est différente ensuite. Les sacs gardent leur propre chemin (tFindMenuNodeByPath, tBagAnnounceSuppress). Fenêtres de métier : Entrée sur une recette ou « Créer » annonçait l'entrée deux fois Simple : Après Entrée sur une recette ou un bouton Créer, l'entrée était dite deux fois. Désormais une fois. Technique : Le chemin de clic classique dans SkuIterateGossipList appelait après func un CheckFrames parlant puis OnUpdate 0,35 s plus tard, en plus de l'annonce du gestionnaire de touches. La troisième annonce était le plus souvent absorbée par le garde-doublon (1 s), la deuxième arrivait 0,9 s plus tard et ne l'était pas. Les entrées avec quietClick (posé par PrepareWindowEntries pour tout bouton cliquable sans containerFrameName) passent désormais par func plus RefreshWindowMenuQuietly ; les boutons à /click sécurisé gardent l'ancien chemin. Boîte aux lettres : « Cible » avant « Nouveau courrier » Simple : À l'ouverture d'une boîte aux lettres, « Cible » était annoncé d'abord, puis seulement « Nouveau courrier ». Désormais seulement « Nouveau courrier ». Technique : Mail:MAIL_SHOW ouvrait le chemin « Local, Courrier » avant que le MailFrame soit visible, or l'entrée Courrier sous Local n'existe que lorsque son frame est visible. Le chemin ne trouvait rien, et l'ouverture silencieuse annonçait à la place la première entrée racine (« Cible », le menu de cible) ; les sacs ramenaient le menu dans le courrier un instant plus tard via CheckFrames. Le journal de débogage montrait « path walk found nothing ... short,lokal,post » à chaque boîte aux lettres depuis au moins la v43.6 ; cela ne s'est remarqué qu'une fois les annonces intermédiaires disparues. Désormais MAIL_SHOW ouvre une image plus tard (C_Timer.After(0)), seulement avec un MailFrame visible et seulement si le curseur n'est pas déjà dans le courrier. Raccourcis clavier : seconde touche pour le moniteur, le scan, la rotation et les autres Simple : Sous « Raccourcis clavier Sku », chaque touche a une première et une seconde assignation. Pour plusieurs touches, la seconde pouvait être définie et était annoncée comme « Touche 2 », mais ne faisait rien. Étaient concernées « Déclencher la sortie continue du moniteur de groupe », distance de la cible, mode panique, scan de la minicarte large et étroit, vérification de portée des membres du groupe, « Tourner vers l'unité » 1 à 6 et 180 degrés, « Continuer le scan », scan 1 à 8 et la notification de ressources. La seconde touche fonctionne désormais partout. Technique : SkuCore/Core.lua, script OnHide de SkuCoreControlOption1 : les assignations de ce frame étaient armées avec SetOverrideBindingClick pour .key seulement (seule exception SKU_KEY_TAXICANCEL), alors que le dispatcher de clic vérifie les deux champs via SkuKeyBindsMatchKey. Les menus d'assignation dans SkuZOptions/Core.lua armaient key2 depuis toujours, d'où le bon fonctionnement de la seconde touche pour d'autres actions. Un helper local tArm(aConst) lie désormais chaque touche renvoyée par SkuKeyBindsGetKeys ; les vingt lignes individuelles écrites à la main disparaissent. Moniteur combat : l'annonce de mort nomme la touche du pavé numérique Simple : Sous Moniteur, Combat, Amical, « Annoncer les morts » annonce un membre du groupe mort. Le numéro qu'elle contient est désormais toujours la touche qui cible ce membre. En groupe, ce sont les touches par défaut de Sku : pavé 1 = soi, 2 à 5 = les quatre autres. Le quatrième membre du groupe devient donc « party 5, dead », et non plus « party 4 ». Le moniteur de santé et l'aperçu ont toujours compté ainsi. En raid, c'est le numéro de ciblage au cadran qui est dit : la touche unique jusqu'à 10 joueurs, la séquence à deux chiffres à partir de 11. Avant, on entendait « party 2 » pour les membres de son propre sous-groupe et « raid 17 » (la place brute du raid) pour tous les autres, deux numérotations dans un même combat, aucune ne correspondant au pavé. Les places de raid 26 à 40 n'étaient pas annoncées du tout. « Compter les morts » est supprimé : en mode « numéro seul », son « 3, dead » ne se distinguait pas de « party 3, dead », et le compteur ne redescendait jamais lors d'une résurrection. Technique : SkuCore/aqCombat.lua, aqCombat_SKU_UNIT_DIED. Nouveau helper tDeathUnitToken(GUID) renvoyant séparément le jeton parlé et l'id d'unité réel, les vérifications UnitIsDeadOrGhost et familier ont besoin de la place brute. Groupe : jeton de aqCombatGroupGuidToUnitId, chiffre plus 1. Raid : toutes les places MAX_RAID_MEMBERS parcourues par GUID (les 25 de tAllPartyRaidUnits ne suffisaient pas), numéro lu dans la grille du ciblage au cadran (SkuSecureTargetingFrame, unitNameSlotGG-SS, touche = (GG-1)*5+SS ; du code non sécurisé peut lire les attributs d'un cadre sécurisé), sinon tDTRaidRoster de aq.lua, sinon calcul direct (sous-groupe-1)*5+position. Grille et roster ne sont reconstruits qu'hors combat, ils vieillissent donc avec le pavé. partyDeadCount, son compteur, son menu et sa clé de locale supprimés, la valeur enregistrée est effacée à la connexion. Moniteur combat : « Ignorer les familiers morts du groupe » reste sur Non Simple : Le réglage pouvait être mis sur « Non » mais revenait sur « Oui » après le rechargement suivant. Corrigé. Technique : aqCombatOnLogin fixait la valeur avec « x or true » ; un false enregistré est faux en Lua et était remplacé par true à chaque chargement. Désormais seulement si nil. Moniteur santé : « Ajouter Mort à 0 pour cent » désactivé par défaut Simple : Le style hauteur de son du moniteur de santé de groupe et de raid ajoute un clip « dead » au son d'un membre à 0 pour cent. C'était activé pour le groupe et le raid et c'est désormais désactivé, y compris pour les personnages existants. Pour le réactiver : Moniteur, Groupe ou Raid, Santé. L'annonce de mort du moniteur de combat (ci-dessus) devient ainsi le seul message de mort actif par lui-même. Technique : SkuCore/aq.lua, AqOnLogin : valeur par défaut false au lieu de true, bascule unique des personnages existants via addDeadOn0PercentDefaultOff (même schéma que *DefaultOff dans aqCombat). Ciblage au cadran : uniquement en raid, une touche jusqu'à 10 joueurs Simple : « Activé » sous Ciblage au cadran est désormais un interrupteur Activé/Désactivé et n'agit qu'en raid. Les choix « Groupe » et « Groupe et raid » disparaissent : en groupe, les touches par défaut de Sku, pavé 1 à 5, atteignent déjà tout le monde, et le mode groupe posait par-dessus une numérotation décalée (0 = soi, 1 à 4 = les autres) et masquait les touches par défaut tant qu'il était actif. Un « Groupe » enregistré devient Désactivé, « Raid » et « Groupe et raid » deviennent Activé. Dans les raids jusqu'à 10 joueurs, une touche par membre est désormais toujours active (pavé 1 à 9, 0 = membre 10, dans l'ordre des sous-groupes), à partir de 11 joueurs la saisie à deux chiffres. L'option « Action à touche unique dans les raids jusqu'à 10 joueurs » disparaît, c'est la règle désormais. Technique : SkuCore/DialTargeting.lua. enabled est converti une fois à la connexion en Désactivé/Activé, singleKeyinRaid10 effacé. DialTargetingRosterUpdate et DialTargeting_EndableDisable ne vérifient plus que UnitInRaid et enabled == Activé ; la branche groupe (unitNameSlot01-0x = partyx) et la branche « party » morte du snippet sécurisé sont supprimées. Mode raid : tNumCurMembers > 10 signifie deux chiffres, sinon raid10. Trois clés de locale supprimées. Rien de tout cela n'est testé en jeu. ------------------------------------------------------------------------------------------------------- Numéros de groupe : un seul comptage pour tous les moniteurs Simple : Chaque numéro que Sku dit pour un membre du groupe doit être la touche qui le cible. En groupe, c'est le pavé numérique 1 pour vous et 2 à 5 pour les autres ; en raid, le numéro du ciblage au cadran, une touche 1 à 10 jusqu'à 10 joueurs et deux chiffres à partir de 11. L'annonce de mort compte ainsi depuis cette version. Désormais le moniteur de santé et le moniteur de debuffs en raid, les avertissements de menace, « cible de la cible » et « hors de portée » du moniteur de combat ainsi que les sorties d'aura « unité source » et « unité cible » le font aussi. Les moniteurs de santé et de debuffs du groupe gardent le comptage de groupe en raid, car qui ne surveille que son sous-groupe le cible là avec le pavé 2 à 5. Le moniteur de santé en raid n'a des fichiers de hauteur que pour les numéros 1 à 25, les membres 26 à 40 restaient donc muets. Ils sont désormais prononcés, le numéro d'abord, puis la santé par dizaines, avec la voix du nouveau réglage « Voix pour les membres au-delà de 25 » (Moniteur, Raid, Santé ; Justin par défaut). Technique : SkuCore/aq.lua : SkuCore.Monitor.RaidMemberNumbersByName() construit la table nom -> numéro selon la règle du cadran (jusqu'à 10 membres à plat 1-10 dans l'ordre des sous-groupes, à partir de 11 (sous-groupe-1)*5+position) ; tDTRaidRoster et la grille du ciblage au cadran (DialTargeting.lua) en sont remplis, tDTRaidRoster calculait toujours à deux chiffres. SkuCore.Monitor.GroupMemberNumber(unitId) renvoie le numéro de touche des jetons party/raid. La sortie continue des debuffs en raid disait string.sub(unitId, 5)+1. MonitorOutputRaidPercent2 prononce les numéros au-delà de 25 via MonitorOutputPlayerStatus (raid.health2.voice, 1 par défaut) et renvoie la durée à la file ; les entrées de la file portent pour cela la santé absolue. SkuCore/aqCombat.lua : tAllPartyRaidUnits et tUnitsToTestOnGameRaidTargets contenaient raid1-25 en dur ; ils sont désormais regarnis sur place d'après la taille réelle du raid à chaque événement de roster. tSpokenGroupUnit(unitId) applique la correspondance de l'annonce de mort à toutes les annonces amicales. « party5 »/« partypet5 » n'existent pas comme mots dans les voix de combat, SkuCoreAqCombatGetVoiceString les découpe en « party », « pet », « 5 ». SkuAuras/data.lua : tUnitIdToSpokenName dit party N comme N+1 et raid N comme numéro du cadran. Touches de sous-groupe de raid : sauter dans un groupe de raid d'une seule touche Simple : Qui ne soigne que son propre groupe en raid a besoin d'un accès rapide à un groupe sans la saisie à deux chiffres du cadran. Alt+pavé 1 à 8 cible le premier membre du sous-groupe de raid 1 à 8 (le membre numéro 1 de ce groupe, le même ordre que le ciblage au cadran et les moniteurs). La cible est annoncée comme toute cible. Si le groupe est vide, Sku dit « Groupe N vide » et rien ne se passe ; hors raid « Pas de raid ». Les huit touches ont leur propre sous-menu « Touches de sous-groupe de raid » dans les raccourcis Sku, la seconde touche fonctionne aussi. La fonction peut être désactivée sous Fonctions. Comme pour toute touche de ciblage : les changements de groupe pendant un combat sont pris en compte après le combat, d'ici là la touche saute vers l'ancien premier membre. Technique : Nouveau fichier SkuCore/subgroupTargeting.lua (sous-module SubgroupTargeting, dans la TOC après skuFocus.lua). Huit SecureActionButtons SkuSubgroupTarget1-8 avec type1=macro, macrotext1="/tar ", liés par SetOverrideBindingClick SANS cinquième argument (LeftButton, pour que type1/macrotext1 soit lu) ; les noms viennent de GetRaidRosterInfo (premier nom par sous-groupe dans l'ordre du roster) sur GROUP_ROSTER_UPDATE, GROUP_FORMED/JOINED/LEFT et PLAYER_ENTERING_WORLD hors combat, reporté à PLAYER_REGEN_ENABLED en combat. PreClick (non sécurisé, s'exécute aussi en combat) prononce la ligne vide / pas de raid. Cadre de contrôle SkuCoreSubgroupControl, script OnHide, arme les deux touches de chaque constante via SkuOptions:SkuKeyBindsGetKeys. Constantes SKU_KEY_SUBGROUPTARGET1-8, défaut ALT-NUMPAD1-8 (SkuZOptions/SkuKeyBinds.lua), groupe « Raid Untergruppen Tasten » dans SkuCore/Options.lua. Changements dans Sku v43.8 Aperçu Nouveautés Réglages du jeu : les cases à cocher avec curseur ou liste déroulante (par exemple « FPS max au premier plan »), les boutons, le bloc « Qualité graphique » et les réglages daltonisme sont maintenant utilisables ; ils disaient jusqu'ici « non pris en charge ». Réglages du jeu : les valeurs numériques commencent par une entrée « Saisir une valeur » (zone de texte), les chiffres filtrent la liste des valeurs, et les valeurs portent les libellés du jeu (« 60 FPS », « 150% »). Réglages du jeu : les listes déroulantes disent « (recommandé) » et « (non disponible) » comme le jeu, les lignes grisées « (désactivé) », et les sous-catégories des réglages d'extensions sont affichées. Changements Les messages d'erreur du jeu (par exemple « Vous n'avez pas de cible ») ne sont lus qu'une seule fois quand vous martelez une touche. Le même message n'est annoncé de nouveau qu'après avoir laissé la touche tranquille environ deux secondes et demie, ou lorsqu'un autre message d'erreur est arrivé entre-temps. Se tourner vers la balise, le point de passage ou l'unité : la rotation atterrit désormais avec la même précision sur toutes les machines et est nettement plus rapide. À 20 images par seconde, un demi-tour prenait presque une demi-seconde et un appui sur quatre était perdu ; maintenant un quart de seconde, et presque aucun appui n'est perdu. Sur les machines rapides, les grandes rotations atterrissent à environ un degré au lieu de trois à cinq. L'auto-étalonnage de la v43.7 disparaît. Corrections Synthèse vocale : depuis la v43.7, la même ligne se remettait en file à chaque appui et était lue de nombreuses fois de suite, même après une annonce plus importante. Corrigé. Menu : appuyer sur une touche (Fin, Début, flèche) juste après « menu ouvert » faisait encore lire la première entrée du menu principal (« Menu cible ») après la nouvelle entrée. Corrigé. Synthèse vocale : une ligne ne se met plus en file derrière elle-même Simple : Depuis la v43.7, un message déclenché plusieurs fois de suite pouvait entrer dans la file autant de fois que l'on voulait. En martelant une touche qui provoque une erreur, on entendait ensuite « Vous n'avez pas de cible » dix ou vingt fois, et après une annonce de cible intercalée, le message revenait encore une fois. Désormais : si le même texte attend déjà ou est en train d'être prononcé, la nouvelle copie est écartée. Les lignes de menu et les autres annonces que vous redemandez expressément avec une touche ne sont pas concernées. Technique : SkuVoice:OutputStringBTtts (Libs/SkuVoice-1.0). Le contrôle « est-ce exactement cela qui est prononcé » s'exécutait à la remise au client. Depuis le verrou client de la v43.7, une copie n'atteint la remise qu'une fois son prédécesseur FINISHED - le contrôle ne correspondait plus jamais (journal : file de 35 x la même ligne). Les lignes sans queuereset sont donc vérifiées à la MISE EN FILE, contre mSkuVoiceQueueBTTS et mSkuVoiceQueueBTTS_Speaking ; marque de journal « BTTS ENQUEUE-DUP ». Nouveau scénario de banc d'essai « errspam » (dev/rework-docs/voice-harness). Menu : la première entrée était encore prononcée en retard après un appui rapide Simple : À l'ouverture du menu, Sku dit « menu ouvert » puis la première entrée. Si vous appuyiez sur une touche pendant que « menu ouvert » parlait encore, vous entendiez la nouvelle entrée - puis quand même « Menu cible », la première entrée, qui n'était plus la bonne. Cela ne dépendait que de la vitesse de l'appui, pas de la touche ni du menu. Désormais l'ancienne première entrée est écartée dès qu'une autre entrée de menu est annoncée. Technique : Deux causes. (1) La règle de la v43.7 « conserver une ligne en attente sans réinitialisation derrière la nouvelle ligne d'écrasement » (prévue pour le chat dans lequel un avertissement s'intercale) s'appliquait aussi aux lignes de menu. La boucle de conservation de SkuVoice (balayage queuereset, Libs/SkuVoice-1.0) écarte maintenant une ligne en attente dont la portée est celle de la nouvelle ligne d'écrasement : une portée est une position dans une interface, seule sa ligne la plus récente est vraie. (2) Les deux lignes prononcées à l'ouverture (« Menu;open » et SkuOptions.Menu[1].name, SkuZOptions/Core.lua) appelaient OutputStringBTtts directement SANS la portée « menu » que VocalizeCurrentMenuName donne à toute autre ligne de menu - la comparaison des portées n'avait donc rien à comparer. Les deux portent maintenant « menu » ; par ricochet, EndScope("menu") écarte les deux si le menu est refermé aussitôt. Journal avant : « BTTS queuereset kept 1 waiting line(s) behind the overwrite ». Messages d'erreur : annoncés seulement quand ils apparaissent Simple : Sku suit désormais la règle que le jeu applique pour les voyants. Un message d'erreur y reste à l'écran environ deux secondes et demie ; si le même message revient pendant ce temps, il clignote brièvement et reste plus longtemps au lieu d'apparaître de nouveau. De même, Sku lit un message d'erreur quand il apparaît et se tait tant qu'il est encore « à l'écran ». Chaque nouvel appui prolonge ce délai. Le clic du jeu vous indique toujours à chaque appui que cela ne fonctionne pas encore. Si vous laissez la touche tranquille environ deux secondes et demie puis appuyez de nouveau, le message est annoncé de nouveau. Un message d'erreur DIFFÉRENT est toujours lu immédiatement, et après lui le premier compte de nouveau comme nouveau : erreur A, erreur B, erreur A font trois annonces. Cela ne concerne que les erreurs prononcées, pas les indices sonores et vocaux. Technique : UIErrors:OutputError (SkuCore/UIErrors.lua), branche TTS. Seule la DERNIÈRE erreur prononcée est retenue : tSpokenKey plus tSpokenShownUntil = GetTime() + 2.5 (displayDuration 2 + fadeDuration 0.5 de UIErrorsFrame.xml). Chaque répétition réécrit le délai, comme ResetMessageFadeByID ; toute autre erreur, y compris un indice sonore ou vocal, remplace ou efface tSpokenKey. La première version gardait un horodatage PAR texte - l'erreur A restait donc muette alors que l'erreur B s'était intercalée. Volontairement un horodatage et non une lecture de UIErrorsFrame : Blizzard ne limite qu'une vingtaine de types de messages, pour les autres une ligne s'ajoute à chaque appui, et l'ordre des deux gestionnaires UI_ERROR_MESSAGE n'est pas défini. Réglages du jeu : tous les contrôles des réglages Blizzard Simple : Dans le menu Réglages, Réglages du jeu, beaucoup d'entrées ne disaient que « (non pris en charge) », par exemple « FPS max au premier plan ». Elles sont maintenant utilisables. Une case à cocher avec curseur devient deux entrées : l'interrupteur et « ... valeur ». Les valeurs numériques sont une liste avec les libellés du jeu (« 60 FPS », « 150% ») ; les chiffres filtrent la liste, et la première entrée « Saisir une valeur » ouvre la zone de texte. Les listes déroulantes marquent la recommandation par « (recommandé) » et les valeurs que cette machine ne peut pas exécuter par « (non disponible) ». Les boutons comme « Réinitialiser la position du chat » exécutent leur action. Le bloc « Qualité graphique » est un sous-menu avec « Raid et champ de bataille ». Les raccourcis clavier renvoient au menu propre de Sku. Un réglage que le jeu grise en ce moment dit « (désactivé) ». De plus, les réglages graphiques, d'affichage et d'échelle de l'interface n'étaient jusqu'ici que mis en attente et jamais appliqués - ils prennent effet immédiatement maintenant. Technique : SkuCore/gameOptions.lua aiguille maintenant selon le frame template de l'initializer (Blizzard_SettingControls.lua) au lieu du type de valeur : Checkbox, Slider, Dropdown, CheckboxSlider (cbSetting/sliderSetting), CheckboxDropdown, CheckboxWithButton, Button, AdvancedQualitySection (ses listes d'options sont des closures dans le frame ; Sku les dérive d'une table par cvar et interroge IsGraphicsSettingValueSupported), ColorblindSelector, sections de raccourcis. setting:SetValue(v) sans second argument ne stocke qu'un pendingValue sur les réglages portant CommitFlag.Apply, que seul le bouton Appliquer du panneau écrit - Sku n'ouvre jamais le panneau, d'où SetValue(v, true) maintenant, plus RestartGx / UpdateWindow / SaveBindings selon le commit flag. GetAllCategories ne renvoie que les catégories de premier niveau ; GetSubcategories est parcouru récursivement. ShouldShow false = ligne omise, GetModifyPredicates false = suffixe « (désactivé) ». Les libellés des valeurs viennent de options.formatters[Label.Right] ; une saisie est reconvertie linéairement via le formatter (pourcentage, curseur de qualité 1..10). Chaque écriture journalise « gameOptions: set ». Un harnais hors ligne contre de faux objets Setting a réussi ; vérifié en jeu : l'interrupteur et la valeur FPS (journal : PROXY_FOREGROUND_FPS = 20, TurnCal fps 20). Se tourner vers la balise : exact à l'image près au lieu d'être basé sur le temps Simple : Jusqu'ici Sku faisait tourner la caméra pendant une durée calculée, puis l'arrêtait. Or le jeu ne déplace la caméra qu'une fois par image, et un minuteur ne tombe sur la limite d'image qu'approximativement - à faible cadence, il la rate. Sur une machine lente (20 images par seconde, reproduit dans le test avec la limite de FPS), chaque rotation de plus de 15 degrés prenait donc presque une demi-seconde, les petites rotations restaient trop courtes d'un tiers, un appui sur quatre tombait dans le délai de verrouillage et était perdu, et à monture on tournait de nouveau autour des points de passage proches. Désormais Sku compte les images et utilise leur durée réelle : la vitesse de rotation est choisie pour que l'angle soit un nombre entier de pas d'image, et si le dernier pas dépassait la cible, il est raccourci. Résultat à 20 images par seconde : un demi-tour en un quart de seconde, toutes les rotations à moins de deux degrés, presque aucun appui perdu, plus de cercles. À 80 à 120 images : grandes rotations avec environ un degré d'erreur au lieu de trois à cinq, petites sous un demi-degré. L'auto-étalonnage de la v43.7 disparaît - il apprenait des valeurs identiques sur toutes les machines. /skuturn n'indique plus que si la vitesse rapide fonctionne sur cette machine ; /skuturn reset réinitialise ce contrôle. Technique : GameWorldObjects:TurnToWorldPosition (SkuCore/gameWorldObjects.lua). Mesuré sur environ 400 rotations à 20 et 77 à 125 fps (résidu 0,5 ms) : la caméra avance une fois par image de vitesse fois la durée RÉELLE de l'image ; l'image dont l'OnUpdate émet l'arrêt n'avance plus ; la durée d'image mesurée dans un OnUpdate est exactement celle du pas suivant. Planificateur : n = ceil(angle / (1440 * f)) pas, v = angle / (n * f), CVar au plus 360, le reste en facteur MoveView. L'arrêt vient d'un compteur OnUpdate qui additionne les durées d'image réelles et s'arrête dès que la somme atteint le temps de mouvement angle/v ; si le dernier pas dépassait, cameraYawMoveSpeed est mis à l'échelle du reste pour cette seule image et l'arrêt suit à l'image suivante - la CVar agit immédiatement (54 rotations ajustées : erreur 0,5 degré au lieu des 5,3 qu'une CVar inerte aurait donnés). C_Timer était la cause de l'ancienne dispersion : il ne se déclenche qu'aux limites d'image et glisse d'une image entière à 2 pour cent de gigue. Le « glissement » de la caméra de la v43.7 était cette unique image inerte, pas de l'inertie. L'étalonnage (échelle et latence par vitesse, store v4/v5) apprenait 1,00 et une image avec une dispersion de 0,01 et a été retiré ; le store turnCal v6 ne garde que noGear2 (le facteur n'agit pas : médiane vitesse réelle/demandée sous 0,7 sur 6+ rotations rapides). Verrouillage après l'arrêt 2 images ou 50 ms au lieu de 3 ou 100. Journal « TurnCal » par appui avec n, m, mt, trim, dts (toutes les durées d'image), fest/fact/fmax, rest_land, lead_shift ; modèle de script d'analyse an4.py dans le scratchpad. Leçons pour WowVision : dev/rework-docs/TURN-TO-BEACON-LESSONS.md. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.7 Aperçu Changements Se tourner vers la balise (T), vers l'unité et vers la cible : les grandes rotations arrivent désormais en UN appui. Jusqu'ici chaque appui restait bloqué à environ 88 degrés, un demi-tour en demandait toujours deux. Les rotations sont nettement plus rapides : un demi-tour prend un bon dixième de seconde sur une machine rapide au lieu d'un quart de seconde. Si vous êtes déjà face à la balise (à 3 degrés près), vous n'êtes plus tourné. Cela met fin au va-et-vient autour du ping de la balise. La rotation s'étalonne elle-même : après chaque rotation, Sku vérifie de combien vous avez réellement tourné et s'adapte à votre machine et à votre fréquence d'images. Les toutes premières rotations après la mise à jour peuvent être imprécises. Écho clavier avec une vraie voix Windows (pas le pont NVDA) : la longue pause après chaque lettre tapée a disparu, les lettres se suivent de près. Coller du texte dans un champ de saisie (Ctrl+V) : Sku dit désormais une seule fois « Collé » au lieu de lire le texte collé lettre par lettre. Corrections Nage et vol : le verrou d'inclinaison s'active désormais aussi en combat. En entrant dans l'eau en plein combat, les premières rotations vous faisaient plonger. Objets d'un ensemble d'équipement : l'infobulle annonçait deux fois le type d'armure (par exemple « Tissu »). Désormais une seule fois. Synthèse vocale avec une vraie voix Windows (pas le pont NVDA) : des lignes remplacées depuis longtemps étaient quand même prononcées des secondes ou des minutes plus tard - lettres de filtre tapées après « Menu fermé », entrées de liste sautées après avoir relâché la flèche, discussion de guilde avec une minute de retard. Corrigé. Menu : quand le menu s'ouvrait directement dans une fenêtre ou une liste (sac, marchand, Maj+F10), « Menu de la cible » était encore annoncé après le contenu. Corrigé. Vraie voix Windows : si un autre addon parlait pendant la saisie, une lettre pouvait suivre après « annulé ». Corrigé. Rotation : plus rapide, plus précise, auto-étalonnée Simple : « Se tourner vers la balise » (T) et les deux autres commandes de rotation ont été retravaillées à partir de mesures en jeu. Trois choses changent nettement. D'abord : les grandes rotations arrivent en un appui. Jusqu'ici un appui faisait au mieux environ 88 degrés, quelle que soit la distance à parcourir - un bogue passé inaperçu depuis la v43.2. Ensuite : la rotation est plus rapide, un demi-tour ne prend plus qu'un bon dixième de seconde. Enfin : si vous êtes déjà aligné à 3 degrés près, un nouvel appui ne fait plus rien. Avant, chaque appui tournait d'au moins 5 degrés, on oscillait donc autour du ping de la balise sans jamais se stabiliser. Dans l'eau, cela évite aussi des à-coups de plongée inutiles. Sku apprend votre machine au passage : après chaque rotation, il mesure de combien le personnage a réellement tourné et calcule la suivante d'après cela. À fréquence d'images élevée, Sku tourne plus vite de lui-même ; à fréquence basse, plus lentement mais précisément. Après la mise à jour, les premières rotations sont des étapes d'apprentissage et peuvent manquer la cible. Les très grandes rotations proches de 180 degrés arrivent à environ 5 degrés près ; de temps en temps un second appui affine. Un appui pendant une rotation en cours est ignoré comme avant. Technique : GameWorldObjects:TurnToWorldPosition (SkuCore/gameWorldObjects.lua). Mesuré le 2026-09-21 : cameraYawMoveSpeed n'a plus d'effet au-delà de 360 - des valeurs plus hautes ne tournent pas plus vite (376, 453 et 731 ont tous donné ~88 degrés en 0,25 s). La règle max(360, angle/0,25) de la v43.2 plafonnait donc en silence chaque appui à ~88 degrés. La vitesse supplémentaire ne vient que de l'argument de MoveViewLeftStart/MoveViewRightStart, qui multiplie la CVar (technique de WowVision). Deux rapports : rapport 1 = CVar seule jusqu'à 360 deg/s, rapport 2 = CVar 360 fois un facteur jusqu'à 1440 deg/s. Modèle par rapport : tourné = k * vitesse voulue * (durée du minuteur + L) ; k par la médiane dès la première mesure, puis à partir de 8 mesures aux durées variées une droite d'ajustement pour k et L. Vitesse selon la fréquence d'images : la dispersion de l'arrêt vaut une demi-trame fois la vitesse, on autorise 3 degrés ou 6 pour cent de l'angle ; jamais plus lent qu'angle/0,25 s ; ralenti quand le minuteur serait plus court que 2 trames. Si le facteur ne multiplie pas sur un client, Sku abandonne définitivement le rapport 2. Mesures par compte dans SkuOptions.db.global.SkuCore.turnCal. Le verrou contre les appuis suivants couvre désormais aussi l'attente de mesure (un appui dans cet intervalle calculait avec l'ancienne orientation). Chaque rotation écrit une ligne « TurnCal » dans le journal de débogage. /skuturn affiche l'état, /skuturn reset efface les mesures, /skuturn legacy revient à l'ancien comportement. Construit, testé puis retiré : une correction dans le même appui - la caméra a de l'élan et continue de glisser après l'arrêt, une commande dans ce glissement coûtait jusqu'à 35 degrés. Nage et vol : le verrou d'inclinaison s'active aussi en combat Simple : Le verrou d'inclinaison automatique, qui vous maintient à l'horizontale en nageant et en volant, attendait jusqu'ici la fin du combat. En entrant dans l'eau en combattant, vous restiez sans protection pendant ce temps, et chaque rotation vers la balise vous enfonçait davantage. Le verrou s'active maintenant immédiatement. La touche Ctrl+Maj+N fonctionne désormais aussi en combat. Technique : Les tests InCombatLockdown dans SkuCore:TogglePitchLock et dans le tick automatique (SkuCore/Core.lua) reposaient sur l'hypothèse non vérifiée que pitchlimit est protégé en combat. C'est une commande de console, modifiable en combat (testé le 2026-09-21). Dans le journal de ce jour-là, 6 secondes avec trois rotations non protégées séparaient le début de la nage et le verrou. Infobulle des objets d'ensemble : le type d'armure était lu deux fois Simple : Si un objet fait partie de l'un de vos ensembles d'équipement enregistrés, Sku insère la ligne « Appartient à l'ensemble » dans l'infobulle. Sur ces objets précisément, le type d'armure s'entendait ensuite deux fois, par exemple « Lié, Tissu, Tête, Tissu ». Le premier « Tissu » était un reste qui n'avait rien à faire là. Il n'arrive plus qu'une fois, après l'emplacement : « Lié, Tête, Tissu ». Sur les armes, il en allait de même pour le type d'arme et la vitesse. Les objets hors ensemble n'étaient pas touchés. Technique : tInsertSetLineAtTop (SkuCore/equipmentSets.lua) décale d'un cran vers le bas toutes les lignes de l'infobulle à partir de la ligne 2. La colonne de droite n'était écrite que si la ligne source en avait une. La ligne que « Tête | Tissu » quittait recevait « Lié » à gauche et gardait son ancien « Tissu » à droite. TooltipLines_helper lit chaque région FontString séparément, d'où le reste audible. La colonne de droite est maintenant toujours écrite : renseignée et affichée, ou vidée et masquée ; de même pour la ligne 2. Le défaut était dans le code depuis la v42.00 et n'est apparu qu'avec des ensembles enregistrés. Synthèse vocale : des lignes remplacées étaient prononcées plus tard (vraies voix Windows) Simple : Cela ne concerne que les joueurs qui font parler Sku avec une vraie voix Windows, pas avec le pont NVDA. Là, une ligne que Sku avait remplacée depuis longtemps pouvait rester coincée dans le jeu et être prononcée bien plus tard : en tapant vite un filtre dans un menu, on entendait « g r » ou « g r e i f » seulement après « Menu fermé ». En maintenant la flèche, on entendait deux ou trois entrées sautées après la dernière. Et les lignes de discussion arrivées pendant le défilement venaient jusqu'à 70 secondes trop tard et dans le désordre. Dans un journal envoyé par un joueur, cela faisait 24 annonces sur 524 en une demi-heure. C'est corrigé : ce que Sku a remplacé ou annulé reste muet. Les règles sur ce qui peut interrompre quoi ne changent pas. Le mélange reste aussi : quand une annonce importante s'insère dans une série de lignes en attente (discussion, messages d'état), elle est prononcée d'abord, puis la série continue, la plus ancienne d'abord. Seul ce qui a alors plus de 30 secondes est abandonné. Technique : Libs/SkuVoice-1.0. C_VoiceChat.StopSpeakingText n'arrête que l'énoncé EN COURS de lecture. Il n'atteint pas un énoncé transmis mais pas encore démarré, et tout ce qui est transmis pendant qu'un autre énoncé est encore dans le client y est mis en attente et ne démarre qu'au prochain PLAYBACK_FINISHED naturel - quel que soit le nombre d'arrêts entre-temps. Nouveau : une porte commune (mClientGate) pour TOUTES les transmissions. Sku ne donne au client qu'UN énoncé à la fois ; la ligne suivante attend dans la file de Sku le FINISHED/FAILED de l'identifiant correspondant, là où queuereset, EndScope et CancelBttsOutput peuvent encore l'atteindre. Comme les lignes attendent désormais dans Sku, la règle du queuereset que le client fournissait par hasard a dû être énoncée : une ligne d'écrasement en attente est dépassée et supprimée (mSkuVoiceQueueBTTS_Overwrite) ; une ligne en attente sans queuereset est placée derrière la nouvelle ligne d'écrasement tant qu'elle a moins de tBttsKeptMaxAge (30 s). Les cinq points d'arrêt passent par BttsStop : si l'énoncé dans le client n'a pas démarré, l'arrêt est noté et délivré 0,1 s après son STARTED, depuis OnUpdate - jamais depuis le gestionnaire STARTED lui-même : un StopSpeakingText émis de là a bloqué toute la synthèse vocale du client jusqu'au redémarrage lors des tests (l'ancienne fenêtre d'élimination faisait cela). Cela remplace la fenêtre d'élimination de la v43.5 (la même idée avec une durée estimée, câblée à deux endroits seulement) et la porte propre à l'écho de frappe. Garde-fous : 0,5 s sans STARTED ouvre la porte (et envoie quand même un arrêt noté) ; après trois transmissions sans aucun STARTED, la porte se désactive jusqu'au prochain. Avec le pont NVDA, STARTED et FINISHED arrivent dans l'image de la transmission : la porte n'y est jamais fermée - comportement inchangé. Vérifié avec un modèle hors ligne du client (dev/rework-docs/voice-harness) : ancien code 27-33 énoncés en retard en 3 minutes de charge aléatoire, nouveau code 0 ; modèle du pont identique avant et après. Dans le journal : "stop deferred (handover not started)", "deferred stop fires", "client gate ttl", "queuereset kept", "STALE-DROP". Bogue induit, corrigé au passage : la touche « arrêter la sortie audio » (Ctrl+V) n'annulait plus que la ligne en cours, les lignes en attente continuaient - un appui par ligne. Avant, toute la rafale se trouvait déjà dans le client et l'unique arrêt touchait tout ; depuis la porte, les lignes attendent chez Sku, et StopOutputEmptyQueue ne vidait jamais cette file. StopOutputEmptyQueue(true, true) appelle maintenant CancelBttsOutput : lignes et caractères tapés en attente supprimés, puis l'arrêt. Les appelants avec (true, nil) restent tels quels - les lignes de chat en attente doivent y survivre. Scénario du banc d'essai « stopkey » : ancienne version quatre lignes audibles, nouvelle version une seule (coupée). Menu : « Menu de la cible » était annoncé après le contenu Simple : Quand le menu ne s'ouvre pas à sa racine mais directement à un endroit précis - ouverture d'un sac, d'un marchand ou d'une boîte aux lettres, Maj+F9 et Maj+F10, les touches de sélection rapide -, on entendait encore, après le contenu, le premier élément de la racine du menu, « Menu de la cible ». Par exemple « Sac 1, Menu de la cible ». C'est corrigé : vous n'entendez plus que l'endroit où vous arrivez. « Menu ouvert » n'est plus prononcé pour ces ouvertures ; il était de toute façon coupé aussitôt, et le son d'ouverture reste. L'ouverture normale du menu est inchangée : « menu ouvert », puis « Menu de la cible ». Technique : SkuOptions:SlashFunc (SkuZOptions/Core.lua) ouvre un menu fermé via le OnClick de OnSkuOptionsMain puis parcourt le chemin. La branche d'ouverture prononçait « Menu ouvert » en overwrite et plaçait Menu[1].name derrière comme ligne en attente. L'annonce du parcours posait un queuereset, et depuis la porte client de cette version un queuereset conserve les lignes en attente sans overwrite derrière la nouvelle ligne - prévu pour le chat, ici cela touchait l'élément racine (journal : "queuereset kept 1 waiting line(s)"). Corrigé à la source plutôt que par suppression : avec un chemin (au moins deux champs), SlashFunc pose SkuOptions.tOpeningForPathWalk autour de l'appel d'ouverture, et la branche d'ouverture ne dit alors rien. Le drapeau est effacé juste après l'appel, même en cas d'erreur (pcall, erreur relancée). Si le chemin ne trouve rien et que le menu reste à la racine, SlashFunc rattrape l'annonce et écrit "menu: path walk found nothing" dans le journal de débogage. Les étapes intermédiaires du chemin étaient déjà muettes auparavant. Écho clavier avec une vraie voix Windows : la longue pause entre les lettres a disparu Simple : Avec une vraie voix Windows (pas le pont NVDA), la saisie dans le chat laissait une pause d'une demi-seconde à une seconde après chaque lettre - environ une lettre par seconde, quelle que soit la vitesse de la voix. Une voix plus lente allongeait seulement la lettre, la pause restait la même. Cette pause a disparu, les lettres se suivent de près. Chaque lettre est toujours prononcée, y compris les lettres doublées, et plus aucune lettre ne suit après Échap. La même pause existait entre des lignes en attente l'une derrière l'autre (parties d'un texte de quête, plusieurs lignes de chat) ; elle s'y remarque à peine, mais elle est raccourcie elle aussi. Rien ne change avec le pont NVDA. Technique : Le client signale VOICE_CHAT_TTS_PLAYBACK_FINISHED de façon fixe 0,5 à 1 s APRÈS la fin du son (signet « End ») et ne démarre rien avant. Transmettre plus tôt n'aide pas (v43.5e : l'énoncé est seulement mis en attente et ne peut plus être annulé). Ce qui libère le client immédiatement, c'est un arrêt. Nouveau dans Libs/SkuVoice-1.0 : dès que « Start » puis « End » sont arrivés pour notre énoncé et que quelque chose attend (voie d'écho ou file), OnUpdate envoie un StopSpeakingText après tTailCutDelay (0,03 s) et transmet après tTailCutHold (0,06 s). Jamais depuis le gestionnaire de signets. Un « End » avant « Start » (arrive sur le premier énoncé après un arrêt) ne compte pas. Si rien n'attend, la ligne se termine d'elle-même comme avant. La coupe ne pose pas la marque du pont, bloque une fois le contournement « #queue > 1 » et vide elle-même l'entrée du garde anti-doublon (un énoncé arrêté ne signale pas FINISHED). Mesuré dans le modèle hors ligne : cycle par lettre 1,15 s -> 0,73 s. Pour essayer, session uniquement : /skudebug tts tail , /skudebug tts tail off. Dans le journal : « tail cut -> StopSpeakingText ». La parole d'autres addons perturbait l'écho clavier Simple : Si un autre addon (ou une commande /run) parlait via la synthèse vocale du jeu pendant que vous tapiez, une lettre pouvait encore suivre après « annulé », donc après la fermeture du champ de chat. Uniquement avec une vraie voix Windows. Corrigé : tant qu'une parole étrangère est en cours, Sku retient ses lettres et ses lignes de chat ; si vous fermez le champ, les lettres en attente sont abandonnées. Les annonces qui interrompent tout de toute façon (menu, « annulé », lignes prioritaires) interrompent aussi la parole étrangère immédiatement, comme avant. Technique : La porte du client ne connaissait que les transmissions de Sku. Un énoncé étranger (capture : id 403, en attente, démarré 11 s plus tard sous le premier caractère tapé) laissait le caractère suivant sans STARTED, le TTL de 0,5 s l'abandonnait, un deuxième entrait dans le client et prenait le STARTED du premier pour le sien. Nouveau (mForeign, tMaxSeenId, tClientGateFreedAt) : un STARTED que personne n'attend, ou dont l'id est inférieur au plus élevé vu avant notre transmission, est étranger - jamais adopté comme notre id, et rien n'est transmis tant qu'il joue (plafond de silence 8 s ; nos propres arrêts y mettent fin). Sur le pont tous les id valent 0, « plus ancien » est donc strictement inférieur et ne s'y déclenche jamais. Dans l'image où l'un de nos énoncés signale FINISHED, rien n'est transmis : un énoncé étranger en attente démarre exactement sur ce FINISHED et doit être vu d'abord. Scénario du banc d'essai « foreign » : ancienne version quatre lettres après « annulé », nouvelle version aucune. Dans le journal : « BTTS foreign utterance », « BTTS foreign cleared ». Coller dans un champ de saisie : « Collé » au lieu d'une suite de lettres Simple : Si vous colliez du texte avec Ctrl+V dans la ligne de chat ou dans un champ de saisie de Sku, tout le texte collé était lu lettre par lettre. Sku dit maintenant une seule fois « Collé ». Lisez le contenu du champ comme d'habitude avec flèche haut ou flèche bas. La frappe normale est inchangée, même rapide. Si vous ne collez qu'un ou deux caractères, ils sont toujours prononcés comme des caractères. Technique : Un collage livre le presse-papiers sous forme d'appels OnChar individuels, tous dans la même image. tSpeakTypedChar (SkuZOptions/Core.lua) compte les caractères par image (GetTime() est constant dans une image) : à partir du troisième, la suite compte comme un collage - CancelEcho supprime les caractères déjà en attente (aucun n'a encore été transmis dans cette image, la pompe tourne dans OnUpdate), puis un seul SpeakEcho(« Collé »), tout autre caractère de l'image est ignoré. Seuil 3, car deux frappes peuvent tomber dans une même image à faible fréquence d'images. Vaut pour AttachInputEcho (chat) et le champ de saisie de Sku. Dans le journal : « inputEcho paste erkannt ». ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.6 Aperçu Changements Fenêtres de métier : la liste des recettes montre maintenant tout le métier avec des catégories à replier et déplier, au lieu d'une liste défilante de 8 entrées. Fenêtres de métier : Page précédente et Page suivante sautent de catégorie en catégorie. Fenêtres de métier : le filtre « matériaux disponibles » est un interrupteur normal et ne mène plus à des pages vides. Corrections Chat : « expéditeur du chuchotement » et « envoyer le message au canal » dans le menu de ligne (Ctrl+Entrée sur une ligne de chat) ne faisaient rien du tout sur certaines machines - la ligne de chat ne s'ouvrait pas. Chuchoter depuis le menu de la cible et depuis le navigateur de donjons ne faisait rien non plus sur ces mêmes machines. Touches : si vous aviez lancé la v43 une fois, étiez revenu à la v42 et y aviez réattribué Maj+F9 à F12, la v43 vous laissait sans touche pour la liste des points de passage, les destinations d'itinéraire, les barres d'action et « terminer l'itinéraire » - les touches sont maintenant reprises après coup. Classic Era : la liste des sorts non interruptibles s'arrêtait sur une erreur au chargement ; elle se charge maintenant sur tous les clients. Fenêtres de métier : l'infobulle d'une recette (Maj+Flèche bas) omettait le nom de certains composants, on n'entendait que des chiffres comme « 0 2 ». Chat : « expéditeur du chuchotement » n'ouvrait pas de ligne de chat Simple : Si vous ouvriez le menu de ligne dans le chat avec Ctrl+Entrée et choisissiez « expéditeur du chuchotement », il ne se passait rien sur certaines machines : aucun son, aucune ligne de chat au nom de l'expéditeur. Il en allait de même pour « envoyer le message au canal » et « envoyer le lien de l'objet au canal ». Copier la ligne de chat et copier le nom de l'expéditeur fonctionnaient toujours, car ils passent par un autre chemin. Les trois entrées ouvrent de nouveau la ligne de chat, et le canal cible est annoncé comme d'habitude (« À Kai »). Technique : SkuChat:SetEditboxToCustom appelait le global ChatEdit_UpdateHeader et n'affichait la zone de saisie qu'APRÈS. Sur le client 2.5.6, ce nom n'est plus une vraie fonction mais un alias de Blizzard_DeprecatedChatInfo (Deprecated_ChatFrame.lua), et tout ce fichier s'arrête aussitôt quand la CVar loadDeprecationFallbacks est désactivée. Le nom vaut alors nil, l'appel lève une erreur, et Show() et SetFocus() ne s'exécutent jamais. Nouveau : SkuChat:UpdateEditboxHeader appelle la vraie méthode du cadre ChatFrame1EditBox:UpdateHeader() et ne garde le nom global qu'en secours. SkuChat_CanAddChannel lit Constants.ChatFrameConstants.MaxChatChannels au lieu de l'ancien MAX_WOW_CHAT_CHANNELS. Chuchoter depuis le menu de la cible et le navigateur de donjons ne faisait rien Simple : « Chuchoter » dans le menu de la cible actuelle, et pour un chef de groupe dans le navigateur de donjons, n'ouvrait aucune ligne de chat sur ces mêmes machines - sans le moindre message. Les deux fonctionnent de nouveau. Technique : Les deux endroits appelaient ChatFrame_OpenChat derrière un test « s'il existe ». Ce n'est là aussi qu'un alias de Blizzard_DeprecatedChatInfo ; s'il manque, il ne se passait rien, en silence. ChatFrameUtil.OpenChat est utilisé maintenant, l'ancien nom seulement en secours (SkuMob/Options.lua, SkuCore/dungeonBrowser.lua). Touches : Maj+F9 à F12 ne menaient nulle part après un passage v43 - v42 - v43 Simple : Étaient concernés ceux qui avaient lancé la v43 une fois, étaient revenus à la v42 et y avaient remis les accès rapides 1 à 4 sur Maj+F9 à F12. Au lancement suivant de la v43, les actions fixes (liste des points de passage, destinations d'itinéraire, barres d'action, terminer l'itinéraire ou le point de passage) n'avaient plus de touche, et les quatre touches appelaient à la place d'anciens chemins d'accès rapide. L'un d'eux pointait vers une entrée de menu qui n'existe plus : le menu s'ouvrait et le curseur restait sur le premier élément - exactement comme l'ancien blocage « les points de passage sont encore en cours de chargement ». Sku reconnaît maintenant cet état au démarrage et reporte de lui-même les quatre touches sur les actions fixes. Vos propres touches sur les accès rapides ne sont pas touchées. Merci à Kai pour son analyse précise (issue 7). Technique : tMigrateQuickKeys (SkuZOptions/SkuKeyBinds.lua) prenait la seule présence de SKU_KEY_NAVWAYPOINTSQUICK pour « déjà migré ». La v43 avait créé l'entrée à son premier lancement, la v42 l'a vidée en attribuant les touches à MENUQUICK1 à 4 - présente, mais vide. Nouveau : une passe de rattrapage. Si NAVWAYPOINTSQUICK, NAVROUTEDESTINATIONSQUICK et ACTIONBARSOPEN sont toutes les trois vides ET qu'au moins un MENUQUICK1 à 4 porte une des touches par défaut de la v42, SHIFT-F9 à SHIFT-F12, la reprise s'exécute encore une fois - dans cette passe uniquement pour les emplacements qui portent une telle touche par défaut. Qui a vidé exprès les nouvelles touches garde ainsi chacune de ses propres touches d'accès rapide. La passe ne s'exécute qu'une fois : ensuite les nouvelles entrées ne sont plus vides. Classic Era : la liste des sorts non interruptibles ne se chargeait pas Simple : Sur Classic Era, un fichier de données de Sku s'arrêtait sur une erreur Lua à la connexion. Sku se retrouvait sans la liste dont se sert « N'annoncer que les sorts interruptibles ». Le fichier se charge maintenant entièrement sur tous les clients. Rien ne change sur TBC Anniversary. Technique : SkuDB/uninterruptibleCasts.lua construit ses clés de nom avec GetSpellInfo(id) directement dans le constructeur de table. Era ne connaît pas les sorts TBC 29121 (Tir à l'arc) et 33808 (Tir avec une arme à feu), GetSpellInfo renvoie nil, et « [nil] = true » lève « table index is nil » - le constructeur s'interrompt, SkuDB.uninterruptibleCasts.spells et .npcSpells restent nil. Le fichier utilise maintenant une enveloppe locale qui renvoie, pour un identifiant inconnu, une valeur de remplacement qu'aucun nom de sort ne peut égaler. L'entrée est sans effet sur ce client, les constructeurs restent identiques ligne à ligne à la source amont (ClassicCastbars), et la même protection couvre les lignes npcSpells, où un nil aurait levé une erreur de concaténation. Fenêtres de métier retravaillées : catégories repliables au lieu de 8 entrées par page Simple : Les fenêtres de métier (tous les métiers de fabrication, l'enchantement et le dressage des familiers) se comportent maintenant de façon cohérente. Jusqu'ici, le menu ne montrait que les 8 lignes affichées par la fenêtre de Blizzard, plus « défiler vers le haut » et « défiler vers le bas ». Désormais tout le métier tient dans une seule liste : chaque catégorie comme titre, ses recettes en dessous. Entrée sur une catégorie la replie ou la déplie, le nouvel état est annoncé, et Sku le mémorise par personnage et par métier. Page suivante et Page précédente sautent à la catégorie suivante ou précédente. « Sélectionné » et les boutons de création restent à la fin de la fenêtre. Si deux catégories portent le même nom (la couture a deux fois « Tissu »), la seconde s'appelle « Tissu 2 ». Le filtre « matériaux disponibles » est maintenant un interrupteur normal : Entrée le bascule et le curseur reste en place. Avec le filtre, les recettes sans matériaux disparaissent, ainsi que les catégories où rien n'est fabricable. On n'arrive plus sur des pages vides sans issue, et l'enchantement n'a plus de pages où il ne reste que les titres. La liste se met à jour toute seule après une fabrication et ne parle que si l'entrée sous le curseur a disparu. Technique : Build_TradeSkillFrame et Build_CraftFrame (SkuCore/LocalMenu.lua) lisent la liste via GetTradeSkillInfo / GetCraftInfo au lieu de relever les lignes TradeSkillSkill1-8 et Craft1-8 ; la sélection passe par TradeSkillFrame_SetSelection / CraftFrame_SetSelection. TradeSkillOnlyShowMakeable de Blizzard n'est plus utilisé : il ne remet le défilement à zéro que si l'index de SÉLECTION sort de la liste raccourcie - sinon le décalage restait derrière la fin de la liste, et les 8 lignes ainsi que la barre de défilement avaient disparu. Le filtre vérifie maintenant numAvailable dans les deux fenêtres ; l'ancien cache resFilterApplied (par métier, alors que l'interrupteur de Blizzard est global) est supprimé. Nouveau : TRADE_SKILL_UPDATE et CRAFT_UPDATE reconstruisent le menu, avec anti-rebond et seulement si la liste affichée a changé (SkuCore:RefreshProfessionMenu). Le constructeur de listes de fenêtre connaît deux nouveaux champs : « toggle » (un interrupteur MakeToggleNode dans une fenêtre) et « isSectionHeader » (cible de saut pour Page précédente/suivante). L'état du filtre est maintenant stocké sous le nom du métier au lieu du titre de la fenêtre ; une ancienne valeur est reprise. Fenêtres de métier : l'infobulle d'une recette citait certains composants sans nom Simple : Dans l'infobulle d'une recette (Maj+Flèche bas), quelques composants n'affichaient que la quantité, par exemple « 0 2 » au lieu de « Eau de Javel 0 sur 2 ». Cela touchait les objets que le client du jeu n'a encore jamais vus - surtout des articles vendus uniquement par les marchands, comme l'eau de Javel, les teintures ou le fondant. Les étoffes et les cuirs sont presque toujours déjà connus du client, d'où l'impression de hasard. Sku dit maintenant « chargement en cours » à cet endroit et récupère le nom en arrière-plan. Fermez l'infobulle et appuyez de nouveau sur Maj+Flèche bas : le nom est là. Cela n'arrive qu'une fois par objet, ensuite le client le connaît durablement. Technique : GetTradeSkillReagentInfo / GetCraftReagentInfo ne renvoient aucun nom pour un objet absent du cache d'objets, et le lien du composant porte alors des crochets vides « [] ». Le repli écrivait un « ? », que la synthèse vocale ne prononce pas. De plus, le texte fini était rangé dans textFull du nœud de menu et jamais reconstruit : le défaut restait même après le chargement de l'objet. Nouveau (SkuZOptions/Core.lua, SHIFT-DOWN) : sans nom, l'identifiant est lu dans le lien et GetItemInfo(id) est appelé, ce qui demande aussi l'objet au serveur. Une infobulle incomplète est marquée skuRecipeIncomplete et reconstruite à la prochaine pression. Chaque cas écrit une ligne « recipeTooltip » dans le journal de débogage (index de recette, composant, lien). ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.5-tools - installateur 5.2 et outil de connexion 3.4, pas de nouvelle version de l'addon Aperçu Nouveautés Outil de connexion 3.4 : le contrat social de Blizzard est reconnu, défilé jusqu'à la fin et accepté - jusqu'ici, un nouveau joueur aveugle restait bloqué devant. Outil de connexion 3.4 : l'avertissement JcJ affiché à la création d'un personnage sur un royaume JcJ est lu, et l'on répond avec Entrée ou Échap. Changements Outil de connexion 3.4 : les avertissements hardcore et JcJ sont trouvés à toutes les résolutions et tailles de fenêtre, et plus seulement sur les grands écrans. Outil de connexion 3.4 : chaque voix est essayée de façon inaudible avant d'être acceptée - une voix défectueuse ne peut plus rendre l'outil muet. Installateur 5.2 : le paquet de journaux contient désormais la liste de toutes les voix Windows (voices.txt). Installateur 5.2 : mesures contre les fausses alertes de Windows Defender - le fichier de l'installateur indique son produit et son auteur, et le fichier du pont NVDA fourni n'a plus de signature de développeur résiduelle. Corrections Outil de connexion 3.4 : « Séléctionner la voix » était vide sur certaines machines, et la première configuration n'allait jamais plus loin. Outil de connexion 3.4 : le contrat social de Blizzard est accepté automatiquement Simple : Les nouveaux comptes - et tous les autres dès que Blizzard modifie le texte - voient un « contrat social » sur l'écran de sélection du personnage. La fenêtre avale toutes les touches, « Accepter » reste verrouillé tant que le texte n'a pas été défilé jusqu'au bout à la souris, et « Refuser » quitte le jeu. L'outil restait muet devant, ou disait au mieux « écran inconnu ». Il annonce maintenant que le contrat social est ouvert, fait défiler le texte jusqu'à la fin, appuie sur Accepter, vérifie que la fenêtre a disparu, puis construit la liste des personnages comme d'habitude. Il en va de même si le contrat réapparaît en cours de session. En cas d'échec, l'outil dit ce qu'une personne voyante doit faire une fois, et réessaie après une minute. Refuser n'est jamais pressé. Technique : La fenêtre est SocialContractFrame (Blizzard_GlueXML\SocialContract.xml/.lua), déclenchée par le SERVEUR (C_SocialContractGlue.GetShouldShowSocialContract -> SOCIAL_CONTRACT_STATUS_UPDATE) ; aucune CVar ni aucun réglage n'existe, on ne peut donc ni la provoquer ni y répondre à l'avance. L'ancien code ne pouvait pas fonctionner sur le client Anniversary : le test de l'assistant exige le logo jaune assombri, que l'écran BC n'a pas, et son troisième point de mesure tombe au milieu du texte du contrat - test faux, écran « unknown », l'outil n'entrait jamais en mode connexion ; la partie clic était un balayage aveugle de 34 clics. Nouveau : huit points Sc* dans data.ini (déduits du XML : cadre 510x632, boutons à ui y 706..728, PAS un DefaultScaleFrame), plus IsSocialContract, AcceptContract et HandleSocialContract dans flows.ahk et deux branches CheckMode dans menu.ahk - sans recompiler l'assistant. Vérifié avec /validate, zéro faux positif sur toutes les captures de référence et détection sur des images synthétiques du contrat à trois résolutions. Pas encore testé sur la vraie fenêtre, qu'on ne peut pas faire apparaître sur commande ; chaque tentative écrit ses mesures dans log.txt (lignes « SocialContract: »). Outil de connexion 3.4 : avertissements hardcore et JcJ à toutes les résolutions, avertissement JcJ de création nouveau Simple : L'avertissement hardcore de la liste des royaumes, les règles hardcore à la création d'un personnage et les avertissements JcJ sont des fenêtres que Blizzard garde à la même taille en pixels réels. Leur position dépend donc de la résolution, et les points de mesure fixes de l'outil n'étaient justes que sur les grands écrans (à partir de 1200 pixels de haut). Sur un écran 1080p courant, ou dans une petite fenêtre, l'outil serait resté muet devant les règles hardcore. Il cherche maintenant ces fenêtres lui-même et les trouve à toutes les résolutions. S'y ajoute l'avertissement JcJ à la création d'un personnage sur un royaume JcJ : il est lu, Entrée accepte, Échap refuse. L'avertissement JcJ de la liste des royaumes a exactement l'aspect de l'avertissement hardcore et était donc déjà lu. Technique : HardcorePopUpFrame (240 et 580 de haut) et RealmWarningPopUpFrame (240 et 360 de haut) héritent de DefaultScaleFrame et sont mis à l'échelle par GetDefaultScale() - une fonction du client sans source Lua. Mesuré en jeu avec /wdeval : 0,64 en 2880x1800, là où 768/hauteur donnerait 0,43 ; il existe donc un plancher, et le 1080p donnerait sans doute 0,71. Au lieu de supposer l'échelle, GlueDialogScan (flows.ahk) la trouve : dans les quatre formes, les deux boutons sont sur une droite partant du centre de l'écran, et seule la distance dépend de l'échelle. Une capture (nouvelle classe ScreenGrab dans ui.ahk, 1000 pixels en 16 ms), puis échelle 0,60..1,25 par hauteur : rouge sur trois colonnes, pas de rouge dans l'interstice ni juste à l'extérieur, noir au point de fond. Les points fixes restent le premier test, le balayage le second. Les clics (GlueDialogClick) et les zones de texte OCR (GlueDialogBand) suivent l'échelle trouvée. Vérifié avec un portage Python sur 66 fenêtres synthétiques (6 résolutions, 3 hauteurs, 4 échelles) : hauteur toujours juste, échelle à 0,012 près, zéro faux positif. Pas encore testé sur de vraies fenêtres ; chaque détection est consignée dans log.txt (ligne « GlueDialog: » avec l'échelle). Outil de connexion 3.4 : sélection de voix vide corrigée, la première configuration ne reste plus bloquée Simple : Sur certaines machines, Windows ne peut pas fournir la liste des voix installées alors que la synthèse vocale elle-même fonctionne - par exemple après une voix tierce mal désinstallée ou à moitié installée. Depuis la 3.3, l'outil ne plante plus dans ce cas, mais « Séléctionner la voix » était alors vide : la flèche droite ne faisait rien, et comme la première configuration ne passe à la langue qu'après le choix d'une voix, on ne la traversait jamais. L'outil lit désormais lui-même les voix dans le registre Windows dans ce cas. S'il n'en trouve pas non plus ainsi, le menu contient l'entrée « Aucune voix trouvée, appuyez sur Entrée pour garder la voix du système » - Entrée conserve la voix avec laquelle l'outil parle, et la configuration continue. Technique : Le paquet de journaux de l'utilisateur contenait quatre fois « GetVoices FAILED, voice list empty: (0x80045039) », puis « SwitchToSetup ». Ce n'est pas un jeton isolé qui était illisible (le cas de la 3.3) : sap.GetVoices() ou .Count lève lui-même SPERR_NO_MORE_ITEMS, alors que sap.Voice.GetDescription() fonctionne. Nouveau : RegistryVoiceTokens() dans sapi.ahk construit un SAPI.SpObjectToken par clé sous Speech\Voices\Tokens (HKLM et HKCU) via SetId - uniquement en cas d'échec. Seules les entrées ayant un nom et un CLSID sont listées, car une entrée défectueuse ne lève rien ici, GetDescription() renvoie simplement "". AddVoiceNodes() dans menu.ahk remplace les trois boucles identiques et ajoute l'entrée de secours quand la liste est vide. Vérifié avec un banc d'essai qui charge le vrai sapi.ahk et fait lever exactement cette erreur à GetVoices : mêmes voix que par la voie normale, toutes utilisables, deux entrées volontairement défectueuses ignorées. La panne réelle n'a pas pu être reproduite ; pas encore testé sur la machine de l'utilisateur. Outil de connexion 3.4 : une voix défectueuse ne peut plus rendre l'outil muet Simple : Une voix dont le programme a été supprimé mais dont l'entrée est restée dans Windows figure toujours dans la liste des voix. La choisir était accepté par Windows sans aucune erreur - et l'outil était ensuite muet, et même définitivement depuis le menu principal, car le choix est enregistré. L'outil essaie désormais chaque voix de façon inaudible avant de l'accepter. Si elle ne fonctionne pas, tout reste en l'état et l'outil dit « Cette voix ne fonctionne pas, choisissez-en une autre ». Au démarrage non plus, une voix enregistrée qui ne parle plus n'est pas appliquée ; la voix par défaut de Windows reste en place. Technique : Mesuré avec un enregistrement ayant un nom et un CLSID mais pas de moteur : sap.Voice := token est accepté, seul Speak lève 0x80040154 (classe non enregistrée). VoiceWorks() dans sapi.ahk parle donc sur un SpVoice séparé dans un SpMemoryStream, 16 à 31 ms, sans jamais toucher à la voix en cours. SetSapiVoiceByName et SetToolVoiceByName renvoient maintenant le succès, VoiceSelectAction et ApplyToolVoice en tiennent compte. SAPI2SR est exempté : le pont transmet le texte au lecteur d'écran quel que soit le flux de sortie, l'essai serait donc audible. Une voix qui se charge mais ne produit que du silence n'est pas détectée. Installateur 5.2 : le paquet de journaux liste toutes les voix Windows Simple : « Collecter les journaux » dans l'installateur ajoute désormais un fichier voices.txt au paquet. Il nomme chaque voix enregistrée dans Windows et indique si le programme correspondant existe encore. En cas de problème de voix dans l'outil de connexion ou dans le jeu, on voit ainsi tout de suite quelle entrée est défectueuse. Livré avec l'installateur 5.2. Technique : LogCollector.WriteVoiceRegistrations lit Speech\Voices\Tokens, Speech\Voices\TokenEnums et Speech_OneCore\Voices\Tokens dans les vues 64 bits et 32 bits du registre (l'outil de connexion est en 64 bits et ne voit pas les voix uniquement 32 bits) ainsi que dans HKCU, plus DefaultTokenId. Par entrée : nom, CLSID, la DLL InprocServer32 avec vérification qu'elle est enregistrée et présente sur le disque, VoicePath/LangDataPath et les attributs. Installateur 5.2 : mesures contre les fausses alertes de Windows Defender Simple : Quelques joueurs ont signalé que Windows Defender avait supprimé l'installateur fraîchement téléchargé comme dangereux. L'installateur n'est pas dangereux ; notre propre analyse Defender, avec des signatures à jour, n'y trouve rien. Deux choses qui rendaient le fichier plus suspect que nécessaire sont corrigées : ses propriétés disent maintenant ce qu'il est et de qui il vient (produit « Sku Installer », société « Sku », auteur JeanStiletto, lien vers le projet) au lieu d'être vides, et un fichier du pont NVDA fourni ne porte plus une signature qui venait du PC du développeur et ne signifiait rien sur une autre machine. Pour être honnête sur la limite : l'installateur n'est toujours pas signé avec un certificat acheté. Chaque nouvelle version démarre donc sans réputation de téléchargement, et Defender peut encore la retenir pendant ses premiers jours. Si cela arrive : ouvrir Sécurité Windows, Historique de protection, choisir l'entrée et autoriser ou restaurer le fichier - et nous envoyer le nom de la détection, s'il vous plaît. Technique : SkuInstaller.exe n'était pas et n'est pas signé Authenticode ; release.ps1 n'a aucune étape de signature, seules les DLL du pont sont signées, sur la machine de l'utilisateur, par sku-nvda-voice-sign.ps1. Un exe non signé n'a qu'une réputation cloud par hash, et l'exe de la v43.5 avait un jour et neuf téléchargements au moment des signalements. L'analyse locale (moteur 1.1.26080.3, signatures 1.459.278.0) est propre pour l'exe, la charge utile du pont et l'AutoHotkey fourni : le verdict vient donc du cloud/ML ou de l'analyse comportementale, pas d'une signature statique. Modifié : Company, Product, AssemblyTitle, Description, Authors et Copyright dans SkuInstaller.csproj (tous les champs disaient « SkuInstaller », copyright vide). Et x86\sapi2sr_engine.dll dans payload\sapi2sr-payload.zip avait été recopiée depuis le dossier Program Files du développeur et portait encore la signature jetable de cette machine (partout ailleurs « signé par une racine non approuvée ») ; c'est maintenant la DLL nue, 123320 -> 115712 octets. Le hash PE Authenticode exclut la signature, les deux empreintes du script de signature correspondent toujours (5A76A572... x64, 1281E2B9... x86) et la signature sur la machine de l'utilisateur est inchangée. dev\rework-docs\nvda-voice-signing\strip_payload_signature.py fait ce retrait pour la prochaine mise à jour de la charge utile. Non modifié, et toujours ce que voit une analyse comportementale : exécution avec élévation, exécutables embarqués, regsvr32 /s, un PowerShell caché qui ajoute un certificat de signature de code au magasin racine de la machine, auto-remplacement. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.5 Aperçu Corrections Base de données des quêtes : ouvrir « Tout » (ainsi que « Commence dans la zone » et « Quêtes à proximité ») figeait le jeu plusieurs secondes. Chat : après ouverture de la ligne de chat avec « / », l'ancien canal était annoncé (par ex. « À Kai »), alors que seule la commande tapée ensuite décide du canal. Voix : certaines lignes restaient muettes au hasard via le pont NVDA/SAPI (par ex. un objet de la liste d'équipement) alors que leurs voisines étaient lues. Quête : « Le nettoyage selon Raene » (Bâtonnet de Dartol, Orneval) n'avait pas de cible ; elle mène maintenant au Coffre rouillé laissé par les Gelées pourrissantes. Voix : les caractères tapés et les annonces annulées arrivaient en retard - des lettres d'une ligne de chat fermée depuis longtemps surgissaient plus tard dans un autre menu. Outil de connexion 3.3 : une voix Windows endommagée sur le PC faisait quitter l'outil avec une fenêtre d'erreur (GetVoices, 0x80045039) juste après l'annonce de sa version. Voix : en parcourant vite des entrées longues via le pont NVDA/SAPI, l'entrée précédente était lue jusqu'au bout avant celle choisie. Base de données des quêtes : ouvrir les listes de quêtes figeait le jeu Simple : Ouvrir la liste « Tout » sous Quête > Base de données des quêtes provoquait depuis la v43.2 un blocage de plusieurs secondes. La liste s'ouvre de nouveau instantanément ; les entrées d'une quête (acceptation, objectif, remise ...) ne sont construites qu'en entrant dans la quête. Il en va de même pour « Commence dans la zone » et « Quêtes à proximité ». Technique : « Tout » construisait d'emblée le sous-menu complet (CreateQuestSubmenu) pour chacune des plusieurs milliers de quêtes de la base. Depuis la v43.2, la branche objectif de ce sous-menu résout géométriquement le point de route le plus proche pour les quêtes à lieu déclencheur (triggerEnd) via GetTriggerEndWps - un balayage complet du cache de points de route d'un continent - et depuis la v43.3 pour chacune de ces quêtes, et non plus seulement celles sans autre objectif. Une ouverture coûtait ainsi environ 170 balayages de continent dans une seule image. Les entrées de quête sont désormais des nœuds dynamiques avec leur propre BuildChildren, comme « Quêtes actuelles » l'a toujours été. Chat : annonce de canal erronée après ouverture avec la touche slash Simple : Si vous aviez chuchoté à quelqu'un en dernier et ouvriez la ligne de chat avec « / », vous entendiez « À Kai » - puis, juste après « /g », « Guilde ». La première annonce était fausse : tant que la ligne commence par un slash, rien ne part vers ce canal, c'est la commande qui décide. L'annonce reste désormais silencieuse tant qu'une commande est dans la ligne, et dit le canal dès qu'il est fixé (« /g » devient « Guilde ») ou quand le slash est effacé. Un passage volontaire à « Dire » (« /s ») est maintenant annoncé lui aussi, sauf si Dire était de toute façon le canal par défaut. Au rappel d'une ligne envoyée (flèche haut), le préfixe de canal est omis si la ligne est une commande. Technique : L'en-tête de Blizzard affiche toujours l'attribut chatType, et à l'ouverture c'est le canal « sticky » du dernier message envoyé (le chuchotement l'est aussi, tellTarget compris). La touche slash appelle ChatFrameUtil.OpenChat("/") : le même en-tête plus un « / » dans le champ, posé seulement au prochain OnUpdate (editBox.setText), si bien que les appels UpdateHeader de l'ouverture tournent encore avec un champ vide. Un texte qui commence par « / » ne part jamais vers le canal de l'en-tête : ce n'est que lorsque ParseText résout la commande (« /g » à l'espace, « /w Nom » après le nom) que Blizzard fixe chatType, SetText(reste) puis UpdateHeader. Sku vérifie désormais le texte effectif, setText en attente compris (SkuChat:EditboxTextIsCommand), à chaque point d'annonce (hooks UpdateHeader, focus, parole différée) ; un nouveau hook OnTextChanged couvre l'effacement du slash, pour lequel Blizzard n'appelle aucun UpdateHeader. Voix : certaines lignes restaient muettes au hasard Simple : En parcourant rapidement une liste, une ligne isolée n'était parfois pas lue - par ex. « Gants de combat naga » dans l'équipement, alors que ses voisines l'étaient - et elle restait muette même en repassant dessus plusieurs fois. Cela concernait le pont NVDA/SAPI et pouvait arriver partout dans Sku, surtout après un /reload. Chaque ligne est de nouveau lue de façon fiable. Technique : Le client WoW met en cache la voix rendue par texte et la rejoue lors d'une répétition sans rappeler la voix - avec le pont, cette relecture est du silence. Sku ajoute donc à chaque texte une suite d'espaces insécables comptée par texte (1..64) pour que chaque répétition soit un texte nouveau. Mais le compteur ne vivait qu'en Lua et repartait de zéro à chaque /reload, alors que le cache du client survit au reload (les identifiants d'énoncés continuent sans rupture). Une ligne revue après le reload pouvait ainsi retomber dans une plage déjà rendue et restait muette jusqu'à ce que le compteur en sorte - six pressions d'affilée dans le journal, chacune avec un SpeakText + STARTED + FINISHED propres. Les compteurs vivent maintenant dans SkuOptionsDB.global (bttsCacheBust) et continuent au-delà du reload ; de plus l'espace par texte passe de 64 à 512 variantes (des espaces insécables en tête, 1..8, portent la partie haute du compteur). Quête : « Le nettoyage selon Raene » (Bâtonnet de Dartol) n'avait pas de cible Simple : La quête 1027 « Le nettoyage selon Raene » en Orneval demande le dernier morceau du Bâtonnet de Dartol, le pommeau de fer. Sku ne connaissait aucun lieu pour lui, « Cible de quête » restait vide. La cible mène maintenant dans la zone des gelées au sud-est de la Retraite de Raynewood : les Gelées pourrissantes y laissent un Coffre rouillé à leur mort, et le pommeau s'y trouve. Technique : L'objet 5519 (Iron Pommel) renvoie correctement à l'objet de jeu 19021 (Rusty Chest) dans la base, mais cet enregistrement n'avait aucune coordonnée de spawn, ni dans objects.lua ni dans objects_fixes.lua - la branche « item » de GetResultingWps ne trouvait rien. objects_fixes.lua (TBC et Era) ajoute maintenant les 44 positions du coffre relevées par Wowhead en Orneval (zone 331, environ 68-78 / 68-80) ; c'est la même surface que les spawns de la Gelée pourrissante (3928) qui laisse le coffre. Voix : les caractères tapés et les annonces annulées arrivaient en retard Simple : Si vous tapiez un message un peu long puis fermiez la ligne de chat, vous continuiez à entendre les lettres ensuite - une par une, alors que vous naviguiez depuis longtemps dans un autre menu ; près d'une minute dans un enregistrement. Il en allait de même pour une annonce annulée : après « Annulé » arrivait encore un reste de l'ancienne ligne, par exemple une distance du menu des points de passage que vous veniez de fermer. Tout ce qui reste en attente à la fermeture ou à l'annulation est désormais abandonné. L'écho des touches reste complet - aucune lettre n'est perdue. Techniquement : Le client accepte chaque SpeakText sans limite mais ne les joue qu'un à la fois, et C_VoiceChat.StopSpeakingText n'annule que l'énoncé EN COURS - il n'existe pas de vidage de file comme le PurgeBeforeSpeak de SAPI. Tout ce qui est déjà transmis est donc irrécupérable. La voie de frappe transmettait un énoncé par touche, trois à quatre par seconde, alors que le client en traite environ un : une ligne tapée de 104 caractères garait ainsi environ 80 énoncés dans le client, qui ressortaient ensuite un par seconde. Pour la même raison CancelBttsOutput et EndScope restaient sans effet - ils vident la file propre à Sku, où les caractères n'ont jamais été (85 des 93 appels à EndScope d'un enregistrement n'ont rien abandonné). Les caractères attendent maintenant dans Sku, un seul part vers le client à la fois, et le suivant seulement à son PLAYBACK_FINISHED - le moment où le client accepte de nouveau un énoncé (chaque STARTED de l'enregistrement coïncide avec un FINISHED). Rien n'est donc jamais transmis sans être déjà en train de jouer, et une annulation atteint tout. Une courte fenêtre d'annulation couvre le reste : après une annulation franche plus rien n'est transmis et tout énoncé qui démarrerait malgré tout est stoppé aussitôt. Cela reprend la structure de NVDA, qui garde lui aussi la file et ne donne à la voix qu'un énoncé à la fois. Outil de connexion 3.3 : plantage au démarrage à cause d'une voix Windows endommagée Simple : Après une installation neuve, l'outil annonçait encore « version 3.2 » puis s'arrêtait avec une fenêtre d'erreur (« Error: (0x80045039) Specifically: GetVoices »). En cause : une voix endommagée dans Windows - par exemple une voix désinstallée qui a laissé des restes. L'outil ignore désormais une telle voix, la note dans log.txt et démarre normalement ; elle manque seulement dans le menu « choisir la voix ». Technique : 0x80045039 est SPERR_NO_MORE_ITEMS : SAPI annonce N voix, mais l'une d'elles est illisible (entrée cassée sous HKLM\SOFTWARE\Microsoft\Speech\Voices\Tokens). L'outil plantait en construisant le menu des voix, dans la boucle for sur sap.GetVoices(). GetVoices() et SetSapiVoiceByName() passent maintenant par la nouvelle fonction ReadableVoiceTokens(), qui lit chaque voix par Item(i) dans son propre try ; une voix illisible est ignorée et journalisée, et si la liste elle-même échoue, elle reste vide. SapiInit() lit la voix par défaut avec la même protection. Pas encore testé sur un PC avec une voix cassée. Voix : un défilement rapide via le pont NVDA lisait l'entrée longue précédente jusqu'au bout Simple : Avec la voix du pont NVDA/SAPI, parcourir vite une liste aux entrées longues (liste des points de passage, textes de quête, historique du chat) lisait d'abord l'entrée précédente en entier, puis seulement celle sous le curseur. L'ancienne entrée est maintenant coupée après un bref instant et celle choisie est lue, comme avec une voix Windows. Les textes longs en plusieurs parties restent lus en entier, et l'écho de frappe ne change pas. La correction nécessite le nouveau pont NVDA installé par l'installateur Sku 5.1 : mettez à jour via l'installateur (pas seulement le zip de l'addon) puis redémarrez complètement WoW. Technique : StopSpeakingText du jeu n'arrête que la lecture du client. Sur le pont, le texte est déjà dans la file de NVDA, et SAPI ne transmet jamais « purge before speak » à un moteur : rien sur le chemin ne pouvait interrompre NVDA. Seul l'énoncé lui-même, balisage compris, atteint le moteur - l'intention voyage donc à l'intérieur, comme avec speak(text, interrupt) de Prism : chaque arrêt pose un drapeau, et la ligne suivante porte . Le moteur livré par l'installateur (SAPI2SR avec le patch C de Sku) appelle nvdaController_cancelSpeech en voyant ce signet, puis transmet le texte. Les lignes transmises sans arrêt préalable ne portent pas de marque et restent en file. Les vraies voix SAPI ignorent le signet ; un moteur plus ancien le saute. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.4 Aperçu Nouveautés Pawn : Addons > Pawn regroupe les réglages d'infobulle de Pawn, et les infobulles Sku annoncent l'amélioration en pour cent, la différence par attribut et les attributs manquants. Par Yennesta. Nouvelle touche « ouvrir le compteur de dégâts » : accès direct aux rapports de Details ; les raccourcis ont désormais leur propre groupe « Extensions ». Changements Chat : à l'envoi ou à l'annulation, un écho clavier encore en cours est arrêté de façon fiable, sans jamais couper la lecture du message envoyé. Les annonces d'un menu ou d'une fenêtre s'arrêtent avec la fenêtre : ce qui attend encore à la fermeture est abandonné, ce qui est en cours est stoppé. Corrections Mac : les touches fléchées, de fonction et de page arrivaient deux fois - les emplacements vides de sacs et de barres étaient sautés, et le lecteur d'infobulle repartait du début à chaque Maj+Flèche bas. Moniteur de vie du raid : un rôle attribué à la main tombait sur le mauvais joueur, et sans détection de rôle le moniteur restait muet. Marchand : l'onglet Rachat lisait et achetait l'objet portant le même numéro dans le stock normal. La comparaison avec l'objet équipé manquait parfois dans les infobulles - l'infobulle de lecture avait perdu son propriétaire. Lecteur d'infobulle : en fin de texte, par exemple dans la liste d'amis, Maj+Flèche bas alternait entre silence et dernière ligne. Les annonces demandées par touche (vie de la cible, distance, cible ...) restaient muettes à une seconde pression dans la même seconde. Mac : les touches fléchées et de fonction arrivaient deux fois Simple : Sur Mac, le défilement rapide sautait les emplacements vides de sacs et de barres d'action, et le lecteur d'infobulle recommençait au début à chaque Maj+Flèche bas. Les deux sont corrigés ; Windows n'a jamais été touché. Technique : Pour les touches sans caractère - flèches, touches F, page haut et bas, début, fin, insertion, suppression - le client Mac transmet à OnChar les caractères de substitution du système, pris dans la zone Unicode à usage privé (U+F700 et suivants) ; Windows ne déclenche pas OnChar pour ces touches. Chaque pression de flèche atteignait donc le menu deux fois : comme touche liée et comme caractère fantôme transmis sans contrôle au gestionnaire de touches. Le compteur de double appui y comptait deux pressions par pression (chaque entrée « Vide » sautée), et la règle « toute autre touche ferme le lecteur » fermait le lecteur d'infobulle après chaque Maj+Flèche bas. Le menu ne transmet plus depuis OnChar que les caractères connus de la recherche par lettres (liste positive), les deux hooks d'écho clavier rejettent toute la zone à usage privé U+E000 à U+F8FF, et un double appui exige désormais deux fois la même touche. Les caractères rejetés vont dans l'anneau de débogage avec leurs octets, pour qu'un testeur puisse prouver le fantôme sur son client. Diagnostiqué et paré en premier par Yennesta (PR 19). Moniteur de vie du raid : les rôles sur le bon joueur Simple : Attribuer un rôle à la main à un membre du raid touchait souvent un autre joueur dès que l'ordre des groupes différait de l'ordre d'arrivée. Et sans détection automatique de rôle, le moniteur restait complètement muet pour ce joueur. Technique : Le menu d'attribution numérote les membres par leur numéro audible, trié par groupe, et enregistrait le rôle sous ce numéro ; l'évaluation de la vie le lisait via l'index réel raidN. Le menu garde sa numérotation et retient l'index réel par entrée, sous lequel il enregistre et lit. Sans rôle (SkuAuras désactivé, ou aucun détecté), l'identifiant restait nil et UNIT_HEALTH indexait la table de filtres avec - abandon de toute l'évaluation. La résolution du rôle est désormais une fonction : attribution, puis SkuAuras, sinon la catégorie « Sans rôle ». Par Yennesta. Marchand : le rachat rachète bien ce qui a été vendu Simple : Dans l'onglet Rachat, Sku nommait le mauvais objet et, à la confirmation, achetait l'offre du marchand portant le même numéro au lieu de la pièce vendue. Technique : L'onglet Rachat réutilise les boutons et numéros du stock normal. Sku les lisait avec SetMerchantItem et achetait avec BuyMerchantItem, tous deux relatifs au stock. Onglet Rachat actif, la lecture passe désormais par SetBuybackItem et l'achat par BuybackItem ; un rachat est une seule pile, il n'y a donc pas de sous-menu de quantité. Par Yennesta. Pawn : amélioration et différences de caractéristiques dans l'infobulle Simple : Avec Pawn installé, l'infobulle Sku d'un objet annonce juste sous le nom l'amélioration en pour cent et l'échelle, ajoute à chaque ligne d'attribut la différence avec l'objet équipé (dite « plus » ou « moins », dégâts par seconde avec une décimale) et liste les attributs que l'objet équipé possède et que le nouveau n'a pas. Addons > Pawn regroupe l'interrupteur « Pawn dans les infobulles Sku » et les réglages d'infobulle propres à Pawn ; sans Pawn, il n'y a qu'un rappel de son absence. Par Yennesta. Technique : Nouveau fichier SkuCore/pawnIntegration.lua ; chaque appel à Pawn est protégé et le passage sur l'infobulle tourne dans pcall, sans Pawn rien n'est ajouté. Par rapport à la contribution : l'interrupteur est une valeur par défaut du schéma de réglages SkuCore (activé), la correspondance de ligne ignore la casse (sur les clients anglais la statistique s'appelle « Damage Per Second », la ligne d'infobulle « damage per second » - la différence de DPS y disparaissait en silence), et un passage en échec écrit son erreur dans l'anneau de débogage. Touche « ouvrir le compteur de dégâts » et groupe de touches « Extensions » Simple : Une nouvelle touche, non attribuée par défaut, mène directement à Addons > Damage Meter > Rapports. Les raccourcis ont désormais un groupe « Extensions » avec les touches d'AtlasLoot et du compteur de dégâts. Technique : Liée et évaluée comme la touche du navigateur de donjons ; le chemin de menu passe par des identifiants de noeuds (Addons, DamageMeter, Reports) et fonctionne donc dans chaque langue. Les entrées de rapport fournissent leur texte à la lecture (une fonction au lieu d'une table, comme les entrées de l'hôtel des ventes) : données Details fraîches, aucun coût pour les combats non lus, et cela marche aussi quand le curseur arrive sur une entrée par touche sans passer par OnEnter. Touche et identifiants par Yennesta. La comparaison avec l'objet équipé manquait parfois Simple : Avec Maj+Flèche bas sur un objet, la section « objet actuellement équipé » manquait parfois, apparemment au hasard. Elle est désormais toujours là. Technique : Sku lit les infobulles via un GameTooltip partagé et invisible. Un GameTooltip ne remplit ses lignes que tant qu'il a un propriétaire, et le perd tout seul : un appel sans contenu (emplacement de barre, de sac ou d'équipement vide, objet pas encore chargé) le masque et efface le propriétaire. Dès lors chaque lecture ne renvoyait rien en silence, jusqu'à ce qu'un autre chemin appelle SetOwner par hasard. Trois endroits l'avaient déjà corrigé pour eux-mêmes, ce qui donnait au défaut son air aléatoire. Les 18 points de lecture rétablissent désormais le propriétaire avant chaque lecture via une fonction commune. Lecteur d'infobulle : la fin d'un texte Simple : Dans la liste d'amis (et partout ailleurs), le lecteur ne restait pas sur la dernière ligne avec Maj+Flèche bas mais alternait entre silence et dernière ligne - comme une boucle sans fin. Il relit désormais la dernière ligne à chaque pression. Technique : En fin de texte, le lecteur retransmet la même ligne à la couche vocale. Son garde-fou anti-doublons de la v43.2 rejette une ligne identique dans la même seconde sauf si elle est marquée comme déclenchée par touche - et le lecteur ne marquait jamais. Chaque ligne du lecteur, liens compris, porte désormais la marque. Les annonces demandées par touche ne restent plus muettes Simple : Vie de la cible, distance, annonce de cible, infos de jet, se tourner vers la balise, touches de points de passage et autres : une seconde pression dans la même seconde avec la même réponse restait muette. La réponse vient désormais à chaque pression. Technique : Le même garde-fou ; seul le gestionnaire de touches du menu marquait jusqu'ici. Plutôt que de marquer chaque annonce une à une, les trois répartiteurs de touches (cadre principal, cadre de contrôle SkuCore, SkuNav) retiennent la frame de leur appel, et la couche vocale traite toute transmission dans les 0,1 seconde qui suivent comme déclenchée par touche. La ré-annonce d'une reconstruction de menu vient d'un minuteur, dans des frames ultérieures, hors de cette fenêtre ; le gestionnaire du menu continue de marquer explicitement. Portées vocales : les annonces s'arrêtent avec leur fenêtre Simple : Deux cas avec des voix SAPI lentes : après envoi ou annulation dans le chat, la voix finissait de lire une ligne rappelée par Flèche haut, et après trois apprentissages chez le maître et Échap, le maître était encore annoncé trois fois. Les deux sont réglés : l'écho du chat est coupé à la fermeture - jamais la lecture du message envoyé - et ce qu'un menu ou une fenêtre avait encore à dire à la fermeture est abandonné ou stoppé. Les annonces de combat, de chat ou de navigation ne sont pas concernées. Technique : Une pression de touche seule n'interrompt toujours rien (règle de la v43.2), et sur la passerelle du lecteur d'écran la fin d'un énoncé est indéterminable, car FINISHED arrive dans la frame de transmission - la comptabilité « quelque chose parle » y est toujours vide, raison pour laquelle même l'arrêt à la fermeture du menu était supprimé comme inutile. Ce qui est connu exactement, c'est ce qui a été transmis en dernier et pour qui. Une ligne peut désormais porter une portée (« menu » pour les annonces de menu, « echo » pour la voie de frappe), et SkuVoice:EndScope abandonne les lignes en attente de cette portée à sa fin et n'arrête la voix que si l'énoncé en cours lui appartient. C'est appelé à la fermeture du menu et dans le hook Hide de chaque fenêtre surveillée, donc aussi sur l'Échap du jeu. L'arrêt doux du champ de chat interroge désormais SkuVoice:IsEchoInFlight au lieu de la règle d'une seconde : l'écho est coupé quelle que soit sa durée ; si autre chose a été transmis depuis - avant tout la réponse du serveur au message envoyé - rien n'est arrêté. Yennesta avait proposé l'annulation inconditionnelle dans la PR 19 ; ceci en est la forme ciblée. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.3 Aperçu Nouveautés Le programme d'installation existe désormais aussi pour Mac - installation, mise à jour et auto-mise à jour comme sous Windows. Merci à Yennesta. Programme d'installation 5.0 : sept addons compagnons éprouvés comme Questie et Deadly Boss Mods peuvent être installés en plus et sont tenus à jour - Windows et Mac, Anniversary et Classic Era. Mac : le programme d'installation et l'outil de connexion parlent allemand, anglais et français ; l'outil de connexion reconnaît aussi les clients français. Changements Se tourner vers l'unité et Se tourner vers la cible tournent désormais comme la rotation vers la balise : le chemin le plus court, un quart de seconde au maximum. Corrections Synthèse vocale : certaines annonces restaient muettes alors que le son était joué - surtout les lignes courtes que l’on entend en boucle. Touche Interagir : après un kill, elle attrape parfois une créature au loin et vous y fait courir au lieu de dépouiller - Sku vous prévient maintenant par un son. Mac : la synthèse vocale lisait à chaque ligne un balisage technique de contrôle. Maître des écuries : le menu affichait et échangeait les mauvais emplacements de familier, et l'annonce d'échange signalait un échec trop tôt. Chat : un onglet Journal de combat restait complètement muet. Le Ciblage au cadran était complètement mort sur le client Anniversary - il fonctionne de nouveau, testé en raid à 25. Français : quatre textes des réglages du chat parlaient encore anglais. Français : quatre textes du chat parlaient encore anglais Simple : Sur un client français, le réglage des flèches dans la saisie du chat, l'annonce du canal avec son explication et le message pour une commande inconnue s'entendaient encore en anglais. Ils sont maintenant traduits. Technique : Ces quatre textes de la v43.1 manquaient dans le magasin de traduction à partir duquel le fichier de langue français est généré. En corrigeant cela, il est apparu que 29 textes écrits à la main dans le fichier généré pour la v43.2 n'existaient qu'à cet endroit - la prochaine génération les aurait silencieusement ramenés à l'anglais. Tous sont désormais repris dans le magasin, et l'outil de génération n'écrit plus le fichier que sur commande explicite, au lieu de le faire pour tout argument inconnu. Synthèse vocale : les annonces répétées ne restent plus muettes Simple : En naviguant dans les menus et les infobulles, on n’obtenait parfois que le son, sans l’annonce elle-même. Cela touchait surtout les lignes courtes que l’on entend en boucle toute la soirée - « menu fermé », « accepter », « buffs », ou un simple caractère tapé. Technique : Le client conserve en mémoire la parole déjà synthétisée pour un texte donné et, en cas de répétition, la rejoue depuis ce cache sans rappeler la voix. Via une passerelle vers un lecteur d’écran, cette relecture est muette. Sku ajoute donc à chaque ligne un marqueur inaudible afin que chaque appel soit régénéré - jusqu’ici à partir d’un compteur qui recommençait toutes les 64 annonces. Une ligne revenant exactement 64 annonces plus tard était donc identique caractère pour caractère et retombait dans le cache. Le compteur est désormais tenu par texte et non plus en commun ; une ligne vue pour la première fois reçoit toujours sa valeur de départ du compteur commun, afin que le vidage de la table (à 512 lignes distinctes) ne renvoie aucune ligne sur sa propre première valeur. Rejoué sur un enregistrement de 1307 annonces : 27 répétitions identiques auparavant, 80 avec un compteur par texte privé de cette valeur de départ - donc pire - et 0 dans la version livrée. Mac : la synthèse vocale ne lit plus le balisage de contrôle Simple : Sur Mac, la voix lisait à voix haute, avec les lignes, des caractères de contrôle destinés uniquement à la technique. Ils ont disparu là-bas. Technique : Pour la poignée de main avec le lecteur d'écran, Sku ajoute un marqueur à chaque ligne ; le client Mac ne filtre pas ce balisage de la synthèse et le lisait tel quel. Sur Mac, le marqueur est désormais omis ; les caractères inaudibles qui déjouent le cache de répétition restent, si bien que chaque répétition y est elle aussi régénérée. Idée de Yennesta. Se tourner vers l'unité : plus rapide et plus précis Simple : « Se tourner vers l'unité » et « Se tourner vers la cible » tournent désormais exactement comme la rotation vers la balise : par le chemin le plus court, en un quart de seconde au maximum, au lieu de chercher jusqu'à une seconde et demie en tournant vers la droite. Technique : Le coeur de rotation construit et longuement testé pour la v43.2 (alignement de direction, plafond de vitesse, anticipation en mouvement, remise à plat avec le verrou d'inclinaison) a été séparé de la résolution des points de passage ; la rotation vers une unité l'appelle donc dès que le client fournit des coordonnées pour l'unité. L'ancienne recherche par barres de nom reste en secours pour les instances, où il n'y a pas de coordonnées. La résolution de la cible vers une unité suit une idée de Yennesta. Maître des écuries : l'échange de familier retrouve les bons emplacements Simple : Dans le menu du maître des écuries du chasseur, les emplacements étaient décalés - le mauvais familier était affiché et échangé. De plus, sur une connexion lente, l'échange annonçait à tort que le familier n'avait pas pu être changé alors qu'il avait réussi. Les deux sont corrigés, et l'annonce donne désormais le nom du nouveau familier. Technique : La fenêtre d'écurie de Blizzard utilise les indices d'action 1 à 3 (familier actif plus deux emplacements) ; Sku interrogeait un cran trop bas. Correctif de Yennesta. L'annonce de réussite ne se décide en outre plus sur le premier événement d'écurie (qui se déclenche déjà à la prise en main), mais attend que le nom du familier actif corresponde ou que deux secondes soient passées - et elle est maintenant traduite dans les trois langues. Nouveau : le programme d'installation existe aussi pour Mac Simple : Sku s'installe et se tient à jour sur macOS aussi confortablement que sous Windows : un programme d'installation avec sortie vocale, auto-mise à jour et les mêmes composants. La page de téléchargement propose un lien par plateforme. Contribué par Yennesta - merci ! Technique : La branche Mac est accrochée au même flux de publication que Windows : la détection de version suit la redirection releases/latest de GitHub au lieu de lire une page web, et les fichiers avec leurs sommes sha256 sont vérifiés localement à la publication puis attachés à la même publication. Touche Interagir : un son d'avertissement quand elle attrape la mauvaise cible Simple : Juste après la mort d'une créature, la touche Interagir (G par défaut) prend parfois pour cible une tout autre créature attaquable - même lointaine - au lieu du cadavre devant vous, et vous y fait courir automatiquement au lieu de dépouiller. Sku joue maintenant un son d'avertissement à cet instant précis, pour que vous entendiez aussitôt la mauvaise prise et puissiez annuler la course automatique à la main. Technique : La touche appelle InteractUnit("anyinteract") de Blizzard avec un ciblage relâché ; quand le cadavre tout juste dépouillé disparaît sous l'appui, le client attrape de façon relâchée l'unité attaquable la plus proche à portée de Tab, en fait votre cible dure et y court. La touche elle-même ne peut pas être remplacée (InteractUnit est protégée), et la cible ne peut pas non plus être effacée depuis un addon (ClearTarget est protégée aussi, en combat comme hors combat). Reste la détection : juste après qu'un cadavre quitte le réticule, une cible vivante, attaquable et PAS en combat avec vous - alors le son. Une cible choisie volontairement au clavier (Alt+H / Ctrl+H) ne le déclenche pas. Programme d'installation 5.0 : installer des addons compagnons éprouvés Simple : Sur la page des composants, sept addons éprouvés peuvent désormais être cochés - Questie, Deadly Boss Mods, AtlasLoot, Details, GTFO, BugSack avec BugGrabber, et Pawn. Ce qui est coché, le programme d'installation l'installe avec Sku et le tient à jour à chaque exécution - sous Windows et sur Mac, pour Anniversary et Classic Era, chaque client recevant la version qui lui correspond. Tout est activé par défaut sauf Pawn. Technique : Les deux plateformes lisent le même catalogue. Les addons publiés sur GitHub viennent directement de là (résolus via la redirection releases/latest, sans clé d'API ni limite de débit) ; le reste est épinglé sur des fichiers CurseForge vérifiés par sommes sha256. Deadly Boss Mods est une entrée composée de quatre paquets, et BugSack et BugGrabber partagent délibérément une seule case. Chaque client reçoit la version qui lui correspond - Pawn pour Classic Era vient par exemple du paquet Classic. Mac : programme d'installation et outil de connexion en trois langues Simple : Les deux parlent désormais allemand, anglais et français. La langue se change dans le menu correspondant et suit sinon la langue du système. L'outil de connexion reconnaît en outre les clients français et lit les lignes de niveau mot pour mot - un client allemand redit donc « Stufe 36 » au lieu de « Level 36 ». Technique : L'application, le script d'installation et l'outil de connexion partagent un même choix de langue (réglage enregistré avant langue du système avant anglais) ; les valeurs enregistrées comme les noms des packs vocaux restent allemandes en interne pour que les préférences existantes continuent de fonctionner. La détection d'écran de l'outil de connexion est indépendante de la langue de l'interface. Chat : le journal de combat reçoit de nouveau ses événements Simple : Un onglet de chat « Journal de combat » restait complètement muet. Il se remplit de nouveau. Merci au testeur de la communauté pour le signalement accompagné de la solution. Technique : L'onglet s'était abonné à l'événement COMBAT_LOG_EVENT, qui ne se déclenche pas sur ce client comme le code l'attend ; il s'abonne désormais à COMBAT_LOG_EVENT_UNFILTERED - le même événement que les autres modules de Sku utilisent depuis longtemps. Le Ciblage au cadran fonctionne de nouveau Simple : Le ciblage par le pavé numérique (entrée « Ciblage au cadran » dans le menu Sku) était complètement mort sur le client Anniversary - aucun appui n'arrivait. Il fonctionne de nouveau, testé en raid à 25. En plus : dans un groupe normal (hors raid), il n'avait jamais trouvé personne, les membres 30 à 40 n'étaient pas composables, et un numéro à moitié saisi pouvait corrompre la saisie suivante - tout est corrigé. Technique : Le client moderne ne délivre les clics clavier que si le bouton les a enregistrés via RegisterForClicks - cela manquait, ce qui rendait toute la fonction muette. L'action se déclenche en outre exactement une fois par appui, quel que soit le réglage de clic de l'utilisateur. La disposition du groupe vient de party1 à party4 au lieu de la seule requête de raid, le seuil du premier chiffre laisse passer les sous-groupes 6 à 8, et la grille de touches remplie est écrite dans le journal de débogage à chaque changement. Note : les numéros correspondent à la section raid de la page d'aperçu ; la section groupe y compte toujours vous-même comme 1 et décale les autres - c'est voulu et non un défaut du ciblage. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.2 Aperçu Nouveautés Les quêtes peuvent être suivies ou retirées du suivi depuis le menu de quête - nouvelle entrée « Suivre la quête » en bas de chaque menu de quête, désactivable. Nouvelle liste « Quêtes à proximité » dans le menu de quête : tout le journal de quêtes en une seule liste, triée selon la distance de ce qui compte pour chaque quête - avec la distance et le point cardinal. Désactivée par défaut. Nouveau raccourci « Cibler la créature de quête la plus proche » (Alt+H) : cible une créature liée à un objectif de quête en cours - y compris celle qui se contente de faire tomber un objet requis. Le raccourci « Ennemi suivant en combat » (désormais sur Ctrl+H par défaut) trouve bien plus souvent quelque chose : il ne dépend plus du cône de vue. Nouveau raccourci « Annoncer l'infobulle complète de la cible » (Ctrl+Maj+V) : lit à voix haute l'infobulle complète de la cible actuelle, y compris ce que révèle la compétence de chasseur Connaissance des bêtes. Menu des sacs : nouvelle entrée « toute la banque » juste après « tous les sacs » tant que la banque est ouverte - tout le contenu de la banque en une seule liste à plat, exactement comme « tous les sacs » pour vos sacs. Ctrl+Entrée y déplace un objet vers vos sacs, comme il le faisait déjà dans chaque sac de banque. Chat : la ligne de saisie parle désormais comme tout autre champ de saisie de Sku - chaque caractère tapé, le caractère ou le mot sous le curseur, et avec la flèche haut les derniers messages envoyés, pour les renvoyer ou les modifier. Chat : le canal cible est annoncé dès qu'il n'est pas « Dire » - à l'ouverture et à chaque changement ; désactivable dans les réglages du chat, activé par défaut. Chat : une commande slash inconnue est maintenant annoncée au lieu d'être ignorée en silence. Nage et vol : Sku vous maintient à l'horizontale. Le nouveau verrouillage de l'inclinaison met fin à la plongée insidieuse lors des virages automatiques, et après une approche automatique vers un PNJ ou un appui sur monter/descendre, vous êtes activement redressé. Activé par défaut ; désactivable sous Paramètres -> Général. Nouveau raccourci « verrouillage de l'inclinaison en nage et en vol » (Ctrl+Maj+N) : verrouille et libère l'inclinaison manuellement - l'éteindre puis le rallumer vous remet à l'horizontale à tout moment. Expérimental : itinéraires de récolte pour les minerais, les herbes, les nuages de gaz et les coffres - Sku vous guide de gisement en gisement à travers la zone, le plus proche d'abord. Sous Réglages -> Scan -> analyse des ressources -> Itinéraires de récolte. Nouveau et à peine testé en jeu ; vos retours sont expressément souhaités. Nouveau menu « Barres de nom » sous Réglages -> Aides visuelles : taille des barres, mise en évidence de la cible, couleurs de classe et atténuation des autres barres - des réglages que le jeu sait faire mais n'offre nulle part dans sa propre fenêtre d'options. La fenêtre de lecture des textes de quête, infobulles, historique de discussion et wiki devient configurable : taille de police, police, couleurs, opacité et contour sous Réglages -> Aides visuelles -> Fenêtre de texte. Changements Se tourner vers la balise (T) : la rotation est désormais nettement plus précise. Avant, elle tournait si vite que le simple arrêt à la trame près pouvait coûter 20 degrés et plus de dépassement selon la fréquence d'images ; la vitesse s'adapte maintenant à l'ampleur de la rotation, et aucune rotation ne dure plus d'un quart de seconde. Se tourner vers la balise : en mouvement, la rotation anticipe - elle vise l'endroit où se trouvera le point de passage à la fin de la rotation. Cela met fin aux tours en rond autour des points proches, surtout connus en monture et en vol. Se tourner vers la balise : un appui pendant une rotation en cours est ignoré au lieu d'interrompre la rotation - appuyer plusieurs fois continue d'affiner comme avant. Navigation : sous « Point de passage direct », « Carte actuelle par distance » vient désormais en premier et « Récent » en second. Base de données des quêtes : « Commence dans la zone » affiche directement la liste triée par distance - le niveau intermédiaire « Par distance » a disparu. Base de données des quêtes : « Commence dans la zone » indique désormais aussi le point cardinal de chaque quête, et non plus seulement la distance. Base de données des quêtes : mise au niveau des données actuelles de Questie - 33 quêtes sans cible en ont désormais une, cinq quêtes manquantes ajoutées, plus environ 2000 petites corrections de noms, niveaux, donneurs de quête et prérequis. Sacs : après un clic droit sur un objet, le curseur reste sur cet objet au lieu de sauter en tête de liste. Sacs : les actions avec temps d'incantation (enchantement, appât de pêche, huile d'arme, kit d'armure, désenchantement) annoncent à nouveau l'objet une fois l'incantation terminée et y laissent le curseur. Sacs : une boîte de dialogue de confirmation comme « Remplacer l'enchantement existant ? » ne coûte plus la position - après « Oui » comme après « Non », on revient sur l'objet. Chat : « Ajouter le lien au chat » insère l'objet à la position du curseur au lieu de remplacer la ligne, et laisse le curseur devant - un canal comme « /p » tient donc encore avant. Chat : un lien d'objet compte pour un seul caractère à la lecture - les flèches gauche et droite franchissent tout le lien et en disent le nom. Chat : « Ajouter le lien au chat » ne ferme plus les sacs. Chat : les flèches déplacent le curseur de texte dans la saisie du chat ; désactivable dans les paramètres du chat. Auras : un enchantement d'arme (appât de pêche, poison, huile) s'adresse désormais comme un sort - l'événement « Enchantement d'arme expiré » avec « nom du sort ». Auras : nouvelle condition et nouvelle sortie « main de l'enchantement d'arme » (main droite ou main gauche). Menu : la fermeture d'une fenêtre ne reconstruit plus les données du menu qu'une seule fois au lieu de six. Menu : le filtre de saisie ignore les accents - « general » trouve maintenant « Fournitures générales », et « ingenieur » trouve « Ingénieur ». Sur un clavier AZERTY, la touche ù alimente désormais aussi le filtre. Français : les commentaires de points de passage existent maintenant en français - 207 textes sur 546 points de passage, traduits depuis l'original allemand. Programme d'installation : un WoW installé à un endroit inhabituel ne doit être indiqué qu'une seule fois - le programme d'installation retient désormais où se trouve chaque client et le retrouve tout seul à chaque mise à jour ultérieure. La fenêtre de lecture démarre désormais dans une police sans empattement en 18 points au lieu de Playfair Display en 12 - l'ancien réglage par défaut était la surface de texte la plus petite et la plus fine de tout l'addon. La barre de lecture se règle elle aussi en police et en couleurs, avec ses propres valeurs distinctes de celles de la fenêtre de texte. Hôtel des ventes : « Niveau décroissant » trie désormais toute la liste des résultats et non plus sa seule première page ; à niveau égal, c'est le prix à l'unité qui décide. Hôtel des ventes : « niveau » désigne partout le niveau requis de l'objet - et n'est lu que là où il distingue réellement les entrées. Corrections Menu : pendant la marche, un menu ouvert était comme mort - toutes les touches sauf Échap étaient avalées jusqu'à l'arrêt complet. Échap ne fermait pas le menu lorsqu'on se tenait juste sur un objet de quête - la fenêtre de quête se rouvrait sans fin. Menu : Entrée sur une entrée activé/désactivé dans une liste filtrée annonçait la première entrée de la liste au lieu de l'état basculé. Menu : une ligne redemandée par une touche est relue au lieu d'être avalée. Menu : le filtre de saisie interprétait le texte tapé comme un motif de recherche - une saisie comme « 50% » ou « (long » ne trouvait rien ou échouait. Écho clavier : les caractères tapés sont annoncés immédiatement ; plus de lettres avalées ni en retard. Synthèse vocale : les annonces devenaient régulièrement muettes pendant un moment lors de la navigation - surtout sur un résultat unique après filtrage. Sortie vocale : les liens d'objet étaient lus comme une suite de codes de couleur au lieu de leur nom - partout dans Sku, pas seulement dans le chat. Se tourner vers la balise : après des rotations déclenchées coup sur coup, la vitesse de rotation des touches de caméra pouvait rester quadruplée en permanence. La suppression d'un point de passage personnel retirait le mauvais identifiant interne, et « /skucheck wp » signalait quatre erreurs sur les points de passage rapides qui n'en étaient pas. Menu de quête : pour les quêtes à objectifs mixtes (« tuer X et ramasser Y »), l'entrée « Cible de quête » ne montrait que le premier type d'objectif - ennemis, sources d'objets et point d'exploration figurent désormais côte à côte. Menu de quête : pour 174 quêtes dont l'objectif est un LIEU, le lieu figurait bien dans les données, mais « Objectif de quête » ne répondait que « Vide » - la recherche portait sur un point de passage dont personne n'attribue jamais le nom. Le point de passage de route le plus proche est désormais calculé à partir des coordonnées. Pour 77 AUTRES quêtes, les données d'objectif manquaient en revanche totalement - objets, créatures et 26 coordonnées de lieu y ont été ajoutés, une partie provenant de sources web : vos retours sur leur exactitude sont les bienvenus. Menu de quête : environ 300 quêtes dont le donneur, la cible ou la remise se trouve dans un donjon affichaient des entrées vides - elles mènent désormais à l'entrée du donjon. Treize quêtes de dialogue (dont les anciennes quêtes de sorts de prêtre) n'avaient aucun point de remise dans les données - l'entrée Livraison mène désormais au PNJ que nomme le texte de la quête. Base de données des quêtes : juste après un /reload, des quêtes d'événements mondiaux inactifs (par exemple les quêtes du Solstice d'été) pouvaient apparaître dans « Commence dans la zone ». Base de données des quêtes : la liste « Commence dans la zone, Par distance » s'interrompait après la première quête. Sacs : le filtre de saisie restait bloqué après l'utilisation d'un objet et ne pouvait plus être levé qu'en fermant les sacs. Après une action avec temps d'incantation, un mauvais objet était annoncé juste après le bon. La liste des sacs tenue pour le combat pouvait se périmer lorsqu'on fermait une fenêtre de quête alors que les sacs étaient ouverts. Menu des sacs : avec un addon de remplacement des sacs comme Bagnon, qui masque la fenêtre de banque de Blizzard, tous les compartiments de banque disparaissaient du menu. Auras : le buff Rassasié figurait deux fois dans le sélecteur de sorts - les deux entrées sonnaient pareil et une seule pouvait se déclencher. Auras : les entrées qui n'existent que comme enchantement d'arme (par exemple « Compétence Pêche +75 ») sont désormais signalées comme telles dans les sélecteurs. Auras : « enchantement d'arme main droite contient X » avec « Enchantement d'arme expiré » ne pouvait jamais se déclencher - /skucheck auras signale désormais cette combinaison. Auras : la fin d'un enchantement d'arme est détectée à la frame près au lieu d'un quart de seconde plus tard. Barres d'action : Maj+Bas ne lisait plus l'infobulle du sort dans le menu d'affectation. Navigateur de donjons : accepter une invitation de groupe ne vous dépose plus tout seul dans le navigateur de donjons. Français : les commentaires de points de passage étaient totalement muets depuis la v42.11 - y compris les avertissements comme « attention, ravin » ou « attendre le zeppelin ici ». Français : 22 des 94 noms de ressources du scanner de minicarte étaient erronés, ces gisements n'étaient donc jamais annoncés. Français : dans la Caverne des Brumes, la liste des points de passage restait vide. La coloration des barres de nom était morte après chaque connexion et chaque rechargement : l'interrupteur indiquait « activé », mais plus rien n'était coloré tant qu'on ne le basculait pas deux fois à la main. La barre de lecture restait invisible en combat - précisément quand elle est la plus utile. La barre de lecture n'affichait que le nom d'une entrée de menu, pas la valeur lue ensuite - les états d'interrupteur et les quantités étaient audibles mais pas lisibles. Une fenêtre fermée pouvait être annoncée de nouveau plus tard - typiquement le maître, bien après l'ouverture de la fenêtre de métier. Le menu s'ouvrait parfois tout seul à son niveau le plus haut, au lieu de là où l'on voulait aller. Avec deux fenêtres ouvertes en même temps, la fermeture de la seconde passait inaperçue. Après la fermeture d'une fenêtre, ses entrées pouvaient subsister sous « Local ». Maître : apprendre plusieurs sorts coup sur coup puis fermer la fenêtre juste après la laissait continuer à parler. Hôtel des ventes : « utilisables uniquement » cachait précisément les objets de haut niveau recherchés dans la liste du scan complet. Hôtel des ventes : la liste du scan complet filtrait par niveau et par qualité alors qu'aucun filtre n'était réglé - les marchandises, les composants et tout le gris manquaient. Hôtel des ventes : une catégorie sans sous-catégories n'affichait rien d'autre que « Tout ». Hôtel des ventes : un achat annulé pouvait détourner la recherche suivante et armer un achat que personne n'avait demandé. Hôtel des ventes : une recherche interrompue par le chien de garde le dit désormais - une liste à moitié remplie ressemblait à un résultat terminé. Nage et vol : le verrouillage de l'inclinaison met fin à la plongée involontaire Simple : En nageant ou en volant, chaque virage automatique vers la balise tirait un peu vers le bas - sur une longue distance, cela s'additionnait jusqu'à se retrouver sous l'eau ou au sol sans s'en apercevoir. Désormais Sku verrouille l'inclinaison dès que vous nagez ou volez : vous vous déplacez parfaitement à l'horizontale, et Espace et X continuent de vous porter vers le haut et vers le bas. Et si quelque chose vous incline malgré tout - par exemple l'approche automatique en interagissant avec un PNJ - Sku vous redresse activement : à l'ouverture de la fenêtre du PNJ, peu après chaque virage, et à chaque appui sur monter ou descendre. Activé par défaut ; qui veut plonger à la souris en inclinant la vue (vision résiduelle) le désactive sous Paramètres -> Général. S'y ajoute le nouveau raccourci « verrouillage de l'inclinaison » (Ctrl+Maj+N) pour verrouiller et libérer manuellement. Technique : Trois éléments. Premièrement, la variable console pitchlimit 0 plafonne l'inclinaison de déplacement du personnage (la technique des addons LevelFlight et FlightHUD) - posée durablement plutôt qu'autour de chaque impulsion de virage, car c'est exactement cette course au basculement qui a fait échouer les tentatives précédentes. pitchlimit s'écrit mais ne se lit pas ; chaque connexion rétablit donc aveuglément la valeur par défaut 88, pour qu'un verrouillage ne reste jamais coincé. Deuxièmement, le verrouillage ne fait que conserver ce qui est : un déplacement piloté par le moteur, comme l'approche d'interaction, incline quand même le personnage, et ensuite aucune touche du clavier ne permet de relever l'inclinaison. Le redressement actif utilise précisément le transfert qui causait le bug à l'origine : ResetView/SetView cale la caméra sur le préréglage par défaut connu, presque horizontal, une impulsion mouselook le transfère au personnage, et le verrouillage plafonne le petit reste à zéro. Les déclencheurs sont la fenêtre du PNJ, chaque virage (différé de 0,5 s, car le moteur n'applique le transfert de cap qu'une frame plus tard - une seconde impulsion immédiate annulerait le virage) et, via hooksecurefunc, les fonctions monter/descendre JumpOrAscendStart, AscendStop, SitStandOrDescendStart et DescendStop - quelles que soient les touches où elles sont assignées. Troisièmement, un inclinomètre pour le journal : la vitesse horizontale mesurée divisée par GetUnitSpeed donne le cosinus de l'angle de trajectoire - la seule mesure que ce client offre (GetUnitPitch a été retiré au patch 7.1 et manque ici aussi). Il mesure la trajectoire, pas l'inclinaison du corps, et ne sert qu'au diagnostic. Le curseur reste sur l'objet dans le sac Simple : Faire un clic droit sur un objet de la liste des sacs - l'utiliser, l'équiper, le vendre à un marchand, appliquer un enchantement - vous déposait sur la première entrée de toute la liste. Pour les objets qui disparaissent aussitôt, cela se voyait à peine, puisque la liste se réordonne de toute façon ; pour tout le reste, c'était simplement une position perdue. Désormais le curseur reste sur l'objet sur lequel vous avez travaillé, et quand l'objet quitte le sac, vous atterrissez sur son emplacement, c'est-à-dire sur ce qui a pris sa place. Technique : La cause n'a jamais été un décalage d'indices, mais la troisième ligne de la macro de clic droit, « /script SkuCore:CheckFrames() ». Elle datait d'avant la confirmation pilotée par les événements et s'exécutait AU MOMENT DE LA TOUCHE - donc avant que l'action ait changé quoi que ce soit, et pour une action avec temps d'incantation, plusieurs secondes avant. Elle reconstruisait donc des données périmées et coûtait la position pour rien. Supprimée ; le rafraîchissement appartient à la seule confirmation pilotée par BAG_UPDATE, qui ne reconstruit qu'une fois les sacs réellement stabilisés et réaccroche alors le curseur par emplacement de sac et identifiant d'objet. De plus, CheckFrames ne retient plus seulement le numéro de ligne de l'entrée à travers une reconstruction, mais aussi son nom, qu'il recherche dans la liste reconstruite - le numéro de ligne seul cesse d'être juste dès que la liste a eu une autre forme entre-temps. Enchantements, appâts et huiles : la liste se rafraîchit après l'incantation Simple : Un enchantement, un appât de pêche, une huile d'arme, un kit d'armure ou un désenchantement ne modifient les sacs qu'une fois l'incantation terminée, souvent cinq secondes après le clic. Sku avait depuis longtemps renoncé : la liste restait périmée et le curseur se trouvait quelque part. Désormais Sku attend la fin de l'incantation, annonce ensuite l'objet une fois et y laisse le curseur. Si vous naviguez vous-même entre-temps, vous gardez votre propre position - Sku ne s'en mêle plus. Technique : La fenêtre de confirmation ouverte par le clic droit expirait au bout de 2,5 secondes. UNIT_SPELLCAST_START/-CHANNEL_START repoussent maintenant son échéance jusqu'à la fin de l'incantation tout en conservant l'ancre saisie au clic, de sorte que le réaccrochage retombe exactement sur l'objet traité. Volontairement sans liste de sorts : une fenêtre ouverte est en elle-même la preuve que cette incantation appartient à l'action. Deux garde-fous stricts, tous deux appris d'un cas réel : l'incantation doit suivre le clic dans la seconde (« /use » la déclenche dans la même image), et le report n'a lieu qu'une seule fois. Sans eux, une pêche de 22 secondes repoussait l'échéance devant elle sans fin et la fenêtre ne mourait jamais. La boîte de dialogue de confirmation ne coûte plus la position Simple : Appliquer un enchantement sur un objet déjà enchanté déclenche la question de Blizzard « Remplacer l'enchantement existant ? ». Après « Oui » ou « Non », on se retrouvait tout en haut de la hiérarchie des sacs, sur « Sac 1 » au lieu de l'objet. Désormais, les deux réponses ramènent à l'objet, et il est annoncé une fois. Technique : La boîte de dialogue est une fenêtre modale entre le clic et son effet, et elle cassait la position doublement : l'échéance de 2,5 secondes expirait pendant qu'elle était affichée, et Sku ancre le menu DANS la boîte, si bien qu'à sa fermeture il ne restait rien où revenir - l'entrée générique prenait alors la seule fenêtre ouverte et atterrissait en haut de la liste des sacs. SkuBagPostActionPopupGate maintient la fenêtre ouverte tant que la boîte est affichée (jusqu'à 30 secondes) et déclenche la confirmation à sa fermeture. C'est le bon outil précisément parce qu'il couvre les deux réponses : « Non » ne produit ni incantation ni changement de sac, rien d'autre ne pourrait donc jamais replacer le curseur. Tant que la boîte est affichée, la confirmation s'abstient entièrement - sinon un changement de sac sans rapport (du butin qui arrive, un échange qui se conclut) arracherait le curseur hors de la boîte. Le filtre de saisie de la liste des sacs se lève à nouveau Simple : Taper un nom dans la liste des sacs la filtre sur les correspondances. Ce filtre ne se levait plus dès que vous aviez utilisé l'objet filtré : même flèche gauche puis droite ne ramenait pas la liste complète, seule la fermeture des sacs y parvenait. Désormais, chacune de ces touches lève à nouveau le filtre, comme vous en avez l'habitude. Quand rien ne correspond au filtre, la liste reste vide comme avant et les flèches ne font que répéter le filtre saisi - c'est précisément ainsi que l'on entend qu'il n'y a aucun résultat. Technique : SkuOptions:ClearFilter n'oubliait que le texte saisi ; la liste d'enfants filtrée installée par ApplyFilter restait dans l'arbre jusqu'à ce que quelque chose la remplace intégralement - et un niveau construit à partir des fenêtres du jeu, comme les sacs, ne peut pas se rafraîchir sur place, il lui faut une reconstruction complète. Masqué pendant des années par le CheckFrames forcé de la macro (voir ci-dessus), cela est apparu dès qu'une action ne modifie pas du tout les sacs. ClearFilter restaure maintenant réellement la liste, chaînages avant et arrière compris, et ce sur le nœud qu'il a filtré et non sur celui qui a le focus. Échap ne fermait pas le menu lorsqu'on se tenait juste sur un objet de quête Simple : Se tenir à environ zéro mètre devant un objet de quête et fermer le menu avec Échap faisait aussitôt remonter la fenêtre de quête - sans fin. À cet endroit, le menu ne pouvait pratiquement plus être fermé. Désormais les fenêtres de jeu ouvertes sont fermées d'abord, et le menu lui-même ensuite. Technique : Le menu se cachait d'abord et traitait ensuite les fenêtres d'interaction ouvertes. Le cadre de quête était donc fermé alors que le jeu signalait toujours l'objet à portée, et il se rouvrait immédiatement. L'ordre est maintenant inversé et protégé par un compteur que « /skucheck menu » relit : une violation de cet ordre se remarque sans que quiconque ait à se tenir par hasard exactement à cet endroit. Moins de reconstructions en double à la fermeture d'une fenêtre Simple : Un seul Échap déclenchait jusqu'à six reconstructions complètes des données du menu, parce que plusieurs fenêtres se ferment en même temps et que la fenêtre de quête de Blizzard à elle seule masque quatre sous-panneaux. Cela coûtait un temps perceptible. Désormais, exactement une reconstruction s'exécute par image. Par ailleurs, la liste des sacs tenue pour le combat pouvait se périmer lorsqu'une fenêtre de quête était fermée alors que les sacs étaient ouverts - un défaut qui ne serait apparu que plus tard, en combat. Technique : Les chemins de masquage passent par SkuCore:CheckFramesCoalesced, dont le verrou s'ouvre une fois par image ; le passage effectif est de toute façon différé de 0,01 s et lit l'état vivant, il voit donc exactement ce qu'aurait vu le dernier appel de la rafale. Le côté OnShow est volontairement laissé de côté. La comptabilité de GENERIC_OnClose a reçu son propre verrou : elle dépendait auparavant de la valeur de retour de ce même verrou partagé, et comme les sous-panneaux de quête se déclenchent en premier, ils le consommaient et la comptabilité - dont le rafraîchissement de la liste des sacs de combat - était sautée. Synthèse vocale : les annonces ne restent plus muettes Simple : Pendant la navigation, la voix se taisait régulièrement pendant un moment - une touche fléchée ne lisait tout simplement pas l'entrée, dans le calme et sans que rien d'autre ne tourne. C'était le plus reproductible quand on filtrait une liste jusqu'à un seul résultat puis qu'on allait vite et venait entre le filtre et ce résultat : après quelques passages, le résultat restait définitivement muet, même en attendant une seconde. Les deux sont corrigés. Technique : Deux causes indépendantes, toutes deux sous la file d'attente - Sku avait transmis proprement chaque annonce ; sur une capture de 95 minutes, chaque appui produisait exactement un SpeakText avec un STARTED. D'abord, la même ligne était réémise jusqu'à quatre fois en une seconde, parce qu'une vérification de fenêtre superflue réannonce l'entrée de menu ; 95 des 1512 énoncés étaient de telles répétitions. Chacune apporte son propre « queuereset », donc un StopSpeakingText, et celui-ci atteint bel et bien le lecteur d'écran - la ligne était donc coupée en plein mot puis reprise, et quand le renvoi se perdait en chemin il ne restait rien. Le garde-fou anti-doublon existant ne pouvait rien y faire : il dépend de VOICE_CHAT_TTS_PLAYBACK_FINISHED, et via le pont vers le lecteur d'écran STARTED et FINISHED reviennent dans la même image - il y est donc toujours vide et ne peut jamais se déclencher. Un garde-fou temporel a été ajouté : il écarte une ligne identique immédiatement suivante dans la seconde, avec son queuereset ; n'écarter que le texte annulerait quand même la ligne en cours sans rien mettre à sa place. Il ne se compare qu'à la ligne transmise juste avant, il ne peut donc pas avaler une vraie relecture, et il ne s'applique que si la première copie a réellement commencé à être jouée. Ensuite, le casseur de cache ne fonctionnait plus. Le client range la parole synthétisée par texte et la rejoue lors d'une répétition sans rappeler la voix - via un pont, cette relecture est silencieuse. La parade était jusqu'ici un tournant, mais le client retire le balisage et n'indexe que sur le texte parlé : dans la capture, chaque énoncé portait sa propre marque et le résultat se taisait quand même. Le casseur d'origine faisait varier de vrais caractères et fonctionnait ; le passage à une marque devait ménager la prosodie et a discrètement ramené le défaut même contre lequel il avait été écrit. On rajoute donc du vrai texte : une suite tournante de 1 à 64 espaces insécables après le dernier mot, où elle n'est ni audible ni capable de colorer la prosodie. Rassasié : une seule entrée dans le sélecteur de sorts Simple : Qui voulait bâtir une aura sur le buff Rassasié trouvait dans le sélecteur de sorts deux entrées prononcées toutes deux « Rassasié ». Une seule pouvait se déclencher, l'autre était une fiche morte - laquelle on attrapait relevait du pur hasard et ne s'entendait pas. Il n'y a plus qu'une entrée, et c'est la bonne. Une aura déjà enregistrée pointant sur la mauvaise est basculée une fois à la prochaine connexion et fonctionne ensuite. Technique : Depuis la v43.0, les auras sont tenues par l'identité anglaise d'un sort, afin que la même aura convienne dans toutes les langues. Or, dans la base de sorts livrée, dix lignes portent le nom anglais ENTRE GUILLEMETS - 44097 à 44106 ainsi que 69559 et 65365 portent le nom entouré de deux guillemets, là où les 65 autres le portent sans. Cela créait une seconde identité derrière un seul et même nom localisé, et la désambiguïsation du menu ajoute dans ce cas l'identité anglaise : une entrée se lisait « Rassasié (Well Fed) », l'autre pareil mais avec deux guillemets supplémentaires autour de Well Fed. La synthèse vocale ne lit pas les guillemets : les deux entrées sonnaient donc à l'identique. Rassasié est le seul des 26 650 noms localisés du jeu de données dont les deux identités ne diffèrent que par ces caractères. Le nom est désormais déguillemeté lors de la formation de l'identité, et des DEUX côtés - dans la liste de valeurs et à chaque résolution d'un événement en cours - car ne changer qu'un côté produirait une valeur enregistrée qu'aucun événement ne peut plus atteindre. Seuls les noms encadrés de guillemets aux DEUX extrémités sont traités ; les 17 lignes restantes avec des guillemets à l'intérieur du nom (Goblin "Boom" Box, parfum « TRIUMPH ») les conservent, car ils y font partie du nom. Une migration unique bascule les auras enregistrées. Les enchantements d'arme sont reconnaissables dans le sélecteur de sorts Simple : Un appât de pêche, une pierre à aiguiser ou une huile d'arme n'est pas un buff mais un enchantement posé sur l'arme. Il n'apparaît dans aucune liste de buffs du jeu et ne déclenche aucun événement d'aura - une aura sur « événement = aura perdue et nom du sort = Compétence Pêche +75 » ne pouvait donc jamais se déclencher, alors même que l'entrée figure dans le sélecteur et sonne tout à fait normalement. De telles entrées portent désormais la mention « (enchantement d'arme) » dans tous les sélecteurs, afin que l'on entende de quoi il s'agit. Pour y réagir, on prend soit l'événement « Enchantement d'arme expiré », soit la condition « Ton enchantement d'arme main droite ». La mention n'est pas lue dans l'annonce d'une aura déclenchée. Technique : La liste de valeurs de l'attribut « nom du sort » est la base de sorts complète, y compris les lignes qui n'atteignent le joueur que sous forme d'enchantement d'arme temporaire - l'appât est le sort 8084 derrière l'identifiant d'enchantement 265. Les identités concernées sont maintenant déterminées lors de la construction des listes (chaque identifiant de sort de l'identité est référencé par la base des enchantements) et reçoivent la mention. Volontairement SIGNALÉES plutôt que retirées : elles sont 395 et ne sont pas toutes inertes - Arme enflammée, Bourreau et Force impie sont des procédures d'enchantement qui apparaissent bel et bien sous ce nom exact dans le journal de combat. Les retirer aurait privé des auras fonctionnelles de leur seul moyen de les nommer. La mention emprunte le même mécanisme que la désambiguïsation anglaise et disparaît donc de nouveau dans l'énoncé d'une aura déclenchée. Les enchantements d'arme s'adressent comme des sorts Simple : Pour un sort de dégâts sur la durée, on écrit « événement égale aura perdue » et « nom du sort égale » - l'événement dit lui-même de quoi il s'agit. Pour un enchantement d'arme, c'était impossible : l'appât ne pouvait être nommé que par « ton enchantement d'arme main droite », qui est une question d'état et non d'événement. Sous la forme « contient », une telle aura ne pouvait même jamais se déclencher - quand l'événement arrive, l'enchantement a déjà disparu, la liste ne le contient donc plus. Seule la forme inversée « ne contient pas » fonctionnait. L'événement porte maintenant lui-même le nom de l'enchantement, et l'aura s'écrit exactement comme pour un sort : « événement égale Enchantement d'arme expiré » et « nom du sort égale Compétence Pêche +75 ». « ne contient pas » continue de marcher. Technique : Les deux événements générés WEAPON_ENCHANT_REMOVED et WEAPON_ENCHANT_UPDATE plaçaient le nom de la MAIN dans le champ 13 - et le champ 13 est celui du nom du sort. Une condition sur « nom du sort » se comparait donc à « Main droite », et une sortie « nom du sort » annonçait la main au lieu de l'enchantement. C'est désormais l'enchantement qui s'y trouve : le champ 12 reçoit son identifiant de sort, le champ 13 son nom, l'un et l'autre par les mêmes résolveurs qui construisent la liste de valeurs - la valeur enregistrée est la même que pour la condition d'enchantement. Une expiration signale l'enchantement qui ÉTAIT là, c'est l'identité visée. Les auras existantes ne peuvent pas devenir muettes : « Main droite », « Main gauche », « Haupthand » et « Nebenhand » n'apparaissent pas une seule fois comme nom de sort dans les 49 117 lignes de la base de sorts ; une condition sur « nom du sort » ne pouvait donc de toute façon jamais correspondre sur cet événement. La main n'est pas perdue, elle passe dans son propre champ avec sa propre condition et sa propre sortie (« main de l'enchantement d'arme »), enregistrée sous forme de jeton neutre afin qu'une aura partagée désigne la même main dans toutes les langues. Ce qui n'a volontairement PAS été fait, c'est l'autre voie évidente - remplir la liste d'état lors d'une expiration avec l'enchantement sortant : la liste est partagée, elle mentirait à toutes les autres auras durant ce passage, réarmerait une barrière « ne contient pas » et produirait ainsi des annonces en double, et elle contredirait la durée restante, délibérément forcée à 0 lors d'une expiration. De plus, /skucheck auras signale désormais la combinaison « événement d'expiration plus contient » comme une aura qui ne peut jamais se déclencher. Corrigé au passage : lorsque seule la main gauche changeait, l'événement était tout de même signalé comme la main droite. La fin d'un enchantement d'arme est détectée immédiatement Simple : Le jeu n'annonce pas qu'un appât de pêche ou un poison a expiré - Sku regarde toutes les 0,25 seconde. L'annonce arrivait donc jusqu'à un quart de seconde trop tard. Elle arrive maintenant au moment de l'expiration. Technique : La durée restante d'un enchantement est connue dès l'instant où on le voit - GetWeaponEnchantInfo renvoie le temps restant en millisecondes. Le ticker en retient la prochaine expiration, et le pilote de frame compare un nombre par frame puis appelle exactement le ticker joueur habituel. C'est la même construction que le planificateur d'échéances de durée introduit en v43.0 et cela ne coûte aucune interrogation supplémentaire. Les annonces en double sont exclues, car le ticker n'émet que si son état mémorisé a réellement changé. Volontairement NON fait : passer le ticker à chaque frame - ce serait quinze fois plus d'interrogations pour le même gain. Basculer un réglage dans une liste filtrée annonce de nouveau le nouvel état Simple : Restreindre une liste avec le filtre de saisie puis appuyer sur Entrée sur une entrée activé/désactivé retire le filtre - c'est voulu. L'entrée était bien basculée, mais on ne l'entendait pas : le curseur sautait en tête de liste et c'est la première entrée qui était annoncée, pas le nouvel état. Dans les sorties d'auras, l'aperçu sonore de la première entrée se déclenchait en plus. On reste désormais sur l'entrée que l'on vient de basculer et on entend son nouvel état. Technique : Entrée retire le filtre AVANT d'exécuter l'action - le nœud de menu appelle ApplyFilter("") pour cela. Son nettoyage se terminait par un OnFirst() systématique sur le niveau, le curseur partait donc en tête de liste dans TOUS les cas. La bascule elle-même n'a jamais été en cause : elle agit sur le nœud lui-même (actionInPlace), pas sur le curseur. Seul ce qui était lu ensuite était faux - et OnFirst déclenchait au passage le OnEnter de la première entrée, c'est-à-dire l'aperçu sonore pour les sons d'auras. ApplyFilter("") suit maintenant la même règle que ClearFilter : la liste restaurée contient les MÊMES objets de nœud, un curseur posé sur une vraie entrée reste donc valide ; seule la ligne artificielle « Filter; » n'y a plus sa place, et c'est pour elle seule que le OnFirst() complet, OnEnter compris, continue de s'exécuter, afin de ne pas perdre l'aperçu sonore à l'arrivée. Sans filtre actif, ApplyFilter("") n'a toujours rien fait - d'où le fait que basculer dans une liste non filtrée n'a jamais posé problème ; les deux cas se comportent désormais de la même façon. Effet de bord de la même règle : Retour arrière laisse maintenant le curseur sur l'entrée où il se trouvait au lieu de sauter en tête de liste. Navigateur de donjons : accepter une invitation ne mène plus au navigateur Simple : Si vous étiez inscrit dans le navigateur de donjons et acceptiez ensuite une invitation de groupe, vous vous retrouviez dans le navigateur sans avoir appuyé sur la moindre touche : le menu se rouvrait tout seul et le curseur se posait sur « Non inscrit ». Pour les joueurs voyants, rien ne se passe à ce moment-là - la fenêtre de recherche de groupe de Blizzard ne s'ouvre jamais lorsqu'on accepte une invitation. Sku reste désormais silencieux lui aussi : vous restez là où vous étiez. Technique : Deux chemins y menaient, tous deux sont fermés. (1) Dès que le serveur supprime votre inscription - c'est exactement ce qu'il fait lorsque vous rejoignez un groupe -, LFG_LIST_ACTIVE_ENTRY_UPDATE arrive, et le navigateur reconstruisait alors son entrée de menu. Cette reconstruction se terminait par un SlashFunc vers son propre chemin de menu, et celui-ci rouvre un menu FERMÉ et y descend. Elle ne s'exécute plus que si le menu est ouvert ET que le curseur se trouve réellement dans le navigateur de donjons - ce n'est qu'alors qu'il faut replacer les entrées fraîchement construites sous le curseur. L'omettre ne coûte rien : l'entrée de menu est « dynamic » et se reconstruit de toute façon à la prochaine descente. Ainsi, aucun changement d'inscription que vous n'avez pas provoqué (expiration, modification par le chef de groupe) ne peut plus vous arracher à vos sacs pour vous jeter dans le navigateur. (2) Un tiers de seconde après la disparition de la fenêtre d'invitation, le navigateur vérifie s'il doit se rouvrir. Il demandait seulement « une fenêtre de recherche de groupe est-elle ouverte en ce moment ? » et en faisait une ouverture. Désormais, l'état présent à l'ARRIVÉE de l'invitation est mémorisé, et c'est exactement lui - et rien d'autre - qui est restauré. Si l'invitation vous a fait rejoindre un groupe, il ne se passe plus rien du tout : ni ouverture, ni fermeture. Nouveau raccourci : lire l'infobulle complète de la cible Simple : lors d'un changement de cible, Sku annonce un ensemble fixe et court - nom, niveau, élite ou rare, plus la deuxième ligne de l'infobulle avec le type de créature. Tout ce qui se trouve en dessous dans l'infobulle était jusqu'ici inaccessible : la ligne de faction, le marqueur JcJ et surtout ce que la compétence de chasseur Connaissance des bêtes révèle d'une bête - sa famille, son régime et si elle est apprivoisable. Le nouveau raccourci (par défaut : Ctrl+Maj+V) lit l'infobulle complète à la demande. Il n'agit que lorsqu'on appuie dessus : l'annonce de cible habituelle reste aussi courte qu'avant, et qui n'a pas besoin de ce détail ne l'entend jamais. C'est aussi pourquoi il n'y a pas d'option pour cela - appuyer sur la touche EST la demande. Sans cible fixe, la touche décrit la cible souple actuelle, ce qui permet aussi de décrire quelque chose sur quoi on ne s'est pas encore engagé. Réassignable dans Raccourcis clavier Sku, cible et ciblage souple. Technique : l'idée, la démonstration et l'implémentation de référence viennent de ZenqFR, dans l'addon compagnon SkuBeastLore (github.com/ZenqFR/Sku-BeastLore) ; la sortie Connaissance des bêtes y a été testée sur un vrai chasseur. Ici la fonction est reconstruite nativement sur les interfaces de Sku, pour qu'aucun addon supplémentaire ne soit nécessaire. Les lignes de Connaissance des bêtes sont produites par le client lui-même dans l'infobulle d'unité ; aucun Lua de l'interface Blizzard ne les construit et aucune API ne les renvoie séparément - lire l'infobulle est le seul moyen d'y accéder. C'est aussi pourquoi une option distincte pour la seule information de chasseur n'est pas possible : ces lignes ne pourraient être isolées que par une comparaison de texte dépendante de la langue. SkuMob:OutputTargetTooltip résout target, softenemy, softfriend puis softinteract dans cet ordre et lit via le SkuScanningTooltip partagé de Sku. Comme ce frame est partagé - sacs, hôtel des ventes, liens de discussion et textes de quête lisent le même -, il est vidé avant ET après, et son propriétaire restauré exactement comme SkuCore le règle à la connexion. Suivre une quête et retirer le suivi Simple : le menu de chaque quête se termine désormais par une entrée « Suivre la quête ». Entrée la bascule, le curseur reste en place et le nouvel état est annoncé aussitôt - « Suivre la quête;Activé » ou « Suivre la quête;Désactivé ». Suivie signifie : le marqueur propre à WoW sur la minicarte et la carte du monde. Qui garde un reste de vue trouve ainsi ses objectifs plus vite ; qui ne voit rien n'en tire rien. L'entrée peut donc être désactivée entièrement - sous Réglages Sku, SkuQuest, « afficher l'entrée Suivre la quête » ; elle est active par défaut. Elle n'apparaît que pour les quêtes de votre propre journal, car une quête de la base de données de quêtes n'est pas encore la vôtre. Deux limites viennent du jeu lui-même : The Burning Crusade n'autorise que cinq quêtes suivies à la fois, et une quête sans aucun objectif ne peut pas être suivie. Dans les deux cas, Sku annonce le message du jeu et l'état reste inchangé. Technique : l'idée et l'implémentation de référence viennent de ZenqFR, dans l'addon compagnon SkuQuestNearby (github.com/ZenqFR/Sku-QuestNearby) ; avec l'accord de ZenqFR, la fonction est ici reconstruite nativement sur les interfaces propres à Sku, afin qu'aucun addon supplémentaire ne soit nécessaire. L'entrée est placée dans le CreateQuestSubmenu local au fichier plutôt que sur un hook du wrapper public - c'est pourquoi toutes les vues de quête en bénéficient : journal de quêtes, base de données de quêtes et menus de quête de SkuChat. Il n'y a pas de C_QuestLog sur 2.5.6 : AddQuestWatch, RemoveQuestWatch et IsQuestWatched prennent un INDEX du journal de quêtes, pas un identifiant de quête, et cet index se décale à chaque quête acceptée ou rendue. Sku tient donc une correspondance identifiant vers index, revérifie l'index mémorisé à chaque lecture et abandonne la correspondance lors des événements de quête ; sans ce cache, la liste « base de données de quêtes, toutes » ferait un parcours complet du journal pour chaque quête. L'activation passe par AutoQuestWatch_Insert avec QUEST_WATCH_NO_EXPIRE, pour que la quête entre dans QUEST_WATCH_LIST et qu'aucun minuteur d'expiration ne l'en retire ; la désactivation supprime d'abord la ligne de QUEST_WATCH_LIST puis seulement appelle RemoveQuestWatch - exactement comme le chemin de clic de Blizzard, sinon une quête depuis longtemps non suivie continue de compter dans la limite de cinq. QuestWatch_Update ne s'exécute qu'en dehors du combat, car il finit par déplacer des cadres protégés ; le prochain QUEST_LOG_UPDATE s'en charge de toute façon. Nouveau raccourci « Cibler la créature de quête la plus proche » (Alt+H) Simple : Une pression cible une créature liée à un objectif de quête encore ouvert - y compris une créature qu'on n'a pas à tuer, mais qui se contente de faire tomber un objet requis. Exemple : pour « Essences pour les moteurs », le journal ne nomme que l'objet ; la touche cible malgré tout le Spectre de mana qui le fait tomber. Si rien ne correspond à portée, Sku annonce « aucune cible de quête à portée » ; si le journal ne contient rien de pertinent, « aucune cible de quête dans le journal ». La touche est sur Alt+H par défaut et se réassigne dans les raccourcis Sku, « Ciblage et ciblage souple ». Technique : L'idée et l'implémentation de référence viennent de ZenqFR, dans l'addon compagnon SkuQuestTarget (github.com/ZenqFR/Sku-QuestTarget) ; la fonction a été reconstruite nativement avec l'accord de ZenqFR, pour qu'aucun addon supplémentaire ne soit nécessaire. Les candidats proviennent du journal de quêtes via SkuQuest:GetQuestTargetIds ; les objectifs de type objet sont remontés jusqu'aux créatures qui les font tomber (npcDrops, et un niveau plus loin itemDrops). La recherche commence par la zone où l'on se trouve - une créature sans point d'apparition sur place ne peut pas être à portée de ciblage et ne doit pas occuper une place dans la macro ; ce n'est que si rien n'est enregistré pour la zone que le tri se fait sur tout le continent. La distance retient le point d'apparition enregistré le plus proche, et non le premier de la liste. La macro est multiligne, et une particularité du jeu décide du résultat : chaque ligne qui correspond s'exécute et chaque correspondance écrase la précédente - c'est donc la DERNIÈRE qui gagne. Les candidats sont donc retenus du plus proche au plus lointain, afin que le budget de longueur ne coupe que le lointain, mais écrits à l'envers, pour que le plus proche soit la dernière ligne. La macro est reconstruite à chaque pression hors combat ; en combat, c'est le dernier texte construit qui part. « Ennemi suivant en combat » trouve aussi ce qu'on ne regarde pas (Ctrl+H) Simple : La touche ne trouvait que ce que l'on regardait déjà - le même ennemi à cinq mètres derrière soi ne donnait rien. Ce n'est pas un réglage, c'est le fonctionnement du ciblage par tabulation du jeu. Sku retient donc désormais les noms des créatures hostiles croisées et les essaie en plus par leur nom - et un nom fonctionne dans toutes les directions. En pratique : ce que l'on a vu, combattu ou ciblé une fois reste accessible de partout pendant deux minutes, même dos tourné. La recherche repart par ailleurs de l'ennemi le plus proche à chaque pression au lieu de continuer le cycle. La touche est sur Ctrl+H par défaut. Technique : L'approche est reprise du SkuQuestTarget de ZenqFR, qui emprunte la même voie pour sa fonction secondaire. /targetenemy est la tabulation et cherche dans le cône de vue - rien ne peut changer cela. « /tar nom » interroge en revanche la liste de noms du client et ne dépend pas de l'orientation, mais il lui faut un nom, et aucune interface ne permet d'énumérer les unités situées derrière soi. Sku collecte donc les noms en continu depuis quatre sources : les plaques hostiles, sa propre cible, l'unité sous le curseur et la liste d'ennemis du moniteur de combat. La collecte tourne à la seconde tant que la touche est assignée, car une plaque n'existe que tant que la créature est en vue - c'est-à-dire précisément quand on n'a pas besoin de la touche. Seuls les noms y sont collectés ; la mesure de distance, coûteuse, ne tourne qu'une fois par pression. Les noms retenus durent deux minutes. La macro est « /cleartarget », puis « /targetenemy », puis les noms retenus du plus lointain au plus proche - la dernière ligne qui correspond l'emporte, donc la plus proche. Nouvelle liste « Quêtes à proximité » Simple : Le menu de quête possède désormais une troisième entrée, après « Quêtes actuelles » et « Base de données des quêtes » : « Quêtes à proximité ». Elle contient tout le journal de quêtes en UNE seule liste - quêtes en cours et quêtes terminées mélangées - triée selon la distance de ce qui compte actuellement pour chaque quête. Chaque entrée annonce d'abord la distance, puis le point cardinal, puis le type, puis le nom de la quête : « 180 Mètre ; nordest ; objectif de quête ; Kobolds voleurs » ou « 340 Mètre ; sud ; Rendre la quête ; Le chef de la meute ». Pour une quête aux objectifs encore ouverts, c'est la distance jusqu'à l'objectif le plus proche ; pour une quête signalée terminée, la distance jusqu'au lieu de remise. « Quêtes actuelles » regroupe selon les en-têtes du journal et trie donc par zone - la nouvelle liste répond à l'autre question : qu'est-ce qui est le plus proche d'ici. Entrée ouvre le même menu de quête que partout ailleurs, points de passage et entrée « Suivre la quête » compris. La liste est DÉSACTIVÉE par défaut et s'active sous Paramètres Sku, SkuQuest, « afficher l'entrée Quêtes à proximité » : elle recalcule tout le journal de quêtes contre la base de données de spawns de Sku à chaque ouverture, et seul celui qui s'en sert devrait le payer. Là où les données de Sku ne donnent aucun lieu, l'entrée indique « Lieu inconnu » au lieu d'une distance et passe en fin de liste - elle ne disparaît pas. Technique : Idée et implémentation de référence de ZenqFR, tirées de l'addon compagnon SkuQuestNearby (github.com/ZenqFR/Sku-QuestNearby) ; reconstruite ici nativement avec l'accord de ZenqFR afin qu'aucun addon supplémentaire ne soit nécessaire - en particulier sans Questie et sans GatherMate2. Le calcul vit dans le nouveau fichier sans état SkuQuest/Proximity.lua. Deux étages, et l'ordre est l'essentiel : d'abord le point de spawn enregistré le plus proche DANS la zone où l'on se trouve - la sélection tourne à bas coût en coordonnées de carte, seul le gagnant est converti une fois en coordonnées du monde ; ensuite seulement, si cela ne donne rien, le minimum sur toutes les zones du même continent. Un autre continent n'est pas une distance imprécise, c'est une absence de distance, et il reste dehors. Les objectifs d'objet sont ramenés aux créatures et objets du monde qui les font tomber, les objectifs d'exploration à leurs coordonnées enregistrées. Contrairement au chemin des points de passage, TOUTES les listes d'objectifs d'une quête sont lues, pas seulement la première non vide - une quête « tuez huit loups ET récoltez cinq peaux » n'afficherait sinon que les loups. Le filtrage porte uniquement sur le TYPE d'objectif : quels types sont encore ouverts vient de l'affichage de progression de Blizzard lui-même (« monster », « item », « object »), et il correspond exactement aux trois listes ; comparer une ligne de progression à une entrée de base de données précise relèverait de la devinette, car aucun des deux ordres n'est lié à l'autre. Douze identifiants au maximum sont évalués par quête et par type - la limite ici est le budget de script des royaumes hardcore, pas la dernière décimale. Le point cardinal provient des mêmes coordonnées du monde, via la même fonction qu'utilisent les listes de points de passage et le scanner de minicarte. Volontairement un point cardinal et non une position horaire : une position horaire est relative à l'orientation du regard et serait fausse dès qu'on se tourne - or la liste est construite une fois puis parcourue. Base de données des quêtes : « Commence dans la zone » sans niveau intermédiaire Simple : Sous « Base de données des quêtes, Commence dans la zone » se trouvait exactement une entrée, « Par distance », et seulement à l'intérieur les quêtes. Un niveau où il n'y a rien à choisir n'est qu'une pression de touche. La liste se trouve désormais directement sous « Commence dans la zone ». Cette liste était par ailleurs cassée : elle s'interrompait après la première quête, on obtenait donc exactement une entrée au lieu de toutes les quêtes de la zone. Technique : Le niveau intermédiaire venait de ce qu'un second tri (« par difficulté ») devait un jour s'installer à côté ; ce chantier est resté commenté à côté pendant des années et n'a jamais été construit. L'interruption venait d'une ligne « tcount = tcount + 1 » dans la construction de la liste : tcount n'y était pas une variable locale mais une globale jamais définie, l'incrémentation levait donc une erreur sur nil. Une erreur dans la construction des enfants d'un menu est avalée - la construction se terminait donc silencieusement après la première entrée. Le compteur n'était lu par rien ; il est supprimé plutôt qu'initialisé. Tout le contenu de la banque en une seule liste, et la banque reconnue aussi sous Bagnon Simple : Le menu des sacs avait « tous les sacs » - une liste à plat sur l'ensemble des sacs, triée par nom. La banque n'avait pas d'équivalent : devant une banque ouverte le menu affichait bien « Banque » et « Banque Sac 1 » à « Banque Sac 7 », mais chercher quelque chose obligeait à entrer dans chaque compartiment l'un après l'autre. Désormais, tant que la banque est ouverte, une nouvelle entrée « toute la banque » se place juste après « tous les sacs » : tout ce qui se trouve dans la banque en une seule liste, avec le même filtre à la frappe. Ctrl+Entrée y déplace un objet vers vos sacs - exactement comme il le faisait déjà dans chaque sac de banque. De plus, Sku détermine maintenant si la banque est ouverte à partir des messages du jeu lui-même, et non plus de la fenêtre de Blizzard. Qui utilise un addon de remplacement des sacs comme Bagnon, lequel masque cette fenêtre, n'avait jusqu'ici aucune banque dans le menu - Sku la croyait fermée et faisait disparaître tous les compartiments de banque. Aucun addon-pont n'est plus nécessaire pour cela. Technique : La suggestion vient de l'addon compagnon SkuBagnonBridge de ZenqFR (github.com/ZenqFR/Sku-BagnonBridge), qui comble la même lacune à sa manière et reconnaît lui aussi la banque via les événements du jeu ; avec l'accord de ZenqFR, la part qui se passe de tout addon supplémentaire est construite ici nativement. La liste à plat se limitait aux sacs 0 à 4 ; les conteneurs de banque (-1, -3, 5 à 11) n'y étaient jamais copiés. Ils sont désormais rassemblés dans une liste propre, délibérément séparée de celle des sacs : SkuCore.combatBagOrder en dérive - le miroir utilisé en combat - et celui-ci ne peut utiliser que des emplacements de sac. Les entrées de la nouvelle liste sont des copies des mêmes entrées par emplacement et emportent le sac et l'emplacement, si bien que toutes les actions existantes fonctionnent sans changement. L'état de la banque tient maintenant à BANKFRAME_OPENED et BANKFRAME_CLOSED : tous deux proviennent de l'interaction avec le banquier elle-même et sont indépendants de qui dessine la fenêtre. Il est réinitialisé au chargement et après /reload, et l'ancienne vérification de la fenêtre est conservée à côté - la détection ne peut donc que reconnaître plus de cas qu'avant, jamais moins. La suppression d'un point de passage personnel effaçait la mauvaise entrée Simple : supprimer un point de passage que vous aviez créé vous-même ne retirait pas son propre identifiant d'une table de recherche interne, mais celui d'un autre point de passage. Et « /skucheck wp » signalait quatre erreurs sur les quatre points de passage rapides alors que tout allait bien pour eux. Technique : le wpId d'un point de passage est son identité - la table des liaisons est indexée dessus -, pas une description de l'endroit où il se trouve. L'identifiant de zone qui y est encodé date du moment de la construction du cache et ne bouge plus quand un point de passage personnel se déplace ; c'est exactement ce qui arrive aux quatre points de passage rapides à chaque écran de chargement, puisqu'ils sont alors replacés sur la position du joueur. La vérification reconstruisait l'identifiant à partir de l'areaId actuel de l'enregistrement et comparait - elle utilise désormais la zone encodée dans l'identifiant lui-même. Le chemin de suppression construisait l'identifiant à partir de « .areaid » (le champ s'appelle areaId), donc avec la zone 1 au lieu de la vraie, et effaçait ainsi l'entrée de l'enregistrement à qui cette clé appartient réellement ; il utilise maintenant l'identifiant stocké. La saisie du chat est devenue utilisable Simple : La ligne de chat était le seul champ de saisie de Sku dans lequel on tapait à l'aveugle. Il y avait un son à l'ouverture et un à la fermeture, rien d'autre - pas d'écho des caractères, pas de lecture sous le curseur, et aucune indication sur la destination réelle du message. Elle se comporte désormais comme tout autre champ de saisie de Sku. Chaque caractère tapé ou effacé est prononcé, les flèches gauche et droite lisent le caractère sous le curseur et, avec Ctrl, le mot entier, la flèche haut rappelle les derniers messages envoyés - pour les renvoyer ou les modifier - et annonce le canal avec eux, car une ligne rappelée ne montre pas où elle irait. Le canal est aussi annoncé à l'ouverture et à chaque changement, mais volontairement pas pour « Dire » : c'est le cas inoffensif, et l'annoncer chaque fois ne serait que du bruit. Si l'annonce ne convient pas à votre façon de faire, elle se désactive dans les réglages du chat ; elle est activée par défaut. La désactiver retire aussi le canal devant une ligne rappelée avec la flèche haut. Un objet dans le chat compte pour un seul caractère à la lecture au lieu de soixante-cinq caractères de balisage. Et une commande mal tapée comme « /tets » dit maintenant « commande inconnue » au lieu de ne rien faire. Technique : L'écho clavier des champs propres à Sku s'attache maintenant aussi aux champs étrangers via SkuOptions:AttachInputEcho. Trois choses y diffèrent : l'attache se fait avec HookScript et non SetScript, car le champ de Blizzard porte ses propres gestionnaires d'historique et de complétion de noms ; le modèle pose ignoreArrows, d'où des flèches simples envoyées au jeu et un curseur de texte exigeant ALT - ce qui est inversé ; et l'armement se fait sur le focus, car un champ étranger n'a pas de paire ouverture-fermeture à laquelle accrocher l'écho. Le canal est lu dans l'en-tête de la saisie plutôt que reconstruit depuis chatType - l'en-tête est déjà traduit et couvre les cibles de chuchotement, les canaux numérotés et les bascules propres à Blizzard en plein appel. Son annonce est différée et tient dans un seul emplacement, car tout changement de canal est déclenché par une frappe, et l'écho de cette frappe éjectait sinon l'annonce de la file avant même qu'elle ne soit prononcée. Les liens d'objet sont lus par leur nom, pas en codes de couleur Simple : Un objet - dans le chat, dans le courrier, dans le butin - était lu comme une longue suite de codes de couleur, d'identifiants et de deux-points au lieu de son nom. Technique : La sortie vocale remplaçait d'abord chaque barre verticale par une espace, PUIS appelait la fonction qui retire les codes de couleur, les liens et les textures. Tous ses motifs cherchent une barre verticale, qui n'existait plus à ce moment - ils ne correspondaient donc à rien depuis toujours. Ordre inversé ; le remplacement général n'est plus qu'un filet de sécurité derrière. Base de données des quêtes : « Commence dans la zone » indique aussi le point cardinal Simple : La liste des quêtes qui commencent dans cette zone ne donnait jusqu'ici que la distance du donneur de quête. Elle indique maintenant aussi, comme la liste « Quêtes à proximité », dans quelle direction elle se trouve - d'abord la distance, puis le point cardinal. Technique : Les coordonnées mondiales du donneur de quête figuraient déjà dans cette liste ; la direction vient de la même fonction que celle qu'utilisent les listes de points de passage. Volontairement le point cardinal et non l'heure : un relèvement horaire dépend de l'orientation du regard et serait faux dès qu'on tourne, alors que cette liste est construite une fois puis parcourue. L'identifiant de quête d'une entrée provient désormais directement de la ligne, au lieu d'une table de correspondance indexée sur l'ancien libellé sans direction. ------------------------------------------------------------------------------------------------------- Écho clavier : les caractères sont annoncés immédiatement Simple : En tapant dans un champ de saisie, les lettres ne suivaient pas les doigts : certaines étaient avalées, d'autres arrivaient nettement en retard, et les lettres doubles comme dans « aller » ou « pomme » n'étaient souvent entendues qu'une fois. Chaque caractère tapé arrive maintenant immédiatement, et une touche pressée deux fois est prononcée deux fois. Technique : L'écho passait jusqu'ici par la même file d'attente que chaque annonce de menu, et cette file est conçue pour des annonces, pas pour des frappes. Elle regroupait les caractères pendant un dixième de seconde et les prononçait en mode écrasement - et écraser signifie ici : d'abord StopSpeakingText, puis attendre encore un dixième de seconde pour que cet arrêt ne supprime pas justement la ligne à laquelle il devait faire place. Les deux à la suite font environ 0,2 seconde par frappe, et l'arrêt d'une frappe atterrissait régulièrement sur la suivante. S'y ajoutait le nouveau garde-fou anti-doublon de cette version, pour lequel un caractère isolé est un énoncé identique à lui-même - d'où les lettres doubles avalées. Les caractères tapés empruntent désormais leur propre voie rapide : une seule place où la frappe la plus récente l'emporte, transmission dans la même image, l'arrêt émis une seule fois au début d'une série de frappes au lieu d'une fois par touche, et le garde-fou anti-doublon ne voit plus du tout les caractères tapés. Contexte : un lecteur d'écran n'a jamais besoin de tout cela, car son interface propose l'interruption comme partie intégrante de la commande de parole - une seule opération. L'interface du jeu ne le permet pas, Sku doit donc la reconstituer à partir de deux appels qui peuvent se doubler. La nouveauté est de ne le faire qu'une fois par série de frappes. Une ligne redemandée par une touche est de nouveau prononcée Simple : Redemander une ligne de menu par une touche produisait parfois du silence - la synthèse vocale considérait la répétition comme superflue. Technique : Le garde-fou anti-doublon écarte une ligne déjà prononcée à l'identique dans la seconde précédente. C'est juste pour la reprise superflue d'une reconstruction de menu, et faux pour le même texte que l'utilisateur vient de demander par une touche. Les deux sont indiscernables par leur texte, et aucun délai ne les sépare - seul leur déclencheur diffère. La gestion des touches du menu marque donc maintenant son annonce comme déclenchée par une touche, et le garde-fou laisse toujours passer les lignes marquées. Les répétitions superflues restent écartées, désormais quelle que soit leur rapidité. Les quêtes à objectifs mixtes montrent tous les types d'objectifs Simple : Pour une quête comme « tuez 8 loups ET ramassez 5 peaux », l'entrée « Cible de quête » du menu de quête ne montrait que le premier type d'objectif - le plus souvent les ennemis ; les sources de l'objet à ramasser, ou un point d'exploration, manquaient en silence. 95 quêtes étaient concernées. Tous les types d'objectifs figurent désormais côte à côte dans une seule liste, et le raccourci « Cibler la créature de quête la plus proche » connaît lui aussi les deux moitiés de ces quêtes. Corrigé au passage : une quête qui n'a qu'un point d'exploration et aucune autre donnée d'objectif reçoit enfin une entrée « Cible de quête », et un point d'exploration irrésoluble ne produit plus une entrée qui se contente d'annoncer « vide ». Technique : SkuQuest:GetQuestTargetIds était une chaîne de elseif sur les listes d'objectifs de la base de données (créatures, sinon objets, sinon objets à ramasser, ...) et ne pouvait, par sa signature même, renvoyer qu'UNE liste avec UN type. Remplacée par GetQuestTargetGroups : un tableau de groupes {targets, type} - créatures fusionnées avec les crédits de mise à mort dédupliqués, objets, objets à ramasser et, pour les seuls appelants objectives, le point triggerEnd de la quête. Les sept sites d'appel sont convertis (menu de quête, cache des points de passage, marqueurs de quête, touche de cible de quête) ; l'ancien nom demeure comme enveloppe premier-groupe pour le port WowVision. La branche item de GetResultingWps est en outre protégée contre les identifiants d'objets inconnus - elle indexait sans vérification et laissait la construction du menu mourir en un silencieux « liste vide ». Base de données des quêtes mise au niveau actuel de Questie Simple : La base de données de quêtes intégrée à Sku provient de Questie et datait d'environ deux ans. Après la synchronisation, 33 quêtes dont la « Cible de quête » restait vide ont de vraies cibles (les jeux de cartes de Sombrelune, les quêtes répétables d'Ogri'la et de l'Œil pourpre, « Trouver le maître des clés »), les six quêtes « Enquêter sur le Fléau » ont désormais des points d'exploration, cinq quêtes manquantes ont été ajoutées, et environ 2000 enregistrements portent de petites corrections de noms, niveaux, donneurs de quête et chaînes de prérequis. Vérifié : les corrections sont identiques pour Era et TBC, la mise à jour est sûre sur les deux. Technique : Une fusion sémantique, pas une copie d'octets (nouvel outil dev/rework-docs/_refresh_questdata_from_questie.py, puis _db_convert.py) : le schéma de Questie diverge à partir du champ 27, seuls les champs 1-26 sont repris ; les champs propres à Sku, extraObjectives et skuData, sont préservés par enregistrement. La convention requiredRaces (ancien masque toutes-races 2047, désormais 0 - identique pour la logique de Sku) compte comme égale dans la comparaison, sans quoi 2117 enregistrements sans changement de contenu auraient encombré le diff. Chaque identifiant de créature, d'objet et d'objet à ramasser référencé a été validé contre les bases livrées (base + WotLK) ; un enregistrement est resté à son ancien état pour cette raison. Les quêtes d'événement n'apparaissent plus juste après le chargement Simple : Dans les premières secondes après un /reload, des quêtes d'événements mondiaux inactifs pouvaient apparaître dans « Commence dans la zone » - des quêtes du Solstice d'été en août, par exemple. Une fois Questie complètement chargé, elles disparaissaient d'elles-mêmes ; elles sont désormais masquées aussi dans cette fenêtre. Technique : Les deux barrières de disponibilité n'interrogeaient Questie qu'à partir de Questie.started, et ce drapeau ne bascule qu'à la toute FIN de l'initialisation de Questie. La liste noire statique de Questie, QuestieCorrections.hiddenQuests - quêtes retirées du jeu plus TOUTES les quêtes d'événement ; les actives ne sont dévoilées qu'après la réponse du calendrier du serveur - existe pourtant bien plus tôt. IsQuestieUnavailable la lit désormais directement avant started : une simple lecture de table ; le risque d'empoisonnement de mémoïsation derrière la barrière started ne concerne que IsDoable. La valeur « HIDE_ON_MAP » est ignorée comme le fait Questie lui-même, et chaque masquage d'avant-démarrage écrit une ligne dprint dans le journal. Les quêtes de donjon mènent à l'entrée au lieu de nulle part Simple : Pour environ 300 quêtes, le donneur, la cible ou la remise se trouve dans un donjon - « La vengeance de Vorrel » commence au Monastère écarlate, « Willix l'importateur » se termine au Kraal de Tranchebauge. Début de quête, Cible de quête et Livraison y restaient vides, car les instances n'ont pas de carte du monde. Ces entrées affichent désormais « PNJ, entrée, nom du donjon, zone » avec une route vers l'entrée du donjon - quêtes de champ de bataille comprises (la vallée d'Alterac choisit la bonne entrée selon la faction). Les sources d'un OBJET dans le même donjon sont regroupées en une seule entrée « nom du donjon, entrée, zone » - « Le destin de Ramaladni » énumérait sinon un à un 29 monstres de Naxxramas menant tous à la même porte ; les objectifs d'élimination gardent leurs noms de monstres individuels. Technique : Nouvelle table générée SkuDB.InstanceEntrances (depuis la table des donjons de Questie, outil dev/rework-docs/_gen_instance_entrances.py) : areaId d'instance -> entrées {zone parente, x, y, faction éventuelle}, y compris les identifiants des ailes et une barrière GetBuildInfo pour les déplacements d'entrées de WotLK. Les résolveurs de spawns (CreatureIdHelper, branches objet et objet-ramassé de GetResultingWps) ne sautent plus les zones sans carte ; ils répondent comme le détour triggerEnd de la v43.2 : convertir les coordonnées de l'entrée en coordonnées du monde, prendre le point de passage de route le plus proche. Mémoïsé par génération du cache de points de passage ; le filtre de faction lit UnitFactionGroup. 77 quêtes à objectif de terrain ont désormais une vraie cible Simple : 77 quêtes dont l'objectif est un lieu ou un objet sur le terrain (les puits de Mulgore, la pierre de sélénien des druides, les pylônes de cristal du cratère d'Un'Goro, la dépouille de Rolf, les quatre totems draeneï, la série Dressage de bêtes des chasseurs et bien d'autres) n'avaient aucune donnée de cible - l'entrée « Cible de quête » manquait tout simplement. Toutes ont désormais une vraie cible routable. Important : les objets et créatures viennent des données d'apparition propres de Sku, mais 26 lieux proviennent de sources web (warcraft.wiki.gg, Wowhead) et n'ont pas tous pu être vérifiés en jeu. Ils ont été choisis avec soin mais peuvent être inexacts dans certains cas - un retour indiquant si une telle cible est juste ou non aide beaucoup. Même un point imprécis vaut mieux que rien : la route mène dans la bonne région. Technique : Résultat de la liste de relecture dev/rework-docs/QUEST-TARGET-PROPOSALS.txt (sections 1-3, relue à la main) : 27 cibles objet et 24 cibles créature comme entrées objectives dans quests_fixes.lua (identifiants validés contre les bases propres, zones d'apparition vérifiées, aucune position -1,-1 en extérieur) plus 26 coordonnées triggerEnd issues de la recherche web. Deux quêtes (9410, 10166) portaient déjà le bon identifiant d'objet dans extraObjectives, que les menus de cible ne lisent pas - le champ objectives y a été ajouté. Treize quêtes de dialogue ont retrouvé leur point de remise Simple : Treize quêtes - surtout les anciennes quêtes de sorts de prêtre (Étoiles d'Élune, Grâce d'Élune, Sans peur, Maléfice de faiblesse), plus entre autres « Le tome de noblesse » et « Allié des Aile-du-Néant » - n'avaient aucun point de remise dans les données : ni cible ni livraison, rien n'était routable. L'entrée Livraison mène désormais au PNJ que le texte de la quête nomme lui-même. Technique : Entrées finishedBy dans quests_fixes.lua ; les identifiants de créature ont été résolus depuis le texte de quête contre la base de données des créatures (chaque nom y est unique, et les points d'apparition se trouvent dans la zone que le texte nomme). Les candidats proviennent du nouvel outil dev/rework-docs/_quest_target_candidates.py, qui classe toutes les quêtes sans cible et écrit la liste de relecture QUEST-TARGET-CANDIDATES.txt pour les restantes (vrais objectifs de terrain sans source de coordonnées). Expérimental : itinéraires de récolte Simplement : Vous choisissez une ressource - un minerai, une herbe, un nuage de gaz ou une sorte de coffre - et Sku vous guide à travers la zone, gisement après gisement, toujours le plus proche d'abord. Là où les données de chemin couvrent le gisement, il emprunte un véritable itinéraire ; sinon vous obtenez une balise directe et Sku vous le dit - une balise peut pointer à travers un mur, un itinéraire non. En vol, il n'y a jamais d'itinéraire : là-haut la ligne droite est la bonne réponse, pas le pis-aller. Au gisement, Sku attend brièvement pendant que vous récoltez, puis poursuit. Le raccourci « point de passage suivant de l'itinéraire » (Ctrl+Maj+W par défaut) ignore le gisement courant pendant un itinéraire de récolte ; aucune nouvelle touche n'est donc nécessaire. Si le pistage correspondant est actif (Détection des minerais, Détection des herbes), l'analyseur de minicarte vérifie à l'approche si le gisement est encore là, et ignore ceux déjà récoltés avec une annonce propre. Sans pistage l'itinéraire fonctionne quand même, simplement sans cette vérification. À trouver sous Réglages -> Scan -> analyse des ressources -> Itinéraires de récolte, avec le rayon de vérification, le nombre de gisements par itinéraire et l'interrupteur de la vérification. Pour les minerais, les herbes et les coffres, « Afficher les points de passage d'herbes et de filons » doit être activé dans les réglages de navigation - sinon ces gisements n'existent pas du tout en tant que points de passage, et Sku le dit précisément au démarrage au lieu de déclarer la zone vide à tort. Les nuages de gaz n'ont pas besoin de ce réglage. Important : Cette fonction est expressément expérimentale. Elle n'a été essayée en jeu que dans les grandes lignes, et la vérification « le gisement est-il encore là » n'a pas pu être exercée dans des conditions réelles, car aucun personnage ici n'a de métier de récolte - sans Détection des minerais ni Détection des herbes, il n'y a aucun point sur la minicarte que l'analyseur puisse lire. Si vous jouez Minage ou Herboristerie : essayez et signalez ce qui ne va pas. Technique : L'idée et l'implémentation de référence viennent de ZenqFR, de l'addon compagnon SkuGatherRoute (github.com/ZenqFR/Sku-GatherRoute) ; la mécanique d'itinéraire y a été conçue et démontrée - une famille de points de passage sous un nom de base commun, chaque gisement atteint par un itinéraire proche, une annonce distincte pour chaque transition, et le raccourci existant comme touche pour ignorer. Ici la fonction est reconstruite nativement avec l'accord de ZenqFR, afin qu'aucun addon supplémentaire ne soit nécessaire. Une différence est délibérée : l'original nécessite GatherMate2, cette version non. Nous n'y voyons aucun avantage - les gisements se trouvent depuis longtemps dans le cache de points de passage de SkuNav, coordonnées mondiales comprises, et nommés de telle sorte que la ressource EST le nom de base du point de passage ; une deuxième source de données et un deuxième addon n'y ajouteraient rien tout en coûtant une installation de plus à chaque utilisateur. Si GatherMate2 fait quelque chose qui nous a échappé - l'enregistrement automatique des gisements réellement récoltés serait le candidat -, dites-nous alors à quoi cela sert, et nous y regarderons. La vérification de présence s'accroche à MinimapScanner:MinimapScanFastStop, le point unique par lequel passe chaque analyse. Si « notifier les ressources » est activé, l'analyse déjà en cours suffit ; sinon l'itinéraire analyse lui-même, de manière limitée et uniquement à portée de la cible, et ces analyses restent silencieuses. Une correspondance confirme, un échec ne prouve rien - une analyse ne rapporte jamais qu'un seul nom et peut avoir attrapé un autre point. Ce n'est qu'après plusieurs analyses infructueuses à portée qu'un gisement est considéré comme disparu. Dans le doute, rien n'est jamais ignoré : un gisement conservé à tort coûte quelques pas, un gisement ignoré à tort ne se remarque pas du tout. Le menu reste utilisable pendant la marche Simple : Si vous ouvriez le menu en marchant, il s'affichait et annonçait « menu ouvert », mais plus aucune touche ne faisait quoi que ce soit - ni flèche, ni lettre, ni Entrée. Seule Échap passait. Il ne redevenait utilisable qu'une fois à l'arrêt. C'est corrigé : un menu ouvert accepte désormais les touches que vous marchiez, couriez ou soyez en course automatique. Technique : Il y avait deux verrous de mouvement, pas un. Le premier empêche l'OUVERTURE tant qu'une touche de déplacement est maintenue - il reste, et il est nécessaire : les raccourcis du menu avaleraient le relâchement de la touche et vous courriez sans fin. La course automatique en est exemptée depuis la v42.00 (son propre indicateur AutoRun, que IsPlayerMoving ne lit délibérément pas). Le second se trouvait dans le gestionnaire de touches du menu lui-même et, tant qu'un verrou de mouvement était posé, laissait expirer chaque appui sauf Échap. Ce second verrou n'apporte rien : une fois le menu ouvert, les raccourcis lui appartiennent déjà, écarter l'appui n'arrête aucun mouvement - il ne fait que rendre le menu sourd, et marque de surcroît « ouvrir plus tard » sur un menu déjà ouvert, que la boucle de réouverture doit ensuite défaire. Il ne s'applique plus que si le menu n'est PAS ouvert. Les quatre verrous d'ouverture restants écrivent en outre une ligne dans le journal de débogage nommant la touche de déplacement qui les retient - auparavant ils étaient muets, et un rapport « le menu ne s'ouvrait pas en marchant » ne laissait aucune trace. Navigation : « Carte actuelle par distance » vient désormais en premier Simple : Sous « Point de passage direct », « Récent » était en première position et « Carte actuelle par distance » en deuxième. Les deux sont permutés - la liste des points de passage de la zone actuelle est l'entrée la plus fréquente, et elle se trouve maintenant directement sous le curseur. Technique : Uniquement l'ordre des injections dans SkuNav:MenuBuilder. « Récent » reste conditionné à une liste non vide de points de passage récents ; si elle est vide, « Carte actuelle par distance » est de toute façon en tête, comme avant. Le filtre de saisie ignore les accents Simple : Dans chaque liste que l'on peut restreindre en tapant, les lettres accentuées étaient traitées comme des caractères d'une autre langue. « general » ne trouvait rien alors que « Fournitures générales » était là, et « ingenieur » ne trouvait pas « Ingénieur » - alors que « ing » y parvenait. C'est maintenant la lettre qui compte, pas l'accent. De plus, la touche ù d'un clavier AZERTY alimente désormais aussi le filtre : c'est la seule lettre accentuée que ce clavier livre comme véritable événement de touche. Technique : string.lower en Lua travaille octet par octet et ne transpose que A-Z ; il ne replie donc jamais un caractère accentué, d'aucun côté de la comparaison. SkuMenuFilterMatch (utilisé par ApplyFilter et JumpToFilterMatch) et SettingsSearchMatch replient désormais le nom de l'entrée et le texte tapé en ASCII sans accents avant de comparer ; seules les séquences de 2 octets du supplément Latin-1 et du latin étendu A sont touchées, l'ASCII pur emprunte une voie rapide. Strictement plus permissif qu'avant : ce qui correspondait correspond toujours. Les deux appels string.find passent en outre plain=true - le texte tapé était lu comme un motif Lua, si bien qu'une saisie comme « 50% » ou « (long » ne trouvait rien ou levait une erreur. Le repli des accents vient de la PR #9 (ZenqFR), la touche ù de la PR #13 (Naxedim). Barres d'action : l'infobulle du sort est de nouveau lue Simple : Dans le menu des barres d'action (Maj+F11), Maj+Bas sur un sort de la liste d'affectation ne lisait plus rien du tout. L'infobulle est de retour - de même que pour le contenu actuel d'une touche et pour les deux lectures de capacités du familier voisines, qui présentaient la même faiblesse. Technique : Deux causes indépendantes, toutes deux traitées car aucune ne pouvait être écartée à la seule lecture du code. D'abord, SkuScanningTooltip est créé une fois à la connexion, sans modèle, et partagé par tous les modules ; celui qui l'a utilisé en dernier peut le laisser sans propriétaire exploitable, après quoi chaque appel Set* ne remplit plus rien - en silence, sans erreur. Il est désormais réapproprié juste avant d'être rempli. Ensuite, SetSpellByID recevait la troisième valeur de retour de GetSpellBookItemName, qui suit sur ce client la signature Classic et ne renvoie que (name, rank) - l'identifiant est un ajout retail et valait nil. L'identifiant provient maintenant de GetSpellBookItemInfo, et l'infobulle est remplie par SetSpellBookItem, qui n'a besoin d'aucun identifiant. La lecture est en outre encapsulée dans un pcall, puisqu'elle se déclenche à chaque déplacement du curseur. Issu de la PR #10 (ZenqFR). Objectif de quête : le lieu figurait dans les données, mais restait introuvable Simple : 174 quêtes qui demandent d'explorer un endroit ou d'y utiliser quelque chose ne répondaient que « Vide » sous « Objectif de quête ». L'entrée mène désormais au point de passage de route le plus proche de ce lieu. Technique : GetTriggerEndWps composait un NOM de point de passage (« ;;;; ») et le résolvait via la recherche par nom exact dans le cache des points de passage. Or rien ne génère de point de passage sous ce nom - contrairement aux apparitions de créatures et d'objets - il ne trouvait donc quelque chose que si quelqu'un en avait écrit un à la main dans les données de route : il en existe exactement un (quête 10211, Shattrath). Le nom composé reste utilisé lorsqu'il se résout ; sinon la géométrie prend le relais - coordonnées de carte vers coordonnées monde, puis le point de passage de ROUTE réel le plus proche. GetNearestWpToCoords2 a reçu pour cela un filtre typeId optionnel, afin que le repli n'atterrisse pas parmi les quelque 145 000 points d'apparition générés, plus une protection contre un seau de continent manquant, où pairs(nil) levait une erreur. Toutes les paires de coordonnées de chaque zone sont désormais lues au lieu de la première seule, et le résultat est mémorisé par génération du cache. Français : les commentaires de points de passage reparlent - et en français Simple : Les commentaires attachés aux points de passage - dont ceux qui touchent à la sécurité, comme « attention, à partir d'ici la route longe le bord d'un ravin », « attendre le zeppelin ici » ou « ce PNJ se déplace » - étaient totalement muets sur un client français depuis la v42.11. Ils reparlent, et ils parlent désormais français : 207 textes répartis sur 546 points de passage. Technique : Les données de route ne portent lComments que pour enUS et deDE (environ 296 points de passage). SkuNav:PlayWpComments lisait comments[Sku.Loc] sans repli. Jusqu'à la v42.11, Sku.Loc valait « enUS » sur un client français faute de locale frFR, et l'on entendait les textes anglais ; depuis que locales/frFR.lua est livré, Sku.Loc vaut « frFR », la recherche renvoie nil et la fonction sort sans un mot. Nouveauté : Sku.locList (SkuUtil.lua), le pendant liste de Sku.locStr, renvoie la première liste NON VIDE parmi Sku.Loc / enUS / deDE. « Non vide » est déterminant, car SkuMM.lua matérialise un comments[Sku.Loc] vide sur chaque point de passage qu'il dessine - une simple chaîne de « ou » s'y accrocherait. La traduction est faite depuis l'original ALLEMAND, non via l'anglais : l'anglais avait perdu du détail sur précisément ces textes de sécurité, et plusieurs entrées anglaises sont vides, dont l'avertissement sur les ascenseurs mortels qui figure sur huit points de passage. Français : noms de ressources et la Caverne des Brumes Simple : Deux défauts qui ne touchaient qu'un client français. D'abord, 22 des 94 noms de ressources du scanner de minicarte étaient erronés - le scanner les compare à l'infobulle du jeu, un nom faux signifie donc que ce gisement n'est jamais annoncé, sans message d'erreur. Ensuite, dans la Caverne des Brumes la liste des points de passage restait vide, alors qu'une route lancée à l'extérieur continuait de fonctionner. Technique : Chacun des 22 noms a été tranché à l'aide d'une source indépendante des deux addons : 19 par les tables frFR de SkuDB (importées des chaînes Blizzard de Questie), trois par l'API d'infobulle de Wowhead. Pour la caverne, Geo.lua compare GetMinimapZoneText() mot pour mot à L["Caverne des Brumes"] ; la locale frFR générée portait « Grotte des Brumes », une traduction libre - le client dit « Caverne des Brumes ». La comparaison était donc morte, uiMap restait sur le continent, GetAreaIdFromUiMapId répondait -1 et GetCurrentAreaId nil, si bien que ListWaypoints2 abandonnait. La valeur a été relevée avec /szp dans la zone, non traduite, et figure désormais tant dans le fichier généré que dans le magasin TSV à partir duquel il est assemblé - avec la note interdisant de la traduire librement. Issu des PR #14 et #16 (Naxedim). Tourner vers la balise : plus précis, et avec anticipation Simple : La rotation automatique vers la balise (T) tombe désormais bien plus juste. Auparavant elle allait si vite que le seul arrêt à la frontière d'une image pouvait coûter vingt degrés de dépassement ou plus, selon la fréquence d'images. La vitesse suit maintenant l'ampleur de la rotation, et aucune rotation ne dure plus d'un quart de seconde. En mouvement, elle anticipe également : elle vise là où se trouvera le point de passage à la fin de la rotation - ce qui met fin aux tours en rond autour des points de passage proches, surtout monté et en vol. Et un appui pendant une rotation en cours est écarté au lieu d'interrompre celle-ci en plein milieu ; les appuis répétés habituels affinent toujours comme avant. Technique : Quatre éléments, testés ensemble. Le calage sur la caméra par défaut avant la rotation reste - il porte le lacet, sans lui l'angle est faux. La vitesse de rotation est plafonnée d'après l'angle restant au lieu de tourner à plein régime. L'anticipation calcule, à partir de la vitesse actuelle et de la durée prévue de la rotation, où sera le point de passage à la fin. S'y ajoute une protection contre le défaut par lequel, après des rotations déclenchées coup sur coup, la vitesse de rotation des touches de caméra pouvait rester quadruplée définitivement. Le programme d'installation retient où se trouve votre WoW Simple : Le programme d'installation cherche le jeu aux endroits habituels - Program Files, plus une poignée de dossiers probables sur C, D et E. Qui a son WoW ailleurs devait chercher le dossier à la main, et pas une fois mais à CHAQUE mise à jour : le programme oubliait la réponse au moment même où il se fermait. Il note maintenant le dossier dès qu'une installation a abouti, et recommence là la fois suivante. Lui montrer un client permet aussi de trouver l'autre à côté : une Classic Era voisine d'une Anniversary sur le même disque inhabituel est prise en compte toute seule. Si le jeu déménage plus tard ou est désinstallé, la note est abandonnée et la recherche se déroule comme avant. Le programme d'installation passe ainsi en version 4.5. Technique : Nouveau fichier PathMemory.cs. Le manifeste d'installation ne pouvait pas servir à cela - SkuInstall.json se trouve DANS le dossier AddOns, le lire suppose donc d'avoir déjà trouvé le dossier que l'on veut retenir. Le dossier de chaque client est donc écrit sous forme d'une simple ligne « produit=chemin » dans SkuPaths.txt, à deux endroits : %LOCALAPPDATA%\SkuUpdater, à côté de la copie permanente de l'updater que lance le raccourci « Sku Updater », et %ProgramData%\SkuUpdater comme miroir valable pour toute la machine. Ce miroir n'est pas une redondance gratuite : le programme demande l'élévation, un utilisateur standard qui répond à l'invite UAC avec les identifiants administrateur de quelqu'un d'autre exécute donc tout sous cet autre profil et écrirait sinon la note dans un LOCALAPPDATA qu'il ne reverra jamais. Au démarrage suivant, un dossier retenu l'emporte sur la détection - c'est une décision, alors que la détection n'est qu'une supposition sur une courte liste de disques - et son répertoire de base est placé en tête de la liste sondée, ce qui est précisément ce qui fait que retenir un client profite à l'autre. Garde-fous contre une note périmée : le dossier doit exister encore, et son .flavor.info ne doit pas annoncer entre-temps un autre client ; sinon la note est ignorée plutôt que d'envoyer une installation dans le vide. L'écriture n'a lieu qu'après l'installation propre d'un client : une exécution annulée ou échouée ne laisse donc rien. Du texte brut à dessein - le fichier se laisse lire à voix haute et corriger dans le Bloc-notes, et supprimer une ligne rend la détection automatique à ce client. Nouveau contrôle sans interface, « SkuSelfTest pathmemory » : il affiche ce qui est noté sur cette machine et déroule l'écriture, la lecture et les cas de note périmée sur un stockage jetable, sans jamais toucher au vrai. La fenêtre de lecture devient configurable Simple : Les textes de quête, les infobulles, l'historique de discussion et les pages du wiki aboutissent tous dans la même fenêtre de lecture. Son apparence était figée dans le code : Playfair Display en 12 points, une police à empattements fine, dans la plus petite taille utilisée par l'addon. Pour une personne ayant une vision résiduelle, c'était à la fois la surface la plus importante de l'addon et la moins lisible. Réglages -> Aides visuelles -> Fenêtre de texte propose désormais des tailles de 14 à 44 points, quatre polices, quatre combinaisons de couleurs (blanc sur noir, jaune sur noir, noir sur blanc, blanc sur bleu), l'opacité du fond et un contour. Par défaut la fenêtre s'affiche maintenant sans empattement en 18 points : même sans rien changer, elle est nettement plus lisible qu'avant. Technique : La surface appartient à Libs/SkuTTS-1.0 (cadre SkuTTSMainFrame et son FontString .FS), où l'apparence tenait en un seul appel SetFont codé en dur. Les réglages vivent désormais dans SkuCore VisualAids sous visualAids.textWindow, et SkuTTS:Output appelle VisualAids:TextWindowLayout avant chaque affichage - protégé par pcall et dans le même sens que SkuZOptions alimente déjà la barre de lecture. La bibliothèque n'a ainsi rien à savoir des options, et un module Aides visuelles désactivé laisse simplement la fenêtre telle quelle. Chaque attribution de police est vérifiée et retombe sur la police standard du client au lieu de laisser une surface vide - les deux familles fournies, Raleway et Playfair, se trouvent sous Libs/SkuTTS-1.0/fonts et ne sont livrées que par l'archive de version. Les paliers de taille sont volontairement plus petits que ceux de la barre : un texte de quête entier tient ici, et la fenêtre n'a pas d'ascenseur, donc elle coupe d'autant plus tôt que la police est grande. Tout reste lu intégralement ; l'affichage est la seconde piste. La barre de lecture affiche ce que Sku dit - et fonctionne en combat Simple : Deux choses à la fois. D'abord, la barre restait vide en combat, c'est-à-dire précisément quand suivre le texte compte le plus. Ensuite, elle n'affichait que le nom d'une entrée de menu : sur un interrupteur on avait le nom mais pas l'état, sur une ligne de sac le nom mais pas la quantité - tout ce que Sku lit en plus manquait sur la barre. Les deux sont corrigés ; la barre affiche maintenant la même ligne que Sku prononce, y compris en plein combat. Sa police et ses couleurs sont en outre réglables, avec des valeurs propres à côté de celles de la fenêtre de texte. Technique : Le blocage en combat venait d'une confusion entre visible et ouvert : le test s'appuyait sur SkuOptions:IsMenuOpen(), qui interroge OnSkuOptionsMain:IsVisible(). Ce cadre est un ancêtre des boutons ENTER sécurisés, donc protégé, et son Show() est bloqué en combat - le menu de combat tourne volontairement sans affichage et se signale via SkuOptions.combatMenuActive. templates.lua et SkuZOptions testent déjà exactement ce couple ; la barre de lecture ne le connaissait pas. Elle-même est un cadre ordinaire sans enfant sécurisé : son Show() n'a jamais posé problème en combat. Comme le chemin de combat ne ferme le menu que logiquement (le Hide du cadre y est protégé) et le fait à six endroits, la barre se masque désormais d'elle-même : un OnUpdate vérifie toutes les 0,25 s si le menu est encore ouvert - WoW n'appelle OnUpdate que sur les cadres visibles, cela ne coûte donc rien une fois masquée. Le contenu, lui, était un paramètre jeté : l'appelant transmettait depuis toujours la chaîne parlée complète, mais c'est currentMenuPosition.name qui était affiché. La chaîne transmise est désormais prioritaire, et les points-virgules du séparateur de parole deviennent des tirets à l'affichage. Barres de nom : les réglages que le jeu cache Simple : Le client sait depuis longtemps afficher ses barres de nom plus grandes, plus colorées et plus contrastées - simplement, il ne le propose nulle part. La fenêtre d'options de Blizzard n'affiche aucun de ces réglages sous Classic ; la case pour des barres de nom plus grandes qui existe dans le WoW actuel est explicitement laissée vide sous Classic. Sans Sku, on n'y accédait que par des commandes console. Le nouveau menu sous Réglages -> Aides visuelles -> Barres de nom les rend accessibles : taille des barres, mise en évidence de la cible, atténuation de toutes les autres, couleurs de classe pour les ennemis et les alliés, et noms seuls pour les joueurs alliés. Contrairement à l'ancienne coloration, cela ne peint pas quelque chose derrière la barre mais modifie la vraie barre. Par défaut, tout reste tel que le client le livre : si vous ne choisissez rien, rien ne change. Technique : Uniquement des réglages de CVar, aucun cadre propre, aucune contamination. La taille écrit nameplateMinScale et nameplateMaxScale ensemble (les séparer produirait une mise à l'échelle dépendante de la distance, dont personne ne veut ici), la mise en évidence écrit nameplateSelectedScale, l'atténuation nameplateMinAlpha, auxquels s'ajoutent nameplateShowClassColor, nameplateShowFriendlyClassColor et nameplateShowOnlyNameForFriendlyPlayerUnits. Toutes les valeurs et l'existence de chaque CVar ont été mesurées sur le client vivant 2.5.6.69546, et le plafond de 2.0 vérifié contre l'écrêtage du client. Important pour la suite : le dossier BlizzardInterfaceCode extrait dans le répertoire du client date de mars/avril 2026 et est donc antérieur au changement des barres de nom de juillet ; il cite des noms de CVar de la version actuelle comme NamePlateVerticalScale ou ShowClassColorInNameplate qui n'existent pas du tout dans cet exécutable. Ce sont les chaînes de l'exécutable qui font foi. Les écritures de CVar sont bloquées en combat et échouent en silence : une modification faite là est donc mémorisée et appliquée à PLAYER_REGEN_ENABLED. Sku réapplique les valeurs à la connexion, mais seulement une fois qu'un choix a réellement été fait dans le menu - qui a réglé ses barres de nom à la main par la console les conserve intactes. La coloration des barres de nom survit à un redémarrage Simple : Qui activait la coloration des barres de nom l'avait exactement jusqu'à sa prochaine connexion ou au prochain rechargement de l'interface. Ensuite l'interrupteur indiquait toujours « activé », mais plus rien n'était coloré. Le seul moyen de la relancer était de basculer l'interrupteur à la main, à chaque session. C'est corrigé ; le réglage fonctionne désormais simplement. Technique : L'armement du pilote d'événements (NAME_PLATE_UNIT_ADDED, _REMOVED et PLAYER_TARGET_CHANGED) ne tenait qu'au OnAction de l'interrupteur du menu. La valeur enregistrée était bien lue au chargement mais appliquée nulle part : après chaque connexion, aucun événement n'était donc enregistré. L'armement se trouve maintenant dans VisualAids:OnEnable, là où l'avertissement de suivi et le bouton du prochain ennemi ont toujours été armés, avec une seconde tentative différée au cas où le profil ne serait pas encore prêt au premier passage. Une fenêtre fermée n'est plus annoncée après coup Simple : Apprendre plusieurs sorts chez un maître, fermer la fenêtre rapidement puis ouvrir sa fenêtre de métier ramenait la fenêtre du maître au bout d'un moment - parfois seulement après la lecture d'une infobulle. Sku retenait où il comptait encore sauter dans le menu et le faisait plus tard, sans vérifier si cette fenêtre existait toujours. Technique : Le saut de menu différé tient en trois champs (openMenuAfterCombat, openMenuAfterMoving, openMenuAfterPath) qui étaient jusqu'ici écrits indépendamment. SlashFunc arme le drapeau ET le chemin ensemble ; d'autres endroits n'armaient que le drapeau - et héritaient donc du chemin qui traînait encore. Le chemin n'était d'ailleurs effacé que dans la partie différée de CheckFrames, qui abandonne tant qu'on se déplace et ne reprend qu'une demi-seconde plus tard, alors que la libération s'exécute à chaque image et se déclenche à l'instant où l'on s'arrête - une course que la libération gagnait. Les trois champs forment désormais un tout (SkuCore:ClearDeferredMenuOpen), armer un drapeau efface le chemin, et l'effacement a lieu dans le hook Hide de la fenêtre (SkuCore:GENERIC_OnClose) - exactement au moment où le saut devient caduc. Délibérément sans délai d'expiration ni contrôle de visibilité : l'état est effacé là où il devient faux, au lieu d'être distancé. Le menu n'ouvre plus son niveau le plus haut tout seul Simple : De temps en temps le menu s'ouvrait de lui-même - et tout en haut, au lieu de là où l'on voulait aller. Technique : SkuCoreControlOption1 porte des raccourcis clavier (distance de la cible, mode panique, scan de la minicarte, annuler le vol, se tourner vers une unité) et n'a rien à voir avec le menu. Son OnShow abandonne tant qu'on se déplace ou qu'on est en combat, car SetOverrideBindingClick y est interdit et avalerait le relâchement d'une touche de déplacement maintenue - mais il abandonnait en posant openMenuAfterMoving, et la libération, faute de chemin, en faisait une ouverture du menu à sa racine. Ce cadre a maintenant son propre drapeau (rebindControlKeysAfterBlock) et ne récupère que ses touches. La fermeture d'une seconde fenêtre ouverte est de nouveau remarquée Simple : Avec deux fenêtres ouvertes en même temps - un marchand et les sacs, par exemple - seule la première fermeture était remarquée. À la seconde, le menu ne se reconstruisait pas et ne se fermait pas. Technique : L'un des deux installateurs de hooks enveloppait Show et Hide autour d'un unique interrupteur partagé (SkuDropdownlistGenericFlag) : tout Show le posait, et seul le premier Hide suivant le consommait. Quel installateur prenait une fenêtre était une pure question de temps - celui-ci prend tout ce qui existe une seconde après l'entrée dans le monde, tandis que des fenêtres comme le maître, le métier et l'hôtel des ventes naissent plus tard et reviennent à l'autre. L'interrupteur a disparu ; GENERIC_OnClose regroupe de toute façon lui-même. Plus de contenu d'une fenêtre fermée sous « Local » Simple : Après la fermeture d'une fenêtre, ses entrées pouvaient rester sous « Local » et réapparaître à la prochaine ouverture du menu. Technique : SkuCore.GossipList est la source de données du menu Local, mais elle n'était vidée que si le menu était ouvert à cet instant précis. Sur le chemin d'Échap il ne l'est jamais : le cadre du menu descend volontairement avant les fenêtres, et la partie de code concernée s'exécute une image plus tard. Elle est maintenant vidée dès qu'aucune fenêtre n'est ouverte. Maître : le silence une fois la fenêtre fermée Simple : Apprendre plusieurs sorts coup sur coup et fermer la fenêtre juste après laissait les annonces du maître continuer. Technique : Chaque clic sur « Apprendre » mettait en file une re-lecture après une demi-seconde et une annonce après 0,85 seconde, sans possibilité d'annulation. Les deux étapes vérifient maintenant que ClassTrainerFrame est encore visible. Le garde-fou de la v43.1, qui évite de parler dans un menu fermé, n'aidait pas ici : dès que la fenêtre suivante rouvrait le menu, il était ouvert. De plus, la reprise des re-lectures silencieuses en déplacement perdait sa consigne de silence et était planifiée une fois par clic - elle transporte désormais ses arguments et ne s'exécute qu'une fois. Hôtel des ventes : la liste des résultats est vraiment triée Simple : Trier par « Niveau décroissant » donnait malgré tout une liste qui commençait par du bas niveau. Le tri n'agissait qu'à l'intérieur d'une page - et une page, ce sont les 50 enchères les moins chères. En haut se trouvait donc le plus haut niveau parmi la camelote grise la moins chère, tandis que les objets de niveau 70 étaient des centaines d'entrées plus bas. Même après le son de fin, car rien n'était jamais retrié. Maintenant, c'est le serveur qui trie lui-même la liste par niveau ; à niveau égal, c'est le prix à l'unité qui décide. Technique : Chaque requête partait avec le tri serveur figé sur « unitprice », et la liste des résultats se construit page après page, par simple ajout à la fin (les nouveaux objets vont en bas pour que le curseur ne saute pas). L'ordre de la liste est donc toujours celui du serveur, et notre propre tri n'ordonnait qu'une page. Pour « Niveau croissant » et « décroissant », la réponse du serveur est désormais triée sur la colonne « level » - celle-là même que règle le bouton de tri par niveau de Blizzard. L'achat et l'achat stratégique restent sur « unitprice » : ils ont besoin de l'offre la moins chère en page 0. Une recherche par nom aussi : tous les résultats y ont le même niveau, le serveur aurait trié sur un champ où rien ne diffère et aurait renvoyé ses pages dans son propre ordre au lieu des moins chères d'abord. De plus, les comparateurs de niveau utilisent maintenant le prix à l'unité comme second critère ; les enchères sans achat immédiat comptent avec leur enchère à l'unité au lieu d'un prix d'achat de 0. Hôtel des ventes : « niveau » signifie partout le niveau requis Simple : Le niveau affiché dans les résultats était tantôt le niveau requis, tantôt le niveau d'objet, selon la liste d'où il venait et selon que l'objet a ou non une exigence. Une pile d'Étoffe du Néant (exigence 0, niveau d'objet 55) passait ainsi devant une épée de niveau 40. C'est désormais toujours le niveau requis, la colonne que l'hôtel des ventes lui-même appelle « niveau ». Et il n'est plus lu que là où il distingue les entrées : dans une catégorie ou dans la liste du scan complet. Une recherche par nom d'objet ne renvoie que des enchères du même objet - le même nombre y revenait 800 fois. Qui filtre par niveau continue de l'entendre partout. Technique : Un lecteur commun aux deux listes de résultats : le champ 6 de la réponse du serveur, sinon la valeur de SkuDB. Le champ 7 est pris en compte - pour les recettes, le champ 6 contient la compétence de métier requise, pour les contenants le nombre d'emplacements, et ce ne sont pas des niveaux. Le niveau d'objet n'est plus jamais utilisé en remplacement. 0 signifie maintenant « aucune exigence de niveau » et non « inconnu », d'où la disparition du « Niveau 0 » annoncé (0 est vrai en Lua, c'est ainsi qu'il finissait par être lu). Hôtel des ventes : « utilisables uniquement » ne cache plus rien Simple : Dans la liste du scan complet, ce filtre ne laissait plus, dans certaines catégories, que du bas niveau - précisément les objets de haut niveau recherchés avaient disparu. La recherche normale n'a jamais eu ce défaut. Technique : Dans la liste en direct, c'est le serveur qui filtre. Le scan complet récupère tout sans filtre et Sku décide lui-même - jusqu'ici strictement d'après le champ canUse de la réponse du scan. Le client ne peut remplir ce champ que s'il disposait déjà des données de l'objet ; lors d'un scan complet, il ne les a pas pour la majorité des lignes. Ce sont exactement les lignes dont le nom, le niveau et la qualité doivent être réparés - seul canUse n'avait aucune réparation. Disparaissait donc ce que le joueur n'a jamais vu, c'est-à-dire surtout le haut niveau. Un « non utilisable » ne compte plus que sur une ligne complète ; sinon, c'est le niveau requis de SkuDB qui décide. La base ne connaît ni les restrictions de classe ni celles de race - dans le doute, mieux vaut montrer un objet de trop que taire celui que l'on cherche. Hôtel des ventes : sans filtre, rien n'est filtré Simple : Même sans filtre réglé, la liste du scan complet écartait tout ce qui n'a pas d'exigence de niveau - marchandises, composants, beaucoup de recettes - et en plus tout le gris, alors que le menu de qualité affichait toujours « Médiocre », donc aucune restriction. Technique : Les valeurs par défaut des filtres non réglés étaient un 1 au lieu d'un 0, et le niveau était lu brut dans la réponse du scan. Une ligne dont le client ne connaissait pas le niveau pendant le scan revenait à 0 et tombait donc sous le niveau minimum de 1 ; la réparation depuis la base ne se déclenchait que sur une valeur absente, pas sur un 0. Les deux valeurs par défaut sont maintenant 0, le niveau vient du lecteur commun, et la réparation à la lecture traite 0 comme « absent ». Le niveau minimum de l'utilisateur employé en dernier recours a disparu : une ligne ainsi marquée passait toujours son propre filtre et était en plus annoncée avec ce niveau. Hôtel des ventes : les catégories sans sous-catégories affichent de nouveau leurs objets Simple : Sous « Enchères par objet », certaines catégories ne contenaient rien d'autre que « Tout » - les objets de quête, par exemple. Technique : La liste d'objets comparait la sous-classe même là où il n'y en a aucune. Une catégorie sans sous-catégories est construite sans sous-classe, et la comparaison est alors fausse pour chaque objet. Le jumeau du scan complet portait cette protection depuis toujours ; la liste d'objets l'a maintenant aussi. Hôtel des ventes : un achat annulé reste annulé Simple : Annulez un achat, faites autre chose, et le dialogue d'achat pouvait revenir de nulle part - et la touche Entrée suivante, destinée à ouvrir le champ de recherche, achetait. C'était au moins le bon objet, mais personne ne l'avait demandé. Technique : L'annulation libérait les minuteurs et les raccourcis, mais pas la cible d'achat. La fonction de requête déduit son mode exactement de là - il existe une cible d'achat, donc c'est une requête d'achat -, et la requête suivante, pourtant sans aucun rapport, partait dans le chemin d'achat : il retrouvait l'ancienne cible dans la liste fraîche, construisait le dialogue et armait la touche Entrée. Dans le journal du 01/09/2026, l'annulation (18:45:40), la nouvelle recherche (18:46:12, aussitôt « MATCH FOUND, showing popup ») et l'achat (18:46:16, « enchère acceptée ») ne sont séparés que par quatre appuis de touche. La cible d'achat est désormais effacée aux trois endroits où l'intention d'acheter prend fin : annulation du dialogue (y compris par la sécurité de 30 secondes), démarrage d'une nouvelle recherche ou liste, et fermeture de l'hôtel des ventes. Le chemin d'achat lui-même reste intact : il ne passe pas par l'entrée de recherche, et la nouvelle tentative après une course perdue conserve sa cible. Hôtel des ventes : une recherche interrompue le dit Simple : Quand une recherche s'interrompt parce que le serveur ne répond plus, la liste à moitié remplie restait affichée et ressemblait à un résultat terminé. On entend maintenant « Recherche interrompue, liste incomplète ». Technique : Le chien de garde met fin à une recherche paginée après 60 secondes sans réponse du serveur, ou après 180 secondes au total - en silence, jusqu'ici. Le son de fin est le signal du « complet », mais son absence ne s'entend pas. L'annonce n'a lieu que si l'utilisateur se trouve réellement dans cette liste de résultats ; une recherche qui tourne en arrière-plan reste silencieuse. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.1 Aperçu Changements L'achat de plusieurs exemplaires à l'hôtel des ventes continue désormais tant qu'il existe d'autres offres au même prix audible. Chaque achat réussi est immédiatement confirmé par « Acheté, n sur m ». Quand un prix est épuisé, Sku annonce « Plus aucune offre à ce prix » avec le bilan et reste dans la liste des résultats. Nouveau réglage « Énoncer l'écho du clavier » (Réglages -> Général, activé par défaut) : la lecture caractère par caractère pendant la saisie dans les champs de texte peut être désactivée. Hôtel des ventes : le scan complet se confirme une première fois au bout de 5 secondes, puis toutes les 10 secondes comme avant. Menu des quêtes : l'entrée « Membres du groupe » se place désormais en troisième position au lieu du milieu, ce qui laisse « Quêtes actuelles » et « Base de données des quêtes » toujours à la même place. Corrections Hôtel des ventes : l'achat de plusieurs exemplaires s'interrompait à tort après presque chaque achat réussi avec « Enchère épuisée » et éjectait de la recherche. Après la fermeture du menu, des annonces différées continuaient de parler (« Nouvelle lettre plus », « Sujet : », « Envoyé », « Tout ouvert » depuis une boîte aux lettres fermée depuis longtemps). Hôtel des ventes : une réponse incomplète du serveur ne termine plus la recherche à tort par « vide » - une vraie recherche sans résultat répond toujours immédiatement. Hôtel des ventes : une recherche qui ne peut pas démarrer indique désormais pourquoi (« impossible, analyse en cours » ou « Hôtel des ventes occupé ») au lieu d'annoncer « vide ». Hôtel des ventes : une recherche sans résultat annonce désormais « aucun résultat » au lieu de « vide » - et le champ de recherche reste dans la liste, le curseur s'y pose directement. Hôtel des ventes : un scan complet annulé en fermant la fenêtre bloquait ensuite toute recherche avec « impossible, analyse en cours », alors qu'aucun scan ne tournait plus. Hôtel des ventes : le scan complet mourait parfois en silence juste après l'annonce de départ ; le verrou de 16 minutes était tout de même consommé. Clients français : la liste des points de passage était vide dans plusieurs zones (correction de Naxedim). Nouveau réglage : Énoncer l'écho du clavier Simple : Dans les champs de saisie (rédaction d'une lettre, recherche, nom de profil...), Sku lit chaque caractère tapé, ainsi que la touche retour arrière et les lectures du curseur avec les flèches. Si cela vous dérange ou si vous l'entendez en double, vous pouvez désormais le désactiver sous Réglages -> Général, « Énoncer l'écho du clavier » (juste à côté de « Énoncer les numéros du menu » et « Annoncer les sous-menus »). Par défaut, c'est ACTIVÉ : rien ne change si vous ne touchez à rien. Les annonces d'état comme « Annulé » ou la confirmation du champ après ENTRÉE restent toujours. Plus d'annonces dans un menu fermé Simple : Valider un champ de saisie avec ENTRÉE puis fermer aussitôt le menu produisait quand même « Sujet : » - comme si l'écho du clavier traînait après la fermeture. De même, « Nouvelle lettre plus », « Envoyé » ou « Tout ouvert » parlaient encore depuis une boîte aux lettres fermée depuis longtemps. Désormais : tout ce qui veut annoncer une entrée de menu vérifie d'abord que le menu est encore ouvert - et les accusés de la boîte aux lettres vérifient que celle-ci est encore ouverte. La ligne système du serveur « Courrier envoyé. » dans le chat n'est pas concernée. Technique : Les retardataires ne venaient jamais de la file vocale (la fermeture la vide déjà via queuereset + StopSpeakingText) mais de producteurs qui n'enfilent qu'APRÈS la fermeture : confirmations C_Timer (0,3 s après pièce jointe/saisie/ envoi, 0,5 s après « Tout ouvrir ») et événements serveur (MAIL_SEND_SUCCESS). Une purge générale supplémentaire de la file à la fermeture d'une fenêtre ne les attraperait donc pas, mais couperait en plein mot des annonces légitimes (chat, combat, navigation). À la place : une garde centrale dans SkuOptions:VocalizeCurrentMenuName (pas de menu ouvert, pas de menu de combat sans affichage et pas de menu de ligne SkuChat -> silence ; le mode chaîne renvoie toujours le texte), plus trois gardes côté producteurs dans le courrier (MailboxOpenFlag pour « Envoyé », IsMenuOpen pour la confirmation de champ, visibilité de MailFrame pour « Tout ouvert »). MAIL_FAILED reste volontairement non filtré - un échec doit rester audible même après la fermeture. Hôtel des ventes : l'achat de plusieurs exemplaires s'interrompait à tort après presque chaque achat avec « Enchère épuisée » Simple : En choisissant une entrée de résultats comme « 14 fois Sphère du Vide » pour en acheter cinq, on entendait « Enchère épuisée » après presque chaque achat et on se retrouvait hors de la recherche - alors que l'achat avait réussi et que l'argent était correctement débité. Comme le succès lui-même n'était jamais annoncé, cela ressemblait à une série d'échecs, alors que les objets étaient déjà dans le sac. La cause : les vendeurs se sous-enchérissent de 1 cuivre. La liste des résultats regroupe toutes les enchères dont le prix SONNE pareil - au-dessus de 1 or, la part de cuivre n'est pas prononcée -, mais la continuation d'achat cherchait l'exemplaire suivant au montant de cuivre exact, qui n'existait généralement plus une fois le moins cher acheté. La continuation parcourt maintenant un tel groupe pièce par pièce : l'offre suivante ne peut différer que par le cuivre, la partie inaudible du prix. Une offre audiblement plus chère - que ce soit de 1 argent ou de 100 or - n'est JAMAIS proposée automatiquement, et chaque exemplaire exige toujours sa propre confirmation. Technique : Prouvé dans le journal de débogage du 2026-08-25 : cinq achats de 52po99a81c à 52po99a84c, chaque tentative achetait exactement un exemplaire puis s'interrompait. La continuation (_ABReQueryBuy → match scan) comparait ID d'objet + taille de pile + prix d'achat immédiat EXACT ; or la liste des résultats regroupe selon le libellé formaté (SkuGetCoinText very-short supprime le cuivre au-dessus de 1 or), un groupe couvre donc une tranche de 1 argent. Le scan retient maintenant l'offre achetable la moins chère avec le même ID d'objet et la même taille de pile dans la même tranche (floor(buyout/100) identique, seulement à partir de 10000 cuivre) et reprend ses champs de prix (8/9/10/11) dans QueryBuyData ; failCount repart à 0 pour le nouveau groupe de prix. Sous 1 or, le libellé prononce le cuivre - l'ancien comportement exact y demeure. Les achats par enchère gardent la comparaison stricte sur 17 champs. Hôtel des ventes : chaque achat est confirmé, la fin d'un groupe de prix annonce le bilan et reste dans la liste Simple : Après chaque exemplaire réussi, Sku dit maintenant immédiatement « Acheté, n sur m » - avant, le seul signal de succès était l'invite d'achat suivante. Quand toutes les offres au même prix audible sont parties, on entend « Plus aucune offre à ce prix » avec le bilan, par exemple « 2 sur 5 acheté », et le curseur reste sur l'entrée de la liste des résultats au lieu de remonter de plusieurs niveaux vers « Enchères » comme avant. De là, on peut naviguer directement vers l'entrée suivante, plus chère, et racheter. Technique : L'annonce de succès se trouve dans _ABContinueOrFinish, juste après le « Enchère acceptée. » du serveur. Le chemin « épuisée » positionne d'abord (AuctionStayOnResultsEntry ; si l'élagage échoue parce que l'enregistrement a déjà été retiré après l'achat, un repli via AuctionResultsItemEntryFromCursor garde le curseur sur l'entrée de l'objet) puis prononce le message 0,4 s plus tard sans interruption - le même schéma que le « Terminé. Tous achetés » existant. Hôtel des ventes : une réponse incomplète du serveur ne vaut plus « aucun résultat » Simple : Quand le serveur répond de façon contradictoire - il annonce des résultats mais ne livre aucune ligne -, Sku terminait aussitôt la recherche par « vide ». Dans ce cas, il attend désormais la réponse complète et redemande la page. Une recherche qui ne trouve réellement rien (faute de frappe dans le champ de recherche, catégorie vide) répond toujours immédiatement - avec 0 résultat et « aucun résultat ». Technique : Dans AUCTION_ITEM_LIST_UPDATE_LIST, une page vide ne compte comme prématurée que si le serveur se contredit : tBatch == 0 (aucune ligne sur cette page) alors que tCount > 0 (nombre total de résultats supérieur à 0). QueryListEmptyWaits s'incrémente alors, le scan reste en « waiting » et le ticker redemande la même page ; après 10 réponses de ce type, le résultat est tout de même accepté (sortie de secours). tCount == 0 est au contraire la réponse honnête « aucun résultat » et doit passer sans délai : le chemin d'achat compte ici CHAQUE réponse vide (QueryBuyEmptyWaits) parce qu'il cherche à retrouver une enchère peut-être encore vivante - pour une recherche, la même règle serait fausse. Dans le journal de débogage du 2026-08-26, le serveur répond à la recherche « feuerpartiekel » par 0 sur 0 dans la même seconde : c'est la vraie réponse, pas une réponse fantôme. Hôtel des ventes : une recherche qui ne peut pas démarrer indique désormais pourquoi Simple : Il y a des moments où l'hôtel des ventes n'accepte pas une recherche : pendant qu'un scan complet tourne en arrière-plan, pendant qu'un achat est armé, ou quand le serveur limite les requêtes. Jusqu'ici Sku ne s'en apercevait pas : il construisait quand même la liste et annonçait « vide », comme si la recherche n'avait rien trouvé. Au premier essai d'une session, cela ressemblait à une catégorie vide ; plus tard, la liste affichait même les résultats de la recherche PRÉCÉDENTE. Sku indique maintenant la raison. Si un scan complet tourne, « impossible, analyse en cours » figure dans la liste dès l'ouverture - le message que le menu de vente donne depuis toujours dans cette situation. Attendre n'y a aucun sens : un scan complet occupe l'hôtel des ventes pendant des minutes. Pour les obstacles courts (achat armé, limitation), Sku reste sur « Attendre » et continue d'essayer de lui-même ; si la recherche passe dans les 5 secondes, tout continue normalement, sinon il annonce « Hôtel des ventes occupé, recherche non démarrée ». Si un scan complet démarre pendant ces tentatives, Sku les interrompt aussitôt et le signale. Flèche gauche puis droite relance un essai à tout moment. Technique : AuctionHouseStartQuery renvoie false à trois endroits et, depuis cette version, la RAISON en deuxième valeur (« secureBuy », « getAllRunning », « getAllCooldown », « queryThrew »). Les trois appelants de navigation - champ de recherche, « Tous » et objet unique dans AuctionHouseBuildItemDBMenu - ignoraient cette valeur de retour et posaient malgré tout QueryResultsHost, vidaient children et reconstruisaient ; le constructeur de résultats trouvait state == « idle », sautait sa branche « Attendre » et tombait sur le test de liste vide. Ils passent désormais par AuctionBrowseStart, qui décide selon la raison : les raisons getAll sont signalées immédiatement, tout le reste est mis en file comme nouvel essai (QueryStartPending). AuctionBrowseRetryTick s'accroche au ticker OnUpdate existant de l'hôtel des ventes - volontairement piloté par les frames plutôt qu'une chaîne C_Timer, car une chaîne interrompue n'atteint jamais sa ligne de réarmement -, se place tout en tête avant ses retours anticipés (sans quoi il gelait pendant toute l'ingestion getAll), tente au plus toutes les 0,25 s et seulement si CanSendAuctionQuery, revérifie la raison, et se remet explicitement en file après un échec, puisque StartQuery appelle ResetQuery en interne et efface ainsi le marqueur même lorsqu'il renvoie ensuite false. Après un succès, QueryResultsHost est reposé, car la nouvelle requête le remet à nil. QueryStartFailed porte le TEXTE à afficher (AH_QueryBusy ou le message de scan du menu de vente) et est testé AVANT la condition « Attendre » : le scan bloquant maintient lui-même AuctionScan.state sur « waiting »/« paging », sinon « Attendre » l'emporterait toujours et la raison resterait inaccessible. Le chemin immédiat ne parle volontairement PAS de lui-même : le message est la seule entrée de la liste et est lu à l'ouverture de toute façon, alors qu'une annonce distincte serait coupée par le vocalize suivant et repartirait à chaque passage (OnEnter s'exécute déjà à l'arrivée). ResetQuery efface les deux marqueurs. Hôtel des ventes : « aucun résultat » au lieu de « vide », et le champ de recherche reste accessible Simple : Une recherche sans résultat - une faute de frappe, par exemple - annonçait « vide ». Cela évoque une liste vide, alors que le sens est « rien trouvé » : elle annonce désormais « aucun résultat ». De plus, le champ de recherche disparaissait de la liste après une recherche ; pour en lancer une seconde, il fallait sortir puis revenir. Il reste maintenant dans la liste sous « Suchbegriff eingeben », et en cas de zéro résultat le curseur s'y pose directement - une pression sur ENTRÉE et l'on saisit le terme suivant. S'il y a des résultats, le curseur se pose sur le premier comme auparavant. Technique : La construction incrémentale appelait directement le constructeur de résultats et contournait ainsi le BuildChildren de l'entrée de recherche - c'est là qu'est inséré « Suchbegriff eingeben », d'où sa disparition après la première page. Elle appelle maintenant aHost:BuildChildren(aHost) (pour « Tous » et objet unique, c'EST le constructeur de résultats). Pour que l'entrée de recherche ne retombe pas sur « Attendre » au passage, sa condition vérifie aussi QueryResultsPartialReady. AuctionFocusAfterResults choisit le curseur : la première entrée qui n'est ni le champ de saisie, ni « aucun résultat », ni « Attendre » ; s'il n'y en a pas, le champ de saisie. L'annonce de zéro résultat part en UNE seule sortie (« aucun résultat;Suchbegriff eingeben ») - un second OutputStringBTtts aurait coupé le premier. Dans la liste de catégories du scan complet, l'entrée zéro résultat s'appelle également « aucun résultat » ; la branche sans aucune donnée de scan reste « vide », car c'est un autre constat. Hôtel des ventes : un scan complet annulé bloquait ensuite toute recherche Simple : Annuler un scan complet en cours en fermant l'hôtel des ventes avait pour effet que TOUTE recherche et toute catégorie annonçaient ensuite « Nicht möglich, scan läuft » - alors qu'aucun scan ne tournait plus. Cela persistait jusqu'à ce qu'on relance un scan complet et qu'on le laisse aller au bout. La fermeture démonte désormais correctement le scan et la recherche suivante fonctionne normalement. (Le verrou serveur de 16 minutes sur le prochain scan complet n'est pas concerné : il est côté serveur et s'applique aussi après une annulation.) Technique : AuctionHouseResetQuery refuse la réinitialisation tant qu'un scan getAll tourne, sauf si l'appelant insiste avec force - cela protège un scan complet en cours d'être tué par une réinitialisation incidente (menu de vente, chemins de navigation). AUCTION_HOUSE_CLOSED appelait AuctionScanFinish SANS force : fermer en plein scan laissait donc state = « waiting » avec QueryData.getAll = true ; c'est exactement ce que teste la logique de refus de la recherche, si bien que toute requête ultérieure était rejetée en « getAllRunning ». La fermeture force désormais - elle EST la fin du scan, et plus aucun événement n'arrive ensuite. Deux lacunes de journalisation qui masquaient le bug sont corrigées au passage : le refus dans AuctionHouseResetQuery écrit maintenant sa propre ligne (auparavant on ne voyait que l'appel juste avant et l'on croyait l'état nettoyé), et AuctionScanFinish ne signale « finished » qu'APRÈS la réinitialisation, avec reset=true/false - le journal affirmait jusqu'ici une fin de scan qui n'avait jamais eu lieu. Hôtel des ventes : première annonce du scan complet au bout de 5 secondes Simple : Pendant que le scan complet attend la réponse du serveur, Sku annonce « scan complet, n secondes » toutes les 10 secondes. La toute première de ces annonces n'arrivait donc qu'au bout de 10 secondes - dix secondes durant lesquelles on ignorait si le scan tournait seulement. La première confirmation arrive maintenant après 5 secondes, la deuxième 10 secondes plus tard, puis au rythme de 10 secondes (soit à 5, 15, 25 ... secondes). Rien d'autre ne change : les messages de 25 pour cent de la phase de lecture et l'annonce finale restent identiques. Technique : Le rythme dans le ticker OnUpdate de l'hôtel des ventes se trouve désormais dans tFullScanSpeakNext, qui démarre à tFullScanSpeakFirst (5) et bascule sur tFullScanSpeakEvery (10) après la première annonce. Le compteur est diminué du seuil en vigueur plutôt que d'un 10 fixe, afin que la phase reste juste. À la fin de la phase de travail, le seuil retombe à 5 - sinon le scan suivant n'aurait de nouveau donné sa première confirmation qu'après 10 secondes. Hôtel des ventes : le scan complet mourait parfois en silence juste après l'annonce de départ Simple : Parfois, « Scan complet démarré » n'était suivi de rien : pas d'annonce de progression toutes les 10 secondes, pas de message de fin, et les menus censés être verrouillés pendant un scan ne l'étaient pas - le verrou de 16 minutes était pourtant consommé. Cela touchait surtout ceux qui attendaient la fin du délai directement à l'hôtel des ventes ouvert. En interne, le scan était tué aussitôt après le départ ; il démarre maintenant toujours avec une horloge remise à zéro. Technique : Prouvé dans le journal de débogage du 2026-08-26 : QueryAuctionItems et « watchdog: getAll timeout 600s » dans la MÊME seconde. Les compteurs du chien de garde du ticker de scan étaient déjà pleins avant le départ : tTime accumulait tout le temps d'inactivité à l'hôtel des ventes ouvert (la branche idle ne le remettait jamais à zéro - l'attente de 16 minutes > 600 s suffisait à elle seule), et tFullScanElapsed survivait à chaque fin de scan parce que sa remise à zéro se trouve derrière la barrière de tick de 0,2 s de la branche de sérialisation, qu'une sérialisation rapide ne franchit jamais. Le premier tick du chien de garde après le départ additionnait les deux et tuait le scan tout frais ; le chien de garde se remettait alors lui-même à zéro, d'où le « la plupart du temps ça marche ». SkuCore.AuctionScanWatchdogReset remet maintenant les deux compteurs à zéro après chaque QueryAuctionItems réellement envoyé, et la branche idle remet elle-même tTime à zéro (ce qui protège aussi le chien de garde de blocage des recherches paginées). Vérifié le 2026-08-26 : un scan après ~28 min d'attente à l'hôtel des ventes est allé au bout, lisant 173841 enchères. Clients français : la liste des points de passage était vide dans plusieurs zones (correction de Naxedim) Simple : Dans les Cavernes des lamentations, dans le Tram des profondeurs, au Repaire des Grumegueules et au Repaire d'Onyxia, Sku annonçait une liste de points de passage vide alors que la zone est entièrement cartographiée. Deux observations ont permis de cerner le problème : un itinéraire déjà lancé continuait de fonctionner, et les mêmes points de passage restaient sélectionnables depuis l'extérieur de la zone. Il ne manquait donc aucune donnée : Sku ne savait simplement pas dans quelle zone se trouvait le personnage. Les clients allemands et anglais n'ont jamais été concernés. Technique : Deux causes indépendantes produisant le même symptôme, toutes deux trouvées, corrigées et vérifiées en jeu par Naxedim sur un client français (PR #8). Premièrement, SkuNav/Geo.lua compare quelques noms de zone MOT POUR MOT avec GetMinimapZoneText() ; quatre d'entre eux avaient été traduits librement dans locales/frFR.lua, plus aucune comparaison ne correspondait et GetBestMapForUnit renvoyait la carte du continent au lieu de celle de la zone. Les nouvelles valeurs ont été lues avec C_Map.GetAreaInfo() sur un client français, elles ne sont pas traduites. Deuxièmement, les instances dont la vraie ligne de zone est commentée dans SkuDB/assets/maps.lua dépendent d'une ligne synthétique (id à partir de 100000) pour laquelle C_Map n'a rien à résoudre ; elle restait en anglais alors que le dernier recours de GetCurrentAreaId la compare au nom de CARTE localisé - aucune correspondance, aucun areaId, aucun continent, liste vide. Ces lignes prennent désormais leur nom de l'uiMap qui porte le même nom anglais ; en français il s'agit d'exactement deux zones, le Repaire d'Onyxia et les Cavernes des lamentations. Ajouté ensuite : un garde-fou pour qu'aucune ligne synthétique ne reçoive un nom qu'une vraie ligne porte déjà (Joug-d'hiver), la mention « ne pas traduire librement » sur tous les noms de zone comparés mot pour mot dans locales/enUS.lua, et un magasin de traduction français indexé par la clé et non plus par le numéro de ligne - il avait glissé d'une ligne et aurait décalé presque toute la table à la prochaine régénération. Au passage, pour les clients anglais : L["Die Höhlen des Wehklagens"] contenait « The Wailing Caverns », ce qu'aucun client ne dit. Le même cas particulier y était donc mort lui aussi et vient d'être corrigé. Menu des quêtes : « Membres du groupe » passe en dernier Simple : Avec Questie chargé, Sku affiche en plus les quêtes des membres du groupe. Cette entrée se trouvait jusqu'ici entre « Quêtes actuelles » et « Base de données des quêtes » - et comme elle apparaît et disparaît avec le groupe, la base de données passait de la deuxième à la troisième place selon que vous étiez en groupe ou non. Qui s'y rendait de mémoire n'atterrissait pas au même endroit seul et en groupe. « Membres du groupe » est désormais toujours en dernier, en troisième position ; les deux premières entrées ne bougent plus, seul comme en groupe. Technique : Dans SkuQuest:MenuBuilder, le bloc tSpecs conditionnel (showGroupQuests + QuestieReady + IsInGroup) passe après le bloc « Base de données des quêtes ». SkuMenu:Build rend les specs dans l'ordre du tableau, sans aucun tri : l'ordre du code source est donc l'ordre du menu. L'entrée elle-même, sa condition et ses sous-menus ne changent pas. ------------------------------------------------------------------------------------------------------- Changements dans Sku v43.0 Aperçu Nouveautés Shift-F9 à Shift-F12 ont désormais leurs propres entrées de raccourcis, et les accès rapides 1 à 4 sont de nouveau libres. Les annonces de vol en taxi peuvent être désactivées, et elles ne nomment plus jamais le point de départ ni celui d'arrivée comme atterrissage possible. Nouvelle condition d'aura « Buff debuff vos sorts uniquement » qui filtre les listes de buffs et de debuffs de cette aura sur les effets que vous avez lancés. Nouvelle commande /skucheck qui vérifie des règles fixes sur l'état réel du jeu ; elle reprend aussi les outils de base de données comme sous-commandes (wp, db, mem). L'installateur collecte tous les journaux pour un rapport de bogue en un seul bouton. L'écran de connexion est désormais utilisable avec l'outil de connexion. Les listes de points de passage annoncent l'avancement du chargement. Changements Les avertissements d'expiration d'aura arrivent à l'heure au lieu du prochain événement de combat. L'évaluation des auras par événement de combat est nettement moins coûteuse. Annuler la navigation ne passe plus que par une seule touche. Le moniteur de ressource peut suivre la barre que le jeu affiche : un druide obtient ainsi mana, rage ou énergie selon sa forme. Le journal de l'outil de connexion survit à un redémarrage de l'outil. Les données de points de passage sont utilisables bien plus tôt après la connexion, votre propre continent d'abord, et occupent environ 22 Mo de mémoire en moins ; la partie des données d'itinéraires que votre version de jeu ne lit pas n'est plus construite du tout. Les auras sont désormais interchangeables entre langues : une aura fonctionne sur tous les clients. Les ensembles d'auras partagés arrivent utilisables chez un équipier d'une autre langue. Tous les rangs d'un sort comptent pour une seule entrée d'aura. Les noms de sorts attribués plusieurs fois par le jeu se distinguent maintenant dans les listes de sélection. Les ensembles d'auras fournis ne sont plus réservés aux clients allemands. Le menu dans lequel on construit une aura a été refait : liste d'attributs triée par nom, conditions à deux valeurs en bascules, sorts saisissables autant que sélectionnables, et toutes les listes de valeurs s'ouvrent plus vite. Chaque entrée de menu de tout l'addon coûte environ 13 fois moins à créer, ce qui ouvre les grandes listes de sorts environ trois fois plus vite. Une liste dont la construction a été interrompue par le serveur est poursuivie à la frame suivante au lieu de rester incomplète sans le dire. Les réglages à deux valeurs annoncent leur valeur avec le nom et se basculent avec Entrée ; la flèche droite et le sous-menu disparaissent. Les curseurs de volume du menu Sku règlent directement le réglage du jeu ; Sku n'en garde plus de copie. Corrections Clients français : tous les sons d'auras et les clips vocaux enregistrés étaient muets (correctif de Naxedim). Clients français : la vérification de distance n'annonçait rien et son menu levait une erreur (correctif de Naxedim). Clients français : deux suites du même rapport corrigées. Neuf libellés traduits signifiaient autre chose selon la langue (correctif de ZenqFR). Auras : certaines places de groupe étaient annoncées par un bip au lieu d'un mot. Auras : une aura réglée sur « une fois » pouvait se déclencher plusieurs fois de suite en combat dense. Menu : sur les grandes cartes, la liste de points de passage de Shift-F9 ne s'ouvrait qu'au deuxième essai. Les clés du porte-clés étaient annoncées comme « Vide ». Sur les royaumes hardcore, Sku termine désormais le chargement de ses points de passage au lieu de s'arrêter en silence. Navigation : la liste des itinéraires pouvait rester vide et muette lorsque les points de passage étaient reconstruits pendant qu'un menu était ouvert. Des données d'itinéraires manquantes sont désormais signalées au lieu d'apparaître comme une liste vide. Une fenêtre surgissante ouverte en combat (invitation de groupe, invocation, libération) pouvait être lue mais pas répondue. Un itinéraire ne démarrait pas si sa liste finissait de charger pendant que vous y étiez. L'outil de connexion prenait l'écran de connexion pour la sélection de personnage. L'outil de connexion : sur les royaumes comptant dix personnages ou plus, la liste des personnages était incomplète, mal numérotée ou refusait de revenir en arrière. Moniteur de ressource : le réglage « rien » provoquait une série d'erreurs Lua. Moniteur de ressource : un moniteur désactivé dans le menu des fonctions annonçait quand même les changements de ressource. Clients français : la liste des enchantements d'arme pour les auras était vide. La vérification de préparation n'arrivait pas directement sur « Prêt » comme le fait toute autre fenêtre surgissante. Un volume de jeu réglé dans le menu Sku pouvait revenir à une ancienne valeur sur un autre personnage. Les quatre profils standard sont créés sans changer de profil entre-temps - sur les royaumes hardcore, cela pouvait vous laisser dans un profil qui n'est pas le vôtre. Avec nos remerciements à Naxedim et ZenqFR Les quatre correctifs suivants sont les premières contributions de code directes au dépôt Sku désormais public. Naxedim a trouvé, analysé et corrigé les deux bogues qui rendaient muette une grande partie de la sortie audio de Sku sur les clients français ; ZenqFR a audité les fichiers de traduction, résolu tous les doublons contradictoires côté anglais et français et amélioré l'outil qui protège contre de nouveaux doublons. Tout le mérite de ces correctifs leur revient. Clients français : tous les sons d'auras et les clips vocaux enregistrés étaient muets (correctif de Naxedim) Simple : Sur un client français, depuis la v42.11, chaque aura dont la sortie est un son ne jouait plus rien du tout - pas d'erreur, pas de ligne de journal, juste le silence - et chaque clip vocal enregistré retombait sans bruit sur la synthèse vocale (TTS). La cause : depuis que Sku parle français, il cherche aussi un pack vocal FRANÇAIS, et un tel pack n'existe pas ; la recherche ne trouvait donc rien et tout ce que le pack vocal fournit devenait muet. La parole ne faisait que se dégrader en TTS, mais un effet sonore n'a rien vers quoi se dégrader - c'est pourquoi cela ressemblait à « mes auras ne fonctionnent plus » plutôt qu'à « l'audio a changé ». La recherche de pack utilise maintenant le même repli vers l'anglais que le reste du système audio depuis la v42.09. Les clients allemands et anglais ne sont absolument pas affectés. Technique : Le résolveur de pack vocal dans Core.lua comparait les locales des packs à Sku.Loc ; depuis que la v42.11 livre locales/frFR.lua, Sku.Loc vaut « frFR » sur un client français, aucun pack ne pouvait la déclarer, Sku.AudiodataPath restait "" et VoicePackAudioDir() renvoyait nil - réduisant au silence les 64 entrées de SkuCore.outputSoundFiles (sons d'auras, son de combat de SkuMob, son de nouvelle ligne de SkuChat) plus chaque clip vocal du pack. La comparaison se fait maintenant sur Sku.LocAudio ou Sku.Loc, exactement la règle « vers quelle langue enregistrée est-ce que je me replie » qu'IntegratedAudioDir() utilise déjà ; Sku.LocAudio vaut « enUS » sur tout client non allemand, le comportement deDE/enUS est donc identique à l'octet près. Trouvé sur un vrai client français, corrigé et vérifié en jeu par Naxedim (PR #5). Clients français : la vérification de distance n'annonçait plus rien et son menu levait une erreur (correctif de Naxedim) Simple : Sur un client français, la vérification de distance n'annonçait plus aucune distance, quelle que soit la cible, et l'ouverture du menu de configuration des distances produisait une erreur au lieu du menu. Deux causes, toutes deux propres au français : premièrement, le réglage « prononcer la distance » est enregistré dans la configuration sous la forme du MOT traduit pour « Parlé » - une configuration écrite quand Sku tournait encore en anglais (avant la v42.11) contient le mot anglais, que le client désormais français ne reconnaît plus. Deuxièmement, les clips de nombres n'existent enregistrés qu'en allemand et en anglais, et le code énumérait exactement ces deux langues - un client français restait donc silencieux même avec une configuration française toute fraîche. Le marqueur enregistré est maintenant accepté dans les trois langues, une valeur inconnue ne peut plus casser le menu, et les clips se replient sur les enregistrements anglais comme le reste du système audio. Les clients allemands et anglais sont inchangés : même marqueur, mêmes clips, même audio. Technique : Un nouveau RangeCheck:IsSpokenSound() accepte « Gesprochen »/« Spoken »/ « Parlé » (plus le L["vocalized"] traduit à chaud), utilisé par DoRangeCheck et par le constructeur de menu dans Options.lua - ce dernier tombait jusqu'ici dans la branche else pour une valeur inconnue et concaténait nil depuis SkuCore.RangeCheckSounds, ce qui levait l'erreur. Le dossier de clips vient maintenant de Sku.LocAudio (hans_de-de pour deDE, hans_en-us sinon) au lieu d'une chaîne de Sku.Loc qui ne correspondait à aucune branche en frFR. Les nouvelles entrées écrivent toujours L["vocalized"] ; pas de migration de données. Trouvé et vérifié sur un vrai client français par Naxedim (PR #6). Traductions : neuf libellés signifiaient autre chose selon la langue (correctif de ZenqFR) Simple : Neuf textes figuraient en double dans les fichiers de traduction avec deux significations DIFFÉRENTES, et chaque langue pouvait finir par retenir une version différente. C'était le plus audible sur les clients anglais, où le mauvais jumeau l'emportait plusieurs fois : la barre d'action de posture était annoncée « Special Action bar » (une toute autre barre), la touche de débogage « debug mode » au lieu de « debug output », une catégorie de menu « Features » au lieu de « Values ». Pour chaque paire, la signification réellement correcte a été déterminée d'après l'endroit où le texte est utilisé dans le code, et le mauvais jumeau a été supprimé dans les trois langues - l'anglais, l'allemand et le français sont maintenant d'accord sur les neuf. Six doublons purement allemands restent volontairement ouverts jusqu'à ce qu'un germanophone tranche. Technique : AceLocale résout les clés en double par « le premier gagne » pour la langue par défaut (enUS) et par « le dernier gagne » partout ailleurs - un doublon à deux valeurs signifie donc silencieusement autre chose selon la langue. ZenqFR a retracé les points d'appel réels de chaque clé pour déterminer la valeur survivante (PR #3) et a appris à dev/rework-docs/_locale_dupes.py à ne comparer que le littéral de chaîne Lua analysé au lieu de la ligne brute, pour que les commentaires « -- » en fin de ligne ne comptent plus comme conflits (PR #4) - sur 64 conflits signalés, seuls 30 étaient réels ; enUS et frFR passent maintenant le lint à zéro, deDE avec les six paires de formulations allemandes laissées à un locuteur natif. Clients français : deux suites du rapport de Naxedim sur le pack vocal Simple : Le son nommé « bring » - une cloche, nommée d'après le bruit qu'elle fait - apparaissait dans les menus français comme « apporter » : la traduction l'avait lu comme le verbe anglais « to bring ». C'est un nom, pas un mot, et il reste désormais « bring » comme ses voisins non traduits « brang », « dang » et « drmm ». Par ailleurs, le diagnostic interne qui note quel pack vocal a été trouvé nomme maintenant la langue réellement cherchée au lieu de la langue du client - avant, un client français sans le pack anglais installé aurait affirmé « no voice pack found for frFR », désignant un pack qui ne peut pas exister. Technique : locales/frFR.lua remet L["bring"] sur l'onomatopée. Core.lua renseigne maintenant Sku.AudiodataPathInfo dans une branche else du résolveur à partir de tWantedLoc (la clé de recherche basée sur Sku.LocAudio) plus la locale du client ; lisible via /wdeval Sku.AudiodataPathInfo. Le troisième point du rapport de la PR #5 - frFR traduit le préfixe des libellés de sortie en « aura;son » - a été examiné et volontairement CONSERVÉ : chaque site de comparaison reconstruit le préfixe depuis la table de locale active, et les réglages/auras enregistrés conservent des clés d'identifiants (« sound-brass1 »), jamais le libellé - la traduction est donc sûre ; une synthèse vocale française pour un libellé français vaut mieux qu'un clip anglais. Sacs : les clés du porte-clés étaient annoncées comme « Vide » Simple : Depuis la v42.13, chaque emplacement du porte-clés disait « Vide » alors que les clés étaient bien là et restaient utilisables - cela ne s'est vu que lorsque quelqu'un a vraiment eu besoin du porte-clés. Le déclencheur était le correctif de la banque de la v42.13 : dans le client du jeu, le porte-clés présente la même particularité que le conteneur principal de la banque et demandait le même traitement spécial, que seule la banque avait reçu à l'époque. Les clés sont de nouveau annoncées par leur nom. Le porte-clés n'affiche en outre plus que les emplacements qu'il utilise réellement au lieu de 32 fixes - auparavant, des dizaines d'emplacements vides suivaient les clés. Technique : GameTooltip:SetBagItem(-2, slot) ne remplit rien sur ce client, exactement comme le conteneur de banque -1. Les noms de clés ne fonctionnaient que grâce au repli générique SetHyperlink de 67f2132 ; quand la v42.13 a volontairement supprimé ce repli et n'a basculé que la banque (-1) vers SetInventoryItem, le porte-clés est retombé en silence sur le SetBagItem vide. tSetTooltipContainerItem lit désormais -2 via SetInventoryItem("player", KeyRingButtonIDToInvSlotID(slot)) - exactement ce que fait ContainerFrameItemButton_OnEnter de Blizzard pour le porte-clés. Build_BagsFrame limite en plus le nombre d'emplacements de -2 avec GetKeyRingSize() (emplacements occupés arrondis à des rangées complètes de quatre), comme la fenêtre de porte-clés de Blizzard, au lieu de reprendre les 32 bruts de GetContainerNumSlots. Diagnostic : nouvelle commande /skucheck qui vérifie des règles fixes sur l'état réel du jeu Simple : /skucheck parcourt toutes les données des sacs et vérifie que chaque emplacement occupé fournit un nom prononçable ; le résultat tient en une ligne parlée, par exemple « Vérification des sacs : 64 vérifiés, aucun problème », avec un renvoi vers le journal en cas de problème. La commande vérifie les données elles-mêmes, pas les menus - elle aurait donc signalé le bogue du porte-clés même sans jamais l'ouvrir. La liste des règles ne grandit qu'avec les bogues corrigés : chaque correctif apportera désormais sa propre règle de garde, afin qu'une modification ultérieure ne puisse pas réintroduire le même bogue sans bruit. Depuis cette version, /skucheck est en outre la seule commande de vérification : les trois outils de base de données sont devenus des sous-commandes - /skucheck wp vérifie les données de points de passage et s'exécute avec un /skucheck sans argument, tandis que /skucheck db et /skucheck mem sont des mesures qui ne s'exécutent que sur demande. Les anciens noms /skudbwpcheck, /skudbcheck et /skudbmem fonctionnent toujours. Technique : En fin de LocalMenu.lua ; parcourt tBagSlotListSorted avec la même API de conteneurs et la même résolution de noms que le menu des sacs. Règle 1 : emplacement avec un identifiant d'objet mais un texte d'infobulle vide = violation ; chaque violation est écrite dans l'anneau SkuDebugLog en ligne « skucheck » avec sac, emplacement, identifiant et lien. La banque fermée est sautée avec une ligne de journal (pas en silence), le porte-clés est volontairement balayé sur ses 32 emplacements bruts, et les objets pas encore envoyés par le serveur comptent comme « en attente » et non comme des échecs. Les domaines wp/db/mem appellent SkuDBTools.RunWpCheck/RunDbCheck/RunMem (SkuDBTools.lua) ; ils s'exécutent en tâche de fond et annoncent eux-mêmes leur résultat, mais l'écrivent aussi dans l'anneau en lignes « skucheck » - les violations de points de passage une par une, alors qu'elles n'étaient lisibles que dans les SavedVariables. /skucheck db indique en plus combien de jeux de données ont changé depuis la capture précédente. Raison de ce regroupement : avec trois commandes séparées, on pouvait lancer « la vérification » et manquer entièrement les règles des points de passage. Auras : les avertissements d'expiration arrivent à l'heure, pas au prochain événement de combat Simple : Deux plaintes anciennes avaient la même cause : une aura comme « affaiblissement sur la cible sous 3 secondes » ou « mon affaiblissement a disparu de la cible » n'était revérifiée que lorsqu'un AUTRE événement de combat arrivait. Dans un combat purement au corps à corps, l'avertissement pouvait donc arriver avec un coup d'arme entier de retard, hors combat avec des minutes - ou ne jamais partir avant l'expiration elle-même. Sku note désormais, en vérifiant l'effet, QUAND le seuil sera atteint et réveille la vérification exactement à ce moment ; l'apparition ou la disparition d'un effet déclenche aussi la vérification directement. De plus, les mots parlés d'une aura peuvent maintenant dépasser les annonces en attente, comme le font déjà les sons d'alerte - ils n'interrompent rien et ne chevauchent rien, ils ne se mettent simplement plus en queue. Les avertissements d'enchantement d'arme en profitent aussi : au lieu d'un retard pouvant atteindre une seconde, ils partent désormais à l'image près. Technique : Trois mécanismes : (1) un planificateur d'échéances dans SkuAuras/Core.lua - chaque passe d'évaluation note dans tNextDurationDeadline le moment de seuil le plus proche (expirationTime moins seuil) de toutes les conditions de durée actives (opérateur « plus petit ») ; le pilote de trame compare un nombre par trame puis déclenche UNE passe synthétique DURATION_DEADLINE. Remplace la revérification approchée à la seconde des enchantements d'arme. (2) UNIT_AURA déclenche une évaluation lors d'un vrai changement d'ensemble des effets (ajout/retrait, pas les mises à jour de piles ou de durée) - différence de noms contre un instantané, filtrée contre les passes du journal de combat dans la même trame et contre les changements de cible. (3) les sorties en mots utilisent le chemin aInstant d'OutputString, présent mais jamais branché (insertion en tête au lieu d'ajout en queue) ; un curseur de trame garde les sorties en plusieurs parties dans l'ordre parlé. Auras : évaluation par événement de combat nettement moins coûteuse Simple : En raid, Sku évalue les auras des centaines de fois par seconde. Plusieurs coûts cachés ont disparu : les conditions étaient vérifiées deux fois, les durées restantes redemandées au client à chaque événement, et le groupe entier parcouru requête par requête pour chaque événement. Deux bogues silencieux corrigés au passage : une aura sans condition « sort en temps de recharge » pouvait annoncer le nom de recharge d'une AUTRE aura, et les temps de recharge d'objets étaient réhorodatés à chaque changement de sac. /skucheck gagne pour cela un domaine « auras » qui vérifie que l'évaluation n'écrit plus de globales. Technique : Évaluer une fois au lieu de deux (la branche à valeur unique de la boucle d'attributs tournait deux fois par accident ; plus deux globales fuitées, tSpellNameOnCdValue injectait des valeurs périmées d'une aura à l'autre). Les durées restantes viennent d'une extension expirationTime du cache de listes de niveau 2 au lieu de rebalayages UnitAura (/skuauracache verify vérifie maintenant aussi les valeurs exp, /skuauracache off désactive les deux). Les GUID de groupe se résolvent via une carte invalidée aux événements de roster au lieu de boucles raid1..40 par événement (l'ordre du résultat et l'horizon historique raid1..25 du RoleChecker sont préservés exactement). targetUnitDistance et targetTargetUnitId sont paresseux, LogRecorder fait un accès aux réglages au lieu de quatre, et la garde UNIT_INVENTORY_CHANGED teste le vrai itemID au lieu d'une globale nil. Installateur : un bouton collecte tous les journaux pour un rapport de bogue Simple : L'écran d'accueil de l'installateur comporte un nouveau bouton, « Collecter tous les journaux ». Il crée UN seul fichier zip dans votre dossier Téléchargements contenant tout ce qu'il faut pour diagnostiquer un problème - et ce pour TOUTES les versions du jeu détectées à la fois, pas seulement une : le journal de débogage et d'erreurs de Sku, les captures de BugGrabber/BugSack et de WVDebug, les journaux du jeu lui-même avec ses erreurs Lua, le fichier de CVar Config.wtf, la liste de tous les addons installés avec leur version, le journal de l'outil de connexion et celui de l'installateur. Jusqu'ici il fallait aller chercher ces fichiers un par un dans trois arborescences différentes, et en oublier un valait une question de plus au lieu d'une réponse. Le fichier s'appelle Sku-Logs-.zip ; l'installateur en énonce le chemin complet et ouvre le dossier avec le fichier déjà sélectionné, prêt à être joint à un rapport de bogue. Une collecte typique pèse environ 7 Mo. Technique : Nouveau fichier LogCollector.cs dans l'installateur, déclenché par un quatrième bouton dans UpdatePromptForm (libellé et texte vocalisé dans les trois langues). La collecte se fait dans un sous-dossier par client : les SavedVariables de la portée compte ET de la portée personnage (Sku écrit dans les deux), y compris les copies .bak - celles-ci sont l'état de la session PRÉCÉDENTE et donc souvent la seule copie subsistante de l'anneau de débogage sur lequel porte le rapport - ainsi que Logs, Errors, Config.wtf, Sku.toc, .build.info et une liste des addons avec versions de TOC et marquage des liens symboliques. S'y ajoutent system-info.txt (système, langue, un bloc par client et la liste de ce qui n'a PAS été inclus et pourquoi - une omission doit se lire comme une omission et non comme une absence) et le journal de l'installateur, forcé pour l'occasion du tampon mémoire de Logger vers le disque. Le dossier Errors est délibérément exempté de la limite d'âge de 30 jours - il ne contient quelque chose que lorsque quelque chose a réellement cassé, et le premier essai réel n'en livrait sinon aucun des 21 rapports de plantage présents - et il est plafonné aux 25 plus récents à la place. L'installateur passe ainsi en version 4.2. Outil de connexion : son journal survit désormais à un redémarrage de l'outil Simple : L'outil de connexion effaçait son journal à CHAQUE démarrage. Quiconque tombait sur un bogue, fermait l'outil puis le rouvrait avant de collecter les journaux détruisait donc la preuve même qu'il s'apprêtait à signaler - et c'est la séquence la plus courante d'un rapport de bogue. Les cinq dernières sessions restent maintenant à côté du log.txt courant sous les noms log-1.txt à log-5.txt, et le nouveau bouton de collecte de l'installateur les emporte automatiquement. Technique : log.ahk écrit désormais vers un chemin ABSOLU dérivé de l'emplacement du script au lieu du nom relatif « log.txt », qui se résout par rapport au répertoire de travail du processus - START.ahk le règle correctement, mais c'est un état global au processus que n'importe quoi peut changer ensuite, et un journal qui atterrit là où personne ne regarde ne vaut pas mieux que pas de journal. ClearLogFile() effectue une rotation au lieu d'effacer. L'empaquetage et le .gitignore excluent log-*.txt exactement comme log.txt, et les journaux d'une installation existante ne sont jamais touchés par une mise à jour. Outil de connexion : l'écran de connexion s'annonçait comme la sélection de personnage Simple : Quand le jeu n'atteint pas le serveur au démarrage, on n'arrive pas sur la sélection de personnage mais sur l'écran de connexion, avec nom de compte, mot de passe et bouton Login. L'outil y menait au menu principal, dont la première entrée est « sélectionner un personnage » : l'écran qui prouve justement qu'il n'y a aucune liste de personnages s'annonçait donc comme la sélection de personnage. Qui ne le voit pas n'entendait rien qui le distingue du vrai. L'outil annonce désormais une fois à l'arrivée : pas connecté, soit il n'y a pas de connexion au serveur, soit le nom de compte et le mot de passe doivent encore être saisis dans le jeu. Il remarque aussi maintenant le changement d'écran : si l'on se connecte à la main, la liste des personnages est construite aussitôt, au lieu de laisser l'outil muet sur l'ancien menu. Technique : « pas de connexion » n'est PAS un écran distinct - vérifié sur le client 2.5.6 en fonctionnement, il est identique au pixel et à l'OCR près à un écran de connexion normal (mêmes sondes de widgets, checks.login = true, aucune fenêtre). Il n'y a donc rien à marquer : l'outil n'annonce que ce qui y est toujours vrai, ce client n'est pas connecté. Le menu est désormais son propre arbre plutôt que le menu principal, et InitLogin ne s'exécutait qu'au changement de MODE, d'où un changement d'écran passé inaperçu ; le veilleur déclenche maintenant une réinitialisation complète dès que l'écran de connexion est atteint ou quitté. Outil de connexion : l'écran de connexion est désormais utilisable Simple : Le menu de l'écran de connexion commence maintenant par les commandes de l'écran lui-même : nom de compte, mot de passe, se connecter, enregistrer le nom de compte - puis, inchangés, la voix, la langue, le type de jeu, la région et la version, afin que les réglages restent accessibles même si la connexion échoue. L'outil n'enregistre RIEN et ne saisit rien : il place seulement le curseur du jeu dans le champ et passe le clavier au client, c'est vous qui tapez. Il n'y a pas de connexion automatique et il n'y en aura pas. Le nom de compte est lu à voix haute, « vide » s'il est vide ; le mot de passe n'est JAMAIS lu. Tant que le clavier appartient à un champ, les flèches vont au jeu, pour pouvoir atteindre une faute de frappe ; Entrée termine la saisie, Échap la quitte, et aucune des deux n'est transmise au jeu - se connecter est une entrée à part, pour que rien ne parte par accident. Fermer et rouvrir Battle.net reste généralement plus rapide ; la voie manuelle existe pour ceux qui la veulent. Technique : quatre nouveaux widgets dans data.ini ([Classic] et [BurningCrusade]) : LoginAccountField 10000,403, LoginPasswordField 10000,478, LoginSubmitButton 10000,531 et LoginSaveAccountCheckbox 26,662 - mesurés sur le client en fonctionnement en 2880x1800 puis convertis dans l'espace d'interface de 768 de haut, donc indépendants de la résolution, et recoupés avec des entrées déjà présentes (« Account erstellen » mesuré à 549 contre les 550 enregistrés). Le contenu du champ est lu par OCR du CHAMP seul, pour qu'aucune étiquette venue d'ailleurs ne passe pour du contenu. « Se connecter » suit la tentative : les fenêtres de progression à un seul bouton sont lues mais PAS cliquées, car leur unique bouton est Annuler et il coupe la connexion en cours - le même piège que pour l'entrée en royaume ; les fenêtres à deux boutons sont lues et traitées. Là où les widgets manquent (Cata, Retail), les entrées le disent et ne font rien. Outil de connexion : la liste des personnages sur les royaumes de dix personnages ou plus Simple : Le panneau des personnages en affiche exactement neuf à la fois. À partir du dixième, la liste défile, et tout a déraillé en même temps : des personnages manquaient, la numérotation ne correspondait pas à la liste réelle, et le retour au début de la liste ne fonctionnait pas de façon fiable. Les trois ont la même cause. L'outil parcourt la liste avec les touches fléchées et observe le déplacement de la barre de sélection - or dès que la liste défile, la barre ne bouge plus, et « rien n'a changé » était lu comme « la liste s'arrête ici ». Une touche avalée par le jeu a exactement la même allure, et c'est elle qui tronquait les listes. Avec neuf personnages ou moins la liste ne défile jamais, ce qui explique que rien de tout cela n'ait jamais été visible. L'outil demande maintenant deux fois avant de croire à une fin de liste, et s'appuie sur le grand nom affiché sous le modèle du personnage pour trancher - ce nom est celui du personnage sélectionné, quel que soit le défilement. Il annonce également le compte en cours tous les dix personnages, car une longue liste représente sinon une demi-minute de silence, et le menu reste utilisable pendant sa reconstruction au lieu de devenir muet. NON TESTÉ en jeu : il n'existe ici aucun royaume de plus de neuf personnages. Si la liste reste fausse, le journal de l'outil nomme désormais précisément la décision erronée. Technique : Vérifié sur le code d'interface du client lui-même. Blizzard_GlueXML_TBC.toc charge Vanilla\CharacterSelectConstants.lua avec CHARACTER_SELECT_MAX_CHARACTERS = 9, et CharacterSelect_OnShow l'affecte à MAX_CHARACTERS_DISPLAYED. Toute condition d'arrêt du parcours qui reposait sur UNE seule observation en exige désormais deux, avec une attente plus longue entre les deux, et ne represse qu'après qu'un coup d'œil a prouvé que rien n'avait bougé - une seconde pression sur une étape déjà enregistrée saute un personnage sans laisser de trace. La détection du bouclage exige maintenant l'emplacement 1 exactement, car CharacterSelectScrollDown_OnClick, passé le dernier personnage, fait CHARACTER_LIST_OFFSET = 0 puis SelectCharacter(1) ; le retour en haut est en outre attendu avant confirmation. Le repli qui ne lit que la portion visible remonte désormais d'abord en haut, car UpdateCharacterSelection fixe le décalage à selectedIndex - MAX_CHARACTERS_DISPLAYED dès que le dernier personnage joué se trouve sous la limite - les neuf lignes visibles étaient alors les personnages 6 à 14, annoncés 1 à 9. Les sondes de pixels de la barre de sélection ont été déplacées vers la gauche : à dix personnages, UpdateCharacterList élargit le panneau de 260 à 280 et affiche une barre de défilement dont le fond noir recouvre précisément la bande que la barre de sélection libère. Le menu est maintenant construit détaché et publié en une seule affectation, comme la liste des royaumes. Royaumes hardcore : Sku termine désormais le chargement de ses points de passage au lieu de s'arrêter en silence Simple : Sur les royaumes hardcore, le client de jeu accorde aux addons nettement moins de temps de calcul par image que sur les royaumes normaux. Sku construit ses données de points de passage et d'itinéraires juste après la connexion, et c'est précisément cette construction qui se heurtait à cette limite : le client interrompait purement et simplement le travail en cours, et Sku ne s'en apercevait pas. Résultat : toutes les listes de points de passage répondaient « points de passage en cours de chargement » pendant toute la session - sans erreur, sans annonce vocale, sans entrée de journal - et recharger l'interface n'y changeait rien. Sku détecte maintenant que le client le freine, réduit la taille de ses lots de travail et continue au lieu de s'arrêter ; si un lot est malgré tout interrompu, la construction repart d'elle-même. Le prix à payer : sur ces royaumes, il faut quelques secondes de plus après la connexion avant que les itinéraires et les points de passage soient complets. Tout le reste de Sku est utilisable immédiatement, et les listes indiquent maintenant l'avancement (entrée suivante). Les royaumes normaux ne changent pas. Technique : Le client signale l'interruption par le LUA_WARNING « insecure scripts exceeded execution limit for addon Sku », ou par « script ran too long » levé à l'intérieur de la coroutine en cours. Sur le royaume hardcore, cela s'est produit à chaque connexion (52/25/25 avertissements par connexion) et pas une seule fois dans les huit sessions enregistrées sur des royaumes Era et Anniversary normaux - pour un travail identique (1936 ms sur Era contre 1955 ms en hardcore pour la même construction). Sku:BuildFrameBudgetMs est désormais adaptatif : chaque avertissement de ce type divise par deux le plafond par image (150 -> 75 -> 37,5 -> 20 ms), ce dont profite toute la séquence de construction d'après-connexion, pas seulement le cache des points de passage. La construction n'est plus rythmée par une chaîne C_Timer qui se réarmait elle-même - c'est justement ce réarmement que l'interruption avalait, laissant la coroutine suspendue pour toujours sans personne pour la reprendre - mais par un pilote OnUpdate que le client appelle lui-même à chaque image : un lot interrompu coûte une image au lieu de toute la construction. Si la coroutine meurt malgré tout, la construction est relancée jusqu'à trois fois, et l'étape de construction remet sa demande en place au lieu de la perdre dans un pcall. Navigation : une liste d'itinéraires pouvait rester vide sans dire pourquoi Simple : un testeur sur un royaume hardcore a signalé que la liste d'itinéraires de Shift-F10 annonçait seulement « liste vide » à un endroit où il y a des itinéraires. Derrière cela se cachaient deux chemins différents par lesquels une liste pouvait devenir vide sans que rien ne l'indique. Premièrement : depuis que Sku redémarre sa construction de points de passage sur les royaumes hardcore lorsque le client l'interrompt (voir ci-dessus), les points de passage peuvent être remplacés pendant qu'un menu est ouvert. Une liste issue de la construction précédente pointait alors vers un point de passage qui n'existait plus. Cela levait une erreur en plein milieu de la construction des sous-entrées, et comme cette erreur était capturée, le niveau restait simplement vide : aucune entrée, aucune indication, aucun message d'erreur, rien à entendre. Deuxièmement : si les données d'itinéraires elles-mêmes manquaient, Sku annonçait malgré tout « chargé » et toutes les listes d'itinéraires étaient vides - là encore sans la moindre indication. Les deux cas sont désormais visibles : la liste répond correctement au lieu de rester muette, et des données d'itinéraires manquantes sont annoncées comme n'importe quelle autre erreur de base de données. Technique : trois endroits, tous de la même forme - un échec ne doit pas rester invisible. 1. SkuNav:GetAllMetaTargetsFromWp5 accédait au point de passage de départ (.worldX) sans vérification. Après une reconstruction du cache de points de passage - nouveauté de la v43.0, puisque la construction se relance d'elle-même sur les royaumes hardcore et que CleanupWaypoints supprime les points de passage sans liaison - ce nom peut avoir disparu. La fonction renvoie désormais une réponse vide et écrit une ligne de journal avec la génération du cache. 2. SkuOptions:VocalizeCurrentMenuName et le parcours de chemin appellent BuildChildren dans un pcall afin qu'un constructeur de sous-menu défectueux n'avale jamais l'annonce du NOM de l'entrée. Cela reste ainsi - mais l'erreur est maintenant journalisée avec le nom du nœud, et le nœud est marqué (buildChildrenFailed). Sans cela, un niveau à zéro entrée était indiscernable d'un niveau réellement vide. 3. Sku:EnsureData ignorait sans un mot une globale de constructeur qui n'est pas une fonction, puis déclarait quand même le jeu de données prêt. C'est la pire forme possible : toutes les protections du reste du code demandent « est-ce prêt », pas « est-ce là ». Pour les itinéraires, cela finit en données de liaison vides, points de passage d'itinéraire supprimés et menus annonçant tranquillement « liste vide ». Chaque constructeur manquant est désormais journalisé individuellement ; s'ils manquent TOUS, le jeu de données est considéré comme en échec et annoncé. S'y ajoute une ligne de journal dans SkuNav:GetAllLinkedWPsInRangeToCoords : lorsque Sku ne parvient pas à rattacher la zone actuelle à un continent (grottes, le Tram des Profondeurs, sous-zones non cartographiées), cette requête abandonne à vide - et la liste disait alors « liste vide », ce qui sonne comme une affirmation sur les données alors que ce n'en est pas une. /skuzoneprobe expose le même cas en entier. Connexion : les données de points de passage des itinéraires sont prêtes bien plus tôt Simple : Après la connexion, Sku reconstruit tous les points de passage et toutes leurs liaisons ; jusque-là, les listes répondent « les points de passage sont encore en cours de chargement ». Plus de la moitié de cette attente partait dans les liaisons, et les mêmes données étaient parcourues quatre fois. C'est un seul parcours maintenant, et il commence par le continent sur lequel vous vous trouvez : celui-ci est terminé en premier, et à partir de cet instant vos listes sont utilisables pendant que le reste du monde se construit encore. Si vous êtes justement sur le message d'attente, la première entrée vous est lue automatiquement, sans appuyer sur une touche. Mesuré sur la même machine : la partie liaisons coûte environ la moitié, et les listes de votre propre continent arrivent environ 0,7 seconde plus tôt. Les itinéraires eux-mêmes ne changent pas : mêmes points, mêmes liaisons, simplement plus tôt. S'y ajoute environ 22 Mo de mémoire en moins pour la gestion des points de passage, car chaque liaison n'est plus stockée en double - sur les machines modestes, c'est la moitié la plus perceptible. Et une autre part de l'attente disparaît complètement : Sku livre deux gros fichiers d'itinéraires, et la version TBC n'a besoin que des liaisons du second. Ses points de passage étaient malgré tout construits à chaque connexion, puis jetés sans avoir été lus un instant plus tard ; ils ne sont désormais tout simplement plus construits. Cela économise environ un quart de seconde et environ 21 Mo de mémoire de pointe par connexion - sur les royaumes Era, où le fichier entier est inutilisé, environ 0,4 seconde. Les deux fichiers continuent d'être livrés en entier : lors du passage ultérieur à Wrath of the Lich King, ce sont précisément ces données qui deviendront les bonnes, et la sélection s'inversera. Technique : La partie liaisons de la construction du cache de points de passage était QUATRE parcours complets sur ~197 000 arêtes orientées : élaguer et symétriser (CheckAndUpdateProfileLinkData), matérialiser byId/byName, recalculer toute la table à partir du cache (SaveLinkDataToProfile) et un parcours final sur les ~145 000 entrées du cache (CleanupWaypoints). C'est un seul parcours désormais : l'élagage se fait au passage, l'arête inverse est écrite directement dans l'entrée cible - d'où l'indifférence à l'ordre des continents - et le recalcul disparaît, puisqu'il réécrivait la table déjà en place. Le seul cas où il pourrait encore changer quelque chose (une extrémité dont le nom appartient à une autre entrée du cache) est compté et déclenche l'ancien chemin ; il n'existe pas dans les données d'itinéraires livrées. Le parcours final reçoit de la passe « custom » la liste des entrées réellement supprimables, triée par continent, et s'exécute par continent dès que celui-ci est terminé. Corrigé au passage : l'ancienne passe d'élagage insérait de nouvelles CLÉS dans la table même qu'elle parcourait avec pairs() - comportement indéfini en Lua ; les arêtes inverses sont maintenant collectées puis appliquées ensuite. De plus, la séparation « son propre continent d'abord » ajoutée en juillet n'était jamais active à la connexion : elle résout la zone via GetMinimapZoneText, encore vide au démarrage de la construction - le continent est désormais résolu à nouveau avant la partie liaisons. Vérifié par une réimplémentation des deux variantes sur les vraies données (dev/rework-docs/simulate_link_build.py : cache identique, table de liaisons identique, pour chaque continent) et en jeu par /skucheck wp, qui vérifie désormais aussi que chaque liaison possède son arête inverse. Technique, deuxième partie : chaque liaison était conservée deux fois - sous l'indice de cache du point cible (byId) et sous son nom (byName). byName a disparu et les 14 points d'appel utilisent byId ; le nouveau WaypointCacheGetIdForIndex est le pendant de WaypointCacheGetIdForName. L'écriture dans la table de liaisons est même devenue moins coûteuse : elle retraduisait chaque nom de cible en identifiant, et elle dispose maintenant directement de l'entrée. Mesuré : environ 70 000 tables et 224 000 chaînes en moins, 22 Mo de moins dans le rapport mémoire (/skucheck mem), et encore ~40 ms de gagnées sur la partie liaisons de la construction. Pourquoi ce doublon : une migration jamais terminée - Sku 32.31 contient déjà, en commentaire, l'appel censé écrire la table byName directement dans la table de liaisons enregistrée ; byId est arrivé plus tard comme chemin rapide pour la recherche d'itinéraire, et les deux sont restés. Trois bogues latents sont partis avec : une boucle de suppression de point qui ne nettoyait que le point supprimé contre lui-même, des identifiants construits sur « .areaid » au lieu de « .areaId » (donc toujours avec la zone 1), et un accès à Links[nil] pour les points temporaires. Technique, troisième partie : chacun des deux fichiers d'itinéraires était UN seul constructeur qui bâtissait tout le fichier. Mesuré en jeu : 375 ms pour le fichier WotLK et 355 ms pour le fichier de base, à chaque connexion. Sur TBC la navigation utilise les points de passage du fichier de base et, du fichier WotLK, uniquement les LIAISONS (l'union des deux graphes, depuis juillet) - LoadDefaultMapData remettait à nil les points de passage, niveaux et numéros de séquence WotLK juste après la fusion. Cela représente 71 % des octets de ce fichier, construits puis jetés sans lecture. Les fichiers sont maintenant emballés par SECTION (dev/rework-docs/_wrap_deferred.py, mode « sections ») : un constructeur par section de premier niveau de routedata.global, chacun contenant une tranche exacte, octet pour octet, des données d'origine - l'outil vérifie que les tranches se recomposent en le fichier source, rien n'est re-sérialisé. SkuDeferredData.lua choisit les constructeurs selon la version du jeu (TBC : toutes les sections du fichier de base plus les liaisons WotLK ; Era : le fichier de base seul) et met à nil les globales des constructeurs ignorés, sans quoi leurs constantes de texte source resteraient en mémoire. Nouveau : /skucheck routes. La vérification est conçue pour qu'une sélection erronée soit bruyante au lieu de coûter silencieusement la moitié du graphe : après EnsureData, plus aucune globale SkuDBBuildRoute* ne doit exister - une survivante signale une section que la sélection ne connaît pas -, les tables que lit la navigation doivent être présentes et non vides, et selon la version du jeu c'est exactement la bonne moitié qui doit manquer. Les listes de points de passage indiquent désormais l'avancement du chargement Simple : Le message « points de passage en cours de chargement » avait été écrit pour une attente d'une ou deux secondes. Quand le client freine Sku (voir l'entrée précédente), il peut rester affiché dix secondes ou plus, et il devient alors impossible à distinguer d'un blocage. Il indique maintenant un pourcentage, par exemple « points de passage en cours de chargement, 47 % ». Le nombre bouge dès le début : les premières étapes sont les parties de la base de données que la construction doit attendre (0, 20, 40 pour cent), puis la construction elle-même compte jusqu'à 99. Chaque appui de touche sur l'entrée annonce la valeur actuelle, et dès que tout est là, le premier vrai point de passage est annoncé automatiquement comme avant. Technique : L'étalon est le temps de travail d'une construction COMPLÈTE, que chaque exécution réussie enregistre dans les réglages globaux de SkuNav (wpcTotalWorkMs). Ce qui est mesuré, c'est le travail, pas le temps réel : le travail est stable, le temps réel varie maintenant avec la réduction du budget. Le pourcentage reste donc honnête sur n'importe quelle machine ; seule la toute première construction après une nouvelle installation utilise la valeur par défaut. Coût par image : une comparaison d'entiers - seul un pourcentage MODIFIÉ reconstruit le texte, et uniquement pour une entrée sur laquelle se trouve le focus. L'entrée porte pour cela un marqueur, car son nom n'est plus un texte fixe : les deux endroits qui la reconnaissaient au nom - le verrou « non sélectionnable » et le rafraîchissement automatique à la fin de la construction - testent maintenant le marqueur. Shift-F9 à Shift-F12 : des raccourcis nommés, et quatre accès rapides retrouvés Simple : quatre actions de Sku - les deux listes rapides de navigation (Shift-F9 et Shift-F10), le menu des barres d'action (Shift-F11) et l'annulation de la navigation (Shift-F12) - étaient câblées directement sur les touches des accès rapides du menu audio 1 à 4. Cela avait deux conséquences. Ces quatre accès rapides étaient morts : leurs touches de définition (Ctrl-Shift-F9 à Ctrl-Shift-F12) enregistraient toujours un chemin de menu et le confirmaient même à voix haute, mais aucune touche ne pouvait plus le rappeler. Et les quatre actions ne pouvaient être déplacées que en réassignant une entrée nommée « accès rapide au menu audio 1 » à « 4 » - ce n'est pas là que l'on cherche « annuler la navigation ». Chaque action possède maintenant sa propre entrée dans le menu des raccourcis, sous son propre nom, toujours sur Shift-F9 à Shift-F12. Les accès rapides 1 à 4 sont de nouveau libres et non assignés, exactement comme les emplacements 5 à 10. Vos réglages existants sont repris à la première connexion : la touche qui se trouve aujourd'hui sur l'accès rapide 1 à 4 continue d'exécuter la même action - elle se déplace simplement vers la nouvelle entrée, rien ne change pour vos doigts. Un emplacement que vous aviez volontairement vidé reste vide. Technique : le OnClick de SkuZOptions/Core.lua interceptait SKU_KEY_MENUQUICK1..4 et retournait avant la boucle générale de sélection rapide située plus bas dans le même gestionnaire. Les trois actions déplacées s'appellent désormais SKU_KEY_NAVWAYPOINTSQUICK, SKU_KEY_NAVROUTEDESTINATIONSQUICK et SKU_KEY_ACTIONBARSOPEN, chacune distribuée par le module qui la possède ; les deux listes de navigation ont rejoint le groupe « Navigation et points de passage ». Une migration unique dans SkuKeyBindsUpdate déplace key et key2 de l'ancien emplacement vers la nouvelle constante ; elle se détecte à l'absence de la nouvelle constante dans le profil, elle s'exécute donc exactement une fois et avant le remplissage des valeurs par défaut. Les chemins de sélection rapide enregistrés sont conservés : assigner une touche à l'emplacement 1 à 4 restaure l'ancien raccourci. Nouveau garde-fou : /skucheck keys signale toute touche détenue par deux assignations Sku à la fois (sauf les touches d'écrasement transitoires, qui la partagent par conception). Annuler la navigation passait par deux touches différentes Simple : il existait deux façons d'arrêter de suivre un itinéraire ou un point de passage, et elles ne se comportaient pas pareil. L'assignation correctement nommée « arrêter de suivre l'itinéraire ou le point de passage » était livrée sans aucune touche, et quand on lui en donnait une, elle annonçait « suivi arrêté » - les mêmes mots que l'arrivée automatique, impossible donc de distinguer les deux - et elle ne disait strictement rien lorsque ce que l'on annulait était un itinéraire et non un point de passage isolé. L'autre était le Shift-F12 câblé en dur, qui disait « Navigation annulée » mais n'existait que comme accès rapide 4. Les deux ne font plus qu'une touche : « arrêter de suivre l'itinéraire ou le point de passage », sur Shift-F12 par défaut et librement réassignable, avec le son de confirmation et le « Navigation annulée » distinct. Elle reste totalement silencieuse quand il n'y avait rien à annuler, et elle fonctionne toujours en mouvement et en combat. Technique : la branche OnClick de SkuNav pour SKU_KEY_STOPROUTEORWAYPOINT appelle maintenant SkuNav:CancelNavigationSilent() et ne parle et ne joue le son 835 que sur un retour true. Cela supprime l'ancien chemin non protégé, qui déclenchait SKU_NAVIGATION_STOPPED et le son même lorsque rien ne tournait, et dont l'annonce venait de EndFollowingWpOrRt et dépendait d'un point de passage sélectionné. EndFollowingWpOrRt lui-même reste inchangé : ses quinze autres appels l'utilisent comme démontage avant de sélectionner une nouvelle cible, où effacer les points de passage temporaires serait faux. Navigation : un itinéraire ne démarrait pas si sa liste finissait de charger pendant que vous y étiez Simple : Si vous ouvriez la liste des itinéraires (Shift-F10) pendant que Sku construisait encore ses données de points de passage, la liste se rafraîchissait au moment où ces données étaient prêtes - et à partir de là, Entrée sur un point d'itinéraire ne lançait plus la navigation. Le menu remontait au contraire au niveau des points d'entrée, comme si la touche n'avait rien fait du tout. Fermer puis rouvrir le menu corrigeait le problème, ce qui donnait un aspect aléatoire à toute l'affaire. La liste rafraîchie se comporte désormais exactement comme une liste fraîchement ouverte : Entrée démarre la navigation. Cela vaut aussi pour la liste des points de passage à proximité (Shift-F9) et pour toute liste de points de passage qui se rafraîchit à l'arrivée des données. Technique : le rafraîchissement à la fin de la construction asynchrone du cache des points de passage reconstruisait le niveau avec un simple children = {} suivi de BuildChildren. Cela saute ce que fait OnPostSelect pour un niveau de sélection : initialiser selectTarget sur le niveau et le copier sur chaque enfant fraîchement construit. Sans cela, tout le sous-niveau hérite de selectTarget = nil, donc l'Entrée de la feuille tombe dans la branche générique d'OnPostSelect - aucun OnAction ne s'exécute (RouteFollowOnAction n'a jamais été appelé) et le curseur est placé sur self.parent, exactement le saut observé. Cette reconstruction se trouve maintenant à un seul endroit, SkuOptions:RebuildNodeChildren, utilisé par OnPostSelect lui-même, par le rafraîchissement des points de passage et par celui des listes volatiles - ce dernier en mode conservation, afin qu'un rafraîchissement en cours ne jette pas une cible déjà sélectionnée. Deux fils-pièges accompagnent la correction : OnPostSelect journalise et compte chaque Entrée qui atterrit sur une feuille sans selectTarget sous un niveau de sélection, et /skucheck menu reconstruit un niveau de sélection jetable pour vérifier le contrat et rapporte ce décompte de session. Auras : les places de groupe étaient annoncées par un bip au lieu d'un mot Simple : Quand une aura annonce l'unité qui a déclenché l'événement, certaines places de groupe ne produisaient qu'un bref bip au lieu d'un mot - le son que Sku joue quand l'enregistrement d'un mot lui manque. La place 3 et toutes les places « cible de » étaient concernées. Ces indications sont désormais découpées en mots que le pack vocal fournit à coup sûr : « party 3 », « cible party 2 ». Toutes les autres unités continuent de dire leur nom traduit. Technique : sourceUnitId et destUnitId passaient le jeton d'unité brut à la sortie audio comme UN seul mot. Les packs livrent party0/1/2/4, mais pas party3 ni le moindre fichier partyNtarget ; le reste retombait donc sur le bip missingAudio (party3 à lui seul en comptait 129). tUnitIdToSpokenName dans SkuAuras/data.lua découpe les jetons party ; les autres jetons retombent sur leur friendlyName traduit issu des listes de valeurs, puis seulement sur le jeton brut. Corrigé au passage : notifyChat affichait la référence de table au lieu de l'unité. Auras : une aura réglée sur « une fois » pouvait se déclencher plusieurs fois de suite en combat dense Simple : Une aura censée signaler UNE seule fois par application - typiquement « Mot de l'ombre : douleur expire dans une seconde » - pouvait produire quatre sons en une seconde lors d'un combat de boss. Cela arrivait quand plusieurs joueurs avaient le même effet sur la cible, ou quand la durée restante n'était pas lisible du tout lors d'un passage : Sku l'interprétait à tort comme « l'effet est de nouveau frais » et levait le verrou « une fois ». Sans lecture, le verrou reste désormais fermé. Un vrai rafraîchissement ou une nouvelle application le lève toujours immédiatement. Technique : La formule de réinitialisation de la condition de comptage réarmait used à chaque passage où les autres conditions tenaient et où la condition de durée « plus petit » lisait false - et un passage SANS lecture (nom absent de la liste, pas d'entrée exp, ou la carte exp répondant avec l'aura de même nom d'un autre lanceur) remplit exactement cela. tSmallerDurationNoRead dans SkuAuras/Core.lua empêche la réinitialisation en l'absence de lecture ; la nouvelle ligne de journal « aura gate re-armed » consigne chaque levée. Observé : quatre déclenchements en 987 ms (DURATION_DEADLINE, puis deux fois UNIT_POWER et SPELL_PERIODIC_DAMAGE). Menu : sur les grandes cartes, la liste de points de passage de Shift-F9 ne s'ouvrait qu'au deuxième essai Simple : Sur les cartes comptant beaucoup de points de passage, Shift-F9 restait sur l'entrée du haut au lieu d'ouvrir la liste ; une pression de flèche supplémentaire l'ouvrait ensuite. La cause : un travail effectué trois fois au lieu d'une. Le saut construisait la même liste - environ 1900 entrées sur certaines cartes - trois fois dans une seule image, et le client du jeu interrompait tout via sa limite de temps de script. Cette interruption est silencieuse, d'où l'impression qu'il ne se passait rien. S'y ajoutait que chaque appui de touche sur une entrée reconstruisait sa sous-liste en doublant les entrées. Les deux sont réglés, la liste est construite une fois. Shift-F10 n'a jamais été concerné, cette liste étant limitée à dix destinations. Technique : SlashFunc construisait les enfants trois fois : avant la comparaison de nom, dans le OnSelect de la boucle, puis encore dans la phase finale. La comparaison se décide maintenant AVANT la construction, et la phase finale saute sa resélection quand la boucle a déjà sélectionné ce même nœud (nœuds actionOnEnter exclus : là, les deux appels diffèrent réellement). VocalizeCurrentMenuName ne construit plus que s'il n'y a rien à compter - il n'a besoin des enfants que pour le marqueur « ;plus », et la plupart des constructeurs AJOUTENT via InjectMenuItems au lieu de remplacer (mesuré 1859 -> 3718 -> 5577 entrées en parcourant). Suite du test : les niveaux de fenêtre sous « Local » (Dialogue, Marchand, Quête, Maître de vol) construisent leurs enfants paresseusement et ne portent pas d'indicateur dynamic ; sans pré-construction ils ressemblaient à des feuilles sans enfants, et la branche feuille appelle CloseMenu - or fermer clique le bouton de fermeture de chaque fenêtre d'interactFramesList, si bien que le saut vers un maître de vol refermait aussitôt sa fenêtre. La construction a donc lieu quand la liste est vide, sauf si le nœud est dynamic. Deux fils déclencheurs dans /skucheck menu comptent les deux cas. Vol en taxi : les annonces peuvent être désactivées - et elles ne nomment plus le point de vol de départ ni celui d'arrivée Simple : Pendant un vol en taxi, Sku annonce « Prochain point de vol X » puis, environ 800 mètres avant, « Atterrissage anticipé possible à X », afin de pouvoir descendre plus tôt avec le raccourci. Pour qui vole beaucoup, c'est du bavardage : il existe désormais un réglage, Paramètres > Général > « Annoncer les points de vol où atterrir ». Il est activé par défaut et ne coupe QUE la parole - le vol reste suivi, et la touche d'atterrissage anticipé fonctionne exactement comme avant, avec sa confirmation vocale, car celle-ci répond à une touche que l'on vient d'appuyer. Ensuite, plusieurs retours signalaient que Sku nommait le point de vol où le vol avait COMMENCÉ, ou celui vers lequel il se dirigeait, comme un endroit où atterrir par anticipation. Ni l'un ni l'autre n'est un atterrissage anticipé : le premier est l'endroit d'où l'on vient, le second n'est que l'arrivée. Les deux sont désormais impossibles. Troisièmement, dès que l'atterrissage anticipé a réellement été demandé, Sku cesse de commenter l'itinéraire. Il nommait auparavant le point APRÈS celui où l'on était déposé - dix secondes avant de se poser à Ratchet, il annonçait encore « Prochain point de vol Astranaar », comme si le vol continuait. Technique : L'annonce avait un angle mort, le repli sur le point de vol le plus proche. L'itinéraire est capturé au décollage depuis la carte des vols (le seul moment où l'interface des itinéraires répond), et toutes les annonces dépendent de lui. S'il manque, le code se rabat sur « le point de vol utilisable le plus proche » - qui ne sait rien du vol : peu après le décollage, c'est précisément le point de départ pendant un moment, et peu avant l'atterrissage, le point d'arrivée. La seule manière courante de perdre l'itinéraire est un /reload en plein vol : le crochet de capture s'est déclenché avant le rechargement et ne peut pas se déclencher une seconde fois. C'est exactement pour cela que le problème était reproductible chez les personnes concernées et invisible ici. Trois changements : (1) l'itinéraire capturé survit à un /reload - il est écrit dans les variables sauvegardées du personnage avec l'étape en cours, puis relu en vol, si bien qu'un rechargement ne fait plus du tout basculer dans le repli ; (2) le point de départ du vol est désormais lu lui aussi dans la capture de l'itinéraire (l'emplacement source de la première étape), donc départ et destination sont connus par leur nom et aucun des deux ne peut plus être annoncé, dans quelque mode que ce soit ; (3) en mode repli, un point de vol doit se RAPPROCHER avant d'être proposé comme possibilité d'atterrissage - un point dont on s'éloigne est derrière soi ; (4) une demande acceptée met fin à l'itinéraire pour le curseur aussi : le point demandé est la fin du vol, le franchir c'est arriver, pas faire une étape. La porte de sortie est géométrique et non minutée - encore en vol mille mètres au-delà signifie que le serveur a ignoré la demande, et l'itinéraire reprend aussitôt. Le nouveau contrôle « /skucheck taxi » fixe la règle : un atterrissage anticipé n'est jamais le point de départ ni le point d'arrivée du vol lui-même. Le réglage est enregistré par profil sous taxiAnnounceLandingPoints. Moniteur de ressource : « rien » devient « Ressource actuelle », et elle suit la barre que vous voyez Simple : Le moniteur de vie & de ressource surveille exactement une ressource, et c'était un choix figé : mana, rage, énergie - ou « rien ». Mais « rien » n'était pas un interrupteur, c'était un trou : il provoquait une série d'erreurs Lua, et ce que le moniteur surveillait à la place n'avait été choisi par personne. L'entrée s'appelle désormais « Ressource actuelle » et figure en tête de liste. Elle suit la barre que le jeu vous affiche : un druide obtient le mana en forme normale, la rage en ours et l'énergie en félin, sans jamais retourner dans le menu. Toutes les autres classes n'ont qu'une seule barre - pour elles, c'est simplement leur ressource. Choisir une ressource fixe fonctionne exactement comme avant. Pour désactiver le moniteur, utilisez « Activé : Non » - c'est à cela que sert le réglage. Si vous aviez « rien » de sélectionné, vous trouverez le moniteur désactivé et la ressource réglée sur « Ressource actuelle » ; réactivez-le si vous voulez l'entendre. Technique : « NOTHING » transmettait l'indice de ressource -1 (Enum.PowerType.None) directement à UnitPower et UnitPowerMax - une valeur que le moniteur n'a aucune raison de demander. À sa place vient GetMonitoredPowerType(), qui résout la valeur enregistrée au moment de la lecture : un choix fixe vers son indice, « Ressource actuelle » via UnitPowerType("player") vers la barre affichée. Les deux chemins de lecture passent par là, y compris la comparaison de jeton dans UNIT_POWER_UPDATE - avec le type automatique, le jeton auquel réagir n'est pas celui qui est enregistré. Les deux chemins vérifient aussi désormais nil et un maximum de 0 au lieu de diviser par lui ; c'était la véritable source des erreurs, et comme l'un des deux se trouve dans le pilote OnUpdate, il ne produisait pas une erreur mais une par tick. Comme la ressource surveillée peut maintenant changer sous le moniteur, le pourcentage précédent est réinitialisé à chaque changement de jeton - sinon la première annonce après un changement de forme serait avalée ou recevrait la mauvaise direction (hausse/baisse). En outre, le gestionnaire s'arrête si le module est désactivé : il dépend de deux enregistrements (son propre objet AceEvent et le répartiteur dans Core.lua) alors qu'OnDisable ne retirait que le premier, si bien qu'un moniteur désactivé dans le menu des fonctions continuait d'annoncer. Le nouveau contrôle « /skucheck power » fixe la règle : la ressource configurée doit toujours se résoudre vers quelque chose que le client peut lire ; une ressource choisie volontairement mais absente de la classe compte comme « en attente », pas comme une infraction. La valeur enregistrée « NOTHING » est migrée une seule fois vers « ACTIVE » à la connexion, moniteur désactivé. Les auras reconnaissent les sorts à leur identité, plus à leur nom traduit Simple : Une aura que vous aviez créée était jusqu'ici liée à la langue de votre jeu. La valeur enregistrée était le nom traduit du sort - « Frostblitz » - et un client anglais ne connaît que « Frostbolt » : la même aura ne s'y déclenchait donc jamais. Pour la même raison, un ensemble d'auras partagé en groupe arrivait mort chez un équipier d'une autre langue, et les ensembles d'auras fournis n'existaient que sur les clients allemands. Sku retient désormais l'identité du sort, indépendante de la langue. Ce que vous voyez et entendez reste exactement le nom qu'emploie votre jeu - dans le menu, dans le nom de l'aura et dans l'annonce quand elle se déclenche. Vos auras existantes sont converties une seule fois à la première connexion, sans rien faire de votre côté ; leurs noms ne changent pas. Un second gain vient avec : tous les rangs d'un sort, ainsi que les variantes lancées par les ennemis, appartiennent à une seule entrée. « Préviens-moi quand ma cible lance Éclair de givre » couvre donc les 103 variantes au lieu de la seule qui se trouvait dans la liste. Savoir si c'est VOUS ou un ennemi qui l'a lancé reste décidé par la condition « Source de l'événement », pas par le sort. Là où un nom traduit recouvre plusieurs noms anglais différents - l'allemand « Verblassen » est à la fois « Fade » et « Fade Out » - la liste ajoute le nom anglais entre parenthèses pour que les entrées restent distinguables. La liste seulement ; l'annonce au déclenchement reste courte. Si vous choisissez une telle entrée, c'est la variante la plus ancienne qui l'emporte : pour « Verblassen », le sort de prêtre et non « Fade Out ». Les auras déjà enregistrées sous un tel nom restent intactes et continuent de réagir à toutes les variantes, comme avant. Technique : L'identité est le nom anglais du sort tiré de SkuDB.SpellDataTBC, déjà livré et déjà maintenu - aucun nouveau fichier, aucune table d'ID générée (il faudrait la garder synchronisée avec spells.lua, et une régénération orphelinerait silencieusement les auras enregistrées). Les valeurs enregistrées portent la marque « spellgroup: » ; l'affichage vient toujours de SkuAuras.values[key].friendlyName. La liste vivante issue de UnitAura est ramenée au nom de groupe via la valeur de retour 10 (spellId) ; sur un client non anglais, le nom localisé reste à côté comme alias de compatibilité pour qu'une ancienne valeur non convertie continue de correspondre. Sur un client anglais, nom de groupe et nom vivant sont la même chaîne, l'alias n'est jamais écrit et le coût est exactement nul. Un ID que SpellDataTBC ne connaît pas - la population la plus susceptible de dériver au fil du cycle Anniversary - retombe sur le nom localisé, c'est-à-dire sur le comportement précédent. 838 des 26 603 noms allemands (3,15 %) recouvrent plus d'un nom anglais. À l'exécution, c'est le groupe dont l'ID de sort est la plus basse qui l'emporte : les variantes en conflit sont sans exception des ajouts ultérieurs ; vérifié sur les cas dont la réponse est connue (Verblassen 586 contre 5543, Geschwächte Seele 6788 contre 36788, Schattenwort: Schmerz 589 contre 37603, Erneuerung 139 contre 37563, Gedankenkontrolle 605 contre 7645). Les valeurs DÉJÀ ENREGISTRÉES sous un tel nom ne sont malgré tout PAS converties, car elles correspondent aujourd'hui à toutes les variantes et une conversion supprimerait silencieusement les autres ; leurs entrées de menu reçoivent le nom anglais en suffixe. Le nom affiché d'un groupe provient toujours de l'ID la plus basse qu'il contient, afin qu'il ne change pas d'une session à l'autre. Nouvelles vérifications sous « /skucheck auras » : il existe bien des entrées de groupe, tous les ID d'un sort se résolvent vers UN seul groupe, et la conversion unique n'a laissé aucune valeur derrière elle. Partage d'ensembles d'auras : nouveau format sans indicateur de langue Simple : Un ensemble d'auras partagé ne porte plus d'indicateur de langue, et l'avertissement « l'ensemble est pour une autre langue » disparaît - les conditions sont désormais indépendantes de la langue. À l'acceptation, chaque aura reçoit en plus son nom dans VOTRE langue au lieu de garder la phrase de l'expéditeur. Les auras que vous avez nommées vous-même gardent leur nom. Technique : C'est SkuAuraSetV2 (charge « AURASET2 », sans champ loc) qui est envoyé ; V1 reste reçu, avertissement compris, car de tels paquets portent encore des valeurs localisées. Seul V2 est envoyé, volontairement : un paquet V1 supplémentaire déposerait chez les anciens clients des auras avec des valeurs de groupe qu'ils ne savent pas résoudre. Le nom est redérivé via BuildAuraName à l'import (SkuAuras:RelocalizedAuraName) ; les auras customName en sont exclues, et ce sont justement celles auxquelles d'autres auras peuvent renvoyer : les renvois restent donc intacts. Créer une aura : le menu dans lequel on construit une aura a été refait En clair : Une aura est toujours faite de conditions, et tout ce que vous avez déjà construit continue de s'évaluer sans changement - mais le chemin vers une condition est plus court à beaucoup d'endroits, et les listes que l'on parcourt en route sont triées, courtes et rapides. Dans l'ordre où on les rencontre : La liste des attributs est triée par nom de bout en bout - y compris les entrées de groupe, qui se plaçaient auparavant toujours en tête quel que soit leur nom. Une entrée se trouve donc là où son nom la place, et les premières lettres suffisent pour l'atteindre. Les 35 événements tiennent derrière deux entrées, « Événements généraux » et « Événements de sort » ; santé, ressource, mana, rage, énergie, puissance runique et points de combo sont ensemble dans « Votre santé ou ressource » ; et toutes les auras que vous avez nommées vous-même sont des conditions sous « Auras créées par vous » au lieu d'être éparpillées parmi les vrais attributs - la liste du haut grandissait d'une entrée à chaque aura nommée, et ces entrées se rangeaient dans toutes les lettres de l'alphabet. Chaque entrée de groupe annonce elle-même ce qui est sélectionné à l'intérieur : inutile d'y entrer pour le savoir. Une condition à deux valeurs seulement - « vrai » et « faux », ou « buff » et « debuff » - est désormais une bascule au lieu de trois niveaux. La valeur figure sur l'entrée et Entrée la change : « En combat ; vrai », Entrée, « En combat ; faux ». Pendant la CRÉATION, Entrée va un cran plus loin sur « non défini » puis revient, pour qu'une bascule posée par erreur puisse être retirée. À comprendre : « non défini » n'est pas une troisième valeur, cela veut dire que la condition n'existe pas du tout - c'est l'état neutre, pas « faux ». « faux » est une comparaison comme une autre : « en combat égal faux » ne se déclenche QU'EN dehors du combat. Les sorts viennent toujours d'une liste, mais on peut aussi les saisir : « Saisir le sort » est la première entrée de chaque liste de valeurs de sorts - Entrée, nom ou numéro, Entrée, et Sku annonce le sort qu'il en a fait. En échange, les deux conditions « ID du sort » et « ID de l'objet » ont quitté la liste des attributs : elles désignaient un SEUL rang de sort et signifiaient donc, juste à côté de « nom du sort », l'inverse de ce que la même valeur saisie y produit. Nouvelle venue : « Buff debuff vos sorts uniquement » - réglée sur vrai, les listes de buffs et de debuffs de cette aura ne voient plus que les effets que VOUS avez lancés, si bien que « mon Mot de l'ombre : douleur expire » ne signale plus celui d'un autre prêtre sur la même cible. Et comme « Source (L) » et « cible (L) » étaient régulièrement pris pour votre propre cible, elles s'appellent maintenant « Source de l'événement (L) » et « Cible de l'événement (L) » : elles filtrent l'ÉVÉNEMENT déclencheur. Enfin le comportement des listes : toutes les listes de valeurs s'ouvrent nettement plus vite, « Sort utilisable (L) » ne fige plus le jeu à l'ouverture, changer le sort d'une condition de durée se fait sans à-coup et sans continuer d'annoncer l'ancien sort comme sélectionné, et « Supprimer » demande confirmation avant qu'une aura disparaisse - c'était la seule entrée de cette liste à ne pas le faire, juste à côté de « Dupliquer », qui a toujours demandé. Techniquement : Un attribut devient une bascule quand son type est BINARY, qu'il a exactement deux valeurs et que son type n'autorise qu'un seul opérateur (tBinaryValuesForAttribute dans SkuAuras/Options.lua) ; ce qui est écrit est exactement ce qu'écrivait l'ancienne liste de valeurs, une aura construite par la bascule est donc indiscernable d'une aura construite à l'ancienne, et une liste values vide sort du brouillon - c'est cela, « non défini ». La ligne de condition associe actionInPlace et actionOnEnter, ce qui distingue Entrée de la flèche droite sur le même nœud. La liste des attributs collecte groupes et attributs avec leur nom affiché et insère les deux dans l'ordre des noms. Les deux familles d'événements sont UN attribut et UNE condition stockée : chaque liste de famille propose l'autre comme une seule entrée, et les deux pointent vers la condition que le brouillon détient déjà - sans quoi passer par les deux laisserait deux conditions sur `event`, que l'enregistrement fusionne en un groupe OU toujours vrai. listsOwnOnly est un modificateur binaire : getAuraList récupère le lanceur (7e valeur de retour d'UnitAura) dans le même balayage et remplit des ensembles own parallèles qu'EvaluateAllAuras échange par aura marquée. Les listes de valeurs trient avec une clé de tri par entrée au lieu de deux par comparaison (27 057 au lieu d'environ 800 000 nouvelles chaînes par liste de sorts), « Sort utilisable (L) » interroge les 132 emplacements d'action au lieu des 49 000 sorts de la base, et la condition de durée laisse chaque entrée annoncer son propre état à la lecture (tValueToggleRefreshLiveName, UNE fonction partagée par référence) au lieu de réinitialiser 27 000 entrées à chaque Entrée. « ID du sort » et « ID de l'objet » sont toujours évaluées et seulement masquées du constructeur par un drapeau retired, pour qu'une aura importée ou ancienne ne tombe pas sur un nil ; avec elles disparaissent leurs listes de valeurs, environ 49 000 ID de sorts et 25 000 ID d'objets reconstruites à chaque chargement. Les CLÉS de localisation des conditions renommées sont inchangées, aucune aura stockée ne bouge donc. La suppression a également imposé de retirer la branche aName du menu de gestion : selectTarget n'est repointé qu'une fois le niveau entré, jusque-là la confirmation aurait été cosmétique. Supprimer rafraîchit désormais aussi les pseudo-attributs d'auras - une aura supprimée restait auparavant proposée comme condition. Clients français : la liste des enchantements d'arme pour les auras était vide Simple : Sur un client français, la condition d'aura portant sur les enchantements d'arme ne proposait pas une seule entrée, si bien qu'une telle condition ne pouvait pas être créée. Technique : SkuAuras:ResolveWeaponEnchantName lisait la colonne 1 de la base d'enchantements pour enUS et la colonne 2 pour deDE, et laissait le nom à nil sinon - pour toute autre langue la fonction renvoyait donc nil pour CHAQUE enchantement et la liste de valeurs sortait vide. Elle prend maintenant la colonne de votre langue, sinon l'anglaise, comme le reste du système de noms depuis la v42.09. Trouvé lors du portage vers l'identité de groupe, pas via un rapport. Menu : chaque entrée coûte désormais environ 13 fois moins à créer Simple : Les longues listes se construisent nettement plus vite. Cela se voit surtout sur les listes de sorts d'une condition d'aura : elles comptent 27 057 entrées, et sur un royaume hardcore elles ne s'ouvraient plus du tout - ce serveur interrompt les scripts d'addon trop longs, et la construction n'allait qu'à peu près à la moitié. Cela ne concerne pas que les auras : tous les menus de Sku sont faits des mêmes entrées. Technique : Une entrée de menu était une copie profonde du modèle SkuGenericMenuItem - par entrée une table auxiliaire, un parcours pairs() des 28 champs avec un appel type() et trois comparaisons de clé chacun, 28 écritures, et une copie récursive de la table children. Une entrée est maintenant une table presque vide dont la métatable pointe vers le modèle via __index : deux tables et une écriture. Mesuré sur 27 057 entrées : la création des nœuds passe de 78 à 6 ms (13x), et la construction complète de la liste de 100 à 31 ms (3,2x, et 2,7x plus rapide qu'avant la refonte des auras). Écrire un champ sur une entrée masque toujours le modèle comme avant ; children reste une table propre à chaque entrée, sinon tous les éléments de menu de l'addon partageraient UNE seule liste d'enfants. Le seul endroit qui copie une entrée (l'entrée de filtre dans SkuOptions:ApplyFilter) rattache la métatable. /skucheck menu vérifie les deux sur des entrées jetables. Mesuré pour comparaison : la refonte des auras elle-même n'a presque pas ralenti ces listes. Le passage aux groupes a fait croître la liste de sorts de 460 entrées sur 26 597 (+1,7 %), et les bascules au lieu d'entrées simples coûtent environ 19 % de temps de construction en plus. La construction était coûteuse bien avant ; sur un royaume hardcore, cependant, une limite de script est une falaise et non une pente, et ces 19 % ont suffi à la franchir. Menu : une liste interrompue restait incomplète sans le dire Simple : Sur un royaume hardcore, le serveur interrompt un script d'addon trop long. Quand cela touchait la construction d'une longue liste, celle-ci s'ouvrait bien à la DEUXIÈME flèche droite - mais ce n'était pas une nouvelle tentative, c'était le reste à moitié construit de la première. Sur les listes de sorts, cela représentait environ 6 900 sorts sur 27 000, sans le moindre avertissement : qui ne trouvait pas son sort devait en conclure qu'il n'existait pas. La construction reprend désormais à la frame suivante là où elle s'est arrêtée, et la liste se complète en quelques frames sans que vous ayez à appuyer sur quoi que ce soit. Technique : Le budget de script vaut par exécution, la frame suivante en a donc un neuf - c'est précisément pour cela que la construction des données d'itinéraires est découpée. SkuOptions:ContinueInterruptedBuild rappelle BuildChildren sur un minuteur à délai nul tant que le niveau est incomplet. Seul un constructeur qui se déclare via `resumableBuild` et tient son propre curseur est réinvoqué : un BuildChildren ordinaire AJOUTE, un second appel créerait donc chaque entrée en double - c'est exactement la raison d'être de l'ancien verrou. Le curseur est posé APRÈS une entrée, jamais avant, sinon une entrée empêchée par l'interruption compterait comme construite. Une passe sans progrès abandonne au lieu de se répéter indéfiniment. /skucheck menu vérifie sur un niveau jetable qu'une construction interrompue se poursuit au lieu de recommencer. Les réglages à deux valeurs sont désormais des bascules, plus des sous-menus Simple : Un réglage qui ne connaît que deux états - « Activé » et « Désactivé », « Oui » et « Non » - annonce maintenant sa valeur en même temps que son nom : vous entendez par exemple « Menu Sku en combat ; activé ». Entrée bascule, le focus ne bouge pas, et vous entendez aussitôt l'entrée dans son nouvel état. La flèche droite n'est plus nécessaire, car le sous-menu contenant les deux valeurs a disparu. Il fallait auparavant trois touches - droite, flèche, Entrée - et il fallait y entrer rien que pour savoir où en était le réglage. Cela concerne tous les réglages, le moniteur de santé et de combat, les filtres du chat et du journal de combat, le menu des fonctions, le menu de la caméra et les réglages des autres extensions : environ 150 entrées. Les réglages à PLUS de deux valeurs gardent leur liste, puisqu'il y a réellement quelque chose à y choisir. Technique : Nouvel élément de menu partagé, SkuOptions:MakeToggleNode dans SkuZOptions/templates.lua ; SkuOptions:MakeInPlaceToggle convertit un emplacement isSelect existant en UNE ligne et continue d'utiliser ses propres GetCurrentValue et OnAction, afin que chaque particularité d'un réglage reste telle quelle. Peu importe laquelle des deux valeurs est passée comme « activé », et c'est précisément ce qui a rendu mécanique la conversion des quelque 50 emplacements Oui/Non existants. Une bascule est une feuille (actionInPlace, pas de dynamic, pas d'enfants) - d'où l'inaction de la flèche droite - et un RefreshLiveName relit l'état juste avant chaque annonce ; l'ancien sous-menu l'obtenait gratuitement en appelant GetCurrentValue à chaque descente. Trois pièges : le parcours de chemin de SkuOptions:SlashFunc et la recherche dans les réglages sélectionnent avec aEnterFlag = true et auraient BASCULÉ une bascule nommée dans un chemin au lieu d'y amener le curseur - les deux se contentent désormais de placer le focus, et la recherche compare avec toggleLabel puisque le nom porte l'état. RefreshLiveName ne s'applique pas à l'entrée de filtre d'une liste : SkuOptions:ApplyFilter la construit en copiant le premier enfant, et la copie écraserait sinon le texte que vous saisissez. Enfin, une bascule dont le lecteur lève une erreur est comptée par /skucheck menu - elle sonnerait sinon comme un réglage sans valeur plutôt que comme un défaut. Vérification de préparation : la fenêtre n'arrivait pas sur la réponse comme toutes les autres En clair : Quand quelqu'un du groupe lance une vérification de préparation, Sku ouvre le menu et y saute, exactement comme pour n'importe quelle fenêtre surgissante. Pour celle-ci, le curseur n'arrivait pas sur « Prêt » mais un niveau au-dessus, ce qui laissait deux appuis de touche de plus avant la réponse. Vous arrivez maintenant directement sur « Prêt », « Pas prêt » est juste en dessous, et la flèche gauche ramène sur « vérification de préparation ». Qui a lancé la vérification figure sur les deux entrées en texte complet. Techniquement : ReadyCheckFrame n'est qu'une enveloppe autour de ReadyCheckListenerFrame, qui porte le message et les deux boutons ; parcourue génériquement, cette enveloppe devenait un niveau de menu à part entière et la descente automatique de SkuCore:CheckFrames y atterrissait. Il existe désormais un constructeur dédié (SkuCore:Build_ReadyCheckFrame dans SkuCore/LocalMenu.lua, inscrit dans interactFramesListManual) qui construit les deux réponses à plat en entrées directAction - le même schéma que Build_RolePollPopup pour le sondage de rôle. Les libellés viennent des boutons eux-mêmes, que Blizzard traduit dans ReadyCheckFrame_OnLoad : aucune nouvelle entrée de localisation n'est nécessaire. Seuls les boutons visibles sont listés : si vous avez lancé la vérification vous-même, Blizzard masque le côté réponse et il n'y a rien à répondre. Volumes du jeu : une valeur réglée revenait forte sur un autre personnage Simple : Si vous baissiez le volume général dans le menu Sku, vous le retrouviez à l'ancienne valeur sur un autre personnage. Sku conservait en effet sa propre copie des cinq volumes et des cinq interrupteurs de son, en plus de celle du jeu, et réécrivait cette copie dans le jeu à chaque connexion. Cette copie dépend du profil Sku, alors que le réglage du jeu dépend du compte : deux personnages dans des profils différents entendaient donc deux volumes différents, et une modification faite dans les options de son de Blizzard disparaissait sans bruit à la connexion suivante. Sku ne fait plus que régler les valeurs ; c'est au jeu de les retenir, comme pour tout autre joueur. Les curseurs du menu affichent désormais la valeur réelle du jeu, y compris si vous l'avez changée ailleurs. Ce que l'on perd : les volumes ne suivent plus un profil Sku. Technique : SkuOptions:OnEnable réécrivait soundChannels et soundSettings du profil vers les CVars à chaque connexion, précédé d'une règle spéciale qui prenait une valeur stockée de 0 ou -1 pour un profil corrompu et réadoptait alors les cinq canaux depuis les CVars. Tout ce bloc a disparu. Les nœuds de menu dans SkuZOptions/Options.lua lisent et écrivent les CVars directement (tGetSoundCVarPercent, tSetSoundCVarPercent, tGetSoundCVarBool) ; les dix clés sont retirées de SkuOptions.defaults et du schéma, et un passage unique dans OnInitialize les nettoie de chaque profil enregistré - pas seulement de l'actif, car sans entrée dans les défauts AceDB ne les retirerait plus jamais. soundChannels reste : SkuChannel, le canal sur lequel Sku diffuse sa propre sortie, est un vrai réglage Sku. La lecture arrondit désormais au lieu de tronquer - tonumber("0.35") * 100 vaut 34,999... et serait revenu à 34. Les nouveaux profils n'apportent plus aucune valeur de volume par défaut ; ce sont les défauts du jeu qui s'appliquent. Les quatre profils standard sont créés sans changer de profil entre-temps Simple : À la première connexion, Sku crée les quatre profils standard s'ils manquent. Il le faisait jusqu'ici en basculant tour à tour dans chaque profil manquant puis en revenant dans le vôtre. Sur un royaume hardcore, le serveur interrompt les scripts d'addon trop longs, et si cela tombait sur cette séquence le personnage restait dans le profil d'un autre - avec tous les réglages de ce profil au lieu des vôtres. Or un profil existe dès qu'il porte un nom ; il n'y a rien à basculer pour cela. Dix étapes lourdes sont devenues quatre étapes courtes qui ne peuvent pas échouer à mi-chemin. Technique : Les quatre appels à SetProfile plus le retour dans SkuCore/Core.lua sont remplacés par quatre affectations dans SkuOptions.db.sv.profiles. Le GetProfiles d'AceDB énumère exactement cette table, et un profil n'est de toute façon rempli par copyDefaults qu'au premier basculement - l'état enregistré final est le même qu'auparavant, quatre tables vides. Chaque appel à SetProfile déclenchait au contraire OnProfileShutdown et parcourait avec removeDefaults et copyDefaults l'arbre complet des défauts des huit modules, soit une dizaine de parcours dans une seule frame. Supprimer les basculements supprime aussi le drapeau SkuCore.AutoChange, qui rendait OnProfileChanged muet pendant ce temps et restait à true en cas d'interruption - après quoi plus aucun changement de profil ne passait pour le reste de la session. Le répartiteur enveloppe ce gestionnaire dans un pcall, l'interruption était donc invisible. ------------------------------------------------------------------------------------------------------- Changements dans Sku v42.13 Combat : « Annoncer les interruptions sur votre cible » est désormais désactivé par défaut Simple : Le second des deux interrupteurs d'interruption démarre lui aussi désactivé. Les deux moitiés de la séparation - votre cible actuelle et tous les ennemis - sont dorénavant facultatives, car une annonce d'interruption s'impose au milieu de tout ce que Sku est en train de dire, et la plupart des joueurs ne veulent pas la voir tourner d'elle-même. L'annonce elle-même ne change pas, seulement le fait qu'elle soit active si vous n'avez jamais touché au réglage. Pour la réactiver : Combat, Ennemis, « Annoncer les interruptions sur votre cible ». Comme pour l'interrupteur « tous les ennemis », cela s'applique une fois aux profils existants, afin qu'une valeur héritée de l'ancien interrupteur unique ne le laisse pas activé en silence. Technique : Dans aqCombat.lua, le bloc de migration ne préremplit plus yourTargetInterrupts depuis l'ancien indicateur outputInterrupts ; la valeur par défaut est false comme pour allEnemiesInterrupts, et un marqueur unique yourTargetInterruptsDefaultOff nettoie les profils que des builds 42.13 antérieurs avaient déjà remplis à partir de l'ancienne valeur. Les valeurs par défaut d'AceDB ne remplissent que les nil, d'où le marqueur plutôt qu'un simple changement de défaut. tOldOutputInterrupts disparaît avec son dernier lecteur. Hôtel des ventes : l'analyse complète s'annonçait comme « enchères démarrées » Simple : Le démarrage d'une analyse complète de l'hôtel des ventes disait « Analyse des enchères démarrée, veuillez patienter », ce qui ne dit pas laquelle des deux sortes d'analyse tourne - une recherche ressemblait bien trop à une analyse complète, qui occupe ensuite l'hôtel des ventes pendant des minutes. Il est désormais annoncé « Analyse complète de l'hôtel des ventes démarrée, veuillez patienter », en accord avec le message de fin, qui dit depuis toujours « Analyse complète de l'hôtel des ventes terminée ». Technique : La chaîne de localisation « Full scan started » a été reformulée en deDE, enUS et frFR. Aucun chemin de code n'a changé ; le point d'annonce dans auctionHouse.lua reste intact. Focalisation Sku : l'appel d'une focalisation arrivait en retard et hachait Simple : appuyer sur une touche de focalisation pour prendre la cible mémorisée annonçait le nom avec un retard perceptible - souvent commencé, coupé, puis repris. Un appui produit deux événements, un à l'enfoncement et un au relâchement, et Sku exécutait la macro de ciblage sur les deux. Le second passage reprenait la même cible, et comme chaque annonce de cible vide d'abord la file de parole, la ligne à peine commencée était arrêtée puis relancée - en retard exactement du temps pendant lequel la touche était maintenue. La macro ne s'exécute plus qu'une fois par appui. Cela ressort maintenant parce que les touches de focalisation sont sur le pavé numérique par défaut depuis la v42.13, et ne sont donc utilisées que depuis peu. Technique : les huit boutons de focalisation dans skuFocus.lua s'enregistraient avec RegisterForClicks("AnyUp", "AnyDown") et déclenchaient donc la macro sur les deux fronts. Le cadre de contrôle des touches SET avait le même défaut et était déjà passé à un seul front ; les boutons GET en avaient alors été explicitement exclus. Ils s'enregistrent désormais eux aussi en "AnyDown". Il reste dans le journal deux PLAYER_TARGET_CHANGED et exactement une annonce par appui ; ces deux événements viennent de /target lui-même, qui efface l'ancienne cible avant de poser la nouvelle - visible au verrou de ciblage doux, qui écrit deux valeurs différentes (0 puis 2) et non deux fois la même. Changement de cible : une vérification de portée en double et des boucles de groupe à vide Simple : à chaque changement de cible, Sku parcourait tout le groupe et tout le raid pour savoir si la cible combat déjà quelqu'un de votre groupe - y compris en solo, où ni l'un ni l'autre n'existe. Cela faisait 88 requêtes dans le vide, et avec l'option « Annoncer les unités contrôlées par un joueur avec des descriptions génériques », plus de deux mille. De plus, la vérification de portée tournait deux fois par changement de cible : le second appel se trouvait dans une portion de code qui ne pouvait jamais recevoir son résultat et n'annonçait donc jamais rien. Les deux ont été retirés. Rien de ce que Sku dit ne change, et la détection de l'état de combat reste complète : en groupe ou en raid, tous les membres sont toujours vérifiés, plus votre familier et vous-même, et la question « qui ma cible attaque-t-elle » est posée comme avant. Technique : dans SkuMob PLAYER_TARGET_CHANGED, les boucles party1-4 et raid1-40 sont désormais conditionnées par IsInGroup() / IsInRaid(), et le raid ne va que jusqu'à GetNumGroupMembers() - la même écriture que dans SkuAuras et SkuQuest. L'ordre est conservé (groupe avant raid) pour qu'un membre de votre propre sous-groupe réponde toujours « party N ». Dans SkuNav, la branche aForTarget de GetNonAutoLevel a disparu : elle commençait par DoRangeCheck, qui ne renvoie aucune valeur, si bien que sa première condition n'était jamais remplie - il ne restait que l'effet de bord, une deuxième vérification de portée forcée et un éventuel second son de distance par changement de cible. Son appelant dans SkuMob et la fonction utilitaire GetCreatureIdFromCreatureGUID, utilisée nulle part ailleurs, sont partis avec elle. Cible en combat : l'indicateur restait allumé en permanence pour une partie des utilisateurs Simple : cela ne concernait que ceux qui avaient activé « Annoncer les unités contrôlées par un joueur avec des descriptions génériques » (désactivé par défaut). Chez eux, toute cible était signalée « en combat » - bip ou annonce -, y compris un animal totalement étranger au combat. Pour une unité que Sku ne peut pas classer, ce qui inclut une unité qui n'existe pas, la fonction de nom renvoie une chaîne vide. Cette chaîne vide se retrouvait comme entrée dans la liste des noms de votre propre groupe. Le test suivant - « ma cible attaque-t-elle quelqu'un de mon groupe ? » - recevait lui aussi une chaîne vide pour « ma cible n'attaque personne », et celle-ci correspondait à cette entrée. La réponse était donc toujours oui. Les noms vides ne sont désormais ni enregistrés ni comparés. Pour les personnes concernées, le son de combat arrivera bien plus rarement, uniquement quand la cible combat réellement vous-même, votre familier ou votre groupe. La même cause touchait « Placer automatiquement les marques de raid privées Sku sur les cibles en combat » : là, pratiquement chaque cible recevait une marque. Technique : avec vocalizePlayerNamePlaceholdersSkuTts = true, GetTtsAwareUnitName renvoie "" pour toute unité qui n'est ni vous, ni votre familier, ni un membre du groupe. tRosterNames récupérait cette clé vide, et la consultation de targettarget la retrouvait. Les deux côtés testent maintenant aussi name ~= "". Auras : une aide de vérification issue de la refonte était livrée activée Simple : le cache des listes d'auras reconstruisait aussi chaque liste une seconde fois de zéro afin de comparer les deux résultats. C'était un contrôle de la phase de refonte, livré activé par erreur - et il coûtait exactement le travail que le cache fait économiser. Il est désormais désactivé ; /skuauracache verify on le réactive pour une recherche de panne. Atlas Loot : saccades à la connexion et à l'ouverture Simple : depuis peu, le jeu se figeait complètement pendant quatre à cinq secondes quelques secondes après l'écran de chargement, puis mettait une vingtaine de secondes au lieu de huit à se calmer. En cause : l'intégration Atlas Loot. À chaque connexion et à chaque rechargement, Sku parcourait toute la base de données d'Atlas Loot - environ 40 000 entrées d'un seul tenant - même dans une session où l'on n'ouvrait jamais Atlas Loot. Cela n'arrive plus à la connexion : cela se produit quand on ouvre réellement Atlas Loot, et le travail est découpé en petites tranches en arrière-plan au lieu de bloquer l'image. Si vous n'utilisez pas Atlas Loot, vous ne payez plus rien pour lui, et ses modules de données ne sont plus chargés inutilement. Deuxième partie : l'ouverture de la liste de recherche dans Atlas Loot marquait elle aussi un temps d'arrêt sensible. Cette liste compte environ 20 000 entrées, et sa construction redéterminait le nom et demandait au serveur les données d'objet pour chacune d'elles - pour 20 000 entrées qu'on n'a même pas encore consultées. Le nom est déjà connu à cet endroit, et seule l'entrée sur laquelle se trouve le curseur a besoin des données. La liste s'ouvre donc nettement plus vite. Technique : alIntegrationQueryAll construit deux index inversés sur les données d'Atlas Loot : « quel boss fait tomber cet objet » et la liste de noms derrière la recherche. Le parcours dépendait d'une série de tentatives sur PLAYER_LOGIN (5/10/20/35/60 s) plus d'un filet PLAYER_ENTERING_WORLD/ZONE_CHANGED_NEW_AREA ; il se déclenchait donc environ trois secondes après l'entrée dans le monde, sans découpage, en une seule image. Sa justification était périmée : il devait épargner au raccourci Ctrl+Maj+L une attente à froid, mais ce raccourci a perdu sa détection de contexte depuis longtemps et n'ouvre plus que l'entrée de menu, ce qui lance le calcul de lui-même. Trois des quatre tables remplies par ce parcours n'avaient aucun lecteur : alLookupBosses et alLookupInstances n'étaient qu'écrites et servaient uniquement d'indicateur « déjà exécuté ? » pour la série qui l'exécutait ; alDropsByBoss et alDropsByInstance n'étaient lues que derrière une condition qui ne peut jamais être vraie, puisque l'indicateur de contexte n'est jamais mis qu'à nil. Les quatre sont supprimées, avec cet indicateur, son minuteur de 120 s et BuildContextualWishlistEntry. Ce qui reste tourne en coroutine avec un budget de 30 ms par image : le menu le lance en arrière-plan (QueryAllStart), et ceux qui ont besoin d'un index complet - recherche, liste de souhaits par donjon, « Lâché par » - l'attendent à leur propre endroit (alIntegrationQueryAll, sans effet une fois l'index construit). Chaque ligne coûte aussi moins cher : la résolution du nom interroge désormais la table d'objets de Sku avant GetItemInfo, qui déclenchait une requête serveur pour chaque identifiant non mis en cache, et évite les six passes de substitution pour les noms issus de cette table, qui ne contiennent de toute façon aucun balisage. La construction du menu de recherche était le second point chaud : alIntegrationItemMenuBuilder accepte désormais en option le nom que l'appelant possède déjà (l'index de recherche est justement indexé PAR ce nom), ce qui supprime par entrée la résolution du nom, la passe Unescape et surtout SkuCore:RequestItemData - soit un ItemMixin avec son rappel de chargement enregistré et une requête serveur par identifiant non mis en cache, environ 20 000 fois en une seule image. La liste de butin d'un boss (quelques dizaines de lignes) continue de demander les données à l'avance, afin qu'un nom non mis en cache s'y complète tout seul, et l'entrée sous le curseur les demande de toute façon dans son OnEnter : le texte prononcé est donc inchangé. Avertissement de collision : des bips sans obstacle Simple : le bip « tu es bloqué » pouvait survenir pendant qu'on marchait simplement d'un PNJ au suivant, alors que rien ne barrait le passage. Le compteur derrière l'avertissement n'était jamais remis à zéro, si bien que des mesures isolées et anodines s'accumulaient sur plusieurs minutes et qu'une sur six déclenchait un bip. Technique : l'avertissement compare la distance parcourue en un tic de 0,15 s à celle que la vitesse actuelle aurait dû couvrir, et bipe après six tics trop lents. Seulement, le compteur n'avait aucune remise à zéro en dehors de celle du déclenchement - ni sur un bon tic, ni quand l'avertissement n'était pas armé : il accumulait donc sur toute la session au lieu d'exiger six tics d'affilée. Le premier tic après chaque PLAYER_STARTED_MOVING échantillonne une fenêtre tronquée, puisque la mesure précédente a été prise à l'arrêt, et compte donc forcément comme « trop lent » : environ un démarrage de mouvement sur six produisait un bip, à des minutes d'intervalle et sans le moindre rapport. Cela existe depuis longtemps, mais ne comptait que tant qu'on tenait soi-même une touche de déplacement ; depuis que les trajets pilotés par le moteur du jeu sont couverts dans cette version, chaque marche automatique vers une cible compte aussi - d'où la gêne quand on enchaîne les PNJ. Le compteur est maintenant remis à zéro à chaque bon tic et dès que l'avertissement n'est pas armé, exactement comme le faisait déjà la variante « suivi ». Cette variante reste inchangée : elle est désarmée par AUTOFOLLOW_END, et le son de fin de suivi provient précisément de ce chemin - l'entendre prouve que l'état a bien été remis à zéro. L'avertissement laisse en outre une ligne dans le journal de débogage au déclenchement, pour qu'un signalement de « bips venus de nulle part » puisse être rattaché au chemin d'où ils viennent. Vol en taxi : deux annonces de vol qui ne collaient pas Simple : deux choses étaient annoncées à tort. D'abord, peu avant l'arrivée, Sku disait « atterrissage anticipé possible à X » - et X était la destination vers laquelle on volait déjà ; y atterrir « par anticipation », c'est simplement arriver. Ensuite, « vol terminé » (ou « vol démarré ») arrivait parfois longtemps après le vol, typiquement au moment où l'on montait ou descendait de monture, parfois plusieurs minutes plus tard et sans le moindre rapport avec un point de vol. La première phrase disparaît ; la seconde arrive désormais exactement quand le vol commence ou se termine réellement, et jamais autrement. Technique, la destination comme point d'atterrissage : l'exclusion du dernier point existait bien, mais le curseur d'itinéraire lui glissait dessous au moment décisif. L'itinéraire est capté au décollage et une escale est considérée comme franchie dès qu'elle est à moins de 250 yards. Or le taxi entre droit dans son dernier point : celui-ci était donc « franchi » lui aussi, le curseur dépassait la fin de l'itinéraire, l'escale suivante était nulle, et la détermination de la cible retombait silencieusement sur la solution de secours « point de vol le plus proche ». Cette solution ignore l'itinéraire : la source passait de « route » à « nearest » - et c'est précisément ce dont dépend la règle qui rend la dernière étape muette. La destination était alors largement sous le seuil des 800 yards, d'où l'annonce. Le curseur s'arrête désormais sur la destination au lieu de la dépasser : la destination n'est pas une escale que l'on franchit, c'est là où le vol se termine. La règle tient donc jusqu'à l'atterrissage, et le raccourci peut toujours nommer la destination à la demande. Technique, l'annonce tardive : elle reposait sur deux drapeaux que PLAYER_CONTROL_LOST et PLAYER_CONTROL_GAINED mettaient à 1 et que SEUL le PLAYER_MOUNT_DISPLAY_CHANGED suivant remettait à zéro. Si cet événement ne suivait pas - ou arrivait avant l'événement de contrôle à l'atterrissage - le 1 attendait, tout simplement, et la monture suivante le consommait. PLAYER_CONTROL_GAINED se déclenche en outre pour quantité de choses qui ne sont pas un atterrissage (fin d'un contrôle mental, cinématique, recul, véhicule) : le drapeau pouvait donc être posé sans le moindre vol. L'état est maintenant DÉDUIT de UnitOnTaxi au lieu d'être mémorisé : on ne parle que sur une vraie transition faux->vrai / vrai->faux, chaque événement concerné rejoue le même test idempotent (les événements de contrôle le rejouent aussi en différé, car le drapeau du client est en retard sur l'événement), et une connexion ou un /reload adopte l'état courant en silence. Technique, par précaution : le module taxi (« atterrissage anticipé possible à X ») ne pouvait pas échouer ainsi - cette phrase est gardée par UnitOnTaxi ET par le bouton d'annulation visible. Renforcé tout de même, ces deux éléments étant de l'état client : reprendre le contrôle signifie désormais « le vol est terminé, point » - la surveillance est arrêtée et aucun événement ne peut la relancer avant qu'un nouveau vol soit réellement pris. De plus, un bouton d'annulation momentanément invisible (un changement de zone en vol suffit) n'efface plus les verrous d'annonce, ce qui pouvait sinon nommer deux fois le même point de vol. « /skutaxi » indique le nouvel état sous « landed= ». Invitation de groupe et invocation : le bouton Refuser est de retour Simple : lors d'une invitation de groupe ou d'une invocation (« port »), le menu ne proposait plus que « Accepter » - le bouton pour refuser manquait, et il manquait aussi longtemps que la fenêtre restait ouverte. Les deux boutons figurent à nouveau dans la liste. Technique : Blizzard verrouille le bouton Refuser sur précisément ces demandes pendant un instant après l'apparition de la fenêtre (SetupLockOnDeclineButtonAndEscape, la même fonction qui y désactive aussi Échap), pour éviter un clic malencontreux en cas de spam d'invitations ou d'invocations. Le lecteur de fenêtres générique de Sku ignore tout élément grisé, et il analyse une fenêtre contextuelle exactement UNE fois, 0,1 s après son affichage (GENERIC_OnOpen) - en plein dans cette période de verrouillage. Le bouton Refuser disparaissait donc, et comme rien ne reconstruit le menu tant que la fenêtre reste simplement affichée, il ne revenait jamais. SkuCore:IterateChildren exempte désormais les boutons des dialogues StaticPopup de la règle « ne jamais lister un élément désactivé » : atteindre une entrée de menu et appuyer sur Entrée est un acte délibéré, pas un clic malencontreux. Ces boutons conservent également leur gestionnaire OnClick même lorsque l'élément désactivé ne signale aucune entrée de clic souris - sinon l'entrée figurerait dans la liste mais resterait sans effet. Combat : une fenêtre surgissante pouvait être lue mais pas répondue Simple : lorsqu'une fenêtre surgissante s'ouvrait pendant un combat - une invitation de groupe, une invocation, la demande de libération -, Sku pouvait la lire et vous pouviez y naviguer, mais la touche d'activation ne faisait rien : l'entrée était simplement annoncée à nouveau. Il fallait attendre la fin du combat, et une invitation expirait entre-temps. Les boutons des fenêtres surgissantes peuvent désormais être pressés en combat, les quatre, pas seulement Accepter et Refuser. Technique : en combat, la touche d'activation n'atteint même pas le bouton de menu sécurisé : PLAYER_REGEN_DISABLED masque OnSkuOptionsMain, dont le OnHide efface les liaisons de substitution de ce bouton alors que la fenêtre de grâce est encore ouverte, et la touche passe alors par le snippet de SkuCombatMenuKey, qui traite Entrée comme « itinéraire uniquement » et la remet directement au répartiteur non sécurisé (SkuCore/combatMenuKeys.lua). Lisible, navigable, morte sur Entrée. Contrairement aux chemins des sacs et de l'échange, cela ne demande ni miroir ni armement sécurisé : un bouton de StaticPopup n'est PAS un cadre protégé et son OnClick est du Lua ordinaire (StaticPopup_OnClick appelle les OnAccept/OnCancel du dialogue : AcceptGroup, DeclineGroup, ResurrectAccept, RepopMe, ConfirmSummon). Aucun de ceux-ci n'est conditionné à l'événement matériel, l'appel direct fonctionne donc en combat exactement comme hors combat. Volontairement limité aux boutons de fenêtre surgissante - toute autre charge de clic de ce constructeur a besoin du véritable événement matériel et relève du miroir. Protections : uniquement en combat, uniquement tant que le bouton est bien là, et uniquement tant que Blizzard le laisse ACTIF (le bouton Refuser est verrouillé pendant sa première seconde, où « /click » est tout aussi inopérant). Nouveauté également : les quatre boutons sont traités ainsi ; les boutons 3 et 4 retombaient auparavant sur le chemin de clic générique sans aucune action non sécurisée. /skucheck menu compte les cas où une activation en combat n'a plus trouvé de bouton à cliquer. Saisie dans les champs de texte : plus de lettres qui continuent après la fermeture Simple : en tapant dans un champ de saisie de Sku (écrire un courrier, un champ de recherche, l'hôtel des ventes), l'annonce des caractères prenait du retard, se bousculait, et des lettres arrivaient encore alors que le champ était fermé depuis longtemps - surtout avec les voix SAPI, qu'une frappe n'interrompait pas. Sku se comporte désormais comme un lecteur d'écran : la frappe suivante interrompt l'annonce de la précédente au lieu de se mettre à la file. Ce qui a été tapé pendant une annonce en cours est regroupé en UNE seule annonce, si bien qu'un collage avec Ctrl+V ou une touche fléchée maintenue ne provoque plus de déluge. Et à la fermeture du champ - par Entrée, par OK, par Échap ou par tout autre chemin - Sku ne se contente plus de jeter ce qui attend : il interrompt aussi ce qui est en train d'être prononcé. Corrigé au passage : les caractères ( et % n'étaient jamais annoncés pendant la saisie. Technique : l'écho de SkuOptions:EditBoxShow ajoutait un énoncé par caractère tapé à la file BTTS. Le plafond introduit en v42.11 (GetBttsQueueDepth / TrimBttsQueue) ne pouvait pas fonctionner : la pompe de SkuVoice saute sa cadence de 0,1 s dès que plus d'une entrée attend (`#mSkuVoiceQueueBTTS > 1`) et transmet toute la rafale à C_VoiceChat.SpeakText en quelques images, donc dans la file du CLIENT. La file propre de Sku est ensuite vide, la profondeur mesurée est quasiment toujours 0, le plafond ne se déclenche jamais, et à la fermeture Trim ne trouve plus rien à jeter - à ce moment-là le retard est dans la file client/SAPI, que seul un vrai StopSpeakingText peut atteindre. L'écho passe maintenant par un seul emplacement : les caractères sont accumulés jusqu'au prochain vidage (6 au maximum, les plus récents gagnent), prononcés en UN énoncé en mode écrasement, avec un intervalle minimal de 0,1 s ; un compteur de génération annule les minuteries de vidage et les lectures de flèches différées dès que le champ est fermé. Nouveau : SkuVoice:CancelBttsOutput - vide la file BTTS, appelle StopSpeakingText et arme le délai d'attente de la pompe à 0,15 s pour que la confirmation émise juste après ne soit pas emportée par l'arrêt asynchrone ; il est branché sur ENTRÉE, OK, ÉCHAP et sur le OnHide du cadre (un :Hide() extérieur est donc couvert aussi). Également : SkuVoice:CheckIgnore utilise désormais string.find(..., plain=true) - une ponctuation tapée comme ( ou % était interprétée comme un motif Lua et levait « unfinished capture » / « malformed pattern », erreur avalée par le pcall de l'appelant ; et l'écho passe ignoreLinks, car chaque frappe traversait auparavant toute la recherche de liens wiki dont le résultat est ici ignoré. Améliorations de performance et corrections lors du chargement de la base de sorts Simple : À la connexion, certains joueurs entendaient « Sku erreur de base de données, jeu de données spells ». Le problème a été signalé depuis Era, mais le facteur commun est la puissance de la machine et non le client : sur une machine rapide cela n'arrivait jamais, sur une machine plus faible à chaque fois, et TBC aurait tout aussi bien pu être touché. La base elle-même n'a jamais été endommagée : seule échouait la liste de sélection des auras qui en est dérivée, construite en UNE seule passe sur environ 90 000 entrées. Sur une machine lente, le client interrompait cette passe avec « script ran too long » - et comme la liste était écrite au fur et à mesure, elle restait ensuite à moitié remplie : une partie des sorts manquait dans les réglages d'auras, et les auras pointant sur une entrée manquante n'annonçaient plus rien ou laissaient le menu sur une erreur. La construction se fait maintenant par petites tranches réparties sur plusieurs images, exactement comme le reste de la construction de la base après la connexion, et n'est publiée d'un seul bloc qu'à la toute fin. Si elle est tout de même interrompue, les listes précédentes restent intactes au lieu d'être à moitié remplies, aucune erreur de base de données n'est prononcée, et le reste des données de sorts reste considéré comme valide. De plus, la liste n'est construite qu'une fois par session au lieu de l'être à chaque écran de chargement. Technique : SkuAuras:BuildAttributeValueLists parcourt itemLookup ET SpellDataTBC et alloue une table plus une ou deux chaînes concaténées par ligne. Elle était appelée de façon synchrone depuis deux endroits : l'étape de construction « auraValueLists » (après items+spells) et PLAYER_ENTERING_WORLD. La fonction accepte désormais un callback de yield facultatif, consulté toutes les 1000 lignes, construit dans des tables locales et les publie à la fin par une affectation chacune (atomique - une interruption ne change plus rien). Elle est pilotée par sa propre coroutine avec une pompe OnUpdate qui partage le budget d'images d'après-connexion (Sku:BuildFrameBudgetMs, enregistrée comme worker « skuAuraLists ») : trois workers actifs coûtent toujours 150 ms par image au total. L'étape de construction ne fait que démarrer ce pilote et n'appelle plus ctx.fail : l'échec de la liste dérivée marquait auparavant toute la famille de données « spells » comme échouée et déclenchait l'annonce d'erreur, alors que les données de sorts étaient complètes. Les erreurs vont maintenant dans SkuErrorLog (« skuAuraLists ») et dans le journal de débogage. PLAYER_ENTERING_WORLD démarre le même pilote, qui ne fait rien une fois la construction réussie - la reconstruction complète à chaque changement de zone et à chaque entrée en instance disparaît donc. La construction ignore également une ligne de sort sans nom pour la langue active au lieu de s'y arrêter, et les trois accès friendlyName non protégés du menu des auras retombent sur la clé brute. Un ennemi qui vous a empoisonné avant de mourir n'est plus recompté Simple : L'annonce du nombre d'ennemis est fiable dans les combats ordinaires depuis la v42.10, mais il restait un cas : si un ennemi vous infligeait un poison - ou tout autre effet persistant - juste avant de mourir, le nombre descendait correctement à sa mort, remontait d'un cran un instant plus tard, et ne redescendait définitivement qu'à l'expiration du poison, bien après la mort de l'ennemi. C'est corrigé, et sans toucher à la détection elle-même : rien de nouveau n'est compté, une seule chose cesse d'être comptée à tort. Technique : Un ennemi mort est mémorisé par son GUID afin qu'une ligne de journal de combat tardive ne puisse pas le ressusciter. Cette marque était jusqu'ici effacée en bloc dans PLAYER_REGEN_ENABLED - c'est-à-dire exactement au moment où la mort du dernier ennemi vous sort du combat, donc juste avant l'arrivée du premier tic de poison. Chaque tic est une ligne de journal de combat avec l'ennemi mort comme source et vous comme cible ; la logique d'ajout y lit « un ennemi nous combat » et réadmettait le cadavre par son GUID. Le deuxième zéro ne venait plus alors que du balayage d'inactivité de 6 secondes après le dernier tic. La marque porte désormais un horodatage et n'est plus effacée en bloc à la fin du combat, mais seulement lorsqu'elle a plus de 60 secondes. Rien ne change à l'intérieur d'un combat - la marque y vivait de toute façon jusqu'à la fin du combat, et c'est pourquoi le cas « l'un de deux ennemis meurt empoisonné pendant que le second continue » était déjà compté correctement. Aucun nouvel état ni aucune nouvelle règle particulière n'est ajouté, seulement une durée de vie pour une marque qui existait déjà. La soupape existante demeure : si le même GUID réapparaît manifestement vivant sur un jeton d'unité, la marque est levée immédiatement, un vrai ennemi ne peut donc jamais disparaître durablement du décompte. Un échange conclu avec succès était annoncé comme une confirmation retirée Simple : Quand un échange aboutissait vraiment, il se terminait malgré tout sur « a retiré sa confirmation ». Rien n'avait échoué : à la conclusion, le serveur enlève d'abord les deux coches de confirmation et ne ferme la fenêtre qu'ensuite, et cet effacement ressemble exactement à un vrai retrait. La fausse ligne disparaît. Aucune annonce « échange terminé » ne la remplace, et c'est délibéré : le client 2.5.6 ne fournit aucun signal distinguant la conclusion de l'annulation, et un message deviné serait pire que pas de message du tout. Technique : Capturé pour un enchantement réussi dans un échange, le tout en une seconde : TRADE_ACCEPT_UPDATE(1,1) -> (0,1) -> (0,0) -> TRADE_CLOSED. TRADE_ACCEPT_UPDATE lisait ces transitions 1->0 comme un vrai retrait. Impossible de les distinguer : TRADE_CLOSED ne transporte aucune donnée, et ERR_TRADE_COMPLETE / ERR_TRADE_CANCELLED n'existent pas sur 2.5.6 (vérifié contre le TradeInfoDocumentation.lua livré avec le client). Les deux annonces de retrait attendent maintenant 0,5 s derrière un compteur de génération que ResetTradeAcceptState() incrémente à chaque frontière d'échange, et sont abandonnées si l'échange est terminé, si la fenêtre a disparu ou si le côté concerné est repassé à confirmé. « Échange confirmé » était avalé à chaque fois Simple : Si vous confirmiez l'échange depuis le menu, vous n'entendiez jamais votre propre confirmation. Et sur plusieurs annonces d'échange rapprochées, seule la dernière était prononcée - donc justement la mauvaise. Elles arrivent maintenant toutes, dans le bon ordre. Technique : OutputStringBTtts avec aOverwrite=true ajoute un « queuereset », et la pompe supprime tout ce qui est en file AVANT ce marqueur ; dans une rafale, chaque annonce d'échange dévorait donc la précédente. Toutes les annonces d'échange passent désormais false. Cela ne sauve pas encore votre propre confirmation : lorsque vous acceptez depuis le menu, la réponse du serveur est déjà en file avant que le menu n'ajoute sa propre ligne pour l'appui de touche, dont la réinitialisation la supprime alors. Les annonces de confirmation passent donc par tSayAccept, avec un délai fixe de 0,35 s qui les place derrière la ligne du menu. De l'ordonnancement de file uniquement, aucune déduction d'état - et volontairement sans contrôle de génération, car un échange qui se ferme juste après est précisément le cas où cette confirmation compte le plus. Entrée désenchante ou enchante à nouveau un objet du sac, dans les deux ordres Simple : Si vous lanciez Désenchantement (ou preniez un enchantement, une pièce d'armure, une huile d'arme) alors que vous étiez déjà sur l'objet cible dans la liste des sacs, puis appuyiez sur Entrée, il ne se passait rien du tout - seul Ctrl+Entrée fonctionnait. L'ordre inverse marchait : lancer d'abord, puis naviguer jusqu'à l'objet. Entrée applique maintenant dans les deux ordres. Le clic gauche est bien la voie prévue par le jeu : dans le code des sacs de Blizzard, un clic gauche sur un objet l'utilise pour le sort tant qu'un sort attend une cible objet. En v41 cela passait par l'ancienne entrée « clic gauche » ; la refonte du menu en v42.00 l'a perdue, et la v42.04 n'a rétabli l'application que pour l'ordre « lancer d'abord ». Technique : Le bouton sécurisé du clic gauche est garni au moment où une entrée est FOCALISÉE, et la macro d'application d'un objet de sac (« /use sac emplacement ») uniquement tant que SpellIsTargeting() est vrai à cet instant. Si le sort est lancé après, le bouton gardait la garniture d'avant - pour un objet de sac, donc aucune -, et l'instantané de ciblage pris dans PreClick fait en outre renoncer le repli non sécurisé PickupContainerItem : l'appui de touche ne faisait littéralement rien. La garniture a quitté le OnEnter générique pour SkuOptions:StageClickMacros (les deux boutons sécurisés) et est réexécutée depuis deux endroits : CURRENT_SPELL_CAST_CHANGED, dont le gestionnaire était vide et dont la signature de répartiteur était décalée d'un cran, et le PreClick du bouton gauche lui-même, qui s'exécute avant que le gestionnaire sécurisé ne lise les attributs - l'échange compte donc encore pour cet appui précis. Seules les entrées porteuses d'une macro d'application sont regarnies, uniquement menu ouvert et hors combat. Ctrl+Entrée sur un objet de marchand ouvrait la cabine d'essayage au lieu d'acheter Simple : Chez un marchand, Ctrl+Entrée (clic droit) sur un objet doit en acheter un. À la place, la cabine d'essayage s'ouvrait. L'achat par le sous-menu de l'objet a toujours fonctionné et ne change pas ; la touche achète désormais aussi, y compris dans l'onglet de rachat. Technique : La touche du clic droit porte Ctrl par défaut, une macro « /click RightButton » lit l'état CLAVIER EN DIRECT, et le XML de chaque bouton d'objet natif redirige TOUT clic modifié vers *_OnModifiedClick -> HandleModifiedItemClick -> DressUpLink : la branche du clic droit simple n'est jamais atteinte. C'est le même piège qui avait cassé les huiles sur les armes équipées, résolu là par « /use ». Les objets de marchand appellent maintenant directement le MerchantItemButton_OnClick non enveloppé de Blizzard, ce qui conserve les confirmations de coût spécial et de prix élevé. Les sacs, la banque et les emplacements d'équipement étaient déjà immunisés car ils passent par « /use » ou l'API des conteneurs. Partout ailleurs où Sku clique un bouton natif (butin, échange, courrier, grimoire, banque de guilde), la touche du clic droit n'a pas d'action propre à perdre : Ctrl+Entrée n'y fait rien ou affiche la cabine d'essayage, tandis qu'Entrée déclenche la vraie action. Le clic gauche peut désormais être assigné à une touche avec Maj, Ctrl ou Alt Simple : La touche d'activation peut être réassignée à une combinaison comme Ctrl+Entrée et fonctionne quand même. Auparavant, une telle touche aurait ouvert la cabine d'essayage chez un marchand ou sur un objet équipé au lieu d'agir, pour la même raison que le clic droit chez le marchand. Technique : Les entrées dont le clic serait avalé par un modificateur portent maintenant une seconde charge, insensible aux modificateurs (plainMacrotext), qui appelle l'action directement au lieu de cliquer le bouton natif : objets de marchand via le MerchantItemButton_OnClick non enveloppé de Blizzard, emplacements d'équipement via PickupInventoryItem, emplacements d'échange via ClickTradeButton / ClickTargetTradeButton. Elle n'est mise en place que tant qu'un modificateur est réellement enfoncé - la décision se prend dans le PreClick du bouton sécurisé, seul moment où l'état du clavier est lisible, et qui s'exécute avant la lecture des attributs. Avec la touche Entrée par défaut, sans modificateur, la mise en place reste identique à l'octet près : aucune régression possible de ce côté. Les sacs, la banque, les récompenses de quête, la banque de guilde, les fenêtres de confirmation et les onglets n'avaient besoin de rien : ils passent par l'API des conteneurs ou n'ont aucune branche de clic modifié. Les boutons de butin, les pièces jointes du courrier et les boutons du grimoire ont bien cette branche, mais Sku ne les clique jamais - le butin a son propre traitement, les pièces jointes passent par TakeInboxItem, et le grimoire ne fait pas partie des fenêtres ouvertes par Sku. S'il venait à l'être, il lui faudrait « /cast » plutôt que ce mécanisme, car l'incantation est une fonction protégée qu'aucun appel direct ne peut déclencher. Les touches de clic du menu ne pouvaient pas être assignées du tout Simple : Dans les raccourcis, « Menu ; clic gauche/activer » et « Menu ; clic droit » répondaient « Invalide. Appuyez sur une autre touche. » à chaque Entrée pressée - avec ou sans modificateur - et ne pouvaient donc pas être réglées, pas même remises à leur propre valeur par défaut. Les deux sont assignables désormais. Technique : La liste des touches réservées est comparée en SOUS-CHAÎNE : « ENTER » attrape donc aussi NUMPADENTER et CTRL-ENTER. Elle protège les touches de navigation du menu - mais ces deux assignations vivent précisément sur cette famille : Entrée est le défaut de l'une, Ctrl+Entrée celui de l'autre. Elles en sont maintenant exemptées, uniquement pour les touches Entrée ; les flèches, le retour arrière et la tabulation y restent bloqués, car ils pilotent la navigation, qui ne fait pas partie de cette assignation. Les touches du menu de combat disposaient déjà d'une telle exemption. Appliqué aux deux endroits qui capturent une touche : le menu des raccourcis et l'assistant d'entrée unique. La touche de clic du menu n'efface plus l'assignation de jeu de la même touche Simple : Remettre « Menu ; clic gauche/activer » sur Entrée avertissait que la touche était « déjà assignée à Ouvrir la discussion » puis, après confirmation, faisait exactement cela : Entrée n'ouvrait plus la discussion. Impossible de revenir en arrière non plus, car pour les commandes du jeu le menu des raccourcis refusait toute touche Entrée comme invalide. Or ces deux assignations ne se sont jamais gênées - elles partageaient Entrée depuis des années : tant que le menu est ouvert, Entrée active l'entrée, sinon elle ouvre la discussion. C'est de nouveau le cas. Quand une touche est ainsi partagée, Sku l'annonce désormais (« Remarque ! Cette touche est également utilisée par ... Les deux assignations sont conservées. ») au lieu de supprimer l'autre assignation. Si la discussion vous manque déjà : réassignez simplement « Ouvrir la discussion » à Entrée dans le menu des raccourcis, c'est possible maintenant. Le reste du comportement en cas de conflit ne change pas - deux touches Sku sur la même touche sont toujours signalées et l'ancienne libérée. Technique : Les touches de clic du menu ne sont jamais une véritable assignation mais une liaison de substitution sur le bouton sécurisé du menu, armée par son OnShow et effacée par son OnHide : elle n'existe que tant que le menu est ouvert et prime sur la commande du jeu pendant ce laps de temps. Il en va de même pour les touches du menu de combat, armées uniquement pendant un combat. La vérification des conflits ignorait cette différence : elle compare des noms de touches bruts, signalait donc un conflit et, à la confirmation, appelait SetBinding(touche) puis SaveBindings - ce qui retirait définitivement OPENCHAT d'Entrée. Cela n'est devenu atteignable que parce que le correctif ci-dessus a rendu ces assignations possibles sur Entrée. Les entrées concernées portent désormais un marqueur transientOverride dans skuDefaultKeyBindings, lu via SkuOptions:SkuKeyBindsIsTransientOverride ; pour elles, la gestion de conflit contre les COMMANDES DU JEU est abandonnée (pas de confirmation, pas de libération) et remplacée par la remarque vocale. Contre d'autres assignations Sku, elle reste entièrement en place, car celles-ci sont armées en même temps et se chassent réellement. La même règle s'applique dans l'autre sens afin qu'une commande du jeu ne libère pas la touche du menu, et la famille Entrée n'est plus bloquée lors de l'assignation des commandes du jeu - les flèches, le retour arrière et la tabulation le restent. La remarque est accrochée à la même annonce que la nouvelle touche, car un appel séparé avec aOverwrite vide la file d'attente et se serait avalé lui-même. Les objets de la banque portent de nouveau leur nom Simple : À la banque - et occasionnellement ailleurs - certains objets étaient annoncés avec le texte d'attente du client (« Retrieving item information » en anglais) au lieu de leur nom. Ce n'est pas un message d'erreur de l'addon mais un texte d'attente du jeu : le client l'affiche tant qu'il récupère encore les données d'un objet auprès du serveur. Étaient surtout touchés les objets à infobulle volumineuse - recettes, pièces d'ensemble, équipement serti -, car leur affichage n'est complet qu'une fois TOUTES les données qui y sont mentionnées arrivées. Jusqu'ici Sku reprenait ce texte d'attente comme NOM de l'entrée, et n'en démordait plus : il ne disparaissait pas, ni en revenant sur l'entrée, ni en quittant puis rouvrant le menu. Doublement gênant, car la phrase est identique pour chaque emplacement concerné - plusieurs objets en attente sonnaient pareil, impossible de les distinguer ni de viser l'un d'eux, et c'est aussi ce que comparaient le tri par nom et le saut par première lettre. Désormais Sku prend le nom dans sa propre liste d'objets fournie avec l'addon quand le client ne l'a pas encore, n'ajoute qu'un bref « (chargement) », demande activement les données au serveur, et relit l'entrée dès qu'elles arrivent. À l'ouverture de la banque, les données de tous les emplacements sont en outre demandées à l'avance, de sorte que le premier passage n'attend généralement pas du tout. Technique : La phrase est la constante du jeu RETRIEVING_ITEM_INFO. Il n'existait pas une seule vérification contre elle dans l'addon - la seule mention dans le code se trouvait dans un outil de développement et comparait un texte allemand écrit en dur. L'état restait permanent pour trois raisons : aucun récepteur de GET_ITEM_INFO_RECEIVED nulle part, aucune demande de chargement (écrire dans une infobulle n'est pas une requête que l'on peut suivre), et aucune seconde voie vers un nom. Nouveautés : SkuUtil:IsRetrievingItemInfo (détecte le texte d'attente via la constante du jeu plutôt que via du texte allemand), SkuCore:ResolveItemName (cache du client, puis SkuDB.itemLookup, puis le nom dans le lien), SkuCore:PendingItemLabel, SkuCore:RequestItemData et SkuCore:ContinueOnItemData via Item:CreateFromItemID():ContinueOnItemLoad. Un cadre d'événements dédié collecte GET_ITEM_INFO_RECEIVED et en déclenche une seule reconstruction silencieuse groupée ; BANKFRAME_OPENED demande à l'avance le conteneur -1 et les sacs de banque 5 à 11. Si la banque était le point noir, il y a une raison précise : SetBagItem(-1, emplacement) ne renvoie rien sur 2.5.6, si bien que la refonte des sacs s'y rabattait sur SetHyperlink(GetContainerItemLink(...)) - précisément la voie qui dépend du cache d'objets. Les emplacements de banque sont de vrais emplacements d'équipement (emplacement n = emplacement d'équipement n + 39), donc SetInventoryItem les lit aussi localement que SetBagItem lit un sac ; ce n'est pas un retour aux éléments de fenêtre lus que la refonte a supprimés - pas de cadre, pas de OnEnter, pas d'ouverture forcée des sacs. Aucune voie de repli n'a été laissée derrière, volontairement : elle ne ferait que masquer si la bonne voie fonctionne. Les objets ne disparaissent plus des listes de butin d'AtlasLoot Simple : Des entrées manquaient dans les listes de butin - sans trou, sans indication. Une liste de douze butins possibles en affichait sept et se lisait comme une liste complète de sept. La cause est la même qu'au point précédent : les objets dont le client n'avait pas encore reçu les données du serveur n'étaient pas reconnus comme des objets et étaient purement et simplement omis. Cela touchait justement les entrées qui font l'intérêt de la fonction, car une liste de butin est presque uniquement composée d'objets que l'on n'a jamais ramassés ni consultés - moins le boss est familier, plus il en manquait. Ce n'était pas non plus reproductible : ouvrir la même liste une seconde fois pouvait en donner une plus longue, avec des numéros d'entrée décalés. Désormais toutes les entrées sont là, et la recherche par nom trouve aussi les objets que l'on n'a jamais eus en main. Les listes deviennent donc plus longues que d'habitude, et les numéros d'entrée se décalent une fois en conséquence. Technique : À quatre endroits, un aiguillage décidait à partir de C_Item.GetItemNameByID(id) si une ligne de butin est un objet ou un sort - un test qui confond « pas un objet » et « pas encore envoyé par le serveur ». Un objet non chargé tombait dans la branche des sorts, GetSpellInfo ne renvoyait rien pour lui, et la ligne était écartée. L'intention du test est juste (garder les identifiants aberrants hors du menu), seule la question était mal posée : GetItemInfoInstant y répond localement depuis la base d'objets du client. Il existe maintenant l'assistant tIsItemId pour cela ; à l'un des quatre endroits, le problème avait déjà été contourné isolément par un ajout SkuDB, que ceci remplace. L'ordre des branches est inchangé. La sortie anticipée de la branche « item » du constructeur de menu disparaît également ; l'entrée est nommée via SkuCore:ResolveItemName ou - de façon stable - « objet », volontairement pas la forme « (chargement) », qui déplacerait l'entrée sous le curseur une fois les données arrivées. tItemNameTable, derrière la recherche par nom, est également remplie via ResolveItemName, et l'appel GetItemNameByID sur les favoris à la connexion, dont le résultat était jeté, est désormais une vraie demande de chargement. Le navigateur de donjons proposait tous les donjons au lieu des seuls accessibles Simple : sous « Créer une entrée » figurait la liste complète des donjons de la catégorie - la même à tous les niveaux. Un personnage de niveau vingt se voyait proposer les instances héroïques de l'Outreterre, un niveau soixante-dix les Mines de Sombrefer, et les deux pouvaient être cochés et publiés alors qu'on ne peut pas y entrer. La fenêtre visible n'a jamais fonctionné ainsi : le jeu y restreint la liste à ce qui correspond à votre niveau, et c'est précisément cette restriction qui manquait chez nous - nous lisions simplement la liste non filtrée. Sku affiche désormais la même sélection que la fenêtre. Si vous avez malgré tout besoin de la liste entière, par exemple pour vous proposer sur un donjon très inférieur à votre niveau, désactivez la nouvelle entrée « Donjons adaptés uniquement » ; elle se trouve juste au-dessus des groupes de donjons et indique, lorsqu'elle est active, combien d'entrées elle masque. Les donjons déjà cochés restent toujours visibles même si la restriction devait les masquer - sinon vous publieriez quelque chose que vous ne retrouveriez plus dans le menu. Technique : tGetCategoryActivities appelait C_LFGList.GetAvailableActivities(categoryID) sans l'argument filters et obtenait donc toute la catégorie ; les niveaux issus de GetActivityInfoTable n'étaient lus que pour l'intitulé, jamais pour filtrer. Le filtrage se fait maintenant en deux couches combinées par ET : d'une part via l'ensemble du jeu lui-même (GetAvailableActivities(categoryID, nil, Enum.LFGListFilter.Recommended)), d'autre part localement face à UnitLevel("player") au moyen de minLevelSuggestion/ maxLevelSuggestion - sur 2.5.x ce sont les seuls champs portant de vrais niveaux. L'ensemble du jeu n'est retenu que s'il revient non vide ET comme un véritable sous-ensemble de la liste non filtrée, afin qu'une signature divergente ne puisse jamais vider le menu ; une donnée de niveau absente laisse passer l'entrée. Nouveautés : le réglage dungeonBrowser.showAllActivities (char), la bascule SkuCoreDungeonToggleShowAll (qui reconstruit l'onglet, la liste des enfants changeant), les compteurs de DungeonBrowser.tFilterStats et une ligne dprint « activity filter » portant les décomptes individuels des deux couches. Menu rapide : erreurs Lua sur certains réglages Simple : dans le menu rapide, plusieurs réglages déclenchaient une erreur Lua dès qu'on les ouvrait avec la flèche droite - surtout sous « Synthèse vocale », mais aussi le volume de la balise sous « Volumes » et les interrupteurs oui/non sous « Divers ». Les mêmes réglages atteints par leur chemin normal dans le menu des réglages fonctionnaient parfaitement. C'est que le menu rapide ne possède aucun réglage propre : il affiche les mêmes entrées une seconde fois à un second endroit, et cet affichage secondaire omettait une information sans laquelle Sku ne retrouvait pas la valeur enregistrée. Les deux chemins affichent désormais la même valeur et écrivent dans le même réglage. De plus, « Divers » présente à nouveau les quatre entrées prévues : « Ne pas masquer l'infobulle » et « Jouer les salutations des PNJ » en avaient discrètement disparu lorsqu'elles ont reçu une nouvelle place ailleurs dans le menu des réglages. Technique : SkuOptions:IterateOptionsArgs décide à partir de son quatrième argument (aModule) si un nœud est géré par le schéma : skuManaged = (v.get == nil and v.set == nil and aModule ~= nil). Sinon, GetCurrentValue lit via self.optionsPath[self.profileIndex]:get() - or ces closures get/set sont précisément ce que les nœuds gérés par le schéma n'ont pas, puisqu'ils vivent d'aModule. Les six appels miroir du menu rapide dans SkuZOptions/Core.lua ne transmettaient pas aModule : ouvrir un tel nœud tombait donc droit sur « attempt to call method 'get' (a nil value) ». Étaient touchés WowTtsVoice/-Speed/-Volume (SkuChat), beaconVolume (SkuNav), readAllTooltips/interactMove (SkuCore) et vocalizeMenuNumbers/vocalizeSubmenus (SkuOptions) ; les canaux audio et les réglages de son restaient intacts car ils portent leurs propres get/set. Les six appels transmettent maintenant module et keyPrefix exactement comme l'emplacement de menu d'origine (« SkuOptions »/ « soundChannels. », « SkuNav »/« », « SkuOptions »/« soundSettings. », « SkuChat »/« », « SkuCore »/« », « SkuOptions »/« »), de sorte que les valeurs enregistrées restent inchangées. L'appel de « Divers » reçoit en plus aIncludeHidden = true, car doNotHideTooltip et playNPCGreetings portent désormais forAudioMenu = false et étaient filtrés sans un mot. Par ailleurs, tous les nœuds produits par IterateOptionsArgs lisent et écrivent maintenant via deux aides centrales (tOptGet/tOptSet) à trois niveaux - SkuSettings, closure get/set propre, accès direct à la table -, si bien qu'un futur emplacement miroir sans aModule lira au pire la valeur directement au lieu de lever une erreur. Deux déplacements dans le menu : les réglages de son des balises et « Divers » Simple : Les réglages de balise (volume de balise, clic sur les balises avec son angle et son son, ainsi que les jeux de sons pour les balises étroites, larges et la dernière) se trouvaient jusqu'ici sous Moniteur. Ils sont désormais là où se trouvent tous les autres réglages de navigation : Réglages, Navigation, Réglages de son des balises. L'entrée ne s'appelle donc plus simplement « Balise » mais dit de quoi il s'agit, et Moniteur ne la contient plus du tout. Par ailleurs, « Divers » est de nouveau la toute dernière entrée du menu rapide - « Dial Targeting » et « Soft Targeting » se placent maintenant avant, et non plus après. Technique : Le bloc balise passe tel quel de Aq:MonitorMenuBuilder (SkuCore/aq.lua) dans la branche Navigation de SkuCore:MenuBuilder (SkuCore/Options.lua), où il est accroché comme sous-entrée « Réglages de son des balises » (en allemand « Beacon Soundeinstellungen », en anglais « Beacon sound settings ») sous les réglages de navigation. Ce sont les mêmes nœuds d'options issus de SkuNav.options.args, transmis via IterateOptionsArgs vers SkuSettings:Sub(« SkuNav ») avec le même module et le même keyPrefix - les valeurs enregistrées restent donc inchangées, et les nœuds de jeux de sons conservent leur OnAction qui joue une balise d'exemple. Comme avant, la liste est une sélection explicite et non toute la table args : un nouveau nœud de balise doit y être ajouté aussi. Dans le menu rapide (SkuZOptions/Core.lua), Dial Targeting et Soft Targeting ont été déplacés avant le bloc 7.4 ; cette liste d'enfants ne porte pas d'indicateur sorting, l'ordre d'insertion est donc l'ordre d'affichage. ------------------------------------------------------------------------------------------------------- Changements dans Sku v42.12 La feuille de personnage relit la description de chaque caractéristique Simple : Sous « Stats », chaque valeur - dégâts des sorts, score de toucher des sorts, endurance, vitesse d'arme et toutes les autres - n'avait que son nom et son chiffre. La description que le jeu affiche à côté était vide : ce que fait la caractéristique, le détail par école de magie, main droite et main gauche. La touche de texte intégral ne disait rien sur ces entrées. Seules les résistances et votre équipement avaient une description. Désormais chaque caractéristique en a une, et elle est relue chaque fois que vous arrivez sur l'entrée, elle est donc à jour après un buff ou un changement d'équipement également. Technique : La lecture de l'infobulle était mise en commentaire à cet endroit et chaque entrée était construite avec un textFull vide - inchangé depuis la v41.06, ce n'est donc pas la refonte qui l'a cassé, cela n'a jamais fonctionné. Deux raisons pour lesquelles cela ne pouvait pas fonctionner tel qu'écrit : GetButtonTooltipLines était appelé avec GameTooltip comme deuxième argument, ce qui saute précisément l'étape qui remplit l'infobulle ; et sans cet argument l'assistant aurait quand même abandonné, car il ne déclenche un OnEnter que pour les objets portant un marqueur .type - les cadres de caractéristiques n'en ont pas, ce qui est exactement pourquoi la boucle des résistances le pose avant sa propre lecture. S'y ajoutait une troisième raison, celle qui aurait rendu un correctif naïf faux : TBC fait passer les 29 caractéristiques par UN SEUL widget partagé (PlayerStatFrameLeft1), et les setters de Blizzard y laissent leur état - .tooltip/.tooltip2 ainsi que .bonusDamage/.spellCrit/.damage pour les quatre caractéristiques qui installent leur propre OnEnter. Aucun d'eux n'efface ce que le précédent a laissé, quatre d'entre eux remplacent OnEnter sans jamais le remettre ; le propre UpdatePaperdollStats de Blizzard le repointe avant chaque groupe pour exactement cette raison. Sans la même remise à zéro, la caractéristique N aurait lu la description de la caractéristique N-1. La lecture réinitialise maintenant le widget avant chaque setter et parcourt l'infobulle via NumLines/GameTooltipTextLeft|Right plutôt que GetRegions - ce dernier rend la colonne de gauche et celle de droite comme deux séries distinctes, si bien que le détail par école de magie aurait été lu comme six noms d'école suivis de six chiffres. Le getter dynamique qui relit la valeur à chaque arrivée renvoie désormais la description comme deuxième valeur à côté d'elle, et le widget est ensuite rendu à l'état que Blizzard attend via PaperDollFrame_UpdateStats. Sku ne dépend plus du fait que des jetons internes restent non traduits Simple : Sur un client français, la touche du journal de quêtes ne faisait absolument rien - pas de focus, pas de son, pas d'erreur. Un seul mot traduit dans le fichier de localisation française en était la cause. Certaines entrées de ce fichier ne sont pas un libellé mais un jeton interne que Sku utilise pour construire et reconnaître ses propres chemins de menu ; traduisez-en un et l'addon ne retrouve plus son propre chemin. C'est corrigé, et corrigé d'une manière qui ne peut pas se reproduire : le jeton n'est plus un texte traduisible. Si vous avez pris l'habitude de taper un chemin en français, il vous mène toujours au bon endroit. Une deuxième trouvaille du même genre aurait fait basculer silencieusement tous les clients français vers les noms anglais des créatures, des objets et des quêtes à la prochaine remise en ordre du fichier de localisation - cela aussi a disparu. Technique : Trouvé, diagnostiqué et corrigé par ZenqFR (Games-Access) sur un vrai client français, sous la forme de la PR #2 de ce dépôt. Les modifications qui suivent s'appuient dessus. L["short"] portait deux sens sans rapport : dans environ 25 endroits c'était le premier champ d'un chemin SkuOptions:SlashFunc, c'est-à-dire un jeton de protocole que le code compare avec lui-même, et dans exactement un endroit (SkuCore/aq.lua) c'est un véritable texte destiné à l'utilisateur. Un traducteur ne voit jamais que le second sens. Les deux sont désormais séparés : Sku.MENU_ROOT est le jeton, utilisé par les 23 endroits qui construisent des chemins ainsi que par les six qui codaient en dur le littéral "short," - dont la touche du journal de quêtes. La comparaison accepte toujours L["short"] également, de sorte qu'un chemin localisé que quelqu'un a appris continue de fonctionner. Corrigé au passage : le repli dans SkuCore/dungeonBrowser.lua était "sku", ce qui n'était pas un jeton valide du tout. Deuxièmement, frFR.lua définissait L["locale"] deux fois - "enUS" dans le corps trié et "frFR" ajouté à la fin. Cela ne se résolvait correctement que grâce à l'ordre d'AceLocale : une locale non par défaut est enregistrée via un proxy qui fait un rawset inconditionnel, donc la DERNIÈRE affectation gagne. Sku.Loc est lu directement depuis cette clé, et elle détermine quelles tables de noms SkuDB tout le client utilise - régénérer ou trier le fichier aurait fait basculer silencieusement tous les clients français vers les DONNÉES anglaises : pas d'erreur, pas de ligne de journal, juste les mauvais noms partout. La ligne perdante est supprimée, la survivante documentée, et dev/rework-docs/_locale_dupes.py signale désormais les clés en double par fichier de locale. Ce lint rend aussi visible qu'enUS, en tant que locale par défaut d'AceLocale, fonctionne au contraire en PREMIER-gagnant : la même clé dupliquée peut se résoudre en entrées différentes selon la langue. 64 conflits de ce type existent aujourd'hui, presque tous du bruit de traducteurs anciens ; ils ont été délibérément laissés en l'état et sont au moins visibles maintenant. Troisièmement, SkuDBEnsureActiveLocaleTables tient enfin sa promesse - il s'exécute aussi à ADDON_LOADED (auparavant seulement un OnUpdate après PLAYER_LOGIN, si bien que tout ce qui touchait à [Sku.Loc] dans son propre gestionnaire PLAYER_LOGIN voyait nil un instant) et ne retourne plus silencieusement sur un Sku.Loc inutilisable : il valide, journalise le coupable probable, et construit les tables quoi qu'il arrive. La parole ne se tait plus quand vous parcourez une liste rapidement Simple : Faire défiler rapidement un menu, une liste d'objets proches ou vos sacs pouvait couper la parole entièrement - il ne restait que les sons de clic et de balise - jusqu'à ce que vous attendiez quelques secondes ou que vous alliez dans une autre partie de l'interface. Chaque entrée devant laquelle vous passiez était avalée, et souvent aussi celle sur laquelle vous vous arrêtiez finalement. Désormais chaque entrée survolée est remise à la voix, et l'entrée sur laquelle vous vous arrêtez est prononcée. Rien n'a changé quant à l'annonce qui l'emporte : une nouvelle remplace toujours la précédente, un défilement rapide ne lit donc toujours que ce sur quoi vous vous arrêtez, et l'interruption de la parole en combat est inchangée. Technique : La pompe TTS de Blizzard se cadençait avec un compte à rebours qui soustrayait un seul delta d'image par exécution, mais son corps ne s'exécute que toutes les 0,01 s - donc au-dessus d'environ 100 images par seconde elle soustrayait bien moins que le temps réellement écoulé, et la pause de 0,1 s prévue avant l'énoncé suivant s'étirait à 0,2-0,3 s réelles. Comme une réinitialisation de file supprime la ligne qui attend encore, une pause plus longue signifiait plus d'annonces perdues : le bug empirait à mesure que la fréquence d'images montait. La pause est maintenant une échéance GetTime, identique à 60 ips et correcte au-dessus. Mesuré sur une capture à 6-7 appuis de touche par seconde : avant, 7 réinitialisations produisaient 2 lignes prononcées ; après, 7 réinitialisations en produisent 7. Une seconde protection supprime un StopSpeakingText quand rien n'est en vol et qu'un autre a déjà été envoyé dans les 0,15 s - le client traite les arrêts de façon asynchrone, si bien qu'un arrêt tardif pouvait annuler l'énoncé suivant avant qu'il ne commence. Cette protection ne se déclenche plus jamais pendant la navigation ; elle demeure pour les rafales événementielles (courrier, discussion) qui produisaient 17 arrêts en une seconde sans aucune parole. Moins de travail par ligne prononcée Simple : Sku effectue nettement moins de travail pour chaque ligne qu'il prononce et chaque son qu'il joue, ce qui se traduit par un défilement plus fluide. Technique : Deux diagnostics temporaires [SOUND-PROBE] ont été supprimés. Tous deux passaient debugstack(...) en argument à dprint, et dprint ne teste l'indicateur de débogage qu'à l'intérieur de lui-même - un parcours complet de la pile s'exécutait donc pour chaque son même avec la journalisation désactivée, et celui de Sku/Core.lua accrochait le PlaySound global, si bien que les propres appels de Blizzard le payaient aussi. Ils représentaient un quart de toutes les lignes de journal capturées. La pompe de file saute désormais entièrement ses balayages tant que les deux files sont vides, au lieu de les exécuter une centaine de fois par seconde, et la recherche de lien wiki abandonne avant de normaliser la chaîne plutôt qu'après : elle est appelée pour chaque ligne prononcée depuis OutputStringBTtts, où son résultat est de toute façon jeté, et sans index wiki elle effectuait environ 19 passes de string.gsub pour atteindre une garde qui ne faisait rien. L'installateur et la page de téléchargement parlent français eux aussi Simple : Sku lui-même parle français depuis la v42.11, mais tout ce qui l'entoure non. Le Sku Installer fonctionne désormais en français - il prend sa langue de Windows, et vous pouvez la changer à tout moment dans la liste « Langue de cet installateur » - et la page de téléchargement existe en version française, accessible par les liens de langue en haut de chaque page. Ces notes de version existent elles aussi en français, à partir de la v42.11. Il n'y a toujours pas de pack de voix français : sur un client français, Sku parle par votre lecteur d'écran, et c'est pourquoi l'installateur y propose le pack anglais. L'installateur passe ainsi en version 4.1. Technique : Une table française dans le Loc.cs de l'installateur (Lang.Fr, le même jeu de clés que l'anglais et l'allemand, « fr » repris de la culture d'interface du système ; la liste des langues est construite à partir de ComponentsForm._langOrder, plus aucun littéral d'index à maintenir), docs/index-fr.html, et « Patch Notes Sku FR.txt » que release.ps1 recopie sur le site. Les réécritures de la page dans release.ps1 parcourent maintenant une LISTE de pages et s'appuient sur des fragments neutres du point de vue de la langue, pour qu'une page traduite ne puisse pas conserver un numéro de version périmé - exactement la panne corrigée pour le titre en v42.11, une page plus loin ; au passage, le titre de SkuMapper est couvert lui aussi, alors qu'il dérivait sans que personne le remarque. L'annonce Discord se fait désormais par canal : une ligne de webhook peut nommer les langues de ce canal, un serveur uniquement germanophone reçoit donc une seule ligne allemande avec des liens allemands, au lieu de trois langues qu'il n'a pas demandées. Deux menus qui n'étaient qu'un détour ont disparu Simple : Sous « Réglages » se trouvait une entrée « Combat » qui ne contenait qu'un seul élément : « Menu Sku en combat ». Cet élément se trouve désormais directement dans « Réglages, Général », et le niveau « Combat » vide au-dessus n'existe plus. Même chose dans le menu principal : « Outils » ne contenait que « Dial Targeting » et « Soft Targeting » ; les deux sont maintenant dans le « Menu rapide », et « Outils » a quitté le menu principal. Les réglages eux-mêmes ne changent pas, et toutes les valeurs déjà enregistrées sont conservées. Technique : Les trois entrées gardent leurs builders, leur db et leur keyPrefix - seul l'endroit où elles sont accrochées change, les valeurs enregistrées sont donc intactes. « Werkzeuge » est retiré comme module SkuMenu et de SkuMenu:SetRootLayout (SkuZOptions/SkuMenu.lua) ; DialTargetingMenuBuilder et le groupe softTargeting (toujours via IterateOptionsArgs avec aIncludeHidden=true, puisqu'il est marqué forAudioMenu=false) sont désormais accrochés dans le BuildChildren du menu rapide (SkuZOptions/Core.lua). La spécification « Kampf » sort de tSpecs dans SkuCore/Options.lua ; CombatMenuOpenMenuBuilder est maintenant appelé à la fin du builder Général, sur le même réglage combatMenuOpen. Aucun chemin SlashFunc ne pointait vers l'un de ces deux menus, il n'y avait donc rien d'autre à suivre. ------------------------------------------------------------------------------------------------------- Changements dans Sku v42.11 Maître de vol : Sku nomme le prochain point de vol et peut vous y faire descendre Simple : Sur un trajet en taxi chez le maître de vol, Sku annonce désormais quel point de vol arrive ensuite, et vous pouvez y descendre au lieu de voler jusqu'à la destination. La touche pour cela est CTRL-MAJ-E par défaut ; comme toutes les autres touches de Sku, elle est réassignable, listée sous « Navigation et points de passage ». Il y a deux annonces par étape parce qu'elles répondent à des questions différentes : le prochain arrêt au décollage et après chaque saut, et « atterrissage anticipé possible » environ 800 mètres avant. Avant le dernier arrêt, Sku reste silencieux - y atterrir en avance revient simplement à arriver. L'ensemble peut être désactivé sous « Maître de vol ». Uniquement les taxis des maîtres de vol, jamais les véhicules. Technique : Nouveau module SkuCore/taxi.lua. La route est enregistrée, pas devinée : hooksecurefunc sur TakeTaxiNode se déclenche pendant que la carte des taxis est encore ouverte, la seule fenêtre où GetNumRoutes, TaxiGetNodeSlot et TaxiNodeName répondent encore. La liste des arrêts est donc exacte, et chaque arrêt qui y figure est réellement accessible pour ce personnage. Un curseur de proximité la fait avancer à mesure que le taxi survole chaque nœud ; la recherche du nœud le plus proche ne subsiste que comme repli signalé pour un /reload en plein vol, filtré par faction et par découverte. Trois particularités du client sont consignées dans l'en-tête du fichier pour ne pas avoir à être redécouvertes : la demande vous fait toujours atterrir au point de vol SUIVANT (comme le dit aussi TAXI_CANCEL_DESCRIPTION), ce qui explique pourquoi le curseur de route est la bonne réponse ; IsPossessBarVisible renvoie false pendant que PossessButton2 est affiché - le seul élément fiable est MainMenuBarVehicleLeaveButton, lu avec IsVisible, et il est présent pendant tout le vol ; et PLAYER_CONTROL_LOST se déclenche alors qu'UnitOnTaxi est encore false, de sorte que tout nettoyage branché dessus effacerait des données fraîchement enregistrées. Chaque point d'entrée revérifie UnitOnTaxi - le même test que Blizzard utilise pour choisir TaxiRequestEarlyLanding plutôt que VehicleExit. Diagnostics : lignes « taxi: » dans SkuDebugLog et /skutaxi. Les invitations de groupe ne sont plus refusées silencieusement Simple : Fermer le menu de Sku - pour quelque raison que ce soit : sortir des sacs avec Échap, ouvrir une autre fenêtre, un passage en combat - refusait une invitation de groupe en attente, et la personne qui vous invitait voyait un refus. Pour un joueur voyant, il ne se passe absolument rien à ce moment-là ; la boîte de dialogue reste simplement là pendant ses 60 secondes. C'était donc une perte que seuls les utilisateurs de Sku subissaient. L'invitation n'est plus répondue à la fermeture du menu ; elle quitte simplement l'écran et reste accessible sous « Local ». Technique : StaticPopupDialogs["PARTY_INVITE"].OnHide appelle DeclineGroup à moins que dialog.inviteAccepted ne soit défini - et OnHide se déclenche bel et bien sur un simple frame:Hide() (OnHide XML -> GameDialogMixin:OnHide -> StaticPopup_OnHide -> dialogInfo.OnHide). Le OnHide du menu de Sku exécute exactement ce StaticPopup1:Hide(). Nouveau : SkuCore:NeutralizePopupHide(frame), appelé juste avant : pour PARTY_INVITE il pose inviteAccepted afin que le OnHide de Blizzard saute le refus, et marque le cadre pour que notre propre hook de masquage distingue un masquage neutralisé d'une vraie réponse - de simples champs sur un cadre non sécurisé, aucune contamination ; OnShow réinitialise inviteAccepted, de sorte qu'un affichage ultérieur véritable n'est pas affecté. Il n'existe pas d'API « invitation en attente » sur ce client, le cache est donc le nôtre : alimenté par PARTY_INVITE_REQUEST, abandonné sur PARTY_INVITE_CANCEL, sur tout masquage non neutralisé de la boîte de dialogue (une vraie acceptation, un refus ou une expiration) et par sa propre échéance. Pour mémoire, toutes les boîtes de dialogue dont le OnHide est destructeur ont été auditées : PARTY_INVITE (corrigé ici), LEVEL_GRANT_PROPOSED (DeclineLevelGrant), EQUIP_BIND et apparentées (CancelPendingEquip - pas d'indicateur d'esquive, demande un autre correctif) et QUIT (CancelLogout, sans danger). Les demandes en attente restent accessibles dans le menu Local Simple : Quand vous quittez le menu de Sku avec Échap, une boîte de dialogue ouverte disparaît de l'écran - une invocation, la demande de libérer l'esprit, une offre de résurrection, une vérification de préparation. Jusqu'ici elle était perdue jusqu'au prochain /reload, alors même qu'elle restait valide sur le serveur pendant tout ce temps. De telles demandes apparaissent désormais comme une entrée à part directement sous « Local », nommée d'après la demande ; DROITE ou ENTRÉE affiche à nouveau la vraie fenêtre, et Sku y descend pour que vous utilisiez les boutons habituels. Technique : Nouveau module SkuCore/pendingPrompts.lua. Les demandes sont réémises à partir de l'ÉTAT, pas du cadre : l'invocation via C_SummonInfo, la résurrection via ResurrectGetOfferer, la libération après la mort via GetReleaseTimeRemaining plus les options de résurrection personnelle de C_DeathInfo, la vérification de préparation via GetReadyCheckTimeLeft/Status. Un :Hide() brut n'atteint que StaticPopup_OnHide et jamais OnCancel, rien n'était donc jamais refusé ni libéré - PLAYER_ENTERING_WORLD est le seul endroit où Blizzard réaffiche DEATH et CONFIRM_SUMMON, d'où le « perdu jusqu'au /reload ». Tant que sa propre boîte de dialogue est à l'écran, l'entrée est supprimée, elle ne fait donc jamais doublon avec le nœud issu du balayage de la popup. Il est important de noter que ces entrées sont PASSIVES : le prédicat « quelque chose est-il ouvert » dans CheckFrames a un double rôle - il maintient un menu ouvert en vie et il force l'ouverture du menu via SlashFunc. Les demandes en attente n'alimentent que le premier (tPendingOnly), sans quoi une demande accessible en permanence arracherait le menu à chaque mise à jour des sacs ; le même raisonnement les tient à l'écart de combatMenuHasWindow. La première forme était imbriquée, avec ses propres feuilles d'action - fonctionnel, mais trop de navigation. C'est maintenant un unique nœud plat par demande : kind="action" avec dynamic=true, uniquement pour que le gestionnaire de touches achemine DROITE vers OnSelect, plus une surcharge d'OnSelect qui contourne la mécanique de descente générique ; les vrais boutons arrivent par le balayage normal de la fenêtre réaffichée. Échange : ce qui se trouve dans la fenêtre d'échange est désormais annoncé en direct Simple : Faire un clic droit sur un objet pour le mettre dans la fenêtre d'échange était complètement silencieux, et son entrée dans le menu des sacs restait inchangée. Sku dit maintenant « dans l'emplacement d'échange N » ou « emplacement d'échange N vidé », y compris pour le côté de votre partenaire d'échange, ce qui n'était auparavant ni visible ni audible. Les listes « Vos objets » et « Objets du partenaire » se rafraîchissent d'elles-mêmes ; l'entrée « Actualiser » n'est plus nécessaire pour cela. Tant qu'un objet est dans l'échange, son entrée de sac porte un préfixe « en échange », prononcé avant le nom de l'objet, et à l'instant où le marqueur apparaît sur l'entrée où vous vous trouvez, Sku dit seulement « en échange » - il ne vous relit pas tout le nom de l'objet. Et après un échange terminé, l'objet que vous avez donné disparaît désormais de façon fiable de la liste des sacs, au lieu de seulement parfois. Technique : La mécanique existante de vente et de banque ne pouvait pas s'appliquer ici : mettre un objet dans la fenêtre d'échange ne le retire pas du sac. Le client se contente de poser isLocked sur l'emplacement (le propre ContainerFrame de Blizzard réagit à ITEM_LOCK_CHANGED en désaturant simplement l'icône), aucun BAG_UPDATE n'est donc déclenché et il n'y avait rien à quoi les gestionnaires conditionnés puissent réagir. Les objets ne quittent les sacs que lorsque l'échange S'ACHÈVE - et ce BAG_UPDATE fait la course avec le CheckFrames de TRADE_CLOSED, alors que Sku.tBagPostAction avait déjà expiré 2,5 s après le clic droit. Perdre cette course était le symptôme « parfois il reste dans la liste ». Nouveau : Sku.tBagSettleWindow, une seconde barrière plus étroite dans SkuCore:BAG_UPDATE_DELAYED, armée pour 3 s par TRADE_CLOSED, qui pilote le SkuBagIdleRefresh SILENCIEUX - pas la confirmation parlée, puisque aucun curseur n'est ancré sur l'objet échangé - plus un repli d'1 s pour la course inverse. TRADE_PLAYER_ITEM_CHANGED et TRADE_TARGET_ITEM_CHANGED sont enregistrés pour la première fois, dédoublonnés par emplacement selon l'objet et la quantité et conditionnés à TradeFrame:IsVisible pour que les salves de fin d'échange restent silencieuses. Les deux partagent SkuCore:TradeMenuRefresh, une reconstruction silencieuse temporisée. Le marqueur « en échange » est un préfixe : dans une liste de sac il suit le numéro de position, dans la liste « tous les objets » il est ajouté après le tri (comme le préfixe « nouveau »), de sorte que le marqueur transitoire ne déplace jamais un objet hors de sa place alphabétique. L'annonce est accrochée au réancrage silencieux de SkuBagIdleRefresh, qui compare le marqueur sur l'entrée ciblée avant et après la reconstruction et ne parle que s'il est apparu - auto-dédoublonnant, puisque les rafraîchissements suivants le voient des deux côtés. SkuCore:TradePartnerName est désormais la source unique du nom du partenaire, y compris pour l'annonce existante de l'argent échangé. Combat : Sku annonce quand un sort a été interrompu Simple : Sous Hostile -> Incantation, « Annoncer les interruptions » existe désormais séparément pour votre cible actuelle et pour tous les ennemis - exactement comme les annonces d'incantation elles-mêmes. Cela permet d'entendre les interruptions sur votre propre cible sans entendre toutes celles du raid. Quand vous ou votre familier interrompez, vous entendez « Vous avez interrompu le sort » avec le nom du sort ; quand quelqu'un de votre groupe interrompt, « sort interrompu ». Seule l'annonce pour votre cible actuelle est activée par défaut, tous les ennemis reste à activer vous-même ; celles et ceux qui l'avaient désactivée restent tranquilles. Technique : SPELL_INTERRUPT depuis le journal de combat via le nouvel événement du répartiteur SKU_SPELL_INTERRUPT. L'ancien interrupteur unique outputInterrupts est remplacé par yourTargetInterrupts et allEnemiesInterrupts ; seul yourTargetInterrupts est prérempli depuis l'ancienne valeur là où c'était nil, allEnemiesInterrupts démarre désactivé. Avec l'interrupteur global désactivé, une interruption n'est annoncée que si le destGUID de l'incantation interrompue est votre cible actuelle ; avec les deux activés, elle n'est toujours annoncée qu'une fois. « Vous » signifie une affiliation de source MINE, ce qui couvre votre propre familier (le Blocage de sort du chasseur corrompu, par exemple). Combat : les annonces d'incantation peuvent être limitées aux sorts interruptibles Simple : Nouveau réglage « N'annoncer que les sorts interruptibles » (désactivé par défaut) sous Hostile -> Incantation. Une fois activé, Sku ne signale plus les incantations qu'une interruption n'affecterait de toute façon pas. Technique : La valeur de retour notInterruptible d'UnitCastingInfo et d'UnitChannelInfo est une donnée morte sur le client Anniversary 2.5.6 - elle revenait nil à chaque incantation tandis que la 9e valeur de retour livrait le bon spellId ; la propre barre d'incantation de Blizzard force l'indicateur à false pour BC et antérieur. L'interruptibilité provient donc d'une liste statique, nouvelle sous SkuDB/uninterruptibleCasts.lua, importée de ClassicCastbars (licence MIT). Elle couvre l'ancien monde ; des tables de fusion SkuTbcAdditions vides sont prêtes pour nos propres trouvailles TBC. La correspondance se fait par spellID, par nom localisé, ou par npcID plus nom. Ciblage souple : « si aucune cible n'est verrouillée » atteint enfin réellement le client Simple : Le réglage qui détermine si le ciblage souple est utilisé pour interagir n'a jamais rien fait. La touche d'interaction ne se souciait pas de savoir si vous aviez une cible verrouillée - un cadavre ou une chaise à côté captait l'appui de touche quand même. C'est corrigé, et corrigé dans le client plutôt que dans une émulation. À noter : ces réglages du client sont verrouillés pendant le combat. Sku les maintient donc au repos dans l'état où la suppression est déjà en place, au cas où quelque chose vous saute dessus et que vous ne le cibliez qu'une fois le combat engagé. Deux cas limites subsistent : si vous tuez votre cible, l'état persiste jusqu'à votre prochain changement de cible - pendant ce laps de temps les objets proches ne sont pas adressables (le cadavre que vous avez ciblé se pille normalement). Et si vous entrez en combat en tenant une cible non attaquable, le ciblage souple reste actif pour ce combat. Cela demanderait une écriture pendant le combat et ne peut pas être résolu depuis un addon. Technique : Les 32 CVars SoftTarget sont toujours enregistrées dans la 2.5.6 (repérées dans WowClassic.exe) - le client n'est donc pas en cause. Les deux branches de l'ancien if/else écrivaient SoftTargetMatchLocked = 0, du code mort identique dans la v32.31 et dans la v41.04 ; le réglage n'a jamais quitté Lua. La 41.05 a ensuite écrit 1, mais a fusionné l'option 2 dans l'option 1, et SoftTargetWithLocked - la CVar qui régit réellement ceci - n'a jamais été écrite par aucune version de Sku. Mesuré sur la 2.5.6.68575, hors combat, un cadavre et une créature vivante à portée, touche d'interaction pressée avec la créature verrouillée comme cible : WithLocked 0 agit sur la créature et ignore le cadavre, 1 et 2 pillent tous deux le cadavre. Seul 0 supprime donc le ciblage souple tant qu'une cible est verrouillée, et il n'existe pas de valeur intermédiaire « seulement si attaquable ». L'option correspond désormais directement à cela : 0 (toujours) -> WithLocked 2, MatchLocked 0 ; 1 (si aucune cible verrouillée) -> WithLocked 0, MatchLocked 1 ; 2 (aucune cible attaquable) -> MatchLocked 2, avec WithLocked basculé entre 0 et 2 sur PLAYER_TARGET_CHANGED. L'état de repos est 0, parce que WithLocked n'agit que tant qu'une cible est verrouillée - s'y reposer ne coûte rien et couvre le seul cas que les CVars ne pourraient autrement jamais atteindre. 2 n'est désormais écrit que pour le seul état qui a véritablement besoin du ciblage souple AVEC une cible verrouillée : une cible que vous ne pouvez pas attaquer - un cadavre que vous pillez, un PNJ à qui vous parlez, un joueur que vous suivez. Supprimés : le ticker de SkuMob qui réécrivait SoftTargetInteract quatre fois par seconde, son doublon dans PLAYER_TARGET_CHANGED et l'état fantôme interactTempDisabled - cette émulation ne pouvait pas fonctionner du tout, puisque les CVars sont protégées en combat. tSetSoftTargetCVar vérifie maintenant chaque écriture en relisant la CVar (comparaison numérique, pour qu'un « 15.000000 » normalisé ne soit pas un faux échec) et réarme la relecture sur PLAYER_REGEN_ENABLED lorsqu'une écriture est refusée, au lieu de mettre en cache une valeur qui n'a jamais abouti. Une écriture en combat journalise « softTarget refused ... {want=3, got=0} » et lève ADDON_ACTION_BLOCKED. Menu : ENTRÉE continuait d'agir dans le menu invisible après sa fermeture Simple : Après avoir sélectionné un point de passage - et dans des cas semblables - le menu se fermait correctement, mais chaque nouvel appui sur ENTRÉE cliquait encore dans le menu invisible : le son de navigation se jouait, la sélection se relançait (« nouvelle navigation »), et ENTRÉE n'ouvrait plus la fenêtre de discussion. En plus de cela, le menu pouvait rester bloqué ouvert si une touche de déplacement était enfoncée au même instant : le point de passage était posé, mais le menu restait ouvert et gardait les touches capturées. Les deux sont corrigés. Technique : Deux causes distinctes. Premièrement, OnPostSelect continue après l'action qui a fermé le menu : il repointait currentMenuPosition sur le selectTarget - écrasant la remise à la racine faite par la fermeture - et déclenchait son OnEnter ; le OnEnter générique réarmait alors sans condition les liaisons de substitution SKU_KEY_MENULEFTCLICK sur le bouton sécurisé. Les liaisons de substitution sur un cadre MASQUÉ sont pleinement actives, ENTRÉE était donc ressuscitée juste après que OnHide l'avait effacée. Désormais le OnEnter générique n'arme les touches de clic que tant que le bouton sécurisé est affiché, et OnPostSelect s'arrête avant le refocus final quand le menu n'est plus ouvert (le menu de combat sans affichage est exempté via combatMenuActive). Deuxièmement, CloseMenu passe par le même gestionnaire de bascule que l'ouverture, dont les deux barrières de report en cas de déplacement annulaient silencieusement la fermeture - l'une lit les indicateurs de déplacement en direct, l'autre le SkuCore.isMoving mis en cache, ce qui explique pourquoi un chemin passait et l'autre avortait. Les demandes de fermeture contournent maintenant ces barrières ; Hide et ClearOverrideBindings sont sans danger pendant le déplacement, et une fermeture réussie atteint de nouveau les remises à zéro d'openMenuAfter. Par précaution supplémentaire, le ticker de réouverture désarme les indicateurs qui ne peuvent de toute façon jamais se déclencher et ne relance la mise en place de la navigation que lorsque le menu est visiblement ouvert mais insensible aux touches (nouveau : SkuOptions.menuNavKeysBound). Avertissement de collision pendant la marche automatique vers la cible Simple : L'avertissement indiquant que vous marchez dans quelque chose ne s'armait que tant que vous mainteniez vous-même une touche de déplacement. Avec la fonction « Interagir avec la cible » du jeu, le moteur vous y conduit sans qu'aucune touche ne soit maintenue - et là, vous pouviez vous coincer sur un obstacle sans vous en rendre compte. Ces déplacements sont désormais couverts eux aussi. Technique : Un nouvel indicateur EngineMoving, issu de PLAYER_STARTED_MOVING et PLAYER_STOPPED_MOVING, arme l'avertissement basé sur les coordonnées pour les déplacements pilotés par le moteur également. PLAYER_STOPPED_MOVING ne se déclenche pas tant que vous poussez contre un mur - précisément le cas pour lequel l'avertissement existe (démontré par la purge de l'AutoRun périmé). La rotation pure, la chute et le suivi automatique sont exclus ; le suivi a son propre avertissement de collision. Moins d'erreurs en instance et en raid Simple : Deux sources d'erreurs qui produisaient des erreurs Lua constantes à l'intérieur des instances sont corrigées - perceptible sous forme de messages d'erreur qui n'apparaissent plus et de combats de boss plus calmes. Technique : Premièrement, UnitPosition("player") renvoie nil à l'intérieur des instances, des raids et des champs de bataille, si bien que SkuNav:Distance renvoyait nil et que GetUnsortedAvailableQuestsTable plantait sur une concaténation à chaque QUEST_LOG_UPDATE (24 fois en une seule session de raid). La convention existante des 99999 mètres s'applique désormais : la quête reste listée et se trie en dernier. Deuxièmement, ClearOverrideBindings et SetOverrideBindingClick sont protégées en combat, et le pcall qui les entoure ne supprime pas l'événement ADDON_ACTION_BLOCKED (84 fois en un seul combat, plus des avertissements de limite d'exécution de BugGrabber). L'application de la liaison de touche d'AtlasLoot est maintenant sautée pendant le combat et retentée une fois via PLAYER_REGEN_ENABLED. « Interrompre temporairement le suivi pendant une incantation » est réactivé pour un test Simple : Ce réglage figurait dans le menu, mais sa fonction était désactivée depuis l'état amont de la v41.06. Il est réactivé, désactivé par défaut, afin d'établir si ce client permet ne serait-ce que de reprendre le suivi automatiquement. Si vous l'activez, attendez-vous à ce que le suivi ne revienne pas de lui-même après une incantation. Technique : Les corps d'UnfollowOnCast et de FollowOnCast étaient mis en commentaire, pour une raison qui n'a jamais été consignée. Ils répondent maintenant à une question : ce client exécute-t-il FollowUnit depuis un contexte d'événement serveur, c'est-à-dire sans événement matériel, ou est-ce silencieusement bloqué ? Le canal de preuve est la sortie vocale AUTOFOLLOW_END/BEGIN et les lignes « followcast » dans SkuDebugLog, y compris une vérification d'état 0,5 s après l'appel. Corrigé en chemin : dans les gestionnaires CHANNEL_START/STOP, unitTarget et aUnitTarget ne concordaient pas, si bien que l'arrêt et la reprise du suivi ne se déclenchaient jamais pour les sorts à canalisation. Courrier : après l'envoi d'une lettre, Sku relisait plus tard le texte saisi Simple : Un certain temps après l'envoi d'une lettre, Sku se mettait à répéter les caractères qui avaient été tapés - parfois longtemps après la fermeture de la boîte aux lettres. Deux bugs indépendants en étaient la cause. Premièrement, une fois que vous aviez ouvert une boîte aux lettres, elle comptait comme ouverte en interne jusqu'au prochain /reload, si bien que chaque courrier arrivant plus tard reconstruisait le menu et réannonçait l'entrée courante - où que vous soyez dans le menu. Si vous vous trouviez sur un champ de lettre, cette entrée était le texte que vous veniez de taper. Deuxièmement, la lecture caractère par caractère pendant la saisie ajoutait chaque caractère à la fin de la file de parole : si vous tapez plus vite que la voix ne parle, cela accumule un retard, et fermer le champ de saisie n'y mettait pas fin - la lecture continuait simplement. Désormais la lecture suit votre frappe : si elle prend trop de retard, ce qui n'a pas encore été prononcé est abandonné au lieu de traîner. « Envoyé » et la confirmation d'un champ arrivent immédiatement, plus derrière tout ce que vous avez tapé. Et une mise à jour de la boîte de réception n'a d'effet que tant que la boîte aux lettres est réellement ouverte et que vous êtes dans la branche Courrier du menu. Deux petites choses au même endroit : le champ de collage exécutait son action OK n fois à la n-ième ouverture, et les deux champs de saisie de Sku libèrent maintenant le clavier de façon fiable quand ils se ferment. Technique : MailboxOpenFlag était posé dans MAIL_SHOW mais n'était effacé nulle part ; MAIL_CLOSED l'efface désormais. MAIL_INBOX_UPDATE n'arrive pas seulement quand une boîte aux lettres est ouverte - le serveur l'envoie aussi lorsque du courrier arrive plus tard - et il filait sans contrôle dans le OnUpdate générique (SkuZOptions/templates.lua). Ce n'est pas un rafraîchissement discret : il jette les enfants du niveau courant, les reconstruit et appelle VocalizeCurrentMenuName. En plus de l'indicateur, nous vérifions maintenant MailFrame:IsVisible() et remontons la chaîne des parents jusqu'au nœud contributeur Local L["Mail"] - ce n'est que là qu'une reconstruction est un rafraîchissement et non une intrusion. L'appel forcé à SlashFunc sans position de menu ne subsiste que pour le cas authentique de l'ouverture de la boîte aux lettres ; un événement d'arrière-plan ne doit jamais forcer l'ouverture du menu. La lecture de frappe de la v42.08 ajoute au lieu d'écraser à dessein, sans quoi une frappe rapide avale des caractères - mais elle n'avait aucun plafond. À environ 0,4 s par caractère contre quatre à cinq frappes par seconde, la file croît sans limite, et le défilement pousse ce retard dans C_VoiceChat.SpeakText à environ 100 entrées par seconde, là où Sku ne peut plus le rattraper. D'où deux nouvelles méthodes dans SkuVoice-1.0, GetBttsQueueDepth et TrimBttsQueue(aKeep) : seul ce qui ATTEND encore dans la file propre à Sku est abandonné, jamais ce qui a déjà été transmis - c'est pourquoi cela ne peut pas couper un mot en deux, et pourquoi c'est légitime même avec « ne jamais réinitialiser les files », un réglage qui concerne l'interruption d'une parole en cours. Le plafond est de trois caractères en attente. Le retard est abandonné avant l'exécution du rappel OK, et MAIL_SEND_SUCCESS ainsi que la confirmation de champ dans MailEditor parlent maintenant en écrasant plutôt qu'en ajoutant. EditBoxPasteShow attachait son OnEnterPressed via HookScript à CHAQUE appel - HookScript ne remplace pas, il enchaîne - si bien que le rappel OK s'exécutait n fois à la n-ième ouverture ; il vit désormais dans la branche exécutée une seule fois à la création. Les deux champs de saisie appellent ClearFocus lorsqu'ils se masquent, parce que le champ est un petit-enfant du cadre masqué et que le focus clavier resterait sinon sur une zone d'édition invisible. Niveau d'objet : tous les objets étaient annoncés avec le même chiffre Simple : Le niveau d'objet dans les descriptions d'objets était faux depuis qu'il existe. Il n'appartenait pas du tout à l'objet que vous étiez en train de lire - il appartenait au dernier objet consulté quelque part dans Sku, généralement des heures plus tôt, et il le restait pour toute la session. C'est pourquoi il lisait le même chiffre sur chaque objet, et pourquoi c'était si souvent un 1 : une clé ou un objet de quête, le genre de chose qui est consultée tôt et qui est réellement de niveau d'objet 1. Un /reload n'aidait pas, car le mauvais chiffre n'était pas une donnée périmée que l'on rafraîchit - c'était simplement le mauvais objet qui était interrogé. Ce même mauvais objet décidait aussi de la qualité ajoutée aux noms d'objets. Les deux sont corrects maintenant. Le niveau d'objet n'est en outre annoncé que pour l'équipement que vous pouvez réellement porter : les clés, les objets de quête, les composants et les consommables sont tous de niveau d'objet 1 par définition, cette ligne ne disait donc rien à leur sujet et elle a disparu. Elle a également disparu des endroits où elle n'avait jamais sa place, comme les descriptions de buffs et les résistances de la feuille de personnage. Technique : Le GetItem() d'une infobulle est un cache rémanent, pas une description de ce que le cadre affiche : ClearLines() ne le réinitialise pas, et un SetX() qui échoue à remplir non plus - un objet non mis en cache, le conteneur de banque -1, ou toute infobulle de sort, d'aura ou de caractéristique qui n'a aucun objet. SkuScanningTooltip est créée une seule fois à PLAYER_LOGIN et n'est jamais réattribuée, si bien que son GetItem() continue de répondre avec le dernier objet qui s'est jamais résolu à travers elle. En plus de cela, TooltipLines_helper lisait le lien depuis SkuScanningTooltip quelles que soient les régions qu'on lui avait passées, et la plupart des appelants lui passent celles de GameTooltip. Deux défauts plus petits aggravaient le tout : la deuxième valeur de retour de GetSpell(), qui n'est pas un lien, était affectée à ItemLink et transmise à GetDetailedItemLevelInfo ; et un objet non mis en cache produit un lien VIDE, qui est vrai en Lua et passait tout droit à travers la garde « if ItemLink then ». La résolution vit désormais dans SkuUtil - TooltipFirstLine, TooltipItemLink, ItemQualityString, ItemLevel - et ne fait confiance à GetItem() que lorsque le nom qu'il rapporte correspond à la première ligne affichée de l'infobulle, qui est le seul point de vérité disponible. Les trois blocs dupliqués dans LocalMenu et utilities s'y ramènent ; tScanTooltipRegions résout lui-même le lien, la qualité et le niveau d'objet et renvoie le lien validé, de sorte que les appelants en aval obtiennent celui que le texte décrit. La condition « équipable » lit equipLoc depuis GetItemInfo et se rabat sur C_Item.GetItemInventoryTypeByID tant que le cache d'objets est encore froid. Français : Sku est désormais disponible en français Simple : Sku peut désormais être utilisé en français - testé sur un client français. Environ 95 pour cent des textes de menu et d'annonce sont traduits (3063 entrées sur 3220), et tout le reste retombe automatiquement sur l'anglais, il n'y a donc jamais de trou ni d'entrée vide. Deux choses qui manquaient encore ont été ajoutées : les noms de routes et de points de passage de la chaîne de traduction ont leur propre table française de 153 entrées, dont les noms de lieux sont importés d'une capture faite sur un vrai client français plutôt que devinés, et les 158 endroits où du texte est conservé de façon bilingue dans le code portent maintenant aussi une version française. Les noms du JEU - créatures, quêtes, objets, éléments du décor - sont importés en bloc depuis Questie, ce sont donc les propres chaînes françaises de Blizzard et non des traductions automatiques, et elles correspondent à ce qu'affiche un client français, ce qui est précisément ce qui garde les noms de routes et de points de passage cherchables. Les noms de zones viennent directement du client. La sortie vocale n'existe toujours qu'en allemand et en anglais ; toutes les autres langues de client utilisent les fichiers audio anglais. Pour les joueurs allemands et anglais, rien ne change dans le texte - mais l'utilisation de la mémoire, si : Sku ne construit plus que les tables de noms de sa propre langue, plus l'anglais comme repli, au lieu de toutes les construire systématiquement. Technique : Sku.Locs est maintenant une liste ORDONNÉE de trois locales, et les noms de routes empaquetés dans SkuNav:LoadDefaultMapData sont découpés par position d'après elle au lieu de l'être par un string.match avec un « (.+) » gourmand de part et d'autre du séparateur. Cette ancienne analyse n'était pas seulement limitée à deux locales : « .+ » est gourmand, donc avec trois champs « a », « b » et « c » les deux premiers seraient arrivés comme un unique champ anglais et seul le dernier comme le champ allemand. Les fichiers ayant moins de champs que de locales restent valides et retombent sur enUS. Sku.LocAudio sépare la locale AUDIO de la locale de données, et deux bugs préexistants sont apparus et ont été corrigés en chemin : SkuAudioFileIndexIntegrated[Sku.Loc] n'était pas protégé et indexait nil sur toute langue de client sans sous-table propre, et GetAudiodata n'avait aucun repli sur la casse alors que l'index enUS stocke ses identifiants en minuscules et que les appelants les passent avec une majuscule - ce qui explique pourquoi l'annonce d'entrée et de sortie de zone est silencieusement morte sur les clients anglais. ChunkLoader a désormais une barrière de locale : auparavant il construisait TOUS les blocs enregistrés quelle que soit la langue du client, si bien qu'un client allemand matérialisait aussi les tables de noms anglaises complètes et inversement. La règle est maintenant « locale active plus enUS », avec objectLookup.deDE structurellement exempté parce que l'objectLookup anglais est DÉRIVÉ de son ensemble de clés - le filtrer naïvement aurait laissé les clients anglais sans aucun nom d'élément du décor. Les quatre fusions .deDE codées en dur sont devenues SkuDBMergeLocales, ce qui corrige au passage un bug latent : un client français aurait obtenu les noms TBC de base mais silencieusement aucun nom WotLK. Après la fusion, les trous de la locale active sont comblés depuis enUS (Questie n'a pas de nom français pour environ 2700 identifiants que Sku connaît) ; délibérément une étape de construction plutôt qu'une métatable __index, parce que pairs() ne traverse pas __index et que plusieurs consommateurs ÉNUMÈRENT ces tables au lieu de les indexer - une métatable aurait corrigé les recherches et laissé discrètement les menus incomplets. deDE et enUS sont exclus du comblement pour que la construction allemande reste identique octet pour octet (vérifié, 37 jeux de données sur 37). Le fichier de locale lui-même est Sku/locales/frFR.lua, généré à partir de fichiers partiels : les entrées non traduites conservent leur valeur anglaise, la table est donc toujours complète et du Lua valide, et AceLocale renvoie de toute façon nil pour de/en - là, le fichier ne coûte qu'une analyse et rien d'autre. Les espaces de début et de fin sont porteurs de sens dans cette table mais ne survivent pas à un aller-retour TSV, ils sont donc réappliqués depuis chaque original anglais pendant l'assemblage, et le validateur contrôle la chaîne FINALE pour le nombre de marqueurs, les espaces en bordure et le nombre de points-virgules. Pour tester sans un second client, il y a /skudebug locale , qui force la locale de DONNÉES et persiste ; elle s'applique à ADDON_LOADED plutôt qu'à PLAYER_LOGIN, parce que la construction différée des routes (mesurée à 1,87 s contre 2,01 s pour PLAYER_LOGIN) décide déjà quels champs de noms sont matérialisés. Trois erreurs bloquantes sur un client forcé en français sont corrigées : maps.lua est une table simple, non découpée en blocs, et ne portait AreaName_lang/Name_lang que pour deDE et enUS - la locale manquante est maintenant remplie une fois à ADDON_LOADED depuis C_Map (GetAreaInfo et GetMapInfo ; 2819 entrées, dont 1672 issues du client), ce qui donne de vrais noms Blizzard sans avoir à livrer de données de noms de zones ; SkuDB.SpellDataTBC imbrique ses locales À L'INTÉRIEUR de chaque entrée, si bien que rien ne les a jamais localisées et que onze endroits levaient une erreur en frFR - la locale active pointe maintenant sur la sous-table anglaise par référence (pas de copie, pas de chaînes supplémentaires), ce qui suffit pour des noms utilisés surtout comme clés de correspondance d'auras ; et SkuDB.objectResourceNames est indexé par nom d'élément du décor et par locale et était lu sans protection pendant la construction du cache de points de passage - un repli anglais y aurait été faux (il n'aurait jamais correspondu et aurait silencieusement transformé chaque minerai et chaque plante en point de passage ordinaire), l'ensemble est donc dérivé à travers les identifiants d'objets à la place. Toute la branche française a depuis été jouée de bout en bout sur un client français et fonctionne : menus, noms et routes s'affichent en français, et là où aucune traduction n'existe, le repli anglais prend le relais sans trou. La table de routes routeOverrides_frFR.lua et le troisième argument sur chaque site Sku.deEn en font partie. Comme zhCN et ruRU tournent avec Sku.Loc = enUS, la même passe les couvre aussi. Page de téléchargement : le titre annonçait une version périmée Simple : Sur la page de téléchargement, le titre de l'addon principal indiquait encore « Version 42.06 » alors que le lien en dessous pointait correctement sur la 42.10. Comme les utilisateurs de lecteur d'écran naviguent par titres, la première chose annoncée dans cette section était la mauvaise version - on aurait cru que la page servait un Sku obsolète. Le titre et le lien concordent désormais. Technique : release.ps1 réécrivait le href et le texte du lien à chaque publication mais jamais le titre, qui était donc figé depuis la 42.06 (17/07/2026) sur quatre publications. Le titre fait maintenant partie de Set-DocsSkuLink et ne peut plus dériver. Le zip lié était correct depuis le début (vérifié : Sku-42.10.zip contient « ## Version: 42.10 »). Balises : un son distinct pour le dernier point de passage d'une route Simple : Il existe désormais un troisième son de balise à côté de ceux des petites et des grandes balises : le son de la dernière balise d'une route. Il ne se joue que pour le dernier point de passage d'une méta-route, vous pouvez donc entendre si le point vers lequel vous marchez est la destination ou seulement une étape de plus. Le réglage se trouve sous Moniteur -> Balise, juste en dessous des sons de petite et de grande balise, et sa valeur par défaut est Balise 3. Suivre un point de passage isolé est inchangé - cela utilise toujours le son petit ou grand selon la taille du point de passage. Technique : Nouveau réglage de profil SkuNav beaconSoundSetLast (« Beacon 3 » par défaut), déclaré dans le schéma SkuNav et ajouté à la liste blanche explicite des nœuds du sous-menu Balise dans SkuCore/aq.lua - cette liste n'est pas la table args complète, un nouveau nœud d'option doit donc y être nommé aussi, sans quoi il n'apparaît jamais dans le menu. GetBeaconSoundSetName prend un deuxième argument et renvoie le jeu de la dernière balise lorsqu'il est vrai, laissant intact le comportement étroit/large et les appelants à un seul argument (la balise de panique, le hook TomTom). Le nouveau SkuNav:IsLastMetaRouteWp compare le NOM du point de passage à la dernière entrée de metapathFollowingMetapaths[target].pathWps plutôt que de lire metapathFollowingCurrentWp, parce que SelectWP s'exécute avant que cet index ne soit avancé - un test sur l'index serait décalé d'un cran. Passer par le nom couvre aussi le saut en avant avec MoveToWp et le départ en route inverse, qui posent tous l'état de la méta-route avant d'appeler SelectWP. De meilleurs réglages par défaut pour les nouveaux joueurs Simple : Installer Sku à neuf part désormais d'une configuration utilisable au lieu de vous laisser trouver l'essentiel d'abord. Quelques touches sont préassignées, la discussion est répartie de façon sensée, et certaines annonces sont actives dès le départ - globalement la façon dont les joueurs expérimentés le configurent de toute manière. Chacun de ces réglages par défaut reste à sa place habituelle dans le menu et peut être modifié comme toujours. Les profils et personnages existants ne sont pas touchés : un réglage par défaut ne s'applique que là où rien n'a jamais été enregistré. Pour récupérer les nouvelles touches, utilisez Assignations de touches Sku -> tout réinitialiser - attention, cela réinitialise vraiment TOUTES les touches, y compris celles que vous avez assignées vous-même. Technique : Modifications de valeurs par défaut uniquement, dans SkuZOptions/SkuKeyBinds.lua (skuDefaultKeyBindings), SkuChat/Core.lua et Options.lua (ChatFrameDefaultTabs plus le jeu d'onglets par défaut), SkuCore/Options.lua (SkuCore.defaults) et SkuCore/aqCombat.lua (aqCombatOnLogin). Les valeurs enregistrées survivent, parce que chaque endroit n'écrit que sur nil et qu'AceDB ne remplit les valeurs par défaut que dans les trous. Deux vrais changements de comportement les accompagnent : le lieur dans SkuNav:CreateSkuNavMain n'appliquait jamais que .key, si bien qu'une SECONDE touche assignée par l'utilisateur ne faisait absolument rien pour toutes les actions de navigation (SkuKeyBindsMatchKey l'avait compris depuis toujours) - il passe maintenant par SkuKeyBindsGetKeys et lie les deux. Et comme SkuChat émet un message une fois par onglet où le type est activé, un type de message ayant son propre onglet doit être coupé dans tous les autres onglets, sinon il est lu deux fois. Hôtel des ventes : acheter plusieurs fois le même objet fonctionne de nouveau Simple : Acheter plus d'une enchère du même objet achetait la première puis s'arrêtait sur « erreur interne des enchères » ; le second achat n'était jamais proposé à nouveau. Les deux sont corrigés - les achats suivants aboutissent, et le message d'erreur intempestif du serveur n'est plus lu à voix haute pendant qu'une recherche est en cours.