Questbook-v0.0.2
What's new
Questbook Changelog
[v0.0.2] - 2026-09-19 - The HUD, routes, the maps, the Active and Places tabs, Guide me
One day's work after the first release, in the order it happened (newest first): a fighter-jet HUD
that leads the player to whatever they click in the quest panel; routes over QuestDB's travel graph
that take the boat, the zeppelin or the flight path; the route drawn on the world map and the
minimap; the Active tab tied to the game's quest log; the quest menu on the launcher; Guide me
from the panel and from each Active row; and the Places tab, which puts any dungeon entrance, flight
master, inn, graveyard or named place on the HUD. The version was set by the operator at release
time.
Verified in game by the operator during the day: the HUD, the routes (the walk to the flight
master, -> FLY, ALT), the lines on both maps, the Places tab, Guide me, and the flight curves
once QuestDB's MINOR 7 was loaded. Not verified: the boat/zeppelin path work (no data yet, below),
boarding a flight the route did not plan, the aboard-a-boat detection, the Active-row arrows, the
wanted-quest handover on accept, the Titan Panel hover, and the minimap line with rotation on.
Needs QuestDB's travel graph (LibQuestDB-1.0 MINOR 6) for routes, the map lines and ALT, and
MINOR 7 for the flight curves; without the graph the HUD still points straight at the target, and
with MINOR 6 a flight draws straight.
The boat's and the zeppelin's own path (2026-09-19, morning)
Operator, after QuestDB's MINOR 7 put the gryphon's curve on the world map: "the flight path is better, can you ensure the zepplin and boat paths are incliuded". QuestDB's graph carries no polyline for a transport link, so a boat or zeppelin still drew as the straight line between its two ends -- across the sea, a line through nothing.
- Asked QuestDB for the ship's path on every transport link (inbox
ba15a5493430): the sameTaxiPathNodecurve the flights came from, since a transport moves along a taxi path too. With one rule the flights never needed: a zeppelin's path CHANGES CONTINENT mid-route, and the two continents' server yards are different frames, so a crossing ships in two halves under the same key -- each continent's table holds only the points on that continent, and the library's per-continentgraph.pathanswers with the half it has, no API change. Nothing further is needed here when the data lands. - Questbook is built for it.
Router:Routereads a ride's near half from the boarding continent's graph and its far half from the far end's, and stamps every point with the continent it is on (point.continent);Tracker:Pathcarries that through, so each map draws the half on its own continent and the line breaks where the map changes. A ride with no path stays its two ends, as before. - Aboard a boat, the HUD now knows it. The client says nothing about being on a transport
(
UnitOnTaxiis flights only), soRouter:Aboardtells from the ship's path alone: withinABOARD(40) yards of it -- the perpendicular distance to its segments, since a straight crossing's points can be hundreds of yards apart -- and at leastABOARD_MIN(30) yards along it from the boarding point, so a player waiting on the dock is not aboard. Aboard,Router:Steerpoints at the next turn of the ship's path and flagsriding(soTracker:Waypointdoes not count the dock falling behind as straying and re-plan from the deck, which is what happened in the air before the taxi hold),Router:Remainingcounts what is left of the path on this continent, and the map line runs from the ship forward instead of back to the dock. - The fixture graph was dishonest and is fixed. Its east-to-west flight crossed continents with every point in one table, ending in the far continent's frame -- a shape QuestDB never ships (a map-changing flight has no polyline). It now splits like the contract says, and the boat east-to-west carries a two-halves path (out to sea on continent 0, in from the sea on continent 1); three specs that had pinned the flight's middle point as being on the FAR continent were wrong by the old approximation and now say continent 0, which is where (2000, 1500) is.
- 578 specs (+3: the two-halves route, aboard the boat through the router, and through the tracker and HUD), 100% over Router and Tracker, luacheck clean.
Not verified in game: any of it -- there is no transport polyline to ride yet. When QuestDB ships,
the checks are: from the Orgrimmar zeppelin tower the line should follow the zeppelin out over the
Barrens edge; aboard, the HUD's caret should point along the zeppelin's path and the readout keep
BOARD ...; the Undercity map should show the path in from the edge to the tower.
A waypoint you walk past is passed (2026-09-18, night)
Operator, with a minimap screenshot of the line doubling back behind them: "if i 'move around' a pin point, we fix it and snap to me. right now it's waiting for me to hit a point i passed and i'm waaaay past it".
- The route's cursor moved on only when the player came within 15 yards of a point, or when a
later point of the same leg was nearer -- so where the road's points are far apart, a player who
walked round one at twenty yards was held to it until they were nearer the next, and the HUD and
the map line pointed back. A point now also counts as passed once the player is beyond it in the
direction of the next one, however far to the side (
Router.Passed, a projection onto the route's direction;Router:NextPointfinds the next point that is somewhere else, since a road's first point is the node before it and a ride boards where the road ends). A player who starts on the road past its first node is no longer sent back to it either -- which changed two router examples that had pinned the old behaviour. - Also found by the operator on the Places tab and fixed in the hour: a second target from the same
spot raised in
Tracker:Waypoint(the stray record's key survived a new route while its base did not); both are reset together now. - The world-map line stopped at Orgrimmar's gate (operator: "i'm in org, and the path should
lead me to the zepp, but it's not unless i go to the next map zone"). This answered the question
the map line's audit had left open:
C_Map.GetMapPosFromWorldPoswith the open map as override returns NOTHING for a point off that map, so inside a city every point of the route past the gate was a gap. Points are now placed by arithmetic against the open map's own world corners (MapLine:MapRect, twoGetWorldPosFromMapPosreads per map, cached -- HereBeDragons' way of reading a map), which puts a point off the map outside 0..1 for the scroll frame to clip, so the line runs out through the gate toward the zeppelin. The operator's next report -- the flight showing right only on the continent map, not the zone's -- is the same fault: from the zone map, the far flight master was off the map and unplaceable. - In the air, the route held; it re-planned from the gryphon. (Operator, over the Barrens:
the line ran from the gryphon to a road node and down the road on foot.) The cursor sat on the
boarding point behind, so the stray rule saw the player far from it and planned a WALKING route
from mid-air. Now, while
UnitOnTaxi("player")is true (the call Era's own UI reads, Blizzard_UIParent/Vanilla/UIParent.lua:664), the route's flight leg is the one in hand wherever the cursor was when the player boarded, the waypoint is where it lands, the yards left count straight from the air, the stray rule stands down, and the map line runs from the gryphon along the flight's points still ahead. NoALTin the air: the gryphon's height is nobody's data. - A flight draws as a straight line between the two flight masters (operator: "it's a
straight line between FP, not the actual flight path, do you have coords for the path?").
QuestDB's graph carries a polyline for road links only; the curve a taxi flies is the client's
TaxiPathNodedata, which QuestDB's pipeline can ship in the same shape -- requested (threaddae84f2eca56). Questbook is ready for it: a ride leg is now board -> the link's polyline -> far end when the data has one (the fixture graph carries one flight curve), the ride's far end is its last point everywhere, and the map line draws the curve faint. QuestDB delivered it the same night (LibQuestDB-1.0 MINOR 7, in its working tree, not yet released): every outdoor flight on Vanilla carries the gryphon's route, thinned at 30 yards -- 266 of 266, ~2,600 points per continent; Orgrimmar -> The Crossroads is 27 points. On TBC the nine flights that change map mid-route ship no curve and draw the chord. Questbook needed no change: the suite loads that working tree and stays at 575 green. What the operator sees depends on which QuestDB their client has loaded -- the curve appears once QuestDB ships MINOR 7. - Two more from the self-audit, fixed before filing. The passed-point rule was a bare
projection, and on a switchback (a mesa ramp) a player halfway up one leg is also "beyond" the
start of the leg above, which runs the other way -- the cursor would have skipped the hairpin.
The player's facing now has to agree with the segment's direction as well (
Router.Passed; specced on a sparse hairpin, with and without a facing). And a place from the Places tab -- or a[hud]line from another quest's panel -- was taken off the HUD by the next quest-log update, which fires on any loot, because the active quest re-aimed on every update. A hand-picked target now stands until the player stops it or sets a quest on purpose (ActiveQuest.HandPicked, theforceonPoint), and stopping a place brings the active quest straight back. - From Peer Review on that audit (
1a232a148585): the route now knows where a flight is bound. No call says where a taxi in the air is going, but the client's own flight map takes a flight withTakeTaxiNode(index)and names the node withTaxiNodeName(index)(Blizzard_UIPanels_Game/Classic/TaxiFrame.xml:18, TaxiFrame.lua:134), so a post-hook on the global sees the destination at boarding -- the technique Leatrix_Plus, FlightTimerClassic and NovaWorldBuffs ship on this box. Once aloft, if the flight taken does not land where the route's flight does (the taxi node's name, "The Crossroads, The Barrens", begins with the graph's flight master's, "The Crossroads"), the route is planned again FROM the real landing and the HUD steers at it for the rest of the flight -- rather than at the wrong tower, which was the accepted cost before. A non-English client, whose taxi nodes are named in its own language, keeps the old behaviour. Two more from the same audit: the giver of a wanted quest comes back on the HUD at login (once, at Questie-ready -- never from a log update, which would undohud offon the next loot); and the Places arrow knows its row by id, so it stays gold after the list rebuilds on Questie-ready. 575 in all, 100% over 26 files.
The Places tab: anywhere in Azeroth on the HUD (2026-09-18, evening)
Operator: "can we add a tab to allow folks to select targets like the dungeons, so the hud can guide them there? we'll want a strip list of filters to filter it down, but this could be a great tool for navigating around azeroth".
- A fourth tab, Places: every instance entrance, flight master, inn, graveyard and named subzone the HUD can be pointed at, with a one-row strip -- Search, Kind, Zone, Clear -- and the Active tab's arrow at the end of each row. A click on a row (or its arrow) puts the place on the HUD; the same click again takes it off. The status line says how many places pass and what is being guided to. The strip's values are remembered per character, like the other two strips'.
- Two sources, both already in hand (
Modules/PlacesIndex.lua). Questie's instance table (ZoneDB:GetDungeons, Database/Zones/data/dungeons.lua) gives every dungeon, raid and battleground with its outdoor entrance, faction-aware for the battlegrounds; the table spans every expansion, so an entrance in a zone this client cannot place (Howling Fjord on Era) is dropped. Raids and battlegrounds are told apart from dungeons by a short table of Era's area ids -- Questie's entries carry no kind. QuestDB's travel graph gives ~1200 named points on Era:<Zone> <Subzone>for every subzone,<Place> flightMaster,<Place> innkeeper,<Place> spirithealer-- the kind is read off the name's last word, and the engine-named nodes (Zeppelin - ...,[PH] Teleport to ..., portals, elevators, instance doors) are left out. Each node's zone is the CLIENT's name for the map its world position falls in (C_Map.GetMapPosFromWorldPoswith no override, thenGetMapInfo), so it is localised and needs no table of ours. - A place from the graph is tracked by its world position directly (
Tracker:Tracktakes aworld = { continent, x, y }alongside the Questie-coordinate form), so no map conversion is guessed for it; a dungeon entrance is tracked by Questie's coordinates as a quest place is. - The list is built once and rebuilt when Questie reports ready. Guiding to a place leaves the active quest alone; the HUD shows the place until the quest's next place changes.
QuestieAdapter:GetDungeons(); the fake Questie modelsGetDungeonsandGetLocalizedDungeonName.Tests/places_spec.lua(14 examples: classifying nodes, both row sources, the filters, the tab). 560 in all, 100% over 26 files.
Verified in game by the operator: "the places and guide me works!" -- and it found a bug of
mine: clicking a second place from the same spot raised attempt to perform arithmetic on field 'strayBase' (Tracker.lua:156). A fresh route reset the stray record's base but not its key, and
every route planned from one spot opens at the same nearest node, so the second route's first
waypoint matched the old key with no base behind it. Both halves are reset together now, and
router_spec pins a second target from the same spot. Still to look at: whether the Zone dropdown
(~60 entries) is comfortable, and how the graph's English subzone names read beside the client's.
Guide me: from the quest panel, and from each Active row (2026-09-18, evening)
Operator, with the panel open on Etched Parchment: "now we need a button in the drilldown to change that quest to your active target, so it guides you to the quest giver" -- and, on the Active tab, "put a button at the end of each row here, to make that your active quest in the HUD". Then, on the HUD: "can you put the ALT next to the top compass row instead of lost in the text? i'm ok with it being on the right side a bit".
Guide meat the top right of the quest panel, on every tab. A quest in your log becomes the active quest, exactly as the Active tab does it. A quest you do not have yet has the HUD pointed at its giver -- or, for a quest that starts from an item, at what drops it -- and is remembered as WANTED: the moment it appears in your log it takes over as the active quest, so the guidance runs on from the giver to the objectives without another click (ActiveQuest:Guide,ActiveQuest.PickStart, the log update inOnLogChanged). The button readsStop guidingon the quest being guided, and a click then stops. Hovering says which of those a click will do, or why it cannot guide (a giver Questie has no place for).- An arrow at the end of every Active-tab row -- the map's own player arrow, gold on the active quest's row, grey on the rest -- makes that quest active. The row's click now only shows the quest in the panel, as on Browse; it used to make the quest active, and with a button asked for, that would have been two things doing one job.
ALTmoved from the readout line to the centre mark's row, at the right end, above the tape: an instrument of its own rather than a word after the ETA.RowList's per-row actions are wired throughResultsList:Create(a third argument); they are part of the list's construction and each tab's list has its own.- Specs: active_spec (seven, a fixture quest for each of the three Guide cases). 546 in all, 100% over 24 files.
Not verified in game: the Guide button, the row arrows, the wanted-quest handover on accept, the
moved ALT.
Markers in Questie yellow, the ride the walk is for, and altitude (2026-09-18, evening)
Operator, from the second in-game run, with a screenshot of the HUD in Thousand Needles: "the markers need to be a different color than the rest of the HUD. it's pointing me at my quest target with a green arrow ... how about questie yellow? The HUD should ALSO be pointing me to the nearest flight path which is the shortest route, which is in a different direction and above me. we should add elevation on the readout ... so you know how far above or below you are of the target."
The screenshot shows both carets already drawn -- the target's ^ under SW, and the route's steer
chevron << at the tape's left edge -- in the same green, the same shape, with nothing saying what
the second one was for. Three changes:
- The two carets are now drawn in a MARKER colour, Questie's quest yellow (
FFFF00, QuestieLib.lua:58) by default, with its own picker under Appearance > HUD. The instrument stays in the HUD colour; what to steer for stands out from it. - The steer caret has its own chevrons,
<*and*>, so past the shoulder it no longer reads as a second copy of the target's<<. And the readout says what the walk is for: on a walk leg it names the ride the route is heading to ---> FLY Freewind Post-- or, with no ride ahead, the road's end; on the ride itself it saysBOARD/FLYas before. Before this a route via a flight master showed a bare walk with no destination named, which is what the operator was looking at. ALT +120at the readout's right: how far above (+) or below (-) the player what the leg leads to is. The client gives no height for the player --UnitPosition's Z is a placeholder, always 0 (warcraft.wiki.gg/API_UnitPosition), and nothing else does -- so both heights come from QuestDB's graph: every node and every road point carries the bots' Z. The leg's height is the node it ends at (or the boarding point of a ride); the player's is the nearest route point's, once one is within 50 yards (Router.GROUND_RANGE), remembered as the cursor moves on. SoALTappears once the player is on the route, not before, and never for the final walk to a Questie target -- Questie records no heights. A flight master on a mesa reads+100from the foot of its ramp.- A player already on a road joins it where they stand. A route always opened with a walk to
the nearest node, and
Router:Steeronly snapped the cursor within a leg -- so someone halfway along the first road was walked back to its start (found writing the map-line spec; Peer Review ranked it the first thing a real player would hit). The approach leg is now dropped when the first road passes nearer the player than its start node does, and the cursor starts at that point; the yards left count from there. A ride straight from the node, or a player nearer the node than any of the road, still walks to the node. - The operator's second screenshot showed the two markers working (yellow
*on the tape toward the flight master,<<for Dire Maul behind,-> FLY The Crossroads...,ALT +38) and asked what the*was: the Appearance > HUD blurb now says what each glyph is. - Peer Review on the map line's audit (
7c745af8c98b):Minimap:GetZoomLevels()is documented, so the six-level table is checked against the client's count once at install and a mismatch is said in chat; a zoom past the table takes its last entry rather than its widest. - Specs: hud_spec (four), router_spec (four), mapline_spec (one); the fixture graph's East Flight Master now stands 100 yards up with a road climbing to it. 539 in all, 100% over 24 files.
Verified in game by the operator's screenshot: the yellow markers, the -> FLY line and
ALT +38 all drew. Not verified: the elevation's sign at the foot of a ramp (Z up is the
emulator's convention), and the road-join fix, which came after the screenshot.
The route on the world map and the minimap (2026-09-18, late afternoon)
Operator, when the HUD work began: "i want it on the map and minimap as well" -- the one piece of that ask still unbuilt after the HUD, the Active tab, the menu and the router.
Modules/MapLine.lua-- the HUD's route drawn as a line, in the HUD colour, on both maps. What is drawn isTracker:Path(): the player, then what is left of the route from where they are on it (a ride's boarding point, then its far end drawn fainter), ending at the target -- or the straight line to the target when there is no route. OneLinetexture per segment, pooled and reused.- The world map gets a data provider (
WorldMapFrame:AddDataProvider, the mixin every Blizzard layer uses), so the line is laid out again whenever the map opens, changes or resizes -- no pin template and no map library. Each world point is placed on the OPEN map throughC_Map.GetMapPosFromWorldPos(continent, position, openMapId); a point on another continent is a gap in the line, so a sea crossing shows the near shore on one continent's map and the far shore on the other's, and a point off the open map's edge runs to the edge and is clipped by the scroll frame. The line follows the player while the map is open, stays one width on screen as the map zooms (the canvas is scaled, not resized --MapCanvas_ScrollContainerMixin.lua:326), and sits at frame level 1999: over the art, under every pin (Blizzard's start at 2000). Points under 4 canvas pixels apart are folded into one segment: QuestDB's roads are thinned to 8 yards, under a pixel on a continent map, and a cross-continent route drawn point for point would be a thousand lines re-anchored ten times a second while the map is open. - The minimap gets a surface over
Minimapwith each point at its yard offset from the player, scaled by the yards the minimap shows atMinimap:GetZoom(). Era has noC_Minimap.GetViewRadius, so those are the figures every minimap library has carried since Astrolabe (HereBeDragons'266 + 2/6at level 3 is a typo for 800/3 -- its own comment says 1.75, and 1400/3 / 1.75 is 266.67), with the indoor set when the minimap's zoom is not theminimapZoomCVar's. When the minimap rotates (rotateMinimap) the offsets are turned by the player's facing so the line points the way the map does. Every segment is clipped to the disc at 92% of the half-width, so nothing paints over the rim. - Sampling: the HUD samples the tracker while it is on screen. With the HUD switched off and the line kept, the minimap surface's own OnUpdate samples instead -- so the line moves either way, and never twice in one frame.
- Two settings under Appearance > HUD: Line on the world map and Line on the minimap, both on. The colour picker repaints the line.
Routerlegs now carry theircontinent;Tracker.Vectorbuilds positions withCreateVector2D(the client's own callers do; whether a plain table is accepted was never verified).Tests/mapline_spec.lua(35 examples: the geometry,Tracker.Path, both surfaces, the sampler); the router's fixture graph moved toTests/fixture_paths.luaso both specs share it;env_questbookstands in aWorldMapFramecanvas, the provider mixin andMinimap:GetZoom, all requested from the harness (threadee519ebd4bfe). 531 in all, 100% line coverage over 24 files.- Found on the way and filed as work, not fixed: a player standing mid-road is routed BACK to the
nearest node first, because a route always opens with a walk to that node and
Steeronly snaps the cursor within a leg. Whether the bot graph's nodes are dense enough to hide it is a question for the first in-game run.
Verified in game by the operator the same evening: "i see the lines on the minimap, that's
cool" and "it's on the main map too" -- so the yards-per-zoom table, the world-to-minimap
convention and the world-map placement through GetMapPosFromWorldPos with an override map all
hold on the real client. Not verified: the minimap line with minimap rotation on.
Routes: the boat, the zeppelin, the flight -- and instance entrances (2026-09-18, afternoon)
Operator, from the first in-game HUD run: "i selected a quest, the hud isn't pointing me anywhere, no arrows or indications where to go. in this case, it should be to the right zepplin or to the boat, whichever i'm closer to?" -- and, on a Dire Maul quest, "i selected a quest for the continent i'm on, and the HUD disappeared". Both are the same gap: nothing outdoors to aim at.
Modules/Router.lua-- Dijkstra over QuestDB's travel graph (LibQuestDB:GetPathGraph, MINOR 6, built from the emulator's own bot-walking network: named nodes, directed WALK / AREA_TRIGGER / TRANSPORT / FLIGHT / PORTAL links, walked polylines thinned to 8 yards). Costs are in SECONDS, so a zeppelin and a boat compete on time and the nearer one wins: a walk at 7 yd/s with swimming penalised, a boat as a 90-second wait plus the crossing at 10 yd/s, a flight as 15 seconds' boarding plus the flight at 32 yd/s, a portal free. All rules of thumb, none measured; they rank, they do not predict a clock. Per your directive relayed through QuestDB, transports and flights are ROUTABLE, never pruned; outdoor area-trigger links (the continent portals) route as a zero-wait step-through. A route is legs -- walk to the dock, board, walk on -- andRouter:Steerfollows them one waypoint at a time, snapping to the nearest point ahead so a cut corner is not walked back, and holding "board" until the player has actually crossed.- In the HUD: with a route, a target on another continent is no longer
OTHER CONTINENT-- the steer caret points at the next waypoint, the range is the yards left along the route, and the readout names the leg (BOARD Zeppelin to Grom'gol). The route is planned once per track and re-planned when the player strays past twice their starting distance from the current waypoint. A new setting, Route over flight paths (on), for a character who has not learned them; boats and zeppelins are always used. - Instance targets aim at the instance ENTRANCE. Questie records every instance's outdoor
entrance, faction-aware for the battlegrounds (
ZoneDB:GetDungeonLocation); a mob, object or turn-in inside one is now tracked there, with the designator sayingvia Alterac Valley entrance. One rule,QuestView:TrackOf, replaces the two copies the pane and the active-quest picker carried. - The HUD no longer vanishes on a quest with no place. It stays up and says
NO PLACE -- <quest> -- Questie records no location for anything this quest needs. Right-clicking the minimap button with nothing tracked shows the HUD with a hint the same way. - The minimap button: left-click the window, right-click shows or hides the HUD, shift-click the settings, and its hover is a tooltip of exactly that. The quest menu is Titan's hover only -- the two libraries prefer different hooks on the same LDB object, so both live on it.
.pkgmeta: thequestdbrelation is back (QuestDB is live on CurseForge), the "install QuestDB by hand" lines are gone from the README and the description, andenv_specpins all six slugs.wow-version-replication.ps1added, the fleet's dev watcher (from QuestDB's copy, which carries the fixed bare-folder rule), targeting_classic_era_and_anniversary_; the TOC now lists20506alongside11509so the Anniversary copy loads. Questbook has never run on TBC -- the interface number says it may load, not that it works there.Tests/router_spec.lua(18 examples, through the real LibQuestDB decoding a two-continent fixture graph with a boat and a flight); 495 in all, 100% line coverage over 23 files.
Not verified in game: the router, entirely. The first check: track an Alterac Valley quest from
Orgrimmar and see whether the HUD walks you to the zeppelin tower (BOARD ...), then across. The
continent ids are assumed equal between QuestDB's server map ids (0, 1) and C_Map's continentID
-- if the HUD says OTHER CONTINENT with a graph loaded, that assumption is the first suspect.
ItemDB v0.10.0 is released (commit e524304, tag ItemDB-v0.10.0, 2026-09-18 15:02): LibItemDB-1.0
MINOR 27 carries CanUse / ClassProficient, the contract Questbook opened this morning (thread
34f589b2). "Usable by me" on the Rewards tab is therefore fully right for any player on that
release; the class-tags-only fallback in RewardsIndex now only serves an older install. One
deliberate widening on ItemDB's side, stated by them: a class the tables do not know fails OPEN in
every branch, shields included.
Peer review e0673211 and the two refactors it called due (2026-09-18)
- Watching a quest goes through Blizzard's own path.
QuestLog:SelectcalledAddQuestWatchdirectly; the review readVanilla/QuestLogFrame.lua:492-517and:742-768: Blizzard's shift-click refuses a quest with no objectives and a full list with two on-screen messages, then callsAutoQuestWatch_Insert, which keepsQUEST_WATCH_LIST-- the Lua list its auto-watch and eviction reason from. A bare engine call left that list short, so Blizzard's cap check would undercount after Questbook had added watches, and a refused watch was a silent no-op. NowQuestLog:Watchreplicates the two guards with Blizzard's strings and callsAutoQuestWatch_Insert(index, QUEST_WATCH_NO_EXPIRE);Selectreturns the refusal reason as a second value. - The quest menu hangs ABOVE a launcher in the bottom half of the screen. A bottom-edge minimap
button is the common case; a menu hung below would be clamped up over the button and the flush edge
would become a gap on the far side, closing the menu before the pointer reached it.
QuestMenu.HangsAbovecompares the anchor's bottom to half the screen. Modules/Player.lua-- the one place that asks the client who the character is (class and race ids and names, name, sex, level with a caller's default, faction). Five files read slices of it straight from theUnit*globals; QuestieAdapter, QuestDBAdapter, RewardsIndex and FilterStrip now read through it. No behaviour change.ns.Adopt(saved, defaults)(Core.lua) -- the one saved-table idiom: fill BY TYPE, copy table defaults, leave nil-default keys alone, return the saved table itself. Replaces four spellings:FilterStrip:Adopt,RewardsView:Adopt(its three number-or-Any keys stay explicit),Hud:Settings,Options:Appearance(which now also fills a missing key on an old save).- A one-line spec pins the list's row pool at or above the Vanilla log cap (review F3).
Tests/player_spec.lua(7 examples); 471 in all, 100% over 22 files.
The quest menu on the launcher: Titan Panel and the minimap button (QB-015, 2026-09-18)
Operator: "do a titan panel integration that shows questbook and when you hover over it, you get a list of your active quests allowing you which one to select to update the hud, so you don't have to open the window to change between your active quests".
Modules/QuestMenu.lua-- a frame of clickable rows, one per quest in the log ([11] Wanted: Hogger, the active one marked>, a complete one(complete)), a header saying what a click does, and the left-click hint at the bottom. A click makes that quest active (the same seam as the Active tab: the game's log selects and watches it, the HUD aims) and the menu stays up so the mark can be seen to move.- No Titan code. Titan Panel 9.3.2 shows every LibDataBroker launcher on its bar (
TitanLDB.lua) and, when the object declaresOnEnter/OnLeave, hands the hover to them and draws nothing of its own (TitanLDB.lua:184-198). Questbook's launcher is that object (Modules/Minimap.lua), so it now declaresOnEnter/OnLeaveINSTEAD ofOnTooltipShow-- LibDBIcon prefersOnTooltipShowwhen present (LibDBIcon-1.0.lua:79-86), and a tooltip cannot be clicked. One object, one menu, both the minimap button and Titan's bar; Titan is not a dependency and is never referenced. - Closes on plain leave events, no timer. The menu is anchored FLUSH under the launcher so the
pointer never crosses a gap; the launcher's
OnLeavehides it unless the pointer is already over the menu (frame:IsMouseOver()), and the menu's -- or a row's, since stepping onto a child Button leaves the frame -- hides it unless the pointer is over the menu or back on the launcher. Tests/questmenu_spec.lua(11 examples), andTests/minimap_spec.luanow proves LibDBIcon's own hover script reaches the menu. 459 specs, 100% line coverage over 21 files.
Not verified in game: that Titan lists the launcher (its "LDB plugins" menu; the player enables
it there), that the flush anchor really leaves no gap on Titan's bar (the bar may sit at the top
edge, so the menu hangs below it), and whether IsMouseOver() on a TOOLTIP-strata frame answers
as expected while the launcher's OnLeave runs.
The Active tab and the active quest (QB-014, 2026-09-18)
Operator: "a 3rd tab that mimics the first tab, that shows you're current engaged quests, with the same split for the details window, and the tie in with the game quest log so you can set the active quest here, to engage the HUD, we'll need that to be able to decide what's in the hud".
Modules/QuestLog.lua-- the only file that reads the game's quest log. Era's log is the Vanilla bare-global API, notC_QuestLog(whose Era documentation carries three functions and the events):GetNumQuestLogEntries,GetQuestLogTitle,GetQuestLogIndexByID,SelectQuestLogEntry,IsQuestWatched/AddQuestWatch,GetNumQuestLeaderBoards/GetQuestLogLeaderBoard, every one read the way Blizzard's ownVanilla/QuestLogFrame.luareads it, every one feature-detected.Entries()(headers skipped, complete and watched flags),Leaderboards(questId)(the client's own counters, "Hogger slain: 0/1"),Select(questId)(the game's log selects AND watches it).Modules/ActiveQuest.lua-- one quest from the log is THE active quest, remembered per character (char.activeQuest).Pick(view, leaderboards, isComplete)is pure and decides what the HUD points at: a complete quest's turn-in; else the first UNFINISHED counter's objective, matched because a counter's text begins with the thing it counts (an item objective goes to its best drop source); else the first objective with a place; else the turn-in. Instance objectives (no coordinates) are skipped.Set(questId)selects it in the game's log, remembers it and aims the HUD; everyQUEST_LOG_UPDATEre-aims it (the HUD moves on as counters complete), and only when the place CHANGES, so the speed readout is not reset every update.Modules/ActiveView.lua-- the third tab, "Active": the sameResultsListandDetailPaneas Browse on the tab's own host frames, no filter strip (a log is twenty quests at most). The active quest's row is marked with a leading>, a complete one(complete); a click shows the quest in the panel and makes it active. The tab opens on the active quest. The status line says how many, and which is active.- Forgetting is the client's call.
QUEST_REMOVED(payload: the quest id) is the only thing that forgets the active quest and hides the HUD. An empty or incomplete log onQUEST_LOG_UPDATEdoes NOT: measured offline, Questie reports ready before the client has filled the log on a warm login, and the first cut cleared the remembered quest on every login for exactly that reason. - The "i" tooltip explains the tab and the
[hud]lines.Tests/active_spec.lua(28 examples);Tests/env_questbook.luastands the eight log globals in (the harness models none of them -- filed with WoWAPITesting as7a01527417ac). 448 specs in all, 100% line coverage over 20 files.
Not verified in game: whether GetQuestLogTitle's isComplete is 1 on Era as
Vanilla/QuestLogFrame.lua:150 reads it (the code accepts 1 or true); whether the client's counter
text really begins with the target's name on a non-English client (it is the localised name there,
against Questie's English one -- the same gap the HUD's LOCKED has).
The HUD (QB-013a, 2026-09-18)
A head-up display that leads the player to a place they click in the quest panel (operator: "what if we injected a hud like tomtom but more like a fighter jet hud instead of a dumb arrow?"). Three new files, one job each:
Modules/HudMath.lua-- the arithmetic, no client in it: range and bearing between two world points, the bearing relative to the facing, compass degrees, the tape's ticks, the caret's offset (clamped with an off-tape flag), speed and ETA from two samples, a smoothing average, and the target cue's colour ramp. Coordinate conventions verified rather than assumed:GetPlayerFacingis radians, 0 = north, counter-clockwise (warcraft.wiki.gg), nil in instances;C_Map.GetWorldPosFromMapPosanswers.x= north,.y= west in yards (HereBeDragons-2.0.lua:179 reads it astop, left).Modules/Tracker.lua-- the target and the client reads: a Questie location becomes a world point throughZoneDB:GetUiMapIdByAreaId(newQuestieAdapter:GetUiMapId) andGetWorldPosFromMapPos, the player throughGetBestMapForUnit/GetPlayerMapPosition/GetPlayerFacing. OneSample()answersokwith range, bearing, relative bearing, heading, speed, ETA,inRangeandlocked, or one ofnotarget/nomap/noposition/othercontinent. No timer of its own -- the HUD samples it from its frame'sOnUpdate, throttled to 0.1s, so sampling runs exactly while the HUD is on screen.Modules/Hud.lua-- the frame: a heading tape (ticks every 15 degrees across 60 either side, the eight compass points lettered), a fixed centre mark with the heading in three digits, the target caret at the bearing (a<</>>chevron at the edge when the target is past the shoulder), a steer caret above the tape for a route's next waypoint (drawn when a state carriessteer; nothing produces one yet), the readouts (RNG 100 yd SPD 7.0 ETA 0:14, thenIN RANGEorLOCKED), the designator (name, level, zone), and the[T]cue. Click-through when locked;/questbook hud unlock(or the Appearance setting) makes it draggable with an outline, and the position is saved per profile.NO POSITIONin an instance,NO MAP DATAfor a target Questie cannot place,OTHER CONTINENTwith the tape still live.
The [T] cue is a SecureActionButton drawn as text. TargetUnit is protected -- accepted only
from Blizzard's secure code, whoever's click it is inside -- so the cue is a SecureActionButtonTemplate
with no texture, type = "macro", macrotext = "/targetexact <name>" (/targetexact, not /target,
which takes a prefix; verified as a secure slash in Era's Blizzard_ChatFrameBase/Shared/SlashCommands.lua:484),
registered for AnyDown and AnyUp as Blizzard's own action buttons are. Attributes are written only
out of combat; a target change mid-fight greys the cue until PLAYER_REGEN_ENABLED applies it. Its
colour ramps from grey through amber to the HUD colour as the range closes, it breathes inside the
targeting range (LibAceGUIWidgets Breathe, MINOR 28), and goes steady once the player's live target
is the tracked name (LOCKED, read back on PLAYER_TARGET_CHANGED). Bindings.xml adds
CLICK QuestbookHudTarget:LeftButton under a Questbook header so a key can press it; the cue's
tooltip names the key.
The targeting range is 40 yards by default and is a guess, stated as such in Tracker.lua: Era
documents no range for a name-target. It is a setting (Appearance > HUD > Targeting range).
In the quest panel, every giver, mob, drop source, event location and turn-in with recorded
coordinates now ends in a muted [hud] and is clickable: the click starts tracking it, the hover says
so. Instance spawns (no coordinates) are not offered. Settings: Appearance gains a HUD section --
show, lock, colour, size, targeting range. /questbook hud unlock | lock | off.
What the HUD cannot do, and why (verified 2026-09-18, all in the bank): nothing can be drawn in
the 3D world on Era -- there is no world-to-screen projection for addons and no C_Navigation in the
Era tree; and nothing can target on the player's behalf outside a secure button. A steer caret that
leads around cliffs needs a walkable road graph, which is requested from QuestDB.
Tests: Tests/hudmath_spec.lua, Tests/tracker_spec.lua, Tests/hud_spec.lua -- 77 new
examples; 419 in all, 100% line coverage over 17 files. Two of them pin cases found on the
self-audit (inbox eee485b94277): a first [hud] click DURING A FIGHT builds the frame then, so no
attribute at all is written until PLAYER_REGEN_ENABLED -- the type = "macro" write moved out of
CreateCue for that reason; and the tape's width reads 0 before the client's first layout pass, so
0 or WIDTH would have put every glyph at the centre for a frame -- Hud:TapeHalf guards on zero. The harness models none of
GetPlayerMapPosition, GetWorldPosFromMapPos or GetPlayerFacing, so Tests/env_questbook.lua
stands them in (a per-map world rectangle, a driveable position and facing) and the gap is filed with
WoWAPITesting; the fake Questie gains a ZoneDB with GetUiMapIdByAreaId fed from uiMaps.
Harness pin cbae63f -> 58f1281, the same afternoon: WoWAPITesting delivered
GetPlayerMapPosition, GetWorldPosFromMapPos, GetMapPosFromWorldPos, GetPlayerFacing and a real
Vector2D (their thread 55fd6e56), so the stand-in above lasted one session and is deleted; the
specs drive wow.playerPosition, wow.playerFacing and wow.mapWorld. One new example rides the
harness's note that the client sometimes answers a facing in [-pi, pi]: the maths already wrapped
it, now it is pinned. Every other Adoption entry between the two pins says "nothing" or "run your
suite"; the suite is green on the new pin.
GetPlayerFacing is UNDOCUMENTED in the Era tree (peer review on the self-audit, and the harness
independently): the doc entry I first cited is the root union file's, not Era's. Its presence on Era
rests on shipped Era code -- Questie's HereBeDragons and DBM-Core call it -- and the code guards for
its absence. So the FIRST in-game check is /dump GetPlayerFacing() outdoors: nil there would
silence the whole HUD, honestly, as NO POSITION.
Not verified in game: all of it. The first things to look at: /dump GetPlayerFacing() (above),
whether the tape's letters read at size 11 in ARIALN, whether the caret lands on the tape where the
target actually is (the coordinate convention is from the wiki and HereBeDragons, not from a run),
and whether [T] targets Hogger on a click and on its bound key.
[v0.0.1] - 2026-09-18 - First release
The first release: the quest browser, the quest panel with rewards and text, and the Rewards tab. Everything below was built before this release, in the order it happened. The version was set by the operator at release time; the sections carried "v0.1.0 (unreleased)" as a placeholder until then.
Not verified in game before release (offline suite only: 339 specs, 100% line coverage): the Rewards section and its item tooltips, the quest text, the Rewards tab, the tab strip, the class/race filter, and the window at its minimum size. The operator's screenshots covered the quest list and the quest panel on real data. The first Rewards-tab open on the full database is unmeasured.
QuestDB is not auto-installed by the CurseForge app in this release. CurseForge rejected the
upload while questdb was listed as a dependency (error 1018, "Invalid slug in project relations:
'questdb' does not exist, is not accessible"), because QuestDB had no published file there yet. The
relation is out of .pkgmeta for v0.0.1 and comes back the moment QuestDB's first file is approved;
in game QuestDB is still required, and the install notes say to add it by hand.
ItemDB is a hard dependency, and was wrongly an optional one until the release (operator:
"libitemdb is a hard dep, why did you make it an optional dep?"). It had been listed as optional when
the TOC was first written and never revisited as the reward links, the tooltips and the whole Rewards
tab came to depend on it. Now in the TOC's ## Dependencies and .pkgmeta's required-dependencies,
so the CurseForge app installs it. The code's fallbacks for a missing ItemDB stay, for a broken install.
Peer review 687ee0b2, 2026-09-18 -- three fixes
- The Rewards tab's checkbox is "Usable by me", not "For me". The Browse tab's "For me" means quests your class and race can take; the Rewards one also means gear your class can use. Two checkboxes with one label and two meanings was the review's finding; the label now carries the difference.
- The Rewards strip's height was marginal. AceGUI's Flow layout makes a row
max(alignoffset) + max(height)/2 + 3, and a row that starts with a dropdown draws 4px lower than that -- so two rows of these controls are 106px or 110px depending on which control leads, against a 108px strip. Now 120px, and a spec lays the strip out at the window's floor and measures where the lowest control lands rather than reasoning about it. Offline only: the harness does not model text metrics faithfully, so this proves the Flow arithmetic, not the pixels in game. - The window floor's
+ 14is explained as scale-rounding slack, in the comment that defines it.
Self-audit, 2026-09-18 -- two fixes
- Fixed: at the window's smallest width the Browse strip's Clear button wrapped under the list. The strip's seven controls sum to 712px, but a tab's content is 94px narrower than the frame, so the old 760px floor left 666px and AceGUI's Flow layout put the last control on a second row the strip has no height for. The floor is now derived from the strip (820px), and a spec sums the real control widths against it.
- Fixed: jumping from a reward to a quest that Browse's "For me" hides left it out of the list. A hunter with the Rewards tab's "For me" off clicks a shaman quest's reward; Browse clears its filters to show the quest, but Clear turns "For me" back on, which is the filter hiding it. Now that filter is switched off too when the list still cannot show the quest, and the checkbox shows it.
QB-013 -- the Rewards tab
- A second tab, Rewards: every item any quest awards (operator, 2026-09-18: "a new rewards tab that shows all the quest rewards for all the quests ... people can search through the available gear from questing and find the quest that awards it, click on that and it brings them back to the browse tab with the quest selected"). One row per reward or choice: icon, type, the level to use it, the item as its coloured link, and the quest that awards it with its level. Hovering a row shows the game's tooltip for the item; clicking it goes to the quest on the Browse tab.
- The strip is TOGBank's Guild Bank browse strip: Search (item, quest and type, every word), Type, Subtype (of the chosen type), Slot, Quality, Min lvl, Max lvl, For me, Clear -- two rows, remembered per character like the Browse strip's.
- "For me" here is two things at once: the quest must be one this character can take (class and
race, as on Browse) AND the item one this class can use. The second half is LibItemDB's new
CanUse-- the class tags plus the proficiency rules (a hunter and a mace, a level-39 hunter and mail, a mage and a shield) -- delivered on contract 34f589b2 the same afternoon it was raised, so those rules now live in one library instead of as copies in Dibs, TOGBank and (for an hour) here. On an older ItemDB the tags alone apply and nothing is hidden on a rule it cannot evaluate. The level to use an item is deliberately not part of "for me": the Lvl column says when. - Going to the quest switches to Browse, shows the quest in the panel and scrolls the list to its row. The Browse filters are left alone unless they hide that quest -- then they are cleared, and the status line says so. RowList has no scroll-to and no selection highlight, so this reads its sorted order through a private method (feature-detected) and "selected" means in view and in the panel; both are requested from LibAceGUIWidgets.
- The rewards list is built on first opening the tab, not at login -- one QuestDB read per quest and one LibItemDB read per item, ~4,000 quests -- and thrown away with the quest list it derives from. Not measured in game: how long that first open takes on the real database.
- The "i" tooltip covers the Rewards tab.
Harness adoption -- cbae63f
- Pinned the test harness at
cbae63f, which delivered the three gaps this session filed the same afternoon:UnitSex, a race id fromUnitRace, andSetHyperlinksEnabledon every frame. The stand-ins in the specs are deleted. One consequence the harness warned of and the suite met: its player is now a real Orc, so specs that stand in an Alliance character had to stand in an Alliance race too, or "For me" correctly hid every Alliance quest from them.
QB-012 -- a tabbed window, like Dibs
- The window is now a tab strip (operator, 2026-09-18: "we need to make the questbook tabbed like
Dibs, as we're going to have multiple views"), built the way Dibs' main window is
(
GUI/MainWindow.lua:197-229,:352-379): one AceGUI TabGroup filling the frame, branded through the shared widget library where it hasBrandTabGroup, each tab a view drawn into a raw host frame. The quest browser -- filters, list, detail pane -- is the first tab, Browse. A new view is one entry inWindow.VIEWS(draw / refresh / release) and one inWindow.TABS. - The open tab is remembered in the window's saved table next to its size, and a remembered tab that no longer exists falls back to Browse rather than leaving an empty body under the strip.
- The view hosts now live under Questbook's own hidden root frame between opens, not on the AceGUI frame's content. Caching the list host and pane on the pooled frame only worked because the pool happened to hand the same frame back; the TabGroup is released on every close, so its content is no place to keep anything. The no-leak property the doubled-list fix established (one RowList, one pane per session) is kept and re-asserted against the new home.
- A view's
Refreshonly reaches the tab on screen, and the Browse-onlyApplyFiltersdoes nothing on another view. A view whose release raises does not stop the window closing. - Not verified in game: that the filter strip's controls stay clickable with the Browse host frame (created after the strip, mouse not enabled) sharing the tab's content area, and how the pane's 45% cap looks now that the tab strip takes 60px more of the width than the plain frame did.
QB-011 -- reward items are real links
- The item lines in the Rewards section are now real item links, the way TOGBank's rows are
(operator, 2026-09-18: "the FULL reconstructed link that includes the game tooltip like ItemDB does
for TOGBank"). Each line carries LibItemDB's reconstructed link; hovering the line shows the game's
own tooltip for it -- stats and all, filled by the client from the link, nothing composed here --
and a modified click does what it does on a bag item, by the player's own bindings: Shift links it
into chat, Ctrl previews it in the dressing room (the game's
HandleModifiedItemClick, as TOGBank'sHandleLinkClick). Covers the rewards, the choices, and the item handed over at the start. - I got this wrong the first time, and the operator's screenshot showed it. The first version hung
the behaviour on the frame's in-text hyperlink events (
SetHyperlinksEnabledandOnHyperlink*), which did nothing in game; the white[Nomadic Belt]was the right colour (a common item) but not a link. TOGBank shows its tooltip on the whole ROW's hover (Modules/UI.lua:1043-1063), which is what a Button's provenOnEnterdoes, so that is what this does now. The env stand-in forSetHyperlinksEnabledis gone with it; the harness request stands as a gap report only.
QB-010 -- "Only quests I can do"
- A checkbox in the filter strip, ON by default, that hides quests this character's class or race
cannot take (operator, 2026-09-18: "if i'm a hunter, filter out the shaman quest classes"). The rule
is Questie's own
IsDoabletest of the two bitmasks (QuestieDB.lua:691-713,QuestiePlayer.lua:84-93): the player's bit is2 ^ (id - 1)fromUnitClass/UnitRace's third return, and a mask of 0 restricts nothing. Race covers the other side's quests too, since every race is one side's. - Deliberately NOT level, prerequisites, reputation or profession. "Quests I can do" in a browser means "quests for a character like mine": a level-5 hunter wants to see the level-40 hunter quests coming, not have them vanish.
- The panel says "Not available to your class or race." right under the facts when the filter is off and such a quest is opened -- before the reader has gone through the whole thing.
- The filter is remembered per character like the others, and Clear puts it back on. A save from before the checkbox existed gets the default.
requiredClassesjoins the six keys the index asks Questie for. It is a fixed-width field next torequiredRaces(questDB.lua:102), so it costs no extra walk of the row. The class and race bits are read once, not once per quest.- The strip's controls were narrowed (search 180, zone 140, levels 56, side 100, checkbox 90, Clear 90) so one row still fits the window's 760px floor with the seventh control; before, they summed past the default width and AceGUI would have wrapped the last one under the list.
- The checkbox is labelled "For me" and Clear got room for its word (operator's screenshot, 2026-09-18: "Only quests I can..." and "Cle..."). The full explanation of the filter is the checkbox's hover tooltip, Clear has one too, and the "i" tooltip by the close button now covers the filter, the reward links and chain walking as well.
- The harness's
UnitRacegives no race id, so with nothing stood in the race half does not filter (a nil bit means "do not filter", never "no quest is for me"); the specs stand the id in per example. Requested from the harness along withUnitSex.
QB-009 -- the quest's own text, from QuestDB
- A Description section in the detail pane: what the quest-giver says, from QuestDB MINOR 5
(thread 0e512e48). It is the LAST section, after the rewards, because it is the longest and the least
needed to act on the quest -- a player scrolling for the turn-in should not have to pass three
paragraphs of speech. The finisher's turn-in speech (
offerReward,requestItems,endText) is deliberately not shown: it is the quest's ending told in advance. - The Objective section now falls back to QuestDB's summary when Questie carries none. Questie's own wording is preferred where it exists, because it is in the client's language and QuestDB's is not.
- The server's markup is expanded for the character, in
QuestDBAdapter.ExpandMarkup, since the text is stored as the server holds it:$Nname,$Cclass,$Rrace,$Bline break,$gLord:Lady;by the player's sex (UnitSex == 2is male, per Blizzard's own Era code). Upper-case codes capitalise the value -- Questbook's own reading of a convention no offline source documents, so a wrong capital is the likeliest defect here. The three war-effort quests carry a$2063wworld-state counter only the live server knows; that renders as "many" rather than as a raw code. - English text on a non-English client is labelled ("Quest text is shown in English; no translation is available yet."). QuestDB ships English only until a translation source exists, and its locale framework tells Questbook which language actually loaded. Presenting English unmarked would read as Questbook's own choice of language.
- The "not available" notice now says exactly what is missing -- description, rewards, or both -- and nothing at all when neither is.
- Feature-gated on QuestDB
MINOR >= 5; an older QuestDB simply shows no description. Specs drive QuestDB's real parser with rows in its packed text format, one copied verbatim from its generatedData/Vanilla/enUS/Text.lua. The harness models noUnitSex; the spec stands it in per example and the gap has been requested from the harness.
QB-008a -- experience, and a fix to the pane's resize
- Experience in the Rewards section, from QuestDB MINOR 3, which derives it from the emulator's own
formula (
Quest::XPValue) because the CMaNGOS dump has no XP column. A player sees what THEY would get, with the full value beside it when their level cuts it down: "Experience: 136 (170 at full value)". Feature-gated onMINOR >= 3; an older QuestDB simply shows no line. QuestDB's caveat stands: this is the server's arithmetic, not a measurement against the live client. - No experience is shown at the level cap, where the quest pays money instead. QuestDB's
GetQuestXPdoes not know about the cap -- it scales by level difference only and returns full XP for a level-60 player -- which contradicts its own documentation. Questbook checksGetMaxPlayerLevel()itself rather than trusting it, and the finding went to QuestDB. - QuestDB is now in
.pkgmetarequired-dependenciesasquestdb, so the CurseForge app installs it alongside Questbook. The slug was read off the CurseForge project page by the operator and relayed by QuestDB. Until this landed, a CurseForge install would have skipped QuestDB and the client would have refused to load Questbook -- that release blocker is now cleared. - Money at the level cap now shows only AT the cap, following QuestDB's renderer contract (MINOR
4): below the cap, money and experience; at the cap, money and cap money, no experience. It was
shown to every player, so a level-2 character was told what a quest pays at 60 -- a reward they will
never receive for it, having long outlevelled it. QuestDB also now suppresses cap money for the 139
quests flagged
NO_MONEY_FROM_XP, which pay a capped player nothing; those simply show no line. - Fixed: narrowing the window clipped the quest panel's text instead of re-wrapping it. The panel narrowed, but the scroll area its text wraps to kept its opening width, and a scroll frame clips what sits outside it -- so the right-hand side of every long line was cut off. The spec for this checked the panel's width, which was right, and passed the whole time. It now checks the width the text actually uses. Found by the session's own audit.
QB-008 -- quest rewards, from QuestDB
- QuestDB is now a HARD dependency (operator, 2026-09-18):
## Dependencies: Questie, QuestDB, .... QuestDB is a separate addon, built in its own workspace, that carries the reward list Questie does not. Not yet in.pkgmetarequired-dependencies: its CurseForge slug is unknown -- the project (1700352) answers 404 while unpublished and its own.pkgmetadoes not state it -- and a guessed slug fails silently. Requested through QuestDB's inbox; the TOC already enforces the dependency in game. - A Rewards section in the detail pane: items you receive, "Choose one of" for the alternatives, money, reputation (with its sign -- some quests cost standing), and any spell the quest teaches. The item you are HANDED when you accept goes under Start, where it belongs, not under Rewards.
- Negative money renders as "Costs", never as money received. 69 Classic quests carry one; it is a payment the player makes.
- Money at the level cap is worded as paid INSTEAD of experience, because that is what it is
(QuestDB's contract,
LibQuestDB-1.0.lua:122) -- not a second reward on top of the first. - "No data" and "no rewards" are kept apart. When QuestDB cannot answer (a flavour it has not captured) the pane says rewards are not available; only when it answers with nothing does it say "None." Collapsing them would tell every player on an uncaptured flavour that every quest pays nothing.
Modules/QuestDBAdapter.luais the only file that touches QuestDB, for the same reasonQuestieAdapteris the only one that touches Questie: QuestDB is in active development and its API can still change. Item names come from LibItemDB's coloured link first (it names items the player has never seen), then its plain name, then the client's cache, then a labelled id.- The specs drive QuestDB's REAL parser with rows in QuestDB's own packed format, two of them copied verbatim from its generated data, so a change to that format goes red here rather than as a blank section in game. They load QuestDB's working tree, which is in active development -- a pass is evidence about the file on this disk, not about a QuestDB release.
QB-004a -- a quest item dropped by forty mobs now costs the same click as one dropped by three
- Drop sources are capped at 12, with the real count kept beside them. The pane lists them and
then says "and 28 more sources" rather than showing a truncated list that reads as the whole answer.
The cap and the count-beside-a-shorter-list contract are TOGProfessionMaster's
(its
Data/SourceDB.lua:108-110), so the two addons truncate alike. - The ordering of the two steps is the whole fix. Resolving a source is a
QueryNPC-- the expensive call -- but the drop CHANCE is keyed on ids alone and needs no NPC record. So the cheap call ranks every candidate and only the top twelve are resolved. Ranking first and truncating second is what bounds the work; truncating an already-resolved list would have paid for all forty. - A spec counts the actual
QueryNPCcalls for a 40-source item and fails if more than twelve happen -- the bound is measured, not asserted.
QB-003c -- fixed: the list drew itself twice after reopening the window
- Every quest name was drawn over another quest name, every level over another level, and both the Lvl and Req headers showed a sort arrow at once. Reported from the game.
- My bug, and my earlier fix for a related one is what hid it.
RowList:Newbuilds a whole new list -- its own row pool, its own header bar, its own sort state -- parented into the frame it is given, and there is no destroy (WoW frames cannot be destroyed). Questbook called it on every open. When I fixed the per-open frame leak I cached the host frame and stopped there, so each open kept adding another list inside the same reused host. Two lists, two headers, two sorts, one frame. The two lit arrows were the tell: each list was tracking its own sort independently. - The list is now cached on the host alongside it, and the click handler is reached through the module rather than captured, so a reopened window's clicks run its own handler and not the first one's.
- The sort you choose now survives closing the window, like the filters.
- Four specs cover the reopen path. The old code fails the first of them.
QB-005 -- search by mob, object and item name
- Typing a mob's name finds the quests that send you after it, even though the mob's name appears nowhere in the quest's title or zone. Same for an object and for a quest item. Words still combine in any order, and each word may be satisfied by either side -- "hogger bandanas" matches on the mob for one word and the title for the other.
- Built from Questie's own reverse fields, not by inverting every quest's objectives. An NPC or
object records which quests it starts and ends (
npcDB.lua:17-18,objectDB.lua:7-8) and an item records which quest it starts and which it relates to (itemDB.lua:10,:20) -- so this is one pass over those tables instead of a full quest pass plus a lookup per objective.startQuestis a scalar where the other three are arrays, and0means "no quest" rather than quest zero. - Built on FIRST SEARCH, never at login, and never for a filter that is not a search. Questie's Classic NPC and item tables are about 2 MB each -- together more than twice the quest table the list is built from -- so a player who only filters by zone and level never pays for it. Each search word's answer is then remembered, or every keystroke would walk the whole name table again, and so would every later change of zone while the search box still holds text.
- The name table is thrown away with the rest of the index, so one built while Questie was still loading cannot survive and keep answering nothing.
Player-facing docs corrected to what the addon actually does
README.txt and docs/Curseforge_Description.html were written from the design and described seven
things that do not exist. Nothing had shipped, so nobody was misled -- but they would have been, at the
first release:
- Searching by mob, item or quest-giver name; filtering by quest type or by quest status. Neither is built; both are now listed under what is still to come.
- "What it requires: earlier quests, level, profession skill or reputation" -- skill and reputation requirements are not shown. Now says level and earlier quests, and which of ANY or ALL applies.
- "Rewards: experience and reputation" -- no rewards are shown at all. Removed, and the missing quest text and reward list are named as needing a separate data source.
- ItemDB was listed as required. It is
OptionalDepsin the.tocand nothing calls it yet, so a player without it would have been told to go and install it for nothing. - The CMaNGOS credit claimed quest text and rewards "come from" their database. Nothing in Questbook uses it today. The credit stays -- the plan and the licence decision are unchanged -- but it now says the part that is not built yet is not built yet.
The CurseForge page is 10,531 characters, well under the 90,000 working ceiling.
QB-003b -- the filters are remembered
- The filter strip now survives closing the window and logging out. Narrow to Westfall 15-20, close, come back tomorrow, and it is still there. Saved per character, because which quests you are looking at belongs to the character you are playing -- and a side filter shared between a Horde and an Alliance alt is nonsense.
- The strip writes into the saved table itself, the way the minimap button is handed its saved
table rather than a copy; a copy would save nothing and the bug would only appear after a
/reload.Clearwrites the defaults into that table rather than replacing it, or it would leave the saved one behind and silently stop remembering for the rest of the session. - A save from an older version fills in key by key, so a filter added later arrives with its default instead of nil, and a corrupted saved value falls back to "no filter" rather than hiding every quest.
QB-004 -- the detail pane: "what do I do for this quest"
- The window is split: quest list on the left, quest detail on the right, which is
TOGProfessionMaster's Browser tab. Chosen over a top strip because a quest's detail is long and
vertical -- giver, each objective with mob levels and spawn points, turn-in, chain -- so a strip
would take height from the list and still not hold it. The pane is capped at 45% of the window
(
DetailPane.MAX_SHARE), so narrowing the window narrows the pane rather than squeezing the list to nothing. - Clicking a quest fills the pane: header (level, required level, zone, side, and "scales to you" where the quest has no level of its own), where it starts, what it requires, what to do, where to turn it in, the chain, and the objective text.
- The enrichment the operator asked for is in the objectives: each mob with its level range and whether it is Elite or Rare, where it spawns with coordinates, and for a quest item each thing that drops it with the drop chance, best chance first. A source with no recorded chance sorts after the ones that have one rather than as zero -- "Questie has no figure" and "never drops" are different facts and must not render the same.
- Every chain line is clickable and there is a Back button, so following a chain and returning is one click. Back appears only when there is somewhere to go back to, and the scroll area reclaims its space when it is hidden.
- The pane says out loud that Questie carries no quest description or reward list. An empty section would read as "this quest has no rewards", which is false -- it is missing data, and section 4 of the design says where it will come from.
Three of Questie's shapes decoded, each against the line of Questie that builds it, because two were assumed on 2026-09-17 and both were wrong:
startedBy/finishedByare three arrays, always a table, each slot possibly nil (compiler.lua:259-265, named atQuestieDB.lua:1503-1509). Nothing in Questie ever readsfinishedBy[3], so Questbook does not guess at it.objectivesis six slots of six different shapes, and slot 4 is a single{factionId, value}pair rather than a list (QuestieDB.lua:1593-1599). Read as a list, the faction id becomes an objective and the real requirement disappears silently.preQuestGroupis the one signed quest-id array: a negative entry is a real prerequisite whose id is its absolute value (compiler.lua:184-193,QuestieDB.lua:605-611). Passing it through unchanged would look up a quest that does not exist.preQuestSinglemeans ANY,preQuestGroupmeans ALL, and the pane says which -- the difference between doing one quest and doing four.breadcrumbswas missing from the offline fake's key table, which would have made Questie reject the whole detail query rather than leave one field nil (compiler.lua:1155-1158). Added, fromquestDB.lua:45.
Test harness -- adopted bcd81e3
- The Appearance tab is now asserted, not hoped for. The harness's
Settingsmodel looked categories up by name where the client compares numeric ids (Blizzard_CategoryList.lua:235), so the sub-panel never registered offline and the tab's placement was untestable. With that fixed, the best-effortpcallaround the sub-panel registration is gone -- it is now a bare call, matching TOGBankClassic's shipped one (itsOptions.lua:1102) -- and a spec checks the tab really hangs under Questbook.
QB-005 -- the bottom row, and settings
- The "i" and the gear, beside the Close button, the same pair FastGuildInvite and TOGBankClassic
carry and at their numbers: a 24px
help-iat -133, a 20pxTrade_Engineeringgear one icon and an 8px gap further left, both lifted above AceGUI's resize strips (which otherwise eat most of the hitbox -- FGI'sLiftAboveSizers, its functions.lua:32). The status bar now ends before the gear. Hovering the "i" says what the window does and what the filters do; the gear opens the settings. - A settings panel, with Appearance as its own tab. General carries the minimap button. Appearance carries text and row size (80-200%, the shared widget library's accessibility scale, so rows, fonts and columns grow together) and window opacity, which fades the window's frame and title art only -- never the quest list, because a see-through window you cannot read is worse than a solid one.
- A client that cannot show an options panel now costs the panel and nothing else. Registering goes through the client's Settings API and raises where that is missing; unhandled, that error aborted the rest of start-up and took the slash command and the minimap button with it.
- Registering the panel twice is harmless rather than fatal.
QB-003a -- "-1" in the level column
- Fixed: class quests showed a level of
-1and sorted above level 1. Questie stores-1for a quest that has no level of its own and scales to the player -- most class quests, some seasonal ones (QuestieLib.lua:216-222). The list now shows what Questie itself works out: the player's level, or the quest's required level when the player is below it. The rows carry ascalesflag, which the detail pane will say out loud.
QB-002a -- two wrong readings of Questie's API, one of which shipped
- Fixed: opening the window threw "attempt to concatenate field 'name' (a table value)" and no
quests appeared. Questie's
Query(id, keys)returns one table indexed by the order of the keys (Questie/Database/compiler.lua:1143), not one value per key. The adapter read it as multiple returns, so the whole table landed innameand everything else was nil. - Fixed before it shipped: drop rates would always have been nil.
GetItemDroprateanswers the array{ percent, source }(Database/DropTables/dropDB.lua:122); the adapter read a named.percentfield off it. Nothing would have errored -- every drop chance would simply have been missing. - Why the suite did not catch the first one: the fake Questie was written from the same assumption
as the code it checks, so both were wrong together and 99 specs passed. The fake is now written
from the lines of Questie that build the answers, and specs assert the raw shape -- a table from
Query, an array fromGetItemDroprate-- before asserting what the adapter makes of it.
QB-003 -- the quest list
- The window shows quests.
Modules/Index.luabuilds one row per quest from the adapter -- name, level, required level, zone and side -- sorted by level then name, built once and kept.Modules/ResultsList.luarenders them through LibAceGUIWidgets' sharedRowList(16px striped rows, click a header to sort, virtual scrolling) andModules/FilterStrip.luaputs the filters across the top in the house layout: search, zone, min and max level, side, and Clear. Filters apply as they change and combine with each other; the status line says how many quests are showing, and of how many when a filter is hiding some. - Search matches the quest name, its zone and its id, word by word and in any order, so "bandanas red" finds "Red Leather Bandanas". Matching NPC and item names is a later ticket -- it needs a lookup per quest, which the design's QB-005 covers.
- The side filter opens on the player's own faction, and always keeps quests open to both sides.
QB-002 -- reading Questie
- One file touches Questie.
Modules/QuestieAdapter.luaholds everyQuestieLoader/QuestieDBcall and returns plain Questbook tables, so a Questie change breaks one file with its own specs rather than every screen. It carries quests, NPCs (levels, rank, spawns), objects, items with their drop sources, Questie's recorded drop rates, zone and category names, and which side a quest belongs to from its race bitmask. - Questbook waits for Questie's own ready signal (
Questie.API.RegisterOnReady), never a timer, and the window says "Waiting for Questie to finish loading its quest database..." until then, filling itself in when the callback arrives. - An import before Questie has loaded is refused.
QuestieLoader:ImportModulesilently creates an empty module for a name it does not know, so the adapter checks for the functions it actually calls instead of trusting that a table came back.
QB-001 -- skeleton
- The addon loads and opens an empty window.
Core.luacreates the AceAddon object, bindsQuestbookDBthrough AceDB (the window's position and size live inchar.window), registers/questbook, and registers with VersionCheck-1.0 exactly as its README prescribes --VC:Enable(self)behind a silent lookup, with the raw## Version:string so a dev copy'sQuestbook-v0.0.2keeps update popups off.UI/Window.luais a stock AceGUI Frame titled "Questbook" that opens at 900x540 with a 760x380 floor (TOGBankClassic's Guild Bank window size), remembers its size and position through LibAceGUIWidgets'PersistWindow, and releases itself when closed. - Offline test suite. The WoWAPITesting harness as the
Tests/wowapisubmodule (the.bustedshim,Testsalready in.pkgmetaignore,.luacheckrc), aTests/env_questbook.luathat loads the installed Ace3 and LibAceGUIWidgets fresh per example, and 22 specs: ADDON_LOADED through AceAddon, the raw version, VersionCheck present and absent,/questbooktoggling, saved sizes, the size floor, the close button releasing the widget, and the env's file list matching the TOC. 100% line coverage ofCore.luaandUI/Window.lua. - Minimap button. LibDataBroker-1.1 and LibDBIcon-1.0 copied unchanged from TOGProfessionMaster's
libs/(hashes checked identical) and loaded first in the TOC.UI/Minimap.luaregisters a launcher with a stock book icon (Interface\Icons\INV_Misc_Book_09, also the addon-list icon via## IconTexture) until Questbook has its own; left-click opens or closes the window through the sameToggleWindowthe slash command uses, and the tooltip says so. LibDBIcon is handed the AceDBprofile.minimaptable itself, so a dragged position survives a reload. Operator: "you need to pull in the libraries from TOGPM to give this a mini map button. use a book icon from wow for now". - The empty window says it is empty on purpose. The operator opened it and saw "a completely empty screen" -- correct for this stage, but it read as broken. The window now carries one line saying the quest list, filters and details arrive in a coming update.
- 32 specs (the real copied LibDataBroker and LibDBIcon under test), 100% line coverage of
Core.lua,Modules/Window.luaandModules/Minimap.lua. - Layout: core files in the root, everything else in
Modules/, as FastGuildInvite does it (operator, 2026-09-17).UI/is gone. - 99 specs across the suite, 100% line coverage of every addon file.
Project
- Design written in
docs/DESIGN.md: a quest browser for Classic Era that answers "what do I do for this quest?" for any quest, including ones not in the player's log. The data comes from Questie (a required dependency, read at runtime and never copied), enriched with mob levels, spawns and drop sources. The window uses the house layout: a filter strip across the top, striped rows below, and a detail pane on the right. - Repository scaffolding, modelled on TOGBankClassic: MIT
LICENSE,.pkgmeta(Questie, VersionCheck, Ace3, LibAceGUIWidgets and LibItemDB as required CurseForge dependencies), the BigWigs packager release workflow,.gitattributes,.gitignore,.markdownlint.json,README.txtand the CurseForge description. CurseForge project 1699997, in.pkgmetaand the newQuestbook.toc(Classic Era interface 11509, dependencies Questie, Ace3, VersionCheck-1.0, LibAceGUIWidgets and ItemDB; no Lua files yet). - Licence and credits. MIT, with Questie, CMaNGOS and LibItemDB credited in the README and the
CurseForge description the way LibItemDB credits its sources. The CMaNGOS database is named as
cmangos/classic-db, GPL-3.0 per GitHub. - CurseForge description and README follow TOGBankClassic's layout: Core Features, How It Works, Why Choose, Slash Commands, Configuration, Dependencies & Compatibility, Shared Foundations, Recent Updates, Credits, License, Perfect For, Tips, and Bug Reports with the Discord link. Credits name the Questie team and the CMaNGOS/MaNGOS contributors in full, with thanks.
- No peer-review board.
writ onboardcreateddocs/AUDIT.mdfrom its template; it was empty and was deleted. Findings for this addon travel through the writ inbox.
This mod has no additional files

