# Dungeons

> Dungeon quest journal, pick-up and hand-in guides

Page: https://polestar.guide/docs/contributors/architecture/dungeons (Polestar docs > Contributors > Architecture)

The dungeon pages come from the Codex's instances (Toolchain, "Codex"): in a
release build the Forever client's tables and `data/forever/*.json`; with
`--local-extras` also `dungeons.json` (DungeonJournal).

The Dungeons section of the hub reads them through the Codex: `codex build` always runs
`tools/lib/codex/journal.js`, which writes one page per Codex instance into the
pack as `Dungeons1.lua` (a summary table per dungeon for the list, and the page
as Lua source compiled only when opened): the quests the community quest database files under the
instance's areas (the areas on the instance's map and the outdoor areas of its
name, as the solo filter's instance classification reads them,
`tools/lib/sim/solo.js`), whose objectives or starting item point into it,
that the Forever web database's dungeon and raid quest lists place in it (`quests.json`'s
`<web-db>.zone`, counted only when the community quest database's Forever store has the quest,
which keeps out `<UNUSED>`, `<NYI>` and retired quests), whose start item drops
or lies inside one instance (that page too, even when the quest has an area),
or that end its story (a follow-up with the same title, no objectives, taken
where the quest ends: Hidden Enemies, The Lord of Blackrock); plus, with
`--local-extras`, `dungeons.json`'s. An objective item that is sold, crafted,
or dropped or held outside every instance ties no quest to a dungeon (the
false-positive rule: Murloc Poachers, Craftsman's Writs), nor does a stray
drop, one only the web database's lists record and under 5% (The Ectoplasmic
Distiller's Greater Eternal Essence from Gri'lek and Maexxna); a quest of a
game event (the Era quest database's `event`: the Darkmoon Faire, the holidays, the war
efforts) is on a page only where the community quest database or the web database files it (Copper
Modulator, Rinling's Faire turn-in, is on none). What no rule can catch is
in the cited table `tools/lib/codex/journal-quests.json` (Shards of the
Felvine). Each quest has giver and ender and their positions, its objective
facts (`objectives`: kind, NPC, item or object id, count and the English
name; the escorts' and events' text), composed at run time into our own
objective line in the client's language (`Dungeons.ObjectiveText`:
`L["Kill %d %s"]`, `L["Collect %d %s"]`, `L["Use %s"]` with the names from
`Polestar.Names`; an event's line is the quest's own objective summary from
the names pack, kind `objective`), chain (every prerequisite and breadcrumb of the quest's side,
all of `pre` and `preAny`, oldest first, at most 20), rewards (items with
quality, count and choice from recordings and the leveling gear lists, else
the web database's list rewards, choice versus fixed as the web database files them; XP from
`quests.json`; money, else the web database's; reputation from the web database's rewards, its
"+1" placeholders left out, never the standing a quest asks for) and, with
`--local-extras`, DungeonJournal's audited shareability; bosses in kill order
with the quests tied to them (an encounter's boss carries its DungeonEncounter
id, `encounter`, for its name in every language); entrances from the Codex, else the client's Map
table (the corpse point, placed on its zone); and the page's `attunement`
list (below). No loot tables. The page also carries the instance's Map id
(`map`): the list shows the dungeon's name in the client's language
(`Names.Instance`) and its zone's (`C_Map`); attunement notes are our prose,
read through `L[note]` (`i18n keys` lists them from
`tools/lib/codex/attunements.json`). `codex attune` writes the local comparison with
the the attunement data addon (`tools/lib/codex/questaudit.js`,
`tools/lib/consume/attune.js`; `data/local/dungeon-quests-audit.md` and
`.json`, then the attunements report). Runtime: `addon/Polestar/Dungeons/Dungeons.lua`
(`Polestar.Dungeons`: List, Get, Entrance, Quests, Quest, ObjectiveText, Bosses, Open,
GuideTo, GuideToPoint, ShowOnMap; `SHOW_DUNGEON`). `dungeons build` writes no
guides.

The pick-up guide is built in game (`addon/Polestar/Dungeons/Pickup.lua`):
`Dungeons.PickupPlan(key)` takes the page's quests that apply to the character
(faction, race and class masks through `Lookup.QuestBlock`, the page's class
list), not done, not in the log (those are `carried`), not closed for good (a
quest of the page's `closedBy` done or in the log: a breadcrumb whose quest is
taken, such as Klockmort's Essentials once Essential Artificials is; each
accept carries `closedBy` too, so the Engine passes it once taken on the way),
open by max(level, the dungeon's lowest level), picked up outside from an NPC or object
(not from an item, not inside), and not a follow-up of another quest of the
page; each with its open prerequisites (`Lookup.QuestChain`: all of `pre`, one
of `preAny` this character can take, in the order they are done). The giver
is `Dungeons.Giver(pq, from)`: the page keeps one (the Codex's first), so a
quest several NPCs give goes to the one of the character's side nearest the
player (`Lookup.QuestEnd`, which takes an NPC of the other side only when no
other is known and marks it `hostile`; Tabetha's Task: the mage trainers of
both sides), never to one of the other side; the quest card and the map's
giver markers show the same giver.
`Dungeons.PickupGuide(key)` orders them as the cheapest path by
`ns.TravelCost` from where the player stands, through every stop, to the
entrance (`Dungeons.PlanStops(stops, from, finish, travel) -> order, cost`:
an exact search over the stops done and the last one, up to 12 stops; above
that nearest-next, then single stops moved while the path gets shorter). A
nearest-next walk with the entrance only appended left a city and came back
to it (Thunder Bluff, Orgrimmar, the Undercity, back to Orgrimmar for
Ragefire Chasm); the planned path goes Thunder Bluff, the Undercity,
Orgrimmar and in. Stops: each prerequisite once, after the one before it in
its chain (a full quest step, `Lookup.QuestStep`, from its giver nearest the
player; the player then stands at its turn-in), and a giver once per set of
prerequisites its quests wait on (one step with an accept per quest; the
giver comes up again for a quest still waiting, and visits to the same giver
in a row are one step). The last step lists the carried quests as notes,
goes to the entrance and says where the hand-in guide is (a note naming
`/polestar dungeons <name>`). Nothing to take: nil, "nothing". The route is
kept per character (`CharState().dungeonPickup[key]`, the stop keys in
order) and used when the guide is rebuilt without a position
(`Dungeons.OrderStops(stops, from, finish, field, key)`, shared with the
hand-in guide). It is a Lookup planner kind
(`Lookup.RegisterPlanner("pickup", build)`): `Dungeons.PickUp(key)` ->
`Lookup.Guide("pickup", key)` registers it as `lookup/pickup/<key>` (pack
"lookup") and opens it in a new tab, and a saved tab of it is rebuilt at
ADDON_LOADED like Lookup's own; it is gated by `dungeons.enabled`, not
`lookup.enabled`.

The hand-in guide is the run's other half (`addon/Polestar/Dungeons/Handin.lua`,
planner kind `handin`, `lookup/handin/<key>`, `Dungeons.HandIn(key)`).
`Dungeons.HandinPlan(key)` sorts the page's quests in the log: `hand`
(objectives done, by `Compat.IsQuestReadyForTurnIn`, or a delivery with no
objectives; turned in outside at the page's ender, else the Codex ender
nearest the player), `unfinished`, `inside` (turned in inside the dungeon)
and `unplaced`; it searches no follow-ups, so the page can ask for it on
every render. `Dungeons.HandinFollowUps(plan)` adds the quests the `hand`
quests open (`Codex.FollowUps(id)`: the quests whose `pre` or `preAny` name
it, one scan of the quest records per id, kept until the quest chunks
change): every `pre` done or handed in here and one of `preAny` likewise,
takeable now (`Lookup.QuestBlock`: faction, race, class, level), not done or
in the log, from the NPC or object a quest it waits on is turned in to or
one within 60 seconds of it (`ns.TravelCost`). `Dungeons.HandinGuide(key)`
makes a stop per turn-in place (its turn-ins, then the follow-ups that same
NPC gives) and per other follow-up giver (after the turn-ins it waits on),
ordered by `Dungeons.OrderStops` with no fixed end (`CharState().dungeonHandin[key]`);
the last step lists the rest as notes ("Not finished yet: …", "Turned in
inside the dungeon: …"). Nothing routable: nil, "nothing", and
`Dungeons.HandinText(plan)` says why in one line. `Dungeons.HandIn` puts the
guide in the tab of the dungeon's pick-up guide when that one is finished or
at its last step (its job ends at the entrance), else in a new tab. A
finished pick-up guide's "Guide complete" panel (UI/GuideFrame.lua) offers
"Hand in the quests" while the dungeon has quests in the log.

Attunements and keys (`addon/Polestar/Dungeons/Attune.lua`, planner kind
`attune`, `lookup/attune/<key>:<id>`). A page's `attunement` list (Codex,
"Dungeons and raids": `tools/lib/codex/attunements.js`) is read by
`Dungeons.Attunements(key, faction)` (faction "A" | "H" | nil: the entries
of that side and the side-free ones). `Dungeons.AttunementState(a)` gives
this character's state per step: a quest `done`, `ready`, `log`, `open`,
`locked` (a step it waits on, `after` all, `afterAny` one, is not done),
`level`, `rep` (the quest's standing, `Compat.GetFactionStanding`), `na`
(faction, race or class through `Lookup.QuestBlock`) or `skip` (an
alternative not taken: of `any` goals the one whose standing fits best, the
highest first; a `preAny` sibling of a done one); a drop, chest or gift is
`done` once its item is owned (`Compat.GetItemCount(id, true)`, the bank
included) or the quest it starts is taken; a standing once reached. The
attunement is `done` when its item is owned (a key, the Drakefire Amulet,
Aqual Quintessence) or, item-less, when its goals are done (one of them with
`any`); `goalDone` with no item says the key was lost. `Dungeons.AttuneGuide(key, id)`
makes a stop per quest still to do (`Lookup.QuestStep`, with any open
prerequisite `Lookup.QuestChain` finds that the list lacks), each after the
stops it waits on, ordered by `Dungeons.OrderStops` from where the player
stands with no fixed end (`CharState().dungeonAttune["<key>:<id>"]`). Each
quest step is whole (accept, objectives, turn-in): `Lookup.QuestStep`'s
`opts.inside` places a giver, target, chest or turn-in without a spawn at
the entrance of the instance it is in (the Codex NPC's `instance`, else the
attunement's `inside` from the cited table) and the step's note says what is
inside where; without it such a quest would be a lone "First do the quest"
note, which the Engine passes as done. An accept above the character's
level stays, the Engine's detail adding "from level N". A drop
or chest is a collect step at its NPC or object (inside an instance: at its
entrance, with a note naming it), a standing a rep step, a gift a talk step,
each after the quest before it in the list; the last step holds the note and
the entrance. Nothing left: nil, "done"; the other side's attunement: nil,
"nothing". `Dungeons.Attune(key, id)` opens it in a new tab.

---

Next page: [UI](https://polestar.guide/docs/contributors/architecture/ui.md) · Every docs page: https://polestar.guide/llms.txt · All docs in one file: https://polestar.guide/llms-full.txt
