AppearanceMatic

Star appearances and mounts in the Wardrobe and Mount Journal, see every alt's raid lockouts for them in one panel, and get guided door to door through portals, boats and flight paths. 3D try-on on any tooltip, collected alerts.
Back to Files

v0.5.4

File nameAppearanceMatic-v0.5.4.zip
Uploaded
Sep 2, 2026
Downloads
8
Size
5.4 MB
Flavors
Retail
File ID
8792544
Type
R
Release
Supported game versions
  • 12.1.0

What's new

AppearanceMatic Change Log

[v0.5.4-release] - 09/02/2026

In-Game

  • The try-on preview beside the wardrobe no longer stands in shadow: the character is lit evenly whichever way you turn it and wherever you happen to be standing.
  • The star on wardrobe appearances and mount journal rows is bigger and easier to hit.

Fixed

  • The wardrobe try-on panel's DressUpModel never set its own light, so it took the world's directional light with it and the character rendered dark at some facings and locations. It now installs the same ambient-only light Blizzard's wardrobe models use (SetLight(true, { omnidirectional = false, point = (-1, 1, -1), ambientIntensity = 1.05, diffuseIntensity = 0 }), from WardrobeItemsModelMixin:OnLoad), once, when the model is built. tests/preview_light_spec.lua PREVIEW_LIGHT_PASS.

Changed

  • Wardrobe star button 18 -> 31, its route icon 18 -> 25; mount journal star 22 -> 39, its route icon 22 -> 31.

[v0.5.3-release] - 08/31/2026

In-Game

  • The Dalaran and Garrison stones speak only when truly needed: standing near a decent portal chain they stay quiet, and they step in only when using one saves serious travel or no portal chain exists at all. Your own Hearthstone and class recall behave as before.
  • Standing in the city your hearth is set to never hears "use your Hearthstone" anymore; the stone only speaks up when it would actually take you somewhere.
  • The moment your hearth lands, "Use your Hearthstone" leaves the guide and the route re-checks right away, instead of showing the stone for a few more seconds; a canceled cast just carries on.
  • The last of the frame skips: the world-boss check no longer sweeps every zone in one gulp; it pays a few zones per frame, so the occasional single-frame hitch it caused (about 157ms every 30 seconds while a route ran) is gone.
  • Stopping a route now takes its map pin with it; no more waypoint left behind that you cannot get rid of.
  • The guide stops flip-flopping between flight points: a chosen "pick it up on the way" stop stays chosen while it is on your way, an announced flight keeps its landing while it still pays, and a detour never costs more than a stone's throw extra however long the ride.

Fixed

  • Special stones (prio 2/3) now need a real win to be suggested: their score must beat the chain by more than three chain hops' worth (R.SPECIAL_HEARTH_MARGIN, default 3 x CHAIN_HOP_YARDS), or there must be no chain at all; his diag case (Garrison 500 vs chain 5073 while beside Silvermoon's portals) goes silent, the stranded ambiguous-bind case still rescues. The bag stone and class recall keep the plain comparison. tests/route_spec.lua HEARTH_SPECIAL_ONLY_WHEN_NEEDED_PASS.
  • The hearth's same-continent suppression compared only physical world instances, and zones like Quel'Thalas live on their own instance (530) while the bind area logs the logical continent (Eastern Kingdoms), so a Silvermoon-bound player standing in Silvermoon was offered the stone: HearthHop now also walks the player's map parents to its continent map and excludes candidates matching that logical identity. tests/route_spec.lua HEARTH_HOME_CITY_QUIET_PASS.
  • The hearth-cast hold remembers the map the cast started on: ending on a different map (landed) clears the hearth hop, the arrow and the query throttles so the same tick recomputes fresh; ending on the same map (canceled) resumes without a flap. tests/route_tick_spec.lua ROUTE_TICK_HEARTH_LANDING_PASS.
  • The ActiveWorldBossSet sweep is sliced across frames: with a profiler clock present it processes zones under a 4ms budget per frame (one slice per GetTime timestamp), the last committed answer standing mid-scan and everything (vignettes, continent skulls, network union) committing atomically at the end; without debugprofilestop (test harness) it stays fully synchronous. InvalidateIndexes drops an in-flight scan. His second capture had it at 157ms worst inside a single frame. tests/route_untracked_spec.lua WORLDBOSS_SLICED_SWEEP_PASS.
  • /am stop left the Blizzard user waypoint standing (only /am reset cleared it): Route:PlacePin marks the pin as the route's own and Route:Stop clears it (C_Map.ClearUserWaypoint, super-track flag off). tests/route_tick_spec.lua ROUTE_TICK_STOP_CLEARS_PIN_PASS.
  • The learn detour and the taxi landing re-optimized every 0.3s tick with no hysteresis (his log: 12 hop changes in 6 minutes, each a fresh pin and chat line, including "Dragonscale Basecamp, The Waking Shores on the way" from Ohn'ahran Plains): the chosen learn stop is sticky while still on the way (learnNode on the hop), the announced taxiDest holds while it still beats the gate, and detour extra is capped at 1000 yards absolute on top of the 15% (LEARN_DETOUR_MAX_EXTRA). tests/route_spec.lua TAXI_LEARN_STICKY_PASS, TAXI_DEST_STICKY_PASS.

[v0.5.2-release] - 08/31/2026

In-Game

  • Riding somewhere with no useful flight point learned, the guide swings you past an unlearned one that lies on the way, so you pick it up and the next trip flies.
  • The flight assist no longer aims at a flight point in another zone's corner of the world: with few flight points learned it now says plainly to ride to the entrance instead of inventing a detour like flying to Bel'ameth for an Azure Span raid.
  • Flying no longer stutters with the panel open: the panel used to redraw itself for every zone's map pins streaming past, a visible frame skip each time; now it redraws only when an access line truly changed.
  • While your Hearthstone cast is running, the guide holds still instead of flashing the flight-master route for those ten seconds; it picks up again where you land.
  • The guide's spoken portal and flight emotes are limited to one every 30 seconds; a quick change of answer is still told to you privately, so the room never hears it twice.

Fixed

  • Learn-on-the-way detours: when SameContinentHop answers "ride there", Route:LearnDetour checks the undiscovered flight points of both the player's and the destination's maps and, if going through one costs at most 15% extra (LEARN_DETOUR_RATIO), makes it a taxi-learn stop named in the hop (route_hop_learn_fp_fmt, enUS/enGB, remaining locales at the release gate); passing it or learning it recomputes naturally, and an undiscovered node is still never boarded. tests/route_spec.lua TAXI_LEARN_DETOUR_PASS.
  • Off-map taxi nodes are dropped: C_TaxiMap.GetTaxiNodesForMap returns the whole network in the asked map's coordinate space (positions past 0..1 for other zones' nodes), and worldFromMap extrapolated outside the map region is unreliable, which made a Waking Shores flight master score as "beside the player" in Ohn'ahran Plains and Bel'ameth as the landing for a Brackenhide Hollow route (his diag: lastHop at -0.303,1.332). Route:TaxiNodes keeps only nodes with map coordinates inside 0..1. tests/route_spec.lua TAXI_OFFMAP_DROPPED_PASS.
  • The in-game profiler named the stutter: BuildContent at 141ms mean / 370ms worst, asked for every ~5s by GATE_EVENTS (AREA_POIS_UPDATED and kin fire for every zone a flight streams). The events now call Lockouts:RefreshGates(), which recomputes the handful of cached gate answers and reports whether any moved (state, text or textRich), and the rebuild is scheduled only on a true change. tests/gate_state_spec.lua GATE_REFRESH_PASS.
  • Mid-cast the route no longer recomputes: the stone's own cooldown starts with the cast, which made Route:HearthHop non-viable and flipped the answer to the flight master until the hearth landed; Route:Tick now holds while UnitCastingInfo("player") reports a cast and the shown hop is the hearth, the same hold a taxi gets. tests/route_tick_spec.lua ROUTE_TICK_HEARTH_CAST_HOLD_PASS.
  • Aloud hop announcements rate limit to one per 30 seconds (Route.lastSpokeAt): an aloud-worthy change inside the window downgrades to the private tell, the arrow and pin still move; past the window the emote returns. tests/route_tick_spec.lua ROUTE_TICK_EMOTE_RATE_LIMIT_PASS.

[v0.5.1-release] - 08/31/2026

In-Game

  • A world boss group now routes to the boss that is actually up this week, straight to its world quest on the map, and the guide names that boss when it starts. In the panel the active boss leads its group with an "up this week" tag beside its name, so one look says which boss the week gives you.
  • Adding something to the route while searching clears the search box, so the panel and its Routing section come straight back.
  • Adding a world boss group to the route looks for the live boss right then, no waiting.
  • Boss quests titled with the boss's name ("DEADLY: ...") are found even when the game gives them no world boss tag, which is how the older weekly bosses actually surface.
  • A route brought back by a reload while parked at a lair moves onto the live boss by itself, same as a fresh one.
  • When the week turns, a world boss route follows the rotation to the new boss on its own; nothing to clear or re-add.
  • A world boss group that cannot drop anything you starred this week steps aside like a finished raid, and steps back in when its week comes.
  • Access lines for anything already unlocked show in the same cream white as the rest of the panel; orange now always means something still stands in the way.
  • Searching puts the best hits first: names that start with what you typed lead, names that merely contain it follow, and groups that only hold a matching item come last.
  • Searching "cloth", "plate" or "mount" shows just those rows inside each group, not everything the group drops.
  • Search by what a thing is: type "plate" or "cloth" for armor of that kind, a weapon type like "sword", or "mount" for every mount, in your own language.
  • While a Hearthstone hop is up, the stone itself shows on the guide instead of a direction arrow, and the guide arrow is a touch bigger.
  • Drag the guide arrow anywhere and it stays there, through new hops and reloads alike; an arrow never moved starts just right of top center, out of the way of the action.
  • A far stop on your own continent sends you to the nearest flight master first, even when the game's own tracker picked the stop.
  • The guide can now tell you to use your Hearthstone (or the Dalaran and Garrison stones) when it is ready and clearly faster than the portals, and a new toggle under the Guide arrow settings turns that off. On cooldown, the portals and flight masters guide as always.
  • The skull the map draws for a rotating boss is read directly, so bosses like Calamir route by name even when the game gives them no quest or journal pin.
  • A route that had to start at a known lair moves itself onto the live boss as soon as the game shows its skull, naming the boss in chat as it does.
  • When no world boss is visible to your character at all, the guide says so as it heads to a known lair, instead of walking you somewhere without a word.
  • The boss the week gives you is read straight off the continent map, the same skull you would check yourself, so older groups like the Broken Isles crown their live boss too.
  • Ordos on the Timeless Isle shows why he is out of reach: a red line under his name names the legendary cloak quest this character still needs, and disappears once it is done.
  • Baradin Hold and Vault of Archavon now say who holds their zone at a glance: the access line shows the map's own control icon for Tol Barad or Wintergrasp and the ruling faction's name painted in that faction's color, updating live as control flips.
  • The game only tells anyone who holds Tol Barad or Wintergrasp while they are near it, so the addon now remembers the last holder it saw: visit the zone (or catch its map pin) once and the faction badge shows from anywhere, counting down to the next battle; once a battle could have flipped control, the line says who held it when last seen instead of guessing.
  • Until a holder has been seen, the access line says where the answer starts filling in: get near the zone once and it shows from anywhere.
  • The control line counts down to the next battle even while the zone is yours, and while a battle is running (up to 30 minutes past its start) it says the battle is underway right now and who held the zone going in.
  • AppearanceMatic players now help each other see: guildmates, groupmates and battleground raid mates running the addon quietly trade what they saw, so one of them passing Tol Barad or Wintergrasp lights the faction badge and battle timer for everyone, and a boss scan from any of them fills in the week's "up this week" tag even for a character who cannot see the quest yet. Nothing to set up, it just works between addon users.
  • A world boss you cannot see yet says why: groups whose weekly boss needs that expansion's world quests unlocked first (Broken Isles and later) show the unlock quest on the group, in its tooltip, and in chat when the route starts, so the quest to do is always named.

Added

  • The Hearthstone joins the route options: when a trip crosses continents, the guide weighs the player's bound Hearthstone, the Dalaran Hearthstone and the Garrison Hearthstone against the best portal chain (Route:HearthHop, scored beside BestGatewayToward); a stone that is owned, off cooldown and clearly cheaper becomes the hop ("Use your Hearthstone", localized stone names via C_Item.GetItemInfo), and on cooldown or with the new Guide arrow toggle off (useHearth, default on) the portals and flight masters stand exactly as before. Bind locations resolve by zone name; a bind the map index cannot name simply leaves the regular stone out. Three new keys (toggle_hearth_hops, toggle_hearth_hops_desc, route_hop_hearth_fmt) in all 12 locales. tests/route_spec.lua HEARTH_HOP_PASS.
  • Search understands what a row is: gear answers its own localized type and subtype straight from the item data ("plate", "cloth", "sword", no list to maintain, works in every language), and every mount row answers the localized word for mount ("mount", "reittier", "montura"; lock_search_type_mount, all 12 locales). tests/show_filter_spec.lua SHOW_FILTER_GEAR_TYPE_PASS, SHOW_FILTER_MOUNT_KEYWORD_PASS.
  • The control gate line carries the controlling faction's badge: the live Tol Barad control pin's own icon (C_AreaPoiInfo.GetAreaPOIInfo atlas when present, else its textureIndex cell on Interface\Minimap\POIIcons via C_Minimap.GetPOITextureCoords) plus the holder's name in Blizzard's faction colors; Wintergrasp, which has no control pin in AreaPOI.db2, uses the same shipped keep-icon indexes (46/48, controlIcon on Data/Gates.lua control rows). GateState keeps text escape-free for route titles and adds textRich for the panel, route section and wardrobe tooltip (Lockouts:ControlBadge). tests/gate_state_spec.lua GATE_CONTROL_BADGE_PASS.
  • Live probes proved Blizzard serves the Tol Barad control pins, GetAreaPOIInfo by ID and GetOutdoorPvPWaitTime only near the zone (all nil from another continent, map open or not), so control sightings persist account-wide in db.global.zoneControl (holder, seenAt, battleAt from the observed wait): a remembered holder answers as fact until battleAt passes (control flips only when a battle ends), then the line falls to a last-seen wording (gate_control_last_seen_fmt, enUS/enGB now, remaining locales at the release gate). tests/gate_state_spec.lua GATE_CONTROL_MEMORY_PASS.
  • A player network over addon messages (Network.lua, prefix APPRMATICNET, raw C_ChatInfo since retail carries addon data only on GUILD/RAID/PARTY/INSTANCE_CHAT, no YELL/SAY/CHANNEL): each client broadcasts only its own sightings (control stones: zoneControl entries without a peer via; boss scans: activeBossSet ids) on login and a 60s changed-only ticker, tiny pipe-format messages under the 255-byte and burst limits; instance groups (battlegrounds, dungeon and raid finder, IsInGroup(LE_PARTY_CATEGORY_INSTANCE)) ride INSTANCE_CHAT; receivers merge newest-wins into db.global.zoneControl (gates invalidated, panel rebuilt) and week-scoped db.global.bossNetwork (unioned by ActiveWorldBossSet, expiring at the weekly reset via C_DateAndTime.GetSecondsUntilWeeklyReset). Peer entries never rebroadcast, so the mesh cannot echo. /am diag prints the network line. tests/network_spec.lua NETWORK_SPEC_PASS, tests/route_untracked_spec.lua NETWORK_BOSS_UNION_PASS.

Fixed

  • A group kept in the results only by its contents shows only the rows that answer: searching "plate" brings the plate pieces without the raid's whole wardrobe, "mount" brings the mount rows alone, and a boss whose own name answers keeps all of its rows. Name-matched groups still render whole. tests/show_filter_spec.lua SHOW_FILTER_ROW_LEVEL_PASS.
  • Search reads what was typed the way a person means it: a query that starts a word in the raid's own name ranks first ("und" leads with Liberation of Undermine), a name it merely sits inside (Blackrock Foundry) comes after, then zone and expansion hits, and content-only matches last; nothing is hidden, only ordered. tests/show_filter_spec.lua SHOW_FILTER_WORD_START_PASS.
  • The stones line up by succession: the bag Hearthstone leads whenever it is viable, the class recall (Astral Recall, known and off cooldown) follows, and the Dalaran and Garrison stones come last; score only breaks ties on a rung. Spell hops carry hearthSpellID, say the spell's own name and show its icon. The guide icon draws at 85% of the frame (Arrow.IconInset) so it reads as an item, not a fill, and the sprite swaps cleanly between icon and arrow modes. tests/route_spec.lua HEARTH_PRIORITY_PASS; tests/guide_arrow_spec.lua GUIDE_ARROW_ICON_INSET_PASS.
  • The bag stone leads from an inn bind too: GetBindLocation() names the inn's area (his diag: "Wayfarer's Rest", the Silvermoon City inn), which is no uiMap, so the bind rungs were never built and the Garrison stone won by default. A shipped area table (Data/BindAreas.lua, generated by tools/gen-bind-areas.py from Blizzard's AreaTable and Map tables, areaIDs per world continent) resolves the bind name to its continent through the client's own localized C_Map.GetAreaInfo (Route:BindAreaContinent, scanned once per bind name); a name two continents share stays unresolved and the line moves on to the next stone. Map-name binds match in any casing now (Route:ResolveBindMaps), and /am diag gained a hearth: line telling the bind, its resolution and every stone's verdict from the last routing pass. tests/route_spec.lua HEARTH_BIND_CASE_PASS, HEARTH_BIND_AREA_PASS.
  • A moved guide arrow stays put: the drag handler saved only the anchor's offsets while dropping which corner the client re-anchored by, then restored them against CENTER, so the frame landed somewhere else after a reload. savePoint keeps the whole anchor (point, relPoint, offsets) and restorePoint re-applies it exactly; the default position is the operator's own tested spot (TOP anchor, x 30, y -4, read from his live layout) in Core.lua, the restore fallbacks and Arrow:ResetPosition. tests/guide_arrow_spec.lua GUIDE_ARROW_POINT_PASS.
  • A hearth hop shows the stone, not a bearing: the guide swaps the arrow for the chosen stone's own item icon (Arrow.ItemIconForHop, hearthItemID on the hop) with no rotation and no "no bearing from here" line, and the arrow frame grew 15% (56 to 64) for readability. tests/guide_arrow_spec.lua GUIDE_ARROW_ITEM_HOP_PASS.
  • The stone competes with the game's tracker too: HearthHop lived only in the table router, and a tracked route (his Brackenhide case) bypassed it entirely, so a ready Hearthstone was never weighed; a hearth that beats the whole portal chain toward the final target now outranks the tracker's hop as well. tests/route_spec.lua TRACKED_HEARTH_PASS.
  • The game's tracked answer flies too: a tracked hop thousands of yards away on the player's continent runs through the same flight-master assist as the table's hops ("Flight master: X, then fly to Y" instead of a four-thousand-yard walk to a portal); near hops, taxi answers and cross-continent answers stand untouched. tests/route_spec.lua TRACKED_TAXI_ASSIST_PASS.
  • Search ranks by relevance: a raid whose own name, zone or expansion carries the query renders above raids that only match through a boss or item inside, so searching "under" leads with Liberation of Undermine instead of a raid that merely drops a Thunder-something. No fuzzy matching involved: the item search was and stays plain substring; only the order changed. tests/show_filter_spec.lua SHOW_FILTER_NAME_FIRST_PASS.
  • An open gate reads calm: unlocked access lines render in the panel's own cream white, only blocked and unknown gates keep the legendary orange (post-0.5.0-final). tests/gate_state_spec.lua GATE_OPEN_CREAM_PASS.
  • A no-chance week acts like a done lockout: when the group's active boss is positively known and none of the starred bosses is it, the group sinks with the done raids, the route walk passes over it (Lockouts:WorldBossNoChance, noChance on the route record), and it comes back alive when the rotation turns or the active boss is unknown. Single-drop rows keep their behavior when their group cannot be judged. tests/route_untracked_spec.lua WORLDBOSS_NOCHANCE_VERDICT_PASS, WORLDBOSS_NOCHANCE_SKIPPED_PASS; tests/route_panel_spec.lua ROUTE_PANEL_NOCHANCE_SINKS_PASS.
  • The weekly turn is followed: a world-boss route recognized by its group (route.target.raidName) retargets onto the new live boss when the rotation moves, whether it stood on the shipped anchor or on last week's boss spot; a route already standing on the live boss is never restarted. tests/route_untracked_spec.lua WORLDBOSS_WEEKLY_TURN_PASS.
  • A route resumed onto a group's anchor upgrades too: the anchor flag does not survive a reload, so the route tick now recognizes a target standing on a world-boss group's own shipped anchor and moves the trip onto the live boss the moment the scan finds one. tests/route_untracked_spec.lua WORLDBOSS_RESUME_UPGRADE_PASS.
  • The boss quest that answers no tag is caught by its title: the map-630 probe showed the live boss's quest sitting in the task list ("DEADLY: Withered J'im") with a nil tag that even GetQuestTagInfo never resolves, so the sweep kept discarding it. Every titled task quest is a candidate now; the boss-name containment match decides, and the WorldBoss tag keeps its pin-proximity fast path. tests/route_untracked_spec.lua WORLDBOSS_TAGLESS_QUEST_PASS.
  • The vignette layer joins the hunt: the full pin dump proved journal pins exist only for the permanently-up bosses (Sha of Anger through Kazzak and the BfA statics), rotating bosses never get one, and the skull the zone map draws for them is a vignette. The active scan now keeps every world-map vignette by name (C_VignetteInfo.GetVignettes/GetVignetteInfo, live and on the world map only); a vignette named for a group's boss marks it live and WorldBossSpot resolves its position across the zone maps (GetVignettePosition) and routes to it by name. /am diag worldboss line shows vign=N with the names. tests/route_untracked_spec.lua WORLDBOSS_VIGNETTE_PASS.
  • A route action scans fresh: RouteUntracked drops the pin and quest scan throttles before asking, so adding or starting a world-boss route sees the live boss at click time instead of waiting out a timer. tests/route_untracked_spec.lua WORLDBOSS_FRESH_ON_CLICK_PASS.
  • A partial pin index is never final: the wbindex diag proved the boss index perfect (all 15 Broken Isles encounters) while the pin index held 22 current-content pins cached for good, built before old-expansion skulls streamed in, so Calamir's Azsuna skull (visible on the player's own map) never entered it. EncounterPinIndex now rescans every minute once built (empty asks keep the 30 s retry), and a rescan that answers empty keeps what it had. A group with exactly one pinned boss and no quest signal treats that boss as the live one (only the active boss of an old rotating group gets a skull), so it leads the group tagged "up this week". And an anchored world-boss trip upgrades itself: RouteAdvanceIfDone retargets the route onto the live boss the moment its pin streams in, telling the boss's name. tests/route_untracked_spec.lua WORLDBOSS_PIN_RESCAN_PASS, WORLDBOSS_LONE_PIN_ACTIVE_PASS, WORLDBOSS_ANCHOR_UPGRADE_PASS.
  • The world-quest sweep reads continent maps too: Blizzard aggregates zone world quests onto the continent view, and for some old groups that is the only place the boss quest surfaces, so ActiveWorldBossSet sweeps continent-type maps after the zones (deduped by questID). A group with no pin, no quest and an open gate no longer anchors wordlessly: the guide says no world boss is showing for this character (lock_worldboss_none_fmt, all 12 locales). His diag also proved the gate side correct: char=true on Uniting the Isles, gate open, line rightly silent. tests/route_untracked_spec.lua WORLDBOSS_CONTINENT_QUEST_PASS, WORLDBOSS_NONE_TOLD_PASS.
  • The continent map's skull crowns the live boss: old expansions carry no zone-map encounter pins at all (his diag: 22 pins, none Legion), but the game draws the week's active world boss on the continent map itself, so EncounterPinIndex now sweeps continent maps too and every continent-pinned encounter is marked the live one (liveContinentBosses folded into ActiveWorldBossSet). Standing at Nithogg now means Nithogg leads the group tagged "up this week" and the route aims at his skull. /am diag gains a gate: line (gate kind/state plus each unlock quest's char and account flags) and liveCont=N. tests/route_untracked_spec.lua WORLDBOSS_CONTINENT_SKULL_PASS.
  • A per-character unlock is judged by this character alone: questDone fell through to IsQuestFlaggedCompletedOnAccount, so another character's Uniting the Isles opened the Broken Isles gate for a character that personally cannot see Legion world quests, the line went quiet-open and no tip showed anywhere. Legion, BfA and Dragon Isles worldquest rows now carry perCharacter = true and check only the character's own flag; Khaz Algar's Surface Bound grants warband-wide World Quests and stays account-checked. tests/gate_state_spec.lua covers the account-flagged-but-blocked case.
  • Ordos carries his cloak gate now: the one world boss AllTheThings records a real prerequisite for (and the wiki confirms still holds on retail), a red line under his name in the Pandaria group says A Pandaren Legend must be done on this character, gone once it is. New R.BOSS_GATES table (per-encounter, Lockouts:BossGateText) for a single gated boss inside an otherwise free group; gate_boss_quest_fmt translated in all 12 locales. The full ATT audit found no other live gate missing: every other world boss carries no prerequisite in ATT's data, and the old raid attunements it records are all flagged removed by Blizzard. tests/gate_state_spec.lua GATE_BOSS_QUEST_PASS; tests/route_panel_spec.lua ROUTE_PANEL_BOSS_GATE_PASS.
  • An unlocked world-quest gate goes silent: world-boss groups sit in the panel for good, so the access line shows only while the unlock quest is actually missing and leaves once it is done (quietOpen on the open worldquest state; blocked lines are untouched). GATE_WORLDQUEST_PASS covers both states.
  • A gated world-boss group says the gate in chat too: when the route can find no live boss (the unlock quest is missing, so the character cannot see it), the guide tells the access line ("the weekly world boss stays hidden ... until Uniting the Isles is done") before anchoring, so the fix is named right where the player is looking. tests/route_untracked_spec.lua WORLDBOSS_GATE_TOLD_PASS.
  • Boss world quests titled with a prefix ("Lair: Nymrissa Wavecaller") still place their boss: the quest-title match accepts a title that contains the boss's name, not only an exact match (seen live in /am diag). tests/route_untracked_spec.lua WORLDBOSS_TITLE_PREFIX_PASS.
  • World-boss groups gated behind a world-quest unlock say so now: a new worldquest gate kind in Data/Gates.lua covers Broken Isles (Uniting the Isles, 43341), Battle for Azeroth (Uniting Kul Tiras 51916 / Uniting Zandalar 51918), Dragon Isles (Moving On, 66221) and Khaz Algar (Surface Bound, 79197); without the unlock the character cannot even see the weekly boss, so the panel's access line and the raid tooltip both carry the requirement in red, green once unlocked (gate_worldquest_blocked_fmt/gate_worldquest_open_fmt). Shadowlands names no unlock quest on the wiki (level + covenant) and carries no row. Lockouts:FillRaidTooltip now appends the gate line for every gated raid. tests/gate_state_spec.lua GATE_WORLDQUEST_PASS; tests/route_panel_spec.lua ROUTE_PANEL_GATE_TOOLTIP_PASS.
  • The world quest is the boss's own address now: old world-boss groups (Broken Isles and every rotating group before the current content) get no journal pin at all, so the active scan sweeps every zone for world-boss quests (the tag read from C_QuestLog.GetQuestTagInfo(questID).worldQuestType, the field Blizzard's own map pins use; the task POI's questTagType rides along nil on the live client), reads each quest's title (C_TaskQuest.GetQuestInfoByQuestID) and routes to the quest's own map position when its title names one of the group's bosses; the pin match and the shipped anchor stay as the floor below it. /am diag gains a worldboss: line showing every world-boss quest the scan sees (and the total task quests swept, so an empty sweep and an empty tag match read differently) and the spot the first open route group resolves to. tests/route_untracked_spec.lua WORLDBOSS_QUEST_SPOT_PASS.
  • Adding a raid or an item to the route from a search clears the search box (Lockouts:ClearSearch), so the panel comes straight back whole with the Routing section on top instead of staying filtered. tests/route_panel_spec.lua ROUTE_PANEL_SEARCH_CLEARS_PASS.
  • Lockouts:ActiveWorldBossSet (new): the week's active world boss is the encounter pin carrying a live world quest tagged Enum.QuestTagType.WorldBoss (C_TaskQuest.GetQuestsOnMap per pinned zone, matched to the pin within 0.05); the scan re-asks every 30 s so an empty early answer is never kept and a mid-session weekly reset is seen. WorldBossSpot prefers the active boss's pin over the first pinned boss and returns the boss's name (EJ_GetEncounterInfo), RouteUntracked says and titles that name, SortBossOrder leads with the active boss, and the panel's boss line carries a green lock_boss_active tag (new key, translated in all 12 locales). InvalidateIndexes clears the set. tests/route_untracked_spec.lua WORLDBOSS_ACTIVE_PASS, WORLDBOSS_ACTIVE_RETRY_PASS; tests/route_panel_spec.lua ROUTE_PANEL_ACTIVE_BOSS_PASS.

[v0.5.0-release] - 08/30/2026

In-Game

AppearanceMatic finds the looks you are missing and walks you to them. Star any appearance in the Wardrobe or any mount in the Mount Journal; the panel (/am) gathers every starred item by the raid, dungeon, vendor or zone it comes from, with each character's lockouts, the cost, the location and what must be done first. Add an entry to the route and the guide takes over: the arrow and the game's yellow waypoint lead you portal by portal, boat by boat, flight by flight, never through a gateway your character has not unlocked. Collect the item and the star comes off by itself, the alert fires with your own message and sound, and the route moves on to the next stop. A try-on model on item tooltips shows a look before you chase it, and the FAQ tab in the settings answers the common questions.

This first public version is a beta: reports shape it. Bugs and ideas belong on the CurseForge page or in the Discord (the button sits on the General settings tab). If a direction from the arrow ever leaves you unsure where to go next, say what it said and what would have made it clear: your wording becomes the guide's wording for every player after you. And if a line in your language reads wrong, post the language, the line and the words a native speaker would use.

Added

  • An FAQ tab in the settings: what the addon does best, starting a route, what the arrow and the yellow waypoint mean, how travel is chosen, where beta reports go, how to suggest clearer directions, how to report a translation, and how the guide behaves beside TomTom and Blizzard's own tracking. The changelog popup opens on the beta welcome.
  • Collected announcements go to your guild from the start, paced to one guild line a minute with the rest following in order so a lucky run never floods guild chat; party, raid, battleground and the emote are on too, and every Social box stays the player's to change.

Changed

  • Core.lua social defaults: guild = true again; a one-time _socialGuildRejoined migration turns the box back on for profiles carrying the 0.5.0 opt-out write, and the 0.5.0 _socialGuildOptIn shim is gone. The box stays the player's to change.
  • Social:Announce guild lines pass through Social:PushGuild, a one-a-minute queue (GUILD_GAP_SECONDS, 60): the first goes at once, the rest drain in order through C_Timer.After, none dropped; with no clock or timer available lines go straight out. tests/social_spec.lua SOCIAL_GUILD_SPACING_PASS.
  • Route chat splits by hop kind: only portal and flight hops go through say (the emote path); route starts, plain next stops, arrivals, inside-the-instance lines, pin rejections, lock_route_next and lock_collected_guide_ended go through tell. tests/route_tick_spec.lua ROUTE_TICK_EMOTE_GATE_PASS plus the reworked chat assertions.

Fixed

  • Route:TableHop: chain selection minimized continent hops alone (GatewaysToward's shortest-chain set), then took the nearest of that set, so from Silvermoon a discovered EK-to-Outland gateway 11,000 yd away beat the Orgrimmar portal 60 yd away (Horde ships no EK-to-Outland row; the 2-hop Silvermoon -> Orgrimmar -> Shattrath chain never entered the candidate set). New Route:BestGatewayToward scores every unlocked same-continent gateway with a live chain by yards-to-reach-it plus R.CHAIN_HOP_YARDS (2500) per remaining continent hop, and TableHop routes to the cheapest. The unreachable route_reach_first_fmt branch (candidates are always on the player's continent) is gone. tests/route_spec.lua CHAIN_COST_PASS.

  • Route:TrackedNextHop (new): a route carrying a tracked item now asks C_ContentTracking.GetNextWaypointForTrackable (via Tracking:NextWaypoint, throttled to 1 s) every tick, settle window included: content tracking is asked by the player's current map and answers nothing while pending, so waiting out MarkSettling's 3 s (ZONE_CHANGED_NEW_AREA) / 5 s (PLAYER_ENTERING_WORLD) parked the pin on the far destination after every loading screen, the "takes 3 to 5 secs to update" report. tests/route_spec.lua TRACKED_NO_SETTLE_WAIT_PASS. It is asked and feeds the answer in as the game hop. GameNextHop's C_SuperTrack.GetNextWaypointForMap query is blinded by the addon's own super-tracked user pin outside the 3-tick window, so Blizzard's correct "Take the portal from Silvermoon to Orgrimmar" was computed and never heard. Wordless points are no hop (the table guides), and an off-world hop for a same-continent target is refused like the super-track path's. Cleared on map change, on reaching a flown-to node, and in Stop. tests/route_spec.lua TRACKED_GAME_ROUTE_PASS.

  • Route:OnTaxiMapOpened: with an off-continent target it compared taxi-node world coordinates against the target's coordinates on another continent's grid (instance 530 yards vs instance 0 yards), picking a nonsense "closest" node and storing its position relative to the player's map (the out-of-range map 2393 @ -1.772,5.710 hop). Cross-continent it now aims at BestGatewayToward's first gateway, and gives up the override rather than guess when no chain exists. tests/route_spec.lua TAXI_CROSS_CONTINENT_PASS.

  • Route:Tick returns while UnitOnTaxi("player"): every zone crossed under a taxi re-queried the route (mapChanged), re-placed the pin and re-set the arrow's hop at the node under the player, ringing the arrow's 40 yd arrival ping at each flight point; GuideArrow's playArrivalPing is taxi-guarded too, so the ping waits for touchdown. The landing tick recomputes and announces once. tests/route_tick_spec.lua ROUTE_TICK_TAXI_SILENT_PASS.

  • Route:SameContinentHop: near a flight master, any flight whose landing node was even one yard closer than the straight ride was boarded (destNodeD > straight was the only gate), which chained a second flight after every landing. A flight now has to land R.TAXI_GAIN_YARDS (1000) closer than riding straight. tests/route_spec.lua TAXI_GAIN_PASS.

  • LockoutsEvents.lua PLAYER_ENTERING_WORLD: Route:Resume and Lockouts:ResumeGuide sat behind a flat C_Timer.After(6), so every /reload left the route, arrow and pin dead for six seconds (portal loading screens re-entered the same wait, harmlessly but pointlessly). The resume now runs synchronously on reloads and portal transitions, with a 2 s wait only on a cold login (isInitialLogin per the event payload); the chained 6 s RouteAdvanceIfDone kick is unchanged. New tests/resume_start_spec.lua RESUME_START_PASS.

  • Route:NextHop / Route:Tick: with a tracked item aboard, the pin-query dance is skipped entirely. C_ContentTracking answers no matter where the pin sits, and Blizzard's router needs ~3 s after a loading screen (the guide path's old DataPending retry already measured this), so the provisional window parked the pin on the far destination exactly that long after every portal. The table's hop is now final at once, pin included; TrackedNextHop re-asks a pending answer every tick instead of throttling nothing for a second; and a hop change on the tick the player's map changed skips the two-tick flap gate, so the hop after a portal announces and pins immediately. /am diag prints the tracked hop and the age of the last ask. tests/route_tick_spec.lua ROUTE_TICK_TRACKED_NO_PIN_DANCE_PASS.

  • Route:GatewaysToward never wrote its computed list back to towardCache (the cache was read-only since it was added), so the BFS re-ran every 0.3 s tick. The hop map is factored into Route:ContinentHops (shared with BestGatewayToward) and both now cache beside the toward lists, cleared by the same invalidations.

  • AnnounceRoute (both branches), ToggleRoutePause, the /am stop handler in LockoutsEvents.lua (which gained its own tell local), HandleTrackLink's limit notice, GuideRouteNext's no-entrance notice and Favorites:GuideToVisual's two fav_print_no_location replies now go through pluginShared.tell instead of say. New tests/announce_voice_spec.lua asserts route adds, removals and both switch replies reach tell and never say.

  • Lockouts:NoteRouteLink / routeKey: the route-target store was keyed by visualID alone, and every raid dropping that visual overwrote it (rows render for each raid that has a source of a starred model); GuideRouteNext then followed the last writer's link, raid name, source and entrance. Raid rows now store and look up under <kind>:<id>:<raidName> (the routed raid's own row wins), single-item records carry their raidName, and dropTarget links pass the raid through. tests/route_panel_spec.lua ROUTE_PANEL_SHARED_VISUAL_PASS covers two raids sharing one visualID.

  • Route:GameNextHop: C_SuperTrack.GetNextWaypointForMap returning coordinates with an empty description was wrapped as a "game" hop titled with the final target; the answer coming and going (result 2 failures) flapped the hop against the table's flight-master answer and announced every swing. A wordless point is now no hop at all; the table guides. tests/route_tick_spec.lua ROUTE_TICK_WORDLESS_GAME_PASS.

  • Route:Start says route_print_routing_fmt before its first tick (the tick used to say "next" first), and Tick announces a hop only once a first hop was seen (Route.hopSeen). NextHop's "reached the cached gateway" reset no longer applies to a hop that leaves the world (LeavesWorld): standing at a portal at route start wiped lastHop and re-announced the same hop. sameHop treats two hops on the same map within SAME_HOP_YARDS (15) as one; the game's spot for a gateway replaces the table's silently (arrow and pin move, no chat line). A route's first hop and provisional hops skip the two-tick confirmation.

  • Route:CanFlyToTarget: IsFlying() no longer chooses a straight line; only guideArrow.preferOwnMount does. Being airborne at the flight master flipped the hop to the far target and landing flipped it back.

  • Route:Gateways(): the TravelGuide sweep ships every hand-written hub portal again as one room node per map (Portal to X, hubName = the map's name, no floor; Data/Routes.lua:280 and the map-85 rows), and Blizzard's own map links do the same at runtime. NextHop took the nearest candidate and DescribeGateway the nearest row within 60 yd, so the twin won and the hub row's label, name and floor never showed. A generated, game-reported or learned row for the same destination within HUB_TWIN_YARDS (150) of a hub portal now folds into the hub row, which keeps its words and takes the twin's req when it has none (the Zuldazar portal now carries quest 46931 account-wide from its twin). The old same-map floor borrow is gone. DescribeGateway prefers a hub row within 60 yd; a sweep row whose destination no hub row names (Caverns of Time by its own map, Warspear, the newer Razorwind Shores and Founder's Point portals) is placed by Route:HubNear as "(Pathfinder's Den)" rather than "(Orgrimmar)".

  • Route:TableHop (the table half of NextHop): the hop verb comes from the row's kind, so generated labels lose their own gateway word (placeName: "Portal to", "Boat to", "Zeppelin to", "Deeprun Tram to", "Rootway to", "Return to" and the rest of the sweep's vocabulary).

  • Route:PlacePin / Route:PinIsAt track where the route last put the game's user waypoint. NextHop re-asks Blizzard's router every 60 s by moving the pin onto the destination; when the answer was the same hop, Tick only called EnsureGameArrow, which returns while any pin is tracked, so the pin stayed on the destination for good. An unchanged hop now moves the pin back onto it, and a table gateway hop (portal, boat, zeppelin, flight master) is not re-asked at all: the game would name the same gateway.

  • Route:NextHop: while the game's three query ticks run, the table's answer is returned as a provisional hop that Tick sets on the arrow at once (announced once, no two-tick confirmation) while the game's pin stays on the destination for the game to route from; the table's answer standing after the window moves the pin onto it without a second chat line. A tick with no player position (a loading screen, the moment after a portal) returns pending instead of a final hop to the far destination the arrow could not bear on. Tick no longer sits out the 5 s settle after PLAYER_ENTERING_WORLD; only the game query waits for it.

  • Tests: tests/route_spec.lua (OWN_MOUNT_OPTION_PASS; hub twins fold, requirement adoption, room-node hops, DescribeGateway, label cleanup, spec maps for Durotar, Zandalar, Zuldazar and old Silvermoon) and the new tests/route_tick_spec.lua (provisional hop at once, pin waits on the destination then moves onto the hop, no re-ask for a table gateway, pin returns after a re-ask, no hop without a position, settling gates only the game query, one chat line per start, the game's same-gateway answer taken over silently, standing at the portal changes nothing, a hop change announced once).

  • World-boss groups always have somewhere to route: Data/Entrances.lua now ships a curated anchor for Broken Isles, Azeroth (BfA), Shadowlands, Dragon Isles and Khaz Algar (Blizzard's JournalEncounter table carries junk coordinates for every post-WoD world-boss group, so these are wiki-read spawn spots of one boss each), plus the Return to Karazhan doorway. Shipped world-boss rows carry worldBoss = true; RouteUntracked prefers the week's live boss pin over the shipped anchor and falls back to the anchor when no pin is up, so the route exists from shipped data on login instead of depending on the Encounter Journal waking. /am diag route line shows the live pin count (pins=N).

  • The whole empty-cache class swept, every once-per-session index that could be built too early and keep a hollow answer forever: Route:MapIndex (an empty map index broke every name-resolved gateway all session), Route:DiscoverGateways (a sweep that found nothing stuck at nothing), and Lockouts:BuildBossOrdinalIndex (an unloaded journal cached an empty boss index, and the entrance retry never cleared it; it does now). All three follow the same rule as the entrance and pin indexes: an empty answer is never kept for good. Lockouts:CurrentExpansionName and the npc-name cache were checked and already never store an empty answer. tests/route_spec.lua MAP_INDEX_EMPTY_RETRY_PASS, DISCOVER_EMPTY_RETRY_PASS; tests/route_untracked_spec.lua BOSS_INDEX_EMPTY_RETRY_PASS.

  • Lockouts:EncounterPinIndex cached an empty scan forever when it first ran before the journal data loaded (the live reason Broken Isles still failed after the pin fix); an empty result now retries every 30 s until pins arrive, and only a non-empty scan is kept for the session. tools/gen-entrances.py also ships world-boss spots from JournalEncounter where Blizzard's data is trustworthy (an outdoor zone under a real continent; Legion and BfA rows point at a battleground map and are refused), adding Pandaria and Draenor rows additively, zero removals.

  • LockoutsUI:BuildContent gains routeHidden: any non-routing search skips the Routing section render while routeOrder still builds for the guide, routed raids and pulled rows rejoin the normal lists as search results. tests/show_filter_spec.lua SHOW_FILTER_ROUTE_SEARCH_PASS.

  • The full dead-end sweep, every route-entry shape that could fail with nowhere to point: world-boss groups (Broken Isles and kin) have no entrance anywhere, so Lockouts:EncounterPinIndex reads the game's own zone-map boss pins (C_EncounterJournal.GetEncountersOnMap, keyed by the journal encounter ids BuildBossOrdinalIndex now keeps) and WorldBossSpot routes to the boss's skull; raid-drop links now carry their shipped spot when the record knows one (TrackTarget no longer strips the fallback when a raidName rides along, dropTarget passes the acq coordinates), so an entrance-less group like an old Acquisition-named raid still routes; and when truly nothing is routable GuideRouteNext says "no route and no entrance" once instead of failing silently. /am diag prints the tracker's last start error. tests/route_untracked_spec.lua covers all six shapes.

  • Lockouts:HandleTrackLink returned true when Tracking:Toggle failed (WoW's tracker refusing the item or at its limit), so FollowTarget, GuideRouteNext and ResumeRoute all believed a guide started while nothing did: a Broken Isles world-boss entry sat first on the route with no guide, no route, no arrow and no word. The failed branch reports false now. GuideRouteNext then falls back to the new Lockouts:RouteUntracked, a plain route to the entry's entrance (LegacyEntrance) or the link's shipped spot with no tracked item aboard, and the tracking watchdog (trackMisses >= 3, sustained not-tracked) takes the same fallback instead of a silent StopGuide. Each fallback tells the player it is guiding untracked. tests/route_untracked_spec.lua.

[v1.1.2-release] - 08/30/2026

In-Game

  • Removing something from the route, and a star coming off (when you collect the item, or when the addon clears stars from things you already have), are now told to you alone in chat, never as an emote. Adding to the route and the routing lines still go out as an emote while the Emote box is on.

Changed

  • Social:Tell(text): the printed form of Social:Line (star texture, pink name, chat_line_fmt), never broadcast. Favorites.lua exports it as pluginShared.tell beside say; LockoutsRoute.lua uses it for lock_route_removed_fmt (AnnounceRoute with added = false), lock_stars_removed, lock_mount_collected_star_removed and lock_appearance_collected_star_removed. tests/social_spec.lua asserts Tell prints and never broadcasts with the Emote box on.

[v1.1.1-release] - 08/30/2026

In-Game

  • The widget no longer errors five times over while it opens when the game withholds a vendor's or creature's name (in combat and inside some instances). The row shows without the name for now and picks it up on a later refresh.

Fixed

  • Acquisition:NpcName compared line.leftText from C_TooltipInfo.GetHyperlink("unit:Creature-...") before checking whether the client had handed back a secret string, so the panel's Other section raised attempt to compare local 'name' (a secret string value) on every row with a creature source, five times per Init. A secret name is now dropped with issecretvalue (the same guard PlaceTitle already used for zone names), recorded as a miss and asked for again after the retry window. tests/acquisition_access_spec.lua covers it (red before, green after).

[v1.1.0-release] - 08/30/2026

Data

  • Data/Acquisition.lua refreshed from AllTheThings 5.3.7 (the GitHub release of 2026-08-30, which carries the "usual sunday parse" commit 0031c1c4; the installed client copy was still 5.2.4, so the dump ran against the release zip). Additive: vendors 112700 to 114670 (+1970, 0 removed), located vendor rows 96476 to 98070, mounts by spell 1080 to 1102 (+22), notes 7407 to 7783, gates 64989 to 66219, 39 event names, 9 availability codes. tools/check-acquisition-diff.lua ADDITIVE_OK, no dashes, tests/acquisition_ensemble_spec.lua and tests/acquisition_access_spec.lua pass.

In-Game

  • Routing and alert lines no longer say the addon's name twice. The emote and the chat line read "AppearanceMatic: routing to Ulduar" once, with the star in front, instead of "AppearanceMatic: AppearanceMatic: routing to Ulduar". /am stop says it the same way as the Routing switch now.
  • The try-on model now shows on item tooltips for gear in your bags, the same as it always did when you hover a row in the widget. The tooltip had an extra check in front of it that the widget does not have, and that check was rejecting gear the widget shows perfectly well. Both now go through exactly the same code, so anything the widget can preview, a tooltip can preview.
  • Starring something in the middle of a fight no longer puts an error on the widget. While you are in combat the game deliberately withholds some of the information the panel reads, and the panel was tripping over it. It now skips what it cannot see, keeps showing everything it can, and adds one line telling you plainly that some of it is hidden until you leave combat. The moment the fight ends it works out what was missing and fills itself in, with no action from you.
  • Starred gear that only drops in this season's Raid Finder now tells you where to queue. Those raids have no Raid Finder NPC to talk to (older ones do), so the row used to say nothing at all, which read as though there was no way in. It now reads "Raid Finder: queue from [Group Finder]", and clicking that opens the Group Finder straight to the Raid Finder tab. Legacy raids still name the NPC to talk to, as before.
  • The panel now leaves alone any line that already reads exactly the way it should. A refresh used to redraw all of its lines, well over a thousand of them with a few hundred starred items, even when the list was identical; now only the lines that actually changed are touched. Each starred item's name and icon is also looked up once instead of on every refresh. Together these are the bulk of what the panel cost while open, and it shows most in a raid, where a stream of loot was making it refresh again and again.
  • The panel also limits how much of your frame time it will take. If a refresh turns out to be expensive it waits proportionally longer before the next one, so a burst of activity (a raid full of loot, a run of lockout updates) can no longer make the panel the most expensive thing running. A quick refresh still happens as fast as it ever did.
  • The addon costs much less while you play, whether the panel is open or closed. The list of your starred mounts is no longer worked out by walking every mount in the game each time anything refreshes; the Mount Journal no longer redraws every star and route pin several times a second when nothing about them has changed; an item tooltip works out only once whether a piece can be tried on, instead of every time a tooltip opens or refreshes itself; and a vendor the game cannot name yet is no longer looked up again for every row.
  • The Raid Lockouts panel no longer redraws itself over and over while it is open. With a few hundred starred items it was rebuilding the whole list several times a second for as long as the panel was up, which ate a real slice of your frame rate and made the game feel choppy while travelling. The panel now redraws only when something it shows has actually changed, and each redraw does far less work: starred items whose drop information has not arrived yet are no longer re-checked from scratch every time, and the route order, the panel font and the Raid Finder lookups are worked out once instead of once per row. Routing is lighter on the way as well. Nothing on the panel changes; it just costs a fraction of what it did.
  • The widget shows a turning loader over itself while it is still gathering the drop data for your starred appearances, so an opening panel is never a half-filled list. It clears the moment the list is complete, and once the panel has shown you a full list it never covers it again while you play.
  • The guide arrow appears as soon as the waypoint is set. When a bearing is not available yet (another continent, or a spot the map cannot place), the arrow now says so within a third of a second instead of sitting invisible for seconds, and it retries straight away rather than waiting; the wait no longer stretches out when your frame rate is low.
  • New option, on by default (General > Tooltip): every item tooltip for gear with an appearance, in bags, at vendors, on loot, chat links and quest rewards, shows the same try-on model beside it that the widget shows when you hover a gear row (flipping to the tooltip's left when it would run off the screen). Turn it off to keep the model to the widget rows.
  • Starring an appearance or mount you already have no longer sticks: the star comes straight off (a leftover star on something you own goes too) and a chat line tells you why; the wardrobe star reads "Already collected" on hover; the route pin refuses those items the same way.
  • The currency and item icons in front of vendor prices in the widget are 40 percent smaller, sitting at the text height.
  • New: the Route, one routing for the whole panel. Every raid and dungeon in the Raid Lockouts panel has [Add to route] beside its title: click it and the whole raid goes on your route. Vendor, mount and world-drop rows end with their own [Add to route]; the gold pin next to the star in the Appearances tab and the Mount Journal adds a single item too. Everything on the route sits under a Routing header with the big gold pin at the very top of the panel (the only icon in the panel), nearest first from where you stand, routed raids shown whole with their bosses and rows; [Remove all routes] at the bottom empties it (after a Yes). The route is the only thing that drives the guide: it starts on the nearest route entry as soon as you add one (or log in with a route), moves to the next entry when an item is collected or when the raid you just cleared is done for the week, and raids stay on the route through weekly resets until you take them off or every starred item in them is collected. Nothing else moves the list any more: raids below the Routing section sit in one fixed order by name.
  • New option, Hide unattainable items (General): starred appearances and mounts that were removed from the game or never made it in stay out of the Raid Lockouts panel; the panel refreshes the moment you flip it.
  • The [Guide me] and [Guiding] links after each row are gone (the route guides); every starred row ends with its star: clicking the star asks "Remove the star from this item?" and removes it on Yes (and, if the guide was leading there, moves it to the next route entry), the row disappears.
  • The little map-pin icon after location links in the panel is gone; the link itself is the guide.
  • New: General > Lockouts panel font. One font face, size and outline for everything in the Raid Lockouts panel: rows, boss lines, headers, the title and the toolbar. Boss lines and headers sit a step above the chosen size; the title and the toolbar labels shrink to fit their space. The face list is the same shared-media list the alerts use; on a non-English client the panel draws with the game's own font for your language (the bundled faces only carry Latin letters) and the size and outline still apply. Reset font puts the defaults back.
  • A raid cleared on this character reads "Everything done this lockout" on its own line under the title instead of a tag on the title line.
  • Each character's lockout state now sits directly under the item it applies to, and only for the difficulties that item drops on: an item tagged Heroic has "Cryptstep [Heroic]: Available" under it, an item tagged Normal and Looking For Raid has those two lines. A boss no longer ends in one block covering every difficulty. A single drop on the route shows the same under its row in the Routing section.
  • The character picker still defaults to This character, but its menu now also lets you tick other characters to show alongside it; All characters still shows everyone. Which characters you ticked is remembered between sessions.
  • New Ignore Characters tab in settings: check off characters you never want to see and they drop out of the character picker, All characters and search by name, everywhere in the panel. One click ignores every character except the ones on your current server. The character you are playing is never hidden, even if it is checked.
  • Every Social box is on by default, the emote included: a profile saved while one of them was off gets them all switched on once, and from there your own choice stands.
  • In the Routing section, a raid or dungeon that reads "Everything done this lockout" always sits at the bottom of the Routing list, whether or not it is the one being guided. It stays on the route and inside the section; it just goes last, since there is nothing to go there for until the reset.
  • Access lines under raids (and in the wardrobe tooltip) draw in legendary orange, whatever their state says.
  • Every route change answers in chat ("Tomb of Sargeras added to the route", "removed from the route"), so a click is never silent.
  • Item icons on the rows are a quarter bigger; currency icons on cost lines are half again as big.
  • Raid and dungeon headers are just the name with [Add to route] / [Remove from route] beside it; hover the name for where the raid is (its zone) and which expansion it belongs to. Zone and expansion still match the search box.
  • Searching "routing" (or "route") in the panel shows only the route. /am stop rests the guide until the route changes; the route itself stays.
  • A gold switch sits beside the Routing header: flip it off to rest the route's guide for now, so TomTom's own waypoints and arrow show again; flip it on to resume. The route keeps its entries, the switch stays where you left it through reloads and logouts, and the guide comes back on its own when you change the route (/am stop flips it off too). The Routing pin is a touch smaller.
  • Event-only vendors no longer show a bare "acq_note_limited_event" / "acq_note_timewalking" line: the note reads "Only present during a limited-time event." or "Only sold during the Timewalking event." in your language.
  • Access lines name every holiday (Brewfest, Hallow's End, the Darkmoon Faire and the rest) from the game's own calendar instead of "event 1719", on every region.
  • A quest requirement names the quest when the game knows it ("Requires the quest "The Tabiqa" first") and otherwise reads plain "Quested", never "quest 9121".
  • The Raid Lockouts panel wears the same window as the other Infinite Loop Alchemist addons: dark backdrop, the AppearanceMatic medallion in the corner, a big pink title, a divider, and a resize grip in the bottom-right corner (drag it to make the panel wider or taller; the lines re-wrap and the size is remembered). Search and character picker sit on one toolbar strip under the divider, each taking half of the panel's width (they grow with the panel); the minimize button stays next to the close button.
  • New Detection tab right after Alerts: it shows the other Infinite Loop Alchemist addons AppearanceMatic looks for on your client. GearMatic has its card (icon, description, link) with a switch that sits off until GearMatic is installed, what its gear auto-sell (legacy gear, or gear under the item level you set) and auto repair are set to right now, and the note that while GearMatic is installed AppearanceMatic keeps every item whose appearance you have not collected yet out of that auto-sell (needs the GearMatic release that carries the keep hook).
  • The Social tab does something now. Collecting a starred appearance or mount posts one line: to your guild (Guild box), and to your party, raid or instance chat when you are grouped (their boxes), or as an emote when you are not grouped or Emote is the only box on. All five boxes start on; profiles saved before this get Guild switched on once.
  • Options: the guide arrow's settings moved from General to their own Guide arrow tab right after Sound, opening with a note on what the arrow does.
  • Options: the panel's settings (window and font) moved from General to their own Widget Panel tab right after Alerts, opening with a note on what the panel is and is for; the login line option is now Welcome message (its tooltip says it carries the version and the help command); Discord, Buy Me a Coffee and Follow on X in General now show their icons, and the Infinite Loop Alchemist line shows its profile picture.
  • /am help lists every command: /am, /am settings, /am test [appearance|mount], /am stop, /am reset, /am here, /am diag, /am changelog and /am help. New: /am changelog opens the change log.
  • Hover the panel's medallion for a hint, right-click it for a menu: Wardrobe, Mounts, Change log, Settings. A new minimap button with the AppearanceMatic medallion opens the panel on left-click and the settings on right-click; drag it around the minimap ring, or hide it with the new Show minimap button option (General).
  • The panel is titled AppearanceMatic, the name as written. Its text is a cream white throughout (title, rows, boss lines, the Routing header, [Add to route], the search box and the character picker); [Remove from route] is red.
  • The expansion filter button is gone. A row of tick boxes under the search box (they stay put while you scroll) picks what the panel shows: the current expansion (Midnight), Legacy (earlier expansions), Vendors, Mounts and Other. All are on to start; untick one and that part leaves the list, with a count of what is hidden at the bottom. Your choices are remembered.
  • The star's Yes/No prompt now says the star also comes off your favorites in the Appearances tab or the Mount Journal.
  • The guide no longer sends you through a portal, boat or rootway your character has not unlocked yet. When the only way across needs a quest, level, spell, reputation or race you do not have, the guide names it ("Portal to Undermine is not unlocked on this character yet, travel manually") instead of pointing at a door that will not open. Unlocks are picked up the moment a quest turns in or you level.
  • Vendor and world items now route with the guide's own directions to their known location; Blizzard's tracker keeps the map pin, the objective and the collected alert. Items whose zone is known but not the exact spot are guided to the zone first, then to Blizzard's exact waypoint inside it.
  • Flight hops now tell you when an unlearned flight point would shorten the trip: "Fly to X (discover Y for a shorter route)".
  • New "Access:" line under a raid, on guided rows and in the wardrobe tooltip when something must happen first: which faction controls Tol Barad or Wintergrasp and when the next battle starts (Baradin Hold, Vault of Archavon), or which campaign unlocks travel to Undermine, K'aresh, Azj-Kahet, Voidstorm, the Isle of Quel'Danas, Harandar, Zereth Mortis, The Maw and the Isle of Thunder. The guide's directions carry the same note while the gate is closed.
  • Guides resume cleanly after a reload: the saved route and the saved guide join up instead of starting twice.
  • Vendor, mount and world-drop rows now say what stands between you and them, in the panel and in the wardrobe tooltip: a quest to finish first, a reputation standing to reach, a faction or race the source is limited to, an event it only appears during, or that it was removed from the game (with the patch). Red when it blocks your character, grey when it is only good to know.
  • Manaforge Omega's access line names its door quest, "The Tabiqa", once travel to K'aresh is open.
  • Access lines now also cover Antorus, the Burning Throne (the Argus campaign up to the Antoran Wastes beacon), The Eternal Palace (the Nazjatar storyline), Amirdrassil, the Dream's Hope (the Emerald Dream portal from the Guardians of the Dream campaign) and Sporefall (the rootway to Harandar).
  • The Eternal Palace can now be guided to: its entrance at the Gate of the Queen in Nazjatar was missing from the guide.
  • Crucible of Storms and Ny'alotha, the Waking City show what opens their doors: the Azsharan Medallion questline ("Twist the Knife") and the Black Empire campaign up to "Network Diagnostics", named by the quest title once the client has it.
  • When every way to a raid runs through a portal you have not unlocked, the guide now names the portal that is actually closed (the K'aresh portal in Dornogal), not the open Dornogal portal that merely starts the trip.
  • Amirdrassil's access line accepts either Emerald Dream portal quest and Liberation of Undermine's accepts the Zandalar rocket drill, so a character who unlocked the zone another way is no longer told travel is blocked.
  • The guide arrow and the lockouts panel do less work every frame and on every event: the arrow only rewrites its distance when the number changes, the panel builds each starred row once instead of twice, back-to-back lockout updates fold into one rebuild, and wardrobe tooltips remember an appearance's source and access lines until something about your character changes.
  • The Bel'ameth boat to Gilneas is routable again (both directions).
  • Antorus, the Burning Throne (and anything else in the Antoran Wastes) is routable: the guide leads to the Vindicaar beacon on Krasus' Landing in Dalaran, then points at the target below once you are aboard.
  • Vendor and mount facts come only from the data shipped with the addon (and what your own panel already remembered), so the same items show the same information for everyone, with no other addon loaded.
  • The /am harvest command is gone: everything it used to gather in game now ships with the addon.

Added

  • Data/Routes.lua gateway rows carry req = { quest, accquest, level, spell, reputation, race } generated from the TravelGuide requirements block; Route:GatewayUsable evaluates them (fail closed), Route:GatewaysToward skips locked rows, Route:NextHop names the locked gateway (route_gateway_locked_fmt); locks re-evaluate on QUEST_TURNED_IN, PLAYER_LEVEL_UP, SPELLS_CHANGED, UPDATE_FACTION.
  • Data/Gates.lua + LockoutsGates.lua: access gates keyed by journal instance ID (control via Blizzard's per-faction AreaPOI pins 2485/2486 and C_PvP.GetZonePVPInfo/GetOutdoorPvPWaitTime; travel via the zone portal quests, character then account-wide), rendered by Lockouts:GateLine in the panel, tooltip and route title; re-read on AREA_POIS_UPDATED, PVP_WORLDSTATE_UPDATE, QUEST_TURNED_IN, zone changes.
  • R.GATEWAYS_STAMP (TravelGuide commit) and R.ENTRANCES_STAMP (WoW build); tools/refresh-travel.sh --check (exit 0 / 3 NEWER / 2 CHECK_FAILED); gen-gateways aborts when a regeneration would drop a shipped row unless --allow-removals; /am diag prints the stamps and locked gateways.
  • Route:DiscoverHint and Route:TaxiNodes(..., includeUndiscovered); route_discover_hint_fmt.
  • Tests: tests/gate_state_spec.lua, tests/gen_gateways_req_test.py (+ fixture), lock/hint cases in tests/route_spec.lua, shipped-route and resume cases in tests/track_link_fallback_spec.lua, stamp and req-shape checks in tests/gateway_coverage_spec.lua.
  • tools/webgrab-extension/ (Firefox WebExtension, MV2): reads wiki/wowhead pages in the user's own browser at a person's pace (one tab, 8-20 s between pages, breaks, 60 pages per run, an hour off on a block screen) and saves page digests; tools/webgrab-jobs.py gates|urls builds the page list, tools/webgrab-import.py reports door quests with IDs, access sentences and campaign mentions per gated raid; tests/webgrab_import_test.py.
  • Acquisition:GateFromGroup reads the gating facts recorded up a source's group chain (availability code, event, removed-with-patch, prerequisite quests, minimum reputation standing, faction or race restriction; a header's value applies to everything under it) into gate = {...} on every vendor/mount/drop row; the dump and export tools ship it, plus D.events (event names in all 11 client languages) and D.phases (availability codes and states). Acquisition:AccessLines(rec) checks them live (C_QuestLog character/account, C_Reputation.GetFactionDataByID().currentStanding against the shipped standing value, UnitFactionGroup, UnitRace/C_CreatureInfo.GetRaceInfo, GetBuildInfo against the removal patch) and the panel (addSourceLines) and wardrobe tooltip render them as Access lines; acq_access_* strings in 12 locales; tests/acquisition_access_spec.lua.
  • Data/Gates.lua rows for 946 (Antoran Wastes, quest 48200), 1179 (Nazjatar, 54972/55053), 1207 (Emerald Dream, 76318) and 1305 (Harandar), read from the WebGrab sweep of every raid's wiki page against the TravelGuide portal requirements; gate_campaign_argus, gate_campaign_nazjatar, gate_campaign_guardians_of_the_dream in all 12 locales; tests/gate_state_spec.lua covers kind = "quest" rows.
  • tools/gen-entrances.py carries a SUPPLEMENT table for raids Blizzard draws no entrance pin for (The Eternal Palace, 1179, at the Gate of the Queen per the wiki), used only while the pin table still lacks the instance; tools/webgrab-jobs.py follows up to 12 quest links per raid page (was 4/8) so a sweep reads whole questlines.
  • Data/Routes.lua TRANSITS: Dalaran (627) -> The Vindicaar (887) at the Krasus' Landing flight master position from Blizzard's TaxiNodes row 1774 (req = { quest = 48200 }); tests/gateway_coverage_spec.lua asserts the row.
  • Data/Gates.lua kind = "quest" rows for Crucible of Storms 1177 (doorQuests 53763 Alliance / 54126 Horde) and Ny'alotha 1180 (58506), quest IDs read from the wiki quest pages by the follow-up WebGrab job.
  • .pkgmeta (package-as AppearanceMatic, manual changelog, LibMatic as a file:// external, tools/tests/docs/release.sh ignored) and release.sh (localization parity gate, in-game changelog gate, BigWigs packager in .release/, zip deleted after a clean upload); Libs/LibMatic is no longer tracked (local symlink, packager external), .release/ and *.zip ignored. Verified with a -d -z dry run: the package holds only the addon files and libraries.
  • Gate rows accept doorQuests = { id } (and kind = "quest"): once travel is unlocked the line names the raid-door quest by its client-side title (C_QuestLog.GetTitleForQuestID, loaded via RequestLoadQuestByID, refreshed on QUEST_DATA_LOAD_RESULT); gate_quest_* strings in all 12 locales.

Fixed

  • Doubled addon name on every say() line: Social:Line (43ecddd) prepends the star and AppearanceMatic: to each routing and alert line, but the 17 localized strings that flow through say() (route_print_*, lock_route_*, lock_routing_*, lock_*_collected_*, lock_stars_removed, lock_no_route_no_entrance, lock_track_limit_reached, fav_print_no_location) still opened with their own |cffffb6d9AppearanceMatic|r:, so the emote went out as {rt1} AppearanceMatic: AppearanceMatic: .... The prefix is stripped from those keys in all 12 locales; Social:Line is the only place the name is added. /am stop in LockoutsEvents.lua printed lock_routing_stopped directly and now goes through say like the Routing switch. tests/locale_load_spec.lua asserts none of the say() keys names the addon, in any locale.
  • Rebuild storm: the panel drove its own redraw. Lockouts:ScheduleRebuild cleared pendingRebuild before calling Rebuild, so any ask raised from inside the draw armed a fresh 0.1 s timer instead of coalescing; Lockouts:ResolveItemDisplay calls RequestItemLoad for every source whose itemLink carries no name, on every rebuild, and ItemMixin:ContinueOnItemLoad runs its callback immediately for an item the client already has (wow-api MCP), so that callback fired inside the draw and unconditionally called ScheduleRebuild. Measured in game at 280 rebuilds in 118 s, 2.37/s, 20.06 s of CPU, 17 percent of frame time, with CollectStarredDrops re-scanning all 400 favourites each time (112000 VisualDrops calls = 280 x 400). Fix at the source: RequestItemLoad records each item in Lockouts.loadedItems and never asks twice, and only schedules a redraw when C_Item.GetItemNameByID shows a name actually arrived; Lockouts:ForgetNamelessItems reopens them on TRANSMOG_COLLECTION_UPDATED and on Blizzard_EncounterJournal load. Defence in depth: pendingRebuild is now held across the draw behind a rebuilding flag, so any number of asks raised during a draw coalesce into one follow-up, and a draw that errors still clears both flags and re-raises. tests/rebuild_loop_spec.lua.
  • Lockouts:VisualDrops never cached an incomplete result, so a starred appearance whose drop data had not arrived (encounter journal not loaded, secret values inside an instance) was re-derived from C_TransmogCollection on every rebuild forever, about 21 ms of every rebuild. It is now cached with a retryAt stamp and re-asked after DROP_RETRY_SECONDS (20 s) instead; the item stays listed and dropsIncomplete still drives the loader exactly as before. tests/drop_cache_retry_spec.lua.

Fixed (tooltip preview)

  • TooltipPreview:OnItemTooltip gated on its own Transmoggable(link) before calling Lockouts:ShowHoverPreview, while the widget's own hover (LockoutsUI.lua:1536) calls ShowHoverPreview(link) with no gate at all. The gate was strictly more restrictive than the function it guarded, in three separate ways, so bag gear the widget previews fine showed nothing on its tooltip:
    • Transmoggable tested C_TransmogCollection.GetItemInfo's FIRST return, itemAppearanceID, while ShowHoverPreview uses the SECOND, itemModifiedAppearanceID (LockoutsUI.lua:1753). Confirmed return order via the wow-api MCP. Two different questions.
    • ShowHoverPreview falls back to the wrapped raw link when there is no source ID at all (sourceID or ("|H" .. link .. "|h[]|h"), LockoutsUI.lua:1755), so it can show items the gate rejected outright.
    • The moggable memo added earlier this session cached a negative per itemID, and C_Item.IsItemDataCachedByID can report an item cached before its appearance data has arrived, making a false answer permanent for the session. The gate and its memo are deleted. OnItemTooltip now makes the identical call the widget makes and lets ShowHoverPreview decide, which it already did: a failed TryOn sets shown = false and hides the frame (LockoutsUI.lua:1756-1766).
  • ShowHoverPreview now remembers a refusal on the frame (previewRefused) alongside previewKey, so an item tooltip refreshing over something that cannot be worn does not redo the model work on every refresh. That was the only thing the gate was buying, and it belongs in the shared function where the widget path gets it too. tests/tooltip_memo_spec.lua deleted with the memo it covered.

Fixed (error reporting)

  • Every swallowed error was classified, and the ones that mean this addon is broken now reach the game's error handler. New pluginShared.reported(fn, ...) in Favorites.lua is guarded's twin: same return shape, but it runs xpcall(fn, handleError, ...) so the error still reaches CallErrorHandler (falling back to geterrorhandler()) with a traceback taken where the error happened, while the addon carries on. 35 call sites across 12 files converted, including: Favorites:Notify's listener dispatch (a listener throwing is this addon breaking, and swallowing it is how the combat bug stayed hidden); every widget setter in the wardrobe and Mount Journal star refresh; the model-scene calls in the hover preview; the loading spinner's animation group; Lockouts:VisualDrops' collection scan; Acquisition:PlaceTitle's C_Map.GetMapInfo on a shipped mapID; Route:GatewayUsable's six requirement checks against shipped Data/Routes.lua rows; and Lockouts:DryBuild, which runs BuildContent with the panel closed and was the one path where a broken draw could still go unseen.

  • Lockouts:ScheduleRebuild used pcall then error(err, 0) to rethrow. That reported, but the traceback pointed at the rethrow rather than at whatever actually broke. It now uses xpcall(Lockouts.Rebuild, handleError, Lockouts), so the trace is the real failure site.

  • Tracking:Toggle's untrack branch discarded the result of C_ContentTracking.StopTracking and returned true regardless, telling the caller it had worked when it may not have. It now reports a genuine failure and hands the real answer back. The bulk StopAll loop got the same treatment.

  • Deliberately left quiet, each for a named reason rather than "defensive": GameTooltip:SetHyperlink's catch-all fallback and ModelSceneActor:TryOn (Blizzard's designed "cannot preview this" channel, both fire constantly on ordinary hovers); C_QuestLog.GetTitleForQuestID (answers empty until QUEST_DATA_LOAD_RESULT arrives, by design, with RequestLoadQuestByID as the deliberate follow-up); C_Map.CanSetUserWaypointOnMap (legitimately false in instances and sanctuaries, and the caller already retries up to five parent maps); worldFromMap (many uiMapIDs genuinely have no world position, the canonical case); C_SuperTrack.GetNextWaypointForMap and C_ContentTracking.GetNextWaypointForTrackable / IsTrackable / IsTracking (a "no" is the everyday answer, called per row on every build); the TomTom cleanup calls in Route:Stop and GuideArrow's pin hiding (a third-party addon owns that state and a uid can be stale); the optional-addon probes in LockoutsEvents, FavoritesMounts and Detection; and TooltipPreview:Transmoggable's GetItemInfo (item-not-cached is the expected outcome, as the code's own comment says). Reporting any of these would push hundreds of entries an hour into BugSack and bury the errors worth seeing.

  • A broken panel draw no longer disappears. Lockouts:Rebuild swallowed it with pcall and printed it into the panel, which is exactly why the combat bug above never reached BugGrabber. It now uses xpcall(self.BuildContent, handler, self) where the handler passes the error to CallErrorHandler (falling back to geterrorhandler()), so BugGrabber and friends record it with a traceback taken at the point of the error. The panel still draws every row it managed, still shows the friendly line, and still records AppearanceMaticDB.diag.rebuildError. xpcall with extra arguments is confirmed supported from Blizzard's own use of the same shape at Blizzard_TransformTree/TransformTreeBaseNodeMixin.lua:195 and Blizzard_Settings_Shared/Blizzard_SettingsPanel.lua:525, read through the FrameXML index rather than recalled.

Fixed (secret values in combat)

  • Starring anything mid-fight raised a Lua error that the panel printed into itself. Lockouts:Rebuild wraps BuildContent in pcall (LockoutsUI.lua:970) and renders the error as a line, which is why it never reached BugSack. Root cause: WoW 12.x returns "secret" values from several APIs in combat and inside instances, and touching one raises, including comparing it, concatenating it, running a string method on it, sorting it, and using it as a table key. type() on a secret string still returns "string", which is precisely why this codebase's working guards (Favorites.lua:463, :557, Lockouts.lua SnapshotCharacter) always pair the type check with issecretvalue. Several sites checked only the type.
  • Shared pluginShared.plain(value) added in Favorites.lua (first in the TOC), with a local fallback in Lockouts.lua so that file still stands alone under the specs. Lockouts:Plain(value) wraps it and records combatHidden / hiddenCount so a rebuild can explain itself.
  • Sites fixed: linkHasName (Lockouts.lua:83, did link == "" then link:match on C_TransmogCollection.GetAppearanceSourceInfo's itemLink, reached from ResolveItemDisplay for every source of every starred item on every rebuild, the prime suspect); itemHasName (name ~= "" on C_Item.GetItemNameByID); ResolveItemDisplay's icon and itemLink; VisualDrops' info.isCollected, info.sourceType (compared against BOSS_DROP_SOURCE and VENDOR_SOURCE), info.itemID, info.icon, and every entry of drop.difficulties, which become table keys in CollectStarredDrops and are then sorted and table.concated in renderDropRow; and the for index = 1, GetNumSavedInstances() loop bound in SnapshotCharacter, the one value in that function that was not already run through its local plain.
  • A second sweep found five more, all reachable from a rebuild: ResolveItemDisplay stored C_TransmogCollection.GetSourceItemID's return unguarded and passed it to RequestItemLoad, which uses it as a key into loadedItems and pendingItems, and a secret value used as a table key raises (Blizzard's own Blizzard_SharedXMLBase/SecureTypes.lua:34 asserts not issecretvalue(key) for exactly this); Lockouts:MountLink compared C_MountJournal.GetMountLink's return against ""; Favorites:FavoritedUncollectedMounts stored GetMountInfoByID's name, spellID and icon raw, and the name is concatenated into a row in LockoutsUI.lua; Acquisition:PlaceTitle compared and then concatenated C_Map.GetMapInfo's name; and Lockouts:VisualOfSource stored GetSourceInfo's visualID raw, then compared it and used it as a table key. RequestItemLoad is now guarded at the point of use as well as at its caller, since it is the one that holds the table keys.
  • A VisualDrops scan during which the game withheld anything now sets result.incomplete, so a partial answer is never cached as final.
  • The panel now explains itself instead of showing Lua: Rebuild adds one lock_combat_hidden line when anything was withheld, and if a draw does still fail while InCombatLockdown() is true the player gets that same line rather than the raw error. The error text still goes to AppearanceMaticDB.diag.rebuildError for bug reports.
  • PLAYER_REGEN_ENABLED is registered: leaving combat drops the drop and display caches and rebuilds, so the withheld rows fill in on their own. New key lock_combat_hidden in all 12 locales, 267 keys x 12. tests/combat_hidden_spec.lua.

Added

  • Lockouts:RaidFinderHint answers for a current-season raid instead of returning an empty string. Lockouts:RaidFinderNPC deliberately returns nil when IsCurrentSeasonInstance is true (LockoutsRoute.lua:1795, current-season raids queue from the Group Finder rather than an NPC), and the hint's only caller silently dropped the row's line as a result, so an LFR-only drop in the current tier showed no route information whatsoever. The hint now names the Group Finder and carries a w2tsgroupfinder link, handled in Lockouts:HandleRouteLink, that calls Lockouts:OpenRaidFinder: C_AddOns.LoadAddOn("Blizzard_GroupFinder") then PVEFrame_ShowFrame("GroupFinderFrame", RaidFinderFrame), matching the call Blizzard's own Raid Finder makes at Blizzard_GroupFinder/Shared/RaidFinder.lua:20 (read through the wow-api FrameXML index, not from memory). The link label prefers the game's own DUNGEONS_BUTTON so it reads exactly like the button the player clicks, falling back to lock_group_finder. New keys lock_raid_finder_current and lock_group_finder in all 12 locales. tests/group_finder_hint_spec.lua.

Changed (performance)

Measured from a live whole-addon run (344 s, LFR with constant loot, panel open, ~400 starred items): Lockouts:BuildContent totalled 30954.7 ms, about 9 percent of wall time, across 429 calls at 72.155 ms each.

  • Lockouts:AddLine was the largest single cost: 727159 calls, 7529.0 ms self, roughly 1695 lines per rebuild, every one of them re-fonted, re-placed, re-coloured, re-texted and re-measured even when the rebuild produced an identical list. A line now records what it was drawn as (text, font object, indent, width, vertical offset, colour) plus its measured height, and a line that would be drawn the same way is only Show()n. This also removes the font chain that rode on top of it: Lockouts:ApplyFont 4799.9 ms total, Lockouts:FontSettings 2182767 calls, PanelFontPath / PanelFontSize / PanelFontOutline / CustomFontsAllowed 727589 calls each. Lockouts:ForgetLineCache, called from RefreshFonts, drops the record when a font change makes the measured heights wrong; a secret string never takes the fast path since it cannot be compared. tests/line_reuse_spec.lua.

  • Lockouts:ResolveItemDisplay was second: 220672 calls, 6190.4 ms self, two Blizzard lookups per source per rebuild for an answer that never changes once the game has it. Now cached per sourceID, with the same retryAt treatment as the drop cache for a source the game cannot name yet, cleared by InvalidateDropCache. tests/resolve_display_cache_spec.lua.

  • Lockouts:RaidFinderNPC ran 357786 times, about 834 per rebuild. Past the difficulty test its answer turns only on the raid name, so each raid's queue NPC is worked out once.

  • Rebuild rate is now self-limiting. 429 rebuilds in 344 s is 1.25/s, driven by event bursts rather than by anything the player did. Lockouts:RebuildWait times the previous draw with debugprofilestop and waits REBUILD_DUTY (19) times that long before the next, floored at 0.1 s and capped at 3 s, so the panel takes at most about a twentieth of wall time no matter what storms the event queue. A cheap draw still waits the old tenth of a second. tests/rebuild_loop_spec.lua.

  • Favorites:FavoritedUncollectedMounts walked C_MountJournal.GetMountIDs(), every mount in the client, on every BuildContent. That is an O(all mounts) floor per rebuild that does not shrink when the player has few starred mounts, and it was paid again by Lockouts:DryBuild on every route check with the panel closed. It is now built once and kept, invalidated by Favorites:Notify (which every star mutator already calls), by NEW_MOUNT_ADDED in Alert.lua, and by TRANSMOG_COLLECTION_UPDATED. tests/mount_scan_cache_spec.lua.

  • TooltipPreview:Transmoggable ran C_TransmogCollection.GetItemInfo and C_Item.IsDressableItemByID on every firing of a hook that fires for every item tooltip client-wide and re-fires while a tooltip stays open. Both answers are fixed per item, so they are memoised by itemID. A negative reached before the client had the item's data is deliberately not kept: it is only stored once C_Item.IsItemDataCachedByID confirms the answer was real. tests/tooltip_memo_spec.lua. tests/tooltip_preview_spec.lua gained a separate undressable item, since flipping one item's dressability mid-session is not a state the game can produce.

  • Acquisition:NpcName had a positive cache but no negative one, so an NPC the client could not name re-issued a full C_TooltipInfo.GetHyperlink tooltip build on every row of every rebuild. Misses are now held for NPC_RETRY_SECONDS (30) in a session table, cleared when the name finally arrives.

  • The Mount Journal star poller (Favorites:EnsureMountStar / EnsureMountRoute, driven at 0.15 s by FavoritesMounts.lua) re-applied SetDesaturated, SetAlpha and SetTexture to every visible row on every tick regardless of whether anything had changed, which is the common case. Each row now remembers what it was last drawn as, keyed by mountID, and skips the setters when they match.

  • Not changed, and why: the 0.3 s guide ticker was reported as uncapped per-tick API work, but the live profile measured Lockouts:RouteGuideToTomTom at 9.1 ms across 381 calls, 0.024 ms each including everything it calls, which is 0.008 percent of the run. Lockouts:DryBuild genuinely runs a full BuildContent with no panel-visible guard (LockoutsRoute.lua:1213-1228), throttled to 5 s while guided and 60 s while idle; its per-call cost drops with the mount and drop caches above, and it is now its own profiler row, so it is measured before the routing core is restructured on an estimate.

  • The profiler measures the whole addon instead of a hand-kept list of 16 methods, which is what let the rebuild storm's neighbours hide. Profiler:Targets() walks pluginShared and wraps every method of every module it owns (Module:Method labels), skipping only Profiler itself, Ace's db, and the addon object whose methods are Ace's rather than this addon's. The wrapper is allocation free and passes through every return value via a tail call into a finish(rec, started, ...) helper, replacing the old five-value truncation that was only safe because the target list was curated. Nesting is tracked with a depth counter and a per-level child-time table, so the report can order by time a method spent in its OWN body rather than by a total that is inflated by everything it called; a frame OnUpdate resets the depth each frame so a method that throws cannot leave the counter skewed. The report caps at 20 rows and sums the rest into one line, adds the game's own GetAddOnCPUUsage figure when script profiling is on so the numbers can be compared against an external CPU meter, and states that measuring everything costs time of its own. New locale keys profile_more_fmt, profile_cpu_fmt, profile_overhead in all 12 locales; profile_row_fmt gained a self-time column. tests/profiler_spec.lua.

  • RouteList:Ordered is built once and kept until the route changes (cleared in notify, which every mutator already calls), and carries a position index so RouteList:Position / RaidPosition are table reads instead of a rebuild-plus-sort-plus-linear-scan. BuildContent asked for a position once per starred row, several hundred times per rebuild. tests/route_order_cache_spec.lua.

  • Lockouts:PanelFontPath keeps its answer against the client locale and the chosen face instead of calling LibStub and LibSharedMedia:Fetch on every AddLine, roughly a thousand times per rebuild. tests/panel_font_spec.lua.

  • raidFinderNames() and Lockouts:ExpansionLevelByName memoise (LFR difficulty names and EXPANSION_NAME* are fixed for the session); both were re-derived per starred raid row, ExpansionLevelByName walking 21 globals each time.

  • Route:ZoneMap memoises per mapID (up to 6 C_Map.GetMapInfo pcalls per route tick for the same map) and Route:GatewaysToward memoises its BFS per continent pair, cleared by Route:InvalidateGateways and by Route:RefreshGatewayLocks since that mutates g.locked in place. GatewaysToward was 464 ms of the run, the bulk of Route:NextHop.

  • GuideArrow counts the wait for a bearing in seconds, not frames: ATTACH_RETRY 1.0 s, NO_BEARING_AFTER 0.35 s, STALL_GIVE_UP 5.0 s replace the 60 / 90 / 300 / 600 tick gates (at 30 fps those were 2 s / 3 s / 10 s / 20 s), and the pin re-attaches on the first missed read instead of 60 frames later; tests/guide_arrow_timing_spec.lua.

  • Lockouts:ToggleRouteItem tells a removal apart from a refusal (RouteList:Has before the toggle), so clicking the route control on something already collected no longer prints "removed from the route"; Lockouts:PlaceHoverPreview(frame, anchor) splits the placement out of ShowHoverPreview and runs on every show, so a model that keeps its scene still moves clear of a comparison tooltip that opened since.

  • The show-box strip shrinks its labels together (Lockouts:FitFilterBoxes, floor 8 px) when five labels at the panel font size would pass the panel's right edge (narrow panel, long localized names); re-fit on resize, on a label change and after a font change. Vendor rows that are mounts skip the current/legacy expansion test (a mount has no expansion of its own) instead of always counting as legacy. tests/filter_fit_spec.lua, tests/show_filter_spec.lua.

  • Panel gate lines drew white: local gateText, gr, gg, gb = self.GateLine and self:GateLine(raidName) kept only the first return (x and f() truncates to one value), so the colour never reached AddLine; both draw sites call GateLine plainly now. tests/gate_line_color_spec.lua records line colours through AddLine under a listed raid and under a routed drop.

  • Localization/enUS.lua and enGB.lua carried an unescaped quote in gate_quest_blocked_fmt, which raised at load and dropped the 14 keys after it (blank campaign names, a Lua error on every door-quest access line); tests/locale_load_spec.lua now loads every locale file the way AceLocale does and checks parity and non-empty strings.

  • Route:BlockingGateway walks the locked-inclusive chain and names the first locked hop; Route:NextHop used the nearest first-hop gateway even when it was open (tests/route_spec.lua).

  • Route:Gateways records rows whose map the client cannot name in Route.unresolvedGateways (/am diag prints them) instead of dropping them silently; the two Data/Routes.lua Moonlit Rose rows now carry mapID = 217.

  • Data/Gates.lua: 1207 adds 76326, 1296 adds 83933, Harandar rows drop 86897 (a TravelGuide hideAfterQuest, not a requirement).

  • GuideArrow.lua OnUpdate: locale strings cached, colour table reused, yards text formatted only when the rounded value changes, stall text formatted once; LockoutsUI.lua decides the guided rows from IsGuided/the route title instead of rendering every row into a capture buffer first; UPDATE_INSTANCE_INFO goes through ScheduleRebuild; Favorites.lua caches the tooltip record and access lines per source (invalidated on the lock and gate events) and shows "Location" instead of a raw map ID when the zone name is unavailable.

Changed

  • Profiler:Elapsed() freezes the measured window where Stop() left it instead of counting idle time into a later /am profile report; /am profile reset says whether a run is still going (profile_reset_idle, 12 locales); Lockouts:HideLoadingCover() forgets an unfinished wait so the loader's 15 second ceiling only counts time it was actually on screen, not time the panel spent closed.
  • LockoutsLoading.lua (new): Lockouts:PanelLoading() reads the panel's own dropsIncomplete signal, EnsureLoadingCover() builds a DIALOG-strata cover on the panel (a 25 percent black scrim, a 48 px Interface\COMMON\StreamCircle disc falling back to the addon's own icon, a looping Rotation of -360 degrees over 1.1 s, a GameFontNormal label), UpdateLoadingCover() holds it for a 0.4 s floor so it never flashes and reveals at a 15 s ceiling whatever the data does, polling at 0.2 s while up, and sets panelRevealed so a later background refresh never covers a list in use; HideLoadingCover() on the panel's hide; called from the end of Lockouts:Rebuild; lock_loading in 12 locales; tests/panel_loading_spec.lua.
  • Performance: Route:TaxiNodes keeps each map's flight-point list for 3 seconds instead of rebuilding it from C_TaxiMap.GetTaxiNodesForMap twice on every 0.3 s route tick (about 7 rebuilds a second while guiding), cleared at once by Route:InvalidateTaxiNodes() on TAXIMAP_OPENED and on a gateway rebuild, and never kept when there is no clock to age it by; Acquisition:WithCost builds a record's costText once the game has every name in it (FormatCost / ItemName / CurrencyName now return whether the name came from the game, and a line still holding a placeholder is rebuilt next time rather than kept for the session) instead of reformatting it on every panel rebuild; tests/route_taxi_cache_spec.lua.
  • Profiler.lua (new) with /am profile: Wrap(owner, key, label) swaps a method for a timing wrapper reading debugprofilestop(), Start() wraps sixteen named hot paths (guide arrow OnUpdate, wardrobe preview poll, route tick / next hop / flight nodes / gateway search, guide router, panel rebuild, starred drop scan, appearance drop lookup, try-on model, item tooltip hook, wardrobe and mount stars, tooltip acquisition lines, acquisition lookup), Rows() reports count, total, mean and worst milliseconds slowest first, Memory() reads UpdateAddOnMemoryUsage / GetAddOnMemoryUsage, Stop() restores every real method; nothing is wrapped until a run starts. /am profile toggles a run and prints the report, /am profile report prints without stopping, /am profile reset zeroes the counters; profile_* and core_cmd_profile in 12 locales; tests/profiler_spec.lua.
  • TooltipPreview.lua (new): TooltipDataProcessor.AddTooltipPostCall(Enum.TooltipDataType.Item) on GameTooltip and ItemRefTooltip only, link from TooltipUtil.GetDisplayedItem (tooltipData.hyperlink fallback), transmoggable = C_TransmogCollection.GetItemInfo appearance > 0 and C_Item.IsDressableItemByID; Lockouts:EnsureHoverPreview(anchor) keeps one model frame per tooltip (self.hoverPreviews), clamped to the screen; ShowHoverPreview(link, anchor) keeps the model it has when the same item comes back on the same tooltip with the same dress setting (item tooltips re-run their post call on every refresh) and places it past any shown ShoppingTooltip1/2 through PreviewSideOffset(anchor, toRight), flipping left at the screen edge; HideHoverPreview(tooltip) hides that tooltip's model, no argument hides all; a SetItemRef hook drops the chat-link tooltip's model when it is reused for a non-item link (dungeon score, PvP rating); OnHide hooks on both tooltips; generalSettings.itemTooltipPreview default true, toggle order 45 in the tooltip group; item_tooltip_preview_* in 12 locales; tests/tooltip_preview_spec.lua, tests/hover_preview_repeat_spec.lua.
  • Favorites:AppearanceCollected(visualID) (C_TransmogCollection.GetAppearanceSources any isCollected), Favorites:MountCollected(mountID) (GetMountInfoByID 11th return), AlreadyCollected(isMount, id), SayCollected(); Favorites:Set / MountSet refuse on for collected things (clearing a stale star, notifying only then) and return the final state; RouteList:Add refuses collected items before the star side effect and RouteList:Toggle returns its answer (a refusal used to read as a route add, announcing it and starting a guide); wardrobe star tooltip shows fav_tooltip_collected; fav_print_already_collected in 12 locales; tests/star_collected_spec.lua.
  • Detection.lua (new): Installed(name) / Version(name) (C_AddOns.IsAddOnLoaded, GetAddOnMetadata), SwitchTexture(name) (the route switch art), StatusText(name), GearMaticState (missing / hookless / installed), GearMaticAutomation (reads GearMatic.db.profile.autoSellGear/autoSellGearRule/autoSellGearIlvl/autoRepair), AutoSellText / AutoRepairText, KeepUncollected(link) (C_TransmogCollection.GetItemInfo source, PlayerHasTransmogItemModifiedAppearance), AttachGuard() registering with GearMatic.VendorAutomation.RegisterSellGuard on ADDON_LOADED(GearMatic) and PLAYER_LOGIN; BuildDetectionPage (order 2.2) with the roster card, live automation lines and the keep note; opt_detection_*, detect_* in 12 locales; tests/detection_spec.lua. GearMatic side: VendorAutomation.RegisterSellGuard (GearMatic, unreleased).
  • BuildArrowPage (order 3.5): the guide arrow frame controls and toggles under an intro description (opt_arrow_tab_desc, 12 locales) as their own tab.
  • Social.lua (new): Social:Grouping (IsInGroup/IsInRaid/IsPartyLFG/IsInInstance pvp/IsInGuild), Social:ChannelsFor(social, grouping) (one group chat type, GUILD, EMOTE only without a group line), Social:Send (group/guild through LibMatic-Broadcast-1.0:Broadcast marker 8, emotes through C_ChatInfo.SendChatMessage), Social:Announce(kind, link) called from Alert.lua's OnSourceAdded (item link) and OnMountAdded (C_MountJournal.GetMountLink); social.guild defaults to true with a one-time _socialAnnounceWired flip; social_collected_*_fmt / social_emote_*_fmt in 12 locales; tests/social_spec.lua. The new-matic-addon skill now requires a sender behind BuildSocialPage.
  • BuildPanelPage (order 2.5) holds lockoutsFrameGroup and lockoutsFontGroup under an intro description (opt_panel_tab_name/desc); General overrides the extras' loginBanner label with toggle_welcome_message*; support.discordIcon/coffeeIcon and nav.extraLinks (X, opt_x_link) with Media/Textures/discord.tga, bmc-logo.tga, x-logo.tga; the Startup command list carries every slash branch (core_cmd_test/stop/reset/here/diag/changelog); /am changelog branch. The new-matic-addon skill now requires the support icons and the X link.
  • Launcher.lua (new): Launcher:AttachMedallion (emblem hover hint, right-click MenuUtil.CreateContextMenu with MenuEntries, drag forwards to the panel), Launcher:Setup (LibDataBroker launcher object shown through LibDBIcon, state in profile.minimap), SetMinimapShown / MinimapShown; Core re-runs Setup on profile change/copy/reset; Libs/LibDataBroker-1.1 and Libs/LibDBIcon-1.0 embedded; menu_*, medallion_hint, minimap_*, toggle_minimap_button* in 12 locales; tests/launcher_spec.lua.
  • Lockouts:ShowFilter / SetShow / ExpansionFilterActive (LockoutsUI.lua) hold the show boxes in AppearanceMaticDB.widget.show (missing = on); PassesExpansionFilter reads them (current covers the current expansion and season instances, legacy the rest); ExpansionFilter / CycleExpansionFilter / ExpansionFilterLabel and the filter button are gone, with lock_filter_label, lock_hide_expansion, lock_all_expansions, lock_expansion_only, lock_expansion_filter_title/desc. Five UICheckButtonTemplate boxes (FILTER_LEFT/TOP, CONTENT_TOP 104) with lock_filter_legacy/other and lock_filter_*_hint in 12 locales; toolbar rows under the medallion (TOOLBAR_TOP -64, FILTER_TOP -90, CONTENT_TOP 118) spanning the full width; lock_panel_title is AppearanceMatic (was APPEARANCEMATIC until 2026-08-30); lock_unstar_confirm reworded. tests/lockout_panel_spec.lua, tests/show_filter_spec.lua.
  • Lockouts:PauseRoute / ResumeRoute / ToggleRoutePause own the guide's rest state (routePaused); /am stop goes through PauseRoute. The Routing header places one Button (Lockouts:EnsureRouteToggle / PlaceRouteToggle, GetUnboundedStringWidth after the text) drawn from Media/Textures/route-toggle-on.tga / route-toggle-off.tga (256x128, both switches fit the same 244x116 box); the header pin is 18 px. lock_routing_resumed, lock_route_toggle_on/off/hint in 12 locales; tests/route_pause_spec.lua.
  • Route rework: RouteList gains raids (HasRaid/AddRaid/RemoveRaid/ToggleRaid/RaidPosition) and Clear; Lockouts:TrackTarget/EntranceTarget/RaidFinderTarget return raw w2ts* targets (the old TrackLink labels are gone) that NoteRouteLink records for the route; BuildContent renders a Routing section from Lockouts:TravelOrder (nearest first, the guided entry anchored, MapWorld for vendor and world spots) with renderRaid shared by the section and the list, records routeOrder for the guide, prunes routed raids with nothing left once routeSettled (set by PruneCollectedFavorites) and dropsIncomplete allow, and keeps the list itself sorted by name (BuildRaidRoute removed, PriorityRaid only pins the instance you stand in). HandleRouteLink handles w2tsrouteraid: and w2tsrouteclear (APPEARANCEMATIC_ROUTE_CLEAR popup); ToggleRouteItem/ToggleRouteRaid/ClearRoute/EndGuide/GuideRouteNext(announce)/RouteAdvanceIfDone drive the guide from the route (login +12 s, ZONE_CHANGED_NEW_AREA +3.5 s, UPDATE_INSTANCE_INFO); ResumeGuide no longer adopts Blizzard-tracked items; IsGuidingQuery is IsRoutingQuery; Favorites:GuideToVisual follows TrackTarget. Stale route entries (an unstarred item; a raid with nothing starred left once settled, never one the unattainable filter hid) are pruned before the empty-panel return, which also keeps the clear link; single routed drops leave their raid block below; the guided entry is anchored whether it is tracked, entrance-routed or Raid Finder-routed (routeOrder[i].guided), and RouteAdvanceIfDone reads that flag; a collected item only advances the guide when it was on the route (or was the guided item) and /am stop is not holding it. Locale keys lock_routing_header, lock_nothing_routing, lock_route_clear, lock_route_clear_confirm, lock_route_clear_hint, lock_route_raid_hint added and lock_guide_me, lock_guiding, lock_guiding_marker, lock_nothing_guiding, lock_guide_hint_track, lock_route_header removed in all 12 locales; tests/route_panel_spec.lua (new), tests/route_list_spec.lua, tests/routing_query_spec.lua (was guiding_query_spec), tests/track_link_fallback_spec.lua.
  • LockoutsFont.lua: Lockouts:ApplyFont(fs, role, fixedSize) / RefreshFonts / ResetFontSettings / PanelFontPath (LibSharedMedia face on enUS/enGB, the locale font file on koKR/zhCN/zhTW/ruRU, STANDARD_TEXT_FONT elsewhere, the GearMatic handling) driven by profile.lockoutsFont = { font, fontSize, fontOutline }; AddLine fonts every line by role (lines keep wrapping: the client's hyperlink hit-testing ignores SetTextScale, so a FitText-shrunk header answered clicks only where scaled and unscaled text overlapped); the RosterFrame title (applyFont), search box, character dropdown, filter and minimize buttons take the same font; options group lockoutsFontGroup built with Settings:BuildAlertFontControls; lockfont_* keys in 12 locales; tests/panel_font_spec.lua. The derived-font TEXT_SCALE approach is gone.
  • Acquisition:EventNote returns a locale key and the row builders store it as noteKey (the offline dump has no locale, so the key used to be baked into note); Acquisition:NoteText(rec) resolves it at render (panel, mount rows, wardrobe tooltip); tools/export-acquisition.lua emits noteKey and migrates legacy note keys on regeneration; Data/Acquisition.lua regenerated.
  • Acquisition:CalendarEventNames scans the client calendar once per session (18 months back, 12 ahead, C_Calendar.GetDayEvent holidays) with the region aliases (EU/KR/TW IDs onto the shipped US ID, 237 onto 239); EventName falls back to it after the shipped D.events. Acquisition:QuestTitle returns nil until the client names the quest; the access line then reads acq_access_quested ("Quested", 12 locales) instead of a number (acq_access_quest_id_fmt removed).
  • RouteList.lua (AppearanceMaticDB.routeList, sequence-ordered appearances[visualID] / mounts[mountID], Has/Add/Remove/Toggle/Ordered/Position/First, adding stars the item); wardrobe and mount-journal route buttons (Favorites:EnsureRouteButton, EnsureMountRoute) with Media/Textures/add-to-route.tga / add-to-route-dim.tga (256x256 uncompressed 32-bit); Lockouts:RouteIconLink / HandleRouteLink / ToggleRouteItem (w2tsroute:<a|m>:<id>:<sourceID>), NoteRouteLink records each row's own track link during a build and GuideRouteItem follows it after a dry build, GuideRouteNext after OnTrackedCollected drops the collected item; BuildContent renders the Route section above Guiding and hoists route raids to the front; lock_route_header, route_tooltip_*, lock_route_next in 12 locales; tests/route_list_spec.lua.
  • Acquisition:IsUnattainable(rec) (availability state 1, or rwp at or below the client build); generalSettings.hideUnattainable (default off) with toggle_hide_unattainable* strings; Lockouts:AttainableOnly / PruneUnattainableDrops filter vendors, other items, mounts and raid-drop mounts in BuildContent (mount entries now carry acq); Lockouts:UnstarLink / HandleUnstarLink (w2tsunstar:<a|m>:<id>:<sourceID>, first in the click chain, lock_unstar* strings) call Favorites:Set / MountSet and stop a guide aimed at the item.
  • Lockouts:EnsurePanel builds the panel with LibMatic-RosterFrame-1.0:New (skipRows, the library's scroll/content pair hosts the lines; onRefresh re-wraps only when the content width changed, tracked by Lockouts.builtWidth); drag and the resize grip respect the lock option; AppearanceMaticDB.widget also stores w/h; ApplyMinimized hides the toolbar and grip at 60 px; embeds.xml loads the library after FitText; tests/lockout_panel_spec.lua.
  • /am harvest, Acquisition:Harvest and the lock_harvest_* / core_cmd_harvest strings are removed; shipped data comes from the offline refresh.
  • Acquisition:GetMount / GetVendor read shipped rows and the SavedVariables cache only; the live _G.AllTheThings lookup (SourceDB, HasSourceDB, NeedsRefresh, MergeWalk) is gone from the client. WalkVendor / WalkMount take groups from Acquisition.SearchGroups, which only tools/dump-source.lua sets while it builds Data/Acquisition.lua (the data itself is derived from the AllTheThings database, MIT licensed).
  • Lockouts:RouteGuideToTomTom routes vendor/world items from Lockouts:ShippedTarget before asking Blizzard's tracker (ShippedHopOverride removed); the track link carries a zone-only flag (:map:x:y:z), saved as guide.fallbackZoneOnly; Favorites:GuideToVisual goes through TrackLink/HandleTrackLink.
  • PLAYER_ENTERING_WORLD resumes the saved route and then the saved guide; ResumeGuide marks the route as already owned so nothing starts twice.
  • Route:InvalidateGateways also drops the discovered-pin cache (/am reset rescans).
  • Localization: route_gateway_locked_fmt, route_discover_hint_fmt, lock_access_label, gate_* in all 12 locales.

In-Game

  • AppearanceMatic now speaks your language. Options, tooltips, alerts, the lockouts panel, guide messages, and the what's-new popup are fully translated into German, Spanish, Mexican Spanish, French, Italian, Korean, Brazilian Portuguese, Russian, and both Chinese scripts. New collected-item alerts also use your language's wording by default.

Changed

  • Full localization pass: every user-facing string across Core, Alert, Acquisition, Favorites, Bookmark, Preview, GuideArrow, Route, the Lockouts UI, and the options panel moved into AceLocale keys (157 enUS keys, 12-locale parity); the changelog popup chrome now resolves through clp_* keys; AceDB alert-format defaults read the locale at runtime.

In-Game

  • Fixed the custom font choices showing up on non-English game clients. The decorative fonts only contain Latin letters and render as empty boxes in other languages, so the custom fonts are now offered on English clients only; every other language keeps the game's own fonts.

Fixed

  • Vendored LibMatic-FontPack-1.0 (MINOR 2) and LibMatic-CustomMedia-1.0 (MINOR 3) now refuse to register fonts on non-English clients, so the shared-lib path can no longer put Latin-only fonts into the font dropdowns.

Added

  • Retrofitted the addon onto the shared LibMatic framework base (the same base CritMatic, GearMatic, RepMatic, and XPMatic use), bringing full options tabs, alert stacking, bounce, sound, social broadcast, changelog popup, and AceDB profiles.
  • New Core.lua (AceAddon) and Alert.lua (stacked alert engine) replace the old hand-rolled Alerts.lua. Alerts now fire through LibMatic-AlertStack-1.0, LibMatic-CombatTextBounce-1.0, LibMatic-AlertShadow-1.0, LibMatic-SoundPack-1.0/SoundThrottle-1.0, and LibMatic-OverlayGuard-1.0 instead of a raw RaidWarning-style popup and PlaySound call.
  • New Modules/AppearanceMaticOptions.lua builds a full LibMatic-Settings-1.0 panel: General (alerts toggle, login banner/update notify/chat notifications/debug, tooltip expansion toggle, Lockouts panel frame controls), Alerts (per-type message formats with preview, globals, animation, icon, font, bounce style, position), Sound (per-slot sound pickers, mute, channel/rate limit/once-per-login/in-combat, custom sound scan), Social (party/raid/guild/battleground/emote), Change Log, and Profiles.
  • New Modules/ChangeLogPopUp.lua wired through LibMatic-ChangeLog-1.0 with a v1.1.0 entry.
  • New AppearanceMaticSettingsDB AceDB SavedVariable (profiles) holds all new settings, kept fully separate from the existing AppearanceMaticDB data table (favorites, mountFavorites, preview, widget, lockouts, acquisition, alerts, diag) so no player data could be lost in the migration.
  • The legacy AppearanceMaticDB.alerts.enabled/.sound values are migrated once into the new settings profile on first load (sound kit 888 maps to the "Level Up" LibSharedMedia sound, kit 0 maps to muting sound). The old table is left in place untouched.
  • Lockouts panel gained Show, Lock Position, Scale, Opacity, and Reset Position controls (LockoutsUI.lua: Lockouts:IsLocked/SetLocked/SetScale/GetScale/SetAlpha/GetAlpha/ResetPosition), driven from the new options panel and persisted in AppearanceMaticSettingsDB.profile.lockoutsFrame.
  • Full Ace3 + LibSharedMedia-3.0 library stack vendored under Libs/ (matching CritMatic's set); Libs/LibMatic remains a symlink to the shared libmatic repo.
  • embeds.xml rewritten to load the full LibMatic module set (CombatTextBounce, IconPicker, CountUp, ChangeLog, Settings, AlertPositioner, AlertShadow, AlertStack, Startup, Broadcast, OverlayGuard, SoundPack, SoundThrottle, CustomMedia, VersionCheck, FontPack, FitText) plus the 12-locale Localization set.
  • Full localization: Localization/enUS.lua plus enGB, deDE, frFR, esES, esMX, itIT, ruRU, koKR, ptBR, zhCN, zhTW, all translated with the same key set.
  • tests/settings_migration_spec.lua: pure-Lua coverage of the legacy sound-kit-to-LibSharedMedia-name migration.

Changed

  • /am settings, /am options, and /am config now open the new LibMatic-Settings options panel instead of the old Blizzard Settings panel. Added /am help.
  • Favorites.lua's expansion-in-tooltip hook is now gated by the new "Show Expansion in Tooltip" option (generalSettings.expansionInTooltip, default on).
  • AppearanceMatic.toc: embeds.xml first, added Core.lua, Alert.lua, Modules\AppearanceMaticOptions.lua, Modules\ChangeLogPopUp.lua; added ## SavedVariables: AppearanceMaticDB, AppearanceMaticSettingsDB and ## IconTexture; version bumped to 1.1.0.

Removed

  • Alerts.lua (hand-rolled Blizzard Settings panel + raw PlaySound kit 888 alert) replaced by Core.lua/Alert.lua/Modules/AppearanceMaticOptions.lua.

1.0.0

  • Initial release: favorites, mount favorites, preview panel, bookmark, tracking, raid lockout tracking, acquisition data, and alert notifications.

This mod has no additional files